Managed Anthos Service Mesh の Proxy Injection の仕組み

(Qiita Advent Calendar - GCP(Google Cloud Platform) Advent Calendar 2022 の 5 日目の記事です)

Google Cloud が提供している Managed Anthos Service Mesh(以下、Managed ASM)は、Istio をベースとしたサービスメッシュのマネージドサービスです。
最近は、Managed Data Plane という Istio-proxy の自動アップグレードオプションも登場してきています。

今回は、Managed ASM や Managed Data Plane がどのようにバージョンをコントロールしているのか、GKE に展開されているカスタムリソースから分かる情報や、環境を構築してみて得られた知見をいくつかご紹介します。
当記事内の情報は公式ドキュメントに仕様として明記されているものではなく、あくまで実機のカスタムリソースの定義などからいちユーザが推測したものとなりますのでご承知おきください。
そもそもマネージドであるがゆえ、できる限りユーザがその挙動を知らなくても良いようにしてくれているものではあるのですが、裏で動いている仕組みを推測・理解しておくことでトラブルシューティングなどの際に役立つこともあるかと思います。

GKE Autopilot + Managed ASM + Managed Data Plane による Kubernetes, Istio 環境をセットアップし、アプリケーションの Pod 起動時に ASM の Data Plane として Istio-proxy が自動インジェクションされるところまでを 前回記事 でご紹介していますので、ご興味のある方はこちらもご参照ください。

おさらい

前回記事の最後では、Istio-proxy の自動インジェクションを Namespace へのラベル付与で有効化し、サンプルアプリケーションとして httpd の Pod を起動した結果、ASM の Data Plane である Istio-proxy がサイドカーとして Pod に挿入されていることを確認しました。
以下のとおり、コンテナイメージの情報を取得してみると、インジェクションされた Istio-proxy のバージョンは 1.14.5-asm.3 だとわかります。

$ kubectl get pod httpd -o jsonpath='{.spec.containers[*].image}' | tr ' ' '\n'
httpd
gcr.io/gke-release/asm/proxyv2:1.14.5-asm.3

Istio-proxy のバージョンの決定

Managed ASM の場合、公式ドキュメントによれば、Istio-proxy のバージョンは ASM のリリースチャネルに沿って決定されるとあります。
Managed ASM のリリースチャネルは、GKE のリリースチャネルとは独立していますが、似たようなコンセプトで Rapid, Regular, Stable の 3 種類のチャネルが提供されています。

cloud.google.com

記事執筆時点では、Regular Channel のバージョンは ASM 1.14 となっていました。
なお、ASM のセマンティックバージョニング部分の表記は基本的に Istio のバージョンと一致しています。
Istio のバージョンに -asm**サフィックスを付与したものが ASM の完全なバージョン表記となります。

上記リンクで紹介したように、各リリースチャネルに対応した ASM のバージョンはドキュメントから確認することができますが、挿入されるはずのバージョンを実機から確認したいというケースもあるでしょう。
各チャネルに対応する現在のバージョンは、以下のコマンドを実行することで実機から確認することができます。

$ kubectl get dataplanecontrols.mesh.cloud.google.com -n istio-system
NAME                       REVISION             VERSION        TARGETBASISPOINTS   STATUS   AGE
asm-managed-9kxsf          asm-managed          1.14.5-asm.6   10000               Ready    3d1h
asm-managed-rapid-7zclm    asm-managed-rapid    1.15.3-asm.1   10000               Ready    3d1h
asm-managed-stable-9wss4   asm-managed-stable   1.13.9-asm.2   10000               Ready    3d1h

$ kubectl describe dataplanecontrols.mesh.cloud.google.com asm-managed-9kxsf
Name:         asm-managed-9kxsf
Namespace:
Labels:       <none>
Annotations:  <none>
API Version:  mesh.cloud.google.com/v1alpha1
Kind:         DataPlaneControl
Metadata:
  Creation Timestamp:  2022-11-26T12:29:07Z
  Generate Name:       asm-managed-
  Generation:          1
  Managed Fields:
    API Version:  mesh.cloud.google.com/v1alpha1
    Fields Type:  FieldsV1
    fieldsV1:
      f:metadata:
        f:generateName:
      f:spec:
        .:
        f:proxyTargetBasisPoints:
        f:proxyVersion:
        f:revision:
    Manager:      Google-GKEHub-Controllers-Servicemesh
    Operation:    Update
    Time:         2022-11-26T12:29:07Z
    API Version:  mesh.cloud.google.com/v1alpha1
    Fields Type:  FieldsV1
    fieldsV1:
      f:status:
        .:
        f:observedGeneration:
        f:proxyMetrics:
        f:proxyTargetBasisPoints:
        f:state:
    Manager:         mdp
    Operation:       Update
    Subresource:     status
    Time:            2022-11-26T12:32:15Z
  Resource Version:  28883
  UID:               ed9323f2-a420-4bc3-976a-13c70fd0d906
Spec:
  Proxy Target Basis Points:  10000
  Proxy Version:              1.14.5-asm.6
  Revision:                   asm-managed
Status:
  Observed Generation:  1
  Proxy Metrics:
  Proxy Target Basis Points:  10000
  State:                      Ready
Events:                       <none>

dataplanecontrols は、ASM のために Google が提供しているカスタムリソースです。
metadata.managedFields を見る限り、各リリースチャネルの ASM バージョンは Google-GKEHub-Controllers-Servicemesh というサービスが更新しているようですね。
他にも、mdp(Managed Data Plane の略でしょうか)というサービスがこのリソースを共同所有していることがわかります。

デフォルトでは、Regular Channel を示す asm-managed のバージョンが適用されるため、1.14.5 の istio-proxy が挿入されたことがわかります。
ASM 固有のリビジョンまで細かく見ていくと、httpd に挿入されている Istio-proxy のバージョンが 1.14.5-asm.3 であるのに対し、dataplanecontrols の asm-managed は 1.14.5-asm.6 となっています。
httpd の Pod をデプロイしてから asm-managed のリビジョンに更新が入ったのかとも考えましたが、新たに別の Pod を展開しても 1.14.5-asm.3 の Istio-proxy が挿入されました。

$ kubectl get dataplanecontrols.mesh.cloud.google.com -n istio-system
NAME                       REVISION             VERSION        TARGETBASISPOINTS   STATUS   AGE
asm-managed-9kxsf          asm-managed          1.14.5-asm.6   10000               Ready    3d1h
asm-managed-rapid-7zclm    asm-managed-rapid    1.15.3-asm.1   10000               Ready    3d1h
asm-managed-stable-9wss4   asm-managed-stable   1.13.9-asm.2   10000               Ready    3d1h

$ kubectl run httpd-alt --image httpd
Warning: Autopilot set default resource requests on Pod default/httpd-alt for container httpd-alt, as resource requests were not specified, and adjusted resource requests to meet requirements. See http://g.co/gke/autopilot-defaults and http://g.co/gke/autopilot-resources.
pod/httpd-alt created

$ kubectl get pod
NAME        READY   STATUS    RESTARTS   AGE
httpd       2/2     Running   0          12h
httpd-alt   2/2     Running   0          2m27s

$ kubectl get pod httpd-alt -o jsonpath='{.spec.containers[*].image}' | tr ' ' '\n'
httpd
gcr.io/gke-release/asm/proxyv2:1.14.5-asm.3

インジェクション後にマネージドコントロールプレーンのバージョンが上がった場合は、「マネージド コントロール プレーンがアップグレードされてから 1〜2 週間後に完了します」と公式ドキュメントに記載があるため、一時的にバージョンが一致しない期間ができるとは想定していました。
一方で、新規にインジェクションする場合は基本的に dataplanecontrols の Proxy Version が適用されるものと想像していたため、これは意外な結果となりました。

上記の結果からすると、どうやら dataplanecontrols のリソースに定義されているバージョンは、実際に自動でインジェクションされる Istio-proxy のバージョンとは完全には連動していないようです。
あるいは、ASM 固有のリビジョンでは更新がかからず、Istio のマイナーバージョンやパッチバージョンに変更が発生した場合にのみ更新がかかる、という条件になっているのかもしれません。

cloud.google.com

ASM における Proxy Injection の実装先

では、実際に Istio-procy をインジェクションする際のバージョンを決定する仕組みはどこに実装されているのでしょうか。
自動インジェクションのメカニズムは Mutating Webhook によって実装されているため、Webhook の定義を確認してみましょう。
Webhook の名前からして、istio-revision-tag-default というリソースがそれらしいと推測できます。

$ kubectl get mutatingwebhookconfigurations.admissionregistration.k8s.io
NAME                                                                  WEBHOOKS   AGE
admissionwebhookcontroller.config.common-webhooks.networking.gke.io   1          3d2h
filestorecsi-mutation-webhook.storage.k8s.io                          1          3d2h
gke-vpa-webhook-config                                                1          3d2h
istio-revision-tag-default                                            4          3d1h
istiod-asm-managed                                                    2          3d1h
mutate-scheduler-profile.config.common-webhooks.networking.gke.io     1          3d2h
neg-annotation.config.common-webhooks.networking.gke.io               1          3d2h
pod-ready.config.common-webhooks.networking.gke.io                    1          3d2h
sasecret-redacter.config.common-webhooks.networking.gke.io            1          3d2h
workload-defaulter.config.common-webhooks.networking.gke.io           1          3d2h

$ kubectl describe mutatingwebhookconfigurations.admissionregistration.k8s.io istio-revision-tag-default
Name:         istio-revision-tag-default
Namespace:
Labels:       app=sidecar-injector
              istio.io/owned-by=mesh.googleapis.com
              istio.io/rev=asm-managed
              istio.io/tag=default
Annotations:  <none>
API Version:  admissionregistration.k8s.io/v1
Kind:         MutatingWebhookConfiguration
Metadata:
  Creation Timestamp:  2022-11-26T12:31:43Z
  Generation:          1
  Managed Fields:
    API Version:  admissionregistration.k8s.io/v1
    Fields Type:  FieldsV1
    fieldsV1:
        ... (略) ...
    Manager:         Google-GKEHub-Controllers-Servicemesh
    Operation:       Update
    Time:            2022-11-26T12:31:43Z
  Resource Version:  28506
  UID:               ae84eef6-0371-4375-8979-2c268a513feb
Webhooks:
  Name:            rev.namespace.sidecar-injector.istio.io
  ... (略) ...
  Name:            rev.object.sidecar-injector.istio.io
  ... (略) ...
  Admission Review Versions:
    v1beta1
    v1
  Client Config:
    URL:           https://asm-autopilot-cluster-asm-managedtiwu6ybzqbglr-n354hllh2a-uc.a.run.app:443/inject/ISTIO_META_CLOUDRUN_ADDR/asm-autopilot-cluster-asm-managedtiwu6ybzqbglr-n354hllh2a-uc.a.run.app:443
  Failure Policy:  Fail
  Match Policy:    Equivalent
  Name:            namespace.sidecar-injector.istio.io
  Namespace Selector:
    Match Expressions:
      Key:       istio-injection
      Operator:  In
      Values:
        enabled
  Object Selector:
    Match Expressions:
      Key:       sidecar.istio.io/inject
      Operator:  NotIn
      Values:
        false
  Reinvocation Policy:  Never
  Rules:
    API Groups:

    API Versions:
      v1
    Operations:
      CREATE
    Resources:
      pods
    Scope:          *
  Side Effects:     None
  Timeout Seconds:  10
  ... (略) ...
  Name:            object.sidecar-injector.istio.io
  ... (略) ...
Events:             <none>

(今回はリビジョン指定なしの istio-injection ラベルを Namespace に付与して自動インジェクションを利用しているため、kubectl describe の結果は対応する Webhook の namespace.sidecar-injector.istio.io だけを抜粋しています)

各 webhook の Client Config の URL を見るに、Managed ASM の Istio-proxy の自動インジェクションは Cloud Run で実装されていることがわかります。
したがって、ASM の自動インジェクションはクラスタに展開された CRD と Webhook によって、Cloud Run を呼び出すことで Istio-proxy のバージョンを決定し、Pod にサイドカーとしてインジェクションしている、といった実装になっているものと考えられます。
dataplanecontrols の ASM のバージョン定義が更新されると、恐らく Cloud Run 側にも反映され、いずれ更新後のバージョンの Istio-proxy がインジェクションされるようになると思われますが、どうやら dataplanecontrols の更新の Cloud Run への反映はリアルタイムでは行われない実装になっていると思われます。

自動インジェクションの実装からわかる Managed ASM 利用時の注意点

上記の調査結果からわかる、ユーザが気をつけるべき点としては、自動インジェクション発生時に Cloud Run のコールドスタートの影響を受ける可能性がある、ということが挙げられます。
Webhook の呼び出し先の Cloud Run は一定時間呼び出しがなければインスタンスの数がゼロにスケールインすることができるため、下記のように自動インジェクションが失敗することで Pod の起動に失敗する可能性があります。
CI / CD パイプラインなど自動化の仕組みで Pod を展開する場合には、この事象が発生する可能性を考慮してリカバリの仕組みを備えておく必要がありそうです。

$ kubectl run httpd --image httpd
Error from server (InternalError): Internal error occurred: failed calling webhook "namespace.sidecar-injector.istio.io": failed to call webhook: Post "https://asm-autopilot-cluster-asm-managedtiwu6ybzqbglr-n354hllh2a-uc.a.run.app:443/inject/ISTIO_META_CLOUDRUN_ADDR/asm-autopilot-cluster-asm-managedtiwu6ybzqbglr-n354hllh2a-uc.a.run.app:443?timeout=10s": context deadline exceeded

なお、最近の Cloud Run には、startup CPU boost や Always on CPU など、コールドスタートの影響を低減あるいは解消するためのアップデートも出てきていますので、今後は無視できるレベルになる可能性も期待できそうですね。

GKE Autopilot 利用時の追加の注意点

今回の検証には GKE Autopilot を利用しました。
GKE Autopilot では、ゼロにスケールした(クラスタに割り当てられたノードが存在しない)状態でも Pod の作成リクエストが成功するため、リクエストを受け付けてから実際にノードが割り当てられるまでの時間が一定程度かかった場合、結果的に Pod のスケジューリング先がないものとして失敗してしまうケースがあります。
Managed ASM と併せて GKE Autopilot を利用する場合はこちらも注意が必要ですね。

$ kubectl run httpd --image httpd
Warning: Autopilot set default resource requests on Pod default/httpd for container httpd, as resource requests were not specified, and adjusted resource requests to meet requirements. See http://g.co/gke/autopilot-defaults and http://g.co/gke/autopilot-resources.
pod/httpd created

$ kubectl get po -w
NAME    READY   STATUS     RESTARTS   AGE
httpd   0/2     Init:0/1   0          11s
httpd   0/2     Init:0/1   0          25s
httpd   0/2     Init:Error   0          33s
httpd   0/2     Init:0/1     1 (6s ago)   35s
httpd   0/2     Terminating   1 (6s ago)   35s

$ kubectl get po
No resources found in default namespace.

まとめ

前回記事の補完的な位置付けで、Managed ASM の自動インジェクションの仕組みを調べました。
クラスタに展開されたカスタムリソースや Webhook の定義から仕組みを推測し、実機の調査を通じて、利用時にユーザが注意すべきいくつかのポイントをご紹介しました。
普段ユーザが考慮すべき設計・運用の要素を低減してくれるという点でとても便利なマネージドサービスですが、その仕様を実機の設定や挙動から把握しておくことで、より上手に使っていくことができるのではと思います。