最近部分NE需要使用到webhook来实现注入sidecar进业务pod中。今天把这个过程梳理一下,刚好OpenKruise中也使用到此对象。
具体的内容参考:https://medium.com/ibm-cloud/diving-into-kubernetes-mutatingadmissionwebhook-6ef3c5695f74
- 1.webhook 逻辑:
```properties
具体的webhook的定义我们这里不做赘述,这里可以从kubernetes的官网上找到对应的介绍和一张比较经典的图例。其中涉及到一些概念,我们通常只是去看了有一个大致,但是没有真是的去看它的逻辑。
1.首先webhook是会去拦截到API Server的数据,这里就有一个疑问,为什么我们能够拦截到这个数据,或是说APIServer为什么会等待webhook处理完成以后,才进行下边的处理。其实这里我们需要验证的是:
[root@dev1 kubernetes]# kubectl api-versions | grep admissionregistration.k8s.io
admissionregistration.k8s.io/v1
admissionregistration.k8s.io/v1beta1
[root@dev1 kubernetes]#
而此结果需要我们在API Server中做配置:
[root@dev1 kubernetes]# kubectl -nkube-system get pods kube-apiserver-dev1 -o yaml
- —enable-admission-plugins=NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook # 不过在较高的kuber版本以后,这个配置默认是打开的。 所以这里配置了以后,我们其实可以看到我们的API Server每次在处理的时候,都会被这个HOOK给“勾一下”。所以这个看起来是一个trigger。但是我们我们把整个的webhook的过程走下来,我们发现,具体的,而是依赖service来处理。找到对应的后端的endpoint list。其实在OpenKruise中也有一段描述,我们看看:
除了 controller 之外,kruise-controller-manager-xxx 中还包含了针对 Kruise CRD 以及 Pod 资源的 admission webhook。Kruise-manager 会创建一些 webhook configurations 来配置哪些资源需要感知处理、以及提供一个 Service 来给 kube-apiserver 调用。
$ kubectl get svc -n kruise-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kruise-webhook-service ClusterIP 172.24.9.234
所以我们来看webhook的第一个对象: 1.MutatingWebhookConfiguration: 2.MutatingAdmissionWebhook 本身 3.Webhook Admission Server
下边我们就具体看看这个配置,弄清楚这个配置以后,我们就知道webhook的处理逻辑了。
[所有的配置可以参考这里]: https://github.com/BurlyLuo/train/blob/main/Webhook.zip。
- [x] **2.MutatingWebhookConfiguration**
```properties
MutatingAdmissionWebhook 需要根据 MutatingWebhookConfiguration 向 apiserver 注册。在注册过程中,MutatingAdmissionWebhook 需要说明:
如何连接 webhook admission server;
如何验证 webhook admission server;
webhook admission server 的 URL path;
webhook 需要操作对象满足的规则;
webhook admission server 处理时遇到错误时如何处理。
我们给一个demo:
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
name: sidecar-injector-webhook-cfg
labels:
app: sidecar-injector
webhooks:
- name: sidecar-injector.morven.me
clientConfig:
service:
name: sidecar-injector-webhook-svc
namespace: webhook
path: "/mutate"
caBundle: ${CA_BUNDLE}
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
namespaceSelector:
matchLabels:
sidecar-injection: enabled
# 然后使用istio 项目的脚本去补全 caBundle: ${CA_BUNDLE} 这里的信息。
caBundle: "Ci0tLS0tQk...<base64-encoded PEM bundle containing the CA that signed the webhook's serving certificate>...tLS0K"
https://kubernetes.io/zh/docs/reference/access-authn-authz/extensible-admission-controllers/
如何连接 webhook admission server; # 这里指的是:配置service 然后解析service找到对应的admission server。
如何验证 webhook admission server;
webhook admission server 的 URL path;
webhook 需要操作对象满足的规则;
namespaceSelector:
matchLabels:
sidecar-injection: enabled
webhook admission server 处理时遇到错误时如何处理。
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
#
clientConfig:
#用来校验服务的证书是否正确的CA证书(https的证书域名是admission-webhook.default.svc)
caBundle: #base64的ca证书
service:
namespace: webhook #请求的pod所在的namespace
name: sidecar-injector-webhook-svc #请求的service name
path: /mutate #请求的url
[]https://github.com/YaoZengzeng/KubernetesResearch/blob/master/KubernetesAdmissionController%E8%A7%A3%E6%9E%90.md
其实我们从这个配置中结合上边的描述,我们大致也能知道个大概:
其中,由于API-Server之支持HTTPS,所以需要配置CA,这个可以参考istio的设置来处理。
所以这里我们需要创建一个service:
[root@dev1 deploy]# cat service.yaml
apiVersion: v1
kind: Service
metadata:
name: sidecar-injector-webhook-svc
namespace: webhook
labels:
app: sidecar-injector
spec:
ports:
- port: 443
targetPort: 8443
selector:
app: sidecar-injector
[root@dev1 deploy]#
而这个service对应的backend为:
[root@dev1 ~]# kubectl -nwebhook describe svc sidecar-injector-webhook-svc
Name: sidecar-injector-webhook-svc
Namespace: webhook
Labels: app=sidecar-injector
Annotations: <none>
Selector: app=sidecar-injector
Type: ClusterIP
IP Families: <none>
IP: 10.107.143.88
IPs: 10.107.143.88
Port: <unset> 443/TCP
TargetPort: 8443/TCP
Endpoints: 10.0.0.181:8443 # 这里便是我们webhook server的地址。
Session Affinity: None
Events: <none>
[root@dev1 ~]#
也就是说我们还需要webhook server:
[root@dev1 ~]# kubectl -nwebhook get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
sidecar-injector-webhook-deployment-86b5b4fb49-vkpj2 1/1 Running 0 47h 10.0.0.181 dev2 <none> <none>
[root@dev1 ~]#
这个时候就知道发给谁了,也就是webhook server的地址了。
与其对应的还有一个path,可以理解为访问对应的某一个服务,一个路径。
此时逻辑貌似就不缺少什么了。关键是webhook中的mutating,在做什么,这里我们也需要弄清楚:
在webhook server中:
[root@dev1 deploy]# cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: sidecar-injector-webhook-deployment
namespace: webhook
labels:
app: sidecar-injector
spec:
replicas: 1
selector:
matchLabels:
app: sidecar-injector
template:
metadata:
labels:
app: sidecar-injector
spec:
containers:
- name: sidecar-injector
image: morvencao/sidecar-injector:latest
imagePullPolicy: IfNotPresent
args:
- -sidecarCfgFile=/etc/webhook/config/sidecarconfig.yaml # 注入的内容。
- -tlsCertFile=/etc/webhook/certs/cert.pem
- -tlsKeyFile=/etc/webhook/certs/key.pem
- -alsologtostderr
- -v=4
- 2>&1
volumeMounts:
- name: webhook-certs
mountPath: /etc/webhook/certs
readOnly: true
- name: webhook-config
mountPath: /etc/webhook/config
volumes:
- name: webhook-certs
secret:
secretName: sidecar-injector-webhook-certs
- name: webhook-config
configMap:
name: sidecar-injector-webhook-configmap # 挂载configmap
[root@dev1 deploy]# kubectl -nwebhook get cm
[root@dev1 deploy]# cat configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: sidecar-injector-webhook-configmap
namespace: webhook
data:
sidecarconfig.yaml: |
containers:
- name: sidecar-nginx
image: nginx:1.12.2
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
volumeMounts:
- name: nginx-conf
mountPath: /etc/nginx
volumes:
- name: nginx-conf
configMap:
name: nginx-configmap
[root@dev1 deploy]#
我们需要在创建的时候:
规则:
rules:
- operations: ["CREATE", "UPDATE"] # 操作
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"] # 资源类型
namespaceSelector:
matchLabels:
sidecar-injection: enabled # ns级别
[创建apline,这里会注入我们配置的configmap信息的nginx:]
kubectl run alpine --image=alpine --restart=Never -n webhook --overrides='{"apiVersion":"v1","metadata":{"annotations":{"sidecar-injector-webhook.morven.me/inject":"yes"}}}' --command -- sleep infinity
但是这里会加入:注解来实现。这点很重要。
- 3.MutatingAdmissionWebhook 本身 ```properties MutatingAdmissionWebhook 是一种插件形式的 admission controller ,且可以配置到 apiserver 中。MutatingAdmissionWebhook 插件可以从 MutatingWebhookConfiguration 中获取所有感兴趣的 admission webhooks。
然后 MutatingAdmissionWebhook 监听 apiserver 的请求,拦截满足条件的请求,并并行执行。
- [x] **4.Webhook Admission Server**
```properties
Webhook Admission Server 只是一个附着到 k8s apiserver 的 http server。对于每一个 apiserver 的请求,MutatingAdmissionWebhook 都会发送一个 admissionReview 到相关的 webhook admission server。webhook admission server 再决定如何更改资源。
- 5.webhook server log ```properties [root@dev1 ~]# kubectl -nwebhook logs -f sidecar-injector-webhook-deployment-86b5b4fb49-vkpj2 I1227 13:47:06.875470 1 webhook.go:88] New configuration: sha256sum 25d2a45d9d8301d08c7b99b6c6570e5626beeb39513488833daebc51453a836b I1227 13:48:47.590584 1 webhook.go:220] AdmissionReview for Kind=/v1, Kind=Pod, Namespace=webhook Name=alpine (alpine) UID=89f529e6-6abd-4319-b4c7-afd8b0a5941a patchOperation=CREATE UserInfo={kubernetes-admin [system:masters system:authenticated] map[]} I1227 13:48:47.590624 1 webhook.go:128] Mutation policy for /alpine: status: “” required:true I1227 13:48:47.590664 1 webhook.go:243] AdmissionResponse: patch=[{“op”:”add”,”path”:”/spec/containers/-“,”value”:{“name”:”sidecar-nginx”,”image”:”nginx:1.12.2”,”ports”:[{“containerPort”:80,”protocol”:”TCP”}],”resources”:{},”volumeMounts”:[{“name”:”nginx-conf”,”mountPath”:”/etc/nginx”}],”terminationMessagePath”:”/dev/termination-log”,”terminationMessagePolicy”:”File”,”imagePullPolicy”:”IfNotPresent”}},{“op”:”add”,”path”:”/spec/volumes/-“,”value”:{“name”:”nginx-conf”,”configMap”:{“name”:”nginx-configmap”,”defaultMode”:420}}},{“op”:”add”,”path”:”/metadata/annotations”,”value”:{“sidecar-injector-webhook.morven.me/status”:”injected”}}] I1227 13:48:47.590721 1 webhook.go:302] Ready to write reponse …
^C ```