feat(docs-portal): pipeline GitOps y manifiestos K8s para el portal MkDocs
Agrega lo que faltaba para desplegar workloads/docs-portal/ (ya existente sin commitear): deployment/service/ingress + kustomization siguiendo el patrón de workloads/nginx, Applications de workload y gobernanza separadas, y el workflow de Gitea Actions (build+push a Gitea Registry, bump de versión en el manifiesto) siguiendo el mismo patrón que build.yaml/build-medusa.yaml.
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
# Del commit al despliegue: flujo GitOps
|
||||
|
||||
> **TODO (fase 2+):** narrar cada paso con ejemplos reales tomados de
|
||||
> `.gitea/workflows/build-medusa.yaml` y `build.yaml`. El diagrama de
|
||||
> secuencia de abajo refleja el pipeline real (build → push imagen →
|
||||
> promoción segura → commit del tag → sync de Argo CD).
|
||||
|
||||
## Secuencia: un commit hasta quedar corriendo en el cluster
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor Dev as Devops
|
||||
participant Gitea
|
||||
participant CI as Gitea Actions
|
||||
participant Reg as Gitea Registry
|
||||
participant Argo as Argo CD
|
||||
participant K8s as Cluster k3d
|
||||
|
||||
Dev->>Gitea: push a fix/<rama>
|
||||
Dev->>Gitea: abre PR → main
|
||||
Gitea->>Gitea: merge a main
|
||||
Gitea->>CI: dispara workflow (paths: workloads/**)
|
||||
CI->>CI: build de la imagen (Docker)
|
||||
CI->>Reg: push imagen vX.Y.Z
|
||||
CI->>CI: verifica promoción segura (HEAD == origin/main)
|
||||
CI->>Gitea: commit "chore(gitops): release vX.Y.Z" actualizando el manifiesto
|
||||
Argo->>Gitea: detecta el nuevo commit (poll/webhook)
|
||||
Argo->>K8s: sync (prune:true, selfHeal:true)
|
||||
K8s->>K8s: rollout del nuevo pod
|
||||
```
|
||||
|
||||
## Por qué esta separación (build vs. deploy)
|
||||
|
||||
- El **pipeline de CI** solo construye y publica la imagen — nunca
|
||||
aplica nada directo al cluster.
|
||||
- El **commit al manifiesto** (`sed` sobre el tag de imagen) es lo único
|
||||
que representa "intención de desplegar" — y vive en el mismo repo Git
|
||||
que Argo CD vigila.
|
||||
- **Argo CD** es la única pieza con permisos de escritura sobre el
|
||||
cluster real. Nada fuera de GitOps toca `kubectl apply` en producción.
|
||||
- El paso de "promoción segura" (comparar `HEAD` contra `origin/main`)
|
||||
evita que un pipeline viejo sobreescriba el tag con una versión
|
||||
anterior si hubo un push más reciente mientras corría.
|
||||
|
||||
## TODO
|
||||
|
||||
- Ejemplo real con hashes de commit (tomar del historial de
|
||||
`apps-registry`).
|
||||
- Explicar `syncPolicy.automated.selfHeal` con un caso concreto (qué
|
||||
pasa si alguien hace `kubectl edit` a mano).
|
||||
@@ -0,0 +1,73 @@
|
||||
# Glosario
|
||||
|
||||
> Pensado para alguien que ve estos términos por primera vez. Definiciones
|
||||
> cortas primero, detalle técnico después. Se irá expandiendo en fases
|
||||
> siguientes — por ahora cubre los términos que ya aparecen en el resto
|
||||
> del sitio.
|
||||
|
||||
## GitOps
|
||||
|
||||
**En una frase:** el estado deseado del sistema vive en Git, y un
|
||||
proceso automático (no una persona con `kubectl`) se encarga de que el
|
||||
cluster real coincida con lo que dice Git.
|
||||
|
||||
**Detalle:** en vez de ejecutar comandos contra el cluster a mano, se
|
||||
edita un archivo YAML, se hace commit, y una herramienta (aquí, Argo CD)
|
||||
detecta el cambio y lo aplica. Si alguien cambia algo directo en el
|
||||
cluster sin pasar por Git, GitOps lo revierte (`selfHeal`) — Git es la
|
||||
única fuente de verdad.
|
||||
|
||||
## Tunnel (Cloudflare Tunnel)
|
||||
|
||||
**En una frase:** una forma de exponer un servicio interno a internet
|
||||
sin abrir puertos en el router/firewall.
|
||||
|
||||
**Detalle:** un proceso (`cloudflared`) corre dentro de la red local y
|
||||
abre una conexión saliente hacia Cloudflare. El tráfico público llega a
|
||||
Cloudflare y viaja hacia adentro por esa misma conexión — no hace falta
|
||||
port-forwarding ni IP pública propia.
|
||||
|
||||
## Argo CD
|
||||
|
||||
**En una frase:** el "robot" que sincroniza el cluster Kubernetes con
|
||||
lo que está escrito en Git.
|
||||
|
||||
**Detalle:** vigila uno o más repos Git, compara el estado declarado
|
||||
(manifiestos YAML/Kustomize) contra el estado real del cluster, y
|
||||
aplica la diferencia. Ver [flujo GitOps](gitops-flujo.md).
|
||||
|
||||
## Manifest (manifiesto)
|
||||
|
||||
**En una frase:** un archivo YAML que describe qué quieres que exista
|
||||
en Kubernetes (un Deployment, un Service, un Ingress...).
|
||||
|
||||
**Detalle:** Kubernetes no se controla escribiendo código imperativo
|
||||
("crea un pod"), sino declarando el resultado deseado ("quiero 2
|
||||
réplicas de esta imagen corriendo"). El manifiesto es ese documento.
|
||||
|
||||
## App of Apps
|
||||
|
||||
**En una frase:** un patrón donde una Application de Argo CD no
|
||||
despliega una app directamente, sino que despliega *otras*
|
||||
Applications.
|
||||
|
||||
**Detalle:** en este lab, `application.yaml` (raíz) vigila la carpeta
|
||||
`apps/` de `apps-registry`; cada archivo ahí dentro es una Application
|
||||
hija que a su vez apunta a un workload real. Permite agregar una app
|
||||
nueva con un solo archivo YAML nuevo.
|
||||
|
||||
## Kustomize
|
||||
|
||||
**En una frase:** una forma de componer manifiestos de Kubernetes sin
|
||||
plantillas (a diferencia de Helm).
|
||||
|
||||
**Detalle:** un archivo `kustomization.yaml` lista qué recursos YAML
|
||||
incluir y qué parches aplicarles, sin necesidad de un lenguaje de
|
||||
templating separado.
|
||||
|
||||
## TODO
|
||||
|
||||
- Namespace, ResourceQuota, LimitRange, NetworkPolicy
|
||||
- Ingress vs. Service vs. Deployment
|
||||
- StatefulSet (por qué Postgres lo usa y el frontend no)
|
||||
- Init container
|
||||
@@ -0,0 +1,25 @@
|
||||
# Red y exposición
|
||||
|
||||
> **TODO (fase 2+):** explicar cada dominio, por qué está proxied vs
|
||||
> DNS-only en Cloudflare, y el manejo de TLS extremo a extremo. Tabla
|
||||
> de dominios abajo es real, pendiente de expandir con capturas/ejemplos.
|
||||
|
||||
## Dominios activos
|
||||
|
||||
| Dominio | Servicio | Namespace | TLS |
|
||||
|---|---|---|---|
|
||||
| `shop.cruzcloud.net` | Frontend Next.js (storefront) | `ecommerce` | Cloudflare (edge) |
|
||||
| `commerce.cruzcloud.net` | Medusa Store/Admin API | `ecommerce` | Cloudflare (edge) |
|
||||
| `media.cruzcloud.net` | MinIO (imágenes de producto) | `ecommerce` | Cloudflare (edge) |
|
||||
| `gitea.cruzcloud.net` | Gitea | — | Cloudflare (edge) |
|
||||
| `docs.cruzcloud.net` | Este portal | `docs` | Cloudflare (edge) |
|
||||
|
||||
## TODO
|
||||
|
||||
- Diagrama de TLS termination (dónde termina TLS realmente: en
|
||||
Cloudflare, no en Traefik — el tráfico interno NPM→k3d es HTTP plano).
|
||||
- Explicar por qué el transporte del túnel se fijó a HTTP/2 en vez de
|
||||
QUIC (ver [ADR 0003](../decisiones/0003-por-que-http2-sobre-quic-tunnel.md)
|
||||
y el [playbook del 504](../playbooks/incidente-504-gitea-tunnel.md)).
|
||||
- Patrón de diagnóstico "interno sano, público roto" (ver
|
||||
[playbook de imágenes NPM](../playbooks/incidente-imagenes-npm.md)).
|
||||
@@ -0,0 +1,43 @@
|
||||
# Visión general
|
||||
|
||||
> **TODO (fase 2+):** narrativa completa explicando cada capa del
|
||||
> diagrama, con foco en el "por qué" de cada frontera de confianza. El
|
||||
> diagrama de abajo es real (validado contra `docs/known-issues.md` y
|
||||
> los playbooks de incidentes de `apps-registry`), pero el texto que lo
|
||||
> acompaña es solo el esqueleto.
|
||||
|
||||
## Flujo de una petición pública
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
User(["Usuario"]) -->|HTTPS| CF["Cloudflare<br/>(DNS proxied + túnel)"]
|
||||
CF -->|HTTP/2, red host| CFD["cloudflared<br/>(app ZimaOS)"]
|
||||
CFD --> NPM["Nginx Proxy Manager<br/>192.168.68.61:90"]
|
||||
NPM --> LB["k3d-lab-cluster-serverlb<br/>(k3d-proxy, 90→80)"]
|
||||
LB --> TR["Traefik<br/>(ingress del cluster)"]
|
||||
TR -->|Host: shop.cruzcloud.net| FE["frontend-svc<br/>(Next.js)"]
|
||||
TR -->|Host: commerce.cruzcloud.net| MED["medusa-svc<br/>(Medusa API)"]
|
||||
TR -->|Host: media.cruzcloud.net| MIN["minio-svc<br/>(imágenes de producto)"]
|
||||
TR -->|Host: gitea.cruzcloud.net| GIT["Gitea"]
|
||||
TR -->|Host: docs.cruzcloud.net| DOCS["docs-portal-svc<br/>(este sitio)"]
|
||||
|
||||
subgraph GitOps["Control plane GitOps"]
|
||||
ARGO["Argo CD"] -->|sync| FE
|
||||
ARGO -->|sync| MED
|
||||
ARGO -->|sync| DOCS
|
||||
GIT -.->|watch repos| ARGO
|
||||
end
|
||||
```
|
||||
|
||||
## Componentes
|
||||
|
||||
| Capa | Qué es | Dónde vive |
|
||||
|---|---|---|
|
||||
| Cloudflare Tunnel | Expone `*.cruzcloud.net` a internet sin abrir puertos en el NAS | Fuera del cluster, app nativa ZimaOS (`cloudflared`) |
|
||||
| Nginx Proxy Manager | Enruta por dominio hacia el cluster | Fuera del cluster, contenedor `nginxproxymanager` |
|
||||
| k3d | Cluster Kubernetes local (multi-nodo, Docker) | Host ZimaOS |
|
||||
| Traefik | Ingress controller del cluster | Dentro de k3d |
|
||||
| Argo CD | Sincroniza el estado del cluster con Git (patrón App of Apps) | Namespace `argocd` |
|
||||
| Gitea | Origen de verdad de los manifiestos y del código de las apps | Contenedor propio, expuesto vía túnel |
|
||||
|
||||
Ver también: [Red y exposición](red-y-exposicion.md) · [Flujo GitOps](gitops-flujo.md) · [Glosario](glosario.md)
|
||||
Reference in New Issue
Block a user