Kubernetes Admission Webhook Deep Dive
Kubernetes Admission Webhook Deep Dive
この記事は CloudNative Days Tokyo 2022 のセッション「Kubernetes Admission Webhook Deep Dive」の補足です。
- セッション情報
- スライド
- サンプルプログラム
controller-runtime での 3 種類の Webhook 実装方法
controller-runtime では、Admission Webhook を実装するための interface が 3 種類用意されています。
Defaulter/Validator
Defaulter と Validator は、Kubebuilder で Webhook のコードを生成した場合に利用される方式です。
Mutating Webhook を実装する場合は Defaulter を、Validating Webhook を実装する場合は Validator を利用します。
type Defaulter interface {
runtime.Object
Default()
}
type Validator interface {
runtime.Object
ValidateCreate() error
ValidateUpdate(old runtime.Object) error
ValidateDelete() error
}
リソースを表現する構造体のメソッドとして定義しなければならないため、Kubernetesの標準リソースや第三者のカスタムリソースは扱いにくくなっています。
具体的な実装方法は以下のコードをご覧ください。
CustomDefaulter/CustomValidator
CustomDefaulter と CustomValidator は、Defaulter と Validator よりも拡張性の高い実装方式です。
Mutating Webhook を実装する場合は CustomDefaulter を、Validating Webhook を実装する場合は CustomValidator を利用します。
type CustomDefaulter interface {
Default(ctx context.Context, obj runtime.Object) error
}
type CustomValidator interface {
ValidateCreate(ctx context.Context, obj runtime.Object) error
ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) error
ValidateDelete(ctx context.Context, obj runtime.Object) error
}
Defaulter/Validatorとは違い、リソースを表現する構造体のメソッドとして定義する必要はないため、Kubernetesの標準リソースや第三者のカスタムリソースも扱うことができます。
引数として渡ってくるオブジェクトはすでにデコード済みなので、以下のように適応する型にキャストして利用することができます。
pod, ok := obj.(*corev1.Pod)
if !ok {
return fmt.Errorf("unknown newObj type %T", obj)
}
また引数として渡ってくる context から admission.Request を取り出すことができます。
req, err := admission.RequestFromContext(ctx)
具体的な実装方法は以下のコードをご覧ください。
- https://github.com/zoetrope/sample-webhook/blob/main/hooks/pod_webhook.go
- https://github.com/zoetrope/sample-webhook/blob/main/hooks/namespace_webhook.go
Handler
Handler は最も拡張性の高い実装方式です。
Defaulter/Validator や CustomDefaulter/CustomValidator と違い、受け取ったオブジェクトのデコード処理や、JSON Patchの生成処理などを自前で実装する必要があります。
Mutating Webhook と Validating Webhook のどちらを実装する場合も同じ interface を利用します。
type Handler interface {
Handle(context.Context, Request) Response
}
なお、オブジェクトのデコード処理に必要な Decoder は、以下のように DecoderInjector
interface を実装することで取得できます。
type deploymentValidator struct {
decoder *admission.Decoder
}
func (v *deploymentValidator) InjectDecoder(d *admission.Decoder) error {
v.decoder = d
return nil
}
Decoder
を利用して、以下のようにデコード処理をおこなうことができます。
func (v *deploymentValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
dep := &appsv1.Deployment{}
err := v.decoder.Decode(req, dep)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
return admission.Allowed("ok")
}
デコード処理を自前で実装できるため、1 つの Webhook で複数種類のオブジェクトを処理することもできます。
(サンプルプログラムでは、Deployment と Scale を 1 つの Webhook で処理しています)
具体的な実装方法は以下のコードをご覧ください。
クライアント認証
ここでは Kubebuilder で生成したプロジェクトで Admission Webhook のクライアント認証を有効にする方法を紹介します。
必要なファイルは以下のリポジトリにあります。
はじめに cfssl を利用して証明書の作成をおこないます。
各種設定ファイルは https://github.com/zoetrope/sample-webhook/tree/main/certs をご覧ください。
# CA証明書の作成
cfssl gencert -initca ./certs/ca-csr.json | cfssljson -bare ./certs/clientca
# クライアント証明書の作成
cfssl genkey ./certs/client.json | cfssljson -bare ./certs/client
# クライアント証明書に署名する
cfssl sign -ca ./certs/clientca.pem -ca-key ./certs/clientca-key.pem -config ./certs/ca-config.json ./certs/client.csr | cfssljson -bare ./certs/client
つぎに kubeconfig.yaml
を作成します。
name
には Admission Webhook サービスの DNS 名 <サービス名>.<サービスのNamespace>.svc
を指定します。
client-certificate
と client-key
には、先ほど作成したクライアント証明書と秘密鍵を指定します。
apiVersion: v1
kind: Config
users:
- name: sample-webhook-webhook-service.sample-webhook-system.svc
user:
client-certificate: /etc/certs/client.pem
client-key: /etc/certs/client-key.pem
つづいて API Server の設定ファイル admissionconfig.yaml
を作成します。
kubeConfigFile
には先ほど作成した kubeconfig.yaml
を指定します。
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ValidatingAdmissionWebhook
configuration:
apiVersion: apiserver.config.k8s.io/v1
kind: WebhookAdmissionConfiguration
kubeConfigFile: "/etc/cluster/kubeconfig.yaml"
- name: MutatingAdmissionWebhook
configuration:
apiVersion: apiserver.config.k8s.io/v1
kind: WebhookAdmissionConfiguration
kubeConfigFile: "/etc/cluster/kubeconfig.yaml"
そして API Server の --admission-control-config-file
オプションに admissionconfig.yaml
を渡します。
kind を利用している場合は、以下のような設定ファイルを用意してクラスターを起動します。
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: sample-webhook-dev
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: ClusterConfiguration
apiServer:
extraArgs:
admission-control-config-file: /etc/cluster/admissionconfig.yaml
extraVolumes:
- name: cluster
hostPath: /etc/cluster
mountPath: /etc/cluster
readOnly: true
pathType: "DirectoryOrCreate"
- name: certs
hostPath: /etc/certs
mountPath: /etc/certs
readOnly: true
pathType: "DirectoryOrCreate"
extraMounts:
- hostPath: ./cluster
containerPath: /etc/cluster
readOnly: true
- hostPath: ./certs
containerPath: /etc/certs
readOnly: true
つぎに先ほど作成した CA 証明書を Secret リソースにします。
cd certs
kubectl create secret generic webhook-client-cert -n sample-webhook-system --from-file=clientca.pem
この Secret リソースを Admission Webhook の Pod から利用できるようにマウントします。
なお、サーバー証明書とクライアント証明書を同じディレクトリに配置する必要があるため、Projected Volumes を利用しています。
apiVersion: apps/v1
kind: Deployment
metadata:
name: controller-manager
namespace: system
spec:
template:
spec:
containers:
- name: manager
ports:
- containerPort: 9443
name: webhook-server
protocol: TCP
volumeMounts:
- mountPath: /tmp/k8s-webhook-server/serving-certs
name: cert
readOnly: true
volumes:
- name: cert
projected:
defaultMode: 420
sources:
- secret:
name: webhook-server-cert
- secret:
name: webhook-client-cert
最後に Admission Webhook のプログラム中でクライアント証明書の CA 証明書のファイル名を設定します。
controller-runtime を利用している場合は、manager の GetWebhookServer()
の ClientCAName
を指定するだけです。
func main() {
// 省略
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
Port: 9443,
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "0c46b1d7.zoetrope.github.io",
})
// 省略
wh := mgr.GetWebhookServer()
// クライアント証明書のCA証明書のファイル名を設定する
wh.ClientCAName = "clientca.pem"
// 省略
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}
以上で、Admission Webhook のクライアント認証が有効化されます。
Discussion