Use a letsencrypt certificate on Kubernetes with cert-manager and Traefik

traefik k3s ingress letsencrypt cert-manager

4 min read | by Jordi Prats

To be able to automatically request letsencrypt certificates for the TLS-eanble Ingress objects in a kubernetes cluster with the traefik ingress controller we can use the cert-manager controller.

First we'll be installing it using helm. To do se we don't need to add any values, unless we want it to configuew the ClusterIssuer for us. First we are going to load the repository and update it:s

$ helm repo add jetstack https://charts.jetstack.io
$ helm repo update

Once ready we can install it as follows:

helm install cert-manager jetstack/cert-manager --namespace cert-manager

We'll need to give it a few seconds to spin up all the containers:

$ kubectl get pods -n cert-manager
NAME                                      READY   STATUS    RESTARTS   AGE
cert-manager-6ffb79dfdb-mqkvm             1/1     Running   0          13s
cert-manager-cainjector-5fcd49c96-2zfmw   1/1     Running   0          13s
cert-manager-webhook-796ff7697b-9w67w     1/1     Running   0          13s

Once ready we can now create the ClusterIssuer objects, which defines how to connect to the external provider via the ingress controller. For this example we are going to use K3S's default ingress controller: traefik, requesting certificates to letsencrypt:

apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: email@pet2cattle.com
    privateKeySecretRef:
      name: letsencrypt-ca
    solvers:
      - http01:
          ingress:
            ingressTemplate:
              metadata:
                annotations:
                  "kubernetes.io/ingress.class": "traefik"

privateKeySecretRef is where the data is stored, it will be created for us.

We can use kubectl describe to check whether or now the ClusterIssuer is able to issue new certificates

$ kubectl describe clusterissuer letsencrypt
Name:         letsencrypt
Namespace:
Labels:       <none>
Annotations:  <none>
API Version:  cert-manager.io/v1
Kind:         ClusterIssuer
Metadata:
  (...)
Status:
  Acme:
    Last Registered Email:  jprats@systemadmin.es
    Uri:                    https://acme-v02.api.letsencrypt.org/acme/acct/1112900837
  Conditions:
    Last Transition Time:  2023-05-16T17:30:44Z
    Message:               The ACME account was registered with the ACME server
    Observed Generation:   1
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready
Events:                    <none>

Once we have the ClusterIssuer we'll need to make sure we have the application exposed using an Ingress with the tls options:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: awscost
spec:
  ingressClassName: traefik
  rules:
  - host: awscost.pet2cattle.com
    http:
      paths:
      - backend:
          service:
            name: awscost
            port:
              name: http
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - awscost.pet2cattle.com
    secretName: awscost-pet2cattle-https-cert

The secret referenced in secretName is where the certificate is going to be stored, we don't need to populate it with anything.

The requirements, depending on the issuer, we'll be different. For letsencrypt we'll need to make this reachable for them to validate it so we'll need to make sure DNS is updated as well.

With the application reachable we can now proceed to create the Certificate with the commonName and SAN (dnsNames) we need:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: awscost-pet2cattle-https-cert
spec:
  commonName: awscost.pet2cattle.com
  dnsNames:
  - awscost.pet2cattle.com
  issuerRef:
    kind: ClusterIssuer
    name: letsencrypt
  secretName: awscost-pet2cattle-https-cert

Once created, the cert-manager controller will need to request the certificate and with for the CA to create it. It's going to take a few minutes. We can check it's status directly with kubectl get looking as the READY column:

$ kubectl get certificate
NAME                            READY   SECRET                          AGE
awscost-pet2cattle-https-cert   False   awscost-pet2cattle-https-cert   6s
(...)
$ kubectl get certificate
NAME                            READY   SECRET                          AGE
awscost-pet2cattle-https-cert   True    awscost-pet2cattle-https-cert   5m42s

In the status section we can see some details of the certificate itself:

$ kubectl get certificate awscost-pet2cattle-https-cert -n awscost -o yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  (...)
spec:
  commonName: awscost.pet2cattle.com
  dnsNames:
  - awscost.pet2cattle.com
  issuerRef:
    kind: ClusterIssuer
    name: letsencrypt
  secretName: awscost-pet2cattle-https-cert
status:
  conditions:
  - lastTransitionTime: "2023-05-17T18:12:20Z"
    message: Certificate is up to date and has not expired
    observedGeneration: 2
    reason: Ready
    status: "True"
    type: Ready
  notAfter: "2023-08-15T17:12:18Z"
  notBefore: "2023-05-17T17:12:19Z"
  renewalTime: "2023-07-16T17:12:18Z"
  revision: 1

Posted on 23/05/2023