istiodはなぜ一つに統合されたのか
一言でいうと
Istioは、「設定を作る側(istiod)」と「パケットに触れる側(Envoy)」を完全に分離しました。この分離のおかげで、コントロールプレーンが落ちてもトラフィックは流れ続け、逆にコントロールプレーンだけを直しても、メッシュ全体の動作が変わります。
なぜ必要なのか
Istio 1.5より前は、Pilot(トラフィック)、Citadel(証明書)、Galley(設定の検証)、Mixer(ポリシー・テレメトリー)が、それぞれ別のPodとして動いていました。問題は、この4つがお互いを呼び出すことで生じる運用コストでした。構成要素が1つ落ちたときに、どの機能が止まるのか把握しにくく、特にMixerは、リクエストごとにコントロールプレーンを呼び出す構造だったので、データパスの遅延と障害が、コントロールプレーンに直結していました。
そこで1.5で、Pilot・Citadel・Galleyがistiodという単一のバイナリに統合され、Mixerは1.8で完全に削除されました。テレメトリーは、Envoyの中のフィルターが直接生成する方式に変わりました。要点は「コンポーネントを減らした」ことではなく、データパスからコントロールプレーンの呼び出しを完全に取り除いたことです。
どう動くのか
istiodはKubernetesのAPIを監視して、Service・Endpoint・PodとIstioのCRDの変化を読み取り、それをEnvoyが理解できる設定に変換して、gRPCストリームで押し込みます。このプロトコルがxDSです。
| API | 何を送り出すか |
|---|---|
| LDS | リスナー: どのポートをどのプロトコルで待ち受けるか |
| RDS | ルート: どのリクエストをどのクラスターへ送るか |
| CDS | クラスター: 宛先グループの定義(LB、サーキットブレーカー、TLS) |
| EDS | エンドポイント: そのクラスターに実際に入っているPodのIP |
| SDS | 証明書と鍵 |
Istioはこの5つを、ADS(Aggregated Discovery Service)という1つのgRPCストリームにまとめて送ります。理由は帯域ではなく順序です。クラスター(CDS)が定義される前にエンドポイント(EDS)が届くと、Envoyは行き先のないアドレスを受け取ることになり、ルート(RDS)がリスナー(LDS)より先に来ると、付ける先がありません。ストリームが1つなら順序が保証され、設定がアトミックに入れ替わります。
Envoyの中で、宛先は次のような名前で存在します。
outbound|9080|v2|reviews.default.svc.cluster.local
방향 포트 subset FQDN
istioctl proxy-config clusterでこの文字列を読めるようになれば、半分は終わったようなものです。subsetの位置が空なら、DestinationRuleがないという意味で、名前自体がないなら、そのサービスはこのプロキシの視野の外にあるという意味です。
Podの中では、istio-initコンテナがiptablesのルールを仕込んで、トラフィックをプロキシへ曲げます。インバウンドは15006、アウトバウンドは15001へ向かいます。このときUID 1337のトラフィックだけはリダイレクトから除外されますが、そのUIDがまさにistio-proxy自身だからです。除外しないと、プロキシが送り出したパケットが再びプロキシに戻ってきて、無限ループになります。そのほかに、15008(HBONEトンネル)、15020(エージェント統合のヘルス・メトリクス)、15021(ヘルスチェック)、15090(Prometheusメトリクス)を覚えておけば、試験でポートの問題を取りこぼしません。
最後に、重要な性質を1つ。istiodがすべて落ちても、すでに動いているEnvoyは、最後に受け取った設定でトラフィックを処理し続けます。止まるのは新しい設定の伝播と証明書の更新であって、データパスではありません。そのため、istiodの障害は「即時の全面障害」ではなく「期限付きの状態」であり、証明書の寿命(デフォルトは24時間)が、実質的なタイマーになります。
現場での姿
筆者のホームラボは、7ノード(cp-1/2/3 + gpu-a/b/c/d)で、Kubernetes v1.34.10、containerd 1.7.27、カーネル6.14の上にCilium 1.20.1で構成されています。サイドカーメッシュは立ち上げていませんが、この対比がかえってIstioを理解する助けになります。
このクラスターは、kubeadm init --skip-phases=addon/kube-proxyでkube-proxyを最初からインストールせずに構築しました。一度でも動いたkube-proxyは、ノードにKUBE-SERVICES / KUBE-SVC-* / KUBE-SEP-*のチェーンを刻み込み、DaemonSetを削除してもそのルールが残って、eBPFのデータパスと衝突します。実測の結果は、kube-proxyのPodが0個、iptablesのKUBE-チェーンが0個でした。
ここで学べる点は、Istioにもそのまま当てはまります。サイドカーの注入とは、Podのネットワーク名前空間の中にiptablesのルールを刻むことで、注入のラベルをあとから消しても、すでに起動したPodのルールは、Podを再起動するまでそのまま残ります。「ラベルは消したのに、なぜまだプロキシを通るのか」という問いの答えがこれです。設定は宣言的ですが、ノードとPodに残る痕跡は命令的です。
次のクイズで確認すること
このモジュールは概念だけを扱います。クイズでアーキテクチャの感覚を点検したあと、次のモジュールで、VirtualServiceとDestinationRuleを自分で書いて、上で見たoutbound|포트|subset|FQDNがどのように作られるかを、手で確認します(プレースホルダーはポートです)。