Webhook 准入控制
Webhook 类型
请求 → API Server
│
├── MutatingAdmissionWebhook(变更准入)
│ 修改对象(注入 Sidecar、设置默认值)
│
├── ValidatingAdmissionWebhook(验证准入)
│ 验证对象(拒绝不合规的请求)
│
└── 写入 etcdkubebuilder 实现 Webhook
go
// api/v1alpha1/myapp_webhook.go
// +kubebuilder:webhook:path=/mutate-apps-mycompany-io-v1alpha1-myapp,mutating=true,failurePolicy=fail,sideEffects=None,groups=apps.mycompany.io,resources=myapps,verbs=create;update,versions=v1alpha1,name=mmyapp.kb.io,admissionReviewVersions=v1
type MyAppCustomDefaulter struct{}
func (d *MyAppCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error {
myApp := obj.(*MyApp)
// 设置默认值
if myApp.Spec.Replicas == 0 {
myApp.Spec.Replicas = 1
}
if myApp.Labels == nil {
myApp.Labels = make(map[string]string)
}
myApp.Labels["managed-by"] = "my-operator"
return nil
}
// +kubebuilder:webhook:path=/validate-apps-mycompany-io-v1alpha1-myapp,mutating=false,failurePolicy=fail,sideEffects=None,groups=apps.mycompany.io,resources=myapps,verbs=create;update,versions=v1alpha1,name=vmyapp.kb.io,admissionReviewVersions=v1
type MyAppCustomValidator struct{}
func (v *MyAppCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
myApp := obj.(*MyApp)
return v.validate(myApp)
}
func (v *MyAppCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
myApp := newObj.(*MyApp)
return v.validate(myApp)
}
func (v *MyAppCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
return nil, nil
}
func (v *MyAppCustomValidator) validate(myApp *MyApp) (admission.Warnings, error) {
var errs field.ErrorList
if myApp.Spec.Replicas > 100 {
errs = append(errs, field.Invalid(
field.NewPath("spec", "replicas"),
myApp.Spec.Replicas,
"副本数不能超过 100",
))
}
if myApp.Spec.Image == "" {
errs = append(errs, field.Required(
field.NewPath("spec", "image"),
"镜像不能为空",
))
}
if len(errs) > 0 {
return nil, apierrors.NewInvalid(
schema.GroupKind{Group: "apps.mycompany.io", Kind: "MyApp"},
myApp.Name,
errs,
)
}
return nil, nil
}Webhook 配置
yaml
# MutatingWebhookConfiguration
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: my-operator-mutating
annotations:
cert-manager.io/inject-ca-from: my-operator-system/my-operator-serving-cert
webhooks:
- name: mmyapp.kb.io
admissionReviewVersions: ["v1"]
clientConfig:
service:
name: my-operator-webhook-service
namespace: my-operator-system
path: /mutate-apps-mycompany-io-v1alpha1-myapp
rules:
- apiGroups: ["apps.mycompany.io"]
apiVersions: ["v1alpha1"]
operations: ["CREATE", "UPDATE"]
resources: ["myapps"]
failurePolicy: Fail
sideEffects: NoneSidecar 注入示例
go
// 自动注入日志收集 Sidecar
func (d *PodDefaulter) Default(ctx context.Context, obj runtime.Object) error {
pod := obj.(*corev1.Pod)
// 检查是否需要注入
if pod.Labels["inject-logging"] != "true" {
return nil
}
// 注入 Sidecar
sidecar := corev1.Container{
Name: "log-collector",
Image: "fluentd:v1.16",
VolumeMounts: []corev1.VolumeMount{
{Name: "app-logs", MountPath: "/var/log/app"},
},
}
pod.Spec.Containers = append(pod.Spec.Containers, sidecar)
// 注入 Volume
pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{
Name: "app-logs",
VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
})
return nil
}