Pourquoi et comment Dropbox est passé de Nginx à Envoy
(dropbox.tech)<p>Un article qui explique très bien les avantages d’Envoy par rapport à Nginx chez Dropbox, qui gère des dizaines de millions de connexions simultanées, plusieurs millions de requêtes par seconde et une bande passante de l’ordre du téraoctet<br />
<br />
Avant : nginx (version open source) + python2 + Jinja2 + YAML <br />
→ le moindre changement imposait un redéploiement complet<br />
→ les parties dynamiques étaient développées en Lua<br />
→ la logique complexe était traitée par Bandaid, un proxy en Go<br />
<br />
Cela a bien fonctionné pendant près de 10 ans, mais ne correspond plus bien à l’environnement actuel<br />
→ les API internes et externes (privées) migrent progressivement de REST vers gRPC, ce qui exige des fonctions de transcodage dans le proxy<br />
→ Protocol Buffers est devenu le standard interne de définition des services <br />
→ tous les logiciels sont buildés et testés avec Bazel, quel que soit le langage<br />
→ les employés participent assez intensément aux communautés open source de plusieurs projets d’infrastructure majeurs<br />
<br />
Nginx est aussi coûteux à maintenir sur le plan opérationnel<br />
→ la logique de génération de configuration est trop flexible et répartie entre YAML, Jinja2 et Python<br />
→ l’observabilité est un mélange de Lua, de parsing de logs et de monitoring système<br />
→ la dépendance croissante à des modules tiers affecte la stabilité et les performances, tout en générant des coûts liés aux mises à niveau fréquentes <br />
→ le déploiement et la gestion des processus de nginx sont très différents des autres services, avec une forte dépendance à des éléments distincts du système standard comme syslog ou logrotate<br />
<br />
Dropbox a donc décidé, pour la première fois en 10 ans, de chercher un remplaçant à Nginx<br />
<br />
* Pourquoi ne pas passer à Bandaid (le proxy en Go développé en interne par Dropbox) ? * <br />
→ Go consomme plus de ressources que C/C++. <br />
→ La stack TLS de Go ne prend pas en charge FIPS (Federal Information Processing Standards des États-Unis)<br />
→ c’est un outil interne, donc sans possibilité de support par une communauté externe <br />
<br />
Aujourd’hui : migration vers une infrastructure de trafic basée sur Envoy <br />
<br />
----- Ce qui rendait Envoy meilleur que Nginx ------<br />
<br />
* Performance *<br />
<br />
L’architecture de Nginx est event-driven et multiprocessus. Prise en charge de SO_REUSEPORT & EPOLLEXCLUSIVE<br />
Elle repose sur une boucle d’événements mais n’est pas totalement non bloquante. Lors de l’ouverture de fichiers ou de la journalisation, la boucle d’événements peut être interrompue (même avec aio, aio_write et un threadpool activés)<br />
Cela provoque de la latence en queue de distribution, avec parfois des retards de plusieurs secondes<br />
<br />
Envoy a une architecture similaire, event-driven également, mais basée sur des threads plutôt que sur des processus <br />
Prise en charge de SO_REUSEPORT (avec support des filtres BPF), et boucle d’événements via libevent <br />
Pas d’I/O bloquantes dans la boucle d’événements. La journalisation des événements est elle aussi implémentée de manière non bloquante.<br />
<br />
En théorie, les caractéristiques de performance semblaient devoir être proches, et c’était effectivement le cas sur la plupart des tests de charge.<br />
En revanche, Nginx présentait une latence plus élevée sur la longue traîne, car la boucle d’événements s’arrêtait lors de fortes charges I/O.<br />
<br />
Sans collecte de statistiques, Nginx offre des performances comparables à Envoy, mais l’outil interne de collecte de statistiques en Lua rendait Nginx trois fois plus lent dans les tests à fort RPS. (Cela venait de `lua_shared_dict`, synchronisé par mutex). Dropbox reconnaît que son mode de collecte de statistiques avait ses propres problèmes, mais a renoncé à le réécrire efficacement. (L’idée était qu’ajouter de l’instrumentation à l’intérieur de Nginx compliquerait les futures mises à niveau.)<br />
<br />
En tout cas, comme Envoy n’avait pas ces problèmes, la migration a permis de libérer jusqu’à 60 % des serveurs auparavant utilisés par Nginx.<br />
<br />
* Observabilité *<br />
<br />
La version gratuite de Nginx ne fournit que 7 statistiques via le module stub status <br />
C’était évidemment insuffisant, donc un handler `log_by_lua` avait été ajouté pour exposer davantage de statistiques.<br />
Il existait aussi un parseur de `error.log` pour exporter les informations d’erreur, ainsi qu’un exporter séparé pour exposer l’état interne de nginx.<br />
<br />
Une installation de base d’Envoy fournit des milliers de métriques différentes au format Prometheus <br />
Des informations sur le trafic proxy jusqu’à l’état interne du serveur,<br />
en passant par les statistiques par cluster / upstream / virtual host et les statistiques downstream TCP/HTTP/TLS par listener, etc.<br />
<br />
Avec cette richesse de statistiques, Envoy permet aussi de brancher des Tracing Providers.<br />
C’est utile non seulement pour l’équipe trafic, mais aussi pour les développeurs d’applications.<br />
<br />
Enfin, Envoy peut diffuser les logs d’accès en streaming via gRPC.<br />
Cela réduit la charge de maintenance du pont syslog-to-hive de l’équipe trafic.<br />
Exécuter un service gRPC standard est bien plus simple et plus sûr que d’ajouter un listener TCP/UDP personnalisé.<br />
<br />
* Intégration *<br />
<br />
L’intégration de Nginx est très « Unix ». Sa configuration est très statique.<br />
Elle dépend de fichiers pour la configuration, les certificats TLS, les allowlists/blocklists, etc.<br />
C’est simple et rétrocompatible, donc automatisable avec quelques scripts shell,<br />
mais à mesure que le système grossit, la testabilité et la standardisation deviennent de plus en plus importantes.<br />
<br />
Envoy a sa propre approche de cette intégration.<br />
Il fournit une API appelée xDS, qui encourage l’usage de protobuf et gRPC.<br />
Envoy interroge xDS pour découvrir des ressources dynamiques.<br />
<br />
- xDS évolue désormais au-delà d’Envoy sous le nom de Universal Data Place API (UDPA), avec l’ambition de devenir le standard de facto des load balancers L4/L7, et selon leur expérience cela progresse bien. Ils essaient aussi d’utiliser UDPA pour le load balancer L4 Katran eBPF/XDP, qui n’appartient pas à Envoy.<br />
<br />
Comme Dropbox connecte déjà ses services en interne via gRPC, c’est nettement plus adapté.<br />
<br />
* Configuration *<br />
<br />
Le grand avantage de Nginx est d’avoir des fichiers de configuration faciles à lire pour un humain. <br />
Mais cet avantage s’estompe à mesure que la configuration se complexifie et qu’elle est générée automatiquement.<br />
Chez Dropbox, comme elle était générée via Python2, Jinja2 et YAML, le modèle de données est devenu lui aussi embrouillé et complexe.<br />
<br />
Envoy dispose d’un modèle de données unifié pour la configuration. Toutes les valeurs de configuration sont définies en Protocol Buffers. Cela résout les problèmes de modélisation des données et ajoute des informations de type aux paramètres.<br />
Comme protobuf est déjà largement utilisé en interne chez Dropbox, l’intégration est facilitée <br />
<br />
* Extensibilité * <br />
<br />
Pour étendre Nginx, il faut écrire des modules en C. Pour développer des modules sûrs, il faut des développeurs seniors. Il existe bien des interfaces Perl / JS pour développer des modules plus légers, mais elles sont très limitées. La méthode la plus courante consiste donc à passer par `lua-nginx-module`. <br />
<br />
Le principal mécanisme d’extension d’Envoy repose sur des plugins C++. La documentation n’est pas aussi aboutie que celle de nginx, mais c’est très simple. Cela tient à une interface propre et bien commentée, ainsi qu’à C++14 et à la bibliothèque standard <br />
<br />
La grande différence d’Envoy par rapport aux autres serveurs web est la prise en charge de WebAssembly (WASM).<br />
Cela permet de développer des extensions dans divers langages comme Rust. <br />
Dropbox n’utilise pas encore WASM, mais cela pourrait changer si la prise en charge du SDK Go pour proxy-wasm arrivait un jour<br />
<br />
* Building and Testing *<br />
<br />
Nginx utilise essentiellement une configuration shell custom et un build basé sur make. C’est simple et excellent, mais l’intégrer à un monorepo buildé avec Bazel demande pas mal d’efforts <br />
Nginx a des tests d’intégration en Perl, mais pas de tests unitaires.<br />
<br />
Envoy dispose déjà d’un système de build basé sur Bazel, et l’intégration dans leur monorepo a été simple.<br />
Prise en charge des tests unitaires basés sur gtest/gmock et d’un framework de tests d’intégration<br />
<br />
* Sécurité *<br />
<br />
Le code de Nginx est très compact et a peu de dépendances externes, donc il présente relativement peu de vulnérabilités de sécurité.<br />
<br />
Envoy a beaucoup plus de code, donc la surface d’attaque paraît naturellement plus large. Pour y répondre, Envoy s’appuie fortement sur des pratiques de sécurité modernes : AddressSanitizer, ThreadSanitizer, MemorySanitizer, etc. <br />
<br />
* Fonctionnalités * <br />
<br />
Cette partie contient beaucoup d’avis subjectifs, donc à prendre comme telle<br />
<br />
Nginx a commencé comme un serveur web capable de servir des fichiers statiques avec très peu de ressources. <br />
Ses fonctions principales sont donc le static serving, le caching et le range caching<br />
Du point de vue proxy, Nginx manque aujourd’hui de nombreuses fonctions demandées dans les infrastructures modernes. <br />
Pas de connexion HTTP/2 vers les backends, pas de proxy gRPC multiplexer, pas de transcodage gRPC, etc.<br />
Son modèle de licence open core fait que certaines fonctionnalités importantes sont absentes de la « version communautaire »<br />
<br />
Envoy, lui, a été conçu dès l’origine comme proxy ingress/egress et est largement utilisé dans des environnements très chargés en gRPC.<br />
Ses fonctions de serveur web restent très basiques. Pas de file serving, le caching est encore en cours de développement, pas de support de brotli, etc. <br />
Pour ce type d’environnement, ils utilisent aussi une configuration Nginx avec Envoy comme upstream cluster <br />
Ils estiment que lorsqu’Envoy saura faire du cache HTTP, ces environnements de serving statique pourront aussi migrer <br />
<br />
Envoy prend largement en charge les fonctionnalités liées à gRPC<br />
- gRPC proxying<br />
- HTTP/2 to backends<br />
- gRPC → HTTP bridge (+ reverse.) <br />
- gRPC-WEB <br />
- gRPC JSON transcoder<br />
<br />
Envoy peut aussi être utilisé comme proxy sortant <br />
- Egress Proxy<br />
- Service discovery pour logiciels tiers avec la bibliothèque gRPC Courier <br />
<br />
* Communauté *<br />
<br />
Le développement de Nginx est centralisé et en grande partie opaque. <br />
Le développement d’Envoy est ouvert et décentralisé. Il se fait via les issues/PR GitHub, avec aussi une activité soutenue sur les mailing lists, Slack, etc. </p><p>----- État actuel de la migration chez Dropbox -----<br />
<br />
Nginx et Envoy tournent ensemble depuis six mois, avec une migration progressive du trafic via DNS <br />
Tout ne s’est pas déroulé sans aucun souci : il y a eu de petits problèmes, mais aucune panne grave.<br />
Ils ont aussi documenté des solutions aux problèmes rencontrés à cause de comportements « unusual » ou « non-RFC » (voir le corps de l’article pour les détails)<br />
<br />
** Ce qu’il reste à faire **<br />
<br />
- HTTP/3 : Envoy commence aussi à le prendre en charge expérimentalement. Ils prévoient de l’essayer après une mise à niveau du noyau Linux pour accélérer l’UDP<br />
- Load balancer interne basé sur xDS et Outlier Detection<br />
- Extensions Envoy basées sur WASM <br />
- Remplacer Bandaid (proxy en Go) par Envoy <br />
- Appliquer Envoy aux applications mobiles avec Envoy Mobile</p>
3 commentaires