路线图

K8s 网络模型:从 Pod 网络到服务发现

星辉 2026-07-02 阅读 5 min 1,042 字 路线图
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 插件都必须满足:

  1. Pod-to-Pod:同节点或跨节点的 Pod 之间可以直接通信,不需要 NAT
  2. Pod-to-Node:Pod 可以访问所在节点,节点可以访问所有 Pod
  3. Pod-to-Service:Pod 可以通过 Service ClusterIP 访问服务
  4. 外部流量入口:外部流量可以通过 NodePort/LoadBalancer 进入集群

这里最关键的是第一条——Pod 间通信无 NAT。每个 Pod 都有独立的 IP,Pod 看到的源 IP 就是对端真实的 Pod IP,不经过任何地址转换。这和 Docker bridge 网络的 NAT 模式完全不同。

veth pair:Pod 网络的基石#

当 kubelet 创建 Pod 时,CNI 插件负责:

  1. 创建 veth pair(虚拟以太网对):一端放入 Pod 的 network namespace,另一端留在宿主机
  2. 给 Pod 端分配 IP(IPAM:IP Address Management)
  3. 配置路由,让 Pod 能访问集群内其他地址
text
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。

验证方式

bash
# 在宿主机上查看 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 头,兼容性好但有封包开销。

text
原始包:[ Pod A IP | Pod B IP | 数据 ]
Overlay 封装后:[ 节点1 IP | 节点2 IP | VXLAN 头 | 原始包 ]

Underlay(路由模式)

修改底层路由表,直接路由,性能更好但需要网络设备支持(BGP 或同一 L2 域)。

text
Pod A (节点1) → 查路由表 → 直接发往节点2的 Pod B

CNI 插件对比:选对插件事半功倍#

CNI(Container Network Interface)是 K8s 的网络插件标准。不同插件的实现差异很大,选错了后期迁移成本很高。

主流 CNI 插件对比表#

CNI 插件网络模式NetworkPolicy性能适用场景
FlannelOverlay (VXLAN)✗ 不支持中等小集群、简单场景、入门学习
CalicoBGP 路由 / IPIP Overlay✓ 支持(iptables/eBPF)中大型集群、生产环境首选
CiliumeBPF 直接转发✓ 支持(原生 L7)极高大集群、需要可观测性、性能敏感
Weave NetOverlay (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 模式验证

bash
# 查看 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。

text
传统 iptables 路径:
用户态 → 内核网络栈 → iptables 链(NAT/filter)→ 转发

Cilium eBPF 路径:
用户态 → XDP/TC hook(eBPF 程序直接处理)→ 转发

eBPF 的核心优势

对比项iptableseBPF
规则查找复杂度O(n) 链式遍历O(1) 哈希表
5000 Service 规则数~25 万条 iptables 规则哈希表几乎无影响
可观测性有限Hubble 提供完整 L7 可见性
NetworkPolicyL3/L4L3/L4/L7(HTTP path/method)

Cilium 状态检查

bash
# 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 节点CiliumeBPF 性能优势明显,可观测性强
AWS EKSVPC CNICalicoVPC CNI 原生集成,Calico 功能更强
阿里云 ACK厂商 CNI(Terway)托管 K8s 通常强制使用厂商 CNI

DNS 与服务发现:Pod 里的域名是怎么解析的?#

在 K8s 里访问服务,你可以用服务名而不是 IP。这背后是 CoreDNS 在起作用。

K8s DNS 解析链路#

text
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 配置

bash
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)格式:

text
<service-name>.<namespace>.svc.cluster.local

示例

服务命名空间FQDN
my-svcdefaultmy-svc.default.svc.cluster.local
api-svcproductionapi-svc.production.svc.cluster.local
rediscacheredis.cache.svc.cluster.local

短名解析规则(search 域)#

当你在 Pod 里执行 curl http://my-svc,K8s 会根据 /etc/resolv.conf 中的 search 列表自动补全后缀:

bash
# /etc/resolv.conf 中的 search 列表
search production.svc.cluster.local svc.cluster.local cluster.local

解析过程

text
查询 my-svc
  → 先查 my-svc.production.svc.cluster.local(在同级 namespace)
  → 找不到再查 my-svc.svc.cluster.local
  → 找不到再查 my-svc.cluster.local
  → 都找不到,才查原始的 my-svc(外部 DNS)

跨命名空间访问必须带命名空间:

bash
# 在 default namespace 的 Pod 里访问 production namespace 的服务
curl http://api-svc.production.svc.cluster.local

ndots:5 的坑(入门级了解)#

ndots:5 是 K8s 给每个 Pod 设置的默认值,意思是:如果查询的域名中点的个数少于 5 个,就先在 search 列表中逐一尝试追加后缀。

问题:假设 Pod 查询 api.example.com(点数为 2,小于 5),会先走 search 列表,产生 3 次无效查询,最后才查外部 DNS。

简化方案(入门阶段了解即可):

yaml
# 在 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=backendrole=api-server 的 Pod 访问 3306 端口。

第一步:为 MySQL Pod 打标签

yaml
apiVersion: v1
kind: Pod
metadata:
  name: mysql
  labels:
    app: mysql
    role: database
spec:
  containers:
  - name: mysql
    image: mysql:8

第二步:创建 NetworkPolicy

yaml
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

默认拒绝策略(零信任起点)#

生产环境推荐先应用"默认拒绝"策略,然后按需开放:

yaml
# 拒绝所有入站流量
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 解析会失败:

yaml
# 允许 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 网络模型的四个核心部分:

  1. Pod 网络:每个 Pod 有独立 IP,通过 veth pair 连接 Pod 和宿主机,CNI 插件负责 IP 分配(IPAM)和路由配置。跨节点通信可以用 Overlay(VXLAN 封装)或 Underlay(BGP 直接路由)模式。

  2. CNI 插件选型

    • 小集群 / 入门学习 → Flannel(简单,但不支持 NetworkPolicy)
    • 中大型生产集群 → Calico(BGP 模式性能优秀,支持 NetworkPolicy)
    • 大集群 / 性能敏感 → Cilium(eBPF 架构,性能最强,支持 L7 策略)
  3. DNS 与服务发现:CoreDNS 负责解析 K8s 内部服务域名,FQDN 格式为 <service>.<ns>.svc.cluster.local。Pod 内的 /etc/resolv.conf 配置了 search 域,支持短名解析(自动补全同级命名空间的后缀)。

  4. NetworkPolicy:实现 Pod 间的流量隔离,基于标签选择器控制入站(Ingress)和出站(Egress)流量。生产环境推荐先应用"默认拒绝"策略,然后按需开放白名单。注意:Flannel 不支持 NetworkPolicy

下一步:我们学会了怎么让 Pod 通信、怎么隔离 Pod 流量,但还有一个问题没解决——配置和密钥怎么管理?下一章我们将学习 ConfigMap 与 Secret,搞清楚配置外置、密钥安全存储的最佳实践。


实战要点

  • CNI 选型要趁早:后期迁移 CNI 插件需要停机窗口,初期就要想清楚
  • Flannel 用户必看:如果需要 NetworkPolicy,换成 Calico 或用 "Canal"(Flannel + Calico NetworkPolicy)
  • DNS 解析失败时:先检查 /etc/resolv.confnameserver 是否正确,再检查 CoreDNS Pod 是否正常运行
  • NetworkPolicy 不生效:先确认你的 CNI 插件支持 NetworkPolicy(Flannel 不支持!)
  • 默认拒绝策略要谨慎:首次应用会断开所有流量,务必先放开 DNS 和必要的访问规则