Logo Kubeship

Install cert-manager on Google Kubernetes Engine (GKE)


cert-manager is a Kubernetes-native tool for requesting and managing SSL certificates, similar to what certbot does on the command line. Integrating it with Let’s Encrypt is straightforward — let’s walk through the setup on Google Cloud.

Prerequisites

Before configuring Kubernetes, we need to set up a Google Cloud service account.

Create the service account:

gcloud iam service-accounts create cert-manager \
    --display-name="DNS01 Solver for cert-manager"

Grant it DNS admin permissions so it can update DNS challenges:

gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:cert-manager@$PROJECT_ID.iam.gserviceaccount.com" \
    --role="roles/dns.admin"

Enable Workload Identity for the service account:

gcloud iam service-accounts add-iam-policy-binding \
    "cert-manager@$PROJECT_ID.iam.gserviceaccount.com" \
    --role roles/iam.workloadIdentityUser \
    --member "serviceAccount:$PROJECT_ID.svc.id.goog[cert-manager/cert-manager]"

Install cert-manager

Deploy cert-manager using the official Helm chart:

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

helm upgrade cert-manager jetstack/cert-manager \
    --install                                   \
    --create-namespace                          \
    --namespace         cert-manager            \
    --set               installCRDs=true        \
    --set               serviceAccount.annotations."iam\.gke\.io/gcp-service-account"=cert-manager@$PROJECT_ID.iam.gserviceaccount.com \
    --wait

Create the Cluster Issuer

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: <EMAIL_ADDRESS>
    profile: tlsserver
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - dns01:
        cloudDNS:
          project: $PROJECT_ID
envsubst < clusterissuer-yaml | kubectl apply -f  -

There are several ways to request a certificate. The recommended approach is to create a Certificate resource and verify it has been issued — this allows the certificate to be reused across multiple ingresses.

Create the following resource in your target namespace:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: my-certificate
  namespace: example
spec:
  secretName: my-certificate-tls
  privateKey:
    rotationPolicy: Always
  dnsNames:
    - example.mydomain.com
  usages:
    - digital signature
    - key encipherment
    - server auth
  issuerRef:
    kind: ClusterIssuer
    name: letsencrypt-prod

If you have cmctl installed, check the certificate status:

cmctl status certificate my-certificate -n example

You should see output similar to:

Name: my-certificate
Namespace: example
Created at: 2025-10-17T16:05:27-04:00
Conditions:
  Ready: False, Reason: RequestChanged, Message: Fields on existing CertificateRequest resource not up to date: [spec.dnsNames]
  Issuing: True, Reason: RequestChanged, Message: Fields on existing CertificateRequest resource not up to date: [spec.dnsNames]
DNS Names:
- example.mydomain.com
Events:
  Type    Reason     Age   From                                       Message
  ----    ------     ----  ----                                       -------
  Normal  Issuing    16m   cert-manager-certificates-trigger          Issuing certificate as Secret does not exist
...
Challenges:
- Name: my-certificate-1234-5678, Type: DNS-01, Token: 1234567890, Key: 1234567890, State: pending, Reason: , Processing: true, Presented: false

Once issued, the certificate is stored in the my-certificate-tls secret. Inspect it with:

$ cmctl inspect secret my-certificate-tls -n example
Valid for:
	DNS Names: 
		- example.mydomain.com
	URIs: <none>
	IP Addresses: <none>
	Email Addresses: <none>
	Usages: 
		- digital signature
		- key encipherment
		- server auth
...

Continue to Part II — ExternalDNS.

Keywords : Kubernetes, Google Kubernetes Engine, GKE, cert-manager, SSL, Let's Encrypt