GCE上でDocker Swarm使ってクラスタ構築した(Getting Startedレベル) Part1


GKE使えよって話ではあるんですが（汗）
Docker 1.12からSwarmがビルトインで入るようになりました。
そのおかげでクラスタ作成とかがずいぶんとやりやすくなったみたいなので試してみました。

[Docker Swarm on Google Cloud Platform](https://medium.com/google-cloud/docker-swarm-on-google-cloud-platform-c9925bd7863c#.pjy3noaf5)を見てやってます。
あとは公式のドキュメントを見ながら。

## 準備

Docker 1.12をインストールするだけ。

## 概念

まずは簡単に概念を説明

- Swarm：有り体に言えばクラスタ
- ノード：クラスタに属しているインスタンス
	- マネージャノード：Swarmを管理するノード。
	- ワーカーノード：タスクが動くノード。デフォルトだとマネージャもワーカーになる。(manager-only nodeにできる)
- サービス：アプリケーションが動くための定義。要するにコンテナを動かす時のパラメータとかを定義する。
	- レプリカサービスとグローバルサービスがある
	- レプリカサービス：レプリカ数を指定して指定した数だけノードにタスクを分散させるやつ
	- グローバルサービス：クラスタ内の全ノードでタスクが動くやつ（正直いつ使うのかわからん）
- タスク：コンテナが実行するコマンド。大体動いてるコンテナのこと。動いてるノードは移動できない。（あとから出てきます。）
- ロードバランシング
	- 内部DNSのおかげでSwarm内のサービス間はサービス名でやりとりできたりしてまあいい感じ。
	- 対象ノードに対してアクセスした時にタスク（コンテナ）が実行されてなくても他の所にバランシングされる。

とりあえずサービスがk8sとは別物だよって所だけ認識しとけばいいんじゃないかな。
k8sだとサービスはpodsとservice両方かな。

## Swarm(クラスタ)作る

作ると言っても、上記リンクのスクリプト([`swarm-up.sh`](https://github.com/thesandlord/google-cloud-swarm/blob/master/swarm-up.sh))を実行するだけなんですが、ザックリやってることを説明しときます。

```
./swarm-up.sh
```

1. Deployment ManagerでGCP上にインスタンスを作る
	2. マネージャノードのインスタンス作る
		3. startup-scriptでDockerをインストールする
		4. swarmクラスタ初期化する(`docker swarm init`)
		5. プロジェクトメタデータにswarmにjoinするためのトークンを保存する
	6. ワーカーノードのインスタンスグループ作る
		7. インスタンステンプレート作る
			8. startup-scriptでDockerをインストールする
			9. プロジェクトメタデータからトークンとってくる
			10. swarmクラスタにJoinする(`docker swarm join --token $TOKEN:2377`)
		1. インスタンステンプレ使ってインスタンスグループ作る
2. マネージャノードにDocker Machineをインストールする

超ざっくりこんな感じ。
同じような事を自分でしようとしてもわりかしまんま簡単にできるんじゃないかと思います。

だってそれぞれのインスタンスにDockerインストールしてクラスタ初期化してワーカーがジョインするだけだもの。

ちなみに、インスタンステンプレートを確認してもらえばわかりますが、シャットダウンスクリプトで`docker swarm leave`というコマンドが実行されてます。
Part2で触れますが、このままにしとくとワーカーノードで動かす分には問題ないけどさらにどうにかしようと思うとたぶんハマりますのでご注意を。

以下、マネージャノードでコマンド実行するとノードの状態が見れます。

```
$ docker node ls 
ID                           HOSTNAME              STATUS  AVAILABILITY  MANAGER STATUS
74wca83291ouja14y6qbx5v1d    my-swarm-worker-81du  Ready    Active        
8x1ev7mve4znsgs9f4n7rqa5x *  my-swarm-manager      Ready   Active        Leader
```

## サービス作成

サービス作ります。

```
docker service create --replicas 1 -p 80:80/tcp --name nginx nginx
```

これでOK。
マウントオプションやらこの時に指定できます。

GCPで使っててかつContainer Registryを使ってる場合なんかはマネージャノードで`gcloud docker --authorize-only`しておいて、サービスを作るときに`--with-registry-auth`で認証状態をワーカーノードにも継承してあげればいいんじゃないかと思います。
今までDocker使ってた人なら大体どのパラメータがどういう動きするのかは分かるんじゃないかな、と思います。
自分は`-v`が`--mount`とかになってたりしてあれ？ってなりますけどまあ。

```
$ docker service ls 
ID            NAME  REPLICAS  IMAGE  COMMAND
9wx1evw8mxvd  web   1/1       nginx 
```

## スケール

```
docker service scale web=3
```

これでスケールします。
レプリカ数が変わって、各ノードにタスクが配置されます。

```
$ docker service ls 
ID            NAME  REPLICAS  IMAGE  COMMAND
9wx1evw8mxvd  web   3/3       nginx 
```

こんな感じでどのノードでタスクが動いてるのか確認できます。

```
$ docker service ps web
ID                         NAME   IMAGE  NODE                  DESIRED STATE  CURRENT STATE              ERROR
5is5x1c1lgd8s5qpmlpq1fqlx  web.1  nginx  my-swarm-manager      Running        Running 4 hours ago        
3odaytzvj2b9a0slitc5yw6i7  web.3  nginx  my-swarm-worker-mz79  Running        Running about an hour ago  
1jcynkdz8gf8zy2eub5tojnpw  web.9  nginx  my-swarm-worker-mz79  Running        Running 4 hours ago
```

## ノードを増やす

これはManagedインスタンスグループを使ってるので楽勝ですね。
`swarm-resize.sh`があるのでこいつでもいいです。

```
./swarm-resize.sh 3
```

これでノードが増やせます。

```
$ docker node ls 
ID                           HOSTNAME              STATUS  AVAILABILITY  MANAGER STATUS
30k0ojv3slev13x0nhryhblq7    my-swarm-worker-mz79  Ready   Active        
8x1ev7mve4znsgs9f4n7rqa5x *  my-swarm-manager      Ready   Active        Leader
b1scfttgcghn1z36ng182nv0a    my-swarm-worker-bvm1  Ready   Active        
bg23v4d8jdi953blfbl4xnixf    my-swarm-worker-ir8d  Ready   Active
```

### サービスの再配置

先ほどのサービスをスケールした時点で気付いてる方もいるかと思いますが、1ノードに2つのコンテナが動いてます。
ノードを増やしたからそっちに分散させて欲しいところですが、自動で再配置はしないので注意です。
動いてるタスクは他のノードには移動できないというやつです。

じゃあどうするか。

レプリカ数を変更しましょう。
増やして減らしても、減らして増やしてもどっちでもいいです。場合によりけりですね。

```
$ docker service scale web=4
$ docker service ps web
ID                         NAME       IMAGE  NODE                  DESIRED STATE  CURRENT STATE          ERROR
5is5x1c1lgd8s5qpmlpq1fqlx  web.1      nginx  my-swarm-manager      Running        Running 5 hours ago    
c58nz8o6ieaj9ik50dp7px3o3  web.2      nginx  my-swarm-worker-bvm1  Running        Running 4 minutes ago  
3odaytzvj2b9a0slitc5yw6i7  web.3      nginx  my-swarm-worker-mz79  Running        Running 2 hours ago    
1jcynkdz8gf8zy2eub5tojnpw  web.9      nginx  my-swarm-worker-mz79  Running        Running 5 hours ago    
```

んで、減らす。

```
$ docker service scale web=3
$ docker service ps web
ID                         NAME       IMAGE  NODE                  DESIRED STATE  CURRENT STATE          ERROR
5is5x1c1lgd8s5qpmlpq1fqlx  web.1      nginx  my-swarm-manager      Running        Running 5 hours ago    
c58nz8o6ieaj9ik50dp7px3o3  web.2      nginx  my-swarm-worker-bvm1  Running        Running 4 minutes ago  
3odaytzvj2b9a0slitc5yw6i7  web.3      nginx  my-swarm-worker-mz79  Running        Running 2 hours ago    
```

マネージャノードにお任せではありますが、フラットな状態ならレプリカ数を平坦にならしてくれそうな感じがします。
減らして増やすパターンでも、重複してるやつ優先で落として他の所に配置するとかやってくれそう。

## サービスへのアクセス

参照先だと、3つの方法を提示してます。

- シングルノードを参照
- DNSラウンドロビン
- Google Cloud Load Balancer

### シングルノード参照

シングルノード参照の場合、マネージャーノードを参照するのがオススメらしいです。
マネージャノードへのアクセスの場合、マネージャノードでタスクが動いてなくても他の動いてるノードにリクエストを投げてくれます。
ただ、大して分散されてない感じがするので微妙です。

### DNSラウンドロビン

複数マネージャにしている場合、シンプルな構成になっていいよね、って言ってます。

### Google Cloud Load Balancer

まあGCPで動かすならこれでしょうね。
やり方は割愛。

----

なんかSwarmでLBあるぜ！って言ってるわりにはそんなガッツリLBを提供してるわけじゃなくて、あくまで内部LBで内部のやりとりも分散できてるぜって感じっぽい。
外部からはうまいことやってくれ感がする。

Mediumの記事ではここら辺まで。
以降は公式ドキュメントやってみた感。

## ローリングアップデート

サービスのコンテナイメージの更新をすることで、ローリングアップデートができるっぽいです。

```
$ docker service update redis --update-parallelism 1 --update-delay 10s
```

`--update-parallelism`でタスクを並列で更新する数を決められます。
createの際にも指定できます。
んで、未指定だと全部いっぺんに動かそうとします。
そうするとローリングアップデートが台無しです。

```
$ docker service update redis --image redis:3.0.7
```

## ノードの解放

ノードは追加すると異常がなければステータスはActiveです。
ノードの状態を解放状態にすることで、タスクが振られなくなります。
あと、動いてたタスクは他のActiveなノードに移動します。

```
$ docker node ls 
ID                           HOSTNAME              STATUS  AVAILABILITY  MANAGER STATUS
30k0ojv3slev13x0nhryhblq7    my-swarm-worker-mz79  Ready   Active        
8x1ev7mve4znsgs9f4n7rqa5x *  my-swarm-manager      Ready   Active        Leader
b1scfttgcghn1z36ng182nv0a    my-swarm-worker-bvm1  Ready   Active        
bg23v4d8jdi953blfbl4xnixf    my-swarm-worker-ir8d  Ready   Active        

$ docker node update my-swarm-worker-bvm1 --availability drain
$ docker node ls 
ID                           HOSTNAME              STATUS  AVAILABILITY  MANAGER STATUS
30k0ojv3slev13x0nhryhblq7    my-swarm-worker-mz79  Ready   Active        
8x1ev7mve4znsgs9f4n7rqa5x *  my-swarm-manager      Ready   Active        Leader
b1scfttgcghn1z36ng182nv0a    my-swarm-worker-bvm1  Ready   Drain        
bg23v4d8jdi953blfbl4xnixf    my-swarm-worker-ir8d  Ready   Active        
```

このDrainをマネージャノードに適用すると、最初の方に書いたmanager-only nodeになります。
Drainの間はタスクは振られません。
Activeに戻せば、タスクが振られるようになります。

## その他Tips

### シャットダウンしたサービスがうざい

`docker service ps`使うとサービスの状態が見れますが、そのままだとシャットダウンされたやつもツリー表示されてて邪魔くさいです。
というわけで便利なフィルタパラメータ。

```
docker service ps web -f desired-stat=Running
```

これで動いてるサービスだけ表示できます。
他にidとかnameとかあるけど実際つかわんやろ。


だいぶ長くなったので、とりあえずこれくらいで。
続きはまた後で。

- [GCE上でDocker Swarm使ってクラスタ構築した Part2 (マルチマネージャノード)](http://qiita.com/tora470/items/94bd09785e4313ef836c)