Zebra Continuous Delivery
The continuous-delivery pipeline deploys every commit merged to main to the stage environment and every published release to prod, on Google Cloud Platform. PR-triggered work uses the dev environment.
Topology: one zonal MIG per (environment, branch, network, zone)
The pipeline targets two GCP environments. Each network in each environment deploys to three zonal Managed Instance Groups (MIGs) in us-east1 zones b, c, and d. Each zonal MIG holds one Zebra instance with one stateful cache disk and one static IP.
| Trigger | Environment label | GCP project | MIGs per network | MIG name | Stateful disk |
|---|---|---|---|---|---|
release | prod | zfnd-prod-zebra | 3 (one per zone) | zebrad-${network}-${zone-letter} | zebrad-cache-${network}-${zone-letter} |
push to main | stage | zfnd-dev-zebra | 3 (one per zone) | zebrad-main-${network}-${zone-letter} | zebrad-cache-main-${network}-${zone-letter} |
workflow_dispatch | dev or prod | selected by env | 1 (user-chosen zone) | zebrad-${branch}-${network}-${zone-letter} | zebrad-cache-${branch}-${network}-${zone-letter} |
ADR 0006 records the rationale; the runbook covers day-to-day procedures.
Update mechanics
The event-driven workflow builds an image with its trigger-specific inputs, then passes the complete immutable GAR repository@sha256:... identity to the reusable deployment workflow. Release automation can call that same deployment boundary with an image prepared earlier. Each push and each release fans out to six deploy-nodes jobs (2 networks × 3 zones), while a workflow_dispatch runs one job for the selected zone. Every job runs the same flow for its zonal MIG:
- Build a new instance template with the commit’s container image.
- Ensure the zonal stateful disk exists. On first deploy, create it from the latest matching cache image. On subsequent deploys, attach the existing disk.
- If the zonal MIG exists, run
rolling-action start-update --max-unavailable=1. True per-zone rolling: this zone’s MIG replaces its instance while the other two zones keep serving. The stateful disk persists across the replace. - If the zonal MIG does not exist, create it with
--size=1and apply the stateful policy. - Assign the static IP when the caller passes
use_reserved_ip: always forpushandrelease, and for a workflow_dispatch that selectsprodor runs from themainref. Other dev deployments get no external address (--no-address). Zone-to-IP mapping is deterministic: zoneb→ primary, zonec→ secondary, zoned→ tertiary.
Cache images come from zfnd-deploy-integration-tests-gcp.yml’s create-state-image job. Image names encode branch, commit, state-DB version, network, and timestamp. One image per network seeds all three zones. Lookup priority in gcp-get-cached-disks.sh: current branch, then main, then any branch; most recent first.
Each deploy-nodes cell holds its per-MIG concurrency lock through template rollout and optional application-health verification. See the runbook for details.
Triggers
The workflow runs on:
- a
pushtomainthat touches Rust code, dependencies, Docker files, or the workflow itself - a published
release - a
workflow_dispatchfrom any branch (dispatcher picksnetwork,zone, andenvironment)
Pull requests run only the Docker-configuration tests; they do not deploy.
For implementation details, see the deploy workflow.