Por que e como a Dropbox migrou do Nginx para o Envoy
(dropbox.tech)<p>Um texto que explica muito bem as vantagens do Envoy em relação ao Nginx na Dropbox, que lida com dezenas de milhões de conexões simultâneas, milhões de requisições por segundo e largura de banda na casa dos terabytes<br />
<br />
Antes: nginx (versão open source) + python2 + Jinja2 + YAML <br />
→ mesmo que só uma coisa mudasse, era preciso redeploy de tudo<br />
→ as partes dinâmicas eram desenvolvidas em Lua<br />
→ a lógica complexa era tratada no Bandaid, um proxy em Go<br />
<br />
Isso funcionou bem por quase 10 anos, mas já não se encaixa bem no ambiente atual<br />
→ as APIs internas e externas (privadas) estão migrando gradualmente de REST para gRPC, então precisam de recursos de transcoding no proxy<br />
→ Protocol Buffers virou o padrão interno de definição de serviços <br />
→ todos os softwares são compilados e testados com Bazel, independentemente da linguagem<br />
→ os funcionários participam bastante das comunidades open source dos principais projetos de infraestrutura <br />
<br />
O Nginx também era caro de manter do ponto de vista operacional<br />
→ a lógica de geração de config era flexível demais e estava espalhada entre YAML, Jinja2 e Python<br />
→ o monitoramento era uma mistura de Lua / parsing de logs / monitoramento baseado em sistema<br />
→ a dependência crescente de módulos de terceiros afetava estabilidade/desempenho e gerava custo com upgrades frequentes <br />
→ o deploy e o gerenciamento de processos do nginx são bem diferentes dos demais serviços. Dependem bastante de coisas fora do sistema padrão, como syslog e logrotate<br />
<br />
Por isso, pela primeira vez em 10 anos, decidiram procurar um substituto para o Nginx<br />
<br />
* Por que não migrar para o Bandaid (proxy em Go desenvolvido pela Dropbox)? * <br />
→ Go consome mais recursos do que C/C++. <br />
→ a stack TLS do Go não suporta FIPS (Federal Information Processing Standards dos EUA)<br />
→ por ser uma ferramenta interna, não é possível contar com suporte da comunidade externa <br />
<br />
Agora: migração para uma infraestrutura de tráfego baseada em Envoy <br />
<br />
----- Pontos em que o Envoy foi melhor que o Nginx ------<br />
<br />
* Desempenho *<br />
<br />
A arquitetura do Nginx é event-driven / multiprocesso. Suporta SO_REUSEPORT & EPOLLEXCLUSIVE<br />
Embora seja baseada em event loop, não é totalmente non-blocking. Ao abrir arquivos ou gravar logs, o event loop pode parar. (mesmo com `aio`, `aio_write` e threadpool ativados)<br />
Isso gerava latência de cauda e, às vezes, atrasos de vários segundos<br />
<br />
O Envoy também tem arquitetura event-driven, mas baseada em threads em vez de processos <br />
Suporta SO_REUSEPORT (com suporte a filtro BPF) e event loop via libevent <br />
Não há I/O bloqueante no event loop. O logging de eventos também é implementado de forma non-blocking.<br />
<br />
Na teoria, parecia que os dois teriam características de desempenho parecidas, e na prática isso se confirmou na maioria dos testes de workload.<br />
Mas o Nginx mostrou latência maior na long tail. O motivo era a interrupção do event loop quando havia muito I/O.<br />
<br />
Sem coleta de estatísticas, o Nginx tinha desempenho parecido com o Envoy, mas a ferramenta interna de coleta de métricas em Lua deixava o Nginx 3x mais lento em testes de alto RPS. (isso por causa do `lua_shared_dict`, sincronizado por mutex). Havia problemas na forma como a Dropbox coletava estatísticas, mas eles desistiram de reimplementar isso de forma eficiente. (porque preveram que instrumentar o interior do Nginx dificultaria upgrades futuros)<br />
<br />
De todo modo, como o Envoy não tinha esses problemas, depois da migração foi possível liberar até 60% dos servidores que antes eram usados pelo Nginx.<br />
<br />
* Observabilidade *<br />
<br />
A versão gratuita do Nginx oferece apenas 7 estatísticas no módulo stub status <br />
Como isso obviamente era insuficiente, eles usavam um handler `log_by_lua` para expor mais métricas.<br />
Também havia um parser de `error.log` para exportar erros e um exporter separado para expor estados internos do nginx.<br />
<br />
Uma configuração básica do Envoy já fornece milhares de métricas diferentes em formato Prometheus <br />
Desde informações de tráfego do proxy até estado interno do servidor,<br />
estatísticas por cluster / upstream / virtual host e métricas downstream TCP/HTTP/TLS por listener etc.<br />
<br />
Junto com essas várias estatísticas, o Envoy também permite plugar Tracing Providers.<br />
Isso é útil não só para o time de tráfego, mas também para desenvolvedores de aplicações.<br />
<br />
Por fim, o Envoy consegue fazer streaming de access logs via gRPC.<br />
Isso reduz a carga de manter a bridge syslog-to-hive do time de tráfego.<br />
Rodar um serviço gRPC comum é bem mais fácil e seguro do que acoplar listeners TCP/UDP customizados.<br />
<br />
* Integração *<br />
<br />
A integração do Nginx é muito “Unix-like”. A configuração é bem estática.<br />
Depende de arquivos para config, certificados TLS, allowlist/blocklist etc.<br />
Isso é simples e compatível com versões anteriores, então dá para automatizar com alguns scripts shell,<br />
mas, conforme o sistema cresce, testabilidade e padronização vão ficando cada vez mais importantes.<br />
<br />
O Envoy tem sua própria abordagem para esse tipo de integração.<br />
Ele oferece uma API chamada xDS, incentivando o uso de protobuf e gRPC.<br />
O Envoy encontra recursos dinâmicos consultando esse xDS.<br />
<br />
- O xDS já está evoluindo para além do Envoy como a Universal Data Place API (UDPA), tentando virar o padrão de facto para load balancers L4/L7, e, pela experiência deles, isso está caminhando bem. Eles também estão tentando usar UDPA no Katran, o load balancer L4 eBPF/XDP, não só no Envoy.<br />
<br />
Como a Dropbox já integra seus serviços internamente via gRPC, isso se encaixa muito melhor.<br />
<br />
* Configuração *<br />
<br />
O Nginx tem como grande vantagem arquivos de configuração fáceis para humanos lerem. <br />
Mas essa vantagem vai se perdendo à medida que a configuração fica mais complexa e passa a ser gerada automaticamente.<br />
Na Dropbox, como era gerada por Python2, Jinja2 e YAML, o modelo de dados acabou ficando confuso e complexo.<br />
<br />
O Envoy tem um modelo de dados unificado para configuração. Todos os valores são definidos em Protocol Buffers. Isso resolve problemas de modelagem de dados e adiciona informação de tipo às configs.<br />
Como protobuf já é muito usado internamente na Dropbox, a integração fica fácil <br />
<br />
* Extensibilidade * <br />
<br />
Para expandir o Nginx, é preciso escrever módulos em C. Para criar módulos seguros, são necessários desenvolvedores sêniores. Ele até oferece interfaces em Perl / JS para módulos mais leves, mas elas são muito limitadas. Por isso, o caminho mais comum acaba sendo via `lua-nginx-module`. <br />
<br />
O principal mecanismo de extensão do Envoy é por plugins em C++, e embora a documentação não seja tão boa quanto a do nginx, isso é bem simples. Isso se deve a interfaces limpas e bem comentadas, além de C++14 e da biblioteca padrão <br />
<br />
O grande diferencial do Envoy em relação a outros web servers é o suporte a WebAssembly (WASM).<br />
Isso permite desenvolver extensões em várias linguagens, como Rust. <br />
A Dropbox ainda não usa WASM, mas isso pode mudar no futuro se houver suporte ao Go SDK para proxy-wasm<br />
<br />
* Build e testes *<br />
<br />
O Nginx usa por padrão configuração customizada baseada em shell e build baseado em `make`. É simples e ótimo, mas integrar isso a um monorepo compilado com Bazel exige bastante esforço <br />
O Nginx tem testes de integração em Perl, mas não tem testes unitários.<br />
<br />
O Envoy já usa um sistema de build baseado em Bazel e foi integrado facilmente ao monorepo deles.<br />
Também oferece testes unitários com gtest/gmock e framework de testes de integração<br />
<br />
* Segurança *<br />
<br />
O código do Nginx é bem pequeno e tem poucas dependências externas, então não apresenta muitas vulnerabilidades de segurança.<br />
<br />
O Envoy tem muito mais código, então naturalmente parece ter mais superfície de ataque. Para compensar isso, o Envoy depende fortemente de práticas modernas de segurança, usando AddressSanitizer, ThreadSanitizer, MemorySanitizer etc. <br />
<br />
* Recursos * <br />
<br />
Essa parte tem bastante opinião subjetiva, então vale considerar isso ao ler<br />
<br />
O Nginx começou como um web server para servir arquivos estáticos com pouquíssimos recursos. <br />
Ou seja, suas funções principais são static serving, caching e range caching<br />
Do ponto de vista de proxy, o Nginx carece bastante de recursos que a infraestrutura atual exige. <br />
Não faz conexão HTTP/2 com backends, não funciona como proxy gRPC multiplexado, não faz gRPC transcoding etc.<br />
Como usa um modelo de licença open core, alguns recursos importantes ficam fora da “versão comunitária”<br />
<br />
O Envoy, por outro lado, já nasceu como proxy de ingress/egress e é muito usado em ambientes com alta carga de gRPC.<br />
Os recursos de web service ainda são bem básicos. Ele não serve arquivos, o cache ainda está em desenvolvimento e ainda não suporta brotli etc. <br />
Para esse tipo de ambiente, eles ainda usam setups com Nginx tendo o Envoy como upstream cluster <br />
A expectativa é que, quando o Envoy passar a suportar cache HTTP, esses ambientes de serving estático também possam migrar <br />
<br />
O Envoy oferece muitos recursos ligados a gRPC<br />
- proxying de gRPC<br />
- HTTP/2 para backends<br />
- bridge gRPC → HTTP (+ reverso) <br />
- gRPC-WEB <br />
- transcoder JSON para gRPC<br />
<br />
Além disso, o Envoy também pode ser usado como proxy de saída <br />
- Egress Proxy<br />
- service discovery de software de terceiros com a biblioteca Courier gRPC <br />
<br />
* Comunidade *<br />
<br />
O desenvolvimento do Nginx é centralizado e, em grande parte, fechado. <br />
O desenvolvimento do Envoy é aberto e descentralizado. Acontece via issues/PRs no GitHub, além de bastante atividade em mailing lists e Slack </p><p>----- Estado atual da migração na Dropbox -----<br />
<br />
Eles estão operando Nginx e Envoy juntos há meio ano e migrando o tráfego gradualmente via DNS <br />
A migração não foi totalmente sem problemas: houve pequenos incidentes, mas nenhuma falha grave.<br />
Também organizaram soluções para problemas causados por comportamentos “unusual” ou “non-RFC” (veja o texto original para os detalhes)<br />
<br />
** Próximos passos **<br />
<br />
- HTTP/3: o Envoy também começou a oferecer suporte experimental. Eles pretendem testar isso após atualizar o kernel Linux para acelerar UDP<br />
- load balancer interno baseado em xDS e Outlier Detection<br />
- extensões Envoy baseadas em WASM <br />
- substituir o Bandaid (proxy em Go) por Envoy <br />
- aplicar Envoy também em apps mobile com Envoy Mobile</p>
3 comentários