ActiveJob-only k8s deployment, thin base image#73764
Conversation
Signed-off-by: Nick Lathe <nick.lathe@code.org>
Planning artifacts only, no implementation. Six OpenSpec changes covering the docker image rework (rails-way multi-stage targets, thin runtime composition), the k8s process model, secrets rotation, a devcontainer, and CI seeded-DB snapshots. Each change has proposal/design/specs/tasks and validates with `openspec validate`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
||
| if [ "$IMPORT_IMAGE_TO_K8S_NODE" = true ]; then | ||
| K8S_NODE_CONTAINER=${K8S_NODE_CONTAINER:-desktop-control-plane} | ||
| docker save "$IMAGE_REPO:$image_id" | docker exec -i "$K8S_NODE_CONTAINER" ctr -n k8s.io images import - |
There was a problem hiding this comment.
There is a bit of confusion with image ids. Here you have one format, but skaffold also produces its own on skafffold.yml L158.
| value: k8s/helm/activejob-only.values.yaml | ||
| - op: replace | ||
| path: /deploy/helm/releases/0/setValueTemplates/image | ||
| value: "code-dot-org:{{.IMAGE_TAG_code_dot_org}}" |
There was a problem hiding this comment.
So this traces back to skaffold, but docker-buildx has its own tagging system. (See other comment in that file). Do we need two tags?
There was a problem hiding this comment.
Actually I'm not sure about this - I'll look into it and see if this is also causing the need for the workaround to import images into k8s.
There was a problem hiding this comment.
FYI - skaffold dev seems to fail because:
bundler: command not found: rerun
Install missing gem executables with `bundle install`
There was a problem hiding this comment.
I'm not getting this from a generic skaffold dev but will look into it. Did you just to skaffold dev with no options?
There was a problem hiding this comment.
This is on the logs for cdo-dashboard.
| --build-arg BUNDLE_JOBS=1 \ | ||
| --build-arg BUNDLE_WITHOUT=development:test:production:adhoc:staging:levelbuilder:integration \ | ||
| --build-arg SKIP_FRONTEND_BUILD=1 | ||
| - op: add |
There was a problem hiding this comment.
This seems to produce a duplicate cdo-dashboard deployment on top of skaffold
| if [ "$PUSH_IMAGE" != true ] || [ "$IMPORT_IMAGE_TO_K8S_NODE" = true ]; then | ||
| image_id=$(docker image inspect --format '{{.Id}}' "$IMAGE" | sed 's/^sha256://') | ||
| fi | ||
|
|
||
| if [ "$PUSH_IMAGE" != true ]; then | ||
| docker tag "$IMAGE" "$IMAGE_REPO:$image_id" | ||
| fi |
There was a problem hiding this comment.
I'm a little confused as to the purpose of these
There was a problem hiding this comment.
The point of this was to make the image accessible to the local k8s cluster using Docker Desktop. Even with imagePullPolicy set to never it was never recognizing the locally-build image, so this code imports it into the k8s image store. I can take another stab at getting the k8s to be able to see the local Docker image store, which would allow us to get rid of this workaround.
There was a problem hiding this comment.
If it helps scope, we can probably drop dev completely from skaffold and docker?
There was a problem hiding this comment.
I don't think we want to drop it completely from both - but there is potentially a middle-ground where we use a simpler docker-compose workflow for dev. Local k8s, even the build-in Docker Desktop feature, adds a ton of overhead.
|
|
||
| # Copy in the rest of the source code | ||
| COPY --chown=${UID} --link ./ ./ | ||
| # Temporary locals.yml used to bootstrap build-time optimizations. |
There was a problem hiding this comment.
Let's tag it with a followup to ensure we don't make it permanent.
There was a problem hiding this comment.
I'll see if I can get an agent to fix this without the temp locals.yml.
There was a problem hiding this comment.
What's the purpose of all these keep files?
There was a problem hiding this comment.
These are for the "mimic" functionality that Seth built in for local k8s experimentation. The mimic cluster basically runs dummy images that don't have any of the real app content. You can run skaffold build -p mimic for a faster build compared to building the real Docker images. I also wondered about these keep files, and Codex says to keep them - so that the docker COPY steps continue working for directories that would be gone if there were no files for Git to store.
This is something we could look into removing if we get image sizes/build times down to the point where we can dev using the real images.
Summary
This PR adds an ActiveJob-only Skaffold deployment path and restructures the k8s Docker image lineage around a slim official Ruby base image.
The image hierarchy now follows the Rails-style split:
base: runtime OS packages only, fromruby:3.2.11-slim-bookwormbuild: compiler/toolchain layer used for bundle, uv, and frontend build workruntime: non-root production image with built artifacts and no dev toolchaindev: same base plus local development tools such as build-essential, Node, Chromium, zsh, and awscliIt also adds an
activejob-onlySkaffold profile that builds a smaller runtime image with the frontend build skipped and a narrower Bundler group set. This gives us a working path for ActiveJob experiments without carrying the full frontend asset build through each iteration. This PR doesn't get a cold Skaffold build to complete within the 20-minute goal, but I figured we should start committing some of these base changes first before continuing to iterate.The changes to the base image were generated using Codex 5.5 following an OpenSpec design from Fable 5.
Details
ruby:3.2.11-slim-bookworm.libjemalloc2and an arch-independentLD_PRELOADpath.devtarget mutable.bootsnapout of the development-only Gemfile group and precompiles bootsnap caches during image build.skaffold build -p mimic --cache-artifacts=falsestill validates Dockerfile changes.uv, missing JS runtime behavior for ActiveJob, and zsh assumptions in container commands.Changes to the main Dashboard app
Most of this PR is scoped to the
k8sdirectory, making it very safe in general. However, there are two changes outside of k8s:Bootsnap moved out of dev dependencies in the Gemfile. If there is a reason we need to keep this as a dev dependency, let me know. Otherwise, it seems the modern Rails way is to use Bootsnap to speed up the boot process in production. The Rails-generated Dockerfiles do this. Summary of what we get from using bootsnap outside of dev:
development.rband the Prosopite initializer changed to check forGem.loaded_specs.key?('prosopite'). This avoids situations where the development Rails config is loaded, but the prosopite gem is missing because ofBUNDLE_WITHOUT=development:testbeing set during Docker build.Validation
skaffold dev --trigger=manual -p activejob-only -p setup-db-minimalskaffold build --cache-artifacts=falseskaffold build -p mimic --cache-artifacts=falsebundle installwith native-extension coverage and one dashboardspring testunitdivesize comparison recordedopenspec validate docker-rails-way-image --strictgit diff --check./tools/hooks/pre-commitNotes
The generic cold
skaffold build --cache-artifacts=falsecompleted, but build time is still not acceptable long-term. Measured wall clock was8223.02s; most of the time was spent in Bundler, especially arounddevise_invitable. This PR accepts that as follow-up work because the new image structure is functional and compatible with the existing Skaffold workflow.Ruby 3.2 is still past upstream EOL. This PR pins the slim image digest and makes a later Ruby 3.4 upgrade much smaller, but it does not perform that upgrade.
Image size improvements from switching to a ruby-slim base instead of Ubuntu:
code-dot-orgcode-dot-org-coreactive-job-onlyLinks
Testing story
Deployment notes
Privacy and security