本节重点总结 :
- serveMutate编写
- 准入控制请求参数校验
- 根据annotation标签判断是否需要注入sidecar
- mutatePod 注入函数编写
- 生成注入容器和volume的patch函数
serveMutate编写
普通校验请求
- serveMutate方法
- body是否为空
- req header的Content-Type 是否为application/json
var body []byteif r.Body != nil {if data, err := ioutil.ReadAll(r.Body); err == nil {body = data}}if len(body) == 0 {glog.Error("empty body")http.Error(w, "empty body", http.StatusBadRequest)return}// verify the content type is accuratecontentType := r.Header.Get("Content-Type")if contentType != "application/json" {glog.Errorf("Content-Type=%s, expect application/json", contentType)http.Error(w, "invalid Content-Type, expect `application/json`", http.StatusUnsupportedMediaType)return}
准入控制请求参数校验
- 构造准入控制的审查对象 包括请求和响应
- 然后使用UniversalDeserializer解析传入的申请
- 如果出错就设置响应为报错的信息
- 没出错就调用mutatePod生成响应
// 构造准入控制器的响应var admissionResponse *v1beta1.AdmissionResponse// 构造准入控制的审查对象 包括请求和响应// 然后使用UniversalDeserializer解析传入的申请// 如果出错就设置响应为报错的信息// 没出错就调用mutatePod生成响应ar := v1beta1.AdmissionReview{}if _, _, err := deserializer.Decode(body, nil, &ar); err != nil {glog.Errorf("Can't decode body: %v", err)admissionResponse = &v1beta1.AdmissionResponse{Result: &metav1.Status{Message: err.Error(),},}} else {admissionResponse = ws.mutatePod(&ar)}
- 解析器使用UniversalDeserializer D:\go_path\pkg\mod\k8s.io\apimachinery@v0.22.1\pkg\runtime\serializer\codec_factory.go
package mainimport ("encoding/json""fmt""github.com/golang/glog""gopkg.in/yaml.v2""io/ioutil""k8s.io/api/admission/v1beta1"corev1 "k8s.io/api/core/v1"metav1 "k8s.io/apimachinery/pkg/apis/meta/v1""k8s.io/apimachinery/pkg/runtime""k8s.io/apimachinery/pkg/runtime/serializer""net/http"
)var (runtimeScheme = runtime.NewScheme()codecs = serializer.NewCodecFactory(runtimeScheme)deserializer = codecs.UniversalDeserializer()// (https://github.com/kubernetes/kubernetes/issues/57982)defaulter = runtime.ObjectDefaulter(runtimeScheme)
)
写入响应
- 构造最终响应对象 admissionReview
- 给response赋值
- json解析后用 w.write写入
// 构造最终响应对象 admissionReview// 给response赋值// json解析后用 w.write写入admissionReview := v1beta1.AdmissionReview{}if admissionResponse != nil {admissionReview.Response = admissionResponseif ar.Request != nil {admissionReview.Response.UID = ar.Request.UID}}resp, err := json.Marshal(admissionReview)if err != nil {glog.Errorf("Can't encode response: %v", err)http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)}glog.Infof("Ready to write reponse ...")if _, err := w.Write(resp); err != nil {glog.Errorf("Can't write response: %v", err)http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)}
mutatePod 注入函数编写
- 将请求中的对象解析为pod,如果出错就返回
// 将请求中的对象解析为pod,如果出错就返回req := ar.Requestvar pod corev1.Podif err := json.Unmarshal(req.Object.Raw, &pod); err != nil {glog.Errorf("Could not unmarshal raw object: %v", err)return &v1beta1.AdmissionResponse{Result: &metav1.Status{Message: err.Error(),},}}
是否需要注入判断
// 是否需要注入判断if !mutationRequired(ignoredNamespaces, &pod.ObjectMeta) {glog.Infof("Skipping mutation for %s/%s due to policy check", pod.Namespace, pod.Name)return &v1beta1.AdmissionResponse{Allowed: true,}}
- mutationRequired判断函数,判断这个pod资源要不要注入
-
- 如果pod在高权限的ns中,不注入
-
- 如果pod annotations中 标记为已注入就不再注入了
-
- 如果pod annotations中 配置不愿意注入就不注入
// 判断这个pod资源要不要注入
// 1. 如果pod在高权限的ns中,不注入
// 2. 如果pod annotations中 标记为已注入就不再注入了
// 3. 如果pod annotations中 配置不愿意注入就不注入
func mutationRequired(ignoredList []string, metadata *metav1.ObjectMeta) bool {// skip special kubernete system namespacesfor _, namespace := range ignoredList {if metadata.Namespace == namespace {glog.Infof("Skip mutation for %v for it's in special namespace:%v", metadata.Name, metadata.Namespace)return false}}annotations := metadata.GetAnnotations()if annotations == nil {annotations = map[string]string{}}// 如果 annotation中 标记为已注入就不再注入了status := annotations[admissionWebhookAnnotationStatusKey]if strings.ToLower(status) == "injected" {return false}// 如果pod中配置不愿意注入就不注入switch strings.ToLower(annotations[admissionWebhookAnnotationInjectKey]) {default:return falsecase "true":return false}
}
- 相关的常量定义
const (// 代表这个pod是否要注入 = ture代表要注入admissionWebhookAnnotationInjectKey = "sidecar-injector-webhook.xiaoyi/need_inject"// 代表判断pod已经注入过的标志 = injected代表已经注入了,就不再注入admissionWebhookAnnotationStatusKey = "sidecar-injector-webhook.xiaoyi/status"
)// 为了安全,不给这两个ns中的pod注入 sidecar
var ignoredNamespaces = []string{metav1.NamespaceSystem,metav1.NamespacePublic,
}
添加默认的配置
- https://github.com/kubernetes/kubernetes/pull/58025
defaulter = runtime.ObjectDefaulter(runtimeScheme)
func applyDefaultsWorkaround(containers []corev1.Container, volumes []corev1.Volume) {defaulter.Default(&corev1.Pod{Spec: corev1.PodSpec{Containers: containers,Volumes: volumes,},})
}
定义pathoption
type patchOperation struct {Op string `json:"op"` // 动作Path string `json:"path"` // 操作的pathValue interface{} `json:"value,omitempty"` // 值
}
生成容器端的patch函数
// 添加容器的patch
// 如果是第一个patch 需要在path末尾添加 /-
func addContainer(target, added []corev1.Container, basePath string) (patch []patchOperation) {first := len(target) == 0var value interface{}for _, add := range added {value = addpath := basePathif first {first = falsevalue = []corev1.Container{add}} else {path = path + "/-"}patch = append(patch, patchOperation{Op: "add",Path: path,Value: value,})}return patch
}
生成添加volume的patch函数
func addVolume(target, added []corev1.Volume, basePath string) (patch []patchOperation) {first := len(target) == 0var value interface{}for _, add := range added {value = addpath := basePathif first {first = falsevalue = []corev1.Volume{add}} else {path = path + "/-"}patch = append(patch, patchOperation{Op: "add",Path: path,Value: value,})}return patch
}
更新annotation 的patch
func updateAnnotation(target map[string]string, added map[string]string) (patch []patchOperation) {for key, value := range added {if target == nil || target[key] == "" {target = map[string]string{}patch = append(patch, patchOperation{Op: "add",Path: "/metadata/annotations",Value: map[string]string{key: value,},})} else {patch = append(patch, patchOperation{Op: "replace",Path: "/metadata/annotations/" + key,Value: value,})}}return patch
}
- 最终的patch调用
func createPatch(pod *corev1.Pod, sidecarConfig *Config, annotations map[string]string) ([]byte, error) {var patch []patchOperationpatch = append(patch, addContainer(pod.Spec.Containers, sidecarConfig.Containers, "/spec/containers")...)patch = append(patch, addVolume(pod.Spec.Volumes, sidecarConfig.Volumes, "/spec/volumes")...)patch = append(patch, updateAnnotation(pod.Annotations, annotations)...)return json.Marshal(patch)
}
调用patch 生成patch option
- mutatePod方法中
annotations := map[string]string{admissionWebhookAnnotationStatusKey: "injected"}patchBytes, err := createPatch(&pod, ws.sidecarConfig, annotations)if err != nil {return &v1beta1.AdmissionResponse{Result: &metav1.Status{Message: err.Error(),},}}glog.Infof("AdmissionResponse: patch=%v\n", string(patchBytes))return &v1beta1.AdmissionResponse{Allowed: true,Patch: patchBytes,PatchType: func() *v1beta1.PatchType {pt := v1beta1.PatchTypeJSONPatchreturn &pt}(),}return nil
本节重点总结 :
- serveMutate编写
- 准入控制请求参数校验
- 根据annotation标签判断是否需要注入sidecar
- mutatePod 注入函数编写
- 生成注入容器和volume的patch函数