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网络在集群场景下的三个致命问题:

  1. IP冲突:每台宿主机的docker0默认都是172.17.0.0/16,容器IP只在单机内唯一,跨主机无法直接路由。
  2. NAT性能损耗与端口管理:容器对外通信要SNAT,外部访问容器要做端口映射(-p 8080:80),规模大了端口分配就是灾难。
  3. 缺乏服务抽象:容器重建IP就变化,没有统一的服务发现和稳定的网络标识。

1.2 K8s需要什么样的网络

K8s的设计目标是"把一群机器抽象成一台大计算机",容器(Pod)应该像一台台虚拟机一样拥有独立的、全局可达的IP,应用无需关心NAT和端口映射。这就引出了K8s网络模型的硬性要求。


二、K8s网络模型三原则与CNI规范

2.1 三原则(K8s网络的"宪法")

K8s对网络实现提出了三条强制性要求,任何CNI插件都必须满足:

  1. Pod与Pod之间无需NAT即可直接通信(跨节点也一样)
  2. 节点上的Agent(kubelet、系统进程)能与该节点上所有Pod通信
  3. 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 三种模式对比

维度UDPVxLANhost-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 总结

维度FlannelCalico
实现机制overlay(VxLAN)或host-gwBGP三层路由 / 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/UDPHTTP/HTTPS/gRPC
路由依据IP+端口域名、路径、Header、Cookie
典型实现kube-proxy、LVS、NLBTraefik、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网络的核心链路:

  1. K8s网络三原则(Pod直连无NAT)是所有CNI插件的"宪法",CNI用ADD/DEL接口标准化了网络接入。
  2. Flannel三模式:VxLAN通用、host-gw性能最优但要求二层互通、UDP仅供教学;Flannel不管网络策略。
  3. Calico用BGP做纯三层路由、原生支持NetworkPolicy,是中大型集群和隔离场景的首选。
  4. Service IP是虚拟的,只存在于iptables/IPVS规则中——这是理解一切Service网络问题的钥匙。
  5. 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给本文的集群装上"眼睛"。

Logo

AtomGit AI 社区提供模型库、数据集、Agent、Token等资源

更多推荐