サイドカー — コードを直さずネットワークを変える方法
一言でいうと
サービスメッシュは新しいネットワークではなく、すべてのPodにプロキシを1台ずつ付け、そのプロキシを中央から設定する構造です。
なぜ必要なのか
マイクロサービスを10個ほど作ると、どのチームでも同じコードが繰り返し現れます。リトライ、タイムアウト、サーキットブレーカー、TLS証明書の読み込み、相手の検証、リクエストのログ記録、トレースヘッダーの伝播です。サービスがJava・Go・Python・Nodeに散らばっていると、この一覧を4回実装することになり、4つの実装の動作は微妙に異なります。タイムアウトの既定値を1つ変えるだけでも、4つのリポジトリにPRを出して4回デプロイしなければなりません。
メッシュの発想は単純です。その共通の関心事をアプリケーションプロセスの外へ取り出し、同じPod内のプロキシに代わりに処理させるのです。アプリケーションは今でもhttp://reviews:9080へ平文のリクエストを送りますが、実際にそのリクエストを相手まで運ぶのはプロキシです。そのため、mTLSを有効にする作業は、アプリケーションのデプロイではなく、設定リソースを1つ適用する作業になります。
どう動くのか
Istioは2つの層に分かれます。コントロールプレーン(istiod)とデータプレーン(各PodのEnvoy)です。istiodは1.5でPilot・Citadel・Galleyを1つのバイナリに統合したもので、リクエスト経路に割り込んでいたMixerは1.8で完全に削除されました。つまり1つのリクエストが処理されている間に、コントロールプレーンへ問い合わせることはありません。istiodが停止しても、すでに設定を受け取ったEnvoyは最後の設定でトラフィックを流し続けます。
設定はxDSというgRPCストリームで流れます。
| API | 運ぶもの | Istio側の入力 |
|---|---|---|
| LDS | リスナー(ポート、プロトコル) | ポート名、Gateway |
| RDS | HTTPルーティングルール | VirtualService |
| CDS | アップストリームクラスター | DestinationRule |
| EDS | クラスターの実際のエンドポイント | Kubernetes Endpoints |
| SDS | TLS証明書とキー | istiod CA |
Istioはこれらすべてを、ADSという1つのストリームにまとめて送ります。クラスター定義より先にエンドポイントが届いて、設定が一瞬壊れることを防ぐためです。
注入はKubernetesのMutatingAdmissionWebhookが行います。ネームスペースにistio-injection=enabledラベルが付いていると、そのネームスペースでPodが作成される瞬間にistiodがPodスペックを書き換えて、コンテナを2つ差し込みます。
istio-init(初期化コンテナ):istio-iptablesを実行してREDIRECTルールを仕込みます。インバウンドは15006、アウトバウンドは15001へ向けます。UID 1337で出ていくトラフィックだけを例外にしますが、それがプロキシ自身だからです。この例外がないと無限ループになります。istio-proxy(サイドカー): Envoyとistio-agentが一緒に動くコンテナです。15090でPrometheusメトリクスを、15021でヘルスチェックを公開します。
ここで事故がよく起きる点があります。ラベルが働くのはPodを作成する時点だけです。すでに起動しているPodには何も起こらないので、ラベルを付けたらロールアウトを実行してPodを作り直さないと、サイドカーが入りません。「ラベルを付けたのにどうしてメッシュに入らないのか」への答えは、十中八九これです。
現場での姿
第一に、サイドカーの代償を請求書として見ます。Pod1つあたり約100MB前後のメモリと、数秒の起動時間が追加されます。Podが100個なら10GBです。そのため、サイドカーなしでノード単位のztunnelがL4を、ネームスペース単位のwaypointがL7を担うAmbientモードが登場しました。まったく別の方向としてeBPFデータパス(Ciliumなど)を選び、kube-proxyすら不要にする構成も珍しくなくなりました。メッシュを導入するということは、「このコストを払って何を買うのか」に答えられるという意味でなければなりません。
第二に、アップグレードは再起動そのものです。サイドカーのバージョンを上げるには、すべてのPodを作り直す必要があります。数千Podのクラスターでは数時間がかりの作業になるため、メッシュのアップグレード計画には必ずロールアウトの順序と作業時間枠が入ります。
第三に、istioctl kube-injectはWebhookのオフライン版です。WebhookがPod作成時に行う処理を、マニフェストファイルに事前に適用して結果を見せてくれます。GitOpsで注入結果を固定したいときや、今回のように注入結果の解剖を目で確認したいときに使います。
次のラボですること
istioctl manifest generateでインストール用マニフェストを作って中身を数え、メッシュAPIタイプ(CRD)をクラスターに登録します。mesh-labには注入ラベルを付け、legacyには付けずに対照群を作ります。そのあと、フィクスチャのワークロードにistioctl kube-injectを実行し、コンテナがいくつになるか、初期化コンテナが何をするのかをJSONにまとめます。
あらかじめ断っておきますが、このラボ環境では本物のEnvoyがトラフィックを流しません。PodはRunningになりますが、パケットは流れず、kubectl execやポートフォワーディングもありません。そのため、このコースの採点はすべて、マニフェストの作成とistioctl analyze/validateによる静的検証を見ます。実際のハンドシェイクとメトリクスは、理論とクイズで扱います。設定が正しいかを判断する目は、この方法でも十分に養えますし、現場でも事故の半分はパケットではなく設定で起きます。