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.
206 lines
8.7 KiB
Markdown
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`.
|