MsAutoservicio
sin corte
Blue/Green con Caddy, WebSockets persistentes y migración controlada por autoservicio o por sucursal.
La unidad de migración es la conexión del POS
En WebSocket, Caddy elige Blue o Green una sola vez: durante el handshake HTTP. La conexión queda fijada a esa instancia hasta que el cliente cierre o pierda el socket.
Caddy evalúa headers y ruta al backend elegido. No inspecciona los mensajes posteriores.
Todos los comandos de ese autoservicio permanecen en la misma versión durante la sesión.
Para migrar, se cambia la regla y se ordena una reconexión cuando el POS está IDLE.
Sí: cada handshake puede incluir X-BRANCH-ID y X-POS-ID. Caddy puede enviar un POS puntual, una lista de POS o una sucursal completa a Green.
Los headers describen identidad física: sucursal y terminal. La política Blue/Green vive únicamente en Caddy.
Un endpoint estable, dos versiones simultáneas
Todos los autoservicios siguen usando la misma URL. Caddy absorbe el puerto histórico y deriva cada nueva conexión según la política de rollout.
Mismo servidor
Evita el corte de despliegue, pero no la caída total del Windows Server.
Dos servidores después
La misma política puede evolucionar a dos VMs/EC2 con un load balancer externo.
POS puntual, sucursal completa o rollback
La regla puede abarcar una sucursal completa, pero los WebSockets existentes no se mueven solos. Cada POS migra cuando cierra y reconecta, idealmente al quedar IDLE.
Se activa la regla por X-BRANCH-ID; luego se envía SYSTEM_RECONNECT en ondas. Los POS ocupados esperan hasta finalizar el ticket.
Dos headers, una política central
Los headers deben representar identidad estable del equipo. La asignación de versión se mantiene en el servidor para que una terminal no pueda autoseleccionar un slot.
| Header | Ejemplo | Uso | Validación |
|---|---|---|---|
X-BRANCH-ID | LIM-T1 | Agrupar todos los autoservicios de una sucursal, terminal o local. | Debe coincidir con la sucursal asociada al POS autenticado. |
X-POS-ID | LIM-T1-AS-03 | Migrar, excluir o hacer rollback de una terminal puntual. | Debe coincidir con el claim/token o registro de terminal. |
Authorization | Bearer … | Autenticación de la conexión y autorización de comandos. | Validada por Spring; nunca reemplazada por los headers de routing. |
Orden recomendado de reglas
POS problemático queda en Blue aunque su sucursal esté migrada.
X-POS-IDBlueUno o varios POS concretos prueban la versión nueva.
BRANCH + POSGreenTodos los nuevos handshakes de la sucursal van a Green.
X-BRANCH-IDGreenTodo lo que no coincide sigue en la versión estable.
*BlueCampos de header distintos en un mismo matcher se evalúan con AND; varios valores del mismo header se evalúan con OR. Esto permite “Sucursal LIM-T1 Y POS AS-01/AS-02”.
Routing por POS, por sucursal y con excepción
Configuración de referencia. Blue es el default; Green es primario en los grupos migrados, con Blue como fallback si Green no está saludable.
# Caddy escucha el puerto histórico que conocen los POS. :8888 { log { output file C:/MsAutoservicio/caddy/logs/access.json format json } # Detecta el handshake WebSocket. @websocket { path /ws* header Connection *Upgrade* header Upgrade websocket } handle @websocket { route { # 1) Excepción: este POS queda en BLUE. @forceBlue { header X-POS-ID LIM-T1-AS-03 } handle @forceBlue { reverse_proxy 127.0.0.1:8889 127.0.0.1:8890 { lb_policy first health_uri /readyz health_interval 5s health_timeout 2s health_fails 2 health_passes 1 lb_try_duration 3s stream_close_delay 10m } } # 2) Canary: POS concretos de una sucursal. @greenPos { header X-BRANCH-ID LIM-T1 header X-POS-ID LIM-T1-AS-01 LIM-T1-AS-02 } handle @greenPos { reverse_proxy 127.0.0.1:8890 127.0.0.1:8889 { lb_policy first health_uri /readyz health_interval 5s health_timeout 2s health_fails 2 health_passes 1 lb_try_duration 3s stream_close_delay 10m } } # 3) Sucursales completas migradas. @greenBranch { header X-BRANCH-ID LIM-T2 LIM-T3 } handle @greenBranch { reverse_proxy 127.0.0.1:8890 127.0.0.1:8889 { lb_policy first health_uri /readyz health_interval 5s health_timeout 2s health_fails 2 health_passes 1 lb_try_duration 3s stream_close_delay 10m } } # 4) Todo lo demás permanece en BLUE. handle { reverse_proxy 127.0.0.1:8889 127.0.0.1:8890 { lb_policy first health_uri /readyz health_interval 5s health_timeout 2s health_fails 2 health_passes 1 lb_try_duration 3s stream_close_delay 10m } } } } # No exponer otros endpoints por accidente. handle { respond "Not found" 404 } }
lb_policy first
El primer upstream es el primario. Los health checks permiten usar el segundo como fallback.
stream_close_delay 10m
Evita que un reload de Caddy derribe inmediatamente todos los WebSockets existentes.
Secuencia operativa sin ticket partido
Green listo
Levantar :8890 y validar /readyz.
Cambiar regla
Agregar el POS a @greenPos y recargar Caddy.
Esperar IDLE
Blue pide reconexión; el POS rechaza mientras esté ocupado.
Nuevo socket
El handshake vuelve a Caddy y entra en Green.
{
"type": "SYSTEM_RECONNECT",
"reason": "DEPLOYMENT",
"targetVersion": "2026.07.13-2",
"retryAfterMs": 2000
}Blue debe seguir atendiendo sockets existentes hasta que los POS migren o expire la ventana de drenaje.
La regla es masiva; la ejecución puede ser gradual
Agregar LIM-T1 a @greenBranch hace que todos los nuevos handshakes de esa sucursal prefieran Green. Los sockets ya abiertos siguen en Blue, lo que permite migrarlos en ondas.
Onda 1 · Piloto
Un POS de laboratorio y uno productivo con supervisión.
Onda 2 · IDLE
Enviar reconexión a todos los POS sin ticket activo.
Onda 3 · Remanentes
Migrar los ocupados cuando terminen; excluir problemas puntuales.
| Escenario | Matcher | Resultado |
|---|---|---|
| Un solo autoservicio | X-BRANCH-ID=LIM-T1 AND X-POS-ID=LIM-T1-AS-01 | Sólo AS-01 conecta a Green. |
| Varios autoservicios | X-POS-ID=AS-01 AS-02 AS-04 | Lista explícita de canary. |
| Sucursal completa | X-BRANCH-ID=LIM-T1 | Todos los nuevos sockets de LIM-T1 van a Green. |
| Sucursal con excepción | Regla @forceBlue antes de @greenBranch | Un POS queda en Blue sin frenar el rollout. |
No hace falta editar la URL, reinstalar el cliente ni mantener un Caddyfile con cientos de terminales cuando el rollout se ejecuta por sucursal.
Handshake identificado y reconexión segura
Flutter Desktop
final socket = await WebSocket.connect( 'wss://autoservicio.midominio/ws', headers: { 'Authorization': 'Bearer $token', 'X-BRANCH-ID': branchId, 'X-POS-ID': posId, }, );
Spring Boot
server:
port: ${APP_PORT}
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 60s
management:
endpoint:
health:
probes:
enabled: true
add-additional-paths: true/readyz debe estar en el puerto principal.DEPLOYMENT_SLOT=blue|green en todos los logs.Un header falsificado podría intentar seleccionar otro slot, pero ambas versiones deben rechazar cualquier identidad que no coincida con el token y la configuración del POS.
El proxy no resuelve el estado distribuido
Durante el rollout conviven dos procesos contra la misma base. El diseño debe tolerar que un POS cierre Blue y reabra Green sin perder ni duplicar operaciones.
Persistir ticket, items, pago, impresión y último comando confirmado.
Usar messageId único e idempotencia para pagar, cerrar, cancelar e imprimir.
Desactivar jobs en el candidato o usar lock distribuido en SQL Server.
Aplicar expand-and-contract para que versión N y N+1 sean compatibles.
{
"type": "PAY_TICKET",
"messageId": "LIM-T1-AS-01-20260713-000842",
"ticketId": "T-99881",
"amount": 1234.56
}Si Green modifica el esquema de forma incompatible, volver el tráfico a Blue puede no recuperar el servicio.
Deploy, observación y rollback
Antes del switch
curl http://127.0.0.1:8890/readyz.Durante el rollout
# Validar sintaxis C:\Caddy\caddy.exe validate ` --config C:\Caddy\Caddyfile ` --adapter caddyfile # Aplicar sin reiniciar Caddy C:\Caddy\caddy.exe reload ` --config C:\Caddy\Caddyfile ` --adapter caddyfile # Verificar Green curl.exe -f http://127.0.0.1:8890/readyz
Rollback de una sucursal
Quitar branch
Eliminar LIM-T1 de @greenBranch.
Reload
Las conexiones nuevas vuelven a preferir Blue.
Reconectar
Green ordena reconexión a sus POS IDLE.
Confirmar
Monitorear que el slot Blue recupere la sucursal.
Recomendación final
Caddy en :8888, Blue en :8889, Green en :8890; routing por X-BRANCH-ID y X-POS-ID; migración mediante reconexión controlada al quedar IDLE; SQL compatible e idempotencia obligatoria.
Matcher exacto y reconexión individual.
Matcher por branch y migración en ondas.
Cambiar regla, reload y reconectar.
Fuentes oficiales consultadas
- Caddy — reverse_proxy, health checks, load balancing y WebSockets
- Caddy — request matchers y matching de headers
- Caddy — handle, fallback y rutas mutuamente excluyentes
- Spring Boot — readiness/liveness probes
- Spring Boot — graceful shutdown
- Dart — WebSocket.connect y headers del handshake
Esta solución elimina el corte provocado por actualizar la aplicación. En un único Windows Server todavía existe un punto único de falla de infraestructura.