Cert-Manager is a controller used for certificate management. A Cert-Manager can help to issue certificates from different issuers like Let’s Encrypt, HashiCorp Vault, Venafi, a simple signing key pair, or self-signed. Cert-Manager validates certificates, ensures they are up to date, and renews before expiry. Cert-Manager is made up of several components as mentioned below. 

  1. Issuer: Issuers, and ClusterIssuers, are objects in Kubernetes that represent certificate authorities (CAs) that can generate signed certificates.
  2. Certificate: A Certificate is a namespaced resource that references an Issuer or ClusterIssuer and will be renewed and kept up to date.
  3. CertificateRequest: The CertificateRequest is a namespaced resource that is used to request a certificate from an issuer or cluster issuer.
  4. ACME Orders: An order represents a certificate request which will be created once a new CertificateRequest resource referencing an ACME issuer has been created
  5. ACME Challenges: When an Order resource is created, Challenge resources for each DNS name that is being authorized with the ACME server will be created by the order controller.
  6. Webhook: It is deployed as another pod along with the main Cert-Manager pods and has 3 functions: ValidatingAdmissionWebhook, MutatingAdmissionWebhook,  and CustomResourceConversionWebhook.
  7. CA Injector: It helps to configure certificates for Validating Webhooks, Mutating Webhooks,  and Conversion Webhooks.

In this article, we will set up a Cert-Manager with Let’s Encrypt issuer. We will secure our sample application using the TLS certificates and have HTTPS in our Hostname to access the application using Ingress. To do this, we will add annotations to the Ingress so that the certificate resource is created on our behalf.

To know in detail about Cert-Manager, visit the official documentation here. The focus of this article is on setting up the Cert-Manager using Helm and it is assumed that you are familiar with concepts related to the Cert-Manager.

Pre-requisites

  1. AWS Account(Create if you don’t have one).
  2. Kubernetes Cluster (Click here to learn to create a Kubernetes Cluster using Kops and know more about it.)
  3. Nginx Ingress Controller in the K8S Cluster (Search for “What is Ingress Controller and how to deploy Nginx Ingress Controller in Kubernetes Cluster on AWS using Helm” to learn to set up Nginx Ingress Controller)
  4. Helm v3.5.3 (Click here to learn to install Helm on Ubuntu Server)
  5. S3 Bucket (Click here to learn to create an S3 Bucket on AWS).
  6. Domain Name (Click here to learn to register a Domain on AWS).
  7. IAM Role with admin permissions(Click here to learn to create an IAM role on AWS).

What will we do?

  1. Check Ingress Controller in the Cluster
  2. Setup a Cert-Manager
  3. Create Object definition files for a sample application
  4. Setup Staging and Production Let’s Encrypt Issuer
  5. Deploy a sample application
  6. Deploy an Ingress Object with TLS 

Check Ingress Controller in the Cluster

Before you proceed, check if you have the Nginx Ingress Controller running in the cluster.

kubectl get pods
kubectl get svc

<img alt="Check Ingress Controller in the cluster" data-ezsrc="https://kirelos.com/wp-content/uploads/2021/04/echo/screenshot_2021-04-17_at_103030_pm.png6089928fee185.jpg" ezimgfmt="rs rscb3 src ng ngcb3" height="112" loading="lazy" src="data:image/svg xml,” width=”750″>

Setup Cert Manager

Note: I have used Helm binary present at my current location, hence you can see ./helm in screenshots.

Use Helm v3.5.3 and execute the following commands, this will install the Helm Chart for Cert-Manager.

kubectl create namespace cert-manager
helm repo add jetstack https://charts.jetstack.io
helm repo update helm install cert-manager jetstack/cert-manager --namespace cert-manager --version v1.2.0 --set installCRDs=true

<img alt="Setup Cert Manager" data-ezsrc="https://kirelos.com/wp-content/uploads/2021/04/echo/screenshot_2021-04-17_at_104224_pm.png6089929070491.jpg" ezimgfmt="rs rscb3 src ng ngcb3" loading="lazy" src="data:image/svg xml,”>

In the above screenshot, you can see that the Helm Chart for Cert-Manager has been installed.

Check the pods that have been created as part of the Cert-Manager.

kubectl get pods -A

<img alt="Cert Manager Pods" data-ezsrc="https://kirelos.com/wp-content/uploads/2021/04/echo/screenshot_2021-04-17_at_104543_pm.png60899290e4cd9.jpg" ezimgfmt="rs rscb3 src ng ngcb3" height="276" loading="lazy" src="data:image/svg xml,” width=”750″>

You can see 3 new pods in the “cert-manager” namespace.

Create Object definition files for a sample application and issuers

Create 1-nginx-main-app.yaml for application 1

Github Link: Click here to copy the file from my Github repo.

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: nginx
  name: nginx-deploy-main
spec:
  replicas: 1
  selector:
    matchLabels:
      run: nginx-main
  template:
    metadata:
      labels:
        run: nginx-main
    spec:
      containers:
      - image: nginx
        name: nginx


---
apiVersion: v1
kind: Service
metadata:
  name: nginx-deploy-main
spec:
  type: ClusterIP
  ports:
  - port: 80
    targetPort: 80
  selector:
    run: nginx-main

Create 2-nginx-green-app.yaml for application 2.

Github Link: Click here to copy the file from my Github repo.

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: nginx
  name: nginx-deploy-green
spec:
  replicas: 1
  selector:
    matchLabels:
      run: nginx-green
  template:
    metadata:
      labels:
        run: nginx-green
    spec:
      volumes:
      - name: webdata
        emptyDir: {}
      initContainers:
      - name: web-content
        image: busybox
        volumeMounts:
        - name: webdata
          mountPath: "https://www.howtoforge.com/webdata"
        command: ["https://www.howtoforge.com/bin/sh", "-c", 'echo "

I am GREEN

" > /webdata/index.html'] containers: - image: nginx name: nginx volumeMounts: - name: webdata mountPath: "https://www.howtoforge.com/usr/share/nginx/html" --- --- apiVersion: v1 kind: Service metadata: name: nginx-deploy-green spec: type: ClusterIP ports: - port: 80 targetPort: 80 selector: run: nginx-green

Create 3-nginx-blue-app.yaml for application 3

Github Link: Click here to copy the file from my Github repo.

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: nginx
  name: nginx-deploy-blue
spec:
  replicas: 1
  selector:
    matchLabels:
      run: nginx-blue
  template:
    metadata:
      labels:
        run: nginx-blue
    spec:
      volumes:
      - name: webdata
        emptyDir: {}
      initContainers:
      - name: web-content
        image: busybox
        volumeMounts:
        - name: webdata
          mountPath: "https://www.howtoforge.com/webdata"
        command: ["https://www.howtoforge.com/bin/sh", "-c", 'echo "

I am BLUE

" > /webdata/index.html'] containers: - image: nginx name: nginx volumeMounts: - name: webdata mountPath: "https://www.howtoforge.com/usr/share/nginx/html" --- apiVersion: v1 kind: Service metadata: name: nginx-deploy-blue spec: type: ClusterIP ports: - port: 80 targetPort: 80 selector: run: nginx-blue

Create 4-tls-ingress.yaml for creating path-based Ingress rules with Staging Issuer.

Github Link: Click here to copy the file from my Github repo.

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    cert-manager.io/cluster-issuer: letsencrypt-staging
  name: ingress-resource-3
spec:
  tls:
  - hosts:
    - kops.devopslee.com
    secretName: sample-kubernetes-tls
  rules:
  - host: kops.devopslee.com
    http:
      paths:
      - path: /
        backend:
          serviceName: nginx-deploy-main
          servicePort: 80
      - path: /blue
        backend:
          serviceName: nginx-deploy-blue
          servicePort: 80
      - path: /green
        backend:
          serviceName: nginx-deploy-green
          servicePort: 80

Create 5-tls-ingress-prod-issuer.yaml for creating path-based Ingress rules with Production Issuer. 

Github Link: Click here to copy the file from my Github repo.

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    cert-manager.io/cluster-issuer: letsencrypt-production
  name: ingress-resource-3
spec:
  tls:
  - hosts:
    - kops.devopslee.com
    secretName: sample-kubernetes-tls
  rules:
  - host: kops.devopslee.com
    http:
      paths:
      - path: /
        backend:
          serviceName: nginx-deploy-main
          servicePort: 80
      - path: /blue
        backend:
          serviceName: nginx-deploy-blue
          servicePort: 80
      - path: /green
        backend:
          serviceName: nginx-deploy-green
          servicePort: 80

Create staging_issuer.yaml for Let’s Encrypt Staging Issuer

Github Link: Click here to copy the file from my Github repo.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    # Email address used for ACME registration
    email: your-email-id-here
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      # Name of a secret used to store the ACME account private key
      name: letsencrypt-staging-private-key
    # Add a single challenge solver, HTTP01 using nginx
    solvers:
    - http01:
        ingress:
          class: nginx

Create production_issuer.yaml for Let’s Encrypt Production Issuer

Github Link: Click here to copy the file from my Github repo.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-production
spec:
  acme:
    # Email address used for ACME registration
    email: your-email-id-here
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      # Name of a secret used to store the ACME account private key
      name: letsencrypt-production-private-key
    # Add a single challenge solver, HTTP01 using nginx
    solvers:
    - http01:
        ingress:
          class: nginx

You can find all these files on my Github repo here.

Setup Staging and Production Let’s Encrypt Issuer

We will install both Staging and Production Cluster Issuer.

Staging:

Staging has “server: https://acme-staging-v02.api.letsencrypt.org/directory”

kubectl logs cert-manager-56f5c44b5d-jn46m -n cert-manager -f
kubectl apply -f cluster-issuer/staging_issuer.yaml

<img alt="Setup Let's Encrypt Staging Cluster Issuer" data-ezsrc="https://kirelos.com/wp-content/uploads/2021/04/echo/screenshot_2021-04-17_at_105205_pm.png608992919bd8e.jpg" ezimgfmt="rs rscb3 src ng ngcb3" height="222" loading="lazy" src="data:image/svg xml,” width=”750″>

This creates a secret named “letsencrypt-staging-private-key”

kubectl get secret letsencrypt-staging-private-key -n cert-manager -o json

<img alt="Check the staging secret" data-ezsrc="https://kirelos.com/wp-content/uploads/2021/04/echo/screenshot_2021-04-17_at_105504_pm.png6089929245438.jpg" ezimgfmt="rs rscb3 src ng ngcb3" height="272" loading="lazy" src="data:image/svg xml,” width=”750″>

Production:

Production has “server: https://acme-v02.api.letsencrypt.org/directory”

kubectl logs cert-manager-56f5c44b5d-jn46m -n cert-manager -f
kubectl apply -f cluster-issuer/production_issuer.yaml

<img alt="Setup Let's Encrypt Production Cluster Issuer" data-ezsrc="https://kirelos.com/wp-content/uploads/2021/04/echo/screenshot_2021-04-17_at_113236_pm.png60899292d0ab0.jpg" ezimgfmt="rs rscb3 src ng ngcb3" height="227" loading="lazy" src="data:image/svg xml,” width=”750″>

This creates a secret named “letsencrypt-production-private-key”

kubectl get secret letsencrypt-production-private-key -n cert-manager -o json

<img alt="Check the production secret" data-ezsrc="https://kirelos.com/wp-content/uploads/2021/04/echo/screenshot_2021-04-17_at_113551_pm.png60899293702f8.jpg" ezimgfmt="rs rscb3 src ng ngcb3" height="253" loading="lazy" src="data:image/svg xml,” width=”750″>

Deploy a sample application

Let’s deploy our 3 sample applications.

kubectl apply -f sample-app/1-nginx-main-app.yaml
kubectl apply -f sample-app/2-nginx-green-app.yaml
kubectl apply -f sample-app/3-nginx-blue-app.yaml
Check the deployments, pods, and services created by the above commands.
kubectl get deployments
kubectl get pods kubectl
get service

<img alt="Deploy a sample application" data-ezsrc="https://kirelos.com/wp-content/uploads/2021/04/echo/screenshot_2021-04-17_at_110233_pm.png6089929413f1d.jpg" ezimgfmt="rs rscb3 src ng ngcb3" height="333" loading="lazy" src="data:image/svg xml,” width=”750″>

Deploy Ingress

First, let’s deploy an Ingress with Staging Issuer.

kubectl apply -f sample-app/4-tls-ingress.yaml
kubectl get ingress
kubectl describe ingress ingress-resource-3

<img alt="Create ingress with Staging Issuer" data-ezsrc="https://kirelos.com/wp-content/uploads/2021/04/echo/screenshot_2021-04-17_at_111008_pm.png60899294a4eb9.jpg" ezimgfmt="rs rscb3 src ng ngcb3" height="344" loading="lazy" src="data:image/svg xml,” width=”750″>

After the Ingress resource is created, you can see what all happened in the background to issue the certificate for the TLS section of the Ingress.

kubectl get certificate -A
kubectl get certificaterequests.cert-manager.io -A
kubectl get orders.acme.cert-manager.io -A
kubectl get challenges.acme.cert-manager.io -A
kubectl get certificate  -o json | grep secretName
kubectl get secret sample-kubernetes-tls -o yaml

<img alt="Check the certificate, certificate request, order and secret" data-ezsrc="https://kirelos.com/wp-content/uploads/2021/04/echo/screenshot_2021-04-17_at_111604_pm.png6089929599003.jpg" ezimgfmt="rs rscb3 src ng ngcb3" height="458" loading="lazy" src="data:image/svg xml,” width=”750″>

The application can now be accessed over HTTPS, but since we have used the Staging Environment of Let’s Encrypt issuer we will get a warning  “Your connection to this site is not secure”.

<img alt="Test the URLs with HTTPS, certificates from Staging" data-ezsrc="https://kirelos.com/wp-content/uploads/2021/04/echo/screenshot_2021-04-17_at_112415_pm.png60899296367fe.jpg" ezimgfmt="rs rscb3 src ng ngcb3" height="460" loading="lazy" src="data:image/svg xml,” width=”750″>

Deploy Ingress with Production Issuer.

Now, let’s delete the Ingress using the Staging and create a new Ingress pointed to the Production issuer.

kubectl delete -f sample-app/4-tls-ingress.yaml
kubectl apply -f sample-app/5-tls-ingress-prod-issuer.yaml
kubectl get ingress
kubectl describe ingress ingress-resource-3

<img alt="Create ingress with Production Issuer" data-ezsrc="https://kirelos.com/wp-content/uploads/2021/04/echo/screenshot_2021-04-17_at_114358_pm.png60899296c403f.jpg" ezimgfmt="rs rscb3 src ng ngcb3" height="306" loading="lazy" src="data:image/svg xml,” width=”750″>

This time if you try to access the application, you will not get any warning as “The connection to this site is not secure”.

<img alt="Test the URLs with HTTPS, certificates from Production" data-ezsrc="https://kirelos.com/wp-content/uploads/2021/04/echo/screenshot_2021-04-17_at_114837_pm.png608992977ab34.jpg" ezimgfmt="rs rscb3 src ng ngcb3" height="441" loading="lazy" src="data:image/svg xml,” width=”750″>

Conclusion

In this article, we saw the steps to set up a Cert-Manager on the Kubernetes Cluster. We deployed a sample application and routed traffic through ingress based on path and secured the connection with HTTPS by using a certificate issued by the Let’s Encrypt cluster issuer. We first issued a certificate using Let’s Encrypt Staging environment and then used the Let’s Encrypt production environment