re:Invent 2024: NetflixのTitusで実現する数百万Containerの効率的ネットワーク管理
はじめに
海外の様々な講演を日本語記事に書き起こすことで、隠れた良質な情報をもっと身近なものに。そんなコンセプトで進める本企画で今回取り上げるプレゼンテーションはこちら!
📖 AWS re:Invent 2024 - Netflix’s efficient network configuration for millions of containers (NFX306)
この動画では、NetflixのContainer管理システムであるTitusにおけるネットワーキングの課題と解決策について解説しています。数百万のContainerを効率的に管理するため、ENI TrunkingやPrefix delegationなどの手法を採用し、特にIPv6の活用により大幅なコスト削減を実現しています。また、HTBを用いたトラフィックシェーピングによってNoisy Neighbor問題に対処し、IPManというシステムを構築してContainer起動時のネットワークセットアップを最適化しています。その結果、IPv6のみのネットワークセットアップでは1秒あたり800個のContainer起動を実現し、P99レイテンシーで100ミリ秒という高速なパフォーマンスを達成しています。さらに、netkitなどの新技術を活用した更なる効率化についても言及されています。
※ 画像をクリックすると、動画中の該当シーンに遷移します。
re:Invent 2024関連の書き起こし記事については、こちらのSpreadsheet に情報をまとめています。合わせてご確認ください!
本編
Netflixのコンテナネットワーキング:課題と概要
みなさん、こんにちは。本日のセッションへようこそ。私はJoey Burbaと申します。NetflixをサポートするAWS Solutions Architectの一人です。これから1時間、Netflixの社内プラットフォームであるTitusでのコンテナネットワーキングのスケーリング方法について一緒に学んでいきましょう。現在、TitusはKubernetes APIの仕様から大きな影響を受けています。
Containerには多くの利点があります。開発環境からテスト環境や本番環境への移行が容易で、非常に移植性が高いのです。また、共通のベースとなるオペレーティングシステムを共有しているため、非常に軽量です。私たちは通常、アプリケーションコードと依存関係のみをContainerにパッケージングするので、起動と停止が非常に迅速に行えます。また、1台のサーバーに多数のContainerを配置し、CPU、GPU、メモリなどのリソースを効率的に使用することができます。
しかし、ネットワーキングに関して多くの課題も生じます。例えば、1つのノード上の何百ものContainerにIPアドレスをどのように提供するのか?Containerがサービスにアクセスでき、かつ私たちがContainerにアクセスできることをどのように確保するのか?そして、これらをNetflixのスケールで、パフォーマンス、効率性、セキュリティを確保しながらどのように実現するのか?本日は、NetflixのシニアソフトウェアエンジニアであるHechao Liをお招きしています。彼女は素晴らしいセッションを用意してくれました。それでは、Hechao Liをステージにお迎えしましょう。
コンテナの利点と課題:Netflixの視点
こんにちは、みなさん。私はHechaoと申します。本日は、Netflixが本番環境で稼働している何百万ものContainerのネットワーキングをどのように効率的に設定しているかについてお話しさせていただきます。 本日のアジェンダはとてもシンプルです。 まず、Containerについて話し、次にネットワーキングについて話し、最後にContainer Networkingについて話します。このようにシンプルです。
今日は主にContainer Networkingの課題とその解決方法について時間を費やしますが、その前に、なぜ私たちがこの問題を解決するために苦労しなければならないのか、皆さんに理解していただきたいと思います。そこで、まず第一の質問です:Containerとは、そもそもなぜ必要なのでしょうか?ちょっと手を挙げていただけますか。 食べ物を入れるためではなく、アプリケーションを実行するためにContainerを使用している方は何人いらっしゃいますか?
会場の大半の方が手を挙げてくださいましたね。素晴らしいです。手を挙げなかった方々も、Containerを使わなくても問題なくビジネスを運営されているはずです。実際、Netflixも長年ContainerなしでEC2だけを使って全てを運用していました。しかし、最終的にContainerへの移行を決断しました。その第一の理由は、移植性と一貫性です。ソフトウェアエンジニアとして、「あなたのアプリケーション、あなたのソフトウェアが本番環境で失敗した」と言われたとき、どう返答するでしょうか?きっと「自分の環境では動いているんですけど」と言うはずです。これまでにそう言ったことがないのであれば、あなたは正当なソフトウェアエンジニアとは言えませんね。でも、Containerを使えば、開発環境、テスト環境、本番環境で一貫して動作することが保証され、この「自分の環境では動く」問題を回避できます。
第二の理由は、デプロイメントの高速化です。ContainerはVMと違って、オペレーティングシステムの完全な初期化を待つ必要がないため、かなり素早くデプロイできます。これは特にCI/CDパイプラインで有用で、素早く検証して改善を重ねることができます。そして最後に重要なのが、コストと効率性です。ここ2、3年で「効率性」という言葉をよく耳にされたと思います。私たちは皆、アプリケーションやビジネスをAWS Cloudで運用していますが、AWSへの投資が価値あるものになるようにしたいですよね。全てのリソースが効率的に使用されていることを確認したいのです。
Titusによるコンテナ管理とネットワーキングの基礎
では、Containerはどのようにしてこれを実現するのでしょうか?例を見てみましょう。あなたがAWS Cloudでビジネスを運営していて、開発者たちがソフトウェアを開発しているとします。Aliceという開発者が素晴らしいソフトウェアを作り、AWS Cloudで実行したいと考えたとします。EC2で実行するとしましょう。このとき、Aliceが最初に答えなければならない質問は、ソフトウェアをデプロイするためにどのタイプのEC2マシンを使用すべきかということです。Aliceは「よく分からないな。汎用的なM5タイプを使おう。サイズについては、最小は避けたい。より多くのリソースが必要かもしれない。でも大きすぎるのも避けたい。お金がかかりすぎるから」と考えます。そこで4CPUを搭載したM5 Extra Largeの2番目に大きいサイズを選びます。「今のところ、これで十分だろう」と考えてアプリケーションをデプロイします。その後、Bobという別の開発者が同じような要件でやってきます。
Bobも同じ質問に答えなければなりません:どのタイプのEC2マシンを使うべきか。BobはAliceが詳しそうだと思い、彼女のやり方をそのままコピーすることにします。同じようにM5 Extra Largeの4CPUを使うことにしました。結局のところ、私たち開発者は皆、他人のコードを拝借することに罪悪感を持っているものです。そこでBobは同じ設定をコピーしてデプロイを行います。
CharlieやDaisyが来たときも、お互いのコードをコピーして同じようにデプロイします。ここで、彼らのアプリケーションはそれぞれCPU負荷が高くなく、実行時に1CPUしか使用しないと仮定してみましょう。この設定を見ると、4つのEC2インスタンスで合計16CPUに対して支払いをしていますが、CPU使用率はわずか25%です。ここで、デプロイを手伝ってくれる魔法のボックスがあると想像してください。同じ状況で、開発者のAliceがアプリケーションをデプロイしたいとやってきます。彼女はこの魔法のボックスにデプロイを依頼します。魔法のボックスは設定を確認し、現時点でEC2インスタンスが存在しないことを確認します。そこで魔法のボックスは4CPUを搭載したM5 Extra Largeを起動し、デプロイを実行します。
後に、別の開発者のBobが自分のソフトウェアを持ってきた時、このマジックボックスは現在のセットアップを確認し、すでにM5 Extra Largeが1台あることを見つけます。使用率はわずか25%で、もう1つのアプリケーションを実行できる余裕があります。そこで、Bobのアプリケーションを同じEC2マシンにデプロイすることになります。そして同様に、CharlieとDaisyも、EC2インスタンスタイプを気にすることなく、自分たちのソフトウェアをデプロイすることができます。この設定を見てみると、M5 Extra Largeインスタンスを2台しか使用していません。8個のCPUに対して支払いをしていますが、使用率は50%となり、コストが削減され、効率が向上しています。
では、このマジックボックスの正体は何でしょうか?それはコンテナ管理システムです。Netflixでは、これをTitusと呼んでいます。Titusは、AWSと緊密に統合されたマルチテナントコンテナオーケストレーションシステムです。私たちがサービスをコンテナに移行し始めた当時、Netflixの規模では、そのまま使える既製のソリューションが存在しませんでした。そこで、すべてを自社で構築することを決めました。このトークの目的上、TitusはアプリケーションをコンテナとしてEC2インスタンスにデプロイするのを助けてくれるマジックボックスだと理解していただければ十分です。
コンテナネットワーキングの設計要件と課題
コンテナには興味深い関係性があります。コンテナは分離を求めます。コンテナの重要な特徴の1つは分離であり、同じ物理空間を共有していても、互いに分離されていることを望みます。しかし同時に、互いにコミュニケーションを取る必要もあります。これは私と家族の関係に似ています - 私たちは分離を望みますが、コミュニケーションも必要です。そのため、コンテナにはネットワーク設定が必要になります。
コンテナネットワークの設計に入る前に、一般的なネットワークの仕組みと、データがネットワークを通じてどのように伝送されるかを復習しましょう。学校でネットワークの授業を受けたことがある方なら、パケットがレイヤーを通過することを覚えているかもしれません。ネットワークには理論的な7層のOSIモデルがありますが、今回はそれには触れません。代わりに、より実用的な5層のTCP/IPモデルについて説明します。アプリケーション層では、アプリケーション開発者が送信したいデータを持っており、このレイヤーではHTTPやHTTPSなどのプロトコルを扱います。データの準備ができたら、システムコールを実行し、次のトランスポート層に進みます。このレイヤーではTCPまたはUDPプロトコルを使用します。
TCPを例に説明します。TCPヘッダーがデータに追加され、上位レベルのアプリケーションを識別するためのポート番号が含まれます。これが次のレイヤーに送られ、フレームはインターネット層に到達します。このレイヤーでは、IPヘッダーが追加されます。IPヘッダーには、ネットワーク上のノードを識別するための送信元IPアドレスと宛先IPアドレスが含まれています。次に、IPパケットはネットワークインターフェース層と呼ばれる層に到達します。
Network Interfaceレイヤーでは、Ethernetプロトコルを扱い、ローカルエリアネットワーク上のノードを識別するためにMACアドレスまたはハードウェアアドレスを使用します。 最終的に、すべてのデータは物理ネットワーク上で0と1のビットとして配線を通り、宛先に転送されます。 そして宛先では、パケットは下から上へと全レイヤーを通過し、解凍されて宛先アプリケーションによって受信されます。
これらが、ネットワークを通じてデータを転送するために必要な5つのレイヤーすべてです。もちろん、AliceとBobのようなアプリケーション開発者はApplicationレイヤーを担当し、オペレーティングシステムがTransportレイヤーを処理します。例えば、Linuxのシステムコールを使用する場合、LinuxのTCPスタックがTCPプロトコルを処理してくれます。 EC2インスタンス上でアプリケーションを実行する場合、Amazon EC2サービスが次の2つのレイヤーの面倒を見てくれます。EC2マシンにElastic Network Interface(ENI)を接続し、IPアドレスを割り当てます。これら2つのレイヤーが設定されると、EC2インスタンスはネットワーク接続が可能になります。
しかし現在、私たちはEC2からContainerへの移行を進めています。今度はContainer管理システム、この場合はTitusが、レイヤー2とレイヤー3という2つのレイヤーを設定する役割を担います。まとめると、Titusの目的は各Containerに対してInternetレイヤーとNetwork Interfaceレイヤーを設定すること、言い換えれば、ENI(Elastic Network Interface)とIPアドレスを設定することです。これを念頭に置いて、Containerネットワーキングの設計を始めましょう。
ENI TrunkingとPrefix Delegationによるスケーラビリティの向上
実際の設計に入る前に、Netflixのコンテナネットワーキングの要件を理解する必要があります。要件その1:現在、私たちはアプリケーションをContainerに移行していますが、もしAliceのようなアプリケーション開発者に Container移行に関する要件を尋ねたら、きっと彼女はContainerやネットワーキングについては気にしないと答えるでしょう。アプリケーションやサービスがクラウドで動作する限り、どこに移行されるかは気にしないと。それは当然で、アプリケーション開発者はインフラストラクチャについて心配する必要はありません。それは私のようなインフラストラクチャエンジニアの仕事です。つまりAliceは最初の要件として、ContainerがEC2 VMのように動作することを望んでいます。なぜなら、それはすでにクラウドで動作しているからです。
開発者の体験に摩擦が生じることは避けたいのです。例えば、ContainerネットワーキングがEC2の上に一層の抽象化を加えるとしても、ネットワークブリッジング変換のような仕組みは避けたいと考えています。各ContainerにはEC2 VMと同じように1つのIPアドレスを持たせたいのです。開発者はそのIPアドレスを使ってSSH接続したり、EC2で実行していた時と同じように好きなことができます。現在EC2では、Security Groupsをセキュリティメカニズムとして使用し、すべてのEC2インスタンスの送受信トラフィックを制御しています。そのため、Containerでも同じメカニズムを使用したいと考えています。
2番目の要件はIsolationで、ContainerとEC2の場合、これは2つの側面があります。まず、Data Plane Isolationです。先ほど説明したように、すべてのContainerは同じEC2インスタンス上で実行され、物理リソースを共有していますが、互いに分離されている必要があります。お互いのネットワークトラフィックを見ることができないようにする必要があり、これはLinux Network Namespacesを使用することで簡単に実現できます。しかし、それだけではありません。Netflixにおけるもう一つのIsolation要件は、アカウントレベルのIsolationです。一部のチームには、ほとんどのユーザーが使用しているProduction accountとは別のアカウントでアプリケーションを実行する必要があり、それらの異なるアカウントでCompute resourcesとNetworking resourcesの両方を使用したいという要件があります。
最後に重要なのが、低遅延の要件です。Containerの特徴の一つとして高速なデプロイメントがありますが、Containerを起動する際のセットアップ時間を最小限に抑える必要があり、これにはネットワークのセットアップも含まれます。
私たちの環境では、非常に短命なContainerを見かけることがあります。Containerが起動してわずか5秒間だけ実行され、その5秒間にネットワークを使用して、その後終了するというケースもあります。そのような短命なContainerに対してもネットワークのセットアップが必要ですが、ネットワークの設定に5分もかかってしまうのは非現実的です。
さらに、時にはBursty workloadsに対応する必要があります。例えば、この会場にいる皆さんが突然プレゼンに飽きて、Netflixでお気に入りの番組を見始めたとしましょう。そうすると、私たちの環境でMicroburstが発生する可能性があります。皆さんのニーズに応えるために、数百のContainerを起動する必要があるかもしれません。そのようなBursty workloadsの場合でも、Containerのセットアップとネットワークのセットアップにかかる時間を適切な範囲内に収める必要があります。
これらの要件を念頭に置いて、システムの設計について詳しく見ていきましょう。目標は、各ContainerにNICまたはENI(Elastic Network Interface)とIPを設定することでした。いくつかの選択肢がありますが、まずはEC2ネットワークを見てみましょう。EC2ネットワークでは、Accountがあり、VPC(Virtual Private Cloud)があり、各VPCにはSubnetがあります。このSubnet内に、Container hostとして使用するEC2インスタンスがあります。先ほど説明したように、EC2サービスはこのインスタンスにENIをアタッチし、そこにIPアドレスを割り当てることになります。
ちなみに、ここではIPv6アドレスを使用していますが、このプレゼンテーションを通じて、すべての例でIPv6アドレスを使用します。これは、Netflixがこれまで何年もかけてIPv6への移行に取り組んでおり、現在も継続して取り組んでいるためです。IPv6には他にもメリットがありますが、それについては後ほどお話しします。
さて、セットアップに話を戻しましょう。EC2インスタンスにネットワーク接続ができるようになりましたが、コンテナにもネットワーク接続が必要です。これはどのように実現するのでしょうか?EC2インスタンスには複数のENIを接続することができます。セカンダリENIと呼ばれるものを接続し、これらのENIにSecurity Groupを設定することができます。コンテナ同士が同じSecurity Group設定を共有している限り、1つのセカンダリENIを共有することができます。各ENIには複数のIPアドレスを割り当てることができます。この例では、ENIに3つのIPアドレスを割り当て、それぞれを3つのコンテナに割り当てています。
これで目的は達成されました - コンテナはレイヤー2でENIを持ち、レイヤー3でIPアドレスを持つことができます。もし他のコンテナが異なるSecurity Group設定を使用している場合は、別のセカンダリENIが必要になり、そのENIにIPアドレスを割り当てて、それぞれのコンテナに割り当てることになります。これで一応機能しますが、まだ問題が残っています。最初の問題は、アカウント分離の要件に関連しています。セカンダリENIはEC2インスタンスと同じアカウントに属しているため、ネットワークはEC2ホストと同じアカウントに存在することになります。しかし、私たちの要件では、一部のアプリケーションは別のアカウントにネットワークを持つ必要があります。
もう1つの制限は、EC2インスタンスに接続できるENIの数がインスタンスタイプによって制限されていることです。例えば、Titusがコンテナホストとして使用しているm5.metalでは、最大15個のENI(1つのプライマリENIと14個のセカンダリENI)を接続できます。先ほど説明したように、コンテナは同じSecurity Group設定を共有している場合にのみENIを共有できます。極端な場合、すべてのコンテナが異なるSecurity Group設定を使用している場合、ENIを共有することができず、各コンテナに1つのENIが必要になります。この場合、1台のm5.metalで最大14個のコンテナしか起動できません。m5.metalには96個のCPUがあるため、当然もっと多くのコンテナを実行したいところです。これらの問題があるため、私たちはENI Trunkingという2つ目のオプションを検討しました。
考え方はシンプルです。以前と同じようにプライマリENIを持つセットアップですが、今回は複数のセカンダリENIを接続する代わりに、1つのセカンダリENIを接続し、これをTrunk ENIと呼びます。ここで実行されているコンテナにENIとIPアドレスを割り当てたいと考えています。今回は、このTrunk ENIにBranch ENIを関連付けます。これらのBranch ENIは独自のVPC、アカウント、サブネットに存在することができ、EC2インスタンスと同じサブネットである必要はありません。そして、これらのBranch ENIにIPアドレスを割り当て、それぞれのコンテナに割り当てていきます。
データプレーンでは、IPVlanというLinuxテクノロジーを使用しています。コンテナに向かうすべてのトラフィックは、まずTrunk ENIを通過し、そこでVLAN情報に基づいて、各Branch ENIが1つのVLANにマッピングされます。VLANとIPアドレスに基づいて、トラフィックをコンテナネットワークにルーティングできます。この解決策は有効で、Branch ENIは異なるアカウントに属することができるため、アカウント分離の要件も満たしています。
分離について言えば、もう1つの要件であるデータプレーンの分離についても覚えているでしょう。これはDockerのようなコンテナランタイムを使用する際に自然と得られる利点です。Linuxのネットワーク名前空間によってコンテナ間のネットワークが分離され、お互いのトラフィックを見ることができません。ただし、他のコンテナの存在を感じさせる可能性がある状況が1つあります。それがノイジーネイバーの問題です。すべてのトラフィックは最初にTrunk ENIを通過し、このTrunk ENIリソースを共有します。Trunk ENIには、EC2インスタンスタイプに基づく帯域幅制限があります。1つのコンテナが過度にネットワークを使用すると、Trunk ENIでパケットの遅延や損失を引き起こし、他のコンテナのネットワークトラフィックに影響を与える可能性があります。そのため、これらのノイジーなコンテナを制御する方法が必要になります。つまり、コンテナネットワークの帯域制限やトラフィックシェーピングのメカニズムが必要というわけです。
IPv6への移行とTitus Seccomp Agentの導入
Netflixでは、HTB(Hierarchical Token Bucket)というトラフィックシェーピングアルゴリズムを使用しています。これにはLinuxのTraffic Control(TC)を使用しています。仕組みはこうです:EC2インスタンス上にTrunk ENIがあり、このTrunk ENIにrootのqdiscを付加します。Qdiscはqueue discipline(キュー規律)の略で、パケットがキューから出ていく速度を制御し、結果としてスループットを制御できるトラフィック制御の用語です。このqdisc上にrootクラスを付加し、このクラスにはTrunk ENIの帯域幅制限と同じ帯域幅制限を設定します。コンテナを起動するたびに、リーフクラスを割り当て、各コンテナに帯域幅制限を設定します。すべてのリーフクラスの帯域幅制限の合計は、rootクラスまたはTrunk ENIの帯域幅制限を超えることはできません。
この設定を行い、トラフィックを正しく分類さえすれば、Linux TCがトラフィックのシェーピングと帯域制限を確実に行ってくれます。トラフィックの分類には、BPF Classifierを使用しています - 様々な分類子を使用できますが、ここではBPFを使用しています。パケットのIPアドレスに基づいて分類を行い、子クラスまたはリーフクラスをそれらのパケットに割り当てることで、TCがトラフィックシェーピングを行えるようにします。
最後に、レイテンシーの要件について、ネットワークセットアップのレイテンシーを削減するには、レイテンシーがどこから発生し、コンテナネットワークセットアップのボトルネックが何かを理解する必要があります。私たちの経験では、レイテンシーはAWS APIのレート制限に起因します。例えば、各ENIには、AssignPrivateIpAddressesというAPIを使用して複数のIPを割り当てることができ、これらのIPをBranch ENIに割り当てます。各コンテナはネットワーク上で1つのIPを必要とするため、コンテナごとにこのAPIを呼び出してIPを割り当てる必要があります。つまり、コンテナの起動レートはAPIの呼び出しレートと等しくなります。
Netflixでは、コンテナの起動レートがAWS APIのレート制限をはるかに超えています。 もちろん、AWSに制限の引き上げを依頼することはできます。AWSは非常に協力的で対応してくれますが、無制限に引き上げることはできません。このAPIは、AWSのネットワークを変更するため、本質的に制限があるのです。私たちは同じAWSクラウドを共有しているので、ネットワークを頻繁に変更されると、他のユーザーのネットワークに影響を与える可能性があります。Netflixは「うるさい隣人」になる可能性があるため、 AWSが活動を制限するように言うのは正当なことです。
「うるさい隣人」である私たちは、APIレート制限によってペナルティを受けないように、適切な振る舞いを考える必要があります。 APIを頻繁に呼び出しすぎているため、APIコストを削減する必要があり、そのためにいくつかの選択肢があります。一つの選択肢は、IPアドレスを事前に割り当ててキャッシュすることです。お気づきかもしれませんが、 私たちが呼び出しているAPIは「AssignPrivateIpAddresses」と呼ばれ、1回の呼び出しで複数のIPを割り当てることができます。 そこで、コンテナが起動するたびに1つのIPを要求するのではなく、4つのIPを要求するというアプローチを取ります。これにより、 APIコストをN分の4に削減できます。1つのIPを即座にコンテナに使用し、残りの3つを後続のコンテナ用にキャッシュするわけです。
しかし、このアプローチには問題があります。AWSのネットワーキングにはNetwork Address Unit(NAU)というものがあります。 このNAUは、VPCのサイズを表す合計値に寄与します。想像できると思いますが、VPCごとにNAUの制限があります。Netflixの規模では、常にこの制限に達しており、この制限に達することは、AWSのネットワーキングに影響を与え、他のユーザーにも影響を及ぼす可能性があります。さらに、 NAUの使用を最小限に抑えるために、IPを回収して再利用する必要があります。4つのIPを割り当てるたびに、各IPがこのVPCで1つのNAUに寄与するため、未使用のIPを回収したいのですが、これにより実装がより複雑になってしまいます。
Option 2として「リクエストバッチング」があります。このコンセプトもシンプルです:APIがレート制限されており頻繁に呼び出されているため、 そしてこのAPIはバッチAPIなので、数秒待って、リクエストをバッチにまとめて、1回のAPI呼び出しを行うことができます。1回のAPI呼び出しで要求できるIPの数に制限はありません。APIの呼び出し回数をN分のBに削減できます。ここでBはバッチサイズで、動的な値です。この解決策でもAPIレート制限の問題を解決できます。潜在的な問題として、この待機時間が固定されているため、トラフィックが少なくコンテナの起動が少ない場合でも、1つのコンテナはネットワークのセットアップのために数秒待つ必要があります。ただし、この時間は限定的なので、私たちのニーズとしては妥当です。このようにしてバーストリクエストを吸収し、APIコストを管理します。
複雑な解決策とシンプルな解決策がある場合、私たちは当然シンプルな方を選びました。 レイテンシーをさらに削減するため、NetflixとAWSは「Prefix delegation」という機能を共同で発明しました。これは2019年のre:Inventで共同発明したものです。これまでは特定のIPを割り当てる話をしてきましたが、 IPv6の場合、これはENIに/128のIPを割り当てることを意味します。Prefix delegationのアイデアは、それを行う代わりに、AWSにENIへ/80のプレフィックスを割り当てるよう依頼することです。このプレフィックスがENIに割り当てられると、 /80プレフィックス配下のすべてのIPv6アドレスを使用できます。そして、使用可能なIPの数はどれくらいでしょうか? 2^48個のIPv6 /128アドレスを、AWSと通信することなく自分たちで使用できます。これが、先ほど説明すると約束した、私たちがIPv6をそれほど切望していた理由です。IPv6を強く望んだのは、大幅なコスト削減が実現できるからです。理論的には、この/80を割り当てるためにAWS APIを1回呼び出すだけで、その後はAWSに再度連絡することなく、それだけ多くのコンテナを起動できるのです。
Prefix delegationはIPv4もサポートしていますが、IPv4の場合は/28プレフィックスなので、節約できるのは16個のIPアドレスに限られます。これはIPv6と比べるとそれほど大きな数字ではありません。なお、過去のre:Inventでも私たちのIPv6への移行についての講演がありましたので、この講演の後にぜひご覧ください。
Prefix delegationの力を最大限に活用するため、high-scale network modeと呼ばれる機能も用意しています。コンテナを起動する際に、high-scale network modeのチェックボックスにチェックを入れることができます。先ほどお話ししたように、コンテナ間でENIを共有できるのは、同じサブネットとセキュリティグループを共有している場合に限られます。そのため、high-scale network modeを有効にすることで、より多くの共有を可能にし、APIコールを減らすことができるのです。このモードではIPv6のみのアドレッシングとなり、Prefix delegationを使用したIPv6が利用できますが、IPv6のみというのは簡単なことではありません。
たとえあなたがIPv6のみの環境に移行する準備ができていても、依存関係にあるサービスの中にはまだ準備ができていないものもあるかもしれません。そのため、IPv4のみのエンドポイントと通信する必要が出てくるでしょう。そこで、IPv6とIPv4の間で変換を行うための移行メカニズムが必要になります。そのためのオプションがいくつかあり、その1つがAWS NAT64とDNS64を使用する方法です。その仕組みをご説明しましょう:IPv6のみのインスタンスコンテナがIPv4のみのエンドポイントと通信する必要がある場合、エンドポイントのDNS名を使用します。そして、このIPv4のみのエンドポイントのDNS名を解決するために、DNS resolverのRoute 53に問い合わせる必要があります。resolverはこのエンドポイントをIPv6アドレスに解決します。これは特別なIPv6アドレスで、既知のIPv6プレフィックスを持ち、その中にIPv4アドレスが埋め込まれています。そしてこれをインスタンスに返します。
インスタンスはこのIPv6アドレスを使用してIPv4のみのエンドポイントと通信します。ここでNAT gatewayが別の変換を行います。この特別な既知のIPv6プレフィックスを認識し、埋め込まれたIPv4アドレスに戻し、パケットを変更して、このIPv4パケットを宛先に送信します。戻りの通信も同様です。このようにして、IPv6のみのアプリケーションは、エンドポイントがIPv4であることを意識することなく通信することができます。
ここまでお話ししてきて、私が「オプション1」と言った時点で、何か問題があることにお気づきかもしれません。まず、DNS resolverが重要な役割を果たしています。特別なIPv6アドレスが必要なため、IPv4のみのエンドポイントとの通信はDNS名を通じてのみ可能で、IPアドレスを直接使用することはできません。もう1つの問題は、NAT gatewayがパケットごとに変換を行うため、トラフィックにパケットごとのオーバーヘッドが発生することです。スループットとレイテンシーのオーバーヘッドは無視できない程度になります。最後に、そして最も重要な点として、これにはコストがかかります - NAT64 DNS64の使用には料金が発生します。Netflixのスケールのトラフィックでは、この機能を使用するために数百万ドルを支払う必要があるでしょう。
そのため、社内で Titus Seccomp Agent(TSA)というものを開発しました。このトピックは以前の Linux Plumbers Conference でも発表されました。こちらが当時の発表内容へのQRコードです。発表後にぜひご確認ください。詳細には立ち入りませんが、基本的な考え方についてお話しします。IPv6専用のネットワークコンテナがあり、その中のアプリケーションがIPv4専用のエンドポイントと通信しようとする場合、まずソケットを作成します。Linuxカーネル内では、より複雑なアドレスを持つ内部表現にマッピングされる、シンプルなユーザースペースソケットが作成されます。
次に、このエンドポイントに接続するためにシステムコールまたはconnectシステムコールを呼び出す必要があります。このIPv6専用コンテナ内で何も対策を講じない場合、IPv4アドレスが存在しないため、この呼び出しは失敗してしまいます。IPv4専用アドレスとの通信ができないのです。そこで私たちは、Seccompを使用してこの呼び出しをインターセプトします。
また、コンテナのネットワーク名前空間とは別に、IPv4アドレスを持つIPv4専用のトランジションネットワーク名前空間も用意しています。このIPv4ネットワーク名前空間内で、別のソケット呼び出しを行い、新しいソケットを取得し、もう一度connect呼び出しを行います。ここにはIPv4アドレスが存在するため、この呼び出しは確実に成功します。そして、Seccompコマンドを使用して、元のソケットのLinux内部表現を、この新しいIPv4ソケットに置き換えます。元のソケットの内部表現が変更されたため、IPv6専用コンテナ内の元の呼び出し元に成功を返します。これにより、コンテナとIPv4専用トラフィックの間のすべての通信がコンテナに送信され、コンテナは透過的にIPv4専用エンドポイントと通信できるようになります。
IPManシステムの構築と今後の展望
要件と設計を振り返ってみましょう。最初の要件は、コンテナをVMのように実行することです。EC2と同様に、ネットワーク上で1つのIPを持つ必要があります。これを実現するために、ENI Trunkingを採用しています。また、アカウント分離とデータプレーン分離という分離要件もあります。アカウント分離要件はENIですでに満たされています。データプレーンについては、Noisy Neighbor問題を回避するためにHTBトラフィックシェーピングを採用しています。レイテンシー要件については、リクエストのバッチ処理とPrefix Delegationを使用したIPv6で対応しています。
すべてを組み合わせて、IPManというシステムを構築しました。IPManはIP Managementの略であり、ブルース・リーの師匠であるカンフーマスターの名前でもあります。つまり、非常にパワフルなシステムなのです。これがIPManのアーキテクチャです。AWS Cloudがあり、Titus Agent Clusterと呼ばれるものを管理しています。このクラスターには、コンテナホストとして機能するEC2インスタンスが含まれています。これらのEC2インスタンス上でコンテナを起動し、起動された各コンテナには、そのコンテナのネットワークセットアップを担当するTitus Executorが存在します。
各EC2ホスト上には、ネットワークのセットアップを担当するIPMan Agentが配置されており、このAgentはIPの割り当てのためにリモートのIPMan Serviceクラスターと通信を行います。 各Serviceクラスター内には、1つのリーダーサービスと2つのフォロワーが存在します。フォロワーはフェイルオーバー用であり、リクエストを受け取った際にはリーダーに転送します。 リーダーが障害を起こした場合、フォロワーの1つがリーダーとして選出され、その役割を引き継ぎます。リーダーは 状態をAmazon DynamoDBに保存し、ENIやIPの割り当てのためにAWS EC2 Serviceと通信を行います。
先ほど説明したように、この3つの緑色の矢印を減らすことで、レイテンシーを削減したいと考えています。 AgentからリモートServiceまでのコスト、そしてServiceからAWS EC2 Serviceまでのコストを削減したいのです。ここでIPv6アドレスの割り当ての例を見てみましょう。最初のタスクでは、 IPが必要なため、ローカルのIPMan Agentに問い合わせます。Agentはこの時点で状態を持っていないため、リモートのIPMan Serviceに対してIPを要求します。Serviceも状態を持っていないため、AWS EC2 Serviceに問い合わせて、 /80のIPv6アドレスを持つENIを探します。AWS EC2 ServiceはこのIPv6プレフィックスを持つENIを割り当て、 Serviceに返します。そしてServiceはこれをAgentに返します。
これでAgentは状態を持つことになります。 このENIがIPv6プレフィックスを持っており、このプレフィックス配下の任意のIPを使用できることを把握しています。そこで最初の利用可能なアドレスである ::1を見つけ、このタスクに割り当て、その状態をローカルに保存します。
その後の全てのタスクは、Agentとのみ通信を行います。Agentはすでに状態を持っているため、 使用すべきENIと/80プレフィックスを把握しています。::1はすでにタスク1で使用されているため、次に利用可能な::2を タスク2に割り当てます。このように、最初の1回だけ呼び出せば良く、2番目、3番目、4番目のタスクも同様です。つまり、呼び出しは1回で済むのです。
これは理論上素晴らしく見えますが、実際の本番環境ではどのように機能するのでしょうか?本番環境で収集しているメトリクスを見てみましょう。 あるタイミングでは、1秒あたり800個のコンテナを実行しており、この速度では1日あたり数百万のコンテナ を実行していることになります。IPv6のみのネットワークセットアップでは、 P99レイテンシーで100ミリ秒を達成できています。これはもちろんIPv6の場合で、プレフィックス委譲の恩恵を受けることができ、これが全体的なレイテンシー削減の主な要因となっています。IPv4のP99レイテンシーは4秒で、これはリクエストのバッチ処理と、まだ全てのユーザーがIPv6に移行していないため、時々 APIレート制限に引っかかることが原因です。
私たちは現在もIPv6への移行を進めています。実際、これをIPv6への移行を促すインセンティブとして活用しています。基本的に、まだIPv4を使用している場合、IPv6のみを使用している場合と比べてContainerの起動に時間がかかります。さて、これで私たちの仕事は終わりですよね?何百万ものContainerのネットワーク設定を効率的に行う方法を確立できました。これが今日のトピックでした。では、なぜ私はまだNetflixで仕事を続けているのでしょうか?次は何をするのでしょうか?簡単に言えば、私たちはさらなる効率化を目指しているのです。
最も重要なのは、先ほどから申し上げているIPv6への移行です。Prefix Delegationの恩恵をすべての人が受けられるようにしたいと考えています。IPv6への移行を継続することで、ネットワークのP99セットアップレイテンシー全体を削減することができます。また、先ほどHTBのTraffic Shapingについて話した際に触れなかったことですが、パケット毎秒のレートが高い場合、HTB実装における制御不能なグローバルロックが原因でパフォーマンスの問題が発生することがあります。そのため、より高性能なTraffic Shapingメカニズムも探しています。
もう一つ触れていなかったのは、データプレーンにおけるipvlanについてです。ipvlanを使用するためには、パケットはホスト上とContainerのネットワーク名前空間内の両方でLinuxネットワークTCPスタックを通過する必要があります。そのため、ContainerのネットワーキングはEC2ネットワークほど高性能にはなりません。極端に悪いわけではありませんが、やはり性能面で劣ります。そこで、このTCPスタックの余分な通過層を減らす他の方法を探りたいと考えています。Linuxカーネルにはnetkitという新機能があり、これがこの問題を解決する可能性があるため、現在この選択肢を検討しています。以上が本日のトピックでした。お話を楽しんでいただけたと思います。途中でNetflixの視聴に切り替えた方々も、番組を楽しんでいただけたと思います。ご参加ありがとうございました。質問がある方のために、しばらくここに残っていますので。ありがとうございました。
※ こちらの記事は Amazon Bedrock を利用することで全て自動で作成しています。
※ 生成AI記事によるインターネット汚染の懸念を踏まえ、本記事ではセッション動画を情報量をほぼ変化させずに文字と画像に変換することで、できるだけオリジナルコンテンツそのものの価値を維持しつつ、多言語でのAccessibilityやGooglabilityを高められればと考えています。
Discussion