アプリは知らないのにすべての接続がプロキシを通る理由
一言でいうと
サイドカーの注入は、Podにコンテナを2つ追加します。1回動いて終わるistio-initが、iptablesに番号(15001・15006・1337)を仕込み、長く生きるistio-proxyが、その番号で待ち受けます。アプリは何も変えていないのに、すべての接続がプロキシを通る理由が、この番号です。
なぜ必要なのか
サービスメッシュの約束は、「コードを直さずに、すべての呼び出しにmTLS・リトライ・観測を付ける」ことです。そのためには、アプリがhttp://reviews:9080へ送った接続を、アプリに気づかれずに、プロキシが受け取る必要があります。ライブラリを埋め込む方式は、言語ごとに別々に作らなければならず、アプリがそのライブラリを使ってくれる必要があります。そこでIstioは、カーネル側で横取りします。Podのネットワークネームスペースにiptablesのルールを入れて、出ていく接続は15001へ、入ってくる接続は15006へ振り向けます。
ここですぐに、2つの問題が生じます。1つ目は、プロキシが受け取ったリクエストを再び外へ送ると、その接続もルールに掛かって、15001へ戻ってくることです。無限ループです。2つ目は、ルールを書き換えるにはNET_ADMIN権限が必要ですが、その権限を常に動いているプロキシに与えると、プロキシが突破されたときに、Podのネットワークをまるごと変えられるようになることです。注入の出力物の形は、この2つの問題への答えです。
どう動くのか
istioctl kube-injectが作ったマニフェストを開くと、番号が散らばっています。まとめると、次のとおりです。
| 番号 | 書かれている場所 | 意味 |
|---|---|---|
| 15001 | istio-init -p |
アプリが外へ送る接続がリダイレクトされて入ってくる場所(virtualOutbound) |
| 15006 | istio-init -z |
外から入ってくる接続がリダイレクトされて入ってくる場所(virtualInbound) |
| 1337 | istio-init -u、istio-proxy runAsUser |
このユーザーが送ったパケットは横取りしない |
| 15021 | readinessProbe | プロキシ自身の準備状態 |
| 15020 | prometheus.io/portアノテーション |
アプリとプロキシのメトリクスを合わせて出力する場所 |
| 15090 | コンテナポートhttp-envoy-prom |
Envoy自身のメトリクス |
ループの問題は、-u 1337で解決します。プロキシコンテナをUID 1337で起動し、そのUIDが送ったパケットは、ルールから除きます。2か所の番号がずれると、プロキシが自分のリクエストをまた受け取って、CPUを使います。権限の問題は、コンテナを分けて解決します。istio-initは、rootでNET_ADMIN・NET_RAWを持ち、ルールだけを仕込んで終わり、istio-proxyはrootではなく、すべての権限を捨て、ファイルシステムも読み取り専用です。-d 15090,15021,15020は、入ってくる側の横取りから除くポートです。kubeletのヘルスチェックとPrometheusのスクレイプが、Envoyのルーティングを通ってはいけないからです。
Envoy側から見ると、15006はvirtualInboundリスナーで、元の宛先ポートを見て、inbound|<포트>||(プレースホルダーはポート番号です)クラスターへ渡します。15001はvirtualOutboundで、メッシュが知っている宛先なら、そのサービスのリスナーへ、知らなければPassthroughCluster(そのまま通す)かBlackHoleCluster(エンドポイントがなく503)へ送ります。どちらになるかは、メッシュ設定のoutboundTrafficPolicyが決めます。
現場での姿
注入後、アプリが起動する前にリクエストを送って失敗する場合。プロキシが準備できる前にアプリが先に起動して外へ接続すると、ルールはすでにあるのに、15001で受け取る相手がいません。holdApplicationUntilProxyStartsがある理由です。注入の出力物のコンテナの順序を見れば、その設定がどう反映されるかがわかります。
「外部APIだけが503になります」。ログにBlackHoleClusterが出ていれば、メッシュがREGISTRY_ONLYで、そのホストを知らせるServiceEntryがないという意味です。応答本文のno healthy upstreamは、アプリではなく、サイドカーが作ったものです。
アプリが1337で起動する場合。アプリコンテナを偶然UID 1337で起動すると、アプリの接続も横取りから外れて、mTLSなしの平文で出ていきます。何のエラーも出ないので、セキュリティ点検で初めて表に出ます。
公式ドキュメント: Application requirements・Debugging Envoy and Istiod
次のラボですること
オフラインで実際の注入の出力物を作り、番号が書かれた場所をyqで1つずつ取り出します。その後、virtualInboundとvirtualOutboundを手で立てて、アプリまで届くリクエストと、BlackHoleClusterに落ちるリクエストを、自分で見ます。