Docker入門 〜Docker-compose, ネットワーク, ボリューム編〜

前回の記事
[Docker入門 ～Dockerfile編～](https://qiita.com/takaday/items/08c8269dc926af8cb661)
の続きです。

# Docker-compose

**Docker-compose**は、複数のコンテナを簡単に操作できる仕組みです。
使用したいコンテナの数が増えるとdockerコマンドをうつのが大変ですが、`docker-compose`コマンドで楽になります。

ファイル構成は以下とします。

```
.
├── docker-compose.yml
├── mysql
│   ├── Dockerfile
│   └── my.cnf
└── public
    └── index.html
```

<details><summary>mysqlディレクトリの内容</summary><div>

```:Dockerfile
FROM mysql:5.7.27

RUN apt-get update \
  && apt-get install -y locales \
  && rm -rf /var/lib/apt/lists/* \
  && echo 'ja_JP.UTF-8 UTF-8' >> /etc/locale.gen \
  && locale-gen ja_JP.UTF-8

ENV LANG ja_JP.UTF-8

COPY ./my.cnf /etc/mysql/conf.d/my.cnf
```

```:my.conf
[client]
default-character-set=utf8mb4

[mysqld]
character-set-server=utf8mb4
```
</div></details>

# docker-compose.yml

**docker-compose.yml**で、docker-composeコマンドでまとめて起動するコンテナの設定を記述します。

```docker-compose.yml
version: '3.7'

services:
  php:
    image: my-php-apache:7.3.10
    ports:
      - 80:80
      - 443:443
    networks:
      - web
    volumes:
      - ./:/var/www

  mysql:
    build: ./dockerfiles/mysql
    expose:
      - 3306
    networks:
      - web
    volumes:
      - mysql:/var/lib/mysql
    environment:
      - MYSQL_DATABASE=laravel
      - MYSQL_USER=laravel
      - MYSQL_PASSWORD=secret
      - MYSQL_ROOT_PASSWORD=secret

networks:
  web:

volumes:
  mysql:
```

上記では、phpコンテナとmysqlコンテナを設定しています。`docker-compose up`コマンドで、コンテナをまとめて起動できます。

```
$ docker-compose up                         
Creating network "sample_web" with the default driver
Creating volume "sample_mysql" with default driver
Creating sample_php_1   ... done
Creating sample_mysql_1 ... done
Attaching to sample_php_1, sample_mysql_1
```

# docker-compose.yml解説

上記ymlファイルの解説です。

```docker-compose.yml
version: '3.7'
```

Docker-compose.ymlの記述方法のバージョンを指定しています。古いバーションだと使用できないキーなどがあります。

```docker-compose.yml
services:
  php:
    ...
  mysql:
    ...
```

Docker-composeはコンテナを**サービス**として管理します。ここではphpとmysqlのコンテナ設定を書いていきます。

```docker-compose.yml
services:
  php:
    image: my-php-apache:7.3.10
    ports:
      - 80:80
      - 443:443
    networks:
      - web
    volumes:
      - ./:/var/www
```

- image - 使用するイメージ
- ports - ポートのバインディング。`ホスト:コンテナ`の順番でかく
- networks - コンテナが所属するネットワーク（後述）
- volumes - マウントするボリューム(後述)

ボリュームは前回利用しましたが、データをコンテナとは別に管理して永続化する仕組みでした。後ほどネットワークと合わせて詳しく解説します。

```docker-compose.yml
  mysql:
    build: ./mysql
    expose:
      - 3306
    networks:
      - web
    volumes:
      - mysql:/var/lib/mysql
    environment:
      - MYSQL_DATABASE=laravel
      - MYSQL_USER=laravel
      - MYSQL_PASSWORD=secret
      - MYSQL_ROOT_PASSWORD=secret
```

- build - Dockerfileのパス

イメージではなくDockerfileも指定できます。

- expose - 他のサービスに公開するポート（ホストからはアクセスできない)
- environment - コンテナ内で使用できる環境変数

特定の環境変数を設定しておくと楽にカスタマイズできるように、ベースイメージ側で準備してくれていることがあります。
[MySQLイメージのDockerHubページ](https://hub.docker.com/_/mysql)を見ると、MYSQL_DATABASEでデータベース名を設定できると分かります。
ここでは上からデータベース名、データベースのユーザー名、ユーザーのパスワード、ルートユーザーのパスワードを設定しています。Laravelからデータベースに接続するときに、この情報で接続します。

```docker-compose.yml
networks:
  web:

volumes:
  mysql:
```

各サービスで使用するネットワーク・ボリュームを明示します。忘れると動作しません。

# Dockerネットワーク

Docker-composeというよりDockerの仕組みですが、ここで説明します。

コンテナは同じ仮想ネットワークに配置されないと、相互にやりとりできません。なので、ApacheのコンテナとMySQLのコンテナは同じネットワークにしてあげます。

```docker-compose.yml
services:
  php:
    networks:
      - web

  mysql:
    networks:
      - web

networks:
  web:
```

同じネットワークにすると、コンテナ内から他のコンテナにアクセスするときに、**サービス名でアクセスできるようになります。**
phpコンテナに配置したLaravelアプリケーションから、MySQLコンテナにアクセスするには.envファイルをこう書きます。

```:.env
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=secret
```

DB_HOSTが`mysql`とサービス名になっていて、これで繋がります。内部的にはDockerがDNSサーバーを用意して、名前解決してくれるようです。

# Dockerボリューム

こちらもDocker-composeというよりDockerの仕組みですが、ここで説明します。

MySQLコンテナを立ち上げて、DBにデータを保存してもコンテナを削除するとそのデータは消えてしまいます。データを永続保存するための仕組みがボリュームです。
ボリュームはコンテナとは切り離して管理されるもの、ということです。

ボリューム一覧は以下のコマンドで確認できます。

```
$ docker volume ls
DRIVER              VOLUME NAME
local               tmp
...
```

ボリュームには以下の3種類があります。

- ホスト（host）
- 匿名（anonymous）
- 名前付き（named）

## ホストボリューム

`-v /host/path:/container/path`でホストのパスをコンテナのパスにマウントし、ボリュームとします。

```
$ docker container run -dit -v $HOME/tmp:/tmp alpine
```

上記の例ではコンテナを削除しても、/tmpの内容は$HOME/tmpに残ります。そしてホストボリュームは`docker volume ls`には表示されません。

## 匿名ボリューム

`-v /container/path`で、匿名ボリュームです。

```
$ docker container run -dit -v /tmp alpine
```

Dockerが隠蔽して管理するボリュームで、ハッシュ値がボリューム名となります。

```
$ docker volume ls
DRIVER              VOLUME NAME
local               a1138c254e5df7d419191d29e9e7e5acaf6389d69521c09eb00abe05889151ad
```

名前付きボリュームがなかった時代に使われていたようで、現在は名前付きボリュームを使います。
`docker container run --rm`で立ち上げたコンテナが削除される時、匿名ボリュームも削除されるという特徴があります。

## 名前付きボリューム

`-v name:/container/path`で名前付きボリュームです。

```
$ docker container run -dit -v tmp:/tmp alpine
```

こちらもDockerが隠蔽して管理するボリュームですが、名前がありアクセスしやすいです。

```
$ docker volume ls
DRIVER              VOLUME NAME
local               tmp
```

また`docker container run --rm`で立ち上げたコンテナが削除されても名前付きボリュームは削除されません。

## どの種類のボリュームにするか

最初のdocker-compose.ymlを見ると、phpコンテナはホストでファイルを編集したいので、ホストボリューム`./:/var/www`にしています。
MySQLコンテナは、特にホストでMySQLが管理するファイルをいじることはないですから、名前付きボリューム`mysql:/var/lib/mysql`です。ただホストボリューム`./.db:/var/lib/mysql`などにしても問題はないと思います。

また、前回でやったcomposerの例で考えてみましょう。
`composer install`するためのcomposer.jsonや、インストール結果のvendorディレクトリなどをコンテナとやりとりするだけであれば、ホストボリュームでいいです。
今後別のコンテナで`composer install`するためのキャッシュは、名前付きボリュームでどこからでも参照できるようにしておきましょう。

```
$ docker container run --rm -it -v $PWD:/app -v composer_cache:/tmp composer install
```

上記なら、2回目以降は以下のようにキャッシュからインストールしてくれます。

```
$ docker container run --rm -it -v $PWD:/app -v composer_cache:/tmp composer install
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Package operations: 84 installs, 0 updates, 0 removals
  - Installing doctrine/inflector (v1.3.0): Loading from cache
  - Installing doctrine/lexer (1.1.0): Loading from cache
...
```

## ボリュームにファイルがあると

- ボリュームにファイルあり、コンテナになし
ボリュームのファイルがコンテナにコピーされる

- ボリュームにファイルなし、コンテナにあり
コンテナのファイルがボリュームにコピーされる

- 両方にファイルあり
コンテナのファイルがなくなり、ボリュームのファイルがコンテナにコピーされる

# 実際にdocker-composeコマンドを使ってみよう

docker-compose.ymlがあれば、以下のコマンドでコンテナを一気に立ち上げることができます。

```
$ docker-compose up
```

起動したコンテナ、ネットワーク、ボリュームを確認してみましょう。

```
$ docker container ls                     
CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                                      NAMES
af5e814670de        my-php-apache:7.3.10   "docker-php-entrypoi…"   5 minutes ago       Up 5 minutes        0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   sample_php_1
e4116e1a3bfa        sample_mysql           "docker-entrypoint.s…"   5 minutes ago       Up 5 minutes        3306/tcp, 33060/tcp                        sample_mysql_1

$ docker network ls  
NETWORK ID          NAME                DRIVER              SCOPE
81e33015a6f9        bridge              bridge              local
e2eab5ac1197        host                host                local
a8432cc388c6        none                null                local
37b562bfd9c1        sample_web          bridge              local

$ docker volume ls 
DRIVER              VOLUME NAME
local               sample_mysql
```

ネットワークのbridge,host,noneは、デフォルトで用意されているネットワークなので気にしないでください。

dockerコマンドと同じように、`exec <service> <command>`でコンテナ内でコマンドを実行できます。

```
$ docker-compose exec php bash
root@026c8f03e602:/var/www#
```

MySQLに接続するのであれば

```
$ docker-compose exec mysql mysql -u laravel -D laravel -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
```

コンテナ削除も`docker-compose down`でまとめてできます。コンテナとネットワークが削除され、ボリュームは残ります。

```
$ docker-compose down
Stopping sample_php_1   ... done
Stopping sample_mysql_1 ... done
Removing sample_php_1   ... done
Removing sample_mysql_1 ... done
Removing network sample_web
```

## プロジェクト

コンテナ、ネットワーク、ボリューム名が`sample_*`になっています（コンテナ名はls結果の一番右に書いてあります）。これはsampleディレクトリ下で`docker-compose up`したからです。

他のディレクトリで`docker-compose up`すると、そのディレクトリ名がプリフィックスになります。ディレクトリ名だとわかりにくい場合は、環境変数COMPOSE_PROJECT_NAMEや`-p`オプションでプリフィックスを指定できます。
docker-composeコマンドは.envファイルをみてくれるので、以下のようにしましょう。

```:.env
COMPOSE_PROJECT_NAME=プロジェクト名
```

## プロジェクト固有のイメージ

`docker image ls`コマンドで所持イメージを確認してみます。

```
$ docker image ls                                                        
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
sample_mysql        latest              c8914e3551fe        35 seconds ago       385MB
my-php-apache       7.3.10              ed7f24566ef1        About a minute ago   452MB
php                 7.3.10-apache       b83ebda0d7d4        7 weeks ago          410MB
mysql               5.7.27              383867b75fd2        3 months ago         373MB
```

phpとmysqlはベースにした公式イメージで、my-php-apacheは前回自分でビルドしたイメージでした。
sample_mysqlは、`docker-compose up`した時にビルドされたイメージです。

docker-compose.ymlにbuildを指定すると、プロジェクトごとにイメージがビルドされます。今回は

```docker-compose.yml
  mysql:
    build: ./mysql
```

としていたので、sampleがプリフィックスのプロジェクト固有のmysqlイメージになりました。

プロジェクトごとにイメージを持ちたいならbuildを、使いまわしたいなら最初に`docker build`しておき、それをimageに指定すれば良いでしょう。

# 終わりに

Docker-composeを使うことができれば、開発環境は構築できると思います。
他のイメージを使えば、PostgreSQLやRedisサーバー、メール送信のテストを行えるMailHogなどを立ち上げてLaravelで使用できるので試してみてください。