El pipeline de 05-03 termina en kubectl rollout status y en un commit que Argo CD aplica. Entre "aplicar" y "desplegado" pasa algo que hasta ahora hemos dado por hecho: las réplicas de servicio-pedidos:1.0.0 tienen que dejar paso a las de 1.0.1 sin que un solo POST /v1/pedidos reciba un error de conexión. Cuando se desplegaba cada dos semanas de madrugada, un corte de 30 s se toleraba; cuando cada servicio despliega varias veces al día en horario comercial (que es el objetivo DORA de 05-03), cualquier corte multiplicado por seis servicios y por N despliegues es inaceptable. Esta lección explica las cuatro estrategias (recreate, rolling, blue-green, canary), su implementación en Kubernetes con los manifiestos de 05-02, la condición que todas comparten (dos versiones conviven, así que contratos y esquemas de base de datos deben ser compatibles hacia atrás), los feature flags como forma de separar despliegue de release, y la estrategia elegida por TechCorp para cada servicio. Cómo el mesh hace el canary por peso exacto queda para 05-05.

Contenido

  1. Desplegar sin cortes como requisito
  2. Recreate frente a RollingUpdate
  3. Compatibilidad hacia atrás durante el rolling: contratos y expand/contract
  4. Blue-green
  5. Canary
  6. Feature flags: despliegue no es release
  7. Tabla comparativa
  8. La estrategia de TechCorp por servicio
  9. Desplegar cambios en consumidores de eventos
  10. Rollback, forward-fix y automatización progresiva

  1. Desplegar sin cortes como requisito

Un despliegue "sin cortes" (zero-downtime) exige tres cosas que ya tenemos y una nueva:

  • Que las réplicas nuevas no reciban tráfico hasta estar listas: readinessProbe a /health/ready (05-02).
  • Que las réplicas viejas terminen lo que tienen entre manos antes de morir: SIGTERM → /health/ready en 503 → servidor.close() (04-02, 05-01) dentro de terminationGracePeriodSeconds: 30 (05-02).
  • Que haya más de una réplica, para que siempre quede alguna sirviendo.
  • Y la nueva: que la versión vieja y la nueva puedan convivir durante segundos, minutos o días, hablando con los mismos clientes, la misma base de datos y las mismas colas (apartados 3 y 9).

Sin la cuarta condición, ninguna estrategia funciona: no hay forma de cambiar de versión "instantáneamente" en un sistema distribuido; siempre hay una ventana con las dos.

  1. Recreate frente a RollingUpdate

Recreate para todas las réplicas viejas y luego arranca las nuevas: hay un hueco de segundos sin servicio. Solo tiene sentido cuando dos versiones no pueden convivir (un cambio de esquema destructivo, un consumidor exclusivo de una cola) y se acepta el corte. RollingUpdate, el valor por defecto de un Deployment, sustituye réplicas poco a poco:

# k8s/servicio-pedidos/base/deployment.yaml (fragmento añadido a spec)
spec:
  replicas: 3
  strategy:
    type: RollingUpdate               # (Recreate sería la alternativa)
    rollingUpdate:
      maxSurge: 1                     # cuántos pods POR ENCIMA de replicas puede haber durante el despliegue (número o %)
      maxUnavailable: 0               # cuántos pods por debajo de replicas se toleran: 0 = nunca menos de 3 listos
  minReadySeconds: 10                 # un pod nuevo debe llevar 10 s listo antes de contar como disponible (filtra arranques que mueren enseguida)
  progressDeadlineSeconds: 300        # si en 5 min no progresa, el rollout se marca fallido (y rollout status devuelve error en el pipeline)

Con replicas: 3, maxSurge: 1, maxUnavailable: 0, la secuencia es:

sequenceDiagram
    participant D as Deployment
    participant V1 as ReplicaSet v1 (3 pods)
    participant V2 as ReplicaSet v2
    participant S as Service (Endpoints)
    D->>V2: crear pod v2-a (ahora 4 pods: 3+1 surge)
    V2->>S: v2-a pasa readinessProbe → entra en Endpoints
    Note over S: 4 pods listos: 3 v1 + 1 v2
    D->>V1: borrar pod v1-a
    S-->>V1: v1-a sale de Endpoints; recibe SIGTERM
    V1->>V1: /health/ready 503, servidor.close(), termina peticiones en curso, exit
    D->>V2: crear pod v2-b … (se repite hasta 3 v2 y 0 v1)

Los cuatro parámetros se leen así:

Parámetro Efecto Valor de TechCorp Por qué
maxSurge: 1 Necesita capacidad para un pod extra 1 (o 25 % si hay muchas réplicas) Despliegue rápido sin duplicar recursos
maxUnavailable: 0 Nunca por debajo de las réplicas deseadas 0 La capacidad no baja durante el despliegue; con replicas: 2 y maxUnavailable: 1 habría momentos con un solo pod
minReadySeconds Evita que un pod que muere a los 3 s cuente como éxito 10 Los fallos de arranque tardío (conexión al broker) se detectan antes de seguir
readinessProbe (05-02) Marca el ritmo: no se borra un v1 hasta que un v2 está listo /health/ready Sin ella, Kubernetes consideraría listo un pod en cuanto arranca el contenedor

La relación con terminationGracePeriodSeconds: cuando el Deployment borra un pod v1, el kubelet le envía SIGTERM y, en paralelo, el controlador de Endpoints lo saca del Service. Hay una carrera de milisegundos en la que aún puede llegar una petición; por eso /health/ready pasa a 503 primero y servidor.close() sigue atendiendo las conexiones abiertas. Un preStop: { exec: { command: ["sleep", "5"] } } en el contenedor da margen extra si se observan errores en esa ventana.

Y el rollback:

kubectl rollout history deploy/servicio-pedidos           # REVISION 3: image 1.0.1; REVISION 2: image 1.0.0 ...
kubectl rollout undo deploy/servicio-pedidos              # vuelve a la revisión anterior con otro RollingUpdate (mismos maxSurge/maxUnavailable)
kubectl rollout undo deploy/servicio-pedidos --to-revision=2
kubectl rollout pause deploy/servicio-pedidos             # congela un rollout a medias para investigar; resume para seguir

rollout undo es inmediato y seguro si la 1.0.0 puede convivir con lo que la 1.0.1 dejó en la base de datos: apartado 3. Con GitOps (05-03), el rollback canónico es revertir el commit de plataforma; rollout undo es la vía de emergencia.

  1. Compatibilidad hacia atrás durante el rolling: contratos y expand/contract

Durante el rolling (y durante horas o días en canary), la 1.0.0 y la 1.0.1 de Pedidos atienden peticiones a la vez, publican eventos a la vez y escriben en la misma tabla. Dos consecuencias:

  1. Contratos: la API y los eventos de la 1.0.1 deben ser compatibles hacia atrás con los consumidores actuales (03-06: añadir campos sí, quitar o cambiar tipo no; /v2/ para lo demás). Un cliente que hable con la 1.0.0 en una petición y con la 1.0.1 en la siguiente (el Service balancea por conexión) no debe notar diferencias incompatibles.
  2. Esquema de base de datos: la migración de la 1.0.1 se aplica antes de que arranquen sus pods (el Job de 05-02) y mientras la 1.0.0 sigue escribiendo. Luego la migración tiene que ser compatible con la 1.0.0.

El patrón para conseguirlo es expand/contract: un cambio de esquema se hace en dos (o tres) despliegues, nunca en uno. Con el ejemplo de 03-06, añadir moneda a las líneas de pedido:

Fase Migración Código desplegado Compatible con
Expand (v1.1.0) ALTER TABLE lineas_pedido ADD COLUMN moneda CHAR(3) NOT NULL DEFAULT 'EUR'; v1.1.0 escribe moneda; v1.0.x sigue sin conocer la columna Ambas: el DEFAULT cubre las filas que inserta v1.0.x
Migrar datos UPDATE lineas_pedido SET moneda = 'EUR' WHERE moneda IS NULL (aquí no hace falta gracias al DEFAULT; en otros casos, por lotes) v1.1.0 en todas las réplicas v1.1.0
Contract (v1.2.0) ALTER TABLE lineas_pedido ALTER COLUMN moneda DROP DEFAULT; (o quitar la columna vieja si era un renombrado) v1.2.0 exige moneda siempre Solo v1.1.0+: por eso solo se aplica cuando v1.0.x ya no existe
-- migraciones/005-lineas-moneda-expand.sql   (despliegue v1.1.0; migrar.js de 04-04 la aplica en el Job)
ALTER TABLE lineas_pedido ADD COLUMN moneda CHAR(3) NOT NULL DEFAULT 'EUR';
-- migraciones/006-lineas-moneda-contract.sql (despliegue v1.2.0, días después, cuando ningún pod v1.0.x puede volver)
ALTER TABLE lineas_pedido ALTER COLUMN moneda DROP DEFAULT;

Lo que no se hace nunca en un solo paso: renombrar una columna (precio_unitarioprecio), cambiar su tipo, borrarla, o añadir un NOT NULL sin DEFAULT. Cualquiera de esas rompe a la versión vieja en el instante en que el Job termina, antes de que la nueva esté lista, y hace imposible el rollout undo. Regla práctica de Luis: "una migración solo puede añadir; quitar es otro despliegue, y hay que poder deshacer el despliegue sin deshacer la migración".

  1. Blue-green

Dos entornos completos e idénticos, blue (activo) y green (nuevo). Se despliega la versión nueva en green, se prueba con tráfico interno, y se cambia el enrutamiento de golpe. Si algo falla, se vuelve a blue en un segundo. En Kubernetes, dos Deployment y un Service cuyo selector decide cuál recibe tráfico:

# Dos Deployments idénticos salvo nombre, etiqueta version e imagen (base Kustomize con nameSuffix y patch)
apiVersion: apps/v1
kind: Deployment
metadata: { name: servicio-pedidos-blue, namespace: techcorp }
spec:
  replicas: 3
  selector: { matchLabels: { app: servicio-pedidos, version: blue } }
  template:
    metadata: { labels: { app: servicio-pedidos, version: blue } }
    spec: { containers: [ { name: servicio-pedidos, image: ghcr.io/techcorp/servicio-pedidos:1.0.0 } ] }   # resto igual que 05-02
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: servicio-pedidos-green, namespace: techcorp }
spec:
  replicas: 3
  selector: { matchLabels: { app: servicio-pedidos, version: green } }
  template:
    metadata: { labels: { app: servicio-pedidos, version: green } }
    spec: { containers: [ { name: servicio-pedidos, image: ghcr.io/techcorp/servicio-pedidos:1.0.1 } ] }
---
apiVersion: v1
kind: Service
metadata: { name: servicio-pedidos, namespace: techcorp }
spec:
  selector:
    app: servicio-pedidos
    version: blue                     # ← el interruptor: cambiar a green mueve TODO el tráfico
  ports: [ { name: http, port: 3002, targetPort: http } ]
---
apiVersion: v1                        # Service auxiliar para probar green antes del cambio (port-forward, E2E interna)
kind: Service
metadata: { name: servicio-pedidos-green, namespace: techcorp }
spec:
  selector: { app: servicio-pedidos, version: green }
  ports: [ { name: http, port: 3002, targetPort: http } ]
kubectl apply -f green.yaml && kubectl rollout status deploy/servicio-pedidos-green
kubectl port-forward svc/servicio-pedidos-green 3002:3002 &      # pruebas sobre green sin tráfico real:
curl -s localhost:3002/health/ready && GATEWAY_URL=http://localhost:3002 npm run test:e2e
kubectl patch svc servicio-pedidos -p '{"spec":{"selector":{"app":"servicio-pedidos","version":"green"}}}'   # el cambio
# ¿problema? vuelta atrás igual de rápida:
kubectl patch svc servicio-pedidos -p '{"spec":{"selector":{"app":"servicio-pedidos","version":"blue"}}}'

Ventajas: cambio y rollback instantáneos y sin versiones mezcladas en el Service (aunque las peticiones en curso en blue terminan en blue). Inconvenientes: doble coste mientras conviven, y las mismas exigencias de compatibilidad de datos del apartado 3, porque green ya escribió en la base de datos antes de volver a blue. Cuando lo que se expone es un Ingress, el interruptor puede ser el backend.service.name del Ingress en lugar del selector.

  1. Canary

Un canary envía un porcentaje pequeño de tráfico a la versión nueva, se observan sus métricas (aquí basta con dos: tasa de errores 5xx y latencia; el detalle de qué medir y cómo alertar es de 06-01 y 06-05), y si son iguales o mejores que las de la versión actual se aumenta el porcentaje hasta el 100 %; si no, se retira sin que la mayoría de usuarios lo haya notado. Tres formas de hacerlo, de más básica a más precisa:

(a) Por réplicas. El Service selecciona por app: servicio-pedidos sin mirar version (como en 05-02); un segundo Deployment con una réplica de la versión nueva entra en el mismo grupo:

apiVersion: apps/v1
kind: Deployment
metadata: { name: servicio-pedidos-canary, namespace: techcorp }
spec:
  replicas: 1                                        # 1 canary de 10 pods en total (9 estables) ≈ 10 % del tráfico
  selector: { matchLabels: { app: servicio-pedidos, version: v2 } }
  template:
    metadata: { labels: { app: servicio-pedidos, version: v2 } }
    spec: { containers: [ { name: servicio-pedidos, image: ghcr.io/techcorp/servicio-pedidos:1.1.0 } ] }

Sencillo y sin ninguna pieza nueva; pero el porcentaje es aproximado (kube-proxy reparte por conexión, no por petición) y la granularidad es "una réplica": con 3 réplicas estables, el mínimo canary es el 25 %.

(b) Por peso en el Ingress NGINX. Para lo que entra por el Ingress (en TechCorp, el gateway; o un servicio si se expusiera directamente), un segundo Ingress con anotaciones canary:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: gateway-canary
  namespace: techcorp
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"                # 10 % de las peticiones a gateway-v2
    # alternativa/adición: canary por cabecera, como en el ejercicio 1 de 03-04
    nginx.ingress.kubernetes.io/canary-by-header: "X-Canary"
    nginx.ingress.kubernetes.io/canary-by-header-value: "pedidos"  # X-Canary: pedidos → siempre a la nueva; "never" → nunca
spec:
  ingressClassName: nginx
  rules:
    - host: api.techcorp.example
      http:
        paths:
          - path: /
            pathType: Prefix
            backend: { service: { name: gateway-v2, port: { number: 8080 } } }

Peso exacto por petición y canary por cabecera (X-Canary), que permite al equipo probar la versión nueva en producción antes de abrir el porcentaje: la misma idea que el router del gateway Express de 03-04, ahora declarativa. Limitación: solo en el borde; entre servicios internos el Ingress no interviene.

(c) Por peso entre servicios internos. Que el 10 % de las llamadas del gateway a servicio-pedidos vayan a v2, con peso exacto y sin importar cuántas réplicas hay, es lo que da un service mesh con VirtualService (05-05, donde cerramos este hilo).

En cualquiera de las tres, la parte difícil no es el YAML sino la decisión de avanzar: comparar errores y latencia de v2 con v1 durante N minutos, con suficiente tráfico para que sea significativo (a las 4 de la mañana un 10 % de casi nada no dice nada). Al principio TechCorp lo hará a mano mirando Grafana (06-01); la automatización es del apartado 10.

  1. Feature flags: despliegue no es release

Las estrategias anteriores controlan qué código corre. Un feature flag (04-03: PAGO_NUEVO_PROVEEDOR, CATALOGO_REMOTO) controla qué código se ejecuta de entre el que corre. Con flags, el despliegue de la 1.1.0 puede hacerse con la funcionalidad apagada (dark launch): el código nuevo llega a producción con un rolling normal, sin riesgo, y la release (encenderla para el 1 %, el 10 %, todos) es un cambio de configuración, en segundos, sin desplegar y sin rollout undo. Comparado con el canary:

Canary Feature flag
Granularidad Por petición o por réplica Por usuario, cliente, país... (lo que el código decida)
Requiere Dos versiones desplegadas Una versión con dos caminos
Revertir Redirigir tráfico Cambiar un valor
Coste Infraestructura Complejidad en el código; flags que hay que retirar después
Combinables Sí: canary para el riesgo técnico del despliegue, flag para el riesgo funcional

Regla: un flag tiene fecha de caducidad; cuando la funcionalidad es la única, se borra el if.

  1. Tabla comparativa

Estrategia Corte Coste extra Riesgo Rollback Complejidad Cuándo
Recreate Sí (segundos-minutos) Ninguno Alto Redesplegar (lento) Mínima Cambios no compatibles; entornos de dev; tareas
Rolling No +maxSurge pods Medio: si la nueva falla, ya recibe tráfico proporcional rollout undo (segundos-minutos) Baja: por defecto en Kubernetes Por defecto para todo
Blue-green No Doble mientras conviven Bajo-medio: cambio total de golpe Instantáneo (selector) Media: dos Deployments, dos Services, coordinación Cambios grandes que se quieren probar en prod antes; gateway
Canary No +1 réplica o similar Bajo: exposición limitada y controlada Rápido (retirar el canary) Media-alta: pesos, métricas, decisión Servicios críticos con tráfico suficiente para medir

  1. La estrategia de TechCorp por servicio

Servicio Estrategia Motivo
servicio-catalogo, servicio-inventario, servicio-notificaciones, servicio-clientes Rolling (maxSurge: 1, maxUnavailable: 0) Cambios frecuentes y pequeños; el readinessProbe y el apagado ordenado bastan; flags para lo funcional
servicio-pedidos, servicio-pagos Canary (réplicas al principio; peso exacto cuando haya mesh, 05-05) Es donde está el dinero: un 10 % durante 30 minutos con la tasa de errores vigilada antes del 100 %; promoción a prod con revisión (05-03)
gateway Blue-green (Ingress apuntando a gateway-blue/gateway-green) Único punto de entrada; un fallo afecta a todo; el cambio y la vuelta atrás deben ser instantáneos y probables antes
Job de migraciones, CronJob Recreate (no aplica: no sirven tráfico)

Todas comparten los cuatro requisitos del apartado 1 y la disciplina expand/contract.

  1. Desplegar cambios en consumidores de eventos

Con un rolling de Pedidos, durante un rato hay pods v1 y v2 consumiendo la misma cola pedidos.saga (04-04). RabbitMQ reparte los mensajes entre todos los consumidores conectados, sin saber de versiones. Consecuencias y reglas:

  • Los dos deben entender los mismos eventos: la tolerancia a campos nuevos y el versionado de eventos de 03-06 no son teoría, son lo que hace posible que un stock.reservado publicado por el Inventario nuevo lo procese un Pedidos viejo.
  • Redistribución al morir un pod: cuando v1-a recibe SIGTERM a mitad de procesar un pago.confirmado, el mensaje no confirmado (ack pendiente) vuelve a la cola y lo recibe otro pod, quizá v2. La idempotencia de procesarUnaVez (04-04, tabla eventos_procesados) es lo que evita confirmar dos veces el pedido. El apagado ordenado del consumidor debe dejar de tomar mensajes (channel.cancel), terminar los que tiene, y solo entonces cerrar; el prefetch bajo (10) acota cuántos quedan a medias.
  • Si v2 publica un evento nuevo (pedido.confirmado con un campo más), los consumidores de otros servicios en su versión actual deben tolerarlo: expand/contract también aplica a los eventos.
  • Cambio de topología (nueva cola, nuevo binding): se declara de forma idempotente al arrancar (03-02) y antes de publicar en ella; nunca se borra un binding que la versión anterior aún usa.

Un consumidor de una cola no puede hacerse blue-green "puro" (green ya consume en cuanto conecta): o se despliega green sin arrancar el consumidor (un flag CONSUMIDOR_ACTIVO) o se acepta que consuma desde el principio, que es lo que hace TechCorp porque el consumo ya es idempotente.

  1. Rollback, forward-fix y automatización progresiva

  • Rollback: kubectl rollout undo, cambio de selector, retirar el canary o revertir el commit de plataforma. Es la opción cuando el fallo es evidente y la versión anterior es compatible con el estado actual (por eso las migraciones solo añaden). Objetivo: minutos, y es lo que baja el MTTR de 05-03.
  • Forward-fix: desplegar una versión nueva que corrige, en lugar de volver atrás. Necesario cuando la vuelta atrás es imposible (una fase contract ya aplicada, datos ya escritos en un formato nuevo) y aceptable cuando el pipeline tarda menos de 15 minutos: es más fácil justificar el forward-fix cuanto mejor es el CI/CD.
  • Automatización progresiva: Argo Rollouts (recurso Rollout que sustituye al Deployment, con strategy: canary: steps: [setWeight: 10, pause: 10m, analysis: ...]) o Flagger (opera sobre el Deployment normal y crea el canary solo) consultan Prometheus, avanzan el peso si las métricas cumplen y revierten si no. Ambos necesitan un mecanismo de peso (Ingress NGINX, Istio, Linkerd) y las métricas de 06-01. Es el destino natural del canary manual de Pedidos y Pagos, cuando el equipo de Plataforma tenga el mesh o el Ingress con pesos y las métricas listas.

Errores Comunes y Consejos

  • maxUnavailable por defecto (25 %) con dos réplicas: durante el despliegue queda una; y con replicas: 1 el rolling es un recreate con otro nombre. maxUnavailable: 0 y al menos dos réplicas en cualquier servicio que reciba tráfico.
  • Migración destructiva en el mismo despliegue que el código: el Job renombra la columna, los pods v1 empiezan a fallar antes de que v2 esté lista, y rollout undo no arregla nada. Expand/contract siempre.
  • Canary sin métricas ni tráfico: "lo hemos tenido 10 minutos y no ha pasado nada" a las 3 de la mañana. Definir qué se mira, cuánto tiempo y cuánto tráfico mínimo antes de empezar.
  • Blue-green con estado en el pod (sesiones, caché local): green arranca "frío". Los servicios de TechCorp son stateless precisamente por esto.
  • Olvidar el readinessProbe o hacerla trivial (/health/live en ambas): Kubernetes borra pods viejos en cuanto los nuevos "arrancan", no cuando pueden atender.
  • Flags eternos: cada if (flags.estaActivo(...)) es una rama que probar. Fecha de retirada en la tarjeta que lo crea.
  • Consejo: ensaya el rollback en staging como parte del pipeline (rollout undo + E2E) al menos una vez por versión mayor; un rollback que nunca se ha probado no es un plan.

Ejercicios

Ejercicio 1. servicio-pedidos tiene replicas: 2 y la configuración por defecto de RollingUpdate. Luis observa que durante cada despliegue la latencia p95 se dobla y hay algunos 502 en el gateway. Explica las dos causas más probables con lo visto y escribe el fragmento de spec que las corrige.

Ejercicio 2. El equipo de Pedidos quiere renombrar la columna precio_unitario de lineas_pedido a precio (03-06 lo dejó como ejemplo de cambio incompatible). Diseña la secuencia expand/contract completa: migraciones (numeradas a partir de 007-...), qué escribe y lee el código en cada versión (1.3.0, 1.4.0, 1.5.0) y en qué momento es seguro aplicar cada paso.

Ejercicio 3. Marta pregunta por qué el gateway va en blue-green y no en canary "como Pedidos, que es más moderno". Redacta la respuesta en cinco líneas, y añade qué se necesitaría para que el gateway pudiera ir en canary con seguridad.

Soluciones

Solución 1. (1) Con replicas: 2 y maxUnavailable: 25 % (redondeado a 1 pod), en algún momento hay un solo pod atendiendo todo el tráfico: la latencia se dobla. (2) Los 502 vienen de la ventana entre "el pod sale de Endpoints" y "el pod deja de aceptar": si el readinessProbe tarda 5 s en fallar tres veces (15 s) pero el proceso ya ha cerrado, o si el gateway mantiene conexiones keep-alive al pod que muere; el apagado ordenado de 04-02 (503 inmediato en /health/ready y servidor.close()) reduce la ventana, y un preStop con sleep 5 la cubre. Corrección:

spec:
  replicas: 3
  strategy: { type: RollingUpdate, rollingUpdate: { maxSurge: 1, maxUnavailable: 0 } }
  minReadySeconds: 10
  template:
    spec:
      containers:
        - name: servicio-pedidos
          lifecycle: { preStop: { exec: { command: ["sleep", "5"] } } }   # el kubelet espera este comando antes de enviar SIGTERM

terminationGracePeriodSeconds: 30 sigue siendo suficiente: 5 s de preStop + menos de 10 s de apagado.

Solución 2. v1.3.0 (expand): 007-lineas-precio-expand.sql: ALTER TABLE lineas_pedido ADD COLUMN precio NUMERIC(10,2); (nullable); el código escribe las dos columnas y sigue leyendo precio_unitario. Compatible con v1.2.x (ignora precio, que admite NULL). Migración de datos (Job aparte o dentro de 007 si la tabla es pequeña; por lotes si no): UPDATE lineas_pedido SET precio = precio_unitario WHERE precio IS NULL;. v1.4.0 (cambio de lectura): el código lee precio y sigue escribiendo ambas; se puede añadir ALTER COLUMN precio SET NOT NULL en 008-... una vez relleno. Compatible con v1.3.0 (que escribe ambas). v1.5.0 (contract): 009-lineas-precio-contract.sql: ALTER TABLE lineas_pedido DROP COLUMN precio_unitario;; el código ya no la escribe. Solo es seguro cuando ninguna réplica anterior a v1.4.0 puede volver (días después, tras confirmar que no habrá rollout undo a v1.3.0). Tres despliegues en lugar de uno, y cada uno se puede deshacer.

Solución 3. El gateway es el único punto de entrada: cualquier fallo afecta al 100 % de las peticiones de todos los servicios, y su cambio típico es de enrutamiento o de librería, difícil de "probar con el 10 %" porque un error de configuración suele ser todo o nada, no estadístico. Blue-green permite desplegar green completo, ejecutar la E2E y pruebas de rutas contra gateway-green antes de recibir tráfico, y volver a blue en un segundo con un cambio de Ingress, sin depender de métricas. Para pasar a canary con seguridad haría falta: peso exacto por petición (Ingress NGINX canary-weight, ya visto), métricas RED por ruta del gateway (06-01) con un umbral definido de errores y latencia, tráfico suficiente en la ventana de análisis, y preferiblemente la automatización de Argo Rollouts/Flagger para no depender de que alguien mire Grafana. Hasta entonces, blue-green es la opción más segura para esa pieza.

Conclusión

Desplegar sin cortes descansa en cuatro pilares (readinessProbe, apagado ordenado dentro de terminationGracePeriodSeconds, más de una réplica y convivencia de versiones) y se materializa en cuatro estrategias: recreate solo cuando la convivencia es imposible; rolling como valor por defecto (maxSurge: 1, maxUnavailable: 0, minReadySeconds, rollout undo); blue-green con dos Deployment (-blue/-green) y el selector.version del Service (o el Ingress) como interruptor; y canary por réplicas, por peso y cabecera X-Canary en el Ingress NGINX, o por peso exacto entre servicios internos con un mesh. La convivencia impone contratos compatibles (03-06) y migraciones expand/contract (la columna moneda de lineas_pedido en dos despliegues), también para los consumidores de eventos que comparten cola durante el rolling (idempotencia de 04-04). Los feature flags separan despliegue de release; la decisión de TechCorp es rolling para Catálogo, Inventario, Notificaciones y Clientes, canary para Pedidos y Pagos, blue-green para el gateway; y Argo Rollouts o Flagger automatizarán el canary cuando haya métricas y pesos exactos. Ese peso exacto entre servicios internos, junto con timeouts, reintentos y mTLS que hoy cada servicio resolvería en su código, es lo que promete un service mesh; la siguiente lección examina qué da Istio (y Linkerd), qué cuesta, y si TechCorp lo necesita ya.

Curso de Microservicios

Módulo 1: Introducción a los Microservicios

Módulo 2: Diseño de Microservicios

Módulo 3: Comunicación entre Microservicios

Módulo 4: Implementación de Microservicios

Módulo 5: Despliegue y Orquestación

Módulo 6: Monitoreo y Mantenimiento

Módulo 7: Seguridad en Microservicios

Módulo 8: Casos de Estudio y Ejemplos Prácticos

© Copyright 2026. Todos los derechos reservados