即使有了Docker Compose,项目的部署仍然存在问题,因为Docker Compose只能把项目所有的容器部署在同一台机器上,这在生产环境下是不现实的。
Docker Compose一般只适用于开发环境,而对于生产环境下的项目部署,我们需要用到Docker Swarm。
Docker Swarm介绍
Docker Swarm是Docker官方提供的一套容器编排系统,它将一组Docker主机虚拟成一个单独的虚拟Docker主机。
架构如下:
- swarm节点:
swarm是一系列节点的集合,而节点可以是一台裸机或者一台虚拟机。一个节点能扮演一个或者两个角色,manager或者worker。
manager Docker Swarm集群需要至少一个manager节点,节点之间使用Raft consensus protocol进行协同工作。 通常,第一个启用docker swarm的节点将成为leader,后来加入的都是follower。当前的leader如果挂掉, 剩余的节点将重新选举出一个新的leader。每一个manager都有一个完整的当前集群状态的副本,可以保证manager的高可用。 worker worker节点是运行实际应用服务的容器所在的地方。理论上,一个manager节点也能同时成为worker节点,但在生产环境中, 我们不建议这样做。worker节点之间,通过control plane进行通信,这种通信使用gossip协议,并且是异步的。
- task、service、stack:
多个tasks组成一个service,多个services组成一个stack。
task 在Docker Swarm中,task是一个部署的最小单元,task与容器是一对一的关系。 service swarm service是一个抽象的概念,它只是一个对运行在swarm集群上的应用服务,所期望状态的描述。 它就像一个描述了下面物品的清单列表一样: 服务名称 使用哪个镜像来创建容器 要运行多少个副本 服务的容器要连接到哪个网络上 需要映射哪些端口 stack stack是描述一系列相关services的集合,可以通过在一个YAML文件中来定义一个stack,类似于docker-compose。
- 多主机网络:
对于单主机网络,所有的容器都运行在一个docker主机上,他们之间的通信一般使用本地的bridge network即可。
而对于swarm集群,针对的是一组docker主机,需要使用docker的overlay network。
Docker Stack
前面有讲过,docker run
仅适用于单机启动单个容器;docker compose
仅适用于单机启动多个容器;docker service
仅适用于集群启动单个service的容器。
即使在生产环境下使用docker service
来部署项目,也是无法接受的。当项目足够复杂时,会有更多关联的service需要创建,通过docker service
手动创建多个service,效率也是十分低下的。
因此,Docker Stack应运而生,Docker Stack适用于集群启动多个service的容器,在集群中可以类似Docker Compose那样通过YAML文件直接部署项目。
只不过Docker Stack忽略了build
指令,不能像Docker Compose那样在docker-compose.yml
中引用Dockerfile构建新镜像,它需要镜像是预先已经构建好的。所以Docker Compose更适合于开发场景,而Docker Stack更适用于生产环境。
- 对应关系:
单机 | 集群 |
---|---|
docker run | docker service create |
docker-compose up | docker stack deploy |
- 使用
docker stack
命令:
使用docker stack不再需要安装docker-compose,直接使用docker stack deploy
命令。
docker stack deploy project_name --compose-file docker-compose.yml
当然,后面跟着的YAML文件名不一定是docker-compose.yml
,可以自定义。
-
deploy
指令:
指定与service的部署和运行相关的配置。只在使用docker stack deploy
部署时生效,并且会被docker-compose up
和docker-compose run
忽略。
version: "3.7"services: redis: image: redis:alpine deploy: replicas: 6 update_config: parallelism: 2 delay: 10s restart_policy: condition: on-failure
-
endpoint_mode
指令:
为连接到集群的外部客户端指定服务发现方法。
endpoint_mode: vip(默认配置) 为service分配一个虚拟IP(vip),作为客户端在网络*问service的前端。 Docker在客户端和service可用的工作节点之间路由请求,而客户端不知道有 多少节点参与服务或它们的IP地址或端口。 endpoint_mode: dnsrr DNS循环(dnsrr)服务发现不使用单个虚拟IP。Docker为服务设置DNS条目, 针对service名称的DNS查询将返回一个IP地址列表,客户端将直接连接到其中之一。
deploy: mode: replicated replicas: 2 endpoint_mode: dnsrr
-
labels
指令:
为服务指定标签。这些标签仅在service上设置,而不在service的任何容器上设置。
deploy: labels: com.example.description: "This label will appear on the web service"
要在容器上设置标签,请在deploy
之外使用labels
。
version: "3.7"services: web: image: web labels: com.example.description: "This label will appear on all containers for the web service"
-
mode
指令:
mode有global
和replicated
两种,默认mode是replicated
。当为global
时,每个集群节点仅允许有一个service的容器,不允许有副本;当为replicated
时,每个集群节点可以有指定数量的service的容器。
version: "3.7"services: worker: image: dockersamples/examplevotingapp_worker deploy: mode: global
deploy: mode: replicated replicas: 5
-
placement
指令:
指定约束和偏好的设置。node.role == manager
表示只在manager节点创建service的容器。
deploy: placement: constraints: - node.role == manager - engine.labels.operatingsystem == ubuntu 14.04 preferences: - spread: node.labels.zone
-
replicas
指令:
如果mode为replicated
,则replicas
可指定在任何给定时间应该运行service的容器数量。
deploy: mode: replicated replicas: 6
-
resources
指令:
配置资源限制。limits
限制最大资源,reservations
为预留资源(始终可用)。
deploy: resources: limits: cpus: '0.50' memory: 50M reservations: cpus: '0.25' memory: 20M
-
restart_policy
指令:
配置service的容器退出时是否重启以及如何重启。
condition 重启条件:none、on-failure、any,默认是any delay 延迟,两次重启尝试之间等待的时间,默认为0s max_attempts 最大尝试次数,在放弃之前尝试重新启动容器的次数,默认为永不放弃) window 等待时间,判断一次重启是否成功之前的等待时间,默认为立即判断
deploy: restart_policy: condition: on-failure delay: 5s max_attempts: 3 window: 120s
-
rollback_config
指令:
配置在更新失败时应如何回滚服务。
parallelism 并行数量,每次回滚的容器数量。如果设置为0,则所有容器同时回滚。 delay 延迟,每个容器组两次回滚之间等待的时间,默认为0s failure_action 如果回滚失败该继续或暂停,默认是暂停 monitor 每次任务更新后检测失败的持续时间,默认为0s max_failure_ratio 回滚期间的容错率,默认为0 order 回滚期间的操作顺序。stop-first表示旧任务在启动新任务之前停止; start-first表示新任务首先启动,正在运行的任务暂时重叠。默认是stop-first
deploy: rollback_config: parallelism: 2 delay: 10s order: stop-first
-
update_config
指令:
配置如何更新服务。
parallelism 并行数量,每次更新的容器数量。如果设置为0,则所有容器同时更新。 delay 延迟,每个容器组两次更新之间等待的时间,默认为0s failure_action 如果更新失败该继续或暂停,默认是暂停 monitor 每次任务更新后检测失败的持续时间,默认为0s max_failure_ratio 更新期间的容错率,默认为0 order 更新期间的操作顺序。stop-first表示旧任务在启动新任务之前停止; start-first表示新任务首先启动,正在运行的任务暂时重叠。默认是stop-first。 注意:只支持v3.4或更高版本
version: "3.7"services: vote: image: dockersamples/examplevotingapp_vote:before depends_on: - redis deploy: update_config: parallelism: 2 delay: 10s order: stop-first
Docker Stack 示例
下面使用Docker Stack部署wordpress项目,该项目包含两个service:web和mysql。
- 之前的环境:
role | ip | hostname |
---|---|---|
manager | 192.168.30.128 | test1 |
worker1 | 192.168.30.129 | test2 |
worker2 | 192.168.30.130 | test3 |
- 部署wordpress:
# mkdir /wordpress && cd /wordpress# vim docker-compose.yml
version: "3.7"services: db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: 123456789 MYSQL_DATABASE: wordpress volumes: - db_data:/var/lib/mysql networks: - my-network deploy: mode: global placement: constraints: - node.role == manager web: depends_on: - db image: wordpress ports: - "80:80" environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_PASSWORD: 123456789 networks: - my-network deploy: mode: replicated replicas: 3 restart_policy: condition: on-failure delay: 5s max_attempts: 3 update_config: parallelism: 3 delay: 10s order: stop-first volumes: db_data: networks: my-network: driver: overlay
# docker stack deploy wordpress --compose-file docker-compose.yml
- 查看部署情况:
# docker stack lsNAME SERVICES ORCHESTRATOR wordpress 2 Swarm# docker stack ps wordpress ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS qgi46v9jcgkh wordpress_db.q1n9ztahdj489pltf3gl5pomj mysql:5.7 test1 Running Running 18 seconds ago o848jlfflzfv wordpress_web.1 wordpress:latest test1 Running Running 13 seconds ago 2e0yxcr3man2 wordpress_web.2 wordpress:latest test2 Running Running about a minute ago nsvlklmt2ce4 wordpress_web.3 wordpress:latest test3 Running Running 55 seconds ago# docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a42ca591eb8c wordpress:latest "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 80/tcp wordpress_web.1.o848jlfflzfv2p3zgl15ubq3a 3f35d12a5bce mysql:5.7 "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 3306/tcp, 33060/tcp wordpress_db.q1n9ztahdj489pltf3gl5pomj.qgi46v9jcgkh70xwjtnu83t1i
当前集群中有两个service,其中web有3个副本,分别在集群各节点上。
- 浏览器访问:
打开浏览器,分别访问192.168.30.128
,192.168.30.129
,192.168.30.130
。
可以看到,通过swarm集群的任意节点都可以访问到wordpress界面,这与之前使用Docker Service部署wordpress项目一致。
相比之下,使用Docker Stack与使用Docker Service部署项目的差别,就类似于使用Docker Compose与使用Docker Run部署服务的差别。
Docker Secret
在生产环境下,使用Docker Stack是没问题的,但把项目的一些敏感信息(如数据库密码)写在YAML文件中则是我们不想看到的。可以使用Docker Secret解决该问题。
- 什么是secret?
用户名密码 SSH key TLS认证 任何不想让别人看到的数据
- secret管理:
secret存在于Swarm Manager节点的Raft database里 secret可以分配给一个service,该service就能看到这个secret 在容器内部secret看起来像文件,但实际上是在内存中
-
docker secret
命令:
create 从一个文件或标准输入的内容创建secret inspect 显示一个或多个secret的详细信息 ls 列出所有的secret rm 删除一个或多个secret
- 创建一个secret:
# vim db_root_password123456789# docker secret create -l mysql_root_password password db_root_passwordpa6lx5kt52wtg3md8r1zhtmo2# docker secret lsID NAME DRIVER CREATED UPDATED pa6lx5kt52wtg3md8r1zhtmo2 password 9 seconds ago 9 seconds ago# docker secret inspect password [ { "ID": "pa6lx5kt52wtg3md8r1zhtmo2", "Version": { "Index": 552 }, "CreatedAt": "2019-10-25T03:47:23.552004564Z", "UpdatedAt": "2019-10-25T03:47:23.552004564Z", "Spec": { "Name": "password", "Labels": { "mysql_root_password": "" } } }]# rm -f db_root_password
可以看到,我们根据db_root_password
这个文件的内容来创建一个名为password
的secret,并设置标签为mysql_root_password
。
注意:在创建完secret后,最好删除对应的secret文件。
- 使用刚创建的secret:
# docker service create --name busybox --secret password busybox sh -c "while true; do sleep 3600; done"fefw5tlp44quh2fg01vn0k1ei overall progress: 1 out of 1 tasks 1/1: running [==================================================>] verify: Service converged# docker service lsID NAME MODE REPLICAS IMAGE PORTS fefw5tlp44qu busybox replicated 1/1 busybox:latest # docker service ps busybox ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS tpzod80hckns busybox.1 busybox:latest test3 Running Running 44 seconds ago
192.168.30.130
# docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4ba00cff43c1 busybox:latest "sh -c 'while true; …" About a minute ago Up About a minute busybox.1.tpzod80hcknsm6yce4mms61mz# docker exec -it busybox.1.tpzod80hcknsm6yce4mms61mz sh/ # cd /run/secrets//run/secrets # lspassword /run/secrets # cat password 123456789
可以看到,这与之前的db_root_password
文件的内容一致。通过--secret
可以给一个service指定多个secret。
- 在docker stack中使用secret:
# cd /wordpress# vim docker-compose.yml
version: "3.7"services: db: image: mysql:5.7 secrets: - password environment: MYSQL_ROOT_PASSWORD_FILE: /run/secrets/password MYSQL_DATABASE: wordpress volumes: - db_data:/var/lib/mysql networks: - my-network deploy: mode: global placement: constraints: - node.role == manager web: depends_on: - db image: wordpress ports: - "80:80" secrets: - password environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_PASSWORD_FILE: /run/secrets/password networks: - my-network deploy: mode: replicated replicas: 3 restart_policy: condition: on-failure delay: 5s max_attempts: 3 update_config: parallelism: 3 delay: 10s order: stop-first volumes: db_data: networks: my-network: driver: overlaysecrets: password: external: true#secrets:# password:# file: ./db_root_password
与前面不使用secret的docker-compose.yml
文件对比,上面的docker-compose.yml
文件中增加了secrets
指令,并指定secret所在路径,external: true
表示外部已经创建好secret。
通常来说,建议事先创建好secret,然后在YAML文件中指定即可,否则也可以取消上面docker-compose.yml
文件的注释内容来创建secret。
# docker stack deploy wordpress --compose-file docker-compose.yml
- 查看部署情况:
# docker stack lsNAME SERVICES ORCHESTRATOR wordpress 2 Swarm# docker stack ps wordpress ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS 6ozs7o5eit0i wordpress_db.q1n9ztahdj489pltf3gl5pomj mysql:5.7 test1 Running Running 4 seconds ago aj4mis52w7tv wordpress_web.1 wordpress:latest test3 Running Starting 38 seconds ago r3cn3bs4edwu wordpress_web.2 wordpress:latest test1 Running Running 9 seconds ago mt81de7ihoc4 wordpress_web.3 wordpress:latest test2 Running Starting 34 seconds ago
- 使用secret登入mysql:
# docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e25a893106d5 mysql:5.7 "docker-entrypoint.s…" About a minute ago Up About a minute 3306/tcp, 33060/tcp wordpress_db.q1n9ztahdj489pltf3gl5pomj.6ozs7o5eit0i299hlkaedk8nr 12ce1facb2bb wordpress:latest "docker-entrypoint.s…" 2 minutes ago Up About a minute 80/tcp wordpress_web.2.r3cn3bs4edwunnykn9t83xqvm# docker exec -it wordpress_db.q1n9ztahdj489pltf3gl5pomj.6ozs7o5eit0i299hlkaedk8nr bashroot@e25a893106d5:/# cat /run/secrets/password 123456789 root@e25a893106d5:/# mysql -uroot -p123456789mysql: [Warning] Using a password on the command line interface can be insecure. Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 3 Server version: 5.7.28 MySQL Community Server (GPL)Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> show databases;+--------------------+| Database |+--------------------+| information_schema || mysql || performance_schema || sys || wordpress |+--------------------+ 5 rows in set (0.00 sec)
- 浏览器访问:
打开浏览器,分别访问192.168.30.128
,
这说明wordpress连接mysql没有问题,我们配置的secret也就没有问题。
Service更新
因为在生产环境中使用的是Docker Swarm集群,那么如何在项目运行中更新service呢?这是我们需要解决的问题。
以上面通过Docker Stack部署的项目wordpress为例,更新mysql版本。
- 查看mysql版本:
# docker exec -it wordpress_db.q1n9ztahdj489pltf3gl5pomj.6ozs7o5eit0i299hlkaedk8nr bashroot@e25a893106d5:/# mysql --versionmysql Ver 14.14 Distrib 5.7.28, for Linux (x86_64) using EditLine wrapper
当前版本为5.7.28,循环查看mysql版本
root@e25a893106d5:/# sh -c "while true; do mysql --version && sleep 1; done"
- 更新mysql版本:
新开一个窗口,
# docker service update wordpress_db --image mysql:5.7.27 --update-delay 0s --update-parallelism 1wordpress_db overall progress: 1 out of 1 tasks q1n9ztahdj48: running [==================================================>] verify: Service converged
# docker exec -it wordpress_db.q1n9ztahdj489pltf3gl5pomj.c1f6ea9fpg9hg5wauihkj0ba4 bashroot@5a13c556e1d4:/# mysql --versionmysql Ver 14.14 Distrib 5.7.27, for Linux (x86_64) using EditLine wrapper
可以看到,mysql版本已经更新为5.7.27。
当然,这个例子并不好,因为当前只有一个mysql容器,更新时原容器直接停止运行并删除,一般对数据库容器的更新也不会如此草率,好在这里只是演示如何进行service的更新。
另外,Docker Stack对多个service同时更新还是通过docker stack deploy
命令进行的,只不过事先更改好YAML文件即可。