強制されていない状態が正常に見えてしまう
一言でいうと
メッシュで最も危険なのは、間違った設定ではなく、どこにも適用されていない設定です。前者はエラーを出しますが、後者は正常に見えます。
なぜこれが問題なのか
サービスメッシュで最も危険な状態は、「設定が間違っていること」ではなく「設定がどこにも適用されていないこと」です。前者はエラーを出しますが、後者は静かだからです。
サイドカーがなければすべてが無意味になる
ネームスペースにistio-injection=enabledを付ける前に作ったPodには、サイドカーがありません。そのPodはRunningで、サービスも正常に応答します。ところが、トラフィックがメッシュを通過しないので、mTLSも認可ポリシーもルーティングも適用されません。
セキュリティポリシーをすべて書いておいて、何も守られていない状態です。
確認の方法が変わった
Istioは、いまではKubernetesのネイティブサイドカーを使います。istio-proxyがspec.containersではなくspec.initContainersに入ります(restartPolicy: Alwaysで)。
そのため、kubectl get pod -o jsonpath='{.spec.containers[*].name}'で確認していた習慣は、いまではサイドカーがあるのに、ないと答えてしまいます。その答えを信じると、注入されていないと判断して、見当違いの箇所を直すことになります。
移した理由は順序の問題でした。従来の方式では、アプリがサイドカーより先に起動して、ネットワークが準備できていないままリクエストを送ったり、アプリが終わったのにサイドカーが残って、Jobがいつまでも終わらなかったりすることがありました。ネイティブサイドカーは、アプリより先に始まり、あとに終わります。
2種類の「動かない」
| 症状 | 原因 | 見る場所 |
|---|---|---|
応答コードがない(curl 000、exit 56) |
mTLSの未充足。TLSハンドシェイクで切断されます | 相手がメッシュの外か、サイドカーがあるか |
403(RBAC: access denied) |
認可の拒否。接続とmTLSは成功しています | AuthorizationPolicyのprincipals |
応答コード1つで、接続の層なのかポリシーの層なのかが分かれます。この区別がないと、メッシュの障害を見るたびに、最初から調べ直すことになります。
メッシュが実際に行うことと、その代償
サイドカーが付くと、リクエスト1つが通る道が長くなります。アプリ → 自分側のプロキシ → 相手側のプロキシ → 相手のアプリです。この構造のおかげで、mTLS、リトライ、サーキットブレーカー、細かなルーティング、そしてすべての呼び出しのメトリクスを、アプリのコードを直さずに得られます。その代わりに支払う対価があり、それを知ったうえで導入する必要があります。
遅延が加わります。プロキシを2回通るので、呼び出しごとにミリ秒単位が加わります。1回なら無視できますが、画面1つが内部の呼び出しを20回行えば、その分だけ積み重なります。
リソースを消費します。Podごとにプロキシが1つずつ付くので、Podの数だけメモリとCPUが余計にかかります。Podが数百個なら、この合計はノード数台分になります。
障害点が1つ増えます。プロキシが設定を受け取れなければ、トラフィックが止まります。コントロールプレーンが落ちても、すでに受け取っておいた設定でしばらくは持ちこたえますが、その間の変更は反映されません。
そのため、判断の基準はこう立てます。サービスが少なく、呼び出しの関係が単純なら、メッシュは過剰な選択です。ライブラリでもリトライやサーキットブレーカーを付けられますし、mTLSはゲートウェイで終端できます。メッシュが値打ちを発揮するのは、サービスが数十個を超え、言語が複数あってライブラリを統一できず、チームごとにデプロイの周期が違って、共通のポリシーをコードで強制しにくいときです。
導入するときも、一度にすべてを有効にはしません。ネームスペース1つから注入を有効にし、mTLSはPERMISSIVEで始めます。このモードは、暗号化された接続と平文の接続の両方を受け入れるので、メッシュの内外が混在していても、通信が途切れません。メトリクスで平文の接続が0になったことを確認してから、STRICTに上げます。この順序を飛ばして、最初からSTRICTをかけると、まだ注入されていないPodとの通信がすべて切れて、原因の見えない障害になります。ゲートウェイから入ってくる外部のトラフィックと、メッシュの内部のトラフィックは、ルールが別々に動きやすいので、ポリシーを書くときに、どちらのことを言っているのかを毎回はっきりさせておくのがよいです。
実務で本当に大切なこと
ラベルを付けたあとは、必ずPodを作り直します。istio-injection=enabledは、これから作られるPodにだけ適用されます。すでに動いているPodをそのままにしておくと、永遠にメッシュの外に残りますが、その状態でもサービスは正常に応答するので、誰も気づきません。
サイドカーの確認は、initContainersまで見ます。ネイティブサイドカーに移ってから、spec.containersだけを見る習慣は、あるものをないと答えてしまいます。kubectl get pod -o jsonpath='{.spec.initContainers[*].name}'も一緒に見る必要があります。
応答コードで、層を先に切り分けます。コードがまったくなければ(curl 000)接続の層で、403ならポリシーの層です。この1回の分岐がないと、メッシュの障害を見るたびに、最初から調べ直すことになります。
次のラボで、これらを本物のIstioの上で自分で確認します。