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

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

【Istio⛵️】サイドカーインジェクションの仕組み

目次


01. はじめに

どーも。

正月で激太りしましたが、ダイエットの予定はありません🙋🏻‍♂️

今回は、サービスメッシュを実装するIstio⛵️のサイドカーインジェクションに関する記事を投稿しました。

前回の記事に引き続きIstioです。

hiroki-hasegawa.hatenablog.jp

執筆時点(2023/01/14)では、Istioが実装するサービメッシュには、『サイドカープロキシメッシュ』と『アンビエントメッシュ』があります。

サイドカープロキシメッシュの仕組みの軸になっているものは、サイドカーコンテナであるistio-proxyコンテナです。

Istioは、KubernetesのPodの作成時に、istio-proxyコンテナをPod内に自動的にインジェクション(注入)します

本記事では、このサイドカーのインジェクションの仕組みをもりもり布教しようと思います😗(沼のまわりに餌をまく)


02. サイドカーによるサービスメッシュ

なぜサイドカーが必要なのか

そもそも、なぜサービスメッシュでサイドカーが必要になったのでしょうか。

マイクロサービスアーキテクチャのシステムには、アーキテクチャ固有のインフラ領域の問題(例:サービスディスカバリーの必要性、マイクロサービス間通信の暗号化、テレメトリー収集、など)があります。

アプリエンジニアが各マイクロサービス内にインフラ領域の問題に関するロジックを実装すれば、これらの問題の解決できます。

service-mesh_layer

しかし、アプリエンジニアはアプリ領域の問題に責務を持ち、インフラ領域の問題はインフラエンジニアで解決するようにした方が、互いに効率的に開発できます。

そこで、インフラ領域の問題を解決するロジックをサイドカーとして切り分けます。

service-mesh_sidecar

これにより、アプリエンジニアとインフラエンジニアの責務を分離できるようになります。

また、インフラ領域の共通ロジックをサイドカーとして各マイクロサービスに提供できるため、単純性が高まります。

こういった流れの中で、サイドカーを使用したサービスメッシュが登場しました。

ℹ️ 参考:


サイドカープロキシメッシュ

Istioのサイドカーによるサービスメッシュ(サイドカープロキシメッシュ)は、

  • サイドカーコンテナ(istio-proxyコンテナ)が稼働するデータプレーン
  • サイドカーを中央集権的に管理するIstiod(discoveryコンテナ)が稼働するコントロールプレーン

からなります。

istio_sidecar-mesh_architecture

ℹ️ 参考:Istio / Architecture


03. admission-controllersアドオンについて

admission-controllersアドオンとは

IstioのPod内へのサイドカーインジェクションの前提知識として、admission-controllersアドオンを理解する必要があります。

もし、admission-controllersアドオンをご存知の方は、04. サイドカーインジェクションの仕組み まで飛ばしてください。

kube-apiserverでは、admission-controllersアドオンとして有効化できます。

有効化すると、認証ステップと認可ステップの後にmutating-admissionステップとvalidating-admissionステップを実行でき、admissionプラグインの種類に応じた処理を挿入できます。

クライアント(kubectlクライアント、Kubernetesリソース)からのリクエスト(例:Kubernetesリソースに対する作成/更新/削除、kube-apiserverからのプロキシへの転送)時に、各ステップでadmissionプラグインによる処理(例:アドオンビルトイン処理、独自処理)を発火させられます。

kubernetes_admission-controllers_architecture

ℹ️ 参考:


admissionプラグインの種類

admission-controllersアドオンのadmissionプラグインには、たくさんの種類があります。

IstioがPod内にサイドカーをインジェクションする時に使用しているアドオンは、『MutatingAdmissionWebhook』です。

  • CertificateApproval
  • CertificateSigning
  • CertificateSubjectRestriction
  • DefaultIngressClass
  • DefaultStorageClass
  • DefaultTolerationSeconds
  • LimitRanger
  • MutatingAdmissionWebhook 👈 これ!
  • NamespaceLifecycle
  • PersistentVolumeClaimResize
  • PodSecurity
  • Priority
  • ResourceQuota
  • RuntimeClass
  • ServiceAccount
  • StorageObjectInUseProtection
  • TaintNodesByCondition
  • ValidatingAdmissionWebhook

ℹ️ 参考:Admission Controllers Reference | Kubernetes


MutatingAdmissionWebhookプラグイン

MutatingAdmissionWebhookプラグインとは

MutatingAdmissionWebhookプラグインを使用すると、mutating-admissionステップ時に、リクエスト内容を変更する処理をフックできます。

フックする具体的な処理として、webhookサーバーにAdmissionRequestリクエストとして送信することにより、レスポンスのAdmissionResponseに応じてリクエスト内容を動的に変更します。

MutatingWebhookConfigurationで、MutatingAdmissionWebhookプラグインの発火条件やwebhookサーバーの宛先情報を設定します。

MutatingWebhookConfigurationの具体的な実装については、サイドカーインジェクションの仕組みの中で説明していきます。

admission-controllers_mutating-admission

ℹ️ 参考:

AdmissionReview、AdmissionRequest、AdmissionResponse

▼ AdmissionReview

AdmissionReviewは以下のようなJSONであり、kube-apiserverとwebhookサーバーの間でAdmissionRequestとAdmissionResponseを運びます。

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  # AdmissionRequest
  "request": {},
  # AdmissionResponse
  "response": {}  
}

ℹ️ 参考:v1 package - k8s.io/api/admission/v1 - Go Packages

▼ AdmissionRequest

AdmissionRequestは以下のようなJSONです。

kube-apiserverがクライアントから受信した操作内容が持つことがわかります。

例で挙げたAdmissionRequestでは、クライアントがDeploymentをCREATE操作するリクエストをkube-apiserverに送信したことがわかります。

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  # AdmissionRequest
  "request": {

    ...

    # 変更されるKubernetesリソースの種類を表す。
    "resource": {
      "group": "apps",
      "version": "v1",
      "resource": "deployments"
    },
    # kube-apiserverの操作の種類を表す。
    "operation": "CREATE",

    ...

  }
}

ℹ️ 参考:Dynamic Admission Control | Kubernetes

▼ AdmissionResponse

一方でAdmissionResponseは、例えば以下のようなJSONです。

AdmissionResponseに応じたマニフェスト変更処理をpatchキーの値に持ち、これはbase64方式でエンコードされています。

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  # AdmissionResponse
  "response": {
    "uid": "<value from request.uid>",
    # 宛先のwebhookサーバーが受信したか否かを表す。
    "allowed": true,
    # PathによるPatch処理を行う。
    "patchType": "JSONPatch",
    # Patch処理の対象となるKubernetesリソースと処理内容を表す。base64方式でエンコードされている。
    "patch": "W3sib3AiOiAiYWRkIiwgInBhdGgiOiAiL3NwZWMvcmVwbGljYXMiLCAidmFsdWUiOiAzfV0="
  }
}

エンコード値をデコードしてみると、例えば以下のようなpatch処理が定義されています。

# patchキーをbase64方式でデコードした場合
[
  {
    "op": "add",
    "path": "/spec/replicas",
    "value": 3
  }
]

マニフェストに対する操作(op)、キー(path)、値(value)が設定されています。

kube-apiserverがこれを受信すると、指定されたキー(.spec.replicas)に値(3)に追加します。

ℹ️ 参考:Dynamic Admission Control | Kubernetes


04. サイドカーインジェクションの仕組み

全体のフロー

前提知識を踏まえた上で、admission-controllersアドオンの仕組みの中で、サイドカーistio-proxyコンテナがどのようにPodにインジェクションされるのかを見ていきましょう。

最初に、サイドカーインジェクションのフローは以下の通りになっています。

画像の文字が小さくなってしまったため、拡大していただけると🙇🏻‍♂️

istio_container-injection_flow

ℹ️ 参考:Amazon.co.jp: Istio in Action (English Edition) 電子書籍: Posta, Christian E., Maloku, Rinor: 洋書


クライアント ➡︎ kube-apiserver

ここで説明するフロー箇所

『クライアント ➡︎ kube-apiserver』の箇所を説明します。

istio_container-injection_flow_red_1

(1)Podの作成をリクエス

まずは、クライアントがkube-apiserverにリクエストを送信するところです。

クライアント(Deployment、DaemonSet、StatefulSet、を含む)は、Podの作成リクエストをkube-apiserverに送信します。

この時のリクエスト内容は、以下の通りとします。

# Podを作成する。
$ kubectl apply -f foo-pod.yaml
# foo-pod.yamlファイル
apiVersion: v1
kind: Pod
metadata:
  name: foo-pod
  namespace: foo-namespace
spec:
  containers:
    - name: foo
      image: foo:1.0.0
      ports:
        - containerPort: 80

またNamespaceでは、あらかじめistio-proxyコンテナのインジェクションが有効化されているとします。

Istioではv1.10以降、リビジョンの番号のエイリアスを使用して、istio-proxyコンテナのインジェクションを有効化するようになりました。

エイリアスの名前は自由であり、私の携わっているプロジェクトではstableを使用しています。

apiVersion: v1
kind: Namespace
metadata:
  name: foo-namespace
  labels:
    # istio-proxyコンテナのインジェクションを有効化する。
    # エイリアス名は自由
    istio.io/rev: <エイリアス>

ℹ️ 参考:Istio / Announcing Support for 1.8 to 1.10 Direct Upgrades


kube-apiserver ➡︎ Service

ここで説明するフロー箇所

『kube-apiserver ➡︎ Service』の箇所を説明します。

istio_container-injection_flow_red_2

(2)認証認可処理をコール

kube-apiserverは、認証ステップと認可ステップにて、クライアントからのリクエストを許可します。

(3)アドオンの処理をコール

kube-apiserverは、mutating-admissionステップにて、MutatingAdmissionWebhookプラグインの処理をコールします。

前提知識の部分で具体的な実装を省略しましたが、Istioのバージョン1.14.3時点で、MutatingWebhookConfigurationは以下のようになっています。

Namespaceでサイドカーインジェクションを有効化する時に使用したエイリアスは、このMutatingWebhookConfigurationで実体のリビジョン番号と紐づいています。

$ kubectl get mutatingwebhookconfiguration istio-sidecar-injector-<リビジョン番号> -o yaml
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
  name: istio-sidecar-injector-<リビジョン番号>
  labels:
    app: sidecar-injector
    # エイリアスの実体
    istio.io/rev: <リビジョン番号>
    # リビジョン番号のエイリアス
    istio.io/tag: <エイリアス>
webhooks:
  - name: rev.namespace.sidecar-injector.istio.io
    # MutatingAdmissionWebhookプラグインの処理の発火条件を登録する。
    rules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE"]
        resources: ["pods"]
        scope: "*"
    # Webhookの前段にあるServiceの情報を登録する。
    clientConfig:
      service:
        name: istiod-<リビジョン番号>
        namespace: istio-system
        path: "/inject" # エンドポイント
        port: 443
      caBundle: Ci0tLS0tQk ...
    # Namespace単位のサイドカーインジェクション
    # 特定のNamespaceでMutatingAdmissionWebhookプラグインの処理を発火させる。
    namespaceSelector:
      matchExpressions:
        - key: istio.io/rev
          operator: DoesNotExist
        - key: istio-injection
          operator: DoesNotExist
    # Pod単位のサイドカーインジェクション
    # 特定のオブジェクトでMutatingAdmissionWebhookプラグインの処理を発火させる。
    objectSelector:
      matchExpressions:
        - key: sidecar.istio.io/inject
          operator: NotIn
          values:
            - "false"
        - key: istio.io/rev
          operator: In
          values:
            - <エイリアス>

    ...

MutatingWebhookConfigurationには、MutatingAdmissionWebhookプラグインの発火条件やwebhookサーバーの宛先情報を定義します。

MutatingAdmissionWebhookプラグインの発火条件に関して、例えばIstioでは、NamespaceやPod.metadata.labelsキーに応じてサイドカーインジェクションの有効化/無効化を切り替えることができ、これをMutatingAdmissionWebhookプラグインで制御しています。

webhookサーバーの宛先情報に関して、Istioではwebhookサーバーの前段にServiceを配置しています。

MutatingAdmissionWebhookプラグインが発火した場合、Serviceの/inject:443HTTPSプロトコルのリクエストを送信するようになっています。

一方で発火しなかった場合には、以降のAdmissionReviewの処理には進みません。

(4)AdmissionRequestに値を詰める

kube-apiserverは、mutating-admissionステップにて、クライアントからのリクエスト内容(Podの作成リクエスト)をAdmissionReveiew構造体のAdmissionRequestに詰めます。

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  # AdmissionRequest
  "request": {

    ...

    # 変更されるKubernetesリソースの種類を表す。
    "resource": {
      "group": "core",
      "version": "v1",
      "resource": "pods"
    },
    # kube-apiserverの操作の種類を表す。
    "operation": "CREATE",

    ...

  }
}

(5)AdmissionReviewを送信

kube-apiserverは、mutating-admissionステップにて、Serviceの/inject:443にAdmissionReview構造体を送信します。


Service ➡︎ webhookサーバー

ここで説明するフロー箇所

『Service ➡︎ webhookサーバー』の箇所を説明します。

istio_container-injection_flow_red_3

(6)15017番ポートにポートフォワーディング

istio_admission-controllers_mutating-admission

Serviceは、/inject:443でリクエストを受信し、discoveryコンテナの15017番ポートにポートフォワーディングします。

Istioのバージョン1.14.3時点で、Serviceは以下のようになっています。

$ kubectl get svc istiod-service -n istio-system -o yaml
apiVersion: v1
kind: Service
metadata:
  namespace: istio-system
  name: istiod-<リビジョン番号>
  labels:
    app: istiod
spec:
  type: ClusterIP
  selector:
    app: istiod
    istio.io/rev: <リビジョン番号>
  ports:
    - name: grpc-xds
      port: 15010
      protocol: TCP
      targetPort: 15010
    - name: https-dns
      port: 15012
      protocol: TCP
      targetPort: 15012
    # webhookサーバーにポートフォワーディングする。
    - name: https-webhook
      port: 443
      protocol: TCP
      targetPort: 15017
    - name: http-monitoring
      port: 15014
      protocol: TCP
      targetPort: 15014

.spec.selector.istio.io/revキーに、ポートフォワーディング先のPodを指定するためのリビジョン番号が設定されており、このPodはdiscoveryコンテナを持ちます。

Istioは、discoveryコンテナ内でwebhookサーバーを実行し、15017番ポートでリクエストを待ち受けます。

ここで、discoveryコンテナがリクエストを待ち受けているポート番号を見てみると、15017番ポートでリッスンしていることを確認できます。

$ kubectl exec foo-istiod -n istio-system -- netstat -tulpn

Active Internet connections (only servers)

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.1:9876          0.0.0.0:*               LISTEN      1/pilot-discovery   
tcp6       0      0 :::15017                :::*                    LISTEN      1/pilot-discovery   
tcp6       0      0 :::8080                 :::*                    LISTEN      1/pilot-discovery   
tcp6       0      0 :::15010                :::*                    LISTEN      1/pilot-discovery   
tcp6       0      0 :::15012                :::*                    LISTEN      1/pilot-discovery   
tcp6       0      0 :::15014                :::*                    LISTEN      1/pilot-discovery 

ℹ️ 参考:


kube-apiserver ⬅︎ Service ⬅︎ webhookサーバー

ここで説明するフロー箇所

『kube-apiserver ⬅︎ Service ⬅︎ webhookサーバー』の箇所を説明します。

istio_container-injection_flow_red_4

(7)patch処理を定義

仕組みの中でも、ここは重要な部分です。

discoveryコンテナ内のwebhookサーバーは、リクエスト内容を書き換えるためのpatch処理を定義します。

webhookサーバーは、マニフェスト.spec.containers[1]パスにistio-proxyキーを追加させるようなpatch処理を定義します。

この定義によって、結果的にサイドカーのインジェクションが起こるということになります。

[

  ...

  {
    "op": "add",
    # .spec.initContainers[1] を指定する。
    "path": "/spec/initContainers/1",
    # マニフェストファイルに追加される構造を表す。
    "value": {
      "name": "istio-init",
      "resources": {
                     ...
      }
    }
  },
  {
    "op": "add",
    # .spec.containers[1] を指定する。
    "path": "/spec/containers/1",
    # マニフェストファイルに追加される構造を表す。
    "value": {
      "name": "istio-proxy",
      "resources": {
                     ...
      }
    }
  }

  ...

]

ℹ️ 参考:

本題と話が逸れるため今回は詳しく言及しませんが、上記のpathc処理ではサイドカーコンテナのistio-proxyコンテナの他に、initコンテナのistio-initコンテナもインジェクションできるようにします。

このistio-initコンテナは、istio-proxyコンテナを持つPodでインバウンド/アウトバウンド通信の経路を制御できるように、Pod内にiptablesのルールを適用する責務を担っています。

ℹ️ 参考:Istio Sidecar's interception mechanism for traffic - SoByte

(8)AdmissionResponseに値を詰める

discoveryコンテナ内のwebhookサーバーは、patch処理の定義をAdmissionReveiew構造体のAdmissionResponseに詰めます。

patchキーの値に、先ほどのpatch処理の定義をbase64方式でエンコードした文字列が割り当てられています。

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  # AdmissionResponse
  "response": {
    "uid": "*****",
    "allowed": true,
    "patchType": "JSONPatch",
    # Patch処理の対象となるKubernetesリソースと処理内容を表す。base64方式でエンコードされている。
    "patch": "<先ほどのpatch処理の定義をbase64方式でエンコードした文字列>"
  }
}

ℹ️ 参考:istio/webhook.go at 1.14.3 · istio/istio · GitHub

(9)AdmissionReviewを返信

discoveryコンテナ内のwebhookサーバーは、AdmissionReview構造体をレスポンスとしてkube-apiserverに返信します。


kube-apiserver ➡︎ etcd

ここで説明するフロー箇所

『kube-apiserver ➡︎ etcd』の箇所を説明します。

istio_container-injection_flow_red_5

(10)patch処理をコール

kube-apiserverは、AdmissionReview構造体を受信し、AdmissionResponseに応じてリクエスト内容を書き換えます。

patch処理の定義をAdmissionReview構造体から取り出し、クライアントからのリクエスト内容を書き換えます。

具体的には、istio-proxyコンテナとistio-initコンテナを作成できるように、リクエストしたマニフェストの該当箇所にキーを追加します。

apiVersion: v1
kind: Pod
metadata:
  name: foo-pod
  namespace: foo-namespace
spec:
  containers:
    - name: foo
      image: foo:1.0.0
      ports:
        - containerPort: 80
    # kube-apiserverが追加
    - name: istio-proxy

      ...

  # kube-apiserverが追加
  initContainers:
    - name: istio-init

    ...

(11)マニフェストを永続化

kube-apiserverは、etcdにPodのマニフェストを永続化します。


クライアント ⬅︎ kube-apiserver

ここで説明するフロー箇所

『クライアント ⬅︎ kube-apiserver』の箇所を説明します。

istio_container-injection_flow_red_6

(12)コール完了を返信

kube-apiserverは、クライアントにレスポンスを受信します。

$ kubectl apply -f foo-pod.yaml

# kube-apiserverからレスポンスが返ってくる
pod "foo-pod" created


以降の仕組み

istio_container-injection_flow_red_7

kube-apiserverは、他のNodeコンポーネント(kube-controlleretcd、kube-scheduler、kubelet、など)と通信し、Podを作成します。

このPodのマニフェストは、アプリコンテナの他に、istio-proxyコンテナとistio-initコンテナを持ちます。

結果として、サイドカーコンテナのistio-proxyコンテナをインジェクションしたことになります。

本題と話が逸れるため今回は詳しく言及しませんが、kube-apiserverと他コンポーネントの通信については、以下の方の記事と図が非常に参考になると思います🙇🏻‍♂️

kubernetes_kube-apiserver_communication

ℹ️ 参考:Kubernetes Master Components: Etcd, API Server, Controller Manager, and Scheduler | by Jorge Acetozi | jorgeacetozi | Medium


05. おわりに

今回、Istioのサイドカーインジェクションの仕組みをもりもり布教しました。

最後までお付き合いいただきありがとうございました。

今回登場したMutatingAdmissionWebhookプラグインに関して、私の関わっているプロダクトではIstio以外(例:CertManager、Prometheus、AWSのeks-vpc-cniアドオン、など)でも使用しています。

そのため、MutatingAdmissionWebhookプラグインをどのように使っているのかを一度知れば、知識の汎用性が高いと考えています✌🏻

サイドカーインジェクションはIstioでも基本的な機能であり、もし未体験の方がいらっしゃれば、お手元でサイドカーコンテナが追加されることを確認していただくとよいかもしれません👍