7/23/2025

Guide: Securing a GKE Gateway with a Custom Domain and Google-Managed SSL


This document provides a complete, step-by-step guide for provisioning a Google Cloud External HTTPS Load Balancer for a GKE cluster using the
Kubernetes Gateway API and securing it with a custom domain and a free, auto-renewing Google-managed SSL certificate.


Architecture Overview

The final architecture will use the GKE Gateway Controller to manage an external load balancer.

The load balancer will use a Certificate Map to handle SSL/TLS for a custom domain,

routing traffic to the appropriate backend services based on rules defined in an HTTPRoute.




Prerequisites

  • A GKE Cluster: You must have a GKE cluster with the Gateway API enabled.


You can use a sample Terraform project to create a private GKE cluster.



  • Backend Kubernetes Service: You need at least one Kubernetes Service (e.g., store-german) running in your cluster that you want to expose.


Configuring a Cloud DNS Zone Using gcloud


1. Enable the Cloud DNS API

gcloud services enable dns.googleapis.com

2. Create a Public DNS Zone

export CLOUD_DNS_ZONE_NAME="global-ub-abc-dev-com"
export DOMAIN_NAME="global-ub-abc-dev.com"
export PROJECT_ID="abc-project-386112"

gcloud dns managed-zones create "$CLOUD_DNS_ZONE_NAME" \
  --dns-name="${DOMAIN_NAME}." \
  --description="DNS zone for ${DOMAIN_NAME}" \
  --visibility="public" \
  --project="$PROJECT_ID"

3. Get Nameservers to Update Domain Registrar

gcloud dns managed-zones describe "$CLOUD_DNS_ZONE_NAME" --project="$PROJECT_ID" \
  --format="value(nameServers)"

Update your domain registrar (e.g., GoDaddy, Namecheap) with the returned nameservers.

4. Add DNS Records (CNAME or A)

gcloud dns record-sets transaction start --zone="$CLOUD_DNS_ZONE_NAME" --project="$PROJECT_ID"

# Example: CNAME for certificate verification
gcloud dns record-sets transaction add "CNAME_VALUE" \
  --name="_acme-challenge.${DOMAIN_NAME}." \
  --ttl="300" \
  --type="CNAME" \
  --zone="$CLOUD_DNS_ZONE_NAME" \
  --project="$PROJECT_ID"

# Example: A record for load balancer
gcloud dns record-sets transaction add "34.149.207.45" \
  --name="${DOMAIN_NAME}." \
  --ttl="300" \
  --type="A" \
  --zone="$CLOUD_DNS_ZONE_NAME" \
  --project="$PROJECT_ID"

gcloud dns record-sets transaction execute --zone="$CLOUD_DNS_ZONE_NAME" --project="$PROJECT_ID"



Step 1: Set Up Environment Variables

# Core Project Configuration
export PROJECT_ID="abc-project-386112"
export REGION="europe-north1"

# Domain and DNS Configuration
export DOMAIN_NAME="global-ub-abc-dev.com"
export CLOUD_DNS_ZONE_NAME="global-ub-abc-dev-com"

# Resource Names
export CERTIFICATE_MAP_NAME="cert-map-global-ub-abc-dev-com"
export CERTIFICATE_NAME="cert-global-ub-abc-dev-com"
export DOMAIN_AUTHORIZATION_NAME="dns-auth-global-ub-abc-dev"
export CERTIFICATE_MAP_ENTRY_NAME="cert-map-entry-global-ub-abc-dev"


Step 2: Create Certificate Manager Resources

Create DNS Authorization

gcloud certificate-manager dns-authorizations create $DOMAIN_AUTHORIZATION_NAME \
    --domain="$DOMAIN_NAME" \
    --project $PROJECT_ID

Get CNAME Record for Verification

export CNAME_DATA=$(gcloud certificate-manager dns-authorizations describe $DOMAIN_AUTHORIZATION_NAME --project $PROJECT_ID --format="value(dnsResourceRecord.data)")
echo "CNAME data to add to DNS: ${CNAME_DATA}"

Update Cloud DNS with the CNAME Record

gcloud dns record-sets transaction start --zone="$CLOUD_DNS_ZONE_NAME" --project $PROJECT_ID

gcloud dns record-sets transaction add "$CNAME_DATA" \
    --name="_acme-challenge.${DOMAIN_NAME}." \
    --ttl="30" \
    --type="CNAME" \
    --zone="$CLOUD_DNS_ZONE_NAME" \
    --project $PROJECT_ID

gcloud dns record-sets transaction execute --zone="$CLOUD_DNS_ZONE_NAME" --project $PROJECT_ID

Create the Google-Managed Certificate

gcloud certificate-manager certificates create $CERTIFICATE_NAME \
    --domains="$DOMAIN_NAME" \
    --dns-authorizations="$DOMAIN_AUTHORIZATION_NAME" \
    --project $PROJECT_ID

Monitor the status:

gcloud certificate-manager certificates describe $CERTIFICATE_NAME

Ensure status is ACTIVE before proceeding.

Create Certificate Map and Entry

# Create the certificate map
gcloud certificate-manager maps create $CERTIFICATE_MAP_NAME --project $PROJECT_ID

# Create the certificate map entry
gcloud certificate-manager maps entries create $CERTIFICATE_MAP_ENTRY_NAME \
    --map="$CERTIFICATE_MAP_NAME" \
    --certificates="$CERTIFICATE_NAME" \
    --hostname="$DOMAIN_NAME" \
    --project $PROJECT_ID


Step 3: Provision the Load Balancer using Gateway API


gateway.yaml

apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
  name: external-http
  annotations:
    networking.gke.io/certmap: cert-map-global-ub-abc-dev-com
spec:
  gatewayClassName: gke-l7-global-external-managed
  listeners:
  - name: https
    protocol: HTTPS
    port: 443
  - name: http
    protocol: HTTP
    port: 80



http-route.yaml

apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
  name: store-external
spec:
  parentRefs:
  - kind: Gateway
    name: external-http
  hostnames:
  - "global-ub-abc-dev.com"
  rules:
  - matches:
    - path:
        value: /de
    backendRefs:
    - name: store-german
      port: 8080



Apply resources:

kubectl apply -f gateway.yaml
kubectl apply -f http-route.yaml


Step 4: Retrieve the Load Balancer’s IP Address

kubectl describe gateway external-http

Check the Status section:

Addresses:
  - Type: IPAddress
    Value: 34.149.207.45

Store it in a variable:

export LOAD_BALANCER_IP=$(kubectl describe gateway external-http -o=jsonpath='{.status.addresses[0].value}')
echo "Load Balancer IP: ${LOAD_BALANCER_IP}"


Step 5: Update DNS with an A Record

gcloud dns record-sets transaction start --zone="$CLOUD_DNS_ZONE_NAME" --project $PROJECT_ID

gcloud dns record-sets transaction add "$LOAD_BALANCER_IP" \
    --name="$DOMAIN_NAME." \
    --ttl="300" \
    --type="A" \
    --zone="$CLOUD_DNS_ZONE_NAME" \
    --project $PROJECT_ID

gcloud dns record-sets transaction execute --zone="$CLOUD_DNS_ZONE_NAME" --project $PROJECT_ID



Step 6: Update Target HTTPS Proxy (if needed)

In some cases, especially with pre-provisioned HTTP proxies, you may need to explicitly associate your certificate map with the target HTTPS proxy:

List HTTPS Proxies

gcloud compute target-https-proxies list

Update the Proxy with Certificate Map

gcloud compute target-https-proxies update gkegw1-e3is-default-external-http-jy9mc97xb5yh \
  --certificate-map="$CERTIFICATE_MAP_NAME" \
  --global

Verify the Configuration

gcloud compute target-https-proxies describe gkegw1-e3is-default-external-http-jy9mc97xb5yh \
  --global \
  --format="get(sslCertificates)"


Step 7: Verification

curl --verbose https://${DOMAIN_NAME}/de

Expect a 200 OK response and a valid Google-managed TLS certificate.

You can also confirm in the Google Cloud Console under Network Services > Load Balancing.


No comments:

Post a Comment