《[譯]深刻剖析 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 起,引入了 Initializers
和 External Admission Webhooks
,用以解決這個問題。在 Kubernetes 1.9 中,Initializers
依然停留在 alpha 版本,然而 External Admission Webhooks
被提高到了 beta 版,且被分紅了 MutatingAdmissionWebhook
和 ValidatingAdmissionWebhook
。linux
MutatingAdmissionWebhook
和 ValidatingAdmissionWebhook
兩者合起來就是一個特殊類型的 admission controllers
,一個處理資源更改,一個處理驗證。驗證是經過匹配到 MutatingWebhookConfiguration
中定義的規則完成的。nginx
在這篇文章中,我會深刻剖析 MutatingAdmissionWebhook
的細節,並一步步實現一個可用的 webhook admission server
。git
Kubernetes 集羣管理員可使用 webhooks
來建立額外的資源更改及驗證准入插件,這些准入插件能夠經過 apiserver 的准入鏈來工做,而不須要從新編譯 apiserver。這使得開發者能夠對於不少動做均可以自定義准入邏輯,好比對任何資源的建立、更新、刪除,給開發者提供了很大的自由和靈活度。可使用的應用數量巨大。一些常見的使用常見包括:github
StorageClass
。監聽 PersistentVolumeClaim
資源,並按照事先定好的規則自動的爲之增添對應的 StorageClass
。使用者無需關心 StorageClass
的建立。除了以上列出來的使用場景,基於 webhooks
還能夠建立更多應用。web
基於社區的反饋,以及對 External Admission Webhooks
和 Initializers
的 alpha 版本的使用案例,Kubernetes 社區決定將 webhooks 升級到 beta 版,而且將其分紅兩種 webhooks(MutatingAdmissionWebhook
和 ValidatingAdmissionWebhook
)。這些更新使得 webhooks 與其餘 admission controllers
保持一致,而且強制 mutate-before-validate
。Initializers
能夠在 Kubernetes 資源建立前更改,從而實現動態准入控制。若是你對 Initializers
不熟悉,能夠參考這篇文章。docker
因此,到底 Webhooks
和 Initializers
之間的區別是什麼呢?shell
Webhooks
能夠應用於更多操做,包括對於資源 "增刪改" 的 "mutate" 和 "admit";然而 Initializers
不能夠對 "刪" 資源進行 "admit"。Webhooks
在建立資源前不容許查詢;然而 Initializers
能夠監聽未初始化的資源,經過參數 ?includeUninitialized=true
來實現。Initializers
會把 "預建立" 狀態也持久化到 etcd,所以會引入高延遲且給 etcd 帶來負擔,尤爲在 apiserver 升級或失敗時;然而 Webhooks 消耗的內存和計算資源更少。Webhooks
比 Initializers
對失敗的保障更強大。Webhooks
的配置中能夠配置失敗策略,用以免資源在建立的時候被 hang 住。然而 Initializers
在嘗試建立資源的時候可能會 block 住全部的資源。除了上面列舉的不一樣點,Initializer
在較長一段開發時間內還存在不少已知問題,包括配額補充錯誤等。Webhooks
升級爲 beta 版也就預示着在將來 Webhooks
會是開發目標。若是你須要更穩定的操做,我推薦使用 Webhooks
。json
MutatingAdmissionWebhook
在資源被持久化到 etcd 前,根據規則將其請求攔截,攔截規則定義在 MutatingWebhookConfiguration
中。MutatingAdmissionWebhook
經過對 webhook server
發送准入請求來實現對資源的更改。而 webhook server
只是一個簡單的 http 服務器。
下面這幅圖詳細描述了 MutatingAdmissionWebhook
如何工做:
MutatingAdmissionWebhook
須要三個對象才能運行:
MutatingAdmissionWebhook
須要根據 MutatingWebhookConfiguration
向 apiserver 註冊。在註冊過程當中,MutatingAdmissionWebhook
須要說明:
webhook admission server
;webhook admission server
;webhook admission server
的 URL path;webhook admission server
處理時遇到錯誤時如何處理。MutatingAdmissionWebhook
是一種插件形式的 admission controller
,且能夠配置到 apiserver 中。MutatingAdmissionWebhook
插件能夠從 MutatingWebhookConfiguration
中獲取全部感興趣的 admission webhooks
。
而後 MutatingAdmissionWebhook
監聽 apiserver 的請求,攔截知足條件的請求,並並行執行。
Webhook Admission Server
只是一個附着到 k8s apiserver 的 http server。對於每個 apiserver 的請求,MutatingAdmissionWebhook
都會發送一個 admissionReview
到相關的 webhook admission server
。webhook admission server
再決定如何更改資源。
編寫一個完整的 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
複製代碼
另外,MutatingAdmissionWebhook
和 ValidatingAdmissionWebhook
准入控制器須要以正確的順序加入到 kube-apiserver
的 admission-control
標籤中。
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)
}
}()
複製代碼
以上代碼的詳解:
sidecarCfgFile
包含了 sidecar 注入器模板,其在下面的 ConfigMap 中定義;certFile
和 keyFile
是祕鑰對,會在 webhook server
和 apiserver 之間的 TLS 通訊中用到;'/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 request
和 response writer
。
AdmissionReview
,其中包括 object
、oldobject
及 userInfo
...mutate
來建立 patch
,以實現注入 sidecar 容器及掛載 volume。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
的時候提到。完整代碼請參考這裏。
建立構建腳本:
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
複製代碼
如今咱們來建立一個 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 conf
的 ConfigMap
。完整 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
複製代碼
因爲準入控制是一個高安全性操做,因此對外在的 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
複製代碼
deployment 帶有一個 pod,其中運行的就是 sidecar-injector
容器。該容器以特殊參數運行:
sidecarCfgFile
指的是 sidecar 注入器的配置文件,掛載自上面建立的 ConfigMap sidecar-injector-webhook-configmap
。tlsCertFile
和 tlsKeyFile
是祕鑰對,掛載自 Secret injector-webhook-certs
。alsologtostderr
、v=4
和 2>&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
複製代碼
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
複製代碼
咱們在 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 容器,無一例外。
多虧了 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
。