前言

这篇文章介绍 Docker 生态中的常被提到的 Engine、Machine 和 Swarm,大家以了解为主,工作需要再深入。

Engine

Docker Engine 其实就是我们常说的「Docker」,它是一个 C/S 模型 (Client/Server) 的应用,包含如下组件:

  • Daemon。守护进程,属于 C/S 中的 Server (dockerd)
  • REST API。Daemon 向外暴露的 REST 接口
  • CLI。向外暴露的命令行接口 (Command Line API:docker)

客户端访问服务端的方式有三种:

  • 使用命令行工具,如 docker rundocker ps 等等
  • 直接通过调用 REST API,如发送一个 curl 请求
  • 通过脚本直接和 Daemon 交互

用户通过 Docker CLI 向 Docker Daemon 发送 REST API 请求。Daemon 创建和管理 Docker objects (对象),如:

  • images。镜像
  • containers。容器
  • networks。网络
  • volumes。数据卷

Machine

在没有 Machine 之前,Docker 的安装流程非常复杂,首先需要登录到相应的主机上,根据官方的安装和配置指南来安装 Docker,而且不同的操作系统的安装步骤也不同。

Machine 是一个简化 Docker Engine 安装和管理的命令行工具,这样通过一个简单的命令即可在相应的平台上安装 Docker,如本地 (macOS、Linux 和 Windows)、虚拟化平台 (VirtualBox、macOS xhyve 等),公有云 (AWS、Azure、Digital Ocean 等) 等。使用 Machine 可以启动、审查、停止和重新启动托管的宿主机、升级 Docker 客户端和守护程序、并配置 Docker 客户端与宿主机通信。

通过图示可以看出来,Machine 是管理本地 / 远程带有 Docker Engine 的主机的工具,docker-machine create创建的「machine」包含了 Daemon 和 REST API 这 2 部分,Machine 自己包含 CLI。

我大概演示一下使用 Machine 的方法:

  • 创建 machine。docker-machine create --driver virtualbox default,这就会可以创建一个叫做 default 的「机器」,它是 Virtualbox 类型的,所以可以在 Virtualbox 管理器里面看到这个虚拟机。
  • 连接到 machine。eval "$(docker-machine env default)",其实就是做了对应的环境变量设置
  • 使用 Docker CLI。之后就可以使用 docker ps 等命令查看和管理这个主机里面的镜像 / 容器等内容了

Swarm Mode

Docker Swarm 是 Docker 官方的集群管理和编排工具,可以将多个 Docker 主机封装为单个大型的虚拟 Docker 主机,成为一个容器平台。从Docker 1.12.0+开始Swarm Mode已经内嵌入 Docker Engine,成为子命令docker swarm可以直接使用。

Swarm Mode主要特性如下:

  • 具有容错能力的去中心化设计
  • 服务发现。可以做到 Docker 集群中节点的动态加入和退出的感知,支持主流的 etcd、consul 和 zookeeper。
  • 负载均衡。让资源分配负载,以达到最优化资源使用、最大化吞吐率、最小化响应时间、同时避免过载。
  • 动态伸缩。服务在判断监测指标超出所设定的上下限时,会按照你的设置触发扩容或缩容。
  • 滚动更新 (Rolling updates)。可以实现「零停机时间部署」,下节我们还会再说。
  • 安全传输。集群中的每个节点都执行 TLS 的相互认证和加密,以确保自己与所有其他节点之间的通信安全。
  • 确保期望状态。Swarm 会在后台进行轮训检查,确保实际状态能够满足期望状态的要求。

Swarm 是使用 SwarmKit 构建的 Docker 引擎内置(原生)的集群管理和编排工具。它包含如下三个重要概念:节点、服务和任务,我们挨个看。

节点 (Node)

运行 Docker 的主机可以主动初始化一个 Swarm 集群或者加入一个已存在的 Swarm 集群,这样这个运行 Docker 的主机就成为一个 Swarm 集群的节点。节点分为管理 (manager) 节点和工作 (worker) 节点。

管理节点用于 Swarm 集群的管理,管理节点分发工作单元 (称为 Task「任务」) 到工作节点。docker swarm命令基本只能在管理节点执行(节点退出集群命令docker swarm leave可以在工作节点执行)。一个 Swarm 集群可以有多个管理节点,但只有一个管理节点可以成为领导 (leader, 通过 Raft 协议实现)。管理节点还要通过 leader 执行维护集群所需状态所需的编排和集群管理任务。

工作节点接收并执行从管理节点分派的任务。默认情况下,管理器节点也作为工作节点运行服务 (Service),但是你可以将它们配置为专门运行管理器任务,并且只运行管理器节点。代理在每个工作节点上运行,并报告分配给它的任务。工作节点通知管理节点其分配任务的当前状态,以便管理器可以维护每个工作的所需状态。

swarm-diagram

任务 (Task)

任务是 Swarm 中的最小的调度单位,任务携带 Docker 容器和在容器中运行的命令。管理节点根据服务中设置的副本 (Replicas) 数量将任务分配给工作节点。一旦任务被分配给一个节点,它就不能移动到另一个节点。它只能在指定的节点上运行或失败

任务时这么调度的:

服务 (Service)

服务是指一组任务的集合,它定义了任务的属性,是 Swarm 系统的中心结构:

服务有两种模式:

  • Replicated services。按照一定规则在各个工作节点上运行指定个数的任务
  • Global services。每个可用节点上运行一个任务

PS: 两种模式通过docker service create--mode参数指定。

基本用法

我们创建一个最小的 Swarm 集群,包含一个管理节点和两个工作节点。创建 Docker 主机使用 Docker Machine:

# 管理节点
❯ docker-machine create -d virtualbox manager # 创建管理节点,名字为manager
❯ docker-machine ssh manager  # 登录到管理节点
docker@manager:~$ /sbin/ifconfig eth1 | grep 'inet addr' | cut -d: -f2 | awk '{print $1}'
192.168.99.104  # 获得IP
docker@manager:~$ docker swarm init --advertise-addr 192.168.99.104  # 在管理节点初始化一个Swarm集群,另外Docker主机有多个网卡,所以有多个IP,必须使用 --advertise-addr 指定IP
Swarm initialized: current node (k8cdrnrylrkxpsk4qoyybwpca) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-2gfzia6yrk172e8352jxpolevjr9gx1a9xu95hg0ibkhqat52p-d6j79273adew38s5mzmewgwoo 192.168.99.104:2377  # 记住这句命令

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
❯ docker-machine create -d virtualbox worker1 # 创建工作节点,名字worker1
❯ docker-machine ssh worker1
docker@worker1:~$ docker swarm join --token SWMTKN-1-2gfzia6yrk172e8352jxpolevjr9gx1a9xu95hg0ibkhqat52p-d6j79273adew38s5mzmewgwoo 192.168.99.104:2377  # 从上面直接粘贴来
This node joined a swarm as a worker.
❯ docker-machine create -d virtualbox worker2 # 创建工作节点,名字worker2
❯ docker-machine ssh worker2
docker@worker2:~$ docker swarm join --token SWMTKN-1-2gfzia6yrk172e8352jxpolevjr9gx1a9xu95hg0ibkhqat52p-d6j79273adew38s5mzmewgwoo 192.168.99.104:2377
This node joined a swarm as a worker.

这样集群就做好了,在管理节点看一下状态:

❯ docker-machine ssh manager
docker@manager:~$ docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
k8cdrnrylrkxpsk4qoyybwpca *   manager             Ready               Active              Leader              18.09.9
puzrum9ixbt6holgfk5d8aokl     worker1             Ready               Active                                  18.09.9
sqwpb00zh5fp9z2o6r7vnebbt     worker2             Ready               Active                                  18.09.9

然后部署 Nginx (只能在管理节点运行):

docker@manager:~$ docker service create --replicas 3 -p 80:80 --name nginx nginx:1.17.4-alpine  # 创建服务,三个副本,服务监听在80端口
pydkv5ffwfqiv1aua1ylmwhum
overall progress: 3 out of 3 tasks
1/3: running   [==================================================>]
2/3: running   [==================================================>]
3/3: running   [==================================================>]
verify: Service converged
docker@manager:~$ docker service ls # 查看当前Swarm集群运行的服务
ID                  NAME                MODE                REPLICAS            IMAGE                 PORTS
pydkv5ffwfqi        nginx               replicated          3/3                 nginx:1.17.4-alpine   *:80->80/tcp
# 现在我们使用浏览器,输入任意节点IP(如管理节点的192.168.99.104),都能看到nginx默认页面
docker@manager:~$ docker service logs nginx # 查看Nginx服务的日志
... # 省略输出
docker@manager:~$ docker service scale nginx=5 # 业务高峰期时,扩展服务运行的容器数量
nginx scaled to 5
overall progress: 5 out of 5 tasks
1/5: running   [==================================================>]
2/5: running   [==================================================>]
3/5: running   [==================================================>]
4/5: running   [==================================================>]
5/5: running   [==================================================>]
verify: Service converged
docker@manager:~$ docker service ps nginx # 查看服务详情
i6xkgnneqbbq        nginx.1             nginx:1.17.4-alpine   worker2             Running             Running 4 minutes ago
f517ogp705ne        nginx.2             nginx:1.17.4-alpine   manager             Running             Running 5 minutes ago
... # 省略输出
docker@manager:~$ docker service scale nginx=2 # 业务平稳期时,减少服务运行的容器数量

在 Swarm 集群中使用 Compose 文件

在第一篇中我使用docker-compose.yml一次配置、启动多个容器,在 Swarm 集群中也可以使用 compose 文件来配置、启动多个服务。在上一小节中,使用docker service create一次只能部署一个服务 (Nginx),使用docker-compose.yml可以一次启动多个关联的服务。还是拿 lyanna 项目的 docker-compose.yml 体验一下,不过需要修改配置项,添加 deploy (如 mode 和副本数等项),也要去掉一些 stack 不支持的项 (如 build、restart 等):

version: '3'
services:
  db:
    image: mysql
    environment:
      MYSQL_DATABASE: 'test'
      MYSQL_USER: 'root'
      MYSQL_PASSWORD: ''
      MYSQL_ROOT_PASSWORD: ''
      MYSQL_ALLOW_EMPTY_PASSWORD: 'true'
    volumes:
      - my-datavolume:/var/lib/mysql
    deploy:
      placement:
        constraints: [node.role == manager]
  redis:
    image: redis:alpine
    deploy:
       mode: replicated
       replicas: 3
  memcached:
    image: memcached:1.5-alpine
    deploy:
       mode: replicated
       replicas: 3
  web:
    image: 127.0.0.1:5000/lyanna-app
    build: .
    ports:
      - '8000:8000'
    volumes:
      - .:/app
      - ./local_settings.py.tmpl:/app/local_settings.py
    depends_on:
      - db
      - redis
      - memcached
    environment:
      PYTHONPATH: $PYTHONPATH:/usr/local/src/aiomcache:/usr/local/src/tortoise:/usr/local/src/arq:/usr/local/src
    command: sh -c './setup.sh && python app.py'
  visualizer:
    image: dockersamples/visualizer:stable
    ports:
      - "8080:8080"
    stop_grace_period: 1m30s
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
    deploy:
      placement:
        constraints: [node.role == manager]
volumes:
  my-datavolume:

其中有 3 个需要着重说明的点:

  • visualizer 服务提供一个可视化页面,可以从浏览器中很直观的查看集群中各个服务的运行节点,一会会感受到
  • db/redis/memcached/visualizer 这几项都添加了 deploy 项,mode 指服务模式,replicas 指副本数,placement 可以限定满足条件的 Node,而避免在不合适的 Node 进行部署,这里限制 db/visualizer 都要在管理节点上
  • web 加了 image 项,是一个注册服务的 API 地址,下面会看到如何注册和推送镜像上去

接着部署:

eval "$(docker-machine env manager)"  # 本机切换到管理主机
❯ git clone https://github.com/dongweiming/lyanna  # 下载lyanna项目源码,如果原来已经clone了不用再做cd lyanna
❯ docker service create --name registry --publish published=5000,target=5000 registry:2  # 创建注册服务(名字叫registry),启动在5000端口上
❯ docker-compose -f docker-compose.swarm.yml build  # 用Compose的方式启动,这个过程会构建lyanna-app镜像
❯ docker-compose -f docker-compose.swarm.yml push  # 把lyanna-app推送给本地的注册服务
❯ docker stack deploy -c docker-compose.swarm.yml lyanna # 部署服务
Ignoring unsupported options: build

Creating network lyanna_default
Creating service lyanna_web
Creating service lyanna_visualizer
Creating service lyanna_db
Creating service lyanna_redis
Creating service lyanna_memcached

现在在浏览器输入任一节点IP:8080即可看到各节点运行状态,类似如下效果:

可以看到:

  • db、visualizer 限定在管理节点 manager
  • redis/memcached 由于副本数和节点数一致,所以每个节点都有一个
  • web 和 registry 由于负载平衡的作用分配到了 2 个工作节点上,让服务整体看起来比较均衡

在浏览器新的标签页输入任一节点IP:8000即可看到 lyanna 博客效果!

延伸阅读

  1. https://docs.docker.com/engine/docker-overview/
  2. https://www.docker.com/products/container-runtime
  3. https://docs.docker.com/machine/
  4. https://docs.docker.com/engine/swarm/key-concepts/
  5. https://docs.docker.com/engine/swarm/how-swarm-mode-works/services/
  6. https://docs.docker.com/engine/swarm/stack-deploy/