Dockerコンテナの分割は開発時はプロセス毎にしたほうがいい。

## tl; dr

https://github.com/kotamat/laravel-docker-cluster

## 背景

一つのプロジェクトには、いろんなミドルウェアが入ってくる事があります。例えば、PHPの場合、よく言われるのはLAMPというものですが、これは

- Linux
- Apache
- Mysql
- PHP

を一つにまとめた略称であり、これらがあることによって、PHPのWebアプリケーションが実行可能な状況になります。(もちろんMysqlなくてもPHPは動きますが、DataStoreが無いWebアプリケーションはそんなに無いと思われるので、この中に入れているのでしょう。)

Dockerを初めて使うときは、上記をすべてインストールした一つのimageに固め、一つのコンテナで動作させようと思うかもしれないですが、そうしてしまうと、一つのミドルウェアのバージョンだけを上げたいと思っても、再度すべてビルドし直さなければなりません。

もし`yum install -y mysql`コマンドのようにバージョン指定せずインストールしていた場合、意図せず他のミドルウェアのバージョンが上がってしまうかもしれません。

また、開発時でしか使わないようなミドルウェアを入れたりする場合、アプリケーション側のイメージとは分離してミドルウェアをいれ、実行する環境を物理的に分けたいという場面に出くわすかもしれません。

今回例に上げるLaravelでは、開発補助も含め複数のミドルウェア、ツールが存在するため、これらを物理的に分ける方法を考えたいと思おいます。今回は下記を分離します。

- artisan: Controllerの自動生成やmigration等のcliツール
- elixir: nodejsベースのasset系ファイル(js, css)の生成、browserSyncなどのブラウザとの連携ツール、実態はgulpタスク
- npm: nodejsのライブラリ管理ツール。
- composer: phpのライブラリバージョン管理ツール
- php-fpm: phpのcgiミドルウェア、PHPアプリケーションとして使う
- nginx: php-fpmへのリバースプロキシ・サーバー
- mariadb: データストア

## Volume Mount

DockerにはDockerVolumesというボリュームマウントの仕組みがあります。

```bash:volumemount
docker run -v `pwd`:/backup ubuntu /bin/bash
```

上記の様に`-v`オプションを指定すれば、現在のディレクトリをubuntuコンテナの/backupディレクトリにマウントでき、あたかもubuntuコンテナ内では/backupディレクトリが存在する一つのコンテナのように動作します。

明示的にディレクトリを指定したくない場合はimage作成時にvolumeの割り当てを行います。

```bash:Image作成
docker create -v /backup ubuntu /bin/true
```

## Volume Container

上記のマウントの仕組みを応用した例としてVolume Containerというものを作成してデータを永続化しようというパターンがよく使われます。このパターンを使うときは[docker-compose](https://docs.docker.com/compose/)を使用すると、一連の流れを一つのファイルにまとめられて便利です。下記のように書きます。


```yml:docker-compose.yml
version: "2"
services:
  data:
    image: busybox
    volumes:
      - /var/lib/mysql
  db:
    image: mariadb
    volumes_from:
        - data
```

volumes_fromでvolume containerのコンテナサービス名を指定し、そこに割り当てられているボリュームを共有します。

こうすることで、もし`db`サービスが停止したとしてもbusyboxコンテナに割り当てられている/var/lib/mysqlが存命しているので、再起動すればデータは復活します。

## プロセス毎に分けてみる。

上記背景で述べたプロセス毎に分けると下記の様になります。

```yml:docker-compose.yml
version: "2"
services:
  data:
    build: ./data
    volumes:
      - ../:/data
      - /var/lib/mysql
  db:
    image: mariadb
    volumes_from:
        - data
    environment:
      MYSQL_ROOT_PASSWORD: pass
  fpm:
    build: ./fpm
    volumes_from:
      - data
  nginx:
    build: ./nginx
    volumes_from:
      - data
    links:
      - fpm:fpm
    ports:
      - "80:80"
  composer:
    build: ./composer
    volumes_from:
      - data
  gulp:
    build: ./gulp
    volumes_from:
      - data
  npm:
    build: ./npm
    volumes_from:
      - data
```

各サービスでbuildオプションを指定しているのは、前述のバージョンアップに備えるためにDockerfileを変更可能にするためで、下記のディレクトリ構成でDockerfileを管理しています。チーム運用する場合は、どこかのレジストリにimageを上げて、docker pull できる形で運用した方がいいと思います。

```bash
./
├── composer
│   └── Dockerfile
├── data
│   └── Dockerfile
├── docker-compose.yml
├── fpm
│   ├── Dockerfile
│   └── php-fpm.d
│       ├── docker.conf
│       ├── www.conf
│       ├── www.conf.default
│       └── zz-docker.conf
├── gulp
│   └── Dockerfile
├── nginx
│   ├── Dockerfile
│   └── config
│       ├── laravel.conf
│       ├── nginx.conf
│       └── nginx_run.sh
└── npm
    └── Dockerfile
```

## プロセス毎に実行してみる。

まず、デーモン化するべきコンテナと都度実行でいいコンテナを分けてみましょう。

- デーモン化するべきコンテナ
	- nginx
	- fpm
	- db
- 都度実行で良いコンテナ
	- composer
	- gulp
	- npm

上記認識を元に`docker-compose up -d`を実行してみると

```bash
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                         NAMES
b50f05bbf59d        mariadb             "docker-entrypoint.sh"   15 minutes ago      Up 15 minutes       3306/tcp                      docker_db_1
158a52716aae        docker_nginx        "nginx -g 'daemon off"   15 minutes ago      Up 15 minutes       443/tcp, 0.0.0.0:81->80/tcp   docker_nginx_1
56fd3fab1e52        docker_fpm          "php-fpm"                15 minutes ago      Up 15 minutes       9000/tcp                      docker_fpm_1
```

上記のようにデーモン化するべきコンテナのみプロセスとして動いている事が確認できます。

都度実行でいいコンテナに関しては、`docker run --rm`コマンドを使用することで、実行後コマンド用コンテナが削除され、クリーンな運用をすることができます

### 都度実行例

```bash
# composer
docker-compose run --rm composer create-project --prefer-dist laravel/laravel blog

# npm
docker-compose run --rm npm i -g gulp

# gulp
docker-compose run --rm gulp watch
```


## まとめ

- コンテナの分け方をプロセス毎にすることによって必要なタイミングで必要なコマンドを実行するイメージを作成でき、アプリケーションのイメージをクリーンな状態で保つ事ができる。
- また各プロセス毎のミドルウェアのバージョンアップ、メンテナンスも、設定ファイルが別れているので、簡単に編集が可能。