flannel网络概述

flannel 是 CoreOS 开发的容器网络解决方案。flannel 为每个 host 分配一个 subnet(子网),容器从此 subnet 中分配 IP,这些 IP 可以在主机间路由,容器间无需 NAT 和 port mapping(端口映射) 就可以跨主机通信。

flannel的作用

因为flannel实现跨主机的子网通信是通过主机中的dr0网卡进行通信的,由flannel分配的子网都是从手动指定的一个大的子网中划分出来分配的。
flannel 会在每个主机上运行一个叫 flanneld 的代理。其职责就是从池子中分配 subnet。为了在各个主机间共享信息,flannel 用 etcd(与 consul 类似的 key-value 分布式数据库)存放网络配置、已分配的 subnet、host 的 IP 等信息。

flannel的思路

Flannel是CoreOS团队针对Kubernetes设计的一个网络规划服务,简单来说,它的功能是让集群中的不同节点主机创建的Docker容器都具有全集群唯一的虚拟IP地址。
但在默认的Docker配置中,每个节点上的Docker服务会分别负责所在节点容器的IP分配。这样导致的一个问题是,不同节点上容器可能获得相同的内外IP地址。并使这些容器之间能够之间通过IP地址相互找到,也就是相互ping通。

Flannel的设计目的就是为集群中的所有节点重新规划IP地址的使用规则,从而使得不同节点上的容器能够获得"同属一个内网"且"不重复的"IP地址,并让属于不同节点上的容器能够直接通过内网IP通信。

Flannel实质上是一种"覆盖网络(overlay network)",即表示运行在一个网上的网(应用层网络),并不依靠ip地址来传递消息,而是采用一种映射机制,把ip地址和identifiers做映射来资源定位。也就是将TCP数据包装在另一种网络包里面进行路由转发和通信,目前已经支持UDP、VxLAN、AWS VPC和GCE路由等数据转发方式。

原理是每个主机配置一个ip段和子网个数。例如,可以配置一个覆盖网络使用 10.100.0.0/16段,每个主机/24个子网。比如主机a可以接受10.100.5.0/24,主机B可以接受10.100.18.0/24的包。flannel使用etcd来维护分配的子网到实际的ip地址之间的映射。对于数据路径,flannel 使用udp来封装ip数据报,转发到远程主机。选择UDP作为转发协议是因为他能穿透防火墙。例如,AWS Classic无法转发IPoIP or GRE 网络包,是因为它的安全组仅仅支持TCP/UDP/ICMP。

flannel 使用etcd存储配置数据和子网分配信息。flannel 启动之后,后台进程首先检索配置和正在使用的子网列表,然后选择一个可用的子网,然后尝试去注册它。etcd也存储这个每个主机对应的ip。flannel 使用etcd的watch机制监视/coreos.com/network/subnets下面所有元素的变化信息,并且根据它来维护一个路由表。为了提高性能,flannel优化了Universal TAP/TUN设备,对TUN和UDP之间的ip分片做了代理。

默认的节点间数据通信方式是UDP转发.在Flannel的GitHub页面有如下的一张原理图:
907596-20170516083247463-826250588
• 数据从源容器中发出后,经由所在主机的docker0虚拟网卡转发到flannel0虚拟网卡,这是个P2P的虚拟网卡,flanneld服务监听在网卡的另外一端。
• Flannel通过Etcd服务维护了一张节点间的路由表,详细记录了各节点子网网段 。
• 源主机的flanneld服务将原本的数据内容UDP封装后根据自己的路由表投递给目的节点的flanneld服务,数据到达以后被解包,然后直接进入目的节点的flannel0虚拟网卡,然后被转发到目的主机的docker0虚拟网卡,最后就像本机容器通信一下的有docker0路由到达目标容器。

部署

1. 部署安全的etcd集群

1.1 下载etcd二进制文件,并配置systemd服务(在192.168.4.10和192.168.4.20执行)

下载etcd,解压后得到etcd 和 etcdctl 文件,复制到/usr/bin/下面

wget https://github.com/etcd-io/etcd/releases/download/v3.4.13/etcd-v3.4.13-linux-amd64.tar.gz
tar xzf etcd-v3.4.13-linux-amd64.tar.gz
cp etcd-v3.4.13-linux-amd64/etcd* /usr/bin/
chown root:root /usr/bin/etcd*

创建/usr/lib/systemd/system/etcd.service 启动服务文件,编辑以下内容

[Unit]
Description=etcd key-value store
Documentation=https://github.com/etcd-io/etcd
After=network.target

[Service]
EnvironmentFile=/etc/etcd/etcd.conf
ExecStart=/usr/bin/etcd
Restart=always

[Install]
WantedBy=multi-user.target

1.2 etcd参数配置

在192.168.4.10和192.168.4.20配置不同的配置文件,etcd的规划如下表:

HOST ETCD_NAME
192.168.4.10 etcd1
192.168.4.20 etcd2

对etcd节点进行配置。etcd节点的配置方式包括启动参数、环境变量、配置文件等。
(在192.168.4.10执行)执行mkdir /etc/etcd/命令,把配置放到/etc/etcd/etcd.conf文件中,供systemd服务读取。

ETCD_NAME=etcd1
ETCD_DATA_DIR=/etc/etcd/data

ETCD_LISTEN_CLIENT_URLS=http://192.168.4.10:2379
ETCD_LISTEN_PEER_URLS=http://192.168.4.10:2380

ETCD_ADVERTISE_CLIENT_URLS=http://192.168.4.10:2379
ETCD_INITIAL_ADVERTISE_PEER_URLS=http://192.168.4.10:2380

ETCD_INITIAL_CLUSTER_STATE=new
ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
ETCD_INITIAL_CLUSTER=etcd1=http://192.168.4.10:2380,etcd2=http://192.168.4.20:2380

ETCD_ENABLE_V2=true

(在192.168.4.20执行)执行mkdir /etc/etcd/命令,把配置放到/etc/etcd/etcd.conf文件中,供systemd服务读取。

ETCD_NAME=etcd2
ETCD_DATA_DIR=/etc/etcd/data

ETCD_LISTEN_CLIENT_URLS=http://192.168.4.20:2379
ETCD_LISTEN_PEER_URLS=http://192.168.4.20:2380

ETCD_ADVERTISE_CLIENT_URLS=http://192.168.4.20:2379
ETCD_INITIAL_ADVERTISE_PEER_URLS=http://192.168.4.20:2380

ETCD_INITIAL_CLUSTER_STATE=new
ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
ETCD_INITIAL_CLUSTER=etcd1=http://192.168.4.10:2380,etcd2=http://192.168.4.20:2380

ETCD_ENABLE_V2=true

配置解释如下:

  • name:节点名称。
  • data-dir: 指定节点的数据存储目录。
  • listen-client-urls: 用于监听客户端通讯的URL列表。这个标记告诉 etcd 在特定的 scheme://IP:port 组合上从客户端接收进来的请求。
  • listen-peer-urls: 用于监听伙伴通讯的URL列表。这个标记告诉 etcd 在特定的 scheme://IP:port 组合上从它的伙伴接收进来的请求。
  • advertise-client-urls:列出这个成员的客户端URL,通告给集群中的其他成员。
  • initial-advertise-peer-urls: 列出这个成员的伙伴 URL 以便通告给集群的其他成员。这些地方用于在集群中通讯 etcd 数据。
  • initial-cluster-token :在启动期间用于 etcd 集群的初始化集群记号(cluster token)。
  • initial-cluster 为启动初始化集群配置。格式为 etcd1=http://ip1:2380,etcd2=http://ip2:2380,… 。注意:这里的 etcd1 是节点的 --name 指定的名字;后面的 ip1:2380 是 --initial-advertise-peer-urls 指定的值。
  • ETCD_ENABLE_V2:开启v2接口

1.3 启动etcd集群(在192.168.4.10和192.168.4.20执行)

在所有master分别启动etcd服务,并设置为开机启动。

systemctl start etcd && systemctl enable etcd

查看服务器启动状态, active 为正常。

systemctl status etcd

然后用etdctl客户端命令行工具携带客户端ca证书,运行 etcdctl endpoint health 命令访问etcd集群:

etcdctl --endpoints=http://192.168.4.10:2379,http://192.168.4.20:2379 endpoint health

结果显示均为healthy,说明集群正常运行。

[root@localhost ~]# etcdctl --endpoints=http://192.168.4.10:2379,http://192.168.4.20:2379 endpoint health
http://192.168.4.10:2379 is healthy: successfully committed proposal: took = 8.411417ms
http://192.168.4.20:2379 is healthy: successfully committed proposal: took = 10.943402ms

2. 安装flannel

2.1 下载并解压(在192.168.4.10和192.168.4.20执行)

Flannel的github官网下载软件包,解压够将二进制文件flanneld和mk-docker-opts.sh 复制到/usr/bin 下。即可完成Flannel的安装。

wget https://github.com/flannel-io/flannel/releases/download/v0.14.0/flannel-v0.14.0-linux-amd64.tar.gz
mkdir flannel
tar xzf flannel-v0.14.0-linux-amd64.tar.gz -C flannel
cd flannel/
cp flanneld mk-docker-opts.sh /usr/bin/

2.2 定义flannel网络ip池(只在一个节点上执行就行,比如在192.168.4.10上执行)

flannel-config.json 中,内容为:

{
  "Network": "10.0.0.0/16",
  "SubnetLen": 24,
  "Backend": {
    "Type": "vxlan"
  }
}
  • Network 定义该网络的 IP 池为 10.3.0.0/16。
  • SubnetLen 指定每个主机分配到的 subnet 大小为 24 位,即10.0.X.0/24。
  • Backend 为 vxlan,即主机间通过 vxlan 通信。

使用etcd的v2 API,向etcd写入子网

ETCDCTL_API=2 etcdctl --endpoints="http://192.168.4.10:2379" set /docker-flannel/network/config < flannel-config.json

如果执行成功,会返回写入的子网,如下所示:

[root@localhost ~]# ETCDCTL_API=2 etcdctl --endpoints="http://192.168.4.10:2379" set /docker-flannel/network/config < flannel-config.json
{
  "Network": "10.0.0.0/16",
  "SubnetLen": 24,
  "Backend": {
    "Type": "vxlan"
  }
}

可以通过以下命令查看刚刚写入的子网

ETCDCTL_API=2 etcdctl --endpoints="http://192.168.4.10:2379" get /docker-flannel/network/config
ETCDCTL_API=2 etcdctl --endpoints="http://192.168.4.20:2379" get /docker-flannel/network/config

无意外会在两台etcd中,都可以看到刚刚写入的信息:

[root@localhost ~]# ETCDCTL_API=2 etcdctl --endpoints="http://192.168.4.10:2379" get /docker-flannel/network/config
{
  "Network": "10.0.0.0/16",
  "SubnetLen": 24,
  "Backend": {
    "Type": "vxlan"
  }
}

[root@localhost ~]# ETCDCTL_API=2 etcdctl --endpoints="http://192.168.4.20:2379" get /docker-flannel/network/config
{
  "Network": "10.0.0.0/16",
  "SubnetLen": 24,
  "Backend": {
    "Type": "vxlan"
  }
}

2.3 配置systemd服务(在192.168.4.10和192.168.4.20执行)

编辑flanneld的systemd启动文件/usr/lib/systemd/system/flanneld.service

[Unit]
Description=flanneld overlay address etcd agent
After=docker.target
Before=docker.service

[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/flanneld
ExecStart=/usr/bin/flanneld --etcd-endpoints=${FLANNEL_ETCD} --etcd-prefix=${FLANNEL_ETCD_KEY}
Restart=always

[Install]
RequireBy=docker.service
WantedBy=multi-user.target

编辑/etc/sysconfig/flanneld配置文件,设置etcd 的url地址:

# flanneld configuration options

# etcd url location. Point this to the server where etcd runs
FLANNEL_ETCD="http://192.168.4.10:2379,http://192.168.4.20:2379"

# etcd config key. This the configuration key that flannel queries
# For address range assignment
FLANNEL_ETCD_KEY="/docker-flannel/network"
  • –etcd-endpoints 指定 etcd 的 url。
  • –etcd-prefix 指定 etcd 存放 flannel 网络配置信息的 key。只能指到目录级别,比如上面的key为:/docker-flannel/network/config ,只能指到/docker-flannel/network。

2.4 启动flanneld服务(在192.168.4.10和192.168.4.20执行)

由于Flannel将覆盖docker0网桥,所以如果Docker服务已启动,则需要停止Docker服务。

systemctl stop docker.service
systemctl restart flanneld.service
systemctl status flanneld.service
systemctl enable flanneld.service

2.5 设置docker0网桥的IP地址(在192.168.4.10和192.168.4.20执行)

mk-docker-opts.sh 
source /run/flannel/subnet.env 
ifconfig docker0 ${FLANNEL_SUBNET}

完成后通过以下命令查看IP信息

ip addr

查看docker0flannel.1的两个网卡,确认网络接口docker0的IP地址属于flannel0的子网

3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:78:6d:43:fb brd ff:ff:ff:ff:ff:ff
    inet 10.0.29.1/24 brd 10.0.29.255 scope global docker0
       valid_lft forever preferred_lft forever
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default 
    link/ether be:45:53:1d:54:a6 brd ff:ff:ff:ff:ff:ff
    inet 10.0.29.0/32 brd 10.0.29.0 scope global flannel.1
       valid_lft forever preferred_lft forever
    inet6 fe80::bc45:53ff:fe1d:54a6/64 scope link 
       valid_lft forever preferred_lft forever

2.6 把配置添加到flannel和docker的启动文件上(在192.168.4.10和192.168.4.20执行)

确认docker0能配置到IP后,把配置添加到flannel和docker的启动文件上:

  • 编辑flanneld启动文件:/usr/lib/systemd/system/flanneld.service,在ExecStart下一行添加:
ExecStartPost=/usr/bin/mk-docker-opts.sh -k DOCKER_NETWORK_OPTIONS -d /etc/default/docker
  • 编辑docker配置文件:/usr/lib/systemd/system/docker.service
    • 确认是否在ExecStart前引入EnvironmentFile=/etc/default/docker 文件,没有就添加引入。
    • 在ExecStart最后添加$DOCKER_NETWORK_OPTIONS,引入变量。
      最后重启flanneld和docker服务
systemctl daemon-reload 
systemctl restart flanneld.service 
systemctl restart docker.service 

至此完成了Flannel覆盖的设置,查看docker0的IP地址是否有生效。使用ping命令验证两个Node上是否可以互通,比如Node1(docker0=10.0.29.1/24),Node2(docker0=10.0.9.1/24),然后在Node1上ping 10.0.9.1 验证。