GCP for DevOps — Storage Exercises
Storage — Practice Exercises
Test your knowledge of Cloud Storage, versioning, lifecycle, Persistent Disks, and Filestore.
Exercise 1 — Create a Versioned Bucket with Lifecycle
Create a Cloud Storage bucket with versioning enabled and a lifecycle rule that deletes non-current versions older than 7 days, keeping only 3 versions.
Show Answer
# Create the bucket
$ gcloud storage buckets create gs://devops-exercise-storage \
--location=us-central1 \
--uniform-bucket-level-access
# Enable versioning
$ gcloud storage buckets update gs://devops-exercise-storage \
--versioning
# Create lifecycle.json
$ cat > lifecycle.json << 'EOF'
{
"lifecycle": {
"rule": [
{
"action": {"type": "Delete"},
"condition": {
"isLive": false,
"numNewerVersions": 3,
"age": 7
}
}
]
}
}
EOF
# Apply lifecycle rules
$ gcloud storage buckets update gs://devops-exercise-storage \
--lifecycle-file=lifecycle.json
# Verify
$ gcloud storage buckets describe gs://devops-exercise-storage \
--format="json(versioning,lifecycle)"
Exercise 2 — Terraform State Backend
Set up a GCS bucket suitable for Terraform state: versioned, single-region, with lifecycle to keep 10 state versions.
Show Answer
# Create state bucket
$ gcloud storage buckets create gs://devops-exercise-tfstate \
--location=us-central1 \
--default-storage-class=STANDARD \
--uniform-bucket-level-access
# Enable versioning
$ gcloud storage buckets update gs://devops-exercise-tfstate \
--versioning
# Lifecycle — keep only 10 non-current versions
$ cat > tf-lifecycle.json << 'EOF'
{
"lifecycle": {
"rule": [
{
"action": {"type": "Delete"},
"condition": {
"isLive": false,
"numNewerVersions": 10
}
}
]
}
}
EOF
$ gcloud storage buckets update gs://devops-exercise-tfstate \
--lifecycle-file=tf-lifecycle.json
Terraform backend block:
terraform {
backend "gcs" {
bucket = "devops-exercise-tfstate"
prefix = "exercise/state"
}
}
Exercise 3 — Persistent Disk with Snapshot Schedule
Create a 50 GB pd-ssd disk, attach it to a VM, and set up a daily snapshot schedule that retains snapshots for 5 days.
Show Answer
# Create the disk
$ gcloud compute disks create exercise-data-disk \
--size=50GB \
--type=pd-ssd \
--zone=us-central1-a
# Create a VM (if not existing)
$ gcloud compute instances create exercise-vm \
--zone=us-central1-a \
--machine-type=e2-micro
# Attach the disk
$ gcloud compute instances attach-disk exercise-vm \
--disk=exercise-data-disk \
--zone=us-central1-a
# Create a snapshot schedule
$ gcloud compute resource-policies create snapshot-schedule exercise-daily-snap \
--region=us-central1 \
--max-retention-days=5 \
--daily-schedule \
--start-time=03:00
# Attach the schedule to the disk
$ gcloud compute disks add-resource-policies exercise-data-disk \
--resource-policies=exercise-daily-snap \
--zone=us-central1-a
Exercise 4 — Storage Class Tiering
Create a bucket for logs that automatically transitions from Standard → Nearline (30 days) → Coldline (90 days) → Archive (365 days) → Delete (730 days).
Show Answer
# Create the bucket
$ gcloud storage buckets create gs://devops-exercise-logs \
--location=us-central1
# Create lifecycle rules
$ cat > log-lifecycle.json << 'EOF'
{
"lifecycle": {
"rule": [
{
"action": {"type": "SetStorageClass", "storageClass": "NEARLINE"},
"condition": {"age": 30, "matchesStorageClass": ["STANDARD"]}
},
{
"action": {"type": "SetStorageClass", "storageClass": "COLDLINE"},
"condition": {"age": 90, "matchesStorageClass": ["NEARLINE"]}
},
{
"action": {"type": "SetStorageClass", "storageClass": "ARCHIVE"},
"condition": {"age": 365, "matchesStorageClass": ["COLDLINE"]}
},
{
"action": {"type": "Delete"},
"condition": {"age": 730}
}
]
}
}
EOF
$ gcloud storage buckets update gs://devops-exercise-logs \
--lifecycle-file=log-lifecycle.json
# Verify
$ gcloud storage buckets describe gs://devops-exercise-logs \
--format="json(lifecycle)"
Exercise 5 — Upload with Signed URL
Generate a signed URL that allows a CI/CD runner (without GCP credentials) to upload a build artifact to Cloud Storage.
Show Answer
# Create a service account for signing
$ gcloud iam service-accounts create ci-uploader \
--display-name="CI Artifact Uploader"
# Grant objectCreator role on the bucket
$ gcloud storage buckets add-iam-policy-binding gs://devops-exercise-storage \
--member="serviceAccount:ci-uploader@my-project.iam.gserviceaccount.com" \
--role="roles/storage.objectCreator"
# Create a service account key (for signing)
$ gcloud iam service-accounts keys create ci-uploader-key.json \
--iam-account=ci-uploader@my-project.iam.gserviceaccount.com
# Generate a signed URL for PUT (1-hour expiry)
$ gcloud storage sign-url \
gs://devops-exercise-storage/builds/artifact.tar.gz \
--private-key-file=ci-uploader-key.json \
--http-verb=PUT \
--duration=1h
# Use the signed URL to upload (no gcloud needed)
$ curl -X PUT -H "Content-Type: application/gzip" \
--upload-file artifact.tar.gz \
"SIGNED_URL_HERE"
Best practice: Use Workload Identity Federation instead of service account keys for production CI/CD systems.
Cleanup
$ gcloud storage rm -r gs://devops-exercise-storage
$ gcloud storage rm -r gs://devops-exercise-tfstate
$ gcloud storage rm -r gs://devops-exercise-logs
$ gcloud compute resource-policies delete exercise-daily-snap --region=us-central1 --quiet
$ gcloud compute instances detach-disk exercise-vm --disk=exercise-data-disk --zone=us-central1-a
$ gcloud compute disks delete exercise-data-disk --zone=us-central1-a --quiet
$ gcloud compute instances delete exercise-vm --zone=us-central1-a --quiet
$ gcloud iam service-accounts delete ci-uploader@my-project.iam.gserviceaccount.com --quiet
$ rm -f ci-uploader-key.json lifecycle.json tf-lifecycle.json log-lifecycle.json
Next Chapter → CI/CD