Skip to content

feat(agent): publish the k8s scope env to every k8s worker and take cluster_name as input - #578

Merged
agustincelentano merged 4 commits into
mainfrom
feat/agent-k8s-worker-env
Sep 9, 2026
Merged

feat(agent): publish the k8s scope env to every k8s worker and take cluster_name as input#578
agustincelentano merged 4 commits into
mainfrom
feat/agent-k8s-worker-env

Conversation

@agustincelentano

@agustincelentano agustincelentano commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

El problema

El módulo arma un patch con el env que necesita el scope k8s (DNS_TYPE, K8S_NAMESPACE, las tres rutas de templates, TRAFFIC_CONTAINER_IMAGE, los gateways, más extra_envs) y lo aplica a un solo package, hardcodeado: containers.

Eso asume que el scope k8s corre únicamente en el worker de containers. No es así. En nullplatform/scopes hay un solo código de scope k8s, y varios packages lo ejecutan:

  • containers lo corre tal cual: imagen scopes/containers, NP_SERVICE_PATH=/app/pkg/k8s.
  • scheduled-task corre el mismo código con un overlay: la imagen scopes/scheduled-task hornea NP_SERVICE_PATH=/app/pkg/k8s y NP_OVERRIDES_PATH=/app/pkg/scheduled_task. El overlay solo reemplaza algunos pasos (CronJob en vez de Deployment, saltea networking); todo lo demás es k8s/ leyendo las mismas variables.
  • containers-datadog es el mismo patrón con NP_OVERRIDES_PATH=/app/pkg/datadog.

Es decir: dos packages distintos, dos imágenes distintas, dos workers distintos, pero el código que lee DNS_TYPE, K8S_NAMESPACE o CLUSTER_NAME es exactamente el mismo archivo de k8s/. El módulo hoy le da ese env a uno y al otro nada.

La única salida para una instalación con scheduled task es escribir a mano en su root module un patch como este:

patches = [
  {
    target = { package = "scheduled-task" }
    merge = { spec = { containers = [{ name = "worker", env = [
      { name = "CLUSTER_NAME", value = module.eks.eks_cluster_name },
      { name = "DNS_TYPE",     value = var.dns_type },
    ] }] } }
  }
]

Con dos problemas que medimos en una instalación real:

  1. Si al patch le falta una variable, no hay error: el scope usa su default. Sin DNS_TYPE el default es route53, y el create del scheduled task se quedó 300 segundos en wait for alb hasta el timeout, porque ese paso solo se saltea cuando DNS_TYPE es otro valor. Cada instalación tiene que adivinar qué subconjunto del env del módulo necesita el overlay, y equivocarse cuesta un timeout por acción.
  2. CLUSTER_NAME no tiene input en el módulo. scope/iam/create_role lo lee del env para encontrar el OIDC provider del cluster. Hoy la única manera de que llegue al worker es colarlo por extra_envs, que está pensado para lo que el módulo no conoce, no para una variable que el propio scope k8s exige.

Qué cambia

Dos inputs nuevos en nullplatform/agent:

  • worker_k8s_packages, list(string), default ["containers"]. El módulo genera el patch de env una vez por cada package de la lista, en lugar del patch único para containers. La pregunta que responde la lista es "¿qué packages corren el código del scope k8s?", y la respuesta la conoce quien instala, no el módulo.
  • cluster_name, string, obligatorio cuando cloud_provider = "aws". Se publica como CLUSTER_NAME a esos workers, una entrada más del mismo mapa que DNS_TYPE o K8S_NAMESPACE. Como el scope lo necesita para armar el OIDC del rol IAM, una precondition (el mismo patrón que aws_iam_role_arn) hace fallar el plan si falta, en vez de descubrirlo dentro de create-scope con un error de AWS. Quien todavía lo pasa por extra_envs.CLUSTER_NAME sigue pasando la validación y sigue funcionando, porque extra_envs se mergea último.

worker_orchestrated_packages no cambia de significado: sigue diciendo qué packages corren como worker (service account, memoria). worker_k8s_packages dice cuáles de esos, además, corren el scope k8s.

Qué ganamos

  • Un solo set de variables, definido una vez, para todos los workers que corren el mismo código. Una instalación con scheduled task pasa de un patch a mano a una línea: worker_k8s_packages = ["containers", "scheduled-task"]. Y ya no hay un subconjunto que adivinar: cada worker k8s recibe todo lo que el código de k8s/ puede leer.
  • Cuando el módulo agregue o renombre una variable del scope k8s, llega sola a todos los overlays. Con el patch a mano, cada cambio del módulo obliga a revisar cada root module.
  • CLUSTER_NAME deja de ser un truco. Es un input con nombre y descripción, al lado de namespace y dns_type, en vez de una clave suelta en extra_envs que nadie sabe por qué está.
  • El comportamiento del scope queda alineado con cómo se empaqueta. Una imagen por package, pero un código por scope: el módulo ahora modela eso en vez de asumir que package y scope son lo mismo.

Compatibilidad

Con los defaults, el patch de env sigue siendo uno solo, con target containers, y la única diferencia en los values es la línea CLUSTER_NAME en ese patch. Una instalación AWS existente ya pasa CLUSTER_NAME por extra_envs (era la única forma), así que pasa la precondition y no cambia de comportamiento con solo bumpear la versión; la única que falla en el plan es la que no lo pasaba por ningún lado, y esa hoy falla en runtime en cada create-scope.

Tests

Cinco runs nuevos en agent_values.tftest.hcl: por default el env llega solo a containers y a ningún otro package; con ["containers", "scheduled-task"] llega a los dos; cluster_name = "my-eks" aparece como CLUSTER_NAME en el worker; sin cluster_name en AWS el plan falla en la precondition; con extra_envs.CLUSTER_NAME sigue pasando. La suite del módulo queda en 34/34.

Relacionado

Es la segunda de las tres cosas que salieron de migrar una instalación a workers y que obligan a copiar internals del módulo en el root module. La primera fue #576 (worker_ingress), ya publicada en 7.6.0; este branch está rebaseado sobre ella.

…luster_name as input

The deploy/DNS env used to be hardcoded into a patch for the containers
package only. Any other package running the k8s scope code under an
overlay (scheduled task, datadog) needed a hand-written patch in the
root module, and CLUSTER_NAME could only reach the worker via extra_envs.

- worker_k8s_packages (default ["containers"]): one env patch per listed
  package, replacing worker_container_patch.
- cluster_name: published as CLUSTER_NAME to those workers when set;
  omitted when empty so it cannot shadow a value passed via extra_envs.

Default output is unchanged. Four new tests.
@agustincelentano
agustincelentano force-pushed the feat/agent-k8s-worker-env branch from 035f404 to 751b531 Compare September 8, 2026 19:02
…f the k8s env

extra_envs is merged last, so a value passed there always wins; and
create_role reads $CLUSTER_NAME directly, for which empty and unset are
the same. The conditional bought nothing.
@agustincelentano

Copy link
Copy Markdown
Collaborator Author

Simplificado: CLUSTER_NAME ya no va detrás de un condicional, es una entrada más de worker_default_env, como DOMAIN o IMAGE_PULL_SECRETS. El condicional no aportaba nada: extra_envs se mergea último, así que un valor pasado por ahí gana igual, y create_role lee $CLUSTER_NAME directo, donde vacío y no definido son lo mismo. Con los defaults, el único cambio en los values es una línea CLUSTER_NAME: "" en el patch de containers. Tests 33/33.

@agustincelentano

Copy link
Copy Markdown
Collaborator Author

Probado en una instalación real (EKS, agent 3.0.0 / aws-0.11.1-nonroot, scopes v1.16.2), apuntando el module "agent" a este branch con worker_k8s_packages = ["containers", "scheduled-task"] y cluster_name = module.eks.eks_cluster_name, y borrando el patch a mano de scheduled-task y extra_envs.

  • Plan: un solo cambio, los values del agent. El worker de containers queda idéntico; el de scheduled-task pasa de 2 a 12 variables; CLUSTER_NAME desaparece del Secret del pod del agent, donde nadie lo leía.
  • Después del apply, borramos el Deployment del worker de scheduled-task y creamos un scheduled task: el pod nuevo trae las 12 variables, create-scope corre assume role → wait for alb (salteado por DNS_TYPE) → create role → apply con status: success en menos de un minuto, y delete-scope termina en deleted sin dejar SA, CronJob ni rol IAM.

Tests del módulo 33/33, CI en verde.

…mpty

The k8s scope needs it to resolve the EKS OIDC provider; an empty value
only surfaced inside create-scope as an AWS error. Same precondition
pattern as aws_iam_role_arn; extra_envs.CLUSTER_NAME is still accepted so
existing installations keep working.
@agustincelentano

Copy link
Copy Markdown
Collaborator Author

Tomado el comentario sobre el default de cluster_name: ahora es obligatorio cuando cloud_provider = "aws", con una precondition en cross_variable_validation, el mismo patrón que aws_iam_role_arn. Si falta, el plan falla con un mensaje que dice para qué lo necesita el scope, en vez de fallar dentro de create-scope con un error de aws eks describe-cluster. Sigue aceptando extra_envs.CLUSTER_NAME para que las instalaciones existentes no rompan en un bump menor. Tests: falta en AWS → falla el plan; llega por extra_envs → pasa. 34/34.

@agustincelentano
agustincelentano merged commit cd1fc41 into main Sep 9, 2026
54 checks passed
@agustincelentano
agustincelentano deleted the feat/agent-k8s-worker-env branch September 9, 2026 14:16
release-application Bot added a commit that referenced this pull request Sep 9, 2026
🤖 I have created a release *beep* *boop*
---


##
[7.7.0](v7.6.0...v7.7.0)
(2026-09-09)


### Features

* **agent:** publish the k8s scope env to every k8s worker and take
cluster_name as input
([#578](#578))
([cd1fc41](cd1fc41))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants