Durante siete módulos el curso ha ido dejando puertas entornadas. En la 03-04 configuraste un canary moviendo pesos del ALB a mano y se dijo que existían herramientas que hacen eso solas analizando métricas. En la 04-03 guardaste los secretos en el almacén del repositorio y se dijo que llega un punto en que eso deja de bastar. En la 02-04 mediste cobertura y se admitió que la cobertura miente. En la 07-05 firmaste imágenes con Cosign y se mencionó un marco llamado SLSA sin desarrollarlo.
Esta lección abre esas puertas. Pero con una disciplina estricta, que es la misma que la 08-02 te enseñó a aplicar a los consejos ajenos: cada herramienta se presenta por el problema que resuelve, no por lo que es. Para cada una encontrarás el momento del curso donde el problema apareció, cómo encaja en el pipeline que ya tienes, un ejemplo breve de configuración y —la parte que casi nunca se escribe— cuándo NO la necesitas.
No es un tutorial de nada. Es un catálogo de reconocimiento: para que cuando un problema aparezca, sepas que existe algo que lo resuelve y qué te va a costar.
Contenido
- Gestión de secretos más allá del CI
- Entrega progresiva automatizada
- GitOps
- Políticas como código
- Calidad y análisis
- Cadena de suministro
- Pruebas
- Desarrollo local y reproducibilidad
- Plataforma interna
- Observabilidad
- Tabla de adopción por tamaño de equipo
- El criterio para dejar entrar una herramienta
- Errores Comunes y Consejos
- Ejercicios
- Conclusión
- Gestión de secretos más allá del CI
El problema, y dónde apareció. En la 04-03 y la 07-05 guardaste los secretos en el almacén de secretos del repositorio o de la organización. Funciona bien y es lo correcto para empezar. Deja de bastar cuando aparece alguna de estas cuatro condiciones:
- El secreto lo necesita también la aplicación en ejecución, no solo el pipeline. El almacén del CI inyecta variables durante el workflow; no le sirve de nada al contenedor que lleva tres semanas corriendo en ECS.
- Hay que rotar y no se puede. Rotar la contraseña de la base de datos implica cambiarla en el almacén del CI, en la definición de tarea y en la propia base de datos, coordinadamente y a mano.
- Hace falta auditoría. "¿Quién leyó este secreto y cuándo?" El almacén del CI te dice quién lo editó, no quién lo usó.
- Los secretos se multiplican por entornos y equipos y ya no caben en una lista plana.
1.1. AWS Secrets Manager (y Parameter Store)
Es la opción de menor fricción si ya estás en AWS, como Reservalia. El secreto se guarda en el servicio, la tarea de ECS lo recibe por referencia y nunca pasa por el pipeline: el workflow ya no ve la contraseña de la base de datos en ningún momento, lo que reduce la superficie de exposición de golpe.
{
"containerDefinitions": [{
"name": "api",
"image": "<cuenta>.dkr.ecr.eu-west-1.amazonaws.com/reservalia-api@sha256:abc123...",
"secrets": [
{
"name": "DATABASE_URL",
"valueFrom": "arn:aws:secretsmanager:eu-west-1:<cuenta>:secret:reservalia/prod/db-Ab3xY9"
}
]
}]
}El agente de ECS resuelve el ARN al arrancar la tarea usando el rol de ejecución. La rotación automática se configura en el propio servicio con una función Lambda.
Contrapartidas. Cuesta dinero por secreto y por llamada a la API —poco, pero Diego lo va a preguntar; Parameter Store con parámetros SecureString es la alternativa más barata y menos capaz—. Y te ata al proveedor: los ARN no se migran.
Cuándo NO lo necesitas. Si todos tus secretos son de pipeline (tokens de registro, credenciales de despliegue) y tu aplicación no consume ninguno en ejecución, el almacén del CI más OIDC es suficiente y más simple.
1.2. HashiCorp Vault
El gestor de secretos de propósito general y agnóstico de proveedor. Aporta dos cosas que un servicio de nube básico no da:
- Secretos dinámicos. En lugar de guardar una credencial de base de datos, Vault la genera bajo demanda con caducidad. La aplicación pide credenciales, recibe un usuario y contraseña válidos una hora, y Vault los revoca solos. Un secreto que caduca en una hora es un secreto que casi no se puede filtrar.
- Políticas y auditoría finas, con motor de identidad propio y registro de cada lectura.
# En un workflow: autenticarse en Vault con el token OIDC de GitHub Actions
- uses: hashicorp/vault-action@<sha-completo>
with:
url: https://vault.interno.example.com
method: jwt
role: github-actions-reservalia
secrets: |
secret/data/reservalia/prod token | TOKEN_DESPLIEGUE ;Igual que con AWS en la 03-02, aquí no hay credencial de larga duración: el runner presenta su token OIDC y Vault decide si ese repositorio y esa rama pueden leer esa ruta.
Contrapartidas, y son grandes. Vault autoalojado es un sistema crítico más que operar: alta disponibilidad, sellado y desellado, copias de seguridad, actualizaciones. Si Vault cae, no despliega nadie y posiblemente no arranca nada. Es una pieza de infraestructura seria, y su versión gestionada cuesta dinero. La 06-01 decía de Jenkins que el coste real no es la licencia sino quién lo mantiene; con Vault pasa exactamente igual.
Cuándo NO lo necesitas. Casi siempre, con menos de 20 personas y una sola nube. La respuesta correcta para Reservalia es Secrets Manager, no Vault. Vault empieza a compensar con varias nubes, requisitos de auditoría estrictos o cuando los secretos dinámicos resuelven un problema de cumplimiento concreto.
1.3. SOPS + age: secretos versionados en Git
Un enfoque distinto y muy infravalorado: cifrar los secretos y commitearlos. SOPS cifra solo los valores de un YAML o JSON, dejando las claves legibles, de modo que el git diff sigue siendo útil.
# config/prod.enc.yaml — commiteado sin problema
database:
host: db.reservalia.internal
password: ENC[AES256_GCM,data:pQx8fK2m...,tag:9dK...,type:str]
sops:
age:
- recipient: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8page es la herramienta de cifrado moderna que sustituye a GPG para este uso: claves cortas, sin anillo de claves, sin la complejidad histórica de GPG. En el pipeline, la clave de descifrado es el único secreto que vive en el almacén del CI — has reducido N secretos a uno.
Ventajas reales: los secretos se versionan, se revisan en PR, siguen la misma promoción que el código, y el historial dice quién cambió qué y cuándo. Contrapartidas: rotar un destinatario exige recifrar todos los ficheros; y aunque esté cifrado, el material está en el repositorio para siempre, así que un algoritmo roto o una clave filtrada dentro de cinco años compromete todo el histórico.
Cuándo sí es la elección correcta: equipos pequeños con muchos ficheros de configuración por entorno, sobre todo con Kubernetes, donde encaja de forma natural.
1.4. External Secrets Operator
Específico de Kubernetes. Sincroniza secretos desde un gestor externo (Secrets Manager, Vault, etc.) hacia Secret nativos del clúster, para que las aplicaciones los consuman de la forma estándar sin saber de dónde vienen.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: reservalia-db
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: reservalia-db-secret
data:
- secretKey: DATABASE_URL
remoteRef:
key: reservalia/prod/db
property: urlCuándo NO lo necesitas. Si no usas Kubernetes, es irrelevante. Reservalia, en ECS, no lo necesita.
1.5. El criterio de decisión
graph TD
A["¿El secreto lo usa solo el pipeline?"] -->|Sí| B["Almacén del CI + OIDC<br/>Suficiente"]
A -->|"No: la app lo necesita<br/>en ejecución"| C["¿Una sola nube?"]
C -->|Sí| D["Gestor de la nube<br/>Secrets Manager / Parameter Store"]
C -->|"No, o hace falta<br/>auditoría fina"| E["Vault"]
B --> F["¿Muchos ficheros de config<br/>por entorno?"]
F -->|Sí| G["SOPS + age<br/>como complemento"]
Y una regla que vale más que el diagrama: la mejor gestión de secretos es no tener el secreto. OIDC eliminó las claves de AWS del pipeline en la 03-02; los roles de IAM eliminan credenciales entre servicios de la nube. Cada secreto que consigues eliminar es uno que no hay que gestionar, rotar ni auditar.
- Entrega progresiva automatizada
El problema, y dónde apareció. En la 03-04 montaste un canary desviando un 10 % del tráfico con pesos de ALB, y luego alguien —tú— miraba Grafana y decidía si promocionar o revertir. Eso tiene tres defectos: depende de que haya un humano mirando, el criterio es subjetivo, y en la práctica se acaba promocionando por impaciencia.
La entrega progresiva automatizada convierte ese juicio en una especificación declarativa: qué métricas, qué umbrales, cada cuánto, y qué hacer si fallan.
2.1. Argo Rollouts
Sustituye el Deployment de Kubernetes por un recurso Rollout que sabe hacer canary y blue-green con análisis automático.
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: reservalia-api
spec:
replicas: 6
strategy:
canary:
steps:
- setWeight: 10
- pause: { duration: 5m }
- analysis:
templates:
- templateName: tasa-error
- setWeight: 50
- pause: { duration: 10m }
- setWeight: 100apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: tasa-error
spec:
metrics:
- name: tasa-error-5xx
interval: 1m
count: 5
successCondition: result[0] < 0.01 # menos del 1 % de 5xx
failureLimit: 1
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(http_requests_total{job="reservalia-api",status=~"5.."}[2m]))
/
sum(rate(http_requests_total{job="reservalia-api"}[2m]))Si la consulta supera el umbral una vez, el rollout revierte solo, sin que nadie mire nada. Es exactamente el rollback automático por métricas de la 03-05, pero declarado en el recurso en vez de escrito en un script del workflow.
2.2. Flagger
Resuelve lo mismo con otra filosofía: en lugar de sustituir el Deployment, lo envuelve. Se declara un recurso Canary que referencia el Deployment existente, y Flagger orquesta el despliegue progresivo manipulando la malla de servicio o el ingress.
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: reservalia-api
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: reservalia-api
analysis:
interval: 1m
threshold: 5 # 5 fallos consecutivos -> rollback
maxWeight: 50
stepWeight: 10
metrics:
- name: request-success-rate
thresholdRange: { min: 99 }
interval: 1m
- name: request-duration
thresholdRange: { max: 500 } # p99 en ms
interval: 1m
webhooks:
- name: prueba-de-humo
type: pre-rollout
url: http://flagger-loadtester.test/
metadata:
cmd: "curl -sf http://reservalia-api-canary/health"Fíjate en el webhook de tipo pre-rollout: es el smoke test de la 03-02, ejecutado contra la versión canary antes de enviarle tráfico real.
Diferencia práctica entre ambos. Argo Rollouts da control explícito paso a paso y encaja bien con Argo CD; Flagger es más automático y menos intrusivo con los manifiestos existentes. Ambos requieren métricas fiables y un control de tráfico capaz (malla de servicio, ingress compatible o proveedor de tráfico soportado).
Cuándo NO los necesitas —y es importante—. Los dos son exclusivos de Kubernetes. Reservalia está en ECS: no aplican. Y hay una condición previa más dura que la plataforma: necesitas métricas de calidad y volumen de tráfico suficiente. Un canary al 10 % en un servicio con 5 peticiones por minuto no genera señal estadística: 30 peticiones en cinco minutos no distinguen una tasa de error del 1 % de una del 4 %. En ese régimen, el análisis automático es teatro y un rolling con health checks más un rollback rápido es honestamente mejor.
La condición de entrada: tráfico suficiente para que una degradación del 1 % sea detectable en la ventana de análisis, métricas ya instrumentadas y en las que confías (03-06 hecha de verdad), y despliegues lo bastante frecuentes como para que automatizar el juicio compense configurarlo.
- GitOps
El problema, y dónde apareció. En la 03-02 tu cd.yml hace push: el pipeline tiene credenciales sobre producción y ejecuta el despliegue. Eso implica que el CI es una pieza con permisos muy altos, y que el estado real del entorno puede divergir del código sin que nadie se entere. Alguien toca algo a mano en la consola, y el repositorio deja de describir la realidad. La 03-03 lo llamó drift.
GitOps invierte la dirección: un agente dentro del clúster observa un repositorio y aplica lo que ahí esté. El pipeline ya no despliega: solo escribe en el repositorio de despliegues.
graph LR
A[ci.yml<br/>construye y publica<br/>imagen por digest] --> B[Repositorio de despliegues<br/>manifiestos + digest]
B --> C{Agente GitOps<br/>Argo CD / Flux}
C -->|"reconcilia cada 3 min"| D[Clúster]
D -.->|"detecta drift"| C
C -.->|"revierte cambios<br/>manuales"| D
El patrón del repositorio de despliegues. Se separa el código de la aplicación de los manifiestos de despliegue. El CI, al publicar la imagen, hace commit del nuevo digest en el repositorio de despliegues; el agente lo detecta y reconcilia. El historial de git es el historial de despliegues, y revertir es un git revert.
# Argo CD: una Application que observa el repositorio de despliegues
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: reservalia-prod
spec:
project: default
source:
repoURL: https://github.com/reservalia/despliegues.git
targetRevision: main
path: entornos/produccion
destination:
server: https://kubernetes.default.svc
namespace: reservalia
syncPolicy:
automated:
prune: true # borra lo que ya no está en git
selfHeal: true # revierte cambios manuales en el clústerselfHeal: true es el drift detection en su forma más contundente: si alguien edita algo a mano, se deshace solo en la siguiente reconciliación.
Argo CD frente a Flux. Argo CD tiene interfaz web y es más accesible para equipos que empiezan; Flux es más ligero, más orientado a CLI y a componerse con otras piezas. Ambos son proyectos graduados de la CNCF —con lo que eso significa según la 08-02— y la elección entre ellos rara vez es el factor decisivo.
Contrapartidas. Otra pieza que operar; dos repositorios y por tanto dos historiales que hay que saber correlacionar cuando algo falla; y una depuración menos directa ("¿por qué no ha sincronizado?" es una pregunta nueva). Además, no todo encaja: las migraciones de base de datos de la 04-06 no son declarativas y siguen necesitando su propio mecanismo.
Cuándo NO lo necesitas. Sin Kubernetes, no aplica en su forma canónica. Y con un solo entorno y un equipo pequeño, el cd.yml de la 03-02 con OIDC hace el mismo trabajo con muchas menos piezas. GitOps compensa con varios clústeres, varios entornos, varios equipos y cuando el drift es un problema real y medido, no hipotético.
- Políticas como código
El problema, y dónde apareció. En la 04-03 y la 03-03 estableciste reglas: los contenedores no corren como root, los buckets no son públicos, las acciones se fijan por SHA. Esas reglas viven hoy en la revisión de código, es decir, en la atención de un humano cansado un viernes por la tarde. Las políticas como código las convierten en una verificación automática que falla el PR.
4.1. OPA / Rego y Conftest
OPA es un motor genérico de políticas; Rego, su lenguaje; Conftest, la herramienta que lo aplica a ficheros de configuración en el pipeline.
# politicas/terraform.rego
package main
deny[msg] {
recurso := input.resource_changes[_]
recurso.type == "aws_s3_bucket_public_access_block"
recurso.change.after.block_public_acls == false
msg := sprintf("El bucket '%s' permite ACLs públicas", [recurso.address])
}
deny[msg] {
recurso := input.resource_changes[_]
recurso.type == "aws_db_instance"
not recurso.change.after.storage_encrypted
msg := sprintf("La base de datos '%s' no tiene el almacenamiento cifrado", [recurso.address])
}- name: Validar politicas de infraestructura
run: |
terraform plan -out=plan.tfplan
terraform show -json plan.tfplan > plan.json
conftest test plan.json --policy politicas/Lo potente es que se evalúa el plan, no el estado aplicado: la política falla antes de tocar nada, en el PR, con un mensaje que dice qué recurso y por qué. Es la puerta de calidad de la 02-05 aplicada a la infraestructura.
Y funciona sobre cualquier estructura de datos, incluidos tus propios workflows:
package main
deny[msg] {
job := input.jobs[nombre]
paso := job.steps[_]
contains(paso.uses, "@v") # usa etiqueta en vez de SHA
not startswith(paso.uses, "actions/")
msg := sprintf("El job '%s' usa una accion de terceros sin fijar por SHA: %s", [nombre, paso.uses])
}Esto convierte en automática la regla de la 07-05 que hasta ahora dependía de que alguien se acordara en la revisión.
4.2. Kyverno
Alternativa específica de Kubernetes que se escribe en YAML en lugar de en Rego, lo que baja mucho la barrera de entrada. Actúa como admission controller: rechaza recursos que violen la política en el momento de aplicarlos al clúster.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: exigir-imagenes-por-digest
spec:
validationFailureAction: Enforce
rules:
- name: solo-digest
match:
any:
- resources:
kinds: [Pod]
validate:
message: "Las imagenes deben referenciarse por digest (sha256:), no por etiqueta."
pattern:
spec:
containers:
- image: "*@sha256:*"Es la regla del artefacto inmutable de la 02-06, aplicada por el clúster y no por la buena voluntad de quien escribe el manifiesto.
Contrapartidas. Rego tiene una curva de aprendizaje real y poco transferible. Y hay un riesgo mayor: una política mal escrita bloquea a todo el mundo. Se despliegan siempre primero en modo aviso (Audit), se observa qué rompen durante unas semanas, y solo entonces se pasan a bloqueo.
Cuándo NO lo necesitas. Con tres personas que se revisan los PRs entre ellas, escribir Rego para reglas que se aplican dos veces al mes es sobrecoste. Compensa cuando la regla es importante, se repite mucho, y hay más gente de la que cabe en una conversación —tres o cuatro equipos en adelante—. Excepción: si tienes una sola regla crítica que no puede fallar nunca, un grep en un paso del workflow la aplica igual de bien y en dos minutos.
- Calidad y análisis
El problema, y dónde apareció. La 02-05 montó ESLint, Prettier, tsc y una puerta de calidad. Faltan dos capas: análisis semántico más profundo, y los linters de las propias herramientas del pipeline, que es el hueco que casi todo el mundo tiene abierto.
5.1. Los linters del pipeline (empieza por aquí)
Esta es la sección de mejor relación coste/beneficio de toda la lección. Cinco herramientas, todas gratuitas, todas de una línea:
| Herramienta | Verifica | Qué error real detecta |
|---|---|---|
actionlint |
Workflows de GitHub Actions | Expresiones inválidas, needs a un job inexistente, shell mal escrito dentro del run, contextos no disponibles en ese evento |
yamllint |
YAML en general | Indentación, duplicados de clave, el clásico on: interpretado como booleano |
hadolint |
Dockerfile | apt-get install sin --no-install-recommends, capas mal ordenadas, USER root olvidado, uso de latest |
tflint |
Terraform | Tipos de instancia inexistentes, atributos obsoletos, convenciones no seguidas |
shellcheck |
Scripts de shell | Variables sin comillas, comparaciones mal escritas, errores que solo aparecen con un nombre de fichero con espacios |
- name: Lint del pipeline
run: |
actionlint
hadolint Dockerfile
shellcheck scripts/*.sh
tflint --recursivePor qué importa más de lo que parece. La 04-05 insistía en que el pipeline es código y debe tratarse como tal. Estos linters son las pruebas unitarias del pipeline: detectan en segundos errores que, sin ellos, se descubren tras un ciclo completo de commit, espera y fallo. actionlint en particular ahorra la mayoría de las iteraciones de prueba y error con YAML de Actions.
Cuándo NO los necesitas: nunca. Son la excepción de esta lección: gratis, instantáneos y sin contrapartida. Si te llevas una sola herramienta, que sea esta línea.
5.2. SonarQube / SonarCloud
Ya apareció en la 02-05. Aporta análisis semántico, seguimiento de deuda técnica en el tiempo y —lo más útil— el concepto de código nuevo: la puerta de calidad se aplica solo a lo que cambia el PR, no a los 200.000 errores heredados. Es lo que permite aplicarlo a un legacy como el de la 05-04 sin bloquear a nadie.
Contrapartidas. SonarQube autoalojado es un servicio más que mantener, con su base de datos; SonarCloud es gratuito solo para proyectos públicos. Y genera mucho ruido inicial que hay que calibrar o el equipo aprende a ignorarlo.
5.3. Semgrep
Búsqueda de patrones con conciencia de sintaxis. Su valor es que puedes escribir tus propias reglas en minutos, con una sintaxis parecida al código que buscas:
rules:
- id: sin-console-log-en-produccion
pattern: console.log(...)
paths:
include: ["apps/api/src/**"]
message: "Usa el logger estructurado, no console.log (ver PIPELINE.md)"
severity: WARNING
languages: [typescript]
- id: sql-por-concatenacion
pattern: |
$DB.query("..." + $VAR)
message: "Posible inyeccion SQL: usa consultas parametrizadas"
severity: ERROR
languages: [typescript]Frente a CodeQL (04-03), Semgrep es más rápido y mucho más fácil de extender; CodeQL es más profundo en análisis de flujo de datos. No compiten tanto como parece: Semgrep para las reglas tuyas, CodeQL para las vulnerabilidades genéricas.
Cuándo NO lo necesitas. Si aún no tienes reglas propias que quieras imponer, ESLint con sus plugins de seguridad cubre buena parte en un proyecto TypeScript. Semgrep entra cuando dices "esto ya lo hemos comentado tres veces en revisiones".
- Cadena de suministro
La 04-03 y la 07-05 cubrieron lo esencial. Aquí está lo que faltaba y el marco que lo ordena.
6.1. SLSA: el marco que da nombre a lo que ya hiciste
SLSA (Supply-chain Levels for Software Artifacts, slsa.dev) define niveles progresivos de integridad de la cadena de construcción. La idea central: poder demostrar que el artefacto que despliegas viene del código que crees, construido por el proceso que crees.
| Nivel | Qué exige, en esencia | Qué hiciste en el curso |
|---|---|---|
| Nivel 1 | El proceso de construcción está documentado y genera procedencia | La 07-05 genera atestación de procedencia |
| Nivel 2 | La construcción ocurre en un servicio alojado, con procedencia firmada y verificable | Runners de GitHub + firma con Cosign: estás aquí |
| Nivel 3 | Además, el proceso está aislado y la procedencia es infalsificable incluso ante un constructor comprometido | Requiere garantías del constructor; no se consigue solo con configuración |
Su utilidad práctica no es certificarse: es tener vocabulario para decir dónde estás y qué te falta. En una entrevista o ante un cliente que pregunta por la cadena de suministro, "cumplimos SLSA nivel 2 y esta es la atestación" vale mucho más que enumerar herramientas.
6.2. Sigstore y Cosign
Ya usados en la 07-05. Lo que conviene entender: la firma sin claves (keyless). En lugar de gestionar una clave privada —el problema que arruina la mayoría de intentos de firma—, Cosign obtiene un certificado efímero ligado a la identidad OIDC del workflow y registra la firma en un log público de transparencia (Rekor).
# Firmar (en el workflow, sin ninguna clave que gestionar)
cosign sign --yes "$IMAGEN@$DIGEST"
# Verificar antes de desplegar: no solo que esté firmada,
# sino QUIEN la firmo y desde donde
cosign verify \
--certificate-identity-regexp "https://github.com/reservalia/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
"$IMAGEN@$DIGEST"El error frecuente es verificar solo que existe firma. Sin --certificate-identity-regexp, cualquiera puede firmar esa imagen y la verificación pasará. La firma responde "¿quién?", no "¿es buena?".
6.3. Syft, Grype, Trivy y Dependency-Track
- Syft genera SBOM (CycloneDX o SPDX) a partir de una imagen o un directorio.
- Grype escanea vulnerabilidades sobre ese SBOM.
- Trivy hace ambas cosas y además escanea IaC, secretos y configuración de Kubernetes. Es lo que usaste en la 07-05 y sigue siendo la elección sensata por consolidación.
Lo nuevo es Dependency-Track: un servicio donde almacenas los SBOM de todos tus artefactos desplegados y que te avisa cuando aparece una vulnerabilidad nueva que afecta a algo que ya está en producción.
- name: Publicar SBOM en Dependency-Track
run: |
curl -X POST "https://dtrack.interno.example.com/api/v1/bom" \
-H "X-Api-Key: ${{ secrets.DTRACK_API_KEY }}" \
-F "project=${{ vars.DTRACK_PROJECT_ID }}" \
-F "[email protected]"Por qué esto importa y es el hueco real que deja el curso. Tu escaneo de la 07-05 responde "¿esta imagen tiene vulnerabilidades conocidas hoy?". La pregunta que rompe empresas es la inversa: "acaba de publicarse una vulnerabilidad crítica en una librería, ¿en cuáles de mis 40 servicios en producción está?". Sin un inventario de SBOM, esa respuesta cuesta días de trabajo manual. Con él, es una consulta.
Cuándo NO lo necesitas. Con dos servicios, el inventario cabe en tu cabeza. A partir de diez o quince artefactos desplegados, deja de caber.
6.4. Renovate
Alternativa a Dependabot (04-02) considerablemente más potente:
{
"extends": ["config:recommended"],
"packageRules": [
{
"matchUpdateTypes": ["minor", "patch"],
"matchCurrentVersion": "!/^0/",
"groupName": "dependencias no mayores",
"automerge": true,
"schedule": ["before 6am on monday"]
},
{
"matchManagers": ["github-actions"],
"pinDigests": true,
"groupName": "acciones de GitHub"
}
],
"vulnerabilityAlerts": { "labels": ["seguridad"], "automerge": false }
}Tres cosas que Dependabot no hace igual de bien: agrupar actualizaciones en un solo PR (10 PRs semanales pasan a 1), automerge condicional cuando el CI pasa, y pinDigests que fija automáticamente las acciones por SHA y las mantiene al día — resolviendo la tensión de la 08-02 entre fijar y quedarse atrás. Además cubre más gestores y más ficheros (Dockerfile, Terraform, workflows).
Contrapartidas. Más configuración y una curva más pronunciada. Y el automerge exige confiar de verdad en tu suite de pruebas: si tu CI tiene "verde falso" (04-04), acabas de automatizar la introducción de fallos.
Cuándo NO lo necesitas. Dependabot está integrado y basta hasta que el ruido de PRs sea insoportable. La señal de migrar: cuando la gente empiece a cerrar los PRs de dependencias sin mirarlos.
- Pruebas
7.1. Testcontainers
El problema. En la 02-04 las pruebas de integración usaban una base de datos como servicio del workflow, o dobles. Lo primero es configuración duplicada entre local y CI; lo segundo miente sobre el comportamiento real de PostgreSQL.
Testcontainers levanta dependencias reales en contenedores desde el código de la prueba, con ciclo de vida gestionado:
import { PostgreSqlContainer } from "@testcontainers/postgresql";
let contenedor: StartedPostgreSqlContainer;
beforeAll(async () => {
contenedor = await new PostgreSqlContainer("postgres:16-alpine").start();
process.env.DATABASE_URL = contenedor.getConnectionUri();
await ejecutarMigraciones();
}, 60_000);
afterAll(async () => { await contenedor.stop(); });
test("reserva solapada es rechazada por la restriccion de exclusion", async () => {
await crearReserva({ inicio: "10:00", fin: "11:00" });
await expect(crearReserva({ inicio: "10:30", fin: "11:30" }))
.rejects.toThrow(/exclusion/);
});Ese test verifica una restricción de PostgreSQL que ningún doble puede simular. Y funciona igual en tu portátil que en el runner, sin duplicar configuración: es la reproducibilidad de la 02-03 aplicada a las pruebas.
Contrapartidas. Más lento (arrancar contenedores cuesta segundos) y necesita Docker disponible en el runner. Se mitiga reutilizando contenedores entre suites.
7.2. Pact y las pruebas de contrato
El problema, de la 05-03. Con varios servicios, las pruebas E2E completas son lentas y frágiles, pero sin ellas nadie sabe si un cambio en la API rompe a un consumidor.
Pact invierte el planteamiento: el consumidor declara qué espera, se publica ese contrato, y el proveedor verifica en su propio pipeline que lo cumple. Sin levantar los dos servicios juntos.
// En el pipeline del consumidor (apps/web)
await proveedor.addInteraction({
state: "existe una reserva con id 42",
uponReceiving: "peticion de la reserva 42",
withRequest: { method: "GET", path: "/reservas/42" },
willRespondWith: {
status: 200,
body: { id: 42, estado: like("confirmada"), inicio: iso8601DateTime() }
}
});El valor real no es técnico sino organizativo: el proveedor descubre que va a romper a alguien antes de desplegar, en su propio CI y sin coordinar con nadie.
Cuándo NO lo necesitas. Con un solo backend y un solo frontend en el mismo repositorio y desplegados juntos —Reservalia— el contrato lo garantizan los tipos compartidos de packages/compartido y unas pocas pruebas de integración. Pact entra cuando los servicios se despliegan por separado y los equipos son distintos. Antes de eso es ceremonia.
7.3. k6
Pruebas de carga como código, ejecutables en el pipeline:
import http from 'k6/http';
import { check } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 50 },
{ duration: '1m', target: 50 },
{ duration: '20s', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<400'], // p95 por debajo de 400 ms
http_req_failed: ['rate<0.01'],
},
};
export default function () {
const res = http.get(`${__ENV.URL_BASE}/api/disponibilidad?negocio=42`);
check(res, { 'estado 200': (r) => r.status === 200 });
}Lo importante son los thresholds: si no se cumplen, k6 sale con código distinto de cero y el job falla. Eso convierte el rendimiento en una puerta de calidad más, no en un informe que nadie lee.
Dónde ponerlo. No en cada PR: en la rama principal tras el despliegue a staging, o en una ejecución nocturna. Es la única forma realista de detectar una regresión de rendimiento antes de que la detecte un cliente — el problema que apareció en la 07-04 con el endpoint lento.
Cuándo NO lo necesitas. Si tu tráfico está lejos de cualquier límite y no hay quejas de latencia, es optimización prematura convertida en tiempo de CI.
7.4. Mutation testing (Stryker)
El problema, y es la respuesta honesta a la 02-04. La cobertura mide qué líneas se ejecutan durante los tests, no qué comportamiento se verifica. Un test sin una sola aserción da cobertura del 100 %. Es la métrica más fácil de engañar de todo el pipeline, y todo el mundo lo sabe pero pocos hacen algo.
El mutation testing hace lo que ninguna otra técnica: introduce fallos deliberados en tu código (cambia un > por un >=, invierte una condición, borra una llamada) y comprueba si algún test falla. Si nadie se queja, ese test no estaba verificando nada.
{
"testRunner": "vitest",
"mutate": ["apps/api/src/dominio/**/*.ts"],
"thresholds": { "high": 80, "low": 60, "break": 50 },
"incremental": true
}Es, literalmente, verificar por el lado negativo —la costumbre que la 07-06 señaló como la más valiosa del curso— aplicada automáticamente a la suite entera. La primera ejecución sobre un proyecto con 85 % de cobertura y 45 % de mutantes muertos es una experiencia formativa que ningún argumento sustituye.
Contrapartidas, y son serias. Es lentísimo: cada mutante ejecuta la suite. Por eso mutate apunta solo al dominio, incremental está activado, y se ejecuta semanalmente o bajo demanda, nunca en cada PR.
Cuándo NO lo necesitas. Si tu suite es pequeña o tu cobertura es baja, arregla eso primero. El mutation testing es una herramienta de refinamiento, no de arranque. Y en código de fontanería (controladores, mapeos) da mutantes supervivientes irrelevantes que solo generan ruido.
- Desarrollo local y reproducibilidad
El problema. "En mi máquina funciona", que la 02-03 atacó desde el lado del build, sigue vivo del lado del entorno de desarrollo. Y su primo: el ciclo de commit-esperar-fallar para depurar un workflow.
8.1. act
Ejecuta workflows de GitHub Actions localmente en Docker.
act pull_request -W .github/workflows/ci.yml -j test
act -W .github/workflows/cd.yml --secret-file .secrets.localConvierte un ciclo de 6 minutos en uno de 40 segundos. Limitaciones importantes: no emula fielmente los entornos con revisores, workflow_run, la caché de Actions ni los permisos del token. Sirve para la lógica de los pasos, no para el comportamiento de la plataforma. Con eso claro, ahorra mucho tiempo.
8.2. Dev Containers
Define el entorno de desarrollo como código, en el repositorio:
{
"name": "Reservalia",
"image": "mcr.microsoft.com/devcontainers/typescript-node:20",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/devcontainers/features/terraform:1": { "version": "1.7.5" }
},
"postCreateCommand": "npm ci",
"customizations": {
"vscode": { "extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] }
}
}El valor medible: el tiempo de incorporación de alguien nuevo pasa de un día a media hora. Y elimina la clase entera de bugs "yo tengo Node 18".
8.3. asdf / mise y Nix
asdf y mise fijan versiones de herramientas por proyecto con un fichero declarativo:
Barato, útil y con adopción inmediata. Nix ofrece reproducibilidad mucho más fuerte —hasta las dependencias del sistema— a cambio de una curva de aprendizaje notoria. Nix es de las pocas herramientas de esta lección de las que se puede decir con honestidad: extraordinaria si el equipo entero se compromete, contraproducente si solo la entiende una persona.
8.4. pre-commit
Ejecuta verificaciones antes del commit, con un marco multi-lenguaje que va más allá de Husky (02-05):
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.2
hooks: [{ id: gitleaks }]
- repo: https://github.com/rhysd/actionlint
rev: v1.6.27
hooks: [{ id: actionlint }]Una advertencia que la 02-05 ya daba: los hooks locales son una comodidad, nunca una garantía. Se saltan con --no-verify. Todo lo que importe debe verificarse también en el CI. Gitleaks en pre-commit está bien porque evita el bochorno; Gitleaks en el CI es lo que te protege.
- Plataforma interna
Backstage (creado en Spotify, hoy en la CNCF) es un portal de desarrollo: catálogo de servicios con dueño, plantillas para crear servicios nuevos ya con pipeline, documentación técnica junto al código y una interfaz única sobre herramientas dispersas.
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: reservalia-api
annotations:
github.com/project-slug: reservalia/monorepo
grafana/dashboard-selector: "tags @> 'reservalia-api'"
spec:
type: service
lifecycle: production
owner: equipo-reservas
system: reservasLa idea que sí importa: los golden paths. Un camino recomendado, documentado y automatizado para hacer lo habitual. "Crear un servicio nuevo" pasa de dos días de copiar y pegar a diez minutos con pipeline, observabilidad y alertas ya configuradas. Y —esto es lo que lo hace estratégico— quien se sale del camino puede hacerlo, pero asume el mantenimiento.
La advertencia, y es la más importante de la lección. Backstage es un proyecto de desarrollo de software en sí mismo: hay que desplegarlo, mantenerlo, escribir plugins y actualizarlo. Una plataforma interna sin usuarios es un proyecto de vanidad, y hay muchísimos: portales preciosos que nadie abre porque el equipo tenía diez servicios y ya sabía dónde estaba todo.
Cuándo NO lo necesitas. Casi siempre. La señal legítima: varios equipos, decenas de servicios, y gente perdiendo tiempo real y medible en encontrar quién es el dueño de qué o en montar servicios nuevos. Si puedes nombrar de memoria todos tus servicios y sus dueños, no lo necesitas.
Y el orden correcto: primero el golden path, después el portal. Una plantilla de repositorio con el pipeline ya montado —lo que la 04-05 llamaba workflows reutilizables— da el 80 % del beneficio con el 5 % del coste.
- Observabilidad
OpenTelemetry es el estándar transversal: un conjunto de APIs, SDKs y un colector que instrumentan trazas, métricas y logs de forma independiente del backend. Su valor no es técnico sino estratégico: instrumentas una vez y cambias de proveedor sin tocar el código.
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
new NodeSDK({
serviceName: 'reservalia-api',
instrumentations: [getNodeAutoInstrumentations()],
}).start();Con las auto-instrumentaciones se obtienen trazas de HTTP, PostgreSQL y las librerías comunes sin escribir instrumentación manual. Es la ampliación natural de la 03-06, donde las trazas quedaron como el pilar menos desarrollado.
El resto del ecosistema completa los pilares: Prometheus (métricas, ya usado en la 07-04), Grafana (visualización), Loki (logs con el mismo modelo de etiquetas que Prometheus, lo que permite saltar de un pico a sus logs), Tempo (trazas). Sentry cubre otra cosa distinta y complementaria: errores de aplicación con traza de pila, agrupación inteligente y contexto de la versión desplegada — responde "qué excepción y en qué línea", que ni las métricas ni los logs contestan bien.
Métricas DORA automatizadas. Existen herramientas específicas para calcularlas, pero la conclusión honesta —la misma de la 07-04— es que las cuatro métricas se calculan con consultas a la API de tu forja y de tu sistema de incidentes, y que hacerlo tú te obliga a definir con precisión qué cuentas como despliegue y como fallo. Esa definición es la mitad del valor, y una herramienta que la toma por ti te la roba.
Cuándo NO lo necesitas. OpenTelemetry completo con colector propio es sobrecoste para un servicio pequeño donde las métricas de la 07-04 y unos logs estructurados bastan. Empieza por las cuatro señales de oro y añade trazas cuando tengas un problema de latencia distribuida que no sepas explicar.
- Tabla de adopción por tamaño de equipo
| Herramienta | 3 personas | 15 personas | 50+ personas |
|---|---|---|---|
Linters de pipeline (actionlint, hadolint, shellcheck) |
Imprescindible | Imprescindible | Imprescindible |
| Trivy / escaneo de imagen | Imprescindible | Imprescindible | Imprescindible |
| Dependabot | Imprescindible | Migrar a Renovate | Renovate |
| Gestor de secretos de la nube | Recomendable | Imprescindible | Imprescindible |
| Testcontainers | Recomendable | Recomendable | Recomendable |
| Cosign + SBOM | Recomendable | Imprescindible | Imprescindible |
| Dev Containers / mise | Recomendable | Recomendable | Imprescindible |
act |
Útil | Útil | Útil |
| Semgrep con reglas propias | Prematuro | Recomendable | Imprescindible |
| SonarQube | Prematuro | Recomendable | Recomendable |
| k6 en el pipeline | Prematuro | Recomendable | Imprescindible |
| Dependency-Track | Prematuro | Recomendable | Imprescindible |
| Mutation testing | Prematuro | Útil, semanal | Útil, semanal |
| Pact | Lastre | Recomendable si hay servicios separados | Imprescindible |
| GitOps (Argo CD / Flux) | Lastre | Recomendable con Kubernetes | Imprescindible con Kubernetes |
| Entrega progresiva (Flagger / Rollouts) | Lastre | Depende del tráfico | Recomendable |
| Políticas como código (OPA / Kyverno) | Lastre | Útil para 2-3 reglas críticas | Imprescindible |
| Vault autoalojado | Lastre | Prematuro salvo requisito | Recomendable |
| Backstage | Lastre | Prematuro | Recomendable si hay dolor real |
| OpenTelemetry completo | Prematuro | Recomendable | Imprescindible |
| Nix | Lastre salvo compromiso total | Depende | Depende |
Cómo leer "lastre". No significa mala herramienta: significa que en ese tamaño el coste de operarla supera al beneficio. Vault en un equipo de tres no es un Vault pequeño; es un Vault entero mantenido por gente que también escribe el producto. La complejidad no escala hacia abajo.
- El criterio para dejar entrar una herramienta
Tres preguntas. Si alguna no tiene respuesta, la herramienta no entra.
1. ¿Qué problema medido resuelve?
Medido es la palabra clave. No "mejora la seguridad" sino "el mes pasado perdimos 6 horas rotando un secreto filtrado, y esto lo habría evitado". Si no puedes citar un incidente, un tiempo perdido o una métrica que empeora, no tienes un problema: tienes curiosidad. La curiosidad es legítima —explórala en un proyecto personal, no en el pipeline de producción—.
2. ¿Quién la mantiene?
Doble pregunta. Fuera: ¿quién desarrolla el proyecto? ¿Una empresa, una fundación, una persona? ¿Cuándo fue el último commit? Un proyecto CNCF graduated (08-02) es una apuesta muy distinta a un repositorio personal con doce estrellas. Dentro: ¿quién de tu equipo la mantiene cuando falle un viernes? Si la respuesta es "el que la trajo", tienes un factor bus de uno sobre una pieza del camino a producción.
3. ¿Qué pasa si la quitamos?
Si la respuesta es "nada grave", no debería haber entrado. Si es "no podemos desplegar", necesitas un plan de salida antes de adoptarla: cómo se sale, cuánto cuesta, qué datos hay que migrar. Las herramientas se adoptan en una tarde y se abandonan durante años.
Y una cuarta, opcional pero muy reveladora: ¿podríamos conseguir el 80 % del beneficio con 20 líneas de script? Con sorprendente frecuencia, sí. Un grep en un paso del workflow sustituye una política de OPA para una sola regla. Una plantilla de repositorio sustituye a Backstage al principio. La respuesta no siempre es "usa el script", pero la pregunta te obliga a nombrar qué aporta la herramienta más allá de lo obvio.
Errores Comunes y Consejos
- Adoptar por moda y no por problema. El error central del módulo. Se reconoce por una señal: no puedes explicar en una frase qué dejará de doler.
- Adoptar varias a la vez. Cuando algo se rompe no sabes cuál fue. Una herramienta cada vez, con al menos dos semanas de uso real antes de la siguiente.
- Confundir "lo usa una empresa grande" con "me sirve". Ellos tienen equipo de plataforma dedicado. Es la señal de alarma de la 08-02 aplicada a las herramientas.
- Meter herramientas lentas en el camino crítico. Mutation testing o k6 en cada PR convierten un CI de 4 minutos en uno de 40, y el equipo empieza a saltárselo. Lo lento va fuera del PR: nocturno, semanal o post-merge (04-04).
- Desplegar políticas en modo bloqueo desde el primer día. Modo aviso primero, siempre. Una política mal escrita bloquea a todo el mundo y quema la idea entera para meses.
- Verificar firmas sin verificar identidad.
cosign verifysin--certificate-identity-regexpda una sensación de seguridad falsa. Cualquiera puede firmar. - Automerge sin confiar en la suite. Renovate con automerge sobre un CI con verde falso automatiza la introducción de fallos.
- Construir la plataforma antes que el camino. Primero la plantilla y el workflow reutilizable; el portal después, y solo si duele.
- Consejo: empieza siempre por los linters del pipeline. Cinco minutos, cero coste, beneficio inmediato. No hay otra herramienta de esta lección con esa relación.
- Consejo: documenta cada adopción en el
PIPELINE.mdcon el formato de la 07-06: qué problema, qué se descartó, qué condición obligaría a revisarlo. Tu yo de dentro de dos años necesita saber por qué está eso ahí. - Consejo: escribe también los descartes. "Evaluamos Vault en marzo y lo descartamos porque X" ahorra que alguien reabra la discusión cada seis meses.
Ejercicios
Ejercicio 1 — Tres equipos, tres decisiones
Para cada situación, decide qué herramienta (o ninguna) de esta lección adoptarías, justifica con las tres preguntas del apartado 12, y di qué descartas y por qué:
a) Equipo de 4 personas, monolito Node.js en ECS, un entorno. Han sufrido dos incidentes en tres meses por credenciales de base de datos rotadas mal: alguien cambia la contraseña en RDS y se olvida de actualizarla en algún sitio. Presupuesto de herramientas: bajo pero no nulo.
b) Equipo de 18 personas, 12 microservicios en Kubernetes, despliegan unas 30 veces por semana. Cuando se publica una vulnerabilidad crítica en una librería popular, tardan entre uno y dos días en saber qué servicios están afectados.
c) Equipo de 6 personas, aplicación web con 400 usuarios internos. El tech lead propone montar Backstage "para que todo esté organizado y porque es lo que se lleva".
Ejercicio 2 — El plan de adopción de Reservalia
Reservalia está hoy donde acabó el módulo 4: 12 despliegues/semana, 3,5 h de lead time, 3,8 % de CFR, 9 min de restore. Equipo de tres. ECS Fargate, GitHub Actions, sin Kubernetes. Diego sigue vigilando cada euro.
Elige tres herramientas de esta lección para adoptar en los próximos seis meses, en orden, y para cada una: qué métrica DORA (o qué otro dolor concreto) pretende mover, cómo se lo justificarías a Diego, y qué señal indicaría que la adopción fue un error.
Ejercicio 3 — El descarte razonado
Elige una herramienta de esta lección que te resulte atractiva y que hayas decidido no adoptar en tu contexto. Escribe la entrada de PIPELINE.md correspondiente, con el formato de la 07-06: qué problema resolvería, por qué se descarta hoy, qué alternativa más barata se usa en su lugar, y la condición concreta y observable que obligaría a reabrir la decisión.
Soluciones
Solución 1
a) Equipo de 4, incidentes por rotación de credenciales.
Adoptar: AWS Secrets Manager, con la aplicación leyendo el secreto por referencia desde la definición de tarea de ECS.
- ¿Qué problema medido? Dos incidentes en tres meses con causa idéntica. La causa raíz no es el descuido: es que el mismo valor vive en tres sitios y la consistencia depende de que alguien recuerde los tres. Es un problema de diseño, y eliminar la duplicación lo elimina.
- ¿Quién la mantiene? AWS. Servicio gestionado, sin operación por parte del equipo. Rotación automática configurable con Lambda si más adelante hace falta.
- ¿Qué pasa si la quitamos? Se vuelve al estado anterior con un cambio en la definición de tarea. Salida barata y sin migración de datos.
Descartado: Vault. Resolvería lo mismo y más, pero exige operar un sistema crítico con cuatro personas que ya están al 100 %. Es exactamente el caso de "lastre" de la tabla del apartado 11.
Descartado: SOPS. Versionaría los secretos, pero no resuelve el problema declarado —la aplicación seguiría leyendo el valor desde la configuración desplegada, y la rotación seguiría siendo un cambio coordinado—.
b) 18 personas, 12 microservicios, uno o dos días para saber si están afectados.
Adoptar: Dependency-Track, alimentado con los SBOM que el pipeline ya debería estar generando con Syft o Trivy.
- ¿Qué problema medido? De 24 a 48 horas de trabajo manual por cada vulnerabilidad crítica publicada. Es tiempo caro de gente cara, es recurrente y —lo más grave— es tiempo durante el cual estás expuesto sin saberlo.
- ¿Quién la mantiene? Proyecto OWASP, con comunidad establecida. Dentro: hay que asignar dueño; con 18 personas es viable.
- ¿Qué pasa si la quitamos? Se vuelve al escaneo por artefacto y a la respuesta manual. Los SBOM siguen generándose, así que la salida no pierde nada.
La clave del razonamiento: su escaneo actual responde "¿esta imagen es segura hoy?" y su problema es el inverso, "¿dónde está esta librería ahora mismo?". Ninguna cantidad de escaneo en el pipeline responde a eso; hace falta inventario.
Complemento razonable: Renovate con agrupación, porque con 12 servicios el ruido de Dependabot es alto y la mayoría de esas vulnerabilidades se cierran actualizando.
c) 6 personas, 400 usuarios internos, propuesta de Backstage.
Ninguna. Rechazar la propuesta, y hacerlo con las tres preguntas en vez de con una opinión:
- ¿Qué problema medido? Ninguno declarado. "Que todo esté organizado" no es un problema: es una aspiración estética. La pregunta que zanja la conversación: ¿cuántas horas hemos perdido en los últimos tres meses buscando quién es el dueño de un servicio, o montando un servicio nuevo? Con seis personas, la respuesta será cercana a cero, porque el catálogo cabe en una conversación.
- "Porque es lo que se lleva" es literalmente la señal de alarma de la 08-02 y el antipatrón central de esta lección.
- ¿Qué pasa si lo quitamos? Nada. Que la respuesta sea "nada" antes siquiera de adoptarlo es la prueba de que no debe entrar.
Contrapropuesta constructiva, porque rechazar sin alternativa suele fracasar: si lo que de verdad molesta es la inconsistencia entre servicios, el 80 % del beneficio está en una plantilla de repositorio con el pipeline ya montado y un CODEOWNERS bien mantenido (02-07, 04-05). Coste: una tarde. Y si dentro de un año hay 30 servicios y gente perdiéndose, Backstage entra con un problema medido detrás.
Solución 2
Hay varias combinaciones defendibles. Esta prioriza coste cero, riesgo bajo y beneficio verificable, que es lo que el contexto de Reservalia exige.
Primera: los linters del pipeline (actionlint, hadolint, shellcheck, tflint). Mes 1.
- Qué mueve: lead time. Cada iteración de prueba y error con YAML de Actions cuesta un ciclo completo de CI.
actionlintlos detecta en segundos, en local y en el PR. - A Diego: coste cero en euros, unos 15 segundos de CI, y una tarde de configuración. El argumento que le convence es de minutos de runner: menos ejecuciones fallidas por errores de sintaxis es literalmente menos gasto.
- Señal de error: si genera avisos que el equipo silencia sistemáticamente, la configuración está mal calibrada, no la herramienta. Se ajustan reglas, no se quita.
Segunda: Testcontainers para las pruebas de integración. Meses 2-3.
- Qué mueve: tasa de fallo de cambios (3,8 %). Los fallos que llegan a producción en un sistema con buena cobertura unitaria suelen venir de la frontera con la base de datos: restricciones, transacciones, comportamiento de PostgreSQL que ningún doble reproduce. Y de paso reduce el tiempo de restauración, porque son fallos que se detectan antes.
- A Diego: gratis, y el argumento es de coste evitado. Un incidente de producción en Reservalia cuesta más en tiempo de las tres personas que todo el CI del mes. Se puede cuantificar mirando los últimos tres incidentes y preguntando cuántos habría cazado un test contra PostgreSQL real.
- Señal de error: si el CI se alarga más de dos minutos y no aparece ningún fallo capturado en tres meses, sobra. Se mide antes y después.
Tercera: k6 con thresholds, tras el despliegue a staging, no en cada PR. Meses 4-6.
- Qué mueve: tasa de fallo y tiempo de restauración, en el modo de fallo que peor lleva Reservalia: la degradación gradual. Un endpoint que pasa de 200 ms a 900 ms no rompe ningún test y no dispara ninguna alerta hasta que el SLO empieza a consumirse. Es exactamente lo que apareció en la 07-04.
- A Diego: gratis, ejecutado una vez por despliegue a staging, unos 2 minutos fuera del camino crítico del PR. Y el argumento de negocio es fuerte: la ventana crítica de las reservas es donde más carga hay y donde una caída cuesta clientes —la restricción 3 del encargo de la 07-06—.
- Señal de error: si los umbrales fallan constantemente por ruido del entorno de staging y la gente empieza a reintentar el job sin mirar, el problema es el entorno o los umbrales. Un umbral que se ignora es peor que no tenerlo, porque enseña al equipo a ignorar puertas.
Descartados explícitamente y por qué: Vault (lastre para tres personas), GitOps y entrega progresiva (exigen Kubernetes; Reservalia está en ECS), Backstage (sin problema medido), políticas como código (con tres personas las reglas caben en la revisión; si alguna es crítica, un grep en el workflow basta), mutation testing (interesante, pero la prioridad es que los tests capturen fallos reales antes de refinar los que ya hay).
Solución 3
Ejemplo, sobre entrega progresiva automatizada:
## Decisión 14 — Entrega progresiva automatizada (Flagger / Argo Rollouts)
**Fecha:** 2026-08-02
**Estado:** Descartado por ahora
**Problema que resolvería.** Hoy el despliegue a producción es rolling con
health checks, y la decisión de "esto va mal, revierte" la toma una persona
mirando Grafana durante los diez minutos siguientes. Eso depende de que
alguien mire, el criterio es subjetivo y en la práctica se promociona por
impaciencia. Flagger o Argo Rollouts convertirían ese juicio en umbrales
declarados con análisis automático de métricas.
**Por qué se descarta hoy.** Dos motivos, y el segundo es el decisivo:
1. Ambas herramientas son exclusivas de Kubernetes. Reservalia corre en ECS
Fargate y adoptarlas implicaría migrar la plataforma entera — un coste
desproporcionado para el problema que resuelven.
2. Aunque estuviéramos en Kubernetes, **no tenemos volumen de tráfico para
que el análisis sea estadísticamente significativo**. Con nuestro pico
actual, un canary al 10 % durante 5 minutos recibe unas decenas de
peticiones: no distingue una tasa de error del 1 % de una del 4 %.
El análisis automático daría una falsa sensación de rigor.
**Alternativa en uso.** Rolling con health checks + rollback por digest en
~4 minutos (decisión 6) + alerta por síntoma sobre el SLO (decisión 11). El
tiempo de restauración medido es de 9 minutos, dentro del objetivo.
**Condición de revisión.** Se reabre esta decisión si se cumplen las dos:
- (a) el tráfico sostenido supera las 500 peticiones/minuto en la ventana de
despliegue habitual, de modo que un canary al 10 % genere señal en 5 min; y
- (b) migramos a Kubernetes por otro motivo independiente, o adoptamos una
herramienta equivalente compatible con ECS.
Mientras solo se cumpla (a), la acción correcta es alargar la ventana de
observación post-despliegue y automatizar el rollback por métricas dentro
del propio `cd.yml`, no cambiar de plataforma.Lo que hace válida esta entrada no es el descarte, sino que la condición de revisión sea observable y no opinable: "500 peticiones por minuto" se comprueba en Grafana; "cuando tengamos más tráfico" no se comprueba nunca y garantiza que la discusión se reabra cada seis meses sin datos nuevos.
Conclusión
Este módulo entero se ha ordenado por una idea que conviene dejar dicha con claridad: una herramienta es una respuesta, y solo tiene sentido si existe la pregunta. Todas las de esta lección son buenas; ninguna es buena para todos.
Lo esencial:
- Secretos: el mejor secreto es el que no existe (OIDC, roles). Cuando la aplicación necesita secretos en ejecución, el gestor de la nube es la respuesta sensata; Vault entra con varias nubes o requisitos de auditoría; SOPS+age es una opción infravalorada para versionar configuración cifrada.
- Entrega progresiva y GitOps resuelven problemas reales de la 03-04 y la 03-02, pero exigen Kubernetes y, en el caso del canary automático, tráfico suficiente para que las métricas signifiquen algo.
- Políticas como código convierten reglas de revisión en verificaciones automáticas: valiosas a partir de varios equipos, sobrecoste antes, y siempre desplegadas primero en modo aviso.
- Los linters del pipeline son la única recomendación sin contrapartida de toda la lección. Cinco minutos, cero euros, beneficio inmediato.
- Cadena de suministro: SLSA da el vocabulario para decir dónde estás; verificar firma con identidad es lo que hace que firmar sirva; y el inventario de SBOM responde la pregunta que rompe empresas, que no es "¿es segura esta imagen?" sino "¿dónde está esta librería ahora?".
- Pruebas: Testcontainers acerca las pruebas a la realidad, k6 convierte el rendimiento en puerta, y el mutation testing es la respuesta honesta a la mentira de la cobertura — es verificar por el lado negativo, automatizado.
- Plataforma interna: primero el golden path, después el portal. Una plataforma sin usuarios es un proyecto de vanidad.
- Y el criterio, que vale más que el catálogo: ¿qué problema medido resuelve?, ¿quién la mantiene dentro y fuera?, ¿qué pasa si la quitamos? Más la cuarta, incómoda y reveladora: ¿bastarían veinte líneas de script?
La tabla de adopción por tamaño dice algo que merece repetirse: la complejidad no escala hacia abajo. Vault en un equipo de tres no es un Vault pequeño. Backstage con seis servicios no es un Backstage ligero. Copiar la arquitectura de una empresa de quinientas personas sin tener sus problemas te deja con sus costes y sin sus beneficios.
Ya tienes el fundamento (08-01), el contexto (08-02) y el catálogo (08-03). Falta la pregunta que ninguno de los tres responde: ¿hacia dónde vas tú? Qué sabes hacer ahora y qué no, en qué dirección profundizar, qué certificaciones sirven de algo y cuáles no, cómo demostrar lo que sabes sin un papel, y cómo mantenerse al día sin quemarse.
Esa es la última lección del curso: 08-04, Ruta de Aprendizaje, Certificaciones y Próximos Pasos.
Curso de CI/CD: Integración y Despliegue Continuo
Módulo 1: Introducción a CI/CD
- Conceptos Básicos de CI/CD
- Beneficios de CI/CD
- Herramientas Populares de CI/CD
- El Proyecto del Curso: la Aplicación que Vamos a Automatizar
- Métricas DORA: Cómo se Mide la Entrega de Software
Módulo 2: Integración Continua (CI)
- Introducción a la Integración Continua
- Configuración de un Entorno de CI
- Automatización de la Construcción
- Pruebas Automatizadas
- Calidad de Código y Análisis Estático
- Artefactos, Versionado y Promoción
- Integración con Control de Versiones
Módulo 3: Despliegue Continuo (CD)
- Introducción al Despliegue Continuo
- Automatización del Despliegue
- Infraestructura como Código y Entornos Reproducibles
- Estrategias de Despliegue
- Feature Flags, Rollback y Recuperación ante Fallos
- Monitoreo y Retroalimentación
Módulo 4: Prácticas Avanzadas de CI/CD
- Pipelines de CI/CD
- Gestión de Dependencias
- Seguridad en CI/CD
- Escalabilidad y Rendimiento
- Pipeline as Code: Plantillas, Reutilización y Pruebas del Pipeline
- Bases de Datos en el Pipeline: Migraciones Seguras
Módulo 5: Implementación de CI/CD en Proyectos Reales
- Caso de Estudio: Proyecto Web
- Caso de Estudio: Aplicación Móvil
- Caso de Estudio: Microservicios
- Caso de Estudio: Modernizar un Proyecto Legacy
Módulo 6: Herramientas y Tecnologías
- Jenkins
- GitLab CI/CD
- CircleCI
- Travis CI
- Docker y Kubernetes
- GitHub Actions a Fondo
- Comparativa y Criterios para Elegir Herramienta
Módulo 7: Ejercicios Prácticos
- Ejercicio 1: Configuración de un Pipeline Básico
- Ejercicio 2: Integración de Pruebas Automatizadas
- Ejercicio 3: Despliegue en un Entorno de Producción
- Ejercicio 4: Monitoreo y Retroalimentación
- Ejercicio 5: Endurecer el Pipeline con Seguridad y Secretos
- Proyecto Final: Pipeline Completo de Extremo a Extremo
