Kubernetes Admission Webhook覚書き
はじめに
KubernetesのAdmission Webhookを改めて調べたので、その忘備録です。
Dynamic Admission Controlとは
下記参照
上記の図にあるように、KubernetesのAPI Serverにリクエストが来た際に
- オブジェクトの変更(Mutating)
- オブジェクトの検証(Validating)
が行われます。Kubernetesでは、ここにユーザ独自の処理を挟むことが可能です。
IstioのEnvoy SidecarのInjectionもこのあたりの機能を利用して実装されています。
Mutating Admission Webhookの導入
上記リポジトリをもとにMutating Webhookの仕組みを見ていきます。
Webhookの登録
API Serverから他のWebhook Serverに処理をさせるために、API ServerにWebhookの登録を行う必要があります。
Webhookの設定のためのMutatingWebhookConfiguration
Resourceが提供されているので、これを使ってをクラスタに登録しましょう。
※20201031現在、MutatingWebhookConfiguration
はv1
になっているので、v1beta1
とはパラメータが異なる場合があります。
apiVersion: admissionregistration.k8s.io/v1beta1 kind: MutatingWebhookConfiguration metadata: name: sidecar-injector-webhook-cfg labels: app: sidecar-injector webhooks: - name: sidecar-injector.morven.me clientConfig: # 1 service: name: sidecar-injector-webhook-svc namespace: sidecar-injector path: "/mutate" caBundle: ${CA_BUNDLE} rules: # 2 - operations: ["CREATE", "UPDATE"] apiGroups: [""] apiVersions: ["v1"] resources: ["pods"] namespaceSelector: # 3 matchLabels: sidecar-injection: enabled
主なポイントは3つです。
- Webhook Serverへの通信経路情報
sidecar-injector
Namespaceに存在するsidecar-injector-webhook-svc
Serviceの/mutate
にリクエストを送る
- Webhook Serverへ送るトリガ
- Webhookの対象の制御設定
sidecar-injection: enabled
が設定されたNamespaceのPodのみ処理の対象とする
上記ではKubernetesクラスタ内のServiceに処理を送ることになりますが、外部のサーバでも可能です。
namespaceSelector
以外にもobjectSelector
によるコントロールができたり、Webhookへ到達できなかったなどの想定外の障害時に後続の処理をどうするかなどのfailurePolicy
などの設定もできます。
詳しくはAPI Referenceを参考にしてください。
Webhook Serverのサーバ証明書の作成
API ServerからWebhook Serverへの通信はHTTPS通信で行う必要があるので、Webhook Serverのためのサーバ証明書を作成します。上記リポジトリではCertificateSigningRequest
Resourceを利用してKubernetesのCAを使ってサーバ証明書(と鍵)を作成しているようですが、独自でつくっても問題ないです。
(余談ですが、こういった「証明書を作るためのAPI」「それを承認するためのAPI」が用意されているところがプラットフォームという感じがします)
作成したサーバ証明書と鍵はSecretにしたうえでコンテナからマウントする方式になっていますね。
Webhook Serverの処理
Webhook ServerはAPI ServerからAdmissionReview
を受け取り、その中のAdmissionResponse
にpatchを格納して返却します。
下記処理でAdmissionReview
のAdmissionRequest
からPod
の情報を取り出して処理をしていることがわかるかと思います。
func (whsvr *WebhookServer) mutate(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse { req := ar.Request var pod corev1.Pod if err := json.Unmarshal(req.Object.Raw, &pod); err != nil { glog.Errorf("Could not unmarshal raw object: %v", err) return &v1beta1.AdmissionResponse{ Result: &metav1.Status{ Message: err.Error(), }, } } glog.Infof("AdmissionReview for Kind=%v, Namespace=%v Name=%v (%v) UID=%v patchOperation=%v UserInfo=%v", req.Kind, req.Namespace, req.Name, pod.Name, req.UID, req.Operation, req.UserInfo) // determine whether to perform mutation if !mutationRequired(ignoredNamespaces, &pod.ObjectMeta) { glog.Infof("Skipping mutation for %s/%s due to policy check", pod.Namespace, pod.Name) return &v1beta1.AdmissionResponse{ Allowed: true, } } // Workaround: https://github.com/kubernetes/kubernetes/issues/57982 applyDefaultsWorkaround(whsvr.sidecarConfig.Containers, whsvr.sidecarConfig.Volumes) annotations := map[string]string{admissionWebhookAnnotationStatusKey: "injected"} patchBytes, err := createPatch(&pod, whsvr.sidecarConfig, annotations) if err != nil { return &v1beta1.AdmissionResponse{ Result: &metav1.Status{ Message: err.Error(), }, } } glog.Infof("AdmissionResponse: patch=%v\n", string(patchBytes)) return &v1beta1.AdmissionResponse{ Allowed: true, Patch: patchBytes, PatchType: func() *v1beta1.PatchType { pt := v1beta1.PatchTypeJSONPatch return &pt }(), } }
まとめ
AdmissionReview
を受け取ってAdmissionResponse
を(AdmissionReviewにつめて)返却するサーバを構築し、- Webhookの実行ルールを
MutatingWebhookConfiguration
に記載する
拡張しやすい作りになっていてたすかります。