[SIMPL-30451] Execute T6 and T7 on the sandbox

Adds the k8s_job_executor probe and records the result. Six step Jobs were created, one per step, and four steps executed and succeeded against the platform's live Postgres. The run then failed before summarise_results: run monitoring polls for a dagster-run-<id> Job that a hand-rolled probe never creates, and with no launcher-supplied container context it looked in the launcher's default namespace, where dagster-svc-account is denied jobs/status. Two step pods had already started and logged 'Skipping step execution' before exiting 0, which is why all six Jobs read Complete while only four steps ran.

Changelog: added
This commit is contained in:
ILay
2026-08-31 22:10:12 +02:00
parent 3f8175a17f
commit 64cace2952
2 changed files with 203 additions and 17 deletions

View File

@@ -43,12 +43,8 @@ location image.
### 2.1 Sandbox state, 2026-08-31 ### 2.1 Sandbox state, 2026-08-31
The T checks were assessed against sandbox-cat-dat by reading the live The T checks were assessed against sandbox-cat-dat by reading the live
`dagster-instance` ConfigMap in `dataprovider01` and by running T1 and T4 from a `dagster-instance` ConfigMap in `dataprovider01` and by running T1, T4, T6 and T7
pod there. T6 and T7 could not be executed: the tightly coupled Kubernetes path from pods there.
needs this code location registered on the platform Dagster, which is blocked on
the control plane skew, and unlike the pipes transport it cannot be exercised
from a self-contained pod — every step pod connects to the metadata database on
its own, so they must share instance storage rather than a local SQLite file.
What the configuration shows: What the configuration shows:
@@ -86,19 +82,48 @@ So `TIGHTLY_COUPLED_ENV_VARS` in `preflight.py` names variables Simpl does not
use, and the check reports a misconfiguration that is not one. The reachability use, and the check reports a misconfiguration that is not one. The reachability
probe above is the part that carries real evidence. probe above is the part that carries real evidence.
### Why T6 and T7 remain unexecuted ### T6 and T7, executed 2026-08-31
The run made the obstacle concrete. Step outputs went to Running them needed three things the earlier attempt lacked: dagster pinned to
`/dagster-home/storage/<run-id>/…` via `PickledObjectFilesystemIOManager`. Under 1.12.8 to match the control plane, so writing to the live `dataprovider01_dagster`
`k8s_job_executor` each step is its own pod with its own volume, so a downstream could not trigger a migration; `DAGSTER_HOME` moved onto the RWX
step cannot read an upstream output. Running it needs both a shared I/O manager — `dagster-shared-pvc`, because the default I/O manager writes step outputs under
S3, or the RWX `dagster-shared-pvc` the launcher already mounts at `$DAGSTER_HOME/storage` and each step pod otherwise gets its own empty volume;
`/dagster/shared` — and shared instance storage, which means Postgres. and `automount_service_account_token: true` in `step_k8s_config`, because
`dagster-svc-account` disables it by default. The probe is
`yaml/sandbox/probe-k8s-executor-sandbox.yaml`; the run is
`d05c736a-8ec7-4e59-8cb0-9cb238db40dc`.
The only Postgres available is the platform's live `dataprovider01_dagster`, and | # | Result |
these images carry dagster 1.13.19 against a 1.12.8 control plane. Pointing a |---|---|
newer Dagster at an older schema is not a safe thing to try on a shared database. | T6 | **Passes.** Six Jobs labelled `dagster/job=tightly_coupled_k8s_job` were created, one per step, each in its own pod |
Pin dagster to the control plane version and rebuild first. | T7 | **Passes.** No step hung in `STARTING` and no step reported a connection timeout to port 5432. Every step pod opened the platform Postgres itself — the outputs and inputs below were all recorded through it |
Four steps executed and succeeded: `generate_work_units` (four dynamic outputs),
`report_execution_target`, and `process_work_unit[unit_0]` and `[unit_1]`, each
loading its input from the shared volume that a different pod wrote.
**The run then failed, and `summarise_results` never started.** The cause is a
property of the probe, not of the code or the platform:
> `Detected run worker status UNKNOWN: DagsterK8sUnrecoverableAPIError … jobs.batch`
> `"dagster-run-d05c736a-…" is forbidden: User "system:serviceaccount:dataprovider01:`
> `dagster-svc-account" cannot get resource "jobs/status" … in the namespace "dagster"`
Run monitoring polls for a run worker Job named `dagster-run-<run-id>`. The probe
starts the run with `dagster job execute` from a hand-rolled Job, so no such
object exists; and with no launcher-supplied container context the monitor looked
in the launcher's default namespace, `dagster`, where `dagster-svc-account` holds
no rights. The 403 turned *absent* into *unknown*, and the monitor failed the run
at its first poll. `process_work_unit[unit_2]` and `[unit_3]` had their pods
started already; both logged `Skipping step execution … since the run is in status
DagsterRunStatus.FAILURE` and exited 0 — which is why all six Jobs read `Complete`
while only four steps ran.
A run submitted the normal way, through the webserver to `K8sRunLauncher`, gets a
`dagster-run-<run-id>` Job in `dataprovider01` and is not exposed to this. Closing
T6/T7 end to end, including `summarise_results`, needs the code location
registered on the platform Dagster.
**C6 passes, contrary to what `job_namespace` alone suggests.** The launcher's **C6 passes, contrary to what `job_namespace` alone suggests.** The launcher's
`job_namespace: dagster` is only a default. Each code location ships a `job_namespace: dagster` is only a default. Each code location ships a

View File

@@ -0,0 +1,161 @@
# T6/T7 probe - tightly coupled via k8s_job_executor, one pod per step.
#
# This is the invasive one. Unlike the pipes probe it writes to the platform's
# live metadata database, because that is what tightly coupled means: every step
# pod opens the database itself, so they must share instance storage. Safe only
# because the images are pinned to dagster 1.12.8, exactly matching the control
# plane, so no schema migration can be triggered.
#
# Two things the loosely coupled probe did not need:
#
# 1. Shared instance storage. DAGSTER_HOME is copied from the platform's
# `dagster-instance` ConfigMap into a writable volume, so run and event
# storage are the same Postgres the platform uses. Step pods get the same
# config via the run launcher's `instance_config_map`.
#
# 2. A shared I/O manager. The default writes step outputs to pod-local disk,
# which under this executor means a downstream step cannot read an upstream
# output. base_dir is redirected to the RWX `dagster-shared-pvc` the
# launcher already declares.
#
# The run config lives in its own ConfigMap rather than a heredoc inside the
# container command. A heredoc nested in a YAML block scalar silently produced an
# empty file on the first attempt, and an empty run config is valid YAML, so the
# run started and only failed later with "No image included in either executor
# config or the job" - a confusing distance from the actual cause. The command
# echoes the config before using it so that failure mode cannot recur silently.
#
# --- Running ---------------------------------------------------------------
#
# kubectl -n dataprovider01 apply -f yaml/sandbox/probe-k8s-executor-sandbox.yaml
# kubectl -n dataprovider01 logs -f job/distexec-k8sexec-probe
# kubectl -n dataprovider01 get jobs -l dagster/job=tightly_coupled_k8s_job # T6
#
# --- Teardown --------------------------------------------------------------
#
# kubectl -n dataprovider01 delete job distexec-k8sexec-probe
# kubectl -n dataprovider01 delete jobs -l dagster/job=tightly_coupled_k8s_job
# kubectl -n dataprovider01 delete cm distexec-probe-run-config
#
# The run itself stays in the platform's Dagster database and will be visible in
# its UI. That is not reversible with kubectl.
apiVersion: v1
kind: ConfigMap
metadata:
name: distexec-probe-run-config
namespace: dataprovider01
labels:
app.kubernetes.io/name: distributed-execution
app.kubernetes.io/component: probe
data:
run-config.yaml: |
execution:
config:
job_namespace: dataprovider01
job_image: gitea.dataprovider01.sandbox-cat-dat.simpl-europe.eu/j.r/distributed-execution:3f8175a17f0a
service_account_name: dagster-svc-account
image_pull_policy: IfNotPresent
max_concurrent: 2
# Step pods inherit DAGSTER_HOME from the launching process otherwise,
# which points at a volume only that pod has.
env_vars:
- DAGSTER_HOME=/dagster/shared/distexec-probe/home
volumes:
- name: dagster-shared-storage
persistent_volume_claim:
claim_name: dagster-shared-pvc
volume_mounts:
- name: dagster-shared-storage
mount_path: /dagster/shared
step_k8s_config:
pod_spec_config:
automount_service_account_token: true
---
apiVersion: batch/v1
kind: Job
metadata:
name: distexec-k8sexec-probe
namespace: dataprovider01
labels:
app.kubernetes.io/name: distributed-execution
app.kubernetes.io/component: probe
spec:
backoffLimit: 0
activeDeadlineSeconds: 1800
template:
metadata:
labels:
app.kubernetes.io/name: distributed-execution
app.kubernetes.io/component: probe
spec:
restartPolicy: Never
serviceAccountName: dagster-svc-account
automountServiceAccountToken: true
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: runner
image: gitea.dataprovider01.sandbox-cat-dat.simpl-europe.eu/j.r/distributed-execution:3f8175a17f0a
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
env:
# On the RWX volume, not a pod-local one: the default I/O manager writes
# step outputs under $DAGSTER_HOME/storage, and every step pod has to
# read what the previous one wrote.
- name: DAGSTER_HOME
value: /dagster/shared/distexec-probe/home
# How the chart tells a code location which image its steps run as.
- name: DAGSTER_CURRENT_IMAGE
value: gitea.dataprovider01.sandbox-cat-dat.simpl-europe.eu/j.r/distributed-execution:3f8175a17f0a
- name: DAGSTER_PG_PASSWORD
valueFrom:
secretKeyRef:
name: dagster-postgresql-secret
key: postgresql-password
command:
- sh
- -c
- |
set -e
mkdir -p "$DAGSTER_HOME"
cp /instance-cm/dagster.yaml "$DAGSTER_HOME/dagster.yaml"
echo "===== run config in use ====="
cat /run-config/run-config.yaml
echo "===== T6/T7: tightly_coupled_k8s_job ====="
dagster job execute -f src/distributed_execution/repository.py -j tightly_coupled_k8s_job -c /run-config/run-config.yaml
volumeMounts:
- name: instance-cm
mountPath: /instance-cm
readOnly: true
- name: run-config
mountPath: /run-config
readOnly: true
- name: dagster-shared-storage
mountPath: /dagster/shared
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
volumes:
- name: instance-cm
configMap:
name: dagster-instance
- name: run-config
configMap:
name: distexec-probe-run-config
- name: dagster-shared-storage
persistentVolumeClaim:
claimName: dagster-shared-pvc