7.8 网络模型 (CNI / NetworkPolicy)
一句话
K8s 的网络模型只有三条要求——每个 Pod 一个 IP、Pod 之间不用 NAT 就能直通、节点也能直接访问 Pod IP;CNI 决定 “怎么实现这三条”,NetworkPolicy 决定 “谁能连谁”,Service 只是在这之上加的一层虚拟 IP。
三条要求,三套实现
| 要求 | 谁实现 | 实现方式 |
|---|---|---|
| 每个 Pod 一个独立 IP | CNI 插件 + IPAM | 从节点 PodCIDR 里分配,或直接用 VPC 子网(云 CNI) |
| 跨节点直通、不 NAT | 路由或隧道 | VXLAN/IPIP 封装,或 BGP 宣告 Pod 网段(underlay) |
| 节点与 Pod 互通 | CNI + 路由 | 节点上装路由/iptables 规则,或云路由表 |
CNI 是一个规范:容器运行时调用 ADD/DEL/CHECK,插件返回网卡与 IP。所以 “换网络方案” 通常只是换一个 CNI 二进制 + 一套 IPAM,Pod 清单不用改。
三条路径,各自的 NAT 与坑
| 路径 | 实际转发 | 常见坑 |
|---|---|---|
| Pod → Pod(同节点) | veth → 网桥(或 eBPF)→ veth | 网桥被 net.bridge.bridge-nf-call-iptables 影响,策略失效 |
| Pod → Pod(跨节点) | 封装(VXLAN/IPIP)或 BGP 路由 | MTU 未减封装开销 → 大包不通(见 6.7 容器存储与网络) |
| Pod → Service | DNAT 到某个 Endpoint + conntrack 记录 | conntrack 表满、会话粘滞、Endpoints 摘除竞态 |
| 外部 → Service | NodePort / LB → SNAT → Pod | 源 IP 丢失(除非 externalTrafficPolicy: Local) |
conntrack 是最容易被忽略的容量项:每个连接占一条表项,nf_conntrack_max 打满会丢包且日志稀少。高连接数场景(网关、长连接)要单独调大并监控。
NetworkPolicy:默认全通,且需要 CNI 支持
| 事实 | 含义 |
|---|---|
| 默认全通 | 不写任何 NetworkPolicy 时,所有 Pod 互相可达 |
| 策略是白名单 | 一旦某个 Pod 被某条策略的 podSelector 选中,它的对应方向就变成 “只允许策略里写的” |
| 需要 CNI 支持 | Flannel 默认不支持(会静默忽略),Calico / Cilium 支持 |
| 分方向 | 只写 Ingress 时,出站仍然全通;出站管控要单独写 Egress |
“默认拒绝 + 按需放行” 的标准模板(注意先放行 DNS,否则域名解析全挂):
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: default-deny}
spec:
podSelector: {} # 本命名空间所有 Pod
policyTypes: [Ingress, Egress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: allow-dns-and-web}
spec:
podSelector: {matchLabels: {app: api}}
policyTypes: [Egress]
egress:
- to: # 先放行 DNS(kube-system 的 CoreDNS)
- namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: kube-system}}
ports: [{protocol: UDP, port: 53}, {protocol: TCP, port: 53}]
- to:
- podSelector: {matchLabels: {app: db}}
ports: [{protocol: TCP, port: 5432}]
验证方式是实测,不是看 YAML:从源 Pod 里 nc -zv <目标IP> <端口>,通了就是没生效(或策略写错),不通才算隔离成立。
DNS:集群里最常被忽略的延迟源
| 现象 | 原因 | 修法 |
|---|---|---|
| 首次调用外部域名慢 100–300ms | /etc/resolv.conf 的 ndots:5:外部域名先试 5 个搜索域 | 设 dnsConfig.options: [{name: ndots, value: "2"}] |
| 解析随机失败 | CoreDNS 副本不足或被打满(限流/连接数) | 加副本、开 NodeLocal DNSCache |
| 跨命名空间解析失败 | 用了短名(db),只在本命名空间生效 | 用 FQDN:db.ns.svc.cluster.local |
| Egress 策略后解析全挂 | 没放行到 CoreDNS 的 53 端口 | 见上例的第一条 egress 规则 |
反模式
| 反模式 | 后果 |
|---|---|
| 以为配了 NetworkPolicy 就隔离了 | CNI 不支持时策略被静默忽略,集群实际全通 |
| 只写 Ingress 不写 Egress | 出站全通,横向移动不受限 |
| “默认拒绝” 后忘了放行 DNS | 所有域名解析失败,表现为 “服务全挂” |
| 用 ICMP/ping 验证 Service 连通 | Service IP 是虚拟地址,ICMP 通常被丢,误判故障 |
| 忽略 conntrack 上限 | 高并发下随机丢包,日志里几乎没有线索 |
| 跨节点 MTU 未调整 | 小包通、大包不通(传文件/上传必现) |
| 用 Pod IP 做服务发现 | Pod 重建后 IP 变化,调用方全挂 |
三条铁律
🔑 默认全通:不做策略就是全开;做了策略要实测,不能只看 YAML。
🔑 策略先放行 DNS 与探针,再收紧业务流量;顺序反了会把自己锁在门外。
🔑 Service 只是虚拟 IP:连通性排查看 Endpoint 与 conntrack,不要用 ping 判断。
自检
Q1 从 Pod A curl Pod B 的 IP 超时,但 curl B 的 Service 正常,为什么?
▸ 答案:说明Pod IP 直连被策略或路由挡住,而 Service 路径(DNAT → Endpoint)是通的。常见原因:① NetworkPolicy 只放行了 Service 网段/端口,没放行 Pod 网段;② 跨节点路由/BGP 未宣告该 PodCIDR;③ 目标 Pod 的容器端口只监听在 localhost。按 “策略 → 路由 → 监听地址” 三步查。
Q2 配了 “默认拒绝所有入站” 之后,服务全部 502。最先查什么?
▸ 答案:先查探针与 DNS。默认拒绝会连 kubelet 的健康检查(来自节点 IP)一起拒掉,导致 Pod 一直 NotReady、Endpoint 为空 → 502;同时如果没放行 DNS(Egress 方向),所有域名解析失败。这两条是 “默认拒绝” 的第一批受害者,模板里必须显式放行。
Q3 为什么 ping <ClusterIP> 不通,但服务是好的?
▸ 答案:因为 ClusterIP 是 kube-proxy/eBPF 在数据面实现的虚拟地址,只对 TCP/UDP 做 DNAT;ICMP 通常不被处理,所以 ping 无响应。正确验证方式是 nc -zv <ClusterIP> <port> 或 curl,再看 endpointslices 是否有地址。
Q4 一个外部域名解析要 200ms,怎么把它降到个位数?
▸ 答案:根因通常是 ndots:5:api.example.com 只有 2 个点,会先被拼成 api.example.com.<ns>.svc.cluster.local 等 5 个候选,全部失败后才走真实域名,每次多 5 次查询。把 dnsConfig.options 的 ndots 调到 2,并在热点路径用 NodeLocal DNSCache,通常能把首次解析压到 10ms 内。
Q5 两个团队都说 “网络不通”,你怎么在 10 分钟内定位到层?
▸ 答案:按四层问:① DNS:nslookup/getent hosts 能否解析;② Service:endpointslices 有没有地址;③ 策略:kubectl get networkpolicy -A 有没有选中这两端;④ 数据面:同节点直连 Pod IP 是否通(排除封装/MTU/路由)。四步定位把 “网络不通” 拆成具体一层,避免在 “是不是 CNI 坏了” 上空转。
跨篇相关:Pod 网络与 CNI 插件的对比见 6.7 容器存储与网络;Service 的四种类型与转发模式见 7.3 Service 与 Ingress;四层与七层的分工见 8.9 四层与七层负载均衡;TCP 重传、MTU 与握手见 8.1 TCP/IP 与三次握手;VPC 与安全组的边界见 8.7 VPC、子网、安全组;服务网格下的东西向流量见 30.5 Service Mesh 与 Sidecar。