What's the easiest way to add TLS to a Kubernetes service?

9/25/2019

I have a simple web server exposed publicly on Kubernetes on GKE and a domain registered. I'm looking to add TLS to this so it's accessible via HTTPS. I've heard a lot about using Let's Encrypt and ended up attempting this: https://github.com/jetstack/cert-manager/blob/master/docs/tutorials/acme/quick-start/index.rst but found it totally over-whelming. Is there a simpler approach to using Let's Encrypt given that my deployment is just a single service and pod?

The config I'm using is:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  labels:
    app: web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: web
        image: gcr.io/my-repo
        ports:
        - containerPort: 8080
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
        readinessProbe:
          initialDelaySeconds: 10
          httpGet:
            path: /healthz
            port: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: web-balancer-service
spec:
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    run: web
  type: NodePort
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: my-ingress-app

spec:
  rules:
  - host: my.domain.com
    http:
      paths:
      - path: /*
        backend:
          serviceName: web-balancer-service
          servicePort: 8080

\========================================

EDIT: Following @Utku Özdemir's suggestion I tried to codify those changes into YAML. I created the IP address with

gcloud compute addresses create example-ip-address --global

And the certificate and provisioning with: https://gist.github.com/nickponline/ab74d3d179e21474551b7596c6478eea

Everything provisions correctly but when I inspect the ManagedCertificates with kubectl describe ManagedCertificates example-certificate is says

Spec:
  Domains:
    app.domain.xyz
Status:
  Certificate Name:    xxxxxxxxxxxxxxxxxx
  Certificate Status:  Provisioning
  Domain Status:
    Domain:  app.domain
    Status:  FailedNotVisible
Events:      <none>

I've waited 24 hours so assume that this isn't going to change.

-- nickponline
kubernetes
kubernetes-ingress
lets-encrypt

1 Answer

9/25/2019

Since you use the ingress controller of the GKE itself, when you create an Ingress resource, it triggers the creation of a Load Balancer resource in the Google Cloud Platform. Normally, SSL termination is a responsibility of the ingress controller, therefore that GCP load balancer is responsible of doing the SSL termination.

This means, cert-manager will not work for your case, since the certificates will live outside of your cluster, and the traffic will be already SSL terminated before coming in your cluster.

Luckily, GCP has self-provisioned SSL (Let's Encrypt) support. To make use of that, yo need to follow the steps below:

  1. Go to the Load Balancing screen on GCP, switch to advanced view, and jump to the Certificates tab (or simply click here).

  2. Create a new SSL certificate, with "Create Google-managed certificate" chosen. To the domain field, write down the exact domain you want the SSL certificate for. It should look like this:

enter image description here

  1. Go to the External IP Addresses screen, and reserve a new static IP address. Choose the type to be global (at the time of writing, GCP ingress controller only supports global IP addresses). Should look like this:

enter image description here

  1. Take the static IP that you reserved (in this example it is 34.95.84.106)

enter image description here

Go to your domain registrar, and add an A type record for your domain (the one in the SSL certificate) to point to the static IP you allocated. In this example, it would be my-app.example.com -> 34.95.84.106.

  1. Finally, you will need to edit your ingress to put 2 annotations, so it will hint the Google Cloud's Ingress Controller to use the static IP you reserved, and the certificate you created. See the ingress example below:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: my-ingress-app
  annotations:
    ingress.gcp.kubernetes.io/pre-shared-cert: my-ssl-certificate # the name of the SSL certificate resource you created
    kubernetes.io/ingress.global-static-ip-name: my-static-ip # the name of the static ip resource you created
    kubernetes.io/ingress.allow-http: "false" # if you want to block plain http
spec:
  rules:
    - host: my-app.example.com
      http:
        paths:
          - path: /*
            backend:
              serviceName: web-balancer-service
              servicePort: 8080

Apply it, and verify that the changes are reflected by going to the Load Balancers screen on GCP.

Important Notes:

  • If there already is a GCP Load Balancer that is created by an Ingress, the changes you do (annotations) on the ingress will not reflect to the existing load balancer. Therefore, delete your existing ingress, make sure the existing load-balancer disappears, and create the ingress with correct annotations, so the load balancer will be configured correctly.

  • For the Let's Encrypt provisioning to work, your DNS record should be in place. It checks the owner of the domain using DNS before issuing the certificate. Also, the initial provisioning can take quite some time (up to half an hour).

-- Utku Özdemir
Source: StackOverflow