Kubernets(k8s) 网络原理三:同主机内Pod相互访问

前两篇文章中我们介绍了pod怎么和宿主机通信以及pod怎么访问外网,这两种通信是理解pod间通信的基础。

关于pod间的相互访问,这里还需要细化一下。回想一下pod在k8s节点中的分布,两个pod可能分布在同一台宿主机上,也可能分布在不同宿主机上。这两种不同的拓扑结构,其组网模型也不一样。

本文先介绍第一种,同宿主内pod间如何访问

同宿主内pod间如何访问

关于同主机下的pod如何访问,有多种技术可以实现,因为这个pod发送的数据包,无论如何流转,始终不会出宿主机。

正如前文所说,要分析网络,总是离不开网络设备和路由表。

veth pair

既然之前文章我们说过,veth pair可以实现跨network namespace通信,最简单的我们可以用veth pair来做这件事情。

回想第一篇文章,我们的veth pair一端在pod的network namespace,另外一端在宿主机上,也就是root network namespace。如果我们将另外一端放到要通信pod的network namespace中不就可以了吗?

没错,这种模型完全可以。并且也很简单,但是要维护它却很困难。

因为这种模型类似一个点对点的结构,它要求pod两两之间都需要有veth pair。

试想,如果同一个主机内存在N个Pod。那么每个pod ns中都会存在N-1个veth设备,以及N-1条路由规则,整个主机上存在N*N个veth pair。

那你可能会说了,node节点默认可运行的pod数目上限是110个,也不是天文数字,似乎也能接受

确实,但再试想一个场景,当一个pod的IP发生变化的时候,你需要做什么?这时候可能就需要修改其他所有pod的路由规则。

这样一想,似乎就不太好接受了,veth pair的数量和路由表的维护是一个麻烦事。

那如果有个角色,能够将这些veth pair 束口呢?


bridge

接着刚才那个问题,熟悉linux设备的同学可能马上会想到,Liunx网桥不就能干这件事情吗?

确实可以,Linux bridge可以让这些网络设备作为一个端口连接到网桥上来,如下图所示:

在这里,Linux bridge充当了一个二层交换机的角色。负责将来自不同veth设备的流量进行交换和转发。

网桥的定义 :网桥是一个工作在数据链路层的网络设备,可以连接多个局域网段,它能够根据MAC地址来转发帧,使得多个网络段上的设备能够像在同一个网络中一样进行通信。
Linux网桥:在Linux系统中,网桥是通过软件模拟实现的。它允许系统管理员创建一个逻辑上的交换机,可以绑定多个物理或虚拟网络接口。

这个模型很简单,它解决了第一种模型下veth pair数量多和需要维护路由的问题。

还是以同一个主机下N个Pod为例,在这个模型下,每个Pod的network namespace下只需要存在一个veth设备,整个主机也只需要N个veth pair。同时pod间的路由规则不需要任何路由表来记录。

没了路由表,Linux bridge怎么知道发送给谁呢?

答案是广播和转发表

广播的含义是当Linux bridge一个端口收到报文,而转发表中没有目标地址的MAC时,就会广播所有连接到bridge上的网络接口。

转发表的含义是bridge会记录所有经过的报文的MAC地址,它是自学习的。

有了linux bridge,大大减少了管理的复杂度,下面通过一个小实验,感受一个pod间如何通信。

实验

本实验用到的命令在Kubernets(k8s) 网络原理一:Pod与宿主机通信文章中有简单介绍

如无特殊说明,我们实验都用network namespace代指pod

创建pod-1和pod-2

bash 复制代码
# ip netns add pod-1

# ip netns add pod-2

创建eth0和veth1,并将eth0加入pod-1 ns

bash 复制代码
# ip link add eth0 type veth peer name veth1

# ip link set eth0 netns pod-1

创建eth0和veth2,并将eth0加入pod-2 ns

bash 复制代码
# ip link add eth0 type veth peer name veth2

# ip link set eth0 netns pod-2

分别给pod-1和pod-2中的eth0配置ip

bash 复制代码
# ip netns exec pod-1 ip addr add 10.10.1.10/32 dev eth0

# ip netns exec pod-2 ip addr add 10.10.1.11/32 dev eth0

启动pod-1中veth pair,并添加路由,为什么添加路由请查看前一篇文章

bash 复制代码
# ip netns exec pod-1 ip link set eth0 up

# ip link set veth1 up

# ip netns exec pod-1  ip ro add default dev eth0

启动pod-2中veth pair,并添加路由

bash 复制代码
# ip netns exec pod-2 ip link set eth0 up

# ip link set veth2 up

# ip netns exec pod-2 ip ro add default dev eth0

创建网桥cbr0并启动,需要安装bridge-utils

bash 复制代码
# brctl addbr cbr0

# ip link set cbr0 up

将vteh1和veth2 添加进网桥

bash 复制代码
# brctl addif cbr0 veth1

# brctl addif cbr0 veth2

查看vteh1和veth2是否正确添加到网桥

bash 复制代码
# brctl show
bridge name     bridge id               STP enabled     interfaces
cbr0            8000.d6821b81f256       no              veth1
                                                        veth2

准备工作就绪,此时网络拓扑如下

我们尝试在pod-1中ping pod-2来模拟通信

bash 复制代码
# ip netns exec pod-1 ping -c 1 10.10.1.11
PING 10.10.1.11 (10.10.1.11) 56(84) bytes of data.
64 bytes from 10.10.1.11: icmp_seq=1 ttl=64 time=0.033 ms

--- 10.10.1.11 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.033/0.033/0.033/0.000 ms

一切顺利,可以ping通,我们抓包看看这个过程发生了什么

bash 复制代码
# ip netns exec pod-1 tcpdump -pne -i eth0
12:20:24.329781 12:35:c3:bb:69:bc > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 10.10.1.11 tell 10.10.1.10, length 28
12:20:24.329812 7e:55:17:b0:dc:bb > 12:35:c3:bb:69:bc, ethertype ARP (0x0806), length 42: Reply 10.10.1.11 is-at 7e:55:17:b0:dc:bb, length 28
12:20:24.329814 12:35:c3:bb:69:bc > 7e:55:17:b0:dc:bb, ethertype IPv4 (0x0800), length 98: 10.10.1.10 > 10.10.1.11: ICMP echo request, id 48389, seq 1, length 64
12:20:24.329827 7e:55:17:b0:dc:bb > 12:35:c3:bb:69:bc, ethertype IPv4 (0x0800), length 98: 10.10.1.11 > 10.10.1.10: ICMP echo reply, id 48389, seq 1, length 64
12:20:29.340282 7e:55:17:b0:dc:bb > 12:35:c3:bb:69:bc, ethertype ARP (0x0806), length 42: Request who-has 10.10.1.10 tell 10.10.1.11, length 28
12:20:29.340285 12:35:c3:bb:69:bc > 7e:55:17:b0:dc:bb, ethertype ARP (0x0806), length 42: Reply 10.10.1.10 is-at 12:35:c3:bb:69:bc, length 28

查看MAC地址与端口的映射关系

bash 复制代码
# brctl showmacs cbr0
port no mac addr                is local?       ageing timer
  1     12:35:c3:bb:69:bc       no                 2.30
  2     7e:55:17:b0:dc:bb       no                 2.30
  1     d6:82:1b:81:f2:56       yes                0.00
  2     fe:c5:af:3f:03:b6       yes                0.00

值得说明的是,is local为no表示不是这个MAC地址不在root network namespace,上面回显中d6:82:1b:81:f2:56和fe:c5:af:3f:03:b6是veth1和veth2的mac地址,他们的is local为yes是因为veth1和veth2在root namespace。同时还可以看到相同端口号对应的正好是一对veth pair。

因此我们可以根据这两个信息总结关键流程如下:

  1. pod-1发起ICMP请求,因为无pod-2的MAC地址,于是先发起ARP请求
  2. pod-1 eth0发起ARP请求,希望获取10.1.10.11的mac地址
  3. cbr0收到广播的ARP请求,在转发表中记录MAC地址和端口
  4. 此时crb0中不存在pod-2中eth0的MAC地址,于是向所有将广播转发到其所绑定的所有端口,除了源veth
  5. pod-2 发现ieth0的ip刚好匹配,于是响应ARP请求,告诉自己的MAC地址
  6. crb0收到pod-2的ARP响应,在转发表中记录MAC地址和端口
  7. pod-1 向pod-2发起ICMP请求
  8. pod-2 响应ICMP请求

实验过程非常简单,这个模型能工作的原理就是利用了Linux bridge在二层通信。

实验环境清理

执行以下命令清理上文配置的设备

bash 复制代码
# ip netns del pod-1

# ip netns del pod-2

# ip link set cbr0 down

# brctl delbr cbr0

小结

整个模型非常简单,完全是利用Linux Bridge的特性,没有什么好总结的,既然不总结,那就提几个问题

  • veth1和veth2为什么不需要配置IP?
  • bridge模型下主机内pod通信需要主机的eth0参与吗?
  • 如果主机内pod通信不需要eth0参与,那什么时候需要它参与呢?
  • cbr0网桥什么时候需要配置IP?

这些问题可能没有一个唯一的答案,但是能够触发我们思考。

直接点就是想说,要分析一种网络模型,你必须要知道这个网络下的各种网络设备作用。以及当网络诉求变化时,相关网络设备应该如何变化。


route table

bridge可以充当束口的角色,但是如果不想引入Linux bridge这个网络设备,又该如何处理呢?

我们知道一个主机打开ip_forward转发的时候,他自己就可以转发报文。

转发报文也就意味着从一个网络接口,转给另外一个网络接口,这个好理解,也就是说只要将veth pair的另一端连接到root network namesapce,让主机就能看到这个网络设备就可以做到。

问题在于第二个,如何转发?

话都到这了,肯定会想到路由表嘛

没错,在这个模型下,就是让主机承担这个转发的任务,同时将这些veth pair收拢到root network namesapce里面来。

通信模型如下。

在这个模型下,省去了网桥概念,通信模型更为简单,但是需要维护root network namespace中的route规则。不过好在这不会太多。

还是以同一个主机下N个Pod为例,在这个模型下,每个Pod的network namespace下只需要存在一个veth设备,整个主机也只需要N个veth pair,此外root network namespace中还需要有N条路由规则。

同理,我们也进行一个小实验来模拟

实验

建议先查看Kubernets(k8s) 网络原理一:Pod与宿主机通信,理解network namespace怎么和主机通信

老规矩,我们创建pod-1和pod-2 network namespace代表两个pod

以路由的方式配置nework namespace在上一篇文章中已经介绍了每一条命令的作用,因此这里只罗列命令

配置pod-1

bash 复制代码
# ip netns add pod-1

# ip link add eth0 type veth peer name veth1

# ip link set eth0 netns pod-1

# ip netns exec pod-1 ip addr add 10.10.1.10 dev eth0

# ip netns exec pod-1 ip link set dev eth0 up

# ip netns exec pod-1 ip route add 169.254.1.1 dev eth0

# ip netns exec pod-1 ip route add default via 169.254.1.1 dev eth0

# ip link set dev veth1 up 

# echo 1 > /proc/sys/net/ipv4/conf/veth1/proxy_arp

# echo 1 > /proc/sys/net/ipv4/ip_forward

配置pod-2

bash 复制代码
# ip netns add pod-2

# ip link add eth0 type veth peer name veth2

# ip link set eth0 netns pod-2

# ip netns exec pod-2 ip addr add 10.10.1.11 dev eth0

# ip netns exec pod-2 ip link set dev eth0 up

# ip netns exec pod-2 ip route add 169.254.1.1 dev eth0

# ip netns exec pod-2 ip route add default via 169.254.1.1 dev eth0

# ip link set dev veth2 up 

# echo 1 > /proc/sys/net/ipv4/conf/veth2/proxy_arp

# echo 1 > /proc/sys/net/ipv4/ip_forward

然后在主机上配置路由表

bash 复制代码
# ip route add 10.10.1.10 dev veth1 scope link

# ip route add 10.10.1.11 dev veth2 scope link 

路由表的意思是说,所有发给10.10.1.10的报文都从veth1出去,同理,所有发给10.10.1.11的报文都从veth2 出去。

此时网络拓扑如下图所示:

此时我们尝试在pod-1中ping pod-2来模拟通信

bash 复制代码
# ip netns exec pod-1 ping -c 1 10.10.1.11
PING 10.10.1.11 (10.10.1.11) 56(84) bytes of data.
64 bytes from 10.10.1.11: icmp_seq=1 ttl=63 time=0.035 ms

--- 10.10.1.11 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.035/0.035/0.035/0.000 ms

没有问题,但是整个过程发生了什么呢,我们来抓包看一下

在pod-1中抓包

bash 复制代码
# ip netns exec pod-1 tcpdump -n -i eth0
09:53:29.297355 ARP, Request who-has 169.254.1.1 tell 10.10.1.10, length 28
09:53:29.382379 ARP, Reply 169.254.1.1 is-at be:7c:fa:0e:81:25, length 28
09:53:29.382386 IP 10.10.1.10 > 10.10.1.11: ICMP echo request, id 36618, seq 1, length 64
09:53:29.541810 IP 10.10.1.11 > 10.10.1.10: ICMP echo reply, id 36618, seq 1, length 64

这个过程其实我们已经在pod与宿主机通信文章中分析过了,这里简单梳理以下流程

  • pod-1中执行ping命令,通过socket调用给到pod-1协议栈
  • pod-1协议栈准备发起ICMP报文,查找路由表,获知从eth0出去,下一跳是169.254.1.1
  • pod-1协议栈查找Arp表,没有169.254.1.1的MAC地址,于是先发起Arp请求
  • veth1收到pod-1中eth0发来的Arp请求,因为配置了proxy_arp,于是响应自己的MAC地址
  • pod-1协议栈收到响应,组装ICMP报文,发送给veth-1
  • veth-1收到ICMP报文,交给自己的协议栈,即host协议栈
  • host协议栈因开启了ip_forward,于是查找本地路由表,发现应该从veth2设备发送
  • veth2和pod-2中eth0为veth pair关系,于是eth0收到pod-1发送的ICMP报文,交给pod-2协议栈
  • pod-2协议栈处理报文,按照原路返回响应报文。

小结

路由规则关心的是IP,而不是MAC地址,换句话说当跨物理网络时,路由模式的好处就显示出来了。虽然我们一台主机内不会存在跨网段,但是在后面我们总会用到的。

此外还值得说的一点是使用bridge的方式,Linux bridge会根据目的地址进行广播,所有设备都能收到,而L3 路由的方式则会隔离广播,请求的目的地址是固定的,因此广播限定在指定的广播域中,其它设备不会收到广播。

总结

本文分析两种主机内的组网模型,他们都可以达到主机内Pod相互通信的目的。只是采用不同的技术去实现,这两种方式没有严格的好坏之分,只是适用的场景不一样。

例如fiannel和docker就是采用bridge这种方式,依赖于一个网桥cni0(docker0)进行二层数据的转发。而calico就使用的L3路由的方式。

理解了主机内pod如何通信,在下一篇文章中,我们将分析整个容器组网最复杂的一部分,不同主机上的pod如何通信。

相关推荐
wuxingge3 小时前
k8s1.30.0高可用集群部署
云原生·容器·kubernetes
志凌海纳SmartX4 小时前
趋势洞察|AI 能否带动裸金属 K8s 强势崛起?
云原生·容器·kubernetes
锅总4 小时前
nacos与k8s service健康检查详解
云原生·容器·kubernetes
BUG弄潮儿5 小时前
k8s 集群安装
云原生·容器·kubernetes
Code_Artist5 小时前
Docker镜像加速解决方案:配置HTTP代理,让Docker学会科学上网!
docker·云原生·容器
何遇mirror5 小时前
云原生基础-云计算概览
后端·云原生·云计算
颜淡慕潇6 小时前
【K8S系列】kubectl describe pod显示ImagePullBackOff,如何进一步排查?
后端·云原生·容器·kubernetes
Linux运维日记7 小时前
k8s1.31版本最新版本集群使用容器镜像仓库Harbor
linux·docker·云原生·容器·kubernetes
一名路过的小码农9 小时前
ceph 18.2.4二次开发,docker镜像制作
ceph·docker·容器
AI_小站9 小时前
RAG 示例:使用 langchain、Redis、llama.cpp 构建一个 kubernetes 知识库问答
人工智能·程序人生·langchain·kubernetes·llama·知识库·rag