docker入门基础(八)

十、 Docker Swarm

1、几个概念

1、Docker Swarm Mode

Docker v1.12 是一个非常重要的版本,Docker 重新实现了集群的编排方式。在此之前,提供集群功能的 Docker Swarm 是一个单独的软件,而且依赖外部数据库(比如 Consul、etcd 或 Zookeeper)。

从 v1.12 开始,Docker Swarm 的功能已经完全与 Docker Engine 集成,要管理集群,只需要启动 Swarm Mode。安装好 Docker,Swarm 就已经在那里了,服务发现也在那里了(不需要安装 Consul 等外部数据库)。

相比 Kubernetes,用 Docker Swarm 创建集群非常简单,不需要额外安装任何软件,也不需要做任何额外的配置。很适合作为学习容器编排引擎的起点。

2、swarm

swarm 运行 Docker Engine 的多个主机组成的集群。

从 v1.12 开始,集群管理和编排功能已经集成进 Docker Engine。当 Docker Engine 初始化了一个 swarm 或者加入到一个存在的 swarm 时,它就启动了 swarm mode。

没启动 swarm mode 时,Docker 执行的是容器命令;运行 swarm mode 后,Docker 增加了编排 service 的能力。

Docker 允许在同一个 Docker 主机上既运行 swarm service,又运行单独的容器。

3、node

swarm 中的每个 Docker Engine 都是一个 node,有两种类型的 node:managerworker

为了向 swarm 中部署应用,我们需要在 manager node 上执行部署命令,manager node 会将部署任务拆解并分配给一个或多个 worker node 完成部署。

manager node 负责执行编排和集群管理工作,保持并维护 swarm 处于期望的状态。swarm 中如果有多个 manager node,它们会自动协商并选举出一个 leader 执行编排任务。

woker node 接受并执行由 manager node 派发的任务。默认配置下 manager node 同时也是一个 worker node,不过可以将其配置成 manager-only node,让其专职负责编排和集群管理工作。

work node 会定期向 manager node 报告自己的状态和它正在执行的任务的状态,这样 manager 就可以维护整个集群的状态。

4、service

service 定义了 worker node 上要执行的任务。swarm 的主要编排任务就是保证 service 处于期望的状态下。

举一个 service 的例子:在 swarm 中启动一个 http 服务,使用的镜像是 httpd:latest,副本数为 3。

manager node 负责创建这个 service,经过分析知道需要启动 3 个 httpd 容器,根据当前各 worker node 的状态将运行容器的任务分配下去,比如 worker1 上运行两个容器,worker2 上运行一个容器。

运行了一段时间,worker2 突然宕机了,manager 监控到这个故障,于是立即在 worker3 上启动了一个新的 httpd 容器。

这样就保证了 service 处于期望的三个副本状态。

2、swarm 集群

实验环境

主机名IP系统docker版本
swarm-manager192.168.2.110CentOS 7.5docker-ce-18.09.0-3.el7.x86_64
swarm-worker1192.168.2.120CentOS 7.5docker-ce-18.09.0-3.el7.x86_64
swarm-worker2192.168.2.130CentOS 7.5docker-ce-18.09.0-3.el7.x86_64

在 swarm-manager 上执行如下命令创建 swarm。

[root@manager ~]# docker swarm init --advertise-addr 192.168.2.110
Swarm initialized: current node (rxoqe9tvd0rfd4zxc4uoniu05) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-04d6zlih0yqjgnlz8rlwhpz1qfc2ua9yp18y15wyefrctgrj0x-chtm4rrdysjp8e310um4rjx09 192.168.2.110:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

--advertise-addr 指定与其他 node 通信的地址。

docker swarm init 输出告诉我们:

① swarm 创建成功,swarm-manager 成为 manager node。

② 添加 worker node 需要执行的命令。

③ 添加 manager node 需要执行的命令。

执行 docker node ls 查看当前 swarm 的 node,目前只有一个 manager。

[root@manager ~]# docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
rxoqe9tvd0rfd4zxc4uoniu05 *   manager             Ready               Active              Leader              18.09.0

复制前面的 docker swarm join 命令,在 worker1 和worker2 上执行,将它们添加到 swarm 中。命令输出如下:

[root@swarm-worker2 ~]# docker swarm join --token SWMTKN-1-04d6zlih0yqjgnlz8rlwhpz1qfc2ua9yp18y15wyefrctgrj0x-chtm4rrdysjp8e310um4rjx09 192.168.2.110:2377
This node joined a swarm as a worker.
[root@swarm-manager ~]# docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
rxoqe9tvd0rfd4zxc4uoniu05 *   swarm-manager       Ready               Active              Leader              18.09.0
jfpu6n3qt4gqnoqs8lv36o5fn     swarm-worker1       Ready               Active                                  18.09.0
byxpoc4rgi45jz1d8vi5hgrog     swarm-worker2       Ready               Active                                  18.09.0

如果当时没有记录下 docker swarm init 提示的添加 worker 的完整命令,可以通过 docker swarm join-token worker 查看。

[root@manager ~]# docker swarm join-token worker
To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-04d6zlih0yqjgnlz8rlwhpz1qfc2ua9yp18y15wyefrctgrj0x-chtm4rrdysjp8e310um4rjx09 192.168.2.110:2377

注意:此命令只能在 manager node 上执行。

至此,三节点的 swarm 集群就已经搭建好了。

3、运行第一个 Service

部署一个运行 httpd 镜像的 service,执行如下命令:

docker service create --name web_server httpd

部署 service 的命令形式与运行容器的 docker run 很相似,--name 为 service 命名,nginx:1.14-alpine 为镜像的名字。

[root@swarm-manager ~]# docker service create --name my_web nginx:1.14-alpine
kqhkkqt0i6r6u40qknuyb76ja
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged 

通过 docker service ls 可以查看当前 swarm 中的 service。

[root@swarm-manager ~]# docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
kqhkkqt0i6r6        my_web              replicated          1/1                 nginx:1.14-alpine  

REPLICAS 显示当前副本信息,1/1 的意思是 web_server 这个 service 期望的容器副本数量为 1,目前已经启动的副本数量为1。也就是当前 service 还没有部署完成。命令 docker service ps 可以查看 service 每个副本的状态。

[root@swarm-manager ~]# docker service ps my_web
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE                ERROR               PORTS
kokoy6h24gwp        my_web.1            nginx:1.14-alpine   swarm-worker1       Running             Running about a minute ago                       

可以看到 service 唯一的副本被分派到 swarm-worker1,当前的状态是 running

如果觉得不放心,还可以到 swarm-worker1 去确认 nginx 容器已经运行。

[root@swarm-worker1 ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
a0f7ea32d403        nginx:1.14-alpine   "nginx -g 'daemon of…"   2 minutes ago       Up 2 minutes        80/tcp              my_web.1.kokoy6h24gwpbujbp0id9ay15

目前为止 Service 与普通的容器还没有太大的不同

4、实现 Service 伸缩

对于 web 服务,我们通常会运行多个实例。这样可以负载均衡,同时也能提供高可用。

swarm 要实现这个目标非常简单,增加 service 的副本数就可以了。在 swarm-manager 上执行如下命令:

docker service scale web_server=5
[root@swarm-manager ~]# docker service scale my_web=5
my_web scaled to 5
overall progress: 5 out of 5 tasks 
1/5: running   [==================================================>] 
2/5: running   [==================================================>] 
3/5: running   [==================================================>] 
4/5: running   [==================================================>] 
5/5: running   [==================================================>] 
verify: Service converged 

[root@swarm-manager ~]# docker service scale my_web=5
my_web scaled to 5
overall progress: 5 out of 5 tasks 
1/5: running   [==================================================>] 
2/5: running   [==================================================>] 
3/5: running   [==================================================>] 
4/5: running   [==================================================>] 
5/5: running   [==================================================>] 
verify: Service converged 
[root@swarm-manager ~]# docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
kqhkkqt0i6r6        my_web              replicated          5/5                 nginx:1.14-alpine   
[root@swarm-manager ~]# docker service ps my_web 
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
kokoy6h24gwp        my_web.1            nginx:1.14-alpine   swarm-worker1       Running             Running 3 minutes ago                        
e8yjfo9td44m        my_web.2            nginx:1.14-alpine   swarm-worker2       Running             Running 11 hours ago                         
7fth256vi4dw        my_web.3            nginx:1.14-alpine   swarm-worker2       Running             Running 11 hours ago                         
7dk4zf9li67p        my_web.4            nginx:1.14-alpine   swarm-worker1       Running             Running 50 seconds ago                       
kz9b8mv2q1sk        my_web.5            nginx:1.14-alpine   swarm-worker2       Running             Running 11 hours ago 

5 个副本已经分布在 swarm 的所有三个节点上。

默认配置下 manager node 也是 worker node,所以 swarm-manager 上也运行了副本。如果不希望在 manager 上运行 service,可以执行如下命令:

docker node update --availability drain swarm-manager
[root@swarm-manager ~]# docker node update --availability drain swarm-manager 
swarm-manager
[root@swarm-manager ~]# docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
rxoqe9tvd0rfd4zxc4uoniu05 *   swarm-manager       Ready               Drain               Leader              18.09.0
jfpu6n3qt4gqnoqs8lv36o5fn     swarm-worker1       Ready               Active                                  18.09.0
byxpoc4rgi45jz1d8vi5hgrog     swarm-worker2       Ready               Active                                  18.09.0
[root@swarm-manager ~]# docker service ps web_server
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE                    ERROR               PORTS
qdq9gcq6u01d        web_server.1        httpd:latest        swarm-worker2       Running             Running less than a second ago                       
h3b36zug0o3b        web_server.2        httpd:latest        swarm-worker2       Running             Running less than a second ago                       
pmxnu78a94lm        web_server.3        httpd:latest        swarm-worker1       Running             Running 2 minutes ago                                
j08u3j2ctwal        web_server.4        httpd:latest        swarm-worker1       Running             Running 20 seconds ago                               
2teq0ag7n4u4         \_ web_server.4    httpd:latest        swarm-manager       Shutdown            Shutdown 21 seconds ago                              
pmrzsdue7ryk        web_server.5        httpd:latest        swarm-worker1       Running             Running 20 seconds ago                               
wqtbw718bcx3         \_ web_server.5    httpd:latest        swarm-manager       Shutdown            Shutdown 21 seconds ago   

swarm-manager 上的副本 web_server.4 \5 已经被 Shutdown 了,为了达到 5 个副本数的目标,在 swarm-worker1 上添加了副本 web_server.4\5

前面我们的场景是 scale up,我们还可以 scale down,减少副本数,运行下面的命令:

docker service scale web_server=3
[root@swarm-manager ~]# docker service scale my_web=3
my_web scaled to 3
overall progress: 3 out of 3 tasks 
1/3: running   [==================================================>] 
2/3: running   [==================================================>] 
3/3: running   [==================================================>] 
verify: Service converged 
[root@swarm-manager ~]# docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
kqhkkqt0i6r6        my_web              replicated          3/3                 nginx:1.14-alpine   
[root@swarm-manager ~]# docker service ps my_web 
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE           ERROR               PORTS
kokoy6h24gwp        my_web.1            nginx:1.14-alpine   swarm-worker1       Running             Running 4 minutes ago                       
e8yjfo9td44m        my_web.2            nginx:1.14-alpine   swarm-worker2       Running             Running 11 hours ago                        
7fth256vi4dw        my_web.3            nginx:1.14-alpine   swarm-worker2       Running             Running 11 hours ago 

可以看到,web_server.4web_server.5 这两个副本已经被删除了。

5、实现 Failover

故障是在所难免的,容器可能崩溃,Docker Host 可能宕机,不过幸运的是,Swarm 已经内置了 failover 策略。

创建 service 的时候,我们没有告诉 swarm 发生故障时该如何处理,只是说明了我们期望的状态(比如运行3个副本),swarm 会尽最大的努力达成这个期望状态,无论发生什么状况.

现在我们测试 swarm 的 failover 特性,关闭 swarm-worker1。

Swarm 会检测到 swarm-worker1 的故障,并标记为 Down。

[root@swarm-manager ~]# docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
rxoqe9tvd0rfd4zxc4uoniu05 *   swarm-manager       Ready               Drain               Leader              18.09.0
jfpu6n3qt4gqnoqs8lv36o5fn     swarm-worker1       Down                Active                                  18.09.0
byxpoc4rgi45jz1d8vi5hgrog     swarm-worker2       Ready               Active                                  18.09.0

Swarm 会将 swarm-worker1 上的副本调度到其他可用节点。我们可以通过 docker service ps 观察这个 failover 过程。

[root@swarm-manager ~]# docker service ps my_web 
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE           ERROR               PORTS
w285dbb0spcf        my_web.1            nginx:1.14-alpine   swarm-worker2       Running             Running 11 hours ago                        
kokoy6h24gwp         \_ my_web.1        nginx:1.14-alpine   swarm-worker1       Shutdown            Running 5 minutes ago                       
e8yjfo9td44m        my_web.2            nginx:1.14-alpine   swarm-worker2       Running             Running 11 hours ago                        
7fth256vi4dw        my_web.3            nginx:1.14-alpine   swarm-worker2       Running             Running 11 hours ago 

可以看到,web_server.1web_server.2 已经从 swarm-worker1 迁移到了 swarm-worker2,之前运行在故障节点 swarm-worker1 上的副本状态被标记为 Shutdown。即使worker1恢复也不行

6、内部访问 Service

为了便于分析,我们重新部署 web_server。

[root@swarm-manager ~]# docker service rm my_web 
my_web
[root@swarm-manager ~]# docker service create --name web_server --replicas=2  nginx:1.14-alpine
qnr86tsd8ht8r7d1bfrbhxx5i
overall progress: 2 out of 2 tasks 
1/2: running   [==================================================>] 
2/2: running   [==================================================>] 
verify: Service converged 
[root@swarm-manager ~]# docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
qnr86tsd8ht8        web_server          replicated          2/2                 nginx:1.14-alpine   
[root@swarm-manager ~]# docker service ps web_server 
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE                ERROR               PORTS
ak7uuvnjf8mx        web_server.1        nginx:1.14-alpine   swarm-worker2       Running             Running 11 hours ago                             
z9kck3rspzec        web_server.2        nginx:1.14-alpine   swarm-worker1       Running             Running about a minute ago   

docker service rm 删除 web_server,service 的所有副本(容器)都会被删除。

② 重新创建 service,这次直接用 --replicas=2 创建两个副本。

③ 每个 worker node 上运行了一个副本。

要访问 http 服务,最起码网络得通吧,服务的 IP 我们得知道吧,但这些信息目前我们都不清楚。不过至少我们知道每个副本都是一个运行的容器,要不先看看容器的网络配置吧。

[root@swarm-worker1 ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
68502d64ca5f        nginx:1.14-alpine   "nginx -g 'daemon of…"   46 seconds ago      Up 46 seconds       80/tcp              web_server.2.z9kck3rspzecnyik9llg3pa1f
[root@swarm-worker1 ~]# docker exec web_server.2.z9kck3rspzecnyik9llg3pa1f ip r
default via 172.16.1.1 dev eth0 
172.16.1.0/24 dev eth0 scope link  src 172.16.1.2 

在 swarm-worker1 上运行了一个容器,是 web_server 的一个副本,容器监听了 80 端口,但并没有映射到 Docker Host,所以只能通过容器的 IP 访问。查看一下容器的 IP。

容器 IP 为 172.16.1.2,实际上连接的是 Docker 默认 bridge 网络。

我们可以直接在 swarm-worker1 上访问容器的 http 服务。

[root@swarm-worker1 ~]# curl 172.16.1.2 -I
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Wed, 26 Dec 2018 02:16:48 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Fri, 21 Dec 2018 01:22:26 GMT
Connection: keep-alive
ETag: "5c1c4052-264"
Accept-Ranges: bytes

但这样的访问也仅仅是容器层面的访问,服务并没有暴露给外部网络,只能在 Docker 主机上访问。换句话说,当前配置下,我们无法访问 service web_server。

7、外部访问service

要将 service 暴露到外部,方法其实很简单,执行下面的命令:

[root@swarm-manager ~]# docker service  update --publish-add 8080:80 web_server 
web_server
overall progress: 2 out of 2 tasks 
1/2: running   [==================================================>] 
2/2: running   [==================================================>] 
verify: Service converged 

如果是新建 service,可以直接用使用 --publish 参数,比如:

docker service create --name web_server --publish 8080:80 --replicas=2 httpd

容器在 80 端口上监听 http 请求,--publish-add 8080:80 将容器的 80 映射到主机的 8080 端口,这样外部网络就能访问到 service 了。

[root@swarm-manager ~]# curl -I 192.168.2.110:8080
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Wed, 26 Dec 2018 02:18:17 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Fri, 21 Dec 2018 01:22:26 GMT
Connection: keep-alive
ETag: "5c1c4052-264"
Accept-Ranges: bytes

[root@swarm-manager ~]# curl -I 192.168.2.120:8080
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Wed, 26 Dec 2018 02:18:20 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Fri, 21 Dec 2018 01:22:26 GMT
Connection: keep-alive
ETag: "5c1c4052-264"
Accept-Ranges: bytes

[root@swarm-manager ~]# curl -I 192.168.2.130:8080
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Wed, 26 Dec 2018 02:18:22 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Fri, 21 Dec 2018 01:22:26 GMT
Connection: keep-alive
ETag: "5c1c4052-264"
Accept-Ranges: bytes

当我们访问任何节点的 8080 端口时,swarm 内部的 load balancer 会将请求转发给 web_server 其中的一个副本,无论访问哪个节点,即使该节点上没有运行 service 的副本,最终都能访问到 service。

另外,我们还可以配置一个外部 load balancer,将请求路由到 swarm service。比如配置 HAProxy,将请求分发到各个节点的 8080 端口。

8、ingress 网络

当我们应用 --publish-add 8080:80 时,swarm 会重新配置 service。

[root@swarm-manager ~]# docker service ps web_server 
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE                 ERROR               PORTS
wyl8a3mhheyr        web_server.1        nginx:1.14-alpine   swarm-worker2       Running             Running 11 hours ago                              
ak7uuvnjf8mx         \_ web_server.1    nginx:1.14-alpine   swarm-worker2       Shutdown            Shutdown 11 hours ago                             
v3sw5eq19z4x        web_server.2        nginx:1.14-alpine   swarm-worker1       Running             Running about a minute ago                        
z9kck3rspzec         \_ web_server.2    nginx:1.14-alpine   swarm-worker1       Shutdown            Shutdown about a minute ago    

之前的所有副本都被 Shutdown,然后启动了新的副本。我们查看一下新副本的容器网络配置。

[root@swarm-worker1 ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS               NAMES
9a28cef94e92        nginx:1.14-alpine   "nginx -g 'daemon of…"   About a minute ago   Up About a minute   80/tcp              web_server.2.v3sw5eq19z4x8ytauz3x2wl1w
[root@swarm-worker1 ~]# docker exec web_server.2.v3sw5eq19z4x8ytauz3x2wl1w ip r
default via 172.18.0.1 dev eth1 
10.255.0.0/16 dev eth0 scope link  src 10.255.0.10 
172.18.0.0/16 dev eth1 scope link  src 172.18.0.3 

容器的网络与 --publish-add 之前已经大不一样了,现在有两块网卡,每块网卡连接不同的 Docker 网络

实际上:

  1. eth0 连接的是一个 overlay 类型的网络,名字为 ingress,其作用是让运行在不同主机上的容器可以相互通信。
  2. eth1 连接的是一个 bridge 类型的网络,名字为 docker_gwbridge,其作用是让容器能够访问到外网。
[root@swarm-worker1 ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
d174ec92bfb9        bridge              bridge              local
f57703c9517c        docker_gwbridge     bridge              local
17bf4830406d        harbor_harbor       bridge              local
266cc54ae977        host                host                local
t4xnhmp6y5nr        ingress             overlay             swarm
a5d3ec376305        mybr0               bridge              local
436b29f1c660        none                null                local

ingress 网络是 swarm 创建时 Docker 为自动我们创建的,swarm 中的每个 node 都能使用 ingress

9、service之间通信

微服务架构的应用由若干 service 组成。比如有运行 httpd 的 web 前端,有提供缓存的 memcached,有存放数据的 mysql,每一层都是 swarm 的一个 service,每个 service 运行了若干容器。在这样的架构中,service 之间是必然要通信的。

1)服务发现

一种实现方法是将所有 service 都 publish 出去,然后通过 routing mesh 访问。但明显的缺点是把 memcached 和 mysql 也暴露到外网,增加了安全隐患。

如果不 publish,那么 swarm 就要提供一种机制,能够:

  1. 让 service 通过简单的方法访问到其他 service。
  2. 当 service 副本的 IP 发生变化时,不会影响访问该 service 的其他 service。
  3. 当 service 的副本数发生变化时,不会影响访问该 service 的其他 service。

这其实就是服务发现(service discovery)。Docker Swarm 原生就提供了这项功能,通过服务发现,service 的使用者不需要知道 service 运行在哪里,IP 是多少,有多少个副本,就能与 service 通信。

2)创建 overlay 网络

要使用服务发现,需要相互通信的 service 必须属于同一个 overlay 网络,所以我们先得创建一个新的 overlay 网络。

[root@swarm-manager ~]#  docker network create --driver overlay myapp_net
rdozkrfo8mg9y9t3m8swiyv8q
[root@swarm-manager ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
4508165c1437        bridge              bridge              local
e56359b1d013        docker_gwbridge     bridge              local
266cc54ae977        host                host                local
t4xnhmp6y5nr        ingress             overlay             swarm
rdozkrfo8mg9        myapp_net           overlay             swarm
436b29f1c660        none                null                local

注意:目前 ingress 没有提供服务发现,必须创建自己的 overlay 网络。

3)部署 service 到 overlay

部署一个 web 服务,并将其挂载到新创建的 overlay 网络。

[root@swarm-manager ~]# docker service create --name my_web --replicas=3 --network myapp_net nginx:1.14-alpine
hlv54albpldu99ox7a5bnpsch
overall progress: 3 out of 3 tasks 
1/3: running   [==================================================>] 
2/3: running   [==================================================>] 
3/3: running   [==================================================>] 
verify: Service converged 
[root@swarm-manager ~]# docker service ps my_web 
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
v6luevjpk1of        my_web.1            nginx:1.14-alpine   swarm-worker1       Running             Running 22 seconds ago                       
o6orjanarrk8        my_web.2            nginx:1.14-alpine   swarm-worker2       Running             Running 11 hours ago                         
zo6hgkztxzzx        my_web.3            nginx:1.14-alpine   swarm-worker2       Running             Running 11 hours ago 

部署一个 util 服务用于测试,挂载到同一个 overlay 网络。

[root@swarm-manager ~]# docker service create --name util --network myapp_net busybox sleep 10000000
pa8qw96mx4ru0bcb6krz21shi
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged 

sleep 10000000 的作用是保持 busybox 容器处于运行的状态,我们才能够进入到容器中访问 service my_web

4)验证

通过 docker service ps util 确认 util 所在的节点为 swarm-worker1。

[root@swarm-manager ~]# docker service ps util 
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE                ERROR               PORTS
uuf4yyfra9sd        util.1              busybox:latest      swarm-worker1       Running             Running about a minute ago    

登录到 swarm-worker1,在容器 util.1 中 ping 服务 my_web

[root@swarm-worker1 ~]# docker exec util.1.uuf4yyfra9sdqqbyd9mchcewt ping -c 3 my_web
PING my_web (10.0.0.2): 56 data bytes
64 bytes from 10.0.0.2: seq=0 ttl=64 time=0.141 ms
64 bytes from 10.0.0.2: seq=1 ttl=64 time=0.080 ms
64 bytes from 10.0.0.2: seq=2 ttl=64 time=0.080 ms

--- my_web ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.080/0.100/0.141 ms

可以看到 my_web 的 IP 为 10.0.0.2,这是哪个副本的 IP 呢?

其实哪个副本的 IP 都不是。10.0.0.2my_web service 的 VIP(Virtual IP),swarm 会将对 VIP 的访问负载均衡到每一个副本。

对于服务的使用者(这里是 util.1),根本不需要知道 my_web副本的 IP,也不需要知道 my_web 的 VIP,只需直接用 service 的名字 my_web 就能访问服务。

10、滚动更新 Service

滚动更新降低了应用更新的风险,如果某个副本更新失败,整个更新将暂停,其他副本则可以继续提供服务。同时,在更新的过程中,总是有副本在运行的,因此也保证了业务的连续性。

下面我们将部署三副本的服务,镜像使用 nginx:1.14-alpine,然后将其更新到 nginx:1.15-alpine。

[root@swarm-manager ~]# docker service ps my_web 
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
v6luevjpk1of        my_web.1            nginx:1.14-alpine   swarm-worker1       Running             Running 12 minutes ago                       
o6orjanarrk8        my_web.2            nginx:1.14-alpine   swarm-worker2       Running             Running 11 hours ago                         
zo6hgkztxzzx        my_web.3            nginx:1.14-alpine   swarm-worker2       Running             Running 11 hours ago 
docker service update --image httpd:2.2.32 my_web

--image 指定新的镜像。

Swarm 将按照如下步骤执行滚动更新:

  1. 停止第一个副本。
  2. 调度任务,选择 worker node。
  3. 在 worker 上用新的镜像启动副本。
  4. 如果副本(容器)运行成功,继续更新下一个副本;如果失败,暂停整个更新过程。

docker service ps 查看更新结果。

[root@swarm-manager ~]# docker service update --image nginx:1.15-alpine my_web
my_web
overall progress: 3 out of 3 tasks 
1/3: running   
2/3: running   
3/3: running   
verify: Service converged 
[root@swarm-manager ~]# docker service ps my_web 
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE             ERROR               PORTS
ws8ym7uxht1g        my_web.1            nginx:1.15-alpine   swarm-worker1       Running             Running 15 seconds ago                        
v6luevjpk1of         \_ my_web.1        nginx:1.14-alpine   swarm-worker1       Shutdown            Shutdown 20 seconds ago                       
unbw58qiakb9        my_web.2            nginx:1.15-alpine   swarm-worker2       Running             Running 11 hours ago                          
o6orjanarrk8         \_ my_web.2        nginx:1.14-alpine   swarm-worker2       Shutdown            Shutdown 11 hours ago                         
eqkbqmzyllxi        my_web.3            nginx:1.15-alpine   swarm-worker2       Running             Running 11 hours ago                          
zo6hgkztxzzx         \_ my_web.3        nginx:1.14-alpine   swarm-worker2       Shutdown            Shutdown 11 hours ago    

默认配置下,Swarm 一次只更新一个副本,并且两个副本之间没有等待时间。我们可以通过 --update-parallelism 设置并行更新的副本数目,通过 --update-delay 指定滚动更新的间隔时间。

比如执行如下命令:

docker service update --replicas 6 --update-parallelism 2 --update-delay 1m30s my_web

service 增加到六个副本,每次更新两个副本,间隔时间一分半钟。

[root@swarm-manager ~]# docker service update --replicas 6 --update-parallelism 2 --update-delay 1m30s my_web 
my_web
overall progress: 6 out of 6 tasks 
1/6: running   
2/6: running   
3/6: running   
4/6: running   
5/6: running   
6/6: running   
verify: Service converged 
[root@swarm-manager ~]# docker service inspect --pretty my_web 

ID:     hlv54albpldu99ox7a5bnpsch
Name:       my_web
Service Mode:   Replicated
 Replicas:  6
Placement:
UpdateConfig:
 Parallelism:   2
 Delay:     1m30s
 On failure:    pause
 Monitoring Period: 5s
 Max failure ratio: 0
 Update order:      stop-first
RollbackConfig:
 Parallelism:   1
 On failure:    pause
 Monitoring Period: 5s
 Max failure ratio: 0
 Rollback order:    stop-first
ContainerSpec:
 Image:     nginx:1.15-alpine@sha256:2e497c294e3ba84aaeab7a0fbb1027819cd1f5f5892ed3c4a82b8b05010090da
 Init:      false
Resources:
Networks: myapp_net 
Endpoint Mode:  vip

docker service ps 确保6个副本处于正常状态。

[root@swarm-manager ~]# docker service ps my_web 
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
ws8ym7uxht1g        my_web.1            nginx:1.15-alpine   swarm-worker1       Running             Running 3 minutes ago                        
v6luevjpk1of         \_ my_web.1        nginx:1.14-alpine   swarm-worker1       Shutdown            Shutdown 3 minutes ago                       
unbw58qiakb9        my_web.2            nginx:1.15-alpine   swarm-worker2       Running             Running 11 hours ago                         
o6orjanarrk8         \_ my_web.2        nginx:1.14-alpine   swarm-worker2       Shutdown            Shutdown 11 hours ago                        
eqkbqmzyllxi        my_web.3            nginx:1.15-alpine   swarm-worker2       Running             Running 11 hours ago                         
zo6hgkztxzzx         \_ my_web.3        nginx:1.14-alpine   swarm-worker2       Shutdown            Shutdown 11 hours ago                        
i0nuldq0b6fy        my_web.4            nginx:1.15-alpine   swarm-worker1       Running             Running 52 seconds ago                       
m8ofettpaj9n        my_web.5            nginx:1.15-alpine   swarm-worker2       Running             Running 11 hours ago                         
zphfbb64m5t8        my_web.6            nginx:1.15-alpine   swarm-worker1       Running             Running 52 seconds ago  

将镜像更新到nginx:1.15.7-alpine

[root@swarm-manager ~]# docker service update --image nginx:1.15.7-alpine my_web 
my_web
overall progress: 6 out of 6 tasks 
1/6: running   
2/6: running   
3/6: running   
4/6: running   
5/6: running   
6/6: running   
verify: Service converged 

[root@swarm-manager ~]# docker service ps my_web 
ID                  NAME                IMAGE                 NODE                DESIRED STATE       CURRENT STATE                 ERROR               PORTS
kc2y0tedle3u        my_web.1            nginx:1.15.7-alpine   swarm-worker1       Running             Running 21 seconds ago                            
ws8ym7uxht1g         \_ my_web.1        nginx:1.15-alpine     swarm-worker1       Shutdown            Shutdown 21 seconds ago                           
v6luevjpk1of         \_ my_web.1        nginx:1.14-alpine     swarm-worker1       Shutdown            Shutdown 8 minutes ago                            
mebv7vaxnn3v        my_web.2            nginx:1.15.7-alpine   swarm-worker2       Running             Running 11 hours ago                              
unbw58qiakb9         \_ my_web.2        nginx:1.15-alpine     swarm-worker2       Shutdown            Shutdown 11 hours ago                             
o6orjanarrk8         \_ my_web.2        nginx:1.14-alpine     swarm-worker2       Shutdown            Shutdown 11 hours ago                             
rhyrbqpna2jm        my_web.3            nginx:1.15.7-alpine   swarm-worker2       Running             Running 11 hours ago                              
eqkbqmzyllxi         \_ my_web.3        nginx:1.15-alpine     swarm-worker2       Shutdown            Shutdown 11 hours ago                             
zo6hgkztxzzx         \_ my_web.3        nginx:1.14-alpine     swarm-worker2       Shutdown            Shutdown 11 hours ago                             
vnwofhlgrn3s        my_web.4            nginx:1.15.7-alpine   swarm-worker1       Running             Running about a minute ago                        
i0nuldq0b6fy         \_ my_web.4        nginx:1.15-alpine     swarm-worker1       Shutdown            Shutdown about a minute ago                       
pvfa2dhzqhod        my_web.5            nginx:1.15.7-alpine   swarm-worker2       Running             Running 11 hours ago                              
m8ofettpaj9n         \_ my_web.5        nginx:1.15-alpine     swarm-worker2       Shutdown            Shutdown 11 hours ago                             
yciilsu8di50        my_web.6            nginx:1.15.7-alpine   swarm-worker1       Running             Running 3 minutes ago                             
zphfbb64m5t8         \_ my_web.6        nginx:1.15-alpine     swarm-worker1       Shutdown            Shutdown 3 minutes ago     

Swarm 还有个方便的功能是回滚,如果更新后效果不理想,可以通过 --rollback 快速恢复到更新之前的状态。

请注意,--rollback 只能回滚到上一次执行 docker service update 之前的状态,并不能无限制地回滚。

[root@swarm-manager ~]# docker service update --rollback my_web
my_web
rollback: manually requested rollback 
overall progress: rolling back update: 6 out of 6 tasks 
1/6: running   [>                                                  ] 
2/6: running   [>                                                  ] 
3/6: running   [>                                                  ] 
4/6: running   [>                                                  ] 
5/6: running   [>                                                  ] 
6/6: running   [>                                                  ] 
verify: Service converged 

[root@swarm-manager ~]# docker service ps my_web 
ID                  NAME                IMAGE                 NODE                DESIRED STATE       CURRENT STATE                 ERROR               PORTS
h3ajwiltt5si        my_web.1            nginx:1.15-alpine     swarm-worker1       Running             Running about a minute ago                        
kc2y0tedle3u         \_ my_web.1        nginx:1.15.7-alpine   swarm-worker1       Shutdown            Shutdown about a minute ago                       
ws8ym7uxht1g         \_ my_web.1        nginx:1.15-alpine     swarm-worker1       Shutdown            Shutdown 3 minutes ago                            
v6luevjpk1of         \_ my_web.1        nginx:1.14-alpine     swarm-worker1       Shutdown            Shutdown 11 minutes ago                           
mz9biuxzrjpl        my_web.2            nginx:1.15-alpine     swarm-worker2       Running             Running 11 hours ago                              
mebv7vaxnn3v         \_ my_web.2        nginx:1.15.7-alpine   swarm-worker2       Shutdown            Shutdown 11 hours ago                             
unbw58qiakb9         \_ my_web.2        nginx:1.15-alpine     swarm-worker2       Shutdown            Shutdown 11 hours ago                             
o6orjanarrk8         \_ my_web.2        nginx:1.14-alpine     swarm-worker2       Shutdown            Shutdown 11 hours ago                             
7kjpoptdhxgl        my_web.3            nginx:1.15-alpine     swarm-worker2       Running             Running 11 hours ago                              
rhyrbqpna2jm         \_ my_web.3        nginx:1.15.7-alpine   swarm-worker2       Shutdown            Shutdown 11 hours ago                             
eqkbqmzyllxi         \_ my_web.3        nginx:1.15-alpine     swarm-worker2       Shutdown            Shutdown 11 hours ago                             
zo6hgkztxzzx         \_ my_web.3        nginx:1.14-alpine     swarm-worker2       Shutdown            Shutdown 11 hours ago                             
dehr4bbs3e58        my_web.4            nginx:1.15-alpine     swarm-worker1       Running             Running 50 seconds ago                            
vnwofhlgrn3s         \_ my_web.4        nginx:1.15.7-alpine   swarm-worker1       Shutdown            Shutdown 51 seconds ago                           
i0nuldq0b6fy         \_ my_web.4        nginx:1.15-alpine     swarm-worker1       Shutdown            Shutdown 5 minutes ago                            
wshw5cdo3v43        my_web.5            nginx:1.15-alpine     swarm-worker2       Running             Running 11 hours ago                              
pvfa2dhzqhod         \_ my_web.5        nginx:1.15.7-alpine   swarm-worker2       Shutdown            Shutdown 11 hours ago                             
m8ofettpaj9n         \_ my_web.5        nginx:1.15-alpine     swarm-worker2       Shutdown            Shutdown 11 hours ago                             
uqejx1yrc6yx        my_web.6            nginx:1.15-alpine     swarm-worker1       Running             Running 58 seconds ago                            
yciilsu8di50         \_ my_web.6        nginx:1.15.7-alpine   swarm-worker1       Shutdown            Shutdown 59 seconds ago                           
zphfbb64m5t8         \_ my_web.6        nginx:1.15-alpine     swarm-worker1       Shutdown            Shutdown 6 minutes ago    

11、replicated mode vs global mode

Swarm 可以在 service 创建或运行过程中灵活地通过 --replicas 调整容器副本的数量,内部调度器则会根据当前集群的资源使用状况在不同 node 上启停容器,这就是 service 默认的 replicated mode。在此模式下,node 上运行的副本数有多有少,一般情况下,资源更丰富的 node 运行的副本数更多,反之亦然。

除了 replicated mode,service 还提供了一个 globalmode,其作用是强制在每个 node 上都运行一个且最多一个副本。

此模式特别适合需要运行 daemon 的集群环境。比如要收集所有容器的日志,就可以 global mode 创建 service,在所有 node 上都运行 gliderlabs/logspout 容器,即使之后有新的 node 加入,swarm 也会自动在新 node 上启动一个 gliderlabs/logspout 副本。

[root@swarm-manager ~]# docker service create \
>        --mode global \
>        --name logspout \
>        --mount type=bind,source=/var/run/docker.sock,destination=/var/run/docker.sock \
>        gliderlabs/logspout
su2vyv5n6vaazpj8ds3cvl14y
overall progress: 2 out of 2 tasks 
byxpoc4rgi45: running   
jfpu6n3qt4gq: running   
verify: Service converged 
[root@swarm-manager ~]# docker service ps logspout
ID                  NAME                                 IMAGE                        NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
z1633gpkqvh7        logspout.jfpu6n3qt4gqnoqs8lv36o5fn   gliderlabs/logspout:latest   swarm-worker1       Running             Running 21 seconds ago                       
8uibx9n8dt56        logspout.byxpoc4rgi45jz1d8vi5hgrog   gliderlabs/logspout:latest   swarm-worker2       Running             Running 11 hours ago     

可以通过 docker service inspect 查看 service 的 mode。

[root@swarm-manager ~]# docker service inspect logspout 
......
"Mode": {
                "Global": {}
            },
......

这里是 Global,如果创建 service 时不指定,默认是 Replicated

无论采用 global mode 还是 replicated mode,副本运行在哪些节点都是由 Swarm 决定的,作为用户我们有没有可能精细控制 service 的运行位置呢?

能,使用 label

12、Label 控制 Service 的位置

逻辑分两步:

  1. 为每个 node 定义 label。
  2. 设置 service 运行在指定 label 的 node 上。

label 可以灵活描述 node 的属性,其形式是 key=value,用户可以任意指定,例如将 swarm-worker1 作为测试环境,为其添加 label env=test

[root@swarm-manager ~]# docker node update --label-add env=test swarm-worker1 
swarm-worker1
[root@swarm-manager ~]# docker node inspect swarm-worker1 
[
    {
        "ID": "jfpu6n3qt4gqnoqs8lv36o5fn",
        "Version": {
            "Index": 388
        },
        "CreatedAt": "2018-12-25T02:43:42.19392138Z",
        "UpdatedAt": "2018-12-26T03:52:58.276285242Z",
        "Spec": {
            "Labels": {
                "env": "test"

对应的,将 swarm-worker2 作为生产环境,添加 label env=prod

[root@swarm-manager ~]# docker node update --label-add env=prod swarm-worker2 
swarm-worker2
[root@swarm-manager ~]# docker node inspect swarm-worker2
[
    {
        "ID": "byxpoc4rgi45jz1d8vi5hgrog",
        "Version": {
            "Index": 389
        },
        "CreatedAt": "2018-12-25T02:43:44.590690728Z",
        "UpdatedAt": "2018-12-26T03:54:09.646331335Z",
        "Spec": {
            "Labels": {
                "env": "prod"
            },

现在部署 service 到测试环境:

[root@swarm-manager ~]# docker service create \
> --constraint node.labels.env==test \
> --replicas 3 \
> --name my_web \
> --publish 8080:80 \
> nginx:1.15.7-alpine
[root@swarm-manager ~]# docker service ps my_web 
ID                  NAME                IMAGE                 NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
9huefun6bfw8        my_web.1            nginx:1.15.7-alpine   swarm-worker1       Running             Running 24 seconds ago                       
tdyah0y5wflf        my_web.2            nginx:1.15.7-alpine   swarm-worker1       Running             Running 24 seconds ago                       
6dssuoosib4w        my_web.3            nginx:1.15.7-alpine   swarm-worker1       Running             Running 24 seconds ago    

--constraint node.labels.env==test 限制将 service 部署到 label=test 的 node,即 swarm-worker1。从部署结果看,三个副本全部都运行在 swarm-worker1 上。

可以通过 docker service inspect 查看 --constraint 的设置:

[root@swarm-manager ~]# docker service inspect my_web --pretty 

ID:     vgcmd2r8pznw6koqwf4l896hv
Name:       my_web
Service Mode:   Replicated
 Replicas:  3
Placement:
 Constraints:   [node.labels.env==test]

更新 service,将其迁移到生产环境:

[root@swarm-manager ~]# docker service update --constraint-add node.labels.env==prod my_web
my_web
overall progress: 3 out of 3 tasks 
1/3: running   
2/3: running   
3/3: running   
verify: Service converged 
[root@swarm-manager ~]# docker service ps my_web 
ID                  NAME                IMAGE                 NODE                DESIRED STATE       CURRENT STATE          ERROR               PORTS
0z09qjd1iaj8        my_web.1            nginx:1.15.7-alpine   swarm-worker2       Running             Running 13 hours ago                       
9huefun6bfw8         \_ my_web.1        nginx:1.15.7-alpine   swarm-worker1       Shutdown            Shutdown 2 hours ago                       
wi9ab6ubjd6l        my_web.2            nginx:1.15.7-alpine   swarm-worker2       Running             Running 13 hours ago                       
tdyah0y5wflf         \_ my_web.2        nginx:1.15.7-alpine   swarm-worker1       Shutdown            Shutdown 2 hours ago                       
yk7a48parkrn        my_web.3            nginx:1.15.7-alpine   swarm-worker2       Running             Running 13 hours ago                       
6dssuoosib4w         \_ my_web.3        nginx:1.15.7-alpine   swarm-worker1       Shutdown    

删除并添加新的 constraint,设置 node.labels.env==prod,最终所有副本都迁移到了 swarm-worker2

label 还可以跟 global 模式配合起来使用,比如只收集生产环境中容器的日志。

[root@swarm-manager ~]# docker service create \
>        --mode global \
>        --constraint node.labels.env==prod \
>        --name logspout \
>        --mount type=bind,source=/var/run/docker.sock,destination=/var/run/docker.sock \
>        gliderlabs/logspout
[root@swarm-manager ~]# docker service ps logspout 
ID                  NAME                                 IMAGE                        NODE                DESIRED STATE       CURRENT STATE          ERROR               PORTS
6kvcjfddostu        logspout.byxpoc4rgi45jz1d8vi5hgrog   gliderlabs/logspout:latest   swarm-worker2       Running             Running 12 hours ago    

只有 swarm-worker2 节点上才会运行 logspout。

13、 Health Check

Docker 只能从容器启动进程的返回代码判断其状态,而对于容器内部应用的运行情况基本没有了解。

执行 docker run 命令时,通常会根据 Dockerfile 中的 CMD 或 ENTRYPOINT 启动一个进程,这个进程的状态就是 docker ps STATUS 列显示容器的状态。

[root@swarm-worker2 ~]# docker ps -a
CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS                   PORTS               NAMES
7ee058b8f935        gliderlabs/logspout:latest   "/bin/logspout"          2 minutes ago       Up 2 minutes             80/tcp              logspout.byxpoc4rgi45jz1d8vi5hgrog.6kvcjfddostuz9bv6bltx9vl1
b92cb165214c        nginx:1.15.7-alpine          "nginx -g 'daemon of…"   2 hours ago         Up 2 hours               80/tcp              my_web.2.wi9ab6ubjd6lyzc4g05f863w6
866b27b17a11        nginx:1.15.7-alpine          "nginx -g 'daemon of…"   2 hours ago         Up 2 hours               80/tcp              my_web.1.0z09qjd1iaj8aq6ih039od4bi
ae8a52638e94        nginx:1.15.7-alpine          "nginx -g 'daemon of…"   2 hours ago         Up 2 hours               80/tcp              my_web.3.yk7a48parkrnct7r74bp5m24v
97fd40918b21        nginx:1.15-alpine            "nginx -g 'daemon of…"   3 hours ago         Exited (0) 2 hours ago                       my_web.3.7kjpoptdhxgldkpgo8gib29df
9b62f2347853        nginx:1.15-alpine            "nginx -g 'daemon of…"   3 hours ago         Exited (0) 2 hours ago                       my_web.5.wshw5cdo3v43zz4do2zi30hfi

命令显示:

  1. 有的容器正在运行,状态为 UP
  2. 有的容器已经正常停止了,状态是 Exited (0)
  3. 有的则因发生故障停止了,退出代码为非 0,例如 Exited (137)Exited (1) 等。

即使容器状态是 UP,也不能保证应用没有问题。web server 虽然没有崩溃,但如果总是返回 HTTP 500 - Internal Server Error ,对应用来说这就是很严重的故障。

Docker 支持的 Health Check 可以是任何一个单独的命令,Docker 会在容器中执行该命令,如果返回 0,容器被认为是 healthy,如果返回 1,则为 unhealthy

对于提供 HTTP 服务接口的应用,常用的 Health Check 是通过 curl 检查 HTTP 状态码,比如:

curl --fail http://localhost:8080/ || exit 1

如果 curl 命令检测到任何一个错误的 HTTP 状态码,则返回 1,Health Check 失败。

[root@swarm-manager ~]# docker service create --name my_db \
        --health-cmd "curl --fail http://localhost:8091/pools || exit 1" \
        couchbase
x12gbja26pb9unlx2l2of5q7x
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged 
[root@swarm-manager ~]# docker service ps my_db 
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE                ERROR               PORTS
80c0p3cs7y7k        my_db.1             couchbase:latest    swarm-worker1       Running             Running about a minute ago  

--health-cmd Health Check 的命令,还有几个相关的参数:

  1. --timeout 命令超时的时间,默认 30s。
  2. --interval 命令执行的间隔时间,默认 30s。
  3. --retries 命令失败重试的次数,默认为 3,如果 3 次都失败了则会将容器标记为 unhealthy。swarm 会销毁并重建 unhealthy 的副本。

通过 docker ps 可以查看到容器的状态为 healthy

[root@swarm-worker1 ~]# docker ps 
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                   PORTS                                                        NAMES
072cad1caa46        couchbase:latest    "/entrypoint.sh couc…"   2 minutes ago       Up 2 minutes (healthy)   8091-8096/tcp, 11207/tcp, 11210-11211/tcp, 18091-18096/tcp   my_db.1.80c0p3cs7y7kc9836ngxaetat

下面模拟一个 unhealthy 的场景,curl 指向一个不存在的 url。(未测试成功)

docker service create --name my_db \
       --health-cmd "curl --fail http://localhost:8091/non-exist || exit 1" \
       couchbase

容器被标记为 unhealthy,其原因是 curl 连续三次返回 404 错误。

Docker 默认只能通过容器进程的返回码判断容器的状态,Health Check 则能够从业务角度判断应用是否发生故障,是否需要重启。

14、使用 Secret

我们经常要向容器传递敏感信息,最常见的莫过于密码了。比如:

docker run -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql

在启动 MySQL 容器时我们通过环境变量 MYSQL_ROOT_PASSWORD 设置了 MySQL 的管理员密码。不过密码是以明文的形式写在 docker run 命令中,有潜在的安全隐患。

为了解决这个问题,docker swarm 提供了 secret 机制,允许将敏感信息加密后保存到 secret 中,用户可以指定哪些容器可以使用此 secret。

如果使用 secret 启动 MySQL 容器,方法是:

1、在 swarm manager 中创建 secret my_secret_data,将密码保存其中。

[root@swarm-manager ~]# echo "my-secret-pw" | docker secret create my_secret_data -
i0n061n5kpat7q8kmp4q8n2ud

2、启动 MySQL service,并指定使用 secret my_secret_data

[root@swarm-manager ~]# docker service create \
>         --name mysql \
>         --secret source=my_secret_data,target=mysql_root_password \
>         -e MYSQL_ROOT_PASSWORD_FILE="/run/secrets/mysql_root_password" \
>         mysql:latest
t92t9rmooxab9netuxs20itzf
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged 

① source 指定容器使用 secret 后,secret 会被解密并存放到容器的文件系统中,默认位置为 /run/secrets/。--secretsource=my_secret_data,target=mysql_root_password 的作用就是指定使用 secret my_secret_data,然后把器解密后的内容保存到容器 /run/secrets/mysql_root_password 文件中,文件名称 mysql_root_passwordtarget 指定。

② 环境变量 MYSQL_ROOT_PASSWORD_FILE 指定从 /run/secrets/mysql_root_password 中读取并设置 MySQL 的管理员密码。

  1. 问:在第一步创建 secret 时,不也是使用了明文吗?这跟在环境变量中直接指定密码有什么不同呢?

答:在我们的例子中创建 secret 和使用 secret 是分开完成的,其好处是将密码和容器解耦合。secret 可以由专人(比如管理员)创建,而运行容器的用户只需使用 secret 而不需要知道 secret 的内容。也就是说,例子中的这两个步骤可以由不同的人在不同的时间完成。

  1. 问:secret 是以文件的形式 mount 到容器中,容器怎么知道去哪里读取 secret 呢?

答:这需要 image 的支持。如果 image 希望它部署出来的容器能够从 secret 中读取数据,那么此 image 就应该提供一种方式,让用户能够指定 secret 的位置。最常用的方法就是通过环境变量,Docker 的很多官方 image 都是采用这种方式。比如 MySQL 镜像同时提供了 MYSQL_ROOT_PASSWORDMYSQL_ROOT_PASSWORD_FILE 两个环境变量。用户可以用 MYSQL_ROOT_PASSWORD 显示地设置管理员密码,也可以通过 MYSQL_ROOT_PASSWORD_FILE 指定 secret 路径。

Secret 的使用场景

secret 可用于管理:

  1. 用户名和密码。
  2. TLS 证书。
  3. SSH 秘钥。
  4. 其他小于 500 KB 的数据。

secret 只能在 swarm service 中使用。普通容器想使用 secret,可以将其包装成副本数为 1 的 service。

数据中心有三套 swarm 环境,分别用于开发、测试和生产。对于同一个应用,在不同的环境中使用不同的用户名密码。我们可以在三个环境中分别创建 secret,不过使用相同的名字,比如 usernamepassword。应用部署时只需要指定 secret 名字,这样我们就可以用同一套脚本在不同的环境中部署应用了。

除了敏感数据,secret 当然也可以用于非敏感数据,比如配置文件。不过目前新版本的 Docker 提供了 config 子命令来管理不需要加密的数据。config 与 secret 命令的使用方法完全一致。

Secret 的安全性

当在 swarm 中创建 secret 时,Docker 通过 TLS 连接将加密后的 secret 发送给所以的 manager 节点。

secret 创建后,即使是 swarm manager 也无法查看 secret 的明文数据,只能通过 docker secret inspect 查看 secret 的一般信息。

[root@swarm-manager ~]# docker secret inspect my_secret_data 
[
    {
        "ID": "i0n061n5kpat7q8kmp4q8n2ud",
        "Version": {
            "Index": 2820
        },
        "CreatedAt": "2019-01-02T02:11:42.350879188Z",
        "UpdatedAt": "2019-01-02T02:11:42.350879188Z",
        "Spec": {
            "Name": "my_secret_data",
            "Labels": {}
        }
    }
]

只有当 secret 被指定的 service 使用是,Docker 才会将解密后的 secret 以文件的形式 mount 到容器中,默认的路径为/run/secrets/<secret_name>。例如在前面 MySQL 的例子中,我们可以在容器中查看 secret。

[root@swarm-manager ~]# docker service ps mysql 
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE           ERROR               PORTS
fd9ggspslrvv        mysql.1             mysql:latest        swarm-worker1       Running             Running 5 minutes ago    
[root@swarm-worker1 ~]# docker exec -it mysql.1.fd9ggspslrvv3et2s1lhj2drb cat /run/secrets/mysql_root_password
my-secret-pw

当容器停止运行,Docker 会 unmount secret,并从节点上清除。

举例

创建一个 MySQL service,将密码保存到 secret 中。我们还会创建一个 WordPress service,它将使用 secret 连接 MySQL。这个例子将展示如何用 secret 避免在 image 中存放敏感信息,或者在命令行中直接传递敏感数据。

实验步骤如下:

1\创建 secret

创建 secret 存放 MySQL 的管理员密码。

[root@swarm-manager ~]# openssl rand -base64 20 | docker secret create mysql_root_password -
weiz3fof9qe56sjtztvixvjco

注意 ag7injh6juonwl09lq8st36o8 是新创建的 service 的 ID,而非 service 的内容。

上面这种方式是从标准输入读取 secret 的内容,也可以指定从文件中读取,例如:

openssl rand -base64 20 > password.txt
docker secret create my_password ./password.txt

一般情况下,应用不会直接用 root 密码访问 MySQL。我们会创建一个单独的用户 workpress,密码存放到 secret mysql_password中。

[root@swarm-manager ~]# openssl rand -base64 20 | docker secret create mysql_password -
zv55ejrqimhflaajx5f6oez8e
[root@swarm-manager ~]# docker secret ls
ID                          NAME                  DRIVER              CREATED             UPDATED
zv55ejrqimhflaajx5f6oez8e   mysql_password                            45 seconds ago      45 seconds ago
weiz3fof9qe56sjtztvixvjco   mysql_root_password                       2 minutes ago       2 minutes ago
创建自定义的 overlay 网络

MySQL 通过 overlay 网络 mysql_private 与 WordPress 通信,不需要将 MySQL service 暴露给外部网络和其他容器。

[root@swarm-manager ~]# docker network create -d overlay mysql_private
45j2p7ley2b6s8buyxohi9a27
创建 MySQL service

命令如下:

[root@swarm-manager ~]# docker service create \
>      --name mysql \
>      --network mysql_private \
>      --secret source=mysql_root_password,target=mysql_root_password \
>      --secret source=mysql_password,target=mysql_password \
>      -e MYSQL_ROOT_PASSWORD_FILE="/run/secrets/mysql_root_password" \
>      -e MYSQL_PASSWORD_FILE="/run/secrets/mysql_password" \
>      -e MYSQL_USER="wordpress" \
>      -e MYSQL_DATABASE="wordpress" \
>      mysql:latest
8xg5dmt7rn6ecglv1frzcd9h0
overall progress: 1 out of 1 tasks 
1/1: running   
verify: Service converged 

[root@swarm-manager ~]# docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
8xg5dmt7rn6e        mysql               replicated          1/1                 mysql:latest        
[root@swarm-manager ~]# docker service ps mysql
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE                ERROR               PORTS
evv84yno073p        mysql.1             mysql:latest        swarm-worker1       Running             Running about a minute ago  

MYSQL_DATABASE 指明创建数据库 wordpress

MYSQL_USERMYSQL_PASSWORD_FILE 指明创建数据库用户 workpress,密码从 secret mysql_password 中读取。

有关 mysql 镜像环境变量更详细的使用方法可参考 https://hub.docker.com/_/mysql/

创建 WordPress service

MySQL service 已就绪,现在创建 WordPress service。命令如下:

[root@swarm-manager ~]# docker service create \
>      --name wordpress \
>      --network mysql_private \
>      --publish 30000:80 \
>      --secret source=mysql_password,target=wp_db_password \
>      -e WORDPRESS_DB_HOST="mysql:3306" \
>      -e WORDPRESS_DB_NAME="wordpress" \
>      -e WORDPRESS_DB_USER="wordpress" \
>      -e WORDPRESS_DB_PASSWORD_FILE="/run/secrets/wp_db_password" \
>      wordpress:latest
dtunutpigfh8a07xsk33fibe6
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged 

WORDPRESS_DB_HOST 指明 MySQL service 地址 mysql:3306,这里用到了 DNS。

WORDPRESS_DB_NAME 指明 WordPress 的数据库为 wordpress,与前面 MYSQL_DATABASE 一致。

WORDPRESS_DB_USER 指明连接 WordPress 数据库的用户为 wordpress,与前面 MYSQL_USER 一致。

WORDPRESS_DB_PASSWORD_FILE 指明数据库的用户 wordpress 的密码,从 secret mysql_password 中获取。

有关 wordpress 镜像环境变量更详细的使用方法可参考 https://hub.docker.com/_/wordpress/

验证 WordPress

访问 http://[swarm_master_ip]:30000/

能正常显示初始化界面,表明 WordPress 已经连接到 MySQL,部署成功。

15、stack

回忆一下前面部署 WordPress 应用的过程:

  1. 首先创建 secret。
  2. 然后创建 MySQL service,这是 WordPress 依赖的服务。
  3. 最后创建 WordPress service。

也就是说,这个应用包含了两个 service:MySQL 和 WordPress,它们之间有明确的依赖关系,必须先启动 MySQL。

为了保证这个依赖关系,我们控制了 docker secretdocker service 命令的执行顺序,只不过这个过程是手工完成的。

稍微复杂一点的是第三步,通过 if 判断 MySQL service 是否运行,如果是,则运行 WordPress service,否则通过 while 继续等待,直到 MySQL 运行。

这个脚本大体上能够工作,实现了自动化,但有两个缺点:

  1. 目前只有两个 service,还比较简单。现在的应用通常都包含多个 service,特别是采用 microservices 架构的应用,几十个 service 是很正常的。用 shell 脚本启动和管理如此多的 service 将是一件非常有挑战的任务。
  2. whileif 维护 service 之间的依赖关系也是很有挑战的,容易出错。而且如何判断 service 正常运行也不是件容易的事,脚本中只简单检查了 service 是否存在,并没有考虑 service 的实际运行状态。

我们希望有一种更高效和可靠的方法来部署基于 service 的应用,这就是 stack。

stack 包含一系列 service,这些 service 组成了应用。stack 通过一个 YAML 文件定义每个 service,并描述 service 使用的资源和各种依赖。

WordPress 的 stack 版本

如果将前面 WordPress 用 stack 来定义,YAML 文件可以是这样:

[root@swarm-manager ~]#  vim wordpress.yml


version: '3.1'

services:
  db:
    image: mysql:latest
    volumes:
      - db_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD_FILE: /run/secrets/ad_password
    secrets:
      - db_root_password
      - db_password
    
    wordpress:
      depends_on:
        - db
      image: wordpress:latest
      port:
        - "8000:80"
      environment:
        WORDPRESS_DB_HOST: db:3306
        WORDPRESS_DB_USER: wordpress
        WORDPRESS_DB_PASSWORD_FILE: /run/secrets/db_password
      secrets:
        - db_password


secrets:
  db_password:
    file: db_password.txt
  db_root_password:
    file: db_root_password.txt

volumes:
    db_data:

YAML 是一种阅读性很强的文本格式,上面这个 stack 中定义了三种资源:service、secret 和 volume。

services 定义了两个 service:dbwordpress

secrets 定义了两个 secret:db_passworddb_root_password,在 service dbwordpress 的定义中引用了这两个 secret。

volumes 定义了一个 volume:db_data,service db 使用了此 volume。

wordpress 通过 depends_on 指定自己依赖 db 这个 service。Docker 会保证当 db 正常运行后再启动 wordpress

可以在 YAML 中定义的元素远远不止这里看到的这几个,完整列表和使用方法可参考文档 https://docs.docker.com/compose/compose-file/

定义好了 stack YAML 文件,就可以通过 docker stack deploy 命令部署应用。

[root@swarm-manager ~]# vim db_password.txt
[root@swarm-manager ~]# vim db_root_password.txt
[root@swarm-manager ~]# docker stack deploy -c wordpress.yml wpstack
Creating secret wpstack_db_root_password
Creating secret wpstack_db_password
Creating service wpstack_db
Creating service wpstack_wordpress
[root@swarm-manager ~]# docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
b2tblr32vu8g        wpstack_db          replicated          1/1                 mysql:latest        
wiwtgohw332z        wpstack_wordpress   replicated          1/1                 wordpress:latest    *:8000->80/tcp

部署完成后可以通过相关命令查看各种资源的状态。

[root@swarm-manager ~]# docker stack services wpstack 
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
b2tblr32vu8g        wpstack_db          replicated          1/1                 mysql:latest        
wiwtgohw332z        wpstack_wordpress   replicated          1/1                 wordpress:latest    *:8000->80/tcp
[root@swarm-manager ~]# docker stack ps wpstack 
ID                  NAME                      IMAGE               NODE                DESIRED STATE       CURRENT STATE                ERROR                       PORTS
e98ttrud0oga        wpstack_wordpress.1       wordpress:latest    swarm-worker2       Running             Running 7 days ago                                       
3ko8t5y3ja9e         \_ wpstack_wordpress.1   wordpress:latest    swarm-worker2       Shutdown            Failed 7 days ago            "task: non-zero exit (1)"   
qmsyq2ahutxp        wpstack_db.1              mysql:latest        swarm-worker1       Running             Running about a minute ago                               
l0fs3ijwqm5b         \_ wpstack_db.1          mysql:latest        swarm-worker1       Shutdown            Failed about a minute ago    "task: non-zero exit (1)"   
[root@swarm-manager ~]# docker secret ls
ID                          NAME                       DRIVER              CREATED             UPDATED
zv55ejrqimhflaajx5f6oez8e   mysql_password                                 About an hour ago   About an hour ago
weiz3fof9qe56sjtztvixvjco   mysql_root_password                            About an hour ago   About an hour ago
ssqn3cndzy9vh5mw8285td2gq   wpstack_db_password                            9 minutes ago       9 minutes ago
fdudslxllj30van0yyyde4e7o   wpstack_db_root_password                       9 minutes ago       9 minutes ago

如果想更新 stack 的某些属性,直接修改 YAML 文件,然后重新部署。比如将 WordPress 的端口由 8000 改为 8888

    ports:
      - "8888:80"

再次执行 docker stack deploy 命令。

[root@swarm-manager ~]# docker stack deploy -c wordpress.yml wpstack
Updating service wpstack_db (id: b2tblr32vu8gzqxu33iv0xct1)
Updating service wpstack_wordpress (id: wiwtgohw332z20o2snocxukh4)
[root@swarm-manager ~]# docker stack ps wpstack
ID                  NAME                      IMAGE               NODE                DESIRED STATE       CURRENT STATE             ERROR                       PORTS
jp91ygjocy26        wpstack_wordpress.1       wordpress:latest    swarm-worker2       Running             Running 7 days ago                                    
q4zoh1z2ebv5         \_ wpstack_wordpress.1   wordpress:latest    swarm-worker1       Shutdown            Shutdown 22 seconds ago                               
ixmhlli95l2e         \_ wpstack_wordpress.1   wordpress:latest    swarm-worker2       Shutdown            Failed 7 days ago         "task: non-zero exit (1)"   
yn0n3fhnawvr         \_ wpstack_wordpress.1   wordpress:latest    swarm-worker2       Shutdown            Failed 7 days ago         "task: non-zero exit (1)"   
2w9dhvz3ifzx         \_ wpstack_wordpress.1   wordpress:latest    swarm-worker2       Shutdown            Failed 7 days ago         "task: non-zero exit (1)"   
qmsyq2ahutxp        wpstack_db.1              mysql:latest        swarm-worker1       Running             Running 3 minutes ago                                 
l0fs3ijwqm5b         \_ wpstack_db.1          mysql:latest        swarm-worker1       Shutdown            Failed 3 minutes ago      "task: non-zero exit (1)"   

为了更新端口,swarm 启动了一个新的 wpstack_wordpress容器,之前的容器已经被 shutdown。

要删除 stack 也很简单:

[root@swarm-manager ~]# docker stack rm wpstack
Removing service wpstack_db
Removing service wpstack_wordpress
Removing secret wpstack_db_root_password
Removing secret wpstack_db_password
Removing network wpstack_default

docker stack rm 会将 stack 相关的所以资源清除干净。

stack 将应用所包含的 service,依赖的 secret、voluem 等资源,以及它们之间的关系定义在一个 YAML 文件中。相比较手工执行命令或是脚本,stack 有明显的优势。

  1. YAML 描述的是 What,是 stack 最终要达到的状态。
    比如 service 有几个副本?使用哪个 image?映射的端口是什么?而脚本则是描述如何执行命令来达到这个状态,也就是 How。显而易见,What 更直观,也更容易理解。至于如何将 What 翻译成 How,这就是 Docker swarm 的任务了,用户只需要告诉 Docker 想达到什么效果。
  2. 重复部署应用变得非常容易。
    部署应用所需要的一切信息都已经写在 YAML 中,要部署应用只需一条命令 docker stack deploy。stack 的这种自包含特性使得在不同的 Docker 环境中部署应用变得极其简单。在开发、测试和生成环境中部署可以完全采用同一份 YAML,而且每次部署的结果都是一致的。
  3. 可以像管理代码一样管理部署。
    YAML 本质上将应用的部署代码化了,任何对应用部署环境的修改都可以通过修改 YAML 来实现。可以将 YAML 纳入到版本控制系统中进行管理,任何对 YAML 的修改都会被记录和跟踪,甚至可以像评审代码一样对 YAML 执行 code review。应用部署不再是一个黑盒子,也不再是经验丰富的工程师专有的技能,所以的细节都在 YAML 中,清晰可见。

转载于:https://www.cnblogs.com/wlbl/p/10207749.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值