K8s 网络模型:从 Pod 网络到服务发现
概述#
Kubernetes 网络是不少工程师的知识盲区——平时不出问题就忽略,一出问题就不知道从哪下手。本章我们从最底层开始,一层一层搞清楚 K8s 的网络模型:
- Pod 网络:每个 Pod 为什么有独立 IP?veth pair 和 CNI 插件是怎么配合的?
- CNI 插件对比:Flannel、Calico、Cilium 各自适合什么场景?
- DNS 与服务发现:Pod 里为什么
nslookup my-svc能通?FQDN 的解析规则是什么? - NetworkPolicy:怎么用网络策略实现 Pod 间的流量隔离?
学完本章,你能回答这些问题:
- 两个 Pod 在同节点和跨节点是怎么通信的?
- 为什么 Flannel 不支持 NetworkPolicy?
my-svc.other-namespace.svc.cluster.local这个域名是怎么解析到 ClusterIP 的?- 怎么只允许特定标签的 Pod 访问数据库?
Pod 网络:每个 Pod 都有独立 IP#
Kubernetes 对网络有明确的规范(K8s 网络模型),任何 CNI 插件都必须满足:
- Pod-to-Pod:同节点或跨节点的 Pod 之间可以直接通信,不需要 NAT
- Pod-to-Node:Pod 可以访问所在节点,节点可以访问所有 Pod
- Pod-to-Service:Pod 可以通过 Service ClusterIP 访问服务
- 外部流量入口:外部流量可以通过 NodePort/LoadBalancer 进入集群
这里最关键的是第一条——Pod 间通信无 NAT。每个 Pod 都有独立的 IP,Pod 看到的源 IP 就是对端真实的 Pod IP,不经过任何地址转换。这和 Docker bridge 网络的 NAT 模式完全不同。
veth pair:Pod 网络的基石#
当 kubelet 创建 Pod 时,CNI 插件负责:
- 创建 veth pair(虚拟以太网对):一端放入 Pod 的 network namespace,另一端留在宿主机
- 给 Pod 端分配 IP(IPAM:IP Address Management)
- 配置路由,让 Pod 能访问集群内其他地址
Pod 的 network namespace 宿主机的 network namespace
┌─────────────────────┐ ┌────────────────────────────┐
│ eth0 (10.244.1.2) │◄────►│ vethXXXX (通常无 IP) │
│ (Pod 内看到网卡)│ │ (宿主机上看到网卡) │
└─────────────────────┘ └────────────────────────────┘宿主机侧的 veth 端通常不配 IP:网关地址(如
10.244.1.1)挂在网桥上(Flannel 的cni0),或干脆没有网关、由 CNI 用点对点路由 + proxy ARP 处理(Calico)。别把网关 IP 记成 veth 的 IP。
验证方式:
# 在宿主机上查看 veth pair
ip link show type veth
# 进入 Pod 查看网络接口
kubectl exec -it <pod-name> -- ip addr
# eth0@if15 <- if15 是宿主机侧的接口编号
# 在宿主机找对应接口
ip link show | grep "^15:"跨节点通信:Overlay vs Underlay#
Overlay(隧道模式):
VXLAN/IPIP 封装,在原始 IP 包外再包一层 UDP/IP 头,兼容性好但有封包开销。
原始包:[ Pod A IP | Pod B IP | 数据 ]
Overlay 封装后:[ 节点1 IP | 节点2 IP | VXLAN 头 | 原始包 ]Underlay(路由模式):
修改底层路由表,直接路由,性能更好但需要网络设备支持(BGP 或同一 L2 域)。
Pod A (节点1) → 查路由表 → 直接发往节点2的 Pod BCNI 插件对比:选对插件事半功倍#
CNI(Container Network Interface)是 K8s 的网络插件标准。不同插件的实现差异很大,选错了后期迁移成本很高。
主流 CNI 插件对比表#
| CNI 插件 | 网络模式 | NetworkPolicy | 性能 | 适用场景 |
|---|---|---|---|---|
| Flannel | Overlay (VXLAN) | ✗ 不支持 | 中等 | 小集群、简单场景、入门学习 |
| Calico | BGP 路由 / IPIP Overlay | ✓ 支持(iptables/eBPF) | 高 | 中大型集群、生产环境首选 |
| Cilium | eBPF 直接转发 | ✓ 支持(原生 L7) | 极高 | 大集群、需要可观测性、性能敏感 |
| Weave Net | Overlay (VXLAN) | ✓ 支持 | 中等 | 简单场景、快速上手 |
| AWS VPC CNI | 直接使用 VPC 网络 | ✓ 需额外组件 | 高(原生) | AWS EKS 环境 |
Flannel:简单但功能有限#
特点:
- 只解决 Pod 网络连通性,不支持 NetworkPolicy
- 默认使用 VXLAN Overlay,跨节点有封包开销
- 配置简单,几乎不需要运维
适用场景:小集群(< 50 节点)、测试环境、刚开始学 K8s
局限性:
- 不支持 NetworkPolicy(需要配合 Calico 的 NetworkPolicy 能力,即"Canal"方案)
- Overlay 模式性能不如 BGP 路由模式
- 没有网络可观测性能力
Calico:生产环境的主流选择#
特点:
- 支持 BGP 路由模式(推荐):节点之间交换路由,Pod IP 直接可路由,无封包开销
- 支持 NetworkPolicy(基于 iptables 或 eBPF)
- 成熟稳定,文档完善,社区活跃
BGP 模式验证:
# 查看 Calico BGP 状态
calicoctl node status
# 查看 BGP peer
calicoctl get bgpPeer
# 查看路由表(能看到其他节点的 Pod CIDR 路由)
ip route show | grep "via"
# 192.168.1.0/24 via 10.0.1.5 dev eth0 <- 节点 10.0.1.5 上的 Pod 网段IPIP 模式:跨子网时的 fallback,有额外封包开销,性能不如 BGP 模式。
Cilium:新一代高性能 CNI#
核心差异:基于 eBPF 替代 iptables。
传统 iptables 路径:
用户态 → 内核网络栈 → iptables 链(NAT/filter)→ 转发
Cilium eBPF 路径:
用户态 → XDP/TC hook(eBPF 程序直接处理)→ 转发eBPF 的核心优势:
| 对比项 | iptables | eBPF |
|---|---|---|
| 规则查找复杂度 | O(n) 链式遍历 | O(1) 哈希表 |
| 5000 Service 规则数 | ~25 万条 iptables 规则 | 哈希表几乎无影响 |
| 可观测性 | 有限 | Hubble 提供完整 L7 可见性 |
| NetworkPolicy | L3/L4 | L3/L4/L7(HTTP path/method) |
Cilium 状态检查:
# Cilium 状态
cilium status
# 查看 Hubble 网络流量观测
hubble observe --namespace production --last 100
# 查看 NetworkPolicy 命中情况
hubble observe --verdict DROPPED -n production选型建议#
| 集群规模 | 推荐 CNI | 理由 |
|---|---|---|
| < 50 节点 | Flannel | 简单够用,运维成本低 |
| 50-200 节点 | Calico | 成熟稳定,BGP 性能优秀 |
| > 200 节点 | Cilium | eBPF 性能优势明显,可观测性强 |
| AWS EKS | VPC CNI 或 Calico | VPC CNI 原生集成,Calico 功能更强 |
| 阿里云 ACK | 厂商 CNI(Terway) | 托管 K8s 通常强制使用厂商 CNI |
DNS 与服务发现:Pod 里的域名是怎么解析的?#
在 K8s 里访问服务,你可以用服务名而不是 IP。这背后是 CoreDNS 在起作用。
K8s DNS 解析链路#
Pod 内应用
│ DNS 查询 (UDP 53)
▼
/etc/resolv.conf 中的 nameserver(通常是 CoreDNS Service ClusterIP)
│
▼
CoreDNS Pod(通常 2-3 个副本,运行在 kube-system)
│
├─ cluster.local 域 → 查 K8s 内部 Service/Pod DNS
├─ 反向查找 → 查 K8s 内部
└─ 其他域名 → Forward 到上游 DNS(通常是节点的 /etc/resolv.conf)查看 Pod 的 DNS 配置:
kubectl exec -n production my-pod -- cat /etc/resolv.conf
# nameserver 10.96.0.10 ← CoreDNS Service IP
# search production.svc.cluster.local svc.cluster.local cluster.local
# options ndots:5这三行是理解所有 DNS 问题的起点。
FQDN 解析规则#
K8s 内部服务的完整域名(FQDN)格式:
<service-name>.<namespace>.svc.cluster.local示例:
| 服务 | 命名空间 | FQDN |
|---|---|---|
my-svc | default | my-svc.default.svc.cluster.local |
api-svc | production | api-svc.production.svc.cluster.local |
redis | cache | redis.cache.svc.cluster.local |
短名解析规则(search 域)#
当你在 Pod 里执行 curl http://my-svc,K8s 会根据 /etc/resolv.conf 中的 search 列表自动补全后缀:
# /etc/resolv.conf 中的 search 列表
search production.svc.cluster.local svc.cluster.local cluster.local解析过程:
查询 my-svc
→ 先查 my-svc.production.svc.cluster.local(在同级 namespace)
→ 找不到再查 my-svc.svc.cluster.local
→ 找不到再查 my-svc.cluster.local
→ 都找不到,才查原始的 my-svc(外部 DNS)跨命名空间访问必须带命名空间:
# 在 default namespace 的 Pod 里访问 production namespace 的服务
curl http://api-svc.production.svc.cluster.localndots:5 的坑(入门级了解)#
ndots:5 是 K8s 给每个 Pod 设置的默认值,意思是:如果查询的域名中点的个数少于 5 个,就先在 search 列表中逐一尝试追加后缀。
问题:假设 Pod 查询 api.example.com(点数为 2,小于 5),会先走 search 列表,产生 3 次无效查询,最后才查外部 DNS。
简化方案(入门阶段了解即可):
# 在 Pod 的 dnsConfig 中调整
spec:
dnsConfig:
options:
- name: ndots
value: "2" # 减小阈值NetworkPolicy:Pod 间流量隔离#
Kubernetes 默认的网络模型是完全开放的:集群内所有 Pod 可以互相通信,不需要任何授权。
这在开发环境很便利,但在生产环境是一道大缺口:某个前端 Pod 被 SSRF 或 RCE 打穿后,攻击者可以直接从这个 Pod 访问数据库、内部 API、消息队列——没有任何网络层面的阻拦。
NetworkPolicy 就是 K8s 原生解决这件事的工具。
CNI 插件要求#
NetworkPolicy 是 K8s API 对象,但它本身不做任何流量控制。实际执行网络策略的是 CNI 插件。
| CNI 插件 | NetworkPolicy 支持 | 备注 |
|---|---|---|
| Cilium | ✓ | 推荐,基于 eBPF |
| Calico | ✓ | 生产广泛使用 |
| Weave Net | ✓ | 功能基础 |
| Flannel | ✗ | 不支持(这是最大局限) |
踩坑提醒:如果你使用 Flannel,NetworkPolicy 对象可以创建,但完全不生效!
入门级示例:只允许带特定 label 的 Pod 访问数据库#
场景:MySQL Pod 只允许 app=backend 且 role=api-server 的 Pod 访问 3306 端口。
第一步:为 MySQL Pod 打标签
apiVersion: v1
kind: Pod
metadata:
name: mysql
labels:
app: mysql
role: database
spec:
containers:
- name: mysql
image: mysql:8第二步:创建 NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: mysql-access-control
namespace: production
spec:
podSelector:
matchLabels:
app: mysql # 这个策略作用于带 app=mysql 标签的 Pod
policyTypes:
- Ingress # 控制入站流量
ingress:
- from:
- podSelector:
matchLabels:
app: backend
role: api-server
ports:
- protocol: TCP
port: 3306效果:
- ✅
app=backend, role=api-server的 Pod → 可以访问 MySQL 的 3306 - ❌ 其他所有 Pod → 无法访问 MySQL
默认拒绝策略(零信任起点)#
生产环境推荐先应用"默认拒绝"策略,然后按需开放:
# 拒绝所有入站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {} # 空选择器 = 选中命名空间内所有 Pod
policyTypes:
- Ingress
# 没有 ingress 规则 = 拒绝所有入口流量注意:应用默认拒绝后,需要单独放开 DNS(UDP/TCP 53),否则 Pod 的 DNS 解析会失败:
# 允许 DNS 查询
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
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53小结#
本章我们学习了 Kubernetes 网络模型的四个核心部分:
Pod 网络:每个 Pod 有独立 IP,通过 veth pair 连接 Pod 和宿主机,CNI 插件负责 IP 分配(IPAM)和路由配置。跨节点通信可以用 Overlay(VXLAN 封装)或 Underlay(BGP 直接路由)模式。
CNI 插件选型:
- 小集群 / 入门学习 → Flannel(简单,但不支持 NetworkPolicy)
- 中大型生产集群 → Calico(BGP 模式性能优秀,支持 NetworkPolicy)
- 大集群 / 性能敏感 → Cilium(eBPF 架构,性能最强,支持 L7 策略)
DNS 与服务发现:CoreDNS 负责解析 K8s 内部服务域名,FQDN 格式为
<service>.<ns>.svc.cluster.local。Pod 内的/etc/resolv.conf配置了search域,支持短名解析(自动补全同级命名空间的后缀)。NetworkPolicy:实现 Pod 间的流量隔离,基于标签选择器控制入站(Ingress)和出站(Egress)流量。生产环境推荐先应用"默认拒绝"策略,然后按需开放白名单。注意:Flannel 不支持 NetworkPolicy。
下一步:我们学会了怎么让 Pod 通信、怎么隔离 Pod 流量,但还有一个问题没解决——配置和密钥怎么管理?下一章我们将学习 ConfigMap 与 Secret,搞清楚配置外置、密钥安全存储的最佳实践。
实战要点:
- CNI 选型要趁早:后期迁移 CNI 插件需要停机窗口,初期就要想清楚
- Flannel 用户必看:如果需要 NetworkPolicy,换成 Calico 或用 "Canal"(Flannel + Calico NetworkPolicy)
- DNS 解析失败时:先检查
/etc/resolv.conf的nameserver是否正确,再检查 CoreDNS Pod 是否正常运行 - NetworkPolicy 不生效:先确认你的 CNI 插件支持 NetworkPolicy(Flannel 不支持!)
- 默认拒绝策略要谨慎:首次应用会断开所有流量,务必先放开 DNS 和必要的访问规则