tl; dr
背景
一つのプロジェクトには、いろんなミドルウェアが入ってくる事があります。例えば、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というボリュームマウントの仕組みがあります。
docker run -v `pwd`:/backup ubuntu /bin/bash
上記の様に-v
オプションを指定すれば、現在のディレクトリをubuntuコンテナの/backupディレクトリにマウントでき、あたかもubuntuコンテナ内では/backupディレクトリが存在する一つのコンテナのように動作します。
明示的にディレクトリを指定したくない場合はimage作成時にvolumeの割り当てを行います。
docker create -v /backup ubuntu /bin/true
Volume Container
上記のマウントの仕組みを応用した例としてVolume Containerというものを作成してデータを永続化しようというパターンがよく使われます。このパターンを使うときはdocker-composeを使用すると、一連の流れを一つのファイルにまとめられて便利です。下記のように書きます。
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が存命しているので、再起動すればデータは復活します。
プロセス毎に分けてみる。
上記背景で述べたプロセス毎に分けると下記の様になります。
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 できる形で運用した方がいいと思います。
./
├── 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
を実行してみると
$ 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
コマンドを使用することで、実行後コマンド用コンテナが削除され、クリーンな運用をすることができます
都度実行例
# 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
まとめ
- コンテナの分け方をプロセス毎にすることによって必要なタイミングで必要なコマンドを実行するイメージを作成でき、アプリケーションのイメージをクリーンな状態で保つ事ができる。
- また各プロセス毎のミドルウェアのバージョンアップ、メンテナンスも、設定ファイルが別れているので、簡単に編集が可能。