Docker compose ことはじめハンズオン

修正履歴: 
@aosho235 さんのコメントより、Dockerfile の"EXPOSE 8888" の不要な記述を修正
@aosho235 さんのコメントより、node アプリを起動するコマンドが抜けていた点を修正(Dockerfile, docker-compose.yml)
@alt さんより編集リクエスト。シンタックスハイライトを適切なものに修正

感謝<(_ _)>

# Docker Compose 概要 
Docker compose とは、複数のコンテナから成るサービスを構築・実行する手順を自動的にし、管理を容易にする機能です。
Docker compose では、compose ファイルを用意してコマンドを1 回実行することで、そのファイルから設定を読み込んですべてのコンテナサービスを起動することができます。

# Docker Compose を使うまでの主なステップ 
Docker compose を使用するには、大きく分けて以下の3 つの手順から成り立ちます。

* それぞれのコンテナのDockerfile を作成します(既にあるイメージを使う場合は不要)
* docker-compose.yml を作成し、それぞれ独立したコンテナの起動定義を行います(場合によっては構築定義も含まれる)
* "docker-compose up" コマンドを実行してdocker-compose.yml で定義したコンテナを開始します

Docker compose はstart, stop, status, 起動中のコンテナのログ出力, ちょっとしたコマンドの実行といった機能も持ち合わせています(今回は割愛)。

# docker-compose.yml ファイルとは 
`docker-compose.yml` ファイルは以下のようにyaml でDocker コンテナに関する起動オプション(build オプションも含まれることもある)を記述したファイルになります。

```yaml:docker-compose.ymlサンプル
web:
  build: .
  ports:
   - "5000:5000"
  volumes:
   - .:/code
  links:
   - redis
redis:
  image: redis

# yaml の記載方法については下記を参照
# https://docs.docker.com/compose/compose-file/
```


このファイルに記載されている内容は基本的に`docker build`, `docker run` コマンド指定することができるオプションになりますが、Docker compose のyaml ファイルとして記述することで複数のコンテナーから成るサービスを俯瞰的に見ることができるようになり、保守性のコストを軽くすることができます。

# 環境要件
今回実施した環境は以下のとおりです。

* 構成
<table><tbody>
  <tr>
    <th> OS</th>
    <td> Ubuntu 15.04</td>
  </tr>
  <tr>
    <th> Docker Engine</th>
    <td> version 1.9.1</td>
  </tr>
</tbody></table>

# 事前準備 
Docker Engine は既にインストールされているものとします。
*[Docker Engine をDocker リポジトリからインストールする(Ubuntu 15.04, 14.04 LTS)](https://github.com/docker/compose/releases "Docker Engine をDocker リポジトリからインストールする(Ubuntu 15.04, 14.04 LTS)")*

# Docker compose のインストール 
基本的にはdocker compose のリリースページにアクセスして最新情報を確認し、Docker compose をインストールしていくことになります。
*[Docker compose release page](https://github.com/docker/compose/releases "Docker compose release page")*

curl コマンドを使い以下のようにすることでインストールは完了です(一部docker compose のリリースページと異なる手順にしてあります)。

```bash:composeインストールコマンド(onUbuntu)
sudo COMPOSE_VERSION=1.5.2 bash -eu << '__EOT__'
echo ${COMPOSE_VERSION}
curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
chown root:docker /usr/local/bin/docker-compose
chmod ug+x /usr/local/bin/docker-compose
update-alternatives --install /usr/bin/docker-compose docker-compose /usr/local/bin/docker-compose 10
__EOT__
```


インストールが完了したら、`docker-compose` コマンドを実行して動作を確認してみましょう。

```bash:docker-composeコマンド確認
$ docker-compose --version
docker-compose version 1.5.2, build 7240ff3
```


version が出力されれば、インストール成功です。

# 今回動作確認を行う環境について 
今回DockerCompose のハンズオンを行うに当たり、資材の構成は以下のようになっています。

```text
(Working dir)
+- docker-compose.yml
+- app-server/
   +- Dockerfile
   +- src/
     +- app.js
```


# Docker compose を使用した簡単なサンプル 
Docker compose を使用した複数コンテナでひとつのサービスを作成するサンプルを実施してみます。
Document とほぼ同じ内容になりますが、のredis を使用してアクセス数をカウントする簡単なアプリケーションを作成してみます。

# application 側の作成 
アプリケーション側はnodejs を使用します。
今回はnode の公式リポジトリを使用するための、Dockerfile は以下のように簡単なものとなります。


```docker:app-server/Dockerfile
FROM node:5
RUN npm -g install redis
ENV NODE_PATH /usr/local/lib/node_modules

ENTRYPOINT ["node", "app.js"]
```

次に、nodejs のソースコードを作成します。ディレクトリを以下のように作成します。


```javascript:app-server/src/app.js
var redis = require('redis');
var redis_client = redis.createClient(6379, "noderedis");
var listen_port = 10080;

require('http').createServer(function (request, response) {
    redis_client.incr('counter', function(error, reply) {
        response.writeHead(200, {'Content-Type': 'text/plain'});
        response.end("You accessed here " + reply + " times.\n");
    });
}).listen(listen_port, '0.0.0.0');
console.log("Server is running on port " + listen_port + ".");
```

# Redis 側の作成 
Redis 側のコンテナの作成を行いますが、公式のRedis イメージを使用するためこちらはDockerfile は不要です。

# Docker compose ファイルの作成 
アプリ側のnode のアプリケーションとredis 側のアプリケーションをbuild, run するための定義をdocker-compose ファイルに記載します。

```yaml:docker-compose.yml
nodeapp:
  build: "./app-server"
  container_name: "nodeapp"
  working_dir: "/usr/src/app"
  ports:
   - "10080:10080"
  volumes:
   - "$PWD/app-server/src:/usr/src/app"
  links:
   - "noderedis"
noderedis:
  image: "redis:3"
  container_name: "noderedis"
```

※@aosho235 さんのコメントより、Dockerfile に"ENTRYPOINT" を書かなかった場合はdocker-compose.yml に下記のように追記する方法で対応することも可能。

```yaml
nodeapp:
  ......
  command: node app.js
```

今回はnode のアプリケーションが乗るコンテナは、Dockerfile からイメージをbuild してそこからnodeapp という名前のコンテナを起動します。
redis が乗るコンテナについては、Dockerfile からイメージをbuild せずにDocker hub からpull してきた公式イメージを利用し、そこからnoderedis という名前のコンテナを起動します。
`docker-compose.yml` ファイルのあるディレクトリまで移動し、`docker-compose up` コマンドを実行します。


```sh:docker-composeup
$ docker-compose up
...
```

![Docker DockerComposeHandsOn0000.png](https://qiita-image-store.s3.amazonaws.com/0/70152/7435c253-7a13-2175-dbda-ded691424e8b.png)


上記のようにnode アプリとredis アプリのコンテナが同時に起動し、ひとつのコンソール上にそれぞれのコンテナのタグ付きでログが出力されます。
正常に起動したところで、動作確認をしてみましょう。


```sh:動作確認
$ curl http://localhost:10080
You accessed here 1 times.
$ curl http://localhost:10080
You accessed here 2 times.
$ curl http://localhost:10080
You accessed here 3 times.
```


上記のようにredis からデータを引き出してきてアクセス数がカウントできていれば成功です。

## docker-compose コマンドで作成されるイメージ名について 
`docker-compose up` コマンドを使用すると自動的にDocker イメージも作成してくれますが、そのイメージは以下のようなイメージとなります。


```sh:docker-composeによって作成されたイメージ名とコンテナ名
$ docker images
REPOSITORY                           TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
dockercomposeexamplesimple_nodeapp   latest              7d39370b4309        23 hours ago        643.5 MB
redis                                3                   0c4334bed751        4 days ago          151.3 MB
node                                 5                   285fd945c0b6        2 weeks ago         642.7 MB
debian                               jessie              23cb15b0fcec        4 weeks ago         125.1 MB

$ docker ps -a
CONTAINER ID        IMAGE                                COMMAND                  CREATED             STATUS              PORTS                                NAMES
e48a2014b88d        dockercomposeexamplesimple_nodeapp   "node app.js"            4 minutes ago       Up 4 minutes        0.0.0.0:10080->10080/tcp   nodeapp
b8b47ca9a285        redis:3                              "/entrypoint.sh redis"   23 hours ago        Up 4 minutes        6379/tcp                             noderedis
```


上記の出力結果を確認すると、Redis については Docker Hub 公式からpull してきたイメージなので`redis:3` というわかりやすいイメージ名(+ タグ) ですが、nodejs に関しては`dockercomposeexamplesimple_nodeapp` というイメージ名が自動的につけられています。調べたところ、現段階ではイメージの名前を`docker-compose.yml` ファイル内で指定する方法は仕様として無いようです(2015/01/03 現在)。
このイメージは、最終的にはnodejs のコンテナとして`docker-compose.yml` 内で指定した名前で作成されるので、動作上問題はありません(例えばlink でこのnodejs コンテナと接続する必要性のあるコンテナが出てきてもdocker-compose.yml 内で指定したものになるので、予め接続先のコンテナ名は推測できるし、構築の自動化に支障はきたしません)。

しかしどうしても見栄えが悪い、直接docker コマンドを実行して構築するシーンが出てきた時に乱雑なイメージのリストを見るのは嫌だ…という場合は事前にbuild したdocker イメージを指定してcompose-up を実行するという手順があります。

まず、`docker-compose.yml` ファイルを以下の用に編集します。先ほど使用したファイルに`image:` 要素が追加され、`build:` 要素が削除された形となります。


```yaml:docker-compose.yml
nodeapp:                            # <- サービス名
  image: "tsutomu/nodeapp"          # <- 使用するDocker イメージ名(この後ビルドする)
  container_name: "nodeapp"         # <- コンテナ名。指定しなかった場合はDocker compose で勝手に決められる
  working_dir: "/usr/src/app"       # <- コンテナ内のワーキングディレクトリ。docker run コマンドの-w/--workdir に相当
  ports:                            # <- Expose するポート。docker run コマンドの-p/--publish に相当
   - "10080:10080"
  volumes:                          # <- Bind mount するディレクトリ。volume。docker run コマンドの-v/--volume に相当
   - "$PWD/app-server/src:/usr/src/app"
  links:                            # <- 他のコンテナと接続するときのコンテナ名。docker run コマンドの--link に相当
   - "noderedis"
noderedis:
  image: "redis:3"                  # <- イメージIDとtag
  container_name: "noderedis"
```


新規に追加した`image:` の値に一致するように`docker build` コマンドでイメージを作成し、`docker-compose up` コマンドでコンテナを起動します。

```sh:dockerbuild
$ docker build -t "tsutomu/nodeapp" ./app-server
```


ここで、イメージ名とコンテナ名を確認してみましょう。
先ほどと違い、docker-compose によって作成されたイメージは存在しないはずです。


```sh:イメージ名とコンテナ名を確認する
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
tsutomu/nodeapp     latest              2169cc98c4e2        2 minutes ago       643.5 MB
redis               3                   0c4334bed751        4 days ago          151.3 MB
node                5                   285fd945c0b6        2 weeks ago         642.7 MB
debian              jessie              23cb15b0fcec        4 weeks ago         125.1 MB

$ docker ps -a 
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                NAMES
d9c9f2a70735        tsutomu/nodeapp     "node app.js"            9 seconds ago       Up 8 seconds        8888/tcp, 0.0.0.0:10080->10080/tcp   nodeapp
e295c5ff24fc        redis:3             "/entrypoint.sh redis"   9 minutes ago       Up 9 minutes        6379/tcp                             noderedis
```


# 参考 
<dl>
  <dt> Overview of Docker Compose</dt>
  <dd> https://docs.docker.com/compose/#features</dd>
</dl>

<dl>
  <dt> Using Redis with Node.js</dt>
  <dd> http://www.sitepoint.com/using-redis-node-js/</dd>
</dl>

<dl>
  <dt> It should be possible to explicit name images and containers</dt>
  <dd> https://github.com/docker/compose/issues/541</dd>
</dl>