La canalización de la lección anterior deja api-reservas en producción con un artefacto verificado, firmado y probado. Y sin embargo, en el momento en que Argo CD sincroniza el nuevo digest, ocurre algo que ninguna prueba previa puede evitar del todo: todos los usuarios pasan a la versión nueva. Si algo se escapó —una consulta que degrada bajo carga real, una condición de carrera que solo aparece con mil peticiones por segundo, una integración con la pasarela de pagos que falla con datos reales—, lo descubren todos a la vez.
Esta lección trata de eliminar ese salto. Veremos cuatro maneras de reducir el riesgo de publicar una versión: la actualización rodante que ya conocemos, el despliegue azul-verde, el canario y las banderas de funcionalidad. Y al final aplicaremos lo aprendido al caso concreto que tiene delante el equipo de Rutas Norte: publicar la nueva versión de api-reservas la semana antes del puente de mayo, cuando el tráfico se multiplica por diez durante tres días y un fallo cuesta ventas reales.
Contenido
- Por qué el
RollingUpdateno siempre basta - Azul-verde: dos versiones, un interruptor
- Migraciones de base de datos compatibles con ambas versiones
- Canario: liberar a un porcentaje de usuarios
- Argo Rollouts: automatizar el canario con análisis
- Flagger como alternativa
- Banderas de funcionalidad: separar el despliegue de la activación
- El plan de Rutas Norte para el puente de mayo
- Comparativa de las cuatro estrategias
- Por qué el
RollingUpdate no siempre basta
RollingUpdate no siempre bastaEn 02-04 vimos el RollingUpdate y con la configuración de 11-01 (maxSurge: 25%, maxUnavailable: 0) funciona bien: sin corte de servicio, sin pérdida de capacidad. Sus límites no están en la disponibilidad, sino en el control sobre el riesgo.
| Limitación | Consecuencia práctica en Rutas Norte |
|---|---|
| Mezcla versiones durante el despliegue | Con 40 réplicas y un despliegue de 8 minutos, un usuario puede ver la versión antigua en una petición y la nueva en la siguiente. Si el contrato de la API cambió, la SPA se rompe a mitad de una compra |
| No permite validar antes de comprometerse | Cuando notas el problema, la versión nueva ya atiende a una parte grande de los usuarios |
| La reversión no es instantánea | rollout undo vuelve a recorrer 40 pods: otros 6-8 minutos con usuarios afectados |
| El criterio de avance es la sonda de preparación | Un pod que responde 200 en /preparado pero devuelve 500 en el 30 % de las compras avanza igualmente |
| No hay ningún análisis automático | Nadie mira Grafana durante el despliegue de un martes a las 11 de la mañana |
El punto cuarto es el central. La sonda de preparación responde a "¿el proceso puede atender?", no a "¿esta versión funciona bien?". Son preguntas muy distintas, y solo la segunda importa para decidir si seguir adelante.
graph LR
subgraph RU["RollingUpdate"]
direction TB
A1[100% v1] --> A2[75% v1 · 25% v2] --> A3[50/50] --> A4[100% v2]
A5[Criterio de avance:<br/>readinessProbe] -.-> A3
end
subgraph BG["Azul-verde"]
direction TB
B1[100% azul] --> B2[verde desplegado<br/>sin tráfico real] --> B3[100% verde<br/>cambio instantáneo]
B4[Criterio: validación<br/>manual o automática] -.-> B3
end
subgraph CN["Canario"]
direction TB
C1[100% v1] --> C2[95% v1 · 5% v2] --> C3[75/25] --> C4[100% v2]
C5[Criterio: análisis de<br/>métricas reales] -.-> C3
end
- Azul-verde: dos versiones, un interruptor
La idea es simple: dos entornos completos coexistiendo, uno recibiendo tráfico y otro no. El Service apunta a uno de los dos mediante una etiqueta, y publicar es cambiar esa etiqueta.
2.1. Los manifiestos
# Deployment AZUL: la versión que está sirviendo hoy.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-reservas-azul
namespace: rutas-norte-pro
spec:
selector:
matchLabels:
app.kubernetes.io/name: api-reservas
rutasnorte.example/color: azul
template:
metadata:
labels:
app.kubernetes.io/name: api-reservas
rutasnorte.example/color: azul # ← la etiqueta que decide todo
rutasnorte.example/version: "2.7.0"
spec:
# ... idéntico al Deployment de 11-01
containers:
- name: api
image: registry.rutasnorte.example/rutasnorte/api-reservas@sha256:3f9c1b7e5a04c2d8f61b93ae7c05d2419e8f6ab3c4d5e6f708192a3b4c5d6e7f
---
# Deployment VERDE: la versión candidata.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-reservas-verde
namespace: rutas-norte-pro
spec:
selector:
matchLabels:
app.kubernetes.io/name: api-reservas
rutasnorte.example/color: verde
template:
metadata:
labels:
app.kubernetes.io/name: api-reservas
rutasnorte.example/color: verde
rutasnorte.example/version: "2.8.0"
spec:
containers:
- name: api
image: registry.rutasnorte.example/rutasnorte/api-reservas@sha256:9a8b7c6d5e4f30291a8b7c6d5e4f30291a8b7c6d5e4f30291a8b7c6d5e4f3029
---
# Service PRODUCTIVO: el que usa el Ingress. Su selector es el interruptor.
apiVersion: v1
kind: Service
metadata:
name: api-reservas
namespace: rutas-norte-pro
spec:
selector:
app.kubernetes.io/name: api-reservas
rutasnorte.example/color: azul # ← cambiar aquí = publicar
ports:
- { name: http, port: 80, targetPort: http }
---
# Service de PREVISUALIZACIÓN: apunta siempre a verde, sin tráfico de usuarios.
apiVersion: v1
kind: Service
metadata:
name: api-reservas-preview
namespace: rutas-norte-pro
spec:
selector:
app.kubernetes.io/name: api-reservas
rutasnorte.example/color: verde
ports:
- { name: http, port: 80, targetPort: http }
---
# Ingress de previsualización: URL interna para validar la candidata.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-reservas-preview
namespace: rutas-norte-pro
annotations:
# Solo desde la red corporativa: no es una URL pública.
nginx.ingress.kubernetes.io/whitelist-source-range: "203.0.113.0/24"
cert-manager.io/cluster-issuer: letsencrypt-produccion
spec:
ingressClassName: nginx
tls:
- hosts: ["api-preview.rutasnorte.example"]
secretName: api-preview-tls
rules:
- host: api-preview.rutasnorte.example
http:
paths:
- path: /
pathType: Prefix
backend:
service: { name: api-reservas-preview, port: { name: http } }Cada Deployment lleva su propio HPA, su PDB y su ServiceMonitor con la etiqueta de color, para poder observar ambas versiones por separado en Grafana.
2.2. El procedimiento completo
# --- 1. Desplegar la candidata en verde (sin tráfico de usuarios) ---
kubectl -n rutas-norte-pro set image deploy/api-reservas-verde \
api=registry.rutasnorte.example/rutasnorte/api-reservas@sha256:9a8b7c6d...
kubectl -n rutas-norte-pro rollout status deploy/api-reservas-verde --timeout=10m
kubectl -n rutas-norte-pro scale deploy/api-reservas-verde --replicas=8
# --- 2. Validar sin tráfico real ---
# Pruebas de humo contra la URL de previsualización
npm run pruebas:humo -- --base-url https://api-preview.rutasnorte.example
# Prueba de carga suave para descartar regresiones evidentes de rendimiento
k6 run --vus 40 --duration 5m pruebas/carga-preview.js
# Comprobación manual del flujo de compra completo por parte de producto
# --- 3. Conmutar (esto es la publicación: dura milisegundos) ---
kubectl -n rutas-norte-pro patch svc api-reservas \
-p '{"spec":{"selector":{"rutasnorte.example/color":"verde"}}}'
# --- 4. Vigilar ---
watch -n5 'curl -sG http://prometheus.rutas-norte.example/api/v1/query \
--data-urlencode "query=sum(rate(api_reservas_peticiones_total{codigo=~\"5..\"}[2m]))
/ sum(rate(api_reservas_peticiones_total[2m]))" | jq -r ".data.result[0].value[1]"'
# --- 5. Revertir, si hace falta (segundos) ---
kubectl -n rutas-norte-pro patch svc api-reservas \
-p '{"spec":{"selector":{"rutasnorte.example/color":"azul"}}}'El paso 5 es el argumento de venta del azul-verde: la reversión es un cambio de etiqueta. La versión anterior sigue viva, caliente, con sus pods listos y sus conexiones a base de datos abiertas. Se vuelve atrás en menos de cinco segundos, y no en los ocho minutos de un rollout undo.
2.3. Lo que cuesta
| Coste | Detalle en Rutas Norte |
|---|---|
| Recursos | Durante la ventana de validación hay doble capacidad desplegada: 8 réplicas azules + 8 verdes. En el pico del puente de mayo serían 40 + 40, algo inasumible |
| Conexiones en curso | Al conmutar, las conexiones abiertas contra pods azules siguen ahí hasta que se cierran. No es un corte, pero sí una transición de unos segundos |
| Estado compartido | Ambas versiones hablan con la misma base de datos y la misma caché. Este es el problema serio, y va en el apartado siguiente |
| Complejidad de manifiestos | Todo duplicado: Deployment, HPA, PDB, ServiceMonitor. Con Kustomize o Helm se maneja, pero es más superficie |
| Todo o nada | Se conmuta al 100 %. Si el fallo solo se manifiesta con carga real, lo sufren todos los usuarios durante los segundos que tardas en revertir |
La regla de Rutas Norte: azul-verde para cambios grandes y arriesgados fuera de temporada alta; nunca durante el puente de mayo, porque duplicar 40 réplicas no es viable.
- Migraciones de base de datos compatibles con ambas versiones
Aquí está el problema real del azul-verde, y también del canario y del RollingUpdate. Se pueden tener dos versiones de código a la vez. No se pueden tener dos versiones del esquema de la base de datos.
Si api-reservas 2.8.0 necesita que la columna asiento se llame plaza, y la migración se aplica al conmutar, la versión azul deja de funcionar en ese instante y la reversión se vuelve imposible.
La solución es el patrón expandir y contraer: toda migración se divide en pasos, cada uno compatible con la versión anterior y la siguiente.
graph LR V1[v2.7.0<br/>usa 'asiento'] --> E[Despliegue 1: EXPANDIR<br/>añadir columna 'plaza'<br/>+ disparador que sincroniza] E --> V2[Despliegue 2<br/>v2.8.0 escribe en ambas<br/>lee de 'plaza'] V2 --> V3[Despliegue 3<br/>v2.9.0 solo usa 'plaza'] V3 --> C[Despliegue 4: CONTRAER<br/>eliminar 'asiento'<br/>y el disparador]
3.1. El ejemplo completo
-- ===== Migración 1: EXPANDIR (compatible con 2.7.0) =====
-- Añadir, nunca renombrar ni borrar.
ALTER TABLE reservas ADD COLUMN plaza integer;
-- Copiar el histórico en lotes, sin bloquear la tabla.
UPDATE reservas SET plaza = asiento
WHERE plaza IS NULL AND id IN (SELECT id FROM reservas WHERE plaza IS NULL LIMIT 5000);
-- (se repite hasta agotar; en producción, mediante un Job por lotes)
-- Disparador que mantiene ambas columnas sincronizadas mientras
-- conviven versiones que escriben en una o en otra.
CREATE OR REPLACE FUNCTION sincronizar_plaza() RETURNS trigger AS $$
BEGIN
IF NEW.plaza IS NULL THEN NEW.plaza := NEW.asiento; END IF;
IF NEW.asiento IS NULL THEN NEW.asiento := NEW.plaza; END IF;
RETURN NEW;
END; $$ LANGUAGE plpgsql;
CREATE TRIGGER trg_sincronizar_plaza
BEFORE INSERT OR UPDATE ON reservas
FOR EACH ROW EXECUTE FUNCTION sincronizar_plaza();-- ===== Migración 2: CONTRAER (solo cuando NADIE usa 'asiento') =====
-- Semanas después, con la versión antigua ya retirada del todo.
DROP TRIGGER trg_sincronizar_plaza ON reservas;
DROP FUNCTION sincronizar_plaza();
ALTER TABLE reservas DROP COLUMN asiento;
ALTER TABLE reservas ALTER COLUMN plaza SET NOT NULL;3.2. Reglas prácticas
| Cambio deseado | Cómo hacerlo compatible |
|---|---|
| Renombrar columna | Añadir la nueva, sincronizar con disparador, migrar código, borrar la antigua después |
| Borrar columna | Dejar de usarla en el código, esperar dos publicaciones, borrarla |
| Añadir columna obligatoria | Añadirla anulable con valor por defecto, rellenar, poner NOT NULL en un paso posterior |
| Cambiar el tipo de una columna | Columna nueva con el tipo nuevo, doble escritura, migrar lecturas, borrar la antigua |
| Añadir índice | CREATE INDEX CONCURRENTLY para no bloquear la tabla |
| Cambiar el significado de un valor | Valor nuevo distinto; nunca reinterpretar uno existente |
La regla que resume todo: el esquema debe ir siempre un paso por delante del código y nunca romper la versión anterior. Cada migración se aplica en su propio despliegue, antes que el código que la aprovecha. Es lo que en 11-03 obligaba a declarar en la petición de cambio de promoción si la migración era compatible hacia atrás.
- Canario: liberar a un porcentaje de usuarios
El canario resuelve lo que el azul-verde no: en lugar de conmutar al 100 %, se expone la versión nueva a una fracción pequeña de usuarios, se observan las métricas reales y se aumenta progresivamente.
4.1. La forma pobre: por número de réplicas
Sin nada más que un Service y dos Deployments, el reparto de tráfico es proporcional al número de pods.
# 19 réplicas estables + 1 canaria ≈ 5 % del tráfico
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-reservas-canario
namespace: rutas-norte-pro
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: api-reservas
rutasnorte.example/pista: canario
template:
metadata:
labels:
app.kubernetes.io/name: api-reservas # el Service selecciona por esta
rutasnorte.example/pista: canarioEl Service productivo selecciona solo por app.kubernetes.io/name, así que envía tráfico a ambos Deployments repartido entre los 20 pods.
Funciona, y en un clúster sin controlador de Ingress capaz de repartir por peso es una opción razonable. Sus límites:
- La granularidad depende del número de réplicas. Con 4 réplicas, el mínimo es el 20 %. Bajar al 5 % exigiría 19 réplicas estables.
- El HPA interfiere. Si escala el Deployment estable, el porcentaje del canario cambia solo, sin que nadie lo decida.
- No hay pegajosidad. Un usuario alterna entre versiones petición a petición, lo que en una compra de billetes es un problema.
- No se puede segmentar. No se puede decir "solo los empleados de Rutas Norte" o "solo quien tenga esta cabecera".
4.2. La forma buena: pesos en ingress-nginx
ingress-nginx permite declarar un Ingress canario que comparte host con el principal y captura un porcentaje del tráfico.
# Ingress principal: sin cambios respecto a 11-01.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-reservas
namespace: rutas-norte-pro
annotations:
cert-manager.io/cluster-issuer: letsencrypt-produccion
spec:
ingressClassName: nginx
tls:
- hosts: ["api.rutasnorte.example"]
secretName: api-rutasnorte-tls
rules:
- host: api.rutasnorte.example
http:
paths:
- path: /
pathType: Prefix
backend:
service: { name: api-reservas-estable, port: { name: http } }
---
# Ingress CANARIO: mismo host, backend distinto, con peso.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-reservas-canario
namespace: rutas-norte-pro
annotations:
cert-manager.io/cluster-issuer: letsencrypt-produccion
# Activa el modo canario para este Ingress.
nginx.ingress.kubernetes.io/canary: "true"
# Porcentaje del tráfico que va al canario.
nginx.ingress.kubernetes.io/canary-weight: "5"
# Cabecera de escape: fuerza el canario para pruebas internas,
# independientemente del peso.
nginx.ingress.kubernetes.io/canary-by-header: "X-Rutasnorte-Canario"
nginx.ingress.kubernetes.io/canary-by-header-value: "siempre"
# Cookie: una vez asignado, el usuario se queda en su versión.
nginx.ingress.kubernetes.io/canary-by-cookie: "rutasnorte_canario"
spec:
ingressClassName: nginx
tls:
- hosts: ["api.rutasnorte.example"]
secretName: api-rutasnorte-tls
rules:
- host: api.rutasnorte.example # ← MISMO host que el principal
http:
paths:
- path: /
pathType: Prefix
backend:
service: { name: api-reservas-canario, port: { name: http } }El orden de precedencia de las anotaciones es importante y no siempre evidente:
canary-by-headercon el valor exacto → siempre al canario (onever→ nunca).canary-by-cookie→ según el valor de la cookie.canary-weight→ decisión aleatoria por el porcentaje indicado.
La cabecera es lo que permite validar en producción sin exponer a nadie:
# Validación interna dirigida al canario, con 0 % de peso
curl -H 'X-Rutasnorte-Canario: siempre' https://api.rutasnorte.example/horarios?linea=BIL-SANProgresión manual del peso:
for PESO in 5 10 25 50 100; do
kubectl -n rutas-norte-pro annotate ingress api-reservas-canario \
nginx.ingress.kubernetes.io/canary-weight="$PESO" --overwrite
echo "Peso al $PESO %. Observando 10 minutos..."
sleep 600
# aquí alguien mira Grafana y decide si sigue
doneY este bucle, con su sleep 600 y su "aquí alguien mira Grafana", es exactamente lo que hay que automatizar.
- Argo Rollouts: automatizar el canario con análisis
Argo Rollouts sustituye el Deployment por un recurso Rollout que sabe progresar por pasos, consultar métricas y decidir por sí mismo si continuar o abortar.
sequenceDiagram participant G as Git (nuevo digest) participant R as Controlador Rollout participant N as ingress-nginx participant P as Prometheus G->>R: Cambia la imagen del Rollout R->>R: Crea ReplicaSet canario R->>N: setWeight 5 % R->>P: AnalysisRun (tasa de error, p95) P-->>R: 0,04 % errores · p95 210 ms → correcto R->>N: setWeight 25 % R->>P: AnalysisRun P-->>R: 0,9 % errores → FALLO R->>N: setWeight 0 % (aborta) R->>R: Escala el canario a cero, la estable sigue intacta
5.1. El recurso Rollout
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: api-reservas
namespace: rutas-norte-pro
spec:
# replicas lo sigue gobernando el HPA, que ahora apunta al Rollout.
revisionHistoryLimit: 5
selector:
matchLabels:
app.kubernetes.io/name: api-reservas
# 'template' es idéntico al del Deployment de 11-01: mismas sondas,
# mismo securityContext, mismos recursos, mismo topologySpread.
template:
metadata:
labels:
app.kubernetes.io/name: api-reservas
spec:
serviceAccountName: api-reservas
containers:
- name: api
image: registry.rutasnorte.example/rutasnorte/api-reservas@sha256:9a8b7c6d5e4f30291a8b7c6d5e4f30291a8b7c6d5e4f30291a8b7c6d5e4f3029
# ... resto idéntico a 11-01
strategy:
canary:
canaryService: api-reservas-canario # Services que el controlador
stableService: api-reservas-estable # reetiqueta automáticamente
trafficRouting:
nginx:
stableIngress: api-reservas # crea y gestiona el Ingress canario
# Análisis que se ejecuta en paralelo a TODA la progresión.
analysis:
templates:
- templateName: analisis-api-reservas
startingStep: 1 # empieza tras alcanzar el primer peso
args:
- name: servicio-canario
value: api-reservas-canario
steps:
- setWeight: 5
- pause: { duration: 10m } # 10 min con el 5 % del tráfico
- setWeight: 25
- pause: { duration: 15m }
- setWeight: 50
- pause: { duration: 20m }
- setWeight: 75
- pause: { duration: 10m }
# Sin duración: espera aprobación humana explícita antes del 100 %.
- pause: {}
# Ventanas de seguridad
scaleDownDelaySeconds: 600 # la versión anterior sigue viva 10 min tras promocionar
abortScaleDownDelaySeconds: 30
maxSurge: "25%"
maxUnavailable: 0El último pause: {} sin duración es una decisión deliberada de Rutas Norte: el análisis automático puede llevar el canario hasta el 75 %, pero el salto final al 100 % lo confirma una persona. Es un equilibrio entre automatizar la vigilancia y conservar un punto de control humano.
5.2. El AnalysisTemplate contra Prometheus
Aquí se decide de verdad. Las consultas son PromQL sobre las métricas que api-reservas expone desde 07-03.
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: analisis-api-reservas
namespace: rutas-norte-pro
spec:
args:
- name: servicio-canario
metrics:
# --- 1. Tasa de error 5xx ---
- name: tasa-error
interval: 1m
# Tolera 2 medidas malas aisladas; 3 consecutivas abortan.
failureLimit: 2
consecutiveErrorLimit: 3
# No juzga durante los primeros 2 min: los pods se están calentando.
initialDelay: 2m
provider:
prometheus:
address: http://prometheus-operated.monitorizacion:9090
query: |
sum(rate(api_reservas_peticiones_total{
service="{{args.servicio-canario}}", codigo=~"5.."
}[2m]))
/
sum(rate(api_reservas_peticiones_total{
service="{{args.servicio-canario}}"
}[2m]))
# Éxito si el resultado es < 0,5 %. NaN (sin tráfico) también se acepta:
# sin peticiones no hay evidencia de fallo.
successCondition: result[0] < 0.005 || isNaN(result[0])
# --- 2. Percentil 95 de latencia ---
- name: latencia-p95
interval: 1m
failureLimit: 2
initialDelay: 2m
provider:
prometheus:
address: http://prometheus-operated.monitorizacion:9090
query: |
histogram_quantile(0.95,
sum by (le) (rate(api_reservas_duracion_segundos_bucket{
service="{{args.servicio-canario}}"
}[2m]))
)
successCondition: result[0] < 0.35 || isNaN(result[0]) # 350 ms
# --- 3. Comparación relativa contra la versión estable ---
# Un umbral absoluto puede pasar por alto una regresión si el sistema
# ya iba lento. Esta métrica exige que el canario no sea más de un 20 %
# peor que la estable.
- name: latencia-relativa
interval: 2m
failureLimit: 1
initialDelay: 5m
provider:
prometheus:
address: http://prometheus-operated.monitorizacion:9090
query: |
(
histogram_quantile(0.95, sum by (le) (rate(
api_reservas_duracion_segundos_bucket{service="api-reservas-canario"}[3m])))
/
histogram_quantile(0.95, sum by (le) (rate(
api_reservas_duracion_segundos_bucket{service="api-reservas-estable"}[3m])))
)
successCondition: result[0] < 1.2 || isNaN(result[0])
# --- 4. Métrica de negocio: la que de verdad importa ---
# Una versión puede tener 0 errores y latencia perfecta, y haber roto
# el botón de "confirmar reserva". Esto lo detecta.
- name: tasa-conversion
interval: 5m
failureLimit: 1
initialDelay: 10m
provider:
prometheus:
address: http://prometheus-operated.monitorizacion:9090
query: |
sum(rate(api_reservas_reservas_confirmadas_total{
service="api-reservas-canario"}[5m]))
/
sum(rate(api_reservas_reservas_iniciadas_total{
service="api-reservas-canario"}[5m]))
successCondition: result[0] > 0.55 || isNaN(result[0])La cuarta métrica es la más valiosa y la que más equipos olvidan. Un despliegue puede ser impecable en todos los indicadores técnicos y estar perdiendo la mitad de las ventas.
5.3. Promoción y abortado automáticos
Promoción automática: si todas las métricas cumplen su condición durante todos los pasos, el Rollout avanza solo por la escalera de pesos hasta el último pause: {}.
Abortado automático: en cuanto una métrica supera su failureLimit, el controlador pone el peso del canario a 0 inmediatamente, escala el ReplicaSet canario a cero y deja intacto el estable. No hay que revertir nada, porque la versión estable nunca dejó de servir la mayoría del tráfico.
Name: api-reservas
Namespace: rutas-norte-pro
Status: ✖ Degraded
Message: RolloutAborted: metric "tasa-error" assessed Failed
Strategy: Canary
Step: 2/9
SetWeight: 0
ActualWeight: 0
Images: api-reservas@sha256:3f9c1b7e (stable)
api-reservas@sha256:9a8b7c6d (canary)
Replicas:
Desired: 8
Current: 8
Updated: 0
Ready: 8
Available: 8
NAME KIND STATUS AGE INFO
⟳ api-reservas Rollout ✖ Degraded 41d
├──# revision:18
│ └──⧉ api-reservas-6d9f7b4c8 ReplicaSet • ScaledDown 14m canary
│ └──⊞ analisis-api-reservas-18 AnalysisRun ✖ Failed 12m
│ ├──📊 tasa-error Measurement ✖ Failed 0.021
│ └──📊 latencia-p95 Measurement ✔ Successful 0.198
└──# revision:17
└──⧉ api-reservas-7d4b8c9f5 ReplicaSet ✔ Healthy 14d stableEsa salida es diagnóstico completo por sí sola: se abortó en el paso 2, por tasa-error al 2,1 % frente al umbral del 0,5 %, con la latencia perfectamente bien. El siguiente sitio donde mirar son los logs de los pods canarios antes de que desaparezcan (scaleDownDelaySeconds los mantiene un rato justo para eso).
Comandos de control manual:
kubectl argo rollouts promote api-reservas -n rutas-norte-pro # avanzar un paso
kubectl argo rollouts promote api-reservas -n rutas-norte-pro --full # saltar al 100 %
kubectl argo rollouts abort api-reservas -n rutas-norte-pro # abortar ya
kubectl argo rollouts undo api-reservas -n rutas-norte-pro --to-revision=175.4. La interfaz de seguimiento
Levanta una interfaz web en localhost:3100 que muestra la escalera de pasos, el peso actual, el resultado de cada medida y los botones de promocionar y abortar. En Rutas Norte se proyecta en una pantalla durante los despliegues de la temporada alta: no es imprescindible, pero convierte una operación opaca en algo que todo el equipo entiende de un vistazo.
5.5. Convivencia con Argo CD
Un Rollout es un CRD, así que Argo CD lo sincroniza como cualquier otro recurso. Dos ajustes necesarios:
# En la Application de Argo CD
spec:
ignoreDifferences:
- group: argoproj.io
kind: Rollout
jsonPointers:
- /spec/replicas # lo gobierna el HPA
syncPolicy:
automated:
selfHeal: true
# El controlador de Rollouts modifica los Services durante la
# progresión; sin esto, selfHeal lucharía contra él.
syncOptions:
- RespectIgnoreDifferences=trueY el HPA debe apuntar al Rollout, no a un Deployment:
- Flagger como alternativa
Flagger resuelve el mismo problema con una filosofía distinta: en lugar de sustituir el Deployment, lo envuelve. Se mantiene el Deployment de siempre y se añade un recurso Canary que lo gestiona.
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: api-reservas
namespace: rutas-norte-pro
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment # ← el Deployment original, sin tocar
name: api-reservas
autoscalerRef:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
name: api-reservas
provider: nginx
service:
port: 80
targetPort: 8080
analysis:
interval: 1m
threshold: 5 # medidas fallidas antes de abortar
maxWeight: 50
stepWeight: 10
metrics:
- name: request-success-rate
thresholdRange: { min: 99.5 }
interval: 2m
- name: request-duration
thresholdRange: { max: 350 }
interval: 2m
webhooks:
- name: pruebas-de-carga
type: rollout
url: http://flagger-loadtester.plataforma/
metadata:
cmd: "hey -z 2m -q 10 -c 2 http://api-reservas-canary.rutas-norte-pro/horarios"| Aspecto | Argo Rollouts | Flagger |
|---|---|---|
| Recurso de carga | Sustituye el Deployment por Rollout |
Conserva el Deployment y lo envuelve |
| Migrar desde Deployment | Cambiar kind y ajustar HPA y Argo CD |
Añadir un Canary, sin tocar lo existente |
| Interfaz gráfica | Sí, propia y muy buena | No propia; se usa Grafana |
| CLI dedicada | kubectl argo rollouts, muy completa |
kubectl describe canary |
| Estrategias | Canario, azul-verde, con o sin malla | Canario, azul-verde, A/B, espejo de tráfico |
| Métricas | AnalysisTemplate totalmente libre |
Métricas predefinidas + personalizadas |
| Generación de carga | Externa | Integrada (loadtester) |
| Encaja mejor con | Ecosistema Argo (Argo CD, Workflows) | Ecosistema Flux |
| Control manual fino | Muy bueno (promote, abort, pasos) |
Más automático, menos intervención |
No hay una respuesta universal. Rutas Norte eligió Argo Rollouts por coherencia con Argo CD (10-05), por la CLI y la interfaz, y por la libertad total del AnalysisTemplate, que hacía falta para la métrica de conversión. Un equipo que use Flux elegiría Flagger con la misma lógica.
- Banderas de funcionalidad: separar el despliegue de la activación
Todo lo anterior controla qué versión del código corre. Las banderas de funcionalidad controlan algo distinto: qué comportamiento tiene el código que ya está corriendo.
// api-reservas: la funcionalidad viaja desplegada pero apagada.
app.post('/reservas', async (req, res) => {
const usarNuevoMotor = await banderas.activa('motor-precios-v2', {
usuarioId: req.usuario.id,
porcentaje: 5, // despliegue progresivo por usuario
listaBlanca: ['empleados-rutasnorte']
});
const precio = usarNuevoMotor
? await motorPreciosV2.calcular(req.body)
: await motorPreciosV1.calcular(req.body);
// Métrica etiquetada por bandera: permite comparar ambos caminos
// en Grafana sin desplegar nada distinto.
metricas.precioCalculado.inc({ motor: usarNuevoMotor ? 'v2' : 'v1' });
...
});| Aspecto | Canario | Bandera de funcionalidad |
|---|---|---|
| Unidad de control | La versión completa del artefacto | Una funcionalidad concreta |
| Granularidad del público | Porcentaje de peticiones, por cabecera o cookie | Por usuario, plan, región, o cualquier atributo |
| Velocidad de activación | Minutos (progresión de pesos) | Segundos (cambio de configuración) |
| Velocidad de desactivación | Segundos (abortar) | Segundos, y sin tocar el clúster |
| Quién puede accionarla | Plataforma o desarrollo | Producto, soporte, negocio |
| Coste | Doble capacidad temporal | Complejidad en el código |
| Deuda que genera | Ninguna | Ramas muertas si no se limpian |
Cuándo la bandera es mejor que el canario:
- La funcionalidad es de negocio, no técnica: quien decide activarla es producto, no ingeniería.
- Hay que activar para un segmento concreto (los clientes empresa, una región, los empleados internos), no para un porcentaje aleatorio.
- El cambio debe poder apagarse en segundos por alguien de soporte a las tres de la mañana, sin desplegar.
- Se quiere hacer una prueba A/B y medir conversión durante semanas.
- El cambio es grande y se quiere integrar en
mainpor partes sin publicar nada.
Cuándo el canario es mejor:
- El cambio es transversal: nueva versión de una biblioteca, cambio de la imagen base, refactorización interna, actualización del runtime. No hay un "si" que poner.
- El riesgo es de rendimiento o de consumo de recursos, no de comportamiento funcional.
- No quieres código muerto conviviendo en producción.
Se combinan muy bien: canario para publicar el artefacto con seguridad, banderas para activar la funcionalidad después, con el público que decida producto.
Una advertencia sobre la deuda: cada bandera duplica un camino de código y por tanto duplica lo que hay que probar. La regla de Rutas Norte es que toda bandera nace con fecha de caducidad anotada en el código, y hay una revisión trimestral que elimina las que ya cumplieron su función.
- El plan de Rutas Norte para el puente de mayo
Situación real: api-reservas 2.8.0 incluye el nuevo motor de precios dinámicos, que es la funcionalidad con la que la empresa espera aumentar ingresos durante la temporada alta. Hay que publicarla la semana anterior al puente de mayo, el peor momento posible para equivocarse.
8.1. Restricciones
| Restricción | Implicación |
|---|---|
| El puente empieza el viernes 1 de mayo | Congelación de cambios desde el miércoles 29 a las 18:00 |
| El tráfico se multiplica por diez durante tres días | No se puede duplicar capacidad: azul-verde queda descartado |
| El motor de precios afecta a la conversión | Hace falta una métrica de negocio, no solo técnica |
Hay migración de esquema (tabla reglas_precio) |
Debe ser expandir y contraer, y compatible con 2.7.0 |
| Una reversión debe ser inmediata | Descartado cualquier cambio irreversible |
8.2. El calendario
| Momento | Acción | Criterio para continuar |
|---|---|---|
| Lunes 20, 10:00 | Despliegue 1: migración de expansión (tablas nuevas, sin uso) | Migración aplicada, api-reservas 2.7.0 sin cambios en sus métricas |
| Lunes 20, 16:00 | Despliegue 2: Rollout de 2.8.0 con el motor v2 apagado por bandera |
Análisis del canario correcto en todos los pasos |
| Martes 21 | 2.8.0 al 100 %, motor v2 apagado | 24 h sin alertas ni regresión de latencia |
| Miércoles 22, 10:00 | Bandera activada para empleados (lista blanca) | Validación funcional de producto: precios correctos |
| Miércoles 22, 16:00 | Bandera al 1 % de usuarios reales | Conversión del segmento ≥ 55 %, sin quejas en soporte |
| Jueves 23 | Bandera al 10 % | Conversión y ticket medio comparables o mejores; sin errores 5xx atribuibles |
| Viernes 24 | Bandera al 50 % | Igual criterio, con volumen mayor |
| Lunes 27, 10:00 | Bandera al 100 % | Decisión conjunta de producto y plataforma |
| Miércoles 29, 18:00 | Congelación: ningún cambio hasta el martes 5 de mayo | — |
Lo importante de este plan es que separa dos riesgos distintos: el riesgo técnico de la versión nueva (canario, lunes y martes) y el riesgo de negocio del motor de precios (bandera, de miércoles a lunes). Mezclarlos habría hecho imposible saber cuál de los dos causaba un problema.
8.3. Criterios de éxito y umbrales de abortado
# AnalysisTemplate específico para esta publicación, con umbrales más
# estrictos de lo habitual por la cercanía de la temporada alta.
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: analisis-puente-mayo
namespace: rutas-norte-pro
spec:
metrics:
- name: tasa-error
interval: 30s
failureLimit: 1 # tolerancia mínima: una medida mala aborta
initialDelay: 2m
provider:
prometheus:
address: http://prometheus-operated.monitorizacion:9090
query: |
sum(rate(api_reservas_peticiones_total{service="api-reservas-canario",codigo=~"5.."}[2m]))
/ sum(rate(api_reservas_peticiones_total{service="api-reservas-canario"}[2m]))
successCondition: result[0] < 0.003 || isNaN(result[0]) # 0,3 %
- name: latencia-p95
interval: 30s
failureLimit: 1
initialDelay: 2m
provider:
prometheus:
address: http://prometheus-operated.monitorizacion:9090
query: |
histogram_quantile(0.95, sum by (le) (rate(
api_reservas_duracion_segundos_bucket{service="api-reservas-canario"}[2m])))
successCondition: result[0] < 0.30 || isNaN(result[0]) # 300 ms
- name: errores-pasarela-pagos
interval: 1m
failureLimit: 1
initialDelay: 5m
provider:
prometheus:
address: http://prometheus-operated.monitorizacion:9090
query: |
sum(rate(api_reservas_pagos_fallidos_total{service="api-reservas-canario"}[5m]))
/ sum(rate(api_reservas_pagos_intentos_total{service="api-reservas-canario"}[5m]))
successCondition: result[0] < 0.02 || isNaN(result[0]) # 2 %
- name: conversion
interval: 5m
failureLimit: 1
initialDelay: 15m
provider:
prometheus:
address: http://prometheus-operated.monitorizacion:9090
query: |
sum(rate(api_reservas_reservas_confirmadas_total{service="api-reservas-canario"}[10m]))
/ sum(rate(api_reservas_reservas_iniciadas_total{service="api-reservas-canario"}[10m]))
successCondition: result[0] > 0.55 || isNaN(result[0])| Criterio de éxito | Umbral | Umbral de abortado |
|---|---|---|
| Errores 5xx | < 0,1 % | ≥ 0,3 % en una medida |
| Latencia p95 | < 250 ms | ≥ 300 ms en una medida |
| Fallos de pasarela de pagos | < 1 % | ≥ 2 % |
| Conversión de reserva | ≥ 60 % | < 55 % |
| Reinicios de pod canario | 0 | ≥ 1 |
| Alertas nuevas en Alertmanager | 0 | Cualquiera de severidad alta |
8.4. El plan de reversión
Tres niveles, del más rápido al más lento:
- Bandera apagada (5 segundos, sin desplegar): desactiva el motor de precios v2. Cubre el 80 % de los riesgos de esta publicación. Puede hacerlo soporte sin llamar a nadie.
- Abortar el
Rollout(10 segundos):kubectl argo rollouts abort api-reservas. Devuelve todo el tráfico a la versión estable, que nunca dejó de servir. - Revertir en Git (4 minutos):
git revertdel commit de promoción enoverlays/proy sincronización de Argo CD. Es lo que deja el sistema coherente y lo que se hace siempre después del nivel 1 o 2.
La migración de expansión no se revierte: las tablas nuevas quedan ahí, sin usar, sin molestar. Su contracción está programada para junio, pasada la temporada alta.
8.5. Qué pasó realmente
En el segundo paso del canario (peso 25 %), el análisis abortó por latencia-p95 en 340 ms. La causa: el motor de precios v2 consultaba reglas_precio sin índice sobre (linea, franja_horaria). Se detectó en once minutos, afectó al 25 % del tráfico durante menos de dos minutos y no generó ninguna incidencia de cara al cliente. Se añadió el índice con CREATE INDEX CONCURRENTLY, se repitió el canario esa misma tarde y pasó limpio.
Ese es exactamente el valor de todo lo anterior: un problema que con RollingUpdate habría sido un incidente de severidad 2 en pleno puente de mayo se resolvió como una tarea de una tarde.
- Comparativa de las cuatro estrategias
| Criterio | Rodante | Azul-verde | Canario | Banderas |
|---|---|---|---|---|
| Riesgo de exposición | Alto: llega al 100 % sin validar | Medio: todo o nada al conmutar | Bajo: porcentaje pequeño primero | Muy bajo: segmento elegido |
| Coste en recursos | Bajo (+25 % temporal) | Alto (×2 durante la ventana) | Medio (+10-25 %) | Ninguno |
| Complejidad de implantación | Nula, viene de serie | Baja: dos Deployments y un selector | Media-alta: controlador y análisis | Media: servicio de banderas y disciplina |
| Velocidad de reversión | Lenta: minutos | Rápida: segundos | Inmediata: abortar | Inmediata: apagar |
| Validación con tráfico real antes de comprometerse | No | Solo sintética | Sí | Sí, segmentada |
| Automatización de la decisión | No | Difícil | Sí, con métricas | Manual, o por experimento |
| Convivencia de versiones | Sí, descontrolada | No (salto limpio) | Sí, controlada | Sí, en el mismo proceso |
| Exigencia sobre migraciones | Compatibles | Compatibles | Compatibles | Compatibles |
| Deuda que deja | Ninguna | Manifiestos duplicados | Configuración de análisis | Código muerto si no se limpia |
| Cuándo usarla | Cambios pequeños y rutinarios | Cambios grandes fuera de temporada alta | Publicaciones de riesgo con métricas fiables | Funcionalidades de negocio y pruebas A/B |
Elección práctica en Rutas Norte:
- Rodante para
tienda-weby para cambios de configuración: bajo riesgo, no compensa la complejidad. - Canario con Argo Rollouts para toda publicación de
api-reservas: es el componente crítico y tiene métricas de calidad. - Azul-verde para migraciones de infraestructura grandes (como el cambio de versión mayor de PostgreSQL de 11-02), y solo fuera de temporada alta.
- Banderas para toda funcionalidad visible para el usuario, siempre, sobre cualquiera de las anteriores.
Errores Comunes y Consejos
- Hacer azul-verde con una migración de esquema no compatible. La reversión, que era el argumento entero de la estrategia, deja de ser posible en el instante en que se aplica la migración. Expandir y contraer, siempre.
- Umbrales de análisis absolutos y nada más. Si el sistema ya va lento, un p95 de 340 ms puede pasar el umbral de 350 ms siendo una regresión del 70 %. Añade siempre una métrica relativa contra la versión estable.
- Olvidar
isNaN(result[0])en las condiciones. Con peso al 5 % y poco tráfico, una consulta puede devolverNaNy el análisis fallar sin que haya ningún problema real. - Canario sin métrica de negocio. Todos los indicadores técnicos perfectos y la conversión por los suelos es un escenario real y frecuente. La métrica de negocio es la que justifica el canario.
- Analizar desde el primer segundo. Los pods recién arrancados tienen la caché fría y el JIT sin calentar. Sin
initialDelay, se aborta por falsos positivos sistemáticamente. - Pesos por número de réplicas con HPA activo. El autoescalado cambia el porcentaje sin que nadie lo decida. Si hay HPA, usa reparto por peso en el Ingress.
- Banderas eternas. Cada bandera sin caducidad duplica caminos de código para siempre. Fecha de caducidad al crearla y revisión trimestral.
- Consejo: ensaya el canario con una versión idéntica a la actual. Desplegar el mismo digest como canario valida toda la maquinaria sin ningún riesgo, y descubre umbrales mal calibrados antes de que importen.
- Consejo: deja la versión anterior viva un rato tras promocionar (
scaleDownDelaySeconds). Los problemas que aparecen a los cinco minutos son más frecuentes que los de los cinco primeros segundos.
Ejercicios
Ejercicio 1: elegir estrategia y justificarla
Para cada uno de estos cambios en Rutas Norte, elige la estrategia más adecuada y justifícala en dos o tres frases:
- Actualizar la imagen base de
api-reservasde Node 22.4 a 22.9 por un parche de seguridad, sin cambios funcionales. - Cambiar el diseño de la página de selección de asiento de
tienda-web, sobre la que producto quiere medir conversión durante tres semanas. - Sustituir el motor de búsqueda de rutas por uno nuevo, con el mismo contrato de API pero un algoritmo interno completamente distinto.
- Corregir una errata en un texto de la SPA.
Ejercicio 2: detectar el fallo en un AnalysisTemplate
metrics:
- name: tasa-error
interval: 10s
failureLimit: 0
provider:
prometheus:
address: http://prometheus-operated.monitorizacion:9090
query: |
sum(rate(api_reservas_peticiones_total{codigo=~"5.."}[30s]))
/ sum(rate(api_reservas_peticiones_total[30s]))
successCondition: result[0] < 0.001Este análisis aborta prácticamente todos los canarios, incluso los correctos. Identifica cuatro problemas y propón el manifiesto corregido.
Ejercicio 3: diseñar la publicación completa
api-reservas va a incorporar la posibilidad de cancelar una reserva desde la web. Requiere: una columna nueva estado en la tabla reservas (valores activa y cancelada), un endpoint nuevo DELETE /reservas/{id}, un cambio en tienda-web para mostrar el botón, y notificación por correo mediante worker-notificaciones. La publicación es en octubre, fuera de temporada alta. Diseña la secuencia completa: cuántos despliegues, qué estrategia en cada uno, en qué orden, y qué permite revertir en cada punto.
Soluciones
Solución 1.
- Canario. No hay funcionalidad que activar ni condicional que escribir, así que la bandera no aplica. El riesgo es técnico (rendimiento, compatibilidad de dependencias nativas) y precisamente eso es lo que el análisis de métricas detecta. Azul-verde sería innecesariamente caro.
- Bandera de funcionalidad, complementada con el despliegue rodante habitual de
tienda-web. Producto necesita controlar el segmento y medir conversión durante semanas: eso es un experimento A/B, no una publicación. El canario dura minutos, no tres semanas. - Canario con análisis reforzado, incluyendo métricas de negocio (calidad de los resultados de búsqueda medida por clics en la primera opción) y latencia relativa contra la estable. Como el contrato de API no cambia, no hay que tocar la SPA ni el esquema; el riesgo es de comportamiento y rendimiento, exactamente lo que el canario valida. Complementariamente, una bandera permitiría volver al motor antiguo sin desplegar.
- Rodante. Riesgo prácticamente nulo. Montar un canario para una errata es sobreingeniería que además retrasa la corrección.
Solución 2. Problemas:
| # | Problema | Efecto |
|---|---|---|
| 1 | Sin initialDelay |
Juzga los primeros segundos, con los pods calentando y la caché fría: falsos positivos sistemáticos |
| 2 | failureLimit: 0 |
Una única medida mala, aunque sea un pico transitorio de un segundo, aborta |
| 3 | Sin filtrar por service="...-canario" |
Mide el error de toda la aplicación, estable incluida: si la estable tiene errores, el canario nunca puede pasar |
| 4 | Ventana [30s] con interval: 10s |
Ventana demasiado corta: muy poca muestra, mucho ruido, y medidas solapadas |
| 5 | Sin isNaN |
Con el 5 % del tráfico puede no haber peticiones en la ventana y devolver NaN, que falla la condición |
Corrección:
metrics:
- name: tasa-error
interval: 1m
failureLimit: 2
consecutiveErrorLimit: 3
initialDelay: 2m
provider:
prometheus:
address: http://prometheus-operated.monitorizacion:9090
query: |
sum(rate(api_reservas_peticiones_total{service="api-reservas-canario",codigo=~"5.."}[2m]))
/ sum(rate(api_reservas_peticiones_total{service="api-reservas-canario"}[2m]))
successCondition: result[0] < 0.005 || isNaN(result[0])Solución 3. Cinco despliegues:
- Migración de expansión.
ALTER TABLE reservas ADD COLUMN estado text DEFAULT 'activa'(anulable, con valor por defecto). Ninguna versión de código la usa aún. Reversión: innecesaria, la columna es inocua para 2.x actual. api-reservascon el endpoint nuevo, protegido por bandera apagada. Canario con Argo Rollouts. El endpointDELETE /reservas/{id}existe pero devuelve 404 con la bandera apagada. Reversión: abortar el canario, o apagar la bandera (ya está apagada).worker-notificacionescon la plantilla de correo de cancelación. Rodante: es un consumidor de cola, sin tráfico de usuarios. Debe desplegarse antes de que se pueda cancelar, para que no haya cancelaciones sin notificación. Reversión:rollout undo, sin impacto.tienda-webcon el botón, condicionado por la misma bandera. Rodante. El botón no aparece mientras la bandera esté apagada. Reversión: rodante inversa, o simplemente dejar el botón oculto.- Activación progresiva de la bandera: empleados → 5 % → 25 % → 100 %, vigilando la tasa de cancelación (una tasa anormalmente alta indicaría que el botón está mal ubicado o que se cancela por error) y los errores del endpoint. Reversión: apagar la bandera, cinco segundos, sin desplegar nada.
- Contracción, en noviembre:
ALTER COLUMN estado SET NOT NULLuna vez confirmado que todas las filas tienen valor y que ninguna versión antigua sigue viva.
El orden clave es que la capacidad (endpoint y worker) se despliega antes que la interfaz (botón), y que ambas quedan inertes hasta que la bandera se activa. Así, en todo momento anterior al paso 5 el sistema es funcionalmente idéntico al actual, y cualquier despliegue se puede revertir sin consecuencias.
Conclusión
Hemos visto cuatro maneras de reducir el riesgo de publicar. El RollingUpdate es cómodo y suficiente para lo rutinario, pero avanza guiándose por la sonda de preparación, que solo sabe si el proceso responde, no si la versión funciona bien. El azul-verde compra una reversión instantánea a cambio de duplicar la capacidad y de conmutar todo de golpe. El canario expone la versión nueva a una fracción pequeña de usuarios y permite decidir con evidencia real; con Argo Rollouts esa decisión se automatiza consultando Prometheus, con promoción y abortado automáticos, y con Flagger se consigue lo mismo con otra filosofía. Las banderas de funcionalidad operan en otra dimensión: separan el despliegue del código de la activación del comportamiento, y ponen el interruptor en manos de producto y de soporte.
Y por debajo de todas ellas hay una condición que no se puede saltar: las migraciones de base de datos deben ser compatibles con la versión anterior y la siguiente. Sin expandir y contraer, ninguna de las cuatro estrategias puede revertir de verdad.
El caso del puente de mayo resume la lección: separando el riesgo técnico (canario) del riesgo de negocio (bandera), un problema de rendimiento causado por un índice ausente se detectó en once minutos, afectó a una fracción del tráfico durante menos de dos, y se resolvió como una tarea de una tarde en lugar de como un incidente en el fin de semana de más ventas del año.
Hasta aquí hemos trabajado siempre sobre un clúster. En la siguiente lección, Gestión Multi-Clúster, veremos qué cambia cuando la plataforma deja de caber en uno solo: por qué las empresas acaban con varios, cómo se trabaja a diario sin aplicar nunca en el equivocado, cómo Argo CD despliega en toda una flota, y el problema que no tiene solución fácil cuando hay que recuperarse de un desastre, que es el dato.
Curso de Kubernetes
Módulo 1: Introducción a Kubernetes
- ¿Qué es Kubernetes?
- Arquitectura de Kubernetes
- Conceptos y Terminología Clave
- Configuración de un Clúster de Kubernetes
- La CLI de Kubernetes: kubectl
- Objetos, Manifiestos YAML y el Modelo Declarativo
- El Proyecto del Curso: la Plataforma Rutas Norte
Módulo 2: Componentes Principales de Kubernetes
- Pods
- ReplicaSets
- Deployments
- Actualizaciones, Rollbacks y Estrategias de Despliegue
- Servicios
- Namespaces
- Etiquetas, Selectores y Anotaciones
Módulo 3: Gestión de Configuración y Secretos
- ConfigMaps
- Secrets
- Variables de Entorno
- Cuotas y Límites de Recursos
- LimitRanges y Clases de Calidad de Servicio (QoS)
- ServiceAccounts y Acceso a la API desde los Pods
Módulo 4: Redes en Kubernetes
- Redes de Clúster
- Tipos de Servicios
- DNS Interno y Descubrimiento de Servicios
- Controladores de Ingress
- TLS y Gestión de Certificados con cert-manager
- Políticas de Red
Módulo 5: Almacenamiento en Kubernetes
- Volúmenes
- Volúmenes Persistentes
- Reclamaciones de Volúmenes Persistentes
- Clases de Almacenamiento
- Aprovisionamiento Dinámico, Expansión y Snapshots
- Copias de Seguridad y Restauración de Datos
Módulo 6: Conceptos Avanzados de Kubernetes
- StatefulSets
- DaemonSets
- Trabajos y CronJobs
- Init Containers, Sidecars y Patrones Multi-Contenedor
- Planificación: Afinidad, Taints y Tolerations
- Definiciones de Recursos Personalizados (CRDs)
- Operadores y el Patrón Controlador
Módulo 7: Monitoreo y Registro
- Verificaciones de Salud y Sondas
- Servidor de Métricas y kubectl top
- Monitoreo con Prometheus
- Visualización y Alertas con Grafana y Alertmanager
- Registro Centralizado con Elasticsearch, Fluentd y Kibana (EFK)
- Depuración de Aplicaciones y Eventos del Clúster
Módulo 8: Seguridad en Kubernetes
- Control de Acceso Basado en Roles (RBAC)
- Contextos de Seguridad y Endurecimiento del Contenedor
- Políticas de Seguridad de Pods y Pod Security Standards
- Seguridad de Red
- Seguridad de Imágenes
- Auditoría, Escaneo y Gestión de Vulnerabilidades
Módulo 9: Escalado y Rendimiento
- Autoescalado Horizontal de Pods
- Autoescalado Vertical de Pods
- Autoescalado de Clúster
- Escalado por Eventos y Métricas Personalizadas con KEDA
- Alta Disponibilidad: PodDisruptionBudgets y Topología
- Ajuste de Rendimiento
Módulo 10: Ecosistema y Herramientas de Kubernetes
- Minikube y Entornos Locales con kind
- Kubeadm
- Helm
- Kustomize
- GitOps con Argo CD y Flux
- Kubernetes Gestionado: EKS, AKS y GKE
Módulo 11: Estudios de Caso y Aplicaciones del Mundo Real
- Despliegue de una Aplicación Web
- Ejecución de Aplicaciones con Estado
- CI/CD con Kubernetes
- Estrategias de Despliegue: Blue-Green y Canary
- Gestión Multi-Clúster
- Operación en Producción: Incidencias, Runbooks y Costes
