Docker + Laravel 学習メモ

### はじめに

アプリケーションを開発していく上で、開発環境のWindowsと本番環境のLinuxで差分が出てしまうのを
解決したかったので、Dockerを実際に使ってみました

それに伴いDockerの概要や使ってみた感想などをまとめてみました

今回使用する[リポジトリ](https://github.com/watanabeJunna/docker-laravel)です

### Dockerについて

まず、[Docker](http://docs.docker.jp/engine/index.html)とは仮想環境を提供するソフトウェアの事を指します
コンテナは隔離されたOSを持ち、その上でアプリケーションを稼働させることが出来ます
そしてアプリケーションを依存対象と共にカプセル化することが出来ます

またホストOSとリソースを共有出来ることが特徴です

Dockerの大まかな概要は下記を参照してください

[公式](http://docs.docker.jp/get-started/index.html#id11), [Dockerアーキテクチャ](http://docs.docker.jp/engine/introduction/understanding-docker.html), [コンテナについて](https://qiita.com/bremen/items/4604f530fe25786240db#docker%E3%82%92%E4%BD%BF%E3%81%86%E3%81%9F%E3%82%81%E3%81%AB%E6%9C%80%E4%BD%8E%E9%99%90%E7%90%86%E8%A7%A3%E3%81%97%E3%81%A6%E3%81%8A%E3%81%8F%E3%81%B9%E3%81%8D%E3%81%93%E3%81%A8)

### Dockerのメリット

Dockerを利用する上での利点についてです

- フレームワークやミドルウェアを自分で構築する必要がない

例えばLaravelを利用した開発を行う場合に、ホストOSにPHPをインストールしなくても
[Bitnamiが提供するLaravelのイメージ](https://hub.docker.com/r/bitnami/laravel)がDocker Hubと呼ばれるリポジトリに配布されているため
簡単に環境を構築できます

また新しいフレームワークや技術が登場しても、Docker Hubにイメージが配布されていれば、自分で環境構築をする必要はありません

- 複数の開発環境の統一できる

DockerfileにOSやパッケージのバージョンを指定することが出来るため
開発環境では`Ubuntu`、`RHEL`などのOSや`Ruby`、`Nodejs`、`PHP`、`Go`などの言語のバージョンを統一できます

また`apt`、`pip`などのパッケージマネージャで管理されていないライブラリをmsiをでいちいちインストールする必要もありません

僕の認識では[selenium](https://www.seleniumhq.org/)は、[ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/home)を手でインストールする必要がありますが
コンテナの場合、その作業は必要なく、ホストの`\\Program Files`や`/opt`、`/var`などの汚染を気にすることもありません

- 開発環境と本番環境の差分を埋めることが出来る

アプリケーションやミドルウェアはDocker Engine上で稼働するため
開発環境、本番環境のホストOS上でDocker Engineプロセスさえ起動していれば、アプリケーションは差分なく稼働することが出来ます

- VMと比べて早い

[リンクを参照すると早いかもしれません](https://qiita.com/r-tominaga/items/8ac588d603802572185f)
ゲストOSがない分、コンテナの起動が早くキャッシュを利用したコンテナの再ビルドはとても高速です
`vagrant up`に比べて体感速度レベルで起動速度が違います

### Dockerのデメリット

Dockerのデメリットとして

- セキュリティが弱い
- 開発中なので、バグを踏む可能性がある
- 依然として、VMに依存しているのでオーバヘッドなどで恩恵を100%受けることが出来ない

が挙げられます、これについては現在調査中です

### Dockerを理解する

基本的なDockerの操作についてです

まずDockerをホストにインストールする必要があります
Dockerのインストールについては、下記のリンクを参照してください

[Dockerインストールメモ](https://qiita.com/n-yamanaka/items/ddb18943f5e43ca5ac2e)

インストールが終わったところで、簡単にコンテナでコマンドを実行する流れを紹介します
例えばCent OSコンテナで`cat /etc/system-release`コマンドを実行する場合は下記の手順を踏むことで処理を行えます

- `docker pull centos`コマンドで、コンテナのひな形となるイメージファイルを取得する
- `docker run --name mycontainer centos`コマンドで、イメージからコンテナを生成、起動する
- コンテナ起動後、`cat /etc/system-release`コマンドで実行する
- `exit`コマンドでコンテナから抜ける

`CentOS Linux release 7.6.1810 (Core)`が表示されれば処理成功です、バージョンは読み替えて下さい

補足ですが、docker runの引数に指定されたイメージがローカル環境に存在しない場合、Docker Hubから取得できるため、上記の作業はワンライナーで行えます

`docker run -it --rm centos cat /etc/system-release`

基本的な操作を理解したうえで、[サブコマンド](http://docs.docker.jp/engine/reference/commandline/toc.html)や[Dockerアーキテクチャ](http://docs.docker.jp/engine/introduction/understanding-docker.html)について確認してください

### Docker + Laravel

ここではDockerを使ってLaravelをインストールし、起動してみます

まずは環境情報についてです

`PC: Windows 10 Pro`
`Docker: Docker version 19.03.1`
`シェル: cmd、git bash`

ターミナルは`cmd`と`git bash`を使用しています、`>`プロンプトはcmdで`$`プロンプトはgit bashです

ここでは下記の内容を行います

- composerコンテナを作成する
- Laravelをcomposerでインストールする
- Laravelを起動し、ホストOSからLaravelのTOPページが見えることを確認する
- 開発環境でソースを編集し、変更が反映されることを確認する


まずは作業に使うディレクトリとLaravelを配置するディレクトリを作成します

```cmd
> sh -c "mkdir -p work/app"
```

ディレクトリ階層

```cmd
> tree /F /A | sh -c "egrep -e '^\s*[\||\\\\|]' -e '^\s+.*'"
```
```
\---work    --- 親ディレクトリ
    \---app --- Laravel配置ディレクトリ
```

次にDockerfileを作成します

```cmd
> sh -c "touch work/Dockerfile"
```

Dockerfileの詳細です、composerコンテナとして使用します

```Dockerfile
FROM php

ENV COMPOSER_ALLOW_SUPERUSER 1

RUN apt update && apt install -y wget git unzip

RUN EXPECTED_SIGNATURE="$(wget -q -O - https://composer.github.io/installer.sig)" && \
    php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
    ACTUAL_SIGNATURE="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" && \
    [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ] && { \
        >&2 echo 'ERROR: Invalid installer signature' && \
        rm composer-setup.php && \
        exit 1;\
    }; \
    php composer-setup.php --quiet && \
    mv ./composer.phar /usr/local/bin/composer

ENTRYPOINT ["composer"]
```

各パラメータについて説明します

Laravelのインストールのため、phpコンテナを使用します

```Dockerfile
FROM php
```

コンテナの操作する際のユーザはrootを使用していますが、[Laravelをrootで起動すると
警告が出るため](https://getcomposer.org/doc/03-cli.md#composer-allow-superuser)、一旦ここではroot起動を許可する設定を入れています

```Dockerfile
ENV COMPOSER_ALLOW_SUPERUSER 1
```

インストーラ取得時の`wget`、Laravelをインストールする際に必要な`git`、`unzip`をインストールします

```Dockerfile
RUN apt update && apt install -y wget git unzip
```

[公式に従ってインストールを行います](https://getcomposer.org/doc/faqs/how-to-install-composer-programmatically.md)

```Dockerfile
RUN EXPECTED_SIGNATURE="$(wget -q -O - https://composer.github.io/installer.sig)" && \
    php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
    ACTUAL_SIGNATURE="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" && \
    [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ] && { \
        >&2 echo 'ERROR: Invalid installer signature' && \
        rm composer-setup.php && \
        exit 1;\
    }; \
    php composer-setup.php --quiet && \
    mv ./composer.phar /usr/local/bin/composer
```

エントリポイントと呼ばれる起動シェルを指定します
このコンテナはLaravelをインストールするコンテナとして使用するため、値には`composer`を使用します

```Dockerfile
ENTRYPOINT ["composer"]
```

コマンド簡易化のため、ターミナルを切り替えます

```cmd
> sh
```

用意が出来たので、Dockerfileの内容をイメージに固めます
イメージを固める際に`-t`オプションをつけてイメージに名前を付けておくと後で楽なのでcomposerタグをつけます

```sh
$ cd work
$ docker build ./ -t composer
```

composerイメージを使用して、Laravelのインストールを行います、下記コマンドを実行し
`Application key set successfully.`が標準出力に表示され、ホストのディレクトリにLaravel一式が作成された場合は、処理成功です

```sh
$ docker run -it --rm -v /`pwd`/app://app -w //app composer create-project --prefer-dist "laravel/laravel=5.8.*" .
```

コマンドオプションを見てみます
このコマンドでやりたいことは、ホストの`./app`ディレクトリとコンテナの`/app`ディレクトリをマウントさせた状態でLaravelを`/app`にインストールすることです

`-i` ホストの入力をコンテナの標準入力に繋げます
`-t` コンテナの標準出力をホストの標準出力に繋げます

`--rm` コンテナが処理を終了した場合、自動的に停止したコンテナを削除します

`-v` マウントオプションです、[ホストのパス]:[コンテナのパス]によって
     ホストのファイルとコンテナのファイルの同期を行います

`-w` 作業ディレクトリの指定です

`composer` イメージの指定です

`create-project --prefer-dist "laravel/laravel=5.8.*" .` コンテナで実行するコマンドです

コマンドが成功したら、Laravelを起動してみます、下記コマンドを実行し
ブラウザから`localhost:8000`にアクセスし、LaravelのTOPページが表示されたら成功です

```sh
$ docker run -t --rm --name laravel -v /`pwd`/app://app -w //app -p 8000:8000 php php artisan serve --host=0.0.0.0
```

コマンドオプションを見てみます
このコマンドでやりたいことは、ホストとコンテナのポートを同期させた状態でLaravelを起動することです

`-t、--rm、-v、-w` 省略

`-p` ポートフォワードの指定です、コンテナのポートとホストのポートを同期できます

`--name` コンテナ名の指定です

`php` イメージの指定です

`php artisan serve --host=0.0.0.0` コンテナで実行するコマンドです

Laravelを立ちあげることが出来たら、Ctrl + Cでコンテナを停止させ、ソースの編集します

コンテナを停止した場合でもコンテナは動き続け、マウントとポートフォワードは有効なままです

Controller作成

```sh
$ docker run -it --rm -v /`pwd`/app://app -w //app php php artisan make:controller TmpController
```

ルーティングを定義します

```work/app/routes/web.php
<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

// 追加
Route::get('/tmp','TmpController@index');
```

Controllerを編集します

```work/app/app/Http/Controllers/TmpController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TmpController extends Controller
{
    public function index() {
        return "Hello World";
    }
}
```

ブラウザから`localhost:8000/tmp`にアクセスし、`Hello World`が表示されれば成功です

### 掃除

コンテナが残ったままなので、コンテナの削除を行います

```sh
$ docker ps -aq | xargs docker stop | xargs docker rm
```

`docker ps -aq` すべてコンテナのIDのみを表示します、--allと--quietの合わせ技ですね

`docker stop` 標準入力、または引数に指定されたコンテナを停止します

`docker rm` 標準入力、または引数に指定されたコンテナを削除します

イメージの削除を行います

```sh
$ docker rmi composer
```

### 最後に

今回はDBも使用しないシンプルなLaravelを構築しました

実際に開発を行うとなるとこれだけではダメなので、それぞれのプロジェクトに合わせたDockerfileやdocker-composer.ymlを作成しましょう

下記Laravelについてのエントリです

[Laravelの開発環境をDockerを使って構築する](https://qiita.com/ucan-lab/items/17c806973e69792ada99)、[Dockerを使ってLaravel開発環境構築](https://qiita.com/A-Kira/items/1c55ef689c0f91420e81)

開発環境でのみDockerを使用し、複数の環境を整えた上でSCMを利用した開発を行うなど
Dockerの使い方はいろいろとあると思います

またDockerのメリットについて、まだまだかけていない部分がありますので
気になった人は是非調べてみてください