GCP for DevOps — CI/CD Exercises

3 minute read

CI/CD — Practice Exercises

Test your knowledge of Cloud Build, build triggers, Artifact Registry, Cloud Deploy, and deployment strategies.


Exercise 1 — Cloud Build Pipeline

Write a cloudbuild.yaml that builds a Docker image, runs tests, pushes to Artifact Registry, and deploys to Cloud Run. Use parallel steps for linting and testing.

Show Answer
steps:
  # Install dependencies
  - name: 'node:18'
    entrypoint: 'npm'
    args: ['ci']
    id: 'install'

  # Lint (parallel with test)
  - name: 'node:18'
    entrypoint: 'npm'
    args: ['run', 'lint']
    id: 'lint'
    waitFor: ['install']

  # Test (parallel with lint)
  - name: 'node:18'
    entrypoint: 'npm'
    args: ['test']
    id: 'test'
    waitFor: ['install']

  # Build Docker image (after lint + test pass)
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'build'
      - '-t'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$SHORT_SHA'
      - '.'
    id: 'build'
    waitFor: ['lint', 'test']

  # Push image
  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$SHORT_SHA']
    id: 'push'
    waitFor: ['build']

  # Deploy to Cloud Run
  - name: 'gcr.io/cloud-builders/gcloud'
    args:
      - 'run'
      - 'deploy'
      - 'my-app'
      - '--image=us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$SHORT_SHA'
      - '--region=us-central1'
      - '--platform=managed'
    waitFor: ['push']

images:
  - 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$SHORT_SHA'

options:
  machineType: 'E2_HIGHCPU_8'

timeout: '900s'

Exercise 2 — Build Triggers

Create three triggers: (1) CI on all branches, (2) deploy to staging on develop push, (3) deploy to production on semantic version tags with approval.

Show Answer
# 1. CI trigger on all branches
$ gcloud builds triggers create github \
    --name=ci-all-branches \
    --repository=projects/my-project/locations/us-central1/connections/github/repositories/my-repo \
    --branch-pattern=".*" \
    --build-config=cloudbuild-ci.yaml \
    --ignored-files="docs/**,*.md"

# 2. Deploy to staging on develop push
$ gcloud builds triggers create github \
    --name=deploy-staging \
    --repository=projects/my-project/locations/us-central1/connections/github/repositories/my-repo \
    --branch-pattern="^develop$" \
    --build-config=cloudbuild-staging.yaml \
    --substitutions=_ENV=staging

# 3. Deploy to production on version tags (with approval)
$ gcloud builds triggers create github \
    --name=deploy-production \
    --repository=projects/my-project/locations/us-central1/connections/github/repositories/my-repo \
    --tag-pattern="^v[0-9]+\.[0-9]+\.[0-9]+$" \
    --build-config=cloudbuild-prod.yaml \
    --require-approval

# Verify
$ gcloud builds triggers list --region=us-central1

Exercise 3 — Artifact Registry Setup

Create a Docker repository, push an image, and set up a cleanup policy to delete untagged images older than 14 days.

Show Answer
# Create repository
$ gcloud artifacts repositories create app-images \
    --repository-format=docker \
    --location=us-central1 \
    --description="Application Docker images"

# Configure Docker authentication
$ gcloud auth configure-docker us-central1-docker.pkg.dev

# Build and push an image
$ docker build -t us-central1-docker.pkg.dev/my-project/app-images/web:v1.0.0 .
$ docker push us-central1-docker.pkg.dev/my-project/app-images/web:v1.0.0

# Set cleanup policy — delete untagged images older than 14 days
$ gcloud artifacts repositories set-cleanup-policies app-images \
    --location=us-central1 \
    --policy='{"conditionalPolicy":{"tagState":"UNTAGGED","olderThan":"1209600s"},"action":"DELETE"}'

# Verify the image
$ gcloud artifacts docker images list \
    us-central1-docker.pkg.dev/my-project/app-images

Exercise 4 — Cloud Deploy Pipeline

Define a Cloud Deploy pipeline with dev → staging → production stages. The production target should require approval.

Show Answer
# clouddeploy.yaml
apiVersion: deploy.cloud.google.com/v1
kind: DeliveryPipeline
metadata:
  name: web-app-pipeline
description: "Web application delivery pipeline"
serialPipeline:
  stages:
    - targetId: dev
      profiles: [dev]
    - targetId: staging
      profiles: [staging]
    - targetId: production
      profiles: [production]
---
apiVersion: deploy.cloud.google.com/v1
kind: Target
metadata:
  name: dev
description: "Development"
run:
  location: projects/my-project/locations/us-central1
---
apiVersion: deploy.cloud.google.com/v1
kind: Target
metadata:
  name: staging
description: "Staging"
run:
  location: projects/my-project/locations/us-central1
---
apiVersion: deploy.cloud.google.com/v1
kind: Target
metadata:
  name: production
description: "Production"
requireApproval: true
run:
  location: projects/my-project/locations/us-central1
# Apply
$ gcloud deploy apply --file=clouddeploy.yaml --region=us-central1

# Create a release
$ gcloud deploy releases create release-v1 \
    --delivery-pipeline=web-app-pipeline \
    --region=us-central1 \
    --images=web=us-central1-docker.pkg.dev/my-project/app-images/web:v1.0.0

# Promote through stages
$ gcloud deploy releases promote --release=release-v1 \
    --delivery-pipeline=web-app-pipeline --region=us-central1

Exercise 5 — Canary Deployment on Cloud Run

Deploy a new version of a Cloud Run service with canary traffic splitting: start at 10%, verify, then promote to 100%.

Show Answer
# Deploy new revision with no traffic and a canary tag
$ gcloud run deploy my-app \
    --image=us-central1-docker.pkg.dev/my-project/app-images/web:v2.0.0 \
    --region=us-central1 \
    --no-traffic \
    --tag=canary

# Send 10% traffic to canary
$ gcloud run services update-traffic my-app \
    --region=us-central1 \
    --to-tags=canary=10

# Monitor (check error rates, latency)
# Wait and verify...

# Increase to 50%
$ gcloud run services update-traffic my-app \
    --region=us-central1 \
    --to-tags=canary=50

# Verify again, then promote to 100%
$ gcloud run services update-traffic my-app \
    --region=us-central1 \
    --to-latest

# View current traffic split
$ gcloud run services describe my-app \
    --region=us-central1 \
    --format="yaml(status.traffic)"

If issues are detected, rollback:

$ gcloud run services update-traffic my-app \
    --region=us-central1 \
    --to-tags=canary=0

Cleanup

$ gcloud deploy delivery-pipelines delete web-app-pipeline --region=us-central1 --force --quiet
$ gcloud artifacts repositories delete app-images --location=us-central1 --quiet
$ gcloud builds triggers delete ci-all-branches --region=us-central1 --quiet
$ gcloud builds triggers delete deploy-staging --region=us-central1 --quiet
$ gcloud builds triggers delete deploy-production --region=us-central1 --quiet
$ gcloud run services delete my-app --region=us-central1 --quiet

Next Chapter → Containers

Updated: