Kubernetes 学习笔记(十八):NetworkPolicy 如何限制 Pod 之间的访问
Han Lv5

前言

前面写 Service 时,我把它理解成“给一组 Pod 提供稳定入口”。默认情况下,很多集群里的 Pod 之间是可以互相访问的。

这在学习时很方便,但在真实环境里会带来一个问题:

1
2
3
4
frontend 能访问 demo-api
demo-api 能访问 mysql
那 frontend 能不能直接访问 mysql?
其他 namespace 的 Pod 能不能访问 mysql?

如果完全不限制,网络边界就会很松。

NetworkPolicy 要解决的就是 Pod 之间的网络访问控制。

我目前把它理解成:

1
2
3
4
5
Service
解决“怎么找到一组 Pod”

NetworkPolicy
解决“谁可以访问这组 Pod,访问哪些端口”

它像在房间门口贴访问规则:不是知道门牌号就一定能进去,还要看你是不是被允许。

一个重要前提:网络插件要支持

NetworkPolicy 是 Kubernetes API 资源,但真正执行规则的是网络插件。

1
2
3
4
NetworkPolicy 对象
|
v
CNI 网络插件读取并执行

如果集群使用的网络插件不支持 NetworkPolicy,那么创建了 NetworkPolicy 对象也可能不会产生实际限制。

这一点很重要。排查时不能只看:

1
kubectl get networkpolicy

还要确认集群网络方案是否支持它,比如 Calico、Cilium 等通常支持 NetworkPolicy。

所以 NetworkPolicy 不是“写了 YAML 就一定生效”,它依赖底层网络实现。

默认不是隔离的

NetworkPolicy 有一个容易误解的地方:没有 NetworkPolicy 时,Pod 通常不是默认隔离。

可以先这样理解:

1
2
3
4
5
没有 NetworkPolicy
-> 默认允许流量

某个 Pod 被 NetworkPolicy 选中
-> 开始按策略限制对应方向流量

也就是说,策略是加上去的边界。

如果想让某个 Namespace 默认拒绝所有入站流量,可以创建一个选择所有 Pod、但不允许任何 ingress 的策略。

1
2
3
4
5
6
7
8
9
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: demo-prod
spec:
podSelector: {}
policyTypes:
- Ingress

这里的 podSelector: {} 表示选中当前 Namespace 下所有 Pod。

1
2
3
4
demo-prod 所有 Pod
|
v
默认拒绝入站

Ingress 和 Egress

NetworkPolicy 里的 ingress 和 egress 不是 Kubernetes Ingress 资源。

这里的含义是:

1
2
3
4
5
ingress
进入 Pod 的流量

egress
从 Pod 发出的流量

示意图:

1
2
3
4
5
6
7
8
9
Client Pod
|
| ingress 到 demo-api
v
demo-api Pod
|
| egress 到 mysql
v
mysql Pod

所以不要把这两个概念混在一起:

1
2
3
4
5
Ingress 资源
HTTP/HTTPS 外部入口路由

NetworkPolicy ingress
网络策略里的入站方向

名字相同,语境不同。

允许 frontend 访问 demo-api

假设有两个应用:

1
2
3
4
5
frontend Pod labels:
app=frontend

demo-api Pod labels:
app=demo-api

希望只允许 frontend 访问 demo-api 的 8080 端口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-demo-api
namespace: demo-prod
spec:
podSelector:
matchLabels:
app: demo-api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080

拆开看:

1
2
3
4
5
6
7
8
podSelector
这条策略保护哪些 Pod:app=demo-api

ingress.from
允许谁访问:app=frontend

ports
允许访问哪个端口:TCP 8080

图示:

1
2
3
4
5
6
7
8
9
10
11
frontend Pod
|
| TCP 8080 allowed
v
demo-api Pod

other Pod
|
| denied
v
demo-api Pod

namespaceSelector

有时访问来源不是同一个 Namespace 的 Pod,而是某个 Namespace 里的 Pod。

例如只允许带有 team=backend 标签的 Namespace 访问:

1
2
3
4
5
ingress:
- from:
- namespaceSelector:
matchLabels:
team: backend

这会匹配 Namespace 标签,而不是 Pod 标签。

1
2
3
4
namespace labels: team=backend
|
v
这个 namespace 里的 Pod 可以作为来源

如果想同时限制 Namespace 和 Pod,可以组合:

1
2
3
4
5
6
7
8
ingress:
- from:
- namespaceSelector:
matchLabels:
team: backend
podSelector:
matchLabels:
app: frontend

可以理解成:

1
2
3
来源 Pod 必须同时满足:
所在 Namespace 是 team=backend
Pod 自身是 app=frontend

这里缩进和结构很重要。写错后,策略含义可能完全不同。

ipBlock

NetworkPolicy 也可以按 IP 段允许来源或去向。

1
2
3
4
ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/24

还可以排除某些 IP:

1
2
3
4
5
6
ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/24
except:
- 10.0.0.10/32

不过在 Kubernetes 内部 Pod 通信里,我更倾向用 Label 和 Namespace 表达关系,而不是依赖 Pod IP。Pod IP 会变,标签关系更稳定。

ipBlock 更适合表达外部网段或明确的地址范围。

Egress:限制 Pod 能访问哪里

默认情况下,很多集群的 Pod 可以向外发起连接。可以用 egress 限制。

例如只允许 demo-api 访问 MySQL 的 3306 端口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: demo-api-egress
namespace: demo-prod
spec:
podSelector:
matchLabels:
app: demo-api
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: mysql
ports:
- protocol: TCP
port: 3306

这条策略表达:

1
2
被选中的 demo-api Pod
只能向 app=mysql 的 Pod 发起 TCP 3306 访问

如果还需要访问 DNS,通常要额外放行 DNS 服务。否则应用可能连服务名都解析不了。

1
2
3
4
5
6
7
Egress 全限制
|
v
DNS 没放行
|
v
域名解析失败

这类问题挺常见,所以写 egress 策略时要特别注意 DNS。

策略是叠加的

NetworkPolicy 不是按顺序匹配第一条,也不是后面的覆盖前面的。

对于被选中的 Pod,允许规则是叠加的。

1
2
3
4
5
Policy A 允许 frontend
Policy B 允许 monitor
|
v
最终 frontend 和 monitor 都可以访问

可以理解成“允许列表合并”。

如果一个 Pod 被多条策略选中,它的允许流量是这些策略允许流量的并集。

这意味着排查时不能只看一条 NetworkPolicy,要看所有选中了这个 Pod 的策略。

1
2
kubectl get networkpolicy -n demo-prod
kubectl describe networkpolicy <name> -n demo-prod

NetworkPolicy 和 Service 的关系

Service 负责找到后端,NetworkPolicy 负责限制流量是否允许。

1
2
3
4
5
6
7
8
9
10
Client
|
v
Service: demo-api
|
v
NetworkPolicy 检查是否允许
|
v
Pod

如果 Service 正常、EndpointSlice 也有后端,但访问仍然失败,就需要考虑 NetworkPolicy。

1
2
3
4
5
6
7
8
9
Service selector 正确
EndpointSlice 有地址
Pod Ready
|
v
仍然不通
|
v
检查 NetworkPolicy

不过我不会一开始就怀疑 NetworkPolicy,还是先看 Service、EndpointSlice、Pod Ready。这些确认没问题后,再看策略更有层次。

一个更完整的边界示例

目标:

1
2
3
frontend 可以访问 demo-api:8080
demo-api 可以访问 mysql:3306
其他流量默认拒绝

可以拆成:

1
2
3
4
5
1. 默认拒绝 ingress
2. 默认拒绝 egress
3. 允许 frontend -> demo-api
4. 允许 demo-api -> mysql
5. 允许必要 DNS

这比写一条巨大策略更容易理解和维护。

我会尽量让每条 NetworkPolicy 只表达一个清晰意图:

1
2
3
4
5
allow-frontend-to-demo-api
allow-demo-api-to-mysql
allow-dns
default-deny-ingress
default-deny-egress

命名清楚后,排查时也能一眼知道每条策略在做什么。

小结

这一篇整理下来,我对 NetworkPolicy 的理解可以概括成几句话:

  • NetworkPolicy 用来限制 Pod 的入站和出站网络流量;
  • 它依赖网络插件支持,不是所有集群写了就生效;
  • 没有策略时,Pod 通常不是默认隔离;
  • podSelector 选择被策略保护的 Pod;
  • ingress 控制进入 Pod 的流量;
  • egress 控制从 Pod 发出的流量;
  • 可以通过 podSelector、namespaceSelector、ipBlock 描述来源或去向;
  • 多条策略的允许规则会叠加;
  • 限制 egress 时要特别注意 DNS;
  • Service 负责寻址,NetworkPolicy 负责放行或拒绝。

下一篇准备整理 Deployment 之外的工作负载对象。到目前为止大多数例子都用 Deployment,但 Kubernetes 还有 DaemonSet、Job、CronJob、StatefulSet,分别适合不同类型的任务。

参考资料

 评论