DockerCon EU 2015のオーケストレーションハンズオン on Azure

Docker Orchestration Toolsのチュートリアル
===
この度DockerCon EU 2015に参加してきました。
![12281685_1065455416827609_1024759276_o.jpg](https://qiita-image-store.s3.amazonaws.com/0/3470/024e0105-ce97-6ef7-0693-ea1c698843e3.jpeg)
のハンズオンラボの内容が面白そうだったのですが、私は自社で出店していたブースのお守りもあったため、当日はあまりできませんでした。ですので、帰国後自分で楽しんでみることにしました。

今回はAzureで動作させるときの手順を書いてみたいと思います。

![12281855_1065456156827535_719777253_o.jpg](https://qiita-image-store.s3.amazonaws.com/0/3470/0365829f-1025-8ad8-658e-9bc46a775588.jpeg)


[Lab 2 : Docker Orchestration](https://github.com/docker/dceu_tutorials/blob/master/2-orchestration.md)これが、Docker社が公開していた、ハンズオンです。
Swarm、Compose、そしてInterlockを使って、Swarmの環境にスケールするWeb-DBアプリケーションをデプロイするデモです。いろいろはまりましたが楽しかったです。

1. インスタンスの準備
---
実はこのチュートリアル、本来はAWSでインスタンスが提供されるようになっていました。
環境を作ってわかったのですが、そこまで行くのが結構めんどくさいからでした。

普通だと、docker-machineでちょろっとインスタンス用意したらDockerの環境環境構築など簡単です。ところが、本チュートリアルは、TLSを使わない前提になっているのですが、最新のdocker-machineはTLSをオフにすることができないようです。

つまり、TLSを使わないDocker環境を作らないといけないわけです。

また、チュートリアルでは３つのインスタンスを使いますが、最後の一つのインスタンスはスケールアウトするときにポートをたくさん使います。ですので、ポートをレンジで開ける必要がありますが、例えばAzureのクラシックVMはこれが得意ではありません。実際にやろうと思うと、PowerShellでちょろっとやらないといけないようです。

また、最初は、Ubuntu 14.04（LTS)を使うつもりでしたが、Dockerの標準のインストール手順で実施すると、その通りにできない感じでしたので、これを機会に、15.04に取り組んで見ることにしました。

Ubuntu 15.04のVMをブートすればよいですが、classic VMではなく、ResourceManagerのVMをブートしましょう。classic VMだと、たくさんのポートを開けるのは面倒ですが、ResourceManagerのVMを使うと、ポートを範囲指定することができます。

<img width="1290" alt="Screen Shot 2015-11-23 at 19.03.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/d917f055-4123-e1f5-fe7b-23d7a51a758c.png">

<img width="1350" alt="Screen Shot 2015-11-23 at 22.47.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/09fc3e8c-4bc1-4f5a-5858-455140b66488.png">


わたしはこのチュートリアルのためにswarm-master, swarm-agent-01, swarm-agent-02というインスタンスを３台起動しました。

2. Dockerのインストール
---
Dockerのインストールは下記の手順に従うとあっさりできました。

[Docker / Ubuntu](http://docs.docker.com/engine/installation/ubuntulinux/)

わたしが実施した内容をメモとして書いておきます。3台のマシンとも同じ手順です。

### apt-getのリポジトリに使うキーを取得

```
$ sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
```

### apt-getのリポジトリを追加

`/etc/apt/sources.list.d/docker.list`ファイルを下記のようにする

```
# Ubuntu Vivid 15.04
deb https://apt.dockerproject.org/repo ubuntu-vivid main
```

### リポジトリを更新しておく

```
$ sudo apt-get update
```

### おすすめパッケージ群のインストール

```
$ sudo apt-get install linux-image-extra-$(uname -r)
```
### Docker Engineのインストール

```
$ sudo apt-get update
$ sudo apt-get install docker-engine
$ sudo service docker start
```
### 動作確認

```
$ sudo docker run hello-world
```

これでまずはあっさり動きました。

3. Dockerを他のマシンから使えるようにするための設定
---

Swarmを使うときは、Dockerを他のマシンから使えるようにする必要があります。今回実施したチュートリアルは、TLSを使わないようになっているので、TLSを使わずにdockerの設定をします。一般的に出回っているのは、Ubuntu 14.04の手順なので、Ubuntu 15.04だと若干手順が異なります。

これは、Ubuntu 15.04からUpstartの代わりにSystemdが使われるようになったからです。大抵のインターネットの手順には次のように書いています。これは15.04では、動作しない方法です。デーモンを動かすときに、ソケットと、2375ポートをリッスンするようにしています。

```
$ sudo nano /etc/default/docker
# Use DOCKER_OPTS to modify the daemon startup options
DOCKER_OPTS="-H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --label=environment=staging"
```

代わりに次の手順を使います。

`/etc/systemd/system/docker-tcp.socket`というファイルを作ります。systemdでは
このようにファイルを作って、ポートのリッスンを有効にします。

```
[Unit]
Description=Docker Socket for the API  
[Socket]
ListenStream=2375  
BindIPv6Only=both  
Service=docker.service  
[Install]
WantedBy=sockets.target  
```

その後

```
$ sudo systemctl enable docker-tcp.socket
$ sudo systemctl enable docker.socket
$ sudo systemctl stop docker
$ sudo systemctl start docker-tcp.socket
$ sudo systemctl start docker
```
テストは次のようにすると、ネットワーク経由で、dockerがアクセスできるようになっているかを確認できます。

```
$ docker -H tcp://127.0.0.1:2375 ps
```

手元のクライアントから、このdockerホストを操作指定場合、クラウドを利用しているときなど2375ポートをオープンする必要があります。なおこの設定ができていたらお手元のクライアントから。

```
$ export DOCKER_HOST=xxxxxxx:2375
```
を設定することで、リモートからdockerを操作することができます。
ただし、平文で通信していますので、実際の運用ではTLSを使ってください。TLSを使っている場合、ポート番号は2376になります。

[Setting Docker’s DOCKER_OPTS on Ubuntu 15.04](http://blog.benhall.me.uk/2015/07/setting-dockers-docker_opts-on-ubuntu-15-04/)
[Enable Remote API on Docker hosts running systemd (like Ubuntu 15.04)](http://www.campalus.com/enable-remote-tcp-connections-to-docker-host-running-ubuntu-15-04/)

4. SwarmとComposerのインストール
---
swarm-masterのサーバーに、SwarmとComposerをインストールします。

### Composerのインストール (swarm-master)

Composerのインストールはとても簡単です。dockerを外部から使える設定
にしている場合は、お手元のコンピュータにインストールしても結構です。

[Install Docker Compose](https://docs.docker.com/compose/install/)

今回は、swarm-masterのサーバーにインストールしてみました。

```
master $ sudo -i
master # curl -L https://github.com/docker/compose/releases/download/1.5.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
master # chmod +x /usr/local/bin/docker-compose
master # exit
master $ docker-compose --version
docker-compose version: 1.5.1
```


### agentサーバーへ環境のラベルを設定する (swarm-agent-0x)

今回の環境では、

* swarm-master   :  Swarmのマスター
* swarm-agent-01 :  Staging環境
* swarm-agent-02 :  Production環境

となっています。これらの環境の区別をするために、swarm-agent-01, swarm-agent-02でそれぞれ次の設定を行います。

それぞれの環境にdockerデーモン起動時に`--label=environment=staging`と`--label=environment=production`のラベルをつけます。そのためには、先ほどのsystemdを利用します。

`/lib/systemd/system/docker.service`のファイルを次のように編集します。ポイントは、EnvironmentFileの追加と、ExecStartのところへの $DOCKER_OPTSの追加です。

```
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network.target docker.socket
Requires=docker.socket

[Service]
Type=notify
EnvironmentFile=/etc/default/docker
ExecStart=/usr/bin/docker daemon -H fd:// $DOCKER_OPTS
MountFlags=slave
LimitNOFILE=1048576
LimitNPROC=1048576
LimitCORE=infinity

[Install]
WantedBy=multi-user.target
```

`/etc/default/docker`ファイルにも次の行を追加します

swarm-agent-01サーバ

```
DOCKER_OPTS="--label=environment=staging"
```

swarm-agent-02サーバ

```
DOCKER_OPTS="--label=environment=production"
```

その上で、swarm-agent-01, swarm-agent-02の両方のサーバーでdockerデーモンを再起動します。止めようとすると、エラーが出るときがありますが、そのエラーメッセージにかかれている、systemctlコマンド(systemctl daemon-reload)を実行すると問題なくなります。

```
$ sudo systemctl stop docker-tcp.socket
$ sudo systemctl daemon-reload
$ sudo systemctl stop docker
$ sudo systemctl start docker-tcp.socket
$ sudo systemctl start docker
```

### swarm-masterでswarmのインストール

swarmのトークンを振り出します。このトークンはクラスタを区別するためのユニークな識別子です。そして、下記の指定でswarmのマネージサーバーを起動します。これ自体がdockerのコンテナです。尚、本チュートリアルでは、3375ポートはオープンする必要があります。

```
master $ export DOCKER_HOST=127.0.0.1:2375
master $ export TOKEN=$(docker run --rm swarm create)
master $ docker run -d -p 3375:2375 swarm manage token://$TOKEN
```
### エージェントサーバーのswarmへの追加

２台のエージェントサーバーをswarmに追加します。２台のエージェントサーバーのプライベートアドレスを記入します。Resource Managerを利用してVMを起動したときのプライベートアドレスの発見方法はAzureの新ポータルからswarm-masterの場合

swarm-master > Settings > Network interfaces でパブリックと、プライベートのポートを見ることができます。プライベートIPは同じリソースグループの間であればInboundポートの設定をしなくてもアクセス可能です。

```
master $ docker run -d swarm join --addr=<swarm-agent-01_private_ip:2375> token://$TOKEN
master $ docker run -d swarm join --addr=<swarm-agent-02_private_ip:2375> token://$TOKEN
```

動作確認。エージェントサーバーが追加されていることがわかります。下記のように認識されないケースは、docker logs <container id>などで原因を探ってください。わたしは、`token://$TOKEN`のところを間違えて`token//$TOKEN`としていて、エラーがでないため、ちょっとはまりました。

```
$ docker -H tcp://0.0.0.0:3375 info
Containers: 0
Images: 0
Role: primary
Strategy: spread
Filters: health, port, dependency, affinity, constraint
Nodes: 2
 node-1: ************.compute.amazonaws.com:2375
  └ Containers: 3
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 3.859 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=3.19.0-26-generic, operatingsystem=Ubuntu 14.04.3 LTS, storagedriver=aufs
 node-2: *****************.eu-central-1.compute.amazonaws.com:2375
  └ Containers: 2
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 3.859 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=3.19.0-26-generic, operatingsystem=Ubuntu 14.04.3 LTS, storagedriver=aufs
CPUs: 2
Total Memory: 7.718 GiB
Name: 5dc162006656
```
5. デプロイの実施
---

今はdockerコンテナとして、swarm managerが動作しているので、DockerHostを変更しておきます。

```
master $ export DOCKER_HOST={swarm-master-PrivateIP}:3375
```

今回dockerから、サンプルが提供されているのでそれを使ってみます。適当なディレクトリで下記のファイルを展開してください。

```
master $ git clone https://github.com/nicolaka/dockchat.git
```

このような感じになっています。

```
master $ cd dockchat/
swarm-master:~/dockchat$ tree
.
├── docker-compose.yml
├── Dockerfile
├── README.md
├── requirements.txt
├── static
│   └── style.css
├── templates
│   └── form_action.html
└── webapp.py
```

本家のチュートリアルでは、docker-composeのみで動作させる手順も載っていますが、割愛して、いきなりswarmの用の手順を書いておきます。

最初にDockerHubにログインしてください。

```
master $ docker login
```

DockerHubのユーザ名＋dockchatというアプリ名をつけてタグをつけます。そして、DockerHubにpushします。

```
master $ docker build -t {Your_Docker_Hub_username}/dockchat:v1 .
master $ docker push {Your_Docker_Hub_username}/dockchat:v1
```

次に、docker-compose.ymlを、staging.docker-compose.ymlとしてコピーして次のように変更します。environmentがdbとwebに追加されているのと、web:の次の行が、build: . からimageに変更されて、先ほどDockerHubにあげたイメージを取得するようになっています。

```
# Mongo DB
db:
  image: mongo
  expose:
    - 27017
  command: --smallfiles
  environment:
   - "constraint:environment==staging"
# Python App
web:
  image: <your_Docker_Hub_username>/dockchat:v1
  ports:
    - "5000:5000"
  links:
   - db:db
  environment:
   - "constraint:environment==staging"

```

### stagingへのデプロイ

ステージングへデプロイしてみましょう。

```
master $ docker-compose -f staging.docker-compose.yml -p dockchat_staging pull
master $ docker-compose -f staging.docker-compose.yml -p dockchat_staging up -d

Creating dockchatstaging_db_1
Creating dockchatstaging_web_1

master $ docker-compose -f staging.docker-compose.yml -p dockchat_staging ps

        Name                      Command             State             Ports
---------------------------------------------------------------------------------------
dockchatstaging_db_1    /entrypoint.sh --smallfiles   Up      27017/tcp
dockchatstaging_web_1   python webapp.py              Up      10.0.10.77:5000->5000/tcp
```

これでステージング(swarm-agent-01)へデプロイがなされました。興味があればswarm-agent-01のinboundの5000番ポートを開けると、swarm-agent-01のパブリックIP:5000でチャットアプリの画面が見れます。


### productionへのデプロイ

同じく、`production.docker-compose.yml`を作成します。本番では、[interlockのHAProxyプラグイン](https://github.com/ehazlett/interlock/tree/master/plugins/haproxy)を使って、スケールと、負荷分散を実装します。

```
# Mongo DB
db:
  image: mongo
  expose:
   - 27017
  command: --smallfiles
  environment:
   - "constraint:environment==production"
# Python App
web:
  image: nicolaka/dockchat:v1
  ports:
   - "5000"
  links:
   - db:db
  environment:
   - "constraint:environment==production"
   - INTERLOCK_DATA={"hostname":"dockchat.com","domain":"dockchat.com"}
interlock:
  image: ehazlett/interlock:latest
  ports:
    - "80:80"
  volumes:
    - /var/lib/docker:/etc/docker
  environment:
   - "constraint:environment==production"
  command: "--swarm-url tcp://$DOCKER_HOST --debug --plugin haproxy start"
```

同じようにプロダクションにもデプロイします。その前に、ポートを公開しておきましょう。Interlockは、どうやら、30000以上のポートを使うようなので、そこはAzureの新ポータルで公開しておきましょう。

<img width="1200" alt="Screen Shot 2015-11-23 at 22.28.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/c7ccb23b-44d9-e5c7-262e-b55024933039.png">

わたしは30000-65535を公開してみました。swarm-agent-02 > Settings > inbound security rules で、次のように設定しました。これは、Resource ManagerのVMでしかレンジ設定できないので注意が必要です。同様に、80番ポートも公開しておきます。Interlockは、HAProxyなので、このポートから実際のポートに割り振りを行います。ちなみに本当は、2375のポートを開ける必要は本来ありません。



```
Name : Swarm
Priority : 1200
Protocol : TCP
Source port range : *
Destination port range : 30000-65535
Action : Allow
```

```
master $ docker-compose -f production.docker-compose.yml -p dockchat_production pull
master $ docker-compose -f production.docker-compose.yml -p dockchat_production up -d
master $ Creating dockchatproduction_db_1
Creating dockchatproduction_web_1
Creating dockchatproduction_interlock_1

master $ docker-compose -f production.docker-compose.yml -p dockchat_production ps
             Name                           Command               State               Ports
--------------------------------------------------------------------------------------------------------
dockchatproduction_db_1          /entrypoint.sh --smallfiles      Up      27017/tcp
dockchatproduction_interlock_1   /usr/local/bin/interlock - ...   Up      443/tcp, 10.0.11.50:80->80/tcp
dockchatproduction_web_1         python webapp.py                 Up      10.0.11.50:32769->5000/tcp
```

手元のマシンの、/etc/hostsを下記のように書き換えます。プロダクションサーバーの
IPアドレス(swarm-agent-02のアドレス)をdockchat.comとして指定してみてください。

```
mylaptop$ vim /etc/hosts

(+) {PUBLIC_IP_OF_NODE-2}   dockchat.com
```
最後にスケールアップします。注意事項としては、このスケールアウトでは、ポートが自動で割り振られ
ます。スケールアウト時にこのポートが空いている必要があります。そうすれば、Interlockが認識してくれます。
そうでないと、せっかくスケールアウトしても、Interlockが認識してくれません。

```
machine $ docker-compose -f production.docker-compose.yml -p dockchat_production scale web=10

Creating and starting 2 ... done
Creating and starting 3 ... done
Creating and starting 4 ... done
Creating and starting 5 ... done
Creating and starting 6 ... done
Creating and starting 7 ... done
Creating and starting 8 ... done
Creating and starting 9 ... done
Creating and starting 10 ... done


machine $ docker-compose -f production.docker-compose.yml -p dockchat_production ps

             Name                           Command               State               Ports
--------------------------------------------------------------------------------------------------------
dockchatproduction_db_1          /entrypoint.sh --smallfiles      Up      27017/tcp
dockchatproduction_interlock_1   /usr/local/bin/interlock - ...   Up      443/tcp, 10.0.11.50:80->80/tcp
dockchatproduction_web_1         python webapp.py                 Up      10.0.11.50:32769->5000/tcp
dockchatproduction_web_10        python webapp.py                 Up      10.0.11.50:32771->5000/tcp
dockchatproduction_web_2         python webapp.py                 Up      10.0.11.50:32770->5000/tcp
dockchatproduction_web_3         python webapp.py                 Up      10.0.11.50:32775->5000/tcp
dockchatproduction_web_4         python webapp.py                 Up      10.0.11.50:32777->5000/tcp
dockchatproduction_web_5         python webapp.py                 Up      10.0.11.50:32774->5000/tcp
dockchatproduction_web_6         python webapp.py                 Up      10.0.11.50:32773->5000/tcp
dockchatproduction_web_7         python webapp.py                 Up      10.0.11.50:32772->5000/tcp
dockchatproduction_web_8         python webapp.py                 Up      10.0.11.50:32776->5000/tcp
dockchatproduction_web_9         python webapp.py                 Up      10.0.11.50:32778->5000/tcp
```

さて、お手元のブラウザで `dockchat.com`にアクセスしてみてください。リロードごとに違うインスタンスが使われているのがわかると思います。

<img width="1200" alt="Screen Shot 2015-11-23 at 22.28.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/53309379-d812-77fb-b746-c59ce2ef2c62.png">
<img width="1200" alt="Screen Shot 2015-11-23 at 22.28.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/be816a3f-a0a3-19c3-c2d0-146407a2c6e0.png">


　また、`dockchat.com/haproxy?stats`を見ると、管理画面を見ることができます。
ちなみにユーザ/パスワードを聞かれたら `stats/interlock`です。
<img width="1200" alt="Screen Shot 2015-11-23 at 22.25.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/90e84da1-5520-3355-8d9d-649c756712c0.png">



6. 障害時には？
---

さて、スケールアウトのときになぜか画面が出てこないとかの問題に出会ったら、先の管理画面にちゃんとサーバーが認識されているか？というのと、ログを見ることをしてみましょう。

```
master $ docker-compose -f production.docker-compose.yml -p dockchat_production logs
```
という感じでログを見ることができます。

ではみなさまハッピーコーディングを！