【環境構築】docker-composeでDBテーブルの作成まで実行！init.shでmigrate実行を最後に実行する解決方法。

![Docker_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250884/82c05afd-5780-0ae8-c576-b2ef3d24eda0.png)

どうも、たかふみです。

現在、Docker&Laravelで開発をしています。
あるとき、こんなことを思いました。

**「dockerのDBコンテナが立ち上がったら自動的にマイグレーションして
テーブルが出来上がった状態になっててくれないかなぁ！」**

そんなときに、見つけた下記の記述。

```
CMD bash -c "bash /etc/init.sh"
```

init.sh...
いかにも何かしてくれそうなコマンドです。
今回は、dockerのコンテナ起動時にコマンドを実行する！というのをやっていきます！

# コンテナ構成
```
webコンテナ ： amazonlinux:２(+nginx+PHP)
DBコンテナ　： mysql:5.7
```

# まず、CMDとは何なのか。

>The main purpose of a CMD is to provide defaults for an executing container. 
引用：https://docs.docker.com/engine/reference/builder/

**コンテナ起動時に実行されるコマンド**です。今回の用途にぴったりですね。
`docker-compose up` や `run` したときに実行されます。

# init.sh を見てみる。
ここで、用意したinit.shを見てみましょう。

```
#!/bin/bash

DATE=`date "+%Y-%m-%d"`
# log ファイルの作成
sudo -u nginx touch /var/log/nginx/access.log
sudo -u nginx touch /var/log/nginx/error.log
touch /var/log/php-fpm/error.log
sudo -u nginx touch /var/www/html/storage/logs/laravel-${DATE}.log

# マイグレーションの実行
php artisan migrate

# composerによるパッケージのインストール
composer install

exit 0
```

```
【処理順】
1.ログ用のファイルを作成
2.マイグレーションの実行&Seederの実行
3.composer installでパッケージのインストール
```

上記のステップで実行するようにしています。



<img width="802" alt="スクリーンショット 2019-11-09 18.21.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250884/3cc7ce10-0baa-18b8-4653-0fdf1db4c70e.png">


`docker-compose up` を実行してログを見てみましょう。

```
web_1    | 
web_1    |    Illuminate\Database\QueryException  : SQLSTATE[HY000] [2002] Connection refused (SQL: select * from information_schema.tables where table_schema = sample and table_name = migrations and table_type = 'BASE TABLE')
web_1    | 
web_1    |   at /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connection.php:665
web_1    |     661|         // If an exception occurs when attempting to run a query, we'll format the error
web_1    |     662|         // message to include the bindings with SQL, which will make this exception a
web_1    |     663|         // lot more helpful to the developer instead of just the database's errors.
web_1    |     664|         catch (Exception $e) {
web_1    |   > 665|             throw new QueryException(
web_1    |     666|                 $query, $this->prepareBindings($bindings), $e
web_1    |     667|             );
web_1    |     668|         }
web_1    |     669|
web_1    | 
web_1    |   Exception trace:
web_1    | 
web_1    |   1   PDOException::("SQLSTATE[HY000] [2002] Connection refused")
web_1    |       /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70
web_1    | 
web_1    |   2   PDO::__construct("mysql:host=mysql;port=3306;dbname=sample", "user", "password", [])
web_1    |       /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70
web_1    | 
web_1    |   Please use the argument -v to see more details.
```

<img width="784" alt="スクリーンショット 2019-11-09 17.47.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250884/7cd10a00-b48f-4a62-50a1-3d6c1fc4be13.png">

# 原因：DBコンテナが起動していないのでは？
ログを見て、**DBコンテナの接続準備が完了する前にデータ作成を実行しようとしているため、接続に失敗して`Connection refused` となっているのではないかと考えました。**そこで、docker-composeで起動したときにDBコンテナが起動し終わってからWEBコンテナを起動する方法を探しました。

# 調査結果：起動が完了してから...というのはできない。
>しかしながら、Compose はコンテナの準備が「整う」まで待ちません（つまり、特定のアプリケーションが利用可能になるまで待ちません）。単に起動するだけです。これには理由があります。
たとえば、データベースの準備が整うまで待つのであれば、そのことが分散システム全体に対する大きな問題になり得ます。プロダクションでは、データベースは利用不可能になったり、あるいは別のホストに移動したりする場合があるでしょう。アプリケーションは、障害発生に対して復旧する必要があるためです。
引用：http://docs.docker.jp/compose/startup-order.html

僕の調べた限りでは、コンテナの起動が完了してから他コンテナの起動を開始するというのは出来ないということがわかりました。仮に、DBコンテナの起動完了してからWEBコンテナを起動するという処理を可能にした場合、DBコンテナに何らかの障害が生じて起動に失敗したときにWEBコンテナは一生起動しない、ということが起きるからです。

他の記事を見てみると、「sleepで待つ」や「接続の確認ができてから実行する」方法が見つかりました。ただ、他に方法はないかなぁと試行錯誤した結果...。

# 解決：マイグレーションを最後に実行！

init.sh内の処理順序を変えて、マイグレーションを最後に実行するようにしました。

```
#!/bin/bash

DATE=`date "+%Y-%m-%d"`
# log ファイルの作成
sudo -u nginx touch /var/log/nginx/access.log
sudo -u nginx touch /var/log/nginx/error.log
touch /var/log/php-fpm/error.log
sudo -u nginx touch /var/www/html/storage/logs/laravel-${DATE}.log

# composerによるパッケージのインストール
composer install

# マイグレーションの実行
php artisan migrate

exit 0
```

```
【処理順】
1.ログ用のファイルを作成
2.composer installでパッケージのインストール
3.マイグレーションの実行&Seederの実行
```

すると..

```
web_1    | > Illuminate\Foundation\ComposerScripts::postAutoloadDump
web_1    | > @php artisan package:discover --ansi
web_1    | Discovered Package: facade/ignition
web_1    | Discovered Package: fideloper/proxy
web_1    | Discovered Package: laravel/tinker
web_1    | Discovered Package: nesbot/carbon
web_1    | Discovered Package: nunomaduro/collision
web_1    | Package manifest generated successfully.
web_1    | Migration table created successfully.
web_1    | Migrating: 2014_10_12_000000_create_users_table
web_1    | Migrated:  2014_10_12_000000_create_users_table (0.07 seconds)
web_1    | Migrating: 2014_10_12_100000_create_password_resets_table
web_1    | Migrated:  2014_10_12_100000_create_password_resets_table (0.04 seconds)
web_1    | Migrating: 2019_08_19_000000_create_failed_jobs_table
web_1    | Migrated:  2019_08_19_000000_create_failed_jobs_table (0.02 seconds)
```

<img width="795" alt="スクリーンショット 2019-11-09 18.16.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250884/bd354e58-6ab2-6e0c-87e4-92ef30fb1d51.png">

DBを確認したところ、問題なくテーブルが作成されていました。

# まとめ：処理順序を変えるのも手
今回はdockerコンテナを起動したときにマイグレーションを実行するというのをやりました。時間のかかる処理を先に行い、他のコンテナに関わる処理は後に行うというのも一つの解決策になるのではないでしょうか。これなら「起動時にSeederを実行してテストデータを入れておく」というのもできそうです。

<img width="788" alt="スクリーンショット 2019-11-09 18.31.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250884/909749d7-36c7-ada6-b9a6-1666a6dfc8f7.png">

# 参考
■Docker Composeでコンテナの起動順序を制御するには...
https://yoshinorin.net/2017/02/19/docker-compose-wait-other-application/

■docker-composeでDBの起動完了を待ってからWebアプリを実行する
https://qiita.com/shiena/items/47437f4f7874bf70d664

■Dockerfile reference
https://docs.docker.com/engine/reference/builder/

■Compose の起動順番を制御
http://docs.docker.jp/compose/startup-order.html