本节重点总结 :
- serveMutate编写
- 准入控制请求参数校验
- 根据annotation标签判断是否需要注入sidecar
- mutatePod 注入函数编写
- 生成注入容器和volume的patch函数
serveMutate编写
普通校验请求
- serveMutate方法
- body是否为空
- req header的Content-Type 是否为application/json
var body []byte
if 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 accurate
contentType := 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 main
import (
"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 = admissionResponse
if 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.Request
var pod corev1.Pod
if 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 namespaces
for _, 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 false
case "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"` // 操作的path
Value interface{} `json:"value,omitempty"` // 值
}
生成容器端的patch函数
// 添加容器的patch
// 如果是第一个patch 需要在path末尾添加 /-
func addContainer(target, added []corev1.Container, basePath string) (patch []patchOperation) {
first := len(target) == 0
var value interface{}
for _, add := range added {
value = add
path := basePath
if first {
first = false
value = []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) == 0
var value interface{}
for _, add := range added {
value = add
path := basePath
if first {
first = false
value = []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 []patchOperation
patch = 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.PatchTypeJSONPatch
return &pt
}(),
}
return nil
本节重点总结 :
- serveMutate编写
- 准入控制请求参数校验
- 根据annotation标签判断是否需要注入sidecar
- mutatePod 注入函数编写
- 生成注入容器和volume的patch函数