airflow 2.0.0 — deploy into an existing GVC - #519
Merged
Conversation
Airflow was one of eleven templates that created their own GVC. It did so because KEDA is a GVC-level setting, so the chart wanted to own the GVC to turn it on. That is no longer allowed: a chart that creates a GVC and later stops declaring it makes `helm upgrade` prune it, taking every workload, volumeset and identity inside. Three-layer defence, as in the six conversions before this one: 1. A render-time `fail` when the legacy `gvc` values key is present, tested against the shipped 1.5.0 values file (the real upgrade path), not just `--set`. 2. `defaultOptions.minScale/maxScale: 0` on all four workloads with one complete `localOptions` entry for the configured location, so a GVC location the values do not name runs nothing. 3. A boot-time GVC read in the webserver, scoped `targetKind: gvc` + `targetLinks` to the one install GVC, fatal on a fresh Airflow home and a warning on an initialised one. KEDA becomes an opt-in defaulting to false, because the chart can no longer enable it. Measured: a `metric: keda` workload is accepted into a GVC with KEDA off, never autoscales, and sits at minScale -- at minScale 0 that is zero replicas with no error anywhere. The boot check reports it. Also in this version: `location` replaces `gvc.locations` (Airflow is single-location by construction -- a shared volumeset provisions one volume per location), `airflow.celeryWorker.replicas` sets the worker count with KEDA off, `internalAccess` is new with the ownWorkloadLinks merge, and the webserver, Redis and Postgres tiers are pinned to one replica -- before this they carried no options at all and were eligible to scale out per GVC location. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found by the test round while running the 403 control. The warning told the user
their GVC had KEDA disabled; `cpln gvc get` at that same moment returned
`{'enabled': True}`. The real cause was a missing `view` grant.
A 403 body is valid, non-empty JSON, so the retry loop broke and the script
parsed it. `spec.keda.enabled` is absent from an error body and defaults to
False -- indistinguishable from a genuine `enabled: false`. The location check
already defended against this shape (`[ -z "$GVC_LOCS" ]` -> warn and skip); the
KEDA check had no equivalent.
Fixed by gating BOTH checks on one "the read actually succeeded" flag taken from
the HTTP status (`curl -w`), rather than bolting a second emptiness test onto the
KEDA branch: an error body can be well-formed enough to survive an emptiness
test, and only the status distinguishes it. Non-200 attempts now say so, and the
skip message names the likely cause.
Fails closed either way -- the wrong-but-safe outcome was a refusal to start --
so this is about the diagnostic, which was sending users to the GVC's KEDA
setting when the problem was the identity's grant.
Both rendered arms pass `sh -n` and `bash -n`; the script runs under an explicit
bash shebang, and the `$'\n'` extraction was verified in both shells.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lumes `render-vs-stored` on a live install reported `recoveryPolicy: retain` backfilled on the volume mounts of the postgres and redis workloads -- a permanent diff in the deployment UI from the moment they are created. The webserver and celery mounts already matched, so this was two lines, not a pattern. Declared rather than normalized away in the checker: that list is the record of what we have accepted, and a wrong entry blinds the gate permanently. Verified on real infrastructure: 0 unexplained differences afterwards, and the drift gate is fully `Unchanged` by the second no-op upgrade with the gate still clean after both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Review diff:
git diff ebe78a1..HEAD -- airflow/versions/2.0.0/(
ebe78a1is the verbatimcp -Rof 1.5.0; everything real is in the commits after it.)Why a major version
The chart no longer creates a GVC. The gate holds: the in-place
helm upgradefrom 1.5.0 with 1.5.0's real values file was refused at render before any resource was touched.Two product changes, both forced by the conversion
1. Single location, by construction. A
sharedvolumeset provisions one volume per location, so two locations would mean two Airflow homes that cannot see each other's DAGs or logs, while the metadata DB and broker are each one volume on one replica.gvc.locationstherefore became a singlelocationstring, and the default installs on a one-location GVC. The scheduler, dag-processor and triggerer share the webserver container pinned to 1 replica; only Celery workers scale.2. KEDA is now opt-in, default
false— it is a GVC-level setting the chart can no longer set. This is a change from 1.x, and the reason is measured rather than cautious: ametric: kedaworkload in a KEDA-disabled GVC is accepted silently and sits atminScale— at 0 that is zero replicas,desiredScale: None, no versions and no message. A dead install with no diagnostic surface anywhere.A latent multi-instance bug, fixed
In 1.5.0 the webserver and Redis carried no options at all, so on a multi-location GVC they would have started one instance per location — N schedulers, N brokers, N split queues. The
0/0 + localOptionspattern now confines every tier, verified live.A defect testing found, and the control that found it
While running the 403 control, the boot check warned "GVC 'test-gvc' has spec.keda.enabled = false" — while
cpln gvc getreturned{'enabled': True}at that same moment. The real cause was a missingviewgrant.A 403 body is valid, non-empty JSON, so the retry loop broke and the parse succeeded;
spec.keda.enabledis absent from an error body and defaults tofalse, indistinguishable from a genuinefalse. The location check already defended against this shape; the KEDA check did not.Fixed by gating both checks on one "the read actually succeeded" flag from the HTTP status, rather than bolting a second emptiness test onto the KEDA branch — an error body can be well-formed enough to survive an emptiness test, and only the status distinguishes it. Verified on both arms:
Worth reading: the first attempt at that verification failed to reproduce — the policy was deleted and the workload redeployed immediately, and the read still succeeded. That is the ~4-minute authorization edge-cache. Waiting it out and redeploying produced the 403 arm. A single probe after a policy change is inconclusive in either direction, and here it would have read as "the fix does not work".
Testing
16 rows, 0 outstanding FAIL. Proven on real infrastructure: a DAG executing end to end on a Celery worker; the boot GVC read returning 200 from the real endpoint with the 403 control; both KEDA arms (hard-fail against a KEDA-off GVC, then real autoscaling once enabled); the multi-instance confinement; the wrong-
locationsignal; and the fresh-vs-established asymmetry on both arms.render-vs-stored0 unexplained differences — two real backfills (recoveryPolicy: retainon the postgres and redis volume mounts) were declared in the chart, not normalized away in the checker. Drift gate fullyUnchangedby the second upgrade, with the gate still clean afterwards.Probes that settled design questions before build
localOptions[].autoscalingaccepts akedablock, stored verbatim — the only reason KEDA and the 0/0 pattern can coexist.targetundermetric: kedais rejected at apply (target is not allowed when metric is 'keda') and is invisible tohelm template.Two things for you
cpln applyREPLACES a GVC'sspec— applying a file containing onlyspec.kedawipedlocationLinksandendpointNamingFormatfromtest-gvcduring the build. Restored and verified byte-for-byte, and now recorded in CLAUDE.md. The safe form is a-o yaml-slimround-trip. This is the opposite of the workload PATCH endpoint, which deep-merges.locationnames a location the GVC lacks, every workload gets zero replicas, so no container runs and the boot check cannot report it. The honest signal is "the release deployed and nothing started", which the README and values comment both state. Closing it would cost an unpinned guard workload for the life of the install.Untested
The 1.x → 2.0.0 data move. Only tool availability was verified (
pg_dump/pg_restoreinpostgres:18,tarin the airflow image,cpln workload exec --stdin); the README's steps were read against the chart but the piping itself is unproven, and both the README and briefing say so.Briefing updated.
🤖 Generated with Claude Code