Configure LetsEncrypt and cert-manager 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.

  • Kubernetes Cluster
  • Helm package manager installed

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.

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.