ステータス: ドラフト(2026-10-05)、一部実測値を反映。 「実測:Istioサイドカーのコスト」、「サービス間mTLS:設定と実際の動作」、「実測:Serviceが1万個に増えたときのkube-proxyとCilium」の各節は、筆者の実験環境で作ったVM上で直接測定した値であり、該当節の「補強実測」(2026-10-09)は同一ホストのkindクラスターで測定した値です。残りの記述は公式ドキュメントで確認したものであり、実測値はまだありません。 記事末尾の「確認したバージョンと日付」および「確認できていないこと」を先にお読みください。 作成ルール:実測データが基本であり、測定できなかったものは「未実測」と表記します。
目次
- 1. なぜハイブリッドなのか
- 2. クラスター構成の選択肢
- 3. ノードの参加とネットワーク経路
- 4. Ciliumの役割
- 5. Istioの役割
- 実測:Istioサイドカーのコスト
- サービス間mTLS:設定と実際の動作
- 実測:Serviceが1万個に増えたときのkube-proxyとCilium
- 6. 併用時の注意点
- 7. LLMサービングで特に重要なポイント
- 8. サービスディスカバリー、障害ドメイン、セキュリティ
- よくある罠
- ハンズオン
- 面接で問われそうな質問
- よくある誤解
- 参考文献
- 外部の根拠
- 確認したバージョンと日付
ハイブリッドを選択する理由としては、オンプレミスのGPU単価やデータの配置場所、クラウドの調達性と弾力性がよく挙げられます。両者を併用する場合、クラスターの境界、ノードが参加する経路、サービスディスカバリー、障害範囲のすべてを自ら設計する必要があります。この記事では、選択肢と制約を公式ドキュメントに基づいて整理し、LLMサービングで特に問題となる長時間の接続やストリーミングについて個別に扱います。
この記事を読むことで面接で説明できるようになること
- クラスターを1つにまとめるか分割するかを、etcdの遅延、障害範囲、運用の負担の観点から比較し、EKS Hybrid Nodesがどこに該当するかを説明できること。
- Cilium(コンテナネットワークインターフェース(CNI)、ポリシー、Gateway API、ClusterMesh)とIstio(相互TLS(mTLS)、L7ルーティング)の役割の境界と、併用時に必要な設定を説明できること。
- ストリーミング形式のLLM応答が、プロキシ層のアイドルタイムアウト、HTTP/2マルチプレキシング、浅いヘルスチェックによってどのように途切れるかを事例を交えて説明できること。
1. なぜハイブリッドなのか
理由は3つあります。1つ目はコストです。常時負荷は保有するハードウェアに、ピーク時はクラウドに任せるというロジックがよく語られますが、この記事ではコストの数値については検証していません。2つ目はGPUの調達性であり、3つ目はデータの配置場所です。AWSのブログ(2025-03-19)では、Hybrid Nodesの用途として、データに近い場所での検索拡張生成(RAG)推論、ピーク時のクラウド活用、エッジ推論を挙げています。
2. クラスター構成の選択肢
| 選択肢 | メリット | コスト(代償) |
|---|---|---|
| 環境ごとにクラスターを分割し、メッシュやMulti-Cluster Services(MCS)で接続する | 障害とセキュリティの境界が分離されます | サービスディスカバリー、ポリシー、デプロイを環境ごとに重複して管理することになります |
| クラウドが管理するコントロールプレーンにオンプレミスノードを参加させる(EKS Hybrid Nodes) | コントロールプレーンの運用をAWSに委ねることができます | 接続が切断されると、コントロールプレーンとの接続が絶たれます |
| 1つのコントロールプレーンをWANに拡張する | 単一のクラスターになります | etcdの合意形成が遅延に敏感です |
etcdのドキュメントでは、ハートビート間隔を構成メンバー間の往復時間(RTT)の0.5〜1.5倍に、選挙タイムアウトをRTTの10倍以上に設定するよう案内しています。デフォルト値はそれぞれ100ms、1000msです。Kubernetesのドキュメントでは、可用性が重要な場合はコントロールプレーンの構成要素を少なくとも3つの障害ドメインに複製するよう求めています。
EKS Hybrid Nodesのドキュメントにおける主要な条件は次のとおりです。
- AWSがコントロールプレーンを、ユーザーがオンプレミスノードを管理し、1時間あたりのvCPU単位で課金されます。
- 接続が安定している必要があり、切断・中断・間欠的・制限のある環境(DDIL)には適していません。
- クラウドインフラ(他のクラウドを含む)上でハイブリッドノードを実行することはサポート対象外です。
- ノードおよびPodのCIDRを
RemoteNodeNetworkとRemotePodNetworkで渡し、それぞれ最大15個であり、RFC 1918またはキャリアグレードNAT(CGNAT)の帯域である必要があります。 - 接続は最低100Mbps、RTT 200ms以下が推奨されますが、厳格な要件ではありません。
- 混成クラスターでは、クラウドノードにVPC CNI、ハイブリッドノードにCiliumを使用し、Webhookはクラウドノード上で実行します。両側のPod間で直接通信を行うには、オンプレミスのPod CIDRがルーティング可能である必要があります。
GCPについては、GKE Multi-cluster Services(ServiceExportによるサービス共有)とマルチクラスターGateway(設定専用の1つのクラスターがGateway APIリソースを管理)のみを確認しています。
3. ノードの参加とネットワーク経路
flowchart LR
subgraph ONP["온프레미스"]
G1["GPU 노드 A"] --- R["라우터·방화벽"]
G2["GPU 노드 B"] --- R
end
R -- "Site-to-Site VPN 또는 Direct Connect" --> TGW["Transit Gateway 또는 가상 사설 게이트웨이"]
TGW --> VPC["VPC 서브넷"]
VPC --> CP["EKS 제어 평면"]
VPC --> CN["클라우드 노드"]
AWS Site-to-Site VPNは、1つのトンネルあたり最大1.25Gbpsであり、帯域幅最適化型は5Gbpsです。Transit Gatewayで動的ルーティングを使用する場合は、等コストマルチパス(ECMP)によって集約されます。MTUは1446バイトであり、経路MTU探索(PMTUD)をサポートしていません。オーバーレイカプセル化によりPodのMTUがさらに小さくなるため、実測によってフラグメンテーションを確認する必要があります。Direct Connectは専用線であり、MACsecは一部の専用接続においてオプション機能です。モデルウェイトのような大きなファイル(説明用の例:140GBを1Gbpsでダウンロードする場合、理論上約19分)をWAN経由で毎回取得するのではなく、オンプレミスのレジストリやキャッシュに配置することが推奨されます。
Ciliumがノード間に要求するポートは、VXLANのUDP 8472、WireGuardのUDP 51871、ヘルスチェック用のTCP 4240です。ハイブリッド環境では、これらのポートが中間ファイアウォールによってブロックされることがよくあります(後述の「よくある罠」を参照)。
4. Ciliumの役割
- kube-proxyの代替:サービスの負荷分散をeBPF(Extended Berkeley Packet Filter)で処理し、クラスター内部の接続はソケットが
connect()の時点でバックエンドに割り当てられます。接続が長期化する場合、バックエンドは一度決定された後変更されません。 - ネットワークポリシー:IPアドレスではなくアイデンティティに基づいて判定します。
- Gateway API:Gateway API v1.6.1をサポートしており、
kubeProxyReplacement=trueとL7プロキシ(l7Proxy)が前提となります。 - ClusterMesh:クラスター間でのPodの直接通信(平面IP)、グローバルサービス、ポリシーを提供します。条件は、すべてのクラスターでデータパスモードが同一であること、Ciliumのマイナーバージョンの差が1以下であること、Pod CIDRが重複していないこと、ノード間のIP接続性があることです。デフォルトで最大255個のクラスターに対応しており、
maxConnectedClustersにより511まで拡張できますが、インストール時にのみ指定可能です。接続されたクラスターは単一の信頼ドメインとなるため、セキュリティレベルが同じクラスター同士のみを接続するようドキュメントで警告されています。ポリシーは自動配布されず、1.19以降ではポリシーはデフォルトでローカルクラスターのエンドポイントのみを選択します。 - サービスディスカバリー:サービスアノテーション方式のグローバルサービスと標準のMCS-API(
ServiceExport,ServiceImport,clusterset.local)の両方をサポートしています。service.cilium.io/affinity: localである場合、正常なローカルバックエンドが存在する限り、リモートへ送信することはありません。 - 相互認証(mTLS、SPIFFE/SPIREベース)はドキュメント上ではベータ版です。
5. Istioの役割
Istioは、サービス間のmTLSアイデンティティ(SPIFFEベースの証明書)とL7ルーティング(VirtualServiceの weightによるトラフィック分割、リトライ、タイムアウト)を担当します。
| 項目 | サイドカーモード | アンビエントモード(1.24からGA) |
|---|---|---|
| データプレーン | PodごとにEnvoy | ノードごとにztunnel(L4)、必要に応じてwaypoint(L7 Envoy) |
| mTLS | サイドカー間 | ztunnel間のHBONEトンネル(ポート15008) |
| L7機能 | 標準包含 | waypointデプロイ時のみ |
マルチクラスター・アンビエントはマルチネットワーク構成がベータ版であり、ドキュメントに記載されている少ない制約は次のとおりです。既存のサイドカーの本番環境には推奨されず、単一ネットワークは未検証であり、複数のプライマリクラスターのみをサポートし、すべてのクラスターでwaypointの名前が同一である必要があり、設定を手動で同期する必要があります。1.31(2026-08-31)では、ゾーンアウェアな負荷分散(zoneAwareLbSetting)とwaypointのカナリア配信が追加されました。
実測:Istioサイドカーのコスト
ドキュメントに記載されている「サイドカーは遅延を追加する」という内容を数値で確認しました。
環境: KubeVirt VM 1台(8 vCPU、7.9GiB、ゲストカーネル6.8.0-139)、k3s v1.35.8、Istio 1.31.0(profile=default、ネイティブサイドカー)、負荷テストツール fortio 1.75.3。同一VM内でクライアントPodとサーバーPodを起動し、 plain(インジェクションなし)と mesh(両側サイドカー、mTLS)を交互に測定しました。条件は16接続、固定500 QPSで25秒間を5回、および飽和状態での20秒間を3回です。
| 指標 | メッシュなし | サイドカー mTLS | 差分 |
|---|---|---|---|
| 平均遅延(500 QPS、中央値) | 0.241 ms | 2.450 ms | +2.2 ms(約10倍) |
| p50 / p90 / p99 | 0.232 / 0.349 / 0.538 ms | 2.474 / 3.068 / 3.726 ms | +2.2 / +2.7 / +3.2 ms |
| 飽和スループット | 217,476 QPS | 16,246 QPS | メッシュなしの7.5% |
5回の測定間でのばらつきは平均遅延を基準に±3%以内であり、失敗した呼び出しはありませんでした。
この数値が示す意味: 1回のリクエストが2つのサイドカー(クライアント側とサーバー側)を通過することにより、この環境では約2.2msが追加されました。LLMサービングにおいて最初のトークンまでに数百msを要するリクエストであればこのコストは小さく済みますが、トークンを1つずつストリーミングする応答や、社内マイクロサービスのよう遅延が1ms前後の呼び出しにおいては大きく感じられます。飽和スループットが7.5%に低下したのは、同じCPUをプロキシと共有するためであるため、サイドカーには 遅延だけでなくCPUリソースを個別に割り当てる必要がある ことを意味しています。
この数値からは読み取れないこと:
- この表の比較対象は「プロキシなし」対「2つのプロキシとmTLS」です。暗号化とプロキシホップを切り分けた結果は、後述の「サービス間mTLS」節に記載されています。
- 同一ノード内の通信であるため、実際のNICやノード間遅延は含まれていません。追加される増分としてのみ読み取ってください。
- 上記の表は通常のHTTP/1.1(本文が小さい場合)です。gRPC、大きなペイロード、長時間のストリーミングについては、後述の「サービス間mTLS」節の末尾にある別テストを参照してください。
- アンビエントモード(ztunnel)はテストしていません。「サイドカーはコストがかかるためアンビエントの方が優れている」という主張はこの実測値によって裏付けられるものではありません。
測定から得られた教訓: 最初の計測は fortio のデフォルトのヒストグラム解像度(1ms)のせいで不正確でした。遅延が 1ms 未満の場合、パーセンタイルは実際の値ではなくバケット内の位置(0.5、0.9、0.99ms)として記録されます。同じ実行における平均遅延とリトルの法則(同時接続数 / スループット)が食い違っているのを見てそれに気づきました。ベンチマーク結果を信じる前に ツール自体を疑って二つの指標をクロスチェックしなければならないという事例であり、第 5 部で改めて取り上げます。
サービス間の mTLS: 設定と実際の動作
「ソリューションとソリューションの間に mTLS をどう挟むか」を Istio 1.31.0 で実際に適用して確認した結果です。
まず、2 つのレイヤーを区別する必要があります。 Cilium WireGuard はノード間を行き来するポッドトラフィックをノード単位のキーで暗号化しますが(デフォルトでは Cilium が管理するポッド間のトラフィックのみであり、ノードとホストのトラフィックはベータオプションを有効にする必要があり、同じノード内のトラフィックは暗号化しません)ワークロードの身元は確認しません。 Istio mTLS はサービスアカウントから発行された証明書(SPIFFE ID)で 双方が互いを認証し、その身元を基準にアクセスを許可または拒否できます。サービス間認証が目的なら Istio のほうが適切なツールです。
設定は 3 つの部分に分かれます。 以下の YAML はすべてテスト VM に適用して動作を確認しました。 API バージョンは security.istio.io/v1 です。
- ネームスペースに
istio-injection=enabledラベルを付与し、すでに起動しているポッドはサイドカをアタッチするために再起動する必要があります。 - サーバー側で mTLS 以外の接続を受け入れるかどうかを
PeerAuthenticationで指定します。ポリシーがない場合のデフォルトは PERMISSIVE であるため、まずは観測した後に STRICT に引き上げる順序が安全です。 - mTLS が成立してこそ身元を信頼できるため、その上で
AuthorizationPolicyにより呼び出し可能なサービスアカウントを制限します。
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata: {name: default, namespace: <네임스페이스>}
spec: {mtls: {mode: STRICT}}
---
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata: {name: only-good, namespace: <네임스페이스>}
spec:
selector: {matchLabels: {app: <서버 앱>}}
action: ALLOW
rules:
- from: [{source: {principals: ["cluster.local/ns/<네임스페이스>/sa/good"]}}]
呼び出し側の DestinationRule(tls.mode: ISTIO_MUTUAL)は、ドキュメントを基準にするとメッシュ間では自動 mTLS が適用されるため必須ではありません。実験では暗号化の有効・無効を確実に切り替えるために明示しました。
実際の動作。 クライアントは一時的な curl ポッドであり、結果はステータスコードと curl の終了コードです。
| 状況 | 結果 |
|---|---|
| ポリシーなし(PERMISSIVE)、メッシュ外の平文クライアント | 200 |
| STRICT、メッシュ外の平文クライアント | 000 exit=56 (接続が切断される) |
| STRICT、メッシュ内のクライアント | 200 |
| AuthorizationPolicy、許可されたサービスアカウント | 200 |
同じポリシー、許可されていないアカウントと default アカウント |
403 |
運用において重要な点が 2 つあります。第一に、 STRICT が拒否する方式は HTTP エラーではなく接続の切断です。サーバーログで 403 を探しても何も見つからず、クライアントには Connection reset や curl: (56) としてのみ現れます。障害が発生した際の一番の疑いは「STRICT なのに相手がメッシュの外にいるのではないか」であるべきです。第二に、 認可の拒否は 403 です。 mTLS 接続は成立し、身元が許可リストに載っていないだけであるため、2 つの症状が互いに異なり原因を区別できます。
暗号化とプロキシホップのコスト分離。 2 つのサイドカーはそのままで mTLS のみを有効・無効にして比較したものです(同じ VM、平均遅延は 500 QPS 5 回の中央値、飽和スループットは 3 回の中央値)。
| 構成 | 平均遅延 | 飽和スループット |
|---|---|---|
| メッシュなし | 0.241 ms | 217,476 QPS |
| サイドカー、mTLS 無効 | 2.252 ms | 18,339 QPS |
| サイドカー、mTLS 有効 | 2.437 ms | 16,511 QPS |
上記で増加した約 2.2ms のうち、 プロキシの 2 ホップが約 2.0ms(約 92%)、暗号化が約 0.19ms(約 8%) です。 「mTLS は遅い」という通念とは異なり、この環境ではコストの大半は暗号化ではなくプロキシによる HTTP 処理でした。ただし、この VM の CPU は AES ハードウェアアクセラレーション(aes, vaes)を露出しているため、アクセラレーションのない CPU や大きなボディ、長時間のストリーミングでも同じ比率であるとは言えません(未実測)。
HTTP 解析とプロキシホップを切り分けるための追加テスト。 同じサーバーを http- という名前のポート(L7 解析)と tcp- という名前のポート(L4 バイト中継)で公開し、サイドカーを比較しました(同じ VM、500 QPS 20 秒 5 回クロス、平均遅延の中央値)。
| 構成 | 平均遅延 | メッシュなしとの比較 |
|---|---|---|
| メッシュなし | 0.238 ms | 基準 |
サイドカー、L4(tcp-) |
1.585 ms | +1.35 ms |
サイドカー、L7(http-) |
2.459 ms | +2.22 ms |
加算された 2.2ms のうち HTTP の解析が占める割合は約 0.87ms(約 39%)であり、解析を行わない L4 としても、2つのサイドカを経由するだけで +1.35ms(約 61%)が残りました。したがって、「プロキシ2ホップが約 92%」は暗号化ではなくコストの合計であり、その中で HTTP 解析単体の割合は 40% 前後です。外部の研究(「Dissecting Service Mesh Overheads」)はプロトコル解析がプロキシコストの 60〜70% を占めると報告していますが、この環境における L4 と L7 の差で見るとそれより小さくなりました(条件が異なるため断定はしません)。
gRPC、大きなペイロード、長いストリーミング、平文の例外ポート。
| テスト | メッシュなし | サイドカー(mTLS) |
|---|---|---|
| gRPC 単一呼び出しの平均レイテンシ(500 QPS 3回) | 0.397 ms(0.394〜0.408) | 3.834 ms(3.054〜3.961)、約 +3.4ms |
| 100MiB のレスポンスを1回ダウンロード(3回) | 5,363〜8,107 MB/s、0.01〜0.02秒 | 1,517〜1,895 MB/s、0.06〜0.07秒 |
| SSE イベント 200個(サーバーが 20ms ごとに送信、3回) | 間隔の中央値 20.1ms、p99 20.3ms | 間隔の中央値 20.1ms、p99 20.4ms(最大 22.0ms) |
| STRICT におけるメッシュ外の平文クライアント | 000 exit=56(接続断) |
|
STRICT に portLevelMtls を設定しポート 8080 のみ PERMISSIVE に |
200 |
- gRPC は同一環境の HTTP/1.1(+2.2ms)よりも増加値が大きくなりました(+3.4ms)。1つの呼び出しが HTTP/2 のストリーム1つであるため解析コストがかかっていると推測されますが、分解はしていません。
- 大きなペイロードではスループットが約 3.5〜4.5倍低下しました。 同じノード内であるため平文側はメモリコピーの速度に近いです。LLM の重みや大きなレスポンスをサイドカー経由で通過させるとこのコストが発生するため、除外対象を定める必要があります。
- トークンのストリーミング間隔には影響がありませんでした。 20ms 間隔のイベントがサイドカーを通過しても中央値は変わらず、p99 が 0.1ms 増加しました。つまり、サイドカーの遅延はイベントごとに蓄積されるものではなく、リクエスト経由で固定的に加算される値として読み取れます(この間隔とイベントサイズにおいて)。
- 例外ポートは
portLevelMtlsで開放できました。 STRICT を維持したまま1つのポートのみを PERMISSIVE にすると、メッシュ外の平文クライアントが200を受信しました。 - 制限: すべて同一 VM 内の通信です。大きなペイロードの比率は、実際の NIC や CPU が異なれば変化します。
実際のサービスズクラスタに適用する際の注意点は、まだテストしていない推論段階のものです(未実測)。 メッシュ外から流入する平文トラフィック(例: Cilium Gateway)が STRICT サービスに到達すると上記の接続断が発生するため、該当ポートのみを portLevelMtls で PERMISSIVE に設定する方法を使用します(上記テーブルで動作を確認しました)。データベース、DaemonSet、ジョブは最後に切り替えを行い、ジョブについてはサイドカーが終了しない問題を別途確認する必要があります。
実測: サービスが1万個に増加したときの kube-proxy と Cilium
「Cilium が kube-proxy を置き換えればサービスが増えても高速である」という話を数値で確認しました。
環境。 KubeVirt VM 2台(それぞれ 8 vCPU、7.9GiB、ゲストカーネル 6.8.0-139、k3s v1.36.5)。1つは k3s 内蔵の kube-proxy(iptables、nf_tables バックエンド)と flannel、もう1つは Cilium 1.20.1(KubeProxyReplacement=True、VXLAN)です。 2つの VM は互いに異なる物理ホスト上で稼働しました。 サービスを 0、100、1,000、5,000、10,000個へと増やしながら測定しました。
| 測定 | kube-proxy(iptables) | Cilium(eBPF) |
|---|---|---|
| サービスが 10,000個のときのデータプレーン項目 | NAT ルール 110,296行(1サービスあたり約 11行) | BPF LB エントリ 40,104個(1サービスあたり約 4個) |
| 新規 Service が ClusterIP で接続可能になるまでの時間、サービス 0個 | 中央値 約 0.96秒 | 未再測定(同一ホストの kind で中央値 0.24秒、下記で補足) |
| 同遅延、サービス 10,000個 | 中央値 約 2.3秒 | 中央値 0.31秒 |
| 新規接続の平均遅延の増加(0個から 10,000個へ) | 約 +0.05ms(約 15%) | ほとんどなし |
読み方。
- ルール数は両者ともにサービス数に対して線形に増加しました。 異なる点は、新しいサービスがデータプレーンに反映されるまでの時間です。kube-proxy 側はサービスがない場合でも1秒近くかかり(同期の最小間隔が1秒であるというドキュメントの内容と一致しますが、この VM で設定を確認することはできませんでした)、1万個では約 2.3秒に増加しました。Cilium はサービス数に関わらず 0.3秒前後でした。
- 大量作成直後にはさらに大きな差が開きました。 1万個を作成した直後、kube-proxy 側では最初の新規サービスが接続可能になるまでに 45秒かかり(2回中1回のみ発生しました)、Cilium 側は 0.25秒でした。多数のサービスを一度にデプロイする状況において重要な違いです。
- 新規接続の遅延は予想よりもわずかな変化にとどまりました。 ルールが 11万行になっても平均遅延は 0.05ms 程度しか増加しませんでした。「iptables はサービスが多いとデータパスが遅くなる」という通説は、この環境(同一ノード内、接続数 8)では顕著には確認されませんでした。実際の影響は反映遅延のほうが大きかったです。
この数値が示さないこと。
- ホストが異なるため、2つの VM の遅延の絶対値は比較できず、各 VM 内での増加幅のみに意味があります。
- この VM 実験ではノード間通信とサービスごとのバックエンド数の増加は測定していません。同一ホストの kind ノード間で測定した値は下記の補足にあり、実際の物理ノード間については依然として未実測です。接続数が多い場合の conntrack については以下の別のテストに記載しています。
- 測定したサービスが
KUBE-SERVICESチェーン内のどの位置にあるかは確認していません。外部の測定(Huawei 2017年発表、下記の外部根拠)では、最初のパケットの遅延がチェーン内の位置によって大きく変動する可能性があると報告されているため、新規接続の遅延の変化が小さかったという結果はサービスの配置によって変わる可能性があります。 - この VM 実験における新規接続の遅延は、各レベルで1回のみ測定しました(補足の実測では条件ごとに 6,000サンプル)。
- VM 実験では iptables モードのみを比較しました。
ipvs·nftablesモードは下記の補足において同一ホストの kind で測定しており、サービス 1万個の条件についてはこれら2つのモードで測定していません(未実測)。
conntrack テーブルが一杯になった場合。 使い捨て VM における nf_conntrack_max を下げ、ループバックで同時接続4,000件を一度に開いてみました(接続タイムアウト5秒、テスト前に既存のエントリが500件未満に減るのを待機)。
| 上限 | 開始前項目 | 成功 | 失敗(5秒以内に確立できず) | 備考 |
|---|---|---|---|---|
| 262,144(デフォルト) | 223 | 4,000 | 0 | p99 14.5ms |
| 2,048 | 140 | 1,913 | 2,087 | 最大3,068ms(SYN再送により遅れて成功した接続) |
| 1,024 | 59 | 969 | 3,031 |
- 成功した接続数が「上限から既存項目を引いた値」とほぼ同じでした(2,048-140=1,908対1,913、1,024-59=965対969)。上限を超える新しい接続は拒否の応答なしに パケットが静かに破棄され、クライアントは再送後にタイムアウトまで待機しました。
- カーネルログには
nf_conntrack: table full, dropping packetが残りましたが、11〜12行のみでした。ログがレートリミットされるため、この行数をドロップ回数として読んではいけません。 - デフォルトの上限(このVMは262,144)では、同様の負荷が問題なく通過しました。つまり、危険性はノードメモリに比例して決まる上限と同時接続数の積から生じ、上限を超えた瞬間に一部の接続のみが無作為に失敗する形で現れます。上限をモニタリング(
nf_conntrack_count対nf_conntrack_max)する理由がこれです。 - 限界:同一ノードのループバック接続であり、接続がすべて開いている瞬間のテストです。実サービスの短い接続頻度やタイムアウト分布における失敗率は測定していません(未計測)。
補足実測:同一ホストのkindにおける4つのデータプレーン。 上のVM実験で残った空白を、Mac(Apple M1 Max)のDocker Desktop内kindクラスター(Kubernetes v1.36.1、コントロールプレーン1+ワーカー1)で埋めました。kube-proxyのiptables・ipvs・nftablesモードと、Cilium 1.20.1(kube-proxy代替、VXLAN)をそれぞれ新しいクラスターで同じスクリプトにより測定しました。ここで「ノード間」とは 同一ホスト内kindノードコンテナ間であり、物理NICや回線遅延は含まれません。新しい接続の遅延は、新しいTCP接続→HTTPリクエスト→レスポンス1回を条件ごとに6,000回測定した中央値です。クライアントがPythonであるため、絶対値よりも条件間の差を見る必要があります。
| 測定 | iptables | ipvs | nftables | Cilium |
|---|---|---|---|---|
| 新しい接続、同一ノードバックエンド(中央値 / p99) | 0.190 / 0.440ms | 0.202 / 0.477ms | 0.186 / 0.800ms | 0.165 / 0.312ms |
| 新しい接続、異なるノードバックエンド | 0.213 / 0.449ms | 0.237 / 0.693ms | 0.210 / 0.585ms | 0.227 / 0.426ms |
| バックエンド10個→1,000個のときの新しい接続の中央値 | 0.281 → 0.307ms | 0.269 → 0.292ms | 0.290 → 0.350ms | 0.317 → 0.300ms |
| バックエンド1,000個のときのデータプレーン項目 | NAT規則 4,073行 | 未集計 | 未集計 | BPF LB項目 1,016行 |
| サービス0個から新しいService接続まで(10回の中央値) | 1.04秒 | 0.12秒 | 1.03秒 | 0.24秒 |
| エンドポイント変更の反映(バックエンド1個、2秒待って変更、5回) | 7–9ms | 31–36ms | 6–8ms | 初回82ms、残り474–477ms |
| その直後に戻した変更の反映(5回) | 1.02–1.03秒 | 60–83ms | 1.01–1.02秒 | 0.50–0.51秒 |
- データパスのコストはいずれの4つでも小さかったです。 同一ノードにおけるClusterIP経由とPod IP直接接続の中央値の差は、kube-proxyの3つのモードで±0.02ms以内、Ciliumで+0.005msでした。異なるノードへ移動すると、kube-proxy側が0.02〜0.04ms、Ciliumが0.06ms増加しました。Cilium側にはVXLANカプセル化が含まれており、CNIの違いが混ざり合った値となっています。
- バックエンド数を10個から1,000個に増やしても、新しい接続の中央値の増加は0.06ms未満でした(iptables +0.026ms、ipvs +0.023ms、nftables +0.060ms、Cilium変化なし)。iptablesはサービスチェーン内で確率規則を順に下っていきますが、1,000個の規模ではVM実験のサービス1万個のときと同様にデータパスの差は小さかったです。バックエンドはPod 5個×ポート200個で作成したIP:ポート1,000個です。
- 差が出たのは反映時間でした。 iptables・nftablesモードは2秒間隔を空けた変更を数msで反映しましたが、反映直後に続いた変更は約1.02秒待ちました。新しいServiceを作成すると、ServiceとEndpointSliceが順に変わるためサービスが0個であっても1.03〜1.04秒かかり、VM実験の0.96秒と同じ現象です。kindのデフォルト設定で最小同期インターバルが0秒であるipvsモードは0.12秒でした。Ciliumは0.24秒であり、エンドポイントの変更は初回を除けば休憩時間に関係なく約0.47秒(復元時は0.50秒)で一定でした。エージェントが変更を一定間隔でまとめて反映しているように見えますが、その設定値は確認できませんでした。
- 項目数は、iptablesがエンドポイントあたりNAT規則約4行、CiliumがBPF LB項目1行と、どちらも線形でした。 ipvs・nftablesの項目数は、カウントコマンドの誤りにより今回集計できませんでした。
- 限界:同一ホストの仮想ブリッジ上の2ノードであり、同じDockerを他の作業を行うクラスターと共用しました。VMの負荷が大きい区間の実行は破棄して再測定しました。実際の物理ノード間の値や、サービス数とエンドポイント数を同時に増やした条件は測定していません(未計測)。
生データとスクリプト: docs/experiments/2026-10-09-dataplane-scale/.
測定から得た教訓。 初回の測定では、接続試行の制限(0.2秒)によりCilium側の値が206msと411msの2つに固定されてしまいました。同じ値が繰り返し表示されるテーブルは、ツールの解像度を疑う必要があります。試行制限を20msに縮小して再測定しました。
6. 併用時の注意点
Ciliumのドキュメントを基準に、kube-proxyをCiliumに置き換えた上でIstioを使用するには、 socketLB.hostNamespaceOnly=trueと cni.exclusive=falseが必要となります。kube-proxyを残す構成が最小限の変更になると推奨されています。L7 HTTPポリシーはCiliumかIstioのどちらか一方のみを使用するように言われています。アンビエントmTLSの区間は15008にトンネリングされるため、Ciliumポリシーが送信元やL7情報を確認できません。この場合、メッシュ内のL4以上のポリシーはIstioが担当します。
7. LLMサービングで特に重要な点
클라이언트 -> LB/Gateway -> (Envoy 사이드카 or waypoint) -> 모델 서버(vLLM 등)
각 홉마다: 요청 타임아웃 / 스트림 유휴 타임아웃 / 연결 유휴 타임아웃 / 드레인
- 長いストリーミング:Envoyのストリームアイドルタイムアウトのデフォルトは5分であり、レスポンスヘッダーが届く前に期限切れになると408、それ以降はストリームリセットとなります。長いプロンプトのキューイング区間で無応答が続くと該当する可能性があります。
VirtualServiceのリクエストtimeoutはデフォルトが無効です。DestinationRuleのコネクションプールのidleTimeoutはアクティブなリクエストがない時間を基準とし、デフォルトは1時間です。また、HTTP/2 PINGでは接続を維持できません。 - リトライ:Istioのデフォルトリトライは2回であり、
connect-failure,refused-stream,unavailable,cancelledにのみ適用されます。コストの大きい生成リクエストが重複していないか確認する必要があります。 - 負荷分散:デフォルトアルゴリズムは最小リクエスト(
LEAST_REQUEST)です。Istioアンビエントのドキュメントでは、リモートネットワークへフェイルオーバーする際、HTTP多重化とコネクションプーリングのために1つのリモートエンドポイントにリクエストが集中する可能性があると記載されています。生成長のばらつきが大きいLLMでは、リクエスト数のみをカウントする分散の限界がより顕著になります。Gateway API Inference Extensionは、KVキャッシュとリクエストコストを考慮するエンドポイントセレクター(Endpoint Picker)でこの問題に対処しており、READMEには一般提供(GA)と記載され、セレクターの実装はllm-dリポジトリに移行されたと述べられています。 - 終了とロールアウト:サイドカー終了時のドレインのデフォルトは5秒です。数分にわたるストリームでは、
terminationDrainDurationとPodの終了猶予時間をレスポンスの長さに合わせる必要があります。 - ヘルスチェック:サーバープロセスが生存しているというシグナルと、リクエストを処理できるというシグナルは異なります。以下の「よくある落とし穴」の音声合成(TTS)の事例のように、失敗率が高くてもヘルスチェックが200を返すことがあります。
- 遅延分布:入力処理(prefill)とトークン生成(decode)、キューイングが混ざってテールレイテンシが長くなるため、平均ではなくp95・p99でタイムアウトを決定します。
8. サービスディスカバリー、障害ドメイン、セキュリティ
障害ドメインは、クラスター、ネットワークパス(VPN)、コントロールプレーンに分けて考えます。混雑したクラスターでは、Service trafficDistribution(PreferSameZone)によりトラフィックを同一ゾーンに留め、CoreDNSのレプリカを両方に配置します(EKSドキュメントの推奨)。セキュリティは境界ファイアウォールに依存せず、サービス間mTLS、共通ルート下のクラスター別中間CA、cert-managerやSPIREのような自動証明書ローテーションを備えます。オンプレミスノードは、長期鍵の代わりにSSMハイブリッドアクティベーションやIAM Roles Anywhereが発行する一時認証情報でEKSに対して認証を行います(EKSドキュメント)。ClusterMeshやIstioマルチクラスターは、接続した瞬間に信頼ドメインが結合するため、片方が侵害されるともう片方に波及する可能性があります。
よくある落とし穴
筆者の実験環境は1つのプライベートLAN内にあるため、WAN、クラウド、ClusterMesh、アンビエントはありません。以下は、その環境で経験したことを一般的な事例としてまとめたものです。
- トンネルポートのブロック:Windows上のWSL2ノードで、Hyper-VファイアウォールがUDP 8472をブロックしたため、そのノードのPodのみが他のノードと通信できませんでした(tcpdumpで確認)。WireGuardのUDP 51871も同様の理由で確立されませんでした。
- kubeletが自ノードのPodに到達できない:同じWSL2ノードでは、HTTP・TCPプローブがいずれもタイムアウトしたため、
execプローブに変更しました。 - ゲートウェイのタイムアウト:パスごとにリクエストのタイムアウトを分割しました。通常のパスは120秒、Server-Sent Events(SSE)のストリーミングレスポンスは
0s(制限なし)、時間のかかるセッション作成は900秒でした。ゲートウェイが切断するとサーバーログにエラーが残らないため、原因究明が困難でした。 - Istioの最小インストール:Ciliumが
cni-exclusive=trueであるため、istio-cniなしでistio-initを使用することになり、該当するネームスペース1つのみPod Security Admission(PSA)レベルをprivilegedに下げました。上記のCiliumドキュメントはcni.exclusive=falseを推奨しています。 - 浅いヘルスチェック:複数のTTS Podを1つの後ろに配置した際、GPUを他のワークロードと共有していたPodのリクエストの約18%がメモリ不足(OOM)で失敗したにもかかわらず、PodはRunningであり、ヘルスチェックは200を返していました。成果物の1つが数十回の呼び出しで構成されているため、74分間完了した成果物が存在しませんでした。
自分で試してみる
以下はすべて読み取り専用であるか、一時的なネームスペース内で完結します。コマンドはこの記事の執筆時に実行して検証したものではありません。
# 읽기 전용: Cilium 설정 확인
kubectl -n kube-system get cm cilium-config -o yaml | grep -E "routing-mode|tunnel-protocol|enable-wireguard|kube-proxy-replacement|cni-exclusive|bpf-lb-sock-hostns-only"
kubectl get gateway,httproute -A
# 임시: 노드 사이 지연과 스트리밍 간격 (정리 포함)
kubectl create ns lab-net
kubectl -n lab-net run a --image=nicolaka/netshoot --overrides='{"spec":{"nodeName":"NODE_A"}}' -- sleep 3600
kubectl -n lab-net run b --image=nicolaka/netshoot --overrides='{"spec":{"nodeName":"NODE_B"}}' -- sleep 3600
kubectl -n lab-net exec a -- ping -c 10 $(kubectl -n lab-net get pod b -o jsonpath='{.status.podIP}')
kubectl delete ns lab-net
pingの結果は平均だけでなく、最大値と標準偏差を確認してください。同様の方法で一時PodにSSEサーバーを立ち上げ、10秒間隔でチャンクを送信し、 curl -Nで受け取ると、サービス経路がストリームを切断するかどうかを確認できます。ゲートウェイ timeouts.requestを変更する実験は実際のサービスルートに影響を与えるため、必ず専用のテスト用ゲートウェイでのみ行ってください。
面接で想定される質問
- クラスターは1つにまとめるべきか、分けるべきか? 障害やセキュリティの境界を分離したい場合は分け、管理の一元化が重要な場合はEKS Hybrid Nodesのように1つのコントロールプレーンにノードを参加させます。コントロールプレーンをWANに拡張する場合は、etcdのタイムアウトをRTTに合わせて調整する必要があり、接続が切断された際の影響範囲を設計しなければなりません。
- EKS Hybrid Nodesに必要なネットワークは? VPNやDirect ConnectによるVPCとのプライベート接続が必要です。ノードやPodのCIDRが相互に、またVPCやサービスのCIDRと重複してはならず、ウェブフックとノード間の直接通信においてはPodのCIDRがルーティング可能である必要があります。
- CiliumとIstioを併用する場合のポイントは? CiliumはCNI、L3/L4ポリシー、エントリーポイントを担い、IstioはmTLSアイデンティティとL7ルーティングを担います。kube-proxy代替モードでは、ソケットLBをホスト名前空間に制限し、CNIの独占を解除する必要があります。L7ポリシーはどちらか一方で管理します。
- LLMストリーミングの途中で接続が切れる場合は? ホップごとにリクエスト、ストリームアイドル、接続アイドルのタイムアウトを点検し、ログが記録される層を切り分けます。ストリーミング経路のタイムアウトを分離し、リトライ、ドレイン、ヘルスチェックをレスポンスの長さに合わせます。
- マルチクラスターの障害対策とセキュリティは? 平時のトラフィックは地域優先(アフィニティ)によってローカルに維持し、ローカルのバックエンドがすべて異常な状態のときにのみリモートに転送します。信頼ドメインが結合するため、セキュリティレベルが同等のクラスター同士のみを接続します。ポリシーと設定はクラスターごとにデプロイし、GitOpsによって一貫性を維持します。
よくある誤解
- ClusterMeshは複数のクラスターを1つに統合する機能ではありません。各クラスターのAPIサーバーとポリシーは独立しています。
- Hybrid Nodesはクラウドのノードを追加する機能ではなく、オンプレミスのノードをEKSに追加する機能です。
- アンビエントモードにしたからといって、L7機能が自動的に有効になるわけではありません。Waypointが必要です。
- 専用線イコール暗号化ではありません。暗号化はMACsecやmTLSで別途設定する必要があります。
参考文献
- https://docs.aws.amazon.com/eks/latest/userguide/hybrid-nodes-overview.html
- https://docs.aws.amazon.com/eks/latest/userguide/hybrid-nodes-prereqs.html
- https://docs.aws.amazon.com/eks/latest/userguide/hybrid-nodes-cni.html
- https://docs.aws.amazon.com/eks/latest/userguide/hybrid-nodes-webhooks.html
- https://aws.amazon.com/blogs/containers/run-genai-inference-across-environments-with-amazon-eks-hybrid-nodes/
- https://docs.aws.amazon.com/vpn/latest/s2svpn/vpn-limits.html
- https://docs.aws.amazon.com/directconnect/latest/UserGuide/Welcome.html
- https://docs.cilium.io/en/stable/network/clustermesh/intro/
- https://docs.cilium.io/en/stable/network/clustermesh/setup/
- https://docs.cilium.io/en/stable/network/servicemesh/istio/
- https://docs.cilium.io/en/stable/network/servicemesh/gateway-api/gateway-api/
- https://docs.cilium.io/en/stable/operations/system_requirements/
- https://istio.io/latest/docs/ambient/overview/
- https://github.com/istio/istio.io/blob/master/content/en/docs/ambient/install/multicluster/_index.md
- https://istio.io/latest/news/releases/1.31.x/announcing-1.31/
- https://istio.io/latest/docs/reference/config/networking/destination-rule/
- https://istio.io/latest/docs/reference/config/istio.mesh.v1alpha1/
- https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto
- https://etcd.io/docs/v3.6/tuning/
- https://cloud.google.com/kubernetes-engine/docs/concepts/multi-cluster-services
- https://github.com/kubernetes-sigs/gateway-api-inference-extension
外部の根拠資料
直接測定できなかった、あるいは測定だけでは不十分な主張について、以下の資料を参照して確認しました。
| 主張 | 資料 | 確認内容 |
|---|---|---|
| サイドカのオーバーヘッドの大部分はHTTPの解析 | Zhuらによる「Dissecting Service Mesh Overheads」(arXiv 2207.00592, 2022年、事前発表版) | Istio 1.13におけるHTTPモードのオーバーヘッドは、TCPモードと比較してレイテンシが約4倍、CPUが約3倍であり、大部分(62〜73%)がプロトコル解析によるものである。TLS暗号化のコストは別途測定されていない。 |
| mTLS暗号化よりもプロキシの方がコストが高い | Bremler-Barrらによる「Performance Comparison of Service Mesh Frameworks: the MTLS Test Case」(arXiv 2411.02267, 2024年、技術報告書) | メッシュを使用せずにmTLSのみを有効にした基準値では、p99の増加が最大3%にとどまった。IstioサイドカーはmTLSを無効にしてもスループットが同等であり、HTTP解析まで無効にしたTCPモードでは約5倍となった。アンビエントモードではp99の増加が大幅に小さい。サーバー側に200msの遅延を挿入したテストであるため、絶対値には負荷による飽和の影響が混ざっている。 |
| リクエストあたりの追加レイテンシの大きさ | Istio公式パフォーマンスドキュメント | 1.16.2のドキュメントでは、2つのプロキシによってp90が約1.7ms、p99が約2.7ms増加(1,000 QPS、コネクション16、mTLS)。1.20のドキュメントではp90が約0.228ms。本記事の+2.2msは前者の値と同等で、後者の値よりも約10倍大きい。ドキュメント側のハードウェアが変更されたかどうかは確認できておらず、本記事では1台のVM上でクライアント、サーバー、プロキシが同居しているためCPU競合が原因の一つである可能性があるが、検証は行っていない。 |
| アンビエントモードの方が軽量であるという点について | 上記2つ目の論文、およびIstio 1.24ドキュメント | 3,200 QPSでmTLSを強制した場合のp99の増加は、アンビエントが+0.03秒に対し、サイドカーは+0.37秒。ztunnelは約0.06 vCPU・12MBであるのに対し、サイドカーは約0.20 vCPU・60MB(1,000 QPS)。本記事ではアンビエントのテストは行っていない。 |
| kube-proxyの同期間隔 | Kubernetesドキュメント、kube-proxyの minSyncPeriod デフォルトは1秒 |
本記事のサービス数0個における約0.96秒という結果と一致する。 |
| iptablesにおける最初のパケットの遅延の規模依存性 | Huaweiの2017年発表資料(サービス数に応じたルール走査コスト)、Kubernetes 1.28の部分更新に関する情報 | ルールが線形に走査されるため、チェインの後方にあるサービスほど最初のパケットの処理が遅くなる。ipvsモードは1.35以降で非推奨(deprecated)となっており、nftablesが代替となる。 |
| WireGuardの暗号化範囲とコスト | Ciliumドキュメント(WireGuard Transparent Encryption)、WireGuard論文(NDSS 2017) | デフォルトではCiliumが管理するPodトラフィックのみが対象で、ノードトラフィックはベータオプション扱いであり、同一ノード内は暗号化されない。トンネルルーティングモードでは二重カプセル化が発生する。論文では1GbE環境で1,011Mbit/sを記録(著者らによる比較、リンク速度がボトルネック)。 |
| ハイブリッド構成の制約事項 | AWS EKS Hybrid Nodesのドキュメント、Site-to-Site VPNの制限に関するドキュメント | トンネルあたり1.25Gbpsの制限があり、CiliumはハイブリッドノードでサポートされるCNIであり、ハイブリッドノードはvCPUあたり時間単位で課金される。 |
| conntrackが飽和したときの挙動 | 本記事でのテスト(前述) | 外部文献は参照せず、実測値による測定で代用した。 |
確認したバージョンと日付
2026-10-05時点におけるKubernetes 1.37.1(2026-09-23)、Cilium 1.20.2(2026-09-16、1.21.0-pre.3が存在)、Istio 1.31.1(2026-09-21、1.31はKubernetes 1.32〜1.36をサポート)、Gateway API v1.6.1(Ciliumドキュメント基準)。実測に使用したCiliumは1.20.1、Istioは1.31.0です。
確認できなかった事項: EKS Hybrid Nodesの価格表の具体的な数値、最新のサポートCiliumバージョン(AWSドキュメントでは1.17と1.18のみ言及)、GKEにオンプレミスノードを接続するプロダクト、Direct Connectのデフォルト暗号化ポリシー、ClusterMeshでリモートクラスターが切断された際の正確な動作、アンビエントモードにおけるWAN遅延の影響、および前述のコマンドの実際の出力結果です。