Configure LetsEncrypt with Kubernetes


As more and more solutions are built using microservices architecture, it is very important to have all your public endpoints encrypted. The good news is that you can achieve it without spending any additional penny. LetsEncrypt is one such project which is a free and open Certificate Authority and you can easily integrate it with your setup to automatically generate SSL certificates free of cost, FOREVER...
In this article I'll explain how you can setup LetsEncrypt with cert-manager on Kubernetes. I've used Route53 for this demonstration and cert-manager version 0.12. This process is applicable for any flavor of Kubernetes be it bare-metal, EKS, GKE or AKS. I'll be using ACME issuer with DNS validation method. Let's dig in.

Please ensure you have following setup properly configured before working on LetsEncrypt setup.


Now follow the step by step instructions to configure letsencrypt and cert-manager on Kubernetes.

  1. Install the CustomResourceDefinition resources. These are those resources which are not available by default but can be created by extending the Kubernetes API.
    $ kubectl apply --validate=false -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.12/deploy/manifests/00-crds.yaml
  2. Create namespace for cert-manager.
    $ kubectl create ns cert-manager
  3. Add the Jetstack Helm repository and update your local Helm chart repo cache.
    $ helm repo add jetstack https://charts.jetstack.io
    $ helm repo update
  4. Install the cert-manager Helm chart
    $ helm install --name cert-manager --namespace cert-manager --version v0.12.0 jetstack\cert-manager
  5. Now verify that cert-manager pods are created and wait for all of them to be in running state.
    $ kubectl get pods -n cert-manager
    NAME                                       READY   STATUS    RESTARTS   AGE
    cert-manager-6bcc9d894d-d7s9j              1/1     Running   0          23h
    cert-manager-cainjector-594fd9cc45-w2mhf   1/1     Running   0          23h
    cert-manager-webhook-785ff8fc78-gjd88      1/1     Running   0          23h
  6. Create an IAM user and attach following policy to get access to Route53 zones. If your entire setup is on AWS then you can use IAM role as well. But if you're using Kubernetes cluster outside AWS but using only Route53 then you can use IAM user. Attach the following policy to the user and keep a note of AWS ACCESS KEY and SECRET ACCESS KEY.
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "VisualEditor0",
                "Effect": "Allow",
                "Action": [
                    "route53:GetChange",
                    "route53:ChangeResourceRecordSets",
                    "route53:ListResourceRecordSets"
                ],
                "Resource": [
                    "arn:aws:route53:::hostedzone/*",
                    "arn:aws:route53:::change/*"
                ]
            },
            {
                "Sid": "VisualEditor1",
                "Effect": "Allow",
                "Action": "route53:ListHostedZonesByName",
                "Resource": "*"
            }
        ]
    }
  7. Create a secret in cert-manager namespace which contains the SECRET ACCESS KEY. Save the secret key in the file called secretkey.
    $ kubectl create secret generic acme-route53 --from-file=secret-access-key=./secretkey -n cert-manager
    It will create a secret called acme-route53 in the cert-manager namespace. Make sure you delete the file secretkey
  8. Create a resource of type ClusterIssuer which will be used to issue certificates. Let's create it in the cert-manager namespace for better management. Although the namespace doesn't matter because ClusterIssuer resource is applicable to all the namespaces in the cluster.
    apiVersion: cert-manager.io/v1alpha2
    kind: ClusterIssuer
    metadata:
      name: letsencrypt-prod
      namespace: cert-manager
    spec:
      acme:
        server: https://acme-v02.api.letsencrypt.org/directory
        email: YOUR EMAIL ID
        privateKeySecretRef:
          name: letsencrypt-prod
        solvers:
        - selector:
            dnsZones:
              - "YOUR DOMAIN"
          dns01:
            route53:
              region: YOUR REGION
              accessKeyID: YOUR ACCESS KEY
              secretAccessKeySecretRef:
                name: acme-route53
                key: secret-access-key
  9. Now take a look at status of this resource.
    $ kubectl get clusterissuer -n cert-manager
    NAME               READY   AGE
    letsencrypt-prod   True    23h
    Above output confirms that it is ready for use. If the status shows 'False' here then check the logs of 'cert-manager-6bcc9d894d-d7s9j' pod to troubleshoot.
  10. Once we have our issuer setup, let's create a new certificate. It will take around 2-3 minutes as it first verifies the domain name by creating a recordset.
    apiVersion: cert-manager.io/v1alpha2
    kind: Certificate
    metadata:
      name: nginx-tls
      namespace: YOUR NAMESPACE
    spec:
      secretName: nginx-tls
      issuerRef:
        name: letsencrypt-prod
        kind: ClusterIssuer
      dnsNames:
        - '*.YOUR.DOMAIN'
      acme:
        config:
          - dns01:
              provider: route53
            domains:
              - '*.YOUR.DOMAIN'
    Above manifest will create a certificate by name 'nginx-tls' in the namespace you specify. Please do modify the DNS name as you provided while creating the ClusterIssuer.
  11. Let's verify the status of the certificate.
    $ kubectl get cert -n YOUR_NAMESPACE
    NAME       READY   SECRET    AGE
    nginx-tls  True  nginx-tls   23h
    Above output confirms that your certificate is ready to use.

  12. Above setup is all you need to configure a fully functional certificate generation process. Next step is to bind this certificate to your Ingress controller. Here is a sample Ingress manifest for reference. You can modify relevant parameters for your own deployment.
    apiVersion: extensions/v1beta1
    kind: Ingress
    metadata:
      name: nginx
      namespace: default
      annotations:
        kubernetes.io/ingress.class: "nginx"
        cert-manager.io/cluster-issuer: "letsencrypt-prod"
    spec:
      tls:
      - hosts:
        - "YOUR.DOMAIN"
        - "*.YOUR.DOMAIN"
        secretName: nginx-tls
      rules:
      - host: YOUR.DOMAIN
        http:
          paths:
          - path: /
            backend:
              serviceName: YOUR-SERVICE
              servicePort: 80

    Setup of Ingress controller is beyond the scope of this article. Please follow relevant articles of the cloud providers you're working on. Take a note of the annotation for cluster issuer resource name and the secretName under tls section.
    If all goes well then you'll be able to see a fully valid certificate associated with your domain and you don't have to worry about the renewal as well.