GCP for DevOps — Practice Projects Exercises
Practice Exercises — Practice Projects
Exercise 1: Extend the Production VPC
Add a third subnet for a GKE cluster with secondary IP ranges for Pods and Services.
- Add a
gke-subnet(10.0.3.0/24) to the VPC module - Configure secondary ranges:
pods(10.4.0.0/14),services(10.8.0.0/20) - Add a firewall rule allowing GKE master access (port 443) from the GKE control plane CIDR
Show Answer
# Add to modules/vpc/main.tf
resource "google_compute_subnetwork" "gke" {
name = "${var.vpc_name}-gke"
ip_cidr_range = "10.0.3.0/24"
region = var.region
network = google_compute_network.vpc.id
secondary_ip_range {
range_name = "pods"
ip_cidr_range = "10.4.0.0/14"
}
secondary_ip_range {
range_name = "services"
ip_cidr_range = "10.8.0.0/20"
}
private_ip_google_access = true
}
resource "google_compute_firewall" "allow_gke_master" {
name = "${var.vpc_name}-allow-gke-master"
network = google_compute_network.vpc.id
allow {
protocol = "tcp"
ports = ["443", "10250"]
}
source_ranges = ["172.16.0.0/28"] # GKE master CIDR
target_tags = ["gke-node"]
}
$ terraform plan
Plan: 2 to add, 0 to change, 0 to destroy.
$ terraform apply -auto-approve
Apply complete! Resources: 2 added.
Exercise 2: Add Health Check Endpoint to the CI/CD App
Enhance the GKE Autopilot application with a /ready endpoint that checks database connectivity.
- Add a
/readyendpoint that returns 503 if the database is unreachable - Update the Kubernetes deployment to use
/readyfor readinessProbe - Keep
/healthas the livenessProbe (always returns 200)
Show Answer
// Add to main.go
http.HandleFunc("/ready", func(w http.ResponseWriter, r *http.Request) {
// Check database connectivity
dbHost := os.Getenv("DB_HOST")
if dbHost == "" {
// No DB configured, just report ready
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "READY")
return
}
conn, err := net.DialTimeout("tcp", dbHost+":5432", 2*time.Second)
if err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
fmt.Fprintf(w, "NOT READY: %v", err)
return
}
conn.Close()
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "READY")
})
# Update k8s/deployment.yaml probes
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 3
periodSeconds: 5
failureThreshold: 3
Exercise 3: Add Authentication to the Serverless API
Secure the REST API with API key authentication via API Gateway.
- Add API key validation to the OpenAPI spec
- Create an API key in the Console
- Test that requests without the API key are rejected
Show Answer
# Update openapi-spec.yaml — add security definition
swagger: '2.0'
info:
title: Items API
version: '1.0'
schemes:
- https
produces:
- application/json
securityDefinitions:
api_key:
type: apiKey
name: x-api-key
in: header
security:
- api_key: []
paths:
/api/items:
get:
summary: List all items
operationId: listItems
x-google-backend:
address: https://items-api-abc123-uc.a.run.app
security:
- api_key: []
responses:
'200':
description: A list of items
# Create a new API config with the updated spec
$ gcloud api-gateway api-configs create items-config-v2 \
--api=items-api \
--openapi-spec=openapi-spec.yaml
# Update the gateway
$ gcloud api-gateway gateways update items-gateway \
--api=items-api \
--api-config=items-config-v2 \
--location=us-central1
# Test without API key (should fail)
$ curl -i https://items-gateway-abc123.uc.gateway.dev/api/items
HTTP/2 401
{"message": "UNAUTHENTICATED: Method doesn't allow unregistered callers"}
# Test with API key (should work)
$ curl -H "x-api-key: YOUR_API_KEY" \
https://items-gateway-abc123.uc.gateway.dev/api/items
[{"id": "abc123", "name": "Widget"}]
Exercise 4: Add Terraform Modules to the 3-Tier Project
Refactor the 3-tier architecture into reusable Terraform modules.
- Create a
modules/cloudsqlmodule for the database layer - Create a
modules/migmodule for the compute layer - Use both modules from the root
main.tf
Show Answer
# modules/cloudsql/variables.tf
variable "instance_name" { type = string }
variable "region" { type = string }
variable "network_id" { type = string }
variable "db_name" { type = string }
variable "db_user" { type = string }
variable "db_password" { type = string; sensitive = true }
variable "tier" { type = string; default = "db-custom-2-4096" }
# modules/cloudsql/main.tf
resource "google_sql_database_instance" "main" {
name = var.instance_name
database_version = "POSTGRES_15"
region = var.region
settings {
tier = var.tier
ip_configuration {
ipv4_enabled = false
private_network = var.network_id
}
backup_configuration {
enabled = true
point_in_time_recovery_enabled = true
}
}
deletion_protection = false
}
resource "google_sql_database" "db" {
name = var.db_name
instance = google_sql_database_instance.main.name
}
resource "google_sql_user" "user" {
name = var.db_user
instance = google_sql_database_instance.main.name
password = var.db_password
}
# modules/cloudsql/outputs.tf
output "private_ip" {
value = google_sql_database_instance.main.private_ip_address
}
# Root main.tf using modules
module "database" {
source = "./modules/cloudsql"
instance_name = "app-db"
region = var.region
network_id = google_compute_network.main.id
db_name = "appdb"
db_user = "appuser"
db_password = var.db_password
}
Exercise 5: Add SLO Burn Rate Alerts to the Monitoring System
Create multi-window burn rate alerts for a 99.9% availability SLO.
- Create a fast burn alert (14.4x burn rate over 1 hour, confirmed over 5 minutes)
- Create a slow burn alert (3x burn rate over 1 day, confirmed over 6 hours)
- Route fast burn to PagerDuty, slow burn to Slack
Show Answer
# Fast burn alert (pages on-call immediately)
$ gcloud alpha monitoring policies create \
--display-name="SLO Fast Burn - Page" \
--condition-display-name="14.4x burn rate (1h window)" \
--condition-filter='select_slo_burn_rate("projects/my-project/services/items-api-service/serviceLevelObjectives/xyz", "60m")' \
--condition-threshold-value=14.4 \
--condition-threshold-comparison=COMPARISON_GT \
--condition-threshold-duration=300s \
--notification-channels="projects/my-project/notificationChannels/pagerduty-channel" \
--documentation='FAST BURN: Error budget depleting rapidly.
At this rate, monthly budget exhausts in ~3 days.
Immediate investigation required.'
# Slow burn alert (notifies team in Slack)
$ gcloud alpha monitoring policies create \
--display-name="SLO Slow Burn - Notify" \
--condition-display-name="3x burn rate (1d window)" \
--condition-filter='select_slo_burn_rate("projects/my-project/services/items-api-service/serviceLevelObjectives/xyz", "1440m")' \
--condition-threshold-value=3 \
--condition-threshold-comparison=COMPARISON_GT \
--condition-threshold-duration=21600s \
--notification-channels="projects/my-project/notificationChannels/slack-channel" \
--documentation='SLOW BURN: Error budget depleting steadily.
At this rate, monthly budget exhausts in ~10 days.
Schedule investigation this sprint.'
Burn rate reference table:
| Window | Burn Rate | Budget Consumed | Time to Exhaust |
|---|---|---|---|
| 1 hour | 14.4x | 2% | ~3 days |
| 6 hours | 6x | 5% | ~5 days |
| 1 day | 3x | 10% | ~10 days |
| 3 days | 1x | 10% | ~30 days |
Cleanup
# Destroy all Terraform resources from projects 1 and 4
$ cd terraform-3tier && terraform destroy -auto-approve
$ cd ../gcp-production-vpc && terraform destroy -auto-approve
# Delete GKE cluster from project 2
$ gcloud container clusters delete gke-autopilot-dev --region=us-central1 --quiet
# Delete Cloud Run and API Gateway from project 3
$ gcloud run services delete items-api --region=us-central1 --quiet
$ gcloud api-gateway gateways delete items-gateway --location=us-central1 --quiet
# Delete monitoring resources from project 5
$ gcloud monitoring dashboards delete DASHBOARD_ID
$ gcloud alpha monitoring policies delete POLICY_ID
Next Chapter → Next Steps