[譯]深刻剖析 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

參考文獻

相關文章
相關標籤/搜索