LoginSignup
0
1

More than 1 year has passed since last update.

Admission Webhookのすゝめ

Posted at

はじめに

最近公私ともにContoller開発を行うに当たり、validateやmutateを行うWebhookについて触れる機会が多いのです。そこで、自分なりに理解している範囲で現在のwebhook事情についてまとめてみます。

Webhookライブラリの種類

  • kubernetes-sigs/controller-runtime
    言わずと知れたcontroller開発ライブラリです。
    この中で提供されているwebhookを利用することで、kubebuilderなどのフレームワークでもwebhookが簡単に実装可能となっています。
  • knative/pkg/webhook
    サーバーレスな基盤を構築することが可能なknativeですが、パッケージが個別で公開されており、その中でwebhookライブラリが使用できます。若干学習コストが高いです。
  • slok/kubewebhook
    非常に軽量でシンプルなライブラリです。サンプルが提供されており、これをいじるだけで実装出来ます。上記2つと異なり、証明書の自動ローテーションを行うには工夫が必要。
  • snorwin/k8s-generic-webhook
    kubebuilderなどフレームワークで作成するwebhookによく似たコードで実装可能なシンプルなライブラリです。フレームワークでの開発経験がある方であれば、使いこなせるのではないでしょうか。

今回はシンプルで拡張性の高いkubewebhookに絞って紹介します。(私のお気に入りだからというのは内緒

特徴

実装が非常に簡単であり、ライブラリ自体のコードも読みやすく書かれています。
また、通常のvalidateやmutateだけでなく、各処理をchainとしてつなげることが可能です。

type chain struct {
	validators []Validator
	logger     log.Logger
}


// NewChain returns a new chain of validators.
// - If any of the validators returns an error, the chain will end.
// - If any of the validators returns an stopChain == true, the chain will end.
// - If any of the validators returns as no valid, the chain will end.
func NewChain(logger log.Logger, validators ...Validator) Validator {
	return chain{
		validators: validators,
		logger:     logger,
	}
}

chainを用いた実装は以下のコードを確認ください。

kubewebhook提供サンプル

以下のサンプルを確認いただくと分かる通り、ほぼこれだけでmutateが完結します。
★流れ
実際のmutate処理作って

MutatorFunc()に食わせてあげて

Webhookの呼び出しをしてあげるだけ。
注意点として、処理対象のリソースの型をmetav1.Objectにアサーションしてあげるくらいです。
https://github.com/slok/kubewebhook/blob/master/examples/pod-annotate/main.go

func annotatePodMutator(_ context.Context, _ *kwhmodel.AdmissionReview, obj metav1.Object) (*kwhmutating.MutatorResult, error) {
	pod, ok := obj.(*corev1.Pod)
	if !ok {
		// If not a pod just continue the mutation chain(if there is one) and don't do nothing.
		return &kwhmutating.MutatorResult{}, nil
	}

	// Mutate our object with the required annotations.
	if pod.Annotations == nil {
		pod.Annotations = make(map[string]string)
	}
	pod.Annotations["mutated"] = "true"
	pod.Annotations["mutator"] = "pod-annotate"

	return &kwhmutating.MutatorResult{
		MutatedObject: pod,
	}, nil
}

func main() {
	logrusLogEntry := logrus.NewEntry(logrus.New())
	logrusLogEntry.Logger.SetLevel(logrus.DebugLevel)
	logger := kwhlogrus.NewLogrus(logrusLogEntry)

	cfg := initFlags()

	// Create our mutator
	mt := kwhmutating.MutatorFunc(annotatePodMutator)

	mcfg := kwhmutating.WebhookConfig{
		ID:      "podAnnotate",
		Obj:     &corev1.Pod{},
		Mutator: mt,
		Logger:  logger,
	}
	wh, err := kwhmutating.NewWebhook(mcfg)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error creating webhook: %s", err)
		os.Exit(1)
	}

	// Get the handler for our webhook.
	whHandler, err := kwhhttp.HandlerFor(kwhhttp.HandlerConfig{Webhook: wh, Logger: logger})
	if err != nil {
		fmt.Fprintf(os.Stderr, "error creating webhook handler: %s", err)
		os.Exit(1)
	}
	logger.Infof("Listening on :8080")
	err = http.ListenAndServeTLS(":8080", cfg.certFile, cfg.keyFile, whHandler)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error serving webhook: %s", err)
		os.Exit(1)
	}
}

本番で使用する場合のサンプルまであります。最高です。

kubewebhookを使用した実装サンプル

Image tagが付与されていない場合にlatesttagをデフォルト値として付与するMutatorを作成してみました。(すでにkubernetesのデフォルトはlatestであると理解していますが、kubectl describeの見た目を変えたかったのです。

出力結果

kubewebhookでmutateなりvaidateなりを行うと以下のようにjsonで出力され、opからどのようなoperationがなされたのかがわかるようになっています。今回はImage tagの書き換えをおこなったため、replaceとなっていますが、Fieldの追加を行うと、addと出力されます。また、どのような変更がされたかも確認可能となっており、非常に親切なログの出し方と思います。

$ kubectl logs default-imagetag-webhook-5bd6b8b685-t26t4 -f
time="2022-07-11T00:41:14Z" level=info msg="Listening on :8080"
time="2022-07-11T00:42:12Z" level=debug msg="Webhook mutating review finished with: '[{\"op\":\"replace\",\"path\":\"/spec/containers/0/image\",\"value\":\"nginx:latest\"}]' JSON Patch" dry-run=false kind=v1/Pod name= ns=default op=create path=/mutate request-id=682f8ab2-6ace-4663-a748-96299cf4f650 trace-id= webhhok-type=mutating webhook-id=imageTagMutator webhook-kind=mutating wh-version=v1
time="2022-07-11T00:42:12Z" level=info msg="Admission review request handled" dry-run=false duration=276.777541ms kind=v1/Pod name= ns=default op=create path=/mutate request-id=682f8ab2-6ace-4663-a748-96299cf4f650 svc=http.Handler trace-id= webhook-id=imageTagMutator webhook-kind=mutating wh-version=v1

証明書ローテーションについて

WebhookのPodとkube-apiserver1間はTLSで認証にて通信を行います。
そのため、kube-apiserverで証明書がローテーションされる2と、Webhook PodにあるHTTPサーバの証明書も何らかの方法でローテーションしてあげる必要があります。
ただ、kubewebhookには証明書の自動ローテーション機能は実装されていません。
そこで、contoroller-runtimeのcertwatcherを利用することで自動ローテーションが可能です。
https://github.com/kubernetes-sigs/controller-runtime/tree/master/pkg/certwatcher

使い方は、同じくcontoroller-runtimeのpkg/webhook/server.goをご確認ください。
https://github.com/kubernetes-sigs/controller-runtime/blob/master/pkg/webhook/server.go#L212-L296
certwatcherは証明書の変更を監視しており、変更があるとロード対象の証明書を変更します。
そして、webhook側はcertWatcher.GetCertificateでtls.ClientHelloInfoを待ち受けているので、リクエストがあるたびに証明書をロードします。そのため、常に最新の証明書が使えるというわけです。

func (s *Server) Start(ctx context.Context) error {
:
	cfg := &tls.Config{ //nolint:gosec
		NextProtos:     []string{"h2"},
		GetCertificate: certWatcher.GetCertificate,
		MinVersion:     tlsMinVersion,
	}
:

参考リンク

https://github.com/kubernetes-sigs/controller-runtime/tree/master/pkg/webhook
https://github.com/knative/pkg/tree/main/webhook
https://github.com/slok/kubewebhook
https://github.com/snorwin/k8s-generic-webhook

  1. ここでいうkube-apiserverとはValidatingWebhookConfigurationやMutatingWebhookConfigurationを指します。

  2. ValidatingWebhookConfigurationやMutatingWebhookConfigurationの証明書のローテーションはcert-managerで自動で行うか、自前で行うかの2択です。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1