深入浅出Docker 读书笔记(四)

                              第九章:Docker Compose部署应用

Docker Compose 与 Docker Stack 非常类似。它能够在 Docker 节点上,以单引擎模式(Single-Engine Mode)进行多容器应用的部署和管理。Docker Compose 并不是通过脚本和各种冗长的 docker 命令来将应用组件组织起来,而是通过一个声明式的配置文件描述整个应用,从而使用一条命令完成部署。应用部署成功后,还可以通过一系列简单的命令实现对其完整声明周期的管理。甚至,配置文件还可以置于版本控制系统中进行存储和管理。使用它时,首先编写定义多容器(多服务)应用的 YAML 文件,然后将其交由 docker-compose 命令处理,Docker Compose 就会基于 Docker 引擎 API 完成应用的部署。

Docker Compose 使用 YAML 文件来定义多服务的应用。YAML 是 JSON 的一个子集,因此也可以使用 JSON。默认使用文件名 docker-compose.yml。当然,也可以使用 -f 参数指定具体文件。以一个Compose示例,讲述两个服务(web-fe 和 redis)的小型 Flask 应用。这是一个能够对访问者进行计数并将其保存到 Redis的简单的 Web 服务。

version: "3.5"                                     一级 key:version 是必须指定的,且第一行定义文件格式主
services:                                            要是API版本非其他版本信息 services 用于定义不同的应用
   web-fe:                                           服务。两个服务:一个名为 web-fe ,一个名为 redis 的内存
       build: .                                        服务Docker Compose 会将每个服务部署在各自的容器中。
      command: python app.py            数据库,networks 用于指引Docker 创建新的网络。默认情
      ports:                                           况下Docker Compose 会创建 bridge网络。一种单主机网络
          - target: 5000                           只能够实现同一主机上容器连接。当然也可以使用 driver
             published: 5000                    属性来指定不同的网络类型。volumes 用于指引 Docker 来
      networks:                                     创建新的卷。例中定义了两个服务,一个名为 counter-net
         - counter-net                             的网络和一个名为 counter-vol 的卷。services 部分定义了
      volumes:                                      两个二级 key:web-fe 和 redis。它们各自定义了一个应用
    - type: volume                                程序服务。需要明确的是,Docker Compose 会将每个服务
       source: counter-vol                     部署为一个容器,并且会使用 key 作为容器名字的一部分
      target: /code                                 两个key:web-fe 和 redis。docker就起一个容器的名中会
     image: "redis:alpine"                     包含 web-fe,而另一个会包含 redis。web-fe 的服务定义中
        counter-net:                                build指定 Docker 基于当前目录(.)下Dockerfile中定义的指

networks:                                           令来构建一个新镜像。该镜像redis: 会被用于启动该服务的
    counter-net:                                   容器command python app.py指定Docker容器中执行名为

volumes:                                            app.py的Python脚本作为主程序ports指定Docker将容器内
    counter-vol:                                   (-target)的 5000 端口映射到主机(published)的 5000

端口。这意味着发送到 Docker 主机 5000 端口的流量会被转发到容器的 5000 端口。容器中的应用监听端口 5000。networks使得 Docker 可以将服务连接到指定的网络上这个网络应该是已经存在的或者是在networks 一级 key 中定义的网络。volumes指定 Docker 将 counter-vol 卷(source:)挂载到容器内的 /code(target:)。counter-vol 卷应该是已存在的,或者是在文件下方的 volumes 一级 key 中定义的,综上,Docker Compose 会调用 Docker 来为 web-fe 服务部署一个独立的容器。该容器基于与 Compose 文件位于同一目录下的 Dockerfile 构建的镜像。基于该镜像启动的容器会运行 app.py 作为其主程序,将 5000 端口暴露给宿主机,连接到 counter-net 网络上,并挂载一个卷到/code。从技术上讲,本例并不需要配置 command: python app.py。因为镜像的 Dockerfile 已经将 python app.py 定义为了默认的启动程序。但是,本例主要是为了展示其如何执行,因此也可用于覆盖 Dockerfile 中配置的 CMD 指令。redis 服务的定义相对比较简单。 image  redis:alpine 使得 Docker 可以基于 redis:alpine 镜像启动一个独立的名为 redis 的容器。这个镜像会被从 Docker Hub 上拉取下来。 networks配置 redis 容器连接到 counter-net网络。由于两个服务都连接到 counter-net网络,因此它们可以通过名称解析到对方的地址。本例中上层应用被配置为通过名称与 Redis 服务通信。

使用 Docker Compose 将应用启动起来。$ docker-compose up &   如果 Compose 文件是其他文件名,则需要通过 -f 参数来指定。如下命令会基于名为 prod-equus-bass.yml 的 Compose 文件部署应用。$ docker-compose -f prod-equus-bass.yml up使用 -d 参数在后台启动应用也是常见的用法,代码如下。docker-compose up -d   或docker-compose -f prod-equus-bass.yml up -d前面的示例命令在前台启动应用(没有使用 -d 参数),但是使用了 & 将终端窗口返回。这种用法不太正规,所有的日志还是会直接输出到我们后续可能会用的终端窗口上。既然应用已经启动,使其停止将子命令 up 替换成 down 即可。在关闭应用时需要特别注意的是,counter-vol 卷并没有被删除,因为卷应该是用于数据的长期持久化存储的。因此,卷的生命周期是与相应的容器完全解耦的。执行 docker volume ls 可见该卷依然存在于系统中。写到卷上的所有数据都会保存下来。同样,执行 docker-compose up 过程中拉取或构建的镜像也会保留在系统中。因此再次部署该应用将更加快捷。

使用 docker-compose up 命令来查看应用的状态。$ docker-compose ps    输出中会显示容器名称、其中运行的 Command、当前状态以及其监听的网络端口。使用 docker-compose top 命令列出各个服务(容器)内运行的进程,结果中的 PID 编号是在 Docker 主机上(而不是容器内)的进程 ID。docker-compose stop 命令会停止应用,但并不会删除资源。对于已停止的 Compose 应用,可以使用 docker-compose rm 命令来删除。这会删除应用相关的容器和网络,但是不会删除卷和镜像。当然,也不会删除应用源码。执行 docker-compose restart 命令重启应用。使用 docker-compose down 这一个命令就可以停止和关闭应用。应用被删除,仅留下了镜像、卷和源码。当第一次部署该应用的时候,Docker Compose 会检查是否有同名的卷存在。如果不存在,则会创建它。也可使用 docker volume ls 命令手动查看。Docker Compose 会在部署服务之前创建网络和卷。这很合理,因为它们是供服务(容器)使用的底层基础资源。如下可见,Docker Compose 会首先创建网络和卷(甚至先于构建和拉取镜像)。$ docker-compose up -d
Creating network "counterapp_counter-net" with the default driver
Creating volume "counterapp_counter-vol" with default driver
Pulling redis (redis:alpine)...
<Snip>               

再次研读 Dockerfile 中关于 web-fe 服务的定义,会看到它将卷 counter-app 挂载到容器的 /code 目录。还会发现,/code 正是应用安装和执行的目录。由此可知,应用的代码是位于 Docker 卷中的,如下图所示。

应用的代码位于Docker卷中

这意味着,我们在 Docker 主机对卷中文件的修改,会立刻反应到应用中。

                                      第十章:Docker Swarm

Swarm 是Docker官方提供的一款集群管理工具,其主要作用是把若干台 Docker 主机抽象为一个整体,并且通过一个入口统一管理这些 Docker 主机上的各种 Docker 资源。Swarm 和 Kubernetes 比较类似,但是更加轻,具有的功能也较 kubernetes 更少一些。Docker Swarm 包含两方面:一个企业级的 Docker 安全集群,以及一个微服务应用编排引擎。
Swarm 默认内置有加密的分布式集群存储(encrypted distributed cluster store)、加密网络(Encrypted Network)、公用TLS(Mutual TLS)、安全集群接入令牌 Secure Cluster Join Token)以及一套简化数字证书管理的 PKI(Public Key Infrastructure)。我们可以自如地添加或删除节点。
编排方面,Swarm 提供了一套丰富的 API 使得部署和管理复杂的微服务应用变得易如反掌。通过将应用定义在声明式配置文件中,就可以使用原生的 Docker 命令完成部署。此外,甚至还可以执行滚动升级、回滚以及扩缩容操作,同样基于简单的命令即可完成。从集群角度来说,一个 Swarm 由一个或多个 Docker 节点组成。这些节点可以是物理服务器、虚拟机、树莓派(Raspberry Pi)或云实例。唯一的前提就是要求所有节点通过可靠的网络相连。节点会被配置为管理节点(Manager)或工作节点(Worker)。管理节点负责集群控制面(Control Plane),进行诸如监控集群状态、分发任务至工作节点等操作。工作节点接收来自管理节点的任务并执行。Swarm 的配置和状态信息保存在一套位于所有管理节点上的分布式 etcd 数据库中。该数据库运行于内存中,并保持数据的最新状态。关于该数据库最棒的是,它几乎不需要任何配置,作为 Swarm 的一部分被安装,无须管理。关于集群管理,最大的挑战在于保证其安全性。搭建 Swarm 集群时将不可避免地使用 TLS,因为它被 Swarm 紧密集成。
关于应用编排,Swarm 中的最小调度单元是服务。它是随 Swarm 引入的,在 API 中是一个新的对象元素,它基于容器封装了一些高级特性,是一个更高层次的概念。当容器被封装在一个服务中时,我们称之为一个任务或一个副本,服务中增加了诸如扩缩容、滚动升级以及简单回滚等特性。从概括性的视角来看 Swarm,如下图所示。

从概括性的视角看Swarm

实例中包含 3 个管理节点和 3 个工作节点,可以根据需要自行调整管理节点和工作节点的数量、名称和 IP。每个节点都需要安装 Docker,并且能够与 Swarm 的其他节点通信。在网络方面,需要在路由器和防火墙中开放如下端口。

  • 2377/tcp:用于客户端与 Swarm 进行安全通信。
  • 7946/tcp 与 7946/udp:用于控制面 gossip 分发。
  • 4789/udp:用于基于 VXLAN 的覆盖网络。

搭建 Swarm 的过程有时也被称为初始化 Swarm,大体流程包括初始化第一个管理节点 -> 加入额外的管理节点 -> 加入工作节点 -> 完成。1)初始化Swarm 不包含在任何 Swarm 中的 Docker 节点,称为运行于单引擎(Single-Engine)模式。一旦被加入 Swarm 集群,则切换为 Swarm 模式,在单引擎模式下的 Docker 主机上运行 docker swarm init 会将其切换到 Swarm 模式,并创建一个新的 Swarm,将自身设置为 Swarm 的第一个管理节点。1) 登录到 mgr1 并初始化一个新的 Swarm如果在 Windows 的 PowerShell 终端执行如下命令的话,不要忘了将反斜杠替换为反引号。

$ docker swarm init \                       该命令会通知 Docker 来初始化一个新的管理与开启Swarm
--advertise-addr 10.0.0.1:2377 \      并将自身设置为第一个管理节点--advertise-addr 指定其他
--listen-addr 10.0.0.1:2377             节点用来连接到当前管理节点的 IP 和端口,可指定IP。
指定用于承载 Swarm 流量的 IP 和端口其设置通常与 --advertise-addr 相匹配Swarm 模式下的操作默认运行于 2337 端口。虽然它是可配置的,但 2377/tcp 是用于客户端与 Swarm 进行安全(HTTPS)通信的约定俗成的端口配置。2) 列出 Swarm 中的节点。$ docker node ls        3) 在 mgr1 上执行 docker swarm join-token 命令,该命令用来获取添加新的工作节点和管理节点到 Swarm 的命令和 Token。$ docker swarm join-token worker(manager) 4) 登录到 wrk1,并使用包含工作节点接入 Token 的 docker swarm join 命令将其接入 Swarm。

$ docker swarm join \                                        工作节点和管理节点的接入命令中使用的接入
--token SWMTKN-1-0uahebax...c87tu8dx2c \  Token(SWMTKN...) 是不同的。依赖于使用了
10.0.0.1:2377 \                                                  哪个 Token这是将一个节点加入 Swarm 的唯
--advertise-addr 10.0.0.4:2377 \                        一所需!--advertise-addr与--listen-addr属性
--listen-addr 10.0.0.4:2377                                是可选的。在网络配置方面请尽量明确指定

相关参数每次将节点加入 Swarm 都指定 --advertise-addr 与 --listen-addr 属性是痛苦的。一旦 Swarm 中的网络配置出现问题将会更加痛苦,且手动加入也不是一种日常操作,所以在执行该命令时额外指定这两个属性是值得的。

Swarm管理器高可用性(HA): 管理节点内置有对 HA 的支持。这意味着,即使一个或多个节点发生故障,剩余管理节点也会继续保证 Swarm 的运转。从技术上来说,Swarm 实现了一种主从方式的多管理节点的 HA。这意味着,即使你可能有多个管理节点,也总是仅有一个节点处于活动状态。通常处于活动状态的管理节点被称为“主节点”(leader),而主节点也是唯一一个会对 Swarm 发送控制命令的节点。也就是说,只有主节点才会变更配置,或发送任务到工作节点。如果一个备用(非活动)管理节点接收到了 Swarm 命令,则它会将其转发给主节点。这一过程如下图所示。步骤 ① 指命令从一个远程的 Docker 客户端发送给一个管理节点;步骤 ② 指非主节点将命令转发给主节点;步骤 ③ 指主节点对 Swarm 执行命令。

Swarm的高可用(HA)管理


仔细观察上图会发现,管理节点或者是 Leader 或者是 Follower。这是 Raft 的术语,因为 Swarm 使用了 Raft 共识算法的一种具体实现来支持管理节点的HA。关于 HA,有以下两条最佳实践原则。1)部署奇数个管理节点。因为部署奇数个管理节点有利于减少脑裂(Split-Brain)情况的出现机会。假如有 4 个管理节点,当网络发生分区时,可能会在每个分区有两个管理节点。这种情况被称为脑裂。每个分区都知道曾经有 4 个节点,但是当前网络中仅有两个节点,糟糕的是,每个分区都无法知道其余两个节点是否运行,也无从得知本分区是否掌握大多数(Quorum)。如果部署有 3 个或 5 个管理节点,并且也发生了网络分区,就不会出现每个分区拥有同样数量的管理节点的情况。2)不要部署太多管理节点(建议 3 个或 5 个)。因为对于所有的共识算法来说,更多的参与节点就意味着需要花费更多的时间来达成共识。将管理节点分布到不同的可用域(Availability Zone)中是一种不错的实践方式,但是一定要确保它们之间的网络连接是可靠的,否则由于底层网络分区导致的问题将是令人痛苦的。

内置Swarm的安全机制: 集群内置有繁多的安全机制,并提供了开箱即用的合理的默认配置——如 CA 设置、接入 Token、公用 TLS、加密集群存储、加密网络、加密节点 ID 等。尽管内置有如此多的原生安全机制,重启一个旧的管理节点或进行备份恢复仍有可能对集群造成影响。一个旧的管理节点重新接入 Swarm 会自动解密并获得 Raft 数据库中长时间序列的访问权,这会带来安全隐患。进行备份恢复可能会抹掉最新的 Swarm 配置。为了规避以上问题,Docker 提供了自动锁机制来锁定 Swarm,这会强制要求重启的管理节点在提供一个集群解锁码之后才有权从新接入集群。通过在执行 docker swarm init 命令来创建一个新的 Swarm 集群时传入 --autolock 参数可以直接启用锁。然而,前面已经搭建了一个 Swarm 集群,这时也可以使用 docker swarm update 命令来启用锁。在某个 Swarm 管理节点上运行如下命令。$ docker swarm update --autolock=true 就会生成解锁码,重启某一个管理节点,以便观察其是否能够自动重新接入集群。  $ service docker restart     尝试列出Swarm中的节点。$ docker node ls  (Error response from daemon: Swarm is encrypted and needs to be unlocked)尽管 Docker 服务已经重启,该管理节点仍然未被允许重新接入集群。执行 docker swarm unlock 命令来为重启的管理节点解锁 Swarm。该命令需要在重启的节点上执行,同时需要提供解锁码。$ docker swarm unlock
Please enter unlock key: <enter your key>该节点将被允许重新接入 Swarm,并且再次执行 docker node ls 命令会显示 ready 和 reachable。
Docker服务:使用服务仍能够配置大多数熟悉的容器属性,比如容器名、端口映射、接入网络和镜像。此外还增加了额外的特性,比如可以声明应用服务的期望状态,将其告知 Docker 后,Docker 会负责进行服务的部署和管理。使用 docker service create 命令创建一个新的服务。如$ docker service create --name web-fe \         该命令与熟悉的 docker container run 命令的许多参数是相同的使用  
-p 8080:8080 \      使用 --name 和 -p 定义服务的方式与单机启动容器的定义方式是一样的。将每个节点上的 8080
--replicas 5 \          端口映射到服务副本内部的 8080 端口--replicas 参数告知 Docker 应该总是有 5 个此服务的副本
nigelpoulton/pluralsight-docker-ci          告知 Docker 哪个镜像用于副本
主管理节点会在 Swarm 中实例化 5 个副本,管理节点也会作为工作节点运行。相关各工作节点或管理节点会拉取镜像,然后启动一个运行在 8080 端口上的容器。所有的服务都会被 Swarm 持续监控,Swarm 会在后台进行轮训检查(Reconciliation Loop),来持续比较服务的实际状态和期望状态是否一致。如果一致,则无须任何额外操作;如果不一致,Swarm 会使其一致。换句话说,Swarm 会一直确保实际状态能够满足期望状态的要求,比如运行有 web-fe 副本的某个工作节点宕机了,则 web-fe 的实际状态从 5 个副本降为 4 个,Docker 便会启动一个新的 web-fe 副本来使实际状态与期望状态保持一致。 1) 查看服务 docker service ls查看 Swarm 中所有运行中的服务。输出显示有一个运行中的服务及其相关状态信息。比如可以了解服务的名称,以及 5 个期望的副本(容器)中有 5 个是运行状态。执行 docker service ps命令可以查看服务副本列表及各副本的状态。docker service inspect查看服务更为详细的信息如$ docker service inspect --pretty web-fe  使用--pretty 参数,限制输出中仅包含最感兴趣的内容,并以易于阅读的格式打印出来。不加 --pretty 的话会给出更加详尽的输出。2) 副本服务 vs 全局服务。服务的默认复制模式(Replication Mode)是副本模式(replicated)。这种模式会部署期望数量的服务副本,并尽可能均匀地将各个副本分布在整个集群中。另一种模式是全局模式(global),在这种模式下,每个节点上仅运行一个副本。可以通过给 docker service create 命令传递 --mode global 参数来部署一个全局服务。3) 服务的扩缩容服务的另一个强大特性是能够方便地进行扩缩容。如$ docker service scale web-fe=10服务副本数由 5 个增加到 10 个。后台会将服务的期望状态从 5 个增加到 10 个,在底层实现上,Swarm 执行了一个调度算法,默认将副本尽量均衡分配给 Swarm 中的所有节点。各节点分配的副本数是平均分配的,并未将 CPU 负载等指标考虑在内。4) 删除服务  如下docker service rm(服务)命令可用于删除之前部署的服务。5) 滚动升级:对部署的应用进行滚动升级是常见的操作。为了演示如何操作,下面将部署一个新的服务。但是在此之前,先创建一个新的覆盖网络(Overlay Network)给服务使用。这并非必须的操作,只是希望能够让大家了解如何创建网络并将服务接入网络。$ docker network create -d overlay uber-net     该命令会创建一个名为 uber-net 的覆盖网络,覆盖网络是一个二层网络,容器可以接入该网络,并且所有接入的容器均可互相通信。即使这些容器所在的 Docker 主机位于不同的底层网络上,该覆盖网络依然是相通的。本质上说,覆盖网络是创建于底层异构网络之上的一个新的二层容器网络。如下图所示,两个底层网络通过一个三层交换机连接,而基于这两个网络之上是一个覆盖网络。

两个底层网络通过一个三层交换机连接

Docker 主机通过两个底层网络相连,而容器则通过覆盖网络相连。对于同一覆盖网络中的容器来说,即使其各自所在的 Docker 主机接入的是不同的底层网络,也是互通的。执行docker newwork ls查看网络下面创建一个新的服务,并将其接入 uber-net 网络。

$ docker service create --name uber-svc \    服务命名为 uber-svc,并用 --network 参数声明所
--network uber-net \                                       有的副本都连接到 uber-net 网络。且在整个swarm
-p 80:80 --replicas 12 \                                  中将 80 端口暴露出来,并将其映射到 12 个容器副
nigelpoulton/tu-demo:v1                                本的 80 端口。最后声明所有的副本都基于 nigelpoulton/tu-demo:v1 镜像,通过对服务声明 -p 80:80 参数,会建立 Swarm集群范围的网络流量映射,到达 Swarm 任何节点 80 端口的流量,都会映射到任何服务副本的内部 80 端口。默认的模式是在 Swarm 中的所有节点开放端口,称为入站模式(Ingress Mode)此外还有主机模式(Host Mode)即仅在运行有容器副本的节点上开放端口。以主机模式开放服务端口与上一命令差别在于--publish published=80,target=80,mode=host \  即使某个节点上并未运行服务的副本,依然可以进入该页面,所有节点都配置有映射,因此会将请求转发给运行有服务副本的节点。升级镜像替换,那么就可以采用如下的docker service update来完成。如下

$ docker service update \                   升级是通过变更该服务期望状态的方式来更新运行
--image nigelpoulton/tu-demo:v2 \     中的服务。指定了 tag 为 v2 的新镜像接下来用
--update-parallelism 2 \                      --update-parallelism 和 --update-delay 参数声明每
--update-delay 20s uber-svc              次使用新镜像更新两个副本,其间有 20s 的延迟。最终,告知 Docker 以上变更是对 uber-svc 服务展开的。如果在更新操作完成前打开浏览器,使用 Swarm 中任一节点的 IP 进入页面,并多次单击刷新按钮,就会看到滚动更新的效果。

服务的日志:docker service log来查看服务日志,然而并非所有的日志驱动(Logging Driver)都支持该命令。Docker 节点默认的配置是,服务使用 json-file 日志驱动,其他的驱动还有 journald(仅用于运行有 systemd 的Linux主机)、syslog、splunk 和 gelf。json-file 和 journald 是较容易配置的,二者都可用于docker service logs命令。docker service logs <service-name>   若使用第三方日志驱动,那么就需要用相应日志平台的原生工具来查看日志。如在 daemon.json 配置文件中定义使用 syslog 作为日志驱动。{   "log-driver": "syslog"}通过在执行 docker service create 命令时传入 --logdriver 和 --log-opts 参数可以强制某服务使用一个不同的日志驱动,这会覆盖 daemon.json 中的配置。服务日志能够正常工作的前提是,容器内的应用程序运行于 PID 为 1 的进程,并且将日志发送给 STDOUT,错误信息发送给 STDERR。日志驱动会将这些日志转发到其配置指定的位置。对于查看日志命令,可以使用 --follow 进行跟踪、使用 --tail 显示最近的日志,并使用 --details 获取额外细节。Docker Swarm 经常用到的命令做了一下总结,

                   命令                                                       说明
docker swarm init 用于创建一个新的 Swarm。执行该命令的节点会成为第一个管理节点,并且会切换到 Swarm 模式。
docker swarm join-token 用于查询加入管理节点和工作节点到现有 Swarm 时所使用的命令和 Token。
要获取新增管理节点的命令,请执行 docker swarm join-token manager 命令;
要获取新增工作节点的命令,请执行 docker swarm join-token worker 命令。
docker node ls 用于列出 Swarm 中的所有节点及相关信息,包括哪些是管理节点、哪个是主管理节点。
docker service create 用于创建一个新服务。
docker service ls 用于列出 Swarm 中运行的服务,以及诸如服务状态、服务副本等基本信息。
docker service ps <service> 该命令会给出更多关于某个服务副本的信息
docker service inspect 用于获取关于服务的详尽信息。附加 --pretty 参数可限制仅显示重要信息。
docker service scale 用于对服务副本个数进行增减。
docker service update 用于对运行中的服务的属性进行变更。
docker service logs 用于查看服务的日志。
docker service rm 用于从 Swarm 中删除某服务。该命令会在不做确认的情况下删除服务的所有副本,所以使用时应保持警惕。
发布了105 篇原创文章 · 获赞 86 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/dingyahui123/article/details/104302854