Files
devops a3c022c024 docs: READMEs completos, estructura final de arquetipos
Mueve los reusable workflows a .gitea/workflows/ (restriccion real de
Gitea: workflow_call no puede vivir en una ruta arbitraria, confirmado
en vivo). Corrige todos los 'uses:' cross-repo a URL absoluta
(https://gitea.cruzcloud.net/...) -- la forma corta de GitHub se
resuelve contra github.com en esta instancia, confirmado en vivo con un
test real (reusable workflow + composite action ejecutando gitleaks de
punta a punta). Limpia los archivos de prueba.
2026-08-17 00:23:24 -05:00

206 lines
8.7 KiB
Markdown

# platform-templates
Patrones reutilizables para dar de alta una app nueva en el lab
CruzCloud (ZimaOS + k3d + Gitea + Argo CD) sin escribir el pipeline y
los manifiestos K8s desde cero cada vez. Copiás una plantilla, completás
unos pocos valores, y tenés: pipeline de CI con el gate completo de
DevSecOps (Gitleaks, Trivy, Semgrep, Syft, Cosign) + manifiestos K8s con
buenas prácticas por default (resource limits, probes, non-root).
**Para quién es esto:** si estás dando de alta una app nueva en el lab,
seguí el ejemplo de abajo. Si estás migrando una app existente, mirá el
punto "Migración de apps existentes" más abajo, y andá con más cuidado
— tocás algo que ya está en producción.
## Mapa del repo
```
pipelines/ -- documentación de cada arquetipo de pipeline
(el archivo real que se invoca vive en
.gitea/workflows/, ver por qué más abajo)
.gitea/workflows/ -- los reusable workflows reales (workflow_call)
dockerfiles/ -- Dockerfiles genéricos, un ARG por cosa a cambiar
k8s-manifests/base/ -- Kustomize base: Deployment+Service+Ingress
genéricos, con securityContext/probes/resources
ya puestos
k8s-manifests/overlays-ejemplo/ -- el ejemplo de abajo, ya armado
devsecops-snippets/ -- los 6 composite actions que arman cada pipeline
```
## Hallazgo importante sobre Gitea Actions (leer antes de copiar nada)
Este repo se armó verificando en vivo, no asumiendo que Gitea Actions es
100% igual a GitHub Actions. Dos diferencias reales, confirmadas
corriendo pipelines de verdad contra esta instancia:
1. **Los reusable workflows (`workflow_call`) tienen que vivir bajo
`.gitea/workflows/`** — no en una carpeta cualquiera. Por eso
`pipelines/archetype-*/` solo tiene un `README.md`, el archivo
invocable real está en `.gitea/workflows/archetype-*.yaml`.
2. **Toda referencia cross-repo en un `uses:` necesita la URL completa**
(`https://gitea.cruzcloud.net/owner/repo/path@ref`), no la forma
corta de GitHub (`owner/repo/path@ref`) — esa forma corta se resuelve
contra **github.com** por default en esta instancia (confirmado:
intentar usarla hace que el runner intente clonar
`github.com/devops/platform-templates`, que no existe, y falle con
"Repository not found"). `actions/checkout@v3` sigue andando con la
forma corta porque ESE sí existe en github.com de verdad.
## Ejemplo end-to-end: dar de alta "mi-app-ejemplo"
Pensado para seguirse sin conocer nada más del lab que "existe Gitea en
gitea.cruzcloud.net y un cluster k3d con Argo CD sincronizando desde
ahí". Asumimos una API Node (arquetipo `node-api`) — para Next.js o un
sitio estático es el mismo procedimiento, cambiando el arquetipo.
### 1. Creá el repo de tu app en Gitea
Vía la UI (`https://gitea.cruzcloud.net/repo/create`) o `gh`/API si
tenés token con el scope correcto. Nombre: `mi-app-ejemplo`.
### 2. Copiá el Dockerfile
```bash
curl -o Dockerfile \
https://gitea.cruzcloud.net/devops/platform-templates/raw/branch/main/dockerfiles/node-api.Dockerfile
```
Ajustá los `ARG` del inicio del archivo a tu caso (puerto real, comando
de arranque) — están documentados inline, no hace falta adivinar.
### 3. Agregá el pipeline
`.gitea/workflows/build.yaml` en tu repo:
```yaml
name: Build and Push
on:
push:
branches: [main]
paths:
- 'src/**'
- 'package.json'
- 'package-lock.json'
- 'Dockerfile'
- '.gitea/workflows/build.yaml'
jobs:
build:
uses: https://gitea.cruzcloud.net/devops/platform-templates/.gitea/workflows/archetype-node-api.yaml@main
with:
app_dir: .
image_name: gitea.cruzcloud.net/devops/mi-app-ejemplo
manifest_file: k8s/deployment.yaml
secrets:
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
```
Los 4 secrets tienen que existir en tu repo (Settings → Actions →
Secrets). Si tu lab ya los tiene a nivel de organización/compartidos,
puede que no haga falta redeclararlos — depende de cómo esté armado tu
Gitea.
### 4. Generá tu par de llaves de Cosign (una vez)
```bash
cosign generate-key-pair
# genera cosign.key (privada) y cosign.pub (pública)
```
- `cosign.pub` → commiteala en tu repo (es pública a propósito).
- `cosign.key` → **no la commitees**. Subila como secret
`COSIGN_PRIVATE_KEY` (contenido del archivo) y la password que
elegiste como secret `COSIGN_PASSWORD`.
### 5. Manifiestos K8s: copiá el base y armá tu overlay
```bash
mkdir -p k8s/patches
curl -o k8s/kustomization.yaml \
https://gitea.cruzcloud.net/devops/platform-templates/raw/branch/main/k8s-manifests/overlays-ejemplo/mi-app-ejemplo/kustomization.yaml
curl -o k8s/patches/deployment.yaml \
https://gitea.cruzcloud.net/devops/platform-templates/raw/branch/main/k8s-manifests/overlays-ejemplo/mi-app-ejemplo/patches/deployment.yaml
curl -o k8s/patches/service.yaml \
https://gitea.cruzcloud.net/devops/platform-templates/raw/branch/main/k8s-manifests/overlays-ejemplo/mi-app-ejemplo/patches/service.yaml
curl -o k8s/patches/ingress.yaml \
https://gitea.cruzcloud.net/devops/platform-templates/raw/branch/main/k8s-manifests/overlays-ejemplo/mi-app-ejemplo/patches/ingress.yaml
```
Esto ya viene armado con el patrón real (ver
`k8s-manifests/overlays-ejemplo/mi-app-ejemplo/` en este mismo repo) —
solo tenés que:
1. Cambiar `mi-app-ejemplo` por el nombre real de tu app en los 3
`patches/*.yaml` y en el `kustomization.yaml` (nombre del
Deployment/Service/Ingress, `host:`, `namespace:`).
2. Ajustar imagen, puerto, y ruta del healthcheck a los reales de tu
app.
3. **Leer los comentarios de `patches/deployment.yaml` sobre
`$patch: replace`/JSON6902** antes de tocar `ports` — Kustomize NO
reemplaza esa lista con un patch de merge normal, la duplica
(confirmado en vivo armando este repo). Si tu puerto real es
distinto al 8080 del base, el rename+ports va en un patch JSON6902
aparte en tu `kustomization.yaml`, exactamente como en el ejemplo.
4. **Si tu app escucha en un puerto <1024** (80, por ejemplo): el base
saca TODAS las capabilities del contenedor por default —
necesitás agregar `capabilities.add: [NET_BIND_SERVICE]` en tu
patch, o el bind falla. Ver el comentario en
`k8s-manifests/overlays-ejemplo/mi-app-ejemplo/kustomization.yaml`
si tu imagen corre como root en vez de un usuario propio (necesitás
además `runAsNonRoot: false` a nivel pod).
### 6. Validá el resultado ANTES de commitear
```bash
kubectl kustomize k8s/
```
Revisá que el YAML que sale tenga sentido — nombre, imagen, puerto,
host correctos, sin recursos duplicados. Si algo se ve raro (dos
`ports:`, dos contenedores), revisá el punto 3 de arriba.
### 7. Creá la Application de Argo CD
Sigue el patrón de este lab: una Application de workload
(`syncPolicy` con `prune: true, selfHeal: true`) apuntando al
`k8s/` de tu repo. Preguntale a quien mantiene `apps-registry` cómo se
registra — no es parte de este repo.
### 8. Push, mirá el pipeline correr, y listo
Un `git push` a `main` dispara Gitleaks → build → Trivy → Semgrep → Syft
→ Cosign → push al registry → Argo CD sincroniza. Si algo bloquea
(CRITICAL de Trivy, firma de Cosign inválida), el log del job te dice
exactamente cuál.
## Migración de apps existentes
Si estás moviendo una app que **ya está corriendo en producción** hacia
este patrón base+overlay: no es lo mismo que dar de alta una nueva.
Antes de tocar nada:
1. Guardá el manifest actual desplegado (`kubectl get deploy <nombre>
-n <ns> -o yaml`) para comparar.
2. Armá el overlay apuntando **al mismo nombre** que ya existe (usando
los patches JSON6902 de rename, como en el ejemplo) — no dejes que
Kustomize invente un nombre nuevo, rompe el tracking de Argo CD y
la resolución DNS de otros servicios que dependan del nombre viejo.
3. **Revisá con qué usuario corre tu imagen actual.** Si no tiene un
`USER` no-root en su Dockerfile, el `runAsNonRoot: true` del base
rompe el pod (`CreateContainerConfigError`) — hace falta un override
explícito hasta que la imagen se actualice aparte. Pasó exactamente
esto migrando `docs-portal` en este mismo lab.
4. Corré `kubectl kustomize` sobre tu overlay y compará el resultado
contra el manifest guardado en el paso 1 antes de dejar que Argo CD
sincronice nada.
## Documentación ampliada
`docs.cruzcloud.net` tiene una sección dedicada a este repo con un
diagrama del flujo completo y el detalle de la migración real que se
hizo sobre `ecommerce-frontend`, `commerce-backend` y `docs-portal`.