Sandbox runbook¶
Single-sourced
This page renders
acme-sampleapp-multirepo-sandbox-elaborate/RUNBOOK.md
directly, so it can never drift from the scripts it documents. Edit the file in the
sandbox, not here.
Everything here is runnable against a real GCP project. It is not runnable against your organization's real GKE cluster/Consul cluster — it needs its own, which this runbook sets up (a lab-sized GKE cluster you create and destroy yourself, and a local Consul dev agent standing in for a real Consul cluster).
Read the cost section before running terraform apply anywhere near the
GKE modules. Everything else in this sandbox (VPC, DNS zone, Secret
Manager, Cloud SQL at the smallest tier) is cheap-to-free; GKE nodes are the
one thing here that meaningfully costs money per hour they're running.
0. One-time tool setup¶
# Auth — this sandbox uses your own gcloud credentials via Application
# Default Credentials, the same way `google_client_config.default` (used
# throughout for the kubernetes/helm provider tokens) expects.
gcloud auth application-default login
gcloud config set project <your-real-project-id>
# Local Consul dev agent — stands in for your org's real Consul cluster.
# `-dev` mode is in-memory, single-node, no ACLs, no persistence. Perfect for
# this and nothing else — never run -dev mode anywhere but your own machine.
consul agent -dev &
export CONSUL_HTTP_ADDR=http://127.0.0.1:8500
# Verify:
consul kv put test/hello world
consul kv get test/hello # should print "world"
If you don't have the consul CLI: brew install consul (macOS) or grab a
binary from releases.hashicorp.com/consul. No Docker required for -dev
mode. The restore script checks for Consul before running Terraform and waits
until the local agent responds; if startup fails, inspect /tmp/consul-dev.log.
1. Point the tfvars at your own project¶
All five roots already ship a committed terraform.tfvars holding lab
placeholders, so there is nothing to copy — edit them in place. At minimum,
sample-program/infra/terraform.tfvars needs your real project_id; every
other value works as shipped.
Each terraform.tfvars.example sits alongside as the annotated reference copy,
explaining what every variable does and which ones cost money. Read the example,
edit the real file. To reset a root back to shipped defaults:
2. Resume or bootstrap the sandbox¶
For normal learning sessions, run the restore script from the Acme directory:
The script resolves its own directory, preserves the local Consul KV data, runs
terraform plan -detailed-exitcode, and applies only when a change is needed.
A successful destroy-all.sh records a local clean-destroy marker, so the next
plain restore-session.sh can rebuild the normal learning environment without
an extra flag. Unexpectedly empty or missing state still fails closed because it
may indicate lost ownership; use --bootstrap only for the first intentional
setup after confirming no matching live resources exist. If the local Consul dev
agent was restarted, its in-memory data is gone; republish tracked outputs
explicitly:
Before applying the backend, the script verifies that the GKE cluster has at
least one Ready node and waits up to five minutes for startup. This avoids a
Helm timeout when the cluster has no usable node pool. The development values
request only 100m CPU and 256Mi memory so the backend can schedule on the
single non-production lab node. After a clean destroy removes Artifact Registry,
the script rebuilds backend/app for linux/amd64 and pushes the configured
image automatically; Docker Desktop must be running when that image is absent.
If a previous Helm attempt left a failed release outside Terraform state, repair it explicitly:
Use --skip-backend when you intentionally want to restore only the platform
and shared infrastructure:
For the first intentional setup only, allow creation from empty state:
Do not use --bootstrap as a recovery shortcut for a missing state file. Recover
or import the existing state first; otherwise Terraform cannot know that the
real resources already belong to this configuration.
3. Apply order (matches the dependency graph in README.md)¶
Each repo is terraform init && terraform plan && terraform apply from its
own infra/ directory — they are genuinely independent state files, run
independently, exactly like the real 5-repo project.
cd sample-program/infra
terraform init
terraform plan # safe — enable_gke_p/np default to false, nothing costs money yet
Stop here and read the cost section below before going further.
# When ready to actually stand up a cluster (recommended: np only, it's cheaper
# and it's what dev/qa/review environments use):
# edit terraform.tfvars: enable_gke_np = true
terraform apply
cd ../../infrastructure/infra
terraform init && terraform apply
cd ../../cloudsql/infra
terraform init && terraform apply
cd ../../backend/infra
terraform init && terraform apply # requires kubectl-style access — this repo's
# kubernetes/helm providers authenticate using
# the GKE cluster sample-program just published
cd ../../frontend/infra
terraform init && terraform apply
If any apply fails with something like "no host found" or a provider
config error, it almost always means the upstream repo hasn't been applied
yet, or enable_gke_np/enable_gke_p is still false — the dependency
graph in README.md is the actual troubleshooting order.
4. Verifying a learning session¶
Use the read-only health check after restoring or before teaching a session:
It checks local tools and ADC, Consul contracts, Terraform state files, the GKE cluster and node pool, Kubernetes connectivity, Ready nodes, the backend namespace/workloads, recent events, and Helm status. It exits nonzero when a required check fails. Add Terraform refresh plans when you want drift/change checks as well:
--plans is read-only but can take longer because each Terraform root refreshes
its providers and compares state with the live resources.
5. Tearing down¶
The destroy helper resolves its own directory, so it can be run from any
working directory. It asks you to type destroy once, then creates and applies
saved destroy plans in reverse dependency order:
It checks every existing frontend, backend, and Cloud SQL workspace, skips empty
state, destroys in reverse dependency order, clears stale local Consul contracts,
and records the clean teardown used by the next plain restore. Both lifecycle
scripts print potentially billable versus no-direct-charge Terraform resource
action counts; these are resource counts, not price estimates. A failed Helm
release that is not in Terraform state is removed when its GKE cluster is
destroyed; if you are preserving the cluster and only repairing the backend, use
restore-session.sh --repair-failed-helm instead.
Reverse order, same as any dependency graph:
cd frontend/infra && terraform destroy
cd ../../backend/infra && terraform destroy
cd ../../cloudsql/infra && terraform destroy
cd ../../infrastructure/infra && terraform destroy
cd ../../sample-program/infra && terraform destroy
Kill the Consul dev agent when done: kill %1 (or find it with jobs).
-dev mode is in-memory anyway — nothing persists once it's killed.
Cost warning — read before enabling GKE¶
enable_gke_p / enable_gke_np default to false specifically so you can
plan and read the whole graph without spending anything. Once you flip
either to true:
- You're paying for 1 running
e2-smallnode (spot pricing fornp, on-demand forp) — order of cents/hour, but it accrues the whole time it's running, not just while you're actively using it. - Turn it off when you're done for the session.
terraform destroyfromsample-program/infraremoves the cluster and node pool. There's no "pause" — destroy and re-apply next session is the normal workflow for a lab cluster. - Consider enabling only
enable_gke_nprather than both — one cluster is enough to exercise the entire dependency graph end to end (backendandfrontendboth readlocal.program_gcpkeyed byp_or_np, and any non-defaultTerraform workspace resolves tonp). - Check current GKE/Compute Engine pricing for your region before leaving anything running unattended — prices change and this file won't stay current.