K8s Flannel vs Calico:基于 L2 与 L3 的 CNI 之战(一)

** 在童年期,我们更多是处于认知,而不是意欲的状态。—— 《作为意欲和表象的世界》第二卷第三十一章 **

写在前面


  • 博文内容涉及一些名词的前置知识点讲解,VETH,Bridge,IPIP,VXLAN 等
  • Calico 跨主机 Pod 之间链路跟踪
  • 以及Calico CNI kube-proxy 使用 iptables 的 链路跟踪
  • 篇幅问题,Flannel 部分放到下一篇讲解
  • 理解不足小伙伴帮忙指正

** 在童年期,我们更多是处于认知,而不是意欲的状态。—— 《作为意欲和表象的世界》第二卷第三十一章 **


K8s 的环境中。 CNI 常用的有 FlannelCalico ,今天我们来看看 FlannelCalico 是如何实现跨主机容器组网以及服务发布负载网络策略的。

简单总结:

在跨主机的容器网络方案中,Flannel 是使用 VXLAN 后端的覆盖网络(Overlay Network),基于L3构建的L2,可以单纯理解为通过三层UDP构建隧道传递以太网帧,而 Calico 是一个纯三层的路由网络模型(Route-based Network),利用Linux 内核技术 iptables路由表IPIPBGP协议来模拟传统的路由组网方案。

只有 Calico 支持ACL网络策略,可以配置 Pod 级别的 白名单黑名单,基于 iptables 实现。

名词解释:

在讨论下面两个 CNI 前,我们需要先了解一些基本名词

VETH Pair(虚拟网卡对):

VETH(Virtual Ethernet)对 是一种成对的网络设备,它们总是成对出现,一端连着网络命名空间(例如一个容器),另一端连着另一个网络命名空间。两个设备同时 UP 同时 DOWN,彼此之间通过内核软链接实现(类似虚拟的网线),常用于两个命名空间通信,或者一个网络命名空间和网桥通信。

docker 在默认网络驱动(桥接)的情况下,即经典容器组网,veth 对 +网桥 模式,基于这个特性实现单机 docker 的多个容器组网,安装 docker 会默认创建一个 桥接设备 docker0

容器AB 之间的通信即 容器 A网桥 docker0网桥 docerk0 在到容器 B,网桥这里可以理解为一个交换机,可以实现子网中任意网段互通。 容器到 网桥的连接即通过 veth 对 实现,一端在容器对应网络命名空间,一端在网桥。

容器 A 尝试与容器 B 通信时,它发送的数据包首先会被发送到与容器 A 相连的 VETH 设备的 docker0 网桥一端。

容器和主机之间通信也是同理,容器网络命名空间和主机的根网络命名空间之间通过网桥通信。

网桥(Bridge):

上面我们有讲到网桥设备,它充当着交换机的角色,负责将数据包从一个端口转发到另一个端口。

当数据包到达网桥时,网桥会根据目标IP地址,转发到对应的 端口,所以 在安装 docker 的时候,会默认创建一个 docker0 的网桥,同时需要修改内核参数,开启 ipv4 转发

桥接设备(Bridge Device)是在网络层次结构中工作的二层设备(Data Link Layer),它主要用于连接多个网络设备。桥接设备通过学习和转发数据帧的方式,将连接到它的网络设备组成一个共享的以太网段,使得这些设备可以直接通信。桥接设备工作在数据链路层(第二层),它不涉及 IP 地址或路由。基于 MAC 地址构建地址转发表

VETH对 + 网桥 组网 Demo

看一个实际的 Demo,使用Linux内部网桥实用程序创建网桥(vnet-br0),创建红色绿色两个网络名称空间。为redgreen命名空间创建两个veth虚拟网卡对,将veth对的一端连接到特定的命名空间,另一端连接到内部网桥,确保红色绿色命名空间中的接口可以于网桥(vnet-bro)与内部和外部网络通信。

创建两个网络命名空间,创建网桥vnet-br0

1
2
3
4
┌──[root@liruilongs.github.io]-[~]
└─$ip netns add red
┌──[root@liruilongs.github.io]-[~]
└─$ip netns add green

用于在 Linux 上创建一个名为 vnet-br0桥接设备

1
2
┌──[root@liruilongs.github.io]-[~]
└─$ip link add vnet-br0 type bridge

添加虚拟网卡对eth0-r 和 veth-reth0-g 和 veth-g

1
2
3
4
┌──[root@liruilongs.github.io]-[~]
└─$ip link add eth0-r type veth peer name veth-r
┌──[root@liruilongs.github.io]-[~]
└─$ip link add eth0-g type veth peer name veth-g

把两个虚拟网卡对中的一端放到上面创建的网络命名空间

1
2
3
4
┌──[root@liruilongs.github.io]-[~]
└─$ip link set eth0-r netns red
┌──[root@liruilongs.github.io]-[~]
└─$ip link set eth0-g netns green

然后将虚拟网卡对的另一端连接到vnet-br0桥。

1
2
3
4
┌──[root@liruilongs.github.io]-[~]
└─$ip link set veth-r master vnet-br0
┌──[root@liruilongs.github.io]-[~]
└─$ip link set veth-g master vnet-br0

查看根网络命名空间的桥接设备类型的网络设备(桥接表)。

1
2
3
4
┌──[root@liruilongs.github.io]-[~]
└─$ip link show type bridge
5: vnet-br0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 62:2b:41:f9:39:b3 brd ff:ff:ff:ff:ff:ff

查看桥接设备(vnet-br0)关联的网络设备。

1
2
3
4
5
6
┌──[root@liruilongs.github.io]-[~]
└─$ip link show master vnet-br0
6: veth-r@if7: <BROADCAST,MULTICAST> mtu 1500 qdisc noop master vnet-br0 state DOWN mode DEFAULT group default qlen 1000
link/ether 62:2b:41:f9:39:b3 brd ff:ff:ff:ff:ff:ff link-netns red
8: veth-g@if9: <BROADCAST,MULTICAST> mtu 1500 qdisc noop master vnet-br0 state DOWN mode DEFAULT group default qlen 1000
link/ether be:a3:9a:1c:a1:06 brd ff:ff:ff:ff:ff:ff link-netns green

根据输出,有两个网络设备与 vnet-br0 桥接设备关联:

veth-r@if7:这是一个虚拟网络设备(veth pair),它与 vnet-br0 桥接设备关联。它的状态是 DOWN,表示当前处于未激活状态。它的 MAC 地址为 62:2b:41:f9:39:b3。此设备属于 red 网络命名空间。

veth-g@if9:这是另一个虚拟网络设备(veth pair),也与 vnet-br0 桥接设备关联。它的状态是 DOWN,表示当前处于未激活状态。它的 MAC 地址为 be:a3:9a:1c:a1:06。此设备属于 green 网络命名空间。

激活桥接对应的网络设备

1
2
3
4
5
6
┌──[root@liruilongs.github.io]-[~]
└─$ip link set vnet-br0 up
┌──[root@liruilongs.github.io]-[~]
└─$ip link set veth-r up
┌──[root@liruilongs.github.io]-[~]
└─$ip link set veth-g up

激活 网络命名空间中的回环地址和对应的虚拟网卡对

1
2
3
4
5
6
7
8
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec red ip link set lo up
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec red ip link set eth0-r up
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec green ip link set lo up
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec green ip link set eth0-g up

ip link 确认设备状态

1
2
3
4
5
6
7
8
9
10
11
12
13
┌──[root@liruilongs.github.io]-[~]
└─$ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:93:51:67 brd ff:ff:ff:ff:ff:ff
altname enp3s0
5: vnet-br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 62:2b:41:f9:39:b3 brd ff:ff:ff:ff:ff:ff
6: veth-r@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master vnet-br0 state UP mode DEFAULT group default qlen 1000
link/ether 62:2b:41:f9:39:b3 brd ff:ff:ff:ff:ff:ff link-netns red
8: veth-g@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master vnet-br0 state UP mode DEFAULT group default qlen 1000
link/ether be:a3:9a:1c:a1:06 brd ff:ff:ff:ff:ff:ff link-netns green

进入网络命名空间 shell 环境,分配 IP, 给 red 命名空间分配IP 地址 192.168.20.2/24

1
2
3
4
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec red bash
┌──[root@liruilongs.github.io]-[~]
└─$ip address add 192.168.20.2/24 dev eth0-r

查看路由信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌──[root@liruilongs.github.io]-[~]
└─$ip r
# 对于目标网络 192.168.20.0/24 的数据包,它们将使用 eth0-r 设备进行本地通信。
192.168.20.0/24 dev eth0-r proto kernel scope link src 192.168.20.2
┌──[root@liruilongs.github.io]-[~]
└─$ip a
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
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
7: eth0-r@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether ca:b0:b2:80:25:43 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.20.2/24 scope global eth0-r
valid_lft forever preferred_lft forever
inet6 fe80::c8b0:b2ff:fe80:2543/64 scope link
valid_lft forever preferred_lft forever
┌──[root@liruilongs.github.io]-[~]
└─$exit
exit

对另一个命名空间操作,给 green 命名空间分配IP 地址 192.168.20.3/24

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec green bash
┌──[root@liruilongs.github.io]-[~]
└─$ip address add 192.168.20.3/24 dev eth0-g
┌──[root@liruilongs.github.io]-[~]
└─$ip a
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
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
9: eth0-g@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 36:5e:d9:8d:04:a8 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.20.3/24 scope global eth0-g
valid_lft forever preferred_lft forever
inet6 fe80::345e:d9ff:fe8d:4a8/64 scope link
valid_lft forever preferred_lft forever
┌──[root@liruilongs.github.io]-[~]
└─$ip r
192.168.20.0/24 dev eth0-g proto kernel scope link src 192.168.20.3
┌──[root@liruilongs.github.io]-[~]
└─$exit
exit

两个命名空间之间的连通性测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec green bash
┌──[root@liruilongs.github.io]-[~]
└─$ping -c 2 192.168.20.2
PING 192.168.20.2 (192.168.20.2) 56(84) bytes of data.
64 bytes from 192.168.20.2: icmp_seq=1 ttl=64 time=0.252 ms
64 bytes from 192.168.20.2: icmp_seq=2 ttl=64 time=0.047 ms

--- 192.168.20.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1034ms
rtt min/avg/max/mdev = 0.047/0.149/0.252/0.102 ms

┌──[root@liruilongs.github.io]-[~]
└─$exit
exit

我们在上面把两个命名空间的 veth pair 都配置到了网桥,所以可以直接通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec red bash
┌──[root@liruilongs.github.io]-[~]
└─$ping -c 2 192.168.20.3
PING 192.168.20.3 (192.168.20.3) 56(84) bytes of data.
64 bytes from 192.168.20.3: icmp_seq=1 ttl=64 time=0.241 ms
64 bytes from 192.168.20.3: icmp_seq=2 ttl=64 time=0.129 ms

--- 192.168.20.3 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1038ms
rtt min/avg/max/mdev = 0.129/0.185/0.241/0.056 ms
┌──[root@liruilongs.github.io]-[~]
└─$exit
exit

将 IP 192.168.20.1/24 分配给根网络命名空间中的 vnet-br0 桥接口,以允许来自红色和绿色名称空间的外部通信,它将成为该网络的默认网关(这里很重要哦)

1
2
┌──[root@liruilongs.github.io]-[~]
└─$ip address add 192.168.20.1/24 dev vnet-br0

192.168.20.1配置为绿色和红色命名空间中的默认网关。将所有目标不在本地网络中的数据包发送到该网关进行进一步路由。

1
2
3
4
5
6
7
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec red bash
┌──[root@liruilongs.github.io]-[~]
└─$route add default gw 192.168.20.1
┌──[root@liruilongs.github.io]-[~]
└─$exit
exit
1
2
3
4
5
6
7
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec green bash
┌──[root@liruilongs.github.io]-[~]
└─$route add default gw 192.168.20.1
┌──[root@liruilongs.github.io]-[~]
└─$exit
exit

即对应的网络命名空间内的流量都路由到网关,即网桥设备。

NAT 表中添加一个规则,将源 IP 地址为 192.168.20.0/24 的数据包进行源地址转换 (Source NAT),即 SNAT,否则可以出去但是找不到回家的路。

1
2
┌──[root@liruilongs.github.io]-[~]
└─$iptables -s 192.168.20.0/24 -t nat -A POSTROUTING -j MASQUERADE

根命名空间做内网和公网地址Ping

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌──[root@liruilongs.github.io]-[~]
└─$ping 192.169.26.149 -c 3
PING 192.169.26.149 (192.169.26.149) 56(84) bytes of data.
64 bytes from 192.169.26.149: icmp_seq=1 ttl=128 time=199 ms
64 bytes from 192.169.26.149: icmp_seq=2 ttl=128 time=199 ms
64 bytes from 192.169.26.149: icmp_seq=3 ttl=128 time=216 ms

--- 192.169.26.149 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 199.020/204.755/215.909/7.888 ms
┌──[root@liruilongs.github.io]-[~]
└─$ping baidu.com -c 3
PING baidu.com (39.156.66.10) 56(84) bytes of data.
64 bytes from 39.156.66.10 (39.156.66.10): icmp_seq=1 ttl=128 time=11.9 ms
64 bytes from 39.156.66.10 (39.156.66.10): icmp_seq=2 ttl=128 time=11.9 ms
64 bytes from 39.156.66.10 (39.156.66.10): icmp_seq=3 ttl=128 time=12.1 ms

--- baidu.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 11.919/11.999/12.142/0.100 ms

在主机系统上启用IPV4转发以允许外部通信。执行该命令后,系统将开启 IP 转发功能,允许数据包在不同的网络接口之间进行转发。

1
2
3
┌──[root@liruilongs.github.io]-[~]
└─$sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1

在两个命名空间中做内网 ping 测试

1
2
3
4
5
6
7
8
9
10
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec green ping baidu.com -c 3
PING baidu.com (110.242.68.66) 56(84) bytes of data.
64 bytes from 110.242.68.66 (110.242.68.66): icmp_seq=1 ttl=127 time=20.5 ms
64 bytes from 110.242.68.66 (110.242.68.66): icmp_seq=2 ttl=127 time=20.0 ms
64 bytes from 110.242.68.66 (110.242.68.66): icmp_seq=3 ttl=127 time=20.3 ms

--- baidu.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 20.031/20.261/20.475/0.181 ms
1
2
3
4
5
6
7
8
9
10
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec red ping baidu.com -c 3
PING baidu.com (110.242.68.66) 56(84) bytes of data.
64 bytes from 110.242.68.66 (110.242.68.66): icmp_seq=1 ttl=127 time=20.2 ms
64 bytes from 110.242.68.66 (110.242.68.66): icmp_seq=2 ttl=127 time=20.3 ms
64 bytes from 110.242.68.66 (110.242.68.66): icmp_seq=3 ttl=127 time=20.1 ms

--- baidu.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 20.085/20.197/20.278/0.082 ms

在两个命名空间中做内网 ping 测试

1
2
3
4
5
6
7
8
9
10
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec red ping 192.168.26.149 -c 3
PING 192.168.26.149 (192.168.26.149) 56(84) bytes of data.
64 bytes from 192.168.26.149: icmp_seq=1 ttl=64 time=0.241 ms
64 bytes from 192.168.26.149: icmp_seq=2 ttl=64 time=0.110 ms
64 bytes from 192.168.26.149: icmp_seq=3 ttl=64 time=0.075 ms

--- 192.168.26.149 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2074ms
rtt min/avg/max/mdev = 0.075/0.142/0.241/0.071 ms
1
2
3
4
5
6
7
8
9
10
11
12
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec green ping 192.168.26.149 -c 3
PING 192.168.26.149 (192.168.26.149) 56(84) bytes of data.
64 bytes from 192.168.26.149: icmp_seq=1 ttl=64 time=0.258 ms
64 bytes from 192.168.26.149: icmp_seq=2 ttl=64 time=0.097 ms
64 bytes from 192.168.26.149: icmp_seq=3 ttl=64 time=0.094 ms

--- 192.168.26.149 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2043ms
rtt min/avg/max/mdev = 0.094/0.149/0.258/0.076 ms
┌──[root@liruilongs.github.io]-[~]
└─$

拓扑图

到这里,我们实现和两个网络命名空间彼此通信,并且和根命名空间通信,同时可以和公网通信,实际上上面讲的也就是 docker 中容器的经典组网模型(veth pair + Bridge)

简单回顾一下我们干了什么:

  • 在主机的根网络命名空间中创建一个 Linux 网桥,创建两个 Linux 网络命名空间
  • 创建两个 veth pair,将其中一个端口连接到根命名空间中的网桥上,另一个端口放置在目标命名空间中。
  • 在目标命名空间中配置 IP 地址,并将该端口启动起来。
  • 在根命名空间中启用 IP 转发功能(通过设置 net.ipv4.ip_forward=1),给网桥分配IP地址,同时在命名空间配置默认网关地址为网桥地址。
  • 配置 NAT 规则 SNAT,将目标命名空间中的流量转发的源IP地址转化为根命名空间中的IP地址。
  • 目标命名空间中的流量将通过默认网关走网桥IP地址转发到根命名空间中,并通过根命名空间中的网络设备连接到互联网。

IPIP

ipip:即IPv4 in IPv4,没什么好说的 在IPv4报文的基础上封装一个IPv4报文,是 Linux L3隧道的一种,底层实现原理都基于tun设备

看一个 ipip隧道通信 Demo

通过 Linux 上的两个 network namespace 来模拟两个机器节点,每个 network namespce 是一个独立的网络栈

要使用ipip隧道,首先需要内核模块ipip.ko的支持。通过lsmod|grep ipip查看内核是否加载,若没有则用modprobe ipip加载,正常加载应该显示

1
2
3
4
5
6
7
liruilonger@cloudshell:~$ lsmod | grep ipip
liruilonger@cloudshell:~$ sudo modprobe ipip
liruilonger@cloudshell:~$ modprobe ipip
liruilonger@cloudshell:~$ lsmod | grep ipip
ipip 16384 0
ip_tunnel 28672 1 ipip
tunnel4 16384 1 ipip

加载ipip内核模块后,就可以创建隧道了。方法是先创建一个tun设备,然后将该tun设备绑定为一个ipip隧道。ipip隧道网络拓扑如图

在这里插入图片描述

这里我们用两个 Linux network namespace 来模拟 ,创建两个网络命名空间,同时配置两个 veth pair,一端放到命名空间

1
2
3
4
liruilonger@cloudshell:~$ sudo ip netns add ns1
liruilonger@cloudshell:~$ sudo ip netns add ns2
liruilonger@cloudshell:~$ sudo ip link add v1 netns ns1 type veth peer name v1-P
liruilonger@cloudshell:~$ sudo ip link add v2 netns ns2 type veth peer name v2-P

确认创建的 veth pair

1
2
3
4
5
6
7
8
9
10
11
12
13
liruilonger@cloudshell:~$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 0a:10:50:88:eb:09 brd ff:ff:ff:ff:ff:ff link-netnsid 0
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default
link/ether 02:42:f9:2d:29:3e brd ff:ff:ff:ff:ff:ff
4: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
5: v1-P@if3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 76:81:b0:33:e4:2b brd ff:ff:ff:ff:ff:ff link-netns ns1
6: v2-P@if3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether aa:a6:ac:15:b1:64 brd ff:ff:ff:ff:ff:ff link-netns ns2

另一端放到 根网络命名空间,同时两个Veth-pair 配置不同网段IP启动。

1
2
3
4
liruilonger@cloudshell:~$ sudo ip addr add 10.10.10.1/24 dev v1-P
liruilonger@cloudshell:~$ sudo ip link set v1-P up
liruilonger@cloudshell:~$ sudo ip addr add 10.10.20.1/24 dev v2-P
liruilonger@cloudshell:~$ sudo ip link set v2-P up

命名空间一端的同样配置IP 并启用

1
2
3
4
liruilonger@cloudshell:~$ sudo ip netns exec ns1 ip addr add 10.10.10.2/24 dev v1
liruilonger@cloudshell:~$ sudo ip netns exec ns2 ip addr add 10.10.20.2/24 dev v2
liruilonger@cloudshell:~$ sudo ip netns exec ns1 ip link set v1 up
liruilonger@cloudshell:~$ sudo ip netns exec ns2 ip link set v2 up

确定设备在线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
liruilonger@cloudshell:~$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 0a:10:50:88:eb:09 brd ff:ff:ff:ff:ff:ff link-netnsid 0
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default
link/ether 02:42:f9:2d:29:3e brd ff:ff:ff:ff:ff:ff
4: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
5: v1-P@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 76:81:b0:33:e4:2b brd ff:ff:ff:ff:ff:ff link-netns ns1
6: v2-P@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether aa:a6:ac:15:b1:64 brd ff:ff:ff:ff:ff:ff link-netns ns2
liruilonger@cloudshell:~$

调整内核参数,开启 ipv4 转发

1
2
liruilonger@cloudshell:~$ cat /proc/sys/net/ipv4/ip_forward
1

这个时候,Linux 网络命名空间中的 v1 和 v2 veth 任然不通,应为是在两个不同的网段。

1
2
3
4
liruilonger@cloudshell:~$ sudo ip netns exec ns1 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
10.10.10.0 0.0.0.0 255.255.255.0 U 0 0 0 v1

查看路由信息,没有通向 10.10.20.0/24网段的路由

所以我们在 ns1 里面配置一条路由,通向 10.10.20.0 的访问路由到 10.10.10.1 网关,实际上是 veth pair 的另一端。

1
liruilonger@cloudshell:~$ sudo ip netns exec ns1 route add -net 10.10.20.0 netmask 255.255.255.0 gw 10.10.10.1

再查看路由表

1
2
3
4
5
6
liruilonger@cloudshell:~$ sudo ip netns exec  ns1 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
10.10.10.0 0.0.0.0 255.255.255.0 U 0 0 0 v1
10.10.20.0 10.10.10.1 255.255.255.0 UG 0 0 0 v1
liruilonger@cloudshell:~$

同理,也给ns2配上通往10.10.10.0/24网段的路由。

1
2
3
4
5
6
7
liruilonger@cloudshell:~$ sudo ip netns exec ns2 route add -net 10.10.10.0 netmask 255.255.255.0 gw 10.10.20.1
liruilonger@cloudshell:~$ sudo ip netns exec ns2 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
10.10.10.0 10.10.20.1 255.255.255.0 UG 0 0 0 v2
10.10.20.0 0.0.0.0 255.255.255.0 U 0 0 0 v2
liruilonger@cloudshell:~$

这时候我们在 ns1 和 ns2 之间做ping 测试,正常通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
liruilonger@cloudshell:~$ sudo ip netns exec ns1 ping -c 3  10.10.20.2                                                
PING 10.10.20.2 (10.10.20.2) 56(84) bytes of data.
64 bytes from 10.10.20.2: icmp_seq=1 ttl=63 time=0.092 ms
64 bytes from 10.10.20.2: icmp_seq=2 ttl=63 time=0.057 ms
64 bytes from 10.10.20.2: icmp_seq=3 ttl=63 time=0.053 ms

--- 10.10.20.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2036ms
rtt min/avg/max/mdev = 0.053/0.067/0.092/0.017 ms
liruilonger@cloudshell:~$ sudo ip netns exec ns2 ping -c 3 10.10.10.2
PING 10.10.10.2 (10.10.10.2) 56(84) bytes of data.
64 bytes from 10.10.10.2: icmp_seq=1 ttl=63 time=0.042 ms
64 bytes from 10.10.10.2: icmp_seq=2 ttl=63 time=0.052 ms
64 bytes from 10.10.10.2: icmp_seq=3 ttl=63 time=0.049 ms

--- 10.10.10.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2066ms
rtt min/avg/max/mdev = 0.042/0.047/0.052/0.004 ms
liruilonger@cloudshell:~$

v1 和 v2 可以正常通信,即我们模拟了两个不在同一网段的 Linux 机器

创建tun设备,并设置为ipip隧道

  • 在 ns1 上面创建 tun1 设备:ip tunnel add tunl
  • 设置隧道模式为ipip:mode ipip
  • 设置隧道端点,用remotelocal表示隧道外层IP: remote 10.10.20.2 local 10.10.10.2
  • 隧道内层IP配置: ip addr add 10.10.100.10 peer 10.10.200.10 dev tunl
1
2
3
4
liruilonger@cloudshell:~$ sudo ip netns exec ns1 ip tunnel add tunl mode ipip remote 10.10.20.2 local 10.10.10.2
liruilonger@cloudshell:~$ sudo ip netns exec ns1 ip link set tunl up
liruilonger@cloudshell:~$ sudo ip netns exec ns1 ip addr add 10.10.100.10 peer 10.10.200.10 dev tunl
liruilonger@cloudshell:~$

原始的IP 头

  • src: 10.10.100.10
  • dst: 10.10.200.10

封装后的IP头

  • src: 10.10.10.2 | src: 10.10.100.10
  • dst: 10.10.20.2 | dst: 10.10.200.10

同样需要在 ns2 上做相同的配置

1
2
3
4
liruilonger@cloudshell:~$ sudo ip  netns exec ns2 ip tunnel add tunr mode ipip remote 10.10.10.2 local 10.10.20.2
liruilonger@cloudshell:~$ sudo ip netns exec ns2 ip link set tunr up
liruilonger@cloudshell:~$ sudo ip netns exec ns2 ip addr add 10.10.200.10 peer 10.10.100.10 dev tunr
liruilonger@cloudshell:~$

到这里 两个 tun 设备的 隧道就建立成功了,我们可以在其中一个命名空间对另一个命名空间的 tun 设备发起 ping 测试

1
2
3
4
5
6
7
8
9
10
liruilonger@cloudshell:~$ sudo ip netns exec ns1 ping 10.10.200.10 -c 3
PING 10.10.200.10 (10.10.200.10) 56(84) bytes of data.
64 bytes from 10.10.200.10: icmp_seq=1 ttl=64 time=0.091 ms
64 bytes from 10.10.200.10: icmp_seq=2 ttl=64 time=0.062 ms
64 bytes from 10.10.200.10: icmp_seq=3 ttl=64 time=0.067 ms

--- 10.10.200.10 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2082ms
rtt min/avg/max/mdev = 0.062/0.073/0.091/0.012 ms
liruilonger@cloudshell:~$

在看一各个命名空间对应的 链接

1
2
3
4
5
6
7
8
9
10
liruilonger@cloudshell:~$ sudo ip netns exec ns1 ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
3: v1@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 72:de:67:0b:28:e1 brd ff:ff:ff:ff:ff:ff link-netnsid 0
4: tunl@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/ipip 10.10.10.2 peer 10.10.20.2
liruilonger@cloudshell:~$

两个命名空间除了 veth-pair 对应的 veth 虚拟设备,各有个一个 tun 设备,link/ipip 中的内容表示封装后的包的两端地址,即外层IP。

1
2
3
4
5
6
7
8
9
10
liruilonger@cloudshell:~$ sudo ip netns exec ns2 ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
3: v2@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether f2:dd:3c:7d:eb:50 brd ff:ff:ff:ff:ff:ff link-netnsid 0
4: tunr@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/ipip 10.10.20.2 peer 10.10.10.2
liruilonger@cloudshell:~$

这里还需要了解一下 VXLAN 技术

VXLAN

VXLAN 是在底层物理网络(underlay)之上使用隧道技术,依托UDP层(3 层)构建的 overlay 的逻辑网络,使逻辑网络与物理网络解耦,实现灵活的组网需求。不仅能适配虚拟机环境,还能用于容器环境

VXLAN 的工作模型,它创建在原来的 IP 网络(三层)上,只要是三层可达(能够通过 IP 互相通信)的网络就能部署 VXLAN

VXLAN 网络的每个端点都有一个 VTEP 设备,负责 VXLAN 协议报文的封包和解包,也就是在虚拟报文上封装VTEP 通信的报文头部

物理网络上可以创建多个 VXLAN 网络,可以将这些 VXLAN 网络看作一个隧道,不同节点上的虚拟机/容器能够通过隧道直连。通过VNI标识不同的VXLAN网络,使得不同的VXLAN可以相互隔离。

一个VXLAN报文需要确定两个地址信息:

  • 内层报文(对应目的虚拟机/容器)的MAC地址
  • 外层报文(对应目的虚拟机/容器所在宿主机上的 VTEP)IP地址。

如果VNI也是动态感知的,那么VXLAN一共需要知道三个信息:内部MAC、VTEP IP和VNI

一般有两种方式获得以上VXLAN网络的必要信息:

  • 多播:同一个 VXLAN 网络的VTEP加入同一个多播网络
  • 控制中心:某个集中式的地方保存所有虚拟机的上述信息,自动告知VTEP它需要的信息即可,这也是 Flannel 的方式

点对点的 VXLAN Demo

我们通过一个点对点的 VXLAN 通信Demo 来体会一下 VXLAN 网络

点对点 VXLAN 即两台机器构成一个 VXLAN 网络,每台机器上有一个 VTEP,VTEP 之间通过它们的 IP 地址进行通信。点对点 VXLAN 网络拓扑如图

只有一个机器,这里我们和上面一样使用两个 Linux netowrk namespace node1,node2 来模拟两个主机

创建两个 网络命名空间

1
2
3
4
5
liruilonger@cloudshell:~$ sudo ip netns add node1
liruilonger@cloudshell:~$ sudo ip netns add node2
liruilonger@cloudshell:~$ ip netns list
node2
node1

使用 veth 对 来建立通信(VETH1,VETH2),配置 IP

1
2
3
4
5
liruilonger@cloudshell:~$ sudo ip link add veth1  netns node1  type veth peer name veth2 netns node2
liruilonger@cloudshell:~$ sudo ip netns exec node1 ip addr add 192.168.1.2/24 dev veth1
liruilonger@cloudshell:~$ sudo ip netns exec node2 ip addr add 192.168.1.3/24 dev veth2
liruilonger@cloudshell:~$ sudo ip netns exec node1 ip link set dev veth1 up
liruilonger@cloudshell:~$ sudo ip netns exec node2 ip link set dev veth2 up

两个命名空间都开启 ipv4 转发,ping 测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
liruilonger@cloudshell:~$ sudo ip netns exec node1 sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1
liruilonger@cloudshell:~$ sudo ip netns exec node2 sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1
liruilonger@cloudshell:~$ sudo ip netns exec node1 ping -c 3 192.168.1.3
PING 192.168.1.3 (192.168.1.3) 56(84) bytes of data.
64 bytes from 192.168.1.3: icmp_seq=1 ttl=64 time=0.066 ms
64 bytes from 192.168.1.3: icmp_seq=2 ttl=64 time=0.047 ms
64 bytes from 192.168.1.3: icmp_seq=3 ttl=64 time=0.038 ms

--- 192.168.1.3 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2088ms
rtt min/avg/max/mdev = 0.038/0.050/0.066/0.011 ms
liruilonger@cloudshell:~$

模拟好了环境,在 node1 命名空间 使用ip link命令创建VXLAN接口

1
liruilonger@cloudshell:~$ sudo ip netns exec node1 ip link add vxlan0 type vxlan id 42 dstport 4789 remote 192.168.1.3 local 192.168.1.2 dev veth1

在名为 node1 的网络命名空间中创建了一个名为 vxlan0 的 VXLAN 接口。

  • 它使用 VXLAN 标识 VID 42
  • 指定了 VXLAN 流量使用的目标端口号,通常使用 UDP 端口 4789
  • 并设置了远程端点的 IP 地址为 192.168.1.3
  • 本地端点的 IP 地址为 192.168.1.2
  • 该 VXLAN 接口使用veth1作为底层网络接口
1
2
3
4
liruilonger@cloudshell:~$ sudo  ip netns exec node1 ip -d link show  dev veth1
2: veth1@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether ea:d1:4d:8c:c0:9a brd ff:ff:ff:ff:ff:ff link-netns node2 promiscuity 0 minmtu 68 maxmtu 65535
veth addrgenmode eui64 numtxqueues 2 numrxqueues 2 gso_max_size 65536 gso_max_segs 65535

为刚创建的VXLAN网卡配置IP地址 172.17.1.2/24 并启用它

1
2
liruilonger@cloudshell:~$ sudo ip netns exec node1 ip addr add 172.17.1.2/24 dev vxlan0
liruilonger@cloudshell:~$ sudo ip netns exec node1 ip link set vxlan0 up

执行成功后会发现路由表项多了下面的内容,所有目的地址172.17.1.0/24 网段的包要通过 vxlan0(172.17.1.2) 转发

1
2
3
liruilonger@cloudshell:~$ sudo ip netns exec node1 ip route
172.17.1.0/24 dev vxlan0 proto kernel scope link src 172.17.1.2
192.168.1.0/24 dev veth1 proto kernel scope link src 192.168.1.2

vxlan0 的 FDB 地址转发表项中的内容如下

1
2
3
4
5
liruilonger@cloudshell:~$ sudo ip netns exec node1 bridge fdb
33:33:00:00:00:01 dev veth1 self permanent
01:00:5e:00:00:01 dev veth1 self permanent
33:33:ff:8c:c0:9a dev veth1 self permanent
00:00:00:00:00:00 dev vxlan0 dst 192.168.1.3 via veth1 self permanent

默认的VTEP对端地址为192.168.1.3。换句话说,原始报文经过vxlan0后会被内核添加上VXLAN头部,而外部UDP头的目的 IP 地址会被冠上192.168.1.3。这里的IP地址即为我们上面的配置的 远程端点 的 IP

在另一个命名空间也进行相同的配置,配置项这里不做说明

1
2
liruilonger@cloudshell:~$ sudo ip netns exec node2 bash
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger# ip link add vxlan0 type vxlan id 42 dstport 4789 remote 192.168.1.2 local 192.168.1.3 dev veth2
1
2
3
4
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger# ip -d link show  dev vxlan0
3: vxlan0: <BROADCAST,MULTICAST> mtu 1450 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 5a:46:75:cd:ce:9e brd ff:ff:ff:ff:ff:ff promiscuity 0 minmtu 68 maxmtu 65535
vxlan id 42 remote 192.168.1.2 local 192.168.1.3 dev veth2 srcport 0 0 dstport 4789 ttl auto ageing 300 udpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
1
2
3
4
5
6
7
8
9
10
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger# ip addr add 172.17.1.3/24 dev vxlan0
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger# ip link set vxlan0 up
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger# ip route
172.17.1.0/24 dev vxlan0 proto kernel scope link src 172.17.1.3
192.168.1.0/24 dev veth2 proto kernel scope link src 192.168.1.3
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger# bridge fdb
33:33:00:00:00:01 dev veth2 self permanent
01:00:5e:00:00:01 dev veth2 self permanent
33:33:ff:6d:21:bb dev veth2 self permanent
00:00:00:00:00:00 dev vxlan0 dst 192.168.1.2 via veth2 self permanent

测试两个命名空间中 veth 对应的 VTEP 内的 vxlan 设备的连通性

在 node2 对应的网络命令空间 ping node1 的 vxlan IP 地址

1
2
3
4
5
6
7
8
9
10
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger# ping -c 3 172.17.1.2
PING 172.17.1.2 (172.17.1.2) 56(84) bytes of data.
64 bytes from 172.17.1.2: icmp_seq=1 ttl=64 time=0.088 ms
64 bytes from 172.17.1.2: icmp_seq=2 ttl=64 time=0.062 ms
64 bytes from 172.17.1.2: icmp_seq=3 ttl=64 time=0.063 ms

--- 172.17.1.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2035ms
rtt min/avg/max/mdev = 0.062/0.071/0.088/0.012 ms
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger#

上面即为点对点组网模型 VXLAN 网络

抓包测试:

node1 上发起 ping

1
2
3
4
5
6
7
8
9
10
11
12
13
liruilonger@cloudshell:~$ sudo ip netns exec node1  bash
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger# ping -c 5 172.17.1.3
PING 172.17.1.3 (172.17.1.3) 56(84) bytes of data.
64 bytes from 172.17.1.3: icmp_seq=1 ttl=64 time=0.053 ms
64 bytes from 172.17.1.3: icmp_seq=2 ttl=64 time=0.058 ms
64 bytes from 172.17.1.3: icmp_seq=3 ttl=64 time=0.059 ms
64 bytes from 172.17.1.3: icmp_seq=4 ttl=64 time=0.064 ms
64 bytes from 172.17.1.3: icmp_seq=5 ttl=64 time=0.065 ms

--- 172.17.1.3 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4115ms
rtt min/avg/max/mdev = 0.053/0.059/0.065/0.004 ms
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger#

查看 node2 上配置的 vxlan 设备信息

1
2
3
4
5
6
7
8
liruilonger@cloudshell:~$ sudo ip netns exec node2 bash
。。。。。。。。。。。。。。。
3: vxlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default qlen 1000
link/ether 5a:46:75:cd:ce:9e brd ff:ff:ff:ff:ff:ff
inet 172.17.1.3/24 scope global vxlan0
valid_lft forever preferred_lft forever
inet6 fe80::5846:75ff:fecd:ce9e/64 scope link
valid_lft forever preferred_lft forever

指定对应的设备信息抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger# tcpdump -i vxlan0
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on vxlan0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
^C07:40:51.689496 IP6 fe80::5846:75ff:fecd:ce9e > ff02::2: ICMP6, router solicitation, length 16
07:40:56.278116 IP 172.17.1.2 > 172.17.1.3: ICMP echo request, id 7030, seq 1, length 64
07:40:56.278143 IP 172.17.1.3 > 172.17.1.2: ICMP echo reply, id 7030, seq 1, length 64
07:40:57.321585 IP 172.17.1.2 > 172.17.1.3: ICMP echo request, id 7030, seq 2, length 64
07:40:57.321610 IP 172.17.1.3 > 172.17.1.2: ICMP echo reply, id 7030, seq 2, length 64
07:40:58.345557 IP 172.17.1.2 > 172.17.1.3: ICMP echo request, id 7030, seq 3, length 64
07:40:58.345582 IP 172.17.1.3 > 172.17.1.2: ICMP echo reply, id 7030, seq 3, length 64
07:40:59.369546 IP 172.17.1.2 > 172.17.1.3: ICMP echo request, id 7030, seq 4, length 64
07:40:59.369575 IP 172.17.1.3 > 172.17.1.2: ICMP echo reply, id 7030, seq 4, length 64
07:41:00.393571 IP 172.17.1.2 > 172.17.1.3: ICMP echo request, id 7030, seq 5, length 64
07:41:00.393600 IP 172.17.1.3 > 172.17.1.2: ICMP echo reply, id 7030, seq 5, length 64
07:41:01.417503 ARP, Request who-has 172.17.1.2 tell 172.17.1.3, length 28
07:41:01.417543 ARP, Request who-has 172.17.1.3 tell 172.17.1.2, length 28
07:41:01.417599 ARP, Reply 172.17.1.3 is-at 5a:46:75:cd:ce:9e (oui Unknown), length 28
07:41:01.417594 ARP, Reply 172.17.1.2 is-at c6:b1:87:67:d9:e4 (oui Unknown), length 28

15 packets captured
15 packets received by filter
0 packets dropped by kernel
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger#

上面即为一个点对点的 VXLAN 通信 ,回到我们今天要讲的 Flannel 和 Calico,我们先来看一下 Calico

Calico

Calico第 3 层工作,并依赖于 Linux 路由表和 iptables 来移动数据包。

使用 Calico 时 Pod 之间的通信方式

Pod 之间的通信当使用 Calico 作为 CNI 时,每个 Pod 都会被分配一个唯一的 IP 地址,并且这些 IP 地址将用于在 Pod 之间进行通信。

Pod 通信本质上是 不同机器上的两个 network namespace 通信, network namespace 通过 veth pair 会在容器内部和宿主机映射一对虚拟网卡(veth pair),在部署的好的 K8s 集群中,可以在节点上看到好多虚拟网卡,这些就是 veth pair宿主机的虚拟网卡。

Calico 使用 Linux 内核的网络堆栈来实现网络功能(宿主机的 calico 组件的 Felix 程序会在内核的路由表里面写入数据,注明这个IP出去时下一跳地址和进来时的由那个网卡解析), 同时路由程序会获取ip变换,通过 BPG 路由协议扩散到其他宿主机上,这里也包括使用代理 ARP 来处理 Pod 到节点的 ARP 请求。

宿主机,也就是工作节点,可以看做是一个路由器。pod 可以看做是连接到路由器上的网络终端。

这里创建两个 Pod ,简单分析一下,编写 YAML 文件通过拓扑分布约束调度在不同的节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-deployment
labels:
app: os
spec:
replicas: 2
selector:
matchLabels:
app: os
template:
metadata:
labels:
app: os
spec:
containers:
- name: centos
image: centos:latest
args:
- tail
- -f
- /dev/null
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: os

应用之后,查看 Pod 信息

1
2
3
4
5
6
7
┌──[root@vms100.liruilongs.github.io]-[~/docker]
└─$kubectl get pods -n demo -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
demo-deployment-6cbdbd86d5-fbt9d 1/1 Running 0 17m 10.244.169.66 vms105.liruilongs.github.io <none> <none>
demo-deployment-6cbdbd86d5-nm467 1/1 Running 0 16m 10.244.38.174 vms103.liruilongs.github.io <none> <none>
┌──[root@vms100.liruilongs.github.io]-[~/docker]
└─$

分别调度到了不同节点:

  • vms105.liruilongs.github.io : demo-deployment-6cbdbd86d5-fbt9d
  • vms103.liruilongs.github.io : demo-deployment-6cbdbd86d5-nm467

进入容器查看 Pod IP 信息

demo-deployment-6cbdbd86d5-fbt9d Pod 对应 IP 为 :10.244.169.66, 生成的 veth pair容器侧的虚拟网卡为 eth0@if16

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──[root@vms100.liruilongs.github.io]-[~/docker]
└─$kubectl exec -it demo-deployment-6cbdbd86d5-fbt9d -n demo -- bash
[root@demo-deployment-6cbdbd86d5-fbt9d /]# ip a
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
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
4: eth0@if16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default
link/ether 42:17:dd:38:6a:10 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.244.169.66/32 scope global eth0
valid_lft forever preferred_lft forever
[root@demo-deployment-6cbdbd86d5-fbt9d /]# exit
exit

demo-deployment-6cbdbd86d5-nm467 Pod 对应 IP 为 10.244.38.174 ,生成的 veth pair容器侧的虚拟网卡为 eth0@if34

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──[root@vms100.liruilongs.github.io]-[~/docker]
└─$kubectl exec -it demo-deployment-6cbdbd86d5-nm467 -n demo -- bash
[root@demo-deployment-6cbdbd86d5-nm467 /]# ip a
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
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
4: eth0@if34: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default
link/ether 36:a2:81:c4:84:f9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.244.38.174/32 scope global eth0
valid_lft forever preferred_lft forever
[root@demo-deployment-6cbdbd86d5-nm467 /]# exit
exit

进入 demo-deployment-6cbdbd86d5-nm467 Pod 简单做 ping 测试,来看一下这个 ICMP 包是如何出去的。

1
2
3
4
5
6
7
8
9
10
11
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$kubectl exec -it demo-deployment-6cbdbd86d5-nm467 -n demo -- bash
[root@demo-deployment-6cbdbd86d5-nm467 /]# ping -c 3 10.244.169.66
PING 10.244.169.66 (10.244.169.66) 56(84) bytes of data.
64 bytes from 10.244.169.66: icmp_seq=1 ttl=62 time=0.497 ms
64 bytes from 10.244.169.66: icmp_seq=2 ttl=62 time=0.460 ms
64 bytes from 10.244.169.66: icmp_seq=3 ttl=62 time=0.391 ms

--- 10.244.169.66 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2044ms
rtt min/avg/max/mdev = 0.391/0.449/0.497/0.047 ms

当前容器 IP 为 10.244.38.174 , ping 侧的容器 IP 为 10.244.169.66,不在同一个网络内,所以当前容器会在路由表获取一下跳地址.

查看容器路由信息

1
2
3
[root@demo-deployment-6cbdbd86d5-nm467 /]# ip route
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link

第一条为一条默认路由,所有目标地址不在本地子网的流量都会通过这个路由转发到下一跳地址。

下一跳地址为 169.254.1.1 ,这是预留的本地 IP 网段,这里的容器里的路由规则在所有的容器都是一样的,不需要动态更新.

这条路由表明,169.254.1.1 是一个直接可达的地址,属于 eth0 接口,使用 Calico 时,169.254.1.1Calico 默认的虚拟网关地址(Link-local Gateway)

容器会查询下一跳 168.254.1.1MAC 地址,这个 ARP 请求(查找目标设备的 MAC 地址)会如何发出?

这里通过 veth pair 发出,容器内部的虚拟网卡eth0@if34 发到宿主节点的对应的虚拟网卡cali7a4b00317e6 宿主机上的 veth 设备代理 ARP 请求并响应。容器中没有复杂的路由规则,所有非本地流量都通过 169.254.1.1 路由到宿主机

如何确定一对 veth pair 虚拟网卡?

在容器内部我们通过 ethtool -S eth0 可以查看到网卡索引为 34

1
2
3
4
5
6
7
[root@demo-deployment-6cbdbd86d5-nm467 /]# ethtool -S eth0
NIC statistics:
peer_ifindex: 34
rx_queue_0_xdp_packets: 0
rx_queue_0_xdp_bytes: 0
rx_queue_0_xdp_drops: 0
[root@demo-deployment-6cbdbd86d5-nm467 /]#

在宿主节点上对应的索引的虚拟网卡即为 veth pair 的另一端,由前面可知,这个pod 调度到了 192.168.26.103 节点,进入节点可以获取到索引对应的虚拟网卡。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$ansible-console -i host.yaml --limit 192.168.26.103
Welcome to the ansible console.
Type help or ? to list commands.

root@all (1)[f:5]# ip a | grep 34
192.168.26.103 | CHANGED | rc=0 >>
34: cali7a4b00317e6@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP
root@all (1)[f:5]# ifconfig cali7a4b00317e6
192.168.26.103 | CHANGED | rc=0 >>
cali7a4b00317e6: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1480
inet6 fe80::ecee:eeff:feee:eeee prefixlen 64 scopeid 0x20<link>
ether ee:ee:ee:ee:ee:ee txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

这里小伙伴会发现这个虚拟网卡没有随机 MAC 地址,所有的 MAC 地址为 ee:ee:ee:ee:ee:ee , 也没有IP地址。

向所在的节点发送 ARP 请求后,节点上的代理 ARP 进程将接收到这个请求,应答报文中MAC地址是主机自己的MAC地址,容器的后续报文 IP 地址还是 目的容器,但是 MAC 地址就变成了主机上该网卡的地址

也就是说,所有的报文都会发给主机,主机根据IP地址再进行转发.

主机上这块网卡不管 ARP 请求的内容,直接用自己的 MAC 地址作为应答的行为被称为 ARP proxy ,可以通过以下内核参数检查

在 Linux 内核中,ARP Proxy 是通过 proxy_arp 参数配置的,可以按接口逐一启用。以下是常见的检查和配置方法,1:表示启用了 ARP Proxy

1
2
3
4
5
6
7
8
9
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$ansible-console -i host.yaml --limit 192.168.26.103
Welcome to the ansible console.
Type help or ? to list commands.

root@all (1)[f:5]# cat /proc/sys/net/ipv4/conf/cali7a4b00317e6/proxy_arp
192.168.26.103 | CHANGED | rc=0 >>
1
root@all (1)[f:5]#

通俗的话讲,主机假装自己是目标 IP 的实际设备,以自己的 MAC 地址响应

可以认为 Calico 把主机作为容器的默认网关使用,所有的报文发到主机,主机根据路由表进行转发。和经典的网络架构不同的是,Calico 并没有给容器的默认网关配置一个 IP 地址,而是设置了一个固定的IP地址,通过 ARP proxy 和修改容器路由表的机制实现。

主机上的 cali7a4b00317e6 网卡接收到报文之后,所有的报文会根据路由表转发,查看节点的路由表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
root@all (1)[f:5]# route
192.168.26.103 | CHANGED | rc=0 >>
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default gateway 0.0.0.0 UG 0 0 0 ens32
10.244.31.64 vms106.liruilon 255.255.255.192 UG 0 0 0 tunl0
10.244.38.128 0.0.0.0 255.255.255.192 U 0 0 0 *
10.244.38.151 0.0.0.0 255.255.255.255 UH 0 0 0 cali39ccd735ea3
10.244.38.154 0.0.0.0 255.255.255.255 UH 0 0 0 calif39455d9c24
10.244.38.174 0.0.0.0 255.255.255.255 UH 0 0 0 cali7a4b00317e6
10.244.63.64 vms102.liruilon 255.255.255.192 UG 0 0 0 tunl0
10.244.169.64 vms105.liruilon 255.255.255.192 UG 0 0 0 tunl0
10.244.198.0 vms101.liruilon 255.255.255.192 UG 0 0 0 tunl0
10.244.239.128 vms100.liruilon 255.255.255.192 UG 0 0 0 tunl0
link-local 0.0.0.0 255.255.0.0 U 1002 0 0 ens32
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
192.168.26.0 0.0.0.0 255.255.255.0 U 0 0 0 ens32

那么这些路由表是如何生成的,当一个 Pod 在宿主机启动时,Felix 会为该 Pod 创建一个路由条目,指向该 Pod 的 IP 地址。这个路由条目通常是一个单独的路由,指向 Pod 中 虚拟网卡接口,当前Pod 所以在主机节点是 cali7a4b00317e6 接口

1
10.244.38.174   0.0.0.0         255.255.255.255 UH    0      0        0 cali7a4b00317e6

那么其他的路由信息哪里来的?回到刚才的流量分析

访问的Pod IP 为 10.244.169.66,可以看到匹配这一条路由

1
2
3
4
5
6
7
root@all (1)[f:5]# route | grep 169
192.168.26.103 | CHANGED | rc=0 >>
10.244.169.64 vms105.liruilon 255.255.255.192 UG 0 0 0 tunl0
root@all (1)[f:5]# ip route | grep 169
192.168.26.103 | CHANGED | rc=0 >>
10.244.169.64/26 via 192.168.26.105 dev tunl0 proto bird onlink
。。。。

10.244.169.64/26: 表示一个 IP 地址范围,其中 /26(255.255.255.192) 表示子网掩码,确定了网络的大小。具体来说,这个 IP 地址范围包括从 10.244.169.64 到 10.244.169.127 的所有 IP 地址。

通过设备 tunl0 使用协议 bird 和 onlink 选项发送,下一跳IP地址为 192.168.26.105(vms105.liruilon...)。这里 tunl0 设备可以理解我为把 容器到容器的流量封装为节点到节点的流量

这里的 birdonlink 是路由表中的两个选项:

  • bird: 是一种路由协议,它可以帮助路由器动态地学习和适应网络拓扑结构的变化。Calico 使用 BGP 协议来实现网络功能,Bird 可以用于实现 BGP 路由器.
  • onlink: 选项表示下一跳 IP 地址是直接可达的,也就是说,它是在同一子网内的。如果下一跳 IP 地址不在同一子网内,则需要使用网关来转发数据包。

tunl0 是 Calico 使用 IP-in-IP 隧道技术实现容器网络跨节点通信的关键。它通过对容器的流量进行封装,使得集群中的每个节点可以透明地与其他节点上的 Pod 进行通信。

原始数据包的源 IP 为 Pod 的 IP 地址( 10.244.38.174),目标 IP 为目标 Pod 的 IP 地址(10.244.169.66),通过 IPIP 协议封装之后,

  • 外层的 IP 包:封装的外层 IP 包的源 IP 是当前节点的物理 IP(192.168.26.103),目标 IP 是下一跳的 IP 地址(192.168.26.105)。
  • 内层的 IP 包:内层的 IP 包保持原始数据包的源 IP 和目标 IP,即 10.244.38.174 到 10.244.169.66
1
192.168.26.0    0.0.0.0         255.255.255.0   U     0      0        0 ens32

数据包通过物理网络接口(ens32)发送所有到目标 IP(192.168.26.105)的数据包。

然后我们来到下一跳 IP 地址对应的工作节点 192.168.26.105

1
2
3
4
5
6
7
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$kubectl get pods -n demo -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
demo-deployment-6cbdbd86d5-fbt9d 1/1 Running 0 4h36m 10.244.169.66 vms105.liruilongs.github.io <none> <none>
demo-deployment-6cbdbd86d5-nm467 1/1 Running 0 4h36m 10.244.38.174 vms103.liruilongs.github.io <none> <none>
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$

这个地址实际上是 目标 Pod 所在节点的 IP 地址,查看目标节点的路由信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$ansible-console -i host.yaml --limit 192.168.26.105
Welcome to the ansible console.
Type help or ? to list commands.

root@all (1)[f:5]# ip route
192.168.26.105 | CHANGED | rc=0 >>
default via 192.168.26.2 dev ens32
10.244.31.64/26 via 192.168.26.106 dev tunl0 proto bird onlink
10.244.38.128/26 via 192.168.26.103 dev tunl0 proto bird onlink
10.244.63.64/26 via 192.168.26.102 dev tunl0 proto bird onlink
blackhole 10.244.169.64/26 proto bird
10.244.169.65 dev calid5e76ad523e scope link
10.244.169.66 dev cali39888f400bd scope link
10.244.169.70 dev cali0fdeca04347 scope link
10.244.169.73 dev cali7cf9eedbe64 scope link
10.244.169.77 dev calia011d753862 scope link
10.244.169.78 dev caliaa36e67b275 scope link
10.244.169.90 dev califc24aa4e3bd scope link
10.244.169.119 dev calibdca950861e scope link
10.244.169.120 dev cali40813694dd6 scope link
10.244.169.121 dev calie1fd05af50d scope link
10.244.198.0/26 via 192.168.26.101 dev tunl0 proto bird onlink
10.244.239.128/26 via 192.168.26.100 dev tunl0 proto bird onlink
169.254.0.0/16 dev ens32 scope link metric 1002
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.26.0/24 dev ens32 proto kernel scope link src 192.168.26.105

通过路由信息 会匹配10.244.169.66 dev cali39888f400bd scope link 这个路由规则,也就是是创建Pod时在当前节点创建的路由规则

1
2
3
4
root@all (1)[f:5]# ip route | grep 66
192.168.26.105 | CHANGED | rc=0 >>
10.244.169.66 dev cali39888f400bd scope link
root@all (1)[f:5]#

这个规则匹配的是一个IP地址,而不是网段。也就是说,主机上的每个容器都会有一个对应的路由表项。报文被发送到 cali39888f400bd 这个 veth pair

1
2
3
4
5
6
7
root@all (1)[f:5]# ip a | grep -A 4 cali39888f400bd
192.168.26.105 | CHANGED | rc=0 >>
16: cali39888f400bd@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP
link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 9
inet6 fe80::ecee:eeff:feee:eeee/64 scope link
valid_lft forever preferred_lft forever
root@all (1)[f:5]#

然后从 cali39888f400bd 一端发送给目标容器的 eth0@if16。目标容器接收到报文之后,回复 ICMP 报文,应答报文原路返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$kubectl exec -it demo-deployment-6cbdbd86d5-fbt9d -n demo -- bash
[root@demo-deployment-6cbdbd86d5-fbt9d /]# ip a
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
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
4: eth0@if16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default
link/ether 42:17:dd:38:6a:10 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.244.169.66/32 scope global eth0
valid_lft forever preferred_lft forever

简单来回顾一下,跨主机的 Pod 如何通信?

本质是 Linux 中两个不同机器网络命名空间(network namespace)的通信,通过当前容器(网络命名空间)和宿主机内的一对虚拟网卡 veth pair ,把数据包发送到宿主节点上(这里涉及到ARP 代理),然后通过匹配路由表(这里是网段匹配),下一跳 到 目标Pod 所在宿主节点,到达 目标Pod 宿主节点 上之后,在通过 路由匹配(IP 匹配)到宿主节点上的虚拟网卡(和容器对应的一对虚拟网卡 veth pair ),然后从这个虚拟网卡到容器内部的虚拟网卡,实现请求,之后原路返回。

所以从网络命名空间角度理解,是容器 network namespace 到节点 root network namespace,然后 节点 root network namespace 到 容器 network namespace

下面的思维导图为 两个不同节点 Pod 报文的访问路径

在这里插入图片描述

Calico 组网方案想比于传统的 docker 容器组网方案,直接通过容器网络命名空间和根网络命名空间通信,默认网关地址实际在容器中,而 docker 是把容器网络命名空间挂到网桥上实现的通信,默认的网关地址中网桥上,在 Calico 中网桥被代理 ARP 取代,路由同步通过 BGP 进行。

通过上面的Demo我们在来看一下 Calico 的架构

Calico 架构认知

在这里插入图片描述

Calico的核心组件包括: Felix, etcd, BGP Client (BIRD)、 BGPRoute Reflector

在这里插入图片描述

Felix,即Calico代理, “跑”在Kubernetes的Node节点上,主要负责配置路由ACL等信息来确保Endpoint的连通状态。

etcd,分布式键值存储,主要负责网络元数据一致性,确保Calico网络状态的准确性,一般与Kubernetes共用

BGP Client (BIRD),主要负责把Felix写入Kernel的路由信息分发到当前Calico网络,确保work 间通信的有效性。

BGP Route Reflector,大规模部署时使用,摒弃所有节点互联的Mesh模式,通过一个或者多个BGP Route Reflector来完成集中式路由分发

将整个互联网的可扩展IP网络原则压缩到数据中心级别, Calico 在每一个计算节点利用 Linux Kernel 实现了一个高效的 vRouter 来负责数据转发,而每个 vRouter 通过 BGP协议 把在其上运行的容器的路由信息向整个Calico网络内传播,小规模部署可以直接互联,大规模下可通过指定的BGP Route Reflector来完成。这样保证最终所有的容器间的数据流量都是通过IP包的方式完成互联的。

流量隔离基于iptables实现,并且从etcd中获取需要生成的隔离规则,因此会有一些性能上的隐患。

每个主机上都部署了Calico-Node作为虚拟路由器,并且可以通过Calico将宿主机组织成任意的拓扑集群。当集群中的容器需要与外界通信时,就可以通过BGP协议将网关物理路由器加入到集群中,使外界可以直接访问容器IP,而不需要做任何NAT之类的复杂操作。

使用 Calico 时 kube-proxy 时如何提供负载的

这里我们只看一下 kube-proxy 使用 iptables 模式时的场景,kube-proxy 会在每个节点上创建 iptables 规则,将服务的请求路由到 后端Pod。

具体来讲,iptable 会创建一些自定义链,这些链最开始通过 iptables 内置链(INPUT,FORWARD)发生跳转,然后在自定义的链中匹配请求的数据包, 执行对应的动作,比如到其他的自定义链跳转,负载均衡操作(基于iptables 负载均衡,使用随机模式,指定概率 ),以及 DNATSNAT 操作,最后把对 SVC 的 访问发送到 实际的 Pod,这里也涉及到路由查表获取下一跳地址等。

SVC 常见的 服务发布方式有三种(这里不考虑 ExternalName),简单介绍,然后我们依次来跟踪下报文路径,看看其中到底发生了什么

  1. ClusterIP :默认的服务类型。服务暴露在集群内部,并为集群中的其他对象提供了一个稳定的IP地址。只能在集群内部访问。
  2. NodePort :将服务暴露在每个节点的IP地址上的一个端口上。外部客户端可以通过节点的IP地址和端口访问服务。可以在集群内部和外部访问。
  3. LoadBalancer :将服务暴露在外部负载均衡器上。可以通过负载均衡器的IP地址访问服务。可以在集群内部和外部访问。

在最后开始了解这一部分知识的时候,发现和书里讲的略有不同,好多地方请教了大佬,没有解决,后来才发现是 iptables 版本的问题,不同版本的 iptables 显示的内核规则略有不同,所以这里准备了两个版本的 iptables

不管那种服务发布方式,都可以在集群内部访问,所以这里Demo在所有节点的批量操作,为了方便使用了 ansible,下文不在说明。

查看所有节点的 iptalbes 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$ansible-console -i host.yaml
Welcome to the ansible console.
Type help or ? to list commands.

root@all (6)[f:5]# bash -c "iptables -V"
192.168.26.102 | CHANGED | rc=0 >>
iptables v1.4.21
192.168.26.101 | CHANGED | rc=0 >>
iptables v1.4.21
192.168.26.100 | CHANGED | rc=0 >>
iptables v1.8.7 (legacy)
192.168.26.105 | CHANGED | rc=0 >>
iptables v1.4.21
192.168.26.106 | CHANGED | rc=0 >>
iptables v1.4.21
192.168.26.103 | CHANGED | rc=0 >>
iptables v1.4.21
root@all (6)[f:5]#

192.168.26.100 节点使用的 v1.8.7 的版本,这个版本的 iptables 的规则可以正常显示,其他节点使用的 v1.4.21 的版本,1.4 版本的 iptables 在内核 iptables 规则显示上有些问题,实际上内核有正确的规则表示,只是 iptables 1.4没有正确显示而已。当然这里的显示问题只发生在 DNAT 到 Pod 的时候。

当前集群信息

1
2
3
4
5
6
7
8
9
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$kubectl get nodes
NAME STATUS ROLES AGE VERSION
vms100.liruilongs.github.io Ready control-plane 10d v1.25.1
vms101.liruilongs.github.io Ready control-plane 10d v1.25.1
vms102.liruilongs.github.io Ready control-plane 10d v1.25.1
vms103.liruilongs.github.io Ready <none> 10d v1.25.1
vms105.liruilongs.github.io Ready <none> 10d v1.25.1
vms106.liruilongs.github.io Ready <none> 10d v1.25.1

先来看下用的最多的 NodePort

NodePort

找一个当前环境部署的 NodePort

1
2
3
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$kubectl get svc -A | grep NodePort
velero minio NodePort 10.98.180.238 <none> 9000:30934/TCP,9099:30450/TCP 69d

通过 任意节点IP+ 30450 是端口我们可以在集群内外访问这个 SVC ,下面为 SVC 的详细信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$kubectl describe svc -n velero minio
Name: minio
Namespace: velero
Labels: component=minio
Annotations: <none>
Selector: component=minio
Type: NodePort
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.98.180.238
IPs: 10.98.180.238
Port: api 9000/TCP
TargetPort: 9000/TCP
NodePort: api 30934/TCP
Endpoints: 10.244.169.89:9000,10.244.38.178:9000
Port: console 9099/TCP
TargetPort: 9090/TCP
NodePort: console 30450/TCP
Endpoints: 10.244.169.89:9090,10.244.38.178:9090
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$

对应的 deploy 的 Pod 副本

1
2
3
4
5
6
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$kubectl get pod -n velero -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
minio-b6d98746c-lxt2c 1/1 Running 0 66m 10.244.38.178 vms103.liruilongs.github.io <none> <none>
minio-b6d98746c-m7rzc 1/1 Running 6 (15h ago) 11d 10.244.169.89 vms105.liruilongs.github.io <none> <none>
.....

分析一下这个报文访问路径,暴露的端口为 30450, 所以有规则去匹配这个端口,所以先查一下这个端口,通过 iptables-save | grep 30450 命令我们来看一下有哪些链匹配这个端口了,做了哪些动作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$ansible-console -i host.yaml
Welcome to the ansible console.
Type help or ? to list commands.

root@all (6)[f:5]# bash -c "iptables-save | grep 30450"
192.168.26.100 | CHANGED | rc=0 >>
-A KUBE-NODEPORTS -p tcp -m comment --comment "velero/minio:console" -m tcp --dport 30450 -j KUBE-EXT-K6NOPV6C6M2DHO7B
192.168.26.101 | CHANGED | rc=0 >>
-A KUBE-NODEPORTS -p tcp -m comment --comment "velero/minio:console" -m tcp --dport 30450 -j KUBE-EXT-K6NOPV6C6M2DHO7B
192.168.26.102 | CHANGED | rc=0 >>
-A KUBE-NODEPORTS -p tcp -m comment --comment "velero/minio:console" -m tcp --dport 30450 -j KUBE-EXT-K6NOPV6C6M2DHO7B
192.168.26.105 | CHANGED | rc=0 >>
-A KUBE-NODEPORTS -p tcp -m comment --comment "velero/minio:console" -m tcp --dport 30450 -j KUBE-EXT-K6NOPV6C6M2DHO7B
192.168.26.106 | CHANGED | rc=0 >>
-A KUBE-NODEPORTS -p tcp -m comment --comment "velero/minio:console" -m tcp --dport 30450 -j KUBE-EXT-K6NOPV6C6M2DHO7B
192.168.26.103 | CHANGED | rc=0 >>
-A KUBE-NODEPORTS -p tcp -m comment --comment "velero/minio:console" -m tcp --dport 30450 -j KUBE-EXT-K6NOPV6C6M2DHO7B

通过上面的代码可以发现,在所有的节点都存在一条自定义链 KUBE-NODEPORTS , 用于匹配该端口,然后发生跳转,把数据包给了 KUBE-EXT-K6NOPV6C6M2DHO7B 这条自定义链。

这里有个问题,那么 KUBE-NODEPORTS 这个自定义链是从那条链跳转过来的?

在 iptables 中,用户自定义链的规则和系统预定义的 5 条链(PROROUTING/INPUT/FORWARD/OUTPUT/POSTROUTING)里的规则本质上没有区别。不过自定义的链没有与 netfilter 里的钩子进行绑定,所以它不会自动触发,只能从其他链的规则中跳转过来,类似上面 -j KUBE-EXT-K6NOPV6C6M2DHO7B 这样的操作,当前自定义链跳转到另一条自定义链。

这里根据 netfilter 的钩子顺序,依次看下,数据包最先进来的是 PREROUTING 链,过滤没有发现

1
2
3
4
5
6
7
8
9
10
11
12
13
root@all (6)[f:5]# bash -c "iptables-save |  grep -e '-j KUBE-NODEPORTS' | grep PREROUTING"
192.168.26.101 | FAILED | rc=1 >>
non-zero return code
192.168.26.105 | FAILED | rc=1 >>
non-zero return code
192.168.26.100 | FAILED | rc=1 >>
non-zero return code
192.168.26.102 | FAILED | rc=1 >>
non-zero return code
192.168.26.106 | FAILED | rc=1 >>
non-zero return code
192.168.26.103 | FAILED | rc=1 >>
non-zero return code

经过 PREROUTING 链之后这里会有一个路由,可能走 FORWARD 也可能走 INPUT,所以依次看一下

FORWARD 链中没有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@all (6)[f:5]# bash -c "iptables-save |  grep -e '-j KUBE-NODEPORTS' | grep FORWARD"
192.168.26.102 | FAILED | rc=1 >>
non-zero return code
192.168.26.101 | FAILED | rc=1 >>
non-zero return code
192.168.26.106 | FAILED | rc=1 >>
non-zero return code
192.168.26.100 | FAILED | rc=1 >>
non-zero return code
192.168.26.105 | FAILED | rc=1 >>
non-zero return code
192.168.26.103 | FAILED | rc=1 >>
non-zero return code
root@all (6)[f:5]#

INPUT 链中存在跳转,可以看到,在 INPUT链中,-j KUBE-NODEPORTS ,跳转到了我们最开始看到匹配端口的那条链

1
2
3
4
5
6
7
8
9
10
11
12
13
root@all (6)[f:5]# bash -c "iptables-save |  grep -e '-j KUBE-NODEPORTS' | grep INPUT"
192.168.26.101 | CHANGED | rc=0 >>
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS
192.168.26.100 | CHANGED | rc=0 >>
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS
192.168.26.102 | CHANGED | rc=0 >>
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS
192.168.26.106 | CHANGED | rc=0 >>
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS
192.168.26.105 | CHANGED | rc=0 >>
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS
192.168.26.103 | CHANGED | rc=0 >>
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS

上面的自定义链虽然合理,但是这里有一个问题,注释不对劲,做健康检查的,我们需要的是应该是和 Nodeport 相关的,直接grep -e '-j KUBE-NODEPORTS' ,发现还有一条链,即下面的这条,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
root@all (6)[f:5]# bash -c "iptables-save |  grep -e '-j KUBE-NODEPORTS'"
192.168.26.106 | CHANGED | rc=0 >>
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS
192.168.26.102 | CHANGED | rc=0 >>
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS
192.168.26.105 | CHANGED | rc=0 >>
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS
192.168.26.101 | CHANGED | rc=0 >>
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS
192.168.26.100 | CHANGED | rc=0 >>
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS
192.168.26.103 | CHANGED | rc=0 >>
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS
root@all (6)[f:5]#

通过KUBE-NODEPORTS这条链可以看到,跳转到了 KUBE-NODEPORTS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
root@all (6)[f:5]# bash -c "iptables-save |  grep -e '-j KUBE-NODEPORTS'"
192.168.26.106 | CHANGED | rc=0 >>
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS
192.168.26.102 | CHANGED | rc=0 >>
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS
192.168.26.105 | CHANGED | rc=0 >>
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS
192.168.26.101 | CHANGED | rc=0 >>
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS
192.168.26.100 | CHANGED | rc=0 >>
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS
192.168.26.103 | CHANGED | rc=0 >>
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS

然后在跟 KUBE-NODEPORTS 这条链,发现是从 PREROUTING 这条内置链跳转的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@all (6)[f:5]# bash -c "iptables-save |  grep -e '-j KUBE-SERVICES' | grep PREROUTING"
192.168.26.106 | CHANGED | rc=0 >>
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
192.168.26.101 | CHANGED | rc=0 >>
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
192.168.26.102 | CHANGED | rc=0 >>
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
192.168.26.100 | CHANGED | rc=0 >>
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
192.168.26.105 | CHANGED | rc=0 >>
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
192.168.26.103 | CHANGED | rc=0 >>
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
root@all (6)[f:5]#

所以由上面可以知道, NodePort 类型的 SVC 在最开始通过 节点IP+NodePort 的方式访问 SVC 的时候,会从每个节点的 iptables 中 内置链 PREROUTING 链 中跳转到链 KUBE-SERVICES ,然后由 KUBE-SERVICES 跳转到 KUBE-NODEPORTS链,数据包在KUBE-NODEPORTS做了端口匹配。

所以可以理解为 NodePort 类型的 SVC 在到达节点后,数据包最先到达 KUBE-NODEPORTS 这条自定义链。

根据最上面的 KUBE-NODEPORTS 链的定义,可以发现跳转到了 KUBE-EXT-K6NOPV6C6M2DHO7B ,所以在来看一下这条链。

通过 iptables-save | grep -e '-A KUBE-EXT-K6NOPV6C6M2DHO7B' 命令查询这条链,可以发现这条链有两条规则,当前只关注第二条 -A KUBE-EXT-K6NOPV6C6M2DHO7B -j KUBE-SVC-K6NOPV6C6M2DHO7B,又发生了跳转,这条链对应 SVC 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
root@all (6)[f:5]# bash -c "iptables-save |  grep -e '-A KUBE-EXT-K6NOPV6C6M2DHO7B'"
192.168.26.102 | CHANGED | rc=0 >>
-A KUBE-EXT-K6NOPV6C6M2DHO7B -m comment --comment "masquerade traffic for velero/minio:console external destinations" -j KUBE-MARK-MASQ
-A KUBE-EXT-K6NOPV6C6M2DHO7B -j KUBE-SVC-K6NOPV6C6M2DHO7B
192.168.26.101 | CHANGED | rc=0 >>
-A KUBE-EXT-K6NOPV6C6M2DHO7B -m comment --comment "masquerade traffic for velero/minio:console external destinations" -j KUBE-MARK-MASQ
-A KUBE-EXT-K6NOPV6C6M2DHO7B -j KUBE-SVC-K6NOPV6C6M2DHO7B
192.168.26.105 | CHANGED | rc=0 >>
-A KUBE-EXT-K6NOPV6C6M2DHO7B -m comment --comment "masquerade traffic for velero/minio:console external destinations" -j KUBE-MARK-MASQ
-A KUBE-EXT-K6NOPV6C6M2DHO7B -j KUBE-SVC-K6NOPV6C6M2DHO7B
192.168.26.100 | CHANGED | rc=0 >>
-A KUBE-EXT-K6NOPV6C6M2DHO7B -m comment --comment "masquerade traffic for velero/minio:console external destinations" -j KUBE-MARK-MASQ
-A KUBE-EXT-K6NOPV6C6M2DHO7B -j KUBE-SVC-K6NOPV6C6M2DHO7B
192.168.26.106 | CHANGED | rc=0 >>
-A KUBE-EXT-K6NOPV6C6M2DHO7B -m comment --comment "masquerade traffic for velero/minio:console external destinations" -j KUBE-MARK-MASQ
-A KUBE-EXT-K6NOPV6C6M2DHO7B -j KUBE-SVC-K6NOPV6C6M2DHO7B
192.168.26.103 | CHANGED | rc=0 >>
-A KUBE-EXT-K6NOPV6C6M2DHO7B -m comment --comment "masquerade traffic for velero/minio:console external destinations" -j KUBE-MARK-MASQ
-A KUBE-EXT-K6NOPV6C6M2DHO7B -j KUBE-SVC-K6NOPV6C6M2DHO7B
root@all (6)[f:5]#

这里对 跳转链进行查询,每个节点有三条规则,当前只关注后两条:

  • KUBE-SEP-OLNIRCQFCXAN5USW
  • KUBE-SEP-DB3JUIMGX6LERFY3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
root@all (6)[f:5]# bash -c "iptables-save |  grep -e '-A KUBE-SVC-K6NOPV6C6M2DHO7B'"
192.168.26.101 | CHANGED | rc=0 >>
-A KUBE-SVC-K6NOPV6C6M2DHO7B ! -s 10.244.0.0/16 -d 10.98.180.238/32 -p tcp -m comment --comment "velero/minio:console cluster IP" -m tcp --dport 9099 -j KUBE-MARK-MASQ
-A KUBE-SVC-K6NOPV6C6M2DHO7B -m comment --comment "velero/minio:console -> 10.244.169.89:9090" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-OLNIRCQFCXAN5USW
-A KUBE-SVC-K6NOPV6C6M2DHO7B -m comment --comment "velero/minio:console -> 10.244.38.178:9090" -j KUBE-SEP-DB3JUIMGX6LERFY3
192.168.26.105 | CHANGED | rc=0 >>
-A KUBE-SVC-K6NOPV6C6M2DHO7B ! -s 10.244.0.0/16 -d 10.98.180.238/32 -p tcp -m comment --comment "velero/minio:console cluster IP" -m tcp --dport 9099 -j KUBE-MARK-MASQ
-A KUBE-SVC-K6NOPV6C6M2DHO7B -m comment --comment "velero/minio:console -> 10.244.169.89:9090" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-OLNIRCQFCXAN5USW
-A KUBE-SVC-K6NOPV6C6M2DHO7B -m comment --comment "velero/minio:console -> 10.244.38.178:9090" -j KUBE-SEP-DB3JUIMGX6LERFY3
192.168.26.100 | CHANGED | rc=0 >>
-A KUBE-SVC-K6NOPV6C6M2DHO7B ! -s 10.244.0.0/16 -d 10.98.180.238/32 -p tcp -m comment --comment "velero/minio:console cluster IP" -m tcp --dport 9099 -j KUBE-MARK-MASQ
-A KUBE-SVC-K6NOPV6C6M2DHO7B -m comment --comment "velero/minio:console -> 10.244.169.89:9090" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-OLNIRCQFCXAN5USW
-A KUBE-SVC-K6NOPV6C6M2DHO7B -m comment --comment "velero/minio:console -> 10.244.38.178:9090" -j KUBE-SEP-DB3JUIMGX6LERFY3
192.168.26.102 | CHANGED | rc=0 >>
-A KUBE-SVC-K6NOPV6C6M2DHO7B ! -s 10.244.0.0/16 -d 10.98.180.238/32 -p tcp -m comment --comment "velero/minio:console cluster IP" -m tcp --dport 9099 -j KUBE-MARK-MASQ
-A KUBE-SVC-K6NOPV6C6M2DHO7B -m comment --comment "velero/minio:console -> 10.244.169.89:9090" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-OLNIRCQFCXAN5USW
-A KUBE-SVC-K6NOPV6C6M2DHO7B -m comment --comment "velero/minio:console -> 10.244.38.178:9090" -j KUBE-SEP-DB3JUIMGX6LERFY3
192.168.26.106 | CHANGED | rc=0 >>
-A KUBE-SVC-K6NOPV6C6M2DHO7B ! -s 10.244.0.0/16 -d 10.98.180.238/32 -p tcp -m comment --comment "velero/minio:console cluster IP" -m tcp --dport 9099 -j KUBE-MARK-MASQ
-A KUBE-SVC-K6NOPV6C6M2DHO7B -m comment --comment "velero/minio:console -> 10.244.169.89:9090" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-OLNIRCQFCXAN5USW
-A KUBE-SVC-K6NOPV6C6M2DHO7B -m comment --comment "velero/minio:console -> 10.244.38.178:9090" -j KUBE-SEP-DB3JUIMGX6LERFY3
192.168.26.103 | CHANGED | rc=0 >>
-A KUBE-SVC-K6NOPV6C6M2DHO7B ! -s 10.244.0.0/16 -d 10.98.180.238/32 -p tcp -m comment --comment "velero/minio:console cluster IP" -m tcp --dport 9099 -j KUBE-MARK-MASQ
-A KUBE-SVC-K6NOPV6C6M2DHO7B -m comment --comment "velero/minio:console -> 10.244.169.89:9090" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-OLNIRCQFCXAN5USW
-A KUBE-SVC-K6NOPV6C6M2DHO7B -m comment --comment "velero/minio:console -> 10.244.38.178:9090" -j KUBE-SEP-DB3JUIMGX6LERFY3
root@all (6)[f:5]#

这两条链对应集群中SVC的 endpoint 的个数,每条链代表一个 endpoint,其中一条链中的规则 -m statistic --mode random --probability 0.50000000000 表示 iptables 负载均衡使用随机模式,以50%的概率匹配这个规则。

KUBE-SVC-K6NOPV6C6M2DHO7B 这条链有一半的几率跳转到 KUBE-SEP-OLNIRCQFCXAN5USWKUBE-SEP-DB3JUIMGX6LERFY3 任意一条链上。

选择任意一条 KUBE-SEP-XXX 查询 Kube-proxy 创建的对应 iptables 规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
root@all (6)[f:5]# bash -c "iptables-save |  grep -e '-A KUBE-SEP-OLNIRCQFCXAN5USW'"
192.168.26.102 | CHANGED | rc=0 >>
-A KUBE-SEP-OLNIRCQFCXAN5USW -s 10.244.169.89/32 -m comment --comment "velero/minio:console" -j KUBE-MARK-MASQ
-A KUBE-SEP-OLNIRCQFCXAN5USW -p tcp -m comment --comment "velero/minio:console" -m tcp -j DNAT --to-destination :0 --persistent --to-destination :0 --persistent --to-destination 0.0.0.0:0
192.168.26.100 | CHANGED | rc=0 >>
-A KUBE-SEP-OLNIRCQFCXAN5USW -s 10.244.169.89/32 -m comment --comment "velero/minio:console" -j KUBE-MARK-MASQ
-A KUBE-SEP-OLNIRCQFCXAN5USW -p tcp -m comment --comment "velero/minio:console" -m tcp -j DNAT --to-destination 10.244.169.89:9090
192.168.26.105 | CHANGED | rc=0 >>
-A KUBE-SEP-OLNIRCQFCXAN5USW -s 10.244.169.89/32 -m comment --comment "velero/minio:console" -j KUBE-MARK-MASQ
-A KUBE-SEP-OLNIRCQFCXAN5USW -p tcp -m comment --comment "velero/minio:console" -m tcp -j DNAT --to-destination :0 --persistent --to-destination :0 --persistent --to-destination 0.0.0.0:0
192.168.26.101 | CHANGED | rc=0 >>
-A KUBE-SEP-OLNIRCQFCXAN5USW -s 10.244.169.89/32 -m comment --comment "velero/minio:console" -j KUBE-MARK-MASQ
-A KUBE-SEP-OLNIRCQFCXAN5USW -p tcp -m comment --comment "velero/minio:console" -m tcp -j DNAT --to-destination :0 --persistent --to-destination :0 --persistent --to-destination 0.0.0.0:0
192.168.26.106 | CHANGED | rc=0 >>
-A KUBE-SEP-OLNIRCQFCXAN5USW -s 10.244.169.89/32 -m comment --comment "velero/minio:console" -j KUBE-MARK-MASQ
-A KUBE-SEP-OLNIRCQFCXAN5USW -p tcp -m comment --comment "velero/minio:console" -m tcp -j DNAT --to-destination :0 --persistent --to-destination :0 --persistent --to-destination 0.0.0.0:0
192.168.26.103 | CHANGED | rc=0 >>
-A KUBE-SEP-OLNIRCQFCXAN5USW -s 10.244.169.89/32 -m comment --comment "velero/minio:console" -j KUBE-MARK-MASQ
-A KUBE-SEP-OLNIRCQFCXAN5USW -p tcp -m comment --comment "velero/minio:console" -m tcp -j DNAT --to-destination :0 --persistent --to-destination :0 --persistent --to-destination 0.0.0.0:0
root@all (6)[f:5]#

这里会发现,iptables 版本不同,显示方式也不同,高版本的 iptables 会把数据包 DNAT 到 Pod 的 IP+端口 : 10.244.169.89:9090

1
-A KUBE-SEP-OLNIRCQFCXAN5USW -p tcp -m comment --comment "velero/minio:console" -m tcp -j DNAT --to-destination 10.244.169.89:9090

低版本的 iptables 会把数据包发送到任意端口,看上去直接把数据包丢弃了,至于为什么会这样,在 github 的一个 issues 下面找到了答案 ,这是 iptables-1.4 的显示问题;内核有正确的规则表示(从iptables 1.8可以持续正确显示的事实可以看出),只是iptables 1.4没有正确显示而已。

感兴趣小伙伴可以在下的 issues 中找到相关描述

https://github.com/kubernetes/kubernetes/issues/114537

1
-A KUBE-SEP-OLNIRCQFCXAN5USW -p tcp -m comment --comment "velero/minio:console" -m tcp -j DNAT --to-destination :0 --persistent --to-destination :0 --persistent --to-destination 0.0.0.0:0

上面的规则最后 DNAT 到 10.244.169.89:9090,不在一个网段,会读取路由表获取下一跳地址,这里我们查看其中一个节点路由信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$ansible-console -i host.yaml --limit 192.168.26.100
Welcome to the ansible console.
Type help or ? to list commands.

root@all (1)[f:5]# bash -c "route"
192.168.26.100 | CHANGED | rc=0 >>
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default 192.168.26.2 0.0.0.0 UG 0 0 0 ens32
10.244.31.64 vms106.liruilon 255.255.255.192 UG 0 0 0 tunl0
10.244.38.128 vms103.liruilon 255.255.255.192 UG 0 0 0 tunl0
10.244.63.64 vms102.liruilon 255.255.255.192 UG 0 0 0 tunl0
10.244.169.64 vms105.liruilon 255.255.255.192 UG 0 0 0 tunl0
10.244.198.0 vms101.liruilon 255.255.255.192 UG 0 0 0 tunl0
10.244.239.128 0.0.0.0 255.255.255.192 U 0 0 0 *
10.244.239.129 0.0.0.0 255.255.255.255 UH 0 0 0 calic2f7856928d
10.244.239.132 0.0.0.0 255.255.255.255 UH 0 0 0 cali349ff1af8c4
link-local 0.0.0.0 255.255.0.0 U 1002 0 0 ens32
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
192.168.26.0 0.0.0.0 255.255.255.0 U 0 0 0 ens32
root@all (1)[f:5]# bash -c "ip route | grep 169"
192.168.26.100 | CHANGED | rc=0 >>
10.244.169.64/26 via 192.168.26.105 dev tunl0 proto bird onlink
......

IP 为 10.244.169.89,通过路由信息查看可以确认,它匹配 10.244.169.64/26 via 192.168.26.105 dev tunl0 proto bird onlink 这一条路由

这条路由信息表示 它匹配 10.244.169.64/26 这个网段(2^(32-掩码位数)-2,10.244.169.64到10.244.169.127)的 IP ,并且说明下一跳地址为 192.168.26.105.

查看所有节点的路由信息,我们发现 192.168.26.105 这个节点路由没有,直接丢弃了相关的包,这是因为下一跳地址即是 192.168.26.105,即 Pod 位于这个节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@all (6)[f:5]# bash -c "ip route | grep 10.244.169.64"
192.168.26.101 | CHANGED | rc=0 >>
10.244.169.64/26 via 192.168.26.105 dev tunl0 proto bird onlink
192.168.26.102 | CHANGED | rc=0 >>
10.244.169.64/26 via 192.168.26.105 dev tunl0 proto bird onlink
192.168.26.105 | CHANGED | rc=0 >>
blackhole 10.244.169.64/26 proto bird
192.168.26.100 | CHANGED | rc=0 >>
10.244.169.64/26 via 192.168.26.105 dev tunl0 proto bird onlink
192.168.26.106 | CHANGED | rc=0 >>
10.244.169.64/26 via 192.168.26.105 dev tunl0 proto bird onlink
192.168.26.103 | CHANGED | rc=0 >>
10.244.169.64/26 via 192.168.26.105 dev tunl0 proto bird onlink
root@all (6)[f:5]#

所以这里要分两种情况,当 NodePort 访问使用 IP地址 不是 Pod 所在实际的 节点IP时, 会通过路由表跳转过去,当使用Pod所在节点IP 访问,则不需要走路由表获取下一跳地址。

到达目标节点 192.168.26.105 之后,任然要读取路由表,通过下面代码可以看到,通过路由条目 10.244.169.89 dev calia011d753862 scope link 即把对 PodIP 的请求给了虚拟网卡 calia011d753862,这其实是一对 veth pair ,数据包会直接到 对应的 Pod 内的虚拟网卡,也就是我要访问的 Pod。

1
2
3
4
5
6
7
8
9
10
11
12
13
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$ansible-console -i host.yaml --limit 192.168.26.105
Welcome to the ansible console.
Type help or ? to list commands.

root@all (1)[f:5]# bash -c "ip route | grep 89"
192.168.26.105 | CHANGED | rc=0 >>
10.244.169.89 dev calia011d753862 scope link
root@all (1)[f:5]# bash -c "docker ps | grep minio"
192.168.26.105 | CHANGED | rc=0 >>
408fdb941f59 cf9a50a36310 "/usr/bin/docker-ent…" About an hour ago Up About an hour k8s_minio_minio-b6d98746c-m7rzc_velero_1e9b20fc-1a68-4c2a-bb8f-80072e3c5dab_6
ea6fa7f2174f registry.aliyuncs.com/google_containers/pause:3.8 "/pause" About an hour ago Up About an hour k8s_POD_minio-b6d98746c-m7rzc_velero_1e9b20fc-1a68-4c2a-bb8f-80072e3c5dab_6
root@all (1)[f:5]#

我们可直接进入工作节点查看,容器网卡和节点网卡的对应关系。

1
2
3
4
5
6
7
8
9
10
11
12
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$ssh root@192.168.26.105
Last login: Sat Apr 8 10:51:08 2023 from 192.168.26.100
┌──[root@vms105.liruilongs.github.io]-[~]
└─$docker exec -it 408fdb941f59 bash
[root@minio-b6d98746c-m7rzc /]# cat /sys/class/net/eth0/iflink
7
[root@minio-b6d98746c-m7rzc /]# exit
exit
┌──[root@vms105.liruilongs.github.io]-[~]
└─$ip a | grep "7:"
7: calia011d753862@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP

到这一步,数据包由 NodeIP:NodePort 到了 PodIP:PodPort ,实现了 对 Pod 的请求。

在这里插入图片描述

请求的相关链路思维导图

因为在请求的时候,用到了 DNAT ,所以对应的,在回程的时候需要有 SNAT, 保证回程报文能够顺利返回.

为什么 DNAT 之后必须 SNAT?

客户端发起对一个服务的访问,假设源地址和目的地址是(C,VIP),那么客户端期待得到的回程报文的源地址是 VIP,即回程报文的源和目的地址对应该是(VIP,C)。

当网络报文经过网关(Linux 内核的 netfilter,包括 iptables 和 IPVS director)进行一次 DNAT 后,报文的源和目的地址对被修改成了(C,S)。

当报文送到服务端 S 后,服务端一看报文的源地址是 C 便直接把响应报文返回给 C,即此时响应报文的源和目的地址对是(S,C)。

这与客户端期待的报文源和目的地址(VIP,C)对不匹配,客户端收到后会简单丢弃该报文。

回到 kube-proxy 的 自定义 iptables 链分析,在最前面有些自定义链没有看,这部分自定义链最后都跳转到了 KUBE-MARK-MASQ 这里实际上就用于回程的时候做 SNAT。

1
2
3
-A KUBE-EXT-K6NOPV6C6M2DHO7B -m comment --comment "masquerade traffic for velero/minio:console external destinations" -j KUBE-MARK-MASQ
-A KUBE-SEP-OLNIRCQFCXAN5USW -s 10.244.169.89/32 -m comment --comment "velero/minio:console" -j KUBE-MARK-MASQ
-A KUBE-SVC-K6NOPV6C6M2DHO7B ! -s 10.244.0.0/16 -d 10.98.180.238/32 -p tcp -m comment --comment "velero/minio:console cluster IP" -m tcp --dport 9099 -j KUBE-MARK-MASQ

KUBE-MARK-MASQ 链本质上使用了 iptables 的 MARK 命令。对 KUBE-MARK-MASQ 链中的所有规则设置了 Kubernetes 独有的MARK 标记(0x4000/0x4000),可以通过下面的链看到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@all (6)[f:5]# bash -c "ipbash -c "iptables-save |  grep -e '-A KUBE-MARK-MASQ'"
192.168.26.100 | CHANGED | rc=0 >>
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
192.168.26.102 | CHANGED | rc=0 >>
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
192.168.26.106 | CHANGED | rc=0 >>
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
192.168.26.101 | CHANGED | rc=0 >>
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
192.168.26.105 | CHANGED | rc=0 >>
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
192.168.26.103 | CHANGED | rc=0 >>
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
root@all (6)[f:5]#

SNAT 一般发生在内置链POSTROUTING,所以在 节点 POSTROUTING 链中,有一条规则会跳转找 kube-proxy 自定义链 KUBE-POSTROUTING 中,这条链对节点上匹配 MARK 标记(0x4000/0x4000)的数据包在离开节点时进行一次SNAT,即MASQUERADE(用节点IP替换包的源IP)。通过下面的规则我们可以看大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$ansible-console -i host.yaml
Welcome to the ansible console.
Type help or ? to list commands.

root@all (6)[f:5]# bash -c "iptables-save | grep KUBE-POSTROUTING "
192.168.26.105 | CHANGED | rc=0 >>
:KUBE-POSTROUTING - [0:0]
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --set-xmark 0x4000/0x0
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
192.168.26.101 | CHANGED | rc=0 >>
:KUBE-POSTROUTING - [0:0]
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --set-xmark 0x4000/0x0
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
192.168.26.102 | CHANGED | rc=0 >>
:KUBE-POSTROUTING - [0:0]
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --set-xmark 0x4000/0x0
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
192.168.26.106 | CHANGED | rc=0 >>
:KUBE-POSTROUTING - [0:0]
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --set-xmark 0x4000/0x0
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
192.168.26.100 | CHANGED | rc=0 >>
:KUBE-POSTROUTING - [0:0]
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --set-xmark 0x4000/0x0
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
192.168.26.103 | CHANGED | rc=0 >>
:KUBE-POSTROUTING - [0:0]
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --set-xmark 0x4000/0x0
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
root@all (6)[f:5]#

自定义链中 POSTROUTING 链中被引用,被标记的包默认直接 做了 MASQUERADE 没有标记的包 直接 RETURN

在这里插入图片描述

回程的相关链路思维导图

上面即为整个 NodePort 类型的 SVC 从请求到回程的整个过程

ClusterIP

ClusterIPNodePort 类型基本类似

我们来简单看下。选择一个 ClusterIP 类型的 SVC

1
2
3
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$kubectl get svc -A | grep kube-dns
kube-system kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 71d

查看详细信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$kubectl describe svc kube-dns -n kube-system
Name: kube-dns
Namespace: kube-system
Labels: k8s-app=kube-dns
kubernetes.io/cluster-service=true
kubernetes.io/name=CoreDNS
Annotations: prometheus.io/port: 9153
prometheus.io/scrape: true
Selector: k8s-app=kube-dns
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.96.0.10
IPs: 10.96.0.10
Port: dns 53/UDP
TargetPort: 53/UDP
Endpoints: 10.244.239.129:53,10.244.239.132:53
Port: dns-tcp 53/TCP
TargetPort: 53/TCP
Endpoints: 10.244.239.129:53,10.244.239.132:53
Port: metrics 9153/TCP
TargetPort: 9153/TCP
Endpoints: 10.244.239.129:9153,10.244.239.132:9153
Session Affinity: None
Events: <none>

可以看到有两个 endpoint ,通过deploy 副本数我们也可以看到。

1
2
3
4
5
6
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$kubectl get pod -A | grep dns
kube-system coredns-c676cc86f-kpvcj 1/1 Running 18 (16h ago) 71d
kube-system coredns-c676cc86f-xqj8d 1/1 Running 17 (16h ago) 71d
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$

当 SVC 类型为 ClusterIP 的时候,只能在集群访问,并且通过 集群IP+Pod端口 的方式进行访问,所以这里我们通过 目标IP和端口来查询 kube-proxy 生成的 iptables 规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$ansible-console -i host.yaml
Welcome to the ansible console.
Type help or ? to list commands.

root@all (6)[f:5]# bash -c "iptables-save | grep '10.96.0.10' | grep 'dport 9153'"
192.168.26.105 | CHANGED | rc=0 >>
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-SVC-JD5MR3NA4I4DYORP
-A KUBE-SVC-JD5MR3NA4I4DYORP ! -s 10.244.0.0/16 -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-MARK-MASQ
192.168.26.102 | CHANGED | rc=0 >>
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-SVC-JD5MR3NA4I4DYORP
-A KUBE-SVC-JD5MR3NA4I4DYORP ! -s 10.244.0.0/16 -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-MARK-MASQ
192.168.26.106 | CHANGED | rc=0 >>
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-SVC-JD5MR3NA4I4DYORP
-A KUBE-SVC-JD5MR3NA4I4DYORP ! -s 10.244.0.0/16 -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-MARK-MASQ
192.168.26.101 | CHANGED | rc=0 >>
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-SVC-JD5MR3NA4I4DYORP
-A KUBE-SVC-JD5MR3NA4I4DYORP ! -s 10.244.0.0/16 -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-MARK-MASQ
192.168.26.100 | CHANGED | rc=0 >>
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-SVC-JD5MR3NA4I4DYORP
-A KUBE-SVC-JD5MR3NA4I4DYORP ! -s 10.244.0.0/16 -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-MARK-MASQ
192.168.26.103 | CHANGED | rc=0 >>
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-SVC-JD5MR3NA4I4DYORP
-A KUBE-SVC-JD5MR3NA4I4DYORP ! -s 10.244.0.0/16 -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-MARK-MASQ
root@all (6)[f:5]#

通过上面的代码可以确认,生成的第一条链为 KUBE-SERVICES ,它会在每个节点上 匹配 目标IP 为 10.96.0.10/32 端口为 9153 的数据包,动作为 跳转到自定义链 KUBE-SVC-JD5MR3NA4I4DYORP

这条链从那个 内置链跳转过来,我们可以通过下面的方式获取,可以发现,由内置链 PREROUTING 跳转到自定义链 KUBE-SERVICES

1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@all (6)[f:5]# bash -c "iptables-save | grep -e '-j KUBE-SERVICES' | grep PREROUTING"
192.168.26.101 | CHANGED | rc=0 >>
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
192.168.26.102 | CHANGED | rc=0 >>
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
192.168.26.106 | CHANGED | rc=0 >>
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
192.168.26.100 | CHANGED | rc=0 >>
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
192.168.26.105 | CHANGED | rc=0 >>
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
192.168.26.103 | CHANGED | rc=0 >>
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
root@all (6)[f:5]#

继续前面的规则分析,已知 PREROUTING >> KUBE-SERVICES >> KUBE-SVC-JD5MR3NA4I4DYORP,所以看下 KUBE-SVC-JD5MR3NA4I4DYORP 自定义链规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
root@all (6)[f:5]# bash -c "iptables-save | grep -e '-A KUBE-SVC-JD5MR3NA4I4DYORP'"
192.168.26.101 | CHANGED | rc=0 >>
-A KUBE-SVC-JD5MR3NA4I4DYORP ! -s 10.244.0.0/16 -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-MARK-MASQ
-A KUBE-SVC-JD5MR3NA4I4DYORP -m comment --comment "kube-system/kube-dns:metrics -> 10.244.239.129:9153" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-DYDOGONVKJMXNXZO
-A KUBE-SVC-JD5MR3NA4I4DYORP -m comment --comment "kube-system/kube-dns:metrics -> 10.244.239.132:9153" -j KUBE-SEP-PURVZ5VLWID76HVQ
192.168.26.105 | CHANGED | rc=0 >>
-A KUBE-SVC-JD5MR3NA4I4DYORP ! -s 10.244.0.0/16 -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-MARK-MASQ
-A KUBE-SVC-JD5MR3NA4I4DYORP -m comment --comment "kube-system/kube-dns:metrics -> 10.244.239.129:9153" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-DYDOGONVKJMXNXZO
-A KUBE-SVC-JD5MR3NA4I4DYORP -m comment --comment "kube-system/kube-dns:metrics -> 10.244.239.132:9153" -j KUBE-SEP-PURVZ5VLWID76HVQ
192.168.26.102 | CHANGED | rc=0 >>
-A KUBE-SVC-JD5MR3NA4I4DYORP ! -s 10.244.0.0/16 -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-MARK-MASQ
-A KUBE-SVC-JD5MR3NA4I4DYORP -m comment --comment "kube-system/kube-dns:metrics -> 10.244.239.129:9153" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-DYDOGONVKJMXNXZO
-A KUBE-SVC-JD5MR3NA4I4DYORP -m comment --comment "kube-system/kube-dns:metrics -> 10.244.239.132:9153" -j KUBE-SEP-PURVZ5VLWID76HVQ
192.168.26.106 | CHANGED | rc=0 >>
-A KUBE-SVC-JD5MR3NA4I4DYORP ! -s 10.244.0.0/16 -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-MARK-MASQ
-A KUBE-SVC-JD5MR3NA4I4DYORP -m comment --comment "kube-system/kube-dns:metrics -> 10.244.239.129:9153" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-DYDOGONVKJMXNXZO
-A KUBE-SVC-JD5MR3NA4I4DYORP -m comment --comment "kube-system/kube-dns:metrics -> 10.244.239.132:9153" -j KUBE-SEP-PURVZ5VLWID76HVQ
192.168.26.100 | CHANGED | rc=0 >>
-A KUBE-SVC-JD5MR3NA4I4DYORP ! -s 10.244.0.0/16 -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-MARK-MASQ
-A KUBE-SVC-JD5MR3NA4I4DYORP -m comment --comment "kube-system/kube-dns:metrics -> 10.244.239.129:9153" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-DYDOGONVKJMXNXZO
-A KUBE-SVC-JD5MR3NA4I4DYORP -m comment --comment "kube-system/kube-dns:metrics -> 10.244.239.132:9153" -j KUBE-SEP-PURVZ5VLWID76HVQ
192.168.26.103 | CHANGED | rc=0 >>
-A KUBE-SVC-JD5MR3NA4I4DYORP ! -s 10.244.0.0/16 -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-MARK-MASQ
-A KUBE-SVC-JD5MR3NA4I4DYORP -m comment --comment "kube-system/kube-dns:metrics -> 10.244.239.129:9153" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-DYDOGONVKJMXNXZO
-A KUBE-SVC-JD5MR3NA4I4DYORP -m comment --comment "kube-system/kube-dns:metrics -> 10.244.239.132:9153" -j KUBE-SEP-PURVZ5VLWID76HVQ
root@all (6)[f:5]#

可以看到这里和 NodePort 一样, 使用 iptables 内置的随机负载均衡,条到两个 KUBE-SEP- 开头的内置链,对应 SVC 中的 endpoint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
root@all (6)[f:5]# bash -c "iptables-save | grep -e '-A KUBE-SEP-DYDOGONVKJMXNXZO'"
192.168.26.100 | CHANGED | rc=0 >>
-A KUBE-SEP-DYDOGONVKJMXNXZO -s 10.244.239.129/32 -m comment --comment "kube-system/kube-dns:metrics" -j KUBE-MARK-MASQ
-A KUBE-SEP-DYDOGONVKJMXNXZO -p tcp -m comment --comment "kube-system/kube-dns:metrics" -m tcp -j DNAT --to-destination 10.244.239.129:9153
192.168.26.102 | CHANGED | rc=0 >>
-A KUBE-SEP-DYDOGONVKJMXNXZO -s 10.244.239.129/32 -m comment --comment "kube-system/kube-dns:metrics" -j KUBE-MARK-MASQ
-A KUBE-SEP-DYDOGONVKJMXNXZO -p tcp -m comment --comment "kube-system/kube-dns:metrics" -m tcp -j DNAT --to-destination :0 --persistent --to-destination :0 --persistent --to-destination 0.0.0.0:0
192.168.26.101 | CHANGED | rc=0 >>
-A KUBE-SEP-DYDOGONVKJMXNXZO -s 10.244.239.129/32 -m comment --comment "kube-system/kube-dns:metrics" -j KUBE-MARK-MASQ
-A KUBE-SEP-DYDOGONVKJMXNXZO -p tcp -m comment --comment "kube-system/kube-dns:metrics" -m tcp -j DNAT --to-destination :0 --persistent --to-destination :0 --persistent --to-destination 0.0.0.0:0
192.168.26.105 | CHANGED | rc=0 >>
-A KUBE-SEP-DYDOGONVKJMXNXZO -s 10.244.239.129/32 -m comment --comment "kube-system/kube-dns:metrics" -j KUBE-MARK-MASQ
-A KUBE-SEP-DYDOGONVKJMXNXZO -p tcp -m comment --comment "kube-system/kube-dns:metrics" -m tcp -j DNAT --to-destination :0 --persistent --to-destination :0 --persistent --to-destination 0.0.0.0:0
192.168.26.106 | CHANGED | rc=0 >>
-A KUBE-SEP-DYDOGONVKJMXNXZO -s 10.244.239.129/32 -m comment --comment "kube-system/kube-dns:metrics" -j KUBE-MARK-MASQ
-A KUBE-SEP-DYDOGONVKJMXNXZO -p tcp -m comment --comment "kube-system/kube-dns:metrics" -m tcp -j DNAT --to-destination :0 --persistent --to-destination :0 --persistent --to-destination 0.0.0.0:0
192.168.26.103 | CHANGED | rc=0 >>
-A KUBE-SEP-DYDOGONVKJMXNXZO -s 10.244.239.129/32 -m comment --comment "kube-system/kube-dns:metrics" -j KUBE-MARK-MASQ
-A KUBE-SEP-DYDOGONVKJMXNXZO -p tcp -m comment --comment "kube-system/kube-dns:metrics" -m tcp -j DNAT --to-destination :0 --persistent --to-destination :0 --persistent --to-destination 0.0.0.0:0
root@all (6)[f:5]#

Ok ,这部分也和 NodePort 一样,这里不在多讲。

1
2
3
4
5
6
7
8
9
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$ansible-console -i host.yaml --limit 192.168.26.100
Welcome to the ansible console.
Type help or ? to list commands.

root@all (1)[f:5]# bash -c "ip route | grep 129 "
192.168.26.100 | CHANGED | rc=0 >>
10.244.239.129 dev calic2f7856928d scope link
root@all (1)[f:5]#

通过上面的分析可以看到 集群IP 类型的SVC 请求路径为 PREROUTING >> KUBE-SERVICES >> KUBE-SVC-JD5MR3NA4I4DYORP >> KUBE-SEP-XXX >> DNAT 之后下一跳到目标Pod节点

LoadBalancer

最后我们看一下 LoadBalancer 类型的 SVC 的请求过程

1
2
3
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$kubectl get svc | grep LoadBalancer
release-name-grafana LoadBalancer 10.96.85.130 192.168.26.223 80:30174/TCP 55d

选择一个当前集群存在的 LB 类型的 SVC。查看集群详细信息,当前 SVC 的 LB 为 192.168.26.223

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$kubectl describe svc release-name-grafana
Name: release-name-grafana
Namespace: default
Labels: app.kubernetes.io/instance=release-name
app.kubernetes.io/managed-by=Helm
app.kubernetes.io/name=grafana
app.kubernetes.io/version=8.3.3
helm.sh/chart=grafana-6.20.5
Annotations: <none>
Selector: app.kubernetes.io/instance=release-name,app.kubernetes.io/name=grafana
Type: LoadBalancer
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.96.85.130
IPs: 10.96.85.130
LoadBalancer Ingress: 192.168.26.223
Port: http-web 80/TCP
TargetPort: 3000/TCP
NodePort: http-web 30174/TCP
Endpoints: 10.244.31.123:3000
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$

直接通过 LB 对应的 IP 查询。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$ansible-console -i host.yaml
Welcome to the ansible console.
Type help or ? to list commands.

root@all (6)[f:5]# bash -c "iptables-save | grep '192.168.26.223'"
192.168.26.101 | CHANGED | rc=0 >>
-A KUBE-SERVICES -d 192.168.26.223/32 -p tcp -m comment --comment "default/release-name-grafana:http-web loadbalancer IP" -m tcp --dport 80 -j KUBE-EXT-XR6NUEPTVOUAGEC3
192.168.26.100 | CHANGED | rc=0 >>
-A KUBE-SERVICES -d 192.168.26.223/32 -p tcp -m comment --comment "default/release-name-grafana:http-web loadbalancer IP" -m tcp --dport 80 -j KUBE-EXT-XR6NUEPTVOUAGEC3
192.168.26.102 | CHANGED | rc=0 >>
-A KUBE-SERVICES -d 192.168.26.223/32 -p tcp -m comment --comment "default/release-name-grafana:http-web loadbalancer IP" -m tcp --dport 80 -j KUBE-EXT-XR6NUEPTVOUAGEC3
192.168.26.105 | CHANGED | rc=0 >>
-A KUBE-SERVICES -d 192.168.26.223/32 -p tcp -m comment --comment "default/release-name-grafana:http-web loadbalancer IP" -m tcp --dport 80 -j KUBE-EXT-XR6NUEPTVOUAGEC3
192.168.26.106 | CHANGED | rc=0 >>
-A KUBE-SERVICES -d 192.168.26.223/32 -p tcp -m comment --comment "default/release-name-grafana:http-web loadbalancer IP" -m tcp --dport 80 -j KUBE-EXT-XR6NUEPTVOUAGEC3
192.168.26.103 | CHANGED | rc=0 >>
-A KUBE-SERVICES -d 192.168.26.223/32 -p tcp -m comment --comment "default/release-name-grafana:http-web loadbalancer IP" -m tcp --dport 80 -j KUBE-EXT-XR6NUEPTVOUAGEC3
root@all (6)[f:5]#

通过上面的代码可以看到,由 KUBE-SERVICES 到了 KUBE-EXT-XR6NUEPTVOUAGEC3 ,这里虽然和 集群IP一样第一条自定义链都是 KUBE-SERVICES,但是集群IP由 KUBE-SERVICES 直接到了以 KUBE-SVC-XXX 开头的链,这里到了以 KUBE-EXT-XXX开头的链。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
root@all (6)[f:5]# bash -c "iptables-save | grep -e '-A KUBE-EXT-XR6NUEPTVOUAGEC3'"
192.168.26.101 | CHANGED | rc=0 >>
-A KUBE-EXT-XR6NUEPTVOUAGEC3 -m comment --comment "masquerade traffic for default/release-name-grafana:http-web external destinations" -j KUBE-MARK-MASQ
-A KUBE-EXT-XR6NUEPTVOUAGEC3 -j KUBE-SVC-XR6NUEPTVOUAGEC3
192.168.26.102 | CHANGED | rc=0 >>
-A KUBE-EXT-XR6NUEPTVOUAGEC3 -m comment --comment "masquerade traffic for default/release-name-grafana:http-web external destinations" -j KUBE-MARK-MASQ
-A KUBE-EXT-XR6NUEPTVOUAGEC3 -j KUBE-SVC-XR6NUEPTVOUAGEC3
192.168.26.100 | CHANGED | rc=0 >>
-A KUBE-EXT-XR6NUEPTVOUAGEC3 -m comment --comment "masquerade traffic for default/release-name-grafana:http-web external destinations" -j KUBE-MARK-MASQ
-A KUBE-EXT-XR6NUEPTVOUAGEC3 -j KUBE-SVC-XR6NUEPTVOUAGEC3
192.168.26.105 | CHANGED | rc=0 >>
-A KUBE-EXT-XR6NUEPTVOUAGEC3 -m comment --comment "masquerade traffic for default/release-name-grafana:http-web external destinations" -j KUBE-MARK-MASQ
-A KUBE-EXT-XR6NUEPTVOUAGEC3 -j KUBE-SVC-XR6NUEPTVOUAGEC3
192.168.26.106 | CHANGED | rc=0 >>
-A KUBE-EXT-XR6NUEPTVOUAGEC3 -m comment --comment "masquerade traffic for default/release-name-grafana:http-web external destinations" -j KUBE-MARK-MASQ
-A KUBE-EXT-XR6NUEPTVOUAGEC3 -j KUBE-SVC-XR6NUEPTVOUAGEC3
192.168.26.103 | CHANGED | rc=0 >>
-A KUBE-EXT-XR6NUEPTVOUAGEC3 -m comment --comment "masquerade traffic for default/release-name-grafana:http-web external destinations" -j KUBE-MARK-MASQ
-A KUBE-EXT-XR6NUEPTVOUAGEC3 -j KUBE-SVC-XR6NUEPTVOUAGEC3

KUBE-EXT-XR6NUEPTVOUAGEC3 到了 KUBE-SVC-XR6NUEPTVOUAGEC3 ,这里多的 KUBE-EXT-XXXX 的链和 NodePort 类型的SVC一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
root@all (6)[f:5]# bash -c "iptables-save | grep -e '-A KUBE-SVC-XR6NUEPTVOUAGEC3'"
192.168.26.102 | CHANGED | rc=0 >>
-A KUBE-SVC-XR6NUEPTVOUAGEC3 ! -s 10.244.0.0/16 -d 10.96.85.130/32 -p tcp -m comment --comment "default/release-name-grafana:http-web cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SVC-XR6NUEPTVOUAGEC3 -m comment --comment "default/release-name-grafana:http-web -> 10.244.31.123:3000" -j KUBE-SEP-RLRDAS77BUJVWRRK
192.168.26.101 | CHANGED | rc=0 >>
-A KUBE-SVC-XR6NUEPTVOUAGEC3 ! -s 10.244.0.0/16 -d 10.96.85.130/32 -p tcp -m comment --comment "default/release-name-grafana:http-web cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SVC-XR6NUEPTVOUAGEC3 -m comment --comment "default/release-name-grafana:http-web -> 10.244.31.123:3000" -j KUBE-SEP-RLRDAS77BUJVWRRK
192.168.26.106 | CHANGED | rc=0 >>
-A KUBE-SVC-XR6NUEPTVOUAGEC3 ! -s 10.244.0.0/16 -d 10.96.85.130/32 -p tcp -m comment --comment "default/release-name-grafana:http-web cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SVC-XR6NUEPTVOUAGEC3 -m comment --comment "default/release-name-grafana:http-web -> 10.244.31.123:3000" -j KUBE-SEP-RLRDAS77BUJVWRRK
192.168.26.105 | CHANGED | rc=0 >>
-A KUBE-SVC-XR6NUEPTVOUAGEC3 ! -s 10.244.0.0/16 -d 10.96.85.130/32 -p tcp -m comment --comment "default/release-name-grafana:http-web cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SVC-XR6NUEPTVOUAGEC3 -m comment --comment "default/release-name-grafana:http-web -> 10.244.31.123:3000" -j KUBE-SEP-RLRDAS77BUJVWRRK
192.168.26.100 | CHANGED | rc=0 >>
-A KUBE-SVC-XR6NUEPTVOUAGEC3 ! -s 10.244.0.0/16 -d 10.96.85.130/32 -p tcp -m comment --comment "default/release-name-grafana:http-web cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SVC-XR6NUEPTVOUAGEC3 -m comment --comment "default/release-name-grafana:http-web -> 10.244.31.123:3000" -j KUBE-SEP-RLRDAS77BUJVWRRK
192.168.26.103 | CHANGED | rc=0 >>
-A KUBE-SVC-XR6NUEPTVOUAGEC3 ! -s 10.244.0.0/16 -d 10.96.85.130/32 -p tcp -m comment --comment "default/release-name-grafana:http-web cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SVC-XR6NUEPTVOUAGEC3 -m comment --comment "default/release-name-grafana:http-web -> 10.244.31.123:3000" -j KUBE-SEP-RLRDAS77BUJVWRRK

之后到了 KUBE-SEP 开头的链,这部分三种类型的 SVC 都一样,不多介绍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
root@all (6)[f:5]# bash -c "iptables-save | grep -e '-A KUBE-SEP-RLRDAS77BUJVWRRK'"
192.168.26.106 | CHANGED | rc=0 >>
-A KUBE-SEP-RLRDAS77BUJVWRRK -s 10.244.31.123/32 -m comment --comment "default/release-name-grafana:http-web" -j KUBE-MARK-MASQ
-A KUBE-SEP-RLRDAS77BUJVWRRK -p tcp -m comment --comment "default/release-name-grafana:http-web" -m tcp -j DNAT --to-destination :0 --persistent --to-destination :0 --persistent --to-destination 0.0.0.0:0 --persistent
192.168.26.105 | CHANGED | rc=0 >>
-A KUBE-SEP-RLRDAS77BUJVWRRK -s 10.244.31.123/32 -m comment --comment "default/release-name-grafana:http-web" -j KUBE-MARK-MASQ
-A KUBE-SEP-RLRDAS77BUJVWRRK -p tcp -m comment --comment "default/release-name-grafana:http-web" -m tcp -j DNAT --to-destination :0 --persistent --to-destination :0 --persistent --to-destination 0.0.0.0:0 --persistent
192.168.26.102 | CHANGED | rc=0 >>
-A KUBE-SEP-RLRDAS77BUJVWRRK -s 10.244.31.123/32 -m comment --comment "default/release-name-grafana:http-web" -j KUBE-MARK-MASQ
-A KUBE-SEP-RLRDAS77BUJVWRRK -p tcp -m comment --comment "default/release-name-grafana:http-web" -m tcp -j DNAT --to-destination :0 --persistent --to-destination :0 --persistent --to-destination 0.0.0.0:0 --persistent
192.168.26.101 | CHANGED | rc=0 >>
-A KUBE-SEP-RLRDAS77BUJVWRRK -s 10.244.31.123/32 -m comment --comment "default/release-name-grafana:http-web" -j KUBE-MARK-MASQ
-A KUBE-SEP-RLRDAS77BUJVWRRK -p tcp -m comment --comment "default/release-name-grafana:http-web" -m tcp -j DNAT --to-destination :0 --persistent --to-destination :0 --persistent --to-destination 0.0.0.0:0 --persistent
192.168.26.100 | CHANGED | rc=0 >>
-A KUBE-SEP-RLRDAS77BUJVWRRK -s 10.244.31.123/32 -m comment --comment "default/release-name-grafana:http-web" -j KUBE-MARK-MASQ
-A KUBE-SEP-RLRDAS77BUJVWRRK -p tcp -m comment --comment "default/release-name-grafana:http-web" -m tcp -j DNAT --to-destination 10.244.31.123:3000
192.168.26.103 | CHANGED | rc=0 >>
-A KUBE-SEP-RLRDAS77BUJVWRRK -s 10.244.31.123/32 -m comment --comment "default/release-name-grafana:http-web" -j KUBE-MARK-MASQ
-A KUBE-SEP-RLRDAS77BUJVWRRK -p tcp -m comment --comment "default/release-name-grafana:http-web" -m tcp -j DNAT --to-destination :0 --persistent --to-destination :0 --persistent --to-destination 0.0.0.0:0 --persistent
root@all (6)[f:5]#
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$ip route | grep 31
10.244.31.64/26 via 192.168.26.106 dev tunl0 proto bird onlink
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$

关于 SVC 三种不同类型的服务发布到Pod路劲解析就和小伙伴们分享到这,简单总结下:

kube-proxyiptables 模式下, 三种服务类型都是通过 iptables 的自定义链匹配数据包,做 DNAT 后,根据每个节点上的路由表获取下一跳地址,到达 目标 POD 所在节点,在通过路由选择的方式,把数据包发到节点和 Pod 对应的一对 veth pair 虚拟网卡进行通信。
回程的时候 通过在 POSTROUTING 内置链中添加自定义链,对数据包做 SNAT。 地址伪装,修改源IP,这里需要注意下 在自定义链中:

iptables 自定义链对应三种服务略有不同:

  • NodePort : PREROUTING >> KUBE-SERVICES >> KUBE-NODEPORTS >> KUBE-EXT-XXXXXX >> KUBE-SVC-XXXXXX >> KUBE-SEP-XXXXX >> DANT
  • ClusterIP: PREROUTING >> KUBE-SERVICES >> KUBE-SVC-XXXXX >> KUBE-SEP-XXX >> DNAT
  • LoadBalancer:PREROUTING >> KUBE-SERVICES >> KUBE-EXT-XXXXXX >> KUBE-SVC-XXXXXX >> KUBE-SEP-XXXXX >> DANT

篇幅问题,关于 Flannel 的部分放到下一篇

博文部分内容参考

© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知 :)


《Kubernetes 网络权威指南:基础、原理与实践》

《Kubernetes权威指南:从Docker到Kubernetes实践全接触》


© 2018-至今 liruilonger@gmail.com, All rights reserved. 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)

发布于

2024-02-23

更新于

2024-12-30

许可协议

评论
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×