ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

K8S业务禁令黑名单:五条技术路线实现快速服务隔离与权限封禁

K8S业务禁令黑名单:五条技术路线实现快速服务隔离与权限封禁 昨天半夜我被一条告警从被窝里拽了起来。某个内部数据服务突然开始疯狂向外发起连接CPU 直接打满同事的第一反应是“把它删了”但生产环境里的服务哪敢说删就删——删了影响面更大而且后续还要查日志、留证据。最后我们做的操作是在不删除任何资源的前提下五分钟内让这个服务从网络上彻底“消失”其他业务完全不受影响。这个操作在 K8S 里没有一个现成的按钮叫“拉黑”它背后其实是一类很典型的需求——业务禁令黑名单。说得直接一点就是平台或运维侧需要以管理员身份对某个业务实体一个 Pod、一套服务、一个账号、甚至一条业务线快速施加“不可用”限制并且这个限制要可精准控制、可快速撤销、可审计。适用于平台工程师、SRE、运维同学以及所有需要管 K8S 生产集群的同学。这篇文章我就把我在实际环境里用过的几条技术路线完整梳理一遍从原理到配置到坑一次讲透。1. 先搞清楚你要禁的到底是什么很多人一提“黑名单”就急着找工具其实第一步是把需求拆清楚。K8S 里的“业务禁令”不是一个具体的 API 对象它是一类需求的集合。你到底是希望它网络不可达还是不允许被操作还是新版本不允许被创建三件事的技术手段完全不同选错了方案轻则无效重则误伤整个集群。1.1 “优雅”的三个层次我理解的优雅不是说代码写得漂亮而是操作本身要满足这几个条件粒度可控能精确到某个 namespace、某个 Deployment、某个用户而不是一刀切。快速生效、快速撤销上策是改一条配置就生效而不是重启服务、重建 Pod。撤销的时候也是一样不能为了“解封”搞得比“封禁”还麻烦。有迹可循谁禁的、禁的什么、为什么禁这些东西要能追溯。生产环境最怕“神操作”禁完没人记得三个月后查故障发现原来是自己人干的。如果你想要的是这三件事都满足那很多“粗暴方案”直接kubectl delete、直接改副本数为 0其实已经出局了。它们不是不能用而是不可逆、破坏性大、事后无法复盘。1.2 我碰到过的四类典型场景紧急止血某个 Pod 被入侵或者代码出 bug 导致疯狂外呼。这时候需要在秒级把它的网络断掉保留现场供排查。合规整改某条业务线因为监管或合规要求需要立刻暂停对外服务但代码层面来不及发版。多租户隔离某个租户超量占用资源或者被判定为风险账号要限制它对共享集群的访问范围。下线遗留治理某个老版本服务已经下线但依赖方没改配置流量还在往里打。需要“假装它还在但谁都连不上”。每一个场景对应的“拉黑对象”都不一样。紧急止血和遗留治理禁的是流量合规整改禁的可能是入口多租户隔离禁的是人和权限。需求没理清之前不要碰任何配置。2. 五条技术路线的核心原理与选型对比这一章是全文的重点。我挑了五条我在实际环境里验证过的路线每一条都从原理讲起再说它的边界在哪里。2.1 NetworkPolicy网络层的“物理断连”NetworkPolicy 是 K8S 原生的网络策略 API但它不是默认就生效的取决于你的 CNI 插件。Calico、Cilium 支持得很好Flannel 默认不支持后面我会专门讲这个坑。它的工作方式是通过标签选择器把 Pod 分组然后显式声明这个组允许谁访问ingress允许访问谁egress。底层实现是 CNI 下发 iptables 规则或者 eBPF 规则到节点上。这个方案对我的价值在于它是最接近“物理断网”的操作。一条规则下去对应 Pod 的所有连接在数据面被切断应用进程还在日志还能看现场完整保留。而且它是集群范围内的不需要改业务代码也不需要动 Service。缺点是它管不了七层语义URL、Header、Cookie也管不了“人”只针对 Pod 的网络身份。2.2 RBAC权限层的“账号封禁”K8S 的 RBAC 控制的是“谁能对什么资源做什么操作”。很多新人以为 RBAC 可以做黑名单其实这里有个严重的认知误区K8S RBAC 只有一个白名单模型它没有原生的 deny 规则。旧版本里曾经有过--deny之类的标志后来也去掉了。所以在 RBAC 层面做“业务禁令”实际操作不是“加一条黑名单”而是回收授权——把原来绑定的 Role 或 ClusterRole 解绑或者缩小其权限范围。配合审计日志可以确认这个账号在解绑后做了什么、没做什么。这个方案适合禁“人”不适合禁“流量”。它能防止某个开发账号、CI 账号再去操作某个 namespace但挡不住别人继续访问你的服务。2.3 Ingress/Gateway流量层的“拒绝服务”这是我在禁外部访问时最先考虑的方案。如果你用的是 Nginx Ingress可以给 Ingress 对象加一段 server 级别的配置直接对指定域名、路径或来源 IP 返回 403。用 Istio、APISIX、Kong 的话也有对应的黑名单插件或 VirtualService 规则。它的特点是侵入极小不改 Service不改 Pod只改流量入口的规则。缺点也很明显只覆盖南北向流量。如果两个服务都在集群内部通过 Service 互相调用Ingress 的黑名单根本管不到——那个流量根本不过 Ingress。2.4 准入控制器源头的“行政审批”K8S 的 Admission Webhook 能在资源创建、更新的时候拦截请求。OPA Gatekeeper 就是基于这个机制的一套策略引擎用 Rego 写策略能实现“带有某个标签的 Pod 不允许创建”这种黑名单逻辑。这个方案和前面几个不一样的地方在于它是事前拦截不是事后封锁。适合的场景是规范治理比如你已经决定某个 label 代表“被禁令业务”那任何新建的资源都不能再带上这个 label。或者是给高危配置设卡比如禁止latest镜像、禁止hostPID、禁止挂载宿主机目录。缺点是对已存在的资源无效只能靠审计模式发现违规项然后人工或定时任务清理。而且每个请求都过策略引擎策略多了 API Server 的延迟会明显上升。2.5 自定义 CRD Operator业务层的“裁判”如果你是在做 PaaS 平台或者有自己的应用编排系统业务状态本身已经抽象成 CRD 了那最优雅的黑名单可能就是自定义一个 CR。举个例子定义一个Blacklist资源里面有targetNamespace和reason字段控制器 watch 到它之后把目标应用的副本数缩到 0或者给目标 Pod 打上network-policy.blacklisttrue的标签同时触发 NetworkPolicy 生效。这个方案业务语义最清晰——你可以在平台上说“这个应用被冻结了”——但开发和运维成本最高。我是建议先别急着上 Operator除非你已经有了明确的 CRD 体系否则为了一个黑名单需求去维护一套控制器收益不划算。2.6 选型决策表方案作用层级生效速度运维成本最适用场景不建议用的场景NetworkPolicy网络层L3/L4秒级低紧急断网、内部服务隔离需要对 URL 做精细管控RBAC 权限回收控制面 API分钟级低封禁人和服务账号阻断业务流量Ingress / Gateway 黑名单网络层L7秒级低禁外部访问、限流集群内部东西向流量OPA Gatekeeper资源创建/更新秒级但只对新请求中合规治理、源头拦截处理已存在的存量资源自定义 CRD Operator业务语义层取决于实现高自研 PaaS 平台临时紧急止血这张表如果你只能记一句话那就记这条禁流量用 NetworkPolicy禁人用 RBAC禁外网入口用 Ingress禁未来用 Gatekeeper禁业务语义用 CRD。3. 四套可抄作业的拉黑配置理论说完了直接上实操。我给的每一套都是我在生产环境用过的你可以按需取用。3.1 NetworkPolicy30 秒把某个服务全局限网先看场景有个应用在prod-biz这个 namespace 下所有 Pod 都带appdata-worker标签。现在要把它彻底断网保留现场。apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: isolate-data-worker namespace: prod-biz spec: podSelector: matchLabels: app:>kubectl exec -it -n prod-biz deploy/data-worker -- curl -v http://another-service正常会直接卡住或超时。这时候数据面已经掐断了但 Pod 还在 Running日志照常输出供排查用。等你排查完撤销的时候直接把这条 NetworkPolicy 删掉即可kubectl delete networkpolicy isolate-data-worker -n prod-biz删除后网络马上恢复不需要重启任何 Pod。这就是“优雅”的最直观体现。3.2 RBAC 黑名单回收授权 审计留痕前面说过RBAC 没有原生 deny。那怎么落地“封号”呢我常用的做法分两步。第一步找到这个账号绑定了什么权限kubectl get rolebinding -n prod-biz -o jsonpath{range .items[*]}{.metadata.name}{\t}{.subjects[*].name}{\n}{end} | grep banned-user第二步删除对应的绑定kubectl delete rolebinding dev-access -n prod-biz这里有个容易被忽略的细节如果账号是通过ClusterRoleBinding绑定的而你只删了 namespaced 的 RoleBinding它依然有权限因为 ClusterRoleBinding 的权限是全集群范围的。所以一定要先搞清楚绑定链kubectl get clusterrolebinding -o jsonpath{range .items[*]}{.metadata.name}{\t}{.subjects[*].name}{\n}{end} | grep banned-user删完之后还要确认它没有通过“多个 RoleBinding 叠加”来控制权限。如果只删一个别的绑定还在等于白干。还有一个坑这个账号如果用的是长时间有效的 token比如某些 CI 里直接写死的 ServiceAccount token删了绑定之后已发放的 token 并不会立即失效因为 K8S 的鉴权是每次请求实时计算的但 token 本身的校验通过后才进入 RBAC 判断。大部分场景下删掉绑定后旧 token 在缓存期内仍可能短暂通过。所以实际操作中如果条件允许把这个 ServiceAccount 也删掉或者用kubectl delete secret把关联的 token 清掉再配合审计日志确认。审计方面开启 APIServer 的 audit 日志是最可靠的# apiserver 启动参数里加上 --audit-log-path/var/log/kubernetes/audit.log --audit-policy-file/etc/kubernetes/audit-policy.yaml针对这个封禁需求审计策略可以只记录delete和create操作避免日志量爆炸。3.3 Nginx Ingress 黑名单让指定域名直接 403如果要做的是禁止外部访问某个已上线服务又不想动 Deployment那 Ingress annotation 是最快的路径。注意这个方案只适用于 NGINX Ingress Controller使用其他 IngressClass如 Traefik、AWS LB Controller时注解不生效。给目标 Ingress 加一段 server-snippetapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: biz-api namespace: prod-biz annotations: nginx.ingress.kubernetes.io/server-snippet: | if ($host banned.biz.example.com) { return 403; } if ($uri ~* ^/internal/) { return 403; } spec: rules: - host: banned.biz.example.com http: paths: - path: / pathType: Prefix backend: service: name: biz-api port: number: 80注意几个要点server-snippet里的指令会被追加进 NGINX 的 server 块所以可以用 NGINX 的原生指令但不能乱加语法不支持的指令否则 nginx reload 会失败整个 Ingress 的流量全部报 502。示例里第二个条件用$uri匹配路径适合“只禁某个内部接口其它接口正常服务”的场景。如果你用的是 Nginx Ingress 1.x某些版本默认开了allow-snippet-annotationsfalse安全收紧这时候 snippet 注解会被忽略需要确认 controller 的配置项。我自己实测下来这套配置对现有连接是“秒断”新连接直接 403HTTP 层面非常干净。如果还需要更精细的场景按 IP、按 HeaderNginx Ingress 也支持nginx.ingress.kubernetes.io/whitelist-source-range和自定义的canary规则不过那更多是限流和灰度场景不是黑名单的主战场。3.4 Gatekeeper从源头拒绝带“毒”配置的资源这个方案的适用场景和前面几个完全不同——它是防止“黑名单对象”再次出现。举个例子我们规定任何带有blacklisttrue标签的 Pod 都不允许被创建。谁手动加这个标签都不行必须走审批流程。先装 Gatekeeperkubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml然后定义一个 ConstraintTemplateapiVersion: templates.gatekeeper.sh/v1 kind: ConstraintTemplate metadata: name: k8sbanblacklistedlabels spec: crd: spec: names: kind: K8sBanBlacklistedLabels validation: openAPIV3Schema: type: object properties: forbiddenLabels: type: array items: type: object properties: key: type: string value: type: string targets: - target: admission.k8s.gatekeeper.sh rego: | package k8sbanblacklistedlabels violation[{msg: msg}] { input.review.kind.kind in [Pod, Deployment] label : input.review.object.metadata.labels[key] forbidden : input.parameters.forbiddenLabels[_] key forbidden.key label forbidden.value msg : sprintf(资源包含了被禁止的标签 %v%v已被业务禁令拦截, [forbidden.key, forbidden.value]) }这条 Rego 的逻辑拆开讲遍历所有待创建资源的 labels如果某个标签的 key 和 value 同时命中forbiddenLabels参数列表就生成一条 violationAPI Server 会拒绝这个创建请求。然后再定义一个 Constraint 实例apiVersion: constraints.gatekeeper.sh/v1beta1 kind: K8sBanBlacklistedLabels metadata: name: block-blacklisted-labels spec: enforcementAction: deny match: kinds: - apiGroups: [apps, ] kinds: [Deployment, Pod] parameters: forbiddenLabels: - key: blacklist value: true - key: banned value: yes创建带blacklisttrue标签的 Deployment 时会得到明确的拒绝信息类似Error from server (Forbidden): admission webhook validation.gatekeeper.sh denied the request: 资源包含了被禁止的标签 blacklisttrue已被业务禁令拦截我在生产中比较建议的模式是先审计后强制。Gatekeeper 支持enforcementAction: dryrun你先让策略跑一段时间通过kubectl get constraints block-blacklisted-labels -o yaml看它报告了多少违规确认无误后再切成deny。这种方式在合规整改场景里特别好用领导要数据的时候你能直接甩出一张违规清单。4. 实战中的坑与排查速查表这里每一条都是我或者我身边的人真金白银踩出来的。我尽量写成速查的形式遇到问题直接对号入座。4.1 NetworkPolicy 生效不了先别改配置先确认你的 CNI 支不支持。Flannel 在默认配置下不处理 NetworkPolicy你需要看自己的节点上跑的是什么 DaemonSet。kubectl get ds -n kube-system | grep -E calico|cilium|flannel|canal如果是calico-node或cilium基本没问题。如果是flanneld那 NetworkPolicy 配了也是白配。还有一个高频坑egress 拒绝全部之后Pod 连 DNS 都不通了。如果你只想限制 Pod 访问某个特定 IP 段但不想影响它解析域名那 egress 规则里必须显式放行 DNS。比如spec: policyTypes: - Egress egress: - to: - namespaceSelector: {} podSelector: matchLabels: k8s-app: kube-dns ports: - protocol: UDP port: 53否则你会看到业务日志一直在报lookup xxx on ...: no such host但业务本身可能只是想访问一个地址而已。这是我见过最多的误伤案例。4.2 RBAC 封了为什么还能操作如果你删了 RoleBinding对方还能操作按照这三个顺序排查是不是还有别的绑定同一个用户可以被多个 RoleBinding / ClusterRoleBinding 引用删一个不代表权限归零。是不是匹配到了别的 subjectGroup 里的某个成员即使个人没有绑定也可能通过 Group 拿到权限。删掉个人绑定不够要看它所在的组。是不是鉴权缓存APIServer 对鉴权结果有缓存默认 TTL 是 5 分钟。如果你在极短时间窗口内测试有概率碰到旧结果。等几分钟再测或者直接看kubectl auth can-i list deploy -n prod-biz --as banned-user来验证当前真实权限。4.3 Ingress 黑名单生效后业务报 502 和 400如果加完 server-snippet 之后所有流量都变成了 502优先怀疑是 nginx 配置 reload 失败。查一下 controller 的日志kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail50如果显示[emerg]开头的报错比如指令拼写错误、变量不存在那就是 snippet 的问题。我建议先在测试集群里改确认 reload 正常后再上生产。而变成 400 的情况通常是 NGINX 在 L7 解析层面就把请求拒了比如 host 或 uri 里的语法触发了return 400。黑名单的目的是返回 403 表示“禁止”返回 400 表示“请求格式错误”二者语义完全不同排查的时候别绕远了。4.4 Gatekeeper 把集群锁死了怎么办这是所有准入控制器都存在的风险如果策略写得太激进可能阻断所有 Deployment 的创建甚至影响集群自身的组件更新。我的经验是两条永远先开 dryrun。任何新策略至少以 audit 模式跑一周看违规列表和你想的一致了再切 deny。给 Gatekeeper 自身留后门。在 Constraint 的match里排除gatekeeper-system这个命名空间并且把 K8S 系统组件kube-system也排除掉防止策略把集群系统组件给拦了。如果你真的不小心把所有请求都拦了也别慌临时卸掉 Gatekeeper 的 ValidatingWebhookConfiguration 可以让请求先放行。kubectl delete validatingwebhookconfiguration gatekeeper-validating-webhook-configuration应急恢复之后再去慢慢修正策略。4.5 最后的最后我给你三个经验第一个经验黑名单永远不是为了替代治理它是止血用的。所以每次施禁令后一定要在事后建一条根因分析的 TODO确认这个黑名单什么时候可以撤。第二个经验把禁令纳入 GitOps 流程。我个人的习惯是所有的 NetworkPolicy、Gatekeeper Constraint、Ingress annotation 都放到 Git 仓库里。紧急时可以先手敲命令止血但事后必须补提交。这样组里任何人都能查到这个“禁令”是谁在什么时间加的、为什么加。第三个经验仔细选择“标签”而不是“资源名”。NetworkPolicy 的选器用的是标签如果业务 Pod 的标签写得不规范比如不同业务共用app: worker你的禁令很容易误伤到其他服务。所以规范标签体系的前提比学会配置本身更重要。我踩过最惨的一次坑就是把两个环境共用的标签当成了唯一标识一条 NetworkPolicy 下去隔壁环境的服务也断了。在生产环境里业务禁令是一个高频但容易被低估的需求。它不像扩容、滚动更新那样有标准答案而是需要你根据“禁什么、多快生效、能不能撤、要不要留痕”这四个维度临时决策。把这套选型逻辑装在脑子里下次半夜被叫起来的时候你就能在五分钟内做出正确判断了。
返回列表