GCP for DevOps — Project: GKE Autopilot CI/CD Pipeline
Project 2: GKE Autopilot CI/CD Pipeline
Build a complete CI/CD pipeline that automatically builds container images, stores them in Artifact Registry, and deploys to a GKE Autopilot cluster using Cloud Build and Cloud Deploy.
Architecture
GKE CI/CD Pipeline:
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ GitHub │───▶│ Cloud Build │───▶│ Artifact │
│ Push │ │ Trigger │ │ Registry │
└──────────┘ └──────┬───────┘ └──────┬───────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Cloud │───▶│ GKE │
│ Deploy │ │ Autopilot │
│ │ │ │
│ dev → stg │ │ ┌────────┐ │
│ → prod │ │ │ Pods │ │
└──────────────┘ │ └────────┘ │
└──────────────┘
Step 1: Enable Required APIs
$ gcloud services enable \
container.googleapis.com \
cloudbuild.googleapis.com \
clouddeploy.googleapis.com \
artifactregistry.googleapis.com \
--project=my-project
Operation finished successfully.
Step 2: Create Artifact Registry Repository
$ gcloud artifacts repositories create app-images \
--repository-format=docker \
--location=us-central1 \
--description="Application container images"
Created repository [app-images].
Step 3: Create GKE Autopilot Cluster
$ gcloud container clusters create-auto gke-autopilot-dev \
--region=us-central1 \
--project=my-project
Creating cluster gke-autopilot-dev...done.
# Get credentials
$ gcloud container clusters get-credentials gke-autopilot-dev \
--region=us-central1
Fetching cluster endpoint and auth data.
kubeconfig entry generated for gke-autopilot-dev.
Step 4: Sample Application
Create a simple Go web application:
$ mkdir -p sample-app && cd sample-app
// main.go
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
version := os.Getenv("APP_VERSION")
if version == "" {
version = "unknown"
}
fmt.Fprintf(w, "Hello from GKE Autopilot! Version: %s\n", version)
})
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "OK")
})
log.Printf("Starting server on port %s", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
}
# Dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY main.go .
RUN go build -o server main.go
FROM alpine:3.19
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /app/server .
EXPOSE 8080
CMD ["./server"]
Step 5: Kubernetes Manifests
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: sample-app
labels:
app: sample-app
spec:
replicas: 3
selector:
matchLabels:
app: sample-app
template:
metadata:
labels:
app: sample-app
spec:
containers:
- name: sample-app
image: us-central1-docker.pkg.dev/my-project/app-images/sample-app:latest
ports:
- containerPort: 8080
env:
- name: APP_VERSION
value: "1.0.0"
resources:
requests:
cpu: "250m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 3
---
apiVersion: v1
kind: Service
metadata:
name: sample-app
spec:
type: LoadBalancer
selector:
app: sample-app
ports:
- port: 80
targetPort: 8080
Step 6: Cloud Build Configuration
# cloudbuild.yaml
steps:
# Build the container image
- name: 'gcr.io/cloud-builders/docker'
args:
- 'build'
- '-t'
- 'us-central1-docker.pkg.dev/$PROJECT_ID/app-images/sample-app:$SHORT_SHA'
- '-t'
- 'us-central1-docker.pkg.dev/$PROJECT_ID/app-images/sample-app:latest'
- '.'
# Push images to Artifact Registry
- name: 'gcr.io/cloud-builders/docker'
args: ['push', '--all-tags', 'us-central1-docker.pkg.dev/$PROJECT_ID/app-images/sample-app']
# Deploy to GKE
- name: 'gcr.io/cloud-builders/gke-deploy'
args:
- 'run'
- '--filename=k8s/'
- '--image=us-central1-docker.pkg.dev/$PROJECT_ID/app-images/sample-app:$SHORT_SHA'
- '--location=us-central1'
- '--cluster=gke-autopilot-dev'
options:
logging: CLOUD_LOGGING_ONLY
images:
- 'us-central1-docker.pkg.dev/$PROJECT_ID/app-images/sample-app:$SHORT_SHA'
- 'us-central1-docker.pkg.dev/$PROJECT_ID/app-images/sample-app:latest'
Step 7: Create Build Trigger
# Connect your GitHub repository first (via Console)
# Then create a trigger
$ gcloud builds triggers create github \
--name=sample-app-deploy \
--repo-name=sample-app \
--repo-owner=my-github-user \
--branch-pattern="^main$" \
--build-config=cloudbuild.yaml
Created [sample-app-deploy].
# Or trigger a manual build
$ gcloud builds submit \
--config=cloudbuild.yaml \
--project=my-project .
Step 8: Set Up Cloud Deploy (Progressive Delivery)
# clouddeploy.yaml
apiVersion: deploy.cloud.google.com/v1
kind: DeliveryPipeline
metadata:
name: sample-app-pipeline
description: "Sample app deployment pipeline"
serialPipeline:
stages:
- targetId: dev
profiles: [dev]
- targetId: staging
profiles: [staging]
- targetId: production
profiles: [production]
strategy:
canary:
runtimeConfig:
kubernetes:
serviceNetworking:
service: "sample-app"
deployment: "sample-app"
canaryDeployment:
percentages: [25, 50, 75]
---
apiVersion: deploy.cloud.google.com/v1
kind: Target
metadata:
name: dev
description: "Development cluster"
gke:
cluster: projects/my-project/locations/us-central1/clusters/gke-autopilot-dev
# Register the pipeline
$ gcloud deploy apply --file=clouddeploy.yaml --region=us-central1
Waiting for the operation to finish...done.
# Create a release
$ gcloud deploy releases create release-001 \
--delivery-pipeline=sample-app-pipeline \
--region=us-central1 \
--images=sample-app=us-central1-docker.pkg.dev/my-project/app-images/sample-app:abc123
Created release [release-001].
Step 9: Verify Deployment
# Check deployment status
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
sample-app 3/3 3 3 2m
# Check service external IP
$ kubectl get svc sample-app
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
sample-app LoadBalancer 10.0.1.100 35.192.0.50 80:30080/TCP
# Test the application
$ curl http://35.192.0.50
Hello from GKE Autopilot! Version: 1.0.0
Clean Up
# Delete Kubernetes resources
$ kubectl delete -f k8s/
# Delete GKE cluster
$ gcloud container clusters delete gke-autopilot-dev \
--region=us-central1 --quiet
# Delete Artifact Registry
$ gcloud artifacts repositories delete app-images \
--location=us-central1 --quiet
# Delete Cloud Deploy pipeline
$ gcloud deploy delivery-pipelines delete sample-app-pipeline \
--region=us-central1 --force --quiet
Key Learnings
- GKE Autopilot for hands-off node management
- Cloud Build for automated container builds
- Artifact Registry as a private container registry
- Cloud Deploy for progressive delivery with canary stages
- Kubernetes liveness/readiness probes for health checks
Next → Serverless REST API