AGENTS.md carries the owner's rules for every agent: commit identity, secret handling, surgical edits, and writing style. PLAN.md is the owner's worklog and is off limits to agents.
346 lines
14 KiB
Markdown
346 lines
14 KiB
Markdown
# Nereus — Plan del Proyecto Final DevOps
|
|
|
|
## Qué es esto
|
|
|
|
Mi plan para el PF de Tokio. No es la memoria, es mi guion de trabajo.
|
|
Fecha objetivo: infra + integración en 2 días, memoria y vídeo después.
|
|
|
|
## La idea
|
|
|
|
Una API que registra lecturas de boyas oceánicas. Go + Postgres, frontend estático.
|
|
|
|
La app es lo de menos. El proyecto real es todo lo que la rodea: k3s en Fedora,
|
|
CI/CD, blue-green con rollback automático decidido por Prometheus, y observabilidad
|
|
completa. La app solo tiene que generar telemetría interesante y poder fallar a demanda.
|
|
|
|
Empresa ficticia del enunciado: TechWave Solutions. Encaja con la estética de agua,
|
|
así que tiro por ahí y ya.
|
|
|
|
## Arquitectura
|
|
|
|
```
|
|
PC principal (CachyOS, 32GB)
|
|
│
|
|
├── libvirt/KVM
|
|
│ ├── nereus-server Fedora Server 42 · 4 vCPU · 12GB · k3s server + agent
|
|
│ └── nereus-agent Fedora Server 42 · 4 vCPU · 10GB · k3s agent
|
|
│
|
|
└── ~10GB libres para el host
|
|
|
|
Mini PC (homelab, siempre encendido)
|
|
├── Forgejo git.fiwlabs.dev, detrás de Traefik con auto-TLS
|
|
├── Forgejo Runner modo docker, misma LAN que el clúster
|
|
├── Registro de imágenes incluido en Forgejo
|
|
└── MinIO backend remoto de Terraform
|
|
|
|
Espejo automático a GitHub → el link que entrego a Tokio
|
|
```
|
|
|
|
Dentro del clúster:
|
|
|
|
```
|
|
Traefik (viene con k3s)
|
|
└── Ingress → Service activo
|
|
├── Rollout blue (v1, tráfico real)
|
|
└── Rollout green (v2, preview, sin tráfico)
|
|
|
|
Postgres (StatefulSet + PVC local-path)
|
|
|
|
Observabilidad:
|
|
kube-prometheus-stack Prometheus + Grafana + node-exporter + kube-state-metrics
|
|
Loki + Alloy logs
|
|
OTel Collector → Tempo trazas
|
|
Alertmanager → Discord webhook
|
|
```
|
|
|
|
## Por qué Forgejo y no GitHub
|
|
|
|
El enunciado dice "GitHub Actions **o la herramienta de tu preferencia**", así que
|
|
estoy cubierto. Y me interesa porque:
|
|
|
|
- El runner ya vive en mi red, llega al k3s por LAN sin túneles ni runner self-hosted registrado contra un tercero
|
|
- Registro de contenedores incluido, con TLS válido de Traefik → k3s hace pull sin `insecure: true`
|
|
- Ecosistema DevOps entero self-hosted: forja, registro, runner, clúster. Cero dependencia de terceros
|
|
|
|
**Pero**: la entrega pide enlace al repositorio. Si el profe abre el link y mi
|
|
homelab está caído, malo. Por eso **espejo automático a GitHub** (Settings →
|
|
Repository → Mirror Settings, se configura una vez). Entrego el link de GitHub,
|
|
el pipeline real corre en mi infra.
|
|
|
|
Como los workflows viven en `.forgejo/workflows/`, GitHub los ignora. No se me
|
|
ejecuta nada por accidente allí.
|
|
|
|
### Cosas que me van a morder
|
|
|
|
**Actions de terceros.** Forgejo los busca en `code.forgejo.org` por defecto.
|
|
`actions/checkout` está mirrorizado, pero `trivy-action` o `gitleaks-action`
|
|
probablemente no. Solución: **ejecutarlos como CLI en docker**, no como action.
|
|
Menos dependencias y más portable:
|
|
|
|
```yaml
|
|
- run: |
|
|
docker run --rm -v $PWD:/repo zricethezav/gitleaks:latest \
|
|
detect --source /repo --no-git -v
|
|
```
|
|
|
|
Si aun así necesito actions de GitHub: `DEFAULT_ACTIONS_URL = github` en `app.ini`.
|
|
|
|
**El runner necesita Docker.** Modo `docker` en la config, usuario del runner en
|
|
el grupo docker. Ojo con conflictos de puertos con el resto del stack del Mini PC.
|
|
|
|
**Credenciales del pipeline.** El kubeconfig va como secret de repo, pero **no el
|
|
de admin**. Creo un ServiceAccount `deployer` con RBAC limitado al namespace
|
|
`nereus` y genero un kubeconfig con ese token. Eso es un punto directo de
|
|
"manejo seguro de credenciales" y es respuesta preparada si preguntan.
|
|
|
|
## Reparto: yo vs agentes
|
|
|
|
**Yo hago** todo lo que necesita el clúster vivo o hardware real:
|
|
|
|
- VMs Fedora, k3s, firewalld, SELinux
|
|
- Terraform (infra y platform)
|
|
- Forgejo Runner y su acceso al clúster
|
|
- Integración de los manifiestos, iterar hasta que aplique
|
|
- Que el AnalysisRun aborte de verdad
|
|
- Capturas y vídeo
|
|
|
|
**Agentes hacen** lo que se valida sin clúster:
|
|
|
|
- `apps/api/` — Go, endpoints, OTel, tests
|
|
- `apps/loadgen/` — generador de tráfico en Go
|
|
- `apps/web/` — frontend chulo, con badge de versión, panel de salud y contador de errores
|
|
- `build/` — Dockerfile multi-stage + compose
|
|
- `observability/` — dashboards JSON, alert rules, config del collector
|
|
|
|
Cuatro tareas en paralelo, una por carpeta, así no se pisan. Reglas en `AGENTS.md`.
|
|
|
|
`terraform/` y `deploy/` son míos, los agentes no los tocan.
|
|
|
|
## Día 1
|
|
|
|
Infra primero, a mano, antes de codificarla en Terraform. Terraform a ciegas es sufrir.
|
|
|
|
**Paso 0 — la clave SSH, que no la tengo en el PC principal:**
|
|
|
|
```bash
|
|
ssh-keygen -t ed25519 -C "fiw@pc-principal"
|
|
cat ~/.ssh/id_ed25519.pub
|
|
# → Forgejo: Settings → SSH/GPG Keys → Add Key
|
|
|
|
ssh -T git@git.fiwlabs.dev # verificar antes de seguir
|
|
```
|
|
|
|
Esa misma clave la meto en el `cloud-init` de las VMs (`ssh_authorized_keys`),
|
|
así que la necesito **antes** de tocar Terraform. Y como el PC es dual-boot,
|
|
la genero en CachyOS, que es donde va a vivir el clúster.
|
|
|
|
Si el SSH de Forgejo va por un puerto no estándar, `~/.ssh/config`:
|
|
|
|
```
|
|
Host git.fiwlabs.dev
|
|
User git
|
|
Port 2222
|
|
IdentityFile ~/.ssh/id_ed25519
|
|
```
|
|
|
|
- [ ] Clave SSH generada y añadida a Forgejo
|
|
- [ ] Repo `nereus` creado en Forgejo + espejo a GitHub configurado
|
|
- [ ] ISO Fedora Server 42, dos VMs a mano con virt-manager
|
|
- [ ] `systemctl disable --now zram-generator` o el swap toca los huevos a k3s
|
|
- [ ] `dnf install k3s-selinux` antes de instalar k3s
|
|
- [ ] firewalld: abrir `6443/tcp`, `10250/tcp`, `8472/udp`
|
|
- [ ] Instalar k3s server en nereus-server, agent en nereus-agent
|
|
- [ ] **Verificar que un pod en un nodo hace ping a un pod del otro.** Si esto falla es el 8472/udp, siempre
|
|
- [ ] Reserva de IP estática para las dos VMs en la red de libvirt
|
|
- [ ] Snapshot de las dos VMs
|
|
- [ ] Lanzar los 4 agentes en paralelo
|
|
- [ ] Codificar las VMs en `terraform/infra/` (libvirt + cloud-init)
|
|
- [ ] MinIO en el Mini PC como backend de estado
|
|
|
|
## Día 2
|
|
|
|
- [ ] `terraform/platform/` — helm: kube-prometheus-stack, Loki, Tempo, Argo Rollouts, Sealed Secrets
|
|
- [ ] Manifiestos de la app con kustomize, aplicar, iterar
|
|
- [ ] Sealed Secrets: cifrar la password de Postgres y commitearla
|
|
- [ ] **Backup de la clave privada de Sealed Secrets fuera del repo** (sin esto, reencender = perder todos los secretos)
|
|
- [ ] ServiceAccount `deployer` + RBAC limitado a `nereus`, generar su kubeconfig
|
|
- [ ] Forgejo Runner en el Mini PC (modo docker), registrado y conectado al clúster
|
|
- [ ] Pipeline CI: test, lint, gitleaks, trivy, build, push al registro de Forgejo
|
|
- [ ] Pipeline CD: kustomize + promoción del Rollout, con el kubeconfig de `deployer`
|
|
- [ ] Verificar que k3s hace pull del registro sin `insecure: true`
|
|
- [ ] Desplegar `loadgen` y verificar que Prometheus ve tráfico constante
|
|
- [ ] AnalysisTemplate consultando Prometheus, verificar que aborta
|
|
- [ ] **Ciclo de demo completo**: desplegar v2 con `CHAOS_ERROR_RATE=0.3`, ver a Rollouts matarla sola, captura de Grafana + ping de Discord
|
|
|
|
Esa demo es la captura que vale por tres páginas de memoria. Que no se me olvide grabarla.
|
|
|
|
## Trampas de Fedora Server
|
|
|
|
Nunca lo he tocado. Estas tres caen seguro, y las documento como "dificultades
|
|
encontradas" que el enunciado pide literalmente:
|
|
|
|
| Problema | Síntoma | Fix |
|
|
|---|---|---|
|
|
| firewalld bloquea VXLAN | Pods no se ven entre nodos, DNS falla raro | Abrir `8472/udp` |
|
|
| SELinux enforcing | kubelet peta con permisos | `dnf install k3s-selinux` |
|
|
| zram/swap activo | k3s se queja al arrancar | Desactivar zram-generator |
|
|
|
|
## Dónde cubro cada requisito del enunciado
|
|
|
|
| Pide | Dónde |
|
|
|---|---|
|
|
| Docker imágenes personalizadas y optimizadas | Multi-stage → distroless, ~15MB. Captura de `docker images` comparando |
|
|
| Docker Compose | `compose.yaml` para entorno de desarrollo local |
|
|
| Terraform IaC | `terraform/infra/` (libvirt) + `terraform/platform/` (helm) |
|
|
| Modularización y estado remoto | Módulos separados, backend S3 en MinIO |
|
|
| Cloud (EKS/AKS) | **No lo hago.** Justifico on-premise por coste y control. El código Terraform es portable |
|
|
| Deployments, Services, Ingress, ConfigMaps, Secrets | `deploy/base/` con kustomize |
|
|
| Pipeline CI/CD | Forgejo Actions + runner propio. El enunciado permite "la herramienta de tu preferencia" |
|
|
| Registro de contenedores | Registro de Forgejo, self-hosted con TLS de Traefik |
|
|
| Manejo seguro de secretos | Sealed Secrets cifrados en el repo, Gitleaks en CI, ServiceAccount `deployer` con RBAC mínimo |
|
|
| Blue-Green | Argo Rollouts, active + preview service |
|
|
| Rollback automático | AnalysisTemplate consultando Prometheus, aborta solo |
|
|
| OTel Collector | Trazas de la app → collector → Tempo |
|
|
| Prometheus | kube-prometheus-stack |
|
|
| Grafana con dashboards personalizados | `observability/dashboards/` |
|
|
| Loki | Logs centralizados vía Alloy |
|
|
| cAdvisor y node exporter | Vienen con el stack (cAdvisor del kubelet) |
|
|
| Alertas y notificaciones | Alertmanager → webhook de Discord |
|
|
|
|
## El clúster es efímero, y eso es una virtud
|
|
|
|
No voy a dejar esto encendido días. Lo levanto en mi PC, grabo el vídeo showcase,
|
|
capturas al README y a la entrega, y lo apago. Cuando haga falta lo enciendo otra vez.
|
|
|
|
Esto **no es una carencia del proyecto, es la prueba de que el IaC funciona**. Si
|
|
el clúster se reconstruye entero desde el repositorio con un comando, es que
|
|
Terraform y los manifiestos son la fuente de verdad de verdad. Lo escribo así en
|
|
la memoria.
|
|
|
|
### Tres niveles de reproducibilidad
|
|
|
|
El profe no va a montar KVM ni a bajarse Fedora. Necesito niveles:
|
|
|
|
| Nivel | Comando | Requisitos | Qué demuestra |
|
|
|---|---|---|---|
|
|
| 1 | `docker compose up` | Docker, 4GB | App + Postgres + Grafana. 3 minutos, cualquier OS |
|
|
| 2 | `./scripts/up-k3d.sh` | Docker, 8GB | **k3s real multi-nodo en contenedores**: manifiestos, Argo Rollouts, blue-green y rollback completos. Funciona en Windows con Docker Desktop |
|
|
| 3 | `terraform apply` | KVM, 24GB | Mi clúster real de 2 nodos Fedora |
|
|
|
|
El **nivel 2 es el que importa** para la corrección. k3d mete un k3s multi-nodo
|
|
dentro de Docker, así que el proyecto entero corre ahí sin mentir sobre nada. Y
|
|
me sirve a mí para iterar rápido sin arrancar VMs.
|
|
|
|
En el README, el nivel 2 va arriba del todo y bien visible.
|
|
|
|
### ⚠️ Sealed Secrets y el clúster efímero
|
|
|
|
**Si borro el clúster, pierdo la clave privada.** El controlador genera un par
|
|
nuevo al reinstalarse y todos mis `*-sealed.yaml` commiteados quedan imposibles
|
|
de descifrar para siempre.
|
|
|
|
Lo primero al montar Sealed Secrets:
|
|
|
|
```bash
|
|
kubectl get secret -n kube-system \
|
|
-l sealedsecrets.bitnami.com/sealed-secrets-key \
|
|
-o yaml > ~/backups/sealed-secrets-key.yaml # NUNCA al repo
|
|
```
|
|
|
|
`up.sh` restaura esa clave **antes** de aplicar nada. Si no, cada reencendido me
|
|
obliga a re-sellar todos los secretos a mano.
|
|
|
|
### Otras cosas que se rompen al reencender
|
|
|
|
- **IPs de las VMs** — DHCP me da otra y el kubeconfig apunta a la vieja. Reserva estática en la red de libvirt desde el día 1
|
|
- **Datos de Prometheus** — se pierden si el PVC es efímero. Da igual, el loadgen repuebla los dashboards en 5 minutos
|
|
- **Orden de arranque** — si el agent arranca antes que el server, el join falla. El script espera a que el server responda
|
|
|
|
### Scripts
|
|
|
|
```
|
|
scripts/
|
|
├── up.sh arranca VMs, espera a k3s, restaura la clave, aplica todo
|
|
├── down.sh apaga limpio
|
|
├── up-k3d.sh nivel 2, para el profe
|
|
└── demo-rollback.sh dispara la demo entera
|
|
```
|
|
|
|
`demo-rollback.sh` despliega v2 con chaos activado y yo solo grabo. Puedo repetir
|
|
tomas hasta que salga bien sin tocar nada a mano. Encadenar comandos en vivo
|
|
durante 15 minutos de vídeo es sufrimiento innecesario.
|
|
|
|
### Regla de oro
|
|
|
|
**Grabo el vídeo y hago TODAS las capturas mientras funciona.** No lo dejo para
|
|
después de apagar. Si al reencender algo se rompe, y algo se romperá, ya tengo
|
|
el material.
|
|
|
|
## Lo que sí es mío del todo
|
|
|
|
El profe hace **tres preguntas** y grabo vídeo de máximo 15 min defendiendo.
|
|
Ahí no hay agente que valga.
|
|
|
|
Las que caen casi seguro:
|
|
|
|
- ¿Por qué distroless y no alpine? → superficie de ataque, sin shell, sin gestor de paquetes
|
|
- ¿Cómo decide el sistema que un despliegue es malo? → **esta es la buena**, AnalysisTemplate + Prometheus
|
|
- ¿Cómo evitas que un secreto acabe en el repo? → Sealed Secrets + Gitleaks
|
|
- ¿Por qué k3s y no k8s completo? → recursos, mismo API, Traefik incluido
|
|
- ¿Por qué Forgejo y no GitHub? → runner en la misma red que el clúster, registro propio, sin dependencia de terceros. Y el espejo garantiza que el repo sea accesible igual
|
|
- ¿Cómo reproduzco tu entorno? → tres niveles, y el 2 corre en Docker en cualquier OS. El clúster es efímero a propósito, se reconstruye desde el repo
|
|
|
|
Voy apuntando el *por qué* de cada decisión en `docs/decisiones.md` según integro.
|
|
Con eso el vídeo y la memoria se escriben casi solos.
|
|
|
|
## Estructura del repo
|
|
|
|
```
|
|
nereus/
|
|
├── AGENTS.md
|
|
├── PLAN.md
|
|
├── compose.yaml
|
|
├── apps/
|
|
│ ├── api/ Go
|
|
│ ├── loadgen/ generador de tráfico
|
|
│ └── web/ frontend
|
|
├── build/
|
|
│ ├── Dockerfile.api
|
|
│ ├── Dockerfile.loadgen
|
|
│ └── Dockerfile.web
|
|
├── deploy/
|
|
│ ├── base/ kustomize
|
|
│ ├── overlays/{dev,prod}/
|
|
│ ├── rollouts/ Rollout + AnalysisTemplate
|
|
│ └── secrets/ SealedSecrets (cifrados)
|
|
├── terraform/
|
|
│ ├── infra/ libvirt + cloud-init + k3s
|
|
│ └── platform/ helm releases
|
|
├── observability/
|
|
│ ├── dashboards/
|
|
│ ├── alerts/
|
|
│ └── otel-collector/
|
|
├── .forgejo/workflows/
|
|
├── scripts/
|
|
│ ├── up.sh
|
|
│ ├── down.sh
|
|
│ ├── up-k3d.sh
|
|
│ └── demo-rollback.sh
|
|
└── docs/
|
|
├── decisiones.md
|
|
└── memoria/
|
|
```
|
|
|
|
## Comandos que voy a repetir mil veces
|
|
|
|
```bash
|
|
# kubeconfig desde el server
|
|
scp fiw@nereus-server:/etc/rancher/k3s/k3s.yaml ~/.kube/nereus
|
|
# cambiar 127.0.0.1 por la IP de nereus-server
|
|
|
|
kubectl argo rollouts get rollout nereus-api -n nereus --watch
|
|
kubectl argo rollouts promote nereus-api -n nereus
|
|
kubectl argo rollouts abort nereus-api -n nereus
|
|
|
|
kubectl port-forward -n observability svc/grafana 3000:80
|
|
```
|