k3s HTTPS with Let’s Encrypt

K3s is a Certified Kubernetes distribution designed for production workloads in unattended, resource-constrained, remote locations or inside IoT appliances. It provides a ready to go Kubernetes instance packaged a single binary.

This guide will show you how to easily set up k3s with HTTPS via Let’s Encrypt.

K3s comes with Traefik out of the box. Traefik itself supports automatic HTTPS via Let’s Encrypt, but setting this up with k3s turned out to be pretty cumbersome. Instead using the excellent cert-manager add-on, it’s a breeze!

0: Setup k3s.

This guide assumes you have a working k3s instance and kubectl configured to talk to your k3s instance. If you haven’t already just follow the docs and you’ll be up and running in minutes.

1. Install Helm

Helm is a package manager for Kubernetes. It consists of a local client and a server component. Installing Helm is quite straightfoward.

Install the client:

brew install kubernetes-helm

Create the service account so Helm can interact with your k3s instance:

kubectl create serviceaccount tiller --namespace=kube-system

Grant it admin privileges:

kubectl create clusterrolebinding tiller-admin --serviceaccount=kube-system:tiller --clusterrole=cluster-admin

Note: granting admin privileges to Helm might not be a good idea depending on how your cluster is setup. Read here for more details.

Install Helm on k3s:

helm init --service-account=tiller

2. Install cert-manager

Alright, so now we have Helm installed let’s move on to installing cert-manager. cert-manager is a Kubernetes add-on to automate the management and issuance of TLS certificates from various issuing sources, amongst which Let’s Encrypt.

Start with installing cert-managers CRDs:

kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.8/deploy/manifests/00-crds.yaml

Add the cert-manager Helm repo:

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

Install cert-manager:

helm install \
  --name cert-manager \
  --namespace cert-manager \
  --version v0.8.1 \
  jetstack/cert-manager

This might take a minute or so. Verify the installation by checking all cert-manager pods are running:

kubectl get all -n cert-manager

3. Configure cert-manager

With cert-manager installed we need to configure it to use Let’s Encrypt, by creating a certificate issuer:

cat <<EOF > letsencrypt-prod-issuer.yaml
apiVersion: certmanager.k8s.io/v1alpha1
kind: Issuer
metadata:
 name: letsencrypt-prod
spec:
 acme:
   # The ACME server URL
   server: https://acme-v02.api.letsencrypt.org/directory
   # Email address used for ACME registration, update to your own.
   email: user@example.com
   # Name of a secret used to store the ACME account private key
   privateKeySecretRef:
     name: letsencrypt-prod
   # Enable the HTTP-01 challenge provider
   http01: {}
EOF

And apply it:

kubectl apply -f letsencrypt-prod-issuer.yaml

By now, cert-manager is ready to provision Let’s Encrypt certificates as we need it.

4. Deploy a service with automatic HTTPS

To try this out let’s deploy a simple service. You should have a DNS name pointed to your k3s cluster for this to work. The example below uses bootcamp.k3s.example.org, be sure to update this to your own hostname!

Create the manifest:

cat <<EOF > k8s-bootcamp.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: k8s-bootcamp
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: k8s-bootcamp
  template:
    metadata:
      labels:
        app: k8s-bootcamp
    spec:
      containers:
      - name: k8s-bootcamp
        image: gcr.io/google-samples/kubernetes-bootcamp:v1
---
apiVersion: v1
kind: Service
metadata:
  name: k8s-bootcamp
  namespace: default
spec:
  ports:
  - name: http
    targetPort: 8080
    port: 80
  selector:
    app: k8s-bootcamp
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: k8s-bootcamp
  annotations:
    kubernetes.io/ingress.class: "traefik"
    certmanager.k8s.io/issuer: "letsencrypt-prod"
    certmanager.k8s.io/acme-challenge-type: http01

spec:
  tls:
  - hosts:
    # Change this to your own hostname
    - bootcamp.k3s.example.org
    secretName: bootcamp-k3s-example-org-tls
  rules:
  # Change this to your own hostname
  - host: bootcamp.k3s.example.org
    http:
      paths:
      - path: /
        backend:
          serviceName: k8s-bootcamp
          servicePort: http
EOF

And apply it:

kubectl apply -f k8s-bootcamp.yaml

Wait a minute or so and your endpoint should become available with a Let’s Encrypt certificate! 🎉