日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
使用 Cert-manager 管理 Admission Webhooks 證書

使用 Cert-manager 管理 Admission Webhooks 證書

作者:陽明 2023-01-06 08:16:21

云計算

云原生 在 Kubernetes APiServer 中包含兩個特殊的準入控制器:MutatingAdmissionWebhook 和ValidatingAdmissionWebhook,這兩個控制器將發(fā)送準入請求到外部的 HTTP 回調(diào)服務(wù)并接收一個準入響應(yīng)。

Kubernetes 提供了需要擴展其內(nèi)置功能的方法,最常用的可能是自定義資源類型和自定義控制器了,除此之外,Kubernetes 還有一些其他非常有趣的功能,比如 admission webhooks 就可以用于擴展 API,用于修改某些 Kubernetes 資源的基本行為。

準入控制器是在對象持久化之前用于對 Kubernetes API Server 的請求進行攔截的代碼段,在請求經(jīng)過身份驗證和授權(quán)之后放行通過。準入控制器可能正在 validating?、mutating? 或者都在執(zhí)行,Mutating? 控制器可以修改他們處理的資源對象,Validating 控制器不會,如果任何一個階段中的任何控制器拒絕了請求,則會立即拒絕整個請求,并將錯誤返回給最終的用戶。

這意味著有一些特殊的控制器可以攔截 Kubernetes API 請求,并根據(jù)自定義的邏輯修改或者拒絕它們。Kubernetes 有自己實現(xiàn)的一個控制器列表:https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#what-does-each-admission-controller-do,當然你也可以編寫自己的控制器,雖然這些控制器聽起來功能比較強大,但是這些控制器需要被編譯進 kube-apiserver,并且只能在 apiserver 啟動時啟動。

也可以直接使用 kube-apiserver 啟動參數(shù)查看內(nèi)置支持的控制器:

kube-apiserver --help |grep enable-admission-plugins

由于上面的控制器的限制,我們就需要用到動態(tài)的概念了,而不是和 apiserver 耦合在一起,Admission webhooks 就通過一種動態(tài)配置方法解決了這個限制問題。

admission webhook 是什么?

在 Kubernetes apiserver 中包含兩個特殊的準入控制器:MutatingAdmissionWebhook 和ValidatingAdmissionWebhook,這兩個控制器將發(fā)送準入請求到外部的 HTTP 回調(diào)服務(wù)并接收一個準入響應(yīng)。如果啟用了這兩個準入控制器,Kubernetes 管理員可以在集群中創(chuàng)建和配置一個 admission webhook。

整體的步驟如下所示:

  • 檢查集群中是否啟用了 admission webhook 控制器,并根據(jù)需要進行配置。
  • 編寫處理準入請求的 HTTP 回調(diào),回調(diào)可以是一個部署在集群中的簡單 HTTP 服務(wù),甚至也可以是一個serverless 函數(shù)。
  • 通過MutatingWebhookConfiguration 和 ValidatingWebhookConfiguration 資源配置 admission webhook。

這兩種類型的 admission webhook 之間的區(qū)別是非常明顯的:validating webhooks? 可以拒絕請求,但是它們卻不能修改準入請求中獲取的對象,而 mutating webhooks 可以在返回準入響應(yīng)之前通過創(chuàng)建補丁來修改對象,如果 webhook 拒絕了一個請求,則會向最終用戶返回錯誤。

現(xiàn)在非?;馃岬?Service Mesh 應(yīng)用 istio? 就是通過 mutating webhooks 來自動將 Envoy 這個 sidecar 容器注入到 Pod 中去的:https://istio.io/latest/docs/setup/additional-setup/sidecar-injection/。

創(chuàng)建配置一個 Admission Webhook

上面我們介紹了 Admission Webhook 的理論知識,接下來我們在一個真實的 Kubernetes 集群中來實際測試使用下,我們將創(chuàng)建一個 webhook 的 webserver,將其部署到集群中,然后創(chuàng)建 webhook 配置查看是否生效。

首先確保在 apiserver 中啟用了 MutatingAdmissionWebhook 和 ValidatingAdmissionWebhook 這兩個控制器,通過參數(shù) --enable-admission-plugins 進行配置,當前 v1.25+ 版本已經(jīng)內(nèi)置默認開啟了,如果沒有開啟則需要添加上這兩個參數(shù),然后重啟 apiserver。

然后通過運行下面的命令檢查集群中是否啟用了準入注冊 API:

  kubectl api-versions |grep admission
admissionregistration.k8s.io/v1

滿足了這些先決條件之后,我們就可以來編寫 admission webhook 服務(wù)器了,其實就是開發(fā)一個 webserver,在該服務(wù)中處理由 APIServer 發(fā)送過來的一個 AdmissionReview 請求,格式如下所示:

{
"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)造一個 AdmissionReview 對象返回回去即可。

{
"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,前者唯一確定請求,后者表示通過或者不通過,status 字段主要供錯誤提示。

我們可以直接參考 Kubernetes e2e 測試中的示例實現(xiàn)代碼 https://github.com/kubernetes/kubernetes/blob/release-1.25/test/images/agnhost/webhook/main.go。

編寫 webhook

滿足了前面的先決條件后,接下來我們就來實現(xiàn)一個 webhook 示例,通過監(jiān)聽兩個不同的 HTTP 端點(validate 和 mutate)來進行 validating 和 mutating webhook 驗證。

這個 webhook 的完整代碼可以在 Github 上獲取:https://github.com/cnych/admission-webhook-example(train4 分支)。這個 webhook 是一個簡單的帶 TLS 認證的 HTTP 服務(wù),用 Deployment 方式部署在我們的集群中。

代碼中主要的邏輯在兩個文件中:main.go 和 webhook.go,main.go 文件包含創(chuàng)建 HTTP 服務(wù)的代碼,而 webhook.go 包含 validates 和 mutates 兩個 webhook 的邏輯,大部分代碼都比較簡單,首先查看 main.go 文件,查看如何使用標準 golang 包來啟動 HTTP 服務(wù),以及如何從命令行標志中讀取 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.")

然后一個比較重要的是 serve 函數(shù),用來處理傳入的 mutate 和 validating 函數(shù) 的 HTTP 請求。該函數(shù)從請求中反序列化 AdmissionReview 對象,執(zhí)行一些基本的內(nèi)容校驗,根據(jù) URL 路徑調(diào)用相應(yīng)的 mutate 和 validate 函數(shù),然后序列化 AdmissionReview 對象:

// 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)
}
}

主要的準入邏輯是 validate 和 mutate 兩個函數(shù)。validate 函數(shù)檢查資源對象是否需要校驗:不驗證 kube-system 命名空間中的資源,如果想要顯示的聲明不驗證某個資源,可以通過在資源對象中添加一個 admission-webhook-example.qikqiak.com/validate=false 的 annotation 進行聲明。如果需要驗證,則根據(jù)資源類型的 kind,和標簽與其對應(yīng)項進行比較,將 service 或者 deployment 資源從請求中反序列化出來。如果缺少某些 label 標簽,則響應(yīng)中的 Allowed 會被設(shè)置為 false。如果驗證失敗,則會在響應(yīng)中寫入失敗原因,最終用戶在嘗試創(chuàng)建資源時會收到失敗的信息。validate 函數(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,
}
}

判斷是否需要進行校驗的方法如下,可以通過 namespace 進行忽略,也可以通過 annotations 設(shè)置進行配置:

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ù)的代碼是非常類似的,但不是僅僅比較標簽并在響應(yīng)中設(shè)置 Allowed,而是創(chuàng)建一個補丁,將缺失的標簽添加到資源中,并將 not_available 設(shè)置為標簽的值。

// 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)建

其實我們已經(jīng)將代碼打包成一個 docker 鏡像了,你可以直接使用,鏡像倉庫地址為:cnych/admission-webhook-example:v4。當然如果你希望更改部分代碼,那就需要重新構(gòu)建項目了,由于這個項目采用 go 語言開發(fā),包管理工具更改為了 go mod,所以我們需要確保構(gòu)建環(huán)境提前安裝好 go 環(huán)境,當然 docker 也是必不可少的,因為我們需要的是打包成一個 docker 鏡像。

獲取項目:

  mkdir admission-webhook && cd admission-webhook
git clone https://github.com/cnych/admission-webhook-example.git
git checkout train4 # train4分支

我們可以看到代碼根目錄下面有一個 build 的腳本,只需要提供我們自己的 docker 鏡像用戶名然后直接構(gòu)建即可:

  export DOCKER_USER=cnych
./build

部署

向 apiserver 注冊 admission webhook,apiserver 如何知曉服務(wù)存在,如何調(diào)用接口,這就需要使用到 ValidatingWebhookConfiguration 和 MutatingWebhookConfiguration 對象了,通過創(chuàng)建該資源對象,apiserver 會在其 ValidatingAdmissionWebhook 控制器模塊中注冊我們的 webhook,在創(chuàng)建該對象的時候有幾個注意事項:

  • apiserver 只支持 HTTPS webhook,因此必須準備 TLS 證書,一般使用 KubernetesCertificateSigningRequest 或者 cert-manager 獲取即可。
  • clientConfig.caBundle 用于指定簽發(fā) TLS 證書的 CA 證書,如果使用 Kubernetes CertificateSigningRequest 簽發(fā)證書,則可以從 kube-public namespace clusterinfo 獲取集群 CA,base64 格式化再寫入 clientConfig.caBundle 即可; 如果使用 cert-manager 簽發(fā)證書,則組件會自動注入證書。
  • 為防止自己攔截自己,可以使用objectSelector 將自身排除在外。
  • 集群內(nèi)部署時,使用 service ref 指定服務(wù)
  • 集群外部署時,使用 url 指定 HTTPS 接口

我們這里創(chuàng)建一個如下所示的 ValidatingWebhookConfiguration 對象:

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

在該對象中我們注冊了一個 validating 的 webhook,通過 clientConfig.service 指定了該 webhook 的地址,正常情況下我們還需要指定 caBundle 的內(nèi)容,但是我們這里配置了一個 cert-manager.io/inject-ca-from: default/admission-example-tls-secret 的 annotations,這樣我們可以借助 cert-manager 來自動注入 CA 內(nèi)容,另外我們還配置了 namespaceSelector,表示具有 admission-webhook-example: enabled 標簽的命名空間才會應(yīng)用該 webhook。

所以我們這里還需要部署 cert-manager:

  kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.10.1/cert-manager.yaml

安裝完成后會在 cert-manager 命名空間中運行如下幾個 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 具有一個名為 CA injector 的組件,該組件負責將 CA bundle 注入到 Mutating | ValidatingWebhookConfiguration 中去。所謂我們需要在 Mutating | ValidatingWebhookConfiguration 對象中使用 key 為 certmanager.k8s.io/inject-ca-from 的 annotation,annotation 的 value 應(yīng)該以 / 的格式指向現(xiàn)有證書 CR 實例。

這里我們創(chuàng)建如下所示的 Certificate 對象,只需要 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 對象名稱要和上面的 annotations 對應(yīng),然后我們將最后簽發(fā)的證書寫入到名為 admission-webhook-example-certs 的 Secret 對象中了。

接下來就可以部署我們的 webhook server 了,部署是非常簡單的,只是需要配置服務(wù)的 TLS 配置。我們可以在代碼根目錄下面的 deployment 文件夾下面查看 deployment.yaml 文件中關(guān)于證書的配置聲明,會發(fā)現(xiàn)從命令行參數(shù)中讀取的證書和私鑰文件是通過一個 secret 對象掛載進來的:

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

這個 admission-webhook-example-certs 對象就是上面 Cert-manager 創(chuàng)建的 Certificate 對象自動生成的,一旦 secret 對象創(chuàng)建成功,我們就可以直接創(chuàng)建 deployment 和 service 對象。

  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

然后我們在 default 這個 namespace 中添加上如下的標簽:

  kubectl label namespace default admission-webhook-example=enabled
namespace "default" labeled

最后,創(chuàng)建上面的 validatingwebhookconfigurations 對象即可,一旦創(chuàng)建成功后,就會攔截請求然后調(diào)用我們的 webhook 服務(wù)了:

  kubectl get validatingwebhookconfigurations
NAME WEBHOOKS AGE
validation-webhook-example-cfg 1 9m52s

測試

現(xiàn)在讓我們創(chuàng)建一個 deployment 資源來驗證下是否有效,代碼倉庫下有一個 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)建的時候會出現(xiàn)上面的錯誤信息,因為我們沒有帶上任何需要的標簽,然后部署另外一個 sleep-with-labels.yaml 的資源清單:

  kubectl apply -f deployment/sleep-with-labels.yaml
deployment.apps "sleep" created

可以看到可以正常部署,然后我們將上面的 deployment 刪除,然后部署另外一個 sleep-no-validation.yaml 資源清單,該清單中不存在所需的標簽,但是配置了 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)建一個 MutatingWebhookConfiguration 對象。首先,我們將上面的 validating webhook 刪除,防止對 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 標簽:

  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,來一起測試?,F(xiàn)在,嘗試再次創(chuàng)建 sleep 應(yīng)用。正常是可以創(chuàng)建成功的。

準入控制分兩個階段進行,第一階段,運行 mutating admission 控制器,第二階段運行 validating admission 控制器。

所以 mutating webhook 在第一階段添加上缺失的 labels 標簽,然后 validating webhook 在第二階段就不會拒絕這個 deployment 了,因為標簽已經(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)需求就單獨去開發(fā)一個準入控制器的 webhook 是不是就顯得非常麻煩,不夠靈活了,為了解決這個問題我們可以使用 Kubernetes 提供的一些策略管理引擎,在不需要編寫代碼的情況也可以來實現(xiàn)我們的這些需求,比如 Kyverno、Gatekeeper 等等,在 Kubernetes v1.26 版本官方也加入了策略管理的支持。


文章題目:使用 Cert-manager 管理 Admission Webhooks 證書
網(wǎng)址分享:http://www.5511xx.com/article/djdegie.html