What is Cert-Manager and how to setup Cert-Manager for SSL certificates in Kubernetes Cluster on AWS using Helm
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.
- Issuer: Issuers, and ClusterIssuers, are objects in Kubernetes that represent certificate authorities (CAs) that can generate signed certificates.
- Certificate: A Certificate is a namespaced resource that references an Issuer or ClusterIssuer and will be renewed and kept up to date.
- CertificateRequest: The CertificateRequest is a namespaced resource that is used to request a certificate from an issuer or cluster issuer.
- ACME Orders: An order represents a certificate request which will be created once a new CertificateRequest resource referencing an ACME issuer has been created
- 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.
- Webhook: It is deployed as another pod along with the main Cert-Manager pods and has 3 functions: ValidatingAdmissionWebhook, MutatingAdmissionWebhook, and CustomResourceConversionWebhook.
- 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
- AWS Account(Create if you don’t have one).
- Kubernetes Cluster (Click here to learn to create a Kubernetes Cluster using Kops and know more about it.)
- 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)
- Helm v3.5.3 (Click here to learn to install Helm on Ubuntu Server)
- S3 Bucket (Click here to learn to create an S3 Bucket on AWS).
- Domain Name (Click here to learn to register a Domain on AWS).
- IAM Role with admin permissions(Click here to learn to create an IAM role on AWS).
What will we do?
- Check Ingress Controller in the Cluster
- Setup a Cert-Manager
- Create Object definition files for a sample application
- Setup Staging and Production Let's Encrypt Issuer
- Deploy a sample application
- 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
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
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
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: "/webdata" command: ["/bin/sh", "-c", 'echo "<h1>I am <font color=green>GREEN</font></h1>" > /webdata/index.html'] containers: - image: nginx name: nginx volumeMounts: - name: webdata mountPath: "/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: "/webdata" command: ["/bin/sh", "-c", 'echo "<h1>I am <font color=blue>BLUE</font></h1>" > /webdata/index.html'] containers: - image: nginx name: nginx volumeMounts: - name: webdata mountPath: "/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
This creates a secret named "letsencrypt-staging-private-key"
kubectl get secret letsencrypt-staging-private-key -n cert-manager -o json
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
This creates a secret named "letsencrypt-production-private-key"
kubectl get secret letsencrypt-production-private-key -n cert-manager -o json
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
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
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
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".
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
This time if you try to access the application, you will not get any warning as "The connection to this site is not secure".
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