Skip to content

Stop idle App Engine versions after deploy#3733

Draft
anth-volk wants to merge 2 commits into
masterfrom
feat/app-engine-idle-version-cleanup
Draft

Stop idle App Engine versions after deploy#3733
anth-volk wants to merge 2 commits into
masterfrom
feat/app-engine-idle-version-cleanup

Conversation

@anth-volk

@anth-volk anth-volk commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

What & why

The v1 API runs on App Engine Flexible, which keeps at least one always-on VM for every SERVING version regardless of traffic (current app.yaml: 4 vCPU / 24 GB ≈ ~$212/mo per version). The deploy pipeline promotes traffic to the new version but never deactivates the ones it supersedes, so both staging and old prod versions accumulate as idle-but-billing VMs. A recent snapshot showed ~11–14 VMs running with only one serving traffic — the bulk of a ~$6,050/mo App Engine bill.

This adds post-deploy cleanup so idle versions stop costing money, while preserving rollback.

Changes

  • stop-staging-app-engine-version (after integration-tests-staging): stops the staging version once its tests pass. Staging holds 0 VMs between deploys; each deploy makes a fresh one.
  • cleanup-prod-app-engine-versions (after promote-production): keeps the live version plus the newest 2 prod versions warm for instant rollback, and stops every other SERVING version (older prod + leftover staging). It then prunes stopped versions to the newest 10 prod and newest 3 staging, deleting everything else stopped (older prod, older staging, and legacy timestamp-named versions) to stay under App Engine's 210-versions-per-service limit. Never touches the version receiving traffic.
  • New scripts stop_app_engine_version.sh and cleanup_app_engine_versions.sh — idempotent, DRY_RUN=1 supported, written for bash 3.2 so they dry-run locally too.

Why stop, not delete (for the warm/rollback set)

Stopping deactivates the VM (→ ~$0) but keeps the version deployed and restartable, so rollback still works: gcloud app versions start <v> then gcloud app services set-traffic default --splits=<v>=1. Keeping the 2 newest prod versions warm gives instant rollback, and the newest 10 stopped prod versions remain as cold rollback targets — the original reason old versions were kept SERVING (a new deploy's simulation-API dependency could fail). Only prod versions are meaningful rollback targets, so staging retention is kept small.

Verification

  • gcloud app versions stop/start confirmed working live on this project's automatic-scaling Flex versions (stopped prod-2393STOPPED → started → SERVING); it's the same Versions.UpdateVersion call already used to trim the fleet on 2026-07-02. No app.yaml scaling-mode change needed.
  • Dry-run against live policyengine-api produced the expected plan: keep prod-2405 (live) + prod-2403 warm; stop 12 idle versions; keep the newest 10 stopped prod + 3 stopped staging and delete the other 68 stopped versions; traffic-serving version untouched.
  • push.yml validated as YAML; changelog fragment added; no Python files changed (format/lint gate N/A).

Scope

Deploy-pipeline / cost only — no API contract or route changes; everything stays on the App Engine v1 path. This is a bridge until v1 finishes migrating to Cloud Run (min=0 = true scale-to-zero, at which point this machinery retires).

Expected impact

Steady-state running VMs drop from ~11–14 to 2 (live + previous prod); staging ~$0 except during a deploy's test window → roughly $6,050/mo → ~$450–500/mo.

🤖 Generated with Claude Code

App Engine Flexible keeps an always-on VM for every SERVING version
regardless of traffic, so the staging version and superseded prod
versions left SERVING after each deploy keep costing ~$212/mo each.
The pipeline promoted traffic but never deactivated old versions.

- Stop the staging version after its integration tests pass.
- After a production promote, keep the live version plus the newest
  2 prod versions warm for rollback and stop every other SERVING
  version; delete stopped versions beyond the newest 20 to respect
  App Engine's 210-versions-per-service limit.

Stopping (not deleting) preserves rollback via `gcloud app versions
start` + `set-traffic`. Adds stop_app_engine_version.sh and
cleanup_app_engine_versions.sh, plus two jobs in push.yml.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.82%. Comparing base (74c146f) to head (0b064e7).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #3733   +/-   ##
=======================================
  Coverage   79.82%   79.82%           
=======================================
  Files          70       70           
  Lines        4336     4336           
  Branches      808      808           
=======================================
  Hits         3461     3461           
  Misses        655      655           
  Partials      220      220           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The stopped-version retention was a single newest-20 pool shared across
prod and staging. Since only prod versions are meaningful rollback
targets, keep the newest 10 stopped prod versions and only the newest 3
stopped staging versions; delete everything else stopped (older prod,
older staging, and legacy timestamp-named versions).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant