Kubernetes网络深度剖析:Flannel/Calico原理与Traefik Ingress流量路由实战
Kubernetes网络深度剖析:Flannel/Calico原理与Traefik Ingress流量路由实战
内容摘要:本文是K8s核心系列的第七篇,从Docker原生网络的痛点出发,讲透K8s网络模型三原则与CNI规范,深度对比Flannel(UDP/VxLAN/host-gw)与Calico(BGP/IPIP)的实现原理,梳理"Pod→Service→kube-proxy→Pod"的完整访问链路,并在真实K8s 1.29集群中实战部署Traefik Ingress Controller,完成HTTP路由、多Ingress控制器隔离、蓝绿部署与金丝雀发布的全流程演示,所有命令输出均为真实环境实测。
实验环境:Kubernetes v1.29(1 Master + 3 Worker,containerd + Flannel VxLAN,Pod网段10.244.0.0/16)
目录
一、从Docker网络谈起:为什么需要K8s网络模型
1.1 Docker原生网络模型回顾
Docker单机网络有四种模式:bridge(默认,经docker0网桥+NAT出网)、host(共享宿主机网络栈)、none、container(共享另一容器的网络栈)。跨主机场景下Docker提供了overlay网络(基于VXLAN),但那是Swarm体系的方案。
Docker单机bridge网络在集群场景下的三个致命问题:
- IP冲突:每台宿主机的docker0默认都是172.17.0.0/16,容器IP只在单机内唯一,跨主机无法直接路由。
- NAT性能损耗与端口管理:容器对外通信要SNAT,外部访问容器要做端口映射(-p 8080:80),规模大了端口分配就是灾难。
- 缺乏服务抽象:容器重建IP就变化,没有统一的服务发现和稳定的网络标识。
1.2 K8s需要什么样的网络
K8s的设计目标是"把一群机器抽象成一台大计算机",容器(Pod)应该像一台台虚拟机一样拥有独立的、全局可达的IP,应用无需关心NAT和端口映射。这就引出了K8s网络模型的硬性要求。
二、K8s网络模型三原则与CNI规范
2.1 三原则(K8s网络的"宪法")
K8s对网络实现提出了三条强制性要求,任何CNI插件都必须满足:
- Pod与Pod之间无需NAT即可直接通信(跨节点也一样)
- 节点上的Agent(kubelet、系统进程)能与该节点上所有Pod通信
- hostNetwork模式的Pod与其他Pod通信时无需NAT(K8s官方表述为:Pod看到的自己的IP和别人看到的它的IP是同一个)
一句话概括:K8s中每个Pod都是网络中的"一等公民",拥有独立IP,全网直连,无NAT。这意味着底层网络必须实现一个跨节点的二层/三层互通平面——CNI插件就是为了解决这个问题。
2.2 CNI规范:插件化网络的标准接口
CNI(Container Network Interface)是CNCF维护的容器网络规范,定义了容器运行时(containerd)与网络插件之间的接口。它的设计非常精简——插件只需要实现四个操作:
ADD 把容器加入网络(分配IP、建veth对、配路由)
DEL 把容器移出网络(回收IP、清理veth)
CHECK 检查容器网络是否符合预期
VERSION 报告插件支持的规范版本
kubelet创建Pod时,containerd先建立Pause容器(网络命名空间的载体),再调用CNI插件(二进制位于/opt/cni/bin,配置位于/etc/cni/net.d)完成网络配置。Flannel、Calico、Cilium、Weave等都是CNI插件,各自用不同技术实现"三原则"。
CNI插件选型没有银弹,核心权衡点是:性能 vs 功能 vs 运维复杂度。
三、Flannel原理深度解析
Flannel是最流行的入门级CNI插件,定位"简单易用的overlay网络"。它为每个节点从集群Pod网段中分配一个/24子网(如node1分10.244.1.0/24、node2分10.244.2.0/24),子网分配信息记录在API Server(或etcd)中。跨节点通信有三种后端模式。
3.1 UDP模式(最早实现,仅教学意义)
Pod A (10.244.1.2) Pod B (10.244.2.3)
│ cni0网桥 ▲
▼ │
flanneld(用户态进程)
│ ① 查路由发现目标是远端子网
│ ② 把整个IP包封装进UDP包,目的端口8285
▼
节点A内核 ─── UDP over 节点网络 ───► 节点B内核 → flanneld解封 → cni0 → Pod B
UDP模式的封装/解封装在用户态flanneld进程中完成,每个包要经历多次内核态↔用户态拷贝,性能极差(实测带宽损失可达50%+),早已被废弃,只用于理解overlay思想。
3.2 VxLAN模式(默认,生产常用)
VxLAN(Virtual Extensible LAN)是内核态的overlay协议。每个节点上有一个flannel.1 VTEP设备(VxLAN Tunnel Endpoint),封装过程完全在内核完成:
Pod A (10.244.1.2) 想访问 Pod B (10.244.2.3)
① 查Pod A所在节点路由表:
10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink
→ 包被发往VTEP设备flannel.1
② 内核VxLAN模块封装:
[外层: 节点A IP → 节点B IP : UDP 8472]
[VxLAN头: VNI=1]
[内层: 原始以太帧 10.244.1.2 → 10.244.2.3]
(对端VTEP的MAC通过ARP/FDB表项学习,由flanneld维护)
③ 节点B内核收到8472端口的UDP包 → 解封装 → 根据内层目标IP经cni0网桥送达Pod B
跨节点Pod间Ping的报文,物理网线上跑的是UDP包,Pod无感知。性能上比UDP模式好一个量级(内核态处理),代价是约50字节的封装开销(MTU需相应调小,Flannel自动设置为1450)。
3.3 host-gw模式(二层直连的性能之王)
host-gw(Host Gateway)不做任何封装,直接把下一跳节点当作网关,写纯三层路由:
节点A的路由表:
10.244.2.0/24 via 192.168.0.189 dev eth0 # 去node2的Pod子网,下一跳是node2节点IP
10.244.3.0/24 via 192.168.0.136 dev eth0
包到节点B后,节点B本机路由10.244.2.0/24 dev cni0直接送进网桥。零封装开销,性能接近物理网络。
限制也很明显:要求所有节点二层互通(同一子网,或云环境支持自定义路由的VPC)。节点跨子网时host-gw失效。Flannel的DirectRouting选项可以在同子网用host-gw、跨子网自动降级VxLAN,是云上VPC环境的实用组合。
3.4 三种模式对比
| 维度 | UDP | VxLAN | host-gw |
|---|---|---|---|
| 封装位置 | 用户态flanneld | 内核态 | 无封装 |
| 性能 | 极差 | 较好(开销~10%) | 最优(接近物理网) |
| 网络要求 | 无 | 节点间UDP 8472可达 | 节点间二层互通 |
| 适用场景 | 教学 | 通用(跨子网/云环境) | 同机房同子网 |
Flannel不支持NetworkPolicy——它是纯连通性插件。需要网络策略(按namespace/label隔离流量)时,要么换Calico/Cilium,要么给Flannel搭配Calico(Canal方案)。
四、Calico原理与Flannel对比
Calico是功能最全面的CNI方案之一,主打"纯三层网络+网络策略"。
4.1 BGP模式(默认,无封装)
Calico在每个节点运行Felix(负责写路由和iptables策略)和BIRD(BGP客户端)。每个节点都是一台BGP Speaker,把自己Pod子网的路由通过BGP协议宣告给其他节点:
节点A(BIRD):"我是10.244.1.0/24的下一跳,AS 64512"
──BGP路由宣告──► 节点B/C/D 写入内核路由:
10.244.1.0/24 via 192.168.0.34 dev eth0
本质上和Flannel host-gw一样是"路由直连",但用标准的BGP协议做路由分发,规模更大、支持路由反射器(Route Reflector)应对几百节点以上的全互联瓶颈。同样要求节点二层可达。
4.2 IPIP模式(跨子网场景)
节点跨子网时,Calico把原始IP包外面再套一层IP头(协议号4,IP-in-IP):
[外层IP: 节点A → 节点B] [内层IP: PodA → PodB]
封装开销比VxLAN小(没有UDP和VxLAN头,仅多20字节IP头),性能略好,但不支持加密。Calico按节点池粒度自动选择:同子网走BGP直连,跨子网走IPIP。
4.3 Calico vs Flannel 总结
| 维度 | Flannel | Calico |
|---|---|---|
| 实现机制 | overlay(VxLAN)或host-gw | BGP三层路由 / IPIP隧道 |
| NetworkPolicy | 不支持 | 原生支持(且支持全局策略) |
| 性能 | VxLAN有封装开销 | BGP模式接近物理网络 |
| 配置复杂度 | 低,开箱即用 | 较高(BGP概念、IPIP池配置) |
| 适用规模 | 中小集群 | 中大型集群、有网络隔离需求 |
选型建议:中小集群学习/快速落地用Flannel VxLAN;有NetworkPolicy隔离需求或大集群用Calico BGP;追求极致性能和eBPF能力看Cilium。
五、K8s服务访问完整流程总结
至此可以把前几篇和本篇的知识串成一条完整链路。以"集群外用户访问一个NodePort Service"为例:
① 用户请求 http://节点IP:30080
│
▼ ② 节点内核iptables(kube-proxy规则)
KUBE-NODEPORTS链 → KUBE-SVC-XXX链 → 概率均分选中 KUBE-SEP-YYY
DNAT: 目标地址改为 Pod IP 10.244.2.5:80
│
▼ ③ 本机路由判断
若Pod在本地 → 经cni0直达
若Pod在远端 → 经flannel.1 VxLAN封装 → 对端节点解封 → cni0 → Pod
│
▼ ④ 后端Pod处理并响应
响应包经conntrack做反向SNAT,原路返回
│
▼ ⑤ 用户收到响应
集群内Pod经Service名访问时,最前面多两步:CoreDNS解析服务名得到ClusterIP → 本机iptables把ClusterIP DNAT到某个后端Pod IP。整个过程中,Service IP本身不监听任何端口、不转发任何流量——它只存在于iptables/IPVS规则里,这是理解K8s网络最关键的一个认知。
六、实战:部署Traefik Ingress Controller
6.1 为什么需要Ingress
NodePort暴露服务有三个痛点:端口范围受限(30000+)、四层转发无七层能力(不能按域名/路径路由)、每服务占用节点端口难管理。Ingress是K8s的七层流量入口抽象:一个入口IP/端口,按Host和Path把流量路由到不同Service,还能做TLS终止、限流、灰度。
Ingress资源只是"规则声明",真正干活的是Ingress Controller(nginx-ingress、Traefik、HAProxy、APISIX等)。本文选择Traefik:云原生出身、配置即代码、天然支持多入口和中间件,且与K8s CRD生态结合紧密。
6.2 部署Traefik
采用官方YAML方式部署(无需helm,国内网络友好)。核心组件:ServiceAccount+RBAC、Deployment(traefik镜像)、Service(NodePort方式暴露80/443端口)。
# traefik-deploy.yaml(核心部分节选)
apiVersion: apps/v1
kind: Deployment
metadata:
name: traefik
namespace: traefik
spec:
replicas: 1
selector:
matchLabels:
app: traefik
template:
metadata:
labels:
app: traefik
spec:
serviceAccountName: traefik
containers:
- name: traefik
image: traefik:v2.11
args:
- --entrypoints.web.address=:80 # HTTP入口
- --entrypoints.websecure.address=:443 # HTTPS入口
- --providers.kubernetesingress # 启用Ingress provider
- --providers.kubernetescrd # 启用CRD provider(IngressRoute/TraefikService)
- --api.dashboard=true # 开启Dashboard
- --api.insecure=true
- --log.level=INFO
ports:
- containerPort: 80
- containerPort: 443
- containerPort: 8080 # Dashboard
---
apiVersion: v1
kind: Service
metadata:
name: traefik
namespace: traefik
spec:
type: NodePort
selector:
app: traefik
ports:
- name: web
port: 80
nodePort: 31080 # 集群外经任意节点IP:31080访问HTTP入口
- name: websecure
port: 443
nodePort: 31443
- name: dashboard
port: 8080
nodePort: 31808
生产环境通常用LoadBalancer类型Service(云上)或DaemonSet+hostNetwork+前置LB(自建),NodePort是测试环境最方便的方式。
踩坑记录:Traefik启用CRD provider前必须先安装CRD定义(
kubectl apply -f kubernetes-crd-definition-v1.yml),否则创建TraefikService会报no matches for kind "TraefikService";且ClusterRole需要对traefik.io/traefik.containo.us两个API组的全部资源授予get/list/watch权限,否则日志里会出现ingressrouteudps.traefik.containo.us is forbidden之类的watch失败。
本集群实测部署结果:
$ kubectl -n traefik get svc traefik
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
traefik NodePort 10.105.20.198 <none> 80:31080/TCP,443:31443/TCP,8080:31808/TCP 62s
$ kubectl get pods -A -o wide | grep -E 'NAMESPACE|traefik'
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
traefik traefik-7f66544bd7-pl756 1/1 Running 0 62s 10.244.1.14 k8s-node3 <none> <none>
$ kubectl get ingressclass
NAME CONTROLLER PARAMETERS AGE
traefik traefik.io/ingress-controller <none> 89s
七、实战:HTTP七层路由与四层TCP代理
7.1 HTTP路由(Ingress资源)
部署两个demo应用(nginx v1/v2),用Ingress按路径路由:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo-ingress
spec:
ingressClassName: traefik # 指定由哪个控制器处理
rules:
- host: demo.local
http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: nginx-v1
port:
number: 80
- path: /v2
pathType: Prefix
backend:
service:
name: nginx-v2
port:
number: 80
pathType三种取值:Prefix(前缀匹配,/v1也匹配/v1/a/b)、Exact(精确匹配)、ImplementationSpecific(由控制器决定)。注意Prefix按最长路径优先匹配。
7.2 四层(TCP)与七层(HTTP)代理的区别
| 维度 | 四层代理(L4) | 七层代理(L7) |
|---|---|---|
| 工作层级 | TCP/UDP | HTTP/HTTPS/gRPC |
| 路由依据 | IP+端口 | 域名、路径、Header、Cookie |
| 典型实现 | kube-proxy、LVS、NLB | Traefik、Nginx、Envoy |
| 能力 | 转发快、开销小,但"看不懂"内容 | TLS终止、限流、熔断、灰度、改写 |
K8s的Service(iptables/IPVS)是四层;Ingress是七层。Traefik同时支持两者:HTTP路由用Ingress/IngressRoute CRD,TCP路由用Traefik的IngressRouteTCP CRD(如暴露MySQL:按SNI或端口把TCP流量转给后端Service)。四层代理只负责"接通管道",七层代理能基于请求内容做精细控制——这也是灰度发布只能在七层做的原因。
本集群实测HTTP路由。demo应用(nginx-v1返回nginx-v1 (stable)、nginx-v2返回nginx-v2 (canary))与demo-ingress创建后:
$ kubectl get ingress demo-ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
demo-ingress traefik demo.local 80 11s
$ kubectl get pods -A -o wide | grep -E 'traefik|nginx-v'
default nginx-v1-dd9c7d6b7-6xz8b 1/1 Running 0 60s 10.244.1.24 k8s-node3
default nginx-v1-dd9c7d6b7-vcf74 1/1 Running 0 59s 10.244.1.25 k8s-node3
default nginx-v2-678d868-pdgrk 1/1 Running 0 57s 10.244.1.27 k8s-node3
default nginx-v2-678d868-rglhw 1/1 Running 0 57s 10.244.1.28 k8s-node3
traefik traefik-7f66544bd7-pl756 1/1 Running 0 62s 10.244.1.14 k8s-node3
在集群节点上带着Host头访问Traefik的NodePort入口,两个路径精确路由到不同版本:
$ curl -s -H 'Host: demo.local' http://192.168.0.34:31080/v1
<h1>nginx-v1 (stable)</h1>
$ curl -s -H 'Host: demo.local' http://192.168.0.34:31080/v2
<h1>nginx-v2 (canary)</h1>
生产使用:把
demo.local换成真实域名,DNS解析到任一节点IP(或前置LB),即可从集群外访问。
八、实战:多组Ingress控制器隔离
中大型集群常需要多套Ingress控制器并存:比如一套面向公网(配WAF、限流)、一套面向内网(高带宽、宽松策略);或者按团队隔离,各自管理自己的入口规则互不影响。
实现机制就是IngressClass:
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: traefik-external
spec:
controller: traefik.io/ingress-controller-external # 自定义控制器标识
---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: traefik-internal
spec:
controller: traefik.io/ingress-controller-internal
部署两套Traefik(不同namespace、不同--providers.kubernetesingress.ingressclass参数、不同NodePort),各自只watch自己的IngressClass。Ingress资源通过spec.ingressClassName声明归属:
ingressClassName: traefik-external → 公网入口Traefik处理(NodePort 31080)
ingressClassName: traefik-internal → 内网入口Traefik处理(NodePort 32080)
无ingressClassName → 只有配置了"watch所有class"的控制器才会认领
排障提示:Ingress配了却不生效,第一反应就是查ingressClassName与控制器的ingressclass参数是否匹配,以及是否存在多个控制器争抢同一个class。
本集群实测:创建第二个IngressClass(模拟内网入口):
$ cat <<'EOF' | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: traefik-internal
spec:
controller: traefik.io/ingress-controller-internal
EOF
ingressclass.networking.k8s.io/traefik-internal created
$ kubectl get ingressclass
NAME CONTROLLER PARAMETERS AGE
traefik traefik.io/ingress-controller <none> 89s
traefik-internal traefik.io/ingress-controller-internal <none> 1s
此时再部署一套--providers.kubernetesingress.ingressclass=traefik-internal的Traefik,它只会认领ingressClassName: traefik-internal的Ingress,与公网入口完全隔离。
九、实战:蓝绿部署与金丝雀发布
9.1 蓝绿部署(Blue-Green)
蓝绿部署同时运行新旧两个完整环境(蓝=v1旧版、绿=v2新版),流量入口指向其一,切换时整体一次性切流,出问题秒级切回。用两个Deployment+两个Service+Ingress切换backend实现:
┌─────────────┐
请求──►│ Ingress │──► Service nginx-v1 ──► Deployment v1(蓝,承载流量)
└─────────────┘ (切换backend到nginx-v2即完成发布)
Service nginx-v2 ──► Deployment v2(绿,待命)
切换操作:
# 一键切流:修改Ingress的backend指向v2
kubectl patch ingress demo-ingress --type=json \
-p='[{"op":"replace","path":"/spec/rules/0/http/paths/0/backend/service/name","value":"nginx-v2"}]'
# 验证v2无问题后,v1缩容到0(保留现场便于回滚)
kubectl scale deployment nginx-v1 --replicas=0
本集群实测切流效果——patch前/v1返回v1页面,patch后立即返回v2页面:
$ kubectl patch ingress demo-ingress --type=json \
-p='[{"op":"replace","path":"/spec/rules/0/http/paths/0/backend/service/name","value":"nginx-v2"}]'
ingress.networking.k8s.io/demo-ingress patched
$ curl -s -H 'Host: demo.local' http://192.168.0.34:31080/v1
<h1>nginx-v2 (canary)</h1>
特点:切换快、回滚快,但资源双倍占用,且无中间态(所有用户同时看到新版本)。
9.2 金丝雀发布(Canary)
金丝雀发布让新旧版本同时承载流量,按权重逐步放量(5%→20%→50%→100%),任何一步发现问题立即回退。Traefik通过TraefikService(加权轮询CRD)实现:
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: canary-wrr
spec:
weighted:
services:
- name: nginx-v1 # 稳定版
port: 80
weight: 90 # 90%流量
- name: nginx-v2 # 金丝雀版
port: 80
weight: 10 # 10%流量
---
# 用IngressRoute(Traefik CRD)把Host路由到这个加权服务
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: canary-route
spec:
entryPoints:
- web
routes:
- match: Host(`canary.local`)
kind: Rule
services:
- name: canary-wrr
port: 80
kind: TraefikService # ← 必须显式声明,默认是普通Service
观察指标正常后逐步调整weight(90/10 → 50/50 → 0/100),完成全量发布。整个过程无需重建Pod、流量无中断。
更精细的金丝雀(按Header/Cookie分流特定用户群体)可用Traefik的middleware或nginx-ingress的
nginx.ingress.kubernetes.io/canary-by-header注解实现;生产级渐进式交付推荐Argo Rollouts/Flagger。
本集群实测:创建上面的TraefikService(90/10权重)和IngressRoute后,连续请求20次统计版本分布:
$ kubectl get traefikservice canary-wrr; kubectl get ingressroute canary-route
NAME AGE
canary-wrr 2m27s
NAME AGE
canary-route 3s
$ kubectl describe traefikservice canary-wrr | tail -8
Services:
Name: nginx-v1
Port: 80
Weight: 90
Name: nginx-v2
Port: 80
Weight: 10
$ for i in $(seq 1 20); do curl -s -H 'Host: canary.local' http://192.168.0.34:31080/ | grep -o 'nginx-v[12]'; done | sort | uniq -c
18 nginx-v1
2 nginx-v2
20次请求中18次落到v1、2次落到v2,正好是90/10的权重分布——金丝雀放量生效。把weight改为0/100即完成全量切换,改回100/0即秒级回滚。
十、总结与系列导航
本文从原理到实战走完了K8s网络的核心链路:
- K8s网络三原则(Pod直连无NAT)是所有CNI插件的"宪法",CNI用ADD/DEL接口标准化了网络接入。
- Flannel三模式:VxLAN通用、host-gw性能最优但要求二层互通、UDP仅供教学;Flannel不管网络策略。
- Calico用BGP做纯三层路由、原生支持NetworkPolicy,是中大型集群和隔离场景的首选。
- Service IP是虚拟的,只存在于iptables/IPVS规则中——这是理解一切Service网络问题的钥匙。
- Ingress=七层入口,Traefik通过IngressClass支持多控制器隔离,通过TraefikService权重实现金丝雀,配合双环境实现蓝绿。
本系列导航:
-
blog-01《Docker基础入门与核心命令实战》
-
blog-02《Docker镜像与网络深度解析》
-
blog-03《Dockerfile最佳实践与私有镜像仓库搭建》
-
blog-04《Containerd深度解析:从Docker到Containerd的演进与实战迁移》
-
blog-05《Kubernetes生产级集群部署实战:kubeadm搭建K8s 1.29全流程指南》
-
blog-06《Kubernetes核心概念深度解析:Pod/Service/Deployment/存储与配置全攻略》
-
blog-07《Kubernetes网络深度剖析:Flannel/Calico原理与Traefik Ingress流量路由实战》(本篇)
-
blog-08《K8s全栈监控体系搭建:Prometheus+Grafana+AlertManager生产实战》
-
blog-09《K8s日志体系实战:EFK/Loki日志采集与分析》
-
blog-10《Spring Cloud微服务上K8s实战》
-
blog-11《云原生CI/CD与GitOps落地实践》
下一篇将进入可观测性领域——用Prometheus+Grafana+AlertManager给本文的集群装上"眼睛"。
更多推荐



所有评论(0)