Skip to content

Commit 7b834f0

Browse files
authored
Merge pull request #4 from ding-labs/fix-argo-recipe-downward-api
fix(recipes): add downward API for Argo's wait-sidecar-only env vars
2 parents 8668f27 + 63c638e commit 7b834f0

1 file changed

Lines changed: 28 additions & 10 deletions

File tree

docs/recipes/argo-workflows.md

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,19 @@ spec:
9393
- secretRef:
9494
name: ding-secrets
9595
env:
96-
# Argo controller auto-injects ARGO_TEMPLATE, ARGO_WORKFLOW_UID,
97-
# ARGO_WORKFLOW_NAME, ARGO_NODE_ID, ARGO_POD_NAME. Only POD_NAMESPACE
98-
# needs explicit downward API.
96+
# Argo's controller auto-injects ARGO_TEMPLATE, ARGO_NODE_ID, and
97+
# ARGO_CONTAINER_NAME on the main container — but injects the rest
98+
# of the ARGO_* set (ARGO_WORKFLOW_UID, ARGO_WORKFLOW_NAME,
99+
# ARGO_POD_NAME) ONLY on the auxiliary `wait` sidecar. We restore
100+
# the missing pieces on `main` via the downward API. ARGO_WORKFLOW_UID
101+
# isn't recoverable on main; runctx falls back to ARGO_WORKFLOW_NAME
102+
# for run_id (see Configuration below).
99103
- name: POD_NAMESPACE
100104
valueFrom: { fieldRef: { fieldPath: metadata.namespace } }
105+
- name: ARGO_POD_NAME
106+
valueFrom: { fieldRef: { fieldPath: metadata.name } }
107+
- name: ARGO_WORKFLOW_NAME
108+
valueFrom: { fieldRef: { fieldPath: "metadata.labels['workflows.argoproj.io/workflow']" } }
101109
volumeMounts:
102110
- { name: ding-bin, mountPath: /shared }
103111
- { name: ding-config, mountPath: /config, readOnly: true }
@@ -118,12 +126,14 @@ This is the wedge's headline use case in Argo: silent step failure inside a mult
118126

119127
| Label | Source |
120128
|---|---|
121-
| `run_id` | `ARGO_WORKFLOW_UID` (Workflow CR's K8s UID); falls back to `ARGO_WORKFLOW_NAME` |
129+
| `run_id` | `ARGO_WORKFLOW_NAME` via downward API on the `workflows.argoproj.io/workflow` pod label. Argo injects `ARGO_WORKFLOW_UID` only on the `wait` sidecar, so runctx's UID-then-NAME fallback chain lands on the workflow name. |
122130
| `runner` | `"argo-workflows"` (set by runctx) |
123-
| `workflow` | `ARGO_WORKFLOW_NAME` (parent Workflow CR name) |
124-
| `node` | `ARGO_NODE_ID` (per-step DAG node identifier) |
125-
| `pod` | `ARGO_POD_NAME` (step's pod name) |
126-
| `namespace` | `POD_NAMESPACE` (downward API `metadata.namespace` — must be declared in env block; not in Argo's auto-injected set) |
131+
| `workflow` | `ARGO_WORKFLOW_NAME` via downward API (same as `run_id` source) |
132+
| `node` | `ARGO_NODE_ID` — auto-injected by Argo on the main container, no downward API needed |
133+
| `pod` | `ARGO_POD_NAME` via downward API on `metadata.name` |
134+
| `namespace` | `POD_NAMESPACE` via downward API on `metadata.namespace` |
135+
136+
> **Why so much downward API?** Argo's controller injects only `ARGO_TEMPLATE`, `ARGO_NODE_ID`, and `ARGO_CONTAINER_NAME` on the step's main container. The richer set (`ARGO_WORKFLOW_UID`, `ARGO_WORKFLOW_NAME`, `ARGO_POD_NAME`) is hardcoded by the controller onto the auxiliary `wait` sidecar only. The recipe's `env:` block uses the K8s downward API to surface workflow name, pod name, and namespace to the main container so runctx can populate the labels above. `ARGO_WORKFLOW_UID` cannot be recovered on the main container — the controller writes it as a literal env value on the `wait` sidecar with no corresponding pod label or annotation; runctx's UID-then-NAME fallback handles this gracefully.
127137

128138
A self-hosted CI runner (GitHub Actions, GitLab CI, etc.) deployed on Argo-managed Kubernetes will set both its CI env vars *and* `ARGO_TEMPLATE`. In that case `runctx` reports the CI platform — its labels are richer for alerting purposes — and the Argo labels are skipped. See [Configuration](../configuration.md) for the full notifier reference.
129139

@@ -179,8 +189,15 @@ spec:
179189
- "{{inputs.parameters.cmd}}"
180190
envFrom: [{ secretRef: { name: ding-secrets } }]
181191
env:
192+
# See Minimal example for why ARGO_POD_NAME and ARGO_WORKFLOW_NAME
193+
# need explicit downward API entries (Argo only injects them on
194+
# the `wait` sidecar, not on the main container).
182195
- name: POD_NAMESPACE
183196
valueFrom: { fieldRef: { fieldPath: metadata.namespace } }
197+
- name: ARGO_POD_NAME
198+
valueFrom: { fieldRef: { fieldPath: metadata.name } }
199+
- name: ARGO_WORKFLOW_NAME
200+
valueFrom: { fieldRef: { fieldPath: "metadata.labels['workflows.argoproj.io/workflow']" } }
184201
volumeMounts:
185202
- { name: ding-bin, mountPath: /shared }
186203
- { name: ding-config, mountPath: /config, readOnly: true }
@@ -191,7 +208,7 @@ When `train` exits 1, DING in the `train` step's pod fires the alert; `eval` is
191208
- `workflow=<wf-name>` (shared across all three steps)
192209
- `node=<wf>-train-<random>` (distinct per step)
193210
- `pod=<wf>-train-<random>-<random>` (distinct per step; contains the step name as substring)
194-
- `run_id=<wf-uid>` (shared across all three steps — identifies the Workflow run, not the step)
211+
- `run_id=<wf-name>` (shared across all three steps — identifies the Workflow run; runctx falls back to the workflow name because `ARGO_WORKFLOW_UID` isn't available on main containers)
195212

196213
**Per-step matching is constrained.** DING's `match.labels` does exact-match comparison, and Argo's `node`/`pod` values are dynamic per Workflow run. You can't write a `match.labels: { node: my-train-step }` rule that matches "the train step." Pragmatic patterns:
197214

@@ -245,6 +262,7 @@ If the alert doesn't fire, common issues: the Secret wasn't readable (RBAC on th
245262

246263
## Tradeoffs / known limitations
247264

265+
- **Argo injects `ARGO_WORKFLOW_UID`, `ARGO_WORKFLOW_NAME`, and `ARGO_POD_NAME` only on the `wait` sidecar.** The main container (where DING runs) only gets `ARGO_TEMPLATE`, `ARGO_NODE_ID`, and `ARGO_CONTAINER_NAME` from the controller. The recipe's downward-API env block restores workflow name and pod name; `ARGO_WORKFLOW_UID` isn't available on the main container at all (no corresponding pod label/annotation), so runctx falls back to `ARGO_WORKFLOW_NAME` for `run_id`. Workflow name is stable across retried pods of the same step — good for dedup, but not globally unique like the UID would be.
248266
- **Template name not auto-labeled.** `runctx` doesn't parse `ARGO_TEMPLATE` JSON. Surface template name manually via downward API on the `workflows.argoproj.io/template` pod label if needed (see [Surfacing the template name](#surfacing-the-template-name-manual)).
249267
- **Per-step matching is constrained.** Parallel/fan-out steps share `run_id`; `node` and `pod` are dynamic per run, so DING's exact-match `match.labels` can't pre-target a specific DAG step's `run.exit`. Disambiguate in the message template, or emit step labels yourself for during-run rules.
250268
- **`onExit` template alerts get a different `node`.** If you put DING in an `onExit` template instead of (or in addition to) the main step, its alerts have a different `node` label than the failed step they're reporting on, breaking per-step matching for users who copy-paste rules from the K8s recipe.
@@ -257,7 +275,7 @@ This recipe is **a Tier-2 candidate** by the program's standard rubric:
257275

258276
- **Setup commands required:** 1 (`kubectl apply`) — under threshold of 5
259277
- **Boilerplate lines:** ~95 minimal + ~50 DAG subsection ≈ ~145 YAML — over threshold of 50 → **Tier-2 candidate**
260-
- **"Gotcha" callouts:** 5 — over threshold of 2 → **Tier-2 candidate**
278+
- **"Gotcha" callouts:** 6 — over threshold of 2 → **Tier-2 candidate**
261279
- **End-to-end runnable:** yes (kind + open-source Argo controller; ~3-5min cold)
262280

263281
**Tier-2 candidate.** The boilerplate count is the structural problem — both the minimal manifest and the DAG subsection are mostly mechanical plumbing (volumes, initContainers, downward API env block) that every Argo user will copy verbatim. An `argo-workflow-template` repo (separate, mirroring [`ding-k8s-job`](https://github.com/ding-labs/ding-k8s-job)) that publishes a parameterized `WorkflowTemplate` to GHCR — invoked via `argo submit --from workflowtemplate/ding-step --parameter image=my-app --parameter command='python train.py' --parameter slack-url=$SLACK_WEBHOOK_URL` — would collapse the recipe to a one-line invocation. Defer the chart until 2+ users ask.

0 commit comments

Comments
 (0)