Docker Swarm(二)实战篇

前言

此篇博文是笔者所总结的 Docker 系列之一;

本文为作者的原创作品,转载需注明出处;本文采用是 Docker Engine 1.12;

本文通过实战的角度描述如何创建、管理和维护一个 Swarm 集群,包括如何创建、删除、动态扩容以及维护 Docker Service 等;

初始化 Swarm 并创建 Manager Node,

  1. 创建 Swarm Manager Node manager1

    1
    $ docker-machine create --driver virtualbox manager1

    笔者通过 virtualbox 在本地创建了一个名为 manager1 的 Docker Engine;

  2. 查看 manager1 的 IP 地址

    1
    2
    $ docker-machine ip manager1
    192.168.99.100
  3. 使用 docker-machine ssh 命令登录 manager1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    $ docker-machine ssh manager1
    ## .
    ## ## ## ==
    ## ## ## ## ## ===
    /"""""""""""""""""\___/ ===
    ~~~ {~~ ~~~~ ~~~ ~~~~ ~~~ ~ / ===- ~~~
    \______ o __/
    \ \ __/
    \____\_______/
    _ _ ____ _ _
    | |__ ___ ___ | |_|___ \ __| | ___ ___| | _____ _ __
    | '_ \ / _ \ / _ \| __| __) / _` |/ _ \ / __| |/ / _ \ '__|
    | |_) | (_) | (_) | |_ / __/ (_| | (_) | (__| < __/ |
    |_.__/ \___/ \___/ \__|_____\__,_|\___/ \___|_|\_\___|_|
    Boot2Docker version 18.01.0-ce, build HEAD : 0bb7bbd - Thu Jan 11 16:32:39 UTC 2018
    Docker version 18.01.0-ce, build 03596f5
    docker@manager1:~$

    备注,可以通过 exit 命令退出;

  4. 使用如下命令创建 docker swarm,

    1
    docker@manager1:~$ docker swarm init --advertise-addr 192.168.99.100
    --advertise-addr 参数将会把当前 manager1 节点的网络接口 192.168.99.100 注册为 swarm manager 的公共接入的接口( 官文将该接口称作 advertised address ),明确指定的意义在于,通常在云主机的环境下往往有多个网络接口地址,通常情况下有内网和外网网络接口之分,如果 docker swarm 的节点都在内网中,那么指定内网接口地址即可,但是如果 docker swarm 是跨网络或者跨网段的,那么就需要指定外网接口地址;如果当前主机只有一个网络接口地址,可以不使用 --advertise-addr 参数,这样,docker swarm init 命令将会默认使用该网络接口地址既 IP 地址;具体内容将会在后续介绍 swarm mode 的文章中进行介绍;上述命令执行成功以后输出如下日志,
    1
    2
    3
    4
    5
    6
    7
    Swarm initialized: current node (8fgy70pcpdotl092hj7c1j5uq) is now a manager.

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

    docker swarm join --token SWMTKN-1-4t6shqfenq4v54wgso5duoe9lj33ivifghcktqk0l7a7hh0h6g-cj8g09fcai9zefxxjxdyv48xq 192.168.99.100:2377

    To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
  5. 检查 swarm 的当前状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    docker@manager1:~$ docker info
    Containers: 0
    Running: 0
    Paused: 0
    Stopped: 0
    Images: 0
    Server Version: 18.01.0-ce
    Storage Driver: aufs
    Root Dir: /mnt/sda1/var/lib/docker/aufs
    .....
    Swarm: active
    NodeID: 8fgy70pcpdotl092hj7c1j5uq
    Is Manager: true
    ClusterID: u8k2jgq5pr9f9zct1zfwfnev0
    Managers: 1
    Nodes: 1
    Orchestration:
    Task History Retention Limit: 5

    Containers 表示当前宿主机上的 container 数量;重点在 Swarm,active 状态,且 true 表示当前节点是 Manager;

  6. 检查 swarm 集群中的当前节点有哪些,

    1
    2
    3
    docker@manager1:~$ docker node ls
    ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
    8fgy70pcpdotl092hj7c1j5uq * manager1 Ready Active Leader

    可以看到,当前的 docker host 是 manager 节点并且是 Leader,在后续的文章中笔者将会介绍如何搭建高可用的 manager 集群;

创建三个 swarm workers

注意,workers 必须通过 manager node 才能加入 swarm 集群中;

  1. 创建 worker1 docker engine 并使其加入 swarm 集群中

    • 创建 worker1

      1
      $ docker-machine create --driver virtualbox worker1
    • 加入 swarm 集群中
      首先,登录,

      1
      $ docker-machine ssh worker1

      其次,使用创建 manager1 的时候所生成的 token 加入 swarm 集群中,

      1
      2
      3
      docker@worker1:~$ docker swarm join \
      --token SWMTKN-1-4t6shqfenq4v54wgso5duoe9lj33ivifghcktqk0l7a7hh0h6g-cj8g09fcai9zefxxjxdyv48xq \
      192.168.99.100:2377

      执行成功后返回如下日志,

      1
      This node joined a swarm as a worker.

      备注,在加入 swarm 的时候忘记了 token 的话,可以使用如下的命令在 manager1 上执行,

      1
      2
      3
      4
      docker@manager1:~$ docker swarm join-token worker
      To add a worker to this swarm, run the following command:

      docker swarm join --token SWMTKN-1-4t6shqfenq4v54wgso5duoe9lj33ivifghcktqk0l7a7hh0h6g-cj8g09fcai9zefxxjxdyv48xq 192.168.99.100:2377

      可见,命令会返回 token;

  2. 创建 worker2worker3 docker engines,并分别加入 swarm 集群中,
    参照与 worker1 相同的步骤创建 worker2 即可,这里不再赘述;

  3. 再次查看 docker swarm 的状态,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    docker@manager1:~$ docker info
    Containers: 0
    Running: 0
    Paused: 0
    Stopped: 0
    Images: 0
    ....
    Swarm: active
    NodeID: 8fgy70pcpdotl092hj7c1j5uq
    Is Manager: true
    ClusterID: u8k2jgq5pr9f9zct1zfwfnev0
    Managers: 1
    Nodes: 4
    Orchestration:
    Task History Retention Limit: 5

    备注,如果要查看 docker swarm 的完整信息,还是需要登录到 manager node 上查看;

  4. 最后,检查 swarm 集群中的当前节点有哪些,
    1
    2
    3
    4
    5
    6
    docker@manager1:~$ docker node ls
    ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
    8fgy70pcpdotl092hj7c1j5uq * manager1 Ready Active Leader
    70ygvee033wip4a9oiotljvde worker1 Ready Active
    pgu9ejp9n3wwcrmw9lsecvryd worker2 Ready Active
    0dsvrceup4qiu8s23z1ns6560 worker3 Ready Active

创建 Service

  1. ssh 到 manager1,

    1
    $ docker-machine ssh manager1
  2. 创建并部署一个 Alpine Liunx Container,并执行 ping 命令,

    1
    2
    3
    4
    5
    docker@manager1:~$ docker service create --replicas 1 --name helloworld alpine ping docker.com
    xn2br4fq6sfg6iasyqhr26qtm
    overall progress: 1 out of 1 tasks
    1/1: running [==================================================>]
    verify: Service converged
    • docker service create命令表示创建一个 service;
    • --name指定 service 的名字为 helloworld;
    • --replicas指定有多少个实例执行,如果某个 Service 异常退出,那么 swarm 会再部署一个 Service,也就是说,容器内部会保证有这么多的实例在运行;
    • 参数alpine ping docker.com表示使用 Alpine Linux container 并执行 ping 命令;
  3. 查看 services 的状态,
    1
    2
    3
    docker@manager1:~$ docker service ls
    ID NAME MODE REPLICAS IMAGE PORTS
    xn2br4fq6sfg helloworld replicated

检查 Service

  1. ssh 到 manager1,
  2. 使用命令 docker service inspect --pretty <SERVICE-ID> 来查看某个 Service 的详细信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    docker@manager1:~$ docker service inspect --pretty helloworld

    ID: xn2br4fq6sfg6iasyqhr26qtm
    Name: helloworld
    Service Mode: Replicated
    Replicas: 1
    Placement:
    UpdateConfig:
    Parallelism: 1
    On failure: pause
    Monitoring Period: 5s
    Max failure ratio: 0
    Update order: stop-first
    RollbackConfig:
    Parallelism: 1
    On failure: pause
    Monitoring Period: 5s
    Max failure ratio: 0
    Rollback order: stop-first
    ContainerSpec:
    Image: alpine:latest@sha256:7df6db5aa61ae9480f52f0b3a06a140ab98d427f86d8d5de0bedab9b8df6b1c0
    Args: ping docker.com
    Resources:
    Endpoint Mode: vip

    去掉 --pretty,则返回的是 JSON 格式的数据,

  3. 使用命令 docker service ps <SERVICE-ID> 可以查看是哪个节点在执行,

    1
    2
    3
    docker@manager1:~$ docker service ps helloworld
    ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
    4vr324ta3xls helloworld.1 alpine:latest worker1 Running Running about an hour ago

    可以看到,是节点 worker1 在执行 ping 任务;不过默认情况下,manager 节点同样也是 worker 节点,所以,如果你看到 ping 任务是在 manager 节点上执行,不要大惊小怪了;

  4. 登录 worker1 节点通过命令 docker ps 查看该节点上 docker container 执行的情况,
    1
    2
    3
    docker@worker1:~$ docker ps
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    2de1df24ba1d alpine:latest "ping docker.com" About an hour ago Up About an hour helloworld.1.4vr324ta3xlsv1nky741arfw0

Service 扩容

  1. ssh 到 manager1,
  2. 通过下面这个标准命令格式进行扩容,

    1
    docker@manager1:~$ docker service scale <SERVICE-ID>=<NUMBER-OF-TASKS>

    比如,我们可以将 helloworld 服务扩展成 5 个,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    docker@manager1:~$ docker service scale helloworld=5

    helloworld 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 [==================================================>]
  3. 检查 Service 的执行状态,

    1
    2
    3
    4
    5
    6
    7
    docker@manager1:~$ docker service ps helloworld
    ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
    4vr324ta3xls helloworld.1 alpine:latest worker1 Running Running about an hour ago
    jforfe7hrv2g helloworld.2 alpine:latest worker2 Running Running about a minute ago
    la3odi3ch7hw helloworld.3 alpine:latest worker3 Running Running 2 minutes ago
    sz3qq4ldz5do helloworld.4 alpine:latest worker3 Running Running 2 minutes ago
    ja9hzhwqx82u helloworld.5 alpine:latest manager1 Running Running 2 minutes ago

    这样,我们就将 ping 任务扩展成了 5 个任务,注意,你看到了 manager1 节点也作为 worker 在执行 ping 任务了;写到这里,笔者深深的体会到了 docker 容器的强大之处了;

  4. 下面,我们通过 docker ps 命令看看 manager1 上的 Service 的执行状态
    1
    2
    3
    docker@manager1:~$ docker ps
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    a3940885f319 alpine:latest "ping docker.com" 5 minutes ago Up 5 minutes helloworld.5.ja9hzhwqx82uzoo6g7zo134dm

删除 Service

  1. ssh 到 manager1
  2. 使用如下命令删除 helloworld service,

    1
    2
    docker@manager1:~$ docker service rm helloworld
    helloworld
  3. 通过 inspect 或者 docker service ps 命令可以查看到 helloworld service 的确已经被删除,

    1
    2
    3
    docker@manager1:~$ docker service inspect helloworld
    []
    Status: Error: no such service: helloworld, Code: 1

    或者使用 docker service ps

    1
    2
    docker@manager1:~$ docker service ps helloworld
    no such service: helloworld
  4. 但是容器退出仍然需要一点时间,比如当删除以后,立即在 manager1 节点上执行 docker ps

    1
    2
    3
    docker@manager1:~$ docker ps
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    a3940885f319 alpine:latest "ping docker.com" 10 minutes ago Up 10 minutes helloworld.5.ja9hzhwqx82uzoo6g7zo134dm

    可以看到 docker 容器依然在执行,不过,稍等一会儿,

    1
    2
    docker@manager1:~$ docker ps
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

滚动更新

在这个例子中,我们将首先部署三个基于 Redis 3.0.6 镜像的服务(Service),然后,通过滚动更新的方式将现有服务升级为 Redis 3.0.7 镜像的服务;

  1. ssh 到 manager1,
  2. 在 Swarm 中部署 3 个 Redis 3.0.6 Service,并设置 10 秒的更新延迟,

    1
    2
    3
    4
    5
    $ docker service create \
    --replicas 3 \
    --name redis \
    --update-delay 10s \
    redis:3.0.6

    执行结果如下,

    1
    2
    3
    4
    5
    y3jmdpdcp8qqz4b87c348at3r
    overall progress: 3 out of 3 tasks
    1/3: running [==================================================>]
    2/3: running [==================================================>]
    3/3: running [==================================================>]

    --update-delay,该参数表示在各个 udpate 之间停留多长的时间,时间可以设置为 10m,10h 或者 10m30s;也就是说,如果每次只更新一个 Service,那么等该 Service 更新成功以后,将会等待 10 秒后开始下一次的更新;

    默认情况下,每次只会更新一个 task(或者叫做 service),不过可以通过指定 --update-parallelism 指定一次可以同步更新多个 task;

    默认情况下,当调度器在更新某个 task 的时候返回状态 RUNNING,那么调度器会跳过当前的 task 转而对另外的 task 进行更新直到所有的 task 都更新完毕;不过,要注意的是,如果在任何时候,当更新一个 task 返回的状态是 FAILED,调度器将会停止;不过你可以通过设置参数 –update-failure-action 来针对该错误设置回调措施,该参数可以用在 docker service create 或者 docker service update 上来处理错误回调。

  3. 检查已安装的 redis service,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    docker@manager1:~$ docker service inspect --pretty redis

    ID: y3jmdpdcp8qqz4b87c348at3r
    Name: redis
    Service Mode: Replicated
    Replicas: 3
    Placement:
    UpdateConfig:
    Parallelism: 1
    Delay: 10s
    On failure: pause
    Monitoring Period: 5s
    Max failure ratio: 0
    Update order: stop-first
    RollbackConfig:
    Parallelism: 1
    On failure: pause
    Monitoring Period: 5s
    Max failure ratio: 0
    Rollback order: stop-first
    ContainerSpec:
    Image: redis:3.0.6@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842
    Resources:
    Endpoint Mode: vip
  4. 现在你可以通过指定的 image 来更新 redis,swarm manager 会根据 UpdateConfig 的配置策略来进行更新,

    1
    2
    3
    4
    5
    6
    docker@manager1:~$ docker service update --image redis:3.0.7 redis
    redis
    overall progress: 1 out of 3 tasks
    1/3: running [==================================================>]
    2/3: preparing [=================================> ]
    3/3:

    该更新会根据默认的操作步骤执行,

    • 停止第一个 task,
    • 对该停止的 task 进行更新操作,
      从上面执行的 log 中可以看出,次更新操作是一个接一个的,且第一个更新成功以后等待 10 秒对下一个进行更新;
    • 更新完成以后,启动新的 task container;
    • 如果对某个 task 启动更新操作返回 RUNNING 状态,那么会等待一段时间然后启动对下一个 task 的更新操作;
      (备注,该等待时间是否和 --update-delay 有关?还有待验证)
    • 如果在更新的任何时候,只要任何一个 task 返回 FAILED 状态,那么更新将会停止;
  5. 检查更新后的 redis service,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    docker@manager1:~$ docker service inspect --pretty redis

    ID: y3jmdpdcp8qqz4b87c348at3r
    Name: redis
    Service Mode: Replicated
    Replicas: 3
    .....
    UpdateConfig:
    Parallelism: 1
    Delay: 10s
    On failure: pause
    Monitoring Period: 5s
    Max failure ratio: 0
    Update order: stop-first
    RollbackConfig:
    Parallelism: 1
    On failure: pause
    Monitoring Period: 5s
    Max failure ratio: 0
    Rollback order: stop-first
    ContainerSpec:
    Image: redis:3.0.7@sha256:730b765df9fe96af414da64a2b67f3a5f70b8fd13a31e5096fee4807ed802e20
    Resources:
    Endpoint Mode: vip

    可见,已经成功将 redis service 更新为版本 3.0.7 了;

    下面来看一下一些可能的异常情况已经相应的补救措施

    因为失败而暂停的更新,通过 service inspect 可以查看,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    docker@manager1:~$ docker service inspect --pretty redis

    ID: 0u6a4s31ybk7yw2wyvtikmu50
    Name: redis
    ...snip...
    Update status:
    State: paused
    Started: 11 seconds ago
    Message: update paused due to failure or early termination of task 9p7ith557h8ndf0ui9s0q951b
    ...snip...

    可以看到,因为某种异常原因导致更新终止,有可能是网络原因,也有可能是人为的误操作,或者是宿主机出了某种故障,总之,这些错误是可以通过重试而得以解决的时候,可以使用命令 docker service update <SERVICE-ID> 重启更新的任务,比如,

    1
    docker@manager1:~$ docker service update redis

    当然,为了避免可能出现同样的更新异常,可以在重启的时候,对 service update 命令指定某些参数;

  6. 最后,通过 docker service ps <SERVICE-ID> 可以查看滚动更新的日志,

    1
    2
    3
    4
    5
    6
    7
    8
    docker@manager1:~$ docker service ps redis
    ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
    ftsy0a5tk65b redis.1 redis:3.0.7 manager1 Running Running 12 minutes ago
    qw0dco4w0zoq \_ redis.1 redis:3.0.6 manager1 Shutdown Shutdown 13 minutes ago
    wpfmn0gu7xvb redis.2 redis:3.0.7 worker3 Running Running 13 minutes ago
    mx17a28bx6ks \_ redis.2 redis:3.0.6 worker1 Shutdown Shutdown 14 minutes ago
    6kizzy4oqklb redis.3 redis:3.0.7 worker2 Running Running 14 minutes ago
    lawzlublhjo6 \_ redis.3 redis:3.0.6 worker2 Shutdown Shutdown 16 minutes ago

    从日志中可以看到,worker2 上的 redis 最先更新,因为它最先停止,经过 2 分钟的更新时间直至启动,然后更新 worker1,最后更新 manager;

Drain a node

在之前的章节中,我们看到,正在执行的节点的状态为 ACTIVE,也就是意味着 docker manager 可以向这些节点分配任务,因此,换句话说,这些节点都可以接受最新的任务,也都是可用的( available );但是,某些时候,我们相对一些节点进行维护,比如说系统升级,那么就要告诉 docker manager,当前节点暂时不可用,这个时候,我们就可以将该节点设置为 DRAIN 的状态,当将某个节点设置为 DRAIN 状态以后,也就意味着,

  • 该节点不再可用,不再接受任何新的任务,
  • swarm 会把运行在该节点上的任务先进行迁移到其它节点上以后后再全部停止,
    (可见,swarm 时刻都在考虑节点上的任务的高可用)

Ok,那我们来看一下下面的这个例子,

  1. ssh 到 manager1,
  2. 查看当前 nodes 的状态,

    1
    2
    3
    4
    5
    6
    docker@manager1:~$ docker node ls
    ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
    8fgy70pcpdotl092hj7c1j5uq * manager1 Ready Active Leader
    70ygvee033wip4a9oiotljvde worker1 Ready Active
    pgu9ejp9n3wwcrmw9lsecvryd worker2 Ready Active
    0dsvrceup4qiu8s23z1ns6560 worker3 Ready Active
  3. 查看各个节点上所执行 redis 任务的情况

    1
    2
    3
    4
    5
    docker@manager1:~$ docker service ps redis
    ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
    ftsy0a5tk65b redis.1 redis:3.0.7 manager1 Running Running 8 hours ago
    wpfmn0gu7xvb redis.2 redis:3.0.7 worker3 Running Running 8 hours ago
    6kizzy4oqklb redis.3 redis:3.0.7 worker2 Running Running 8 hours ago
  4. 将 worker2 节点的 Availability 状态值更新为 Drain 状态

    1
    2
    docker@manager1:~$ docker node update --availability drain worker2
    worker2
  5. 查看 worker2 节点的 Availability 的状态值,

    1
    2
    3
    4
    5
    6
    7
    8
    docker@manager1:~$ docker node inspect --pretty worker2
    ID: 70ygvee033wip4a9oiotljvde
    Hostname: worker2
    Joined at: 2018-01-18 07:26:19.126093698 +0000 utc
    Status:
    State: Ready
    Availability: Drain
    ...

    可以看到,状态值 Availability 已经改为了 Drain

  6. 查看各个节点上所执行 redis 任务的情况,

    1
    2
    3
    4
    5
    6
    docker@manager1:~$ docker service ps redis
    ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
    ftsy0a5tk65b redis.1 redis:3.0.7 manager1 Running Running 8 hours ago
    wpfmn0gu7xvb redis.2 redis:3.0.7 worker3 Running Running 8 hours ago
    mml6vxpuisf4 redis.3 redis:3.0.7 worker3 Running Running 32 seconds ago
    6kizzy4oqklb \_ redis.3 redis:3.0.7 worker2 Shutdown Shutdown 33 seconds ago

    可以看到,当把 worker2 的 Availability 设置为 DRAIN 状态值以后,worker2 的 DESIRED STATE 表示 worker2 已经关闭了,也就意味着它不再接受任何新的 task,不过要注意的是,虽然 worker2 被设置为 Drain,但是在将它设置为 Drain 状态以前,运行在它上面的 tasks 将会进行迁移,从上面的日志中可以看到,之前运行在 worker2 上的 redis 任务,现在已经被迁移到 worker3 节点上并处于正常执行的状态中

  7. 将 worker2 节点的 Availability 状态值还原为 ACTIVE

    1
    2
    docker@manager1:~$ docker node update --availability active worker2
    worker2
  8. 检查 worker2 节点的状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    docker@manager1:~$ docker node inspect --pretty worker2
    ID: pgu9ejp9n3wwcrmw9lsecvryd
    Hostname: worker2
    Joined at: 2018-01-18 07:44:33.213993625 +0000 utc
    Status:
    State: Ready
    Availability: Active
    Address: 192.168.99.102
    ...

    可见 worker2 的 Availability 状态已经恢复为 ACTIVE

  9. 检查各个节点上所执行任务的情况

    1
    2
    3
    4
    5
    6
    docker@manager1:~$ docker service ps redis
    ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
    ftsy0a5tk65b redis.1 redis:3.0.7 manager1 Running Running 8 hours ago
    wpfmn0gu7xvb redis.2 redis:3.0.7 worker3 Running Running 8 hours ago
    mml6vxpuisf4 redis.3 redis:3.0.7 worker3 Running Running 32 seconds ago
    6kizzy4oqklb \_ redis.3 redis:3.0.7 worker2 Shutdown Shutdown 33 seconds ago

    这里要注意的是,虽然 worker2 恢复了,但是已经执行在 worker3 上的任务并不会归还给 worker2,现在 worker2 相当于是一个全新的节点;

异常情况

本章节着重记录笔者日常在使用 docker swarm 的时候遇到的一些异常情况;

all status down

某一天,在启动完本地的四台虚拟机以后,发现所有的 workers 都不工作了,而且清一色,所有 workers 的状态都是 Down;为什么?

1
2
3
4
5
6
docker@manager1:~$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
8fgy70pcpdotl092hj7c1j5uq * manager1 Ready Active Leader
70ygvee033wip4a9oiotljvde worker1 Down Active
pgu9ejp9n3wwcrmw9lsecvryd worker2 Down Active
0dsvrceup4qiu8s23z1ns6560 worker3 Down Active

里面有详细的讨论 https://forums.docker.com/t/docker-worker-nodes-shown-as-down-after-re-start/22329/10 本以为最有可能的是一个叫 foxzy 提出的解决方案,如图所示,
resolution from foxzy.png
按照它的说法,我尝试着重新生成证书,

1
$ docker-machine regenerate-certs worker1

其实结果还是一样的;结果发现,因为之前我将 manager 和 workers 节点都关闭过,然后再重启过,然后,manager 的 IP 地址变了,那么自然其它的 workers 就不能在原来的 manager 节点上注册了;这才是问题的根本;所以,笔者认为,无非就只有两种解决方案,

  1. 依次修改所有 workers 的配置,让它能够指向 manager 节点的正确 IP 地址进行注册;
    对了,同样的,workers 的 IP 地址也有可能发生了变化,那么还必须在 manager 节点上依次修改 workers 节点所对应的新的 IP 地址;
  2. 让所有的 workers 先离开 Swarm 集群,然后 manager 依次 remove 掉这些 workers 节点,然后,workers 节点依次重新加入 Swarm 集群中;

方案一的修改方式看似轻巧,但是过程其实相当的繁琐,还不清楚是否需要重新启动之类的;方案二其实就是让各个 workers 节点重新加入 Swarm 集群中,而运行在各个 workers 节点上的应用并不会丢失;笔者采用方案二的解决办法,具体步骤如下,

  1. 首先依次登录各个 workers 然后执行如下命令以离开 Swarm 集群;

    1
    docker@worker1:~$ docker swarm leave

    worker2、worker3 进行类似的操作,这样各个 workers 节点将会离开 Swarm 集群;

  2. 然后,在 manager 节点上将各个 workers 移除

    1
    2
    3
    4
    5
    6
    docker@manager1:~$ docker node rm worker1
    worker1
    docker@manager1:~$ docker node rm worker2
    worker2
    docker@manager1:~$ docker node rm worker3
    worker3
  3. 查看 worker 加入 manager 节点的 swarm token,

    1
    2
    3
    4
    docker@manager1:~$ docker swarm join-token worker
    To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-4t6shqfenq4v54wgso5duoe9lj33ivifghcktqk0l7a7hh0h6g-cj8g09fcai9zefxxjxdyv48xq 192.168.99.100:2377
  4. 依次在每个 worker 节点上执行上述 join 命令,本以为,一切都 OK,但是在执行的时候,却

    1
    2
    3
    4
    docker@worker1:~$ docker swarm join \ 
    --token SWMTKN-1-4t6shqfenq4v54wgso5duoe9lj33ivifghcktqk0l7a7hh0h6g-cj8g09fcai9zefxxjxdyv48xq \
    192.168.99.100:2377
    Error response from daemon: rpc error: code = Unavailable desc = grpc: the connection is unavailable

    遇到 connection is unavailable 的错误,而且是每个节点上都遇到这样的错误,但是奇怪的是,我单独在 workers 节点都能 ping 通 192.168.99.100 地址.. 很奇怪,我把 manager 和 worker 节点重启了还是这样,难道非得全部销毁了再重来过?这个代价也太高了吧?后来想想,是不是可以通过更新 manager 节点的 token 和证书能够修复上面这个问题?觉得可行,但是还没来得及试;

没有办法,最后只得采取销毁了重建的方式了…

首先,依次销毁所有的 machines,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
macdeMacBook-Pro:~ mac$ docker-machine rm manager1
About to remove manager1
WARNING: This action will delete both local reference and remote instance.
Are you sure? (y/n): y
Successfully removed manager1
macdeMacBook-Pro:~ mac$ docker-machine rm worker1
About to remove worker1
WARNING: This action will delete both local reference and remote instance.
Are you sure? (y/n): y
Successfully removed worker1
macdeMacBook-Pro:~ mac$ docker-machine rm worker2
About to remove worker2
WARNING: This action will delete both local reference and remote instance.
Are you sure? (y/n): y
Successfully removed worker2
macdeMacBook-Pro:~ mac$ docker-machine rm worker3
About to remove worker3
WARNING: This action will delete both local reference and remote instance.
Are you sure? (y/n): y
Successfully removed worker3

然后,按照上述的介绍步骤,全部重新创建;哈哈,弄到最后,还是报这个错误,

1
2
docker@worker1:~$ docker swarm join --token SWMTKN-1-06ke76olm0gnww43xc4o0ql9h2h5om0rmojh7seft9xve6x5fm-2xfr8lowwrqm3lj1trhb0denq 192.168.99.100:2377
Error response from daemon: rpc error: code = Unavailable desc = grpc: the connection is unavailable

难道是 virtualbox 的问题或者是 Docker 自己的问题?https://forums.docker.com/t/solved-docker-error-response-from-daemon-grpc-the-connection-is-unavailable/32510 这篇文章中讲到要重启 docker 才行;想想也不对啊… manager 和 worker 都是新建的呀… 只有试试重启电脑了…. 重启之后,又发现,原来.. 没有初始化 manager

1
2
docker@manager1:~$ docker swarm join-token worker
Error response from daemon: This node is not a swarm manager. Use "docker swarm init" or "docker swarm join" to connect this node to swarm and try again.

那上面那串 token 是怎么来的?然后创建 swarm manager 节点,

1
2
3
4
5
6
7
8
docker@manager1:~$ docker swarm init --advertise-addr 192.168.99.100
Swarm initialized: current node (yiklhsciyfsj0zh0w8n1glsd5) is now a manager.

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

docker swarm join --token SWMTKN-1-3gjgeshuzv955jbozdc2wsbcfpnp3qq6i7nwgkh0avcoaothvk-7r4ieha6wnf71mwpep6uivmmf 192.168.99.100:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

然后再切换自 worker1 节点上,

1
2
docker@worker1:~$ docker swarm join --token SWMTKN-1-3gjgeshuzv955jbozdc2wsbcfpnp3qq6i7nwgkh0avcoaothvk-7r4ieha6wnf71mwpep6uivmmf 192.168.99.100:2377
This node joined a swarm as a worker.

成功了,真实一波三则呀…….

如果一切都是因为 IP 的问题,那么以后再使用 docker-machine start 节点的时候,是否可以输入固定的 IP 来解决这个问题?

后记,笔者发现 docker-machine start 这个命令不能指定固定的 IP 地址,但是后来发现,通过 docker-machine start 启动 host,host ip 是有规律的,第一个启动的 host 的 ip 地址固定为 192.168.99.100,第二个为 101… 这样依次递增;所以,在重启服务的时候,只要保证启动的顺序和上一次一致,那么是不会出现问题的了;

Reference

https://docs.docker.com/engine/swarm/swarm-tutorial/