Files
apps-registry/workloads/docs-portal/docs/decisiones/0002-por-que-argocd-vs-manual.md
devops 056a7fb76a docs(docs-portal): Fase 2 - contenido narrativo real de arquitectura/decisiones/aprendizajes
Reemplaza los placeholders "TODO (fase 2+)" por narrativa completa,
grounded en el codigo/manifiestos/incidentes reales del lab (no
generico):

- ADR 0001 (por que k3d) y ADR 0002 (por que Argo CD): pasan de
  template vacio a Contexto/Opciones/Decision/Consecuencias reales,
  apoyados en known-issues.md (blackhole MTU) y en la config real de
  ecommerce-app.yaml.
- vision-general.md: el "por que" de cada frontera de confianza del
  diagrama.
- gitops-flujo.md: ejemplo real con hashes de commit (Fase 3b de
  ADR 0006, v1.0.171 -> hotfix -> v1.0.172), selfHeal explicado con el
  syncPolicy real del repo.
- red-y-exposicion.md: diagrama de terminacion TLS real, por que todo
  va "proxied", patron de diagnostico "interno sano, publico roto".
- glosario.md: 9 terminos nuevos, cada uno con ejemplo real y
  verificado del repo (Namespace, ResourceQuota, LimitRange,
  NetworkPolicy, Deployment, Service, Ingress, StatefulSet, Init
  container).
- notas-sueltas.md: MinIO/S3, limites reales del subscriber
  revalidate-storefront.ts, que se automatizaria desde cero.

Validado con `docker build --target build` (mkdocs build --strict,
mismo paso que el pipeline real) -- cero links rotos, cero warnings.
2026-08-26 23:51:18 -05:00

4.6 KiB

ADR 0002 — Por qué Argo CD (y no kubectl apply manual o un script de CI)

Estado: aceptada Fecha: 2026-08 (retroactiva — decisión tomada al arrancar el lab, documentada en Fase 2 del portal)

Contexto

Desde el principio el lab iba a tener más de un repo Git (hoy: apps-registry para las Applications y los manifiestos reales de los workloads, platform-infra para la gobernanza de cada namespace, scripts para bootstrap/operación) y más de una app corriendo en el cluster (ecommerce, docs-portal, nginx, cada una con su propia gobernanza). Se necesitaba una forma de mantener el estado del cluster sincronizado con lo declarado en Git que escalara a "agregar una app nueva" sin volverse un proceso manual que dependiera de que una sola persona recordara qué kubectl apply correr y en qué orden.

También importaba la trazabilidad: en un lab que sirve de portafolio, poder señalar "este commit es exactamente lo que está corriendo en producción" (y por qué) tiene valor por sí mismo — no solo operativo.

Opciones consideradas

Opción A favor En contra
Argo CD (GitOps, pull-based) Reconciliación continua: un proceso dentro del cluster compara el estado real contra Git y corrige la diferencia solo, sin que nadie tenga que acordarse de aplicar nada. Patrón App of Apps nativo — agregar una app nueva es un archivo YAML más en apps/, no un paso manual nuevo Componente extra que mantener sano (su propio namespace, su propio consumo de recursos)
kubectl apply manual Cero infraestructura extra, control total en cada comando Sin detección de drift — si alguien edita algo a mano y lo olvida, nadie se entera hasta que rompe; no escala más allá de un solo operador que recuerde el estado completo de memoria
kubectl apply desde el pipeline de CI (push-based) No agrega un componente nuevo al cluster, reutiliza el CI que ya existe para construir imágenes El pipeline de CI necesitaría credenciales de escritura amplias sobre el cluster — más superficie de riesgo si esas credenciales se filtran. Push-based además significa que nada vuelve a comparar el estado después de aplicado: un drift posterior (alguien con kubectl directo) queda invisible hasta el siguiente deploy

Decisión

Argo CD, patrón App of Apps: una Application raíz (apps-registry/application.yaml) vigila la carpeta apps/, y cada archivo ahí dentro es una Application hija que apunta a un workload real. Toda Application se declara con el mismo syncPolicy:

syncPolicy:
  automated:
    prune: true
    selfHeal: true
  syncOptions:
    - CreateNamespace=true

(configuración real de apps/ecommerce-app.yaml, repetida en cada Application del repo)

Consecuencias

  • selfHeal: true cambia el modelo mental de quién puede tocar el cluster de verdad. No es solo que Argo CD detecte diferencias y las muestre como OutOfSync — las revierte activamente. Si alguien corre kubectl scale o kubectl edit directo contra un recurso gestionado, en el siguiente ciclo de reconciliación (poll periódico, o inmediato si el commit llegó por webhook) Argo CD lo vuelve a dejar exactamente como dice Git. Git deja de ser "la intención" y pasa a ser literalmente la única fuente de la que el cluster puede aprender su propio estado — ver flujo GitOps.
  • prune: true significa que borrar un recurso del repo (por ejemplo, quitar meilisearch.yaml de una kustomization.yaml) lo borra también del cluster en el siguiente sync, sin un paso manual de limpieza — confirmado en la práctica en varias fases de este lab (ver los ADRs de rediseño del storefront y de Meilisearch).
  • El rol de CI se redujo a "construir, publicar imagen, y dejar un commit en Git" — nunca aplica nada directo al cluster. Esto evitó tener que darle a CI credenciales de cluster, y dejó un problema real documentado (la condición de carrera de promoción cuando dos pipelines corren en serie sobre el mismo commit) resoluble comparando contra Git (git merge-base --is-ancestor) en vez de contra el estado del cluster.
  • Se paga con un componente más que mantener sano: Argo CD corre en su propio namespace, con su propio consumo de recursos y su propia superficie de fallo — un Argo CD caído no rompe lo que ya está corriendo (el cluster sigue con el último estado aplicado), pero sí detiene la sincronización de cambios nuevos.
  • Para revertir esta decisión haría falta volver a un modelo push-based (CI aplicando directo) o puramente manual, perdiendo la reconciliación continua y aceptando que un drift manual pueda pasar desapercibido.