k8s网络模式深入

一、POD的完整创建流程

1.1 pod创建流程

image

0、controller-manager、scheduler、kubelet均监听API-Server,只watch自己关注的资源

1、kubectl提交创建pod副本的请求---> API-server

2、创建pod副本的事件记入ETCD

3、上报事件给 API-server

4、controller-manager从API-server那里watch到创建pod副本的事件,会调用具体的控制器来工作,不同的控制器有不同的特性,例如Replicaset控制器,该控制器的核心作用就是控制pod数到指定个数,如果kubectl提交过来的是创建3个pod,而当前pod为2个,则需要新增一个pod

5、创建Pod的事件提交给API-server

6、 API-server将该事件记入ETCD

7、上报

8、scheduler从API-server那里watch到创建pod副本的事件,负责选出一个工作节点来创建pod,筛选过程分为两个阶段

(1)预选:根据污点、选择器等筛选出符合条件的节点

(2)优选:从筛出的节点里进行打分,选择资源最优的

9、将选出的节点提交给API-server

10、记入etcd

11、上报

12、指定的工作节点上的的kubelet组件watch到创建POD的事件,开始创建POD

12.1、kubelet如何创建容器的呢?kubelet创建的每一个容器都经历如下过程

- 1、Docker 作为 k8s 容器运行时,调用关系如下:
kubelet -> docker shim (在 kubelet 进程中) -> dockerd -> containerd
- 2、Containerd 作为 k8s 容器运行时,调用关系如下:
kubelet -> cri plugin(在 containerd 进程中) -> containerd

image-20260705121916687

12.2、POD是如何创建出来的呢?

一个pod内包含多个容器,每一个容器的创建都经历12.1的步骤,至于POD内容器具体的创建流程如下

(1)每创建一个POD,都会先在POD内启动一个pause容器(又叫Infra容器或Sandbox容器,我们检查node节点的时候可以发现每个node上都运行了很多的pause容器),该容器的镜像是由汇编语言开发的非常轻量级,该容器的作用是为整个POD初始化好网络

1、kubelet先创建出pause容器,然后设置该容器的网络模式为None,即不用容器引起自己的网络

2、然后kubelet会调用网络插件,k8s网络模式由很多,此处我们假设你用的是flannel网络模式,则会调用cni插件为pause初始化好网络

(2)pause容器启动且网络初始化完成后,kubelet才会创建业务容器

业务容器的网络直接设置为containerd模式,即与pause共享网络

业务容器的具体创建过程如下

1.2 pod生命周期

在pause容器启动完成后,会先启动init初始化容器。再启动业务容器,业务容器启动、终止前分别设置了两个钩子,用于定制一些功能;业务容器运行时会同步运行两个探针来检查容器的健康。

image-20260705121928911

补充:

由于Pause容器的功能特性,它在 Kubernetes 中有着多个不同的称呼,但不管什么名字,指向的都是pause容器。其中一些常见的有:

1、Pod Sandbox:这个概念来自 Kubernetes 的网络设计,为同一 Pod 内的容器提供了共享的网络命名空间。

2、Infra Container:Infra 直译过来为基础设施,表明这个容器提供了创建和配置命名空间的基础设施。

3、Parent Container:由于其他的容器共享了 Pause 容器的命名空间,这个称呼突出了它对子容器(Pod 中的其他容器)的父级地位。

4、Pod Lifecycle Container:Pause 容器的生命周期与所属的 Pod 生命周期同步。这个称呼显示了Pause 容器在 Pod 生命周期管理中的地位。

二、k8s网络储备知识

2.1 储备知识

储备知识一:

1、大二层网络:https://egonlin.com/?p=8278

2、GRE与Vxlan:https://egonlin.com/?p=8280

储备知识二:k8s Pod IP地址规划(CIDR)https://egonlin.com/?p=10810

事先提一个问题,带着这个问题你往下看

PodA的ip地址为:10.244.0.20/24

PodB的ip地址为:10.244.1.20/24

问:为何PodA内可以ping通PodB的ip地址,而不需要任何路由转发,而这两个地址明明看起来是两个网段的地址

2.2 VXLAN 简介

VXLAN (Virtual Extensible LAN)是一种网络虚拟化技术,它使用一种隧道协议,将二层以太网帧封装在四层UDP报文中,通过三层网络传输,组成一个虚拟大二层网络。VXLAN的报文格式如下:

Outer IP HeaderUDP HeaderUDP Data
Src IPDst IPVXLAN HeaderOriginal L2 Frame
Inner MAC HeaderInner IP Header

VXLAN使用VTEP(VXLAN Tunnel Endpoint)来进行封包和解包,它是VXLAN隧道的起点或终点:

  • 在发送端,源VTEP将原始报文封装成VXLAN报文,通过UDP发送到对端VTEP。

  • 在接收端,VTEP将解开VXLAN报文,将原始的2层数据帧转发给目的的接收方。

VTEP可以是独立的网络设备,例如交换机,也可以是部署在服务器上的虚拟设备。例如使用置顶交换机(TOR)作为VTEP时,VXLAN的网络模型如下图:

image-20260705122711241

但显然,在flannel中,VTEP的能力是通过linux的虚机网络设备实现的。在VXLAN模式下,VTEP的角色由 flannel1.1 虚拟网卡充当。

三、Flannel网络

Flannel网络模式持的后端转发方式有许多种,常用的主要有两种,VXLAN以及host-gw。

3.1 VXLAN模式

3.1.1 关键点

1、采用vxlan模式实现的虚拟大二层网络,它的物理网络可以二层连通也可以三层连通

2、VXLAN是Flannel默认和推荐的模式。当我们使用默认配置安装Flannel时,它会为每个物理节点分配一个24位子网,落到该物理节点上的Pod就该用该网段,每个物理节点分配的段都不一样(例如物理节点1的网络为10.244.0.0/24,物理节点2的网络为10.244.1.0/24),并在每个节点上创建两张虚机网卡: cni0 和 flannel.1。

用下面的yaml部6个pod副本,看下每个节点上pod的网段,你就知道该物理节点手里掌握的pod网段是多少了

apiVersion: apps/v1
kind: Deployment
metadata:
    labels:
    app: web
    name: web
    namespace: default
spec:

replicas: 6
selector:
    matchLabels:
    app: web
strategy: {}
template:
    metadata:
    creationTimestamp: null
    labels:
    app: web
spec:
    containers:
    - image: registry.cn-shanghai.aliyuncs.com/egon-k8s-test/busybox:v1.0
    name: c1
    command: ["tail","-f","/dev/null"]
status: {}
[root@k8s-master-01 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
NOMINATED NODE READINESS GATES
web-dd9b668cb-96rvk 1/1 Running 0 5m31s 10.244.2.141 k8s-
node-02
web-dd9b668cb-h5mnq 1/1 Running 0 5m31s 10.244.1.232 k8s-
node-01
web-dd9b668cb-lbffq 1/1 Running 0 5m31s 10.244.1.233 k8s-
node-01
web-dd9b668cb-qnh57 1/1 Running 0 5m31s 10.244.0.58 k8s-
master-01
web-dd9b668cb-vwfqb 1/1 Running 0 5m31s 10.244.1.231 k8s-
node-01
web-dd9b668cb-xbdns 1/1 Running 0 5m31s 10.244.0.57 k8s-
master-01 
可以看出  
k8s-master-01节点 掌握10.244.0.0段  
k8s-node-01节点   掌握10.244.1.0段  
k8s-node-02节点   掌握10.244.2.0段  

去查看下各个物理节点的路由情况,任一个物理节点都有到所有其他节点的网段路由,例如:

[root@k8s-node-01 ~]# route -n # 当前节点自己的段是10.244.1.0
Kernel IP routing table
DestinationGatewayGenmaskFlagsMetricRefUse Iface
0.0.0.0192.168.71.10.0.0.0UG000 ens36
10.244.0.010.244.0.0255.255.255.0UG000 flannel.1 #
10.244.0.0段走flannel.1设备
10.244.1.00.0.0.0255.255.255.0U000 cni0
#10.244.1.0段的包走cni网桥,即本机转发
10.244.2.010.244.2.0255.255.255.0UG000 flannel.1 #
10.244.2.0段走flannel.1设备
169.254.0.00.0.0.0255.255.0.0U100200 ens36
172.17.0.00.0.0.0255.255.0.0U000 docker0
192.168.71.00.0.0.0255.255.255.0U000 ens36

3、 cni0 是一个网桥设备,类似于 docker0 ,节点上所有的Pod都通过veth pair的形式与 cni0 相连

# 进入容器内,查看容器内eth0网卡关联宿主机veth对的id号,如果结果为61
cat /sys/class/net/eth0/iflink  

# 那么去宿主机上,执行可以找到一个编号为61的veth对
ip link show 

4、flannel.1 则是一个VXLAN类型的设备,充当VTEP的角色,实现对VXLAN报文的封包解包。.1后缀代表封包的vni id为1,这表示flannel的vxlan模式下所有pod都在一个二层网络里

从内核3.7版本开始,Linux就开始支持VXLAN,到3.12版本,支持已经完备。

3.1.2 节点内通信

显然,节点内的容器间通信通过 cni0 网桥就能完成,不涉及任何VXLAN报文的封包解包。例如在下面的图例中,Node1的子网为10.244.0.1/24, PodA 10.244.0.20 和 PodB 10.224.0.21通过 cni0 网桥实现互通。

image-20260705123326719

3.1.3 跨节点通信

下面重点分析一下跨节点的容器通信过程。假设有两个节点Node1和Node2,其中Node1的PodA要跟Node2的PodB通信,则它们之间的通信过程如下图所示:(了解:下面的UDP端口默认为 8472)

FDB Table

Node1

image-20260705123351632

Routing Table

DestinationNexthopInterface
10.244.0.0/24cni0
10.244.1.0/2410.244.1.0flannel.1

Node2

Routing Table

DestinationNexthopInterface
10.244.1.0/24cni0
10.244.0.0/2410.244.0.0flannel.1

ARP Table

IPMAC
10.244.1.0Node2flannel.1_MAC
IPMAC
10.244.0.0Node1flannel.1_MAC
MACDevDst
Node2flannel.1_MACflannel.1192.168.50.3

FDB Table

MACDevDst
Node1flannel.1_MACflannel.1192.168.50.2

大致概括一下整个过程:

发送端:在PodA中发起 ping 10.244.1.21 , ICMP 报文经过 cni0 网桥后交由 flannel.1 设备处理。 flannel.1 设备是一个VXLAN类型的设备,负责VXLAN封包解包。 因此,在发送端,flannel.1 将原始L2报文封装成VXLAN UDP报文,然后从 eth0 发送。

接收端:Node2收到UDP报文,发现是一个VXLAN类型报文,交由 flannel.1 进行解包。根据解包后得到的原始报文中的目的IP,将原始报文经由 cni0 网桥发送给PodB。

哪些IP要交由 flannel.1 处理

flanneld 从 etcd 中可以获取所有节点的子网情况,以此为依据为各节点配置路由,将属于非本节点的子网IP都路由到 flannel.1 处理,本节点的子网路由到 cni0 网桥处理。

[root@Node1 ~]# ip r # 即ip route
...
10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1 
# Node1子网为10.224.0.0/24,本机PodIP都交由cni0处理

10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink
# Node2子网为10.224.1.0/24,Node2的PodID都交由flannel.1处理
...

如果节点信息有变化, flanneld 也会同步的对路由信息做修改。

3.1.4 flannel.1 的封包过程

VXLAN的封包是将二层以太网帧封装到四层UDP报文中的过程。

1、原始L2帧

要生成原始的L2帧, flannel.1 需要得知:

内层源/目的IP地址

内层源/目的MAC地址

内层的源/目的IP地址是已知的,即为PodA/PodB的PodIP,在图例中,分别为10.224.0.20和10.224.1.20。

内层源/目的MAC地址要结合路由表和ARP表来获取。根据路由表①得知:

  1. 下一跳地址是10.224.1.0,关联ARP表②,得到下一跳的MAC地址,也就是目的MAC地址:Node2_flannel.1_MAC ;

  2. 报文要从 flannel.1 虚拟网卡发出,因此源MAC地址为 flannel.1 的MAC地址。

要注意的是,这里ARP表的表项②并不是通过ARP学习得到的,而是 flanneld 预先为每个节点设置好的,由 flanneld负责维护,没有过期时间。

# 查看ARP表----》ip n
[root@Node1 ~]# ip n | grep flannel.1
10.244.1.0 dev flannel.1 lladdr ba:74:f9:db:69:c1 PERMANENT # PERMANENT 表示永不过期

有了上面的信息, flannel.1 就可以构造出内层的2层以太网帧:

Inner MAC HeaderInner IP HeaderData
Src MAC: Node1_flannel.1_MACDst MAC: Node2_flannel.1_MACSrc IP: 10.244.0.20Dst IP: 10.244.1.20ICMP

2、外层VXLAN UDP报文

要将原始L2帧封装成VXLAN UDP报文, flannel.1 还需要填充源/目的IP地址。前面提到,VTEP是VXLAN隧道的起点或终点。因此,目的IP地址即为对端VTEP的IP地址,通过FDB表获取。在FDB表③中,dst字段表示的即为VXLAN隧道目的端点(对端VTEP)的IP地址,也就是VXLAN UDP报文的目的IP地址。FDB表也是由 flanneld 在每个节点上预设并负责维护的。

FDB表(Forwarding database)用于保存二层设备中MAC地址和端口的关联关系,就像交换机中的MAC地址表一样。在二层设备转发二层以太网帧时,根据FDB表项来找到对应的端口。例如cni0网桥上连接了很多veth pair网卡,当网桥要将以太网帧转发给Pod时,FDB表根据Pod网卡的MAC地址查询FDB表,就能找到其对应的veth网卡,从而实现联通。

可以使用 bridge fdb show 查看FDB表:

[root@Node1 ~]# bridge fdb show | grep flannel.1
ba:74:f9:db:69:c1 dev flannel.1 dst 192.168.50.3 self permanent
12 

源IP地址信息来自于 flannel.1 网卡设置本身,根据 local 192.168.50.2 可以得知源IP地址为192.168.50.2。

[root@Node1 ~]# ip -d a show flannel.1
6: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state
UNKNOWN group default
    link/ether 32:02:78:2f:02:cb brd ff:ff:ff:ff:ff:ff promiscuity 0
    vxlan id 1 local 192.168.50.2 dev eth0 srcport 0 0 dstport 8472 nolearning ageing 300 noudpcsum noudp6zerocsumtx noudp6zerocsumrx numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
    inet 10.244.0.0/32 brd 10.244.0.0 scope global flannel.1
    valid_lft forever preferred_lft forever
    inet6 fe80::3002:78ff:fe2f:2cb/64 scope link
    valid_lft forever preferred_lft forever 

至此, flannel.1 已经得到了所有完成VXLAN封包所需的信息,最终通过 eth0 发送一个VXLAN UDP报文:

image-20260705130418257

Flannel的VXLAN模式通过静态配置路由表,ARP表和FDB表的信息,结合VXLAN虚拟网卡 flannel.1,实现了一个所有Pod同属一个大二层网络的VXLAN网络模型。

3.1.5 总结

flannel的vxlan模式核心三张表

由flanneld进程维护,从etcd获取所有pod网络信息写入下面三张表

1.路由表:(拦截内核包请求,给flannel设备处理)本地网段,交给cni0其他网段交给flannel

2.arp表:(永久的不过期)找到去往目标网段对应的flannel.1设备的mac地址------》用于封装内层Original L2 Frame

3.fdb表根据flannel.1的mac地址定位到他在哪台物理机的ip地址-----》用于封装外层Outer IP Header

3.2 host-gw模式

3.2.1 关键点

1、host-gw的核心原理就是由flanneld守护进程再每台物理节点上路由,以实现pod的跨主机通信,每台物理节点都充当容器通信的“网关”。这也正是“host-gw”的含义。

2、Flannel 子网和主机的信息保存在 Etcd。flanneld 只需要 WACTH 数据的变化实时更新路由表即可。

3、Flannel host-gw 模式必须要求集群宿主机之间是二层连通的。 因为目的IP是容器的,在网络中可无法通过arp找到容器的mac地址,只能通过目的mac地址在二层直接通信

4、要使用host-gw模式,需要修改 ConfigMap kube-flannel-cfg ,将 Backend.Type 从vxlan改为host-gw,然后重启所有kube-flannel Pod即可:

[root@k8s-master-01 ~]# kubectl -n kube-flannel edit cm kube-flannel-cfg
...
net-conf.json: |
{
    "Network": "10.244.0.0/16",
    "Backend": {
    "Type": "host-gw" // <- 改成host-gw
    }
}
...
# 或者在部署之初指定
wget https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

[root@master01 flannel]# vim kube-flannel.yml

apiversion: v1
data:
...
net-conf.json: |
{
    "Network": "10.244.0.0/16", # 与--pod-network-cidr保持一致
    "Backend": {
    "Type": "vxlan"
    }
}

3.2.2 节点内通信

与vxlan一样,都是通过cni0网桥通信。

3.2.3 跨节点通信

host-gw模式下的通信过程如下图所示:

image-20260705130702824

DestinationNexthopInterface
10.244.0.0/24cni0
10.244.1.0/24192.168.50.3eth0
DestinationNexthopInterface
10.244.1.0/24cni0
10.244.0.0/24192.168.50.2eth0

在host-gw模式下,由于不涉及VXLAN的封包解包,不再需要flannel.1虚机网卡。 flanneld 负责为各节点设置路由 ,将对应节点Pod子网的下一跳地址指向对应的节点的IP,如图中路由表①所示。

[root@Node1 ~]# ip r
...
10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1
10.244.1.0/24 via 192.168.50.3 dev eth0 # Node2子网的下一跳地址指向Node2的public ip。
...
12345

image-20260705130740267

default via 10.168.0.1 dey eth0 10.244.1.0/24 dey cni0 proto kernel scope link src 10.244.1.1 10.244.0.0/24 via 10.168.0.2 dev eth0 10.168.0.0/24 dev eth0 proto kernel scope link src 10.168.0.3

image-20260705130750036

由于没有像vxlan那样的封包解包带来的消耗,host-gw是性能最好的。不过一般在云环境下,都不支持使用host-gw的模式,在私有化部署的场景下,可以考虑。

四、calico网络

4.1 BGP模式

4.1.1 BGP介绍

Calico的BGP模式 与 Flannel 的 host-gw 模式几乎一样。也会在宿主机上,添加一个格式如下所示的路由规则:

< 目的容器 IP 地址段 > via < 网关的 IP 地址 > dev eth0

网关的 IP 地址是目的容器所在宿主机的 IP 地址。

不同于 Flannel 通过 Etcd 和宿主机上的 flanneld 维护路由信息的做法,Calico 使用“BGP”来自动地在整个集群中分发路由信息。

BGP 即:边界网关协议。专门用在大规模数据中心里维护不同的“自治系统”之间路由信息的、无中心的路由协议。

image-20260705130851151 image-20260705130901552

图中有两个自治系统:AS 1 和 AS 2。自治系统指一个组织管辖下的所有 IP 网络和路由器的全体。可以想象成一个小公司里的所有主机和路由器。正常情况下自治系统不会有“来往”。

但如果两个 AS 里的主机要通过 IP 地址进行通信,就必须使用路由器把这两个自治系统连接。

AS 1 的主机 10.10.0.2 要访问 AS 2 的主机 172.17.0.3。IP 包就会先到达路由器 Router 1。

Router 1 的路由表里有这样一条规则,即:目的地址是 172.17.0.2 包,应该经过 C 接口,发往网关Router 2。

IP 包到达 Router 2,经过 Router 2 的路由表从 B 接口出来到达目的主机 172.17.0.3。

但如果 172.17.0.3 要访问 10.10.0.2,IP 包到达 Router 2 后不知道该去哪了。 Router 2 的路由表里没有关于 AS 1 的路由规则。

所以网管应该给 Router 2 添加一条路由规则,比如:目标地址是 10.10.0.2 的 IP 包,应该经过 C 接口发往网关 Router 1。

像这样负责把 AS 连接在一起的路由器称为:边界网关。跟普通路由器的不同之处在于它的路由表里拥有其他 AS 里的主机路由信息。

在复杂的网络结构中如果还依靠人工来对边界网关的路由表进行配置和维护,那是绝对不现实的。

这时 BGP 大显身手的时刻就到了。 BGP 在每个边界网关上运行一个程序,将各自的路由表信息,通过TCP 传输给其他的边界网关。其他边界网关上的这个程序对收到的数据进行分析,将需要的信息添加到自己的路由表。

图中 Router 2 的路由表里,就会自动出现 10.10.0.2 和 10.10.0.3 对应的路由规则了。

所谓 BGP,就是在大规模网络中实现节点路由信息共享的一种协议。

而 BGP 正好可以取代 Flannel 维护主机上路由表的功能。BGP 这种原生就是为大规模网络环境而实现的协议,其可靠性和可扩展性,远非 Flannel 自己的方案可比。

3.1.2 BGP模式的三个组成部分

一个完整的bgp网络拓扑如下,主要由三个部分组成:

  1. Calico 的 CNI 插件(注意是插件,不是cni0网桥哈)。这是 Calico 与 K8s 对接的部分。

  2. Felix:是一个守护程序(在k8s中以DeaemonSe部署),以agent形式运行在宿主机上,负责在宿主机上插入路由规则(即:写入 Linux 内核的 FIB 转发信息库),以及维护 Calico 所需的网络设备等工作。

  3. BIRD。BGP 的客户端,作用是读取Felix编写到内核中的路由信息,并将路由信息分发到集群中的其他节点。

image-20260705130954282

除了对路由信息的维护方式之外,Calico 与 Flannel 的 host-gw 模式的另一个不同之处就是它不会在宿主机上创建任何网桥设备。Calico 的工作方式如下

Calico 的 CNI 插件会为每个容器设置一个 Veth Pair 设备,把其中的一端放置在宿主机上

由于 Calico 没有使用 CNI 的网桥模式,所以Calico 的 CNI 插件还需要在宿主机上为每个容器的 VethPair 设备配置一条路由规则,用于接收传入的 IP 包。比如,宿主机 Node 2 上的 Container 4 对应的路由规则,如下所示:

10.233.2.3 dev cali5863f3 scope link

即:发往 10.233.2.3 的 IP 包,应该进入 cali5863f3 设备。

基于上述原因,Calico 在宿主机上设置的路由规则,要比 Flannel 多得多。不过 host-gw 模式使用 CNI 网桥的主要原因是为了跟 VXLAN 模式保持一致。否则需要维护两套 CNI 插件。

有了 Veth Pair 后,容器发出的 IP 包经过 Veth Pair 出现在宿主机上。宿主机根据路由规则的下一跳 IP地址,把它们转发给正确的网关。接下来的流程就跟 Flannel host-gw 模式完全一致了。

最核心的“下一跳”路由规则,就是由 Calico 的 Felix 进程负责维护的。这些路由规则信息,则是通过 BGP Client 也就是 BIRD 组件,使用 BGP 协议传输而来的。

而通过 BGP 协议传输的消息可以简单地理解为如下格式:

[BGP 消息]我是宿主机 192.168.1.310.233.2.0/24 网段的容器都在我这里这些容器的下一跳地址是我

Calico 实际上将集群里的所有节点都当作是边界路由器来处理,一起组成了一个全连通的网络,互相之间通过 BGP 协议交换路由规则。这些节点称为 BGP Peer。

4.1.3 BGP Route Reflector

Calico 维护的网络在默认配置下,是一个被称为“Node-to-Node Mesh/网格”的模式。每台宿主机上的BGP Client 都需要跟其他所有节点的 BGP Client 通信以交换路由信息。随着节点 N 的增加,连接数量就会以 N²的规模快速增长,给集群网络带来巨大的压力。

在更大规模的集群中,用到的是 Route Reflector 的模式。

在这种模式下,Calico 会指定一个或者几个专门的节点负责跟所有节点建立 BGP 连接从而学习到全局的路由规则。而其他节点只需跟这几个节点交换路由信息,就可以获得整个集群的路由规则信息了。

Route Reflector 节点扮演了“中间代理”的角色,从而把 BGP 连接的规模控制在 N 的数量级上。

强调一个点:Flannel host-gw 模式最主要的限制就是要求集群宿主机之间是二层连通的。而这个限制对于 Calico 也同样存在。

4.1.4 BGP数据流分析

image-20260705131112119

如上图所示,以容器1访问容器2为例,先进入容器1查看它的路由表和网络配置。

root@nginx-deployment-6b474476c4-p82xb:/# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
    valid_lft forever preferred_lft forever
3: eth0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1440 qdisc noqueue state UP group default
    link/ether f6:64:05:f3:4b:be brd ff:ff:ff:ff:ff:ff link-netsid 0
    inet 172.16.123.5/32 brd 172.16.123.5 scope global eth0
    valid_lft forever preferred_lft forever
4: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0 

可以看到容器1的IP地址为172.16.123.5/32,而MAC地址为ff:ff:ff:ff:ff:ff,这明显是一个固定的MAC地址,因为Calico不关心二层的MAC地址,只关心三层的IP地址。

当容器1执行ping 172.16.215.3/32时,因为两者不在同一网络内,所以容器1会查看自己的路由表。

root@nginx-deployment-6b474476c4-p82xb:/# ip route default via 169.254.1.1 dev eth0 169.254.1.1 dev eth0 scope link 

容器的的所有报文都会通过eth0发送到下一跳169.254.1.1,容器需要知道下一跳的MAC地址,那ARP请求发送到哪里那。eth0是veth pair 的一端,那它的另一端在哪那。我们可以通过ethtool -S eth0列出对端网卡的index。

root@nginx-deployment-6b474476c4-p82xb:/# ethtool -S eth0
NIC statistics:
peer_ifindex: 4
rx_queue_0_xdp_packets: 0
rx_queue_0_xdp_bytes: 0
rx_queue_0_xdp_drops: 0 

主机上index为4的网卡

root@work2:/home/work2/Desktop# ip addr
4: cali85354e41ec1@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1440 qdisc noqueue state UP group default
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::ecee:eeff:feee:eeee/64 scope link
    valid_lft forever preferred_lft forever 

这块网卡并没有IP第地址,它接收到查询169.254.1.1的ARP报文会进行"代答",即将自己的MAC地址告诉容器1,后续报文则会转发给主机处理。

root@work2:/home/work2/Desktop# cat /proc/sys/net/ipv4/conf/cali85354e41ec1/proxy_arp 1 

主机上的calixx网卡接收到报文后,再根据路由表转发

root@work2:/home/work2/Desktop# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.2.2 0.0.0.0 UG 100 0 0 ens33
169.254.0.0 0.0.0.0 255.255.0.0 U 1000 0 0 ens33
172.16.123.0 0.0.0.0 255.255.255.192 U 0 0 0 *
172.16.123.5 0.0.0.0 255.255.255.255 UH 0 0 0
cali85354e41ec1
172.16.123.6 0.0.0.0 255.255.255.255 UH 0 0 0
cali35e3a5cb80b
172.16.215.0 192.168.2.132 255.255.255.192 UG 0 0 0 tun10
172.16.243.192 192.168.2.131 255.255.255.192 UG 0 0 0 tun10
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
192.168.2.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33 

接下来数据包会通过tun0设备发送到下一跳,也就是容器2的宿主机192.168.2.132/32。当数据包到达宿主机时,会先经过iptables规则,如果被iptables拦截那么数据包将会被丢弃。然后通过路由表转发,接着从 cali34af9914629 网卡,发送到容器2。

# 容器2宿主机路由表
root@work1:/home/work1/Desktop# route -n
Kernel IP routing table
DestinationGatewayGenmaskFlagsMetricRefUse Iface
0.0.0.0192.168.2.20.0.0.0UG10000 ens33
169.254.0.00.0.0.0255.255.0.0U100000 ens33
172.16.123.0192.168.2.133255.255.255.192UG000 tun10
172.16.215.00.0.0.0255.255.255.192U000 *
172.16.215.30.0.0.0255.255.255.255UH000
cali34af9914629
172.16.243.192192.168.2.131255.255.255.192UG000 tun10
172.17.0.00.0.0.0255.255.0.0U000 docker0
192.168.2.00.0.0.0255.255.255.0U10000 ens33

4.2 IPIP隧道

calico的BGP模式和flannel的host-gw一样要求二层网络是可达的,但是在实际的部署中集群的网络通常划分在不同的网段中,即物理层面只能是三层连通的,calico如何处理跨网段通信那。在这种情况下就需要为Calico打开IPIP模式。

在IPIP模式下,calico会使用tunl0设备,tunl0是一个ip隧道设备,通过上面的路由表发现,IP包会被tunl0设备接管。tunl0设备会将IP包封装为宿主机IP包。经过封装后的IP包,如下图所示

Out IP Header192.168.3.132
IP Header172.16.215.3
IP Payload

这样新的IP包就可以通过宿主机路由器发送了,如下图所示

image-20260705131142937

Cailco在IPIP模式下会进行封包,也会对性能造成一定的影响,但是相对Flannel的VXLAN模式来说要好一些,但是维护成功显然更高一些。

五、总结

5.1 解答开头提的问题

PodA的ip地址为:10.244.0.20/24

PodB的ip地址为:10.244.1.20/24

问:为何PodA内可以ping通PodB的ip地址,而不需要任何路由转发,而这两个地址明明看起来是两个网段的地址

之前在https://egonlin.com/?p=223做路由管理3.6实验时就重点提过,只要包能送到目标主机,目标主机只会看自己的网卡上是否有这个ip地址,如果有就任何是在访问自己,目标主机压根不会管这个包是从哪个网段来的。我们此处是通过gre或vxlan技术把10.244.0.20---->10.244.1.20的包送到了目标pod里,只要可以送过去,目标pod内看到目标ip也是自己,那就能通信。所以你自己好好克服下自己曾经对网络的误解。egon教你一句话:管他娘的在不在一个网段里,只要能把包送过去就行。

事实上你发送任何网络包都是这个道理。

综上,虽然k8s为每个节点都分配了一个网段,但我们是通过例如vxlan技术将包送达对方,所以你会发现不同网段的pod仍然可以直接二层通信

练习与验证

[root@k8s-master-01 ~]# #kubectl run -ti --image=centos:latest test --rm --restart=Never sh
[root@k8s-master-01 ~]# kubectl run --image=centos:latest test sleep 1000 pod/test created 
[root@k8s-master-01 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
NOMINATED NODE READINESS GATES
test 1/1 Running 0 3s 10.244.2.56 k8s-node-
02 <none> <none>
web-65d5f9fd58-gtzm7 1/1 Running 0 26h 10.244.1.33 k8s-node-
01 <none> <none>
web-65d5f9fd58-rrh92 1/1 Running 0 26h 10.244.2.54 k8s-node-
02 <none> <none>
[root@k8s-master-01 ~]# kubectl exec -ti test sh # 该pod的ip段是10.244.2.56
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future
version. Use kubectl exec [POD] -- [COMMAND] instead.
sh-4.4# ping 10.244.1.33 # 访问10.244.1.33段是通的
PING 10.244.1.33 (10.244.1.33) 56(84) bytes of data.
64 bytes from 10.244.1.33: icmp_seq=1 ttl=62 time=25.6 ms
64 bytes from 10.244.1.33: icmp_seq=2 ttl=62 time=0.316 ms
64 bytes from 10.244.1.33: icmp_seq=3 ttl=62 time=0.356 ms
^C

5.2 gre与vxlan

gre

优点:更通用,可以封装任意协议

缺点:每新增一个物理节点,所有相关节点都需要与其建立GRE隧道链接,节点很多时会性能会有问题

gre用br-tun设备来完成封包解包

vxlan

优点:基于udp协议的转发,gre是有状态的,而vxlan是无状态的,vni标记一个组,VXLAN要求每个VNI都关联一个组播地址。所以对于一次ARP请求,A的VTEP会的发送一个组播IGMP报文给所有同在这个网络组中的其他VTEP(详见文档)

缺点:

不够通用,主要用于封装、转发2层报文

vlxan用vtep设备来实现vxlan的封包与解包

vlxan

VXLAN是Linux内核本身支持的一种网络虚拟化技术,是内核的一个模块,在内核态实现封装解封装,构建出覆盖网络,其实就是一个由各宿主机上的Flannel.1设备组成的虚拟二层网络。

• 由于VXLAN由于额外的封包解包,导致其性能较差,所以Flannel就有了host-gw模式,即把宿主机当作网关,除了本地路由之外没有额外开销,性能和calico差不多,由于没有叠加来实现报文转发,这样会导致路由表庞大。因为一个节点对应一个网络,也就对应一条路由条目。

• host-gw虽然VXLAN网络性能要强很多。,但是种方式有个缺陷:要求各物理节点必须在同一个二层网络中。物理节点必须在同一网段中。这样会使得一个网段中的主机量会非常多,万一发一个广播报文就会产生干扰。在私有云场景下,宿主机不在同一网段是很常见的状态,所以就不能使用host-gw了。

• VXLAN还有另外一种功能,VXLAN也支持类似host-gw的玩法,如果两个节点在同一网段时使用host-gw通信,如果不在同一网段中,即 当前pod所在节点与目标pod所在节点中间有路由器,就使用VXLAN这种方式,使用叠加网络。 结合了Host-gw和VXLAN,这就是VXLAN的Direct routing模式

5.3 Flannel与Calico对比

5.3.1 flannel

Flannel为Kubernetes提供叠加网络的网络插件,基于TUN/TAP隧道技术,使用UDP封装IP报文进行创建叠加网络,借助etcd维护网络的分配情况,缺点:无法支持网络策略访问控制。

Flannel支持vxlan隧道,host-gw路由

(1)vxlan模式在主机规模小的时候可以用,主机只需要三层连同即可,但vxlan封包解包效率低,且不支持网络策略

(2)可以在配置文件中开启直接路由,这样如果主机节点在二层网络中,则使用类似host-gw的直接路由模式,直接走eth0网卡,而不是flanne

5.3.2 calico

caclio:ipip隧道,BGP路由(效率高,但是需要往主机添加路由,云主机不支持)

Calico:基于BGP的三层网络插件,也支持网络策略进而实现网络的访问控制;它在每台主机上都运行一个虚拟路由,利用Linux内核转发网络数据包,并借助iptables实现防火墙功能。

实际上Calico最后的实现就是将每台主机都变成了一台路由器,将各个网络进行连接起来,实现跨主机通信的功能。

Canal:由Flannel和Calico联合发布的一个统一网络插件,提供CNI网络插件,并支持网络策略实现。

posted @ 2026-07-05 18:07  cuckooyang  阅读(20)  评论(0)    收藏  举报