Dagster → VKS (VNG Cloud Kubernetes Service) Adaptation Plan
Status: plan · Branch:
feat/dagster-vks· Date: 2026-10-13 Builds on:dagster-scaling-analysis.md(topology, phases),vks-migration-technical-analysis.md(platform),vks-event-api-migration-runbook.md(VKS gotchas already paid for)
1. Where we are
| Piece | State |
|---|---|
k8s/base/dagster.yaml | One Deployment, dagster dev (webserver + daemon + 9 code locations in one pod), Recreate, no PVC, image customer360-dagster:local. |
k8s/overlays/vks | Rewrites the image to a registry, points c360-config at managed PG/Redis/Kafka/vStorage, adds Ingress. Dagster inherits base unchanged. |
| Instance config | Rendered at start by customer360-backend/scripts/render_dagster_instance.py: PG storage, optional S3 compute logs, QueuedRunCoordinator, run monitoring. No run launcher (runs are in-process subprocesses of the pod). |
| API → Dagster | In-cluster GraphQL dagster:3000 (DAGSTER_GRAPHQL_HOST/PORT). Works as-is on VKS. |
So “adapting Dagster to VKS” is two jobs: (A) make the existing single pod run correctly against managed services (small, do first), then (B) use the cluster for what it is good at — scale and isolate runs (the scaling-analysis Phases 1–3).
2. Defects to fix before anything runs on VKS
These are found by reading the code; none has been run on a VKS cluster yet.
- S3 env-name mismatch.
S3_BLOCKreads{ env: S3_ENDPOINT_URL }, but the k8s ConfigMap (base/config.yaml,overlays/vks/patch-config.yaml) only definesS3_ENDPOINT. The probe (s3_ready) acceptsS3_ENDPOINT, so it can pass and then Dagster fails resolving the unset env var. Fix in the renderer: write the resolved endpoint literally (or fall back toS3_ENDPOINT), and add a unit test. Same fix helps every non-Docker environment. - Renderer is fail-closed, manifests say fail-open. The renderer now exits 1 if PG is
unreachable (and if S3 is down while
DAGSTER_REQUIRE_S3=true), but the header comment ink8s/base/dagster.yamlstill describes SQLite/local fallback, andk8s/README.mdstill saysDAGSTER_HOMEis on a PVC. Update both. On VKS the pod will CrashLoop until PG is right — that is intended; document it. - Dedicated
dagsterDB on managed vDB. The renderer triesCREATE DATABASEbest-effort; a managed-PG app user usually lacks that privilege. Pre-createdagsterin terraform (or the existing db-init Job pattern used fordb_keycloak) rather than relying on the renderer. - S3 credentials.
base/dagster.yamlmapsAWS_*fromMINIO_ROOT_USER/PASSWORD. On VKS those must be the vStorage keys (overlays/vks/secret.env). SetDAGSTER_REQUIRE_S3=truein the vks patch so a bad key stops the pod instead of silently losing logs. - Registry pull.
images:in the vks overlay isREGISTRY.example; add the VCRimagePullSecrets(reuse whatever the event-api runbook settled on) to the Dagster pod spec. - Dagster UI has no auth. Do not put it on the public Ingress as-is. Keep it ClusterIP
(API reaches it in-cluster) and expose only via
kubectl port-forwardor behind the existing oauth2-proxy pattern.
3. Phases
Each phase is independently deployable and reversible. Order follows the scaling analysis (Phase 0 = storage is already done).
Phase V0 — single pod on VKS (the minimum to be live)
- Fix defects 1–6 above (renderer + vks overlay patch
patch-dagster.yaml). - Provision: managed PG with
dagsterDB, vStorage bucket + key, VCR image push (customer360-dagster), one general node group. - Apply
kubectl apply -k k8s/overlays/vks. - Exit criteria: pod Ready; PG storage + S3 compute logs lines in
[render-instance]log; API can submit a job via GraphQL and the run’s logs appear in the bucket; pod restart keeps run history. - Keep
replicas: 1+Recreate(twodagster devpods = duplicate schedule ticks).
Phase V1 — split control plane (webserver ×N, daemon ×1)
- Replace the
dagster devDeployment withdagster-webserveranddagster-daemon(command per scaling-analysis §5). Daemon:replicas: 1,Recreate, never HPA. - Add
dagster instance migrateas an init container / pre-rollout Job (risk #7). - Overlay patch for resources; HPA on the webserver only.
- Gate: no duplicate sensor/schedule ticks across a rollout.
Phase V2 — K8s-native runs (the main VKS payoff)
- Renderer emits
run_launcher: dagster_k8s.K8sRunLauncherwhenDAGSTER_RUN_LAUNCHER=k8s(job_namespace: customer360,env_config_maps: [c360-config],env_secrets: [c360-secrets],image_pull_secrets). Default stays the in-process launcher so local/kind and the Docker vServer deploy are untouched. - Add
dagster-k8sto the Dockerfile wheel list. - RBAC: ServiceAccount + Role/RoleBinding permitting
jobs,pods,pods/login the namespace for the daemon and webserver. - Tag the three implemented jobs (
identity_resolution,segmentation,analytics) withdagster/pool: computeanddagster-k8s/configresources; keepbackend_joblimit of 1. - VKS compute node group (autoscaling min 0 or 1) with a taint/label (
c360.pool: compute); watch the CPU:RAM node-flavour gotcha in scaling-analysis §13.1. - Gate: a CIR run launches as a Job pod on the compute pool, finishes, pod is reaped; PG
connection count stays under the vDB
max_connections(risk #3 — consider PgBouncer). - Prerequisite check: with
python_fileworkspaces, run pods load code from the same image — fine for the single image we ship; confirm with one run before Phase V3.
Phase V3 — gRPC code locations (only if needed)
- Per scaling-analysis §7. Defer until independent deploy/scale of a code location is a real need; six of nine locations are still placeholders.
Out of scope here
Celery pools (Option A), KEDA, per-location images, ingestion/ai node pools — revisit when real
jobs land (scaling-analysis Phase 4).
4. Files expected to change
| File | Change |
|---|---|
customer360-backend/scripts/render_dagster_instance.py | S3 endpoint fix; optional K8s run launcher block; tests |
customer360-backend/Dockerfile | add dagster-k8s |
k8s/base/dagster.yaml | correct stale comments; probes; (V1) split into webserver/daemon |
k8s/overlays/vks/ | patch-dagster.yaml (REQUIRE_S3, imagePullSecrets, resources, nodeSelector), RBAC, no public Dagster ingress |
k8s/README.md | PVC / fail-open statements, VKS section |
| terraform (separate repo/dir) | dagster DB, vStorage bucket/key, node groups |
5. Open decisions
- Dagster UI exposure — port-forward only, or behind oauth2-proxy/Keycloak?
- Jump straight to V2? V0→V1 is cheap, but if run isolation is the goal, V1 can be skipped and
the single
dagster devpod keeps webserver+daemon while only the run launcher changes. (Dagster supportsK8sRunLauncherunderdagster dev; recommended for speed, revisit split when the UI needs HA.) - Compute node group size/flavour and min-nodes (0 saves cost, adds cold start).
- Registry — confirm VCR repo and the pull-secret name already used by
customer360-event-api. - Cutover — Dagster still runs on the vServer UAT/PROD; run both with different
dagsterDBs, or cut over in a window and import history withmigrate_dagster_sqlite_to_postgres.py?