WSL2+Docker+Laravel既存コードでの開発環境作成（XAMPPからの移行）

PC移行に伴い新たな開発環境を構築する必要が出てきたが、XAMPP入れるのも古いかな（nginx使いたいし）、WSL上にごちゃごちゃ入れると何入れたかわからなくなる、と思ったのでDockerでなんとかならないかと試してみることに。
Dockerが動く状況なら移行先でも同様の開発環境がすぐに構築できるようになったはず。

WSLを入れるところから長々書いているので、答えだけdockerのファイルの答えだけ見たい方は、下の方を見るか
https://github.com/miyawa-tarou/docker-test/commit/5d5c78633a6c0e94b0c25849cf683c9a459b891e
あたりを見てください。

# WSL2を入れる

## Windowsのアップデート
- Windows 10、バージョン 2004、ビルド 19041 以上である必要があるので、アップデートします。
- WindowsUpdateのところからできる場合もあるようですが、ゆっくりと順次配布のためない場合は下記からダウンロードできます
  - https://www.microsoft.com/ja-jp/software-download/windows10

## 仮想化を有効にする

### BIOS

- ここはメーカーによるから細かい方法は置いておくが、BIOSに入って
- Intel Virtualizationとかがdisalbeになっていたらenableにする

### Windows

Windowsの仮想化を有効に（GUIからやる方法もあるはず）
PowerShellを管理者モードで開き下記を実行

```powershell
> Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
> Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform
```

再起動

## WSL2の設定

[https://docs.microsoft.com/ja-jp/windows/wsl/install-win10](https://docs.microsoft.com/ja-jp/windows/wsl/install-win10)
再びPowerShellを管理者モードで開き下記を実行

```powershell
> dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
> dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
```
WSLのデフォルトをバージョン2にしておく
```powershell
wsl --set-default-version 2
```

「最新の WSL2 Linux カーネル更新プログラム パッケージ」が必要なこともあるかもしれない。
その場合は下記からダウンロード。

[https://docs.microsoft.com/ja-jp/windows/wsl/wsl2-kernel](https://docs.microsoft.com/ja-jp/windows/wsl/wsl2-kernel)

### ディストリビューションを入れる

[https://docs.microsoft.com/ja-jp/windows/wsl/install-win10#install-your-linux-distribution-of-choice](https://docs.microsoft.com/ja-jp/windows/wsl/install-win10#install-your-linux-distribution-of-choice)

WindowsStoreから好みを入れる（自分はUbuntuを入れた）
入ったら一回起動しておく（ここは起動しなくて動くか確かめてない）

### ディストリビューションをあとからWSL2にする

WSL1で入れてしまった場合は、下記コマンドで変換する

```powershell
> wsl -l -v
  NAME            STATE           VERSION
* Ubuntu-18.04    Stopped         1
  Ubuntu          Running         1
> wsl --set-version Ubuntu 2
```
変換なのでちょい時間がかかる

## Docker Desktopを入れる

[https://www.docker.com/products/docker-desktop](https://www.docker.com/products/docker-desktop)

Enable WSL2 Windows Featuresのチェックは付けたまま
これでDocker環境の準備は整ったはず

# 既存ファイル郡にDockerのファイルを追加する

ここからはDockerの話
既存コードににDocker設定を入れる

- ドキュメントルートに `docker-composer.yml`
- それ以外のDocker関連ファイルを `./docker/` 以下に置く

### docker-composer.yml

XAMPPからの移行だが、本番はに近づけるため、
nodeサーバー、php-fpm、MariaDBと、XAMPPを意識するという点で今回はphpmyadminを入れる

それぞれ別のコンテナで動かす

```docker:docker-composer.yml
version: '3'
services:
  test_db:
    image: mariadb:latest
    volumes:
      # データ永続化用
      - ./docker/dbdata:/var/lib/mysql
      # 初期データ用（ここに置いたsqlファイル等は実行される）
      - ./docker/initdb:/docker-entrypoint-initdb.d
    environment:
      - MYSQL_ROOT_PASSWORD=mariadb
    ports:
      # Docker外（ローカル）からも3306で繋がるようにしておく
      - 3306:3306
  test_myadmin:
    image: phpmyadmin/phpmyadmin
    environment:
      - PMA_ARBITRARY=1
      # 上のDBコンテナ名
      - PMA_HOST=test_db
      # root固定
      - PMA_USER=root
      # 上のMYSQL_ROOT_PASSWORDの値
      - PMA_PASSWORD=mariadb
    links:
      # DBコンテナにつながるようにする
      - test_db
    ports:
      # ローカルのブラウザからはlocalhost:8080で見られるようにする
      - 8080:80
  test_nginx:
    image: nginx:alpine
    depends_on:
      - test_php
    volumes:
      # nginxの設定
      - ./docker/nginx/docker.conf:/etc/nginx/conf.d/default.conf
      # nginxのrootに合わせる（rootにはpublicを付ける必要がある）
      - .:/var/www/test
    ports:
      - 80:80
    environment:
      - NGINX_PORT=80
  test_php:
    # これだけ素のイメージでは足りないのでDockerfileを利用する（Dockfileのパスを指定）
    build: ./docker/php
    links:
      - test_db
    volumes:
      # php.iniの設定を送る（今回は特にめだった何かはしていない）
      - ./docker/php/php.ini:/usr/local/etc/php/php.ini
      - .:/var/www/test
    ports:
      - 9000:9000
```

Dockerを理解してないがゆえに割と悩んだのが、多くの環境構築系資料で、ここにおいたコードが実行されるでしょみたいの書かれるけど、「え？どうやって編集するの？」ということ

結論としては、volumes以下はコピーされるのではなくマウントされるので、更新すれば自動で反映される。なので

```yaml
volumes
  - .:/var/www/hoge
```

とかやれば、ファイル全体が自動的にマウントされる、Dockerコンテナ側に反映される

## Dockerfile（PHPサーバー）

```docker:docker/php/Dockerfile
FROM php:7.3-fpm

RUN apt-get update \
    # Laravel実行には必要
    && apt-get install -y zlib1g-dev libzip-dev mariadb-client \
    && docker-php-ext-install zip pdo_mysql \
    # XDebug用（今回は不要）
    && pecl install xdebug \
    && docker-php-ext-enable xdebug
COPY --from=composer /usr/bin/composer /usr/bin/composer
```

composerは必須ではないが、後述のLaravelクソ重い問題で利用する

[Docker に Composer をインストールするベストプラクティス（と解説）](https://qiita.com/yatsubashi/items/02bbbebbfe7e5a5976bc)

### Nginxのconf

```yaml:docker/nginx/docker.conf
server {
    # nginxコンテナのポートの右側の数値
    listen       80;
    server_name  localhost;
    charset      utf-8;

    # volumesでマウントしているところと一致させる（Laravelなのでこちらはpublicをつける）
    root /var/www/test/public;

    index index.php;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        # test_phpはdockerのPHPコンテナ名、9000はそのポート（右側の数字）
        fastcgi_pass  test_php:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include       fastcgi_params;
    }
}
```

### 初期SQLを置く

`docker-compose.yml` のファイル内のコメントに書いたが、データベース系のコンテナだあと
/docker-entrypoint-initdb.d　に置かれたファイルは自動でよしなにしてくれるらしい。
ここに `.sql` , `.sql.gz` をおけばSQLが実行、`.sh`を置けばシェルが走るらしい。
artisanでmigrationで行う場合は `.sh` で書けば良い

上のdocker-composerで/docker/initdb においたものが/docker-entrypoint-initdb.d にコピーされるのでそこにおいておく

なお、いきなりテーブル作成をしてしまうとエラーになる。ちゃんとdatabaseの作成・指定から行う。

[Docker で MySQL 起動時にデータの初期化を行う](https://qiita.com/moaikids/items/f7c0db2c98425094ef10)

### Laravelの.env設定

関係あるのはDB周りだけなので、そこの部分だけ修正する。

```yaml
DB_CONNECTION=mysql
# DBコンテナ名
DB_HOST=test_db
# DBコンテナで指定したポート（右側）
DB_PORT=3306
# 実際に使っているもの次第（今回は初期データがここに入ってる）
DB_DATABASE=docker_test
DB_USERNAME=root
# 指定したパスワード
DB_PASSWORD=mariadb
```

## 起動してみる

composer-docker.ymlのある場所まで移動して

```
$ docker-compose up -d
```

Windows側でもWSL側でも動くはずである。
ミスがなければ起動するはず。
DockerDesktop側のDashboardからも起動が確認できる

動作確認

- phpMyAdminへのアクセスは：http://localhost:8080/
  - 上でおいたSQLも動いていることを確認
- http://localhost/
  - 表示されるはず

# 問題点１：Laravelがクソ重い

上のを実行すると気づくとも思うが、何もしてない処理でもクソ重い。
大して何もしてないページなのに表示に数秒かかる・・・。これではさすがに厳しい。
どうやら調べてみると、「マウントするファイルが多いと重くなる」模様
下記が解決策として実行したもの・浮かんだもの

### 不要なファイルはマウントしない（今回やった解決策）

自分で触ることのないファイルはわざわざ、マウントする必要がない

- vendor以下は依存を変えない限りは不要である
- storage以下は自動で作られるファイルだからこちらかアップすることはない

しかし単にマウントしないだけだと以下の問題が起きる

- vendor以下がないため、composer installをする必要がある
- storage以下のファイルが存在しないからエラーになる

一旦ファイルを送ってからマウントを切るという手もあるが、一発で構築できないという点でイケてない。
構築後に中に入って準備をするのもイケてない。

なので、必要なファイルはDockerfileで取得するようにする

```yaml:docker-compose.yml
volumes:
  # php.iniの設定を送る
  - ./docker/php/php.ini:/usr/local/etc/php/php.ini
  - .:/var/www/test
  # 除外
  - /var/www/test/storage
  - /var/www/test/vendor
```

Dockerfileに下記を追加

```docker:docker/php/Dockerfile
# storageファイルはマウントしないのでもともと必要なディレクトリは作成する
RUN mkdir -p /var/www/test/storage/app/public
RUN mkdir -p /var/www/test/storage/framework/cache/data
RUN mkdir -p /var/www/test/storage/framework/sessions
RUN mkdir -p /var/www/test/storage/framework/testing
RUN mkdir -p /var/www/test/storage/framework/views
RUN chmod -R 777 /var/www/test/storage

# vendorはマウントしないのでcomposerで入れる
COPY composer.lock /var/www/test/composer.lock
# いらないかも
COPY composer.json /var/www/test/composer.json

# composer.jsonで必須になっているもの（autoloadまわり）
RUN mkdir -p /var/www/test/database/seeds
RUN mkdir -p /var/www/test/database/factories

# cd /var/www/test && composer install　でも動きそう
WORKDIR /var/www/test
# scriptsでファイルチェックをしているが、マウント前のこの時点では存在しないのでそれは避ける
RUN composer install --no-scripts
```

これで再作成

```docker
$ docker-compose build --no-cache
```

表示に1秒かからなくなった！

### その他解決策１：docker-syncを使う

WindowsもLinuxもベータ版らしい

[Docker for MacのLaravel アプリケーションのが遅い？それ、ファイルをいっぱいマウントしてるからかもです。](https://qiita.com/tyamahori/items/f627f85c7316e096a061)

Macだとこれが普通に行われている記事をよく見る

### その他解決策２：ファイルwatch＋rsyncする

上のdocker-syncはrsyncしているだけなので、自分で動かすこともできる気がする。
実際うまく動くかは不明

[https://medium.com/@imranhsayed/rsync-using-phpstorm-on-save-b1e73c079db0](https://medium.com/@imranhsayed/rsync-using-phpstorm-on-save-b1e73c079db0)

## 問題点２：同じ.envファイルでWSLとDocker上両方動かせない

`.env` で `DB_HOST=test_db` としているがDocker内での名称であり、WSL側では認識せず動かせない
そのため、開発を行うWSL側から `php artisan` などを実行してもDB周りが上手く動かない

- 127.0.0.1だとDockerコンテナからは127.0.0.1ではないため動かない
    - Connection refused.
- localhostだとsocketのエラーが出る
    - SQLSTATE[HY000] [2002] No such file or directory

Socketファイルを云々している記事もあったがこれじゃない感。

Docker側から見てlocalhostとはなにかというと、 `host.docker.internal` であり、これを入れて見るとartisanも動いたし、Dockerホスト側からも動いた

```docker:.env
DB_HOST=host.docker.internal
```

# 最終形

こちらにLarvelのテストファイルと共に置いてます：https://github.com/miyawa-tarou/docker-test/commit/5d5c78633a6c0e94b0c25849cf683c9a459b891e

docker-compose.yml

```docker:docker-compose.yml
version: '3'
services:
  test_db:
    image: mariadb:latest
    volumes:
      # データ永続化用
      - ./docker/dbdata:/var/lib/mysql
      # 初期データ用（DB系イメージだとここに置いたsqlファイル等が実行される）
      - ./docker/initdb:/docker-entrypoint-initdb.d
    environment:
      - MYSQL_ROOT_PASSWORD=mariadb
    ports:
      # Docker外からも3306で繋がるようにしておく
      - 3306:3306
  test_myadmin:
    image: phpmyadmin/phpmyadmin
    environment:
      - PMA_ARBITRARY=1
      # 上のDBコンテナのホスト
      - PMA_HOST=test_db
      # root固定
      - PMA_USER=root
      # 上のMYSQL_ROOT_PASSWORDの値
      - PMA_PASSWORD=mariadb
    links:
      # DBコンテナにつながるようにする
      - test_db
    ports:
      # ブラウザからはlocalhost:8080で見られるようにする
      - 8080:80
  test_nginx:
    image: nginx:alpine
    depends_on:
      - test_php
    volumes:
      - ./docker/nginx/docker.conf:/etc/nginx/conf.d/default.conf
    ports:
      - 80:80
    environment:
      - NGINX_PORT=80
  test_php:
    # これだけ素のイメージでは足りないのでDockerfileを利用する（Dockrfileのパスを指定）
    build:
      # composer.lockをDockerfile内で使うために実行場所は変えない（通常だとDockerfileの位置からの参照、親ファイルは読めない）
      context: .
      dockerfile: ./docker/php/Dockerfile
    links:
      - test_db
    volumes:
      # php.iniの設定を送る
      - ./docker/php/php.ini:/usr/local/etc/php/php.ini
      - .:/var/www/test
      # 除外
      - /var/www/test/storage
      - /var/www/test/vendor
    ports:
      - 9000:9000
```

docker/php/Dockerfile

```docker:docker/php/Dockerfile
FROM php:7.3-fpm

# composer.jsonで必須になっているもの
RUN mkdir -p /var/www/test/database/seeds
RUN mkdir -p /var/www/test/database/factories

RUN apt-get update \
    # Laravel実行には必要（多分）
    && apt-get install -y zlib1g-dev libzip-dev mariadb-client \
    && docker-php-ext-install zip pdo_mysql \
    # XDebug用
    && pecl install xdebug \
    && docker-php-ext-enable xdebug
COPY --from=composer /usr/bin/composer /usr/bin/composer

# storageファイルはマウントしないのでもともと必要なディレクトリは作成する
RUN mkdir -p /var/www/test/storage/app/public
RUN mkdir -p /var/www/test/storage/framework/cache/data
RUN mkdir -p /var/www/test/storage/framework/sessions
RUN mkdir -p /var/www/test/storage/framework/testing
RUN mkdir -p /var/www/test/storage/framework/views
RUN chmod -R 777 /var/www/test/storage

# vendorはマウントしないのでcomposerで入れる
COPY composer.json /var/www/test/composer.json
COPY composer.lock /var/www/test/composer.lock

# composer.jsonで必須になっているもの（autoloadまわり）
RUN mkdir -p /var/www/test/database/seeds
RUN mkdir -p /var/www/test/database/factories

# cd /var/www/test && composer installでも動きそう
WORKDIR /var/www/test
# scriptsでファイルチェックをしているが、この時点ではないのでそれは避ける（後でマウントされるため）
RUN composer install --no-scripts
```

docker/nginx/docker.conf

```docker:docker/nginx/docker.conf
server {
    # nginxコンテナのポートの右側の数値
    listen       80;
    server_name  localhost;
    charset      utf-8;

    # volumesでマウントしているところと一致させる
    root /var/www/test/public;

    index index.php;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        # test_phpはdockerのPHPコンテナ、9000はそのポート（右側の数字）
        fastcgi_pass  test_php:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include       fastcgi_params;
    }
}
```

.env（差分では.env.exampleに置いてます）

```docker:.env
DB_CONNECTION=mysql
DB_HOST=host.docker.internal
DB_PORT=3306
# 実際に使っているもの次第
DB_DATABASE=docker_test
DB_USERNAME=root
DB_PASSWORD=mariadb
```

Xdebug入れてますが、まだ設定はしていない・・・。