Webhookの実装
Kubernetesでは、リソースの作成・更新・削除をおこなう直前にWebhookで任意の処理を実行するとことができます。 Mutating Webhookではリソースの値を書き換えることができ、Validating Webhookでは値の検証をおこなうことができます。
controller-runtimeでは、Mutating Webhookを実装するためのadmission.Defaulter/CustomDefaulterとValidating Webhookを実装するためのadmission.Validator/CustomValidatorが用意されています。 ただし、admission.Defaulter/Validatorはcontroller-runtime v0.20.0で廃止予定のため、admission.CustomDefaulter/CustomValidatorの利用が推奨されています。
しかし、Kubebuilder v4.1.1時点ではCustomDefaulter/CustomValidatorがまだサポートされていません。そこで本書ではadmission.Defaulter/Validatorを利用して解説をおこないます。
admission.CustomDefaulter/CustomValidatorを利用したい場合は、以下の記事に実装方法を記載しましたので参考にしてください。
Defaulterの実装
まずはDefaulterの実装です。 Defaultメソッドでは、MarkdownViewリソースの値を書き換えることができます。
package v1
import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
ctrl "sigs.k8s.io/controller-runtime"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
// log is for logging in this package.
var markdownviewlog = logf.Log.WithName("markdownview-resource")
// SetupWebhookWithManager will setup the manager to manage the webhooks
func (r *MarkdownView) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(r).
Complete()
}
// +kubebuilder:webhook:path=/mutate-view-zoetrope-github-io-v1-markdownview,mutating=true,failurePolicy=fail,sideEffects=None,groups=view.zoetrope.github.io,resources=markdownviews,verbs=create;update,versions=v1,name=mmarkdownview.kb.io,admissionReviewVersions=v1
var _ webhook.Defaulter = &MarkdownView{}
// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *MarkdownView) Default() {
markdownviewlog.Info("default", "name", r.Name)
if len(r.Spec.ViewerImage) == 0 {
r.Spec.ViewerImage = "peaceiris/mdbook:latest"
}
}
ここではr.Spec.ViewerImage
が空だった場合に、デフォルトのコンテナイメージを指定しています。
Validatorの実装
次にValidatorの実装です。 ValidateCreate, ValidateUpdate, ValidateDeleteは、それぞれリソースの作成・更新・削除のタイミングで呼び出される関数です。 これらの関数の中でMarkdownViewリソースの内容をチェックし、エラーを返すことでリソースの操作を失敗させることができます。
package v1
import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
ctrl "sigs.k8s.io/controller-runtime"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
// log is for logging in this package.
var markdownviewlog = logf.Log.WithName("markdownview-resource")
// SetupWebhookWithManager will setup the manager to manage the webhooks
func (r *MarkdownView) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(r).
Complete()
}
// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here.
// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook.
// +kubebuilder:webhook:path=/validate-view-zoetrope-github-io-v1-markdownview,mutating=false,failurePolicy=fail,sideEffects=None,groups=view.zoetrope.github.io,resources=markdownviews,verbs=create;update,versions=v1,name=vmarkdownview.kb.io,admissionReviewVersions=v1
var _ webhook.Validator = &MarkdownView{}
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *MarkdownView) ValidateCreate() (admission.Warnings, error) {
markdownviewlog.Info("validate create", "name", r.Name)
return r.validate()
}
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *MarkdownView) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
markdownviewlog.Info("validate update", "name", r.Name)
return r.validate()
}
func (r *MarkdownView) validate() (admission.Warnings, error) {
var errs field.ErrorList
if r.Spec.Replicas < 1 || r.Spec.Replicas > 5 {
errs = append(errs, field.Invalid(field.NewPath("spec", "replicas"), r.Spec.Replicas, "replicas must be in the range of 1 to 5."))
}
hasSummary := false
for name := range r.Spec.Markdowns {
if name == "SUMMARY.md" {
hasSummary = true
}
}
if !hasSummary {
errs = append(errs, field.Required(field.NewPath("spec", "markdowns"), "markdowns must have SUMMARY.md."))
}
if len(errs) > 0 {
err := apierrors.NewInvalid(schema.GroupKind{Group: GroupVersion.Group, Kind: "MarkdownView"}, r.Name, errs)
markdownviewlog.Error(err, "validation error", "name", r.Name)
return nil, err
}
return nil, nil
}
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (r *MarkdownView) ValidateDelete() (admission.Warnings, error) {
markdownviewlog.Info("validate delete", "name", r.Name)
return nil, nil
}
今回はValidateCreateとValidateUpdateで同じバリデーションをおこなうことにしましょう。
.Spec.Replicas
の値が1から5の範囲にない場合と、.Spec.Markdowns
にSUMMARY.md
が含まれない場合はエラーとします。
なお、ValidationWebhookを実装する際には"k8s.io/apimachinery/pkg/util/validation/field"
パッケージが役立ちます。
このパッケージを利用してエラーの原因や問題のあるフィールドを指定することで、バリデーションエラー時のメッセージがわかりやすいものになります。
動作確認
それでは、Webhookの動作確認をしてみましょう。
Webhookの実装をおこなったカスタムコントローラーをKubernetesクラスターにデプロイし、下記のようなViewerImage
を指定していないマニフェストを適用します。
apiVersion: view.zoetrope.github.io/v1
kind: MarkdownView
metadata:
name: markdownview-sample
spec:
markdowns:
SUMMARY.md: |
# Summary
- [Page1](page1.md)
page1.md: |
# Page 1
一ページ目のコンテンツです。
replicas: 1
作成されたリソースを確認して、ViewerImage
にデフォルトのコンテナイメージ名が入っていれば成功です。
$ kubectl get markdownview markdownview-sample -o jsonpath="{.spec.viewerImage}"
peaceiris/mdbook:latest
続いてバリデーションWebhookの動作も確認してみましょう。
先ほど作成したリソースを編集してreplicas
を大きな値にしたり、markdowns
にSUMMARY.md
を含めないようにしたりしてみましょう。
以下のようなエラーが発生すれば成功です。
$ kubectl edit markdownview markdownview-sample
markdownviews.view.zoetrope.github.io "markdownview-sample" was not valid:
* spec.replicas: Invalid value: 10: replicas must be in the range of 1 to 5.
* spec.markdowns: Required value: markdowns must have SUMMARY.md.