第14章 使用Docker Stack 部署应用

大规模场景下的多服务部署和管理是一件很难的事情。幸运的是,Docker Stack为解决该问题而生。Docker Stack 通过提供期望状态、滚动升级、简单易用、扩缩容、健康检查等特性简化了应用的管理。这些功能都封装在一个完美的声明式模型当中。

14.1 使用Docker Stack 部署应用——简介

在笔记本上测试和部署应用很简单。但是这只能算是业余选手。在真实的生产环境进行多服务的应用部署和管理,这才是专业选手的水平。

幸运的是,Stack正为此而生!Stack能够在单个声明文件中定义复杂的多服务应用。Stack还提供了简单的方式来部署应用并管理完整的生命周期:初始化部署—>健康检查—>扩容—>更新—>回滚,以及其他功能。

步骤很简单。在Compose文件中定义应用,然后通过 docker stack deploy 命令完成部署和管理。Compose文件中包含了构成应用所需的完整服务栈,此外还包括了卷、网络、安全以及应用所需的其他架构。然后基于该文件使用docker stack deploy 命令来部署应用。

Stack是基于Docker Swarm之上来完成应用的部署。因此诸如安全等高级特性,其实都是来自Swarm。简而言之,Docker适用于开发和测试。Docker Stack则适用于大规模场景和生产环境。

14.2 使用Docker Stack 部署应用——详解

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

14.2.1 简单应用

本章的示例使用的应用是 AtSea Shop,这个应用我们在前面曾经下载过了(多阶段构建Docker)。该应用是个多服务应用,并且利用了认证和安全相关的技术。应用的架构图如下:

如果还没有下载这个项目,请使用git来下载。

git clone https://github.com/nigelpoulton/atsea-sample-shop-app.git

该应用代码由若干的目录和源码文件组成,我们重点关注 docker-stack.yml 这个文件。该文件通常被称为Stack文件,在该文件中定义了应用及其依赖。

在该文件整体结构中,定义了4种关键字。其中包括 version、services、networks、secrets。与我们之前在Swarm章节介绍的一样,前3个关键字是同样的含义。secrets定义的是应用用到的密钥。

我们展开顶级菜单,就会看到5个服务,3个网络和4个密钥。

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:

14.2.2 深入分析Stack文件

Stack文件就是Docker Compose文件。唯一的要求就是version:一项需要是3.0或者更高的值。在Docker根据某个Stack文件部署应用的时候,首先会检查并创建networks:关键字对应网络。如果网络不存在,Docker会进行创建。下面我们详细看下这几个模块。

  1. 网络
networks:
  front-tier:
  back-tier:
  payment:
    driver: overlay
    driver_opts:
      encrypted: 'yes'

这里定义了3个网络,默认情况下网络都是使用overlay驱动,新建对应的覆盖类型的网络。但是payment网络比较特殊,需要对数据层加密。

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

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

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

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

  1. 密钥

密钥数据顶级对象,在当前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>。但该方式生效的前提是,需要在主机文件系统对应路径下有一个文本文件,其中包含密钥所需的值,并且是未加密的。这种方式存在明显的安全隐患。稍后会介绍如何创建密钥。

  1. 服务

部署中主要的操作都在服务这个环节。每个服务都是一个JSON集合,其中包含了一系列关键字。下面分别介绍着5个服务。

(1) 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

reverse_proxy服务定义了镜像、端口、密钥和网络。

image关键字是服务对象中唯一的必填项,这个关键字定义了将要用于构建服务副本的Docker镜像。默认情况下会从Docker Hub拉取镜像,如果要从第三方服务中拉取,则需要自己添加对应的第三方镜像仓库服务API的DNS名称。

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

ports关键字定义了两个关键字。默认情况下,所有端口映射都采用Ingress模式。这意味着Swarm集群中每个节点的对应端口都会映射并且是可以访问的,即使那些没有运行副本的节点。另一种方式是Host模式,端口只映射了运行副本的Swarm节点上。但是Host模式需要使用完整的格式去配置,我们在前面曾经介绍过。

secrets 关键字定义了两个密钥,这两个密钥必须在顶级关键字 secerts 下定义,并且必须在系统中已经存在。密钥以普通文件的形式被挂载到服务副本当中。文件的名称就是stack文件中定义的 target 属性的值,其在Linux下的路径为 /run/secrets,Linux将 /run/secrets作为内存文件系统挂载。

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

(2)database服务

数据库服务除了定义上述的内容之外,还应用了环境变量和部署约束。

  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个环境变量来定义数据库用户、数据库密码的位置(挂载到每个服务副本的密钥)以及数据库服务的名称。

该服务还在deploy关键字下定义了部署约束。这样保证了当前服务只会运行在Swarm集群的worker节点之上。部署约束是一种拓扑感知定时任务,是一种很好的优化调度选择的方式。Swarm目前允许通过如下几种方式进行调度。

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

(3) appserver 服务

  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

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

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

  • replicas: 2 设置了期望服务的副本数量为2,默认为1.如果服务正在运行,需要调整副本数。可以调整stack文件中的 replicas 的数值,然后重新部署stack。重新部署stack并不会影响那些没有改动的服务。

  • update_config 定义了服务在滚动升级的时候应该如何操作。在这里是每次更新两个副本,升级失败之后会自动回滚。回滚会基于之前的服务定义启动新的副本。failure_action 的默认操作时pause,会在服务升级失败后阻止其他副本的升级。failure_action还支持continue。

  • restart_policy 定义了Swarm针对容器异常退出的重启策略。当前服务的重启策略是:如果某个副本以非0返回值退出,会立即重启当前副本。重启最多尝试3次,每次都是等待之多120s来检测是否成功。每次重启的间隔是5s。

(4) 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'

除了指定镜像、定义端口映射、更新配置以及部署约束之外,这里还挂载了一个指定卷,并且定义了容器的优雅停止方式。

  • stop_grace_period 属性调整了默认为10s的优雅停止时长。

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

docker.sock 文件时Docker提供的套接字,Docker daemon通过该套接字对其他进程暴露其API终端。这意味着如果给某个容器访问该文件的权限,就是允许该容器接受全部的API终端,即等级与给予了容器查询和管理Docker daemon 的能力。在大部分场景下这是不允许的,但是这是个测试环境的示例应用。

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

(5) 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'

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

因为这里定义了两个部署约束,也就是只有同时满足 pcidss=yes 并且时worker的节点才会被部署。

14.2.3 部署应用

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

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

同Swarm的实验一样,这里我们使用三个Dokcer主机来搭建Swarm集群,其中包括1个管理节点和2个工作节点。初始化并添加工作节点的操作这里不重复操作了,请看前面的章节。

实验的节点情况如下:

[pangcm@docker01 ~]$ docker node ls 
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
g6kx0krb978aqlexv6nd2i66x *   docker01            Ready               Active              Leader              19.03.5
px24lw8vvy3kh0ge61oge9cjo     docker02            Ready               Active                                  19.03.4
zha2x49djdhuqch35f6gm4efh     docker03            Ready               Active                                  19.03.5

(1) 添加节点标签(pcidss)

在前面我们分析过了payment_gateway 服务配置了部署约束,限制了只有运行在 pcidss=yes 标签的工作节点之上。这里我们在 docker02 节点上增加该标签。

docker node update --label-add pcidss=yes docker02

可以使用 docker node inspect docker02 命令去确认标签

[pangcm@docker01 ~]$ docker node inspect docker02|grep pcidss
                "pcidss": "yes"

(2) 创建密钥

密钥中有3个是需要加密的key的,所以我们要先创建加密的key,然后把加密 key 放到 Docker 密钥文件当中。

创建键值对

## 不断enter 即可
openssl req -newkey rsa:4096 -nodes -sha256  -keyout domain.key -x509 -days 365 -out domain.crt

创建 revprox_cert、revprox_key、postgres_password的密钥

docker secret create revprox_cert domain.crt
docker secret create revprox_key domain.key
docker secret create postgres_password domain.key

创建stage_token密钥

echo staging |docker secret create staging_token -

列出所有的密钥:

[pangcm@docker01 ~]$ docker secret ls 
ID                          NAME                DRIVER              CREATED             UPDATED
zp591zcfknxprzhbn06r3rrwh   postgres_password                       31 minutes ago      31 minutes ago
tzfh3qgajlnf6yh9s82yw77ws   revprox_cert                            35 minutes ago      35 minutes ago
f109x6mtz1ueerxwxoy772ujv   revprox_key                             34 minutes ago      34 minutes ago
3bmwnmddfgyy8ij98csmfldjm   staging_token                           34 minutes ago      34 minutes ago

这里我们就完成了全部的前置准备,下面开始部署。

  1. 部署示例应用
##进入到项目目录下
cd atsea-sample-shop-app
##使用docker stack deploy 命令部署
docker stack deploy -c docker-stack.yml seastack

这里我们指定了stack文件,并把stack命名为 seastack。我们可以运行 docker network ls 以及docker service ls 命令来擦看应用的网络和服务情况。下面是命令输出需要注意的地方。

网络是先于服务创建的。这是因为服务依赖于网络,所以网络需要在服务启动前创建。Docker将Stack名称附加到由他创建的任何资源名称前作为前缀。在本例中,Stack名为seastack,所以所有资源名称的格式都如: seastack_。例如,payment网络的名称是seastack_payment。而在部署之前创建的资源则不会被重命名,比如密钥。

另外一个需要注意的是查看网络状态的时候会看到多了一个 seastack_default 的网络。该网络我们没有定义,为什么会创建呢?要知道每个服务都要连接到网络的,我们的visualizer服务没有指定具体的网络。于是Docker就创建了这样的一个网络,并且把visualizer连接到该网络。

我们可以通过 docker stack ls 和docker stack ps 去查看 stack的更多信息。

## docker stack ls 
[pangcm@docker01 atsea-sample-shop-app]$ docker stack ls 
NAME                SERVICES            ORCHESTRATOR
seastack            5                   Swarm

## docker stack ps 
[pangcm@docker01 atsea-sample-shop-app]$ docker stack ps seastack 
ID                  NAME                         IMAGE                                   NODE                DESIRED STATE       CURRENT STATE            ERROR 
aioybx1kz4vv        seastack_appserver.1         dockersamples/atsea_app:latest          docker03            Running             Preparing 10 hours ago                                      
i5k69i7gekyc        seastack_database.1          dockersamples/atsea_db:latest           docker02            Running             Running 10 hours ago                                        
8ulzn7q95axq        seastack_appserver.1         dockersamples/atsea_app:latest          docker02            Shutdown            Failed 10 hours ago      "task: n
...

可以看到 appserver 在docker02节点启动失败过,通过docker stack ps 可以看到Stack中每个服务的概况,包括服务副本所在的节点、当前状态、期望状态和异常细节。

如果要查看具体某个服务的详细信息,可以使用 docker service logs 命令。

在服务都起来之后,我们使用浏览器访问 https://xxxx 即可访问这个应用的首页。

14.2.4 管理应用

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

在这样的一个前提下,通过docker service 命令来管理 Stack中的某个服务是可行的。一个简单的例子是通过 docker service scale 命令来扩充appserver的服务副本数,但我们不推荐这么操作。

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

举例,我们把appserver的副本数目从2修改为3,把visuaizer服务的优雅停止时间增加到2分钟,我们修改stack文件。

  appserver:
    deploy:
      replicas: 3
      update_config:
        parallelism: 2
        failure_action: rollback

  visualizer:
    ports:
      - "8001:8080"
    stop_grace_period: 2m

然后重新部署

docker stack deploy -c docker-stack.yml seastack

如果要删除某个 Stack,使用 docker stack rm 命令。一定要谨慎,删除Stack不会进行二次确认。该命令会把网络和服务都删除,但是密钥不会被删除,原因是密钥是我们在部署前就创建并存在的。当然,卷也是不会被删除的。

14.3 使用Docker Stack 部署应用——命令

  • docker stack deploy 用于根据 Stack 文件部署和更新Stack服务的命令。

  • docker stack ls 会列出 Swarm 集群中全部的Stack,包括每个Stack拥有多少服务。

  • docker stack ps 列出某个已经部署的Stack相关详情。该命令支持Stack名称作为其主要参数,列举了服务副本在节点的分布情况,以及期望状态和当前状态。

  • docker stack rm 命令用于从Swarm集群中移除Stack。移除操作执行前并不会进行二次确认。

14.4 本章小结

Stack是Docker原生的部署和管理多服务应用的解决方案。Stack默认集成在Docker引擎中,并且提供了简单的声明式接口对应用进行部署和全生命周期管理。

在本章开始提供了应用代码以及一些基础设施需求,比如网络、端口、卷和密钥。接下来的内容完成了应用的容器化,并且将全部应用服务和基础设施需求集成到一个声明式的Stack文件当中。在Stack文件中设置了服务副本数、滚动升级以及重启策略。然后通过 docker stack deploy 命令基于Stack文件完成了应用的部署。

对于已部署应用的更新操作,应该通过修改Stack文件完成。首先需要从源码管理系统中检出Stack文件,更新该文件,然后重新部署应用,最后将改动后的Stack文件重新提交到源码控制系统中。

因为Stack文件中定义了像服务副本数这样的内容,所以读者需要自己维护多个Stack文件以用于不同的环境,比如dev、test以及prod。

你可能感兴趣的:(第14章 使用Docker Stack 部署应用)