1. 项目概述:为什么说Admission Controller是云原生的“协议审查官”
国内某家做在线教育的公司在一次大促前夜,一个开发人员拿着生产集群的kubeconfig,敲下了一行kubectl delete ns production --force --grace-period=0。所幸当时集群里接了一个自定义的Admission Controller,把这行命令拦了下来,理由是“production命名空间被标记为保护名单”。事后复盘,所有人后背发凉——如果不是这层拦截,整个生产环境几百个服务会在几秒钟内全部消失。
这个场景基本说清楚了Admission Controller是什么:它是Kubernetes API Server处理请求时的最后一道关卡,任何创建、更新、删除资源的请求,在通过认证和鉴权之后、真正持久化到etcd之前,都会经过它。它就像机场安检里的协议审查官,你登机牌有了、身份证核验过了,但随身行李里能不能带液体、能不能带打火机,由安检员说了算。
Admission Controller解决的核心问题有三个:
- 安全防线:阻止不安全的工作负载进入集群,比如以root身份运行的容器、挂载了宿主目录的Pod、请求了过高权限的ServiceAccount。
- 策略统一落地:把团队约定固化到平台层,比如所有应用必须带资源requests和limits、必须配置健康检查探针,不满足条件的Pod直接拒绝创建。
- 变更注入:在资源被创建前动态修改它的配置,比如自动注入sidecar代理、自动添加节点亲和性、自动打标签。
它适合谁来参考?如果你在维护Kubernetes集群,或者你在公司里做内部研发平台、DevOps平台,又或者你正在为团队制定云原生安全基线,这篇内容会把Admission Controller的原理、安全风险、实践落地和排错思路一次讲透。
2. 整体设计思路拆解:Admission Controller如何融入Kubernetes的请求链路
2.1 Admission在整个API请求链路上的位置
要理解Admission Controller的价值,首先得知道它在Kubernetes的API请求链路里到底处在哪个位置。一次完整的API请求经历大致如下:
- 客户端发起请求,可能是
kubectl、控制器循环或者集群内部组件。 - 请求到达API Server,先做TLS终止。
- 认证阶段(Authentication):确认“你是谁”。
- 鉴权阶段(Authorization):确认“你能做什么”。
- 准入控制阶段(Admission Control):确认“这件事允不允许发生”。
- 资源校验和一些默认值填充。
- 持久化到etcd。
注意第5步,这个阶段发生在资源对象被持久化之前,所以它有一个非常大的优势:不仅可以在资源创建时拦截,还可以在更新和删除时拦截。这也是为什么它可以兜底很多安全问题——比如前面提到的误删除生产命名空间,删除动作发生在准入控制阶段,你的拦截逻辑可以在这时候介入。
Admission Controller分为两大类:
- 内置Admission Controller:Kubernetes自带的,比如
NamespaceLifecycle、LimitRanger、ResourceQuota、PodSecurityPolicy(已废弃,被Pod Security Admission取代)、PodNodeSelector等。 - 动态Admission Controller:通过Webhook方式对接自定义的准入逻辑,分为MutatingAdmissionWebhook和ValidatingAdmissionWebhook两种。前者可以在资源持久化前修改资源内容,后者只做校验,不做修改。
2.2 为什么“动态准入控制”是安全治理的关键
在Kubernetes里,“动态”这个词的核心意义在于:你不需要修改API Server的代码或者重启API Server的进程,就能随时注册或者调整准入规则。这对于一个在快速演进的业务环境来说太重要了。
举个例子,你们的DevOps团队决定从明天开始,所有新创建的Deployment必须包含PodDisruptionBudget。如果用的是静态的、需要在API Server启动参数里配置的准入插件(比如老式的ImagePolicyWebhook),你改一下规则,需要改API Server配置并滚动重启所有Master节点——在很多企业里这几乎是不可能的操作,因为变更窗口很难申请。
但用动态准入控制,你只需要部署一个Admission Webhook的Service,然后提交一份ValidatingWebhookConfiguration资源,告诉API Server“哪些请求发到我这里来校验”。从提交这份配置到规则生效,通常只要几十秒。这就是动态准入控制在安全治理里的核心价值——安全策略的变更和迭代速度可以跟上业务变化的速度。
2.3 方案选型的考量:Admission Controller和普通策略引擎的区别
很多刚接触云原生的同学会把Admission Controller和传统的策略引擎(比如OPA、Gatekeeper、Kyverno这种)混为一谈。它们确实都做策略控制,但在Kubernetes生态里的定位有明显区别:
- Admission Controller是Kubernetes自身的安全机制,它负责的是“在Kubernetes内部对API请求做准入判断”,它的存在形态就是Webhook。它就是一条管道,至于管道里跑的是什么策略逻辑,可以由你自己写,也可以用Gatekeeper、Kyverno来承载。
- 基于Admission Controller构建的策略引擎(Gatekeeper/Kyverno)是Admission Controller的“消费方”,它们把Admission Webhook抽象成更友好的策略声明方式。比如Kyverno可以直接写一个策略,要求“所有Pod必须有
app标签”,它底层就是通过Mutating/ValidatingWebhookConfiguration注册到API Server上,由Kyverno的控制器来处理请求。
选型时要考虑什么?如果你们的合规需求比较简单,比如只是禁止特权容器、强制加资源limit,用Kubernetes原生能力(Pod Security Admission + ValidatingAdmissionPolicy)就够了。如果你们有复杂的、基于上下文的策略,比如“不同环境的Service必须匹配特定SNI证书”,那可能就需要Kyverno或者自己写一个专用的Webhook。
3. 核心细节与安全问题:Admission Controller自身的安全风险剖析
3.1 风险一:Admission自身被绕过,规则成了装饰品
Admission Controller拦截请求的前提是:请求必须经过API Server。听起来像是废话,但在真实环境里有很多“旁路”场景,能让你的准入规则变成摆设。
比较典型的一个:有些团队会给节点配置kubelet的--system-reserved之外的参数,管理员或者有高权限的运维同学可能直接改节点上的kubelet配置,然后通过kubelet的API创建静态Pod。静态Pod不经过API Server的Admission阶段,这意味着你写的准入规则对它完全无效。再比如,有些做主机安全的同学直接修改etcd里的数据,那就更绕过了整个Kubernetes API层——直接把Pod的数据写进etcd,API Server重启后这个Pod会出现在集群里,但它从未经过你的准入控制。
这类风险怎么解?注册准入规则只是第一步,还要从多个层面保证它“没有退路”:
- 用Pod Security Admission在Namespace级别设置基线策略,避免遗漏。
- 审计日志和告警要对“非API Server路径创建的资源”保持敏感。
- 控制etcd的访问权限,etcd端口永远不要对非必要成员开放。
- 节点上的kubelet必须启用认证和授权,禁止匿名请求。
3.2 风险二:初始配置的敞口——这些默认值就是安全漏洞
我自己在帮几家企业做云原生安全评估时,发现Admission配置里最常见的几个敞口:
第一,Admission规则只覆盖了部分资源类型。比如你只想拦截privileged容器,你写了一个ValidatingWebhook,配置了resources: pods,但人家的Pod是通过ReplicaSet创建的——注意,创建ReplicaSet时会触发对Pod模板的校验吗?不会。你需要匹配的是pods,但更要匹配replicasets、deployments、statefulsets、jobs、cronjobs等所有“能产生Pod”的父级资源。如果只配pods,那么用户明明创建一个Deployment,API Server校验Deployment的时候已经把你的Webhook忽略了,这个Deployment成功创建后,ReplicaSet控制器再去创建Pod——那时候Pod本身确实会被准入校验,但如果那个Deployment里没有直接生成Pod呢?其实不是的,实际情况是:Deployment创建后,ReplicaSet会被创建,最终Pod会被创建,所以Pod校验还是会发生的。但是要注意一个时间差和规则覆盖范围的问题——很多策略是“对Pod生效”的,可当Pod由控制器创建时,它的PodTemplate里的字段其实在被控制器创建的时候就已经定死了。如果你的规则只针对Pod资源,那么虽然Pod被拦截了,但Deployment/StatefulSet的资源本身是创建成功的——用户看到报错,改Pod模板,没问题,但假如你的规则拦的是“Pod必须带某个标签”,而对方在Deployment的Pod模板里写了这个标签,那Pod创建时标签是有的——看起来没问题。但你如果规则里要检查的是Deployment级的字段(比如副本数、更新策略),只配pods就完全失效了。
第二,规则匹配的operations写错了。很多人写Webhook配置的时候,operations只写了CREATE,忘了UPDATE。于是用户可以通过kubectl edit或者直接patch的方式,把一个原本安全的Deployment改成privileged容器——UPDATE请求不会触发你的校验,这比一开始就没拦截更隐蔽。
第三,没有考虑namespaceSelector的坑。有人想“系统组件所在的kube-system不拦截,以免误伤”,于是namespaceSelector排除了它。但攻击者如果已经能操作集群,他完全可以把恶意工作负载部署到kube-system——这直接绕过了你的Admission Controller。我的建议是:系统组件所在namespace的豁免范围一定要做得极小,通常只保留kube-system里面那几个官方组件需要的路径,其余一律不豁免。
3.3 风险三:Webhook自身故障如何让集群“脑残”
关于Admission Controller的一个反直觉但极其重要的事实:把它配置错了,比没有它更危险。因为API Server默认在Webhook调用失败时,行为是fail closed(拒绝请求)还是fail open(放行请求),取决于你在Webhook配置里设置的failurePolicy。
设成failurePolicy: Fail,你的一套自定义Admission服务挂了,整个集群所有被匹配的写操作都会失败。如果这个策略匹配的正好是Deployments,那你连kubectl scale都做不了,等于集群进入了“只读模式”。如果业务上赶着发布,运维同学会直接杀掉你那个Webhook Pod——你知道的,只要在kube-system里删掉那个Pod,集群就恢复了。
反过来,设成failurePolicy: Ignore,Admission服务出问题的时候,API Server会干脆跳过校验。这意味着你的安全策略在关键时刻是失效的,而你可能毫无察觉。
踩过这个坑之后,我的建议是做健康检查和冗余部署:Webhook服务的Pod至少2副本,配置好/healthz探针,并且把failurePolicy在开发和生产的预期做差异化。生产环境安全策略的Webhook尽量用Fail,但要做好故障转移预案(比如用Kyverno这类可以热加载规则的控制器,至少可以快速关停)。
3.4 风险四:性能和超时引发的“链式雪崩”
Admission Webhook每接到一个匹配的请求,API Server会同步调用它。也就是说,如果Admission服务响应慢,所有匹配的API请求都会被拖慢,直到超时。正常API请求的超时时间是30秒左右,Admission webhook的默认超时是10秒,如果它在10秒内没返回,API Server会按failurePolicy处理。
这会出现一个很有意思的雪崩:一个高并发的发布任务,Deployment创建了几百个Pod请求,每个请求都经过Admission,Admission如果有个缓存未命中时的慢查询,比如从外部数据库同步数据,那么所有请求都会堵在Webhook这一步,后面还有几百个请求在排队。一旦队列堆积,API Server的整体吞吐能力大幅下降,kubelet的心跳上报也受影响,整个集群都可能出现“不健康”的状态。
实践中的规避手段:
- Webhook处理函数里不要做任何外部IO操作,要做的所有数据要么在内存缓存里,要么在Webhook启动的时候就加载好。
- 设置合理的
timeoutSeconds,比如3秒,宁可超时后让API Server按照FailurePolicy处理,也不能让一个请求挂在那里吃掉API Server的连接。 - 在Webhook前面加一层简单的LRU缓存,把高频校验结果缓存起来。
- 给Admission服务设置独立的资源配额和HPA,别让它和主业务抢资源。
3.5 风险五:证书、命名空间和升级带来的“隐形炸弹”
Admission Webhook必须配置TLS证书,而且证书有一个很常见的坑:证书过期。如果API Server和Webhook之间的TLS握手失败,请求会按照failurePolicy处理——如果你的failurePolicy又是Fail,那又是一次集群不可用事故。
再有一点:很多团队用了Helm管理Webhook的部署,但没有处理好升级时的资源冲突。Helm升级可能会覆盖掉ValidatingWebhookConfiguration,而这份配置里通常有通过caBundle写入的CA证书,升级后如果证书变了但caBundle没更新,API Server握手就会失败。
另外还有个细节:如果你的Webhook服务部署在业务集群里,那么当集群Pod网络被策略限制(比如NetworkPolicy拒绝跨Namespace访问)时,API Server到Webhook的请求也会被拦截。这个“隐形炸弹”很难排查,因为从API Server的视角看,请求超时了,但从Webhook的视角看,请求根本没到。
4. 实操过程:从零搭建一个动态准入控制的“协议审查官”
4.1 明确目标:我们要拦截什么
下面我演示一个真实需求落地:公司规定,生产环境所有Pod必须满足以下条件:
- 禁止privileged容器。
- 禁止挂载宿主机的根目录(hostPath类型为Directory且path为/)。
- 必须声明resource requests和limits。
- 必须设置
app标签。
我用两种方式实现:一种是用Kubernetes 1.26+自带的ValidatingAdmissionPolicy(VAP),这是官方最近的推荐方向;另一种是写一个自定义Webhook。
先说VAP。它是Kubernetes 1.26引入的、1.28进入Beta的新特性。用VAP最大的好处是不用自己写和维护Webhook服务,直接用CEL表达式写校验逻辑,API Server本身就可以执行。对于中小集群、常规安全策略,它完全够用,而且是官方能力,后期维护成本低。
4.2 用ValidatingAdmissionPolicy实现批量校验
首先定义一个ValidatingAdmissionPolicy:
apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingAdmissionPolicy metadata: name: require-app-label-and-resources spec: failurePolicy: Fail matchConstraints: resourceRules: - apiGroups: ["apps", ""] apiVersions: ["*"] operations: ["CREATE", "UPDATE"] resources: ["deployments", "statefulsets", "pods"] validations: - expression: "has(object.metadata.labels) && 'app' in object.metadata.labels" message: "所有工作负载必须设置 app 标签" - expression: "object.spec.template.spec.containers.all(c, has(c.resources) && has(c.resources.requests) && has(c.resources.limits))" message: "所有容器必须设置 resources.requests 和 resources.limits" - expression: "!object.spec.template.spec.containers.all(c, has(c.securityContext) && c.securityContext.privileged == true)" message: "禁止创建特权容器" - expression: "!object.spec.template.spec.volumes.exists(v, has(v.hostPath) && v.hostPath.path == '/')" message: "禁止挂载宿主机根目录"注意匹配资源的写法,我故意写了deployments、statefulsets和pods。原因前面讲过,如果只匹配pods,用户在创建时看到的是Pod资源被拦截,但Deployment本身创建成功,报错信息经过控制器包装,往往变得不直观。把deployments等父资源也纳入匹配,用户直接看到“Deployment被拒绝”,排障体验好很多。
然后绑定这个策略到指定命名空间。需要创建ValidatingAdmissionPolicyBinding:
apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingAdmissionPolicyBinding metadata: name: require-app-label-and-resources-binding spec: policyName: require-app-label-and-resources validationActions: ["Deny"] matchResources: namespaceSelector: matchExpressions: - key: environment operator: In values: ["production"]这里有一个细节:validationActions支持Deny、Warn和Audit。我建议在正式切换Deny之前,先设置为Warn运行一段观察期,通过审计日志看看现有工作负载有多少不满足规则,避免新策略上线后大面积拦截。
如果你用的Kubernetes版本低于1.26,不支持VAP,那就要考虑用Kyverno或者自研Webhook。我下面演示一个最小化的自研ValidatingWebhook。
4.3 自研Webhook:最小可靠实现
自研Webhook的本质是:API Server在请求达到时,将AdmissionReview对象POST到我们的Webhook服务,服务处理完,把AdmissionReview带着allowed: true/false返回。
我选Go写,纯标准库加上K8s的几个库,避免引入太重的东西。
package main import ( "encoding/json" "fmt" "io" "log" "net/http" admissionv1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func main() { http.HandleFunc("/validate", handleValidate) http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) log.Fatal(http.ListenAndServeTLS(":8443", "/certs/tls.crt", "/certs/tls.key", nil)) } func handleValidate(w http.ResponseWriter, r *http.Request) { var review admissionv1.AdmissionReview body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "read body error", http.StatusBadRequest) return } if err := json.Unmarshal(body, &review); err != nil { http.Error(w, "unmarshal error", http.StatusBadRequest) return } req := review.Request var pod corev1.Pod if err := json.Unmarshal(req.Object.Raw, &pod); err != nil { http.Error(w, "unmarshal pod error", http.StatusBadRequest) return } allowed, msg := validatePod(&pod) response := admissionv1.AdmissionReview{ TypeMeta: metav1.TypeMeta{ APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview", }, Response: &admissionv1.AdmissionResponse{ UID: req.UID, Allowed: allowed, }, } if !allowed { response.Response.Result = &metav1.Status{ Message: msg, } } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } func validatePod(pod *corev1.Pod) (bool, string) { for _, c := range pod.Spec.Containers { if c.SecurityContext != nil && c.SecurityContext.Privileged != nil && *c.SecurityContext.Privileged { return false, fmt.Sprintf("container %s in pod %s is privileged, not allowed", c.Name, pod.Name) } if c.Resources.Requests == nil || c.Resources.Limits == nil { return false, fmt.Sprintf("container %s in pod %s must have requests and limits", c.Name, pod.Name) } } for _, v := range pod.Spec.Volumes { if v.HostPath != nil && v.HostPath.Path == "/" { return false, fmt.Sprintf("volume %s mounts host root path, not allowed", v.Name) } } return true, "" }这段代码只是演示框架,生产环境至少还要加上:
- 并发处理和超时控制,别让一个慢请求占满goroutine。
- 对非Pod资源(比如我们上文的Deployment)要做好Object的反序列化兼容,或者直接在Webhook配置里就只用
resources: pods,避免兼容问题。 - 日志要结构化输出,用
log/slog打JSON日志,方便后续接入日志平台。
4.4 证书签发与Webhook配置
自研Webhook必须走TLS。我个人测试常用的做法是:在集群内用一个小的cert-manager Certificate,或者直接用openssl自签一个,只要caBundle能对应上即可。
实践里最好用cert-manager自动签发,它的Certificate资源直接签发Webhook所需的证书,并且用caInjector自动把caBundle注入到ValidatingWebhookConfiguration里。手动管理证书的话,太容易踩过期和caBundle不匹配的坑了。如果你现在公司正好有cert-manager,强烈建议选它。
Webhook配置的YAML大致长这样:
apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: name: pod-policy-validator webhooks: - name: pod-policy-validator.example.com admissionReviewVersions: ["v1"] sideEffects: None failurePolicy: Fail timeoutSeconds: 3 clientConfig: service: name: pod-policy-validator namespace: kube-system path: /validate port: 8443 caBundle: <base64-ca> rules: - apiGroups: [""] apiVersions: ["v1"] operations: ["CREATE", "UPDATE"] resources: ["pods"] namespaceSelector: matchExpressions: - key: environment operator: In values: ["production"]注意一个不起眼但又很重要的字段:sideEffects: None。如果你不设置这个字段,API Server会拒绝创建Webhook配置,因为它不确定这个Webhook是否有副作用,不能安全重试。这个字段也是审计和审核Webhook配置时的高频检查点。
4.5 观察与验证
配置完成之后,先别急着大批量发布。我的习惯是:
- 先在一个测试namespace里创建一个符合规则的Pod,确认allowed。
- 再创建一个特权容器Pod,确认被拒绝,且返回信息友好。
- 查看API Server Audit日志,确认AdmissionReview被正确记录。
kubectl run nginx-test --image=nginx kubectl run bad-pod --image=nginx --overrides='{"spec":{"containers":[{"name":"nginx","image":"nginx","securityContext":{"privileged":true}}]}}'第二条命令应该返回类似这样:
Error from server: admission webhook "pod-policy-validator.example.com" denied the request: container nginx in pod bad-pod is privileged, not allowed看到这个报错,说明你的“协议审查官”开始工作了。
5. 常见问题与排查技巧实录
5.1 Webhook配置了但不生效,大概率是匹配条件写错
这是我被问得最多的问题,“我配了ValidatingWebhookConfiguration,但创建Pod没有被拦截”。排查步骤我建议按这个顺序来:
- 先确认API Server有没有收到请求:
kubectl get apiservice看整体健康度,再用kubectl logs看Webhook Pod日志里有没有请求进来。 - 如果日志里压根没有请求,说明请求没有匹配到规则。检查
rules里的apiGroups、resources和operations是否匹配你要拦截的资源。万能的排错方法是临时写一个“什么请求都拒绝”的Webhook,如果它能拦住,就说明是匹配条件问题;如果它都拦不住,那是配置本身没生效。 - 检查namespaceSelector,是不是你测试用的namespace被排除了。
- 确认
failurePolicy不是Ignore——如果配置是Ignore且你的Webhook服务异常,API Server会直接静默放行,看起来就像“配置没生效”。
5.2 Admission Review的版本兼容问题
Kubernetes对不同版本支持不同的AdmissionReview版本。如果你写的Webhook只支持admission.k8s.io/v1beta1,而集群是1.16+,默认只发送v1,那API Server根本不会调用你,它可能在启动Webhook配置时报错,也可能静默失败。
处理方式很简单:admissionReviewVersions里写上我们支持的版本列表,客户端和服务端会协商。写代码的时候,统一用v1类型,同时做好兼容。
5.3 证书过期导致集群“只读”
我曾经在一个客户现场遇到这个问题:集群突然所有写操作都报Internal error occurred: failed calling webhook "xxx": failed to call webhook: Post "https://...": x509: certificate has expired or is not yet valid。
查的时候,一眼看到是证书过期。但因为failurePolicy: Fail,整个集群的写操作全部被拒。处理步骤就是立刻更新证书,然后把caBundle同步更新。
应急经验:如果来不及换证书,可以先临时把failurePolicy改成Ignore,让集群恢复可用,再慢慢换证。注意,如果要改ValidatingWebhookConfiguration,这个操作本身是写操作,如果这个Webhook匹配所有写操作,那改配置的请求也会被它自己拦截——这是个经典的先有鸡还是先有蛋问题。
处理这种“自杀式”Webhook的方法是用非匹配路径绕过:如果Webhook规则匹配pods,那就用kubectl edit validatingwebhookconfiguration而不是通过创建Pod来测试;如果Webhook匹配所有资源,包括validatingwebhookconfigurations,那就只能通过直接修改etcd或者用API Server绕过参数来急救,但更优雅的方案是给集群加一层“管理通道”——比如设置一个永不匹配所有Pod的管理员Namespace。
5.4 Webhook服务性能波动影响发布流水线
有一回客户反馈,发布的时候Jenkins里每个Deployment都要等很久,点创建到Pod起来,中间多出8秒延迟。第一反应就是Webhook。一看我们的Webhook服务,它创建了一个BigQuery客户端,每次收到请求都会去查询外部数据库判断某些标签是否合法——网络来回一次200ms,但并发一高,goroutine堆积,延迟指数上升。
这就是之前说的“不要在Webhook里做外部IO”。后来我们把规则静态化,启动的时候加载一份全量配置到内存,Webhook处理全部走本地逻辑,P95延迟从8秒降到了86ms。
5.5 如何验证你的Admission策略没有“漏风”
最后给一个实战建议:定期做“红队视角验证”。不是真的攻击,而是列一个攻击清单,逐个验证你的Admission Controller能不能拦住:
- 直接创建privileged Pod。
- 创建挂载
/var/lib/kubelet的Pod。 - 创建带有
hostPID: true的Pod。 - 创建一个带有
node-role.kubernetes.io/master:NoSchedule容忍度且调度到Master节点的Pod。 - 创建一个使用
hostNetwork的Pod。 - 创建一个ServiceAccount,给它绑定
cluster-admin角色,然后用这个SA创建特权Pod。
这些场景不需要真的执行,用kubectl create --dry-run=server就能测试到Admission阶段——--dry-run=server会发请求到API Server并执行Admission,只是不持久化,这是测试准入策略的利器。
6. 在云原生运维体系里如何持续演进
Admission Controller不是配一次就不管的,它必须被当成一个持续运行的安全服务来运维。我的建议是,把它纳入云原生运维的日常巡检项和发布流程中。
第一,把Admission策略做成代码。用GitOps的方式管理ValidatingAdmissionPolicy和ValidatingWebhookConfiguration,所有策略变更都必须走代码评审、CI校验,再自动应用到集群。这样不仅可审计,还能回滚。
第二,和审计链路打通。建议开启API Server的Audit日志,尤其把Admission相关的事件接收下来,接入ELK或者云上的日志平台。有了审计日志,出了安全事件才能追溯“当时有没有经过Admission、结果是什么”。没有审计的准入控制等于没有证据链的安全制度。
第三,建立策略的“灰度发布”机制。在把策略设为Deny之前,先用Audit或者Warn方式跑一段时间,通过观察审计日志评估影响面。突然上线一个Deny策略是生产事故的高发原因——你永远不会知道你的业务团队有多少“没设resource limit的Deployment”在跑。
第四,周期性复核策略。每半年做一次策略复核,把过期的、冗余的、冲突的规则清掉。我见过有的团队Webhook规则越加越多,最后自己都说不清楚哪些规则在生效。清理前用试运行模式收集一段时间的数据,确认没有误伤再动手。
回到开头那个“协议审查官”的比喻——一个优秀的审查官不仅要能拦下违规物品,更要能做到规则清晰、执行高效、过程留痕、持续改进。Admission Controller带给云原生环境的,正是这种“策略即代码、安全即服务”的能力。配置一套合格且健壮的准入控制体系,比买一堆安全扫描器有用得多,因为它在源头就挡住了绝大多数错误和攻击。