返回首页

4.5 NetworkPolicy

本章目标

这一章要真正回答下面 4 个问题:

  • 为什么 Kubernetes 默认网络策略看起来偏开放::cite[240]
  • NetworkPolicy 的基本语义到底怎么理解::cite[240]
  • 入站与出站控制应该怎么写、怎么验证::cite[240]
  • 怎样用 NetworkPolicy 做微服务隔离,而不是只写一个“默认拒绝”::cite[240]

很多团队第一次接触 NetworkPolicy,会以为它像传统防火墙一样“默认拒绝,一条条放行”。但 Kubernetes 的默认行为并不是这样。官方文档写得非常明确:默认情况下,Pod 对 ingress 是非隔离的,所有入站都允许;对 egress 也是非隔离的,所有出站都允许。只有当某个 Pod 被至少一条带相应 policyTypes 的 NetworkPolicy 选中时,它才会在该方向进入“隔离”状态。::cite[240]

这就是为什么很多人会说:Kubernetes 默认网络策略偏开放。

1. 为什么默认偏开放

1.1 Kubernetes 优先保证“先连通,再治理”

Kubernetes 的设计首先要保证 Pod 能互相通信、Service 能工作、应用能跑起来。如果默认一启动就全部 deny,新手体验和系统可用性都会很差。于是 Kubernetes 把默认策略设成了“未被策略选中时不隔离”。::cite[240]

换句话说,Kubernetes 本身不会一上来就替你做零信任,它先给你一个可联通的基础网络,再由你通过 NetworkPolicy 把流量边界一点点收紧。::cite[240]

1.2 默认开放不等于不安全,而是“你要主动声明安全边界”

这点非常重要。Kubernetes 没有默认把所有东西拦掉,不代表它不支持隔离;而是把隔离定义成一种显式声明能力。只要某个 Pod 被策略选中,它在相应方向上就不再“全部放行”,而是只允许策略中明确写出来的流量。::cite[240]

所以真正的安全误区不是“Kubernetes 默认开放”,而是“以为装了 CNI 就自动有零信任”。没有 NetworkPolicy,很多集群里的 Pod 其实是天然互通的。::cite[240]

2. NetworkPolicy 的基本语义与匹配规则

2.1 先理解一个核心原则:策略是加法,不是减法

官方文档强调,NetworkPolicy 的规则是 allow-list 语义。也就是说,策略不是去写“拒绝什么”,而是写“允许什么”。一旦 Pod 在某个方向被隔离,那么只有匹配到任一允许规则的流量才可通过,回复流量会被隐式放行。::cite[240]

这意味着你写策略时应该问自己:

  • 哪些 Pod 是被保护对象?
  • 谁可以访问它?
  • 它可以访问谁?
  • 允许哪些端口?::cite[240]

2.2 podSelector:这条策略作用到谁

每条 NetworkPolicy 都有 podSelector。它不是“谁能访问我”,而是“这条策略应用到哪些 Pod 身上”。官方文档明确说明,空的 podSelector 表示当前命名空间内所有 Pod。::cite[240]

2.3 ingressegress:允许谁进、允许谁出

  • ingress:允许哪些来源进入被选中的 Pod。::cite[240]
  • egress:允许被选中的 Pod 对哪些目标发起连接。::cite[240]

如果被策略选中的 Pod 在某方向上处于隔离状态,那么该方向只允许命中这些规则的流量通过。::cite[240]

2.4 namespaceSelectorpodSelectoripBlock

官方文档给出了最常见的 3 类匹配器:

  • podSelector:匹配同命名空间中的 Pod。::cite[240]
  • namespaceSelector:匹配某些命名空间中的所有 Pod。::cite[240]
  • ipBlock:匹配某段 CIDR,可以配 except 排除子网。::cite[240]

一个 from / to 条目里同时写 namespaceSelectorpodSelector,表示“某些命名空间里的某些 Pod”,不是二选一。::cite[240]

2.5 ingress 与 egress 是独立隔离的

官方文档对这一点写得非常明确:Ingress 隔离与 Egress 隔离是分别声明、彼此独立的。一个 Pod 可以只隔离 ingress,也可以只隔离 egress,也可以两个方向都隔离。::cite[240]

这是初学者非常容易忽略的点。很多人写了 Ingress 策略后,误以为出站也被限制了,其实完全不是。::cite[240]

3. 实战前准备:一个可演示的微服务场景

下面我们设计一个很典型的三层微服务调用链:

  • frontend:前端服务,对外入口
  • backend:业务服务,只允许 frontend 访问
  • db:数据库服务,只允许 backend 访问

我们希望达到的隔离目标是:

  • frontend 不能直接访问 db;
  • backend 不能被其他无关 Pod 随便访问;
  • 所有 Pod 都仍然可以做 DNS 解析;
  • 整个命名空间默认收紧,只按最小权限放行。::cite[240]

4. 业务 YAML:先部署业务,再上策略

apiVersion: v1
kind: Namespace
metadata:
  name: unit4-policy
  labels:
    project: unit4-policy
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: unit4-policy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
        - name: frontend
          image: registry.k8s.io/e2e-test-images/agnhost:2.39
          args:
            - netexec
            - --http-port=8080
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: frontend
  namespace: unit4-policy
spec:
  selector:
    app: frontend
  ports:
    - port: 8080
      targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
  namespace: unit4-policy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
        - name: backend
          image: registry.k8s.io/e2e-test-images/agnhost:2.39
          args:
            - netexec
            - --http-port=8080
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: backend
  namespace: unit4-policy
spec:
  selector:
    app: backend
  ports:
    - port: 8080
      targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: db
  namespace: unit4-policy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: db
  template:
    metadata:
      labels:
        app: db
    spec:
      containers:
        - name: db
          image: registry.k8s.io/e2e-test-images/agnhost:2.39
          args:
            - netexec
            - --http-port=3306
          ports:
            - containerPort: 3306
---
apiVersion: v1
kind: Service
metadata:
  name: db
  namespace: unit4-policy
spec:
  selector:
    app: db
  ports:
    - port: 3306
      targetPort: 3306

4.1 先验证“默认全通”

kubectl apply -f app-demo.yaml
kubectl get pod -n unit4-policy -o wide

然后找出 frontend Pod 名称,验证它是否可以直接访问 backend 和 db:

kubectl exec -n unit4-policy deploy/frontend -- wget -qO- http://backend:8080/hostname
kubectl exec -n unit4-policy deploy/frontend -- wget -qO- http://db:3306/hostname

在还没有策略时,这两次请求大概率都能成功,因为默认 ingress/egress 都不隔离。::cite[240]

5. 第一步:做一个命名空间内默认拒绝

5.1 默认拒绝策略 YAML

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: unit4-policy
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

5.2 这条策略的含义

  • podSelector: {} 表示选中当前命名空间所有 Pod。::cite[240]
  • 同时声明 IngressEgress,表示两个方向都进入隔离状态。::cite[240]
  • 由于没有写任何允许规则,所以最终效果就是“全拒绝”。::cite[240]

5.3 验证默认拒绝

kubectl apply -f default-deny.yaml
kubectl exec -n unit4-policy deploy/frontend -- wget -T 3 -qO- http://backend:8080/hostname
kubectl exec -n unit4-policy deploy/frontend -- wget -T 3 -qO- http://db:3306/hostname

此时请求应该超时或失败。做到这一步,你已经把这个命名空间从“天然互通”切换成了“默认不通,按需开洞”。

6. 第二步:先恢复 DNS,否则很多服务名都解析不了

默认拒绝以后,经常第一件坏掉的事不是业务调用,而是 DNS。因为 Pod 的出站也被封掉了,无法访问 CoreDNS。

6.1 允许 DNS 出站策略

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

6.2 验证 DNS 恢复

kubectl apply -f allow-dns-egress.yaml
kubectl exec -n unit4-policy deploy/frontend -- nslookup backend

如果 nslookup 恢复正常,但 wget 仍然失败,说明你的 DNS 通了,而业务流量仍在被正确拦截。这是符合预期的。

7. 第三步:只允许 frontend 访问 backend

7.1 backend 入站放行策略

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

7.2 frontend 出站放行策略

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-egress-backend
  namespace: unit4-policy
spec:
  podSelector:
    matchLabels:
      app: frontend
  policyTypes:
    - Egress
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: backend
      ports:
        - protocol: TCP
          port: 8080
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

7.3 验证结果

kubectl apply -f allow-frontend-to-backend.yaml
kubectl apply -f frontend-egress-backend.yaml
kubectl exec -n unit4-policy deploy/frontend -- wget -qO- http://backend:8080/hostname
kubectl exec -n unit4-policy deploy/frontend -- wget -T 3 -qO- http://db:3306/hostname

预期结果应该是:

  • frontend -> backend 成功
  • frontend -> db 失败

这就是最典型的“最小权限”隔离效果。

8. 第四步:只允许 backend 访问 db

8.1 db 入站放行策略

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-backend-to-db
  namespace: unit4-policy
spec:
  podSelector:
    matchLabels:
      app: db
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: backend
      ports:
        - protocol: TCP
          port: 3306

8.2 backend 出站放行策略

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-egress-db
  namespace: unit4-policy
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
    - Egress
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: db
      ports:
        - protocol: TCP
          port: 3306
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

8.3 验证结果

kubectl apply -f allow-backend-to-db.yaml
kubectl apply -f backend-egress-db.yaml
kubectl exec -n unit4-policy deploy/backend -- wget -qO- http://db:3306/hostname
kubectl exec -n unit4-policy deploy/frontend -- wget -T 3 -qO- http://db:3306/hostname

预期结果:

  • backend -> db 成功
  • frontend -> db 仍然失败

这时你就得到了一个最小可用的三层微服务隔离模型。

9. 读懂这组策略:它体现了哪些规则语义

通过刚才的例子,你应该已经把官方文档里的几个抽象概念真正落地了:

  • podSelector 决定“保护对象是谁”。::cite[240]
  • from / to 决定“谁可以进、谁可以出”。::cite[240]
  • ports 决定“只能走哪些端口”。::cite[240]
  • ingress 与 egress 是独立声明的,所以双向限制时通常要成对写。::cite[240]
  • 多条策略最终是并集效果,只要命中任一允许规则即可放通。::cite[240]

这也是为什么 NetworkPolicy 设计很适合微服务场景:它不要求你写复杂的拒绝链,而是按应用边界去描述“允许关系”。::cite[240]

10. 微服务隔离的实战建议

10.1 不要只写 default deny 就结束

很多团队做到“默认拒绝”就以为完成了安全治理,结果业务全部不可用。真正有价值的做法是:

  1. 先定义默认拒绝;
  2. 立即恢复 DNS;
  3. 再按调用链逐跳开放。::cite[240]

10.2 以“调用关系”而不是“机器列表”来写策略

在 Kubernetes 里,Pod 会漂移、IP 会变化,所以策略应该基于 label 和 namespace 写,而不是基于某个临时 IP。ipBlock 更适合处理集群外部网段或一些固定来源。::cite[240]

10.3 先做命名空间边界,再做服务边界

一个很实用的落地顺序是:

  • 先给命名空间加默认拒绝;
  • 再放开核心系统依赖(如 DNS);
  • 然后按服务角色逐层放行。::cite[240]

这样你的网络策略会更可维护,也更接近真实微服务架构的权限边界。

10.4 别忘了前提:你的 CNI 必须支持 NetworkPolicy

官方文档特别提醒:只有当你使用的网络方案支持 NetworkPolicy 时,创建 NetworkPolicy 才会生效;如果底层没有对应实现,YAML 虽然能创建成功,但不会真正拦截流量。::cite[240]

这也是为什么前一章做 CNI 选型时,是否支持 NetworkPolicy 是非常关键的考量点。

11. 本章小结

这一章最重要的结论有 4 个:

  1. Kubernetes 默认网络策略偏开放,Ingress/Egress 默认都是不隔离。::cite[240]
  2. NetworkPolicy 是 allow-list 语义,重点是声明“允许谁”,不是“拒绝谁”。::cite[240]
  3. Ingress 与 Egress 是独立隔离的,做双向控制时通常要写成对策略。::cite[240]
  4. 微服务隔离的正确姿势是:默认拒绝 + 恢复基础依赖 + 按调用链逐步放行。::cite[240]

如果你把这一章的示例跑通,就已经跨过了“会写 NetworkPolicy YAML”和“真的理解网络隔离模型”之间最关键的一步。


📝 版权声明:本文为原创技术博客,转载请注明出处。

如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!

上一篇

5.4:CSI 插件机制

下一篇

2.3 Service 与服务发现:让应用稳定被访问