gitea:runnersEnabled creates a zonal GKE Standard cluster, used for nothing else, running the official gitea/runner image in rootless Docker-in-Docker, so workflows can `docker build`. Off by default; with the gate off the stack only gains the container API. - private spot nodes, egress through Cloud NAT on a static IP; control plane reachable only through its IAM-gated DNS endpoint - the registration token is a Secret Manager secret created by scripts/bootstrap.sh; Gitea reads it from a file, the runner pod mounts it through the Secret Manager CSI add-on with Workload Identity. Pulumi never sees the value - a NetworkPolicy keeps job containers off the metadata server and private ranges, so jobs hold no GCP identity - Caddy routes /api/actions, /api/actions_pipeline and the artifact service around the WAF (protobuf and PUT/PATCH uploads); fail2ban ignores the NAT address - docs/runners.md: threat model, cost, enable/disable, token rotation Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2.9 KiB
2.9 KiB
Moving CI off GitHub and onto this Gitea instance
Today the repository lives on GitHub and Cloud Build watches it through a
cloudbuildv2 connection. The intent is to move the repository into the Gitea
instance this repo deploys. Doing that well is mostly about not building a loop
that can strand itself.
The bootstrapping problem
The runner that deploys the VM must not live on the VM it deploys. If a
Gitea Actions runner on gitea-vm executes the infra pipeline, then any change
that restarts Gitea — a config sync, an image rollout, a reboot — kills the job
mid-flight. Worse, a bad change locks you out of the tool needed to fix it.
Two workable shapes:
- Keep Cloud Build as the executor. Gitea fires a webhook; Cloud Build runs
the build. Cloud Build has no first-class Gitea trigger, so this means a small
authenticated endpoint (Cloud Run or a Cloud Function) that validates the
webhook signature and calls
cloudbuild.projects.triggers.run. The deploy path stays entirely outside the VM. This is the recommended option. - A Gitea Actions runner on separate infrastructure. That infrastructure
now exists:
gitea:runnersEnabledcreates a dedicated GKE cluster (runners.md). But it is built for CI, not deploys. Job pods deliberately hold no Google Cloud identity, and the threat model assumes jobs cannot reach GCP. Running the infra pipeline there would mean giving adeploy-labelled job a Workload Identity that can runpulumi upwithcb-infra@-level permissions, from a pod whose jobs can already read each other's runner credentials. That needs its own design. It is not a config change.
Either way, keep the GitHub trigger working until the replacement has successfully deployed at least once.
Order of operations
[actions] ENABLED = trueis already set invm/config/app.ini.tmpl, so no Gitea-side config change is needed to start.- Mirror the repository into Gitea and verify history, LFS objects, and tags.
- Stand up the chosen executor and prove it can run
cloudbuild/image.yamlend to end, including the IAP rollout step. - Point
gitea:githubOwnerat nothing / remove the GitHub trigger. The Pulumi program already tolerates GitHub being unconfigured —build.Newlogs a warning and skips the triggers — so the stack still applies cleanly. - Keep the GitHub repo as an archived mirror for a while. It is the cheapest possible disaster recovery for the repo that contains the deployment.
Do not forget
- The
cb-infra@service account and the Pulumi state bucket are created byscripts/bootstrap.sh, not by Pulumi. They survive this migration untouched. - Backups (
gitea-backup.timer) become considerably more important once the repository that describes the infrastructure lives on the infrastructure. Verify a restore before you cut over, not after.