Scaleway Kapsule HowTo: Ingress Nginx TLS with Let's Encrypt and Cert Manager

May 4, 2020
kubernetes howto kapsule

Still a bit unfinished, waiting for review

Ingress Nginx

An ingress implementation build over Nginx. Documentation

Cert Manager

A native Kubernetes certificate management controller. Documentation

HowTo

Prerequisites

A Kapsule Kubernetes cluster deployed with ingress-nginx. Alternatively a vanilla kubernetes cluster with public IPs nodes.

You can check if your nodes IPs with kubectl get nodes -o wide:

% kc get nodes -o wide
NAME                           VERSION  INTERNAL-IP     EXTERNAL-IP
scw-k8s-clever-wright-234d10   1.18.2   10.68.154.43    51.15.218.57
scw-k8s-clever-wright-fa73a0   1.18.2   10.68.116.133   51.15.238.44

Register these IPs in your DNS zone, for example:

clever-wright.shirokumo.net. 300 IN	A	51.15.238.44 ; worker 1
clever-wright.shirokumo.net. 300 IN	A	51.15.218.57 ; worker 2

See here if you need to deploy ingress-nginx.

Ingress-nginx

Check the daemonset:

% kc get ds -n kube-system nginx-ingress
NAME            DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE
nginx-ingress   2         2         2       2            2

Install cert-manager

See the installation doc.

# Kubernetes 1.15+
# create all ressources and custom ressources definition
$ kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.14.3/cert-manager.yaml

Check the pods:

% kubectl get pods --namespace cert-manager
NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-7959697b84-h4pl2              1/1     Running   0          23h
cert-manager-cainjector-6c9b494449-tvcl4   1/1     Running   0          23h
cert-manager-webhook-55994d7747-pm26k      1/1     Running   0          23h

Create a Let’s Encrypt ACME Issuer in the default namespace:

$ cat <<EOF > letsencrypt-prod-issuer.yaml
apiVersion: cert-manager.io/v1alpha2
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
    email: email@example.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-prod
    # Enable the HTTP-01 challenge provider
    solvers:
    - http01:
        ingress:
          class: nginx
EOF
$ kubectl apply -f letsencrypt-prod-issuer.yaml

Deploy an echoserver:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echoserver
spec:
  replicas: 2
  selector:
    matchLabels:
      app: echoserver
  template:
    metadata:
      labels:
        app: echoserver
    spec:
      containers:
      - image: gcr.io/google_containers/echoserver:1.0
        imagePullPolicy: Always
        name: echoserver
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: echoserver
spec:
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
  selector:
    app: echoserver

Create an Ingress with certificates:

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: echoserver
  annotations:
    kubernetes.io/ingress.class: "nginx"
    kubernetes.io/tls-acme: "true"
    cert-manager.io/issuer: letsencrypt-prod
spec:
  rules:
  - host: clever-wright.shirokumo.net
    http:
      paths:
      - path: /
        backend:
          serviceName: echoserver
          servicePort: 80
  tls:
  - hosts:
    -  clever-wright.shirokumo.net
    secretName: clever-wright-shirokumo-net-tls

Check the logs of cert-manager to see if everything is going well:

kc logs -n cert-manager --tail=20 -f cert-manager-7959697b84-h4pl2

or even better if you have installed stern (a multi-pod tailer):

stern -n cert-manager --tail=20 cert-manager

Request the ingress:

# with httpie or curl
% http https://clever-wright.shirokumo.net/
HTTP/1.1 200 OK
Connection: keep-alive
Date: Mon, 04 May 2020 21:09:13 GMT
Server: nginx/1.17.8
Strict-Transport-Security: max-age=15724800; includeSubDomains
Transfer-Encoding: chunked

CLIENT VALUES:
client_address=('100.64.1.129', 48538) (100.64.1.129)
command=GET
path=/
real path=/
query=
request_version=HTTP/1.1

SERVER VALUES:
server_version=BaseHTTP/0.6
sys_version=Python/3.5.0
protocol_version=HTTP/1.0

HEADERS RECEIVED:
Accept=*/*
Accept-Encoding=gzip, deflate
Host=clever-wright.shirokumo.net
User-Agent=HTTPie/1.0.3
X-Forwarded-For=<REAL_IP>
X-Forwarded-Host=clever-wright.shirokumo.net
X-Forwarded-Port=443
X-Forwarded-Proto=https
X-Real-IP=<REAL_IP>
X-Request-ID=a4f2ddcf80933964c81922837b0602ea
X-Scheme=https