はじめに
最近公私ともに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としてつなげることが可能です。
- 以下validatorの該当箇所抜粋
https://github.com/slok/kubewebhook/blob/master/pkg/webhook/validating/validator.go#L46-L60
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が付与されていない場合にlatest
tagをデフォルト値として付与する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
-
ここでいうkube-apiserverとはValidatingWebhookConfigurationやMutatingWebhookConfigurationを指します。 ↩
-
ValidatingWebhookConfigurationやMutatingWebhookConfigurationの証明書のローテーションはcert-managerで自動で行うか、自前で行うかの2択です。 ↩