欢迎光临
我们一直在努力

理解K8s动态准入控制器-基于Admission Webhook实现Sidecar自动注入检验等

K8s 的准入控制器

准入控制器是 Kubernetes API Server 中的一组插件,在对象持久化到 etcd 之前拦截 API 请求,用于强制执行策略和修改对象。

🔵 流程步骤详解
1.API请求接收与初步处理​
Kubernetes API Server接收到创建/修改资源的请求(如Pod创建)。请求先经过身份认证(Authentication)和权限校验(Authorization),失败则立即拒绝。

2.Mutating Admission阶段​

  • API Server根据配置的MutatingWebhookConfiguration,筛选匹配的Webhook,并构建AdmissionReview请求(包含资源对象、操作类型、用户信息等)。
  • 请求通过HTTP POST发送至Webhook服务。Webhook返回AdmissionReview响应,其中可能包含JSONPatch用于修改资源(如注入Sidecar容器)。
  • 关键特性:此阶段可串行调用多个Webhook,需确保处理逻辑的幂等性。

3.Schema校验与Validating Admission阶段​

  • 资源修改后,API Server执行内置的Object Schema Validation,验证资源结构的合法性。
  • 进入Validating Admission阶段,API Server根据ValidatingWebhookConfiguration构建新的AdmissionReview请求,发送至验证型Webhook。Webhook检查业务规则(如资源配额、安全策略),并返回允许/拒绝结论。
  • Validating Webhook不能修改资源,且调用是并行的。

4.最终决策与持久化​

  • 所有Validating Webhook均通过后,资源被持久化到ETCD,请求成功完成。
  • 任意阶段拒绝:若Mutating或Validating阶段中任一Webhook返回拒绝,请求立即终止,错误返回给用户。

🔵准入控制中的流程图

在这里插入图片描述
🔵 典型应用场景

  • 自动Sidecar注入(如Istio):Mutating Webhook通过AdmissionReview向Pod添加容器。
  • 安全策略强制:Validating Webhook检查Pod是否禁止特权模式,违规则拒绝请求。
  • 资源默认值设置:Mutating Webhook为未声明CPU需求的Pod添加默认值。

AdmissionReview​ 是 Kubernetes 准入控制 Webhook 中的核心 API 对象,用于在 API 服务器和外部 Webhook 之间传递准入请求和响应。它是 Kubernetes 动态准入控制流程的标准数据结构。

当 Kubernetes API 服务器需要外部 Webhook 对资源操作(CREATE/UPDATE/DELETE)做准入决策时,会将请求包装成 AdmissionReview发送给 Webhook。Webhook 处理完成后,也必须返回一个 AdmissionReview对象作为响应。

AdmissionReview 的数据结构

apiVersion: admission.k8s.io/v1 # API 版本
kind: AdmissionReview # 固定资源类型
request: # 请求部分(API Server → Webhook)
uid: string # 唯一请求标识,必须原样返回
kind: GroupVersionKind # 资源类型信息
resource: GroupVersionResource # 资源详情
namespace: string # 请求的命名空间
operation: string # 操作类型:CREATE/UPDATE/DELETE
object: RawExtension # 新资源对象(JSON)
oldObject: RawExtension # 旧资源对象(更新/删除时)
options: RawExtension # 操作选项
userInfo: UserInfo # 请求用户信息
response: # 响应部分(Webhook → API Server)
uid: string # 必须与 request.uid 一致
allowed: boolean # 是否允许操作
patchType: string # 补丁类型,如 JSONPatch
patch: []byte # 补丁内容(Base64编码)
status: Status # 错误信息(当 allowed=false 时)

实践

1.自动Sidecar注入

Sidecar-inject.go

package main

import (
"encoding/json"
"io/ioutil"
"log"
"net/http"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
authenticationv1 "k8s.io/api/authentication/v1"
)

type AdmissionReview struct {
APIVersion string `json:"apiVersion"`
Kind string`json:"kind"`
Request *AdmissionRequest `json:"request,omitempty"`
Response *AdmissionResponse `json:"response,omitempty"`
}
type AdmissionResponse struct {
UID string `json:"uid"`
Allowed bool `json:"allowed"`
Patch []byte `json:"patch,omitempty"`
PatchType *string `json:"patchType,omitempty"`
Status *Status `json:"status,omitempty"`
}
type AdmissionRequest struct {
UID string `json:"uid" yaml:"uid"`
Kind metav1.GroupVersionKind `json:"kind" yaml:"kind"`
Resource metav1.GroupVersionResource `json:"resource" yaml:"resource"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
RequestKind metav1.GroupVersionKind `json:"requestKind" yaml:"requestKind"`
RequestResource metav1.GroupVersionResource `json:"requestResource" yaml:"requestResource"`
RequestSubResource string `json:"requestSubResource" yaml:"requestSubResource"`
Operation string `json:"operation" yaml:"operation"`
UserInfo authenticationv1.UserInfo `json:"userInfo" yaml:"userInfo"`
Object interface{} `json:"object,omitempty" yaml:"object,omitempty"`
OldObject interface{} `json:"oldObject,omitempty" yaml:"oldObject,omitempty"`
}

type Status struct {
Message string `json:"message,omitempty"`
Code int32 `json:"code"`
}

func Mutate_admission(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error reading body: %v", err)
http.Error(w, "can't read body", http.StatusBadRequest)
return
}
defer r.Body.Close()
log.Printf("Received admission request: %s", string(body))
var review AdmissionReview
err = json.Unmarshal(body, &review)
if err != nil {
log.Printf("Error unmarshalling request: %v", err)
http.Error(w, "can't unmarshal request", http.StatusBadRequest)
return
}
responseVersion:= "admission.k8s.io/v1"
if review.APIVersion != "" {
responseVersion = review.APIVersion
}
patch :=[]map[string]interface{}{
{
"op": "add",
"path": "/metadata/annotations",
"value": map[string]string{
"kubectl.kubernetes.io/last-applied-configuration": "injected-test",
},
},
{
"op": "add",
"path": "/spec/containers/-",
"value": map[string]interface{}{
"image": "nginx:1.19.9",
"imagePullPolicy": "IfNotPresent",
"name": "nginx-injected",
"ports": []map[string]interface{}{
{
"containerPort": 80,
"name": "http",
"protocol": "TCP",
},
},
},
},
}
patchBytes, err := json.Marshal(patch)
if err != nil {
log.Printf("Error marshalling patch: %v", err)
http.Error(w, "can't marshal patch", http.StatusInternalServerError)
return
}
patchType := "JSONPatch"
resp := &AdmissionReview{
APIVersion: responseVersion,
Kind: "AdmissionReview",
Response: &AdmissionResponse{
Allowed: true,
Patch: patchBytes,
PatchType: &patchType,
UID: review.Request.UID,
},
}
jsonBytes, err := json.Marshal(resp)
if err != nil {
log.Printf("Error marshalling response: %v", err)
http.Error(w, "can't marshal response", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(jsonBytes)
log.Printf("Sending response: %s", string(jsonBytes))

}

func main() {
http.HandleFunc("/mutate", Mutate_admission)
log.Println("Server started")
log.Fatal(http.ListenAndServeTLS(":8443", "tls.crt", "tls.key", nil))
}

创建证书

[ req ]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no

[ req_distinguished_name ]
CN = inject-webhook.default.svc

[ v3_req ]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = inject-webhook
DNS.2 = inject-webhook.default
DNS.3 = inject-webhook.default.svc

openssl req x509 newkey rsa:2048 keyout tls.key out tls.crt days 3650 nodes config tls.cnf extensions v3_req

MutatingWebhookConfiguration

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: sidecar-injector
webhooks:
name: inject-webhook.default.svc
clientConfig:
service:
name: inject-webhook
namespace: default
path: "/mutate"
port: 8443
#caBundle的值为cat tls.crt | base64 | tr -d '\\n'
caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURiRENDQWxTZ0F3SUJBZ0lVQisrSXRqa0gvbDhuQU1aRkxXeTZGdmJ3Sndnd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0pURWpNQ0VHQTFVRUF3d2FhVzVxWldOMExYZGxZbWh2YjJzdVpHVm1ZWFZzZEM1emRtTXdIaGNOTWpZdwpNVEEzTURrMU9ERXlXaGNOTXpZd01UQTFNRGsxT0RFeVdqQWxNU013SVFZRFZRUUREQnBwYm1wbFkzUXRkMlZpCmFHOXZheTVrWldaaGRXeDBMbk4yWXpDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUIKQUswVEhpZlg3ZTRiUmN3ajZZT2h5ME10Y2R5Z2dNQlNJUHBUQ0dxSlVEemVENllvRElIM3FLcmw5TnozSXFlTwpMQ3JrRnkwVzIzbVFuMUs0Y0R1RVI5N1kxQUd1Wm5YMXQyclhmRjZhOC85d2xpa3pvejlVTEZ1V1RVTmJTWXMzCmJtclhYUnRKZUFDT2w5WHVqMFM4OHdkeStSdVhjdjFESHlSRjJjdGo3Y0VjaWI3OVlVd3hSN2pGeG94dE00T2MKVi83eWdHQmJTYnRMM2syMHV1Vm1TbS81SFU2dEJnN0xSSGpnejFPK0hIRnY1aVREb3VVT1VNNkRVYU02aUpaSgpGSFJuQlpvaXAzK2xZb29BTjZYL29rUHJRQTVsbDQ3Ui9jZnNhbmpTZ1JBVVA3N1RXWjcxazdHMUV6Z003TGZmCjVMWXJGYXRnTldpWnBUSUNiOWtGY3pzQ0F3RUFBYU9Ca3pDQmtEQUxCZ05WSFE4RUJBTUNCREF3RXdZRFZSMGwKQkF3d0NnWUlLd1lCQlFVSEF3RXdUUVlEVlIwUkJFWXdSSUlPYVc1cVpXTjBMWGRsWW1odmIydUNGbWx1YW1WagpkQzEzWldKb2IyOXJMbVJsWm1GMWJIU0NHbWx1YW1WamRDMTNaV0pvYjI5ckxtUmxabUYxYkhRdWMzWmpNQjBHCkExVWREZ1FXQkJSZnZBS0ZzbVZwU0c0Vk1oWEFxZ2NsN0ZQSFdEQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEKa0JXRDRXanYwdk5uTUFQb3d6Umc1VnJzOWVHL2xMcUYwNE4zV3BVU3pwNjcvM2p1TlJWY3NGQ2xkODFmRTJPUwpveXFyMXJtWFJBeDVCUFpNaWxZMjhGVi84dXVIQnVQZi9kS3Voc1VaTm90K2d6Y3BpbE52K2RkOXQxSVBkVkRsCkFjRU9mNWgyYjZsSzlMbXFNTGNDakxnbmczdnZGaXdWcGM0NzA1MDIwWk4yZnRNa3hORmhHUUxvYWZnNmJENDYKekZLVzNCT3FucUt6b04rZXFvL3RydXZjUWNOVUpsSGlvQnJscDgyaU54UUxNb2g3ekVvMTN0Q3l5VFZwbFhtUgpncndXQlJZekh5QnN3ZnlydnhjdzJqLytVZVU5QjczaEk4NVNTNmxWWUdZdmEvdjFRN1dlQ0pYMmJjZG1WTzNWCk9vUWRRcDArMERDK3N3N1BDWXUwTWc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
rules:
operations: ["CREATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
failurePolicy: Fail
sideEffects: None
admissionReviewVersions: ["v1"]

webhook运行在k8s集群外,在k8s集群创建svc,指向外部的webhook

apiVersion: v1
kind: Service
metadata:
name: inject-webhook
spec:
ports:
– port: 8443
targetPort: 8443
protocol: TCP
type: ClusterIP

apiVersion: v1
kind: Endpoints
metadata:
name: inject-webhook
namespace: default
subsets:
– addresses:
– ip: 172.16.0.27
ports:
– port: 8443
protocol: TCP

创建pod 测试

kubectl run test-inject –image=redis

验证

检查containers与annotations里面的内容
在这里插入图片描述

2.资源默认值设置

修改逻辑,添加添加默认resource配置

func Mutate_admission(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error reading body: %v", err)
http.Error(w, "can't read body", http.StatusBadRequest)
return
}
defer r.Body.Close()
log.Printf("Received admission request: %s", string(body))
var review AdmissionReview
err = json.Unmarshal(body, &review)
if err != nil {
log.Printf("Error unmarshalling request: %v", err)
http.Error(w, "can't unmarshal request", http.StatusBadRequest)
return
}
responseVersion:= "admission.k8s.io/v1"
if review.APIVersion != "" {
responseVersion = review.APIVersion
}

// 添加注解
patch :=[]map[string]interface{}{
{
"op": "add",
"path": "/metadata/annotations",
"value": map[string]string{
"kubectl.kubernetes.io/last-applied-configuration": "injected-test",
},
},
}

// 检查并自动添加资源限制到现有容器
if review.Request != nil && review.Request.Object != nil {
if objMap, ok := review.Request.Object.(map[string]interface{}); ok {
if spec, ok := objMap["spec"].(map[string]interface{}); ok {
if containers, ok := spec["containers"].([]interface{}); ok {
// 遍历所有现有容器,检查是否已配置resources
for idx, container := range containers {
if containerMap, ok := container.(map[string]interface{}); ok {
// 检查当前容器是否已配置resources,包括空的resources
resources, exists := containerMap["resources"]
if !exists || resources == nil || isEmptyResources(resources) {
// 为没有配置resources的现有容器添加默认配置
patch = append(patch, map[string]interface{}{
"op": "add",
"path": "/spec/containers/" + strconv.Itoa(idx) + "/resources",
"value": map[string]interface{}{
"requests": map[string]interface{}{
"cpu": "10m",
"memory": "20Mi",
},
"limits": map[string]interface{}{
"cpu": "50m",
"memory": "50Mi",
},
},
})
}
}
}
}
}
}
}

// 最后添加 注入 容器
patch = append(patch, map[string]interface{}{
"op": "add",
"path": "/spec/containers/-",
"value": map[string]interface{}{
"image": "nginx:1.19.9",
"imagePullPolicy": "IfNotPresent",
"name": "nginx-injected",
"ports": []map[string]interface{}{
{
"containerPort": 80,
"name": "http",
"protocol": "TCP",
},
},
"resources": map[string]interface{}{
"requests": map[string]interface{}{
"cpu": "10m",
"memory": "20Mi",
},
"limits": map[string]interface{}{
"cpu": "50m",
"memory": "50Mi",
},
},
},
})

patchBytes, err := json.Marshal(patch)
if err != nil {
log.Printf("Error marshalling patch: %v", err)
http.Error(w, "can't marshal patch", http.StatusInternalServerError)
return
}
patchType := "JSONPatch"
resp := &AdmissionReview{
APIVersion: responseVersion,
Kind: "AdmissionReview",
Response: &AdmissionResponse{
Allowed: true,
Patch: patchBytes,
PatchType: &patchType,
UID: review.Request.UID,
},
}
jsonBytes, err := json.Marshal(resp)
if err != nil {
log.Printf("Error marshalling response: %v", err)
http.Error(w, "can't marshal response", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(jsonBytes)
log.Printf("Sending response: %s", string(jsonBytes))

}

// 检查resources是否为空
func isEmptyResources(resources interface{}) bool {
if resources == nil {
return true
}

resMap, ok := resources.(map[string]interface{})
if !ok {
return true
}

// 检查map是否为空
if len(resMap) == 0 {
return true
}

// 检查map的值是否都是空的
hasRequests := false
hasLimits := false

if requests, exists := resMap["requests"]; exists && requests != nil {
if reqMap, ok := requests.(map[string]interface{}); ok && len(reqMap) > 0 {
hasRequests = true
}
}

if limits, exists := resMap["limits"]; exists && limits != nil {
if limMap, ok := limits.(map[string]interface{}); ok && len(limMap) > 0 {
hasLimits = true
}
}

return !hasRequests && !hasLimits
}

在这里插入图片描述

3.Validating Webhook检查Pod是否禁止特权模式,违规则拒绝请求。

validate_privileged.go

package main

import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
authenticationv1 "k8s.io/api/authentication/v1"
)

type AdmissionReview struct {
APIVersion string `json:"apiVersion"`
Kind string `json:"kind"`
Request *AdmissionRequest `json:"request,omitempty"`
Response *AdmissionResponse `json:"response,omitempty"`
}

type AdmissionResponse struct {
UID string `json:"uid"`
Allowed bool `json:"allowed"`
Result *Status `json:"status,omitempty"`
}

type AdmissionRequest struct {
UID string `json:"uid" yaml:"uid"`
Kind metav1.GroupVersionKind `json:"kind" yaml:"kind"`
Resource metav1.GroupVersionResource `json:"resource" yaml:"resource"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
RequestKind metav1.GroupVersionKind `json:"requestKind" yaml:"requestKind"`
RequestResource metav1.GroupVersionResource `json:"requestResource" yaml:"requestResource"`
RequestSubResource string `json:"requestSubResource" yaml:"requestSubResource"`
Operation string `json:"operation" yaml:"operation"`
UserInfo authenticationv1.UserInfo `json:"userInfo" yaml:"userInfo"`
Object interface{} `json:"object,omitempty" yaml:"object,omitempty"`
OldObject interface{} `json:"oldObject,omitempty" yaml:"oldObject,omitempty"`
}

type Status struct {
Message string `json:"message,omitempty"`
Code int32 `json:"code"`
}

// 安全检查函数
func ValidatePrivilegedPods(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error reading body: %v", err)
httpError(w, "can't read body", http.StatusBadRequest)
return
}
defer r.Body.Close()

log.Printf("Received validation request: %s", string(body))

var review AdmissionReview
err = json.Unmarshal(body, &review)
if err != nil {
log.Printf("Error unmarshalling request: %v", err)
httpError(w, "can't unmarshal request", http.StatusBadRequest)
return
}

responseVersion := getResponseVersion(review.APIVersion)

// 检查请求对象是否存在
if review.Request == nil || review.Request.Object == nil {
sendAllowResponse(w, responseVersion, review.Request.UID, "No object to validate")
return
}

// 验证 Pod 是否符合安全规范
validationError := validatePodSecurity(review.Request.Object)
if validationError != "" {
sendDenyResponse(w, responseVersion, review.Request.UID, validationError)
return
}

// 验证通过
sendAllowResponse(w, responseVersion, review.Request.UID, "")
}

// 获取响应版本
func getResponseVersion(apiVersion string) string {
if apiVersion != "" {
return apiVersion
}
return "admission.k8s.io/v1"
}

// 验证 Pod 安全性
func validatePodSecurity(obj interface{}) string {
objMap, ok := obj.(map[string]interface{})
if !ok {
return "Invalid object format"
}

spec, ok := objMap["spec"].(map[string]interface{})
if !ok {
return "Pod spec is not a valid map"
}

// 检查容器的安全上下文
if err := checkContainersSecurity(spec, "containers"); err != nil {
return err.Error()
}

// 检查初始化容器的安全上下文
if err := checkContainersSecurity(spec, "initContainers"); err != nil {
return err.Error()
}

// 检查 Pod 级别的安全上下文
if err := checkPodSecurityContext(spec); err != nil {
return err.Error()
}

return ""
}

// 检查容器安全上下文
func checkContainersSecurity(spec map[string]interface{}, containerType string) error {
containersInterface, exists := spec[containerType]
if !exists {
return nil // 如果没有这种类型的容器,直接返回
}

containers, ok := containersInterface.([]interface{})
if !ok {
return nil // 如果不是数组,直接返回
}

for _, container := range containers {
containerMap, ok := container.(map[string]interface{})
if !ok {
continue
}

securityContext, exists := containerMap["securityContext"]
if !exists {
continue
}

secCtx, ok := securityContext.(map[string]interface{})
if !ok {
continue
}

privileged, exists := secCtx["privileged"]
if !exists {
continue
}

if isTrue(privileged) {
return fmt.Errorf("%s contains privileged: true in securityContext which is not allowed", containerType[:len(containerType)1])
}
}

return nil
}

// 检查 Pod 级别的安全上下文
func checkPodSecurityContext(spec map[string]interface{}) error {
podSecurityContext, exists := spec["securityContext"]
if !exists {
return nil // 如果没有 Pod 级别的安全上下文,直接返回
}

podSecCtx, ok := podSecurityContext.(map[string]interface{})
if !ok {
return nil // 如果不是 map,直接返回
}

runAsNonRoot, exists := podSecCtx["runAsNonRoot"]
if !exists {
return nil // 如果没有设置 runAsNonRoot,直接返回
}

if !isTrue(runAsNonRoot) {
return fmt.Errorf("Pod specifies runAsNonRoot: false which is not allowed")
}

return nil
}

// 检查接口值是否为真
func isTrue(value interface{}) bool {
boolValue, ok := value.(bool)
return ok && boolValue
}

// 发送允许响应
func sendAllowResponse(w http.ResponseWriter, responseVersion, uid, msg string) {
resp := createAdmissionReview(responseVersion, uid, true, nil)
sendResponse(w, resp, msg)
}

// 发送拒绝响应
func sendDenyResponse(w http.ResponseWriter, responseVersion, uid, reason string) {
status := &Status{
Message: reason,
Code: 400,
}
resp := createAdmissionReview(responseVersion, uid, false, status)
sendResponse(w, resp, reason)
}

// 创建 AdmissionReview
func createAdmissionReview(version, uid string, allowed bool, status *Status) *AdmissionReview {
return &AdmissionReview{
APIVersion: version,
Kind: "AdmissionReview",
Response: &AdmissionResponse{
Allowed: allowed,
Result: status,
UID: uid,
},
}
}

// 发送响应
func sendResponse(w http.ResponseWriter, resp *AdmissionReview, msg string) {
jsonBytes, err := json.Marshal(resp)
if err != nil {
log.Printf("Error marshalling response: %v", err)
httpError(w, "can't marshal response", http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(jsonBytes)
log.Printf("Sending validation response: %s", string(jsonBytes))
}

// HTTP 错误响应
func httpError(w http.ResponseWriter, msg string, code int) {
http.Error(w, msg, code)
}

func main() {
http.HandleFunc("/validate", ValidatePrivilegedPods)
log.Println("Validation server started on port 9443")
log.Fatal(http.ListenAndServeTLS(":9443", "vatls.crt", "vatls.key", nil))
}

创建证书

[ req ]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no

[ req_distinguished_name ]
CN = validate-webhook.default.svc

[ v3_req ]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = validate-webhook
DNS.2 = validate-webhook.default
DNS.3 = validate-webhook.default.svc

openssl req -x509 -newkey rsa:2048 -keyout vatls.key -out vatls.crt -days 3650 -nodes -config vatls.cnf -extensions v3_req

配置外部validate webhook 服务

apiVersion: v1
kind: Service
metadata:
name: validate-webhook
spec:
ports:
– port: 9443
targetPort: 9443
protocol: TCP
type: ClusterIP

apiVersion: v1
kind: Endpoints
metadata:
name: validate-webhook
namespace: default
subsets:
– addresses:
– ip: 172.16.0.27
ports:
– port: 9443
protocol: TCP

ValidatingWebhook.yaml

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: privilegedpodvalidator
webhooks:
name: privilegedpods.webhook.default.svc.cluster.local
rules:
apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
scope: "Namespaced"
clientConfig:
service:
name: validatewebhook # 服务名称,需要与实际部署的服务名称匹配
namespace: default
path: "/validate" # 与代码中定义的路径匹配
port: 9443
# 请使用实际的 CA 证书 base64 编码
caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURkakNDQWw2Z0F3SUJBZ0lVSExkTk1iamZhVHBrencvQ2NrRjZKOHZFbitVd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0p6RWxNQ01HQTFVRUF3d2NkbUZzYVdSaGRHVXRkMlZpYUc5dmF5NWtaV1poZFd4MExuTjJZekFlRncweQpOakF4TURnd01qSXpOVGxhRncwek5qQXhNRFl3TWpJek5UbGFNQ2N4SlRBakJnTlZCQU1NSEhaaGJHbGtZWFJsCkxYZGxZbWh2YjJzdVpHVm1ZWFZzZEM1emRtTXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUsKQW9JQkFRREl0WW9BclhVdnNhZVgrVUE3UzlNNDFRaWM3UUZyL1dOeCtxeHVtODUwRDFMOVBEVUxMNXBJRVdMZwpGMW5iR2dzOHNEeVNoS1hQNWpPTUdxS1FVbzJidFMrM2Q0ZldiQ2d5ZnBmbjdUcnZNQmpmVXBZUWxpVTFxaHVYCmh4dmxVZ1ZQc3ZNUWhJVjBnTERmUXZ5WHM5V0IyUjBsZFA0YllFWUJVN25SMEZxQXlTMjNUYURNSUFDa09NUW8KaFJra1c3VENNZ2JiUHRiSWhpUC90MTVpV2hRckN2aTg5cE9xSDZSc09iMm5jYUNpdFZ3RjFPaG9GWHNUbVhVNgpBYkdIUndEbHBEb0JsY29DRjVURlZsTFpodmcvdzdlUm9odjRxMFBESld4VE1qbWdIZ2tmNW9xYm1xZzNLNEF5CkJlY2FCaEFPTnVEMEJmcXhoRmppcm1rS21xSlpBZ01CQUFHamdaa3dnWll3Q3dZRFZSMFBCQVFEQWdRd01CTUcKQTFVZEpRUU1NQW9HQ0NzR0FRVUZCd01CTUZNR0ExVWRFUVJNTUVxQ0VIWmhiR2xrWVhSbExYZGxZbWh2YjJ1QwpHSFpoYkdsa1lYUmxMWGRsWW1odmIyc3VaR1ZtWVhWc2RJSWNkbUZzYVdSaGRHVXRkMlZpYUc5dmF5NWtaV1poCmRXeDBMbk4yWXpBZEJnTlZIUTRFRmdRVUdyZlFJUUxISVFlOWtHYmp4dFRyTzZ4WkdEa3dEUVlKS29aSWh2Y04KQVFFTEJRQURnZ0VCQU1MYzZ5VnhNUXdnTEhLSjNyakgrNG1Yc1lzSEJETkdUbHJ5dnRJNUxEcEw1VkU1bE9JQwpYa29KWmQ3RlUxc3doODY3ZVhpb1FKQTd0d0xpYXA0Uk5NRmxNUEhzcVUzZEFkRXRlWW96RVZ2azFiaVRrenBaCnhiK25Wc0hxSm9MWk9yMTNmcDBmbVZHWUk2SmM4Zms3dXVRZnhIaVJhbnUwb2ZKQnI5Rm41bWtJTHI1eEVxeloKTnJvTGY1Y1UvVVBKdXh1ZU1xbnBCYTdSc0IyZklZejB4QzU4TXVKa2pVbTJ2RUJMa0drcGR6eXkrbEkyM0hqOApQNm1rWmhYWHVkTisrMlV4dm12eWZEKzdIQlIzekFQY2NMZGFKUGtOMm9uU3lubUxZeU5QZTJJQkZOQVh4L1lFCm45UU9VRVlCZGFLN0poYnhTT3FlcXRJOHQ1SkRZMjNvR3BjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
admissionReviewVersions: ["v1", "v1beta1"]
sideEffects: None
timeoutSeconds: 10
failurePolicy: Fail # 如果 webhook 服务不可用,请求将被拒绝
matchPolicy: Equivalent
namespaceSelector: {} # 应用于所有命名空间,可以根据需要修改
objectSelector: {} # 应用于所有 Pod 对象,可以根据需要修改

测试创建特权pod

apiVersion: v1
kind: Pod
metadata:
labels:
run: test
name: valitest
spec:
containers:
image: nginx
name: valitest
resources: {}
securityContext:
privileged: true
dnsPolicy: ClusterFirst
restartPolicy: Always

已被ValidatingWebhook拒绝

在这里插入图片描述
在这里插入图片描述

MutatingWebhookConfiguration的三种选择器:namespaceSelector、objectSelector和规则中的隐式选择器

  • namespaceSelector​,作用对象:资源所在的命名空间。功能:根据命名空间的标签来决定是否触发
  • Webhook。对集群范围的资源(除 Namespace 对象本身)
    objectSelector,​作用对象:资源对象本身,功能:根据资源对象的标签来决定是否触发 Webhook。
  • 规则中的资源选择器​。作用对象:API 请求功能:通过 rules字段下的多个属性,从 API 组、版本、资源类型、操作类型​等维度进行基本匹配

webhooks:
– name: vgpu.hami.io
rules:
– operations: ["CREATE"] # 规则:只关心Pod的创建事件
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
namespaceSelector:
matchExpressions:
– key: hami.io/webhook # 命名空间选择器:排除带有`ignore`标签的命名空间
operator: NotIn
values: ["ignore"]
objectSelector:
matchLabels:
inject-pod: "true" #对象选择器:匹配有`inject-pod`标签的Pod本身

赞(0)
未经允许不得转载:171主机测评 » 理解K8s动态准入控制器-基于Admission Webhook实现Sidecar自动注入检验等
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址