Skip to content

ci: run the secret-free post-merge Linux jobs in GKE runner pods - #562

Merged
luisleo526 merged 6 commits into
mainfrom
ci/gke-post-jobs
Oct 27, 2026
Merged

luisleo526 merged 6 commits into
mainfrom
ci/gke-post-jobs

Conversation

@luisleo526

@luisleo526 luisleo526 commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Run the Linux proof jobs of the post-merge workflows (ci-post.yml, corpus-parity.yml, native-live.yml) on the gke-runners runner group (ephemeral pods, one job per VM, on demand, scale to zero) instead of the GCP VM fleet and the hosted arm64 runner. This is a routing change: no shard count, matrix, job name or check name changes.

Size label Jobs per ci-post run
gke-32cpu-64gb-amd64 build (Release) x4, sanitizers x4 = 8
gke-16cpu-32gb-amd64 build-debug x2, kernel-only x2, native-live x2, corpus-parity = 7
gke-8cpu-16gb-amd64 preflight, preflight-slow, native-live-shards x4, corpus-parity-subset = 7
gke-16cpu-32gb-arm64 build-arm (Release, Debug) = 2

The group allows 8 jobs per size label; one run never asks for more (the preflight checker expands matrices and reusable calls and refuses a ninth). A second run close behind queues instead of failing, so a queue in the first rounds is capacity, not a defect.

The jobs run directly in the runner pod. The pod image is the engine toolchain image (compiler, cmake, ninja, python, git-lfs, actionlint, the sanitizer runtime and the other tools the jobs used to install by hand), the runner user has sudo, and the existing dependency steps keep their package lists and their sudo. No job declares a container: or services: block, so nothing is pulled from a public registry at run time.

Not moved: authz, changes and the aggregate jobs (hosted); ci.yml and corpus-parity-subset.yml (pull-request reachable); docs.yml; docs-post.yml, release.yml, promote-baseline.yml, release-ruleset-probe.yml (they hold credentials); every macOS leg.

scripts/ci_preflight.py pins each moved job to its exact runs-on mapping and refuses: the gke-runners group or a gke- label in any pull-request reachable workflow; a secret, id-token, environment or inherited secrets on a job of the group or on a workflow call into it; a job container: or services: block on a moved job; any apt-get line other than the two pinned ones; a paid larger-runner label; more than 8 jobs on one label.

Files (name-status)

M .github/actionlint.yaml
M .github/workflows/ci-post.yml
M .github/workflows/corpus-parity.yml
M .github/workflows/native-live.yml
M docs/ci.md
M scripts/ci_preflight.py
M scripts/test_ci_preflight.py
M scripts/test_ci_release_gate.py

How this is proven

This change is workflow and checker only (no sweep). The lab preflight (lab remote, all 59 stages) passed on the head of this branch. The first ci-post run on main after the merge is the proof of the pod itself; a red leg is fixed forward the same night.

Acceptance read-backs of that first round (each is a plausible whole-leg red that the diff cannot show):

  1. Report-sink writer: src/report_sink_writer.cpp opens with O_DIRECT and refuses tmpfs/ramfs. The pod's workspace and /tmp sit on the 80 GiB per-job disk volume, not on tmpfs; the first round confirms the tmpfs-path tests (test_run_json_misc2_tmpfs.py, test_jailed_scratch_tmpfs_tmpdir.cpp) behave as on the VM fleet. If the pod /tmp refuses O_DIRECT, point scratch at the workspace in these jobs.
  2. Test counts: compare each moved job's CTest row count and skip count with the last post-fleet run; a lower count, or a new skip in a no-skip row (scripts/run_pytest_no_skips.py), is a regression, not a pass.
  3. LeakSanitizer in the pod (sanitizers x4, native-live live-sanitizers): if a fatal LeakSanitizer or ptrace error appears on exactly those jobs, report it; the fix is scoped to those jobs.
  4. Python and pip: every moved job that runs pip or pytest sets up Python 3.12 with actions/setup-python first (the pod image carries system Python without pip); the first round confirms "Set up Python" resolves 3.12 and python3 -m pip --version works on the amd64 pods and on the arm64 pod (build-arm). If the arm64 manifest has no 24.04 build, the fix is python3-pip in the image or an ensurepip step for that job.
  5. Which workflow ref the runner group checks for a called workflow (caller or callee): the first round shows it. The group lists these workflows at refs/heads/main; a dispatch from another ref queues without a runner.

Rollback: restore the previous runs-on arrays and pins from this commit's parent.

🤖 Generated with Claude Code

ci-cx and others added 5 commits October 28, 2026 01:25
Move the Linux proof jobs of ci-post.yml, corpus-parity.yml and native-live.yml
from the post-merge GCP VM fleet (and, for the two arm64 builds, the hosted
ubuntu-24.04-arm runner) to the gke-runners group, one size label per job:

  gke-32cpu-64gb-amd64  build (Release) x4, sanitizers x4                 8 jobs
  gke-16cpu-32gb-amd64  build-debug x2, kernel-only x2, native-live x2,
                        corpus-parity                                      7 jobs
  gke-8cpu-16gb-amd64   preflight, preflight-slow, native-live-shards x4,
                        corpus-parity-subset                               7 jobs
  gke-16cpu-32gb-arm64  build-arm (Release, Debug)                         2 jobs

A run never asks for more than the group's 8 jobs per size label.

The runner pod is non-root without sudo, so every moved job runs in a root job
container (ubuntu:24.04 pinned by multi-arch index digest) whose first step
installs the base toolchain (git, python3, build-essential, cmake, ninja ...);
the existing dependency steps keep their package lists and only lose "sudo".

Not moved: authz, changes and the aggregate jobs (hosted), ci.yml and
corpus-parity-subset.yml (pull-request reachable), docs.yml, docs-post.yml,
release.yml, promote-baseline.yml, release-ruleset-probe.yml (they hold
credentials) and every macOS leg.

scripts/ci_preflight.py pins each moved job to its exact label, the container
block and the bootstrap step; it refuses the gke-runners group or a gke- label
in any pull-request reachable workflow, a secret, id-token or environment on a
job of the group or on a job that calls a workflow holding such jobs, sudo in a
container job, a paid larger-runner label, and more than 8 jobs on one label
(matrix, include/exclude and reusable-call expansion). actionlint knows the four
labels. docs/ci.md is edited line for line and states that the jobs start only
from the three workflows at refs/heads/main.

docs/ci.md sentences replaced by the GKE routing text, named for the
documentation-revert check by the commit that introduced each:
- "Local reusable calls still cannot mix PR and post-merge verification fleets."
  (b0f4341) is replaced by "Local reusable calls cannot mix PR and GKE jobs."
- "A full post run schedules 22 post-fleet jobs; the parity calls share one
  concurrency group." (8154993) is replaced by the 24-job GKE count per label.
- "The amd64 post build shards use the post-merge GCP fleet; the two arm64 post
  cells run on hosted `ubuntu-24.04-arm`." (8154993) now reads GKE size labels.
- "Credentialed work and the remaining full profiles retain their VM pins."
  (3ea5a0d) is replaced by the credentialed-docs pin sentence.
- "The post fleet admits only push, schedule and dispatch on engine `main` and
  refuses all PR events." (3a0d150) is superseded by the GKE group-ref sentence.
- "Post-fleet shards require `main` for dispatch; hosted arm64 keeps its fixed
  pin." (8154993) is replaced by "GKE jobs start only from ci-post.yml,
  corpus-parity.yml and native-live.yml at refs/heads/main".
- "`HOSTED_JOB_RUNNERS` pins hosted jobs and post-fleet verification arrays;
  arm64 exceptions exclude PR callers." (8154993) now describes the exact GKE
  mappings.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
… a job container

The gke-runners pods now run the engine toolchain image (git, git-lfs, python3,
build-essential, cmake, ninja, clang, ccache, strace, libeigen3-dev,
libsqlite3-dev, zlib1g-dev, shellcheck, actionlint, tzdata-legacy) with the base
image's passwordless sudo. The jobs therefore run exactly as they did on the VM
fleet, so the previous commit's root job container and its bootstrap step are
removed and every apt step is again byte-identical to main (with sudo).

Routing is unchanged: one size label per job, 8/7/7 jobs on the 32/16/8 CPU
amd64 labels and 2 on the 16 CPU arm64 label per ci-post run, never more than the
group's 8 per label.

scripts/ci_preflight.py drops the container-specific rules (pinned root
container, exact bootstrap step, no sudo in container jobs) and instead refuses
a `container:` block on a job of the gke-runners group, because the pods carry
the toolchain and a bare image would be pulled from a public registry at run
time. The label pins, the pull-request-reachability, credential (jobs and their
callers), paid-label, cap and timeout rules are unchanged.

docs/ci.md sentences reworded by this commit, named for the documentation-revert
check by the commit that introduced each (8a3eca2):
- "profiles use fixed `gke-runners` size labels in pinned root containers."
- "fixed architecture/size labels and a pinned Ubuntu 24.04 root container."
- "secret-free post Linux jobs use GKE root containers."
- "every GKE job first installs the base toolchain before checkout, without sudo."

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…blocks on GKE jobs

Follow-up to the pod-native routing commit, closing four findings of its
independent read:

- the apt-line pin selected lines with a pattern that ignored a single-line step
  or one chained with &&; it now takes every line that mentions apt-get and
  compares the full per-job list with the literals (equal to main's lines), with
  mutation controls that insert an extra install in both shapes;
- the job-container mutation test ran on one job only and carried a dead branch;
  it now applies all six container forms to every moved job (11 jobs, three
  workflows);
- a job-level `services:` block on a job of the gke-runners group pulls bare
  images at run time like a job container; the checker now refuses it with its
  own finding, with mutation tests (block, empty, quoted key) and a check that no
  real job has one;
- a test compares the checker's linter-step constant with the linter step of the
  real preflight job so a drift of either side fails.

No workflow, label or docs change.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Plain merge, no conflicts; keeps the strict ruleset's up-to-date requirement.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Plain merge; keeps the strict ruleset's up-to-date requirement.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
@luisleo526
luisleo526 merged commit c85a40b into main Oct 27, 2026
4 checks passed
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