K8s的Flannel网络

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了K8s的Flannel网络相关的知识,希望对你有一定的参考价值。

参考技术A 原文: 一文看懂k8s的Flannel网络 - 知乎 (zhihu.com)

我们先来看图示例,下面则个是k8s的网络模型图。

k8s的网络模型

我们知道,在k8s里面最小的管理单元是pod,一个主机可以跑多个pod,一个pod里面可以跑多个容器。

如上面所示,一个pod里面所有的容器共享一个网络命名空间(network namespace),所以,pod里面的容器之间通信,可以直接通过localhost来完成,pod里面的容器之间通过localhost+端口的方式来通信(这和应用程序在宿主机的通信方式是一样的)。

那么pod和pod之间的通信呢?通常来说,我们给应用程序定死端口会给应用程序水平扩展带来很多不便,所以k8s不会使用定死端口这样的方法,而是采用其他方法来解决pod之间寻址的问题

每个pod都会有一个自己的ip,可以将Pod像VM或物理主机一样对待。这样pod和pod之间的通信就不需要像容器一样,通过内外端口映射来通信了,这样就避免了端口冲突的问题。

特殊的情况下(比如运维做网络检测或者程序调试),可以在pod所在的宿主机想向pod的ip+端口发起请求,这些请求会转发到pod的端口,但是pod本身它自己是不知道端口的存在的。

因此,k8s的网络遵循以下原则:

把上面这个pod替换成容器也是成立的,因为pod里面的容器和pod共享网络。

基本上的原则就是,k8s的里面的pod可以自由的和集群里面的任何其他pod通信(即使他们是部署在不同的宿主机),而且pod直接的通信是直接使用pod自己的ip来通信,他们不知道宿主机的ip,所以,对于pod之间来说,宿主机的网络信息是透明的,好像不存在一样。

然后,定了这几个原则之后,具体的实现k8s的这个网络模型有好多种实现,我们这里介绍的是 Flannel ,是其中最简单的一种实现。

Flannel实现pod之间的通信,是通过一种覆盖网络(overlay network),把数据包封装在另外一个网络来做转发,这个覆盖网络可以给每一个pod分配一个独立的ip地址,使他们看起来都是一台具有独立ip的物理主机一样。

下面这个就是k8s用覆盖网络来实现的一个例子:

flannel覆盖网络

可以看到有3个node,在多个node上建立一个覆盖网络,子网网段是100.95.0.0/16,然后,最终到容器级别,每个容器在这个网段里面获取到一个独立的ip。而宿主机所在的局域网络的网段是172.20.32.0/19

看这两个网段,就知道,fannel给这个集群创建了一个更大的网络给pod使用,可以容纳的主机数量达到65535(2^16)个。

对于每个宿主机,fannel给每个了一个小一点的网络100.96.x.0/24,提供给每个这个宿主机的每一个pod使用,也就是说,每一个宿主机可以有256(2^8)个pod。docker默认的网桥docker0用的就是这个网络,也就是所有的docker通过docker0来使用这个网络。即是说,对于容器来说,都是通过docker0这个桥来通信,和我们平常单机的容器是一样的(如果你不给创建的容器指定网络的话,默认用的是docker0,参考这篇 docker bridge 的文章)

那么,对于同一个host里面的容器通信,我们上面说了是通过这个台宿主机的里面的docker0这个网桥来通信。那对于跨宿主机,也即是两个宿主机之间的容器是怎么通信的呢?fannel使用了宿主机操作系统的kernel route和UDP(这是其中一种实现)包封装来完成。下图演示了这个通信过程:

fannel网络中跨宿主机的容器通信

如图所示,100.96.1.2(container-1) 要和100.96.2.3(container-2)通信,两个容器分别处于不同的宿主机。
假设有一个包是从100.96.1.2发出去给100.96.2.3,它会先经过docker0,因为docker0这个桥是所有容器的网关。 然后这个包会经过route table处理,转发出去到局域网172.20.32.0/19. 而这个route table的对应处理这类包的规则又是从哪里来的呢?它们是由fannel的一个守护程序flanneld创建的。

每一台宿主机都会跑一个flannel的deamon的进程,这个进程的程序会往宿主机的route table里面写入特定的路由规则,这个规则大概是这样的。

Node1的route table

图例的数据包发出去的目标地址是100.96.2.3,它属于网段100.96.0.0/16,这个目标地址命中第二条规则,也就是这个包会发到flannel0这个设备(dev),这 flannel0 是一个TUN设备。是在内核里面的一个 虚拟网络设备(虚拟网卡) 。

在内核(kernel)里面,有两种虚拟网卡设备,分别是TUN和TAP,其中TAP处理的是第二层(数据链路层)的帧,而TUN处理的是第三层(网络层)的ip包。

应用程序可以绑定到TUN和TAP设备,内核会把数据通过TUN或者TAP设备发送给这些程序,反过来,应用程序也可以通过TUN和TAP向内核写入数据,进而由内核的路由处理这些发出去的数据包。

那么上面这个 flannel0 就是一个这样的TUN设备。这个设备连到的是一个flannel的守护进程程序 flanneld

而这个 flanneld 是干嘛的呢?它可以接受所有发往 flannel0 这个设备的数据包,然后做数据封装处理,它的封装的逻辑也很简单,就是根据 目标地址 ,找到这个这地址对应的在整个flannel网络里面对应 物理ip和端口 (这里是Node2对应的物理ip),然后增加一个包头,增加的包头里面 目标地址 为这个 实际的物理ip和端口 (当然源地址也改成了局域网络的ip),将原来的数据包嵌入在新的数据包中,然后再把这个封装后的包扔回去给内核,内核根据目标地址去路由规则匹配规则,发现目标地址ip是172.20.54.98,端口是8285. 根据ip匹配不到任何特定的规则,就用第一条default(默认)的规则,通过eth0这个物理网卡,把数据包发给局域网(这里是UDP广播出去)

当Node2的收到这个包后,然后根据 端口8285 发现他的目标地址原来是发给flanneld的,然后就直接交给flanneld这程序,flanneld收到包后,把包头去掉,发现原来目标地址是100.96.2.3,然后就交换flannel0,flannel0把这个解开后的原包交给内核,内核发现它的目标地址是100.96.2.3,应该交给docker0来处理。(图例里面画的是直接由flannel0交给docker0,没有图示出内核,实际上flannel0是一个TUN设备,是跑在内核的,数据经过它后可以交给内核,由内核根据路由决定进一步怎么forward)

以上就是这个通信的过程,那么这里有一个问题: flanneld是怎么知道100.96.2.3对应的目标地址是172.20.54.98:8285的呢?

这是因为flanneld维护了一个映射关系,每创造一个虚拟的容器ip(分配给容器新ip的时候),它就知道这个容器的ip实际上是在哪台宿主机上,然后把这个映射关系存储起来,在k8s里面flanneld存储的这个映射关系放在etd上,这就是为什么flanneld为什么知道这个怎么去封装这些包了,下面就是etcd里面的数据的:

看上面这个数据,etcd里面存储的100.96.2.0-24这个网段的容器是放在172.20.54.98这台宿主机上的。

那么还有一个问题,端口8285又是怎么知道的?

这个很简单,flanneld的默认监听的端口就是这个8285端口,flanneld启动的时候,就监听了UDP端口8285. 所以发给Node2:8285的所有UDP数据包,flanneld这个进程会直接处理,然后去掉包头就还原出来原来的包了,还原后交给TUN设备flannel0,由flannel0交给内核,内核根据Node2的路由规则交给docker0(Node2的路由规则和node1是基本上一样的,除了第三位的网段标识不一样,一个是100.96.1一个是100.92.2):

看Node2的这个规则,flannld去掉包头解出来的原包的目标ip是100.96.2.3,由flannel0交回去给kennel,kennel发现命中第三条规则,所以会把这个包叫给docker0,继而就进入了docker0这个桥的子网了,接下去就是docker的事情了, 参考以前写的文章 。

最后一个问题,怎么配置docker去使用100.96.x.0/24这个子网呢,如果是手工创建容器的话,这个也是非常简单的, 参考以前写的关于docker bridge的这篇文章 ,但是在k8s里面,是通过配置来实现的:

flanneld会把子网信息写到一个配置文件 /run/flannel/subnet.env 里

docker会使用这个配置的环境变了来作为它的bridge的配置

DOCKER使用 FLANNEL(ETCD+FLANNEL)网络

一、FLANNEL网络简介

Flannel是一种基于overlay网络的跨主机容器网络解决方案,也就是将TCP数据包封装在另一种网络包里面进行路由转发和通信,Flannel是CoreOS开发,专门用于docker多机互联的一个工具,让集群中的不同节点主机创建的容器都具有全集群唯一的虚拟ip地址,Flannel使用go语言编写

二、FLANNEL实现原理

2.1、原理说明

技术图片
1、Flannel为每个host分配一个subnet,容器从这个subnet中分配IP,这些IP可以在host间路由,容器间无需使用nat和端口映射即可实现跨主机通信

2、每个subnet都是从一个更大的IP池中划分的,flannel会在每个主机上运行一个叫flanneld的agent,其职责就是从池子中分配subnet

3、Flannel使用etcd存放网络配置、已分配 的subnet、host的IP等信息
4、Flannel数据包在主机间转发是由backend实现的,目前已经支持UDP、VxLAN、host-gw、AWS VPC和GCE路由等多种backend
技术图片

2.2、数据转发流程

技术图片
技术图片
1、容器直接使用目标容器的ip访问,默认通过容器内部的eth0发送出去。

2、报文通过veth pair被发送到vethXXX。

3、ethXXX是直接连接到虚拟交换机docker0的,报文通过虚拟bridge docker0发送出去。

4、查找路由表,外部容器ip的报文都会转发到flannel0虚拟网卡,这是一个P2P的虚拟网卡,然后报文就被转发到监听在另一端的flanneld。

5、flanneld通过etcd维护了各个节点之间的路由表,把原来的报文UDP封装一层,通过配置的iface发送出去。

6、报文通过主机之间的网络找到目标主机。

7、报文继续往上,到传输层,交给监听在8285端口的flanneld程序处理。

8、数据被解包,然后发送给flannel0虚拟网卡。

9、查找路由表,发现对应容器的报文要交给docker0。

10、docker0找到连到自己的容器,把报文发送过去。
技术图片

三、部署ETCD集群

3.1、环境准备

节点名称
IP地址
安装软件
docker-01
192.168.1.220
etcd
docker-02
192.168.1.221
etcd
docker-03
192.168.1.222
etcd

 

3.2、安装ETCD(三台主机操作)

[root@docker-01 ~]# yum -y install etcd

3.3、配置ETCD

[root@docker-01 ~]# cp /usr/lib/systemd/system/etcd.service{,_bak}
[root@docker-01 ~]# cat /etc/etcd/etcd.conf
ETCD_NAME="docker-01"
ETCD_DATA_DIR="/var/lib/etcd/docker-01.etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.1.220:2380"
ETCD_LISTEN_CLIENT_URLS="http://192.168.1.220:2379,http://127.0.0.1:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.1.220:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.1.220:2379"
ETCD_INITIAL_CLUSTER="docker-01=http://192.168.1.220:2380,docker-02=http://192.168.1.221:2380,docker-03=http://192.168.1.222:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"
[root@docker-02 ~]# cp /etc/etcd/etcd.conf{,_bak}
[root@docker-02 ~]# cat  /etc/etcd/etcd.conf
ETCD_NAME="docker-02"
ETCD_DATA_DIR="/var/lib/etcd/docker-02.etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.1.221:2380"
ETCD_LISTEN_CLIENT_URLS="http://192.168.1.221:2379,http://127.0.0.1:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.1.221:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.1.221:2379"
ETCD_INITIAL_CLUSTER="docker-01=http://192.168.1.220:2380,docker-02=http://192.168.1.221:2380,docker-03=http://192.168.1.222:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"
[root@docker-03 ~]# cp /etc/etcd/etcd.conf{,_bak}
[root@docker-03 ~]# cat /etc/etcd/etcd.conf
ETCD_NAME="docker-03"
ETCD_DATA_DIR="/var/lib/etcd/docker-03.etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.1.222:2380"
ETCD_LISTEN_CLIENT_URLS="http://192.168.1.222:2379,http://127.0.0.1:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.1.222:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.1.222:2379"
ETCD_INITIAL_CLUSTER="docker-01=http://192.168.1.220:2380,docker-02=http://192.168.1.221:2380,docker-03=http://192.168.1.222:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"

3.4、修改ETCD启动文件(三台都操作)

[root@docker-01 ~]# cp /usr/lib/systemd/system/etcd.service{,_bak}
[root@docker-01 ~]# cat /usr/lib/systemd/system/etcd.service
[Service]
Type=notify
WorkingDirectory=/var/lib/etcd/
EnvironmentFile=-/etc/etcd/etcd.conf
User=etcd
# set GOMAXPROCS to number of processors
ExecStart=/bin/bash -c "GOMAXPROCS=$(nproc) /usr/bin/etcd --name="${ETCD_NAME}" --data-dir="${ETCD_DATA_DIR}" --listen-peer-urls="${ETCD_LISTEN_PEER_URLS}" --listen-client-urls="${ETCD_LISTEN_CLIENT_URLS}" --initial-advertise-peer-urls="${ETCD_INITIAL_ADVERTISE_PEER_URLS}" --advertise-client-urls="${ETCD_ADVERTISE_CLIENT_URLS}" --initial-cluster="${ETCD_INITIAL_CLUSTER}"  --initial-cluster-token="${ETCD_INITIAL_CLUSTER_TOKEN}" --initial-cluster-state="${ETCD_INITIAL_CLUSTER_STATE}""
Restart=on-failure
LimitNOFILE=65536

3.5、启动ETCD服务(三台都启动)

[root@docker-02 ~]# systemctl start etcd.service
[root@docker-02 ~]# etcdctl cluster-health
member 164a311aff833bc1 is healthy: got healthy result from http://192.168.1.222:2379
member b1eeb25e6baf68e0 is healthy: got healthy result from http://192.168.1.221:2379
member e7c8f1a60e57abe4 is healthy: got healthy result from http://192.168.1.220:2379
cluster is healthy

3.6、检测ETCD集群状态,至此ETCD安装完成

# 查看cluster状态
[root@docker-02 ~]# etcdctl cluster-health
member 164a311aff833bc1 is healthy: got healthy result from http://192.168.1.222:2379
member b1eeb25e6baf68e0 is healthy: got healthy result from http://192.168.1.221:2379
member e7c8f1a60e57abe4 is healthy: got healthy result from http://192.168.1.220:2379
cluster is healthy
# 列出etcd服务状态,从列出信息可以看出,目前是docker-03为主节点。
[root@docker-02 ~]# etcdctl member list
164a311aff833bc1: name=docker-03 peerURLs=http://192.168.1.222:2380 clientURLs=http://192.168.1.222:2379 isLeader=false
b1eeb25e6baf68e0: name=docker-02 peerURLs=http://192.168.1.221:2380 clientURLs=http://192.168.1.221:2379 isLeader=false
e7c8f1a60e57abe4: name=docker-01 peerURLs=http://192.168.1.220:2380 clientURLs=http://192.168.1.220:2379 isLeader=true

3.7、添加FLANNEL网络配置信息到ETCD

【注释: 此(flannel_use)目录自己可以定义,但是此处设置的目录必须与flannel配置文件中FLANNEL_ETCD_PREFIX="/flannel_use/network"配置保持一致,flannel启动程序只认带“config”的key,否则会报错Not a directory (/flannel_use/network)】 

# 固定配置方式
[root@docker-01 ~]# etcdctl set /flannel_use/network/config ‘{"Network":"10.10.0.0/16"}‘
{"Network":"10.10.0.0/16"}

四、部署FLANNEL

4.1、安装FLANNEL

[root@docker-01 ~]# yum install -y flannel

4.2、修改FLANNEL配置文件

 

 

以上是关于K8s的Flannel网络的主要内容,如果未能解决你的问题,请参考以下文章

flannel网络模式

K8S flannel 网络

k8s flannel网络切换calico步骤

k8s flannel网络切换calico步骤

k8s网络之Flannel网络

k8s部署flannel网络