How to Secure a VKE Cluster Using Traefik, Cert-Manager and Let's Encrypt

Updated on November 21, 2023
How to Secure a VKE Cluster Using Traefik, Cert-Manager and Let's Encrypt header image

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:

Install Cert-manager

  1. 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.

  2. 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, and cert-manager-webhook pods are running, and ready to use.

Set Up the Let's Encrypt Issuer

  1. Using a text editor such as Nano, create a new file named cluster-issuer.yaml.

     $ nano cluster-issuer.yaml
  2. 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.
  3. Apply the ClusterIssuer.

     $ kubectl apply -f cluster-issuer.yaml
  4. 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.

  1. Create a new file named traefik-namespace.yaml.

     $ nano traefik-namespace.yaml
  2. Add the following contents to the file.

     apiVersion: v1
     kind: Namespace
     metadata:
       name: traefik-namespace

    Save and close the file.

  3. Apply the namespace configuration.

     $ kubectl apply -f traefik-namespace.yaml

    The above command creates the traefik-namespace in your cluster.

Install and Configure Traefik

  1. Add the Traefik Helm repository.

     $ helm repo add traefik https://helm.traefik.io/traefik

    Output:

     "traefik" has been added to your repositories
  2. Update your Helm chart repositories.

     $ helm repo update
  3. 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 !
  4. 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

  1. 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 theservice/traefik External IP. In the above output 192.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.

  2. Visit your Domain DNS registrar. For example, Vultr DNS.

  3. 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.

  1. To organize your cluster, create a new namespace YAML file for the application.

     $ nano example-app-namespace.yaml
  2. Add the following contents to the file.

     apiVersion: v1
     kind: Namespace
     metadata:
       name: example-app-namespace

    Save and close the file.

  3. Apply the namespace configuration.

     $ kubectl apply -f example-app-namespace.yaml
  4. Create a new deployment file named example-app-deployment.yaml.

     $ nano example-app-deployment.yaml
  5. 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.
  6. 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.

  7. 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

  1. Create a new service file named example-app-service.yaml.

     $ nano example-app-service.yaml
  2. 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 named app with the value example-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.
  3. Apply the service configuration.

     $ kubectl apply -f example-app-service.yaml
  4. 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
  5. Create a new Ingress file named example-app-ingress.yaml.

     $ nano example-app-ingress.yaml
  6. 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 the websecure 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 the letsencrypt-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.
  7. Apply the ingress configuration.

     $ kubectl apply -f example-app-ingress.yaml
  8. 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
  9. 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.

  10. 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
  11. 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 namespace traefik-namespace, using the traefik/traefik repository chart.
  • --set 'ports.web.redirectTo=websecure': Sets the redirectTo configuration of the Traefik deployment. This enables automatic HTTP to HTTPS redirection.

Test

  1. 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.

  2. 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.