使用Docker Stack部署应用

如果了解Docker Compose,就会发现Docker Stack非常简单。事实上在许多方面,Stack一直是期望的Compose——完全集成到Docker中,并能够管理应用的整个生命周期。

从体系结构上来讲,Stack位于Docker应用层级的最顶端。Stack基于服务进行构建,而服务又基于容器,如图14.1所示。

使用Docker Stack部署应用_第1张图片

图14.1 AtSea商店架构图

接下来的章节分为如下几部分。

  • 简单应用。
  • 深入分析Stack文件。
  • 部署应用。
  • 管理应用。

14.2.1 简单应用

本章后续的内容会一直使用示例应用AtSea Shop。该示例托管在Github的dockersamples/atsea-sample-shop-app库中,基于Apache 2.0许可证开源。

使用该应用是因为其复杂度适中,不会因为太复杂而难以完整解释。除此之外,该应用还是个多服务应用,并且利用了认证和安全相关的技术。应用架构如图14.2所示。

如图所示,该应用由5个服务、3个网络、4个密钥以及3组端口映射构成。具体细节将会结合Stack文件进行分析。

注:

 

在本章中用到服务一词时,指的是Docker服务(由若干容器组成的集合,作为一个整体进行统一管理,并且在Docker API中存在对应的服务对象)。

使用Docker Stack部署应用_第2张图片

图14.2 AtSea商店架构图

复制Github仓库,以获取全部源代码文件。

$ git clone https://github.com/dockersamples/atsea-sample-shop-app.git Cloning
into 'atsea-sample-shop-app'...
remote: Counting objects: 636, done.
remote: Total 636 (delta 0), reused 0 (delta 0), pack-reused 636
Receiving objects: 100% (636/636), 7.23 MiB | 28.25 MiB/s, done.
Resolving deltas: 100% (197/197), done.

该应用的代码由若干目录和源码文件组成。读者可以随意浏览这些文件。但是接下来,重点关注的文件是docker-stack.yml。该文件通常被称为Stack文件,在该文件中定义了应用及其依赖。

在该文件整体结构中,定义了4种顶级关键字。

version:
services:
networks:
secrets:

version代表了Compose文件格式的版本号。为了应用于Stack,需要3.0或者更高的版本。services中定义了组成当前应用的服务都有哪些。networks列出了必需的网络,secrets定义了应用用到的密钥。

如果展开顶级的关键字,可以看到类似图14.2中的结构。Stack文件由5个服务构成,分别为“reverse_proxy”“database”“appserver”“visualizer”“payment_gateway”。Stack文件中包含3个网络,分别为“front-tier”“back-tier”“payment”。最后,Stack文件中有4个密钥,分别为“postgres_password”“staging_token”“revprox_key”“revprox_cert”。

version: "3.2"
services:
    reverse_proxy:
    database:
    appserver:
    visualizer:
    payment_gateway:
networks:
    front-tier:
    back-tier:
    payment:
secrets:
    postgres_password:
    staging_token:
    revprox_key:
    revprox_cert:

Stack文件定义了应用的很多依赖要素,理解这一点很重要。因此,Stack文件是应用的一个自描述文件,并且作为一个很好的工具弥合了开发和运维之间的隔阂。

接下来一起深入分析Stack文件的细节。

14.2.2 深入分析Stack文件

Stack文件就是Docker Compose文件。唯一的要求就是version:一项需要是“3.0”或者更高的值。具体可以关注Docker文档中关于Compose文件的最新版本信息。

在Docker根据某个Stack文件部署应用的时候,首先会检查并创建networks:关键字对应的网络。如果对应网络不存在,Docker会进行创建。

一起看一下Stack文件中的网络定义。

1.网络

networks:
  front-tier:
  back-tier:
  payment:
    driver: overlay
    driver_opts:
      encrypted: 'yes'

该文件中定义了3个网络:front-tier、back-tier以及payment。默认情况下,这些网络都会采用overlay驱动,新建对应的覆盖类型的网络。但是payment网络比较特殊,需要数据层加密。

默认情况下,覆盖网络的所有控制层都是加密的。如果需要加密数据层,有两种选择。

  • 在docker network create命令中指定-o encrypted参数。
  • 在Stack文件中的driver_opts之下指定encrypted:'yes'。

数据层加密会导致额外开销,而影响额外开销大小的因素有很多,比如流量的类型和流量的多少。但是,通常额外开销会在10%的范围之内。

正如前面提到的,全部的3个网络均会先于密钥和服务被创建。

2.密钥

密钥属于顶级对象,在当前Stack文件中定义了4个。

secrets:
  postgres_password:
    external: true
  staging_token:
    external: true
  revprox_key:
    external: true
  revprox_cert:
    external: true

注意,4个密钥都被定义为external。这意味着在Stack部署之前,这些密钥必须存在。

当然在应用部署时按需创建密钥也是可以的,只需要将file: 替换为external: true。但该方式生效的前提是,需要在主机文件系统的对应路径下有一个文本文件,其中包含密钥所需的值,并且是未加密的。这种方式存在明显的安全隐患。

稍后会展示在部署的时候究竟是如何创建这些密钥的。现在,读者只需知道应用定义了4个密钥,并且需要提前创建即可。

下面对服务逐一进行分析。

3.服务

部署中的主要操作都在服务这个环节。

每个服务都是一个JSON集合(字典),其中包含了一系列关键字。本书会依次介绍每个关键字,并解释操作的具体内容。

(1)reverse_proxy服务

正如读者所见,reverse_proxy服务定义了镜像、端口、密钥以及网络。

reverse_proxy:
  image: dockersamples/atseasampleshopapp_reverse_proxy
  ports:
    - "80:80"
    - "443:443"
  secrets:
    - source: revprox_cert
      target: revprox_cert
    - source: revprox_key
      target: revprox_key
networks:
  - front-tier

image关键字是服务对象中唯一的必填项。顾名思义,该关键字定义了将要用于构建服务副本的Docker镜像。

Docker是可选项,除非指定其他值,否则镜像会从Docker Hub拉取。读者可以通过在镜像前添加对应第三方镜像仓库服务API的DNS名称的方式,来指定某个镜像从第三方服务拉取。例如Google的容器服务的DNS名称为gcr.io。

Docker Stack和Docker Compose的一个区别是,Stack不支持构建。这意味着在部署Stack之前,所有镜像必须提前构建完成。

ports关键字定义了两个映射。

  • 80:80将Swarm节点的80端口映射到每个服务副本的80端口。
  • 443:443将Swarm节点的443端口映射到每个服务副本的443端口。

默认情况下,所有端口映射都采用Ingress模式。这意味着Swarm集群中每个节点的对应端口都会映射并且是可访问的,即使是那些没有运行副本的节点。另一种方式是Host模式,端口只映射到了运行副本的Swarm节点上。但是,Host模式需要使用完整格式的配置。例如,在Host模式下将端口映射到80端口的语法如下所示。

ports:
  - target: 80
    published: 80
    mode: host

推荐使用完整语法格式,这样可以提高易读性,并且更灵活(完整语法格式支持Ingress模式和Host模式)。但是,完整格式要求Compose文件格式的版本至少是3.2。

secret关键字中定义了两个密钥:revprox_cert以及revprox_key。这两个密钥必须在顶级关键字secrets下定义,并且必须在系统上已经存在。

密钥以普通文件的形式被挂载到服务副本当中。文件的名称就是stack文件中定义的target属性的值,其在Linux下的路径为/run/secrets,在Windows下的路径为C:\ProgramData\Docker\secrets。Linux将/run/secrets作为内存文件系统挂载,但是Windows并不会这样。

本服务密钥中定义的内容会在每个服务副本中被挂载,具体路径为/run/secrets/revprox_cert和/run/secrets/revprox_key。若将其中之一挂载为/run/secrets/uber_secret,需要在stack文件中定义如下内容。

secrets:
  - source: revprox_cert
    target: uber_secret

networks关键字确保服务所有副本都会连接到front-tier网络。网络相关定义必须位于顶级关键字networks之下,如果定义的网络不存在,Docker会以Overlay网络方式新建一个网络。

(2)database服务

数据库服务也在Stack文件中定义了,包括镜像、网络以及密钥。除上述内容之外,数据库服务还引入了环境变量和部署约束。

database:
  image: dockersamples/atsea_db
  environment:
    POSTGRES_USER: gordonuser
    POSTGRES_DB_PASSWORD_FILE: /run/secrets/postgres_password
    POSTGRES_DB: atsea
  networks:
    - back-tier
  secrets:
    - postgres_password
  deploy:
    placement:
      constraints:
        - 'node.role == worker'

environment关键字允许在服务副本中注入环境变量。在该服务中,使用了3个环境变量来定义数据库用户、数据库密码的位置(挂载到每个服务副本中的密钥)以及数据库服务的名称。

environment:
  POSTGRES_USER: gordonuser
  POSTGRES_DB_PASSWORD_FILE: /run/secrets/postgres_password
  POSTGRES_DB: atsea

注:

 

将三者作为密钥传递会更安全,因为这样可以避免将数据库名称和数据库用户以明文变量的方式记录在文件当中。

该服务还在deploy关键字下定义了部署约束。这样保证了当前服务只会运行在Swarm集群的worker节点之上。

deploy:
  placement:
    constraints:
      - 'node.role == worker'

部署约束是一种拓扑感知定时任务,是一种很好的优化调度选择的方式。Swarm目前允许通过如下几种方式进行调度。

  • 节点ID,如node.id==o2p4kw2uuw2a。
  • 节点名称,如node.hostname==wrk-12。
  • 节点角色,如node.role!=manager。
  • 节点引擎标签,如engine.labels.operatingsystem==ubuntu16.04。
  • 节点自定义标签,如node.labels.zone==prod1。

注意==和!=操作符均支持。

(3)appserver服务

appserver服务使用了一个镜像,连接到3个网络,并且挂载了一个密钥。此外appserver服务还在deploy关键字下引入了一些额外的特性。

appserver:
  image: dockersamples/atsea_app
  networks:
    - front-tier
    - back-tier
    - payment
  deploy:
    replicas: 2
    update_config:
      parallelism: 2
    failure_action: rollback
    placement:
      constraints:
      - 'node.role == worker'
  restart_policy:
    condition: on-failure
    delay: 5s
    max_attempts: 3
    window: 120s
secrets:
  - postgres_password

接下来进一步了解deploy关键字中新增的内容。

首先,services.appserver.deploy.replicas = 2设置期望服务的副本数量为2。缺省情况下,默认值为1。如果服务正在运行,并且需要修改副本数,则读者需要显示声明该值。这意味着需要更新stack文件中的services.appserver.deploy.replicas,设置一个新值,然后重新部署当前stack。后面会进行具体展示,但是重新部署stack并不会影响那些没有改动的服务。

services.appserver.deploy.update_config定义了Docker在服务滚动升级的时候具体如何操作。对于当前服务,Docker每次会更新两个副本(parallelism),并且在升级失败后自动回滚。回滚会基于之前的服务定义启动新的副本。failure_action的默认操作是pause,会在服务升级失败后阻止其他副本的升级。failure_action还支持continue。

update_config:
  parallelism: 2
  failure_action: rollback

services.appserver.deploy.restart-policy定义了Swarm针对容器异常退出的重启策略。当前服务的重启策略是,如果某个副本以非0返回值退出(condition: onfailure),会立即重启当前副本。重启最多重试3次,每次都会等待至多120s来检测是否启动成功。每次重启的间隔是5s。

restart_policy:
  condition: on-failure
  delay: 5s
  max_attempts: 3
  window: 120s

(4)visualizer服务

visualizer服务中指定了镜像,定义了端口映射规则、更新配置以及部署约束。此外还挂载了一个指定卷,并且定义了容器的优雅停止方式。

visualizer:
  image: dockersamples/visualizer:stable
  ports:
    - "8001:8080"
  stop_grace_period: 1m30s
  volumes:
    - "/var/run/docker.sock:/var/run/docker.sock"
  deploy:
    update_config:
      failure_action: rollback
    placement:
      constraints:
        - 'node.role == manager'

当Docker停止某个容器的时候,会给容器内部PID为1的进程发送SIGTERM信号。容器内PID为1的进程会有10s的优雅停止时间来执行一些清理操作。如果进程没有处理该信号,则10s后就会被SIGKILL信号强制结束。stop_grace_period属性可以调整默认为10s的优雅停止时长。

volumes关键字用于挂载提前创建的卷或者主机目录到某个服务副本当中。在本例中,会挂载Docker主机的/var/run/docker.sock目录到每个服务副本的/var/run/docker.sock路径。这意味着在服务副本中任何对/var/run/docker.sock的读写操作都会实际指向Docker主机的对应目录中。

/var/run/docker.sock恰巧是Docker提供的IPC套接字,Docker daemon通过该套接字对其他进程暴露其API终端。这意味着如果给某个容器访问该文件的权限,就是允许该容器接收全部的API终端,即等价于给予了容器查询和管理Docker daemon的能力。在大部分场景下这是决不允许的。但是,这是一个实验室环境中的示例应用。

该服务需要Docker套接字访问权限的原因是需要以图形化方式展示当前Swarm中服务。为了实现这个目标,当前服务需要能访问管理节点的Docker daemon。为了确保能访问管理节点Docker daemon,当前服务通过部署约束的方式,强制服务副本只能部署在管理节点之上,同时将Docker套接字绑定挂载到每个服务副本中。绑定挂载如图14.3所示。

使用Docker Stack部署应用_第3张图片

图14.3 绑定挂载

(5)payment_gateway服务

payment_gateway服务中指定了镜像,挂载了一个密钥,连接到网络,定义了部分部署策略,并且使用了两个部署约束。

payment_gateway:
  image: dockersamples/atseasampleshopapp_payment_gateway
  secrets:
    - source: staging_token
      target: payment_token
  networks:
    - payment
  deploy:
    update_config:
      failure_action: rollback
    placement:
      constraints:
        - 'node.role == worker'
        - 'node.labels.pcidss == yes'

除了部署约束node.label之外,其余配置项在前面都已经出现过了。通过docker node update命令可以自定义节点标签,并添加到Swarm集群的指定节点。因此,node.label配置只适用于Swarm集群中指定的节点上(不能用于单独的容器或者不属于Swarm集群的容器之上)。

在本例中,payment_gateway服务被要求只能运行在符合PCI DSS(支付卡行业标准,译者注)标准的节点之上。为了使其生效,读者可以将某个自定义节点标签应用到Swarm集群中符合要求的节点之上。本书在搭建应用部署实验环境的时候完成了该操作。

因为当前服务定义了两个部署约束,所以服务副本只会部署在两个约束条件均满足的节点之上,即具备pcidss=yes节点标签的worker节点。

关于Stack文件的分析到这里就结束了,目前对于应用需求应该有了较好的理解。前文中提到,Stack文件是应用文档化的重要部分之一。读者已经了解该应用包含5个服务、3个网络以及4个密钥。此外读者还知道了每个服务都会连接到哪个网络、有哪些端口需要发布、应用会使用到哪些镜像以及哪些服务需要在特定的节点上发布。

下面开始部署。

14.2.3 部署应用

在部署应用之前,有几个前置处理需要完成。

  • Swarm模式:应用将采用Docker Stack部署,而Stack依赖Swarm模式。
  • 标签:某个Swarm worker节点需要自定义标签。
  • 密钥:应用所需的密钥需要在部署前创建完成。

1.搭建应用实验环境

在本节中会完成基于Linux的三节点Swarm集群搭建,同时能满足上面应用的全部前置依赖。完成之后,实验环境如图14.4所示。

使用Docker Stack部署应用_第4张图片

图14.4 示例环境

接下来内容分为3个步骤。

(1)创建新的Swarm。

(2)添加节点标签。

(3)创建密钥。

首先创建新的三节点Swarm集群。

(1)初始化Swarm。

在读者期望成为Swarm管理节点的机器上,运行下面的命令。

$ docker swarm init
Swarm initialized: current node (lhma...w4nn) is now a manager.

(2)添加工作节点。

复制前面输出中出现的docker swarm join命令。将复制内容粘贴到工作节点上并运行。

//Worker 1 (wrk-1)
wrk-1$ docker swarm join --token SWMTKN-1-2hl6...-...3lqg 172.31.40.192:2377
This node joined a swarm as a worker.

//Worker 2 (wrk-2)
wrk-2$ docker swarm join --token SWMTKN-1-2hl6...-...3lqg 172.31.40.192:2377
This node joined a swarm as a worker.

(3)确认当前Swarm由一个管理节点和两个工作节点构成。在管理节点中运行下面的命令。

$ docker node ls
ID            HOSTNAME  STATUS    AVAILABILITY   MANAGER STATUS
lhm...4nn *   mgr-1     Ready     Active         Leader
b74...gz3     wrk-1     Ready     Active
o9x...um8     wrk-2     Ready     Active

Swarm集群目前就绪。

payment_gateway服务配置了部署约束,限制该服务只能运行在有pcidss=yes标签的工作节点之上。本步骤中将在wrk-1上添加该节点标签。

在现实世界中,添加该标签之前必须将某个Docker节点按PCI规范进行标准化。但是,这只是一个实验环境,所以就暂且跳过这一过程,直接将标签添加到wrk-1节点。

在Swarm管理节点运行下面的命令。

(1)添加节点标签到wrk-1。

$ docker node update --label-add pcidss=yes wrk-1

Node标签只在Swarm集群之内生效。

(2)确认节点标签。

$ docker node inspect wrk-1
[
{
    "ID": "b74rzajmrimfv7hood6l4lgz3",
    "Version": {
        "Index": 27
    },
    "CreatedAt": "2018-01-25T10:35:18.146831621Z",
    "UpdatedAt": "2018-01-25T10:47:57.189021202Z",
    "Spec": {
        "Labels": {
            "pcidss": "yes"

        },
        

wrk-1工作节点现在已经配置完成,所以该节点可以运行payment_gateway服务副本了。

应用定义了4个密钥,这些都需要在应用部署前创建。

  • postgress_password。
  • staging_token。
  • revprox_cert。
  • revprox_key。

在管理节点运行下面的命令,来创建这些密钥。

(1)创建新的键值对。

密钥中有3个是需要加密key的。在本步骤中会创建加密key,下一步会将加密key放到Docker密钥文件当中。

$ openssl req -newkey rsa:4096 -nodes -sha256 \
  -keyout domain.key -x509 -days 365 -out domain.crt

(2)创建revprox_cert、revprox_key以及postgress_password密钥。

$ docker secret create revprox_cert domain.crt
cqblzfpyv5cxb5wbvtrbpvrrj

$ docker secret create revprox_key domain.key
jqd1ramk2x7g0s2e9ynhdyl4p

$ docker secret create postgres_password domain.key
njpdklhjcg8noy64aileyod6l

(3)创建stage_token密钥。

$ echo staging | docker secret create staging_token -
sqy21qep9w17h04k3600o6qsj

(4)列出所有密钥。

$ docker secret ls
ID          NAME                CREATED             UPDATED
njp...d6l   postgres_password   47 seconds ago      47 seconds ago
cqb...rrj   revprox_cert        About a minute ago  About a minute ago
jqd...l4p   revprox_key         About a minute ago  About a minute ago
sqy...qsj   staging_token       23 seconds ago      23 seconds ago

上面已经完成了全部的前置准备。是时候开始部署应用了!

2.部署示例应用

如果还没有代码,请先复制应用的GitHub仓库到Swarm管理节点。

$ git clone https://github.com/dockersamples/atsea-sample-shop-app.git
Cloning into 'atsea-sample-shop-app'...
remote: Counting objects: 636, done.
Receiving objects: 100% (636/636), 7.23 MiB | 3.30 MiB/s, done. remote:
Total 636 (delta 0), reused 0 (delta 0), pack-reused 636 Resolving
deltas: 100% (197/197), done.
Checking connectivity... done.

$ cd atsea-sample-shop-app

现在已经拥有了源码,可以开始部署应用了。

Stack通过docker stack deploy命令完成部署。基础格式下,该命令允许传入两个参数。

  • Stack文件的名称。
  • Stack的名称。

应用的GitHub仓库中包含一个名为docker-stack.yml的Stack文件。这里会使用该文件。本书中为Stack起名seastack,如果读者不喜欢,也可以选择其他名称。

在Swarm管理节点的atsea-sample-shop-app目录下运行下面的命令。

部署Stack(应用)。

$ docker stack deploy -c docker-stack.yml seastack
Creating network seastack_default
Creating network seastack_back-tier
Creating network seastack_front-tier
Creating network seastack_payment
Creating service seastack_database
Creating service seastack_appserver
Creating service seastack_visualizer
Creating service seastack_payment_gateway
Creating service seastack_reverse_proxy

读者可以运行docker network ls以及docker service ls命令来查看应用的网络和服务情况。

下面是命令输出中几个需要注意的地方。

网络是先于服务创建的。这是因为服务依赖于网络,所以网络需要在服务启动前创建。

Docker将Stack名称附加到由他创建的任何资源名称前作为前缀。在本例中,Stack名为seastack,所以所有资源名称的格式都如:seastack_。例如,payment网络的名称是seastack_payment。而在部署之前创建的资源则没有被重命名,比如密钥。

另一个需要注意的点是出现了新的名为seastack_default的网络。该网络并未在Stack文件中定义,那为什么会创建呢?每个服务都需要连接到网络,但是visualizer服务并没有指定具体的网络。因此,Docker创建了名为seastack_default的网络,并将visualizer连接到该网络。

读者可以通过两个命令来确认当前Stack的状态。docker stack ls列出了系统中全部Stack,包括每个Stack下面包含多少服务。docker stack ps 针对某个指定Stack展示了更详细的信息,例如期望状态以及当前状态。下面一起来了解下这两条命令。

$ docker stack ls
NAME                SERVICES
Seastack            5

$ docker stack ps seastack
NAME                         NODE    DESIRED STATE   CURRENT STATE
seastack_reverse_proxy.1     wrk-2   Running         Running 7 minutes ago
seastack_payment_gateway.1   wrk-1   Running         Running 7 minutes ago
seastack_visualizer.1        mgr-1   Running         Running 7 minutes ago
seastack_appserver.1         wrk-2   Running         Running 7 minutes ago
seastack_database.1          wrk-2   Running         Running 7 minutes ago
seastack_appserver.2         wrk-1   Running         Running 7 minutes ago

在服务启动失败时,docker stack ps命令是首选的问题定位方式。该命令展示了Stack中每个服务的概况,包括服务副本所在节点、当前状态、期望状态以及异常信息。从下面的输出信息中能看出reverse_proxy服务在wrk-2节点上两次尝试启动副本失败。

$ docker stack ps seastack
NAME                NODE      DESIRED    CURRENT ERROR
                              STATE      STATE
reverse_proxy.1     wrk-2     Shutdown   Failed  "task: non-zero exit (1)"
\_reverse_proxy.1   wrk-2     Shutdown   Failed  "task: non-zero exit (1)"

如果想查看具体某个服务的详细信息,可以使用docker service logs命令。读者需要将服务名称/ID或者副本ID作为参数传入。如果传入服务名称或ID,读者可以看到所有服务副本的日志信息。如果传入的是副本ID,读者只会看到对应副本的日志信息。

下面的docker service logs命令展示了seastack_reverse_proxy服务的全部副本日志,其中包含了前面输出中的两次副本启动失败的日志。

$ docker service logs seastack_reverse_proxy
seastack_reverse_proxy.1.zhc3cjeti9d4@wrk-2 | [emerg] 1#1: host not found...
seastack_reverse_proxy.1.6m1nmbzmwh2d@wrk-2 | [emerg] 1#1: host not found...
seastack_reverse_proxy.1.6m1nmbzmwh2d@wrk-2 | nginx: [emerg] host not found..
seastack_reverse_proxy.1.zhc3cjeti9d4@wrk-2 | nginx: [emerg] host not found..
seastack_reverse_proxy.1.1tmya243m5um@mgr-1 | 10.255.0.2 "GET / HTTP/1.1" 302

输出内容为了适应页面展示,已经经过裁剪,但是读者还是可以看到全部3个服务副本的日志(两个启动失败,1个正在运行)。每行的开始都是副本的名称,包括服务名称、副本序号、副本ID以及副本所在主机的名称。接下来是具体的日志输出。

注:

 

读者可能已经注意到前面日志中全部副本的序号都是1。这是因为Docker每次只创建一个副本,并且只有当前面的副本启动失败时才会创建新的。

因为输出内容经过裁剪,所以具体原因很难明确,但看起来前两次副本启动失败原因是其依赖的某个服务仍然在启动中(一种启动时服务间依赖导致的竞争条件)。

读者可以继续跟踪日志(--follow),查看日志尾部内容(--tail),或者获取额外的详细信息(--details)。

现在Stack已经启动并且处于运行中,看一下如何管理stack。

14.2.4 管理应用

Stack是一组相关联的服务和基础设施,需要进行统一的部署和管理。虽然这句话里充斥着术语,但仍提醒我们Stack是由普通的Docker资源构建而来:网络、卷、密钥、服务等。这意味着可以通过普通的Docker命令对其进行查看和重新配置,例如docker network、docker volume、docker secret、docker service等。

在此前提之下,通过docker service命令来管理Stack中某个服务是可行的。一个简单的例子是通过docker service scale命令来扩充appserver服务的副本数。但是,这并不是推荐的方式!

推荐方式是通过声明式方式修改,即将Stack文件作为配置的唯一声明。这样,所有Stack相关的改动都需要体现在Stack文件中,然后更新重新部署应用所需的Stack文件。

下面是一个简单例子,阐述了为什么通过命令修改的方式不好(通过CLI进行变更)。

假设读者已经部署了一个Stack,采用的Stack文件是前面章节中从GitHub复制的仓库中的docker-stack.yml。这意味着目前appserver服务有两个副本。如果通过docker service scale命令将副本修改为4个,当前运行的集群会有4个副本,但是Stack文件中仍然是两个。得承认目前看起来还不是特别糟糕。但是,假设读者又通过修改Stack文件对Stack做了某些改动,然后通过docker stack deploy命令进行滚动部署。这会导致appserver服务副本数被回滚到两个,因为Stack文件就是这么定义的。因此,推荐对Stack所有的变更都通过修改Stack文件来进行,并且将该文件放到一个合适的版本控制系统当中。

一起来回顾对Stack进行两个声明式修改的过程。目标是进行如下改动。

  • 增加appserver副本数,数量为2~10。
  • 将visualizer服务的优雅停止时间增加到2min。

修改docker-stack.yml文件,更新两个值:services.appserver.deploy.replicas=10和services.visualizer.stop_grace_period=2m。

目前,Stack文件中的内容如下。


appserver:
  image: dockersamples/atsea_app
  networks:
    - front-tier
    - back-tier
    - payment
  deploy:
    replicas: 10             <
visualizer:
  image: dockersamples/visualizer:stable
  ports:
    - "8001:8080"
stop_grace_period: 2m        < 
  

保存文件并重新部署应用。

$ docker stack deploy -c docker-stack.yml seastack
Updating service seastack_reverse_proxy (id: z4crmmrz7zi83o0721heohsku)
Updating service seastack_database (id: 3vvpkgunetxaatbvyqxfic115)
Updating service seastack_appserver (id: ljht639w33dhv0dmht1q6mueh)
Updating service seastack_visualizer (id: rbwoyuciglre01hsm5fviabjf)
Updating service seastack_payment_gateway (id: w4gsdxfnb5gofwtvmdiooqvxs)

以上重新部署应用的方式,只会更新存在变更的部分。

运行docker stack ps命令来确认appserver副本数量确实增加。

$ docker stack ps seastack
NAME                    NODE  DESIRED STATE CURRENT STATE
seastack_visualizer.1   mgr-1 Running       Running 1 second ago
seastack_visualizer.1   mgr-1 Shutdown      Shutdown 3 seconds ago
seastack_appserver.1    wrk-2 Running       Running 24 minutes ago
seastack_appserver.2    wrk-1 Running       Running 24 minutes ago
seastack_appserver.3    wrk-2 Running       Running 1 second ago
seastack_appserver.4    wrk-1 Running       Running 1 second ago
seastack_appserver.5    wrk-2 Running       Running 1 second ago
seastack_appserver.6    wrk-1 Running       Starting 7 seconds ago
seastack_appserver.7    wrk-2 Running       Running 1 second ago
seastack_appserver.8    wrk-1 Running       Starting 7 seconds ago
seastack_appserver.9    wrk-2 Running       Running 1 second ago
seastack_appserver.10   wrk-1 Running       Starting 7 seconds ago

为了本书的排版效果,输出内容有所裁剪,只展示了受变更影响的服务。

注意关于visualizer服务有两行内容。其中一行表示某个副本在3s前停止,另一行表示新副本已经运行了1s。这是因为刚才对visualizer服务作了修改,所以Swarm集群终止了正在运行的副本,并且启动了新的副本,新副本中更新了stop_grace_period的值。

还需要注意的是,appserver服务目前拥有10个副本,但不同副本的“CURRENT STATE”一列状态并不相同:有些处于running状态,而有些仍在starting状态。

经过足够的时间,集群的状态会完成收敛,期望状态和当前状态就会保持一致。在那时,集群中实际部署和观察到的状态,就会跟Stack文件中定义的内容完全一致。这真是让人开心的事情。

所有应用/Stack都应采用该方式进行更新。所有的变更都应该通过Stack文件进行声明,然后通过docker stack deploy进行部署

正确的删除某个Stack方式是通过docker stack rm命令。一定要谨慎!删除Stack不会进行二次确认。

$ docker stack rm seastack
Removing service seastack_appserver
Removing service seastack_database
Removing service seastack_payment_gateway
Removing service seastack_reverse_proxy
Removing service seastack_visualizer
Removing network seastack_front-tier
Removing network seastack_payment
Removing network seastack_default
Removing network seastack_back-tier

注意,网络和服务已经删除,但是密钥并没有。这是因为密钥是在Stack部署前就创建并存在了。在Stack最上层结构中定义的卷同样不会被docker stack rm命令删除。这是因为卷的设计初衷是保存持久化数据,其生命周期独立于容器、服务以及Stack之外。

本文摘自《深入浅出Docker》

使用Docker Stack部署应用_第5张图片

本书是一本Docker入门图书,全书分为17章,从Docker概览和Docker技术两部分进行全面解析,深入浅出地介绍了Docker的相关知识,清晰详细的操作步骤结合大量的实际代码帮助读者学以致用,将Docker知识应用到真实的项目开发当中。 
本书适合对Docker感兴趣的入门新手、Docker技术开发人员以及运维人员阅读,本书也可作为Docker认证工程师考试的参考图书。

 

你可能感兴趣的:(docker,容器)