之前写了一个简单的webhook 的tls server,并且写了一个简单的validate的api 用于实现资源的拦截。之前有提到webhook有两类,第二类就是mutatingadmissionwebhook,即对资源进行修改,仔细想想不会太意外,比如istio本质上不就是mutatingadmissionwebhook么,在启动的时候直接sidecar方式插入一个container,然后通过该container进行各类服务收集/治理,那练手的话可以自己插入一个redis 容器,模拟istio的sidecar容器创建。

 

思路

同样整理一下思路先:

  • 和validateadmission 不一样,mutateadmission 需要的是对资源的修改,所以需要先定义一个修改操作的结构体,即所谓的patch:
  • 由于需要插入的是一个sidecar容器,所以需要先定义一个sidecar的容器资源
  • 然后webhook拦截api-server的请求,获取admissionreview,获取创建的资源信息
  • 然后对admissionreview 进行patch 操作,将sidecar的容器注入进去。
  • 然后回调api-server

理完思路写代码,先看下patch的结构体

// 定义patch对象
type patchOperation struct {
	Op    string      `json:"op"`
	Path  string      `json:"path"`
	Value interface{} `json:"value,omitempty"`
}

op是操作的类型,比如add,del, path是修改的资源对象的路径,value是修改资源对象的值。

然后将需要插入的sidecar 容器解析出来

// 需要插入的sidecar 容器
type sideConfig struct {
	Containers []corev1.Container `yaml:"containers"`
}

func loadSideCarConfig() (*sideConfig, error) {
	data, err := ioutil.ReadFile("/config/sidecar.yaml")
	if err != nil {
		return nil, err
	}

	var cfg sideConfig
	if err := yaml.Unmarshal(data, &cfg); err != nil {
		return nil, err
	}

	return &cfg, nil
}

现在有了具体的sidecar后,就只需要将插入sidecar 容器这个动作封装成patch ,以json序列化一下然后返回给api-server。

	// 返回具体的admissionresponse
	return &admissionv1.AdmissionResponse{
		Allowed: allowed,
		Patch:   patchBytes,
		PatchType: func() *admissionv1.PatchType {
			pt := admissionv1.PatchTypeJSONPatch
			return &pt
		}(),
	}

之后进行镜像的打包和上传

docker build -t registry.cn-shanghai.aliyuncs.com/zyxpaomian/webhook:v1.8 .
docker push registry.cn-shanghai.aliyuncs.com/zyxpaomian/webhook:v1.8

定义一个admissionwebhook, 只正对deployment和service的创建做拦截

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: admission-registry-mutate
webhooks:
- name: zyx.test.admission-registry-mutate
  clientConfig:
    service:
      namespace: default
      name: admission-registry
      path: "/v1/api/mutate"
    caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURuakNDQW9hZ0F3SUJBZ0lVT2RVeWVCenVVcFdIenFvMGczOE5XeE1SNm9jd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1p6RUxNQWtHQTFVRUJoTUNRMDR4RVRBUEJnTlZCQWdUQ0ZOb1lXNW5hR0ZwTVJFd0R3WURWUVFIRXdoVAphR0Z1WjJoaGFURU1NQW9HQTFVRUNoTURhemh6TVE4d0RRWURWUVFMRXdaVGVYTjBaVzB4RXpBUkJnTlZCQU1UCkNtdDFZbVZ5Ym1WMFpYTXdIaGNOTWpFd05USTBNRGMwT1RBd1doY05Nall3TlRJek1EYzBPVEF3V2pCbk1Rc3cKQ1FZRFZRUUdFd0pEVGpFUk1BOEdBMVVFQ0JNSVUyaGhibWRvWVdreEVUQVBCZ05WQkFjVENGTm9ZVzVuYUdGcApNUXd3Q2dZRFZRUUtFd05yT0hNeER6QU5CZ05WQkFzVEJsTjVjM1JsYlRFVE1CRUdBMVVFQXhNS2EzVmlaWEp1ClpYUmxjekNDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFLTWJLT0Q5WGMzMDhlWlQKK0J2MGYzR3QxTGtzWk1jUXNUUG5rQXpWeVRQSDdFSkpORkVoVzI0UE8yNzgvaTNWcEdPNU12MVZCcW1rcGwrSgorZ1dwZW5TSDNIbTdsOTZmckt4TmRGWG94Y3VLaWNPOHhvVUVxWCt0cVIvR0toUGYrcjBCWXFUR1o1aVVCZGdVCjU0STJIQjREc1Exc0lCY1JBUms5WVJ5ZjRLYXRZVVEzdGRuNFRyNmFhUXg5OE9Gd1hUNXpiNktINVQ2MTNKdk8KUExpV0JNWUxUZG5WZ256ZzVXeDUreDkvV0FtaTJSK0JjTllLUjY0RWgzeWJZam5NeGtGL1JEMTZPSTlFOTJNRApEaVB6U09oemplQ3RIY1NRcStpTllIQzhzSUJlRFlxL256bTFDR0hTL1NpMDdZVFBjZTBXeE5RelRpMlM2dlZXCkdrWGVPaWNDQXdFQUFhTkNNRUF3RGdZRFZSMFBBUUgvQkFRREFnRUdNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHcKSFFZRFZSME9CQllFRlB0S2U2TXFoTjlLaEJ5S3Ntdy9sR3pZd25iL01BMEdDU3FHU0liM0RRRUJDd1VBQTRJQgpBUUFqNnVyVTVhS0I5MWZuQTF2dE1DVjZRVHFmenMrd1dFcHZVbElXOHdZYXVydDZPWUc1YkxFSHoyRUJJRXplCjMvcGM4ZUkvamJSU0JNZDJGbk9Sa0pVTmh5MDBBZE05MTRFUFo5Y0FIOFpGUVI1UTZCUmw1Qnl6Mk5jOHN3dGMKMmtraW0vWUJ4TkFFYnFLQjBhTmw0TW5LbTc3TzgzSXpwREN2SzBWdlRCVXJVYnlzd1l6d0lCaEZxSTl5ZUo2Zgo1U1JpemdrUHVYRG1qSlZiN092dzNTNkxRT1o4VmdJRS9wZGJvRTBsTHpmZ2pROWVDVFM0ZmRXSE9oNGp1TXJMCklqeG1pMHhOVVVkTXdPeFNvQlJKa3J3eU1Fa1FoN2swYmZROFdsRkFNenVST29RVmVraU1BNkhLeDdQOFd1UmUKYzhKRkVOVmk5VEVMTXkwWHpJNWhoOVlwCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
  rules:
    - operations: [ "CREATE" ]
      apiGroups: ["apps", ""]
      apiVersions: ["v1"]
      resources: ["deployments","services"]
  admissionReviewVersions: [ "v1" ]
  sideEffects: None

进行测试, 和之前一样编写一个deployment,里面只有一个nginx的container:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: docker.io/nginx:latest
        ports:
        - containerPort: 80

然后进行apply:
Kubernetes开发(6)-MutatingAdmissionWebhook练手_k8s
可以看到这个deployment 下面有2个container 在初始化,这第二个container 就是mutatingadmission 拦截后注入的sidecar 容器。

 

Kubernetes开发(6)-MutatingAdmissionWebhook练手_Go_02