O proxy HTTP mínimo para deploy com zero downtime, Kamal Proxy
(github.com/basecamp)- Kamal Proxy é um pequeno proxy HTTP que opera na frente de aplicações web, projetado para alternar o deploy para novas instâncias sem interromper o tráfego em andamento
- Novas instâncias são registradas com
kamal-proxy deploy, e o proxy só roteia novo tráfego para essa instância depois que ela passa em um health check HTTP - O comando de deploy espera até que todo o tráfego saia da instância anterior, então, depois de retornar com sucesso, é possível remover a instância antiga sem interromper requisições em andamento
- Para operar várias aplicações em um único proxy, ele oferece roteamento baseado em host, roteamento baseado em caminho, TLS automático e especificação manual de certificados TLS
- O Kamal Proxy foi projetado como parte da ferramenta de deploy Kamal, mas também pode ser usado de forma independente ou como componente de outras ferramentas de deploy
O que o Kamal Proxy faz
- Kamal Proxy é um pequeno proxy HTTP para facilitar a orquestração de deploys com zero downtime
- Ao executar uma aplicação web atrás do Kamal Proxy, é possível publicar mudanças sem interromper o tráfego em andamento
- Não é necessária nenhuma cooperação especial por parte da aplicação para que isso funcione
- O Kamal Proxy foi projetado para funcionar como parte do Kamal, que inclui empacotamento em contêineres e provisionamento
- Também pode ser usado sozinho ou como parte de outras ferramentas de deploy
Execução e fluxo de deploy
- A instância do proxy é iniciada com o comando
kamal-proxy run - Não há arquivo de configuração; é possível informar opções quando os padrões não forem adequados para a aplicação
- A porta HTTP padrão é
80 - Para executar em outra porta, use
kamal-proxy run --http-port 8080 - Todas as opções podem ser consultadas em
kamal-proxy help run
- A porta HTTP padrão é
- Para enviar tráfego para a aplicação web, faça o deploy da instância da aplicação no proxy
- A instância de destino é especificada no formato
hostname:port- Exemplo:
kamal-proxy deploy service1 --target web-1:3000 - Esse comando registra
web-1:3000com o nome de serviçoservice1
- Exemplo:
Como funciona a troca com zero downtime
- Quando uma nova instância é registrada, o Kamal Proxy executa imediatamente um health check HTTP para verificar acessibilidade e funcionamento
- Se o health check for bem-sucedido, ele começa a rotear o novo tráfego para a nova instância
- Se a nova instância não ficar healthy dentro do tempo adequado, o comando
deployinterrompe o deploy e retorna um código de saída diferente de zero - Cada deploy transfere todo o tráfego das instâncias implantadas anteriormente para a nova instância
- O comando
deploysó retorna depois de esperar que todo o tráfego saia da instância anterior- Depois de retornar com sucesso, é possível remover a instância anterior sem interromper requisições em andamento
- Como a nova instância só recebe tráfego quando está healthy e a instância anterior só é removida depois de ser totalmente drenada, o deploy acontece com zero downtime
Configuração de health check
- O health check padrão envia uma requisição
GETpara/upuma vez por segundo para cada serviço - Uma resposta
200é considerada healthy - Para adaptar o health check à aplicação, use as flags de
deploy--health-check-path--health-check-port--health-check-timeout--health-check-interval
- Exemplo de mudança do caminho do health check:
kamal-proxy deploy service1 --target web-1:3000 --health-check-path web/index.html
- Se o endpoint de health estiver exposto em uma porta diferente da do serviço principal:
kamal-proxy deploy service1 --target web-1:3000 --health-check-port 8080
Recursos de roteamento
-
Roteamento baseado em host
- Com roteamento baseado em host, é possível rotear tráfego para várias aplicações no mesmo servidor com uma única instância do Kamal Proxy
- É possível especificar um host específico no momento do deploy
kamal-proxy deploy service1 --target web-1:3000 --host app1.example.com- Instâncias implantadas dessa forma recebem tráfego apenas do host especificado
- Ao definir hosts separados para cada aplicação, é possível executar várias aplicações no mesmo servidor sem conflito de portas
- Um host específico só pode rotear para um serviço por vez
- Se
service1já estiver usandoapp1.example.com, será retornado um erro quandoservice2for implantado com o mesmo host - Depois de remover
service1, será possível implantarservice2no mesmo host
-
Roteamento baseado em caminho
- O roteamento baseado em caminho é usado em aplicações que dividem o tráfego entre serviços diferentes com base no caminho da requisição
- Os serviços podem ser montados sob diferentes prefixos de caminho
- Exemplo de envio de requisições que começam com
/apiparaweb-1:kamal-proxy deploy service1 --target web-1:3000 --path-prefix=/api - Exemplo de envio do restante para
web-2:kamal-proxy deploy service2 --target web-2:3000 - Por padrão, o prefixo do caminho é removido antes de ser repassado ao upstream
- A requisição
/api/users/123é encaminhada paraweb-1como/users/123 - Para encaminhar o caminho original sem alterações, especifique
--strip-path-prefix=false
Suporte a TLS
- O Kamal Proxy pode obter e renovar automaticamente certificados TLS para aplicações
- O TLS automático é ativado adicionando a flag
--tlsno momento do deploykamal-proxy deploy service1 --target web-1:3000 --host app1.example.com --tls
- O TLS automático exige a especificação de um host
- Essa condição existe para impedir solicitações maliciosas de certificados para nomes de host arbitrários
- No roteamento baseado em caminho, a opção de TLS deve ser configurada na rota raiz
- Serviços implantados em outros caminhos do mesmo host usam a configuração TLS definida na rota raiz
- Se for necessário instalar um certificado TLS obtido manualmente, uma autoridade certificadora própria ou um Cloudflare origin certificate, é possível especificar diretamente o arquivo do certificado e o caminho da chave privada
--tls-certificate-path cert.pem--tls-private-key-path key.pem
Variáveis de ambiente e build
- Em ambientes como execução em contêiner Docker, as opções de
runpodem ser definidas como variáveis de ambiente kamal-proxy runlê o valor de cada opção a partir das variáveis de ambiente, se elas estiverem definidaskamal-proxy run --http-port 8080HTTP_PORT=8080 kamal-proxy run
- Se o nome da variável de ambiente entrar em conflito com o ambiente existente, é possível diferenciá-lo adicionando o prefixo
KAMAL_PROXY_KAMAL_PROXY_HTTP_PORT=8080 kamal-proxy run
- O build local é feito com
makeem um ambiente Go funcional - Para fazer o build com contêiner Docker, use
make docker - Há uma configuração Docker Compose na pasta
examplepara testar os comandos do proxy
1 comentários
Opiniões no Hacker News
É estranho terem escolhido deploy como nome da ação
É um termo já sobrecarregado, então deve gerar confusão do tipo “o app foi implantado? Não, foi implantado no proxy?”
Poderiam ter usado algo como
bind,interceptou simplesmenteproxy; não entendo por quedeployHoje em dia dá para fazer deploy sem downtime facilmente também com outros servidores, então é interessante a escolha de transformar isso em um app separado inteiro
Por exemplo, com um app + proxy web que suporte sockets Unix, dá para fazer sem downtime apenas movendo um arquivo
É atômico, e também dá para enviar requisições de aquecimento com
curlCriar até um sistema de registro parece exagero
Kamal é uma ferramenta de deploy usada para economizar milhões de dólares ao migrar da nuvem para hardware próprio(https://basecamp.com/cloud-exit)
Se houver algum texto de referência, gostaria de aprender mais
Isso parece ser principalmente uma tentativa de lidar com o problema fundamental de queda de tráfego ao trocar contêineres de serviço no Docker Swarm
Passei pelo mesmo problema ao criar um servidor JAMStack na Cloud 66, usei Caddy em vez de um proxy próprio, e acho que Traefik também teria servido muito bem
Não sei por que o Kamal escolheu Swarm em vez de k8s ou k3s, mas a complexidade precisa morar em algum lugar e não dá para escondê-la, então parece que acabaram indo para um proxy próprio
Não usei pessoalmente, mas fico bastante cético, porque parece que terão de ficar correndo atrás de suporte a WebSockets, SSE, HTTP/3, diversos tipos de compressão e criptografia
Parece um exemplo de um proxy reverso que começou simples e acaba absorvendo continuamente a complexidade que tentava evitar
Acho que sistemas de execução amplos e extensíveis precisam de concorrentes, mas o Kamal parece um trabalho que está recuando para dentro da complexidade que queria evitar
A capacidade e a flexibilidade do Kubernetes como framework de gerenciamento de estado desejado acabam ficando ainda mais evidentes
Dá para continuar usando Docker e ainda assim usar Caddy, Traefik ou Envoy como proxy
O Kubernetes também acaba usando esses componentes como controladores de ingresso em muitos casos
Pelo que vejo, não parece usar Swarm
docker runcomuns, e não há Swarm nem orquestraçãoBasicamente é uma ferramenta simples que substitui o Capistrano
O básico de um serviço de orquestração de contêineres é lidar com conexões e deploy blue/green por meio de um controlador de ingresso
Mais precisamente, controlador de ingresso é o nome do papel atribuído a inúmeros proxies reversos que já lidam com esse caso de uso há anos, ou até décadas
Seria bom se alguém pudesse explicar rapidamente como deploy sem downtime geralmente funciona
Imagino que as duas versões do app fiquem no ar ao mesmo tempo e que o tráfego novo seja roteado para a versão nova
Mas, se houver um único banco de dados e a nova versão do app trouxer uma alteração de schema, o script de migração deve alterar o schema na inicialização, enquanto a versão antiga do app espera o schema antigo
Fico curioso para saber como isso é tratado
Muitos frameworks executam migrações ao fazer deploy do código, mas na prática é melhor controlar manualmente o momento da execução
Cada versão do código precisa funcionar tanto com o schema atual quanto com o schema futuro pós-migração, tornando-se, na prática, código compatível com versões anteriores
O fluxo fica: “fazer deploy do novo código que funciona tanto com o schema atual quanto com o futuro → verificar → executar a migração → verificar → remover o código que lidava com o schema desaparecido”
Esse é o custo extra de fazer deploy sem downtime ou rolling deploy, e empresas grandes em geral fazem assim
Especialmente se você atende usuários em uma região específica e consegue escolher um horário tranquilo para fazer o deploy
Duas versões do app podem ser executadas ao mesmo tempo
https://xata.io/blog/multi-version-schema-migrations
O load balancer começa a aceitar conexões no Server2 e deixa de aceitar novas conexões no Server1
Depois, quando todas as conexões do Server1 forem fechadas, o Server1 é desconectado
Podem ser servidores diferentes ou vários workers em um único servidor
Durante esse período, como disseram outros comentários, as migrações precisam ser compatíveis com versões anteriores
O Kamal 2 está atualmente em RC (https://github.com/basecamp/kamal/releases) e, como vai oferecer suporte a SSL automático, estou animado com a possibilidade de rodar facilmente vários apps com Kamal em um único servidor
autocert, que é quase uma implementação mínimaÉ frágil, e os limites de emissão do Let's Encrypt e os limites de certificados simultâneos dificultam escalar horizontalmente instâncias do proxy
Caddy/Certmagic resolve esse problema gravando dados em um armazenamento compartilhado, emitindo apenas um certificado e fazendo todas as instâncias reutilizarem e coordenarem via armazenamento
Também não há troca de emissor, contorno de limites de emissão nem suporte a ARI
Segurar requisições até que o upstream fique disponível também é algo que o Caddy trata bem se você configurar
try_durationetry_intervalemreverse_proxyO tratamento de cabeçalhos de proxy também não leva em conta IPs confiáveis; se ativado, alguém pode definir
X-Forwarded-Fore falsificar o IPAinda bem que vem desativado por padrão, mas também não há nenhum aviso
Entendo que o objetivo seja simplicidade, mas, no estado atual, parece imaturo, e há tantas opções melhores que eu não teria vontade de usar em produção
Não entendi bem como se usa isso e sinto que estou deixando passar alguma coisa
O exemplo inicia 4 réplicas do serviço
webSe é possível implantar em uma dessas réplicas, como
example-web-1, para criar o serviço, não sei o que as outras 3 réplicas fazemPara atualizar o
webe fazer uma implantação sem downtime, parece que seria preciso executar o comando de build no serviçoweb, iniciar esse serviço de alguma forma e então executar o deploy para o novo destinoMas, se você executar
docker compose up --build --force-recreate web, as réplicas existentes são derrubadas, tornando tudo sem sentidoNão verifiquei se esse proxy distribui a carga entre os vários registros retornados por uma consulta DNS, mas pelo menos com balanceamento baseado em VIP ele deve funcionar como esperado
A parte da segunda atualização é menos clara
Talvez a expectativa seja subir um serviço com outro nome na mesma rede e executar
kamal-proxy deployPode ser algo como colocar o número da versão no nome do serviço e, se quiser reverter rapidamente, também faz sentido manter a versão anterior quente
[0]: https://docs.docker.com/reference/compose-file/deploy/#endpo...
k8s rollout restart deploymentOu então alternar o DNS ou o roteador entre dois backends
Fico curioso se isso implementa o padrão de pausa de tráfego
É uma abordagem em que o proxy basicamente congela o tráfego por alguns segundos; as requisições recebidas parecem demorar alguns segundos a mais que o normal, mas são concluídas após um breve atraso
Durante esses segundos, dá para executar uma pequena migração de banco de dados ou alguma mudança de infraestrutura bloqueante um pouco mais complexa que possa terminar em até 5 segundos
Parece bem arriscado, e trabalhei por alguns anos em uma empresa de servidores de aplicação, mas nunca ouvi falar desse padrão
Ainda assim, se vem de um cocriador do Django, ele provavelmente já viu muitas implantações, então é difícil simplesmente ignorar
Lembro de termos conversado sobre esse assunto no passado
Fico curioso onde foi explicado o motivo de criar um proxy próprio, em vez de usar Traefik ou alguma outra ferramenta comprovada
Há contexto no PR sobre por que mudaram e decidiram implementar por conta própria
https://github.com/basecamp/kamal/pull/940
Mas acho que nunca publicaram oficialmente uma comparação com outras opções além do Traefik
Fico curioso se há uma 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/
É NIH. Nada mais a acrescentar