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

理解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发送给 WebhookWebhook 处理完成后,也必须返回一个 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/DELETEobject: RawExtension # 新资源对象(JSON)oldObject: RawExtension # 旧资源对象(更新/删除时)options: RawExtension # 操作选项userInfo: UserInfo # 请求用户信息response:# 响应部分(Webhook → API Server)uid: string # 必须与 request.uid 一致allowed: boolean # 是否允许操作patchType: string # 补丁类型,如 JSONPatchpatch:[]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"`}funcMutate_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))}funcmain(){ 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配置

funcMutate_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 {// 遍历所有现有容器,检查是否已配置resourcesfor 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是否为空funcisEmptyResources(resources interface{})bool{if resources ==nil{returntrue} resMap, ok := resources.(map[string]interface{})if!ok {returntrue}// 检查map是否为空iflen(resMap)==0{returntrue}// 检查map的值是否都是空的 hasRequests :=false hasLimits :=falseif 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"`}// 安全检查函数funcValidatePrivilegedPods(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,"")}// 获取响应版本funcgetResponseVersion(apiVersion string)string{if apiVersion !=""{return apiVersion }return"admission.k8s.io/v1"}// 验证 Pod 安全性funcvalidatePodSecurity(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""}// 检查容器安全上下文funccheckContainersSecurity(spec map[string]interface{}, containerType string)error{ containersInterface, exists := spec[containerType]if!exists {returnnil// 如果没有这种类型的容器,直接返回} containers, ok := containersInterface.([]interface{})if!ok {returnnil// 如果不是数组,直接返回}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}ifisTrue(privileged){return fmt.Errorf("%s contains privileged: true in securityContext which is not allowed", containerType[:len(containerType)-1])}}returnnil}// 检查 Pod 级别的安全上下文funccheckPodSecurityContext(spec map[string]interface{})error{ podSecurityContext, exists := spec["securityContext"]if!exists {returnnil// 如果没有 Pod 级别的安全上下文,直接返回} podSecCtx, ok := podSecurityContext.(map[string]interface{})if!ok {returnnil// 如果不是 map,直接返回} runAsNonRoot, exists := podSecCtx["runAsNonRoot"]if!exists {returnnil// 如果没有设置 runAsNonRoot,直接返回}if!isTrue(runAsNonRoot){return fmt.Errorf("Pod specifies runAsNonRoot: false which is not allowed")}returnnil}// 检查接口值是否为真funcisTrue(value interface{})bool{ boolValue, ok := value.(bool)return ok && boolValue }// 发送允许响应funcsendAllowResponse(w http.ResponseWriter, responseVersion, uid, msg string){ resp :=createAdmissionReview(responseVersion, uid,true,nil)sendResponse(w, resp, msg)}// 发送拒绝响应funcsendDenyResponse(w http.ResponseWriter, responseVersion, uid, reason string){ status :=&Status{ Message: reason, Code:400,} resp :=createAdmissionReview(responseVersion, uid,false, status)sendResponse(w, resp, reason)}// 创建 AdmissionReviewfunccreateAdmissionReview(version, uid string, allowed bool, status *Status)*AdmissionReview {return&AdmissionReview{ APIVersion: version, Kind:"AdmissionReview", Response:&AdmissionResponse{ Allowed: allowed, Result: status, UID: uid,},}}// 发送响应funcsendResponse(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 错误响应funchttpError(w http.ResponseWriter, msg string, code int){ http.Error(w, msg, code)}funcmain(){ 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: privileged-pod-validator webhooks:-name: privileged-pods.webhook.default.svc.cluster.local rules:-apiGroups:[""]apiVersions:["v1"]operations:["CREATE","UPDATE"]resources:["pods"]scope:"Namespaced"clientConfig:service:name: validate-webhook # 服务名称,需要与实际部署的服务名称匹配namespace: default path:"/validate"# 与代码中定义的路径匹配port:9443# 请使用实际的 CA 证书 base64 编码caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURkakNDQWw2Z0F3SUJBZ0lVSExkTk1iamZhVHBrencvQ2NrRjZKOHZFbitVd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0p6RWxNQ01HQTFVRUF3d2NkbUZzYVdSaGRHVXRkMlZpYUc5dmF5NWtaV1poZFd4MExuTjJZekFlRncweQpOakF4TURnd01qSXpOVGxhRncwek5qQXhNRFl3TWpJek5UbGFNQ2N4SlRBakJnTlZCQU1NSEhaaGJHbGtZWFJsCkxYZGxZbWh2YjJzdVpHVm1ZWFZzZEM1emRtTXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUsKQW9JQkFRREl0WW9BclhVdnNhZVgrVUE3UzlNNDFRaWM3UUZyL1dOeCtxeHVtODUwRDFMOVBEVUxMNXBJRVdMZwpGMW5iR2dzOHNEeVNoS1hQNWpPTUdxS1FVbzJidFMrM2Q0ZldiQ2d5ZnBmbjdUcnZNQmpmVXBZUWxpVTFxaHVYCmh4dmxVZ1ZQc3ZNUWhJVjBnTERmUXZ5WHM5V0IyUjBsZFA0YllFWUJVN25SMEZxQXlTMjNUYURNSUFDa09NUW8KaFJra1c3VENNZ2JiUHRiSWhpUC90MTVpV2hRckN2aTg5cE9xSDZSc09iMm5jYUNpdFZ3RjFPaG9GWHNUbVhVNgpBYkdIUndEbHBEb0JsY29DRjVURlZsTFpodmcvdzdlUm9odjRxMFBESld4VE1qbWdIZ2tmNW9xYm1xZzNLNEF5CkJlY2FCaEFPTnVEMEJmcXhoRmppcm1rS21xSlpBZ01CQUFHamdaa3dnWll3Q3dZRFZSMFBCQVFEQWdRd01CTUcKQTFVZEpRUU1NQW9HQ0NzR0FRVUZCd01CTUZNR0ExVWRFUVJNTUVxQ0VIWmhiR2xrWVhSbExYZGxZbWh2YjJ1QwpHSFpoYkdsa1lYUmxMWGRsWW1odmIyc3VaR1ZtWVhWc2RJSWNkbUZzYVdSaGRHVXRkMlZpYUc5dmF5NWtaV1poCmRXeDBMbk4yWXpBZEJnTlZIUTRFRmdRVUdyZlFJUUxISVFlOWtHYmp4dFRyTzZ4WkdEa3dEUVlKS29aSWh2Y04KQVFFTEJRQURnZ0VCQU1MYzZ5VnhNUXdnTEhLSjNyakgrNG1Yc1lzSEJETkdUbHJ5dnRJNUxEcEw1VkU1bE9JQwpYa29KWmQ3RlUxc3doODY3ZVhpb1FKQTd0d0xpYXA0Uk5NRmxNUEhzcVUzZEFkRXRlWW96RVZ2azFiaVRrenBaCnhiK25Wc0hxSm9MWk9yMTNmcDBmbVZHWUk2SmM4Zms3dXVRZnhIaVJhbnUwb2ZKQnI5Rm41bWtJTHI1eEVxeloKTnJvTGY1Y1UvVVBKdXh1ZU1xbnBCYTdSc0IyZklZejB4QzU4TXVKa2pVbTJ2RUJMa0drcGR6eXkrbEkyM0hqOApQNm1rWmhYWHVkTisrMlV4dm12eWZEKzdIQlIzekFQY2NMZGFKUGtOMm9uU3lubUxZeU5QZTJJQkZOQVh4L1lFCm45UU9VRVlCZGFLN0poYnhTT3FlcXRJOHQ1SkRZMjNvR3BjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== admissionReviewVersions:["v1","v1beta1"]sideEffects: None timeoutSeconds:10failurePolicy: 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:truednsPolicy: 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本身

Read more

从微博热搜到深度报告:实测 ToClaw 的信息检索与分析能力,AI 终于开始“先找再写”

从微博热搜到深度报告:实测 ToClaw 的信息检索与分析能力,AI 终于开始“先找再写”

现在做内容、做运营、做市场,最怕的不是没有灵感,而是信息流转得太快。一个热点从冒头到发酵,可能只需要几个小时;而从“看到热搜”到“形成一版可用分析”,往往要经历找榜单、翻链接、看评论、筛信息、做结构、再写结论一整套流程。很多人以为这件事的核心是写,其实真正耗时的,往往是前面的“找”和“判”。 这也是我为什么会特别想测 ToDesk 远程控制新上线的 ToClaw:如果它只是会写几段话,那其实不算新鲜;但如果它能围绕“热点分析”这个真实任务,把检索、筛选、归纳、生成这几个动作串起来,那它就不只是一个聊天入口,而更像是一个真正能进入工作流的 AI 助手。 而从这次实测来看,ToClaw 在这个场景里,确实给了我一点不一样的感觉。 一、开放式测试 为了看清 ToClaw 到底是在“生成”

什么是人工智能?AI、机器学习、深度学习的关系

什么是人工智能?AI、机器学习、深度学习的关系

文章目录 * 什么是人工智能 * 人工智能的定义 * 人工智能的分类 * 什么是机器学习 * 机器学习的基本概念 * 机器学习的工作流程 * 机器学习的主要类型 * 什么是深度学习 * 深度学习的基本概念 * 深度学习的优势 * 深度学习的应用领域 * AI、机器学习、深度学习的关系 * 三者的层次关系 * 三者的发展历程 * 如何选择合适的方法 * 实际应用案例分析 * 案例一:垃圾邮件过滤 * 案例二:图像识别 * 案例三:推荐系统 * 学习路径建议 * 第一阶段:打好基础 * 第二阶段:深入学习 * 第三阶段:实战提升 * 总结 本篇文章将带你深入理解人工智能的核心概念,厘清AI、机器学习、深度学习之间的关系,为后续的学习打下坚实的基础。 什么是人工智能 人工智能的定义 人工智能,英文名称为Artificial Intelligence,简称AI,这个概念最早由约翰·麦卡锡在1956年的达特茅斯会议上提出。那么什么是人工智能呢?简单来说,人工智能就

AI 驱动游戏:鸿蒙生态的机会在哪里?

AI 驱动游戏:鸿蒙生态的机会在哪里?

子玥酱(掘金 / 知乎 / ZEEKLOG / 简书 同名) 大家好,我是子玥酱,一名长期深耕在一线的前端程序媛 👩‍💻。曾就职于多家知名互联网大厂,目前在某国企负责前端软件研发相关工作,主要聚焦于业务型系统的工程化建设与长期维护。 我持续输出和沉淀前端领域的实战经验,日常关注并分享的技术方向包括前端工程化、小程序、React / RN、Flutter、跨端方案, 在复杂业务落地、组件抽象、性能优化以及多端协作方面积累了大量真实项目经验。 技术方向:前端 / 跨端 / 小程序 / 移动端工程化 内容平台:掘金、知乎、ZEEKLOG、简书 创作特点:实战导向、源码拆解、少空谈多落地 文章状态:长期稳定更新,大量原创输出 我的内容主要围绕 前端技术实战、真实业务踩坑总结、框架与方案选型思考、行业趋势解读 展开。文章不会停留在“API 怎么用”,而是更关注为什么这么设计、在什么场景下容易踩坑、

80+提示词 震撼发布|Seedance 2.0 提示词完全指南:从新手到“AI导演“

80+提示词 震撼发布|Seedance 2.0 提示词完全指南:从新手到“AI导演“

编者按 这两天,X.com、微博、小红书被一款名叫 Seedance 2.0 的 AI 视频生成模型刷屏。从 Tom Cruise 和 Brad Pitt 的"对打",到《复仇者联盟》的重制版,再到"水獭版"《老友记》……这些一度被认为需要好莱坞团队耗时数月才能完成的视频,如今只需一句提示词就能秒生成。 作为字节跳动推出的新一代多模态视频生成工具,Seedance 2.0 正式宣告:AI 视频创作时代已至,人人都可能成为"导演"。 今天,我们为你汇总了全网最实用的 Seedance 2.0 提示词和使用技巧,让你快速从入门到精通。