目录

隐藏
  1. 我的 Docker 版本是 1.12,请问我跑的是一代 Swarm 还是二代 Swarm 啊?
  2. Swarm环境中怎么指定某个容器在指定的宿主上运行呢?
  3. 为什么 Swarm 集群的 overlay network 跨宿主无法互访?
  4. Docker 二代Swarm (既 Swarm Mode),docker service create 不可以使用 -v 那怎么使用卷(Volume)?
  5. 对于两节点集群来说,--replicas=2 和 --mode=global 是不是一个意思?
  6. 一代 Swarm 的时候可以用 docker run 啊,二代怎么又弄个 docker service create 出来?为什么要多此一举?
  7. docker service ps 里面总是有一堆失败或者shutdown的历史容器,怎么删啊?
  8. 怎么才能让 docker service create 创建的服务正常退出时不重启啊?
我的 Docker 版本是 1.12,请问我跑的是一代 Swarm 还是二代 Swarm 啊?
……自己运行的 Swarm 怎么会连自己都不知道跑的是啥?

首先,至于是运行的一代 Swarm 还是二代 Swarm,单看版本是没意义的。Docker 1.12+ 的版本同时支持一代 Swarm 和二代 Swarm。

如果是使用容器形式运行的 Swarm,也就是 docker run swarm 形式构建的 Swarm,这是一代 Swarm,也被称为 Docker Swarm。

如果是使用内置命令形式构建的 Swarm,也就是使用命令 docker swarm init 形式构建的 Swarm,这是内置的 Swarm,是二代 Swarm,也被称为 Docker Swarm Mode。

Swarm环境中怎么指定某个容器在指定的宿主上运行呢?
每个 Docker Host 建立时都可以通过 --label 指定其 Docker 引擎的标签,比如:

dockerd \
--label com.example.environment="production" \
--label com.example.storage="ssd"
注意,上面的配置参数应该配置在 Docker 引擎的配置文件里修改,如 docker.service,而不是简单的命令行执行……

然后运行容器时,使用环境变量约束调度即可。可以使用 Compose 文件的 environment 配置,也可以使用 docker run 的 -e 环境变量参数。下面以 Compose 配置文件为例:

version: "2"
services:
mongodb:
image: mongo:latest
environment:
- "constraint:com.example.storage==ssd"
这样这个 mongodb 的服务就会运行在标记为 com.example.storage="ssd" 的宿主上运行。

为什么 Swarm 集群的 overlay network 跨宿主无法互访?
首先,检查建立 Swarm 的时候,对其它节点所宣告的本节点的地址是否正确。

对于单网卡、单IP的宿主,Swarm 会自动选择网卡地址,但是多网卡、多IP的宿主,就必须手动宣告地址。

对于一代 Swarm 而言,检查一下 dockerd 的配置中,--cluster-advertise 地址是否配置正确。
对于二代 Swarm,则检查一下创建、加入 Swarm 的时候,--advertise-addr 是否填写正确。
宣告地址必须是全集群可以互访的,由于该地址端口是 Docker Remote API 端口,所以可以用 curl 来连接其它节点,以判断互通性。

然后,检查宿主间的网络互通问题,特别是宿主的防火墙开启的情况下,检查下列服务端口有没有放开:

7946/{udp,tcp}
4789/{udp,tcp}
{2375,2376,2377,3375,3376}/tcp (具体端口取决于实际 Swarm 或 Engine 守护端口)
可以通过 telnet, curl 之类的工具确保上述端口可以互访。

如果还是有问题,可以进一步启用各个节点的 Docker 引擎的调试模式。和配置 --insecure-registry 的方法一样,编辑 Docker 配置文件,在 dockerd 后添加 -D 参数。然后重新启动 Docker 引擎,建立集群、网络、服务。如果问题重现,可以分析 Docker 引擎的日志,具体查看日志的方法见前面的问答。

需要注意的是,在 1.13 以前的版本中,跨宿主的情况下,无法在容器内 ping 到另一个服务的 VIP,这种情况,可以 ping tasks.<服务名>,来跳过 VIP 进行 ping。

参考:

https://docs.docker.com/swarm/plan-for-production/

https://docs.docker.com/engine/swarm/swarm-tutorial/#/open-ports-between-the-hosts

https://docs.docker.com/engine/swarm/networking/

Docker 二代Swarm (既 Swarm Mode),docker service create 不可以使用 -v 那怎么使用卷(Volume)?
从二代 Swarm 开始,将使用 --mount 参数来进行卷挂载,并且对语义进行更明确的划分。

挂载分两种:

绑定挂载 bind-mount:这类挂载将宿主目录/文件绑定到容器的某个位置。这类挂载需要注意宿主和容器的不同uid导致的权限、访问控制差异问题。
数据卷 data volumes:这类挂载是之前推荐使用的卷。卷可以分为命名卷named volume以及匿名卷anonymous volume
挂载参数的格式基本上为 --mount=[,=,...]。

主要参数有:

type: 如之前所说,两种类型: volume 和 bind。如果不指定 type,默认为 volume;
src 或 source:源:
如果 type=volume,src 则是卷的名字,是可选项。如果存在就是命名卷,如果没指定 src 则是匿名卷;
如果 type=bind,src 是宿主本地的路径;
dst 或 destination 或 target:将在容器内挂载的路径。如果路径不存在,会在挂载前自动建立路径。
readonly 或 ro:是否让该挂载为只读,默认是读写。
除此之外还有一些常见的参数可以设置:

volume-driver:指定卷驱动,默认是 local,可以通过这个参数指定其它(如 flocker, glusterfs, ceph)之类的驱动;
volume-label:指定卷的元数据(metadata),从而方便过滤操作;
volume-opt:不同的卷驱动可能需要额外的参数,这个选项可以指定这些参数。
--mount 和 --volume 有一些差异需要注意:

--mount 可以直接使用卷,而无需事先使用 docker volume create 来创建卷,并且可以多组不同驱动的卷;
--mount 如果 type=bind 的话,宿主必须存在指定目录,否则报错。而 --volume 则在宿主不存在该路径时,在宿主创建一个空目录来进行绑定。
举几个例子:

挂载命名卷:

docker service create \
--name my-service \
--replicas 3 \
--mount type=volume,source=my-volume,destination=/path/in/container,volume-label="color=red",volume-label="shape=round" \
nginx:alpine
挂载匿名卷:

docker service create \
--name my-service \
--replicas 3 \
--mount type=volume,destination=/path/in/container \
nginx:alpine
绑定宿主目录

docker service create \
--name my-service \
--mount type=bind,source=/path/on/host,destination=/path/in/container \
nginx:alpine
参考官网文档: https://docs.docker.com/engine/reference/commandline/service_create/#/add-bind-mounts-or-volumes

对于两节点集群来说,--replicas=2 和 --mode=global 是不是一个意思?
首先,二者语义就不同。

--replicas=2,是要求该服务有2个副本,无论集群多少个节点,也不在乎这两个副本是不是都跑在一个宿主上,所以无法确保每个节点一个副本;
--mode=global,是要求服务在集群每一个节点上跑一个副本。
现象上也不一样,--replicas=2 是要确保副本为2个。那么如果一个节点挂了,会在另一个节点上在起一个副本,从而确保副本数为2。而对于 --mode=global 来说,如果一个节点挂了,不会再另一个节点上起一个副本。

一代 Swarm 的时候可以用 docker run 啊,二代怎么又弄个 docker service create 出来?为什么要多此一举?
因为 docker run 和 docker service create 是两个不同理念的东西。

一代 Swarm 中,将 Swarm 集群视为一个巨大的 Docker 主机,本质上和单机没有区别,都是直接调度运行容器。因此依旧使用单机的 docker run 的方式来启动特定容器。

二代 Swarm 则改变了这个理念,增加了服务栈(Stack)、服务(Service)、任务(Task) 的概念。在二代 Swarm 中,一组服务可以组成一个整体进行部署,也就是部署服务栈,这相当于是之前的 Docker Compose 所完成的目的。但是这次,是真正的针对服务的。

一个服务并非一个容器,一个服务可以有多个副本任务,每个任务对应一个容器。这个概念在一代 Swarm 和单机环境中是没有的,因此 Docker Compose 为了实现服务的概念,用了各种办法去模拟,包括使用 labels,使用网络别名等等,但是本质上,依旧是以容器为单位进行运行,也就是本质上还是一组 docker run。

正是由于二代 Swarm 中用户操作的单元是服务,所以传统的以容器为中心的 docker run 就不再适用,因此有新的一组针对服务的命令,docker service。

docker service ps 里面总是有一堆失败或者shutdown的历史容器,怎么删啊?
使用了一段时间二代 Swarm 后,特别是维护了几次服务后,会发现 docker service ps 中显示了很多之前失败的容器记录,很是烦人。

$ docker service ps web                                                                                                       [e641012]
NAME                    IMAGE                NODE  DESIRED STATE  CURRENT STATE                ERROR                             PORTS
web.1.uultetkovwch      nginx:1.11.3-alpine  moby  Running        Running 43 seconds ago
\_ web.1.etb0jpr21mhe  nginx:1.11.2-alpine  moby  Shutdown       Rejected about a minute ago  "No such image: nginx:1.11.2-a…"
\_ web.1.uhyl9158ldf9  nginx:1.11.2-alpine  moby  Shutdown       Rejected about a minute ago  "No such image: nginx:1.11.2-a…"
\_ web.1.mwallx77rc3m  nginx:1.11.1-alpine  moby  Shutdown       Shutdown about a minute ago
\_ web.1.zmyvszvv21ak  nginx:1.10-alpine    moby  Shutdown       Shutdown 3 minutes ago
web.2.dp0imr8kbmad      nginx:1.11.3-alpine  moby  Running        Running 43 seconds ago
\_ web.2.0xzwj9o8kfzn  nginx:1.11.2-alpine  moby  Shutdown       Rejected about a minute ago  "No such image: nginx:1.11.2-a…"
\_ web.2.mk3g1l5js4ph  nginx:1.11.2-alpine  moby  Shutdown       Rejected about a minute ago  "No such image: nginx:1.11.2-a…"
\_ web.2.v9x98vxma1q3  nginx:1.11.2-alpine  moby  Shutdown       Rejected 2 minutes ago       "No such image: nginx:1.11.2-a…"
\_ web.2.vo5mvyux3vok  nginx:1.11.2-alpine  moby  Shutdown       Rejected 2 minutes ago       "No such image: nginx:1.11.2-a…"
web.3.cc3mrji1dlz2      nginx:1.11.3-alpine  moby  Running        Running 42 seconds ago
\_ web.3.uvubn2pdceaa  nginx:1.11.2-alpine  moby  Shutdown       Rejected about a minute ago  "No such image: nginx:1.11.2-a…"
\_ web.3.mt7xwl4a0bii  nginx:1.11.2-alpine  moby  Shutdown       Rejected 2 minutes ago       "No such image: nginx:1.11.2-a…"
\_ web.3.cdseizjlnzoj  nginx:1.11.2-alpine  moby  Shutdown       Rejected 2 minutes ago       "No such image: nginx:1.11.2-a…"
\_ web.3.kwlz5mxbu2t6  nginx:1.11.2-alpine  moby  Shutdown       Rejected 2 minutes ago       "No such image: nginx:1.11.2-a…"
web.4.zoadbqu3wtzf      nginx:1.11.3-alpine  moby  Running        Running 42 seconds ago
\_ web.4.u6p58nzxlib9  nginx:1.11.2-alpine  moby  Shutdown       Rejected about a minute ago  "No such container: web.4.u6p5…"
\_ web.4.kf420b73lwcc  nginx:1.11.2-alpine  moby  Shutdown       Rejected about a minute ago  "No such image: nginx:1.11.2-a…"
\_ web.4.tzzmc1zuffn7  nginx:1.11.2-alpine  moby  Shutdown       Rejected 2 minutes ago       "No such image: nginx:1.11.2-a…"
\_ web.4.mccsks74u370  nginx:1.11.1-alpine  moby  Shutdown       Shutdown 2 minutes ago
web.5.u0i5cnh0m5jb      nginx:1.11.3-alpine  moby  Running        Running 39 seconds ago
\_ web.5.xzv6o5hoxjay  nginx:1.11.1-alpine  moby  Shutdown       Shutdown 41 seconds ago
\_ web.5.f0ssiwebempk  nginx:1.10-alpine    moby  Shutdown       Shutdown 3 minutes ago
可以看到那些历史上运行过的容器,它们当然已经停止运行,保留在这里是为了帮助进行服务排障,默认情况下,会保留最后 5 个容器历史,其余的会被删除。这个参数可以在集群建立时(docker swarm init)或者更新时(docker swarm update),可以通过参数 --task-history-limit 来调整。

但是不要因为只是看着乱,就将历史记录设的很少,因为历史记录的存在是有原因的,可以通过历史记录来进行排障。如果没有这些信息,在将来维护出现问题的时候,故障根本无从查起。

如果只是觉得看着乱而不想显示,直接用 --filter 加过滤即可。

$ docker service ps -f 'desired-state=running' web                                                                            [e641012]
NAME                IMAGE                NODE  DESIRED STATE  CURRENT STATE           ERROR  PORTS
web.1.uultetkovwch  nginx:1.11.3-alpine  moby  Running        Running 11 minutes ago
web.2.dp0imr8kbmad  nginx:1.11.3-alpine  moby  Running        Running 11 minutes ago
web.3.cc3mrji1dlz2  nginx:1.11.3-alpine  moby  Running        Running 11 minutes ago
web.4.zoadbqu3wtzf  nginx:1.11.3-alpine  moby  Running        Running 11 minutes ago
web.5.u0i5cnh0m5jb  nginx:1.11.3-alpine  moby  Running        Running 10 minutes ago

怎么才能让 docker service create 创建的服务正常退出时不重启啊?
有些时候会有这样的需求,比如服务是由应用层的远程控制指令关闭的,这种进程退出是正常行为,并非错误。但是默认情况下,只要容器退出,引擎就视为异常,就会尝试重新调度启动这个容器。这会导致明明关了的服务,又被启动了。

这种情况可以使用 --restart-condition=on-failure 参数,这样只有在主进程退出码为 非 0 的时候,才会重启,而正常退出(exited code = 0) 无需重启。