nginx+php+MySQL から始めるdocker-compose 事始め

#きっかけ
Dockerの勉強の一つとして、docker-composeを使ってnginx+php+MySQLの環境を構築してみよう、というのを行い、
一通り実装する中で学ぶことも多かったので、自分に向けて学んだことの整理、と言う意味も込めて書き残しておこうと思います。

# 環境
- Mac OS Mojave Version 10.14
- Docker for Mac

# ターゲット
- Dockerの大まかな概念（Docker Hub / image / container）を理解している
- 公式サイトのTutorialのpart1,2(英語版 : https://docs.docker.com/get-started/ 日本語版 : http://docs.docker.jp/get-started/toc.html )をこなしている
    - container を起動するまでの一通りの docker command を理解いる
    - Dockerfileの書き方を知っている

# 話すこと
- docker-compose の使い方
- docker-compose.yml の記述の仕方

# TL;DR
https://github.com/FumiakiOzawa/getstart_nginx_php_mysql
からソースを持ってきて .env ファイルを適当にこしらえれば環境構築できます

# そもそも docker-compose とは
公式のドキュメントを見ると
https://docs.docker.com/compose/overview/
> Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration. 

日本語版では、こう書かれてますね
http://docs.docker.jp/compose/overview.html
> Compose とは、複数のコンテナを使う Docker アプリケーションを、定義・実行するツールです。Compose はアプリケーションのサービスの設定に、Compose ファイルを使います。そして、コマンドを１つ実行するだけで、設定した全てのサービスを作成・起動します。

要は、複数のコンテナを一度に操作してくれたり、 `$ docker run` の `-v` やら `-p` やらを毎回打たなくて済みますよ、ぐらいの認識でいいのかな、と思います。

## docker-compose なんて面倒なことをせずに環境丸ごとImageにすればよいのでは?
これに対して公式では以下のように書いています。
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#decouple-applications
>Each container should have only one concern. Decoupling applications into multiple containers makes it easier to scale horizontally and reuse containers. For instance, a web application stack might consist of three separate containers, each with its own unique image, to manage the web application, database, and an in-memory cache in a decoupled manner.

対応する日本語版(http://docs.docker.jp/engine/userguide/eng-image/dockerfile_best-practice.html#id5 )の部分では少しニュアンスが異なると感じた部分があったので、ざっくりというと、

- 1つのコンテナに対しては1つの`concern`(ここでは関心事、ぐらいの訳になるのはないかと思います)を対応させるべき
- それによってコンテナの再利用（変更していない部分はそのまま再起動時に利用できる）やスケールが容易になる

ということが書かれているのかなと思います。
（ただし、プロセス毎というように分けすぎるのも逆効果、ということももここで書かれています）

# docker-compose.yml って実際どう書くの？
http://docs.docker.jp/compose/compose-file.html
> Compose ファイルは YAML ファイルであり、 サービス（services） 、 ネットワーク（networks） 、 ボリューム（volumes） を定義します。

とあるように、主に service , networks , volumes の3つを記述します
ただし、この内 networks はデフォルトで作成されるものがあるので必ず記載する必要はありません。
また、volumes は名前付きボリュームを使う必要がないので、今回は使用しません。（各service内には使用します）
なので、今回は service だけの記述になります
今回は以下の事項が確認できれば達成できたものとします

- php + nginx の環境構築
    - http://localhost/index.php にアクセスして `phpinfo()` が表示されていれば達成されたとする
- php + nginx + MySQL の環境構築
    - phpのスクリプトからMySQLに接続できることを確認できれば達成されたとする

# imageの選定
phpとnginxで環境を構築する際はphp-fpmというものを使います。
なぜfpmが必要なのか、と言った事に関してはこの記事が非常によくまとまっています
[nginx と PHP-FPM の仕組みをちゃんと理解しながら PHP の実行環境を構築する](https://qiita.com/kotarella1110/items/634f6fafeb33ae0f51dc)
ありがたい事に、docker official image の php Imageのvaliantとして、php-fpmが配布されているので、それを使いましょう
(一応、個人が公開しているimageにもphp+fpmの機能が含まれているものもあります。
しかし、セキュリティの観点や余計な詰まりポイントを防ぐためにも、基本は公式のimageを用いる習慣をつけるべきでしょう)
# フォルダ構成
```
getstart_nginx_php_mysql
├─ html
│  ├─ index.php
│  └─ is_connected_database.php
├─ nginx
│  └─ default.conf
├─ php
│  └─ Dockerfile
│  .env
└─ docker-compose.yml
```
# 各ファイルの作成

```index.php
<?php
echo phpinfo();
```
phpinfo（) を表示されるだけになります。

```is_connected_database.php
<?php
echo mysqli_connect('mysql', "root", getenv('MYSQL_ROOT_PASSWORD')) ?
"Database connection successed.":
"Database connection failed. ";
```
接続を確認するための簡単なスクリプトになります。


```nginx:default.conf
server {
    listen 80;
    root   /usr/share/nginx/html;

    location ~ \.php$ {
        fastcgi_pass   php:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
}
```
nginx内の/etc/nginx/conf.d/default.confに

```nginx:default.conf
    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}
```
という記述があるので、それを参考にして作成します

Dockerfileは以下のように作成します

```docker:Dockerfile
FROM php:7-fpm
RUN docker-php-ext-install pdo_mysql mysqli
```
MySQLの接続の確認のためにpdo_mysqlとmysqliをインストールする必要があります。
このRUNの記述はdocker-compose.ymlでは代用できなかったです。

```docker:.env
MYSQL_ROOT_PASSWORD=root
```
秘匿したい環境変数などはこのように外部ファイルに保存できます。
docker-compose.ymlからは、`${hogehoge}`で呼び出せます。
(参考:https://docs.docker.com/compose/compose-file/#variable-substitution)
公開する際は.gitignore にでも追加しておけば良いでしょう

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

services:

    php:
        build:
            context: ./php
        ports:
            - 9000:9000
        volumes:
            - ./html:/usr/share/nginx/html
        environment:
            MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}

    nginx:
        image: nginx:1.17
        ports:
            - 80:80
        volumes:
            - ./nginx/default.conf:/etc/nginx/conf.d/default.conf

    mysql:
        image: mysql:5.7
        ports:
            - 3306:3306
        environment:
            MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
```

- version
    - 2019年7月現在は3が最新であり、特に問題もないので3を用います
- service
    - 各コンテナのサービス名を指定します。基本的には役割が言葉であればなんでも良いです
    - build / image
        - どちらもcontainerを作成するためのコマンドですが、どちらかしか使えません
        - 細かい違いはあるのですが、以下の認識で問題ないと思います
            - build : Dockerfileからcontainerを作成
            - image : 指定したimageからcontainerを作成
        - 今回は、php側でDockerfileを用いるので、buildを用います。
        - 他2つはimageからそのままcontainerを起動するので、imageで問題ありません。
    - context
        - build時に用いるDockerfileのフォルダを指定します
        - `docker build`コマンドのパス指定に相当します
    - ports
        - <ホスト側>:<コンテナ側>のポートを指定します。コンテナ側のみも可能です
        - `docker run` コマンドの`-p`オプションに相当します
            - php-fpm のデフォルトのポートである 9000番
            - localhost のポートとして一般的に使われる 80番
            - MySQL の接続で一般的に使われる 3306番
        - をそれぞれのコンテナで指定します
    - volumes
        - ボリュームのマウントを行います
        - `docker run` コマンドの`-v` オプションに相当します
    - enviroment
        - 環境変数を設定します
        - `docker run` コマンドの`-e` オプションに相当します
        - 今回はMySQL接続の確認のために、ROOTのパスワードを環境変数にして渡しています

基本的には[Compose ファイル・リファレンス](http://docs.docker.jp/compose/compose-file.html)を参考にしています。
ただ、少し古いので、[英語版](https://docs.docker.com/compose/compose-file/ )も併せて読むことを勧めます。

# docker-compose の起動
docker-compose.ymlの存在するフォルダに移動して
`$ docker-compose up`
でそれぞれのコンテナを起動できます。
バックグラウンドで起動したいときは-dオプションをつけます。
終了するときはdown、コンテナを作成するだけならbuildでできます。
（それぞれ、`docker run / stop / create` に対応していると考えるとわかりやすいと思います）
http://localhost/index.php
にアクセスして、phpinfo()が表示されていること、
http://localhost/is_connected_database.php
にアクセスして、
`Database connection successed.`
と表示されていれば、成功となります。
`Database connection failed. `
と表示される時もありますが、MySQLの起動が完了していない場合があるので、少し（15秒ほど）待つと成功します[^1]。


[^1]: 
 これはdocker-compose の depend-on では解決されません。
 >http://docs.docker.jp/compose/compose-file.html#depends-on
 >待てるのはコンテナを開始するまでです。サービスの準備が整うまで待たせる必要がある場合は、 起動順番の制御 に関するドキュメントで、問題への対処法や方針をご確認ください。

 [Compose の起動順番を制御](http://docs.docker.jp/compose/startup-order.html)
 によれば、アプリケーションのコード上かラッパー用のスクリプトによる対処を推奨しています。
 ただ、今回は簡易的なものなのであり、少し待てば解決されるので、特に対応はしていないです。