How to Secure a VKE Cluster Using Traefik, Cert-Manager and Let's Encrypt
Introduction
Traefik is an open-source reverse proxy and load balancer known for its user-friendly approach to deploying microservices. It seamlessly integrates with existing infrastructure components such as cloud-native, and Kubernetes environments to secure underlying applications.
Traefik offers a range of features that enhance its capabilities. These include load balancing, API gateway, orchestrator ingress, east-west service communication, among others. To add certificates and certificate issuers in a Kubernetes clusters, Traefik integrates with cert-manager to provide a streamlined solution.
Cert-manager can issue certificates from different sources such as Let's Encrypt, HashiCorp Vault, Venafi, or private PKI. It also ensures certificates are valid and up to date by actively attempting to renew them before they expire.
In this article, you will secure a web application in a Vultr Kubernetes Engine (VKE) cluster using Traefik, cert-manager, and Let's Encrypt. You will configure automatic HTTPS redirection to enhance your application security and securely serve all external requests.
Prerequisites
Before you begin, make sure you:
- Deploy a Vultr Kubernetes Engine (VKE) cluster.
- Install Kubectl on your local machine to access the VKE cluster.
- Install the Helm client on your local machine.
- Have access to a domain name. This guide uses
example.com
, replace all occurrences with your actual domain.
Install Cert-manager
To install the latest cert-manager version to your cluster. Run the following command.
$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.2/cert-manager.yaml
In the above command, version
v1.12.2
is used. To find the latest Cert-manager version, visit the cert-manager GitHub repository, and keep note of the latest version number to use in your cluster.Verify that all cert-manager components are installed and running.
$ kubectl get pods --namespace cert-manager
Your output should look like the one below:
NAME READY STATUS RESTARTS AGE cert-manager-66d9545484-2tbv7 1/1 Running 0 28s cert-manager-cainjector-7d8b6bd6fb-w9p8v 1/1 Running 0 28s cert-manager-webhook-669b96dcfd-gffrg 1/1 Running 0 28s
As displayed in the above output, the
cert-manager
,cert-manager-cainjector
, andcert-manager-webhook
pods are running, and ready to use.
Set Up the Let's Encrypt Issuer
Using a text editor such as
Nano
, create a new file namedcluster-issuer.yaml
.$ nano cluster-issuer.yaml
Add the following contents to the file. Replace
<hello@example.com>
with your actual email address.apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod spec: acme: email: hello@example.com server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: name: letsencrypt-prod-key solvers: - http01: ingress: class: traefik
Save and close the file.
Below is what the above configuration declarations represent:
acme
: Specifies the ACME configuration for Let's Encrypt.email
: Your email address to associate with a Let's Encrypt account.server
: The Let's Encrypt server URL.privateKeySecretRef
: Specifies secret that holds the Let's Encrypt account private key.solvers
: Sets the method for solving the ACME challenge.http01
: Sets HTTP-01 as the challenge solver to verify domain ownership.ingress
: Sets the ingress class to use for solving the challenge.
Apply the ClusterIssuer.
$ kubectl apply -f cluster-issuer.yaml
Verify that the ClusterIssuer is created.
$ kubectl get clusterissuer
Output:
NAME READY AGE letsencrypt-prod True 2m44s
Create the Traefik Namespace
In this section, create the Traefik namespace to deploy the Traefik proxy. This allows you to separate Traefik resources from the default namespace for easy management of resources in your cluster as described in the steps below.
Create a new file named
traefik-namespace.yaml
.$ nano traefik-namespace.yaml
Add the following contents to the file.
apiVersion: v1 kind: Namespace metadata: name: traefik-namespace
Save and close the file.
Apply the namespace configuration.
$ kubectl apply -f traefik-namespace.yaml
The above command creates the
traefik-namespace
in your cluster.
Install and Configure Traefik
Add the Traefik Helm repository.
$ helm repo add traefik https://helm.traefik.io/traefik
Output:
"traefik" has been added to your repositories
Update your Helm chart repositories.
$ helm repo update
Using
helm
, install Traefik.$ helm install --namespace=traefik-namespace traefik traefik/traefik
The above command installs Traefik to the
traefik-namespace
you created earlier in your cluster.Output:
STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: Traefik Proxy v2.10.1 has been deployed successfully on traefik-namespace namespace !
Verify that Traefik is installed, and all resources are available in the namespace.
$ kubectl get -n traefik-namespace all
Your output should look like the one below:
NAME READY STATUS RESTARTS AGE pod/traefik-dfb6cf6bb-z9bl7 1/1 Running 0 20s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/traefik LoadBalancer 10.96.37.224 <pending> 80:31574/TCP,443:31926/TCP 20s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/traefik 1/1 1 1 20s NAME DESIRED CURRENT READY AGE replicaset.apps/traefik-dfb6cf6bb 1 1 1 20s
Set up your Domain Name and Verify the Traefik Installation
Verify the Traefik service external IP Address.
$ kubectl get svc -n traefik-namespace traefik
Your output should look like the one below:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE traefik LoadBalancer 10.108.209.185 192.168.0.101 80:30046/TCP,443:30279/TCP 78s
Copy the
service/traefik
External IP. In the above output192.168.0.101
. If the IP is in<pending>
state, wait a few minutes, and run the command again to view the external IP again.Visit your Domain DNS registrar. For example, Vultr DNS.
Set up a domain A record pointing to the Traefik LoadBalancer external IP Address.
Deploy a Sample Application
In this section, deploy the sample application Nginx
to test your Traefik configuration and the cert-manager certificate issuance process as described below.
To organize your cluster, create a new namespace YAML file for the application.
$ nano example-app-namespace.yaml
Add the following contents to the file.
apiVersion: v1 kind: Namespace metadata: name: example-app-namespace
Save and close the file.
Apply the namespace configuration.
$ kubectl apply -f example-app-namespace.yaml
Create a new deployment file named
example-app-deployment.yaml
.$ nano example-app-deployment.yaml
Add the following contents to the file.
apiVersion: apps/v1 kind: Deployment metadata: namespace: example-app-namespace name: example-app-deployment spec: replicas: 3 selector: matchLabels: app: example-app template: metadata: labels: app: example-app spec: containers: - name: web-app image: nginx ports: - containerPort: 80
Save and close the file.
Below is what the deployment configurations represent:
replicas
: The number of replicas (pods) to be created for the Deployment.selector
: The labels used for selecting the pods controlled by the Deployment.template
: Specifies the template for creating the pods.metadata
: Specifies the labels for the pods.spec
: Specification for the pods.containers
: Specifies the containers within the pods.name
: Specifies the container name.image
: Container image to be used in the pods.ports
: Ports opened in the container.
Apply the deployment.
$ kubectl apply -f example-app-deployment.yaml
The above command creates the example application deployment and the associated pods in the
example-app-namespace
.Verify that the example application pods are running.
$ kubectl get -n example-app-namespace pods
Output:
NAME READY STATUS RESTARTS AGE example-app-deployment-5f8bc66b96-bzzrp 1/1 Running 0 5m11s example-app-deployment-5f8bc66b96-lq96g 1/1 Running 0 5m11s example-app-deployment-5f8bc66b96-n5cdf 1/1 Running 0 5m11s
Configure HTTPS using Traefik
Create a new service file named
example-app-service.yaml
.$ nano example-app-service.yaml
Add the following contents to the file.
apiVersion: v1 kind: Service metadata: namespace: example-app-namespace name: example-app-service spec: selector: app: example-app ports: - protocol: TCP port: 80 targetPort: 80
Save and close the file.
Below is what the service declarations represent:
spec
: Describes the specification for the Service.selector
: Specifies a set of labels used for selecting the pods that the service should route traffic to.app
: Specifies a label namedapp
with the valueexample-app
.ports
: Defines the ports exposed by the Service.protocol
: Specifies the protocol to use for the ports.port
: The port number the Service should listen on.targetPort
: Specifies the port number to which traffic should be forwarded within the pods.
Apply the service configuration.
$ kubectl apply -f example-app-service.yaml
Verify that the service is created.
$ kubectl get services -n example-app-namespace
Output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE example-app-service ClusterIP 10.97.221.68 <none> 80/TCP 81s
Create a new Ingress file named
example-app-ingress.yaml
.$ nano example-app-ingress.yaml
Add the following contents to the file. Replace
example.com
with your actual domain.apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: web-app-ingress namespace: example-app-namespace annotations: traefik.ingress.kubernetes.io/router.entrypoints: websecure traefik.ingress.kubernetes.io/router.tls: "true" cert-manager.io/cluster-issuer: letsencrypt-prod spec: rules: - host: example.com http: paths: - path: / pathType: Prefix backend: service: name: example-app-service port: number: 80 tls: - secretName: web-app-cert hosts: - example.com
Save and close the file.
Below is what the Ingress declarations represent.
annotations
: Specifies the annotations for the Ingress, including Traefik-specific and cert-manager-specific annotations.traefik.ingress.kubernetes.io/router.entrypoints: websecure
: Configures the Traefik ingress controller to route incoming traffic to thewebsecure
entrypoint. It specifies the entrypoint to use for handling HTTPS traffic.traefik.ingress.kubernetes.io/router.tls: "true"
: Specifies that the router should handle HTTPS traffic. It enables TLS termination and ensures secure communication between clients and the application.cert-manager.io/cluster-issuer: letsencrypt-prod
: Specifies the cert-manager cluster issuer to use for obtaining SSL/TLS certificates. It associates the Ingress resource with theletsencrypt-prod
cluster issuer you created earlier.spec
: Specifies the specification for the Ingress.rules
: Specifies the rules for routing the traffic.host
: Specifies a domain/host to match the rule.http
: Specifies the HTTP routing for the host.paths
: Specifies the paths to match within the host.path
: Specifies the path to match.backend
: Specifies the backend service to route the traffic to. *service
: Specifies the name of the service to forward the traffic to. *port
: Specifies the port number of the service to use.
tls
: Specifies the TLS configuration for the Ingress resource.secretName
: Specifies the name of the secret that stores the TLS certificate and private key. This secret should be created and managed by cert-manager.hosts
: Specifies the hostnames associated with the TLS certificate. Replace<PUT YOUR DOMAIN NAME HERE>
with your actual domain name.
Apply the ingress configuration.
$ kubectl apply -f example-app-ingress.yaml
Verify that the Ingress resource is created.
$ kubectl get ingress -n example-app-namespace
Output:
NAME CLASS HOSTS ADDRESS PORTS AGE web-app-ingress traefik example.com 80, 443 9s
Verify that a Let's Encrypt SSL certificate is registered for your domain.
$ kubectl get -n example-app-namespace certificates
Output:
NAME READY SECRET AGE web-app-cert True web-app-cert 41s
If the READY column returns
True
, your SSL certificate is successfully issued and ready to use.Verify that the certificate auto-renews on expiry.
$ kubectl describe -n example-app-namespace certificate web-app-cert
The about command prints information about the certificate. Find the
Renewal Time
setting, and make sure its value is a date as below:Renewal Time: 2023-09-17T15:23:45Z
In a web browser such as Chrome, visit you can securely access your application.
https://example.com
When successful, the default Nginx welcome page displays in your browser.
Redirect HTTP Requests to HTTPS
To redirect all HTTP requests to HTTPS, reconfigure Traefik using the following command.
$ helm upgrade --namespace=traefik-namespace traefik traefik/traefik --set 'ports.web.redirectTo=websecure'
In the above command:
helm upgrade
: Upgrades the Traefik Helm release in the specified namespacetraefik-namespace
, using thetraefik/traefik
repository chart.--set 'ports.web.redirectTo=websecure'
: Sets theredirectTo
configuration of the Traefik deployment. This enables automatic HTTP to HTTPS redirection.
Test
In your web browser, visit your configured domain name using HTTP.
http://example.com
Verify that your request auto-redirects to HTTPS to access the application.
Alternatively, in your terminal session, use the
curl
utility to verify that the HTTPS redirection works correctly.$ curl http://example.com
Output:
Moved Permanently
When the tests are successful, Traefik is actively redirecting all HTTP traffic to HTTPS, and correctly serving user requests to configured services in your cluster.
Troubleshooting
To manage Traefik and your cluster resources, below are troubleshooting tips to fix common problems.
Unable to get Let's Encrypt Certificate: If your certificate column returns False, verify that your domain is correctly pointed to your Traefik LoadBalancer external IP Address. Additionally, to investigate further causes, view the cert-manager logs using the command below.
$ kubectl logs deployment/cert-manager -n cert-manager --tail=15 -f
Check Traefik logs: If you're experiencing issues with Traefik, check the logs for any error messages or warnings using the following command.
$ kubectl logs -n <namespace> <traefik-pod>
Verify DNS configuration: Verify that your domain name is correctly pointed to the External IP address of your Traefik load balancer. Wrong DNS configurations prevent Traefik from handling incoming requests correctly.
Verify ingress configuration: In case Traefik does not route external traffic correctly, check your Ingress resource configuration and verify that the necessary annotations for Traefik and cert-manager are included in your YAML file.
$ kubectl get ingress
Inspect Traefik resources: Occasionally, inspect the status of Traefik resources such as pods, services, and ingresses to ensure that they are running correctly.
$ kubectl get pods -n traefik-namespace
Conclusion
In this article, you have secured a Vultr Kubernetes Engine (VKE) cluster web application using Traefik, cert-manager, and Let's Encrypt. You enabled automatic HTTPS redirection to serve all client requests using SSL. For more information and configuration options, visit the following resources.