k8s实践(4)--k8s集群网络详解和flannel

2022-04-14 16:14:41 浏览数 (1)

一、Docker网络模式

在讨论Kubernetes网络之前,让我们先来看一下Docker网络。Docker采用插件化的网络模式,默认提供bridge、host、none、overlay、maclan和Network plugins这几种网络模式,运行容器时可以通过–network参数设置具体使用那一种模式。

  • 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参数,将容器的端口映射到宿主机的随机端口:

代码语言:javascript复制
$ docker run -P {images}

2)通过-p参数,将容器的端口映射到宿主机的制定端口:

代码语言:javascript复制
$ 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通过flannel、calic等网络插件解决Pod间的通信问题。

Flannel是CoreOS开源的CNI网络插件,下图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和阿里云等。

vxlan和UDP的区别是vxlan是内核封包,而UDP是flanneld用户态程序封包,所以UDP的方式性能会稍差;

hostgw模式是一种主机网关模式,容器到另外一个主机上容器的网关设置成所在主机的网卡地址,这个和calico非常相似,只不过calico是通过BGP声明,而hostgw是通过中心的etcd分发,所以hostgw是直连模式,不需要通过overlay封包和拆包,性能比较高,但hostgw模式最大的缺点是必须是在一个二层网络中,毕竟下一跳的路由需要在邻居表中,否则无法通行。

Pod和服务之间,以及外部应用与服务之间的通信请参考《Kubernetes-核心资源之Service》和《Kubernetes-核心资源之Ingress》。

三、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一样,直接从官方下载二进制执行文件就可以用了。当然,你也可以自己编译。

下载地址:https://github.com/coreos/flannel/releases/

解压后主要有flanneldmk-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中的容器进行通信,封包是如何进行转发的?

从上文可知,每个主机的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网桥

在源容器内容查看路由信息:

代码语言:javascript复制
$ kubectl exec -it -p {Podid} -c {ContainerId} -- ip route

2)docker0网桥接受到数据后,将其转交给flannel.1虚拟网卡处理

docker0收到数据包后,docker0的内核栈处理程序会读取这个数据包的目标地址,根据目标地址将数据包发送给下一个路由节点:

查看源容器所在Node的路由信息:

代码语言:javascript复制
$ 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

0 人点赞