【php-fpm,nginx,mysql】docker-composeで開発環境を構築する

#dockerで開発環境を構築する際の手順

## お断り
エンジニアとしてまだまだ未熟なので、
色々と間違っている場合もあるかもしれませんので、その場合は、コメントなどでご指摘いただけると嬉しいです!
知識のブラッシュアップにご協力お願いします！

## 説明する技術
- docker
- docker-compose

## dockerとは
dockerを使えば、自分のpcの中に仮想コンテナを作成できるよってツールです。他にも仮想環境構築のためのツールは存在しますが、dockerは手軽さがウリ！だと思っています！簡単に仮想環境作ったり、消したり、更新したりが可能です。[こちらの記事](https://qiita.com/SatoshiSobue/items/a612ebbb3a9242c09db5)など参照にしたのでよくわからない方は是非ご覧ください。今回はコマンドなどではdocker-composeを利用していますが、基盤はdockerです。

## docker-composerとは
簡単に説明すると以下のイメージです。
dockerコンテナを操作する"docker"コマンドはdockerコンテナ一つ一つを操作するためのコマンドですが、"docker-compose"コマンドは複数のdockerコンテナの相関関係を定義し、一括操作するためのコマンドです。dockerコンテナの複数管理用コマンドって感じですね
。
<img width="1359" alt=" 2020-06-22 18.38.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642236/ef328601-be3e-d0b0-dc2b-b1998705a712.png">


## ディレクトリ構成
関係ないファイルは表示していませんが基本的な構造はこんな感じです。一部フォルダ名とかは見やすいように修正してます。

```shell:Directory
PROJECT-NAME
├── Api
│   ├── docker-compose.yml
│   ├── docker-mysql
│   │   ├── Dockerfile
│   │   ├── conf
│   │   │   └── my.cnf
│   │   └── data
│   │   　　   └── 'dbのデータファイル群（DBのスキーマ構造とかデータとかの自動で作られるファイル）'
│   ├── docker-nginx
│   │   ├── Dockerfile
│   │   └── nginx.conf
│   ├── docker-php
│   │   └── Dockerfile
│   └── project
│       └── ..
└── Front
    ├── Dockerfile
    ├── docker-compose.yml
    ├── docker-nginx
    │   ├── default.conf
    │   └── log
    └── project
        ├── ..
```

## 事前準備（2020/08/31追記）
今回利用するコンテナイメージをローカルPCにダウンロードします。
詳細はDockerhubの公式イメージのdocker pullコマンドを確認してください。

## 【Api側】Dockerfile
各dockerコンテナの設定ファイルです。
### php-fpmのDockerfile
php側では、必要なphpのライブラリとかをinstallするコマンド書いてあげます。初心者目線でのポイントは、FROMで指定したdockerイメージのOSの種類に合わせたコマンドをしっかり調べて書くことです。今回は"php:7.3-fpm"というdockerhubで公開されている[php公式イメージのphp-fpm](https://hub.docker.com/_/php)を使いました。どうやらこいつがDebian系だったので、Dockerfileの中でapt-getコマンドを使ってます。

```dockerfile:Api/docker-php/Dockerfile
# 基盤のimageファイル選択（php公式イメージ）
FROM php:7.3-fpm
# コンテナを立ち上げるときにライブラリ・パッケージのアップデートとインストール
RUN apt-get update && apt-get install -y \
  vim \
  zip \
  unzip \
  iputils-ping \
  git \
  imagemagick \
  gcc \
  make \
  libpng-dev \
  libmcrypt-dev \
  libjpeg-dev \
  libfreetype6-dev \
  && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
  && docker-php-ext-install -j$(nproc) gd

# コンテナを立ち上げるときにPDOのインストール（API作成前のDB接続確認のためインストール）
RUN docker-php-ext-install pdo_mysql

# composerファイルのコピー（APIでLaravel使うので、composerの準備もしておく）
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# 作業ディレクトリの指定（dockerコンテナに接続した時どのディレクトリから開始するか）
WORKDIR /var/www/html/project
```

### nginxのDockerfile
[nginx公式イメージ](https://hub.docker.com/layers/nginx/library/nginx/1.17.9/images/sha256-39f53d91433cac929ec9caadf8719c6dc205c74129c90b76054bee43337996b5?context=explore)を使用しました。vimとかunzipとか必要そうなミドルウェアを追加してます。

```dockerfile:Api/docker-nginx/Dockerfile
# 基盤のimageファイル選択（nginx公式イメージ）
FROM nginx:1.17.9
# コンテナを立ち上げるときにパッケージのアップデートとインストール
RUN apt-get update && apt-get install -y \
  vim \
  unzip \
  zip \
  git

# 作業ディレクトリの指定
WORKDIR /var/www/html
```

### mysqlのDockerfile
ここでは特に指定せず。mysqlの設定ファイルを別で作成しました。イメージは[mysql公式イメージ](https://hub.docker.com/layers/mysql/library/mysql/8.0.19/images/sha256-09de7b17af0c17d397e6b69ff841756b80074aed00c1e91d7bc0f3caa5512113?context=explore)を利用しました。

```dockerfile:Api/docker-mysql/Dockerfile
# 基盤のimageファイル選択
FROM mysql:8.0.19
```

# その他各種設定ファイル
## my.cnf
```
[mysqld]
character-set-server=utf8mb4                           # mysqlサーバー側が使用する文字コード
collation-server=utf8mb4_bin                           # collationの設定
default-authentication-plugin=mysql_native_password    #カスタムpwを使用可能に
sql_mode = NO_ZERO_IN_DATE,NO_ENGINE_SUBSTITUTION

[client]
default-character-set=utf8mb4                          # mysqlのクライアント側が使用する文字コード
```

## nginx.conf
```
server {
    listen       80;
    index index.php index.html;
    server_name php-docker.local;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/html/project/public;

    location / {
     try_files $uri $uri/ @laravel;
    }

    # redirect server error pages to the static page /50x.html
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root /var/www/html;
    }

    location ~ \.php$ {
    try_files $uri $uri/ @laravel;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

    location @laravel {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /var/www/html/project/public/index.php;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}
```

## 【Front側】Dockerfile
ここもApi側と一緒ですね。ApiとFrontでwebサーバを分けたのはマイクロサービス化したかったからです！あんまりわかっていないですが！一緒でもよかったのかなとか後から思いました。

```dockerfile:Front/Dockerfile
# -----------------------------------------------------------------
# Nginx
# -----------------------------------------------------------------
# 基盤のimageファイル選択
FROM nginx:1.17.9
# コンテナを立ち上げるときにパッケージのアップデートとインストール
RUN apt-get update && apt-get install -y \
  vim \
  unzip \
  zip \
  git
```
# その他各種設定ファイル
## default.conf
```
server {
    listen       8080;
    index index.html index.htm;
    server_name aiforce-equipment;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/html/aiforce_equipment_web/;

    location / {
     try_files $uri $uri/ /index.html;
    }

    # redirect server error pages to the static page /50x.html
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root /var/www/html;
    }

    include /etc/nginx/mime.types;

}
```

##【Api側】docker-compose.yml
```yml:Api/docker-compose.yml
version: "3"

services:
    # webサーバ用コンテナ設定
    web:
        # 元となるDockerfileのディレクトリ
        build: ./docker-nginx
        # コンテナ名を明示的に指定する
        container_name: API_Web_Container
        # 他コンテナとの依存関係（コンテナ起動の順番にも関係）
        depends_on:
            - app
        ports:
            - 80:80
        # マウントするホスト側ディレクトリ:マウント先コンテナディレクトリ
        volumes:
            - ./project/lib:/var/www/html/project
            - ./docker-nginx/nginx.conf:/etc/nginx/conf.d/default.conf
        # 他サービスとの接続
        links:
            - app
        # 継続起動のため
        tty: true
    # アプリケーションサーバ設定
    app:
        # 元となるDockerfileのディレクトリ
        build: ./docker-php
        # コンテナ名を明示的に指定する
        container_name: App_Container
        # 他コンテナとの依存関係（コンテナ起動の順番にも関係）
        depends_on:
            - db
        # 他サービスとの接続
        links:
            - db
        # マウントするホスト側ディレクトリ:マウント先コンテナディレクトリ
        volumes:
            - ./project/lib:/var/www/html/project
        # 継続起動のため
        tty: true
    # DBサーバ設定
    db:
        # 元となるDockerfileのディレクトリ
        build: ./docker-mysql
        # コンテナ名を明示的に指定する
        container_name: DB_Container
        ports:
            - 3306:3306
        # DBの変数設定
        environment:
            MYSQL_ROOT_PASSWORD: XXXXX #ここにroot用のパスワードを記載
            MYSQL_DATABASE: XXXXX #ここにデータベースを記載
            MYSQL_USER: XXXXX #ここにユーザ名を記載
            MYSQL_PASSWORD: XXXXX #ここにユーザ用パスワードを記載
        # マウントするホスト側ディレクトリ:マウント先コンテナディレクトリ
        volumes:
            # mysqlの設定ファイルをコンテナにマウント（ミラーリング的な？）
            - ./docker-mysql/conf:/etc/mysql/conf.d
            # 永続化するときにマウントするdir
            - ./docker-mysql/data:/var/lib/mysql
```

##【Front側】docker-compose.yml
```yml:Front/docker-compose.yml
version: '3'

services:

 # webサーバ用コンテナ設定
 front:
    # 元となるDockerfileのディレクトリ
    build: .
    # コンテナ名を明示的に指定する
    container_name: Front_Web_Container
    ports:
        - 8080:8080
    # 継続起動のため
    tty: true
    networks: 
        - api_default
    # マウントするホスト側ディレクトリ:マウント先コンテナディレクトリ
    volumes:
        - ./docker-nginx/log/:/var/log/nginx/
        - ./project/build:/var/www/html/project
        - ./docker-nginx/default.conf:/etc/nginx/conf.d/default.conf
# APIのネットワーク名
networks: 
 api_default: // APIとFRONTのdockerコンテナ群をつなげるネットワーク
    external: true
```
## dockerコンテナのビルド（*設計図に変更がなければ初回だけでOK）
dockerコンテナの*設計図（Dockerfileとdocker-compose.yml）を元に仮想コンテナを作成する。
docker-compose.ymlがある階層で以下のコマンドを実行します。ビルドがうまく行かない場合は各Dockerfileを見直してください。<img width="614" alt=" 2020-06-29 18.45.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642236/7a69f301-84ca-4faf-024f-7a91bc0c2ebe.png">


```
$ docker-compose build --no-cache    // --no-cacheはオプション
```

## ビルドしたコンテナの起動
dockerコマンドと違ってportの指定など細かい設定をコマンドで記載する必要がないのがdocker-composeのいいところ！docker-compose.ymlがある階層で以下のコマンドを実行します。

```
$ docker-compose up -d
```
doneで起動成功していたらOK、失敗したらエラーメッセージを確認して見てください。

```
pc-user@pc-name api % docker-compose up -d
Starting DB_Container ... done
Starting App_Container ... done
Starting API_Web_Container ... done
```
起動がうまく行かない場合はdocker-compose.ymlを見直してください。


## 起動確認
コンテナが正常に起動できるか確認します。docker-compose.ymlがある階層で以下のコマンドを実行します。

```
$ docker-compose ps
```


```
pc-user@pc-name api % docker-compose ps
  Name               Command                         State   Ports
------------------------------------------------------------------------------------
App_Container       docker-php-entrypoint php-fpm   Up      9000/tcp
DB_Container        docker-entrypoint.sh mysqld     Up      0.0.0.0:3306->3306/tcp, 33060/tcp
API_Web_Container   nginx -g daemon off;            Up      0.0.0.0:8086->80/tcp
```
テーブル構造見やすいですね〜
StateがUp以外の場合は、起動後コンテナが停止してしまっているので継続起動の設定部分を確認してみてください。


API側でdocker-composeコマンドでコンテナをビルド・起動した場合はこのような結果となります。全てのコンテナが起動し、停止していなかったらOKです！問題が起こった場合は、[docker-compose logs](https://docs.docker.com/compose/reference/logs/)コマンドなどで原因を調査して、解決してください。

いい感じに起動したら最後に、、、！

## dockerコンテナに接続
```
$ docker-compose exec app bash //appの部分はdocker-compose.ymlで定義したサービス名を入力
$ docker-compose exec web bash //appの部分はdocker-compose.ymlで定義したサービス名を入力
$ docker-compose exec db bash  //appの部分はdocker-compose.ymlで定義したサービス名を入力
```

念のため、3つのコンテナにそれぞれ接続するコマンドを記載しました。例として、DBに接続したい場合は、直接dockerコンテナに接続して、mysql -u -root -p（DBの種類に合わせて変更してください）と入力するか、GUIでDBに接続するツール等で127.0.0.1のIPと3306のポートを指定してあげれば大丈夫です！

<img width="614" alt=" 2020-06-29 18.45.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642236/2759088e-e2ed-683c-cf1c-4c83fe33c016.png">

## 補足
上記のdocker-compose.ymlファイルの場合、API側のdockerコンテナ起動→Front側のコンテナ起動の順番で行ってください！理由は、ネットワークの構築をAPI側で行っているからです。もしFront側を先にdocker-compose upしようとすると、「ネットワークがないよ」と怒られるのでご注意ください。

## 終わりに
ここまで、自分のPC内でdocker,docker-composeを使って環境構築する例を記載しました！
自分は環境構築に2週間くらいかかった感じですが、初心者すぎました！笑

ある程度エンジニアとして働いているかたはさらっと3日以内でできると思いますのでぜひdockerを使ってみてください！

以上、手軽に開発環境が構築できるdocker大好きマンでした！