[译]深刻剖析 Kubernetes MutatingAdmissionWebhook

《[译]深刻剖析 Kubernetes MutatingAdmissionWebhook》最先发布在blog.hdls.me/15564491070…html

翻译自 《Diving into Kubernetes MutatingAdmissionWebhook》 原文连接:medium.com/ibm-cloud/d…node

对于在数据持久化以前,拦截到 Kubernetes API server 的请求,Admission controllers 是很是有用的工具。然而,因为其须要由集群管理员在 kube-apiserver 中编译成二进制文件,因此使用起来不是很灵活。从 Kubernetes 1.7 起,引入了 InitializersExternal Admission Webhooks,用以解决这个问题。在 Kubernetes 1.9 中,Initializers 依然停留在 alpha 版本,然而 External Admission Webhooks 被提高到了 beta 版,且被分红了 MutatingAdmissionWebhookValidatingAdmissionWebhooklinux

MutatingAdmissionWebhookValidatingAdmissionWebhook 两者合起来就是一个特殊类型的 admission controllers,一个处理资源更改,一个处理验证。验证是经过匹配到 MutatingWebhookConfiguration 中定义的规则完成的。nginx

在这篇文章中,我会深刻剖析 MutatingAdmissionWebhook 的细节,并一步步实现一个可用的 webhook admission servergit

Webhooks 的好处

Kubernetes 集群管理员可使用 webhooks 来建立额外的资源更改及验证准入插件,这些准入插件能够经过 apiserver 的准入链来工做,而不须要从新编译 apiserver。这使得开发者能够对于不少动做均可以自定义准入逻辑,好比对任何资源的建立、更新、删除,给开发者提供了很大的自由和灵活度。可使用的应用数量巨大。一些常见的使用常见包括:github

  1. 在建立资源以前作些更改。Istio 是个很是典型的例子,在目标 pods 中注入 Envoy sidecar 容器,来实现流量管理和规则执行。
  2. 自动配置 StorageClass。监听 PersistentVolumeClaim 资源,并按照事先定好的规则自动的为之增添对应的 StorageClass。使用者无需关心 StorageClass 的建立。
  3. 验证复杂的自定义资源。确保只有其被定义后且全部的依赖项都建立好并可用,自定义资源才能够建立。
  4. namespace 的限制。在多租户系统中,避免资源在预先保留的 namespace 中被建立。

除了以上列出来的使用场景,基于 webhooks 还能够建立更多应用。web

Webhooks 和 Initializers

基于社区的反馈,以及对 External Admission WebhooksInitializers 的 alpha 版本的使用案例,Kubernetes 社区决定将 webhooks 升级到 beta 版,而且将其分红两种 webhooks(MutatingAdmissionWebhookValidatingAdmissionWebhook)。这些更新使得 webhooks 与其余 admission controllers 保持一致,而且强制 mutate-before-validateInitializers 能够在 Kubernetes 资源建立前更改,从而实现动态准入控制。若是你对 Initializers 不熟悉,能够参考这篇文章docker

因此,到底 WebhooksInitializers 之间的区别是什么呢?shell

  1. Webhooks 能够应用于更多操做,包括对于资源 "增删改" 的 "mutate" 和 "admit";然而 Initializers 不能够对 "删" 资源进行 "admit"。
  2. Webhooks 在建立资源前不容许查询;然而 Initializers 能够监听未初始化的资源,经过参数 ?includeUninitialized=true 来实现。
  3. 因为 Initializers 会把 "预建立" 状态也持久化到 etcd,所以会引入高延迟且给 etcd 带来负担,尤为在 apiserver 升级或失败时;然而 Webhooks 消耗的内存和计算资源更少。
  4. WebhooksInitializers 对失败的保障更强大。Webhooks 的配置中能够配置失败策略,用以免资源在建立的时候被 hang 住。然而 Initializers 在尝试建立资源的时候可能会 block 住全部的资源。

除了上面列举的不一样点,Initializer 在较长一段开发时间内还存在不少已知问题,包括配额补充错误等。Webhooks 升级为 beta 版也就预示着在将来 Webhooks 会是开发目标。若是你须要更稳定的操做,我推荐使用 Webhooksjson

MutatingAdmissionWebhook 如何工做

MutatingAdmissionWebhook 在资源被持久化到 etcd 前,根据规则将其请求拦截,拦截规则定义在 MutatingWebhookConfiguration 中。MutatingAdmissionWebhook 经过对 webhook server 发送准入请求来实现对资源的更改。而 webhook server 只是一个简单的 http 服务器。

下面这幅图详细描述了 MutatingAdmissionWebhook 如何工做:

MutatingAdmissionWebhook 须要三个对象才能运行:

MutatingWebhookConfiguration

MutatingAdmissionWebhook 须要根据 MutatingWebhookConfiguration 向 apiserver 注册。在注册过程当中,MutatingAdmissionWebhook 须要说明:

  1. 如何链接 webhook admission server
  2. 如何验证 webhook admission server
  3. webhook admission server 的 URL path;
  4. webhook 须要操做对象知足的规则;
  5. webhook admission server 处理时遇到错误时如何处理。

MutatingAdmissionWebhook 自己

MutatingAdmissionWebhook 是一种插件形式的 admission controller ,且能够配置到 apiserver 中。MutatingAdmissionWebhook 插件能够从 MutatingWebhookConfiguration 中获取全部感兴趣的 admission webhooks

而后 MutatingAdmissionWebhook 监听 apiserver 的请求,拦截知足条件的请求,并并行执行。

Webhook Admission Server

Webhook Admission Server 只是一个附着到 k8s apiserver 的 http server。对于每个 apiserver 的请求,MutatingAdmissionWebhook 都会发送一个 admissionReview 到相关的 webhook admission serverwebhook admission server 再决定如何更改资源。

MutatingAdmissionWebhook 教程

编写一个完整的 Webhook Admission Server 可能使人生畏。为了方便起见,咱们编写一个简单的 Webhook Admission Server 来实现注入 nginx sidecar 容器以及挂载 volume。完整代码在 kube-mutating-webhook-tutorial。这个项目参考了 Kubernetes webhook 示例Istio sidecar 注入实现

在接下来的段落里,我会向你展现如何编写可工做的容器化 webhook admission server,并将其部署到 Kubernetes 集群中。

前置条件

MutatingAdmissionWebhook 要求 Kubernetes 版本为 1.9.0 及以上,其 admissionregistration.k8s.io/v1beta1 API 可用。确保下面的命令:

kubectl api-versions | grep admissionregistration.k8s.io/v1beta1
复制代码

其输出为:

admissionregistration.k8s.io/v1beta1
复制代码

另外,MutatingAdmissionWebhookValidatingAdmissionWebhook 准入控制器须要以正确的顺序加入到 kube-apiserveradmission-control 标签中。

编写 Webhook Server

Webhook Admission Server 是一个简单的 http 服务器,遵循 Kubernetes API。我粘贴部分伪代码来描述主逻辑:

sidecarConfig, err := loadConfig(parameters.sidecarCfgFile)
pair, err := tls.LoadX509KeyPair(parameters.certFile, parameters.keyFile)

whsvr := &WebhookServer {
    sidecarConfig:    sidecarConfig,
    server:           &http.Server {
        Addr:        fmt.Sprintf(":%v", 443),
        TLSConfig:   &tls.Config{Certificates: []tls.Certificate{pair}},
    },
}
	
// define http server and server handler
mux := http.NewServeMux()
mux.HandleFunc("/mutate", whsvr.serve)
whsvr.server.Handler = mux

// start webhook server in new rountine
go func() {
    if err := whsvr.server.ListenAndServeTLS("", ""); err != nil {
        glog.Errorf("Filed to listen and serve webhook server: %v", err)
    }
}()
复制代码

以上代码的详解:

  1. sidecarCfgFile 包含了 sidecar 注入器模板,其在下面的 ConfigMap 中定义;
  2. certFilekeyFile 是秘钥对,会在 webhook server 和 apiserver 之间的 TLS 通讯中用到;
  3. 19 行开启了 https server,以监听 443 端口路径为 '/mutate'

接下来咱们关注处理函数 serve 的主要逻辑:

// Serve method for webhook server
func (whsvr *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
	var body []byte
	if r.Body != nil {
		if data, err := ioutil.ReadAll(r.Body); err == nil {
			body = data
		}
	}

	var reviewResponse *v1beta1.AdmissionResponse
	ar := v1beta1.AdmissionReview{}
	deserializer := codecs.UniversalDeserializer()
	if _, _, err := deserializer.Decode(body, nil, &ar); err != nil {
		glog.Error(err)
		reviewResponse = toAdmissionResponse(err)
	} else {
		reviewResponse = mutate(ar)
	}

	response := v1beta1.AdmissionReview{}
	if reviewResponse != nil {
		response.Response = reviewResponse
		response.Response.UID = ar.Request.UID
	}
	// reset the Object and OldObject, they are not needed in a response.
	ar.Request.Object = runtime.RawExtension{}
	ar.Request.OldObject = runtime.RawExtension{}

	resp, err := json.Marshal(response)
	if err != nil {
		glog.Error(err)
	}
	if _, err := w.Write(resp); err != nil {
		glog.Error(err)
	}
}
复制代码

函数 serve 是一个简单的 http 处理器,参数为 http requestresponse writer

  1. 首先将请求组装为 AdmissionReview,其中包括 objectoldobjectuserInfo ...
  2. 而后触发 Webhook 主函数 mutate 来建立 patch,以实现注入 sidecar 容器及挂载 volume。
  3. 最后,将 admission decision 和额外 patch 组装成响应,并发送回给 apiserver。

对于函数 mutate 的实现,你能够随意发挥。我就以个人实现方式作个例子:

// main mutation process
func (whsvr *WebhookServer) mutate(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
	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(),
			},
		}
	}
	
	// determine whether to perform mutation
	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, 
		}
	}

	annotations := map[string]string{admissionWebhookAnnotationStatusKey: "injected"}
	patchBytes, err := createPatch(&pod, whsvr.sidecarConfig, annotations)
	
	return &v1beta1.AdmissionResponse {
		Allowed: true,
		Patch:   patchBytes,
		PatchType: func() *v1beta1.PatchType {
			pt := v1beta1.PatchTypeJSONPatch
			return &pt
		}(),
	}
}
复制代码

从上述代码中能够看出,函数 mutate 请求了 mutationRequired 来决定这个改动是否被容许。对于被容许的请求,函数 mutate 从另外一个函数 createPatch 中获取到修改体 'patch'。注意这里函数 mutationRequired 的诡计,咱们跳过了带有注解 sidecar-injector-webhook.morven.me/inject: true。这里稍后会在部署 deployment 的时候提到。完整代码请参考这里

编写 Dockerfile 并构建

建立构建脚本:

dep ensure
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o kube-mutating-webhook-tutorial .
docker build --no-cache -t morvencao/sidecar-injector:v1 .
rm -rf kube-mutating-webhook-tutorial

docker push morvencao/sidecar-injector:v1
复制代码

如下面为依赖编写 Dockerfile 文件:

FROM alpine:latest

ADD kube-mutating-webhook-tutorial /kube-mutating-webhook-tutorial ENTRYPOINT ["./kube-mutating-webhook-tutorial"] 复制代码

在手动构建容器前,你须要 Docker ID 帐号并将 image name 和 tag (Dockerfile 和 deployment.yaml 文件中)修改为你本身的,而后执行如下命令:

[root@mstnode kube-mutating-webhook-tutorial]# ./build
Sending build context to Docker daemon  44.89MB
Step 1/3 : FROM alpine:latest
 ---> 3fd9065eaf02
Step 2/3 : ADD kube-mutating-webhook-tutorial /kube-mutating-webhook-tutorial
 ---> 432de60c2b3f
Step 3/3 : ENTRYPOINT ["./kube-mutating-webhook-tutorial"]
 ---> Running in da6e956d1755
Removing intermediate container da6e956d1755
 ---> 619faa936145
Successfully built 619faa936145
Successfully tagged morvencao/sidecar-injector:v1
The push refers to repository [docker.io/morvencao/sidecar-injector]
efd05fe119bb: Pushed
cd7100a72410: Layer already exists
v1: digest: sha256:7a4889928ec5a8bcfb91b610dab812e5228d8dfbd2b540cd7a341c11f24729bf size: 739
复制代码

编写 Sidecar 注入配置

如今咱们来建立一个 Kubernetes ConfigMap,包含须要注入到目标 pod 中的容器和 volume 信息:

apiVersion: v1
kind: ConfigMap
metadata:
 name: sidecar-injector-webhook-configmap
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
复制代码

从上面的清单中看,这里须要另外一个包含 nginx confConfigMap。完整 yaml 参考 nginxconfigmap.yaml

而后将这两个 ConfigMap 部署到集群中:

[root@mstnode kube-mutating-webhook-tutorial]# kubectl create -f ./deployment/nginxconfigmap.yaml
configmap "nginx-configmap" created
[root@mstnode kube-mutating-webhook-tutorial]# kubectl create -f ./deployment/configmap.yaml
configmap "sidecar-injector-webhook-configmap" created
复制代码

建立包含秘钥对的 Secret

因为准入控制是一个高安全性操做,因此对外在的 webhook server 提供 TLS 是必须的。做为流程的一部分,咱们须要建立由 Kubernetes CA 签名的 TLS 证书,以确保 webhook server 和 apiserver 之间通讯的安全性。对于 CSR 建立和批准的完整步骤,请参考 这里

简单起见,咱们参考了 Istio 的脚本并建立了一个相似的名为 webhook-create-signed-cert.sh 的脚本,来自动生成证书及秘钥对并将其加入到 secret 中。

#!/bin/bash
while [[ $# -gt 0 ]]; do
    case ${1} in
        --service)
            service="$2"
            shift
            ;;
        --secret)
            secret="$2"
            shift
            ;;
        --namespace)
            namespace="$2"
            shift
            ;;
    esac
    shift
done

[ -z ${service} ] && service=sidecar-injector-webhook-svc
[ -z ${secret} ] && secret=sidecar-injector-webhook-certs
[ -z ${namespace} ] && namespace=default

csrName=${service}.${namespace}
tmpdir=$(mktemp -d)
echo "creating certs in tmpdir ${tmpdir} "

cat <<EOF >> ${tmpdir}/csr.conf
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[req_distinguished_name]
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = ${service}
DNS.2 = ${service}.${namespace}
DNS.3 = ${service}.${namespace}.svc
EOF

openssl genrsa -out ${tmpdir}/server-key.pem 2048
openssl req -new -key ${tmpdir}/server-key.pem -subj "/CN=${service}.${namespace}.svc" -out ${tmpdir}/server.csr -config ${tmpdir}/csr.conf
 # clean-up any previously created CSR for our service. Ignore errors if not present.
kubectl delete csr ${csrName} 2>/dev/null || true
 # create server cert/key CSR and send to k8s API
cat <<EOF | kubectl create -f -
apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
  name: ${csrName}
spec:
  groups:
  - system:authenticated
  request: $(cat ${tmpdir}/server.csr | base64 | tr -d '\n')
  usages:
  - digital signature
  - key encipherment
  - server auth
EOF
 # verify CSR has been created
while true; do
    kubectl get csr ${csrName}
    if [ "$?" -eq 0 ]; then
        break
    fi
done
 # approve and fetch the signed certificate
kubectl certificate approve ${csrName}
# verify certificate has been signed
for x in $(seq 10); do
    serverCert=$(kubectl get csr ${csrName} -o jsonpath='{.status.certificate}')
    if [[ ${serverCert} != '' ]]; then
        break
    fi
    sleep 1
done
if [[ ${serverCert} == '' ]]; then
    echo "ERROR: After approving csr ${csrName}, the signed certificate did not appear on the resource. Giving up after 10 attempts." >&2
    exit 1
fi
echo ${serverCert} | openssl base64 -d -A -out ${tmpdir}/server-cert.pem
 # create the secret with CA cert and server cert/key
kubectl create secret generic ${secret} \
        --from-file=key.pem=${tmpdir}/server-key.pem \
        --from-file=cert.pem=${tmpdir}/server-cert.pem \
        --dry-run -o yaml |
    kubectl -n ${namespace} apply -f -
复制代码

运行脚本后,包含证书和秘钥对的 secret 就被建立出来了:

[root@mstnode kube-mutating-webhook-tutorial]# ./deployment/webhook-create-signed-cert.sh
creating certs in tmpdir /tmp/tmp.wXZywp0wAF
Generating RSA private key, 2048 bit long modulus
...........................................+++
..........+++
e is 65537 (0x10001)
certificatesigningrequest "sidecar-injector-webhook-svc.default" created
NAME                                   AGE       REQUESTOR                                           CONDITION
sidecar-injector-webhook-svc.default   0s        https://mycluster.icp:9443/oidc/endpoint/OP#admin   Pending
certificatesigningrequest "sidecar-injector-webhook-svc.default" approved
secret "sidecar-injector-webhook-certs" created
复制代码

建立 Sidecar 注入器的 Deployment 和 Service

deployment 带有一个 pod,其中运行的就是 sidecar-injector 容器。该容器以特殊参数运行:

  1. sidecarCfgFile 指的是 sidecar 注入器的配置文件,挂载自上面建立的 ConfigMap sidecar-injector-webhook-configmap
  2. tlsCertFiletlsKeyFile 是秘钥对,挂载自 Secret injector-webhook-certs
  3. alsologtostderrv=42>&1 是日志参数。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
 name: sidecar-injector-webhook-deployment
 labels:
 app: sidecar-injector
spec:
 replicas: 1
 template:
 metadata:
 labels:
 app: sidecar-injector
 spec:
 containers:
 - name: sidecar-injector
 image: morvencao/sidecar-injector:v1
 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
复制代码

Service 暴露带有 app=sidecar-injector label 的 pod,使之在集群中可访问。这个 Service 会被 MutatingWebhookConfiguration 中定义的 clientConfig 部分访问,默认的端口 spec.ports.port 须要设置为 443。

apiVersion: v1
kind: Service
metadata:
 name: sidecar-injector-webhook-svc
 labels:
 app: sidecar-injector
spec:
 ports:
 - port: 443
 targetPort: 443
 selector:
 app: sidecar-injector
复制代码

而后将上述 Deployment 和 Service 部署到集群中,而且验证 sidecar 注入器的 webhook server 是否 running:

[root@mstnode kube-mutating-webhook-tutorial]# kubectl create -f ./deployment/deployment.yaml
deployment "sidecar-injector-webhook-deployment" created
[root@mstnode kube-mutating-webhook-tutorial]# kubectl create -f ./deployment/service.yaml
service "sidecar-injector-webhook-svc" created
[root@mstnode kube-mutating-webhook-tutorial]# kubectl get deployment
NAME                                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
sidecar-injector-webhook-deployment   1         1         1            1           2m
[root@mstnode kube-mutating-webhook-tutorial]# kubectl get pod
NAME                                                  READY     STATUS    RESTARTS   AGE
sidecar-injector-webhook-deployment-bbb689d69-fdbgj   1/1       Running   0          3m
复制代码

动态配置 webhook 准入控制器

MutatingWebhookConfiguration 中具体说明了哪一个 webhook admission server 是被使用的而且哪些资源受准入服务器的控制。建议你在建立 MutatingWebhookConfiguration 以前先部署 webhook admission server,并确保其正常工做。不然,请求会被无条件接收或根据失败规则被拒。

如今,咱们根据下面的内容建立 MutatingWebhookConfiguration

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: default
 path: "/mutate"
 caBundle: ${CA_BUNDLE}
 rules:
 - operations: [ "CREATE" ]
 apiGroups: [""]
 apiVersions: ["v1"]
 resources: ["pods"]
 namespaceSelector:
 matchLabels:
 sidecar-injector: enabled
复制代码

第 8 行:name - webhook 的名字,必须指定。多个 webhook 会以提供的顺序排序; 第 9 行:clientConfig - 描述了如何链接到 webhook admission server 以及 TLS 证书; 第 15 行:rules - 描述了 webhook server 处理的资源和操做。在咱们的例子中,只拦截建立 pods 的请求; 第 20 行:namespaceSelector - namespaceSelector 根据资源对象是否匹配 selector 决定了是否针对该资源向 webhook server 发送准入请求。

在部署 MutatingWebhookConfiguration 前,咱们须要将 ${CA_BUNDLE} 替换成 apiserver 的默认 caBundle。咱们写个脚原本自动匹配:

#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail

ROOT=$(cd $(dirname $0)/../../; pwd)

export CA_BUNDLE=$(kubectl get configmap -n kube-system extension-apiserver-authentication -o=jsonpath='{.data.client-ca-file}' | base64 | tr -d '\n')

if command -v envsubst >/dev/null 2>&1; then
    envsubst
else
    sed -e "s|\${CA_BUNDLE}|${CA_BUNDLE}|g"
fi
复制代码

而后执行:

[root@mstnode kube-mutating-webhook-tutorial]# cat ./deployment/mutatingwebhook.yaml |\
> ./deployment/webhook-patch-ca-bundle.sh >\
> ./deployment/mutatingwebhook-ca-bundle.yaml
复制代码

咱们看不到任何日志描述 webhook server 接收到准入请求,彷佛该请求并无发送到 webhook server 同样。因此有一种可能性是这是被 MutatingWebhookConfiguration 中的配置触发的。再确认一下 MutatingWebhookConfiguration 咱们会发现下面的内容:

namespaceSelector:
 matchLabels:
 sidecar-injector: enabled
复制代码

经过 namespaceSelector 控制 sidecar 注入器

咱们在 MutatingWebhookConfiguration 中配置了 namespaceSelector,也就意味着只有在知足条件的 namespace 下的资源可以被发送到  webhook server。因而咱们将 default 这个 namespace 打上标签 sidecar-injector=enabled

[root@mstnode kube-mutating-webhook-tutorial]# kubectl label namespace default sidecar-injector=enabled
namespace "default" labeled
[root@mstnode kube-mutating-webhook-tutorial]# kubectl get namespace -L sidecar-injector
NAME          STATUS    AGE       sidecar-injector
default       Active    1d        enabled
kube-public   Active    1d
kube-system   Active    1d
复制代码

如今咱们配置的 MutatingWebhookConfiguration 会在 pod 建立的时候就注入 sidecar 容器。将运行中的 pod 删除并确认是否建立了新的带 sidecar 容器的 pod:

[root@mstnode kube-mutating-webhook-tutorial]# kubectl delete pod sleep-6d79d8dc54-r66vz
pod "sleep-6d79d8dc54-r66vz" deleted
[root@mstnode kube-mutating-webhook-tutorial]# kubectl get pods
NAME                                                  READY     STATUS              RESTARTS   AGE
sidecar-injector-webhook-deployment-bbb689d69-fdbgj   1/1       Running             0          29m
sleep-6d79d8dc54-b8ztx                                0/2       ContainerCreating   0          3s
sleep-6d79d8dc54-r66vz                                1/1       Terminating         0          11m
[root@mstnode kube-mutating-webhook-tutorial]# kubectl get pod sleep-6d79d8dc54-b8ztx -o yaml
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubernetes.io/psp: default
    sidecar-injector-webhook.morven.me/inject: "true"
    sidecar-injector-webhook.morven.me/status: injected
  labels:
    app: sleep
    pod-template-hash: "2835848710"
  name: sleep-6d79d8dc54-b8ztx
  namespace: default
spec:
  containers:
  - command:
    - /bin/sleep
    - infinity
    image: tutum/curl
    imagePullPolicy: IfNotPresent
    name: sleep
    resources: {}
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-d7t2r
      readOnly: true
  - image: nginx:1.12.2
    imagePullPolicy: IfNotPresent
    name: sidecar-nginx
    ports:
    - containerPort: 80
      protocol: TCP
    resources: {}
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /etc/nginx
      name: nginx-conf
  volumes:
  - name: default-token-d7t2r
    secret:
      defaultMode: 420
      secretName: default-token-d7t2r
  - configMap:
      defaultMode: 420
      name: nginx-configmap
    name: nginx-conf
...
复制代码

能够看到,sidecar 容器和 volume 被成功注入到应用中。至此,咱们成功建立了可运行的带 MutatingAdmissionWebhook 的 sidecar 注入器。经过 namespaceSelector,咱们能够轻易的控制在特定的 namespace 中的 pods 是否须要被注入 sidecar 容器。

但这里有个问题,根据以上的配置,在 default 这个 namespace 下的全部 pods 都会被注入 sidecar 容器,无一例外。

经过注解控制 sidecar 注入器

多亏了 MutatingAdmissionWebhook 的灵活性,咱们能够轻易的自定义变动逻辑来筛选带有特定注解的资源。还记得上面提到的注解 sidecar-injector-webhook.morven.me/inject: "true" 吗?在 sidecar 注入器中这能够当成另外一种控制方式。在 webhook server 中我写了一段逻辑来跳过那行不带这个注解的 pod。

咱们来尝试一下。在这种状况下,咱们建立另外一个 sleep 应用,其 podTemplateSpec 中不带注解 sidecar-injector-webhook.morven.me/inject: "true"

[root@mstnode kube-mutating-webhook-tutorial]# kubectl delete deployment sleep
deployment "sleep" deleted
[root@mstnode kube-mutating-webhook-tutorial]# cat <<EOF | kubectl create -f -
apiVersion: extensions/v1beta1
> kind: Deployment
> metadata:
> name: sleep
> spec:
> replicas: 1
> template:
> metadata:
> labels:
> app: sleep
> spec:
> containers:
> - name: sleep
> image: tutum/curl
> command: ["/bin/sleep","infinity"]
> imagePullPolicy: IfNotPresent
> EOF
deployment "sleep" created
复制代码

而后确认 sidecar 注入器是否跳过了这个 pod:

[root@mstnode kube-mutating-webhook-tutorial]# kubectl get deployment
NAME                                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
sidecar-injector-webhook-deployment   1         1         1            1           45m
sleep                                 1         1         1            1           17s
[root@mstnode kube-mutating-webhook-tutorial]# kubectl get pod
NAME                                                  READY     STATUS        RESTARTS   AGE
sidecar-injector-webhook-deployment-bbb689d69-fdbgj   1/1       Running       0          45m
sleep-776b7bcdcd-4bz58                                1/1       Running       0          21s
复制代码

结果显示,这个 sleep 应用只包含一个容器,没有额外的容器和 volume 注入。而后咱们将这个 deployment 增长注解,并确认其重建后是否被注入 sidecar:

[root@mstnode kube-mutating-webhook-tutorial]# kubectl patch deployment sleep -p '{"spec":{"template":{"metadata":{"annotations":{"sidecar-injector-webhook.morven.me/inject": "true"}}}}}'
deployment "sleep" patched
[root@mstnode kube-mutating-webhook-tutorial]# kubectl delete pod sleep-776b7bcdcd-4bz58
pod "sleep-776b7bcdcd-4bz58" deleted
[root@mstnode kube-mutating-webhook-tutorial]# kubectl get pods
NAME                                                  READY     STATUS              RESTARTS   AGE
sidecar-injector-webhook-deployment-bbb689d69-fdbgj   1/1       Running             0          49m
sleep-3e42ff9e6c-6f87b                                0/2       ContainerCreating   0          18s
sleep-776b7bcdcd-4bz58                                1/1       Terminating         0          3m
复制代码

与预期一致,pod 被注入了额外的 sidecar 容器。至此,咱们就得到了一个可工做的 sidecar 注入器,可由 namespaceSelector 或更细粒度的由注解控制。

总结

MutatingAdmissionWebhook 是 Kubernetes 扩展功能中最简单的方法之一,工做方式是经过全新规则控制、资源更改。

此功能使得更多的工做模式变成了可能,而且支持了更多生态系统,包括服务网格平台 Istio。从 Istio 0.5.0 开始,Istio 的自动注入部分的代码被重构,实现方式从 initializers 变动为 MutatingAdmissionWebhook

参考文献

相关文章
相关标签/搜索