Story 2.7: Deploy and Verify Airflow 3.0 End-to-End
Status: done
Story
As a platform operator, I want the Airflow 3 cutover synced and verified end-to-end on the live (minikube/docker) cluster, so that Airflow 3.0 is proven working before closing Epic 2 (FR8, FR15; SM2, SM3, SM4; architecture D1 step 2).
⚠️ Operator Prerequisites (manual — required before any verification)
This is a live-apply story. Unlike 2.1–2.6 (code-only), it mutates the running cluster and needs host-level access the agent cannot perform:
- Cluster up:
dataminikube profile running (docker driver, k8sv1.33.4, node Ready). If not:cd terraform/main && terraform apply(orminikube start -p data). - Tunnel (real terminal, sudo):
sudo minikube tunnel -p data— must stay running for the whole story. Required because the docker driver's node IP (192.168.49.2) is not host-routable on macOS; the ArgoCDoboukili/argocdprovider andhttps://airflow.databoth need it. [deferred-work: 1.3 / docker switch] - /etc/hosts: add
192.168.49.2 argocd.data airflow.data(re-confirm the IP withminikube ip -p data— it can change on recreate). [deferred-work: minikube IP may change]
The agent will HALT and ask the operator to confirm the tunnel + /etc/hosts are live before running anything that touches the cluster.
Acceptance Criteria
- App Synced/Healthy (SM2, FR8). After
terraform applyprovisions the ArgoCD Project +postgres+airflowApplications and ArgoCD syncs, theairflowapplication is Synced/Healthy and every component is Healthy:api-server,scheduler, standalonedag-processor,triggerer,workers, Redis broker. NoPending/CrashLoopBackOff/ImagePullBackOff. Postgres app also Synced/Healthy. (This completes the deferred Story 1.3 AC#3/#4 GitOps app-sync.) - DB migrate sync-hook ran (FR12/D5). The
migrateDatabaseJob(ArgoCDhook: Sync,useHelmHooks: false) completed successfully (airflow db migrateagainst the metadata DB). No FAB create-user job runs (SimpleAuth;createUserJob.enabled: false). - UI reachable + login (SM3, FR15).
https://airflow.dataloads the Airflow 3 UI and login succeeds via the SimpleAuth admin. The actual admin password mechanism is resolved and documented (theadmin:adminuser is set viasimple_auth_manager_users; the auto-generated password file vs the unusedadmin-passwordsecret key is reconciled — see Task 5). - DAG parses clean (FR15, FR14a-c). The example
ChesterDagDAG (gitSync'd from the public repoairflowsubPath, branchmain) appears in the UI /airflow dags listwith no import or deprecation errors (no AIR301/302-class warnings at parse time). - Manual run succeeds end-to-end (SM4, FR15). A manual trigger of the example DAG completes successfully — scheduler queues it, a Celery worker executes it (broker + result backend working), task state persists to the metadata DB.
- Folded-in runtime checks pass (deferred 2.3/2.4/2.6). Each is verified or its follow-up applied:
- Postgres password (2.6 review, High): confirm the postgres PVC was fresh at apply so
random_password.postgresactually took; if an old PVC carried thedatapassword, recreate the PVC orALTER ROLE mini PASSWORD. Metadata + result-backend auth must succeed. - Redis/broker secrets (2.3): the chart's redis-password/broker-url secrets carry
helm.sh/hook: pre-install; under ArgoCD (no Helm hooks) confirm they exist at steady state and workers reach the broker. - base_url/TLS (2.3):
config.api.base_url=https://airflow.datawith no ingresstls:— confirm UI/redirects behave over the tunnel; if broken, add ingress TLS or adjust base_url and document. - poetry.lock relock (2.4):
poetry lockin a py3.12 env so the lock resolves Airflow 3.2.2 (not 2.8); commit the relocked file. - airflow config lint + imports (2.4):
pre-commit run --hook-stage manual airflow-config-lintand confirmairflow.sdkimports resolve +airflow dags listparses in the Airflow-3 env.
Tasks / Subtasks
- Task 0 — Confirm operator prerequisites (gate) — cluster Running/Ready
v1.33.4; tunnel running;/etc/hostscorrected to127.0.0.1 argocd.data airflow.data(docker tunnel binds localhost, not node IP).datans empty → Bitnami trap moot. - Task 1 — Live apply + ArgoCD sync (AC: 1, 2) — pushed feature branch + merged to
main(ArgoCD source-of-truth);terraform apply(11 add) created secrets + ArgoCD project + apps. BothpostgresandairflowSynced/Healthy; all Airflow 3 components Running; migrate jobDatabase migration done!; no create-user job; no CrashLoop/ImagePull. - Task 2 — Broker/result-backend wiring check (AC: 5, 6) —
airflow-broker-url+airflow-redis-passwordexist at steady state; workerConnected to redis://...@airflow-redis:6379/0, result backendpostgresql://mini:**@postgres-postgresql...,celery@... ready. - Task 3 — UI + login (AC: 3) —
https://airflow.dataUI 200;/api/v2/monitor/healthall-healthy; SimpleAuthPOST /auth/token→ 201. base_url/TLS: functions over tunnel on HTTP (https cosmetic). - Task 4 — DAG parse + manual run (AC: 4, 5) — found + fixed a real bug (
ScheduleArgimport);list-import-errors→ none;my_dag_namelisted; manual runstory27-verify-1→ success (+ auto-scheduled run success). - Task 5 — SimpleAuth admin password reconcile (AC: 3, deferred 2.3) — mechanism resolved + documented (auto-generated ephemeral file; retrieve from api-server logs). Robust pinning deferred (beyond small-fix scope) → deferred-work.
- Task 6 — Python toolchain relock + lint (AC: 6, deferred 2.4) —
poetry lock→ airflow 2.8.2→3.2.2;airflow config lintrun in the live env (remaining findings are upstream chart defaults → deferred-work). - Task 7 — Finalize — pre-commit clean on touched code (isort/black-fixed dag.py); helm-docs no drift; deferred-work updated (folded items RESOLVED + 2 new follow-ups); evidence captured; all committed + pushed to
main. Bonus fix: wiredjwtSecretNameto stop chart-JWT non-determinism → app deterministically Synced/Healthy.
⚠️ Scope boundary: This story is verification + minimal runtime follow-ups (admin password, TLS, poetry.lock). It is NOT a place to re-architect the chart/values. If a defect needs a values.yaml/topology change beyond a small fix, capture it and raise a
correct-courserather than expanding scope.
Dev Notes
Critical guardrails
- Deploy ONLY via ArgoCD (architecture: "deploy only via ArgoCD"). Do not
kubectl apply/helm installthe workload directly. Terraform provisions the ArgoCD Project + Applications + secrets; ArgoCD renders and syncs the chart. Manualkubectlis read-only here except for the explicit AC6 break-glass fixes (PVC/role, which are infra remediation, not workload deploy). - Never re-enable Helm hooks. Jobs use
useHelmHooks: false+argocd.argoproj.io/hook: Sync(D5). If the migrate job didn't run, fix the annotation/sync — don't switch to Helm hooks. - Health on the v2 path. Airflow 3 health is
/api/v2/monitor/health(D7), not the old/health. Ingress routesairflow.data→api-serversvc. - Secret names are a contract. The chart references
airflow-config-credentials,airflow-metadata,airflow-result-backendby exact name (created in 2.1/2.6). Don't rename. - Two-commit rollout (D1) already done — k8s 1.33 (Epic 1) + Airflow-3 cutover (2.1–2.6). This story is the verification of the second. Rollback = revert branch +
terraform apply(cluster is disposable/local, NFR5). - gitSync pulls from the PUBLIC repo (
https://github.com/afranzi/mini-data-platform.git,airflowsubPath, branchmain). The DAG code under test is what's onmain— note: the migrated DAGs (Story 2.4) must be onmainfor gitSync to fetch them. If they're only on the feature branch, the gitSyncbranch/revor the merge state needs confirming (flag to operator).
Known risks pulled into this story (from deferred-work.md)
- [High] Bitnami first-init password trap (2.6 review).
random_password.postgresonly lands if the postgres data dir was empty at apply. Cluster recreate (Story 1.3) likely gave a fresh PVC, but ifterraform applyreuses an existing PVC with the olddatapassword, metadata + result-backend auth fail. Remedy: recreate PVC orALTER ROLE mini PASSWORD '<generated>'(read the generated value fromkubectl -n data get secret airflow-metadata -o jsonpath/ TF state). - [Med] Redis/broker
pre-installhook secrets (2.3). ArgoCD ignores Helm hooks → confirm these secrets still materialize at steady state, else workers can't reach the broker. - [Low-Med] base_url https vs no ingress TLS (2.3). May cause redirect/mixed-content issues over the tunnel.
- [High] SimpleAuth admin password (2.3). The
admin-passwordsecret key is currently unused; the password auto-generates in-pod. Resolve the real login path (Task 5). - [Med] poetry.lock still resolves Airflow 2.8 (2.4). Relock in py3.12. Deployed image is already 3.2.2; this is for local dev/test parity.
Current state (key files — mostly READ-ONLY this story)
terraform/main/12-airflow.tf— finalized in 2.6 (secrets, postgres password, airflow app + depends_on). The liveterraform applyexercises it. [UPDATE only for AC6 break-glass, unlikely]helms/airflow/values.yaml— official-chart config (2.3). [UPDATE only if TLS/admin-password fix needed]airflow/pyproject.toml+poetry.lock— relock target (Task 6).airflow/mini_dags/dags/example.py,mini_dags/chester/dag.py— the DAG under test (2.4). Must be onmainfor gitSync.
Testing standards
- This story's "tests" are live verification: ArgoCD Synced/Healthy, pod readiness, migrate-job success, DAG parse (
airflow dags list→ noimport_errors), and a real DAG run reachingsuccess. Capture command outputs as evidence (no fabricated results — the workflow forbids claiming completion without proof). - Code follow-ups (poetry.lock, any TLS/admin fix) go through
terraform validate/pre-commitas in prior stories.
Previous Story Intelligence
- Branch
feat/airflow-3-k8s-133-upgrade; latest commit305075b(Story 2.6 review). 2.1–2.6 all done. - 2.6 learnings: all secret wiring + postgres password + result-backend done and
terraform validate-clean; the one open runtime risk is the Bitnami first-init password trap (now AC6). Thedb+result-backend prefix is verified correct against the chart template (no double-prefix). - Docker/tunnel gotcha (confirmed): host access to
argocd.data/airflow.dataneedssudo minikube tunnel -p data+/etc/hosts. The ArgoCD provider can't reachargocd.data:443without it (this blocked Story 1.3 AC#3). - Doc gotcha: revert terraform-docs
<br>→<br/>churn after pre-commit. - Tooling:
airflow config lintis a manual-stage pre-commit hook (needs Airflow installed); ruff AIR301/302 needsuvx ruff@latest(system ruff 0.4.10 too old).
References
- [Source: docs/planning-artifacts/epics.md#Story 2.7] — the 4 core ACs (Synced/Healthy, UI+login, DAG parse, manual run).
- [Source: docs/planning-artifacts/architecture.md#D1 Rollout, #D3 Topology, #D5 DB Migration, #D7 Ingress & Health] — deploy-only-via-ArgoCD, v2 health path, sync-hook migrate job, no Helm hooks.
- [Source: docs/implementation-artifacts/deferred-work.md] — all 2.7-tagged items folded into AC6/Tasks 2,5,6 + the 2.6-review Bitnami trap.
- [Source: helms/airflow/values.yaml] — SimpleAuth user, base_url, migrate/create-user jobs, component topology.
- [Source: docs/project-context.md] — minikube docker/tunnel host config, commit/doc conventions.
Dev Agent Record
Agent Model Used
Debug Log References
- Pre-flight (Task 0): cluster
dataRunning, node Readyv1.33.4;sudo minikube tunnel -p datarunning;/etc/hostshas192.168.49.2 argocd.data airflow.data. ArgoCD installed & all pods Running; ingressargocd.data → 192.168.49.2.datanamespace empty (no workloads, no Postgres PVC) → Bitnami first-init password trap (AC6 High) is MOOT (fresh init). terraform plan: clean —Plan: 11 to add, 0 to change, 0 to destroy(5 random_* + 3 kubernetes_secret + argocd project + 2 argocd_application).-
🛑 BLOCKER (GitOps source of truth): The Airflow 3 cutover (Chart.yaml official dep, values.yaml, migrated DAGs from Stories 2.2–2.6) lives ONLY on
feat/airflow-3-k8s-133-upgrade, which is not pushed to origin.origin/main:helms/airflow/Chart.yamlis still the OLD community chart (version 8.8.0,appVersion 2.6.3, depairflow-helm/charts). TheairflowArgoCD app usestarget_revision = "HEAD"(→ origin/main) and gitSync usesbranch: main. Applying now would deploy the OLD Airflow 2.x stack from main — verification (AC1–5) would not exercise Airflow 3. Resolution requires an operator decision (push + merge to main, or point target_revision/gitSync at the pushed feature branch for verification). HALTED pending decision. -
Task 1 —
terraform apply(partial): ✅ all 5random_*+ 3kubernetes_secret(config-credentials, metadata, result-backend) created indata. ❌module.project.argocd_projectfailed:dial tcp 192.168.49.2:443: operation timed out(oboukili/argocd provider →argocd.data:443). - Connectivity root cause (verified): node IP
192.168.49.2:443and:80are unreachable from host (5s timeout each);127.0.0.1:443is OPEN and returns HTTP 200 from ArgoCD withHost: argocd.data. The docker-driverminikube tunnelbinds the ingress to 127.0.0.1, but/etc/hostsmaps the.datahosts to192.168.49.2(node IP, not host-routable on docker). Fix: mapargocd.data/airflow.data→127.0.0.1in/etc/hosts(corrects the stale 192.168.49.2 in the Story 1.3 deferred note). Needs operator sudo. After fixing,terraform applyis idempotent (secrets exist; only the 3 argocd resources remain).
Completion Notes List
- ✅ AC1 —
airflow+postgresArgoCD apps Synced/Healthy; all Airflow 3 components Running (api-server, scheduler, dag-processor, triggerer, worker, redis). Completes the deferred Story 1.3 AC#3/#4 GitOps app-sync. - ✅ AC2 —
migrateDatabaseJob(ArgoCDhook: Sync) ranairflow db migrate→ "Database migration done!"; no create-user job (SimpleAuth). - ⚠️ AC3 (PASS-with-caveat) — UI 200 at
airflow.data(tunnel);/api/v2/monitor/healthall-healthy; SimpleAuth admin login → 201 token (verified). Caveat: the admin password is auto-generated and ephemeral (regenerates on every api-server restart, retrievable from logs); a durable, pinned login is a tracked follow-up (deferred-work). Login is proven to work, but not yet stable across restarts. - ✅ AC4 —
ChesterDag/my_dag_nameparses with no import errors (after fixing theScheduleArgimport bug found here). - ✅ AC5 — manual DAG run
story27-verify-1→ success end-to-end (scheduler → Celery worker → metadata DB). - ✅ AC6 — Bitnami password trap moot (fresh PVC); broker/redis secrets present + worker connected; base_url/TLS functions over tunnel; poetry.lock relocked to 3.2.2; config lint run (remaining = chart defaults).
- GitOps decision (operator-approved): merged feature branch →
mainso ArgoCD (target_revision=HEAD) + gitSync (branch: main) serve the cutover. Rollback = revert + reapply (NFR5). - Two real fixes shipped during verification: (1)
ScheduleArgimport path (DAG parse bug); (2)jwtSecretNamewired to the TF secret (chart was auto-generating a random JWT each render → permanent ArgoCD drift; now deterministically Synced). - Connectivity correction: docker-driver
minikube tunnelbinds ingress to127.0.0.1(not node IP192.168.49.2) —/etc/hostsfixed accordingly.
Review Findings
Adversarial review 2026-06-20 (Blind Hunter + Edge Case Hunter + Acceptance Auditor). AC1/2/5/6 PASS; AC4 PASS (AIR301/302 → "All checks passed"); AC3 PASS-with-caveat (login verified, password ephemeral). 1 patch, 3 deferred, 5 dismissed.
- [Review][Patch] AC3 was overclaimed as unqualified GREEN — corrected to PASS-with-caveat: login verified at deploy time, but the SimpleAuth admin password is ephemeral (regenerates per api-server restart); durable-login pinning is a tracked follow-up.
- [Review][Defer] [Low]
ScheduleArgimported from internalairflow.sdk.definitions.dag— brittle across Airflow 3.x minor bumps [airflow/mini_dags/chester/dag.py:8]. Works on 3.2.2; harden withfrom __future__ import annotations+ TYPE_CHECKING (or try/except) on upgrade. → deferred-work. - [Review][Defer] [Low] JWT-secret rotation rollout gap — rotating
random_password.jwt_secret(TF taint/reapply) invalidates in-flight tokens until api-server+scheduler restart [terraform/main/12-airflow.tf:29]. Inherent to pinning; document rolling-restart on rotation. → deferred-work. - [Review][Defer] [Process] Merging the feature branch →
mainduring a verify story front-runs Epic 3 gates (reproducibility 3.1, rollback rehearsal 3.3). Operator-approved + required for ArgoCD to serve the cutover; note for the Epic 2 retro. → deferred-work.
Dismissed (5): jwtSecretName nesting/key-name "invalid" (Blind High — disproven: chart reads key jwt-secret, TF provides exactly that at 12-airflow.tf:59; live app Synced/Healthy with the chart's own jwt-secret pruned); load_default_connections removal "re-enables example connections" (Blind High — disproven: deprecated since 2.7, Airflow 3 db migrate never auto-loads regardless; Edge-Hunter verified + live platform works); one Secret for fernet/api/jwt keys "conflates rotation" (by design — architecture D4); comment dropped key-name map (false — the new comment retains and extends it with jwt-secret); base_url=https vs no TLS (already verified functional over the tunnel + documented as cosmetic).
File List
airflow/mini_dags/chester/dag.py(modified) — importScheduleArgfromairflow.sdk.definitions.dag(not re-exported atairflow.sdktop level in 3.2.2).helms/airflow/values.yaml(modified) — wirejwtSecretName: airflow-config-credentials(stop chart JWT non-determinism / ArgoCD drift); remove removed-in-3config.database.load_default_connections.airflow/poetry.lock(modified) — relockedapache-airflow2.8.2 → 3.2.2 (py3.12).docs/implementation-artifacts/deferred-work.md(modified) — RESOLVED the folded-in 2.7 items + added 2 follow-ups (SimpleAuth password pinning; chart-default config-lint findings) + corrected the/etc/hostsIP guidance.terraform/main/state/*(state only —terraform applycreated secrets + ArgoCD project/apps; no.tfchanges this story).
Change Log
| Date | Change |
|---|---|
| 2026-06-20 | Story 2.7 created (ready-for-dev). Live deploy-and-verify; folds in all 2.7-deferred runtime checks (Bitnami password trap, redis/broker secrets, base_url/TLS, SimpleAuth admin password, poetry.lock relock, config lint + DAG parse). Operator prerequisites (cluster + sudo minikube tunnel + /etc/hosts) documented as a hard gate. |
| 2026-06-20 | Merged feature branch → main; terraform apply deployed; all ACs verified GREEN on the live cluster (apps Synced/Healthy, migrate done, UI+login, DAG parse + manual run success). Fixed ScheduleArg import + wired jwtSecretName; relocked poetry; corrected /etc/hosts→127.0.0.1. Status → review. |
| 2026-06-20 | Adversarial code review (3 layers): AC1/2/4/5/6 PASS (AIR301/302 clean), AC3 PASS-with-caveat (ephemeral password). Both Blind-Hunter Highs disproven. 1 accuracy patch applied; 3 low/process follow-ups deferred; 5 dismissed. Status → done. |