在《Ubuntu16.04手动部署Kubernetes(1)——Master和Node部署》一文中,我们已经介绍了Kubernetes的Master和Node的手动部署,而且最后执行kubectl get node也成功看到了一个Node节点。本文接着上文继续部署,这次要部署的是网络。回想一下,Kubernetes有一个非常重要的特性就是任意Pod间都可以通过网络进行彼此访问,不管这些Pod是否在同一个Node上面。

Flannel介绍

我们知道docker默认的网络模型是创建一个虚拟网桥docker0,然后每创建一个容器,就创建一个虚拟的veth pair,其中一端关联到docker0这个网桥上,另一端使用Linux的网络命名空间技术映射到容器内的eth0设备,然后从网桥的地址段内给eth0接口分配一个IP地址。如下图(图片来自Google图片搜索,懒得画图了...):

docker-network.png

我们自己可以在机器上面创建几个容器,然后用ip a或者ifconfig命令看一下。比如我的:

Master➜  others ip a | grep veth
28: veth9b8f2f6@if27: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1472 qdisc noqueue master docker0 state UP group default
30: veth70447ef@if29: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1472 qdisc noqueue master docker0 state UP group default
32: veth9f627b0@if31: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1472 qdisc noqueue master docker0 state UP group default
34: veth79f6b76@if33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1472 qdisc noqueue master docker0 state UP group default
36: vethd91d567@if35: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1472 qdisc noqueue master docker0 state UP group default

这样的网络模型非常的简单,但不好处就是处于不同主机的容器之间无法直接通信。这里之所以说“无法直接通信”是因为通过一些手段比如端口映射等可以勉强实现不同主机上的容器通信,但非常不方便。所以Kubernetes在自己的网络模型设计方面做了一些硬性规定:

  • 所有容器间可以相互通信,且不是通过NAT方式。
  • 所有Node可以和任意容器通信(反过来也可以),且不是通过NAT方式。
  • 容器看到自己的IP和别人看到自己的IP是一样的。

这个是Kubernetes集群网络的最基本要求,现在有很多软件都已经实现了这种网络模型,可参阅官方文档https://kubernetes.io/docs/admin/networking/。本文今天介绍的就是其中一种,而且非常的简单——Flannel,这是CoreOS专门为Kubernetes开发的一个网络工具,用于实现其网络模型。其模型图如下所示:

flannel.png

flannel在每个host上面运行了一个代理程序flanneld,它可以为所在的host维护一个网段,并且为运行在这个host上的Kubernetes pods从这个网段里分配IP地址。多个hosts上的flanneld进程可以利用etcd机群互相协调以确保各自拥有不交叠的网段,这样一个集群上的pods各自拥有互不重复的IP地址。这种构筑在一个网络(hosts网络)之上的另一个网络(flanneld维护的pods网络),被称为 overlay network。上面的模型图也非常的清楚,容器里的网络流量先到docker0,然后会转到flannel0,再通过flanneld发到主机的某个网卡上(图中是eth0),然后再转发出去。流量到另外一台主机时,执行相反的过程。

下面我们在上文已经搭建的Kubernetes上面部署flannel网络。

Flannel部署

具体部署之前先说明两个点:

  • 要测试网络至少需要两台主机,上文部署的Master和Node在同一台主机上面,所以我们需要按照上文部署Node的方法,再部署一台Node,部署好以后,会自动向Master注册。具体部署过程我就不赘述了(就是部署Kubelet、kube-proxy、docker即可),我这里已经再部署了一台。而且这两台的网络必须是通的。
  • Flannel支持很多种模式:udp、vxlan、host-gw、aws-vpc、gce等。我们这里部署采用默认的模式即UDP。这种模式就是在应用层再对数据包做了一次封装,然后通过UDP发出去,这种方式下默认使用的端口是8258。这种应用层的再次封装转发必然会对性能有影响,而且因为是UDP,所以还可能丢包,但这些都不是本文要讨论的。

etcd设置

OK,现在我们来开始部署。从前面的介绍我们知道Flannel依赖于etcd,前文中我们已经部署好了,这里只需要再增加一条配置即可:

etcdctl set /coreos.com/network/config '{ "Network": "10.1.0.0/16" }'

这条指令我们指定了一个网段,后面Flannel在host上面构建子网的时候就会从这个网段中选取一个子网。注意,如果这个网段和你已有的网段冲突,那就更换成其他的。而这里/coreos.com/network是Flannel默认从etcd读取的前缀,可以改成其他的,然后启动Flannel时通过--etcd-prefix指定新的前缀即可,这里为了方便,直接使用默认的。这个动作只在Master上面执行。

另外,etcd默认是只监听本地地址的,我们需要改为也监听其他IP,至少要监听其他Node主机可以访问到IP,这里我改为在本机所有IP上面监听,在/etc/default/etcd中增加以下两行:

ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_ADVERTISE_CLIENT_URLS="http://0.0.0.0:2379"

然后重启etcd:systemctl restart etcd.service。这样etcd部分就配置完了。

flanneld配置

然后从https://github.com/coreos/flannel/releases下载最新的Flannel,将解压得到的flanneldmk-docker-opts.sh放到你的PATH里面,我继续放到/opt/bin目录下。然后和上文一样我们为flanneld创建service文件/lib/systemd/system/flanneld.service

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

[Service]
Type=notify
ExecStart=/opt/bin/flanneld -etcd-endpoints=http://192.168.56.101:2379 --iface=enp0s8 --log_dir=/var/log/kubernetes -logtostderr=false -v=1

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

这里有三点注意:

  1. -etcd-endpoints:这个是etcd的地址。可以在本节点上面执行curl http://192.168.56.101:2379/version来验证看是否可以访问到etcd。
  2. Flannel复用了docker的docker0网桥,所以必须在docker启动前就启动Flannel。
  3. 最后要说的就是这个--iface=enp0s8。我使用Virtualbox创建了两个Ubuntu 16.04的虚拟机,默认采用的是NAT网络,一般对应enp0s3这个网卡。但为了让两个虚拟机有在同一网段的不同IP,我又使用host-only的模式增加了一个网卡enp0s8,这样一个节点的地址是192.168.56.101,另外一个是192.168.56.102,且这两个都是静态IP。默认flanneld会使用enp0s3,但两个节点通信其实是通过enp0s8,所以这里我需要显式的指定一下使用哪个网卡。

如果你的docker在运行,必须先停止systemctl stop docker.service,因为flanneld会覆盖docker的docker0网桥。然后启动flanneld服务systemctl restart flanneld.service.此时执行ip a已经可以看到虚拟的flannel0了

然后执行/opt/bin/mk-docker-opts.sh -i获取flanneld使用的网段,查看cat /run/flannel/subnet.env,比如我的是:

FLANNEL_NETWORK=10.1.0.0/16
FLANNEL_SUBNET=10.1.35.1/24
FLANNEL_MTU=1472
FLANNEL_IPMASQ=false

可见flanneld给本机分配的网段是10.1.35.1/24,我们需要将让docker在自己的docker0上面也使用这个网段,创建/etc/systemd/system/docker.service.d目录,并在里面创建任意conf后缀结尾的文件,如果已经有了,直接修改其内容,比如我的这个文件内容如下:

Node➜  ~ cat /etc/systemd/system/docker.service.d/docker.conf
[Service]
ExecStart=
ExecStart=/usr/bin/docker daemon -H fd:// --bip=10.1.35.1/24 --mtu=1472

然后启动docker:systemctl start docker.service。这样docker0和flannel0就都配置好了,比如我的网络如下(如果起了容器的话,还会看到veth网络,这里未列出):

Node➜  ~ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:6e:c8:25 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global enp0s3
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe6e:c825/64 scope link
       valid_lft forever preferred_lft forever
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:ca:35:d1 brd ff:ff:ff:ff:ff:ff
    inet 192.168.56.102/24 brd 192.168.56.255 scope global enp0s8
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:feca:35d1/64 scope link
       valid_lft forever preferred_lft forever
4: flannel0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1472 qdisc pfifo_fast state UNKNOWN group default qlen 500
    link/none
    inet 10.1.35.0/16 scope global flannel0
       valid_lft forever preferred_lft forever
5: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1472 qdisc noqueue state UP group default
    link/ether 02:42:6e:e0:81:0a brd ff:ff:ff:ff:ff:ff
    inet 10.1.35.1/24 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:6eff:fee0:810a/64 scope link
       valid_lft forever preferred_lft forever

然后把这一节的操作在另外一台上面也执行一遍。我的另外一台部署好以后的网络如下:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:af:51:42 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global enp0s3
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:feaf:5142/64 scope link
       valid_lft forever preferred_lft forever
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:91:b8:d0 brd ff:ff:ff:ff:ff:ff
    inet 192.168.56.101/24 brd 192.168.56.255 scope global enp0s8
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe91:b8d0/64 scope link
       valid_lft forever preferred_lft forever
5: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:96:fd:14:ef brd ff:ff:ff:ff:ff:ff
    inet 10.1.85.1/24 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:96ff:fefd:14ef/64 scope link
       valid_lft forever preferred_lft forever
49: flannel0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1472 qdisc pfifo_fast state UNKNOWN group default qlen 500
    link/none
    inet 10.1.85.0/16 scope global flannel0
       valid_lft forever preferred_lft forever

flanneld使用了的子网在Master上面的etcd也会有记录:

Master➜  ~ etcdctl ls /coreos.com/network/subnets  --recursive
/coreos.com/network/subnets/10.1.85.0-24
/coreos.com/network/subnets/10.1.35.0-24
Master➜  ~ etcdctl get /coreos.com/network/subnets/10.1.85.0-24
{"PublicIP":"192.168.56.101"}
Master➜  ~ etcdctl get /coreos.com/network/subnets/10.1.35.0-24
{"PublicIP":"192.168.56.102"}

验证

最后我们验证一下:

  1. 分别在每台上面ping一下另外一台的docker0,看是否可以ping通。如果通了再看2.
  2. 我们在Kubernetes上面部署几个Pod,在一个Node上面ping另外一个Node上面的Pod的IP,如果可以ping通那几成功了。

最后,如果没有通的话,看下各个服务是否启动正常,有没有报错之类的。再看下ufw防火墙和iptable是不是有问题。还是定位不出问题的话,根据Flannel的网络模型使用tcpdump分别在flannel0和docker0上面抓包,看是哪里的问题。