目次
01. はじめに
どーも。
正月で激太りしましたが、ダイエットの予定はありません🙋🏻♂️
今回は、サービスメッシュを実装するIstio⛵️のサイドカーインジェクションに関する記事を投稿しました。
前回の記事に引き続きIstioです。
執筆時点(2023/01/14)では、Istioが実装するサービメッシュには、『サイドカープロキシメッシュ』と『アンビエントメッシュ』があります。
サイドカープロキシメッシュの仕組みの軸になっているものは、サイドカーコンテナであるistio-proxy
コンテナです。
Istioは、KubernetesのPodの作成時に、istio-proxy
コンテナをPod内に自動的にインジェクション(注入)します
本記事では、このサイドカーのインジェクションの仕組みをもりもり布教しようと思います😗(沼のまわりに餌をまく)
02. サイドカーによるサービスメッシュ
なぜサイドカーが必要なのか
そもそも、なぜサービスメッシュでサイドカーが必要になったのでしょうか。
マイクロサービスアーキテクチャのシステムには、アーキテクチャ固有のインフラ領域の問題(例:サービスディスカバリーの必要性、マイクロサービス間通信の暗号化、テレメトリー収集、など)があります。
アプリエンジニアが各マイクロサービス内にインフラ領域の問題に関するロジックを実装すれば、これらの問題の解決できます。
しかし、アプリエンジニアはアプリ領域の問題に責務を持ち、インフラ領域の問題はインフラエンジニアで解決するようにした方が、互いに効率的に開発できます。
そこで、インフラ領域の問題を解決するロジックをサイドカーとして切り分けます。
これにより、アプリエンジニアとインフラエンジニアの責務を分離できるようになります。
また、インフラ領域の共通ロジックをサイドカーとして各マイクロサービスに提供できるため、単純性が高まります。
こういった流れの中で、サイドカーを使用したサービスメッシュが登場しました。
ℹ️ 参考:
サイドカープロキシメッシュ
Istioのサイドカーによるサービスメッシュ(サイドカープロキシメッシュ)は、
からなります。
ℹ️ 参考: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プラグインによる処理(例:アドオンビルトイン処理、独自処理)を発火させられます。
ℹ️ 参考:
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
MutatingAdmissionWebhookプラグイン
MutatingAdmissionWebhookプラグインとは
MutatingAdmissionWebhookプラグインを使用すると、mutating-admissionステップ時に、リクエスト内容を変更する処理をフックできます。
フックする具体的な処理として、webhookサーバーにAdmissionRequestリクエストとして送信することにより、レスポンスのAdmissionResponseに応じてリクエスト内容を動的に変更します。
MutatingWebhookConfigurationで、MutatingAdmissionWebhookプラグインの発火条件やwebhookサーバーの宛先情報を設定します。
MutatingWebhookConfigurationの具体的な実装については、サイドカーインジェクションの仕組みの中で説明していきます。
ℹ️ 参考:
AdmissionReview、AdmissionRequest、AdmissionResponse
▼ AdmissionReview
AdmissionReviewは以下のようなJSONであり、kube-apiserverとwebhookサーバーの間でAdmissionRequestとAdmissionResponseを運びます。
{ "apiVersion": "admission.k8s.io/v1", "kind": "AdmissionReview", # AdmissionRequest "request": {}, # AdmissionResponse "response": {} }
▼ 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", ... } }
▼ 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
)に追加します。
04. サイドカーインジェクションの仕組み
全体のフロー
前提知識を踏まえた上で、admission-controllersアドオンの仕組みの中で、サイドカーのistio-proxy
コンテナがどのようにPodにインジェクションされるのかを見ていきましょう。
最初に、サイドカーインジェクションのフローは以下の通りになっています。
画像の文字が小さくなってしまったため、拡大していただけると🙇🏻♂️
ℹ️ 参考:Amazon.co.jp: Istio in Action (English Edition) 電子書籍: Posta, Christian E., Maloku, Rinor: 洋書
クライアント ➡︎ kube-apiserver
ここで説明するフロー箇所
『クライアント ➡︎ kube-apiserver』の箇所を説明します。
(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』の箇所を説明します。
(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:443
にHTTPSプロトコルのリクエストを送信するようになっています。
一方で発火しなかった場合には、以降の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サーバー』の箇所を説明します。
(6)15017番ポートにポートフォワーディング
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サーバー』の箇所を説明します。
(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方式でエンコードした文字列>" } }
(9)AdmissionReviewを返信
discovery
コンテナ内のwebhookサーバーは、AdmissionReview構造体をレスポンスとしてkube-apiserverに返信します。
kube-apiserver ➡︎ etcd
ここで説明するフロー箇所
『kube-apiserver ➡︎ etcd』の箇所を説明します。
(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』の箇所を説明します。
(12)コール完了を返信
kube-apiserverは、クライアントにレスポンスを受信します。
$ kubectl apply -f foo-pod.yaml # kube-apiserverからレスポンスが返ってくる pod "foo-pod" created
以降の仕組み
kube-apiserverは、他のNodeコンポーネント(kube-controlleretcd、kube-scheduler、kubelet、など)と通信し、Podを作成します。
このPodのマニフェストは、アプリコンテナの他に、istio-proxy
コンテナとistio-init
コンテナを持ちます。
結果として、サイドカーコンテナのistio-proxy
コンテナをインジェクションしたことになります。
本題と話が逸れるため今回は詳しく言及しませんが、kube-apiserverと他コンポーネントの通信については、以下の方の記事と図が非常に参考になると思います🙇🏻♂️
05. おわりに
今回、Istioのサイドカーインジェクションの仕組みをもりもり布教しました。
最後までお付き合いいただきありがとうございました。
今回登場したMutatingAdmissionWebhookプラグインに関して、私の関わっているプロダクトではIstio以外(例:CertManager、Prometheus、AWSのeks-vpc-cniアドオン、など)でも使用しています。
そのため、MutatingAdmissionWebhookプラグインをどのように使っているのかを一度知れば、知識の汎用性が高いと考えています✌🏻
サイドカーインジェクションはIstioでも基本的な機能であり、もし未体験の方がいらっしゃれば、お手元でサイドカーコンテナが追加されることを確認していただくとよいかもしれません👍