ci(k8s): deploy the ActiveJob worker from cdo-rails - #74716
Conversation
🖼️ Storybook Visual Comparison Report✅ No Storybook eyes differences detected! |
a4d4e31 to
9f7a8fa
Compare
9f7a8fa to
2589d6f
Compare
897c1d6 to
609554e
Compare
609554e to
06d62ca
Compare
06d62ca to
a7dfb8c
Compare
carl-codeorg
left a comment
There was a problem hiding this comment.
LGTM, just a couple minor comments
| - production | ||
| # | ||
| # Deploys do not come from here. Kargo watches the cdo-rails registry and | ||
| # writes the deployment values files itself. Environments no longer follow |
There was a problem hiding this comment.
Can probably truncate or remove this comment. This system is still pre-production so there's no need to references how things used to work with it.
There was a problem hiding this comment.
Cleaned up the comment.
| CDO_ACTIVE_JOB_BACKEND_ROLLING_RESTART_IN_N_BATCHES: "1" | ||
| # The worker serves no curriculum, so the preload spends memory for | ||
| # nothing. On an empty database it also aborts boot, before schema load | ||
| # can create the table it reads. |
There was a problem hiding this comment.
Comment seems like unnecessary explanation here
There was a problem hiding this comment.
Cleaned up the comment.
| cpu: 1 | ||
| memory: 2Gi | ||
| # Measured on a kind cluster: the daemon plateaus at 3.2Gi. At 2Gi it is | ||
| # OOMKilled about six seconds into boot, in a loop. |
There was a problem hiding this comment.
Does it rarely exceed 3.2Gi? If so we could separate the memory out into a 3Gi request and 4Gi limit to make more efficient use of memory.
There was a problem hiding this comment.
3.2 was the steady state on my local Kind k8s cluster.
The cluster runs one workload. That workload is the ActiveJob worker. Every push to a deploy branch built the 4.13 GB code-dot-org image to run it. cdo-rails is the same Rails source on the same gem set. It is sliced to what the worker reads. Its own workflow already builds and tests it. Stop deploying from this repository. Publish and stop there. Kargo already subscribes to a registry and its four Stages already write the environment values files. A companion k8s-gitops change points it at cdo-rails. Drop the push build and the k8s-gitops job from k8s.yml. Delete k8s-commit-to-kargo-warehouse.yml, which nothing calls now. Drop the paths filter from the publish trigger, so every push to staging builds, verifies and publishes. The dockerignore is the only slice definition. A second list in a workflow would drift from it. Accept a deploy per staging push. The pipeline being replaced already did that on a far larger image, and the worker restarts cleanly. Telling a docker visible change from a commit change is worth doing on its own evidence, not as a rider here. Give the worker room to boot on this image. Running the chart on kind showed the daemon plateaus at 3.2Gi, so 2Gi killed it in a loop. Raise it to 4Gi and skip the curriculum preload, which the worker never serves and which aborts boot on an empty database. The kustomize twin carries both. Split the missing dependency layer by event. A push ends with a notice, because cdo-deps-image will chain this workflow again. Any other event fails. Keep the image side consumer-agnostic. docker/rails/ states a tag contract and names no consumer. Who deploys the image is k8s.yml's business. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
a7dfb8c to
36b45ff
Compare
The Kubernetes cluster runs one workload. That workload is the ActiveJob worker. To run it, every push to
staging,test,levelbuilderandproductionbuilt the 4.13 GBcode-dot-orgimage. Skaffold built it on two native runners. A second job stitched the result. The worker needs Rails, the gem set andbin/delayed_job. It does not need the frontend bundle, the curriculum, the asset pipeline or the build toolchain. Those are most of the 4.13 GB.cdo-railsis that image without the parts the worker never reads. It is 0.40 GB compressed. It holds the same Rails source on the same gem set. Its own workflow already builds and tests it. A pull request runs the full matrix. That matrix is smoke tests on docker and podman across amd64 and arm64. It also boots the image against real MySQL and Redis on both architectures. A publish runs the docker smoke test and the same boot. It runs them against the bytes it is about to push, natively on each architecture. Nothing was wired to it.The change
This repository stops deploying. It builds, verifies and publishes tags, and stops there.
Kargo already knows how to do the rest. The codeai Warehouse subscribes to a registry, and all four Stages already carry promotion templates that clone k8s-gitops, update the environment values file, commit and push, then sync ArgoCD. Today it watches
code-dot-org. A companion pull request points it atcdo-rails.So
cdo-rails-image.ymlbarely changes.k8s.ymlloses its push trigger and its k8s-gitops job, which leaves it close to what it was.k8s-commit-to-kargo-warehouse.ymlis deleted, because nothing calls it any more.The publish trigger loses its
paths:filter, so every push to staging builds, verifies and publishes.docker/rails/Dockerfile.dockerignorealready defines the slice, and only docker matches against it. A filter in a workflow would be a second copy of that list, and the two would drift apart.That means a push changing nothing the image contains still publishes and still deploys. This is what the pipeline being replaced already did, on a much larger image, so it is accepted rather than solved here. The worker restarts cleanly by design. Dropping the redundant deploys needs a way to tell docker-visible change from commit change, which is a change worth making on its own evidence and not as a rider on this one.
Publishing per push grows the registry. GHCR retention already prunes untagged manifests for these images, and the same lever extends to old
git-<sha>tags when it is worth pulling.A lockfile push is the one case the build cannot serve directly. It changes the content key that names the
cdo-depslayer, and that layer does not exist yet.cdo-deps-imageis building it at that moment. So a push with no layer ends the run with a notice and publishes nothing, and thecdo-depschain runs this workflow again once the layer lands. Any other event still fails loudly, because nothing will re-run it.flowchart TB push["push to staging"] --> img["build the image<br/>smoke + boot-verify"] chain["cdo-deps-image chain"] --> img img --> tag["publish git-sha + latest"] tag --> wh["Kargo Warehouse<br/>sees latest's digest"] wh --> fr["Freight"] --> promo["promote a Stage"] promo --> gitops["Kargo writes<br/>deployments/env/values.yaml"] gitops --> argo["ArgoCD → ActiveJob worker"]What now triggers a deploy
cdo-depschaindocker/rails/changes, skaffold on*k8s*branchesThe accepted regression
test,levelbuilderandproductionstop following their branches for this image. Only staging publishes. Promotion between Kargo Stages replaces DTT and DTP for the worker.This is a real change in how those environments advance, and it applies only to the ActiveJob worker. Nothing else runs on the cluster. Note that the Stage graph is not a straight line.
testpromotes fromstaging, and bothlevelbuilderandproductionpromote fromtest.Links
cdo-railsTesting story
The publish path ran on real runners from a dispatch of this branch. Both
build-pushlegs succeeded natively, pushingsha256:d2b0d07b…for amd64 andsha256:69c33243…for arm64, and the merge job combined them. That is the whole pipeline this change ships.actionlint1.7.12 reports no findings on either changed workflow. It ran shellcheck 0.11.0 over therun:blocks. Both files parse underyqand PyYAML.tools/hooks/pre-commitis clean. Nothing references the deleted workflow.helm templateruns on the unchanged chart. The inputs are the live k8s-gitops staging values and the imageghcr.io/code-dot-org/cdo-rails:git-TEST. It renders theactive-job-workerDeployment on that image. The command and args stay unchanged. No other image appears anywhere in the render.ghcr.io/code-dot-org/cdo-rails:latestbootsRAILS_ENV=stagingagainst MySQL and Redis. That is the environment the staging worker actually runs.rails runnerprintsstaging. The result was reproduced in two independent runs. The boot needs 24 config values that the adhoc boot does not. In the cluster the External Secrets Operator syncsstaging/cdo/*into the pod asCDO_*. That is the same source the local boot tried and could not authenticate to.The chart-deployed worker was run on a disposable kind cluster, against the image this branch publishes. That found the two chart values this pull request changes.
OOMKilledin a loop, about six seconds per attempt1/1 Running, 0 restartsSampleJobenqueued from a separate execRUNNINGthenCOMPLETED after 5.0110delayed_jobsrowhost:cdo-active-job-worker-... pid:14, then 0CDO_active_job_queue_adapterin the pod:delayed_job, from the chart's locals ConfigMap throughenvFromSKIP_SCRIPT_PRELOADwas the other finding. Without it the boot runs the curriculum preload, which reads a table the slice does not carry, and on an empty database it aborts beforedb:schema:loadcan create that table.A hypothesis worth recording rather than asserting. The memory the old image needed and the memory this one needs may differ because
cdo-railsships every locale on the i18n load path, where the image it replaces was English-only. That is untested.The subscription was proven end to end on a disposable local cluster running the same Kargo the cluster runs. The warehouse file from the companion pull request was applied byte for byte, and the Warehouse reported
Ready=TruewithSuccessfully discovered artifacts from 1 subscriptionsabout two seconds later. The Freight it created recordedghcr.io/code-dot-org/cdo-railsatsha256:8b85efa7da0dcee1771f4f40725562e27b0a3bdc62d3df4ed88cca6abf32b9da, which is what the registry reports for thelatestindex. A promotion against a local git server then wroteimage: ghcr.io/code-dot-org/cdo-rails@sha256:8b85efa7…into the staging values file and committedPromote staging to sha256:8b85efa7… [skip ci].One path only CI can exercise, which is the notice a push takes when the
cdo-depslayer is missing. It is shaped so that a wrong answer fails the run rather than publishing on a stale dependency layer.Deployment notes
k8s/experimental.yamlnames Seth Nickell as the owner of this area. Merging waits on his review.The companion pull request also moves the subscription to the
Digeststrategy watchinglatest, so Freight is the image that tag resolves to and promotion pins by digest.NewestBuildwould work too.Digestis preferred because it ties Freight to a tag rather than to timestamp behaviour this side makes no promise about. Every staging publish becomes new Freight,stagingtakes it automatically, and the other stages promote on demand.Merge this repository first. Between the two merges nothing moves. Each environment stays on the
code-dot-orgref already pinned in its values file, and the worker keeps running. The cutover happens when the k8s-gitops pull request merges and the firstcdo-railsFreight promotes.Rollback is a revert of the k8s-gitops pull request. The old
code-dot-orgtags stay in the registry.The chart changes in exactly two worker values, both found by running it.
activeJobWorkermemory goes from 2Gi to 4Gi, andSKIP_SCRIPT_PRELOAD=1joins itsextraEnv. The kustomize twin carries the same two. Everything else stays as it is. Per-component image values,cdo-web,cdo-migrate, and the worker's command are untouched. Scope is the worker, and nothing else runs on the cluster today.🤖 Generated with Claude Code