饮墨

子安饮墨馀三斗,留与卿儿作赋来

3 步落地 Kubernetes NetworkPolicy,Pod 网络隔离从裸奔到零信任

痛点:K8s 集群内网络"裸奔"是常态

默认情况下,Kubernetes 集群内所有 Pod 可以互相访问——不分 Namespace、不分业务。这意味着一旦某个 Pod 被攻破(比如一个有 RCE 漏洞的应用),攻击者可以横向移动到数据库、缓存、内部 API 等关键服务。

真实场景:某电商平台线上环境,测试团队的 debug Pod 部署在同集群,因未做网络隔离,直接访问到了生产 Redis,误执行 FLUSHALL,导致缓存全量失效、数据库瞬间被打满。

K8s 原生的 NetworkPolicy 就是解决这个问题的,但实际用的团队不到 30%——原因很简单:文档抽象、配置容易写错、不知道怎么验证生效。今天用 3 步把它落地。

前提:确认 CNI 插件支持 NetworkPolicy

NetworkPolicy 是 K8s API 对象,但执行靠 CNI 插件。先确认你的 CNI 支持:

CNI 插件 支持 NetworkPolicy 备注
Calico ✅ 完整支持 生产首选,还支持 GlobalNetworkPolicy
Cilium ✅ 完整支持 基于 eBPF,性能更优
Weave Net ✅ 支持 社区活跃度下降
Flannel ❌ 不支持 需配合 Calico 使用(Canal 模式)

检查当前 CNI:

# 查看 CNI 配置目录
ls /etc/cni/net.d/

# 查看 CNI Pod
kubectl get pods -n kube-system | grep -E 'calico|cilium|weave|flannel'

如果是 Flannel,建议迁移到 Calico 或 Cilium,否则 NetworkPolicy 写了也不生效(这是最常见的坑之一)。

第 1 步:Default Deny — 先封死,再按需放行

零信任的核心思路:默认拒绝所有流量,再逐条开白名单

production Namespace 启用默认拒绝所有入站流量:

# default-deny-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: production
spec:
  podSelector: {}    # 匹配该 Namespace 下所有 Pod
  policyTypes:
    - Ingress        # 拒绝所有入站

应用并验证:

kubectl apply -f default-deny-ingress.yaml

# 验证:从其他 Namespace 尝试访问 production 下的服务
kubectl run test-curl --rm -it --image=curlimages/curl \
  -n testing -- curl -s --max-time 3 http://api-service.production.svc:8080
# 预期:超时,无法连接

同理,出站流量也建议默认拒绝:

# default-deny-egress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Egress

注意:Egress Deny 会阻断 DNS 解析(CoreDNS 在 kube-system),需要在下一步显式放行。

第 2 步:按需放行 — 精准开白名单

放行 DNS(必须优先做)

# allow-dns.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

放行前端到后端 API 的流量

假设前端 Pod 标签为 app: frontend,后端 API 标签为 app: api

# allow-frontend-to-api.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-api
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - protocol: TCP
          port: 8080

放行 API 到数据库

# allow-api-to-db.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-api-to-db
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Egress
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: postgres
      ports:
        - protocol: TCP
          port: 5432

这样形成了一条清晰的调用链:frontend → api:8080 → postgres:5432,其他路径全部拒绝。

第 3 步:验证与可视化 — 确认策略真正生效

策略写完不验证等于没写。推荐两种方式:

方式 1:手动验证(快速)

# 测试允许的路径(frontend → api)
kubectl exec -n production deploy/frontend -- \
  curl -s --max-time 3 http://api-service:8080/health
# 预期:200 OK

# 测试拒绝的路径(frontend → postgres)
kubectl exec -n production deploy/frontend -- \
  curl -s --max-time 3 http://postgres-service:5432
# 预期:超时/拒绝

# 测试跨 Namespace 隔离
kubectl exec -n testing deploy/debug-tools -- \
  curl -s --max-time 3 http://api-service.production.svc:8080
# 预期:超时/拒绝

方式 2:kubectl 查看策略覆盖

# 查看 Namespace 下所有 NetworkPolicy
kubectl get networkpolicy -n production

# 查看具体策略的详细规则
kubectl describe networkpolicy allow-frontend-to-api -n production

方式 3:Cilium 用户可用 Hubble 可视化

# 安装 Hubble CLI
hubble observe -n production --verdict DROPPED
# 实时查看被拒绝的流量,快速定位策略是否误杀

3 个常见坑

坑 1:Flannel 不支持 NetworkPolicy 写了 YAML、apply 成功、但流量照样通——因为 Flannel 不执行 NetworkPolicy。解决:换 Calico 或 Cilium。用 kubectl get pods -n kube-system 确认 CNI 类型。

坑 2:忘记放行 DNS Default Deny Egress 后 Pod 无法解析域名,所有 Service 调用全挂。表现为 curl: Could not resolve host。解决:永远在 Deny Egress 后第一时间加 DNS 白名单

坑 3:podSelector 为空和不写的区别 podSelector: {} 表示匹配当前 Namespace 下所有 Pod;而 podSelector 字段完全不写会报错。另外 namespaceSelector: {} 匹配所有 Namespace,千万别搞混——写错等于全局放行。

总结

步骤 动作 效果
1 Default Deny Ingress + Egress 封死所有流量
2 按需 Allow(DNS → 业务链路) 最小权限放行
3 手动验证 + 持续监控 确认策略生效

K8s NetworkPolicy 是集群内零信任网络的第一道防线,成本为零、效果立竿见影。落地建议:先在非生产环境验证,再逐个 Namespace 推到生产。如果需要更细粒度的控制(如 L7 HTTP 路径匹配),可以进阶到 Cilium 的 CiliumNetworkPolicy。

网络隔离不是可选项,是生产环境的底线配置。今天就开始给你的集群加上这道锁。

您还没有登录,请登录后发表评论。