❓HOWTO: cert-manager with ingress-nginx on GKE

A few months ago, I described how to deploy Ghost on Google Cloud's Kubernetes Engine (GKE), using GCP's managed SSL offering. But that setup had some limitations, both with the managed SSL certificates as well as GCP's default ingress (which is an L7 solution).

At the time, I had assumed that managing SSL certificates any other way on Kubernetes would be too complicated. Turns out, it's actually pretty straight-forward to do this without relying on a managed offering. Besides, it's cheaper and provides more features (e.g. automatic HTTP to HTTPS redirects, HSTS support and more). Read on to find out how.

First, create your cluster. I wanted to get Kubernetes 1.6+ so I went with the rapid release channel, but that's obviously not recommended for production workloads. I also enabled automatic upgrades and configured the default node pool with E2 instances. Finally, I also enabled auto-scaling.

Next, install helm. Part of the reason I started with a new cluster with the latest k8s was that I wanted to test drive the new helm 3 – among other things, they completely got rid of tiller, making the installation and deployment significantly simpler! For Mac, a simple brew install helm should do the trick, otherwise check the installation guide.

Cert Manager

For certificate management, we'll use the excellent cert-manager. Installation should be as simple as:

# Create Custom Resource Definitions.
# NOTE: if you're using k8s version 1.15 or earlier, you may need to
# supply the `--validate=false` flag
$ kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.12/deploy/manifests/00-crds.yaml

# Create a dedicated namespace
$ kubectl create namespace cert-manager

# Add the jetstack repo
$ helm repo add jetstack https://charts.jetstack.io
$ help repo update

# Install the chart. I disabled webhooks since I didn't need them.
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --version v0.12.0 \
  --set webhook.enabled=false
Install cert-manager

Finally, setup both staging and production issuers (I opted for the simpler ClusterIssuer configuration) as described here. Basically, your issuer file should look something like this:

apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    # You must replace this email address with your own.
    # Let's Encrypt will use this to contact you about expiring
    # certificates, and issues related to your account.
    email: [email protected]
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      # Secret resource used to store the account's private key.
      name: letsencrypt-staging
    # Add a single challenge solver, HTTP01 using nginx
    solvers:
    - http01:
        ingress:
          class: nginx

Ingress NGINX

For ingress, I went with ingress-nginx. With Helm, installation was trivial:

$ kubectl create namespace nginx-ingress
$ helm install nginx-ingress stable/nginx-ingress \
  --namespace nginx-ingress \
  --set rbac.create=true \
  --set controller.service.loadBalancerIP=<STATIC-IP>
Install ingress-nginx

Note that unlike the GCE Ingress (which is L7), this is an L4 Ingress. And therefore, you will need to allocate a regional static IP address (as opposed to a global reservation for L7).

Now go ahead and deploy your sites. Your ingress configuration should look something like this:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: nginx-ingress
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: floatingsun.net
    http:
      paths:
      - backend:
          serviceName: floatingsun-net
          servicePort: 80
  tls:
  - hosts:
    - floatingsun.net
    secretName: floatingsun-net

Each new entry in the TLS section will trigger cert-manager to issue a certificate, and store the secret in the secretName key. Cert-Manager will take care of renewing the certificates etc. For each new domain you want a certificate for, just add the corresponding entries under the rules and tls sections.

And that's it! If you found the post useful, please like / share.