新しいコントロールプレーンを上げたのに、どのPodも移らなかった
目標
Istioのコントロールプレーンをリビジョン(revision)として2セット並べて立ち上げ、ネームスペースのラベルとdefaultタグで、
ワークロードを古いリビジョン(stable)から新しいリビジョン(canary)へ移したあと、共有リソースを守りながら古いリビジョンを撤去します。
インストールのカスタマイズ(IstioOperator)も、レンダリング結果で確認します。
なぜ重要なのか
in-placeのアップグレードは、コントロールプレーンを一度に入れ替えます。問題が起きると、メッシュ全体が一緒に揺れます。カナリアのアップグレードは、 新しいコントロールプレーンを隣に立てて、ネームスペース単位で移すので、元に戻しやすい反面、「今このネームスペースは、どの コントロールプレーンが注入するのか」を正確に知っている必要があります。現場でよく起きる事故は3つです。
- リビジョンだけをインストールしたところ、
istio-injection=enabledのネームスペースにサイドカーが付かない(defaultタグがない)。 istio.io/rev=canaryを付けたのに、古いラベルが残っていて、依然としてstableが注入する(istio-injectionが優先される)。- 古いリビジョンのマニフェストを丸ごと削除したところ、共有のCRDまで削除されて、VirtualServiceがすべて消える。
このラボ環境で本物のものとそうでないもの
このPodのクラスターはkwokです。APIサーバー・Webhookの選択・CRDは本物で、istiodのプロセスとコンテナはありません。
そのため、Podの作成を--dry-run=serverで要求すると、APIサーバーがネームスペースのラベルで注入Webhookを実際に選んで呼び出し、
istiodがなくて呼び出しが失敗するときに、どのWebhookがどのistiodのServiceへ向かおうとしたかをエラーメッセージに残します。
これを判定の根拠として使います。逆に、サイドカーが新しいistiodに実際につながるかどうかは、この環境では見られません
(それはVMのラボ「メッシュが実際に強制することを見る」で扱います)。2つのリビジョンは、同じistioctl 1.24.2で作ります。
ステップ
- IstioOperatorでカスタムのインストール設定を書いて、マニフェストをレンダリングします。
- リビジョンstableのコントロールプレーンを適用します。
- リビジョンのインストールだけでは、
istio-injection=enabledが注入されないことを確認し、defaultタグで解決します。 - 同じカスタマイズでcanaryのリビジョンを立ち上げ、インストールだけでは、どのネームスペースも移らないことを確認します。
- paymentsネームスペースをcanaryに移します(古いラベルの罠)。
- canaryの注入設定でオフライン注入して、サイドカーがどのコントロールプレーンにつながるように作られるかを見ます。
- defaultタグをcanaryに移して、legacyネームスペースをまとめて移します。
- 共有リソースを残して、stableだけを撤去します。
参考
- 作業ファイルは
/root/ica-upgrade/に置きます。注入Webhookの呼び出しは10秒の制限なので、dry-run 1回に10秒ほどかかります。 - Webhookの設定の読み取り:
kubectl get mutatingwebhookconfiguration <이름> -o yamlのnamespaceSelector・clientConfig.serviceです(プレースホルダーは名前です)。 - タグの一覧:
istioctl tag list。 - 公式ドキュメント: Canary Upgrades・ Customizing the installation configuration・ Installing the Sidecar・ istioctlコマンドリファレンス・ Dynamic Admission Control
インストールする前に、何が入るかを先に出力してみる
/root/ica-upgrade/stable.yamlにIstioOperatorを書いてください。profile: minimal、revision: stable、meshConfig.accessLogFile: /dev/stdout、istiod(pilot)コンテナのリクエスト量cpu: 250m・memory: 512Miです。そして、istioctl manifest generate -fでレンダリングした結果を、/root/ica-upgrade/stable-manifest.yamlに保存します。まだクラスターには適用しません。
IstioOperatorのspec.revisionは、コントロールプレーンのオブジェクト名のあとに付く目印になります。メッシュ全体の設定はspec.meshConfig、構成要素のKubernetes設定はspec.components.<구성요소>.k8sの下に置きます(プレースホルダーは構成要素名です)。レンダリング結果から、Deploymentの名前とリクエスト量、ConfigMapのmeshの設定をyqで探して、望んだ値が入っているかを確認してください。
リビジョンstableのコントロールプレーンを立ち上げる
ネームスペースistio-systemを作成し、stable-manifest.yamlをクラスターに適用します。Istio CRD、istiod-stableのDeploymentとService、istio-sidecar-injector-stableのWebhook設定ができている必要があります。
このクラスターはkwokなので、istiodのPodはRunningに見えますが、実際のプロセスはありません。それでも、APIサーバー・Webhookの設定・CRDは本物です。適用したあと、kubectl get mutatingwebhookconfiguration -o yamlで、Webhookがどのサービスを呼ぶか(clientConfig.service)、どのネームスペースのラベルを選ぶか(namespaceSelector)を読んでおいてください。次のステップの鍵になります。
istio-injection=enabledなのにサイドカーが付かない
ネームスペースshopにラベルistio.io/rev=stableを、ネームスペースlegacyにラベルistio-injection=enabledを付けます。Podの作成がどの注入Webhookへ向かうかを、kubectl -n <ns> run probe --image=registry.example.invalid/app:1 --restart=Never --dry-run=serverで確認して、出力(エラーを含む)を保存します。legacyの結果はprobe-legacy-before.txtに保存します。そのあと、istioctl tag generate default --revision stableの出力をtag-default.yamlとして保存して適用し、再びlegacyをprobe-legacy-after.txtに、shopをprobe-shop.txtに保存します(すべて/root/ica-upgrade/の下)。
リビジョン名が付いたインストールの注入Webhookは、istio.io/rev=<리비전>ラベルだけを選びます(プレースホルダーはリビジョンです)。従来の方式のistio-injection=enabledは、デフォルト(default)のリビジョンを意味しますが、リビジョンだけをインストールすると、その役割を担うWebhookがありません。defaultタグがその位置を埋めます。dry-run=serverのリクエストも変更Webhookを通るので、istiodがないこのクラスターでは、Webhookが呼ばれていれば、失敗メッセージにWebhook名とistiodのServiceのアドレスが記録され、呼ばれていなければ、ただcreatedと出ます。Webhookの呼び出しは10秒の制限なので、少し待ちます。
新しいコントロールプレーンを立ち上げたのに、誰も移っていない
/root/ica-upgrade/canary.yamlに、stableと同じカスタマイズ(accessLogFile・リクエスト量)でrevision: canaryのIstioOperatorを書き、レンダリングしたcanary-manifest.yamlを適用します。ネームスペースpayments(ラベルistio-injection=enabled)を作成し、アップグレードの途中でも既存の設定が生き残るかを見るためのサンプルとして、VirtualService payments/api(hosts [api]、destination host api)を作成して、そのuidを/root/ica-upgrade/sentinel-uid.txtに書きます。最後に、shopをもう一度dry-runして、probe-shop-after-canary.txtに保存します。新しいリビジョンをインストールするだけでは、shopが移らない必要があります。
2つのリビジョンは、名前の目印が違うので、並んで存在します。ネームスペースがどちらへ行くかは、インストールではなく、ラベルとタグが決めます。CRDは、リビジョンとは無関係に、クラスターに1つだけの共有リソースだという点も、2つのマニフェストを比較して確認してみてください。今は、defaultタグの検証Webhookが、Istioリソースの書き込みのたびに呼ばれる(失敗時は無視)ので、VirtualServiceの作成に10秒ほどかかります。
ラベルを変えたのに、依然として古いコントロールプレーンへ向かう
ネームスペースpaymentsをcanaryに移します。最終的に、paymentsにはistio.io/rev=canaryがあり、istio-injectionラベルはない必要があり、dry-runの結果がcanaryのistiodへ向かう必要があります。移したあとのdry-runの出力を、/root/ica-upgrade/probe-payments.txtに保存します。
istio.io/rev=canaryだけを追加して、dry-runをしてみてください。2つのラベルが一緒にあると、どのWebhookが勝つのか、つまり、リビジョンのWebhookのnamespaceSelectorで、istio-injectionに対する条件が何なのかが、答えです。公式のカナリアアップグレードのドキュメントも、同じ理由で古いラベルを消すように書いています。実際のクラスターなら、このあとにPodを再起動して初めて、新しいサイドカーが注入されます。
再起動して初めて移るという話は、どこに書かれているか
/root/ica-upgrade/api.yamlにDeployment api(ネームスペースpayments、レプリカ1つ、コンテナapi、イメージnginx:1.27)を書き、canaryのリビジョンの注入設定でistioctl kube-injectした結果を、/root/ica-upgrade/api-injected.yamlに保存します。注入設定は、クラスターのConfigMap istio-sidecar-injector-canary(キーconfig・values)とistio-canary(キーmesh)から取り出して、ファイルとして渡します。結果はクラスターに適用しません。
kube-injectは、デフォルトでistiodにつながって設定を受け取りますが、このクラスターにはistiodのプロセスがありません。代わりに、--injectConfigFile・--valuesFile・--meshConfigFileの3つのファイルをすべて渡せば、オフラインでレンダリングします。結果から、istio-proxyがどのアドレス(discoveryAddress)のコントロールプレーンにつながるように作られたかを探してみてください。その値はPodが作られるときに埋め込まれるので、すでに動いているPodは、ネームスペースのラベルを変えても、古いistiodにつながったままです。
古いラベルを使うチームまで、一度に移す
defaultタグがcanaryを指すように変えます(/root/ica-upgrade/tag-default-canary.yamlとして保存して適用)。ネームスペースlegacyのラベルには触れずに、legacyのdry-runがcanaryのistiodへ向かう必要があります。その出力を/root/ica-upgrade/probe-legacy-canary.txtに保存します。
タグは、ネームスペースのラベルとリビジョンの間にあるラベルです。ラベルを数十個のネームスペースで変える代わりに、タグが指すリビジョンだけを変えれば、そのタグを使っている場所がまとめて移ります。すでにあるタグを作り直すときに、istioctlが何を求めるかを、エラーメッセージを読んで確認してみてください。
古いコントロールプレーンを削除したら、設定まで消えるところだった
stableを撤去します。まず、まだstableを使っているネームスペース(shop)をcanaryに移し、stableのマニフェストにだけあるオブジェクトを削除します。2つのリビジョンが一緒に使うオブジェクト(Istio CRD、ServiceAccount istio-reader-service-account)は、削除してはいけません。CRDを削除すると、その種類のすべてのリソース(VirtualService payments/apiを含む)が一緒に消えます。削除したオブジェクトの一覧(종류/이름を1行ずつ)を、/root/ica-upgrade/retired.txtに残します(プレースホルダーは種類と名前です)。
2つのマニフェストから종류/이름の一覧を取り出して比較すれば(プレースホルダーは種類と名前です)、stableにだけあるものと、共有しているものが分かれます(yqで取り出してcommで比較)。このラボはkubectlで適用したので、同じマニフェストで元に戻します。公式ドキュメントのistioctl uninstall --revisionは、istioctlでインストールしたクラスターのためのコマンドですが、このクラスターで実測したときは、削除する対象を見つけられませんでした。削除したあと、shopのdry-runがcanaryへ向かうか、サンプルのVirtualServiceのuidがそのままかを確認してください。