3 puntos por GN⁺ 2024-09-22 | 1 comentarios | Compartir por WhatsApp
  • Kamal Proxy es un pequeño proxy HTTP que funciona delante de una aplicación web, diseñado para cambiar un despliegue a nuevas instancias sin cortar el tráfico en curso
  • Las nuevas instancias se registran con kamal-proxy deploy, y el proxy solo enruta el tráfico nuevo hacia ellas después de que pasen un health check HTTP
  • El comando de despliegue espera hasta que todo el tráfico salga de la instancia anterior, así que una vez que termina correctamente, se puede eliminar la instancia previa sin interrumpir las solicitudes en curso
  • Ofrece enrutamiento basado en host, enrutamiento basado en rutas, TLS automático y la opción de especificar certificados TLS manuales para operar múltiples aplicaciones en un solo proxy
  • Kamal Proxy fue diseñado como parte de la herramienta de despliegue Kamal, pero también puede usarse por sí solo o como componente de otras herramientas de despliegue

Qué hace Kamal Proxy

  • Kamal Proxy es un pequeño proxy HTTP para facilitar la coordinación de despliegues con cero downtime
  • Si ejecutas tu aplicación web detrás de Kamal Proxy, puedes desplegar cambios sin interrumpir el tráfico en curso
  • No se requiere ninguna cooperación especial por parte de la aplicación para que esto funcione
  • Kamal Proxy está diseñado para funcionar como parte de Kamal, que incluye empaquetado de contenedores y aprovisionamiento
  • También puede usarse de forma independiente o como parte de otras herramientas de despliegue

Flujo de ejecución y despliegue

  • La instancia del proxy se ejecuta con el comando kamal-proxy run
  • No hay archivo de configuración; se pueden indicar opciones cuando los valores predeterminados no se ajustan a la aplicación
    • El puerto HTTP predeterminado es 80
    • Para ejecutarlo en otro puerto, usa kamal-proxy run --http-port 8080
    • Puedes ver todas las opciones en kamal-proxy help run
  • Para enviar tráfico a la aplicación web, se hace deploy de las instancias de la aplicación en el proxy
  • La instancia de destino se especifica en formato hostname:port
    • Ejemplo: kamal-proxy deploy service1 --target web-1:3000
    • Este comando registra web-1:3000 con el nombre de servicio service1

Cómo funciona el cambio sin downtime

  • Cuando se registra una nueva instancia, Kamal Proxy ejecuta de inmediato un health check HTTP para confirmar que es accesible y funciona correctamente
  • Si el health check tiene éxito, empieza a enrutar el tráfico nuevo hacia la nueva instancia
  • Si la nueva instancia no llega al estado healthy dentro de un tiempo razonable, el comando deploy cancela el despliegue y devuelve un código de salida distinto de cero
  • Cada despliegue transfiere todo el tráfico de la instancia desplegada anteriormente hacia la nueva instancia
  • El comando deploy espera hasta que todo el tráfico haya salido de la instancia anterior antes de terminar
    • Una vez que termina correctamente, se puede eliminar la instancia previa sin cortar solicitudes en curso
  • Como la nueva instancia solo recibe tráfico cuando está healthy, y la instancia anterior se elimina solo después de drenarse por completo, el despliegue se realiza con cero downtime

Configuración de health checks

  • El health check predeterminado envía una solicitud GET a /up una vez por segundo para cada servicio
  • Una respuesta 200 se considera estado healthy
  • Para adaptar el health check a la aplicación, se usan flags de deploy
    • --health-check-path
    • --health-check-port
    • --health-check-timeout
    • --health-check-interval
  • Ejemplo para cambiar la ruta del health check:
    • kamal-proxy deploy service1 --target web-1:3000 --health-check-path web/index.html
  • Si el endpoint de health se expone en un puerto distinto al del servicio principal:
    • kamal-proxy deploy service1 --target web-1:3000 --health-check-port 8080

Funciones de enrutamiento

  • Enrutamiento basado en host

    • Con enrutamiento basado en host, una sola instancia de Kamal Proxy puede enrutar tráfico a varias aplicaciones en el mismo servidor
    • Durante el despliegue, se puede especificar un host concreto
    • kamal-proxy deploy service1 --target web-1:3000 --host app1.example.com
    • Las instancias desplegadas de esta forma solo reciben tráfico del host especificado
    • Si se asigna un host distinto a cada aplicación, se pueden ejecutar varias aplicaciones en el mismo servidor sin conflictos de puertos
    • Un host específico solo puede enrutar a un servicio a la vez
    • Si service1 ya usa app1.example.com, se devolverá un error cuando service2 intente desplegarse con ese mismo host
    • Después de eliminar service1, se podrá desplegar service2 en ese mismo host
  • Enrutamiento basado en rutas

    • El enrutamiento basado en rutas se usa en aplicaciones que dividen el tráfico entre distintos servicios según la ruta de la solicitud
    • Los servicios pueden montarse bajo distintos prefijos de ruta
    • Ejemplo para enviar solicitudes que empiezan con /api a web-1: kamal-proxy deploy service1 --target web-1:3000 --path-prefix=/api
    • Ejemplo para enviar el resto a web-2: kamal-proxy deploy service2 --target web-2:3000
    • De forma predeterminada, el prefijo de ruta se elimina antes de reenviar la solicitud al upstream
    • Una solicitud a /api/users/123 se reenvía a web-1 como /users/123
    • Para reenviar la ruta original sin cambios, especifica --strip-path-prefix=false

Soporte TLS

  • Kamal Proxy puede obtener y renovar automáticamente certificados TLS para aplicaciones
  • El TLS automático se activa agregando el flag --tls durante el despliegue
    • kamal-proxy deploy service1 --target web-1:3000 --host app1.example.com --tls
  • El TLS automático requiere especificar un host
    • Esta condición evita que se soliciten certificados de forma maliciosa para nombres de host arbitrarios
  • En enrutamiento basado en rutas, la opción TLS debe configurarse en la ruta raíz
    • Los servicios desplegados en otras rutas del mismo host usan la configuración TLS definida en la ruta raíz
  • Si necesitas instalar certificados TLS obtenidos manualmente, una autoridad certificadora propia o un Cloudflare origin certificate, puedes indicar directamente la ruta del archivo del certificado y de la clave privada
    • --tls-certificate-path cert.pem
    • --tls-private-key-path key.pem

Variables de entorno y compilación

  • En entornos como la ejecución de contenedores Docker, las opciones de run pueden definirse como variables de entorno
  • kamal-proxy run lee el valor de cada opción desde las variables de entorno configuradas, si existen
    • kamal-proxy run --http-port 8080
    • HTTP_PORT=8080 kamal-proxy run
  • Si el nombre de una variable de entorno entra en conflicto con el entorno existente, se puede diferenciar agregando el prefijo KAMAL_PROXY_
    • KAMAL_PROXY_HTTP_PORT=8080 kamal-proxy run
  • La compilación local se realiza con make en un entorno Go funcional
  • Para compilar como contenedor Docker, usa make docker
  • En la carpeta example hay una configuración de Docker Compose para probar los comandos del proxy

1 comentarios

 
GN⁺ 2024-09-22
Opiniones en Hacker News
  • Es raro que hayan elegido deploy como nombre de la acción.
    Es un término que ya está sobrecargado, así que podría generar confusión del tipo “¿se desplegó la app? ¿O se desplegó en el proxy?”.
    Podrían haber usado palabras como bind, intercept o simplemente proxy, pero no entiendo por qué deploy.

    • “Desplegado” puede entenderse como que está en ejecución y registrado en el proxy.
    • Si el tráfico entrante viene desde el proxy, no veo qué otra cosa podría significar deploy que no sea “el tráfico del proxy empezó a fluir hacia la nueva instancia de la app”.
  • Hoy en día también es fácil hacer despliegues sin downtime con otros servidores, así que es interesante la decisión de convertir esto en una app separada completa.
    Por ejemplo, con una app + proxy web que soporte sockets Unix, se puede lograr cero downtime con solo mover un archivo.
    Es atómico, y también se pueden enviar solicitudes de calentamiento con curl.
    Crear todo un sistema de registro parece excesivo.

    • Esto es solo una parte muy pequeña de Kamal(https://kamal-deploy.org).
      Kamal es una herramienta de despliegue que se usó para ahorrar millones de dólares al pasar de la nube a hardware propio(https://basecamp.com/cloud-exit).
    • Me gustaría que pudieras explicar un poco más este enfoque.
      Si hay algún artículo de referencia, me gustaría aprender más.
  • Esto parece apuntar principalmente a resolver el problema de fondo de los cortes de tráfico al reemplazar contenedores de servicio en Docker Swarm.
    En Cloud 66 nos encontramos con el mismo problema al crear un servidor JAMStack, y usamos Caddy en vez de un proxy propio; Traefik también habría sido suficientemente adecuado.
    No sé por qué Kamal eligió Swarm en vez de k8s o k3s, pero la complejidad tiene que ubicarse en algún lado y no se puede ocultar, así que parece que al final terminaron con un proxy propio.
    No lo he usado personalmente, pero soy bastante escéptico: parece que tendrán que estar persiguiendo soporte para WebSockets, SSE, HTTP/3, distintos tipos de compresión y cifrado.

    • Kamal parece construido sobre la premisa de que “Kubernetes es demasiado complejo”, y a partir de esa justificación está recreando buena parte de lo que hace Kubernetes.
      Parece un ejemplo de cómo un proxy inverso que empezó simple termina absorbiendo la complejidad que intentaba evitar.
      Creo que los sistemas de ejecución amplios y extensibles necesitan competidores, pero Kamal se ve como un trabajo que retrocede hacia la misma complejidad que quería evitar.
      La capacidad y flexibilidad de Kubernetes como framework de gestión de estado deseado termina destacando aún más.
    • Parece que estás mezclando orquestación y proxy.
      Puedes seguir usando Docker y, aun así, usar Caddy, Traefik o Envoy como proxy.
      Kubernetes también suele usarlos como controladores de ingress.
    • No sé de dónde salió que Kamal usa Docker Swarm.
      Por lo que veo, no parece usar Swarm.
    • Kamal usa comandos normales de docker run, sin Swarm ni orquestación.
      Básicamente es una herramienta simple que reemplaza a Capistrano.
    • Si esa es la razón, parece que ni siquiera se hizo una investigación muy básica sobre el tema.
      Lo básico en un servicio de orquestación de contenedores es manejar las conexiones y los despliegues blue/green mediante un controlador de ingress.
      Más precisamente, controlador de ingress es el nombre del rol asignado a muchos proxies inversos que vienen resolviendo este caso de uso desde hace años, incluso décadas.
  • Me gustaría una explicación simple de cómo funcionan generalmente los despliegues sin downtime.
    Supongo que la estructura consiste en tener dos versiones de la app corriendo al mismo tiempo y enrutar el tráfico nuevo a la versión nueva.
    Pero si se usa una sola base de datos y la nueva versión de la app trae cambios de esquema, el script de migración cambiará el esquema al iniciar, mientras que la versión anterior de la app espera el esquema viejo.
    Me da curiosidad cómo se maneja eso.

    • El primer paso es separar las migraciones del despliegue.
      Muchos frameworks ejecutan migraciones al desplegar código, pero en la práctica conviene controlar manualmente cuándo se ejecutan.
      Cada versión del código debe funcionar tanto con el esquema actual como con el esquema futuro después de la migración, así que en la práctica se convierte en código compatible hacia atrás.
      El flujo queda así: “desplegar código nuevo que funcione tanto con el esquema actual como con el futuro → verificar → ejecutar la migración → verificar → limpiar el código que manejaba el esquema desaparecido”.
    • Las migraciones deben ser compatibles hacia atrás, de modo que el esquema de la base de datos pueda soportar ambas versiones de la app.
      Es un costo adicional que hay que pagar para hacer despliegues sin downtime o despliegues progresivos, y las empresas grandes suelen hacerlo así.
    • Otras respuestas explicaron cómo hacerlo cuando realmente se necesita un despliegue sin downtime, pero para la mayoría de las organizaciones y la mayoría de las migraciones, el tiempo de interrupción causado por una migración de DB es prácticamente indistinguible de 0.
      Esto aplica especialmente si atiendes usuarios de una región concreta y puedes elegir un horario tranquilo para desplegar.
    • No lo he usado personalmente, pero parece que Xata creó una solución bastante limpia para migraciones de DB, al menos en Postgres.
      Dos versiones de la app pueden ejecutarse al mismo tiempo.
      https://xata.io/blog/multi-version-schema-migrations
    • Correcto, en algún momento ambas versiones tienen que estar ejecutándose.
      El balanceador de carga empieza a aceptar conexiones en Server2 y deja de aceptar conexiones nuevas en Server1.
      Luego, cuando se cierran todas las conexiones de Server1, se desconecta Server1.
      Pueden ser servidores distintos o varios workers en un mismo servidor.
      Durante ese período, como dicen otras respuestas, las migraciones deben ser compatibles hacia atrás.
  • Kamal 2 actualmente está en RC (https://github.com/basecamp/kamal/releases) y, como tendrá soporte para SSL automático, entusiasma la idea de que sea fácil correr varias apps con Kamal en un solo servidor

    • Usa el paquete autocert, pero eso se acerca a una implementación mínima
      Es frágil, y por los límites de emisión de Let's Encrypt y los límites de certificados simultáneos, se vuelve difícil escalar horizontalmente las instancias del proxy
      Caddy/Certmagic resuelve este problema escribiendo datos en un almacenamiento compartido, emitiendo un solo certificado y haciendo que todas las instancias lo reutilicen y se coordinen a través de ese almacenamiento
      Tampoco tiene cambio de emisor, evasión de límites de emisión ni soporte de ARI
      Lo de mantener las solicitudes hasta que el upstream esté disponible también lo maneja bien Caddy si se configuran try_duration y try_interval en reverse_proxy
      El manejo de encabezados de proxy tampoco considera IPs confiables, así que si se activa, alguien podría configurar X-Forwarded-For y falsificar la IP
      Por suerte viene desactivado por defecto, pero tampoco hay advertencia
      Entiendo que el objetivo sea la simplicidad, pero en su estado actual se siente poco maduro y hay demasiadas alternativas mejores como para querer usarlo en serio
  • No entiendo bien cómo se usa esto y siento que me estoy perdiendo algo
    El ejemplo inicia 4 réplicas del servicio web
    Si puedes crear el servicio desplegando en una réplica como example-web-1, no sé qué hacen las otras 3 réplicas
    Para actualizar web con despliegue sin interrupciones, parece que habría que ejecutar el comando de build en el servicio web, iniciar este servicio de alguna manera y luego ejecutar deploy contra el nuevo destino
    Pero si ejecutas docker compose up --build --force-recreate web, las réplicas existentes se bajan y todo pierde sentido

    • Las otras réplicas de la primera pregunta las balancea Docker devolviendo varias IP en las solicitudes VIP o DNS[0]
      No verifiqué si este proxy distribuye la carga entre los varios registros devueltos por una consulta DNS, pero al menos con balanceo basado en VIP debería funcionar como se espera
      La segunda parte, la de la actualización, es menos clara
      Tal vez la expectativa sea levantar un servicio con otro nombre dentro de la misma red y ejecutar kamal-proxy deploy
      Podría ser algo como poner el número de versión en el nombre del servicio, y si quieres revertir rápido, también tiene sentido mantener caliente la versión anterior
      [0]: https://docs.docker.com/reference/compose-file/deploy/#endpo...
    • No sé por qué no usar simplemente k8s rollout restart deployment
      O también se podría cambiar el DNS o el router entre dos backends
  • Me pregunto si esto implementa el patrón de pausar el tráfico
    Es una forma en la que el proxy básicamente detiene el tráfico durante unos segundos; las solicitudes entrantes parecen tardar unos segundos más de lo normal, pero se completan tras una breve demora
    Durante esos segundos puedes ejecutar una pequeña migración de DB o un cambio de infraestructura bloqueante un poco más complejo que pueda terminar en menos de 5 segundos

    • Me pregunto si alguien ha hecho esto en producción real
      Suena bastante riesgoso, y aunque trabajé varios años en una empresa de servidores de aplicaciones, es la primera vez que escucho este patrón
      Aun así, si viene de un cofundador de Django, seguramente ha visto muchos despliegues, así que no es fácil descartarlo
    • Sinceramente, suena más al patrón de vivir peligrosamente
    • Caddy hace esto
      Recuerdo haber hablado de este tema antes
  • Me pregunto dónde explicaron por qué decidieron crear un proxy propio en lugar de usar Traefik u otra herramienta probada

  • Me pregunto si hay alguna forma de configurar timeouts
    https://github.com/basecamp/kamal-proxy/blob/main/internal/s...
    https://github.com/basecamp/kamal-proxy/blob/main/internal/s...
    https://blog.cloudflare.com/exposing-go-on-the-internet/

  • Es NIH. No hay mucho más que decir