GKE Ingress with Let’s Encrypt using Cert-Manager

- 11 mins

Introduction

Google Kubernetes Engine (GKE) provides a built-in and managed Ingress controller called GKE Ingress. When you create an Ingress object, the GKE Ingress controller creates a Google Cloud HTTP(S) load balancer and configures it according to the information in the Ingress and its associated Services.

This article describes how to setup Ingress for External HTTP(S) Load Balancing, install cert-manager certificate provisioner and setup up a Let’s Encrypt certificate. This was written based on GKE v1.17.17-gke.3000, cert-manager v1.20 and Helm v3.

Prerequisites

Note that a Service exposed through an Ingress must respond to health checks from the load balancer. According to the docs, your app must either serve a response with an HTTP 200 status to GET requests on the / path, or you can configure an HTTP readiness probe, serving a response with an HTTP 200 status to GET requests on the path specified by the readiness probe.

Create a deployment

Here is an example of a sample deployment manifest.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
  labels:
    app: sampleApp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sampleApp
  template:
    metadata:
      labels:
        app: sampleApp
    spec:
      containers:
      - name: sampleContainer
        image: nginx:1.7.9
        ports:
        - name: http
          containerPort: 8080
          protocol: TCP
        readinessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5

Create a service

Here is an example of a sample service manifest.

apiVersion: v1
kind: Service
metadata:
  name: sampleApp-service
  labels:
    app: sampleApp
spec:
  type: NodePort
  selector:
    app: sampleApp
  ports:
    - name: http
      protocol: TCP
      port: 8080
      targetPort: 8080

Install cert-manager

cert-manager runs within your Kubernetes cluster as a series of deployment resources. It utilizes CustomResourceDefinitions to configure Certificate Authorities and request certificates. The following steps installs cert-manager on your Kubernetes cluster.

If all the above steps have completed without error, you are good to go!

Create issuer

The Let’s Encrypt production issuer has very strict rate limits. When you are experimenting and learning, it is very easy to hit those limits, and confuse rate limiting with errors in configuration or operation. Start with Let’s Encrypt staging environment and switch to Let’s Encrypt production after it works fine. In this article, we will be creating a ClusterIssuer.

Create a clusterissuer definition and update the email address to your own. This email is required by Let’s Encrypt and used to notify you of certificate expiration and updates.

cat <<EOF > clusterissuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    # The ACME server URL
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: you@youremail.com # Update to yours
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-staging
    # Enable the HTTP-01 challenge provider
    solvers:
    - http01:
        ingress:
            class: ingress-gce
EOF

Once edited, apply the custom resource:

kubectl apply -f clusterissuer.yaml

Check on the status of the clusterissuer after you create it:

$ kubectl describe clusterissuer letsencrypt-staging

Name:         letsencrypt-staging
...
Status:
  Acme:
    Last Registered Email:  you@youremail.com
    Uri:                    https://acme-staging-v02.api.letsencrypt.org/acme/acct/123456
  Conditions:
    Last Transition Time:  2020-02-24T18:33:56Z
    Message:               The ACME account was registered with the ACME server
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready
Events:                    <none>

You should see the issuer listed with a registered account.

Deploy a TLS Ingress Resource

Create an ingress definition.

cat <<EOF > ingress.yaml
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: sampleApp-ingress
  annotations:
    # specify the name of the global IP address resource to be associated with the HTTP(S) Load Balancer.
    kubernetes.io/ingress.global-static-ip-name: sampleApp-ip
    # add an annotation indicating the issuer to use.
    cert-manager.io/cluster-issuer: letsencrypt-staging
    # controls whether the ingress is modified ‘in-place’,
    # or a new one is created specifically for the HTTP01 challenge.
    acme.cert-manager.io/http01-edit-in-place: "true"
  labels:
    app: sampleApp
spec:
  tls: # < placing a host in the TLS config will indicate a certificate should be created
  - hosts:
    - example.example.com
    secretName: sampleApp-cert-secret # < cert-manager will store the created certificate in this secret
  rules:
  - host: example.example.com
    http:
      paths:
      - path: sample/app/path/*
        backend:
          serviceName: sampleApp-service
          servicePort: 8080
EOF

Once edited, apply ingress resource.

kubectl apply -f ingress.yaml

Verify

View certificate.

$ kubectl get certificate
NAME                    READY     SECRET                AGE
sampleApp-cert-secret   True      sampleApp-cert-secret   6m34s

Describe certificate.

$ kubectl describe certificate sampleApp-cert-secret
Name:         sampleApp-cert-secret
...
Status:
  Conditions:
    Last Transition Time:  2020-03-02T16:30:01Z
    Message:               Certificate is up to date and has not expired
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Not After:               2020-05-24T17:55:46Z
Events:                    <none>

Describe secrets created by cert manager.

$ kubectl describe secret sampleApp-cert-secret

Name:         sampleApp-cert-secret
...
Type:  kubernetes.io/tls

Data
====
ca.crt:   0 bytes
tls.crt:  3598 bytes
tls.key:  1675 bytes

Switch to Let’s Encrypt Prod

Update the clusterissuer to use Let’s encrypt prod.

cat <<EOF > clusterissuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
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: you@youremail.com # Update to yours
    # 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: ingress-gce
EOF

Once edited, apply the custom resource:

kubectl apply -f clusterissuer.yaml

Now that we are sure that everything is configured correctly, you can update the annotations in the ingress to specify the production issuer:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: sampleApp-ingress
  annotations:
    kubernetes.io/ingress.global-static-ip-name: sampleApp-ip
    cert-manager.io/cluster-issuer: letsencrypt-prod
    acme.cert-manager.io/http01-edit-in-place: "true"
  labels:
    app: sampleApp
spec:
  tls:
  - hosts:
    - example.example.com
    secretName: sampleApp-cert-secret
  rules:
  - host: example.example.com
    http:
      paths:
      - path: sample/app/path/*
        backend:
          serviceName: sampleApp-service
          servicePort: 8080
kubectl create --edit -f ingress.yaml

You will also need to delete the existing secret, which cert-manager is watching. This will cause it to reprocess the request with the updated issuer.

kubectl delete secret sampleApp-cert-secret

This will start the process to get a new certificate. Use describe to see the status.

kubectl describe certificate sampleApp-cert-secret

You can see the current state of the ACME Order by running kubectl describe on the Order resource that cert-manager has created for your Certificate:

$ kubectl describe order sampleApp-cert-secret-889745041
...
Events:
  Type    Reason      Age   From          Message
  ----    ------      ----  ----          -------
  Normal  Created     90s   cert-manager  Created Challenge resource "sampleApp-cert-secret-889745041-0" for domain "example.example.com"

You can describe the challenge to see the status of events by doing:

kubectl describe challenge sampleApp-cert-secret-889745041-0

Once the challenge(s) have been completed, their corresponding challenge resources will be deleted, and the ‘Order’ will be updated to reflect the new state of the Order. You can describe order to verify this.

Finally, the ‘Certificate’ resource will be updated to reflect the state of the issuance process. ‘describe’ the Certificate and verify that the status is true and type and reason are ready.

Kosy Anyanwu

Kosy Anyanwu

The lady who travels the world and loves karaoke

comments powered by Disqus
rss facebook twitter github gitlab youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora