Version v0.5.5 of the documentation is no longer actively maintained. The site that you are currently viewing is an archived snapshot. For up-to-date documentation, see the latest version.
ADR 0085: the control plane reads task ServiceAccounts for the register dry run
Status: Accepted Date: 2026-10-16 Relates: ADR 0041 (deploy without a pipeline, cluster wiring), ADR 0049 (split API and scheduler roles), ADR 0063 (executor policy), ADR 0068 (patch content gated by safety) Issues: #1889, #1705
Context
The deploy preflight (#1705) stops a deploy in seconds, before the build, on
anything the server or the registry would refuse. Two failures stayed out of
its reach because nothing on the deploy path could see them: a task
ServiceAccount that does not exist in the task namespace, and one that
carries no pull secret for a private registry. Both surface only when a task
pod starts, after the version has registered, as a pod creation refusal or
as ErrImagePull.
The CLI cannot see the cluster: a deploy from CI rarely has a kubeconfig, and
one that has may point at another cluster. The control plane can, but its
Role in the task namespace granted only what the executor calls (pods, their
logs, the staging PVC and the warm pool anchor ConfigMap), checked both ways
by scripts/rbac-covers-executor.sh. A check of the ServiceAccount needs a
new permission, which is a decision about the control plane’s privileges,
not only about the deploy.
Decision
- The executor’s Role grants
getonserviceaccountsin the task namespace. One verb on one resource: nolistorwatch, so the control plane cannot enumerate the accounts, and nothing onsecrets, so it never reads the pull secrets an account names. It reports how many there are, not their names. - Only the all role holds it. The rule renders when
split.enabledis off. In a split control plane the Role is bound to the scheduler, which does not serve the dry run, and the api role stays without cluster access as ADR 0049 decided; its dry run reports each account unchecked, with that reason, and the deploy prints a note. - The call lives in
internal/executor(ServiceAccounts), wherescripts/rbac-covers-executor.shsees it and fails CI if the Role stops granting it. The API consumes it through an interface of its own, so it imports no Kubernetes package. - The dry run, not the register, reports it. The answer of
POST /api/v2/dags/{dag_id}/versions/dry_rungains atask_podsfield: whether task pods run as non-root, and per ServiceAccount the tasks that use it, whether it was found, missing or unchecked, and its pull secret count. A dry run looks up at most 8 accounts, within 5 seconds in all; a lookup that fails is unchecked, with a fixed detail, and its error goes to the server log only. The register’s answer does not change. - The CLI decides what stops a deploy. A missing account stops it, since those pods would never be created. An account with no pull secret is a note: nodes may pull with credentials of their own (IRSA, Workload Identity, a credential helper), which the control plane cannot see.
Consequences
- A deploy names a missing task ServiceAccount, its tasks and the fix before anything is built, on the install shape most clusters run (one role).
- An install upgrading the chart gains the verb with the upgrade; an operator who manages the Role outside the chart gets an unchecked note on each deploy until the verb is granted. Nothing that worked before stops working (ADR 0068).
- A deployer of any tenant can learn whether a ServiceAccount name exists in
the shared task namespace, and how many pull secrets it has. A deployer
can already name any account allowed by
executor.defaults.execution_allowlist.service_accountsin a task, which the dry run checks first, so this tells little more; the names of the secrets are never sent. - A split control plane does not get the check. Granting the api role a read would undo the point of ADR 0049; if split installs need it, a later decision can let the scheduler answer it.
Alternatives considered
- The CLI reads the cluster with the deployer’s kubeconfig. Rejected: most CI deploys have none, and a kubeconfig may name another cluster than the server deployed to, so the answer would be wrong or missing.
- No Kubernetes call: report the configured default account and whether the chart gave it pull secrets. Rejected: it cannot see an account the operator brought, nor one a task names, which is where the failures come from.