一、Docker网络模式
在讨论Kubernetes网络之前,让我们先来看一下Docker网络。Docker采用插件化的网络模式,默认提供bridge、host、none、overlay、maclan和Network plugins这几种网络模式,运行容器时可以通过–network参数设置具体使用那一种模式。
容器网络模型-The Container Network Model缩写为CNM .
CNM容器网络模型是docker提出的,docker公司推动的是docker容器技术,cni容器网络模型是coreos提出的,coreos推动的rkt容器技术
flannel就是coreos公司的杰作.docker的libnetwork是对CNM的实现, 提供docker核心网络架构的全部功能, Libnetwork使用Go语言编写,目标除了定义一个容器网络模型(CNM),为应用程序提供一致的编程接口以及网络抽象.
- bridge:这是Docker默认的网络驱动,此模式会为每一个容器分配Network Namespace和设置IP等,并将容器连接到一个虚拟网桥上。如果未指定网络驱动,这默认使用此驱动。
- host:此网络驱动直接使用宿主机的网络。
- none:此驱动不构造网络环境。采用了none 网络驱动,那么就只能使用loopback网络设备,容器只能使用127.0.0.1的本机网络。
- overlay:此网络驱动可以使多个Docker daemons连接在一起,并能够使用swarm服务之间进行通讯。也可以使用overlay网络进行swarm服务和容器之间、容器之间进行通讯,
- macvlan:此网络允许为容器指定一个MAC地址,允许容器作为网络中的物理设备,这样Docker daemon就可以通过MAC地址进行访问的路由。对于希望直接连接网络网络的遗留应用,这种网络驱动有时可能是最好的选择。
- Network plugins:可以安装和使用第三方的网络插件。可以在Docker Store或第三方供应商处获取这些插件:flannel等。
在默认情况,Docker使用bridge网络模式,bridge网络驱动的示意图如下,我们先以bridge模式对Docker的网络进行说明。
1.1 bridge网络的构建过程如下:
1)安装Docker时,创建一个名为docke0的虚拟网桥,虚拟网桥使用“10.0.0.0 -10.255.255.255 “、”172.16.0.0-172.31.255.255″和“192.168.0.0——192.168.255.255”这三个私有网络的地址范围。
通过 ifconfig 命令可以查看docker0网桥的信息:
通过 docker network inspect bridge 可以查看网桥的子网网络范围和网关:
2)运行容器时,在宿主机上创建虚拟网卡veth pair设备,veth pair设备是成对出现的,从而组成一个数据通道,数据从一个设备进入,就会从另一个设备出来。将veth pair设备的一端放在新创建的容器中,命名为eth0;另一端放在宿主机的docker0中,以veth为前缀的名字命名。通过 brctl show 命令查看放在docker0中的veth pair设备
1.2 外部访问
bridge的docker0是虚拟出来的网桥,因此无法被外部的网络访问。因此需要在运行容器时通过-p和-P参数对将容器的端口映射到宿主机的端口。实际上Docker是采用 NAT的 方式,将容器内部的服务监听端口与宿主机的某一个端口port 进行绑定,使得宿主机外部可以将网络报文发送至容器。
1)通过-P参数,将容器的端口映射到宿主机的随机端口:
$ docker run -P {images}
2)通过-p参数,将容器的端口映射到宿主机的制定端口:
$ docker run -p {hostPort}:{containerPort} {images}
可以修改docker的默认网段:
第一步 删除原有配置
sudo service docker stop
sudo ip link set dev docker0 down
sudo brctl delbr docker0
sudo iptables -t nat -F POSTROUTING
第二步 创建新的网桥
sudo brctl addbr docker0
sudo ip addr add 172.17.10.1/24 dev docker0
sudo ip link set dev docker0 up
第三步 配置Docker的文件
注意: 这里是 增加下面的配置
vi /etc/docker/daemon.json
##追加的即可
cat /etc/docker/daemon.json
{"registry-mirrors": ["http://224ac393.m.daocloud.io"],
"bip": "172.17.10.1/24"
}
$ systemctl restart docker
二、Kubernetes网络模式
Kubernetes与Docker网络有些不同。Kubernetes网络需要解决下面的4个问题:
- 集群内:
- 容器与容器之间的通信
- Pod和Pod之间的通信
- Pod和服务之间的通信
- 集群外:
- 外部应用与服务之间的通信
因此,Kubernetes假设Pod之间能够进行通讯,这些Pod可能部署在不同的宿主机上。每一个Pod都拥有自己的IP地址,因此能够将Pod看作为物理主机或者虚拟机,从而能实现端口设置、命名、服务发现、负载均衡、应用配置和迁移。为了满足上述需求,则需要通过集群网络来实现。
下文主要分析容器与容器之间,以及Pod和Pod之间的通信;
2.1 同一个Pod中容器之间的通信
这种场景对于Kubernetes来说没有任何问题,根据Kubernetes的架构设计。Kubernetes创建Pod时,首先会创建一个pause容器,为Pod指派一个唯一的IP地址。然后,以pause的网络命名空间为基础,创建同一个Pod内的其它容器(–net=container:xxx)。因此,同一个Pod内的所有容器就会共享同一个网络命名空间,在同一个Pod之间的容器可以直接使用localhost进行通信。
2.2 不同Pod中容器之间的通信:网络插件
对于此场景,情况现对比较复杂一些,这就需要解决Pod间的通信问题。
Kubernetes采用的CNI标准,让Kubernetes生态系统中的网络解决方案百花齐放。更多样的选择,意味着大多数用户将能够找到适合其当前需求和部署环境的CNI插件,同时还可以在环境发生变化时也能找到新的解决方案。
CNI (Container Network Interface) 定义了一组用于实现容器网络接口的配置以及 IP 地址的分配的规范。CNI 只关注容器的网络连接以及当容器删除时移除被分配的网络资源,因此 CNI 得到了广泛的支持,并且规范也易于实现。
CNI的初衷是创建一个框架,用于在配置或销毁容器时动态配置适当的网络配置和资源。下面链接中的CNI规范概括了用于配制网络的插件接口,这个接口可以让容器运行时与插件进行协调:https://github.com/containernetworking/cni/blob/master/SPEC.md。
CNI插件负责为接口配置和管理IP地址,并且通常提供与IP管理、每个容器的IP分配、以及多主机连接相关的功能。容器运行时会调用网络插件,从而在容器启动时分配IP地址并配置网络,并在删除容器时再次调用它以清理这些资源。
运行时或协调器决定了容器应该加入哪个网络以及它需要调用哪个插件。然后,插件会将接口添加到容器网络命名空间中,作为一个veth对的一侧。接着,它会在主机上进行更改,包括将veth的其他部分连接到网桥。再之后,它会通过调用单独的IPAM(IP地址管理)插件来分配IP地址并设置路由。
在Kubernetes中,kubelet可以在适当的时间调用它找到的插件,来为通过kubelet启动的pod进行自动的网络配置。
CNI(Containernetworking Interface)网络插件是一个由Linux基金会维护的开源项目,它可以为容器提供网络连接。在Kubernetes中,可以通过CNI网络插件来为Pod提供网络连接。
目前市面上主流的CNI网络插件有以下几种:
- Flannel:使用VXLAN技术实现网络隔离和扁平化IP;
- Calico:采用BGP协议实现高效的容器网络互连;
- Weave Net:使用虚拟机间通信(VXLAN)技术,在容器之间创建多层网络;
- Canal:结合Flannel和Calico两种CNI网络插件的优点,实现网络隔离和BGP路由。
2.3 常见的几种CNI网络插件对比
下面我们来对比这几种CNI网络插件。
CNI网络插件 | 优点 | 缺点 | 是否支持网络策略 |
---|---|---|---|
Flannel | 部署简单,性能优秀 | 网络层延迟高 | 否 |
Calico | 性能最好,支持容器内BGP协议,支持网络策略 | 配置复杂 | 是 |
Weave Net | 功能强大,跨平台支持 | 性能低下,容易出现网络死锁 | 是 |
Canal | 结合了Flannel和Calico两种插件的优点,支持多种网络模式,可以满足不同的需求 | 部署和配置较为繁琐 | 是 |
在Kubernetes通过flannel、Calico等网络插件解决Pod间的通信问题。
常见的术语包括:
-
第2层网络:OSI(Open Systems Interconnections,开放系统互连)网络模型的“数据链路”层。第2层网络会处理网络上两个相邻节点之间的帧传递。第2层网络的一个值得注意的示例是以太网,其中MAC表示为子层。
-
第3层网络:OSI网络模型的“网络”层。第3层网络的主要关注点,是在第2层连接之上的主机之间路由数据包。IPv4、IPv6和ICMP是第3层网络协议的示例。
-
VXLAN:代表“虚拟可扩展LAN”。首先,VXLAN用于通过在UDP数据报中封装第2层以太网帧来帮助实现大型云部署。VXLAN虚拟化与VLAN类似,但提供更大的灵活性和功能(VLAN仅限于4096个网络ID)。VXLAN是一种封装和覆盖协议,可在现有网络上运行。
-
Overlay网络:Overlay网络是建立在现有网络之上的虚拟逻辑网络。Overlay网络通常用于在现有网络之上提供有用的抽象,并分离和保护不同的逻辑网络。
-
封装:封装是指在附加层中封装网络数据包以提供其他上下文和信息的过程。在overlay网络中,封装被用于从虚拟网络转换到底层地址空间,从而能路由到不同的位置(数据包可以被解封装,并继续到其目的地)。
-
网状网络:网状网络(Mesh network)是指每个节点连接到许多其他节点以协作路由、并实现更大连接的网络。网状网络允许通过多个路径进行路由,从而提供更可靠的网络。网状网格的缺点是每个附加节点都会增加大量开销。
-
BGP:代表“边界网关协议”,用于管理边缘路由器之间数据包的路由方式。BGP通过考虑可用路径,路由规则和特定网络策略,帮助弄清楚如何将数据包从一个网络发送到另一个网络。BGP有时被用作CNI插件中的路由机制,而不是封装的覆盖网络。
2.4 Flannel插件
Flannel是CoreOS开源的CNI网络插件。flannel项目是在三层物理网络之上构建一个可跨节点通信容器网络,负责为节点下发子网和路由等信息,为容器分发唯一IP,flannel只实现简单的网络通信,不支持网络ACL。
在k8s中flannel作为标准CNI插件,每个节点都会运行一个flanneld的二进制代理程序,每个节点分配一个子网,集群网络状态通过apiserver直接存储在etcd当中。
下图flannel官网提供的一个数据包经过封包、传输以及拆包的示意图:
从这个图片里面里面可以看出两台机器的docker0分别处于不同的段:10.1.20.1/24 和 10.1.15.1/24 ,如果从Web App Frontend1 pod(10.1.15.2)去连接另一台主机上的Backend Service2 pod(10.1.20.3),网络包从宿主机192.168.0.100发往192.168.0.200,内层容器的数据包被封装到宿主机的UDP里面,并且在外层包装了宿主机的IP和mac地址。这就是一个经典的overlay网络,因为容器的IP是一个内部IP,无法从跨宿主机通信,所以容器的网络互通,需要承载到宿主机的网络之上。
flannel的支持多种网络模式,常用用都是vxlan、UDP、hostgw、ipip以及gce和阿里云等(udp模式已弃用)。
vxlan和UDP的区别是vxlan是内核封包,而UDP是flanneld用户态程序封包,所以UDP的方式性能会稍差;
hostgw模式是一种主机网关模式,容器到另外一个主机上容器的网关设置成所在主机的网卡地址,这个和calico非常相似,只不过calico是通过BGP声明,而hostgw是通过中心的etcd分发,所以hostgw是直连模式,不需要通过overlay封包和拆包,性能比较高,但hostgw模式最大的缺点是必须是在一个二层网络中,毕竟下一跳的路由需要在邻居表中,否则无法通行。
Pod和服务之间,以及外部应用与服务之间的通信请参考《Kubernetes-核心资源之Service》和《Kubernetes-核心资源之Ingress》。
2.4 Calico插件
Calico是针对容器、虚拟机场景下提供跨宿主机互通的开源网络和安全解决方案,支持复杂的网络策略。
Calico的特点就是把每个宿主机当中一个虚拟路由器建立与物理网络对等的虚拟网络,该模式BGP协议在网络中通告路由信息,巧妙的把二层网络转换成三层路由网络,避免报文被二次封装数据转发效率很高,Pod IP可以在物理网络中全局路由
当然Calico除了路由模式还是支持Overlay Network网络模式比如vxlan
Calico的主要组件:
- Felix:每个节点都要运行的calico agent,负责配置节点上的路由信息和ACL;
- ETCD:配置中心,主要负责网络元数据一致性,确保Calico网络状态的准确性
- BGP Client(BIRD):主要负责把Felix配置的路由信息分发到当前Calico网络,确保节点间能够进行三层通信
Calico网络之间是如何通信的
Calico是一种基于IP路由技术的CNI网络插件,它利用BGP协议来实现高效的容器网络互连。在Calico中,每个容器都被赋予了一个唯一的IP地址,这些IP地址在网络层面上是可达的,并且是通过数据包路由直接到达目标容器的。
Calico使用路由表来管理容器网络,每个主机上都会存在一个Calico Agent,它会监听Kubernetes API服务器,从而了解集群中所有容器的IP地址和状态。当某个容器需要向其他容器发起请求时,Calico会根据路由表信息进行查找,找到合适的路径,并将数据包转发给目标容器。
BGP路由:
路由表:
本地pod通信:
本节点pod间通信不在像flannel一样使用网桥二层通信,而是每个pod的虚拟网卡都会有Pod IP的静态路由指向
pod跨节点通信 :
节点上有路由指向Pod子网所属的宿主机上,整个流程都是普通的三层路由转发
三、flannel安装部署和在Kubernetes中运行的整体过程
flannel运行的基本流程:
1)设置网段(地址空间):flannel利用Kubernetes API或者etcd用于存储整个集群的网络配置,其中最主要的内容为设置集群的网络地址空间。例如,设定整个集群内所有容器的IP都取自网段“10.1.0.0/16”。
2)flannel服务:flannel在每个主机中运行flanneld作为agent,它会为所在主机从集群的网络地址空间中,获取一个小的网段subnet,本主机内所有容器的IP地址都将从中分配。
然后,flanneld再将本主机获取的subnet以及用于主机间通信的Public IP,同样通过kubernetes API或者etcd存储起来。
3)跨主机通信:最后,flannel利用各种backend mechanism,例如udp,vxlan等等,跨主机转发容器间的网络流量,完成容器间的跨主机通信。
1、下载安装
flannel和etcd一样,直接从官方下载二进制执行文件就可以用了。当然,你也可以自己编译。
下载地址:Releases · flannel-io/flannel · GitHub
解压后主要有flanneld
、mk-docker-opts.sh
这两个文件,其中flanneld
为主要的执行文件,sh脚本用于生成Docker启动参数。
2、 etcd注册网段
由于flannel
需要依赖etcd
来保证集群IP分配不冲突的问题,所以首先要在etcd
中设置 flannel
节点所使用的IP段。
所有Node上的flanneld都依赖etcd cluster来做集中配置服务,etcd保证了所有node上flanned所看到的配置是一致的。
etcdctl --endpoints="http://node1.etcd.tulingapi.com:2379,http://node2.etcd.tulingapi.com:2379,http://node2.etcd.tulingapi.com:2379" set /k8s/network/config '{ "Network": "10.0.0.0/16", "Backend": {"Type": "vxlan"}}'
{ "Network": "10.0.0.0/16", "Backend": {"Type": "vxlan"}}
写入的 Pod 网段 ${CLUSTER_CIDR} 必须是 /16 段地址,必须与 kube-controller-manager 的 --cluster-cidr 参数值一致;
flannel默认的backend type
是udp,如果想要使用vxlan
作为backend
,可以加上backend
参数: {"Type": "vxlan"}}
flannel backend为vxlan比起预设的udp性能相对好一些。
通过如下的命令能够查询网络配置信息:
$ etcdctl --endpoints "http://node1.etcd.tulingapi.com:2379" ls /k8s/network/config
/coreos.com/network/subnets/10.0.2.0-24
获取子网列表
$ etcdctl ls /k8s/network/subnets
/k8s/network/subnets/10.0.86.0-24
/k8s/network/subnets/10.0.35.0-24
/k8s/network/subnets/10.0.24.0-24
3、启动flannel
flanneld 运行时需要 root 权限;命令行方式运行:
ln -s /mnt/app/flannel-v0.11.0/flanneld /usr/bin/flanneld
$ flanneld --etcd-endpoints="http://node1.etcd.tulingapi.com:2379" --ip-masq=true #命令行
$ cd /mnt/logs/flannel && nohup flanneld --etcd-endpoints="http://node1.etcd.tulingapi.com:2379" -etcd-prefix=/k8s
/network
--ip-masq=true & #命令行后台
也可以创建一个flannel systemd服务,方便以后管理。
启动参数:
--logtostderr=false
--log_dir= /mnt/logs/flannel
--etcd-endpoints="http://node1.etcd.tulingapi.com:2379"
-etcd-prefix=/k8s/network #
etcd路径前缀
--iface=192.168.10.50 #要绑定的网卡的IP地址,请根据实际情况修改。
启动时如果出现以下错误:
Couldn't fetch network config: 100: Key not found (/coreos.com)
通过-etcd-prefix指定/k8s/network
。
flannel启动过程解析:
flannel服务需要先于Docker启动。flannel服务启动时主要做了以下几步的工作:
1)启动参数设置网卡及对外IP选择
2)从etcd中获取network的配置信息。
3)划分子网subnet,并在etcd中进行注册。
4)将子网信息记录到/run/flannel/subnet.env中。
5)在Node节点上,会创建一个名为flannel.1的虚拟网卡。
可以看到每个node上/run/flannel/subnet.env 子网掩码不一样。
启动参数设置网卡及对外IP选择
flanneld的启动参数中通过”–iface”或者”–iface-regex”进行指定。其中”–iface”的内容可以是完整的网卡名或IP地址,而”–iface-regex”则是用正则表达式表示的网卡名或IP地址,并且两个参数都能指定多个实例。flannel将以如下的优先级顺序来选取:
1) 如果”–iface”和”—-iface-regex”都未指定时,则直接选取默认路由所使用的输出网卡
2) 如果”–iface”参数不为空,则依次遍历其中的各个实例,直到找到和该网卡名或IP匹配的实例为止
3) 如果”–iface-regex”参数不为空,操作方式和2)相同,唯一不同的是使用正则表达式去匹配
最后,对于集群间交互的Public IP,我们同样可以通过启动参数”–public-ip”进行指定。否则,将使用–iface获取网卡的IP作为Public IP。
验证flannel网络:
在node1节点上看etcd中的内容:
$ etcdctl --endpoints "http://node1.etcd.tulingapi.com:2379" ls /k8s/network/subnets
/k8s/network/subnets/10.0.24.0-24
[root@k8s-master flannel]# cat /run/flannel/subnet.env
FLANNEL_NETWORK=10.0.0.0/16
FLANNEL_SUBNET=10.0.24.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true
通过文件/run/flannel/subnet.env设定docker的网络。我们发现这里的MTU并不是以太网规定的1500,这是因为外层的vxlan封包还要占据50 Byte。
查看flannel1.1的网络情况,注意查看docker0和flannel是不是属于同一网段
可以看到flannel1.1网卡的地址和etcd中存储的地址一样,这样flannel网络配置完成。
4、创建docker网桥
容器配置名为docker0的网桥,实际是通过修改Docker的启动参数–bip来实现的。通过这种方式,为每个节点的Docker0网桥设置在整个集群范围内唯一的网段,从保证创建出来的Pod的IP地址是唯一。
在etcd1节点上看etcd中的内容:
etcdctl --endpoints "http://node1.etcd.tulingapi.com:2379" ls /k8s/network/subnets
/k8s/network/subnets/10.0.24.0-24
在各个节点安装好以后最后要更改Docker
的启动参数,使其能够使用flannel
进行IP分配,以及网络通讯。
flannel
运行后会生成一个环境变量文件,包含了当前主机要使用flannel
通讯的相关参数。
1)查看flannel分配的网络参数:
cat /run/flannel/subnet.env
FLANNEL_NETWORK=10.0.0.0/16
FLANNEL_SUBNET=10.0.24.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true
2)创建Docker运行参数
使用flannel提供的脚本mk-docker-opts.sh将subnet.env转写成Docker启动参数,创建好的启动参数位于/run/docker_opts.env
文件中。
/mnt/app/flannel/mk-docker-opts.sh -d /run/docker_opts.env -c
$ cat /run/docker_opts.env
DOCKER_OPTS=" --bip=10.0.24.1/24 --ip-masq=false --mtu=1450"
3) 修改Docker启动参数
修改docker的启动参数,并使其启动后使用由flannel生成的配置参数,修改如下:
编辑 systemd service 配置文件
$ vim /lib/systemd/system/docker.service
(1)、指定这些启动参数所在的文件位置:(这个配置是新增的,同样放在Service标签下)
EnvironmentFile=/run/docker_opts.env
(2)、在启动时增加flannel提供的启动参数:
ExecStart=/usr/bin/dockerd $DOCKER_OPTS
ubuntu修改如下:
然后重新加载systemd配置,并重启Docker
即可
systemctl daemon-reload
systemctl restart docker
此时可以看到docker0
的网卡ip地址已经处于flannel
网卡网段之内。
到此节点etcd1的flannel安装配置完成了,其它两节点按以上方法配置完成就行了。
测试flannel
5、修改路由表
flannel会对路由表进行修改,从而能够实现容器跨主机的通信。
四、backend原理解析
集群范围内的网络地址空间为10.1.0.0/16:
Machine A获取的subnet为10.1.15.0/24,且其中的两个容器IP分别为10.1.15.2/24和10.1.15.3/24,两者都在10.1.15.0/24这一子网范围内。Machine B同理。
Machine A中的容器要与Machine B中的容器进行通信,封包是如何进行转发的?
flannel的核心就是把已知的网络信息存储在etcd当中,并且在本地节点维护其他节点的网络信息,具体网络通信的实现依赖linux网桥、vxlan封装。
从上文可知,每个主机的flanneld会将自己与所获取subnet的关联信息存入etcd中,例如,subnet 10.1.15.0/24所在主机可通过IP 192.168.0.100访问,subnet 10.1.16.0/24可通过IP 192.168.0.200访问。反之,每台主机上的flanneld通过监听etcd,也能够知道其他的subnet与哪些主机相关联。如上图,Machine A上的flanneld通过监听etcd已经知道subnet 10.1.16.0/24所在的主机可以通过Public 192.168.0.200访问,而且熟悉docker桥接模式的同学肯定知道,目的地址为10.1.20.3/24的封包一旦到达Machine B,就能通过veth0网桥转发到相应的pod,从而达到跨宿主机通信的目的。
因此,flanneld只要想办法将封包从Machine A转发到Machine B就OK了,其中backend就是用于完成这一任务。不过,达到这个目的的方法是多种多样的,所以我们也就有了很多种backend. 即网络模式:
flannel的支持多种网络模式,常用用都是vxlan、UDP、hostgw、ipip以及gce和阿里云等。即我们启动Backend参数:
etcdctl --endpoints="http://node1.etcd.tulingapi.com:2379,http://node2.etcd.tulingapi.com:2379,http://node2.etcd.tulingapi.com:2379" set /k8s/network/config '{ "Network": "10.0.0.0/16", "Backend": {"Type": "vxlan"}}'
我们将对hostgw,udp和vxlan三种backend进行解析。
1、 hostgw
hostgw是最简单的backend,它的原理非常简单,直接添加路由,将目的主机当做网关,直接路由原始封包。
因为Machine A和Machine B处于同一个子网内,它们原本就能直接互相访问。因此最简单的方法是:在Machine A中的容器要访问Machine B的容器时,我们可以将Machine B看成是网关,当有封包的目的地址在subnet 10.1.16.0/24范围内时,就将其直接转发至B即可。
图中那条红色标记的路由就能完成:我们从etcd中监听到一个EventAdded事件subnet为10.1.15.0/24被分配给主机Machine A Public IP 192.168.0.100,hostgw要做的工作就是在本主机上添加一条目的地址为10.1.15.0/24,网关地址为192.168.0.100,输出设备为上文中选择的集群间交互的网卡即可。对于EventRemoved事件,只需删除对应的路由。
2、 udp
我们知道当backend为hostgw时,主机之间传输的就是原始的容器网络封包,封包中的源IP地址和目的IP地址都为容器所有。这种方法有一定的限制,就是要求所有的主机都在一个子网内,即二层可达,否则就无法将目的主机当做网关,直接路由。
而udp类型backend的基本思想是:既然主机之间是可以相互通信的(并不要求主机在一个子网中),那么我们为什么不能将容器的网络封包作为负载数据在集群的主机之间进行传输呢?这就是所谓的overlay。具体过程如图所示:
当容器10.1.15.2/24要和容器10.1.20.3/24通信时:
1)因为该封包的目的地不在本主机subnet内,因此封包会首先通过网桥转发到主机中。最终在主机上经过路由匹配,进入如图的网卡flannel0。需要注意的是flannel0是一个tun设备,它是一种工作在三层的虚拟网络设备,而flanneld是一个proxy,它会监听flannel0并转发流量。当封包进入flannel0时,flanneld就可以从flannel0中将封包读出,由于flannel0是三层设备,所以读出的封包仅仅包含IP层的报头及其负载。最后flanneld会将获取的封包作为负载数据,通过udp socket发往目的主机。同时,在目的主机的flanneld会监听Public IP所在的设备,从中读取udp封包的负载,并将其放入flannel0设备内。由此,容器网络封包到达目的主机,之后就可以通过网桥转发到目的容器了。
最后和hostgw不同的是,udp backend并不会将从etcd中监听到的事件里所包含的lease信息作为路由写入主机中。每当收到一个EventAdded事件,flanneld都会将其中的subnet和Public IP保存在一个数组中,用于转发封包时进行查询,找到目的主机的Public IP作为udp封包的目的地址。
3、 vxlan
首先,我们对vxlan的基本原理进行简单的叙述。从下图所示的封包结构来看,vxlan和上文提到的udp backend的封包结构是非常类似的,不同之处是多了一个vxlan header,以及原始报文中多了个二层的报头。
下面让我们来看看,当有一个EventAdded到来时,flanneld如何进行配置,以及封包是如何在flannel网络中流动的。
如上图所示,当主机B加入flannel网络时,和其他所有backend一样,它会将自己的subnet 10.1.16.0/24和Public IP 192.168.0.101写入etcd中,和其他backend不一样的是,它还会将vtep设备flannel.1的mac地址也写入etcd中。
之后,主机A会得到EventAdded事件,并从中获取上文中B添加至etcd的各种信息。这个时候,它会在本机上添加三条信息:
1) 路由信息:所有通往目的地址10.1.16.0/24的封包都通过vtep设备flannel.1设备发出,发往的网关地址为10.1.16.0,即主机B中的flannel.1设备。
2) fdb信息:MAC地址为MAC B的封包,都将通过vxlan首先发往目的地址192.168.0.101,即主机B
3)arp信息:网关地址10.1.16.0的地址为MAC B
现在有一个容器网络封包要从A发往容器B,和其他backend中的场景一样,封包首先通过网桥转发到主机A中。此时通过,查找路由表,该封包应当通过设备flannel.1发往网关10.1.16.0。通过进一步查找arp表,我们知道目的地址10.1.16.0的mac地址为MAC B。到现在为止,vxlan负载部分的数据已经封装完成。由于flannel.1是vtep设备,会对通过它发出的数据进行vxlan封装(这一步是由内核完成的,相当于udp backend中的proxy),那么该vxlan封包外层的目的地址IP地址该如何获取呢?事实上,对于目的mac地址为MAC B的封包,通过查询fdb,我们就能知道目的主机的IP地址为192.168.0.101。
最后,封包到达主机B的eth0,通过内核的vxlan模块解包,容器数据封包将到达vxlan设备flannel.1,封包的目的以太网地址和flannel.1的以太网地址相等,三层封包最终将进入主机B并通过路由转发达到目的容器。
事实上,flannel只使用了vxlan的部分功能,由于VNI被固定为1,本质上工作方式和udp backend是类似的,区别无非是将udp的proxy换成了内核中的vxlan处理模块。而原始负载由三层扩展到了二层,但是这对三层网络方案flannel是没有意义的,这么做也仅仅只是为了适配vxlan的模型。vxlan详细的原理参见文后的参考文献,其中的分析更为具体,也更易理解。
4、数据传递过程
在源容器宿主机中的数据传递过程:
1)源容器向目标容器发送数据,数据首先发送给docker0网桥
在源容器内容查看路由信息:
$ kubectl exec -it -p {Podid} -c {ContainerId} -- ip route
2)docker0网桥接受到数据后,将其转交给flannel.1虚拟网卡处理
docker0收到数据包后,docker0的内核栈处理程序会读取这个数据包的目标地址,根据目标地址将数据包发送给下一个路由节点:
查看源容器所在Node的路由信息:
$ ip route
3)flannel.1接受到数据后,对数据进行封装,并发给宿主机的eth0
flannel.1收到数据后,flannelid会将数据包封装成二层以太包。
Ethernet Header的信息:
- From:{源容器flannel.1虚拟网卡的MAC地址}
- To:{目录容器flannel.1虚拟网卡的MAC地址}
4)对在flannel路由节点封装后的数据,进行再封装后,转发给目标容器Node的eth0
由于目前的数据包只是vxlan tunnel上的数据包,因此还不能在物理网络上进行传输。因此,需要将上述数据包再次进行封装,才能源容器节点传输到目标容器节点,这项工作在由linux内核来完成。
Ethernet Header的信息:
- From:{源容器Node节点网卡的MAC地址}
- To:{目录容器Node节点网卡的MAC地址}
IP Header的信息:
- From:{源容器Node节点网卡的IP地址}
- To:{目录容器Node节点网卡的IP地址}
通过此次封装,就可以通过物理网络发送数据包。
在目标容器宿主机中的数据传递过程:
5)目标容器宿主机的eth0接收到数据后,对数据包进行拆封,并转发给flannel.1虚拟网卡;
6)flannel.1 虚拟网卡接受到数据,将数据发送给docker0网桥;
7)最后,数据到达目标容器,完成容器之间的数据通信。
五、Kubernetes Cluster中的几个“网络”
node network:承载kubernetes集群中各个“物理”Node(master和minion)通信的网络;
service network:由kubernetes集群中的Services所组成的“网络”;
flannel network: 即Pod网络,集群中承载各个Pod相互通信的网络。
1、node network (Node IP)
自不必多说,node间通过你的本地局域网(无论是物理的还是虚拟的)通信。
2、service network (clusterI):比较特殊,每个新创建的service会被分配一个service IP 即spec.clusterIP,在集群中,这个IP的分配范围并不“真实”,更像一个“占位符”并且只有入口流量,所谓的“network”也是“名不符实”的,后续我们会详尽说明。
Service network(看cluster-ip一列):
# kubectl get services
3、flannel network (Pod IP):是我们要理解的重点,cluster中各个Pod要实现相互通信,必须走这个网络,无论是在同一node上的Pod还是跨node的Pod。
Flannel network(看IP那列):
# kubectl get pod -o wide