Skip to main content

App GKE — Lab Guide

📖 Configuration Guide

Overview

App GKE is the foundation deployment engine for all GKE Autopilot application modules in this repository. It is a highly parameterized Terraform child module that provisions a production-ready Kubernetes workload on GKE Autopilot, including Cloud SQL (PostgreSQL or MySQL), Cloud Filestore NFS, GCS storage, Secret Manager, Workload Identity, Cloud Build CI/CD, Cloud Monitoring, and optional Cloud Armor WAF.

Application modules such as Django GKE, Ghost GKE, and Wordpress GKE call this module and pass application-specific configuration. You can also call App GKE directly to deploy a generic containerised workload on GKE Autopilot.

Estimated time: 2–3 hours

What the Module Automates

  • GKE namespace, Deployment (or StatefulSet), Service, and HPA creation
  • Cloud Build image build and push to Artifact Registry (custom mode) or image mirroring (prebuilt mode)
  • Cloud SQL database and user provisioning, with Cloud SQL Auth Proxy sidecar
  • Secret Manager secrets for database credentials and application settings
  • Cloud Filestore NFS provisioning and GCS Fuse volume configuration
  • Workload Identity binding between the Kubernetes service account and GCP service account
  • Kubernetes Jobs for initialisation tasks (e.g. db-init) and CronJobs for scheduled tasks
  • Cloud Monitoring uptime checks and alert policies
  • Optional: Cloud Armor WAF + Ingress, Identity-Aware Proxy, VPC Service Controls, CI/CD trigger, static IP reservation

What You Do Manually

  • Note the deployment outputs (external IP, namespace, etc.) from the RAD UI deployment panel
  • Configure kubectl access to the GKE cluster
  • Inspect running pods and Kubernetes resources
  • Retrieve credentials from Secret Manager and access the application
  • Query application logs in Cloud Logging
  • View GKE dashboards, pod metrics, and uptime checks in Cloud Monitoring
  • Scale the Deployment and observe HPA behaviour

CLI and REST API Overview

Set these shell variables at the start of each session — all gcloud and REST examples below reference them.

export PROJECT="your-gcp-project-id"   # set this first — your GCP project ID
export REGION="us-central1" # the region you deployed into
export TOKEN=$(gcloud auth print-access-token)

# Discover the GKE cluster name (auto-created by Services_GCP)
export CLUSTER=$(gcloud container clusters list \
--project=${PROJECT} \
--format='value(name)' \
--limit=1)

# Configure kubectl
gcloud container clusters get-credentials ${CLUSTER} \
--region=${REGION} \
--project=${PROJECT}

# Discover the namespace (pattern: app<appname><tenant><deploymentid>)
export NAMESPACE=$(kubectl get namespaces --no-headers \
-o custom-columns=":metadata.name" | grep "^appgkeapp" | head -1)
# Replace "gkeapp" with the actual application_name you used

# Discover the external IP
export EXTERNAL_IP=$(kubectl get svc -n ${NAMESPACE} \
-o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}')

# Discover the database password secret
export DB_SECRET=$(gcloud secrets list \
--project=${PROJECT} \
--filter="name~gkeapp" \
--format="value(name)" \
--limit=1)

Prerequisites

RequirementDetail
Access to the RAD UIPermission to deploy modules in the target GCP project
gcloud CLIAuthenticated (gcloud auth login)
kubectlInstalled and on PATH
GCP project with billingActive billing account linked
Services GCP module deployedProvides VPC, GKE cluster, Cloud SQL, Artifact Registry, and Filestore
Service accountroles/owner granted in the target project

The Services GCP module must be deployed and healthy before running this module. It supplies the shared VPC, GKE Autopilot cluster, Cloud SQL instance, Artifact Registry repository, and Filestore NFS server that App GKE discovers automatically at deploy time.


Phase 1 — Deploy Infrastructure [AUTOMATED]

Step 1.1 — Configure Variables

Variables are configured in the RAD UI form before deploying. The table below lists the most commonly configured variables.

VariableDefaultDescription
project_id(required)GCP project ID to deploy into
deployment_id(auto-generated)Short alphanumeric suffix appended to all resource names
regionus-central1GCP region for resource deployment
tenant_deployment_iddemoUnique tenant/environment identifier used in resource naming
application_namegkeappBase name for the Kubernetes workload and associated resources
application_version1.0.0Container image version tag
container_image_sourcecustomcustom to build via Cloud Build; prebuilt to deploy an existing image URI
container_image""Full image URI — required when container_image_source = "prebuilt"
deploy_applicationtrueSet to false to provision infrastructure only without deploying the workload
min_instance_count1Minimum pod replicas (HPA minReplicas)
max_instance_count3Maximum pod replicas (HPA maxReplicas)
container_resources{ cpu_limit = "1000m", memory_limit = "512Mi" }CPU and memory limits per pod
workload_typeDeploymentDeployment for stateless apps; StatefulSet for stateful apps
service_typeLoadBalancerLoadBalancer, ClusterIP, or NodePort
gke_cluster_name""Target cluster name — leave empty to auto-discover from Services GCP
database_typePOSTGRESCloud SQL engine: POSTGRES, MYSQL, or NONE
application_database_namegkeappdbDatabase name created in Cloud SQL
application_database_usergkeappuserDatabase user name
enable_redistrueInjects REDIS_HOST/REDIS_PORT environment variables into pods
enable_nfstrueProvisions Cloud Filestore NFS and mounts it at /mnt/nfs
enable_cloud_armorfalseAttaches a Cloud Armor WAF policy to the GKE Ingress backend
enable_iapfalseEnables Identity-Aware Proxy authentication
reserve_static_iptrueProvisions a global static external IP for the load balancer

Step 1.2 — Initiate Deployment

Deployment is initiated from the RAD UI. Fill in the variable form and click Deploy.

Expected resource provisioning times:

ResourceTypical duration
Kubernetes namespace and RBAC1–2 minutes
Cloud Build image build (custom mode)5–10 minutes
Cloud SQL database and user2–5 minutes
Secret Manager secrets< 1 minute
NFS setup job2–4 minutes
GKE Deployment rollout3–8 minutes
Uptime check and alert policies1–2 minutes
Total15–30 minutes

Note: On the very first deploy of a new inline GKE cluster, the kubernetes_ready output will be false because the cluster endpoint is not yet readable. A second deploy may be required to complete Kubernetes resource deployment.

Step 1.3 — Record Outputs

After deployment completes, the following outputs are available in the RAD UI deployment panel.

OutputDescription
kubernetes_readytrue when all Kubernetes workload resources have been deployed successfully
service_urlService URL (external URL if LoadBalancer with static IP, otherwise internal)
service_external_ipExternal LoadBalancer IP (if static IP is reserved)
database_instance_nameCloud SQL instance name
database_password_secretSecret Manager secret name for the database password
storage_bucketsGCS bucket names created for the application
container_registryArtifact Registry repository name
deployment_idUnique deployment identifier
resource_prefixResource naming prefix applied to all resources
initialization_jobsNames of Kubernetes initialisation jobs
deployment_summaryHuman-readable summary of the full deployment

Set shell variables for use in later steps using discovery commands:

export PROJECT="your-gcp-project-id"   # set this first — your GCP project ID
export REGION="us-central1" # the region you deployed into
export TOKEN=$(gcloud auth print-access-token)

# Discover the GKE cluster
export CLUSTER=$(gcloud container clusters list \
--project=${PROJECT} \
--format="value(name)" \
--limit=1)

# Configure kubectl
gcloud container clusters get-credentials ${CLUSTER} \
--region=${REGION} \
--project=${PROJECT}

# Discover the namespace (pattern: app<appname><tenant><deploymentid>)
export NAMESPACE=$(kubectl get namespaces --no-headers \
-o custom-columns=":metadata.name" | grep "^appgkeapp" | head -1)
# Replace "gkeapp" with the actual application_name you configured

# Discover the external IP
export EXTERNAL_IP=$(kubectl get svc -n ${NAMESPACE} \
-o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}')

# Discover the database password secret
export DB_SECRET=$(gcloud secrets list \
--project=${PROJECT} \
--filter="name~gkeapp" \
--format="value(name)" \
--limit=1)

Phase 2 — Configure kubectl Access [MANUAL]

Step 2.1 — Retrieve Cluster Credentials

gcloud container clusters get-credentials \
$(gcloud container clusters list \
--project=${PROJECT} \
--format='value(name)' \
--limit=1) \
--region=${REGION} \
--project=${PROJECT}

Verify the context is active:

kubectl config current-context

Expected result: A context line referencing your project and cluster, e.g. gke_my-gcp-project_us-central1_gke-cluster-1.

REST API equivalent:

curl -s -H "Authorization: Bearer ${TOKEN}" \
"https://container.googleapis.com/v1/projects/${PROJECT}/locations/${REGION}/clusters" \
| jq '.clusters[] | {name, status, endpoint}'

Step 2.2 — Identify the Namespace

The namespace follows the pattern app<application_name><tenant_deployment_id><deployment_id>:

kubectl get namespaces | grep gkeapp

Set the variable:

export NAMESPACE=$(kubectl get namespaces --no-headers \
-o custom-columns=":metadata.name" | grep "^appgkeapp" | head -1)
echo "Namespace: ${NAMESPACE}"

Step 2.3 — Verify Pods Are Running

kubectl get pods -n ${NAMESPACE}

Expected result: One or more pods with status Running:

NAME                       READY   STATUS    RESTARTS   AGE
gkeapp-7d9f8b6c4-xq2pj 2/2 Running 0 5m

The 2/2 indicates the application container and the Cloud SQL Auth Proxy sidecar are both running.

Step 2.4 — Check the Service External IP

kubectl get service -n ${NAMESPACE}

Expected result: A LoadBalancer service with an EXTERNAL-IP assigned:

NAME      TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
gkeapp LoadBalancer 10.96.100.50 34.123.45.67 80:31234/TCP 5m

Record the external IP — this is your application URL.

gcloud equivalent:

gcloud compute forwarding-rules list --project=${PROJECT} --filter="region:${REGION}"

Phase 3 — Access the Application [MANUAL]

Step 3.1 — Open the Application URL

export APP_IP=$(kubectl get service -n ${NAMESPACE} \
-o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}')
echo "Application URL: http://${APP_IP}"

Open http://<EXTERNAL-IP> in your browser.

Expected result: The deployed container responds over HTTP/HTTPS.

Step 3.2 — Retrieve Credentials from Secret Manager

List secrets created for this deployment:

gcloud secrets list \
--project=${PROJECT} \
--filter="name~gkeapp" \
--format="table(name)"

Access the database password secret:

gcloud secrets versions access latest \
--secret="${DB_SECRET}" \
--project=${PROJECT}

REST API equivalent:

curl -s -H "Authorization: Bearer ${TOKEN}" \
"https://secretmanager.googleapis.com/v1/projects/${PROJECT}/secrets/${DB_SECRET}/versions/latest:access" \
| jq -r '.payload.data' | base64 --decode

Phase 4 — Database and Migrations [MANUAL]

Step 4.1 — Inspect the Cloud SQL Instance

gcloud sql instances list --project=${PROJECT}

REST API equivalent:

curl -s -H "Authorization: Bearer ${TOKEN}" \
"https://sqladmin.googleapis.com/v1/projects/${PROJECT}/instances" \
| jq '.items[] | {name, state, databaseVersion, region}'

Step 4.2 — Verify Initialisation Job Completed

kubectl get jobs -n ${NAMESPACE}

Expected result: The db-init job (if enabled) shows COMPLETIONS: 1/1:

NAME      COMPLETIONS   DURATION   AGE
db-init 1/1 45s 10m

View init job logs:

kubectl logs job/db-init -n ${NAMESPACE}

Step 4.3 — List Databases in Cloud SQL

gcloud sql databases list \
--instance=$(gcloud sql instances list --project=${PROJECT} --format='value(name)' --limit=1) \
--project=${PROJECT}

Expected result: Your application database appears in the list.


Phase 5 — Inspect Storage [MANUAL]

Step 5.1 — Explore the GCS Bucket

gcloud storage ls --project=${PROJECT} | grep gkeapp

List bucket contents:

gcloud storage ls gs://<bucket-name>/

Step 5.2 — Verify GCS Fuse Mount (if configured)

POD=$(kubectl get pods -n ${NAMESPACE} -o jsonpath='{.items[0].metadata.name}')
kubectl exec -n ${NAMESPACE} ${POD} -- df -h | grep fuse

Expected result: A fuse filesystem entry appears, mounted at the configured GCS volume path.

Step 5.3 — Check the NFS Mount (if configured)

kubectl exec -n ${NAMESPACE} ${POD} -- df -h /mnt/nfs

Expected result: An NFS filesystem appears, mounted from the Filestore instance IP.


Phase 6 — Explore Cloud Logging [MANUAL]

Step 6.1 — View Logs in the Console

Navigate to Logging > Logs Explorer in the Cloud Console.

Step 6.2 — Query Application Logs

All application pod logs:

resource.type="k8s_container"
resource.labels.project_id="${PROJECT}"
resource.labels.cluster_name="${CLUSTER}"
resource.labels.namespace_name="${NAMESPACE}"

Error logs only:

resource.type="k8s_container"
resource.labels.namespace_name="${NAMESPACE}"
severity>=ERROR

Cloud SQL Auth Proxy sidecar logs:

resource.type="k8s_container"
resource.labels.namespace_name="${NAMESPACE}"
resource.labels.container_name="cloud-sql-proxy"

gcloud equivalent:

gcloud logging read \
'resource.type="k8s_container" AND resource.labels.namespace_name="'${NAMESPACE}'"' \
--project=${PROJECT} \
--limit=50 \
--format="table(timestamp,severity,textPayload)"

REST API equivalent:

curl -s -X POST -H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
"https://logging.googleapis.com/v2/entries:list" \
-d '{
"resourceNames": ["projects/'${PROJECT}'"],
"filter": "resource.type=\"k8s_container\" AND resource.labels.namespace_name=\"'${NAMESPACE}'\"",
"orderBy": "timestamp desc",
"pageSize": 20
}' | jq '.entries[] | {timestamp, severity, textPayload}'

Phase 7 — Explore Cloud Monitoring [MANUAL]

Step 7.1 — View the GKE Dashboard

Navigate to Monitoring > Dashboards and select the GKE dashboard for your cluster. You will see:

  • Node CPU and memory utilisation
  • Pod count and restart events
  • Network ingress/egress

Step 7.2 — View Pod CPU and Memory Metrics

Pod CPU utilisation:

fetch k8s_container
| metric 'kubernetes.io/container/cpu/core_usage_time'
| filter (resource.namespace_name == '${NAMESPACE}')
| align rate(1m)
| every 1m

Pod memory usage:

fetch k8s_container
| metric 'kubernetes.io/container/memory/used_bytes'
| filter (resource.namespace_name == '${NAMESPACE}')
| every 1m

Step 7.3 — Check Uptime Checks

Navigate to Monitoring > Uptime checks to view the uptime check configured by the deployment.

Expected result: The check shows green/passing status against your application's external IP.

REST API equivalent:

curl -s -H "Authorization: Bearer ${TOKEN}" \
"https://monitoring.googleapis.com/v3/projects/${PROJECT}/uptimeCheckConfigs" \
| jq '.uptimeCheckConfigs[] | {displayName, httpCheck, period}'

Phase 8 — Scaling and Updates [MANUAL]

Step 8.1 — Manually Scale the Deployment

Scale up to 3 replicas:

kubectl scale deployment -n ${NAMESPACE} --all --replicas=3

Watch pods come up:

kubectl get pods -n ${NAMESPACE} -w

Expected result: Three pods reach Running status within 1–2 minutes.

Scale back down:

kubectl scale deployment -n ${NAMESPACE} --all --replicas=1

Step 8.2 — Trigger a Rolling Update

To trigger a rolling update, return to the RAD UI, navigate to your deployment, update the application_version variable, and click Update. Monitor the rollout with:

kubectl rollout status deployment -n ${NAMESPACE}

Expected result: Output ending with successfully rolled out.

Step 8.3 — View the HPA

kubectl get hpa -n ${NAMESPACE}

Expected result: HPA shows current and desired replica counts along with CPU utilisation percentages.


Phase 9 — Undeploy [AUTOMATED]

When you are finished, return to the RAD UI, navigate to your deployment, and click Undeploy (or Delete) to remove all resources provisioned by this module.

Expected destroy times:

ResourceTypical duration
Kubernetes workloads and namespace2–4 minutes
Secret Manager secrets< 1 minute
GCS buckets1–2 minutes
Cloud SQL database and user1–2 minutes
Static IP reservation< 1 minute
Total8–15 minutes

Resources provisioned by the Services GCP module (VPC, Cloud SQL instance, GKE cluster) are managed separately and must be undeployed via their own RAD UI deployment entry.


Summary

ActionPhaseAutomated
Configure variables in RAD UI1.1Manual
Build and deploy to GKE1.2Automated
Configure kubectl access2Manual
Verify pods and service IP2Manual
Access application and retrieve credentials3Manual
Verify database and initialisation job4Manual
Inspect GCS storage and NFS mount5Manual
Query logs in Cloud Logging6Manual
View GKE metrics and uptime checks7Manual
Scale deployment and trigger rolling update8Manual
Undeploy all module resources9Automated