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如何通信。

相关推荐
大江东去了吗3 小时前
k8s HPA
云原生·容器·kubernetes
花菜回锅肉4 小时前
开源可视化大屏superset Docker环境部署
数据仓库·docker·容器·开源·superset
wd5205214 小时前
常用环境部署(十七)——Docker安装pritunl+openvpn
运维·docker·容器
Watermelo6175 小时前
在线编程实现!如何在Java后端通过DockerClient操作Docker生成python环境
java·spring boot·docker·微服务·云原生·容器·devops
Richardlygo6 小时前
编写Dockerfile第二版
docker·容器
码农阿豪6 小时前
OpenObserve云原生可观测平台本地Docker部署与远程访问实战教程
docker·云原生·容器
是芽芽哩!6 小时前
【Kubernetes】常见面试题汇总(五)
云原生·容器·kubernetes
何遇mirror6 小时前
深入解析 Docker exec 命令
容器
芦苇浮绿水8 小时前
记一次升级 Viper、ETCD V3操作Toml
数据库·kubernetes·go·etcd
golove66610 小时前
Docker进入容器运行命令
docker·容器