TT Lab
はじめる
学ぶ 学習パス コース

Istio深化 — なぜそう流れるのか

注入結果の番号を読み、二つの入口を立てる

TT Labで続きを見る

目標

オフラインでサイドカー注入の出力物を作り、横取りの番号が書かれた場所を探して、その番号がEnvoyのどのリスナーとクラスターになるかを、手で立てて確認します。

なぜ重要なのか

サイドカーがおかしな動きをするとき、最初に見るのが注入の出力物です。番号が1つずれると、プロキシが自分のリクエストをまた受け取ったり(UID)、ヘルスチェックがルーティングに乗ったり(-d)、外部呼び出しがすべて503になったりします(BlackHoleCluster)。番号表を読めれば、この3つを症状だけで見分けられます。

ステップ

  1. /root/ist2-injectを作り、その中でistioctl kube-injectで/opt/lab/fixtures/istio/inject-target.yamlにサイドカーを入れて、/root/ist2-inject/inject.yamlとして保存してください(注入設定の3つのファイル/opt/istio/inject-config.yaml・mesh-config.yaml・values-config.yamlをすべて渡します)。次に、/root/ist2-inject/01-containers.txtに3行を書いてください。containers=(注入後のコンテナ名を順番にカンマ区切りで)、init=(初期化コンテナの名前)、proxy_image=(istio-proxyコンテナのイメージ)です。
  2. /root/ist2-inject/inject.yamlのistio-initコンテナのargsを読んで、/root/ist2-inject/02-redirect.txtに5行を書いてください。outbound_port=(-pの値)、inbound_port=(-z)、proxy_uid=(-u)、mode=(-m)、excluded_inbound_ports=(-d、カンマでつながったまま)です。
  3. /root/ist2-inject/inject.yamlから3つのポートを探して、/root/ist2-inject/03-ports.txtに書いてください。readiness_port=(istio-proxyのreadinessProbe.httpGet.port)、merged_metrics_port=(Podテンプレートのアノテーションprometheus.io/port)、envoy_prom_port=(istio-proxyのポートのうち、名前がhttp-envoy-promのもの)です。最後の行のall_excluded=には、3つのポートがすべてistio-initの-dのリストに入っていればyes、そうでなければnoを書いてください。
  4. /root/ist2-inject/inject.yamlのsecurityContextを読んで、/root/ist2-inject/04-uid.txtに5行を書いてください。proxy_uid=(istio-proxyのrunAsUser)、proxy_run_as_non_root=(同じコンテナのrunAsNonRoot)、proxy_caps_drop=(capabilities.dropをカンマ区切りで)、init_run_as_user=(istio-initのrunAsUser)、init_caps_add=(istio-initのcapabilities.addをカンマ区切りで、マニフェストの順序どおりに)です。
  5. /root/ist2-inject/mesh.yamlにEnvoyの設定を書いてください。管理ポートは9981、リスナー名virtualInboundが127.0.0.1:15006でtraffic_direction: INBOUNDで待ち受け、すべてのパスをクラスターinbound|8101||(アプリの役割、127.0.0.1:8101)へ送ります。アップストリームを8101でokとして起動し、Envoyを起動してから、curl localhost:15006/ordersの結果を/root/ist2-inject/05-inbound.txtに、status=(HTTPコード)とbody=(応答本文1行)の2行で書いてください。
  6. /root/ist2-inject/mesh.yamlに、2つ目のリスナーを追加してください(先のvirtualInboundはそのまま)。名前はvirtualOutbound、127.0.0.1:10081、traffic_direction: OUTBOUNDで、すべてのパスをクラスターBlackHoleClusterへ送ります。BlackHoleClusterはtype: STATICで、エンドポイントが1つもないクラスターです。起動し直してから、curl localhost:10081/の結果を/root/ist2-inject/06-outbound.txtに、status=、body=、cluster=(このリスナーのルートが指すクラスター名)の3行で書いてください。
  7. 起動しているEnvoyのlocalhost:9981/config_dump?resource=static_listenersから、リスナーごとに이름 방향 포트(プレースホルダーは名前、方向、ポートです)を空白で区切って1行ずつ取り出し、/root/ist2-inject/07-listeners.txtに保存してください(2行、順序は問いません)。
  8. /root/ist2-inject/08-report.mdに、outbound_capture=、inbound_capture=、proxy_uid=、unknown_destination=の4行を書き(それぞれ、出ていく側をリダイレクトするポート、入ってくる側のポート、横取りから外れるユーザーID、ステップ6で宛先を知らないリクエストが行ったクラスター名)、その下に、- で始まる説明を4行以上書いてください。

参考

注入の出力物を作り、何が増えたかを数える

/root/ist2-injectを作り、その中でistioctl kube-injectで/opt/lab/fixtures/istio/inject-target.yamlにサイドカーを入れて、/root/ist2-inject/inject.yamlとして保存してください(注入設定の3つのファイル/opt/istio/inject-config.yaml・mesh-config.yaml・values-config.yamlをすべて渡します)。次に、/root/ist2-inject/01-containers.txtに3行を書いてください。containers=(注入後のコンテナ名を順番にカンマ区切りで)、init=(初期化コンテナの名前)、proxy_image=(istio-proxyコンテナのイメージ)です。

このPodにはistiodがないので、kube-injectが普段のようにクラスターから注入設定を取ってこられません。3つの設定ファイルをすべて渡して初めて、外へ出ていきません。2つだけ渡すと、残りの1つを探しに出ていきます。出力物の末尾には空のドキュメントが付くので、yqで読むときはselect(.kind=="Deployment")で絞ってください。値は、目で書き写さず、yqで取り出して書くと間違えません。

istio-initの引数から横取りの設計を読む

/root/ist2-inject/inject.yamlのistio-initコンテナのargsを読んで、/root/ist2-inject/02-redirect.txtに5行を書いてください。outbound_port=(-pの値)、inbound_port=(-z)、proxy_uid=(-u)、mode=(-m)、excluded_inbound_ports=(-d、カンマでつながったまま)です。

istio-iptablesは、引数1つ1つがルールのまとまりです。-pは、アプリが外へ送る接続をリダイレクトして入れるポート、-zは、外から入ってくる接続をリダイレクトして入れるポート、-uは、横取りしないユーザーIDです。argsは文字列のリストなので、フラグのすぐ次の要素が値です。awkで「フラグを見た次の行」を取り出せば済みます。-xのように、値が空文字列のフラグもあるので、順序で数えないでください。

横取りから外れたポートが誰のためのものかを探す

/root/ist2-inject/inject.yamlから3つのポートを探して、/root/ist2-inject/03-ports.txtに書いてください。readiness_port=(istio-proxyのreadinessProbe.httpGet.port)、merged_metrics_port=(Podテンプレートのアノテーションprometheus.io/port)、envoy_prom_port=(istio-proxyのポートのうち、名前がhttp-envoy-promのもの)です。最後の行のall_excluded=には、3つのポートがすべてistio-initの-dのリストに入っていればyes、そうでなければnoを書いてください。

入ってくる接続はすべて15006にリダイレクトされると言いましたが、kubeletのヘルスチェックやPrometheusのスクレイプまでリダイレクトされると、Envoyのルーティング規則を通って、見当違いの所へ行きます。そのため、プロキシが直接受け取る必要があるポートは、-dで横取りから除きます。アノテーション名にドットとスラッシュがあるので、.metadata.annotations["prometheus.io/port"]のように、角括弧で読んでください。

UID 1337と権限が、どこに分かれているかを見る

/root/ist2-inject/inject.yamlのsecurityContextを読んで、/root/ist2-inject/04-uid.txtに5行を書いてください。proxy_uid=(istio-proxyのrunAsUser)、proxy_run_as_non_root=(同じコンテナのrunAsNonRoot)、proxy_caps_drop=(capabilities.dropをカンマ区切りで)、init_run_as_user=(istio-initのrunAsUser)、init_caps_add=(istio-initのcapabilities.addをカンマ区切りで、マニフェストの順序どおりに)です。

iptablesのルールを書き換えるにはNET_ADMINが必要で、その権限は、1回動いて終わる初期化コンテナにだけ与えます。長く生きるプロキシは、rootでもなく、すべての権限を捨てた状態で起動します。そしてプロキシのUIDは、ステップ2の-uの値と同じである必要があります。同じでなければ、プロキシが送ったリクエストが、再びプロキシへリダイレクトされて、無限に回ります。リストは、yqのjoin(",")でつなげてください。

15006にvirtualInboundを立てて、アプリまで送る

/root/ist2-inject/mesh.yamlにEnvoyの設定を書いてください。管理ポートは9981、リスナー名virtualInboundが127.0.0.1:15006でtraffic_direction: INBOUNDで待ち受け、すべてのパスをクラスターinbound|8101||(アプリの役割、127.0.0.1:8101)へ送ります。アップストリームを8101でokとして起動し、Envoyを起動してから、curl localhost:15006/ordersの結果を/root/ist2-inject/05-inbound.txtに、status=(HTTPコード)とbody=(応答本文1行)の2行で書いてください。

サイドカーの入ってくる側の入口が、このリスナーです。iptablesが、Podへ来る接続をすべて15006にリダイレクトし、ここで元の宛先ポートを見て、inbound|<포트>||(プレースホルダーはポート番号です)クラスターへ渡します。名前の中央の欄が空なのは、入ってくる側にはサブセットがないからです。クラスター名に|があるので、YAMLでは引用符で囲んでください。traffic_directionは動作を変えませんが、統計とconfig_dumpに方向が残ります。

宛先を知らないリクエストはBlackHoleClusterへ行く

/root/ist2-inject/mesh.yamlに、2つ目のリスナーを追加してください(先のvirtualInboundはそのまま)。名前はvirtualOutbound、127.0.0.1:10081、traffic_direction: OUTBOUNDで、すべてのパスをクラスターBlackHoleClusterへ送ります。BlackHoleClusterはtype: STATICで、エンドポイントが1つもないクラスターです。起動し直してから、curl localhost:10081/の結果を/root/ist2-inject/06-outbound.txtに、status=、body=、cluster=(このリスナーのルートが指すクラスター名)の3行で書いてください。

メッシュが知らない宛先へ行くリクエストを遮断するとき(outboundTrafficPolicy: REGISTRY_ONLY)、Istioはそのリクエストを、エンドポイントのないクラスターへ送ります。デフォルト(ALLOW_ANY)では、代わりに元の宛先へそのまま通すPassthroughClusterへ行きます。エンドポイントが0個のクラスターは、load_assignmentをまったく書かなければ作れます。Envoyが返す本文を、そのまま書き写してください。この文言を知っておけば、本番のログですぐに見分けられます。

config_dumpから、2つの入口の名前・方向・ポートを読み直す

起動しているEnvoyのlocalhost:9981/config_dump?resource=static_listenersから、リスナーごとに이름 방향 포트(プレースホルダーは名前、方向、ポートです)を空白で区切って1行ずつ取り出し、/root/ist2-inject/07-listeners.txtに保存してください(2行、順序は問いません)。

ファイルに書いたものと、Envoyが実際に読み込んだものは、違うことがあります。本番でistioctl proxy-config listenersが見せるものも、結局このダンプです。resource=static_listenersで絞ると、.configs[].listenerにリスナーが1つずつ入っています。jq -rの文字列補間で、3つの値を1行につなげてください。

横取りの番号表にまとめる

/root/ist2-inject/08-report.mdに、outbound_capture=、inbound_capture=、proxy_uid=、unknown_destination=の4行を書き(それぞれ、出ていく側をリダイレクトするポート、入ってくる側のポート、横取りから外れるユーザーID、ステップ6で宛先を知らないリクエストが行ったクラスター名)、その下に、- で始まる説明を4行以上書いてください。

番号は、前のステップのファイルから移せば済みます。推測で書かず、02-redirect.txtと06-outbound.txtをもう一度見てください。説明の行には、「何をした」よりも「なぜそう設計されたのか」を書くと、次に障害に遭ったとき、この表が役に立ちます。