2015年Google主导成立了云原生计算基金会(CNCF),起初CNCF对云原生(Cloud Native)的定义包含以下三个方面:应用容器化,面向微服务架构,应用支持容器的编排调度。
云原生技术帮助公司和机构在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式API。
这些技术能够构建容错性好、易于管理和便于观察的松耦合系统。结合可靠的自动化手段,云原生技术可以使开发者轻松地对系统进行频繁并可预测的重大变更。
etcd是一个高可用的键值存储系统,主要用于共享配置和服务发现。etcd是由CoreOS开发并维护的,灵感来自于 ZooKeeper和 Doozer,它使用Go语言编写,并通过Raft一致性算法处理日志复制以保证强一致性。Raft是一个新的一致性算法,适用于分布式系统的日志复制,Raft通过选举的方式来实现一致性。
相比zookeeper(java编写,复杂的Paxos强一致性算法),etcd的优点如下:
- 简单:使用Go语言编写,部署简单;使用HTTP作为接口使用简单;使用Raft算法保证强一致性让用户易于理解。
- 数据持久化:etcd默认数据一更新就进行持久化。
- 安全:etcd支持SSL客户端安全认证。
目前CoreOS、Kubernetes和CloudFoundry等知名项目均在生产环境中使用了etcd。
Docker是一个开源的应用容器引擎,基于Go语言并遵从Apache2.0协议开源。
Docker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。
容器是完全使用沙箱机制,相互之间不会有任何接口(类似iPhone的app),更重要的是容器性能开销极低。
针对Kubernetes网络模型也涌现出了许多的实现方案,例如Calico、Flannel、Weave等等,虽然实现原理各有千秋,但都围绕着同一个问题即如何实现Kubernetes当中的扁平网络进行展开。Kubernetes只需要负责编排调度相关的事情,修桥铺路的事情交给相应的网络插件即可。
Flannel项目为CoreOS团队对Kubernetes网络设计实现的一种三层网络通信方案。Flannel通过在每一个节点上启动一个叫flanneld的进程,负责每一个节点上的子网划分,并将相关的配置信息如各个节点的子网网段、外部IP等保存到etcd当中,而具体的网络包转发交给具体的Backend来实现(UDP, VXLAN, host-gw)。
VXLAN全称Virtual Extensible LAN,是一种虚拟化隧道通信技术,主要是为了突破VLAN的最多4096个子网的数量限制,以满足大规模云计算数据中心的需求。VLAN技术的缺陷是VLAN Header预留的长度只有12 bit,故最多只能支持2的12次方即4096个子网的划分,无法满足云计算场景下主机数量日益增长的需求。当前VXLAN的报文Header内有24 bit,可以支持2的24次方个子网,并通过VNI(Virtual Network Identifier)来区分不同的子网,相当于VLAN当中的VLAN ID。
不同于其他隧道协议,VXLAN是一个一对多的网络,并不仅仅是一对一的隧道协议。一个VXLAN设备能通过像网桥一样的学习方式学习到其他对端的IP地址,也可以直接配置静态转发表。
当采用VXLAN模式时,flanneld在启动时会通过Netlink机制与Linux内核通信,建立一个VTEP(Virtual Tunnel Access End Point)设备flannel.1 (命名规则为flannel.[VNI],VNI默认为1),类似于交换机当中的一个网口,并将VTEP设备的相关信息上报到etcd当中。网络包在通过宿主机发出前先是加上了UDP头(8个字节),再然后加上了IP头(20个字节)进行封装,因此flannel0的MTU要比eth1的MTU小28个字节(MAC头加IP头)。
Linux container 中用到一个叫做veth的东西,这是一种新的设备,专门为 container 所建。veth 从名字上来看是 Virtual ETHernet 的缩写,它的作用很简单,就是要把从一个 network namespace 发出的数据包转发到另一个 namespace。veth 设备是成对的,一个是 container 之中,另一个在 container 之外,即在真实机器上能看到的。
VETH设备总是成对出现,送到一端请求发送的数据总是从另一端以请求接受的形式出现。创建并配置正确后,向其一端输入数据,VETH会改变数据的方向并将其送入内核网络子系统,完成数据的注入,而在另一端则能读到此数据。(Namespace,其中往veth设备上任意一端上RX到的数据,都会在另一端上以TX的方式发送出去)veth工作在L2数据链路层,veth-pair设备在转发数据包过程中并不串改数据包内容。
搭建flannel网络,使在不同宿主机之间的各个容器能够互相通讯。
如上图,假设:
例如containerA访问containerB:
例如containerA访问containerC
containerA访问containerC时,走的是默认路由指向hostA自己的docker0地址,此时由veth设备到达本宿主机的docker0。
hostA的路由表把包的转发指向hostA的flannel.3,即把包交给flannel接口(按照VTEP的配置进行封包处理)。
flannel通过etcd获取目的节点是hostB。
当采用VXLAN模式时,flanneld在启动时会通过Netlink机制与Linux内核通信,建立一个VTEP(Virtual Tunnel Access End Point)设备flannel.1 (命名规则为flannel.[VNI],VNI默认为1),类似于交换机当中的一个网口。并将VTEP设备的相关信息上报到etcd当中。
# ip -d link show flannel.3 #可看到VTEP端口是8472
# ip r | grep flannel #添加了172.17.0.0/16走flannel设备的路由
# ip n | grep flannel #添加其他docker节点的Arp信息
# netstat -ulnp | grep 8472 #查看VXLAN监听的端口
flannel.3是vxlan设备,vxlan并不会在二层发arp包,而是由linux kernel的 “L3 MISS"事件,将arp发到用户空间和flanneld程序。linux kernel的 “L3 MISS"事件参数由下面的参数设置:
# cat /proc/sys/net/ipv4/neigh/flannel.1/app_solicit
3
flanneld程序收到“L3 MISS”内核事件以及ARP请求后,并不会向外网发送arp request,而是从etcd查找匹配该地址的子网的vtep信息,即MAC地址。
# ETCDCTL_API=2 etcdctl ls / --recursive
# curl -L http://node144:2379/v2/keys/atomic.io/network/subnets/172.17.6.0-24
# bridge fdb show dev flannel.3 #找到目的地后,flanneld将查询到的信息存入arp缓存
# ip n
注意: 从docker0到ens33转发时,系统内核必须设置net.ipv4.ip_forward=1,否则不能转发出去。
flanneld封装好的UDP报文并通过8472端口发出到hostB。网络包在发出前先是加上了UDP头(8个字节),再然后加上了IP头(20个字节)进行封装,因此flannel0的MTU要比eth1的MTU小28个字节(MAC头加IP头)。此时的完整的以太网帧格式为:
Header | Description |
---|---|
MAC头 | Source: 节点A MAC Destination: 节点B MAC |
IP头 | Source: 节点A IP (192.168.5.144) Destination: 节点B IP (192.168.5.145) |
UDP头 | Source Port: 8472 Destination Port: 8472 |
报文 |
hostB收到包后,转发给VTEP设备的flannel.3进行解包。解封后的RAW IP包匹配到hostB上的路由规则,内核将RAW IP包发送到docker0。此时的完整的以太网帧格式为:
Header | Description |
---|---|
MAC头 | Source: 节点B docker0 Destination: container C MAC |
IP头 | Source: container A IP (172.17.8.2) Destination: containerB IP (172.17.6.2) |
payload |
hostB的docker0将IP包转发给连接在该网桥上的容器containerC。
本实验环境是在vmware安装的虚似机中进行的,在VMWare网络配置的NAT中使用的是192.168.5.2作为网关。
主机的角色划分如下:
主机名 | IP地址 | 应用 | 网关 |
---|---|---|---|
node144 | 192.168.5.144 | etcd、docker、flannel | 192.168.5.2 |
node145 | 192.168.5.145 | etcd、docker、flannel | 192.168.5.2 |
node146 | 192.168.5.146 | etcd、docker、flannel | 192.168.5.2 |
注: 以下步骤在所有主机上进行配置
注意根据所属主机,替换相应的静态IP地址。
# ip addr #检查当前的网卡
# cat /etc/sysconfig/network-scripts/ifcfg-ens33
TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=static #修改为static
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=ens33
DEVICE=ens33
ONBOOT=yes
IPADDR=192.168.5.144 #本地静态IP
NETMASK=255.255.255.0 #本地IP网段掩码
GATEWAY=192.168.5.2 #本例使用vmware,这里填上vmware网关地址
DNS1=192.168.5.2 #本例使用vmware,这里填上vmware网关地址
# hostnamectl --static set-hostname master
# hostname master
# cat /etc/hostname
# hostnamectl status
cat /etc/resolv.conf
search localdomain
nameserver 192.168.5.2
nameserver 8.8.8.8 #加上以避免yum失败
# yum -y install wget vim sysstat net-tools bridge-utils ebtables ethtool
# ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# date #检查当前时间是否正确
# cd /etc/yum.repos.d
# mkdir -p repos.bak && mv *.repo repos.bak
# wget -O /etc/yum.repos.d/Centos-7.repo http://mirrors.aliyun.com/repo/Centos-7.repo
# wget -O /etc/yum.repos.d/epel-7.repo http://mirrors.aliyun.com/repo/epel-7.repo
# yum clean all
# yum makecache
# yum repolist enabled
# cat /etc/profile #加入以下行
alias ..='cd ..'
alias ...='cd ../..'
LTGREEN="[\033[40;1;32m]"
LTBLUE="[\033[40;1;34m]"
CLEAR="[\033[0m]"
LIGHT_GRAY="[\033[40;1;33m]"
RED="[\033[40;1;31m]"
export PS1="$LTGREEN\u$RED@$LTBLUE\h:$LIGHT_GRAY\w$CLEAR \$ "
# source /etc/profile #生效
在三台主机上绑定host,内容相同。
# cat /etc/hosts
192.168.5.144 node144
192.168.5.145 node145
192.168.5.146 node146
# yum -y install etcd
# echo "export ETCDCTL_API=2" >> /etc/profile #默认使用v2版本
# echo "export ENDPOINTS=192.168.5.144:2380,192.168.5.145:2380,192.168.5.146:2380" >> /etc/profile
说明:
# firewall-cmd --zone=public --add-port=2379/tcp --permanent
# firewall-cmd --zone=public --add-port=2380/tcp --permanent
# firewall-cmd --reload #生效
# firewall-cmd --list-all
# firewall-cmd --list-ports
以下以主机192.168.5.144为例,注意每台机的主机名及IP需要作相应的修改。
# cp /etc/etcd/etcd.conf{,.orig}
# vim /etc/etcd/etcd.conf
#[Member]
ETCD_NAME="node144"
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_CLIENT_URLS="http://192.168.5.144:2379,http://127.0.0.1:2379"
ETCD_LISTEN_PEER_URLS="http://192.168.5.144:2380"
#[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.5.144:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.5.144:2379"
ETCD_INITIAL_CLUSTER="node144=http://192.168.5.144:2380,node145=http://192.168.5.145:2380,node146=http://192.168.5.146:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"
说明:
# systemctl daemon-reload
# systemctl enable etcd.service
# systemctl start etcd.service
# ps -ef|grep etcd
etcd 989 1 7 16:34 ? 00:00:03 /usr/bin/etcd --name=node144 --data-dir=/var/lib/etcd/default.etcd --listen-client-urls=http://192.168.5.144:2379,http://127.0.0.1:2379
## 日志检查
# journalctl -xe
# systemctl status etcd
## 状态检查
# etcdctl member list
# etcdctl endpoint status --endpoints=$ENDPOINTS --write-out=table
# etcdctl version
## 数据读写测试
# etcdctl put /test/foo "Hello World"
# etcdctl get /test/foo
# etcdctl del /test/foo
# etcdctl --write-out="json" get /test/foo
# etcdctl get /test/foo --prefix #前缀查询
## 查询所有数据
# ETCDCTL_API=2 etcdctl ls / --recursive
# ETCDCTL_API=3 etcdctl get --from-key "" --keys-only=true
## 安装前,清除以前旧的docker, flannel网卡:
# ip link delete docker0
# ip link delete flannel.1
## 安装flannel
# yum -y install flannel
说明:
修改配置文件/etc/sysconfig/flanneld
# vim /etc/sysconfig/flanneld
FLANNEL_ETCD_ENDPOINTS="http://127.0.0.1:2379" #ETCD的endpoint地址, 被/usr/bin/flanneld-start引用
FLANNEL_ETCD_PREFIX="/atomic.io/network" #在etcd中配置的网络参数的key,被/usr/bin/flanneld-start引用,注意不包括/config
FLANNEL_OPTIONS="--iface=ens33" #为flannel的启动参数,这里指定监听的网卡,被flanneld.service引用
查看其它配置文件(以下文件不须改动)
# vim /usr/bin/flanneld-start
#!/bin/sh
exec /usr/bin/flanneld \
-etcd-endpoints=${FLANNEL_ETCD_ENDPOINTS:-${FLANNEL_ETCD}} \
-etcd-prefix=${FLANNEL_ETCD_PREFIX:-${FLANNEL_ETCD_KEY}} \
"$@"
# vim /etc/sysconfig/docker-network
DOCKER_NETWORK_OPTIONS=
# vim /usr/lib/systemd/system/flanneld.service
ExecStart=/usr/bin/flanneld-start $FLANNEL_OPTIONS
ExecStartPost=/usr/libexec/flannel/mk-docker-opts.sh -k DOCKER_NETWORK_OPTIONS -d /run/flannel/docker
注:
flannel使用UDP port 8472,需要配置好防火墙。
# ip -d link show flannel.3
# firewall-cmd --zone=public --add-port=8472/udp --permanent
# firewall-cmd --reload #生效
# firewall-cmd --zone=public list-all
目录路径即/etc/sysconfig/flanneld里配置的FLANNEL_ETCD_PREFIX变量,flannel启动时从etcd的该目录里读取配置,使用v2版本接口。
# ETCDCTL_API=2 etcdctl set /atomic.io/network/config '{ "Network": "172.17.0.0/16", "SubnetMin": "172.17.0.0", "SubnetMax": "172.17.10.0", "Backend": {"Type": "vxlan","VNI":3}}'
# ETCDCTL_API=2 etcdctl get /atomic.io/network/config
# ETCDCTL_API=2 etcdctl get /coreos.com/network/config #读取配置
# cat /run/flannel/subnet.env #flannel启动时会把配置信息写入该文件
说明:
set的key是flannel用来读取配置的地址,必须与FLANNEL_ETCD_KEY一致,但是没有 '/config'字段。
Network 设置容器ip网段,docker0默认是172.17.0.0/16
SubnetMin 起始网段,可不写
SubnetMax 终止网段,可不写
Backend 数据字段
type 不写默认为udp方式,此处指定为vxlan方式
VNI 指定vlan id 默认是1
注意
Etcd V2和V3之间的数据结构完全不同,互不兼容,也就是说使用V2版本的API创建的数据只能使用V2的API访问,V3的版本的API创建的数据只能使用V3的API访问。这就造成我们访问etcd中保存的flannel的数据需要使用etcdctl的V2版本的客户端,而访问kubernetes的数据需要设置ETCDCTL_API=3环境变量来指定V3版本的API。
flannel默认配置在/atomic.io/network/config,在启动时读取。
flannel网络FLANNEL_SUBNET变量的IP范围必须在FLANNEL_NETWORK的范围内才能生效。Flannel在启动时会获取FLANNEL_SUBNET并写入subnet配置文件/run/flannel/subnet.env里。
可指定网卡用于注册到datastore, 使用参数-iface string和-public-ip string
# systemctl start flanneld
# ps -ef|grep flannel | grep flanneld
root 1500 1 0 16:35 ? 00:00:00 /usr/bin/flanneld -etcd-endpoints=http://127.0.0.1:2379 -etcd-prefix=/atomic.io/network --iface=ens33
# flanneld -alsologtostderr #日志查看
# cat /run/flannel/subnet.env #flannel启动后自动写入
# cat /run/flannel/docker
# ip a
# ETCDCTL_API=2 etcdctl get /atomic.io/network/config #获取etcd的配置信息
# ETCDCTL_API=2 etcdctl ls /atomic.io/network/subnets #查看已注册的宿主机
# yum -y install docker
# vim /usr/lib/systemd/system/docker.service
EnvironmentFile=-/run/flannel/docker
在刚配置完flannel后发现无法ping通外部容器,排错发现数据包到目的主机的flannel0后没有被发送到docker0上,主要是由于forward chain中ACTION为 DOCKER的规则阻断了数据包进入docker0
该规则将数据包交给DOCKER chain处理,而docker chain没有放行,就是说,缺省情况下docker service是不容许docker0外部数据经过docker0进入docker内部。
docker 1.13 版本对iptables的规则进行了改动,默认FORWARD 链的规则为DROP ,带来的问题主要一旦DROP后,不同主机间就不能正常通信了(kubernetes的网络使用flannel的情况)。
## 查看iptables
# iptables -nvL
## 临时解决方案,直接设置FORWARD 全局为ACCEPT:
# iptables -P FORWARD ACCEPT
## 永久解决方案,修改Docker启动参数,在[Service] 区域末尾加上参数:
# vim /usr/lib/systemd/system/docker.service
[Service]
ExecStartPost=/sbin/iptables -I FORWARD -s 0.0.0.0/0 -j ACCEPT
# systemctl daemon-reload
# systemctl restart docker
# systemctl daemon-reload
# systemctl restart docker
# ip addr #如果看到到flannel0余docker0的网段相同,则网络配置成功。
# ps aux|grep docker|grep "bip" #本地容器的IP范围
root 1635 1.2 1.4 652444 27776 ? Ssl 17:10 0:01 /usr/bin/dockerd-current --add-runtime docker-runc=/usr/libexec/docker/docker-runc-current --default-runtime=docker-runc --exec-opt native.cgroupdriver=systemd --userland-proxy-path=/usr/libexec/docker/docker-proxy-current --init-path=/usr/libexec/docker/docker-init-current --seccomp-profile=/etc/docker/seccomp.json --selinux-enabled --log-driver=journald --signature-verification=false --storage-driver overlay2 --bip=172.17.8.1/24 --ip-masq=true --mtu=1450
在不同机器上启动容器,然后测试通讯情况:
# docker ps
# docker search centos
# docker pull docker.io/centos
# docker run -it docker.io/centos /bin/bash
# docker exec -it $(hostname).test /bin/bash
$ yum -y install net-tools
$ ifconfig
$ ping xxx.xxx.xxx.xxx #ping别的容器