Entorno local y ligero sobre Docker, provisionado con Terraform, para probar despliegues
GitOps con observabilidad. Terraform hace el bootstrap (cluster k3d, ArgoCD y las Application
raíz) y a partir de ahí ArgoCD entrega las aplicaciones y el stack de observabilidad leyendo
las definiciones de la carpeta gitops-repo/ de este mismo repo en GitHub, al que se conecta
con una deploy key SSH de solo lectura (ver ADR-0007).
Sobre esa base corre un flujo de diagnóstico agéntico de incidentes: una alerta de Grafana se enriquece con la traza de error y se envía a un agente que genera un análisis de causa raíz (RCA) con un LLM local, leyendo el código fuente real (ADR-0006).
Una petición inválida provoca un
500enperson-crud→ Grafana dispara la alerta → elalert-enricheradjunta la traza de error de Tempo (con el commit exacto) → elai-sre-agentclona el repo en ese commit y genera el informe de causa raíz (RCA) con un LLM local (Ollamaqwen3:8b).
Terraform (bootstrap) ArgoCD (GitOps, continuo)
├─ cluster k3d (k3s) ├─ root-apps -> apps/workloads/:
├─ ArgoCD (Helm, ns platform) │ nginx, podinfo, person-crud, telemetrygen
├─ deploy key SSH (repo GitHub) └─ root-platform -> apps/platform/:
└─ 2 Applications raíz (App-of-Apps): otel-collector, loki, tempo, mimir, grafana
root-apps + root-platform
(ArgoCD clona git@github.com:Argarm/rca-lab.git)
Telemetría: apps --OTLP--> OTel Collector --> Loki (logs) / Tempo (trazas) / Mimir (métricas)
^-- Grafana consulta los tres
Diagnóstico: Grafana (alerta) --> alert-enricher (traza de Tempo + stacktrace)
--webhook--> ai-sre-agent (host): clona el repo en el commit de la traza
y genera un RCA con Ollama (qwen3:8b) [proceso en el host]
-
Dos roots App-of-Apps:
root-platformgestiona el stack de observabilidad (apps/platform/) yroot-appsgestiona las aplicaciones (apps/workloads/). Separados para poder iterar sobre las apps sin arriesgar la plataforma, y viceversa. Terraform bootstrapea ambos. -
Namespaces:
platform(ArgoCD + observabilidad) yworkloads(tus apps). -
Fuente GitOps en GitHub: ArgoCD clona
git@github.com:Argarm/rca-lab.git(repo privado) con una deploy key SSH de solo lectura y lee los manifiestos degitops-repo/. El ciclo es GitOps real: editas →git commit→git pusha GitHub → ArgoCD sincroniza. La clave privada vive fuera de git (terraform/.secrets/, gitignored). Ver ADR-0007.
- Windows + Docker Desktop (backend Linux/WSL2) en marcha.
terraformyk3d(el scriptup.ps1los instala con winget si faltan).- Ya presentes en este equipo:
kubectl,helm,git.
# Arrancar todo
./scripts/up.ps1
# Destruir todo
./scripts/down.ps1O directamente con Terraform:
cd terraform
terraform init
terraform apply -target="null_resource.k3d_cluster" # fase 1: cluster (kubeconfig)
terraform apply # fase 2: resto
terraform outputPor qué dos fases: los providers de Kubernetes/Helm necesitan el kubeconfig que k3d escribe al crear el cluster. Crear primero el cluster con
-targetevita fallos de conexión en el primerapply. En aplicaciones posteriores basta conterraform apply.
Los Ingress usan Traefik (incluido en k3s) y *.localhost (resuelve a 127.0.0.1). Puerto host 8080.
| Servicio | URL | Credenciales |
|---|---|---|
| ArgoCD | http://argocd.localhost:8080 | admin / ver comando abajo |
| Grafana | http://grafana.localhost:8080 | admin / admin |
| podinfo | http://podinfo.localhost:8080 | — |
| nginx | http://nginx.localhost:8080 | — |
Password inicial de admin de ArgoCD:
$pw = kubectl -n platform get secret argocd-initial-admin-secret -o jsonpath='{.data.password}'
[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($pw))Alternativa por port-forward (si el Ingress diera problemas):
kubectl -n platform port-forward svc/argocd-server 8081:80 # http://localhost:8081
kubectl -n platform port-forward svc/grafana 3000:80 # http://localhost:3000- Edita
gitops-repo/workloads/podinfo/deployment.yamly ponreplicas: 2. - Haz commit y push a GitHub:
git commit -am "podinfo a 2 réplicas"; git push
- ArgoCD detecta el nuevo commit y sincroniza solo (poll ~3 min). Para forzarlo al momento:
kubectl -n platform annotate app podinfo argocd.argoproj.io/refresh=hard --overwrite kubectl -n workloads get pods -l app=podinfo # deberían aparecer 2
Para llevar una app local (con su Dockerfile) al cluster hay dos planos que no se mezclan:
la imagen viaja por Docker, y el despliegue se declara en git para que ArgoCD lo entregue.
El código fuente de tus apps vive en apps-src/ (una subcarpeta por app, cada una con su
Dockerfile). Ese código no lo lee ArgoCD: solo se construye en imagen y se importa al cluster
con build.ps1. Los manifiestos de Kubernetes, en cambio, sí van en gitops-repo/ (es lo que ArgoCD
lee de GitHub). apps-src/person-crud/ es un submódulo git (repo propio en GitHub).
1. Construir e importar la imagen (no hay registry: hay que meter la imagen en k3d a mano):
./scripts/build.ps1 0.1.0 # construye TODAS las apps de apps-src/ con ese tag e importa a k3d
./scripts/build.ps1 0.1.0 hello-api # solo una appUsa un tag fijo y súbelo en cada cambio (
0.1.0→0.1.1…). ConimagePullPolicy: IfNotPresent, reutilizar el mismo tag no redespliega porque Kubernetes no detecta que la imagen cambió.
2. Declarar el despliegue en GitOps. Crea gitops-repo/workloads/<app>/ con deployment.yaml,
service.yaml, ingress.yaml y kustomization.yaml (clona workloads/podinfo/ como plantilla).
En el deployment, apunta a tu imagen y no la bajes de internet:
containers:
- name: <app>
image: <app>:0.1.0
imagePullPolicy: IfNotPresent # usa la imagen importada en k3d
ports:
- name: http
containerPort: <puerto-del-Dockerfile>Añade la Application hija en gitops-repo/apps/workloads/app-<app>.yaml (clona
apps/workloads/app-podinfo.yaml; root-apps la recoge sola por estar en apps/workloads/), con
path: workloads/<app> y namespace: workloads. Si en cambio fuera un componente de plataforma, iría
en apps/platform/ y lo gestionaría root-platform.
3. Publicar (commit + push a GitHub — es lo que ArgoCD lee):
git add -A; git commit -m "add <app>"; git pushArgoCD sincroniza solo. La app queda en http://<app>.localhost:8080 (según el host del Ingress).
Bucle de actualización: cambia el código → ./scripts/build.ps1 0.1.1 → actualiza
image: <app>:0.1.1 en el deployment → commit. ArgoCD redespliega.
- En Grafana, los 3 datasources (Mimir/Loki/Tempo) deben dar "OK" en su prueba.
- En Explore verás trazas (Tempo), métricas (Mimir) y logs (Loki) que genera
telemetrygencontinuamente hacia el Collector.
terraform/ Capa de aprovisionamiento (bootstrap)
k3d/ Definición del cluster k3d
argocd/ values.yaml del chart + los 2 roots (root-platform.yaml, root-apps.yaml)
gitops-repo/ Carpeta que consume ArgoCD (la lee de GitHub, no es un repo aparte)
apps/
platform/ Applications hijas de plataforma/observabilidad (las vigila root-platform)
workloads/ Applications hijas de aplicaciones (las vigila root-apps)
workloads/ Manifiestos de nginx, podinfo, person-crud, telemetrygen, alert-enricher
observability/ Mimir monolítico (manifiestos)
apps-src/ Código + Dockerfile de tus apps de ejemplo (se construyen con build.ps1)
person-crud/ es un submódulo git; alert-enricher/ el enriquecedor de alertas
scripts/ up.ps1 / down.ps1 / build.ps1
docs/adr/ Decisiones de arquitectura (ADR) y pendientes para futuras iteraciones
Las decisiones de diseño y los problemas de raíz resueltos están documentados en
docs/adr/ (dos roots, entrega de imágenes locales, instrumentación
OpenTelemetry, webhook al agente SRE, y la migración del repo GitOps a GitHub), junto con los
puntos pendientes.
¿Retomando el proyecto? Empieza por docs/ESTADO.md: resume qué hay hecho y los
pasos exactos para volver a levantarlo (up.ps1 + build.ps1).
- Versiones de charts: las
Applicationde observabilidad (gitops-repo/apps/platform/obs-*.yaml) fijan versiones de chart (loki, tempo, grafana, opentelemetry-collector). Si una versión ya no existe o su esquema de values cambió, edita eltargetRevision/valuesde ese fichero y haz commit. host.k3d.internal(legado):terraform/coredns.tfaún provisiona un ConfigMapcoredns-customque resuelvehost.k3d.internalal gateway de la red del cluster. Era necesario cuando ArgoCD clonaba delgit-serverpublicado en el host; tras migrar a GitHub (ADR-0007) ArgoCD resuelvegithub.compor SSH y ya no lo necesita, y el enricher alcanza el host porhost.docker.internal. Queda como recurso vestigial; candidato a eliminar (verdocs/adr/README.md).- Helm:
no cached repo founden elapply: el providerhelmde Terraform carga el índice de todos los repos de Helm registrados, y su caché vive en%TEMP%\helm(que Windows vacía). Si falta, elapplyde ArgoCD revienta.up.ps1ejecutahelm repo updateantes delapplypara regenerarlo; si lo lanzas a mano, correhelm repo updateprimero. - Primer arranque: descarga imágenes (k3s, ArgoCD, LGTM, podinfo); necesita internet la primera vez. Después queda en la caché de Docker.
- Solo Windows: los
local-execusan PowerShell. Portable a bash cambiando elinterpreter.
Lo que antes era "ampliación futura" ya está montado: una alerta de Grafana dispara un análisis de causa raíz automático. Resumido:
- Alerta. Regla
ErrorEnCualquierServicioen Grafana (obs-grafana.yaml): dispara unwebhookcuando alguna traza marcastatus_code=STATUS_CODE_ERROR. - Enriquecimiento.
alert-enricher(workload) recibe el webhook, busca en Tempo la traza de error del servicio y extrae excepción + stacktrace + atributos del span (incluidovcs.repository.url.fully el commitvcs.ref.head.revision). Reenvía el cuerpo enriquecido por webhook. - RCA con contexto de código.
ai-sre-agent(proceso Node en el host, repo aparte) responde202al instante y en background clona el repositorio en el commit exacto de la traza y lanza un RCA agéntico contra un LLM local (Ollamaqwen3:8b). Guarda un informe enreports/*.json.
Detalle y decisiones en ADR-0006. El aprendizaje clave: sin el código el LLM alucinaba infraestructura inexistente; dándole el fuente en el commit correcto, el RCA acierta el bug real. El receptor vive fuera del cluster (depende del Ollama del host).
Próximo paso (roadmap): que el agente no se quede en el diagnóstico y proponga el fix en un Pull Request (de "te digo qué se rompió" a "te dejo el arreglo listo para revisar"), con revisión humana y sin auto-merge. Ver ESTADO.md § Próximos pasos.
