DockerのSwarmモードでクラスタを構築するメモ


以前にDocekrをクラスタ管理するためにDocker Swarmを使ってオーケストレーションしてみたことが過去あったのですが、Docker Swarmを使った構築は、ConsulなどのKVSを別途用意したり大変だったりで、クラスタ構築が大変だったのですが、  

最近は**Swarmモード**というのが追加されているらしく、結構簡単にできるとのことだったので、ホントに簡単にSwarmモードなるものでクラスタを簡単構築できるか調査してみました。
以下は、その時のメモになります。  
ちなみに、現在では「Docker Swarm」というと、一般的にはSwarmモードのことを指すらしいです。


## クラスタ環境の構成

* Managerノード
    * 仮想マシンのホスト名: manager1, manager2
    * CentOS7
* Workerノード
    * 仮想マシンのホスト名: worker1, worker2
    * CentOS7

## 仮想マシンの準備とDockerのインストール

`Vagrantfile`をこんな感じで用意しておきました。  
IPアドレスやらホスト名やらはお好みで。

```Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

  # Managerサーバ#1
  config.vm.define "manager1" do |node|
    node.vm.provider "virtualbox" do |vm|
      vm.name = "manager1"
      vm.customize ["modifyvm", :id, "--memory", "512"]
    end
    node.vm.box = "centos/7"
    node.vm.hostname = "manager1"
    node.vm.network "private_network", ip: "192.168.33.41"
  end

  # Managerサーバ#2 (あとでクラスタを追加したいために用意)
  config.vm.define "manager2" do |node|
    node.vm.provider "virtualbox" do |vm|
      vm.name = "manager2"
      vm.customize ["modifyvm", :id, "--memory", "512"]
    end
    node.vm.box = "centos/7"
    node.vm.hostname = "manager2"
    node.vm.network "private_network", ip: "192.168.33.42"
  end

  # Workerサーバ#1
  config.vm.define "worker1" do |node|
    node.vm.provider "virtualbox" do |vm|
      vm.name = "worker1"
      vm.customize ["modifyvm", :id, "--memory", "512"]
    end
    node.vm.box = "centos/7"
    node.vm.hostname = "worker1"
    node.vm.network "private_network", ip: "192.168.33.51"
  end

  # Workerサーバ#2
  config.vm.define "worker2" do |node|
    node.vm.provider "virtualbox" do |vm|
      vm.name = "worker2"
      vm.customize ["modifyvm", :id, "--memory", "512"]
    end
    node.vm.box = "centos/7"
    node.vm.hostname = "worker2"
    node.vm.network "private_network", ip: "192.168.33.52"
  end

end
```

### 仮想マシンを起動

```
$ vagrant up
$ vagrant ssh-config >> ~/.ssh/config
```

### Docker CEのインストール

コミュニティエディションでインストールします。
Managerサーバ、Workerサーバで以下のコマンドを実行してインストールします。

```
$ sudo yum install -y yum-utils \
  device-mapper-persistent-data \
  lvm2

$ sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo

$ sudo yum install -y docker-ce
```

バージョンでも確認しておきます。

```
$ docker -v

Docker version 18.03.1-ce, build 9ee9f40
```

### Dockerの起動

Managerサーバ、Workerサーバで以下のコマンドを実行して起動します。

```
$ sudo systemctl start docker
$ sudo systemctl enable docker
```

ここまででインストール完了したので、
次はSwarmでクラスタを構築します。

## Docker Swarmでクラスタを構築

### クラスタの初期化

```
$ sudo docker swarm init --advertise-addr 192.168.33.41
Swarm initialized: current node (7i9tnczzu1usm5bijr532j7ou) is now a manager.

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

    docker swarm join --token SWMTKN-1-4ieqe6ymrsxevbyo0875l8h20q54ma80lddvmrqwep0nib51dd-ele3qb3mxsioopz2fqcjteux3 192.168.33.41:2377

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

上記に記載のコマンドによって、あとでワーカーノード(Worker#1, Worker#2)をクラスタに追加します。トークンは実行環境によって異なるので注意してください。


また、注意が必要ですが、マネージャノード(Manager#2)をクラスタに追加する際のトークンが異なります。  
マネージャをクラスタに追加するためのトークンは以下のコマンドで確認できます。


```
$ sudo docker swarm join-token manager
To add a manager to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-4ieqe6ymrsxevbyo0875l8h20q54ma80lddvmrqwep0nib51dd-02ym4vkgpi7r895buzxne1sl3 192.168.33.41:2377
```

```
$ sudo docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
7i9tnczzu1usm5bijr532j7ou *   manager1            Ready               Active              Leader              18.03.1-ce
```

## クラスタに追加


### ワーカーノードをクラスタに追加

ワーカーノードをクラスタに追加します。  
Worker#1, Worker#2で実行してください。

```
$ sudo docker swarm join --token SWMTKN-1-4ieqe6ymrsxevbyo0875l8h20q54ma80lddvmrqwep0nib51dd-ele3qb3mxsioopz2fqcjteux3 192.168.33.41:2377
This node joined a swarm as a worker.
```

### マネジャーノードをクラスタに追加

今度は、Manager#2をマネジャノードとしてクラスタに追加します。
トークンがワーカーノードで実行した時のものと異なっていることに注意してください。

```
$ sudo docker swarm join --token SWMTKN-1-4ieqe6ymrsxevbyo0875l8h20q54ma80lddvmrqwep0nib51dd-02ym4vkgpi7r895buzxne1sl3 192.168.33.41:2377
This node joined a swarm as a manager.
```

### クラスタ構築できたか確認する

クラスタのどこかのノードで実行して確認してください。
以下のようになっていればOKです。

```
[vagrant@manager1 ~]$ sudo docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
7i9tnczzu1usm5bijr532j7ou *   manager1            Ready               Active              Leader              18.03.1-ce
woryol5l22uwaw20cgkdeni8c     manager2            Ready               Active              Reachable           18.03.1-ce
y2ix3jsrefb46u4ibvhxzsvhz     worker1             Ready               Active                                  18.03.1-ce
gs5bzrxb76glo40wn92psl0f3     worker2             Ready               Active                                  18.03.1-ce
```

`*`がついているのノードがこのコマンドを実行しているノードです。

## デプロイ

Alpine Linuxからdocker.comにpingを行うサービスを追加します。起動するコンテナ数は1つです。

```
$ sudo docker service create --replicas 1 --name helloworld alpine ping docker.com
rr5zwn0fyamykt79ihr08c5te
overall progress: 1 out of 1 tasks
1/1: running
verify: Service converged
```

起動しているサービス一覧にhelloworldが追加されている事を確認します。

```
$ sudo docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
rr5zwn0fyamy        helloworld          replicated          1/1                 alpine:latest
```

サービス内(helloworld)でどのようなコンテナを実行しているかを確認するためには、以下のコマンド。

```
$ sudo docker service ps helloworld
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE                ERROR               PORTS
wxjc7ttz1u3h        helloworld.1        alpine:latest       worker1             Running             Running about a minute ago
```

### スケールアウト

サービスを1から5に変更します。

```
$ sudo 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
verify: Service converged
```

```
$ sudo docker service ps helloworld
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE                ERROR               PORTS
wxjc7ttz1u3h        helloworld.1        alpine:latest       worker1             Running             Running 4 minutes ago
2wiqq5ff537d        helloworld.2        alpine:latest       manager1            Running             Running about a minute ago
ah46fj4uwq5k        helloworld.3        alpine:latest       manager2            Running             Running about a minute ago
o6om7iyhtf5e        helloworld.4        alpine:latest       manager2            Running             Running about a minute ago
22ptlffc7bmd        helloworld.5        alpine:latest       worker2             Running             Running about a minute ago
```


## 備忘録

Docker Swarmは過半数のマネージャーを失うとタスクを評価できなくなり自動的に回復できなくなる。
そのため、マネージャーノードは奇数台で構築するのがよさそう。


## 用語

* ノード
    * Swarm内に参加する Docker Engineインスタンス。
* マネージャーノード
    * アプリケーションをswarmにデプロイするには、マネージャノード（manager node）にサービス定義を送信します。マネージャはワーカーノードへタスクと呼ばれる単位を送ります。
    * マネージャノードはswarmの期待状態（desired state）を維持するために、オーケストレーションと管理機能を処理。マネージャはオーケストレーションタスクを処理するため、単一のリーダーを選出（elect）する。
* ワーカーノード
    * ワーカ・ノード（worker nodes） はマネージャ・ノードから送られてきたタスクの受信して処理する。
    * デフォルトでは、マネージャノードはワーカーノードも兼ねるが、マネージャのみのノード（manager-only node）としてもマネージャを設定可能。割り当てられたタスクの現在の状況をマネージャノードに伝えることによって、マネージャは期待状態を維持できます。
* サービス
    * アプリケーションを作り上げるための様々なタスクを、どのように実行するかという定義
    * サービスの作成時に指定するのは、どのコンテナのイメージを使い、コンテナ内でどのようなコマンドを実行するか
    * 以下のサービスがある。
        * 複製サービス
            * 期待状態の指定に基づき、swarm マネージャがノード間に複製タスク（replica task）を指定した数だけ分散する
        * グローバル・サービス
            * 特定のタスクをクラスタ内の全ノード上で利用可能になるようにswarmが実行する。
* タスク
    * Dockerコンテナを運び、コンテナ内でコマンドを実行する。swarmにおける最小スケジューリング単位です。
    * マネージャノードはワーカノードに対してタスクを割り当てる。割り当てる数はサービスのスケールで設定されたレプリカ数に応じる。