プロキシが付いた後に起きること — 資源・起動順・終わらない Job
目標
istioctl kube-injectをオフラインで実行して、アノテーション1つ1つが、注入結果のどのフィールドに降りてくるかを確認します。リソースのリクエストと上限、プロキシ設定(concurrency・ドレイン時間)、起動順序の保証、出ていくポートの除外、注入の除外、そして、バッチ作業が終わらないという古典的な事故とその解決策まで、フィールド単位で見ます。
なぜ重要なのか
サイドカーを付ける作業は1行ですが、付けたあとに生じる問題は、たいてい設定1つから来ます。デフォルトのプロキシは、CPU 100mとメモリ128Miをリクエストします。Podが2,000個なら、それだけでCPU 200コアです。起動順序も問題です。アプリケーションがプロキシより先に起動すると、最初の数秒の出ていくリクエストが、そのまま失敗します。最も古い落とし穴は、バッチ作業です。プロキシが通常のコンテナとして入ると、作業が終わってもPodが完了に移れず、以前は、アプリケーションが終わるときにプロキシへ終了を要求する方法で回避していました。今は、Kubernetesが再起動ポリシーを持つ初期化コンテナをサポートしているので、プロキシをそちらに移して、根本的に解決します。このラボは、これらのつまみを注入結果のマニフェストで直接確認する方法を身につける場です。クラスターに載せる前に確認できれば、設定の変更がレビューの対象になります。
ステップ
/root/ist-lifecycleを作成して、/root/ist-lifecycle/dep.yamlに、lifecycleネームスペースのDeploymentordersを書いてください。replicasは2、ラベルはapp: orders、コンテナは1つ(app、イメージはnginx:1.27、コンテナポートは8080)で、アノテーションはありません。このファイルを注入して、結果を/root/ist-lifecycle/baseline.yamlに保存し、そこから読み取った4つを、/root/ist-lifecycle/baseline.tsvにcontainers・initContainers・proxyCpuRequest・proxyMemRequestの4行で書いてください(コンテナ名は、出てきた順にカンマでつなぎます)。/root/ist-lifecycle/dep-resources.yamlは、ステップ1のDeploymentを名前orders-resでコピーして、Podテンプレートのアノテーションを4つ加えたものです。sidecar.istio.io/proxyCPUは250m、sidecar.istio.io/proxyMemoryは192Mi、sidecar.istio.io/proxyCPULimitは1500m、sidecar.istio.io/proxyMemoryLimitは512Miです。注入結果を/root/ist-lifecycle/resources.yamlに保存してください。/root/ist-lifecycle/dep-proxyconfig.yamlは、名前orders-pcでコピーしたものに、proxy.istio.io/configアノテーションを1つ入れたものです。値は複数行のYAMLで、concurrency: 3とterminationDrainDuration: 45sの2つです。注入結果を/root/ist-lifecycle/proxyconfig.yamlに保存して、結果からistio-proxyの環境変数PROXY_CONFIGの値を/root/ist-lifecycle/proxy-config.jsonに保存してください。/root/ist-lifecycle/dep-hold.yamlは、名前orders-holdでコピーしたものに、proxy.istio.io/configアノテーションでholdApplicationUntilProxyStarts: trueだけを入れたものです。注入結果を/root/ist-lifecycle/hold.yamlに保存して、コンテナの順序と、プロキシにできたライフサイクルフックを、/root/ist-lifecycle/hold.tsvにcontainerOrderとpostStartの2行で書いてください(postStartの列には、フックが実行するコマンドを、空白でつないで書きます)。/root/ist-lifecycle/dep-exclude.yamlは、名前orders-excでコピーしたものに、アノテーションtraffic.sidecar.istio.io/excludeOutboundPortsを"3306,5432"で入れたものです。注入結果を/root/ist-lifecycle/exclude.yamlに保存して、istio-initコンテナの引数を、空白でつないで/root/ist-lifecycle/init-args.txtに1行で書いてください。/root/ist-lifecycle/dep-nosidecar.yamlは、名前orders-offでコピーしたものに、アノテーションsidecar.istio.io/injectを"false"で入れたものです。注入結果を/root/ist-lifecycle/nosidecar.yamlに保存してください。結果にプロキシがない必要があります。/root/ist-lifecycle/job.yamlに、lifecycleネームスペースのJobnightly-settleを書いてください。Podラベルはapp: nightly-settle、restartPolicy: Never、コンテナは1つ(worker、イメージはbusybox:1.36)です。/root/ist-lifecycle/job-native.yamlは、名前だけをnightly-settle-nativeに変えて、Podのアノテーションsidecar.istio.io/nativeSidecarを"true"で加えたものです。2つをそれぞれ注入して、/root/ist-lifecycle/job-injected.yamlと/root/ist-lifecycle/job-native-injected.yamlに保存し、違いを/root/ist-lifecycle/job-compare.tsvに、plainとnativeの2行で、<이름>\t<containers>\t<initContainers>\t<프록시의 restartPolicy>の形式で書いてください(プレースホルダーは名前とプロキシのrestartPolicyです。restartPolicyがなければ-を書きます)。/root/ist-lifecycle/inject-summary.shを作成して、このディレクトリの元のマニフェスト8枚(dep.yaml・dep-resources.yaml・dep-proxyconfig.yaml・dep-hold.yaml・dep-exclude.yaml・dep-nosidecar.yaml・job.yaml・job-native.yaml)を順にもう一度注入して、ファイルごとに<파일이름>\t<containers>\t<initContainers>\t<프록시 CPU 요청>を、その順序で標準出力にだけ出力するようにしてください(プレースホルダーはファイル名とプロキシのCPUリクエストです。ない列は-)。出力を/root/ist-lifecycle/inject-summary.tsvに保存してください。
参考
- 注入コマンドには、設定が3つ必要です。
--injectConfigFile、--meshConfigFile、--valuesFileです。このラボのイメージでは、/opt/istio/の下にあります。 - アノテーションは、Podテンプレートに付けます。Deploymentのメタデータに付けても、何も起こりません。
proxy.istio.io/configは、値の中にYAMLを入れるアノテーションなので、複数行ブロック(|)で書きます。- よくある間違い: プロキシを
containersの中だけで探すこと。ネイティブサイドカーは、initContainersにあります。 - よくある間違い: アノテーションの値の引用符を忘れること。
"false"は文字列でなければなりません。 - 参考: https://istio.io/v1.24/docs/reference/config/annotations/
- 参考: https://istio.io/v1.24/docs/setup/additional-setup/sidecar-injection/
- 参考: https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/
アノテーションが何もないとき、プロキシがどんな形で入るのか
/root/ist-lifecycleを作成して、/root/ist-lifecycle/dep.yamlに、lifecycleネームスペースのDeployment ordersを書いてください。replicasは2、ラベルはapp: orders、コンテナは1つ(app、イメージはnginx:1.27、コンテナポートは8080)で、アノテーションはありません。このファイルを注入して、結果を/root/ist-lifecycle/baseline.yamlに保存し、そこから読み取った4つを、/root/ist-lifecycle/baseline.tsvにcontainers・initContainers・proxyCpuRequest・proxyMemRequestの4行で書いてください(コンテナ名は、出てきた順にカンマでつなぎます)。
istiodがないので、注入設定の3つを自分で渡す必要があります。--injectConfigFile /opt/istio/inject-config.yaml --meshConfigFile /opt/istio/mesh-config.yaml --valuesFile /opt/istio/values-config.yamlです。結果で順序を見るときは、yq -N e '.spec.template.spec.containers[].name'が便利です。
プロキシのリソースを、アノテーションで調整する
/root/ist-lifecycle/dep-resources.yamlは、ステップ1のDeploymentを名前orders-resでコピーして、Podテンプレートのアノテーションを4つ加えたものです。sidecar.istio.io/proxyCPUは250m、sidecar.istio.io/proxyMemoryは192Mi、sidecar.istio.io/proxyCPULimitは1500m、sidecar.istio.io/proxyMemoryLimitは512Miです。注入結果を/root/ist-lifecycle/resources.yamlに保存してください。
アノテーションは、Podテンプレート(spec.template.metadata.annotations)に付ける必要があります。Deploymentのメタデータに付けても、何も起こりません。注入はPodを対象にするからです。デフォルト値からどれだけ変わったかを、ステップ1の結果と並べて見てください。
プロキシ自体の設定は、アノテーション1つにYAMLとして入れる
/root/ist-lifecycle/dep-proxyconfig.yamlは、名前orders-pcでコピーしたものに、proxy.istio.io/configアノテーションを1つ入れたものです。値は複数行のYAMLで、concurrency: 3とterminationDrainDuration: 45sの2つです。注入結果を/root/ist-lifecycle/proxyconfig.yamlに保存して、結果からistio-proxyの環境変数PROXY_CONFIGの値を/root/ist-lifecycle/proxy-config.jsonに保存してください。
このアノテーションは、値が文字列ですが、中にYAMLを入れます。複数行ブロック(|)で書いてください。インジェクターはこれをJSONに変えて、コンテナの環境変数1つに入れます。人が書く形式と、プロキシが読む形式が違うという点が核心です。
アプリケーションがプロキシより先に起動する競合を防ぐ
/root/ist-lifecycle/dep-hold.yamlは、名前orders-holdでコピーしたものに、proxy.istio.io/configアノテーションでholdApplicationUntilProxyStarts: trueだけを入れたものです。注入結果を/root/ist-lifecycle/hold.yamlに保存して、コンテナの順序と、プロキシにできたライフサイクルフックを、/root/ist-lifecycle/hold.tsvにcontainerOrderとpostStartの2行で書いてください(postStartの列には、フックが実行するコマンドを、空白でつないで書きます)。
この値を有効にすると、インジェクターが2つのことを変えます。プロキシをコンテナリストのどこに置くか、そして、プロキシに何を付けるかです。ステップ1の結果とコンテナの順序を比較すれば、すぐ見えます。フックは、.lifecycle.postStart.exec.commandにあります。
プロキシを通ってはいけないポートを除外しておく
/root/ist-lifecycle/dep-exclude.yamlは、名前orders-excでコピーしたものに、アノテーションtraffic.sidecar.istio.io/excludeOutboundPortsを"3306,5432"で入れたものです。注入結果を/root/ist-lifecycle/exclude.yamlに保存して、istio-initコンテナの引数を、空白でつないで/root/ist-lifecycle/init-args.txtに1行で書いてください。
出ていくトラフィックをプロキシに回す作業は、初期化コンテナがiptablesルールで行います。アノテーションは、そのコマンドラインの引数に降りてきます。どのフラグにそれらのポートが付くのかを探してみてください。データベースのプロトコルのように、プロキシが誤って解釈するかもしれないポートを除外するときに使うつまみです。
このPodだけプロキシを受け取らないようにする
/root/ist-lifecycle/dep-nosidecar.yamlは、名前orders-offでコピーしたものに、アノテーションsidecar.istio.io/injectを"false"で入れたものです。注入結果を/root/ist-lifecycle/nosidecar.yamlに保存してください。結果にプロキシがない必要があります。
注入のオンとオフを切り替えるつまみは、ネームスペースラベルとPodアノテーションの2つの層にあり、Pod側が優先されます。プロキシを通ってはいけないワークロード(大容量の転送、プロキシが理解できないプロトコル)を例外にするときに使う場所です。初期化コンテナも一緒になくなるかを、確認してみてください。
終わらないバッチ作業と、その解決策
/root/ist-lifecycle/job.yamlに、lifecycleネームスペースのJob nightly-settleを書いてください。Podラベルはapp: nightly-settle、restartPolicy: Never、コンテナは1つ(worker、イメージはbusybox:1.36)です。/root/ist-lifecycle/job-native.yamlは、名前だけをnightly-settle-nativeに変えて、Podのアノテーションsidecar.istio.io/nativeSidecarを"true"で加えたものです。2つをそれぞれ注入して、/root/ist-lifecycle/job-injected.yamlと/root/ist-lifecycle/job-native-injected.yamlに保存し、違いを/root/ist-lifecycle/job-compare.tsvに、plainとnativeの2行で、<이름>\t<containers>\t<initContainers>\t<프록시의 restartPolicy>の形式で書いてください(プレースホルダーは名前とプロキシのrestartPolicyです。restartPolicyがなければ-を書きます)。
通常のコンテナとして入ったプロキシは、作業が終わっても生き続けるので、Jobが完了に移れません。Kubernetes 1.28からは、初期化コンテナに再起動ポリシーを与えて、「先に起動し、最後まで生き続け、本体が終わると一緒に片付けられる」コンテナを作れます。アノテーションを有効にすると、プロキシがどちらのリストに移るかを見てください。
8枚を一度にもう一度注入して、表にまとめる
/root/ist-lifecycle/inject-summary.shを作成して、このディレクトリの元のマニフェスト8枚(dep.yaml・dep-resources.yaml・dep-proxyconfig.yaml・dep-hold.yaml・dep-exclude.yaml・dep-nosidecar.yaml・job.yaml・job-native.yaml)を順にもう一度注入して、ファイルごとに<파일이름>\t<containers>\t<initContainers>\t<프록시 CPU 요청>を、その順序で標準出力にだけ出力するようにしてください(プレースホルダーはファイル名とプロキシのCPUリクエストです。ない列は-)。出力を/root/ist-lifecycle/inject-summary.tsvに保存してください。
プロキシが通常のコンテナにあるときと、初期化コンテナにあるときの、両方を探す必要があります。2つのリストをつなげてたどれば、1つの式で済みます。こうした表をリポジトリに一緒に置いておけば、注入設定が変わった日に、違いがすぐに表面化します。