Skip to content

feat(gke-test): run every repository's tests on one shared GKE cluster - #32

Merged
chrischeng-c4 merged 3 commits into
mainfrom
claude/gke-test-cluster
Oct 15, 2026
Merged

chrischeng-c4 merged 3 commits into
mainfrom
claude/gke-test-cluster

Conversation

@chrischeng-c4

@chrischeng-c4 chrischeng-c4 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Why

Local kind clusters fight for the same 10 CPU and 31 GB. This moves Kubernetes tests to a new zonal GKE cluster faberline-test (project axiom-502607; a project ID cannot change): one namespace and one node pool per repository, and every pool at 0 nodes between runs.

Who owns what

Owner Owns
aw Cluster, node pools and their max size (infra/gke-test/pools.yaml), identities, the shared workflow, the scripts, the reaper
Each repository k8s/overlays/gke-test/, its image build to GHCR, its test command

What is in this PR

  • infra/gke-test/: Terraform. Creates the cluster faberline-test and its node service account, with only these pools:
    • system-pool: e2-standard-2 Spot, no autoscaling. wake.sh sets it to 1; park.sh sets it to 0.
    • pool-<repo> for each row of pools.yaml: 0..max_nodes, label and taint faberline.dev/pool=<repo>.
    • WIF pool faberline-github (any repository under faberline), one gke-test-<repo> service account per row, and gke-test-reaper.
  • infra/gke-test/bootstrap/: the state bucket axiom-502607-faberline-tfstate.
  • .github/workflows/gke-test.yml: reusable workflow. Render and check the overlay, deploy, run the test command, always tear down, upload evidence.
  • .github/workflows/gke-test-reaper.yml: every 30 minutes, delete expired test namespaces, park system-pool, delete orphan pvc-* disks, and report pods outside test namespaces. A failed run opens one issue; the next clean run closes it. It runs only after this PR is on main.
  • Budget: google_billing_budget.test, 650 TWD (about US$20) a month, emails at 50%, 90%, 100%, and 100% forecast. The billing account ID comes from the project, not from this public repository.
  • scripts/gke-test/: run.sh, teardown.sh, wake.sh, park.sh, reap.sh.
  • docs/standards/test-cluster.md: the guideline for repositories.

Differences from the approved plan

  • Deployers get cluster-admin, not a custom ClusterRole. A run creates ClusterRoles and bindings, which needs bind and escalate, which equal cluster-admin.
  • A new WIF pool, not a wider workspace pool. The workspace bootstrap was never applied, so there was nothing to widen.
  • A new cluster, not the old axiom-operator-acceptance. The repositories moved from axiom to faberline, and a cluster cannot be renamed. The old cluster was empty and is deleted by hand.
  • The cluster uses the OPTIMIZE_UTILIZATION autoscaling profile, so idle repository nodes go away sooner.
  • system-pool is e2-standard-2, not e2-medium. kube-system requests about 700m plus DaemonSets.

Checks

  • shellcheck -x -S warning scripts/gke-test/*.sh
  • actionlint on both workflows
  • terraform fmt -check and terraform validate
  • Bootstrap applied with owner approval: bucket axiom-502607-faberline-tfstate exists.
  • terraform plan against that bucket: 18 to add, 0 to change, 0 to destroy.
  • run.sh render check, on a copy of keep: the template overlay fails with the 3 missing placement items; the gke-test overlay passes. Teardown order is the exact reverse of the rendered order.
  • terraform apply of infra/gke-test with owner approval: 18 added. A new terraform plan shows no changes.
  • Cleanup with owner approval: 34 unattached pvc-* disks (331 GB) deleted; project display name is now faberline; old cluster axiom-operator-acceptance deleted.
  • End-to-end run from keep (feat(gke-test): test keep on the shared faberline GKE cluster keep#6), run 38695988056: attempt 1 failed in wake.sh (rollout status on kube-dns hit its old progress deadline); teardown still deleted the namespace. Fixed with kubectl wait --for=condition=Available. Attempt 2 passed: readyz, round trip, and data survives a pod restart. Teardown parked system-pool; afterwards the project had 0 VMs and 0 disks.
  • terraform plan for the reaper and budget changes: 4 to add, 1 to change (reaper ClusterRole), 0 to destroy.
  • terraform apply of the reaper and budget changes with owner approval: 4 added, 1 changed. A new terraform plan shows no changes.

🤖 Generated with Claude Code

Local kind clusters fight for the same CPU and memory. This moves
Kubernetes tests to the existing zonal cluster axiom-operator-acceptance,
one namespace and one node pool per repository, with every pool at 0
nodes between runs.

aw owns the cluster side:
- infra/gke-test: Terraform that imports the cluster, its node service
  account, and the two old pools; adds system-pool (no autoscaling, woken
  and parked by script) and one tainted pool-<repo> per row of pools.yaml
  (0..max_nodes); adds the faberline-github WIF pool, one gke-test-<repo>
  service account per row, and the reaper identity. acceptance-pool loses
  autoscaling so pending kube-system pods cannot scale it up after a park.
- infra/gke-test/bootstrap: the GCS bucket for the state.
- .github/workflows/gke-test.yml: reusable workflow. It signs in through
  WIF, renders and checks the caller's k8s/overlays/gke-test, deploys,
  runs the caller's test command, and always tears down.
- .github/workflows/gke-test-reaper.yml: every 30 minutes, deletes
  expired test namespaces and parks system-pool.
- scripts/gke-test: run, teardown, wake, park, and reap in bash.
- docs/standards/test-cluster.md: the guideline each repository follows.

Nothing is applied yet; terraform plan shows 6 imports, 15 adds, 3
in-place updates, and 0 destroys.
…ing the axiom one

The repositories moved from axiom to faberline, and a GKE cluster cannot be
renamed. Terraform now creates faberline-test with its own node service
account and only system-pool plus the pool-<repo> pools, so the imports,
acceptance-pool, and data-plane-pool are gone. The old cluster is empty and
is deleted by hand after this one is up.

The cluster also uses the OPTIMIZE_UTILIZATION autoscaling profile, so an
idle repository node goes away sooner. The project ID axiom-502607 stays;
a project ID cannot change. The provider lock files are now tracked.
…budget

So nothing on the shared test cluster keeps costing money unnoticed:

- reap.sh also deletes orphan pvc-* disks (unattached, older than 1 hour,
  named by no PersistentVolume) and reports pods outside test and system
  namespaces without deleting them. It exits 1 when a person is needed.
- The reaper workflow opens one issue on failure and closes it after the
  next clean run.
- teardown.sh parks system-pool at the end, so the node does not wait for
  the next reaper run.
- wake.sh waits for kube-dns Available instead of `rollout status`, which
  failed at once because the deployment passed its progress deadline while
  it sat Pending on 0 nodes (first keep run).
- Terraform: a custom disk-reaper role, read access to PVs and pods for the
  reaper, and a monthly project budget (650 TWD) with 50/90/100% and
  forecast alerts. The billing account comes from the project data source.
@chrischeng-c4
chrischeng-c4 merged commit 2ab4c18 into main Oct 15, 2026
chrischeng-c4 added a commit to faberline/keep that referenced this pull request Oct 15, 2026
faberline/aw#32 is merged, so the pilot no longer needs the feature branch.
aw-ref defaults to main.
chrischeng-c4 added a commit that referenced this pull request Oct 17, 2026
GitHub runs the reaper's */30 schedule every 2.5 to 5 hours, so
system-pool stayed up for hours after the last test.

Cloud Scheduler now starts the Cloud Workflows workflow gke-test-park
every 10 minutes. It runs in GCP outside the cluster, does what park.sh
does, and waits 5 minutes before it parks so it does not park between
the suites of one run. The reaper stays as the second net.

Also fix the reaper step list in test-cluster.md: step 4 lost its second
line and step 5 its number in #32.
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