GCP for DevOps — Serverless Practice Exercises
Serverless Practice Exercises
Exercise 1 — Deploy a Cloud Function
Deploy an HTTP Cloud Function that returns a JSON greeting.
Steps:
- Create a function directory with
main.pyandrequirements.txt - Write a function that accepts a
namequery parameter - Deploy as a gen2 HTTP function
- Test with
curl
Show Answer
# main.py
import functions_framework
from flask import jsonify
@functions_framework.http
def greet(request):
name = request.args.get("name", "World")
return jsonify({"message": f"Hello, {name}!", "service": "cloud-functions"})
# requirements.txt
functions-framework==3.*
flask==3.*
$ gcloud functions deploy greet \
--gen2 \
--runtime=python312 \
--region=us-central1 \
--source=. \
--entry-point=greet \
--trigger-http \
--allow-unauthenticated
$ curl "https://us-central1-PROJECT_ID.cloudfunctions.net/greet?name=DevOps"
# {"message": "Hello, DevOps!", "service": "cloud-functions"}
Exercise 2 — Cloud Run with Traffic Splitting
Deploy two revisions of a Cloud Run service and split traffic between them.
Steps:
- Deploy a Cloud Run service returning “v1”
- Update the service to return “v2”
- Split traffic 80/20 between v2 and v1
- Verify with multiple requests
Show Answer
# Deploy v1
$ gcloud run deploy traffic-demo \
--image=us-docker.pkg.dev/cloudrun/container/hello \
--region=us-central1 \
--tag=v1 \
--allow-unauthenticated
# Note the revision name (e.g., traffic-demo-00001-abc)
# Deploy v2 with an update
$ gcloud run deploy traffic-demo \
--image=us-docker.pkg.dev/cloudrun/container/hello \
--region=us-central1 \
--tag=v2 \
--set-env-vars=VERSION=v2
# Split traffic 80/20
$ gcloud run services update-traffic traffic-demo \
--region=us-central1 \
--to-revisions=traffic-demo-00002-xyz=80,traffic-demo-00001-abc=20
# Verify traffic split
$ gcloud run services describe traffic-demo \
--region=us-central1 \
--format="yaml(status.traffic)"
Exercise 3 — API Gateway with Rate Limiting
Create an API Gateway that proxies requests to a Cloud Function with API key authentication.
Steps:
- Deploy a backend Cloud Function
- Create an OpenAPI spec with API key security
- Create an API Gateway
- Test with and without an API key
Show Answer
# openapi-spec.yaml
swagger: "2.0"
info:
title: My Serverless API
version: "1.0"
schemes:
- https
produces:
- application/json
security:
- api_key: []
securityDefinitions:
api_key:
type: apiKey
name: x-api-key
in: header
paths:
/greet:
get:
operationId: greet
x-google-backend:
address: https://us-central1-PROJECT_ID.cloudfunctions.net/greet
responses:
"200":
description: OK
$ gcloud api-gateway apis create my-api --project=PROJECT_ID
$ gcloud api-gateway api-configs create my-config \
--api=my-api \
--openapi-spec=openapi-spec.yaml \
--project=PROJECT_ID
$ gcloud api-gateway gateways create my-gateway \
--api=my-api \
--api-config=my-config \
--location=us-central1 \
--project=PROJECT_ID
# Test without key (should fail)
$ curl "https://my-gateway-abc123.uc.gateway.dev/greet"
# 403 Forbidden
# Test with key
$ curl -H "x-api-key: YOUR_API_KEY" \
"https://my-gateway-abc123.uc.gateway.dev/greet?name=DevOps"
# {"message": "Hello, DevOps!"}
Exercise 4 — Firestore CRUD with Cloud Function
Create a Cloud Function that performs CRUD operations on Firestore.
Steps:
- Create a Firestore database
- Deploy a Cloud Function that handles GET/POST/DELETE
- Test all operations
Show Answer
# main.py
import functions_framework
from google.cloud import firestore
from flask import jsonify, request
db = firestore.Client()
COLLECTION = "tasks"
@functions_framework.http
def tasks_api(req):
if req.method == "GET":
docs = db.collection(COLLECTION).stream()
tasks = [{**doc.to_dict(), "id": doc.id} for doc in docs]
return jsonify(tasks)
elif req.method == "POST":
data = req.get_json()
doc_ref = db.collection(COLLECTION).add({
"title": data["title"],
"done": False,
"created_at": firestore.SERVER_TIMESTAMP,
})
return jsonify({"id": doc_ref[1].id, "status": "created"}), 201
elif req.method == "DELETE":
task_id = req.args.get("id")
db.collection(COLLECTION).document(task_id).delete()
return jsonify({"status": "deleted"})
return jsonify({"error": "Method not allowed"}), 405
$ gcloud firestore databases create --location=us-central1 --type=firestore-native
$ gcloud functions deploy tasks-api \
--gen2 \
--runtime=python312 \
--region=us-central1 \
--entry-point=tasks_api \
--trigger-http \
--allow-unauthenticated
# Create a task
$ curl -X POST -H "Content-Type: application/json" \
-d '{"title":"Learn Firestore"}' \
"https://us-central1-PROJECT_ID.cloudfunctions.net/tasks-api"
# List tasks
$ curl "https://us-central1-PROJECT_ID.cloudfunctions.net/tasks-api"
# Delete a task
$ curl -X DELETE "https://us-central1-PROJECT_ID.cloudfunctions.net/tasks-api?id=TASK_ID"
Exercise 5 — Eventarc to Workflow Pipeline
Build an event-driven pipeline: Cloud Storage upload triggers Eventarc, which starts a Workflow that calls a Cloud Run service.
Steps:
- Deploy a Cloud Run processing service
- Create a Workflow that calls the service
- Create an Eventarc trigger for GCS uploads
- Upload a file and verify the pipeline
Show Answer
# workflow.yaml
main:
params: [event]
steps:
- extract_info:
assign:
- bucket: ${event.data.bucket}
- filename: ${event.data.name}
- size: ${event.data.size}
- log_event:
call: sys.log
args:
text: ${"Processing file " + filename + " from " + bucket}
severity: "INFO"
- process_file:
call: http.post
args:
url: https://my-processor-abc123.a.run.app/process
body:
bucket: ${bucket}
filename: ${filename}
auth:
type: OIDC
result: process_result
- return_result:
return:
file: ${filename}
status: ${process_result.body.status}
# Deploy workflow
$ gcloud workflows deploy file-pipeline \
--location=us-central1 \
--source=workflow.yaml \
--service-account=workflow-sa@PROJECT_ID.iam.gserviceaccount.com
# Create Eventarc trigger
$ gcloud eventarc triggers create gcs-to-workflow \
--location=us-central1 \
--destination-workflow=file-pipeline \
--destination-workflow-location=us-central1 \
--event-filters="type=google.cloud.storage.object.v1.finalized" \
--event-filters="bucket=my-upload-bucket" \
--service-account=eventarc-sa@PROJECT_ID.iam.gserviceaccount.com
# Test — upload a file
$ gsutil cp test-file.csv gs://my-upload-bucket/
# Verify workflow execution
$ gcloud workflows executions list file-pipeline --location=us-central1
Cleanup
$ gcloud functions delete greet --region=us-central1 --quiet
$ gcloud run services delete traffic-demo --region=us-central1 --quiet
$ gcloud api-gateway gateways delete my-gateway --location=us-central1 --quiet
$ gcloud api-gateway api-configs delete my-config --api=my-api --quiet
$ gcloud api-gateway apis delete my-api --quiet
$ gcloud functions delete tasks-api --region=us-central1 --quiet
$ gcloud firestore databases delete --database="(default)" --quiet
$ gcloud workflows delete file-pipeline --location=us-central1 --quiet
$ gcloud eventarc triggers delete gcs-to-workflow --location=us-central1 --quiet
Next Chapter → Monitoring & Logging