cert-manager
In this section we present cert-manager, a tool commonly used in a Kubernetes cluster to automate the issuance and management of TLS certificates. Cert-Manager can obtain certificates from a variety of issuers.
Prerequisites
We need a Kubernetes cluster and the kubectl binary configured with the cluster’s kubeconfig. We also need the helm binary.
Installing Traefik
First we install a Traefik based Ingress Controller in the cluster:
helm repo add traefik https://traefik.github.io/charts
helm install traefik traefik/traefik --version 32.1.1 -n traefik --create-namespace
Next we get the IP Address of the LoadBalancer used to expose this Ingress Controller:
$ kubectl -n traefik get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
traefik LoadBalancer 10.110.8.55 194.182.160.240 80:30989/TCP,443:30760/TCP 75s

Then we create a DNS A record so the demo.exoscale.dev domain name resolves to this external IP. The screenshot below illustrates how to create this A record using DNS on the Exoscale platform.
Installing cert-manager
Next we install cert-manager using Helm:
helm repo add cert-manager https://charts.jetstack.io
helm install cert-manager cert-manager/cert-manager --set crds.enabled=true --version 1.16.1 -n cert-manager --create-namespace
About cert-manager CRDs
Cert-manager defines several CRDs, they can be devided in 2 groups:
- Issuer and ClusterIssuer define the Certificate Authority (CA). An Issuer is scoped to a specific Namespace while a ClusterIssuer is global to the cluster
- Certificate, CertificateRequest, Order and Challenge are the CRDs responsible for requesting and verifying certificates
Let’s detail how these CRDs are used in the certificate issuance workflow:
- first, a certificate is requested using the Certificate CRD, it specifies the domain name, the secret holding the certificate’s data, and the reference to the Issuer or ClusterIssuer
- it automatically creates a CertificateRequest used by cert-manager to track the certificate issuing process
- the CertificateRequest triggers the creation of an Order, which defines how the certificate will be obtained from the Certificate Authority (CA)
- then, a Challenge is created to verify the ownership of the domain associated with the certificate
- once the Challenge is successfully completed, cert-manager retrieves the signed certificate from the CA and stores it in the specified Kubernetes Secret
Definition of an Issuer
We define a ClusterIssuer, which is the entity in charge of the certificate generation. In this case, the ClusterIssuer is configured to request certificates from the Let’s Encrypt Certificate Authority.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt
spec:
acme:
email: devops@techwhale.io
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: acme-account-key
solvers:
- http01:
ingress:
class: traefik
kubectl apply -f letencrypt-cluster-issuer.yaml
In the next sections we will explore 2 ways to request a certificate.
Sample application
We consider a simple demo application which is a basic web frontend displaying a colorful geometrical shape. The application code is in this GitLab group, which contains 2 repositories:
- www: source code
- helm: Helm packaging of the application
Let’s install this application using Helm:
helm upgrade --install shape oci://registry-1.docker.io/lucj/shapeit --version v1.0.7 -n shapes --create-namespace
By default, it creates a Deployment and a ClusterIP Service.
$ kubectl get deploy,po,svc -n shapes
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/shape-shapeit 1/1 1 1 36s
NAME READY STATUS RESTARTS AGE
pod/shape-shapeit-fb757c944-x6s66 1/1 Running 0 36s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/shape-shapeit ClusterIP 10.107.223.49 <none> 5000/TCP 37s
In the next step, we will add an Ingress resource manually and ensure it exposes the application via HTTPS.
Create certificate manually
First we create the following resource which define a Certificate for domain name demo.exoscale.dev:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: shapes
namespace: shapes
spec:
secretName: tls-cert
dnsNames:
- demo.exoscale.dev
issuerRef:
kind: ClusterIssuer
name: letsencrypt
kubectl apply -f shapes-cert.yaml
We can verify the Certificate, CertificateRequest, Order and Challenge CRDs have been created:
$ kubectl -n shapes get certificate
NAME READY SECRET AGE
shapes False tls-cert 6s
$ kubectl -n shapes get certificateRequest
NAME APPROVED DENIED READY ISSUER REQUESTER AGE
shapes-1 True False letsencrypt system:serviceaccount:cert-manager:cert-manager 12s
$ kubeclt -n shapes get order
NAME STATE AGE
shapes-1-37735903 pending 21s
$ kubectl -n shapes get challenge
NAME STATE DOMAIN AGE
shapes-1-37735903-2335959476 pending demo.exoscale.dev 25s
It takes a few tens of seconds for the certificate to be Ready.
$ kubectl get Certificate -n shapes
NAME READY SECRET AGE
shapes True tls-cert 49s
The certificate and the associated private key are stored in the secret specified in the Certificate specification (tls-cert in this case).
Next we create an Ingress resource to forward all the traffic targeting demo.exoscale.dev to our demo application. It defines the tls configuration for the demo.exoscale.dev domain, referencing the tls-cert Secret containing the certification information.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shapes
namespace: shapes
spec:
ingressClassName: traefik
rules:
- host: demo.exoscale.dev
http:
paths:
- backend:
service:
name: shape-shapeit
port:
number: 5000
path: /
pathType: ImplementationSpecific
tls:
- hosts:
- demo.exoscale.dev
secretName: tls-cert
kubectl apply -f shapes-ingress.yaml
Then we can access the application through HTTPS.
Everything is working fine, let’s now delete the Certificate and Ingress resource.
kubectl delete -f shapes-ingress.yaml -f shapes-cert.yaml
In the next section, we will see how to create a certificate without directly using the Certificate CRD.
Use annotation on an Ingress resource
To automate the process we illustrated above, we can simply use an annotation on the Ingress resource. The following specification defines the Ingress exposing our demo application, it specifies the annotation cert-manager.io/cluster-issuer: letsencrypt
.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shapes
namespace: shapes
annotations:
cert-manager.io/cluster-issuer: letsencrypt
spec:
ingressClassName: traefik
rules:
- host: demo.exoscale.dev
http:
paths:
- backend:
service:
name: shape-shapeit
port:
number: 5000
path: /
pathType: ImplementationSpecific
tls:
- hosts:
- demo.exoscale.dev
secretName: shapes-tls
kubectl apply -f shapes-ingress.yaml
Using this specific annotation, which references the ClusterIssuer existing in the cluster, cert-manager automatically create the Certificate for the domain name specified in the Ingress resource.
$ kubectl -n shapes get certificate
NAME READY SECRET AGE
shapes-tls True shapes-tls 12s
We can access the application through HTTPS as we did previously.
Cleanup
We remove our demo application, cert-manager and Traefik ingress controller
helm uninstall -n shapes shape
helm uninstall -n cert-manager cert-manager
helm uninstall -n traefik traefik