Docker Swarm 踩坑

解决 Swarm 服务副本不能在其他节点运行的问题


最近在读「深入浅出 Docker」,在学习 Docker Swarm 集群实现的时候掉坑里了,差点没爬出来,记录下。

挖坑

Docker Swarm 集群使用原生覆盖网络来实现多个主机之间的连接,Docker 提供了对原生覆盖网络的支持,是基于 Libnetwork 以及相应驱动来构建的,Libnetwork 是 CNM 的典型实现,从而可以通过插拔驱动的方式来实现不同的网络技术和拓扑结构。比如这里 Docker 就提供了 overlay 的原生驱动来实现覆盖网络。

我这里通过两台 CentOS7 虚拟机来实现 Swarm 的搭建。

在 node1 创建 Swarm

使用 init 来创建 Swarm,使其成为管理节点

docker swarm init

将 node2 加入 Swarm

将 node2 节点加入 Swarm 使其成为工作节点

注意,这里的 token 和 ip 需要修改成自己的

docker swarm join --token SWMTKN-1-3c69uwnwp6ijruxacv6ibjd37ml85k4sd3h14jcsus64ptjw1o-eqm34euiubbwlzme5poq70ib8 172.16.155.131:2377

可以在 node1 节点通过下面两条命令分别查询加入工作节点和管理节点的指令

工作节点:
docker swarm join-token worker

管理节点:
docker swarm join-token manager

查看节点列表

可以在管理节点执行如下命令查询 Swarm 中所有节点

docker node ls

创建新的 overlay 网络

在管理节点创建一个名为 uber-net 的 overlay 网络

docker network create -d overlay uber-net 

可以通过 docker network ls 列出管理节点上所有的网络

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
eb2ea6efa97c        bridge              bridge              local
d7bce6256c50        docker_gwbridge     bridge              local
5c78a9e4259c        host                host                local
91qhb0sad14d        ingress             overlay             swarm
d9f858f03165        none                null                local
om9znulbmdei        prod-overlay        overlay             swarm
xe42rvru0nxn        uber-net            overlay             swarm

可以看到列表中有一个名为 uber-net 的 overlay 网络被创建出来了。

注意,这时在 node2 节点上执行 docker network ls 列出的网络中,并不包含 uber-net。这时因为 Swarm service 在 node2 上的副本容器连接到该网络时,该网络才会变为可用状态。

将服务连接到 overlay 网络

在管理节点中创建新的服务,并将服务连接到 overlay 网络。该服务会包含两个服务副本(容器)。理论上,应该是一个运行于 node1 节点,一个运行于 node2 节点。同时,会自动将 node2 节点接入 uber-net 网络。

创建服务:

docker service create --name test --network uber-net --replicas 2 ubuntu sleep infinity

这里使用 ubuntu 镜像创建了一个名为 test 的服务。

如果一切正常,两个副本(容器)应当运行于两个主机节点,也就是 node1 一个 node2 一个。

使用 docker service ps test 查看服务详情:

image.png

命运就是如此,总不是一帆风顺。

可见,服务副本在 node2 上启动失败,也就是图中的 docker2.localdomain。由于在 node2 中启动失败,则两个服务副本就最终全部创建运行在 node1 管理节点中了。

到此,挖坑结束。

填坑

排查了很久,把书翻了一遍又一遍,没看出什么问题来。

通过 Google 查询,很多人都说是 overlay vxlan 的网络驱动问题,由于主机内核版本太低造成。OK,那就升级内核,将内核分别升级到了 kernel-mlkernel-lt,也就是 5.6.4 4.4.219,结果全都和刚才的错误一样。

无奈只能切换 Docker 版本,将最新版降到了和书中一样的版本,结果,还是不行。

怕了怕了,去睡了。

第二天,我开始重新梳理思绪,从提示信息开始。

既然 ERROR 提示:starting container failed: er...,那就是说明副本(容器)在 node2 节点上已经创建成功,只是启动的时候出问题了。

在 node2 节点中查看容器是否创建

docker container ls -a

可以看到容器已经创建,执行 docker container start xxxx 手动启动容器,xxxx 为容器 ID,启动失败,信息大体意思就是没有发现 overlay 网络,所以导致无法启动容器。

原因似乎在慢慢浮出水面,能够确定是 overlay 网络的问题了。

通过查询 docker overlay 资料和对比两个节点网络差异,发现了重要信息:docker_gwbridge

这个 docker_gwbridge 是什么东西呢?

docker_gwbridge 为使用多主机群覆盖网络的所有容器和任务提供默认网关功能。它是在每个Docker 主机上创建的,当 Docker 主机加入集群时创建。

docker_gwbridge 是一个本地桥接网络,在以下两种情况会自动创建:

也就是说,docker_gwbridge 在 Swarm 集群中充当网关的角色,每个主机通过 docker_gwbridge 进行网络连接。那上面创建的 ubet-net 网络是怎么回事呢?

在 node1 管理节点中:

通过 docker network ls 查看网络

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
eb2ea6efa97c        bridge              bridge              local
d7bce6256c50        docker_gwbridge     bridge              local
5c78a9e4259c        host                host                local
91qhb0sad14d        ingress             overlay             swarm
d9f858f03165        none                null                local
om9znulbmdei        prod-overlay        overlay             swarm
xe42rvru0nxn        uber-net            overlay             swarm

通过 ifconfig 查看系统网卡信息

$ ifconfig
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:61:1d:03:6c  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

docker_gwbridge: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.18.0.1  netmask 255.255.0.0  broadcast 172.18.255.255
        inet6 fe80::42:15ff:fe80:20bf  prefixlen 64  scopeid 0x20<link>
        ether 02:42:15:80:20:bf  txqueuelen 0  (Ethernet)
        ......

ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.16.155.131  netmask 255.255.255.0  broadcast 172.16.155.255
        inet6 fe80::a278:13ed:103a:28c8  prefixlen 64  scopeid 0x20<link>
        ......

.....

vethe7c84dd: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet6 fe80::9cdb:61ff:fe36:5793  prefixlen 64  scopeid 0x20<link>
        ether 9e:db:61:36:57:93  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        ......

vethff947fe: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet6 fe80::d430:f8ff:fe7b:6f52  prefixlen 64  scopeid 0x20<link>
        ether d6:30:f8:7b:6f:52  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        ......

其中 veth.... 则是在创建 docker 网络的时候系统上对应创建的网卡。

这些 veth.... 网卡其实是 docker 在主机内核中创建的 Linux 网桥。它和 docker 网络是一对一的。

可以通过使用 Linux brctl 工具来查看系统中的 Linux 网桥。

安装 brctl

yum install bridge-utils

查看 Linux 网桥:

$ brctl show
bridge name       bridge id    STP enabled   interfaces
docker0       8000.0242611d036cno
docker_gwbridge   8000.0242158020bfno       veth4e013cb
                           vethe7c84dd
                               vethff947fe

可以看到 ifconfig 中列出的 docker0docker_gwbridge 是 Linux 网桥,而 veth.... 则是 docker_gwbridge 网桥的 interface。这三个 veth... 对正好对应 docker 网络中的三个 overlay 网络

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
eb2ea6efa97c        bridge              bridge              local
d7bce6256c50        docker_gwbridge     bridge              local
5c78a9e4259c        host                host                local
91qhb0sad14d        ingress             overlay             swarm
d9f858f03165        none                null                local
om9znulbmdei        prod-overlay        overlay             swarm
xe42rvru0nxn        uber-net            overlay             swarm

也就是说 overlay 网络是作为 docker_gwbridge 网桥的接口的,overlay 之间的连接通讯也是通过 docker_gwbridge

在 node2 节点中,查看网卡信息,发现并没有 docker_gwbridge 的存在:

$ ifconfig
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:97:65:2c:a5  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.16.155.132  netmask 255.255.255.0  broadcast 172.16.155.255
        inet6 fe80::2420:d07c:f270:3e77  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:f5:ce:df  txqueuelen 1000  (Ethernet)
        RX packets 38582  bytes 4063671 (3.8 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 46242  bytes 5289150 (5.0 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 69  bytes 6228 (6.0 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 69  bytes 6228 (6.0 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

到这儿,原因就明确了,是 docker_gwbridge 没有创建的问题。按说 Docker Swarm 在初始化之后工作节点在加入 Swarm 时就会创建 docker_gwbridge,但不知为何没有创建,不过这是另一个问题了,日后再整。

现在根据 node1 管理节点中的 docker_gwbridge 网桥信息,在 node2 工作节点中创建 docker_gwbridge:

docker network create  --subnet 172.18.0.0/16  --gateway 172.18.0.1  -o com.docker.network.bridge.enable_icc=false  -o com.docker.network.bridge.name=docker_gwbridge docker_gwbridge

之后删除服务,再重新创建

docker service rm test
docker service create --name test --network uber-net --replicas 2 ubuntu sleep infinity

查看 test 服务详情:

docker service ps test

如图: image.png

可以看到服务副本已在两个节点正常运行。

大功告成!

相关博文资料:

docker 修改gwbridge ip address

Docker 1.12 swarm模式下遇到的各种问题

Swarm使用原生的overlay网络

一种生产环境Docker Overlay Network的配置方案