目录
十、 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:manager 和 worker。
为了向 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-manager | 192.168.2.110 | CentOS 7.5 | docker-ce-18.09.0-3.el7.x86_64 |
swarm-worker1 | 192.168.2.120 | CentOS 7.5 | docker-ce-18.09.0-3.el7.x86_64 |
swarm-worker2 | 192.168.2.130 | CentOS 7.5 | docker-ce-18.09.0-3.el7.x86_64 |
在 swarm-manager 上执行如下命令创建 swarm。
[root@manager ~]# docker swarm init --advertise-addr 192.168.2.110Swarm 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:2377To 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 lsID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSIONrxoqe9tvd0rfd4zxc4uoniu05 * 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:2377This node joined a swarm as a worker.
[root@swarm-manager ~]# docker node lsID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSIONrxoqe9tvd0rfd4zxc4uoniu05 * swarm-manager Ready Active Leader 18.09.0jfpu6n3qt4gqnoqs8lv36o5fn swarm-worker1 Ready Active 18.09.0byxpoc4rgi45jz1d8vi5hgrog swarm-worker2 Ready Active 18.09.0
如果当时没有记录下 docker swarm init
提示的添加 worker 的完整命令,可以通过 docker swarm join-token worker
查看。
[root@manager ~]# docker swarm join-token workerTo 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-alpinekqhkkqt0i6r6u40qknuyb76jaoverall progress: 1 out of 1 tasks 1/1: running [==================================================>] verify: Service converged
通过 docker service ls
可以查看当前 swarm 中的 service。
[root@swarm-manager ~]# docker service lsID NAME MODE REPLICAS IMAGE PORTSkqhkkqt0i6r6 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_webID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTSkokoy6h24gwp 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 psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESa0f7ea32d403 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=5my_web scaled to 5overall 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=5my_web scaled to 5overall 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 lsID NAME MODE REPLICAS IMAGE PORTSkqhkkqt0i6r6 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 PORTSkokoy6h24gwp 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 lsID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSIONrxoqe9tvd0rfd4zxc4uoniu05 * swarm-manager Ready Drain Leader 18.09.0jfpu6n3qt4gqnoqs8lv36o5fn swarm-worker1 Ready Active 18.09.0byxpoc4rgi45jz1d8vi5hgrog swarm-worker2 Ready Active 18.09.0[root@swarm-manager ~]# docker service ps web_serverID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTSqdq9gcq6u01d 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=3my_web scaled to 3overall progress: 3 out of 3 tasks 1/3: running [==================================================>] 2/3: running [==================================================>] 3/3: running [==================================================>] verify: Service converged [root@swarm-manager ~]# docker service lsID NAME MODE REPLICAS IMAGE PORTSkqhkkqt0i6r6 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 PORTSkokoy6h24gwp 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.4
和 web_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 lsID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSIONrxoqe9tvd0rfd4zxc4uoniu05 * swarm-manager Ready Drain Leader 18.09.0jfpu6n3qt4gqnoqs8lv36o5fn swarm-worker1 Down Active 18.09.0byxpoc4rgi45jz1d8vi5hgrog 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 PORTSw285dbb0spcf 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.1
和 web_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-alpineqnr86tsd8ht8r7d1bfrbhxx5ioverall progress: 2 out of 2 tasks 1/2: running [==================================================>] 2/2: running [==================================================>] verify: Service converged [root@swarm-manager ~]# docker service lsID NAME MODE REPLICAS IMAGE PORTSqnr86tsd8ht8 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 PORTSak7uuvnjf8mx 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 psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES68502d64ca5f 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 rdefault 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 -IHTTP/1.1 200 OKServer: nginx/1.14.2Date: Wed, 26 Dec 2018 02:16:48 GMTContent-Type: text/htmlContent-Length: 612Last-Modified: Fri, 21 Dec 2018 01:22:26 GMTConnection: keep-aliveETag: "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_serveroverall 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:8080HTTP/1.1 200 OKServer: nginx/1.14.2Date: Wed, 26 Dec 2018 02:18:17 GMTContent-Type: text/htmlContent-Length: 612Last-Modified: Fri, 21 Dec 2018 01:22:26 GMTConnection: keep-aliveETag: "5c1c4052-264"Accept-Ranges: bytes[root@swarm-manager ~]# curl -I 192.168.2.120:8080HTTP/1.1 200 OKServer: nginx/1.14.2Date: Wed, 26 Dec 2018 02:18:20 GMTContent-Type: text/htmlContent-Length: 612Last-Modified: Fri, 21 Dec 2018 01:22:26 GMTConnection: keep-aliveETag: "5c1c4052-264"Accept-Ranges: bytes[root@swarm-manager ~]# curl -I 192.168.2.130:8080HTTP/1.1 200 OKServer: nginx/1.14.2Date: Wed, 26 Dec 2018 02:18:22 GMTContent-Type: text/htmlContent-Length: 612Last-Modified: Fri, 21 Dec 2018 01:22:26 GMTConnection: keep-aliveETag: "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 PORTSwyl8a3mhheyr 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 psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES9a28cef94e92 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 rdefault 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 网络
实际上:
- eth0 连接的是一个 overlay 类型的网络,名字为
ingress
,其作用是让运行在不同主机上的容器可以相互通信。 - eth1 连接的是一个 bridge 类型的网络,名字为
docker_gwbridge
,其作用是让容器能够访问到外网。
[root@swarm-worker1 ~]# docker network lsNETWORK ID NAME DRIVER SCOPEd174ec92bfb9 bridge bridge localf57703c9517c docker_gwbridge bridge local17bf4830406d harbor_harbor bridge local266cc54ae977 host host localt4xnhmp6y5nr ingress overlay swarma5d3ec376305 mybr0 bridge local436b29f1c660 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 就要提供一种机制,能够:
- 让 service 通过简单的方法访问到其他 service。
- 当 service 副本的 IP 发生变化时,不会影响访问该 service 的其他 service。
- 当 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_netrdozkrfo8mg9y9t3m8swiyv8q[root@swarm-manager ~]# docker network lsNETWORK ID NAME DRIVER SCOPE4508165c1437 bridge bridge locale56359b1d013 docker_gwbridge bridge local266cc54ae977 host host localt4xnhmp6y5nr ingress overlay swarmrdozkrfo8mg9 myapp_net overlay swarm436b29f1c660 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-alpinehlv54albpldu99ox7a5bnpschoverall 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 PORTSv6luevjpk1of 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 10000000pa8qw96mx4ru0bcb6krz21shioverall 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 PORTSuuf4yyfra9sd 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_webPING my_web (10.0.0.2): 56 data bytes64 bytes from 10.0.0.2: seq=0 ttl=64 time=0.141 ms64 bytes from 10.0.0.2: seq=1 ttl=64 time=0.080 ms64 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 lossround-trip min/avg/max = 0.080/0.100/0.141 ms
可以看到 my_web
的 IP 为 10.0.0.2
,这是哪个副本的 IP 呢?
其实哪个副本的 IP 都不是。10.0.0.2
是 my_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 PORTSv6luevjpk1of 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 将按照如下步骤执行滚动更新:
- 停止第一个副本。
- 调度任务,选择 worker node。
- 在 worker 上用新的镜像启动副本。
- 如果副本(容器)运行成功,继续更新下一个副本;如果失败,暂停整个更新过程。
docker service ps
查看更新结果。
[root@swarm-manager ~]# docker service update --image nginx:1.15-alpine my_webmy_weboverall 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 PORTSws8ym7uxht1g 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_weboverall 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: hlv54albpldu99ox7a5bnpschName: my_webService Mode: Replicated Replicas: 6Placement:UpdateConfig: Parallelism: 2 Delay: 1m30s On failure: pause Monitoring Period: 5s Max failure ratio: 0 Update order: stop-firstRollbackConfig: Parallelism: 1 On failure: pause Monitoring Period: 5s Max failure ratio: 0 Rollback order: stop-firstContainerSpec: Image: nginx:1.15-alpine@sha256:2e497c294e3ba84aaeab7a0fbb1027819cd1f5f5892ed3c4a82b8b05010090da Init: falseResources: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 PORTSws8ym7uxht1g 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_weboverall 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 PORTSkc2y0tedle3u 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_webmy_webrollback: 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 PORTSh3ajwiltt5si 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 还提供了一个 global
mode,其作用是强制在每个 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/logspoutsu2vyv5n6vaazpj8ds3cvl14yoverall progress: 2 out of 2 tasks byxpoc4rgi45: running jfpu6n3qt4gq: running verify: Service converged [root@swarm-manager ~]# docker service ps logspoutID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTSz1633gpkqvh7 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 的位置
逻辑分两步:
- 为每个 node 定义 label。
- 设置 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 PORTS9huefun6bfw8 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: vgcmd2r8pznw6koqwf4l896hvName: my_webService Mode: Replicated Replicas: 3Placement: Constraints: [node.labels.env==test]
更新 service,将其迁移到生产环境:
[root@swarm-manager ~]# docker service update --constraint-add node.labels.env==prod my_webmy_weboverall 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 PORTS0z09qjd1iaj8 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 PORTS6kvcjfddostu 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 -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES7ee058b8f935 gliderlabs/logspout:latest "/bin/logspout" 2 minutes ago Up 2 minutes 80/tcp logspout.byxpoc4rgi45jz1d8vi5hgrog.6kvcjfddostuz9bv6bltx9vl1b92cb165214c nginx:1.15.7-alpine "nginx -g 'daemon of…" 2 hours ago Up 2 hours 80/tcp my_web.2.wi9ab6ubjd6lyzc4g05f863w6866b27b17a11 nginx:1.15.7-alpine "nginx -g 'daemon of…" 2 hours ago Up 2 hours 80/tcp my_web.1.0z09qjd1iaj8aq6ih039od4biae8a52638e94 nginx:1.15.7-alpine "nginx -g 'daemon of…" 2 hours ago Up 2 hours 80/tcp my_web.3.yk7a48parkrnct7r74bp5m24v97fd40918b21 nginx:1.15-alpine "nginx -g 'daemon of…" 3 hours ago Exited (0) 2 hours ago my_web.3.7kjpoptdhxgldkpgo8gib29df9b62f2347853 nginx:1.15-alpine "nginx -g 'daemon of…" 3 hours ago Exited (0) 2 hours ago my_web.5.wshw5cdo3v43zz4do2zi30hfi
命令显示:
- 有的容器正在运行,状态为
UP
。 - 有的容器已经正常停止了,状态是
Exited (0)
。 - 有的则因发生故障停止了,退出代码为非 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" \ couchbasex12gbja26pb9unlx2l2of5q7xoverall 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 PORTS80c0p3cs7y7k my_db.1 couchbase:latest swarm-worker1 Running Running about a minute ago
--health-cmd
Health Check 的命令,还有几个相关的参数:
--timeout
命令超时的时间,默认 30s。--interval
命令执行的间隔时间,默认 30s。--retries
命令失败重试的次数,默认为 3,如果 3 次都失败了则会将容器标记为unhealthy
。swarm 会销毁并重建unhealthy
的副本。
通过 docker ps
可以查看到容器的状态为 healthy
:
[root@swarm-worker1 ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES072cad1caa46 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:latestt92t9rmooxab9netuxs20itzfoverall 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_password
由 target
指定。
② 环境变量 MYSQL_ROOT_PASSWORD_FILE
指定从 /run/secrets/mysql_root_password
中读取并设置 MySQL 的管理员密码。
- 问:在第一步创建 secret 时,不也是使用了明文吗?这跟在环境变量中直接指定密码有什么不同呢?
答:在我们的例子中创建 secret 和使用 secret 是分开完成的,其好处是将密码和容器解耦合。secret 可以由专人(比如管理员)创建,而运行容器的用户只需使用 secret 而不需要知道 secret 的内容。也就是说,例子中的这两个步骤可以由不同的人在不同的时间完成。
- 问:secret 是以文件的形式 mount 到容器中,容器怎么知道去哪里读取 secret 呢?
答:这需要 image 的支持。如果 image 希望它部署出来的容器能够从 secret 中读取数据,那么此 image 就应该提供一种方式,让用户能够指定 secret 的位置。最常用的方法就是通过环境变量,Docker 的很多官方 image 都是采用这种方式。比如 MySQL 镜像同时提供了 MYSQL_ROOT_PASSWORD
和 MYSQL_ROOT_PASSWORD_FILE
两个环境变量。用户可以用 MYSQL_ROOT_PASSWORD
显示地设置管理员密码,也可以通过 MYSQL_ROOT_PASSWORD_FILE
指定 secret 路径。
Secret 的使用场景
secret 可用于管理:
- 用户名和密码。
- TLS 证书。
- SSH 秘钥。
- 其他小于 500 KB 的数据。
secret 只能在 swarm service 中使用。普通容器想使用 secret,可以将其包装成副本数为 1 的 service。
数据中心有三套 swarm 环境,分别用于开发、测试和生产。对于同一个应用,在不同的环境中使用不同的用户名密码。我们可以在三个环境中分别创建 secret,不过使用相同的名字,比如 username
和 password
。应用部署时只需要指定 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 PORTSfd9ggspslrvv 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_passwordmy-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.txtdocker 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 lsID NAME DRIVER CREATED UPDATEDzv55ejrqimhflaajx5f6oez8e mysql_password 45 seconds ago 45 seconds agoweiz3fof9qe56sjtztvixvjco 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_private45j2p7ley2b6s8buyxohi9a27
创建 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:latest8xg5dmt7rn6ecglv1frzcd9h0overall progress: 1 out of 1 tasks 1/1: running verify: Service converged [root@swarm-manager ~]# docker service lsID NAME MODE REPLICAS IMAGE PORTS8xg5dmt7rn6e mysql replicated 1/1 mysql:latest [root@swarm-manager ~]# docker service ps mysqlID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTSevv84yno073p mysql.1 mysql:latest swarm-worker1 Running Running about a minute ago
MYSQL_DATABASE
指明创建数据库 wordpress
。
MYSQL_USER
和 MYSQL_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:latestdtunutpigfh8a07xsk33fibe6overall 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 应用的过程:
- 首先创建 secret。
- 然后创建 MySQL service,这是 WordPress 依赖的服务。
- 最后创建 WordPress service。
也就是说,这个应用包含了两个 service:MySQL 和 WordPress,它们之间有明确的依赖关系,必须先启动 MySQL。
为了保证这个依赖关系,我们控制了 docker secret
和 docker service
命令的执行顺序,只不过这个过程是手工完成的。
稍微复杂一点的是第三步,通过 if
判断 MySQL service 是否运行,如果是,则运行 WordPress service,否则通过 while
继续等待,直到 MySQL 运行。
这个脚本大体上能够工作,实现了自动化,但有两个缺点:
- 目前只有两个 service,还比较简单。现在的应用通常都包含多个 service,特别是采用 microservices 架构的应用,几十个 service 是很正常的。用 shell 脚本启动和管理如此多的 service 将是一件非常有挑战的任务。
- 用
while
和if
维护 service 之间的依赖关系也是很有挑战的,容易出错。而且如何判断 service 正常运行也不是件容易的事,脚本中只简单检查了 service 是否存在,并没有考虑 service 的实际运行状态。
我们希望有一种更高效和可靠的方法来部署基于 service 的应用,这就是 stack。
stack 包含一系列 service,这些 service 组成了应用。stack 通过一个 YAML 文件定义每个 service,并描述 service 使用的资源和各种依赖。
WordPress 的 stack 版本
如果将前面 WordPress 用 stack 来定义,YAML 文件可以是这样:
[root@swarm-manager ~]# vim wordpress.ymlversion: '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_passwordsecrets: db_password: file: db_password.txt db_root_password: file: db_root_password.txtvolumes: db_data:
YAML 是一种阅读性很强的文本格式,上面这个 stack 中定义了三种资源:service、secret 和 volume。
① services
定义了两个 service:db
和 wordpress
。
② secrets
定义了两个 secret:db_password
和 db_root_password
,在 service db
和 wordpress
的定义中引用了这两个 secret。
③ volumes
定义了一个 volume:db_data
,service db
使用了此 volume。
④ wordpress
通过 depends_on
指定自己依赖 db
这个 service。Docker 会保证当 db
正常运行后再启动 wordpress
。
可以在 YAML 中定义的元素远远不止这里看到的这几个,完整列表和使用方法可参考文档 https://docs.docker.com/compose/compose-file/
,就可以通过 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 wpstackCreating secret wpstack_db_root_passwordCreating secret wpstack_db_passwordCreating service wpstack_dbCreating service wpstack_wordpress[root@swarm-manager ~]# docker service lsID NAME MODE REPLICAS IMAGE PORTSb2tblr32vu8g 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 PORTSb2tblr32vu8g 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 PORTSe98ttrud0oga 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 lsID NAME DRIVER CREATED UPDATEDzv55ejrqimhflaajx5f6oez8e mysql_password About an hour ago About an hour agoweiz3fof9qe56sjtztvixvjco mysql_root_password About an hour ago About an hour agossqn3cndzy9vh5mw8285td2gq wpstack_db_password 9 minutes ago 9 minutes agofdudslxllj30van0yyyde4e7o 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 wpstackUpdating service wpstack_db (id: b2tblr32vu8gzqxu33iv0xct1)Updating service wpstack_wordpress (id: wiwtgohw332z20o2snocxukh4)
[root@swarm-manager ~]# docker stack ps wpstackID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTSjp91ygjocy26 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 wpstackRemoving service wpstack_dbRemoving service wpstack_wordpressRemoving secret wpstack_db_root_passwordRemoving secret wpstack_db_passwordRemoving network wpstack_default
docker stack rm
会将 stack 相关的所以资源清除干净。
stack 将应用所包含的 service,依赖的 secret、voluem 等资源,以及它们之间的关系定义在一个 YAML 文件中。相比较手工执行命令或是脚本,stack 有明显的优势。
- YAML 描述的是
What
,是 stack 最终要达到的状态。 比如 service 有几个副本?使用哪个 image?映射的端口是什么?而脚本则是描述如何执行命令来达到这个状态,也就是How
。显而易见,What
更直观,也更容易理解。至于如何将What
翻译成How
,这就是 Docker swarm 的任务了,用户只需要告诉 Docker 想达到什么效果。 - 重复部署应用变得非常容易。 部署应用所需要的一切信息都已经写在 YAML 中,要部署应用只需一条命令
docker stack deploy
。stack 的这种自包含特性使得在不同的 Docker 环境中部署应用变得极其简单。在开发、测试和生成环境中部署可以完全采用同一份 YAML,而且每次部署的结果都是一致的。 - 可以像管理代码一样管理部署。 YAML 本质上将应用的部署代码化了,任何对应用部署环境的修改都可以通过修改 YAML 来实现。可以将 YAML 纳入到版本控制系统中进行管理,任何对 YAML 的修改都会被记录和跟踪,甚至可以像评审代码一样对 YAML 执行 code review。应用部署不再是一个黑盒子,也不再是经验丰富的工程师专有的技能,所以的细节都在 YAML 中,清晰可见。