1.什麼是Admission Controller
Admission Controller(准入控制)是 Kubernetes API Server 用於攔截請求的一種手段。Admission可以做到對請求的資源物件進行校驗,修改。service mesh最近很火的專案Istio天生支援Kubernetes,利用的就是admission對服務例項自動注入sidecar。
假如對 Kubernetes 有一定的瞭解的話,應該會知道在 Kubernetes 中還有 authn/authz,為什麼還會引入 admission 這種機制?
1)authn/authz 是 Kubernetes 的認證鑑權,執行在 filter 中,只能獲取 http 請求 header 以及證書,並不能獲取請求的 body。所以 authn/authz 只能對客戶端進行認證和鑑權,不可以對請求的物件進行任何操作,因為這裡根本還獲取不到物件。 2)Admission 執行在 API Server 的增刪改查 handler 中,可以自然地操作 API resource。
下面將對 Admission Controller 工作流做一番詳解。
API Server 接收到客戶端請求後首先進行認證鑑權,認證鑑權通過後才會進行後續的endpoint handler處理。- 當API Server 接收到物件後首先根據 http 的路徑可以知道物件的版本號,然後將 request body 反序列化成 versioned object.
- versioned object 轉化為 internal object,即沒有版本的內部型別,這種資源型別是所有 versioned 型別的超集。只有轉化為 internal 後才能適配所有的客戶端 versioned object 的校驗。
- Admission Controller 具體的 admit 操作,可以通過這裡修改資源物件,例如為 Pod 掛載一個預設的 Service Account 等。 4)API Server internal object validation,校驗某個資源物件資料和格式是否合法,例如:Service Name 的字元個數不能超過63等。 5)Admission Controller validate,可以自定義任何的物件校驗規則。 6)internal object 轉化為 versioned object,並且持久化儲存到 etcd。
注:以上 versioned object 和 internal object 直接的轉換關係會在《深度剖析Kubernetes API Server三部曲 - part 2》詳細解釋,歡迎持續關注。
2.如何使用 admission controller
Kubernetes 1.10之前的版本可以使用--admission-control
開啟 Admission Controller。同時--admission-control
的順序決定 Admission 執行的先後。其實這種方式對於使用者來講其實是挺複雜的,因為這要求使用者對所有的 Admission Controllers 需要完全瞭解。
如果使用Kubernetes 1.10之後的版本,--admission-control
已經廢棄,建議使用
--enable-admission-plugins --disable-admission-plugins
指定需要開啟或者關閉的 Admission Controller。 同時使用者指定的順序並不影響實際 Admission Controllers 的執行順序,對使用者來講非常友好。
值得一提的是,有些 Admission Controller 可能會使用 Alpha 版本的 API,這時必須首先使能其使用的 API 版本。否則 Admission Controller 不能工作,可能會影響系統功能。
2.1 webhook admission
目前 Kubernetes 中已經有非常多的 Admission 外掛, 但是並不能保證滿足所有開發者的需求。 眾所周知,Kbernetes 之所以受到推崇,它的可擴充套件能力功不可沒。Admission 也提供了一種 webhook 的擴充套件機制。
- MutatingAdmissionWebhook:在物件持久化之前進行修改
- ValidatingAdmissionWebhook:在物件持久化之前進行
可能有讀者接觸過另外一種動態可擴充套件的機制 Initializers,不過至今還是 Apha 特性,社群討論有可能會把它移除。所以選擇動態 Admission 首選 webhook。
2.2 如何使用webhook admission
Webhook Admission 屬於同步呼叫,需要使用者部署自己的 webhook server,建立自定義的配置資源物件: ValidatingWebhookConfiguration 或 MutatingWebhookConfiguration。
- 開發webhook server
這裡我推薦參考社群 e2e 測試用的 server,對細節原始碼感興趣的讀者可以自行參考 github.com/kubernetes/…,這裡面利用 golang 標準庫實現的一個基本的 http server,並註冊多個路由,同時服務於多種 resource 的准入控制。重點關注一下資源物件的 decode 過程,這是k8s apimachinery 的高階功能。利用了 apimachinery 的 scheme 的能力,使用之前必須要將 api 註冊到 scheme 中,程式碼詳見: github.com/kubernetes/…。一個典型的 webhook 修改資源物件(Pod)的樣例程式碼如下所示。
func mutatePods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
glog.V(2).Info("mutating pods")
podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
if ar.Request.Resource != podResource {
glog.Errorf("expect resource to be %s", podResource)
return nil
}
raw := ar.Request.Object.Raw
pod := corev1.Pod{}
deserializer := codecs.UniversalDeserializer()
// pod的解碼,利用apimachinery
if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil {
glog.Error(err)
return toAdmissionResponse(err)
}
reviewResponse := v1beta1.AdmissionResponse{}
reviewResponse.Allowed = true
if pod.Name == "webhook-to-be-mutated" {
reviewResponse.Patch = []byte(addInitContainerPatch)
pt := v1beta1.PatchTypeJSONPatch
reviewResponse.PatchType = &pt
}
return &reviewResponse
}
複製程式碼
- 部署webhook server
# kubectl create –f webhook-server.yaml
複製程式碼
apiVersion: v1
kind: Namespace
metadata:
name: e2e-tests-webhook-gbgt6
spec:
finalizers:
- kubernetes
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: sample-webhook
webhook: "true"
name: sample-webhook-deployment
namespace: e2e-tests-webhook-gbgt6
spec:
replicas: 1
selector:
matchLabels:
app: sample-webhook
webhook: "true"
template:
metadata:
labels:
app: sample-webhook
webhook: "true"
spec:
containers:
- args:
- --tls-cert-file=/webhook.local.config/certificates/tls.crt
- --tls-private-key-file=/webhook.local.config/certificates/tls.key
- --alsologtostderr
- -v=4
- 2>&1
image: gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.10v2
imagePullPolicy: IfNotPresent
name: sample-webhook
volumeMounts:
- mountPath: /webhook.local.config/certificates
name: webhook-certs
readOnly: true
volumes:
- name: webhook-certs
secret:
defaultMode: 420
secretName: sample-webhook-secret
---
apiVersion: v1
kind: Service
metadata:
labels:
test: webhook
name: e2e-test-webhook
namespace: e2e-tests-webhook-gbgt6
spec:
ports:
- port: 443
protocol: TCP
targetPort: 443
selector:
webhook: "true"
sessionAffinity: None
type: ClusterIP
複製程式碼
建立 webhook server Deployment 以及 Service,供 API Server 呼叫。
- 建立MutatingWebhookConfiguration
# kubectl create –f webhook-config.yaml
複製程式碼
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
name: e2e-test-mutating-webhook-pod
webhooks:
- clientConfig:
caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMyRENDQWNDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFkTVJzd0dRWURWUVFERXhKbE1tVXQKYzJWeWRtVnlMV05sY25RdFkyRXdIaGNOTVRnd056RTVNRGMwT1RJeFdoY05Namd3TnpFMk1EYzBPVEl4V2pBZApNUnN3R1FZRFZRUURFeEpsTW1VdGMyVnlkbVZ5TFdObGNuUXRZMkV3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBCkE0SUJEd0F3Z2dFS0FvSUJBUURFVVFEWVN6SGl3SUFHU1dHSWRBSmVBbnMrNFhaYjlZc3VuQlBVTkJPdHZqeFoKV3NSbUxydE0zVU9lcEszeGsvMzZCSS96RkdXdUNpMlJ0TWUxSWtEa2tVMzNEZE83K0ExVyt2NVZNVnFqL0lDTApsc29USml3TFhTcGowTHNwSUNVdGtqT1dlRjVhK3lJVHgyR01TMG9ZbWtuaHB0RXMrc2tKQjFMWm1uVTBaWFpzClRKak9Lb05ueHdVaTl4QnRUTXBQRWw2cVhmb3dCWlpvYjlkUzNtNzFLbjJCdU5Ec0s3YnVRcGJvdk9XdUQyNDAKdzNLQVJnT04xcjA4Vm4zd1I1MHVXS09tSkVsLzRUZ2JnSTRkaG85WHNIWUhUdnk4R3JRMXhYZE43ZEhSTlpHNQo5aDhmOUUzdjg1VWxwSEVWQThqUHB4RE5SSm9qRXVGQk9raFJEZEY1QWdNQkFBR2pJekFoTUE0R0ExVWREd0VCCi93UUVBd0lDcERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFDWWl4VUsKYkhsRUpCK2t4THdqdktySDQ1OVVsNUJjb0VXZE1BNnArUC8yWXVZa2NuWC9GRVNjUFRxUS9vdkF3ejU1ZG1FUwpJTjVZOWd2ZlJxdWhZcEdWOHVFSWpzVkczTjdKQm1wM0NyclEyd3FYeHV3cndkVXV1dDltQSt2RkQ4Q2FQSE8xCmVad1J6NEkzTktFQ0xHMHJXQWxseEVvUm9tQ2UvaWZIUnRNRklTRk5sSnZVNlhIbzFDVWNFQ2FwOG9hYXN2cFcKT2JBQjVqQzc5WWJXN2lWVm54cjZGMnRvOG9oSEdNSEpXR1pwSTNKbVpNbGVOK01kVm5ySFdXSXBkOG9iS2E3TgpqSlZTczgzRmlDMzd4d2dqMUQyaTNHUnh5bHNKZEdJWTl4WVpQVmNNUTh6Z2FMMUpJUk1BdVZYbHczUkRzSDR0Cms5WmFybGY1NG9BOUN0Nk8KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
service:
name: e2e-test-webhook
namespace: e2e-tests-webhook-gbgt6
path: /mutating-pods
failurePolicy: Ignore
name: adding-init-container.k8s.io
namespaceSelector: {}
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
-
複製程式碼
rules 表示對於 core/v1/pods 資源物件建立的時候呼叫 mutating webhook。server 的地址及路徑通過 clientConfig 指明。
/mutating-pods
是指呼叫 webhook server 執行 mutatePods,為 pod 增加 init initContainers。
func mutatePods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
glog.V(2).Info("mutating pods")
podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
if ar.Request.Resource != podResource {
glog.Errorf("expect resource to be %s", podResource)
return nil
}
raw := ar.Request.Object.Raw
pod := corev1.Pod{}
deserializer := codecs.UniversalDeserializer()
if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil {
glog.Error(err)
return toAdmissionResponse(err)
}
reviewResponse := v1beta1.AdmissionResponse{}
reviewResponse.Allowed = true
if pod.Name == "webhook-to-be-mutated" {
reviewResponse.Patch = []byte(addInitContainerPatch)
pt := v1beta1.PatchTypeJSONPatch
reviewResponse.PatchType = &pt
}
return &reviewResponse
}
複製程式碼
建立Pod
kubectl create –f pod.yaml
複製程式碼
apiVersion: v1
kind: Pod
metadata:
name: webhook-to-be-mutated
namespace: e2e-tests-webhook-gbgt6
spec:
containers:
- image: k8s.gcr.io/pause:3.1
name: example
複製程式碼
查詢Pod
# kubectl get pod webhook-to-be-mutated –n e2e-tests-webhook-gbgt6 -oyaml
複製程式碼
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: 2018-07-19T07:49:37Z
name: webhook-to-be-mutated
namespace: e2e-tests-webhook-gbgt6
resourceVersion: "806"
selfLink: /api/v1/namespaces/e2e-tests-webhook-gbgt6/pods/webhook-to-be-mutated
uid: 48d2e91d-8b28-11e8-b16d-286ed488dc10
spec:
containers:
- image: k8s.gcr.io/pause:3.1
imagePullPolicy: IfNotPresent
name: example
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: default-token-jhqlb
readOnly: true
dnsPolicy: ClusterFirst
initContainers:
- image: webhook-added-image
imagePullPolicy: Always
name: webhook-added-init-container
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
nodeName: 127.0.0.1
priority: 0
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: default
serviceAccountName: default
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 300
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300
volumes:
- name: default-token-jhqlb
secret:
defaultMode: 420
secretName: default-token-jhqlb
複製程式碼
可以看出,建立成功的pod已經多了一個名字為 webhook-added-init-container的initContainers
。
3.總結
最後我們來總結下 webhook Admission 的優勢
-
webhook 可動態擴充套件 Admission 能力,滿足自定義客戶的需求
-
不需要重啟 API Server,可通過建立 webhook configuration 熱載入 webhook admission。