DEV Community

Cover image for Adding Free SSL/TLS on Kubernetes (using CertManager and LetsEncrypt)
Ileriayo Adebiyi
Ileriayo Adebiyi

Posted on

Adding Free SSL/TLS on Kubernetes (using CertManager and LetsEncrypt)

You have a shiny new Kubernetes cluster. You deploy your hello-world app and can now reach it via a NodePort service at http://:30000.

As the workload begins to increase, you decide to use a Loadbalancer Service instead. But then it occurs to you - using a Loadbalancer service for each workload can become so expensive.

Ingress to the rescue!

You write a simple ingress manifest and kubectl apply! Boom 💥

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  labels:
    app: hello-world
  name: example.com
spec:
  rules:
  - host: example.com
    http:
      paths:
      - backend:
          service:
            name: hello-world
            port:
              number: 80
        path: /
        pathType: Prefix
Enter fullscreen mode Exit fullscreen mode

But without an ingress controller, your ingress resource has zero effect. You remember that some guy on YouTube had mentioned one Ingress-Nginx controller and you think let's check that out.

Dang! After 2 hours, you find on StackOverflow that your k3s cluster came bundled with the Traefik Ingress Controller 😡

You create an A record to configure your domain to point to the public IP Address of the (single) node in your cluster. With a big smile, you open a new tab on your browser, head to the URL bar and navigate to your domain (example.com). You get a response, "your connection is not secure".

What do you do now?

In the following secion, I'll show you how to add TLS to your Kubernetes cluster using cert-manager to automate certificate management operations such as issuance and renewal.

Cert-manager + LetsEncrypt

Cert-manager is a native Kubernetes certificate management controller. It can help with issuing certificates from a variety of sources, such as Let's Encrypt, HashiCorp Vault, Venafi, a simple signing key pair, or self signed.

Let's Encrypt is a nonprofit Certificate Authority that provides TLS certificates to 300 million websites.

Steps

  1. Install Cert-manager onto your cluster
  2. Add LetsEncrypt as an Issuer (or ClusterIssuer)
  3. Update ingress to use certificate

Install Cert-manager

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.1.1/cert-manager.yaml
Enter fullscreen mode Exit fullscreen mode

Verify installation of cert-manager by checking the cert-manager namespace for running pods

$ kubectl get pods --namespace cert-manager

NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-5c6866597-zw7kh               1/1     Running   0          2m
cert-manager-cainjector-577f6d9fd7-tr77l   1/1     Running   0          2m
cert-manager-webhook-787858fcdb-nlzsq      1/1     Running   0          2m
Enter fullscreen mode Exit fullscreen mode

All good! Proceed

Issuer (or ClusterIssuer) - Let's Enncrypt

a. Create either a clusterissuer.yaml manifest

apiVersion: cert-manager.io/v1
kind: ClusterIssuer # I'm using ClusterIssuer here
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: <your-email-address>
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - http01:
        ingress:
          class: traefik 
Enter fullscreen mode Exit fullscreen mode

b. Apply the manifest

kubectl apply -f clusterissuer.yaml
Enter fullscreen mode Exit fullscreen mode

Create (or Update) Ingress

The following manifest is an example ingress resource:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  labels:
    app: hello-world
  name: 
  namespace: <namespace> # if non-default namespace
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  rules:
  - host: example.com # your domain
    http:
      paths:
      - backend:
          service:
            name: <your-service>
            port:
              number: 80 # use appropriate port
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - example.com # your domain 
    secretName: letsencrypt-prod # secret name, same as the privateKeySecretRef in the (Cluster)Issuer
Enter fullscreen mode Exit fullscreen mode

Apply the manifest!

You can verify that a certificate has been issued

$ kubectl -n <namespace> describe certificate letsencrypt-prod
Spec:
  Dns Names:
    example.com
  Issuer Ref:
    Group:      cert-manager.io
    Kind:       ClusterIssuer
    Name:       letsencrypt-prod
  Secret Name:  letsencrypt-prod
  Usages:
    digital signature
    key encipherment
Status:
  Conditions:
    Last Transition Time:  2023-06-14T03:24:49Z
    Message:               Certificate is up to date and has not expired
    Observed Generation:   1
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Not After:               2023-09-12T02:10:00Z
  Not Before:              2023-06-14T02:10:01Z
  Renewal Time:            2023-08-13T02:10:00Z
Events:                    <none>
Enter fullscreen mode Exit fullscreen mode

You should now have TLS configured!

Leave your feedback, comments and questions below, and I'll be happy to res you.

If you liked this, you can follow me on Twitter (@ileriayooo) where I share lot's of info on Kubernetes, Cloud Native and DevOps.

Top comments (1)

Collapse
 
abdullahkhawer profile image
Abdullah Khawer • Edited

For some reason, I had to use a different name for secretName: under tls: in ingress than the one used in name: under privateKeySecretRef: in ClusterIssuer to make it work as I was getting this error on the certificate created by cert-manager: Error finalizing order :: certificate public key must be different than account key