<aside> ⏰ 本文介绍如何实现一个 Mutate Admission Webhook。
</aside>
前面我们已经实现了一个校验的准入控制器,本节我们来尝试开发一个用于 Mutate 的准入控制器,这两个控制器其实都是我们自己通过 Webhook 去实现,而且他们接收和返回的响应数据结构都是 AdmissionReview,唯一不同的是对于 Mutating 的 Webhook 在处理了资源对象后返回的时候需要我们拼接一个 JSONPatch 的数据。
接下来我们在前面的镜像白名单的 Webhook 基础之上新增 mutate 的支持,该项目中我们也预留了 mutate 的入口,通过 /mutate 路径的请求进行 mutate 操作。
比如我们的需求是当我们的资源对象(Deployment 或 Service)中包含一个需要 mutate 的 annotation 注解后,通过这个 Webhook 后我们就给这个对象添加上一个执行了 mutate 操作的注解。
首先在 WebhookServ 的 Serve 函数中新增 mutate 的逻辑入口函数:
// 序列化成功,也就是说获取到了请求的 AdmissionReview 的数据
if request.URL.Path == "/mutate" {
admissionResponse = s.mutate(&requestedAdmissionReview)
} else if request.URL.Path == "/validate" {
admissionResponse = s.validate(&requestedAdmissionReview)
}
然后我们的 mutate 逻辑就在下面的函数中去实现了:
func (s *WebhookServer) mutate(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
req := ar.Request
var (
objectMeta *metav1.ObjectMeta
resourceNamespace, resourceName string
)
klog.Infof("AdmissionReview for Kind=%v, Namespace=%v Name=%v UID=%v Operation=%v",
req.Kind.Kind, req.Namespace, req.Name, req.UID, req.Operation)
switch req.Kind.Kind {
case "Deployment":
var deployment appsv1.Deployment
if err := json.Unmarshal(req.Object.Raw, &deployment); err != nil {
klog.Errorf("Could not unmarshal raw object: %v", err)
return &admissionv1.AdmissionResponse{
Result: &metav1.Status{
Code: http.StatusBadRequest,
Message: err.Error(),
},
}
}
resourceName, resourceNamespace, objectMeta = deployment.Name, deployment.Namespace, &deployment.ObjectMeta
case "Service":
var service corev1.Service
if err := json.Unmarshal(req.Object.Raw, &service); err != nil {
klog.Errorf("Could not unmarshal raw object: %v", err)
return &admissionv1.AdmissionResponse{
Result: &metav1.Status{
Code: http.StatusBadRequest,
Message: err.Error(),
},
}
}
resourceName, resourceNamespace, objectMeta = service.Name, service.Namespace, &service.ObjectMeta
default:
return &admissionv1.AdmissionResponse{
Result: &metav1.Status{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("Can't handle this kind(%s) object", req.Kind.Kind),
},
}
}
if !mutationRequired(objectMeta) {
klog.Infof("Skipping validation for %s/%s due to policy check", resourceNamespace, resourceName)
return &admissionv1.AdmissionResponse{
Allowed: true,
}
}
annotations := map[string]string{AnnotationStatusKey: "mutated"}
var patch []patchOperation
patch = append(patch, updateAnnotation(objectMeta.GetAnnotations(), annotations)...)
patchBytes, err := json.Marshal(patch)
if err != nil {
return &admissionv1.AdmissionResponse{
Result: &metav1.Status{
Message: err.Error(),
},
}
}
klog.Infof("AdmissionResponse: patch=%v\\n", string(patchBytes))
return &admissionv1.AdmissionResponse{
Allowed: true,
Patch: patchBytes,
PatchType: func() *admissionv1.PatchType {
pt := admissionv1.PatchTypeJSONPatch
return &pt
}(),
}
}
在这个函数中我们针对 Deployment 和 Service 两种资源类型进行处理,首先通过 mutationRequired
函数来判断当前资源对象是否需要执行 mutate 操作。
func mutationRequired(metadata *metav1.ObjectMeta) bool {
annotations := metadata.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
}
var required bool
switch strings.ToLower(annotations[AnnotationMutateKey]) {
default:
required = true
case "n", "no", "false", "off":
required = false
}
status := annotations[AnnotationStatusKey]
if strings.ToLower(status) == "mutated" {
required = false
}
klog.Infof("Mutation policy for %v/%v: required:%v", metadata.Namespace, metadata.Name, required)
return required
}
如果资源对象中包含的 AnnotationMutateKey
这个 annotation 对应的值为 "n"、"no"、"false"、"off" 中的任何一个则不需要执行 mutate 操作,或者AnnotationStatusKey
这个 annotation 对应的值已经是 mutated 了则也不需要,否则就需要执行 mutate 操作。
如果需要执行 mutate 操作,则需要我们自己创建 Patch 操作,将 {AnnotationStatusKey: "mutated"} 这个 annotation Patch 到资源中去:
annotations := map[string]string{AnnotationStatusKey: "mutated"}
var patch []patchOperation
patch = append(patch, updateAnnotation(objectMeta.GetAnnotations(), annotations)...)
这里要执行 Patch 操作,需要定义一个如下所示的 patchOperation 的结构体:
type patchOperation struct {
Op string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value,omitempty"`
}
然后在 updateAnnotation
函数中来创建更新 annotation 的 Patch: