新聞中心
使用 Cert-manager 管理 Admission Webhooks 證書
作者:陽明 2023-01-06 08:16:21
云計(jì)算
云原生 在 Kubernetes APiServer 中包含兩個(gè)特殊的準(zhǔn)入控制器:MutatingAdmissionWebhook 和ValidatingAdmissionWebhook,這兩個(gè)控制器將發(fā)送準(zhǔn)入請(qǐng)求到外部的 HTTP 回調(diào)服務(wù)并接收一個(gè)準(zhǔn)入響應(yīng)。

Kubernetes 提供了需要擴(kuò)展其內(nèi)置功能的方法,最常用的可能是自定義資源類型和自定義控制器了,除此之外,Kubernetes 還有一些其他非常有趣的功能,比如 admission webhooks 就可以用于擴(kuò)展 API,用于修改某些 Kubernetes 資源的基本行為。
準(zhǔn)入控制器是在對(duì)象持久化之前用于對(duì) Kubernetes API Server 的請(qǐng)求進(jìn)行攔截的代碼段,在請(qǐng)求經(jīng)過身份驗(yàn)證和授權(quán)之后放行通過。準(zhǔn)入控制器可能正在 validating?、mutating? 或者都在執(zhí)行,Mutating? 控制器可以修改他們處理的資源對(duì)象,Validating 控制器不會(huì),如果任何一個(gè)階段中的任何控制器拒絕了請(qǐng)求,則會(huì)立即拒絕整個(gè)請(qǐng)求,并將錯(cuò)誤返回給最終的用戶。
這意味著有一些特殊的控制器可以攔截 Kubernetes API 請(qǐng)求,并根據(jù)自定義的邏輯修改或者拒絕它們。Kubernetes 有自己實(shí)現(xiàn)的一個(gè)控制器列表:https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#what-does-each-admission-controller-do,當(dāng)然你也可以編寫自己的控制器,雖然這些控制器聽起來功能比較強(qiáng)大,但是這些控制器需要被編譯進(jìn) kube-apiserver,并且只能在 apiserver 啟動(dòng)時(shí)啟動(dòng)。
也可以直接使用 kube-apiserver 啟動(dòng)參數(shù)查看內(nèi)置支持的控制器:
kube-apiserver --help |grep enable-admission-plugins
由于上面的控制器的限制,我們就需要用到動(dòng)態(tài)的概念了,而不是和 apiserver 耦合在一起,Admission webhooks 就通過一種動(dòng)態(tài)配置方法解決了這個(gè)限制問題。
admission webhook 是什么?
在 Kubernetes apiserver 中包含兩個(gè)特殊的準(zhǔn)入控制器:MutatingAdmissionWebhook 和ValidatingAdmissionWebhook,這兩個(gè)控制器將發(fā)送準(zhǔn)入請(qǐng)求到外部的 HTTP 回調(diào)服務(wù)并接收一個(gè)準(zhǔn)入響應(yīng)。如果啟用了這兩個(gè)準(zhǔn)入控制器,Kubernetes 管理員可以在集群中創(chuàng)建和配置一個(gè) admission webhook。
整體的步驟如下所示:
- 檢查集群中是否啟用了 admission webhook 控制器,并根據(jù)需要進(jìn)行配置。
- 編寫處理準(zhǔn)入請(qǐng)求的 HTTP 回調(diào),回調(diào)可以是一個(gè)部署在集群中的簡(jiǎn)單 HTTP 服務(wù),甚至也可以是一個(gè)serverless 函數(shù)。
- 通過MutatingWebhookConfiguration 和 ValidatingWebhookConfiguration 資源配置 admission webhook。
這兩種類型的 admission webhook 之間的區(qū)別是非常明顯的:validating webhooks? 可以拒絕請(qǐng)求,但是它們卻不能修改準(zhǔn)入請(qǐng)求中獲取的對(duì)象,而 mutating webhooks 可以在返回準(zhǔn)入響應(yīng)之前通過創(chuàng)建補(bǔ)丁來修改對(duì)象,如果 webhook 拒絕了一個(gè)請(qǐng)求,則會(huì)向最終用戶返回錯(cuò)誤。
現(xiàn)在非常火熱的 Service Mesh 應(yīng)用 istio? 就是通過 mutating webhooks 來自動(dòng)將 Envoy 這個(gè) sidecar 容器注入到 Pod 中去的:https://istio.io/latest/docs/setup/additional-setup/sidecar-injection/。
創(chuàng)建配置一個(gè) Admission Webhook
上面我們介紹了 Admission Webhook 的理論知識(shí),接下來我們?cè)谝粋€(gè)真實(shí)的 Kubernetes 集群中來實(shí)際測(cè)試使用下,我們將創(chuàng)建一個(gè) webhook 的 webserver,將其部署到集群中,然后創(chuàng)建 webhook 配置查看是否生效。
首先確保在 apiserver 中啟用了 MutatingAdmissionWebhook 和 ValidatingAdmissionWebhook 這兩個(gè)控制器,通過參數(shù) --enable-admission-plugins 進(jìn)行配置,當(dāng)前 v1.25+ 版本已經(jīng)內(nèi)置默認(rèn)開啟了,如果沒有開啟則需要添加上這兩個(gè)參數(shù),然后重啟 apiserver。
然后通過運(yùn)行下面的命令檢查集群中是否啟用了準(zhǔn)入注冊(cè) API:
kubectl api-versions |grep admission
admissionregistration.k8s.io/v1
滿足了這些先決條件之后,我們就可以來編寫 admission webhook 服務(wù)器了,其實(shí)就是開發(fā)一個(gè) webserver,在該服務(wù)中處理由 APIServer 發(fā)送過來的一個(gè) AdmissionReview 請(qǐng)求,格式如下所示:
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
...
"request": {
# Random uid uniquely identifying this admission call
"uid": "d595e125-9489-4d1c-877d-0a05984355c8",
# object is the new object being admitted.
"object": {"apiVersion":"v1","kind":"Pod", ...},
...
}
}然后構(gòu)造一個(gè) AdmissionReview 對(duì)象返回回去即可。
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response": {
"uid": "",
"allowed": true
}
}
// 或者
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response": {
"uid": "",
"allowed": false,
"status": {
"code": 402,
"status": "Failure",
"message": "xxx",
"reason": "xxxx"
}
}
} 其決定作用的字段是 .response.uid 和 .response.allowed,前者唯一確定請(qǐng)求,后者表示通過或者不通過,status 字段主要供錯(cuò)誤提示。
我們可以直接參考 Kubernetes e2e 測(cè)試中的示例實(shí)現(xiàn)代碼 https://github.com/kubernetes/kubernetes/blob/release-1.25/test/images/agnhost/webhook/main.go。
編寫 webhook
滿足了前面的先決條件后,接下來我們就來實(shí)現(xiàn)一個(gè) webhook 示例,通過監(jiān)聽兩個(gè)不同的 HTTP 端點(diǎn)(validate 和 mutate)來進(jìn)行 validating 和 mutating webhook 驗(yàn)證。
這個(gè) webhook 的完整代碼可以在 Github 上獲?。篽ttps://github.com/cnych/admission-webhook-example(train4 分支)。這個(gè) webhook 是一個(gè)簡(jiǎn)單的帶 TLS 認(rèn)證的 HTTP 服務(wù),用 Deployment 方式部署在我們的集群中。
代碼中主要的邏輯在兩個(gè)文件中:main.go 和 webhook.go,main.go 文件包含創(chuàng)建 HTTP 服務(wù)的代碼,而 webhook.go 包含 validates 和 mutates 兩個(gè) webhook 的邏輯,大部分代碼都比較簡(jiǎn)單,首先查看 main.go 文件,查看如何使用標(biāo)準(zhǔn) golang 包來啟動(dòng) HTTP 服務(wù),以及如何從命令行標(biāo)志中讀取 TLS 配置的證書:
flag.StringVar(¶meters.certFile, "tlsCertFile", "/etc/webhook/certs/cert.pem", "File containing the x509 Certificate for HTTPS.")
flag.StringVar(¶meters.keyFile, "tlsKeyFile", "/etc/webhook/certs/key.pem", "File containing the x509 private key to --tlsCertFile.")
然后一個(gè)比較重要的是 serve 函數(shù),用來處理傳入的 mutate 和 validating 函數(shù) 的 HTTP 請(qǐng)求。該函數(shù)從請(qǐng)求中反序列化 AdmissionReview 對(duì)象,執(zhí)行一些基本的內(nèi)容校驗(yàn),根據(jù) URL 路徑調(diào)用相應(yīng)的 mutate 和 validate 函數(shù),然后序列化 AdmissionReview 對(duì)象:
// 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
}
}
if len(body) == 0 {
glog.Error("empty body")
http.Error(w, "empty body", http.StatusBadRequest)
return
}
// verify the content type is accurate
contentType := r.Header.Get("Content-Type")
if contentType != "application/json" {
glog.Errorf("Content-Type=%s, expect application/json", contentType)
http.Error(w, "invalid Content-Type, expect `application/json`", http.StatusUnsupportedMediaType)
return
}
var admissionResponse *admissionv1.AdmissionResponse
ar := admissionv1.AdmissionReview{}
if _, _, err := deserializer.Decode(body, nil, &ar); err != nil {
glog.Errorf("Can't decode body: %v", err)
admissionResponse = &admissionv1.AdmissionResponse{
Result: &metav1.Status{
Message: err.Error(),
},
}
} else {
fmt.Println(r.URL.Path)
if r.URL.Path == "/mutate" {
admissionResponse = whsvr.mutate(&ar)
} else if r.URL.Path == "/validate" {
admissionResponse = whsvr.validate(&ar)
}
}
admissionReview := admissionv1.AdmissionReview{}
admissionReview.TypeMeta = metav1.TypeMeta{
Kind: "AdmissionReview",
APIVersion: "admission.k8s.io/v1",
}
if admissionResponse != nil {
admissionReview.Response = admissionResponse
if ar.Request != nil {
admissionReview.Response.UID = ar.Request.UID
}
}
resp, err := json.Marshal(admissionReview)
if err != nil {
glog.Errorf("Can't encode response: %v", err)
http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
}
glog.Infof("Ready to write reponse ...")
if _, err := w.Write(resp); err != nil {
glog.Errorf("Can't write response: %v", err)
http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
}
}
主要的準(zhǔn)入邏輯是 validate 和 mutate 兩個(gè)函數(shù)。validate 函數(shù)檢查資源對(duì)象是否需要校驗(yàn):不驗(yàn)證 kube-system 命名空間中的資源,如果想要顯示的聲明不驗(yàn)證某個(gè)資源,可以通過在資源對(duì)象中添加一個(gè) admission-webhook-example.qikqiak.com/validate=false 的 annotation 進(jìn)行聲明。如果需要驗(yàn)證,則根據(jù)資源類型的 kind,和標(biāo)簽與其對(duì)應(yīng)項(xiàng)進(jìn)行比較,將 service 或者 deployment 資源從請(qǐng)求中反序列化出來。如果缺少某些 label 標(biāo)簽,則響應(yīng)中的 Allowed 會(huì)被設(shè)置為 false。如果驗(yàn)證失敗,則會(huì)在響應(yīng)中寫入失敗原因,最終用戶在嘗試創(chuàng)建資源時(shí)會(huì)收到失敗的信息。validate 函數(shù)實(shí)現(xiàn)如下所示:
// validate deployments and services
func (whsvr *WebhookServer) validate(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
req := ar.Request
var (
availableLabels map[string]string
objectMeta *metav1.ObjectMeta
resourceNamespace, resourceName string
)
glog.Infof("AdmissionReview for Kind=%v, Namespace=%v Name=%v (%v) UID=%v patchOperatinotallow=%v UserInfo=%v",
req.Kind, req.Namespace, req.Name, resourceName, req.UID, req.Operation, req.UserInfo)
switch req.Kind.Kind {
case "Deployment":
var deployment appsv1.Deployment
if err := json.Unmarshal(req.Object.Raw, &deployment); err != nil {
glog.Errorf("Could not unmarshal raw object: %v", err)
return &admissionv1.AdmissionResponse{
Result: &metav1.Status{
Message: err.Error(),
},
}
}
resourceName, resourceNamespace, objectMeta = deployment.Name, deployment.Namespace, &deployment.ObjectMeta
availableLabels = deployment.Labels
case "Service":
var service corev1.Service
if err := json.Unmarshal(req.Object.Raw, &service); err != nil {
glog.Errorf("Could not unmarshal raw object: %v", err)
return &admissionv1.AdmissionResponse{
Result: &metav1.Status{
Message: err.Error(),
},
}
}
resourceName, resourceNamespace, objectMeta = service.Name, service.Namespace, &service.ObjectMeta
availableLabels = service.Labels
}
if !validationRequired(ignoredNamespaces, objectMeta) {
glog.Infof("Skipping validation for %s/%s due to policy check", resourceNamespace, resourceName)
return &admissionv1.AdmissionResponse{
Allowed: true,
}
}
allowed := true
var result *metav1.Status
glog.Info("available labels:", availableLabels)
glog.Info("required labels", requiredLabels)
for _, rl := range requiredLabels {
if _, ok := availableLabels[rl]; !ok {
allowed = false
result = &metav1.Status{
Reason: "required labels are not set",
}
break
}
}
return &admissionv1.AdmissionResponse{
Allowed: allowed,
Result: result,
}
}
判斷是否需要進(jìn)行校驗(yàn)的方法如下,可以通過 namespace 進(jìn)行忽略,也可以通過 annotations 設(shè)置進(jìn)行配置:
func validationRequired(ignoredList []string, metadata *metav1.ObjectMeta) bool {
required := admissionRequired(ignoredList, admissionWebhookAnnotationValidateKey, metadata)
glog.Infof("Validation policy for %v/%v: required:%v", metadata.Namespace, metadata.Name, required)
return required
}
func admissionRequired(ignoredList []string, admissionAnnotationKey string, metadata *metav1.ObjectMeta) bool {
// skip special kubernetes system namespaces
for _, namespace := range ignoredList {
if metadata.Namespace == namespace {
glog.Infof("Skip validation for %v for it's in special namespace:%v", metadata.Name, metadata.Namespace)
return false
}
}
annotations := metadata.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
}
var required bool
switch strings.ToLower(annotations[admissionAnnotationKey]) {
default:
required = true
case "n", "no", "false", "off":
required = false
}
return required
}mutate 函數(shù)的代碼是非常類似的,但不是僅僅比較標(biāo)簽并在響應(yīng)中設(shè)置 Allowed,而是創(chuàng)建一個(gè)補(bǔ)丁,將缺失的標(biāo)簽添加到資源中,并將 not_available 設(shè)置為標(biāo)簽的值。
// main mutation process
func (whsvr *WebhookServer) mutate(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
req := ar.Request
var (
availableLabels, availableAnnotations map[string]string
objectMeta *metav1.ObjectMeta
resourceNamespace, resourceName string
)
glog.Infof("AdmissionReview for Kind=%v, Namespace=%v Name=%v (%v) UID=%v patchOperatinotallow=%v UserInfo=%v",
req.Kind, req.Namespace, req.Name, resourceName, req.UID, req.Operation, req.UserInfo)
switch req.Kind.Kind {
case "Deployment":
var deployment appsv1.Deployment
if err := json.Unmarshal(req.Object.Raw, &deployment); err != nil {
glog.Errorf("Could not unmarshal raw object: %v", err)
return &admissionv1.AdmissionResponse{
Result: &metav1.Status{
Message: err.Error(),
},
}
}
resourceName, resourceNamespace, objectMeta = deployment.Name, deployment.Namespace, &deployment.ObjectMeta
availableLabels = deployment.Labels
case "Service":
var service corev1.Service
if err := json.Unmarshal(req.Object.Raw, &service); err != nil {
glog.Errorf("Could not unmarshal raw object: %v", err)
return &admissionv1.AdmissionResponse{
Result: &metav1.Status{
Message: err.Error(),
},
}
}
resourceName, resourceNamespace, objectMeta = service.Name, service.Namespace, &service.ObjectMeta
availableLabels = service.Labels
}
if !mutationRequired(ignoredNamespaces, objectMeta) {
glog.Infof("Skipping validation for %s/%s due to policy check", resourceNamespace, resourceName)
return &admissionv1.AdmissionResponse{
Allowed: true,
}
}
annotations := map[string]string{admissionWebhookAnnotationStatusKey: "mutated"}
patchBytes, err := createPatch(availableAnnotations, annotations, availableLabels, addLabels)
if err != nil {
return &admissionv1.AdmissionResponse{
Result: &metav1.Status{
Message: err.Error(),
},
}
}
glog.Infof("AdmissionResponse: patch=%v\n", string(patchBytes))
return &admissionv1.AdmissionResponse{
Allowed: true,
Patch: patchBytes,
PatchType: func() *admissionv1.PatchType {
pt := admissionv1.PatchTypeJSONPatch
return &pt
}(),
}
}
構(gòu)建
其實(shí)我們已經(jīng)將代碼打包成一個(gè) docker 鏡像了,你可以直接使用,鏡像倉(cāng)庫(kù)地址為:cnych/admission-webhook-example:v4。當(dāng)然如果你希望更改部分代碼,那就需要重新構(gòu)建項(xiàng)目了,由于這個(gè)項(xiàng)目采用 go 語言開發(fā),包管理工具更改為了 go mod,所以我們需要確保構(gòu)建環(huán)境提前安裝好 go 環(huán)境,當(dāng)然 docker 也是必不可少的,因?yàn)槲覀冃枰氖谴虬梢粋€(gè) docker 鏡像。
獲取項(xiàng)目:
mkdir admission-webhook && cd admission-webhook
git clone https://github.com/cnych/admission-webhook-example.git
git checkout train4 # train4分支
我們可以看到代碼根目錄下面有一個(gè) build 的腳本,只需要提供我們自己的 docker 鏡像用戶名然后直接構(gòu)建即可:
export DOCKER_USER=cnych
./build
部署
向 apiserver 注冊(cè) admission webhook,apiserver 如何知曉服務(wù)存在,如何調(diào)用接口,這就需要使用到 ValidatingWebhookConfiguration 和 MutatingWebhookConfiguration 對(duì)象了,通過創(chuàng)建該資源對(duì)象,apiserver 會(huì)在其 ValidatingAdmissionWebhook 控制器模塊中注冊(cè)我們的 webhook,在創(chuàng)建該對(duì)象的時(shí)候有幾個(gè)注意事項(xiàng):
- apiserver 只支持 HTTPS webhook,因此必須準(zhǔn)備 TLS 證書,一般使用 KubernetesCertificateSigningRequest 或者 cert-manager 獲取即可。
- clientConfig.caBundle 用于指定簽發(fā) TLS 證書的 CA 證書,如果使用 Kubernetes CertificateSigningRequest 簽發(fā)證書,則可以從 kube-public namespace clusterinfo 獲取集群 CA,base64 格式化再寫入 clientConfig.caBundle 即可; 如果使用 cert-manager 簽發(fā)證書,則組件會(huì)自動(dòng)注入證書。
- 為防止自己攔截自己,可以使用objectSelector 將自身排除在外。
- 集群內(nèi)部署時(shí),使用 service ref 指定服務(wù)
- 集群外部署時(shí),使用 url 指定 HTTPS 接口
我們這里創(chuàng)建一個(gè)如下所示的 ValidatingWebhookConfiguration 對(duì)象:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: validation-webhook-example-cfg
labels:
app: admission-webhook-example
annotations:
cert-manager.io/inject-ca-from: default/admission-example-tls-secret # $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
webhooks:
- name: required-labels.qikqiak.com
admissionReviewVersions:
- v1
clientConfig:
caBundle: "" # "or "
service:
name: admission-webhook-example-svc
namespace: default
port: 443
path: /validate
rules:
- operations: ["CREATE"]
apiGroups: ["apps", ""]
apiVersions: ["v1"]
resources: ["deployments", "services"]
namespaceSelector:
matchLabels:
admission-webhook-example: enabled
failurePolicy: Fail
matchPolicy: Exact
sideEffects: None
在該對(duì)象中我們注冊(cè)了一個(gè) validating 的 webhook,通過 clientConfig.service 指定了該 webhook 的地址,正常情況下我們還需要指定 caBundle 的內(nèi)容,但是我們這里配置了一個(gè) cert-manager.io/inject-ca-from: default/admission-example-tls-secret 的 annotations,這樣我們可以借助 cert-manager 來自動(dòng)注入 CA 內(nèi)容,另外我們還配置了 namespaceSelector,表示具有 admission-webhook-example: enabled 標(biāo)簽的命名空間才會(huì)應(yīng)用該 webhook。
所以我們這里還需要部署 cert-manager:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.10.1/cert-manager.yaml
安裝完成后會(huì)在 cert-manager 命名空間中運(yùn)行如下幾個(gè) Pod:
kubectl get pods -n cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-74d949c895-4v8kj 1/1 Running 0 71s
cert-manager-cainjector-d9bc5979d-2tt4v 1/1 Running 0 71s
cert-manager-webhook-84b7ddd796-7wdgk 1/1 Running 0 71s
cert-manager 具有一個(gè)名為 CA injector 的組件,該組件負(fù)責(zé)將 CA bundle 注入到 Mutating | ValidatingWebhookConfiguration 中去。所謂我們需要在 Mutating | ValidatingWebhookConfiguration 對(duì)象中使用 key 為 certmanager.k8s.io/inject-ca-from 的 annotation,annotation 的 value 應(yīng)該以
這里我們創(chuàng)建如下所示的 Certificate 對(duì)象,只需要 selfSigned 的 Issuer 即可:
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: admission-example-issuer
namespace: default
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: admission-example-tls-secret
spec:
duration: 8760h
renewBefore: 8000h
subject:
organizations:
- qikqiak.com
commonName: admission-webhook-example-svc.default
isCA: false
privateKey:
algorithm: RSA
encoding: PKCS1
size: 2048
usages:
- digital signature
- key encipherment
- server auth
dnsNames:
- admission-webhook-example-svc
- admission-webhook-example-svc.default
- admission-webhook-example-svc.default.svc
issuerRef:
kind: Issuer
name: admission-example-issuer
secretName: admission-webhook-example-certs
需要注意的是這里的 Certificate 對(duì)象名稱要和上面的 annotations 對(duì)應(yīng),然后我們將最后簽發(fā)的證書寫入到名為 admission-webhook-example-certs 的 Secret 對(duì)象中了。
接下來就可以部署我們的 webhook server 了,部署是非常簡(jiǎn)單的,只是需要配置服務(wù)的 TLS 配置。我們可以在代碼根目錄下面的 deployment 文件夾下面查看 deployment.yaml 文件中關(guān)于證書的配置聲明,會(huì)發(fā)現(xiàn)從命令行參數(shù)中讀取的證書和私鑰文件是通過一個(gè) secret 對(duì)象掛載進(jìn)來的:
args:
- -tlsCertFile=/etc/webhook/certs/tls.crt
- -tlsKeyFile=/etc/webhook/certs/tls.key
[...]
volumeMounts:
- name: webhook-certs
mountPath: /etc/webhook/certs
readOnly: true
volumes:
- name: webhook-certs
secret:
secretName: admission-webhook-example-certs
這個(gè) admission-webhook-example-certs 對(duì)象就是上面 Cert-manager 創(chuàng)建的 Certificate 對(duì)象自動(dòng)生成的,一旦 secret 對(duì)象創(chuàng)建成功,我們就可以直接創(chuàng)建 deployment 和 service 對(duì)象。
kubectl apply -f deployment/rbac.yaml
kubectl apply -f deployment/deployment.yaml
deployment.apps "admission-webhook-example-deployment" created
kubectl apply -f deployment/service.yaml
service "admission-webhook-example-svc" created
然后我們?cè)?default 這個(gè) namespace 中添加上如下的標(biāo)簽:
kubectl label namespace default admission-webhook-example=enabled
namespace "default" labeled
最后,創(chuàng)建上面的 validatingwebhookconfigurations 對(duì)象即可,一旦創(chuàng)建成功后,就會(huì)攔截請(qǐng)求然后調(diào)用我們的 webhook 服務(wù)了:
kubectl get validatingwebhookconfigurations
NAME WEBHOOKS AGE
validation-webhook-example-cfg 1 9m52s
測(cè)試
現(xiàn)在讓我們創(chuàng)建一個(gè) deployment 資源來驗(yàn)證下是否有效,代碼倉(cāng)庫(kù)下有一個(gè) sleep.yaml 的資源清單文件,直接創(chuàng)建即可:
kubectl apply -f deployment/sleep.yaml
Error from server (required labels are not set): error when creating "deployment/sleep.yaml": admission webhook "required-labels.qikqiak.com" denied the request: required labels are not set
正常情況下創(chuàng)建的時(shí)候會(huì)出現(xiàn)上面的錯(cuò)誤信息,因?yàn)槲覀儧]有帶上任何需要的標(biāo)簽,然后部署另外一個(gè) sleep-with-labels.yaml 的資源清單:
kubectl apply -f deployment/sleep-with-labels.yaml
deployment.apps "sleep" created
可以看到可以正常部署,然后我們將上面的 deployment 刪除,然后部署另外一個(gè) sleep-no-validation.yaml 資源清單,該清單中不存在所需的標(biāo)簽,但是配置了 admission-webhook-example.qikqiak.com/validate=false 這樣的 annotation,所以正常也是可以正常創(chuàng)建的:
kubectl delete deployment sleep
kubectl apply -f deployment/sleep-no-validation.yaml
deployment.apps "sleep" created
部署 mutating webhook
用同樣的方式我們可以創(chuàng)建一個(gè) MutatingWebhookConfiguration 對(duì)象。首先,我們將上面的 validating webhook 刪除,防止對(duì) mutating 產(chǎn)生干擾,然后部署新的配置。mutating webhook 與 validating webhook 配置基本相同,但是 webook server 的路徑是 /mutate。
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: mutating-webhook-example-cfg
labels:
app: admission-webhook-example
annotations:
cert-manager.io/inject-ca-from: default/admission-example-tls-secret
webhooks:
- name: mutating-example.qikqiak.com
admissionReviewVersions:
- v1
clientConfig:
caBundle: "" # "or "
service:
name: admission-webhook-example-svc
namespace: default
port: 443
path: /mutate
rules:
- operations: ["CREATE"]
apiGroups: ["apps", ""]
apiVersions: ["v1"]
resources: ["deployments", "services"]
namespaceSelector:
matchLabels:
admission-webhook-example: enabled
failurePolicy: Fail
matchPolicy: Exact
sideEffects: None
現(xiàn)在我們可以再次部署上面的 sleep 應(yīng)用程序,然后查看是否正確添加 label 標(biāo)簽:
kubectl get mutatingwebhookconfigurations
NAME WEBHOOKS AGE
mutating-webhook-example-cfg 1 42s
kubectl apply -f deployment/sleep.yaml
deployment.apps "sleep" created
kubectl get deploy sleep -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
admission-webhook-example.qikqiak.com/status: mutated
deployment.kubernetes.io/revision: "1"
creationTimestamp: "2023-01-05T08:43:59Z"
generation: 1
labels:
app.kubernetes.io/component: not_available
app.kubernetes.io/instance: not_available
app.kubernetes.io/managed-by: not_available
app.kubernetes.io/name: not_available
app.kubernetes.io/part-of: not_available
app.kubernetes.io/version: not_available
name: sleep
namespace: default
# ......
最后,我們重新創(chuàng)建 validating webhook,來一起測(cè)試。現(xiàn)在,嘗試再次創(chuàng)建 sleep 應(yīng)用。正常是可以創(chuàng)建成功的。
準(zhǔn)入控制分兩個(gè)階段進(jìn)行,第一階段,運(yùn)行 mutating admission 控制器,第二階段運(yùn)行 validating admission 控制器。
所以 mutating webhook 在第一階段添加上缺失的 labels 標(biāo)簽,然后 validating webhook 在第二階段就不會(huì)拒絕這個(gè) deployment 了,因?yàn)闃?biāo)簽已經(jīng)存在了,用 not_available 設(shè)置他們的值。
kubectl apply -f deployment/validatingwebhook.yaml
validatingwebhookconfiguration.admissionregistration.k8s.io "validation-webhook-example-cfg" created
kubectl delete -f deployment/sleep.yaml
deployment.apps "sleep" deleted
kubectl apply -f deployment/sleep.yaml
deployment.apps/sleep created
但是如果我們有這樣的相關(guān)需求就單獨(dú)去開發(fā)一個(gè)準(zhǔn)入控制器的 webhook 是不是就顯得非常麻煩,不夠靈活了,為了解決這個(gè)問題我們可以使用 Kubernetes 提供的一些策略管理引擎,在不需要編寫代碼的情況也可以來實(shí)現(xiàn)我們的這些需求,比如 Kyverno、Gatekeeper 等等,在 Kubernetes v1.26 版本官方也加入了策略管理的支持。
標(biāo)題名稱:使用 Cert-manager 管理 Admission Webhooks 證書
路徑分享:http://m.fisionsoft.com.cn/article/djdegie.html


咨詢
建站咨詢
