We’ll be continuing our ingress-nginx journey by installing and setting up cert-manager via helm to be able to create TLS certificates to associate with backend service hosts which will create a secure TLS connection. We will use cert-manager to set up a DNS-01 challenge using the DigitalOcean Provider.

If you haven’t already gone through part 1, I would advise to first go through that post and then come back once it has been completed.

Upgrading the ingress-nginx chart

In order for the ingress-nginx controller to be enabled for https, we will need to update a couple of annotations on the service of type LoadBalancer and add an extra argument to the controller pod.

In the values.yaml file from before, update the values to now be:

controller:
  service:
    annotations:
      service.beta.kubernetes.io/do-loadbalancer-protocol: "https"
      service.beta.kubernetes.io/do-loadbalancer-size-unit: "1"
      # specify which port of the loadbalancer should use the HTTPS protocol
      service.beta.kubernetes.io/do-loadbalancer-tls-ports: "443"

Upgrade the chart to now include the values

helm upgrade ingress-nginx . -n ingress-nginx

Validate that it has been updated correctly:

# look for the updated annotations
kubectl describe svc -n ingress-nginx ingress-nginx-controller

Installing Cert-Manager

We are going to use helm to now install cert-manager into the cluster.

First, add the appropriate repo:

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

Install the chart with the helm install command:

helm install \
  cert-manager jetstack/cert-manager \
  # install in the same namespace as the nginx ingress-controller
  --namespace ingress-nginx \
  # install the `CustomResourceDefinitions` (CRDs) which are required for chart to work properly
  --set installCRDs=true \
  # the default issuer name for the ingress-shim to use when a new ingress is created
  --set ingressShim.defaultIssuerName=letsencrypt-test \
  # the default kind for the ingress-shim to use when a new ingress is created
  --set ingressShim.defaultIssuerKind=ClusterIssuer

You’ll notice that we didn’t pull down the chart locally and update the values.yaml file. This is to show that there are multiple ways to install a helm chart and the option that you choose depends on your specific use case.

You can also see that we set a couple of values starting with ingressShim, this will be talked about later in the article but just know at this point that we need it for a fully automated TLS workflow, where our Ingress resources will automatically be provided a Certificate resource.

Create Kubernetes Secret

Since we are using a DNS-01 challenge we will need to provide cert-manager with our DigitalOcean API token. Cert-manager intitiates the DNS-01 challenge, Let’s Encrypt then asks cert-manager to complete it, which cert-manager is able to do by using the DigitalOcean solver (a DNS record (TXT record) needs to be created on our behalf, so that Let’s Encrypt knows that the domain really is owned by you). Once it is successful, Let’s Encrypt issues a valid TLS certificate. This is then contained in a created kubernetes secret by cert-manager, which is then used by nginx.

DO_API_TOKEN="<your-DO-api-token>"

kubectl create secret generic "digitalocean-dns" \
  --namespace ingress-nginx \
  --from-literal=access-token="$DO_API_TOKEN"

Configure ClusterIssuer CRD

When we installed the cert-manager helm chart, it created three important CRDs:

  • Issuer: It is a namespaced certificate issuer, which allows the use of different certificate authorities (CAs) in each namespace.
  • ClusterIssuer: Very similar to Issuer, however it does not belong to a namespace and can be used to issue certificates in any namspace.
  • Certificate: A namespaced resource that references either an Issuer or ClusterIssuer that determine what will be honouring the certificate request.

Generally, either an Issuer OR a ClusterIssuer is used, so let’s go ahead and create a ClusterIssuer now:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer # the same kind that we set as the default in the helm chart installation
metadata:
  name: letsencrypt-test # the same name that we set as the default in the helm chart installation
  namespace: ingress-nginx
  labels:
    app: cert-manager
    app.kubernetes.io/name: cert-manager
    app.kubernetes.io/component: "clusterissuer"
spec:
  acme:
    email: <your-email>
    #  URL used to access the ACME server’s directory endpoint
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-test-private
    # List of challenge solvers that will be used to solve ACME challenges for the matching domains.
    solvers:
    # Use the DigitalOcean DNS API to manage DNS-01 challenge records.
      - dns01:
          digitalocean:
            # Kubernetes secret that contains the DO API token.
            # Must be in the same namespace as the Issuer CRD.
            tokenSecretRef:
              name: digitalocean-dns
              key: access-token

Here add in your email, save it and apply it to the cluster.

Verify that it was created, it should have an output where Ready is True

kubectl get issuer letsencrypt-test -n ingress-nginx

As you can see, from the values that we initially set when we installed the chart, they have the same values as here:

  • kind = ingressShim.defaultIssuerKind=ClusterIssuer
  • metadata.name = ingressShim.defaultIssuerName=letsencrypt-test

Which brings us to the last part of our configuration, ingressShim. In order for cert-manager to be fully automated and able to watch for and request signed TLS certificates for our ingress resources, we set the ingressShim values to the default issuer name and kind in our intial install. This way, cert-manager will create Certificate resources that reference the ClusterIssuer letsencrypt-test for all ingresses that have the appropriate annotations. (Which we will cover next)

Update Ingress to Use a TLS Certificate

The ingress-shim will watch the Ingress resource across your cluster and if it observes an Ingress with the correct annotations, it will ensure a Certificate resource with the name provided in the tls.secretName field exists in the Ingress’s namespace.

Therefore we will update our Ingress resource from before by adding tls to the spec and adding the appropriate annotations:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demo
  annotations:
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true" # redirect from HTTP to HTTPS
    nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
    cert-manager.io/cluster-issuer: "letsencrypt-test" # name of our default ClusterIssuer
    kubernetes.io/tls-acme: "true" # enable fully automated Certificate creation via ingress-shim
spec:
  tls:
    - hosts:
      - <your-domain>
      secretName: demo-tls-secret  # cert-manager will store the created certificate in this secret.
  ingressClassName: nginx
  rules:
  - host: <your-domain>
    http:
      paths:
      - backend:
          service:
            name: demo
            port:
              number: 80
        path: /
        pathType: Prefix

Let’s make sure that it was configured correctly and cert-manager is working as expected. We will follow the typical flow of a certificate request to ensure it is working at each step:

# First check the certificate
kubectl get certificate
# if not created yet, check the certificate request
kubectl get certificaterequest
# move onto the orders, if needed
kubectl get orders
# and finally check if there are any ongoing challenges
kubectl get challenges

If everything seems to be working as expected, attempt to curl or go to your domain through a web browser:

curl -Li https://<your-domain>

# if using http://<your-domain>
# should get a 308 Permanent Redirect and then a 200 response

I hope you enjoyed this two-part series on ingresses, TLS, ingress-nginx and cert-manager!