好きな技術を布教したい 😗

〜 沼のまわりに餌をまく 〜

【Istio⛵️】安全なアップグレード手法の仕組み



01. はじめに

隠しません。

有吉弘行のサンデーナイトドリーマーが生きがいです。

さて今回は、Istioの安全なアップグレード手法の仕組みに関する記事を投稿しました🚀

執筆時点 (2023/02/26) では、IstioのIstiodコントロールプレーン (以降、Istiodとします) のアップグレード手法には、『インプレース方式』と『カナリア方式』があります。

また合わせてアップグレードが必要なIstioのIngressGatewayにも、その手法に『インプレース方式』と『カナリア方式』があります。

今回の安全なアップグレード手法として、Istiodでは『カナリアアップグレード』、IngressGatewayでは『インプレースアップグレード』を採用します。

istio_upgrade_list

それでは、Istioの安全なアップグレード手法の仕組みをもりもり布教しようと思います😗 (沼のまわりに餌をまく)

↪️ 参考:


02. Istioのアップグレード手法を説明する前に

カナリアリリースとは

Istiodのカナリアアップグレードが理解しやすくなるように、カナリアリリースから説明したいと思います。

カナリアリリースは、実際のユーザーにテストしてもらいながらリリースする手法です。

もしカナリアリリースをご存知の方は、 03. アップグレード手法の概要 まで飛ばしてください🙇🏻‍♂️


カナリアリリースの手順

カナリアリリースは、一部のユーザーを犠牲にすることになる一方で、アプリを実地的にテストできる点で優れています。

手順を交えながら説明します。

↪️ 参考:CanaryRelease

【1】

旧環境のアプリを残したまま、新環境をリリースします。

この段階では、全てのユーザー (100%) を旧環境にルーティングします。

canary-release_1

【2】

ロードバランサーで重み付けを変更し、一部のユーザー (ここでは10%) を新環境にルーティングします。

canary-release_2

【3】

ユーザーの手を借りて新環境を実地的にテストします (例:該当のエラーメトリクスが基準値を満たすか) 。

canary-release_3

【4】

新環境に問題が起こらなければ、重み付けを段階的に変更し、最終的には全てのユーザー (100%) を新環境にルーティングします。

canary-release_4


カナリアリリース』の呼称の由来

カナリアリリースについては、その呼称の由来を知ると、より理解が深まります。

カナリアリリースは、20世紀頃の炭坑労働者の危機察知方法に由来します。

炭鉱内には有毒な一酸化炭素が発生する場所がありますが、これは無色無臭なので、気づくことに遅れる可能性があります。

そこで当時の炭鉱労働者は、一酸化炭素に敏感な『カナリア』を炭鉱内に持ち込み、カナリアの様子から一酸化炭素の存在を察知するようにしていたそうです。

つまり、先の『犠牲になる一部のユーザー』が、ここでいうカナリアというわけです😨

canary_release_origin
画像引用:George McCaa, U.S. Bureau of Mines

↪️ 参考:


03. アップグレード手法の概要

手順

カナリアリリースについて理解したところで、Istioの安全なアップグレード手法の概要を説明します。

おおよそ以下の手順からなります。

【1】

旧Istiodが稼働しています。

istio_canary-upgrade_1

【2】

新Istiod (discoveryコンテナ) をインストールします。

istio_canary-upgrade_2

【3】

新Istiodのistio-proxyコンテナをインジェクションできるように、Webhookの宛先のServiceを変更します。

この手順は重要で、後のistioctl tag setコマンドの箇所で詳細を説明しています。

【4】

IngressGatewayをインプレースアップグレードします。

istio_canary-upgrade_4

【5】

一部のNamespaceで、istio-proxyコンテナをカナリアアップグレードします。

ここで、カナリアリリースのような重み付けがなく、カナリアアップグレードの『カナリア』という呼称に違和感を持つ方がいるかもしれません。

これについては、全てのNamespaceのistio-proxyコンテナを一斉にアップグレードするのではなく、段階的にアップグレードしていく様子を『カナリア』と呼称している、と個人的に推測しています。

もし『カナリアアップグレード』の由来をご存じの方は、教えていただきたいです🙇🏻‍♂️

istio_canary-upgrade_5

【6】

ユーザーの手を借りて、実地的にテストします (例:該当のエラーメトリクスが基準値以下を満たすか) 。

istio_canary-upgrade_6

【7】

新Istiodのistio-proxyコンテナに問題が起こらなければ、他のNamespaceでもistio-proxy コンテナを段階的にカナリアアップグレードしていきます。

一方でもし問題が起これば、Namespaceのistio-proxyコンテナとIngressGatewayをダウングレードします。

istio_canary-upgrade_7

【8】

最後に、旧Istiodをアンインストールします。

istio_canary-upgrade_8

↪️ 参考:Istio / Canary Upgrades


04. アップグレード手法の詳細

手順

ここからは、03. アップグレード手法の概要 を深ぼっていきます。

ヤサイニンニクアブラマシマシな説明になってしまったので、ここまでを食べ切れた方のみ進むことをお勧めします🥺

今回は、ドキュメントで一番優先して記載されているistioctlコマンドを使用した手順を説明します。

もちろん、他のツール (例:Helm、ArgoCD) を使用してもアップグレードできます。

細かな手順が異なるだけで、アップグレード手法の概要に関しては同じです🙆‍♂️

それでは、03. アップグレード手法の概要 の【1】〜【8】に対応させながら説明していくゾ。


前提

Namespace

まず最初に、前提となる状況を設定しておきます。

istio_canary-upgrade_rollout-restart_1

各Namespaceのistio.io/revラベルにdefaultが設定されているとします。

$ kubectl get namespace -L istio.io/rev

NAME              STATUS   AGE   REV
foo               Active   34d   default
bar               Active   34d   default
baz               Active   34d   default
istio-ingress     Active   34d   default

...

マニフェストに書き起こすと以下のようになっています。

apiVersion: v1
kind: Namespace
metadata:
  name: foo
  labels:
    istio.io/rev: default

エイリアスはどんな値でも問題なく、よくあるエイリアスとしてdefaultstableなどを使用します。

このistio.io/revラベルがあることで、そのNamespaceのPodにistio-proxyコンテナを自動的にインジェクションします。

istio-proxyコンテナのインジェクションについては、こちら記事で説明しており、もし気になる方はこちらもよろしくどうぞ🙇🏻‍♂️

Istiod

istio_canary-upgrade_rollout-restart_1

すでに1-14-6のIstiodが動いており、1-15-4カナリアアップグレードします。

IstiodはDeployment配下のPodであり、このPodはIstiodの実体であるdiscoveryコンテナを持ちます。

$ kubectl get deployment -n istio-system -l app=istiod

NAME                   READY   UP-TO-DATE   AVAILABLE   AGE
istiod-1-14-6          1/1     1            1           47s # 1-14-6

IngressGateway

IngressGatewayはIstiodとは異なるNamespaceで動いており、インプレースアップグレードします。

istio_canary-upgrade_rollout-restart_1

IngressGatewayはistio-proxyコンテナを持ちます。

$ kubectl get deployment -n istio-ingress

NAME                   READY   UP-TO-DATE   AVAILABLE   AGE
istio-ingressgateway   1/1     1            1           47s

補足として、セキュリティのベストプラクティスでは、IstiodとIngressGatewayは異なるNamespaceで動かすことが推奨されています。

↪️ 参考:Istio / Installing Gateways

マイクロサービス

各Namespaceでマイクロサービスが動いています。

マイクロサービスのPodはistio-proxyコンテナを持ちます。

$ kubectl get deployment -n foo

NAME   READY   UP-TO-DATE   AVAILABLE   AGE
foo    2/2     1            1           47s
...
$ kubectl get deployment -n bar

NAME   READY   UP-TO-DATE   AVAILABLE   AGE
bar    2/2     1            1           47s
..
$ kubectl get deployment -n baz

NAME   READY   UP-TO-DATE   AVAILABLE   AGE
baz    2/2     1            1           47s
...


【1】 アップグレード前の検証

ここで実施すること

アップグレード前に、現在のKubernetes Clusterがアップグレード要件を満たしているかを検証します。

↪️ 参考:Before you upgrade

istioctl x precheckコマンド

istioctl x precheckコマンドを実行し、アップグレード要件を検証します。

$ istioctl x precheck

✅ No issues found when checking the cluster.Istiois safe to install or upgrade!
  To get started, check out https://istio.io/latest/docs/setup/getting-started/

問題がなければ、istioctlコマンドはNo issue ...の文言を出力します。

もし、問題がある場合、istioctlコマンドはエラー文言を出力します。

例えば、Istioのistio-proxyコンテナのインジェクションではkube-apiserverと通信する必要があります。

そのため、kube-apiserverのバージョンが古すぎるせいでIstioが非対応であると、エラーになります。

kubectl getコマンド

▼ IstiodのDeployment

kubectl getコマンドを実行し、現在のIstiodのバージョンを確認します👀

まずはIstiodのDeploymentを確認すると、1-14-6のDeploymentがあります。

$ kubectl get deployment -n istio-system -l app=istiod

NAME                   READY   UP-TO-DATE   AVAILABLE   AGE
istiod-1-14-6          1/1     1            1           47s # 1-14-6

istio-proxyコンテナのインジェクションの仕組みでいうと、以下の赤枠の要素です👇

istio_canary-upgrade_webhook_1-1

▼ Webhookの宛先のService

次に、 Serviceを確認すると、1-14-6のServiceがあります。

$ kubectl get service -n istio-system -l app=istiod

NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                 AGE
istiod-1-14-6   ClusterIP   10.96.93.151     <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP   109s # 1-14-6

このServiceは、kube-apiserverからIstiodへのWebhookを仲介することにより、istio-proxyコンテナのインジェクションを可能にします。

istio-proxyコンテナのインジェクションの仕組みでいうと、以下の赤枠の要素です👇

istio_canary-upgrade_webhook_1-2

▼ 宛先のServiceを決めるMutatingWebhookConfiguration

最後に、MutatingWebhookConfigurationを確認すると、istio-revision-tag-<エイリアス>istio-sidecar-injector-<リビジョン番号>のMutatingWebhookConfigurationがあります。

$ kubectl get mutatingwebhookconfigurations

NAME                            WEBHOOKS   AGE
istio-revision-tag-default      2          114s  # カナリアアップグレード用
istio-sidecar-injector-1-14-6   2          2m16s # インプレースアップグレード用のため今回は言及しない

istio-proxyコンテナのインジェクションの仕組みでいうと、以下の赤枠の要素です👇

istio_canary-upgrade_webhook_1-3

これらのうち、前者 (istio-revision-tag-<エイリアス>) をカナリアアップグレードのために使用します。

このMutatingWebhookConfigurationは、Webhookの宛先のServiceを決めるため、結果的にistio-proxyコンテナのバージョンを決めます。

ここで、MutatingWebhookConfigurationのistio.io/revラベルとistio.io/tagラベルの値も確認しておきます。

$ kubectl get mutatingwebhookconfiguration istio-revision-tag-default -o yaml \
    | yq '.metadata.labels'

...

istio.io/rev: 1-14-6
istio.io/tag: default

...

istio.io/revラベルはIstiodのバージョン、istio.io/tagラベルはこれのエイリアスを表しています。

また、.webhooks[].namespaceSelectorキー配下のistio.io/revキーの検知ルールを確認します。

$ kubectl get mutatingwebhookconfiguration istio-revision-tag-default -o yaml \
    | yq '.webhooks[]'

...

namespaceSelector:
  matchExpressions:
    - key: istio.io/rev
      operator: In
      values:
        - default

...

合わせて、.webhooks[].clientConfig.serviceキー配下のServiceを名前を確認します。

$ kubectl get mutatingwebhookconfiguration istio-revision-tag-default -o yaml \
    | yq '.webhooks[].clientConfig'

...

service:
  name: istiod-1-14-6

...

整理すると、Namespaceでistio.io/revラベルにdefaultを設定しておけば、MutatingWebhookConfigurationがそれを検知し、特定のIstioのバージョンのServiceにWebhookを送信できるようになっています。

↪️ Istio / Safely upgrade the Istio control plane with revisions and tags


【2】 新Istiodのインストール

ここで実施すること

それでは、新Istiodをインストールします。

↪️ 参考:Control plane

istioctl versionコマンド

新しくインストールするIstiodのバージョンは、istioctlコマンドのバージョンで決まります。

そこで、istioctl versionコマンドを実行し、これのバージョンを確認します。

$ istioctl version

client version: 1.15.4        # アップグレード先のバージョン
control plane version: 1.14.6 # 現在のバージョン
data plane version: 1.14.6

istioctl installコマンド

カナリアアップグレードの場合、istioctl installコマンドを実行します。

ドキュメントではrevisionキーの値がcanaryですが、今回は1-15-4とします。

この値は、Istioが使用する様々なKubernetesリソースの接尾辞や、各種リソースのistio.io/revラベルの値になります。

$ istioctl install --set revision=1-15-4

WARNING: Istio is being upgraded from 1.14.6 -> 1.15.4
WARNING: Before upgrading, you may wish to use 'istioctl analyze' to check for IST0002 and IST0135 deprecation warnings.

✅ Istio core installed
✅ Istiod installed
✅ Ingress gateways installed
✅ Installation complete

Thank you for installing Istio 1.15.  Please take a few minutes to tell us about your install/upgrade experience!

kubectl getコマンド

▼ IstiodのDeployment

kubectl getコマンドを実行し、istioctl installコマンドで何をインストールしたのかを確認します👀

まずはIstiodのDeploymentを確認すると、1-15-4というDeploymentが新しく増えています。

$ kubectl get deployment -n istio-system -l app=istiod

NAME            READY   UP-TO-DATE   AVAILABLE   AGE
istiod-1-14-6   1/1     1            1           47s # 1-14-6
istiod-1-15-4   1/1     1            1           47s # 1-15-4

接尾辞の1-15-4は、revisionキーの値で決まります。

この段階では、旧Istiodと新Istioが並行的に稼働しており、kube-apiserverはまだ旧Istiodと通信しています

今の状況は以下の通りです👇

istio_canary-upgrade_webhook_2-1

▼ Webhookの宛先のService

次に Webhookの宛先のServiceを確認すると、istiod-1-15-4というServiceが新しく増えています。

$ kubectl get service -n istio-system -l app=istiod

NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                 AGE
istiod-1-14-6   ClusterIP   10.96.93.151     <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP   109s # 1-14-6
istiod-1-15-4   ClusterIP   10.104.186.250   <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP   87s  # 1-15-4

この段階では、まだWebhookの宛先はistiod-1-14-6のServiceです。

今の状況は以下の通りです👇

istio_canary-upgrade_webhook_2-2

▼ Webhookの宛先のServiceを決めるMutatingWebhookConfiguration

最後にMutatingWebhookConfigurationを確認すると、istio-sidecar-injector-1-15-4というMutatingWebhookConfigurationが新しく増えています。

$ kubectl get mutatingwebhookconfigurations

NAME                            WEBHOOKS   AGE
istio-revision-tag-default      2          114s  # カナリアアップグレードで使用する
istio-sidecar-injector-1-14-6   2          2m16s
istio-sidecar-injector-1-15-4   2          2m16s

カナリアアップグレードでは、istio-revision-tag-<エイリアス>のMutatingWebhookConfigurationを使用します。

今の状況は以下の通りです👇

istio_canary-upgrade_webhook_2-3

※ 実は、他にもインストールしているものがあるのですが、話をわかりやすくするために、今回は言及していません🙇🏻‍♂️


【3】 Webhookの宛先のServiceの変更

ここで実施すること

この手順では、エイリアスistio.io/tagラベルはそのままに、istio.io/revラベルの値を変更します。

さらに、Webhookの宛先のServiceを変更します。

↪️ 参考:

istioctl tag setコマンド

istioctl tag setコマンドを実行し、istio.io/revラベルの値と宛先のServiceを変更します。

$ istioctl tag set default --revision 1-15-4 --overwrite

実行後に、もう一度MutatingWebhookConfigurationを確認すると、istio.io/revラベルの値が変わっています。

$ kubectl get mutatingwebhookconfiguration istio-revision-tag-default -o yaml \
    | yq '.metadata.labels'

...

istio.io/rev: 1-15-4
istio.io/tag: default

...

また、Webhookの宛先のServiceも変わっています。

$ kubectl get mutatingwebhookconfiguration istio-revision-tag-default -o yaml \
    | yq '.webhooks[].clientConfig'

...

service:
  name: istiod-1-15-4

...

これらにより、Webhookの宛先が1-15-4のServiceとなるため、1-15-4istio-proxyコンテナをインジェクションできるようになります。

今の状況は以下の通りです👇

istio_canary-upgrade_webhook_3


【4】 IngressGatewayをインプレースアップグレード

ここで実施すること

Webhookの宛先が1-15-4のServiceに変わったところで、IngressGatewayをインプレースアップグレードします。

↪️ 参考:In place upgrade

kubectl rollout restartコマンド

kubectl rollout restartコマンドを実行し、IngressGatewayをインプレースアップグレードします。

$ kubectl rollout restart deployment istio-ingressgateway-n istio-ingress

再作成したPodのイメージを確認してみると、istio-proxyコンテナを1-15-4にアップグレードできています。

$ kubectl get pod bar -n bar -o yaml | yq '.spec.containers[].image'

docker.io/istio/proxyv2:1.15.4 # istio-proxyコンテナ

補足として、istioctl proxy-statusコマンドを使用して、アップグレードの完了を確認してもよいです。

今の状況は以下の通りです👇

istio_canary-upgrade_rollout-restart_2

なお、IngressGatewayのアップグレード時、マイクロサービスへのインバウンド通信が遮断されてしまうと思った方がいるかもしれません。

この点については、DeploymentがローリングアップグレードでIngressGatewayのPodを入れ替えるため、安心していただいて問題ありません🙆‍♂️


【5】 一部のNamespaceのistio-proxyコンテナをアップグレード

ここで実施すること

続けて、一部のNamespaceのistio-proxyコンテナをアップグレードします。

Podの再作成により、新Istiodのistio-proxyコンテナがインジェクションされるため。istio-proxyコンテナをアップグレードできます。

↪️ 参考:Data plane

kubectl rollout restartコマンド

前提にあるように、Namespaceには foo bar baz があります。

kubectl rollout restartコマンドを実行し、baristio-proxyコンテナからアップグレードします。

$ kubectl rollout restart deployment bar -n bar

再作成したPodのイメージを確認してみると、istio-proxyコンテナを1-15-4にアップグレードできています。

$ kubectl get pod bar -n bar -o yaml | yq '.spec.containers[].image'

bar-app:1.0 # マイクロサービス
docker.io/istio/proxyv2:1.15.4 # istio-proxyコンテナ

補足として、istioctl proxy-statusコマンドを使用して、アップグレードの完了を確認してもよいです。

今の状況は以下の通りです👇

istio_canary-upgrade_rollout-restart_3


【6】 ユーザの手を借りたテスト

ここで実施すること

Istioを部分的にアップグレードしたところで、アップグレードが完了したNamespaceをテストします。

ユーザーの手を借りて実地的にテストします (例:該当のエラーメトリクスが基準値を満たすか) 。

今の状況は以下の通りです👇

istio_canary-upgrade_rollout-restart_4

もし問題が起こった場合

もし問題が起こった場合、1-14-6にダウングレードしていきます。

istioctl tag setコマンドを実行し、istio.io/revラベルの値を元に戻します。

$ istioctl tag set default --revision 1-14-6 --overwrite

その後、kubectl rollout restartコマンドの手順を実行し、istio-proxyコンテナをダウングレードしてきます。


【7】 istio-proxyコンテナの段階的なアップグレード

ここで実施すること

先のNamespaceで問題が起こらなければ、残ったNamespace (foobaz、...) のistio-proxyコンテナも段階的にアップグレードしていきます。

kubectl rollout restartコマンド

同様にkubectl rollout restartコマンドを実行し、istio-proxyコンテナからアップグレードします。

$ kubectl rollout restart deployment foo -n foo

$ kubectl rollout restart deployment baz -n baz

...

最終的に、全てのNamespacemのistio-proxyコンテナが新しくなります。

今の状況は以下の通りです👇

istio_canary-upgrade_rollout-restart_5


【8】 旧Istiodのアンインストール

ここで実施すること

最後に、旧Istiodのアンインストールします。

↪️ 参考:Uninstall old control plane

istioctl uninstallコマンド

istioctl uninstallコマンドを実行し、旧Istiodをアンインストールします。

$ istioctl uninstall --revision 1-14-6

✅ Uninstall complete

今の状況は以下の通りです👇

istio_canary-upgrade_rollout-restart_6

kubectl getコマンド

▼ IstiodのDeployment

kubectl getコマンドを実行し、istioctl uninstallコマンドで何をアンインストールしたのかを確認します👀

まずはIstiodのDeploymentを確認すると、1-14-6というDeploymentが無くなっています。

$ kubectl get deployment -n istio-system -l app=istiod

NAME            READY   UP-TO-DATE   AVAILABLE   AGE
istiod-1-15-4   1/1     1            1           47s # 1-15-4

▼ Webhookの宛先のService

次に Webhookの宛先のServiceを確認すると、istiod-1-14-6というServiceが無くなっています。

$ kubectl get service -n istio-system -l app=istiod

NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                 AGE
istiod-1-15-4   ClusterIP   10.104.186.250   <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP   87s  # 1-15-4

▼ 宛先のServiceを決めるMutatingWebhookConfiguration

最後にMutatingWebhookConfigurationを確認すると、istio-sidecar-injector-1-14-6というMutatingWebhookConfigurationが無くなっています。

$ kubectl get mutatingwebhookconfigurations

NAME                            WEBHOOKS   AGE
istio-revision-tag-default      2          114s  # 次のカナリアアップグレードでも使用する
istio-sidecar-injector-1-15-4   2          2m16s

これで、新Istiodに完全に入れ替わったため、アップグレードは完了です。

今の状況は以下の通りです👇

istio_canary-upgrade_webhook_4

※ 実は、他にもアンインストールしているものがあるのですが、話をわかりやすくするために、今回は言及していません🙇🏻‍♂️


05. おわりに

Istioの安全なアップグレード手法の仕組みをもりもり布教しました。

Istioへの愛が溢れてしまいました。

Istioのアップグレードの異常がシステムに与える影響力は非常に大きく、様々な問題 (体験談:istio-proxyコンテナのPodへのインジェクションがずっと完了せず、アプリコンテナを作成できない) が起こる可能性があります😇

これからIstioを採用予定の方は、Istioを安全にアップグレードするために十分に準備しておくことをお勧めします👍