Tutumで負荷分散をやってみました

本記事は[Docker Advent Calendar 2015](http://qiita.com/advent-calendar/2015/docker)の9日目の記事です。

## 概要

先日書いた以下の記事で、Tutumのチュートリアルを一通りやりました。

* [Dockerコンテナを手軽にデプロイしてくれるサービス「Tutum」を試してみた](http://qiita.com/honeniq/items/a4b655e9a92d1fe84ffb)

「簡単にDockerイメージがデプロイできてスゲエ！」ということは分かったものの、チュートリアルで作ったままの状態で安易にコンテナを増やそうとすると、エラーで怒られてしまいました。

一応、Tutumのサポートサイトに[解決してくれそうな記事](https://support.tutum.co/support/solutions/articles/5000050235-load-balancing-a-web-service)があるのは見つけていましたが、「今後の宿題」ってことで後回しにしていました。

> ロードバランサー的な動きをするコンテナを立てて振り分けてあげれば実現できそうです。サポートサイトにもそれっぽい内容のページがありました。
[Load balancing a Web Service](https://support.tutum.co/support/solutions/articles/5000050235-load-balancing-a-web-service)
これは今後の宿題にしておきます。

そうこうしている内に自分の番が回ってきてしまったので、慌てて検証しつつ書いた記事がこちらとなります！


## 最小構成のスタックを作る

まず、WebとバックエンドのRedisのみのスタックを作成します。 
Tutum公式のチュートリアルで作成するものとほとんど同じ。

### Cloud Providerを追加する

せっかくなので、[以前の記事](http://qiita.com/honeniq/items/a4b655e9a92d1fe84ffb)でリンクしたAmazon Web Servicesに加えて、新たにDigital Oceanを使ってみます。

[Link your Digital Ocean account](https://support.tutum.co/support/solutions/articles/5000012151-link-your-digital-ocean-account)

DigitalOceanとのリンクは説明する必要がないほど簡単でした。「Link account」ボタンを押して、開いたページからログインするだけ。



### Nodeを追加する

DigitalOceanは「node_do」、AWSは「node_ec2」っていう名前でTutumのノードを作成します。

大したものは動かさないので、どちらも最小サイズのインスタンスで問題ありません。


### Stackを作成する

スタックを作成して、こんな感じの``Stackfile``を書きます。

``` stackfile.yml
redis:
  image: 'tutum/redis:latest'
  environment:
    - REDIS_PASS=password
web:
  image: 'honeniq/quickstart-ruby:latest'
  links:
    - redis
  ports:
    - '80:80'
```

全然やらなくても良かったことですが、Web用のコンテナとして[tutumcloud/quickstart-python](https://github.com/tutumcloud/quickstart-python)を真似て、[honeniq/quickstart-ruby](https://github.com/honeniq/quicksta)を作ってみました。動きは全く同じ（はず）です。


### 動作確認

![Kobito.k9DR0X.png](https://qiita-image-store.s3.amazonaws.com/0/2492/79278d5f-b897-5aa9-223e-e4e81cf895b5.png "Kobito.k9DR0X.png")

この時点で既に2つのノードにコンテナが1つずつ分散して動いているのが分かります。

スタックのEndpointsを覗いてみると、Webサーバーの表示が確認できました。

![Kobito.VpRgw8.png](https://qiita-image-store.s3.amazonaws.com/0/2492/db82ecb6-f7a7-b221-9e87-1f329482530d.png "Kobito.VpRgw8.png")


### Endpointについて

Tutumからは、Service endpointsとContainer endpointsの2種類が公開されます。

![Kobito.1a3SGI.png](https://qiita-image-store.s3.amazonaws.com/0/2492/5c74488e-d918-abe8-1771-85833747b085.png "Kobito.1a3SGI.png")


#### Service endpoints

最終的にWebサービスとして公開するべきなのはこちら。 
TutumがDNSラウンドロビンで負荷分散してくれます。

FQDNは *サービス名* . *スタック名* . *ユーザー名* . **svc** .tutum.io の形式。

nslookupしてみると、こう。

```
Name:	web.quickstart-ruby.honeniq.svc.tutum.io
Address: 95.85.30.162
```


#### Container endpoints

各コンテナそのものにアクセスしたいときはこちら。 
ポートを公開しているサービスのコンテナだけが表示されるので、 *redis* のコンテナは出てきません。

FQDNは *コンテナ名* . *スタック名* . *ユーザー名* . **cont** .tutum.io の形式。 

```
Name:	web-1.quickstart-ruby.honeniq.cont.tutum.io
Address: 95.85.30.162
```

今のところ、 *web* のコンテナはまだ1つしかないので、Service endpointもContainer endpointも同じIPアドレスを返しています。


## コンテナを増やしてみる

ここで *web* のコンテナを2つに増やしてみます。 
「Number of containers」を1から2に増やして「Apply」をクリック。

![image](https://qiita-image-store.s3.amazonaws.com/0/2492/bdb506b4-8465-8789-bb20-3f6cbe64aeab.png)

すぐに2つ目のコンテナが立ち上がります。

![image](https://qiita-image-store.s3.amazonaws.com/0/2492/90c807cf-ba76-b8e3-b1d8-45e42004975a.png)

ノードの状態を確認してみたところ、web-1がnode_do、web-2がnode_ec2で実行されているようです。


### TutumのDNSラウンドロビン

この状態でService endpointをnslookupしてみると・・・。

```
Name:   web.quickstart-ruby.honeniq.svc.tutum.io
Address: 95.85.30.162
Name:   web.quickstart-ruby.honeniq.svc.tutum.io
Address: 52.193.37.165
```

IPアドレスが2つ返ってくるようになりました。 
もちろん、それぞれnode_doとnode_ec2のIPアドレスです。


### 3つには増やせない

勢いに乗って3つ目の *web* コンテナを作ろうとするとエラーになります。

![image](https://qiita-image-store.s3.amazonaws.com/0/2492/4f4e97d7-00ca-90a4-256e-83b34aaa9f2b.png)

ノードがあと1つ必要だけど、もう空きがないよ、と言ってますね。

これは``Stackfile``でポートフォワードを設定していることが原因です。 
各コンテナがノードのTCP80番を占有するので、1つのノードで2つ以降のコンテナを実行させることができません。

これを解消するには、もう一工夫が必要になります。


## ロードバランサーを追加

*web* の上位にロードバランサーを追加して、そちらにTCP80番を持ってもらうように変更します。

### HA Proxyを追加

Service dashboardで「Create service」をクリックして、新しいサービスを追加します。
イメージは[Jumpstarts]-[Proxies]にある[tutum/haproxy](https://registry.hub.docker.com/u/tutum/haproxy/)を選択します。

サービスの設定画面では、追加先のスタックを指定します。（今回は *quickstart-ruby* を指定。） 
また、TCP80番を公開するように指定します。

![image](https://qiita-image-store.s3.amazonaws.com/0/2492/2a6d9604-287b-d37e-329a-0fc185a8e86b.png)

こんな感じに設定して左下の「Next: environment variables」をクリック。


#### *web* とリンクさせる

「Link Services」に *web* を指定し、「+Add」を押して追加します。

![image](https://qiita-image-store.s3.amazonaws.com/0/2492/90a631d5-11d9-e81a-869f-6228ada097b4.png)

こんな感じになっていればOK。 
「Create and deploy」をクリックします。
 
まだ *web* がTCP80番を占有しているのでエラーが出ますが、順番的に仕方ないので無視して進めます。


### *web* のポートフォワードを解除

*web* のService configurationを開いて、公開先のポートを「dynamic」に変更します。 
（ノード側のポート番号を空にすれば「dynamnic」になります。）

![image](https://qiita-image-store.s3.amazonaws.com/0/2492/65b46c0f-9133-a4be-bf28-6e798e61a914.png)


すると、こんな感じで警告が出るはず。

![image](https://qiita-image-store.s3.amazonaws.com/0/2492/c68932fc-5e47-6eaa-d90f-ab18237d9a15.png)

実行中のコンテナには設定が反映されないので、それを警告してくれています。 
「Redeploy」を押してコンテナを再起動・設定反映させます。


### *haproxy* も再起動

*web* コンテナ達の状態をうまく拾ってもらうため、*haproxy* についても「Redeploy」を押して再起動しておきます。


### 動作確認

スタックのService endpointsを表示して動作を確認します。 
（アクセス先のFQDNは haproxy.quickstart-ruby.honeniq.svc.tutum.io に変わっています。）

![image](https://qiita-image-store.s3.amazonaws.com/0/2492/411aedc8-1460-e719-fdf7-00eaad9a05da.png)

更新ボタンを押すと・・・。

![image](https://qiita-image-store.s3.amazonaws.com/0/2492/da4fe037-7954-f6ab-53eb-da8a2e535152.png)

地味ですが、アクセス先の *web* コンテナが切り替わりました。 
*haproxy* がちゃんと分散してくれているようです。


## 3つ目のコンテナを動かしてみる

先ほど失敗していた *web* の3つ目を動かしてみます。 
「Number of containers」を2から3に増やして「Apply」。

![image](https://qiita-image-store.s3.amazonaws.com/0/2492/2a7ff926-cd94-5d28-dd7f-765c91abd0b7.png)

今度はエラーも出ずに3つ目が立ち上がりました。


### 動作確認

この状態で先ほど *haproxy* のendpointにアクセスしてみましたが、web-1とweb-2にしか分散してくれていませんでした。 
なので、 *haproxy* をもう一度再起動します。

![image](https://qiita-image-store.s3.amazonaws.com/0/2492/31373cb3-4a4b-121e-d861-9ed2f08fee1f.png)

更新ボタン！

![image](https://qiita-image-store.s3.amazonaws.com/0/2492/b924e70e-fa40-5c7b-53e9-87ad17f23c91.png)

3つ目にも分散されるようになりました！

### 完成形のStackfile

参考までに、ここまで設定した状態の``Stackfile``を載せておきます。 
*haproxy* を手動で追加したせいかゴミが少し残っていたので、それは消しておきました。

``` stackfile.yml
haproxy:
  image: 'tutum/haproxy:latest'
  links:
    - web
  ports:
    - '80:80'
redis:
  image: 'tutum/redis:latest'
  environment:
    - REDIS_PASS=password
web:
  image: 'honeniq/quickstart-ruby:latest'
  links:
    - redis
  ports:
    - '80'
  target_num_containers: 3
```