Merbridge - eBPFでメッシュを高速化

iptablesルールをeBPFに置き換えることで、データをインバウンドソケットからアウトバウンドソケットに直接転送し、サイドカーとサービス間のデータパスを短縮できます。

2022年3月7日 | Kebe Liu - DaoCloud、Xiaopeng Han - DaoCloud、Hui Li - DaoCloud

Istioのトラフィック管理、セキュリティ、可観測性、ポリシーにおける能力の秘密は、すべてEnvoyプロキシにあります。IstioはEnvoyを「サイドカー」として使用してサービストラフィックをインターセプトし、カーネルのnetfilterパケットフィルター機能はiptablesによって設定されます。

iptablesを使用してこのインターセプトを実行するには欠点があります。netfilterはパケットをフィルタリングするための非常に汎用性の高いツールであるため、宛先ソケットに到達する前に、いくつかのルーティングルールとデータフィルタリングプロセスが適用されます。たとえば、ネットワーク層からトランスポート層まで、netfilterはpre_routingpost_routingなどの定義済みルールで複数回処理に使用されます。パケットがTCPパケットまたはUDPパケットになり、ユーザースペースに転送されると、パケット検証、プロトコルポリシー処理、宛先ソケット検索など、いくつかの追加手順が実行されます。サイドカーがトラフィックをインターセプトするように設定されている場合、重複した手順が複数回実行されるため、元のデータパスが非常に長くなる可能性があります。

過去2年間で、eBPFはトレンドのテクノロジーとなり、eBPFに基づく多くのプロジェクトがコミュニティにリリースされました。CiliumPixieなどのツールは、可観測性とネットワークパケット処理におけるeBPFの優れたユースケースを示しています。eBPFのsockopsおよびredir機能を使用すると、データパケットをインバウンドソケットからアウトバウンドソケットに直接転送することで、効率的に処理できます。Istioメッシュでは、eBPFを使用してiptablesルールを置き換え、データパスを短縮することでデータプレーンを高速化できます。

Merbridgeというオープンソースプロジェクトを作成しました。Istioで管理されているクラスターに次のコマンドを適用することで、eBPFを使用してこのようなネットワークの高速化を実現できます。

$ kubectl apply -f https://raw.githubusercontent.com/merbridge/merbridge/main/deploy/all-in-one.yaml

Merbridgeを使用すると、パケットデータパスをあるソケットから別の宛先ソケットに直接短縮できます。仕組みは次のとおりです。

eBPF sockopsを使用したパフォーマンスの最適化

ネットワーク接続は本質的にソケット通信です。eBPFは、アプリケーションがインバウンドソケットで送信したパケットをアウトバウンドソケットに直接転送する関数bpf_msg_redirect_hashを提供します。前述の関数に入ると、開発者はパケットの宛先を決定するためのロジックを実行できます。この特性により、パケットのデータパスをカーネル内で顕著に最適化できます。

sock_mapは、パケット転送の情報を記録する上で重要な部分です。パケットが到着すると、sock_mapから既存のソケットが選択され、パケットが転送されます。そのため、転送プロセスが適切に機能するように、すべてのパケットのソケット情報を保存する必要があります。新しいソケットの作成など、新しいソケット操作がある場合、sock_ops関数が実行されます。ソケットメタデータが取得され、パケットの処理時に使用されるsock_mapに保存されます。sock_mapの一般的なキータイプは、送信元と宛先のアドレスとポートの「4つ組」です。キーとマップに保存されているルールを使用すると、新しいパケットが到着したときに宛先ソケットが見つかります。

Merbridgeのアプローチ

実際のシナリオを使用して、Merbridgeの詳細な設計と実装の原則を段階的に紹介しましょう。

iptablesに基づくIstioサイドカーのトラフィックインターセプト

Istio Sidecar Traffic Interception Based on iptables
iptablesに基づくIstioサイドカーのトラフィックインターセプト

外部トラフィックがアプリケーションのポートに到達すると、iptablesのPREROUTINGルールによってインターセプトされ、サイドカーコンテナのポート15006に転送され、Envoyに処理が渡されます。これは、上記の図の赤いパスの手順1〜4として示されています。

Envoyは、Istioコントロールプレーンによって発行されたポリシーを使用してトラフィックを処理します。許可されている場合、トラフィックはアプリケーションコンテナの実際のコンテナポートに送信されます。

アプリケーションが他のサービスにアクセスしようとすると、iptablesのOUTPUTルールによってインターセプトされ、Envoyがリッスンしているサイドカーコンテナのポート15001に転送されます。これは赤いパスの手順9〜12であり、インバウンドトラフィックの処理に似ています。

アプリケーションポートへのトラフィックはサイドカーに転送され、次にサイドカーポートからコンテナポートに送信される必要があります。これはオーバーヘッドです。さらに、iptablesの汎用性により、さまざまなフィルタリングルールが適用されるため、データパス全体に遅延が必然的に追加されるため、パフォーマンスが常に理想的とは限りません。iptablesはパケットフィルタリングを行うための一般的な方法ですが、Envoyプロキシの場合、データパスが長くなると、カーネルでのパケットフィルタリングプロセスのボトルネックが増幅されます。

sockopsを使用してサイドカーのソケットをアプリケーションのソケットに直接接続すると、トラフィックはiptablesルールを通過する必要がなくなり、パフォーマンスが向上します。

アウトバウンドトラフィックの処理

上記のように、eBPFのsockopsを使用してiptablesをバイパスし、ネットワークリクエストを高速化します。同時に、Istioのどの部分も変更したくないため、Merbridgeはコミュニティバージョンに完全に適応します。そのため、eBPFでiptablesの動作をシミュレートする必要があります。

iptablesでのトラフィックリダイレクトは、そのDNAT機能を利用します。eBPFを使用してiptablesの機能をシミュレートしようとすると、主に2つのことを行う必要があります

  1. 接続が開始されたときに宛先アドレスを変更して、トラフィックを新しいインターフェイスに送信できるようにします。
  2. Envoyが元の宛先アドレスを識別して、トラフィックを識別できるようにします。

最初の部分については、eBPFのconnectプログラムを使用して、user_ipuser_portを変更することで処理できます。

2番目の部分については、カーネルのnetfilterモジュールに属するORIGINAL_DSTの概念を理解する必要があります。

アプリケーション(Envoyを含む)が接続を受信すると、get_sockopt関数を呼び出してORIGINAL_DSTを取得します。iptables DNATプロセスを経由する場合、iptablesはこのパラメータを「元のIP +ポート」値で現在のソケットに設定します。したがって、アプリケーションは接続に従って元の宛先アドレスを取得できます。

eBPFのget_sockopts関数を使用して、この呼び出しプロセスを変更する必要があります。(このパラメータは現在SO_ORIGINAL_DSTのoptnameをサポートしていないため、bpf_setsockoptはここでは使用されません)。

下の図を参照すると、アプリケーションがリクエストを開始すると、次の手順が実行されます。

  1. アプリケーションが接続を開始すると、connectプログラムは宛先アドレスを127.x.y.z:15001に変更し、cookie_original_dstを使用して元の宛先アドレスを保存します。
  2. sockopsプログラムでは、現在のソケット情報と4つ組がsock_pair_mapに保存されます。同時に、同じ4つ組とそれに対応する元の宛先アドレスがpair_original_destに書き込まれます。(get_sockoptプログラムでは取得できないため、Cookieはここでは使用されません)。
  3. Envoyが接続を受信すると、get_sockopt関数を呼び出して、現在の接続の宛先アドレスを読み取ります。get_sockoptは、4つ組の情報に基づいて、pair_original_destから元の宛先アドレスを抽出して返します。したがって、接続は完全に確立されます。
  4. データ転送ステップでは、redirプログラムは4つ組の情報に基づいてsock_pair_mapからソック情報をを読み取り、bpf_msg_redirect_hashを介して直接転送してリクエストを高速化します。
Processing Outbound Traffic
アウトバウンドトラフィックの処理

宛先アドレスを127.0.0.1ではなく127.x.y.zに設定するのはなぜですか?異なるポッドが存在する場合、競合する4つ組が存在する可能性があり、これは競合を適切に回避します。(ポッドのIPは異なり、いつでも競合状態にはなりません。)

インバウンドトラフィックの処理

インバウンドトラフィックの処理は基本的にアウトバウンドトラフィックと似ていますが、唯一の違いは、宛先のポートを15006に修正することです。

eBPFはiptablesのように特定の名前空間で有効にすることができないため、変更はグローバルになります。つまり、Istioによって元々管理されていないPod、または外部IPアドレスを使用すると、接続がまったく確立されないなど、深刻な問題が発生します。

そのため、ノード上のポッドを監視するkubeletと同様に、すべてのポッドを監視する小さなコントロールプレーン(DaemonSetとしてデプロイ)を設計し、サイドカーに挿入されたポッドIPアドレスをlocal_pod_ipsマップに書き込みます。

インバウンドトラフィックを処理するときに、宛先アドレスがマップにない場合、トラフィックに対して何も行いません。

それ以外の場合、手順はアウトバウンドトラフィックと同じです。

Processing Inbound Traffic
インバウンドトラフィックの処理

同一ノードの高速化

理論的には、同じノード上のEnvoyサイドカー間の高速化は、インバウンドトラフィック処理によって直接実現できます。ただし、このシナリオで現在のポッドのアプリケーションにアクセスすると、Envoyはエラーを発生させます。

Istioでは、Envoyは現在のポッドIPとポート番号を使用してアプリケーションにアクセスします。上記のシナリオでは、ポッドIPがlocal_pod_ipsマップにも存在し、インバウンドトラフィックの送信元と同じアドレスであるため、トラフィックがポート15006のポッドIPに再びリダイレクトされることがわかりました。同じインバウンドアドレスにリダイレクトすると、無限ループが発生します。

ここで疑問が生じます。eBPFで現在の名前空間のIPアドレスを取得する方法はありますか?答えはイエスです!

フィードバックメカニズムを設計しました。Envoyが接続を確立しようとすると、ポート15006にリダイレクトします。ただし、sockopsステップでは、送信元IPと宛先IPが同じかどうかを判断します。同じである場合、それは間違ったリクエストが送信されたことを意味し、sockopsプロセスでこの接続を破棄します。その間、現在のProcessIDIP情報がprocess_ipマップに書き込まれ、eBPFがプロセスとIP間の対応をサポートできるようにします。

次のリクエストが送信されると、同じプロセスを再度実行する必要はありません。宛先アドレスが現在のIPアドレスと同じかどうかをprocess_ipマップから直接確認します。

Same-node acceleration
同一ノードの高速化

接続関係

Merbridge を適用する前の Pod 間のデータパスは次のようになります。

iptables's data path
iptables のデータパス

Merbridge を適用した後、送信トラフィックは多くのフィルタステップをスキップし、パフォーマンスを向上させます。

eBPF's data path
eBPF のデータパス

2 つの Pod が同じマシン上にある場合、接続はさらに高速になります。

eBPF's data path on the same machine
同じマシン上での eBPF のデータパス

パフォーマンス結果

iptables の代わりに eBPF を使用した場合の全体的なレイテンシへの影響を見てみましょう(低いほど良い)。

Latency vs Client Connections Graph
レイテンシ vs クライアント接続グラフ

eBPF を使用した後の全体的な QPS も確認できます(高いほど良い)。テスト結果は `wrk` を使用して生成されています。

QPS vs Client Connections Graph
QPS vs クライアント接続グラフ

まとめ

この記事では、Merbridge の中核となるアイデアを紹介しました。iptables を eBPF に置き換えることで、メッシュシナリオでのデータ転送プロセスを高速化できます。同時に、Istio はまったく変更されません。つまり、eBPF を使用したくない場合は、DaemonSet を削除するだけで、データパスは問題なく従来の iptables ベースのルーティングに戻ります。

Merbridge は完全に独立したオープンソースプロジェクトです。まだ初期段階にあり、より多くのユーザーと開発者の参加を期待しています。この新しいテクノロジーを試してメッシュを高速化し、フィードバックを提供していただければ幸いです。

関連項目

この記事を共有する