DropboxがNginxからEnvoyへ移行した理由と方法
(dropbox.tech)<p>数千万の同時接続、毎秒数百万リクエスト、テラ単位の帯域を使用しているDropboxが、Nginxと比べたEnvoyの利点をうまく説明した記事<br />
<br />
従来 : nginx(オープンソース版) + python2 + Jinja2 + YAML <br />
→ 1つだけ変わっても全体の再デプロイが必要<br />
→ 動的な部分はLuaで開発<br />
→ 複雑なロジックはGoベースのプロキシであるBandaidで処理<br />
<br />
この10年近くうまく動いていたが、現在の環境にはあまり合っていない<br />
→ 内部および外部(非公開)APIは徐々にRESTからgRPCへ移行中であり、プロキシのトランスコーディング機能が必要<br />
→ Protocol Buffersが内部サービス定義の標準に <br />
→ すべてのソフトウェアは言語に関係なくBazelでビルドおよびテスト<br />
→ 主要インフラプロジェクトのオープンソースコミュニティに社員がかなり積極的に参加中<br />
<br />
Nginxは運用面でも維持コストが高い<br />
→ Config生成ロジックが柔軟すぎて、YAML、Jinja2、Pythonに分散している<br />
→ 監視がLua / Log parsing / システムベース監視の混在<br />
→ サードパーティモジュールへの依存が高まるにつれて、安定性/性能に影響し、頻繁なアップグレードによるコストが発生 <br />
→ nginx自体のデプロイおよびプロセス管理は他のサービスと大きく異なる。syslog、logrotateなど基本システムとはかなり異なるものに依存<br />
<br />
そこで、10年ぶりに初めてNginxを置き換えるものを探すことにした<br />
<br />
* なぜBandaid(Dropboxが独自開発したGoベースのプロキシ)ではないのか? <br />
→ GoはC/C++よりリソースを多く消費する。 <br />
→ GoのTLSスタックはFIPS対応ではない(米国連邦情報処理標準)<br />
→ 内部ツールのため外部コミュニティの支援を受けられない <br />
<br />
現在 : Envoyベースのトラフィックインフラへ移行中 <br />
<br />
----- NginxよりEnvoyが優れていた点 ------<br />
<br />
* 性能 *<br />
<br />
Nginxのアーキテクチャはイベントドリブン / マルチプロセス。SO_REUSEPORT & EPOLLEXCLUSIVEをサポート<br />
イベントループベースだが完全なノンブロッキングではない。ファイルオープン/ロギング時にはイベントループが停止する可能性がある。(aio、aio_writeおよびThreadpoolを有効化しても)<br />
これによってテールレイテンシが発生し、数秒単位の遅延が生じることもある<br />
<br />
Envoyも同様にイベントドリブンアーキテクチャだが、プロセスではなくスレッドベース <br />
SO_REUSEPORTをサポート(BPFフィルタ対応)、libeventによるイベントループをサポート <br />
イベントループでブロッキングI/Oはなく、イベントロギングもノンブロッキング方式で実装。<br />
<br />
理論的には似たような性能特性を示しそうで、実際にほとんどのワークロードテストでも似ていた。<br />
ただし、Nginxはロングテールでレイテンシがより大きかった。I/Oが多いときにイベントループが停止したため。<br />
<br />
統計収集がなければNginxはEnvoyと近い性能を示すが、内部で使用中のLua統計収集ツールがhigh-RPSテストでNginxを3倍遅くしていた。(これはmutexで同期されるlua_shared_dictのため)。Dropboxの統計収集方式にも問題はあったが、これを効率的に再開発するのは断念した。(Nginx内部にインストルメンテーションすると将来のアップグレードが難しくなると見込んだため)<br />
<br />
とにかく、このような問題がEnvoyにはなかったため、移行後は従来Nginxが使っていたサーバーの最大60%を削減できた。<br />
<br />
* Observability *<br />
<br />
無料版Nginxはstub statusモジュールで7種類のstatしか提供しない <br />
これでは当然不足していたため、log_by_luaハンドラを付けてさらに多くのstatを提供していた。<br />
また、error.logパーサーを通じてエラー情報を出力し、nginx内部状態値を出力するための別個のexporterも存在。<br />
<br />
基本的なEnvoyセットアップはPrometheus形式で数千種類の異なるメトリクスを提供 <br />
プロキシトラフィック情報からサーバーの内部状態情報、<br />
クラスタ別/アップストリーム別/仮想ホスト別の統計値や、リスナー別のTCP/HTTP/TLSダウンストリーム統計情報など<br />
<br />
このような多様な統計に加えて、EnvoyはTracing Providerをプラグイン可能。<br />
トラフィックチームだけでなく、アプリケーション開発者にも有用。<br />
<br />
最後に、EnvoyはgRPCを通じてアクセスログをストリーミング可能。<br />
これにより、トラフィックチームがsyslog-to-hiveブリッジを支える負担が軽減される。<br />
カスタムTCP/UDPリスナーを付けるより、一般的なgRPCサービスを実行するほうがはるかに簡単で安全。<br />
<br />
* Integration *<br />
<br />
Nginxの統合は非常にUnix的。Configurationが非常に静的。<br />
configファイルやTLS証明書、allowlist/blocklistなどをファイルに依存。<br />
単純で後方互換性があるため、いくつかのシェルスクリプトで自動化は可能だが、<br />
システムが大きくなるにつれて、テスト可能性と標準化がますます重要になる。<br />
<br />
Envoyはこのような統合に対して独自の方法を持っている。<br />
xDSと呼ばれるAPIを提供し、protobufとgRPCの利用を推奨。<br />
EnvoyはこのxDSにクエリを送って動的リソースを見つける。<br />
<br />
- このxDSは今やEnvoyを超えて、Universal Data Place API(UDPA)という名前でL4/L7ロードバランサーのde facto標準になろうと進化しており、私たちの経験ではこれがうまく進んでいる。EnvoyだけでなくKatran eBPF/XDP L4ロードバランサーでもUDPAを使おうとしている。<br />
<br />
Dropboxは内部でgRPCを通じてサービスが連携しているため、はるかに都合がよい。<br />
<br />
* Configuration *<br />
<br />
Nginxは人間が読みやすい設定ファイルという大きな利点を持っている。 <br />
しかしこの利点は、設定が複雑化し自動生成されるにつれて失われていった。<br />
DropboxではPython2、Jinja2、YAMLなどを通じて生成されていたため、データモデルも絡み合って複雑だった。<br />
<br />
Envoyは設定に対する統一されたデータモデルを持っている。すべての設定値はProtocol Bufferで定義される。データモデリングの問題が解消され、設定値に型情報が追加される。<br />
Dropbox内部ではprotobufが多く使われているため、統合も容易だった <br />
<br />
* Extensibility * <br />
<br />
Nginxの拡張にはCモジュールの作成が必要。安全なモジュールを書くにはシニア開発者が必要。より軽量なモジュール開発のためにPerl / JSインターフェースも提供しているが非常に限定的。そのため最も一般的に使われる方法はlua-nginx-moduleを通すこと。 <br />
<br />
Envoyの主な拡張メカニズムはC++プラグインだが、ドキュメントはnginxほど充実していないものの非常に単純。これは整理されコメントの行き届いたインターフェース、C++14言語と標準ライブラリのおかげ <br />
<br />
Envoyが他のWebサーバーと大きく異なる点は、WebAssembly(WASM)をサポートしていること。<br />
これによりRustのようなさまざまな言語で拡張開発できる。 <br />
まだDropboxではWASMを使っていないが、いつかproxy-wasm向けGo SDKがサポートされれば変わるかもしれない<br />
<br />
* Building and Testing *<br />
<br />
Nginxは基本的にカスタムシェルベースの設定とmakeベースのビルドを使用。単純で優れているが、これをBazelでビルドされるモノレポに統合するにはかなりの労力がかかる <br />
NginxにはPerlベースのintegration testはあるが、unit testはない。<br />
<br />
EnvoyはすでにビルドシステムがBazelベースで、簡単に私たちのモノレポへ統合できた。<br />
gtest/gmockベースのunit testとintegration test frameworkをサポート<br />
<br />
* Security *<br />
<br />
Nginxのコードは非常に小さく、外部依存も少ないため、セキュリティ上の脆弱性は多くない。<br />
<br />
Envoyはコード量が多いため、そのぶん攻撃面も多く見える。そのためEnvoyは現代的なセキュリティプラクティスに大きく依存している。AddressSanitizer、ThreadSanitizer、MemorySanitizerなどを使用。 <br />
<br />
* Features * <br />
<br />
この部分は主観的な意見が多いので参考までに<br />
<br />
Nginxは当初、ごく少ないリソースで静的ファイルを配信するWebサーバーとして始まった。 <br />
つまりstatic serving、caching、range cachingが主機能<br />
プロキシの観点では、Nginxには現代のインフラで求められる機能が多く不足している。 <br />
バックエンドとのHTTP/2接続もできず、マルチプレックスされるgRPCプロキシにも対応せず、gRPCトランスコーディングも不可など。<br />
オープンコア型のライセンスモデルのため、いくつかの主要機能は「コミュニティ版」には含まれていない<br />
<br />
Envoyはそもそもingress/egress proxyとして始まり、gRPC負荷の高い環境で多く使われている。<br />
Webサービス機能は非常に初歩的なレベル。ファイル配信もできず、キャッシュもまだ開発中で、brotliも未対応など <br />
このような環境向けには、Envoyをアップストリームクラスタとして使うNginxセットアップも使用中 <br />
EnvoyがHTTPキャッシュに対応すれば、このような静的配信環境も移行できると見込んでいる <br />
<br />
EnvoyはgRPC関連機能を多くサポート<br />
- gRPC proxying<br />
- HTTP/2 to backends<br />
- gRPC → HTTP bridge (+ reverse.) <br />
- gRPC-WEB <br />
- gRPC JSON transcoder<br />
<br />
またEnvoyはoutbound proxyとしても利用可能 <br />
- Egress Proxy<br />
- Courier gRPCライブラリによるサードパーティソフトウェアのサービスディスカバリ <br />
<br />
* Community *<br />
<br />
Nginxの開発は中央集権的で、その大半が見えない。 <br />
Envoyの開発はオープンで分散的。GitHubのissue/PRで進み、メーリングリストやSlackなどでも活発 </p><p>----- Dropboxの現在のMigration状況 -----<br />
<br />
NginxとEnvoyを半年前から並行運用しながら、DNSを通じて段階的にトラフィックを移行中 <br />
何の問題もなく移行できたわけではなく、小さな問題はあったが深刻な障害はなかった。<br />
"unusual" または "non-RFC" 的な挙動によって経験した問題に対する解決策も整理している(本文に詳細があるので参照)<br />
<br />
** 今後やること **<br />
<br />
- HTTP/3 : Envoyも実験的サポートを開始。UDP高速化のためにLinuxカーネルをアップグレードしたら試してみる予定<br />
- 内部xDSベースのロードバランサーとOutlier Detection<br />
- WASMベースのEnvoy拡張 <br />
- Bandaid(Goベースのプロキシ)をEnvoyに置き換え <br />
- Envoy MobileでモバイルアプリにもEnvoyを適用</p>
3件のコメント