play-with-dockerでswarmクラスター組んでみた

#play-with-dockerとは
クラウド上で素早くdockerを試すことが出来るツール。
簡単にノードを追加することが出来、swarmやkubernetesのクラスターを作成するのにもとても役立つツールなのである。
４時間限定でdockerの仮想環境が素早く構築することが出来、ノードを増やすことが出来る。追加するノードの数は限られているが。


なお今回のこの記事はudemyの
[Docker Mastery: with Kubernetes +Swarm from a Docker Captain](https://www.udemy.com/course/docker-mastery/)
を参考にしながら作成しております。
解釈などの間違っている箇所はあると思いますがその時はコメントしていただければ幸いです。文法や作り方はめちゃくちゃだと思います。これから頑張ります。

##今回やってみること
今回はノード3台を使用してswarmクラスターを作ってみたいと思います!!その後に投票アプリケーションを作成します。

###準備完了までの手順、３台のノードを追加するまで
###①[play-with-docker](https://labs.play-with-docker.com/)にアクセスしdockerにログインする
持っているdocker IDでアクセスする。dockerに登録してない人は登録する必要がある。
<img width="1266" alt="スクリーンショット 2020-06-10 16.55.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620960/0edc1c05-52a9-007b-e6ef-f5ffb47d7624.png">


###②ログインしたら+ADD NEW INSTANCEボタンを押してノードを３つ追加する。

###③swarmクラスターに必要なtokenを取得する
```bash
node1
$ docker swarm init
Error response from daemon: could not choose an IP address to advertise since this system has multiple addresses on different interfaces (192.168.0.23 on eth0 and 172.18.0.60 on eth1) - specify one with --advertise-addr
```
docker swarm initがswarmのスタートですがサーバーでは、SwarmサービスをアドバタイズするIPアドレスを指定する必要があります。今回はErrorに書かれている「192.168.0.23」を指定して再度実行します。
###IP指定
```
$ docker swarm init --advertise-addr="192.168.0.23"
Swarm initialized: current node (yoetefvzciclzubndes69sywy) is now a manager.

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

    docker swarm join --token SWMTKN-1-3ifftddkbbe9yl8ir43l9zujir5umk8oh7nq1td1g5w2m68hg2-cisy1qe7f8g67vu11z5gg5v4v 192.168.0.23:2377

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

###④「③」のコマンドの出力結果から以下のようなトークンを取得する
```bash
docker swarm join --token SWMTKN-1-3ifftddkbbe9yl8ir43l9zujir5umk8oh7nq1td1g5w2m68hg2-cisy1qe7f8g67vu11z5gg5v4v 192.168.0.23:2377
```

###⑤取得したtokenをコピーしnode2とnode3に貼り付け実行する
```bash
node2
$ docker swarm join --token SWMTKN-1-3ifftddkbbe9yl8ir43l9zujir5umk8oh7nq1td1g5w2m68hg2-cisy1qe7f8g67vu11z5gg5v4v 192.168.0.23:2377
This node joined a swarm as a worker.

node3
$ docker swarm join --token SWMTKN-1-3ifftddkbbe9yl8ir43l9zujir5umk8oh7nq1td1g5w2m68hg2-cisy1qe7f8g67vu11z5gg5v4v 192.168.0.23:2377
This node joined a swarm as a worker.
```
これによりnodeがswarmの一部となる。
node1がクラスターの「Leader」となる。node2とnode3は「worker」となる。
現在の状態になるとnode1が「Leader」のためnode1からswarm全体を操作することが可能になる

###⑥「worker」を「manager」に昇格させる
```bash
node1
$ docker node update --role manager node2
node2(output)

node1
$ docker node update --role manager node3
node3(output)
```

###⑦クラスターが３つのノードで構成されているか確認する
```bash
node1
$ docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
yoetefvzciclzubndes69sywy *   node1               Ready               Active              Leader              19.03.11
2njm4yd3lvrjyjy61g06n4u9g     node2               Ready               Active              Reachable           19.03.11
40ooxzfo4m117oftkbocbooze     node3               Ready               Active              Reachable           19.03.11
```
*は今使用しているノードです。という意味。
これで「swarmクラスターを３つのノードで構成したという状態になる」

#基本コマンド
###現在のクラスターのノード一覧
```bash
$ docker node ls
```
ちなみにnode-2,node-3は普通のworkerのためdocker node lsをしても効果はない


###現在稼働しているクラスターのトークンの入手方法
```bash
$ docker swarm join-token manager
```
新しくノードを追加するにはtokenを取得して新規のノードに貼り付けすればいけると思います。

###稼働しているserviceの確認
```bash
$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
po9c2ygakrbs        amazing_ramanujan   replicated          3/3                 alpine:latest       
```


###ローカルノードがどのタスク又はどのコンテナを起動しているかの確認
```bash
$ docker node ps
```

###サービスを確認したい場合
```bash
$ docker service ps <service name>
```

###サービスの削除
```bash
$ docker service rm <service name>
```

#swarmクラスターを使用しアプリを作成してみる
これからは全てnode1で操作を行なっていきます。

##オーバーレイネットワークを作成
Swarmでは、オーバーレイを使用すると、まるでローカルネットワーク上にあるかのように、ノードをまたがることができます。
###ネットワーク 1
```bash
node1
$ docker network create -d overlay backend
skkw2p6ndobql31crysmjv9fe 
```
出力結果の「skkw2p6ndobql31crysmjv9fe」はネットワーク IDとなります。
###ネットワーク 2
```bash
$ docker network create -d overlay frontend
zjo3f6xpmky5cewrdjtp98qt3 
```

##使用するサービスの作成
###サービスを作成 1
```bash
$ docker service create --name vote -p 81:80 --network frontend --replicas 2 dockersamples/examplevotingapp_vote:before
in32sdlgnefwm8rcm8uqz58sl
overall progress: 2 out of 2 tasks 
1/2: running   [==================================================>] 
2/2: running   [==================================================>] 
verify: Service converged
```
コマンド実行すぐに現れた文字列はサービス IDです。
--name -> 作成したサービスに名前を付与する
--network -> 作成したサービスはどのネットワークを使用するか
--replica -> 起動するタスクの数
今回host側portを81にしているのは80のポートが塞がっているためです。塞いだ覚えはないのですが
遠い昔にwebサーバーを立ち上げた時にそのままにしてしまったようです。psコマンドでもプロセスが見当たらないので
ちょっと困り中です。
###サービスを作成 2
```bash
$ docker service create --name redis --network frontend --replicas 2 redis:3.2
xxnws00rb51gdzdqx9xl37nx0
overall progress: 2 out of 2 tasks 
1/2: running   [==================================================>] 
2/2: running   [==================================================>] 
verify: Service converged 
```

###サービスを作成 3
```bash
$ docker service create --name worker --network frontend --network backend dockersamples/examplevotingapp_worker
n4hijthb2anlbe4sra9koy3ad
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged
```

###サービスを作成 4
```bash
$ docker service create --name db --network backend --mount type=volume,source=db-data,target=/var/lib/postgresql/data postgres:9.4
e0lf7bv4ni8bovty3c1qx42kd
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged
```
--mountフラグは、ボリュームが作成される前に、ボリュームのカスタムメタデータ（「ラベル」）を指定することができます。
type=volume -> マウント方法はボリュームマウントを指定するという意味です。
※バインドマウントとボリュームマウントの違いとは 参考文献(https://qiita.com/kubocchi/items/793c327cf3c72cc7af1c)
source=db-data -> ボリューム名を指定する
target=/var/lib/postgresql/data -> コンテナ上のマウント先ディレクトリ
([docker document参照](https://docs.docker.com/engine/reference/commandline/service_create/#description))

###サービスを作成 5
```bash
$ docker service create --name result --network backend -p 5001:80 dockersamples/examplevotingapp_result:before
uf9e9pew31gp45brh79vsb6db
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged
```

###ここで起動したサービスが全て作成されているか確認する
```bash
$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                                          PORTS
e0lf7bv4ni8b        db                  replicated          0/1                 postgres:9.4                                   
xxnws00rb51g        redis               replicated          2/2                 redis:3.2                                      
uf9e9pew31gp        result              replicated          1/1                 dockersamples/examplevotingapp_result:before   *:5001->80/tcp
in32sdlgnefw        vote                replicated          2/2                 dockersamples/examplevotingapp_vote:before     *:81->80/tcp
n4hijthb2anl        worker              replicated          0/1                 dockersamples/examplevotingapp_worker:latest
```
REPLICASは本来設定しないとデフォルトでは１になります。
なぜかworkerとdbが思うように動いていません。
###各サービスの状況を確認してみます。
```bash
$ docker service ps db
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE           ERROR                              PORTS
0l67ecgagwwg        db.1                postgres:9.4        node1               Ready               Ready 1 second ago                                         
24ew7b6x7bfj         \_ db.1            postgres:9.4        node1               Shutdown            Failed 1 second ago     "task: non-zero exit (1)"          
cddv0r2zt8vf         \_ db.1            postgres:9.4        node1               Shutdown            Failed 8 seconds ago    "task: non-zero exit (1)"          
jba84u0j4eai         \_ db.1            postgres:9.4        node1               Shutdown            Failed 14 seconds ago   "task: non-zero exit (1)"          
c72n1rv6iuwe         \_ db.1            postgres:9.4        node2               Shutdown            Failed 21 seconds ago   "starting container failed: fa…"

$ docker service ps redis
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
t8hkcojxg271        redis.1             redis:3.2           node1               Running             Running 10 minutes ago                       
2mjjlnfnvnau        redis.2             redis:3.2           node3               Running             Running 10 minutes ago

$ docker service ps vote
ID                  NAME                IMAGE                                        NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
0cdz4ggwhotl        vote.1              dockersamples/examplevotingapp_vote:before   node1               Running             Running 11 minutes ago                       
pkm3ir1y8ukz        vote.2              dockersamples/examplevotingapp_vote:before   node2               Running             Running 11 minutes ago

$ docker service ps worker
ID                  NAME                IMAGE                                          NODE                DESIRED STATE       CURRENT STATE                    ERROR                       PORTS
lbysaf6osku8        worker.1            dockersamples/examplevotingapp_worker:latest   node3               Running             Running less than a second ago                               
yyra79dzg3at         \_ worker.1        dockersamples/examplevotingapp_worker:latest   node1               Shutdown            Failed 7 seconds ago             "task: non-zero exit (1)"   
q7f4bzw8zeuj         \_ worker.1        dockersamples/examplevotingapp_worker:latest   node3               Shutdown            Failed 14 seconds ago            "task: non-zero exit (1)"   
t9ccjueiu5yg         \_ worker.1        dockersamples/examplevotingapp_worker:latest   node2               Shutdown            Failed 21 seconds ago            "task: non-zero exit (1)"   
hxivx6ttwcwm         \_ worker.1        dockersamples/examplevotingapp_worker:latest   node3               Shutdown            Failed 32 seconds ago            "task: non-zero exit (1)"

$ docker service ps result
ID                  NAME                IMAGE                                          NODE                DESIRED STATE       CURRENT STATE                ERROR               PORTS
n1fqj9teruwx        result.1            dockersamples/examplevotingapp_result:before   node2               Running             Running about a minute ago
```
こうみるとwokerサービスなどははじめは何回かシャットダウンされてるみたいです。
この状態で全てREPLICASが1/1,3/3みたいな動いていてくれるとありがたいですが動いてないのでログをみてみます。

###サービスのログを確認する(db service)
```bash
$ docker service logs db
db.1.09758ad58qj3@node1    | Error: Database is uninitialized and superuser password is not specified.
db.1.09758ad58qj3@node1    |        You must specify POSTGRES_PASSWORD for the superuser. Use
db.1.09758ad58qj3@node1    |        "-e POSTGRES_PASSWORD=password" to set it in "docker run".
db.1.09758ad58qj3@node1    | 
db.1.09758ad58qj3@node1    |        You may also use POSTGRES_HOST_AUTH_METHOD=trust to allow all connections
db.1.09758ad58qj3@node1    |        without a password. This is *not* recommended. See PostgreSQL
db.1.yvqhwmc1nm4k@node3    | Error: Database is uninitialized and superuser password is not specified.
db.1.09758ad58qj3@node1    |        documentation about "trust":
db.1.yvqhwmc1nm4k@node3    |        You must specify POSTGRES_PASSWORD for the superuser. Use
db.1.yvqhwmc1nm4k@node3    |        "-e POSTGRES_PASSWORD=password" to set it in "docker run".
(以下同じことが起きていたので省略)
```
パスワードが設定されていないので設定してくださいみたいなことが書かれている気がします。

###--envオプションで環境変数を指定し再度実行
```bash
$ docker service create --name db --network backend --mount type=volume,source=db-data,target=/var/lib/postgresql/data --env POSTGRES_PASSWORD=mypasswd postgres:9.4
r8fn4ue8gcpfbqdj3ve1ifbos
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged
```
結果：正常に動きました。

###サービスのログを確認する(worker service)
```bash
$ docker service logs 
workerworker.1.pyc8dghi4pp5@node3    | System.AggregateException: One or more errors occurred. (No such device or address) ---> System.Net.Internals.SocketExceptionFactory+ExtendedSocketException: No such device or address
worker.1.pyc8dghi4pp5@node3    |    at System.Net.Dns.HostResolutionEndHelper(IAsyncResult asyncResult)
worker.1.pyc8dghi4pp5@node3    |    at System.Net.Dns.EndGetHostAddresses(IAsyncResult asyncResult)
worker.1.pyc8dghi4pp5@node3    |    at System.Net.Dns.<>c.<GetHostAddressesAsync>b__14_1(IAsyncResult asyncResult)
worker.1.pyc8dghi4pp5@node3    |    at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
```
よくわかりません。デバイスやアドレスがないみたいです。

###再度実行してみる
```bash
$ docker service create --name worker --network frontend --network backend dockersamples/examplevotingapp_workerz9z81eeqgkyxov44rhtmg6mys
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged
```
dbとworkerの順番は関係ないだろうと思いworkerの方を何度も再起動していましたが、dbの方の環境変数の方を修正しdbが動いてからこっちも動かすと正常に動きました。
うーん、こういう仕組みがわからないとまだまだな気がします。

###この状態が見たかった!!
```bash
$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                                          PORTS
r8fn4ue8gcpf        db                  replicated          1/1                 postgres:9.4                                   
xxnws00rb51g        redis               replicated          2/2                 redis:3.2                                      
uf9e9pew31gp        result              replicated          1/1                 dockersamples/examplevotingapp_result:before   *:5001->80/tcp
in32sdlgnefw        vote                replicated          2/2                 dockersamples/examplevotingapp_vote:before     *:81->80/tcp
z9z81eeqgkyx        worker              replicated          1/1                 dockersamples/examplevotingapp_worker:latest
```

###webアプリにアクセスしてみます
```bash
$ curl 192.168.0.21:81
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Cats vs Dogs!</title>
    <base href="/index.html">
    <meta name = "viewport" content = "width=device-width, initial-scale = 1.0">
    <meta name="keywords" content="docker-compose, docker, stack">
    <meta name="author" content="Tutum dev team">
    <link rel='stylesheet' href="/static/stylesheets/style.css" />
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
  </head>
  <body>
    <div id="content-container">
      <div id="content-container-center">
        <h3>Cats vs Dogs!</h3>
        <form id="choice" name='form' method="POST" action="/">
          <button id="a" type="submit" name="vote" class="a" value="a">Cats</button>
          <button id="b" type="submit" name="vote" class="b" value="b">Dogs</button>
        </form>
        <div id="tip">
          (Tip: you can change your vote)
        </div>
        <div id="hostname">
          Processed by container ID b10bce8983a9
        </div>
      </div>
    </div>
    <script src="http://code.jquery.com/jquery-latest.min.js" type="text/javascript"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script>

    
  </body>
</html>
```
chromeなどの方でアクセスするにはplay-with-docker画面のOPEN PORTボタンの横のポート番号ボタンにアクセスするといけます。
<img width="676" alt="スクリーンショット 2020-06-10 16.53.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620960/4f823f72-6291-c2e5-cf2c-0da6b04ac13c.png">
:point_up: