Docker開発環境構築 PHP7.3-Laravel5.8を例に学ぶDockerの仕組み

# Dockerを使って、現場の開発内容にあった環境を構築する力を付ける
これをゴールに今回はphp on Laravelを例に取って開発環境を構築していきたいと思います。Dockerによる開発環境構築能力は、バックエンドに属するか、インフラに属するか境目、つまり、infrastructure as codeに対しての知見は備えておいて損は無いと考えました。

docker-compose.ymlの記述の意味にはこちらを参照しました。
[Docker Compose - docker-compose.yml リファレンス](https://qiita.com/zembutsu/items/9e9d80e05e36e882caaa)


前提として

- Docker version 18.09.2, build 6247962

- docker-compose version 1.23.2, build 1110ad01


であることです。

諸注意として、私はneovimを利用してファイル作成をしています。しかし、テキストエディタは、個人のお好みのものを使用して作ってください。
nvimコマンドの部分は、各々のテキストエディタに読み替えて作業して下さい。

間違っているところ、こうした方がいいというものがありましたら、ご指摘おねがいします！

## 作っていく環境
- PHP-fpm :7.3
- Laravel Framework :5.8.23
- nginx :1.17.0
- mysql :8.0

**Githubはこちらです**
[docker-compose-origin-laravel](https://github.com/Ohnao/docker-compose-origin-laravel)

## Docker基礎知識と用語
端的に言うと、Dockerの利点は、ホストOSの上でコンテナを利用し、アプリを起動するので、最もホストOSに負荷がかからないし、メモリやディスクの消費量を抑えられることです。
コンテナとは、ホストOSの上で、別のOSのカーネルがプロセスを実行し、複数のルート・ファイルシステムを実行する仮想空間のことで、このようにコンテナを用いてOSを仮想展開する事を、コンテナ化と呼びます。
コンテナの特徴を書きだすと、以下の通り。

- コンテナは、環境を汚さずにすむ事が最大のメリット。

- コンテナは、イメージを基に作られる

- コンテナは仮想的にPCとしての役割を持つが、ローカルで開発しているプロダクトをコンテナに上げる場合、コンテナの中身とローカルで共通化されている必要がある。故に、ボリューム共有と言う概念がある。

- 各々のコンテナは自身のリソースを持つ。


## DockerのDockerfileとdocker-composeのdocker-compose.yml
基本的にこの２つのファイルを用いることで、dockerを利用し、開発環境を構築します。
Dockerは、Dockerfileからコンテナを立ち上げると言うメインの機能を担っています。
Dockerでコンテナを立ち上げる際にコマンドオプションを使ってポートを指定したり、連携するコンテナを指定したりするなどしてやたら長いコマンドを打つ必要がありました。

```
$ docker run -p 80:3000 --name nginx -it /bin/bash 
```
上記のコマンドは、『ポート80番で受けたリクエストを3000番に転送する形でコンテナネームはnginxでイメージはDockerfileで作られたものを使ってbashでゲストOSを立ち上げる』と言う意味になります。
長いですね。これに更に、連携するコンテナの指定やら、コンテナが立ち上がる時に転送したいファイルの指定などをつらつら書けばどうなるかは推して知るべしです。
そこで生まれたのがdocker-composeです。
docker-compsoeはdocker-compose.ymlを使って、今まで長く書かねばならなかったdockerコマンドのオプション部分をより詳しく、より読みやすく、より機能を盛り込める形でまとめてくれる機能を備えています。ですので、Dockerの開発環境の立ち上がりは、作成されたDockerfileをbuildし、コンテナイメージを作成し、作成されたイメージがコンテナとして立ち上がる際にdocker-compose.ymlに記載された設定に沿った機能を持ち合わせたコンテナが立ち上がると言う流れになります。
ここまでがDockerとdocker-composeの機能です。
後々にDockerfileの更新がいる場合、イメージの更新をせねばならないので、先ずは、buildし、後にupするという手順をふまえること。

### Dockerfile
Dockerfileには、コンテナをビルドする手順を書きこみます。書き込みに必要なものは、

FROM・・・docker hubで何を使うかを指定します。Dockerイメージの使用するわけですね。これをDockerfielではベースイメージと呼びます。ベースイメージは、定義の仕方がDocker hubで提供している側で変更されると呼び出せなくなることがあります。
（ex:centos:7 => centos:centos7

#### ADD
ファイル追加。 localにあるファイルをdockerのパスに通す

#### RUN
コマンドを実行させる

#### WORKDIR
コンテナに入った時に最初に来るディレクトリパスを指定できる

Runコマンドは、基本的に纏める必要があります。しかし、それは環境としてほとんど変更性の無い物に限定され、行進が定期的に行なわれるもに関しては、纏める必要はありません。

### docker-compose.yml
ymlには開発環境の大元のイメージがあり、コンテナをこの中で作っていきます。ymlにはルールがあり、2行のインデントと決まっています。servicesにコンテナの内容を示し、その中に立てたいコンテナのオプションを記述し、その下のインデントでそのコンテナの詳細な仕様を書き込んでいきます。

以上がdockerを使う上での基本的な知識となります。
こちらを適宜参照しながら、dockerの仕組みを手を動かして学んで行きます。

## Dockerfile作成
下記が今回のDockerfileになります。

```Dockerfile
FROM ubuntu:18.04

ENV DEBIAN_FRONTEND=noninteractive
##php設定
RUN apt-get update && \
    apt-get install software-properties-common -y && \
    apt-add-repository ppa:ondrej/php -y && \
    apt-get update && \
    apt-get install php7.3 -y && \
    apt-get install php7.3-fpm -y && \
    apt-get install php7.3-cgi -y && \
    apt-get install php7.3-cli -y && \
    apt-get install php7.3-mysql -y && \
    apt-get install php7.3-gd -y && \
    apt-get install php7.3-imagick -y && \
    apt-get install php7.3-recode -y && \
    apt-get install php7.3-tidy -y && \
    apt-get install php7.3-xmlrpc -y && \
    apt-get install php7.3-common -y && \
    apt-get install php7.3-curl -y && \
    apt-get install php7.3-mbstring -y && \
    apt-get install php7.3-xml -y && \
    apt-get install php7.3-bcmath -y && \
    apt-get install php7.3-bz2 -y && \
    apt-get install php7.3-intl -y && \
    apt-get install php7.3-json -y && \
    apt-get install php7.3-readline -y && \
    apt-get install php7.3-zip -y
## 自動起動設定
RUN apt-get install wget -y && \
    apt-get install make -y && \
    apt-get install libcurses-ui-perl -y && \
    apt-get install libterm-readkey-perl -y && \
    apt-get install libcurses-perl -y && \
    wget http://archive.ubuntu.com/ubuntu/pool/universe/s/sysv-rc-conf/sysv-rc-conf_0.99.orig.tar.gz && \
    tar zxvf sysv-rc-conf_0.99.orig.tar.gz && \
    cd sysv-rc-conf-0.99 && \
    make && \
    make install && \
    service php7.3-fpm start && \
    sysv-rc-conf php7.3-fpm on
## composerをインストール
RUN apt-get install curl -y && \
    apt-get clean && \
    curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

WORKDIR /var/www/html/

```
イメージはUbuntuの最新を使っています。ここにapt-getを使って必要なパッケージをどんどん入れていきます。この辺はローカルで開発環境を作っている時に１つ１つ打ったであろうコマンドを一括でやってくれるようになっています。そしてphp-fpmの自動起動を仕込んでおきます。最後にcomposerをインストールします。

## docker-compose.yml作成
ここから具体的なコンテナの定義を書いていきます。

### nginxコンテナを作ります。
ローカル環境に構築用のディレクトリを作り、その中に入ってdocker-compose.ymlを作ります。

```
$ mkdir laravel-test
$ cd laravel-test
$ nvim docker-compose.yml
```
ファイルの中身の全貌は下記の通りです。ここでは、nginxコンテナを作っていくところを説明します。

```laravel-test/docker-compose.yml
version: '2'
services:
  nginx:
    image: nginx:1.17.0
    container_name: "laravel-nginx"
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      - ./php-fpm-run:/var/run/php
    depends_on:
      - php

  php:
    build: ./
    container_name: "laravel-php"
    tty: true
    volumes:
      - ./php-conf/php.ini:/etc/php/7.3/fpm/php.ini
      - ./php-fpm-run:/var/run/php
      - ./php-pool/www.conf:/etc/php/7.3/fpm/pool.d/www.conf
      - ./laravel-product:/var/www/html/

  mysql:
    image: mysql:8.0
    container_name: "laravel-mysql"
    restart: always
    environment:
      MYSQL_DATABASE: root
      MYSQL_ROOT_PASSWORD: root
    ports:
      - 13306:3306

```

serviceの下で宣言することで1コンテナを定義できます。
nginxのdocker imageを引っ張ってきて、コンテナの名前を定義します。

```laravel-test/docker-compose.yml
services:
  nginx:
    image: nginx:1.17.0
    container_name: "laravel-nginx"
```

ここで前述したポートの指定などを行なっています。クラアントから80番でアクセスされたらnginxの80番に返す設定です。

```laravel-test/docker-compose.yml

ports:
      - "80:80"
```

volumesのところですが、[[ローカルのnginxの設定ファイル]:[コンテナで作成されるnginxの設定ファイル]]という構造になっています。ここでnginxの設定ファイル自体を共有させています。
depends_onでこれから作るphpのコンテナと接続させます。

```laravel-test/docker-compose.yml
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      - ./php-fpm-run:/var/run/php
    depends_on:
      - php
```

このように、使用するイメージとかも指定まで機能を盛り込めるので、dockerとdocker-composeの役割が混合しやすかったりしますので筆者のようにdockerとdocker-composeからセットで学習を始めた人は注意です。イメージをymlで取り込めてしまうので、Dockerfileの無い環境構築さえ可能ですが、ここではDockerfileも作ってみます。

次に、ディレクトリの中に更にnginxのディレクトリを作成し、そこにnginx.confのファイルを作成してvolume用のnginxのconfファイルを作成します。

```
$ mkdir nginx
$ nvim nginx.conf
```

confファイルの中身は以下のとおりです。
rootのパスにあるprojectの部分は開発するプロダクト名に任意で変えてください。

```laravel-test/nginx/nginx.conf
server{
  listen 80;
  listen [::]:80;
  root /var/www/html/project/public;
  index index.php index.html index.htm;
  server_name laravel_project.com;

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

  location ~ \.php$ {
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    include fastcgi_params;
    fastcgi_pass unix:/var/run/php/php7.3-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  }
}
```
これでnginxのコンテナ定義完了です。

### phpコンテナを作ります
新しく、phpのコンテナを作成する記述をします。buildまでymlの方で指定できてしまうので、コンテナ全てにbuildオプションを指定しておけば、runやupするだけでコンテナを立ち上げられます。

phpコンテナは先ほどの全容部分から抜粋すると下記の部分です。

``` docker-compose.yml
  php:
    build: ./
    container_name: "laravel-php"
    tty: true
    volumes:
      - ./php-conf/php.ini:/etc/php/7.3/fpm/php.ini
      - ./php-fpm-run:/var/run/php
      - ./php-pool/www.conf:/etc/php/7.3/fpm/pool.d/www.conf
      - ./laravel-product:/var/www/html/

```

これから作るDockerfileを指定し、コンテナ名を指定します。

```docker-compose.yml
build: ./
container_name: "laravel-php"
```

このttyでコンテナを永続的に起動させます。

```docker-compose.yml
tty: true
```

ここで様々なphpの設定ファイルをvolumeさせます。

```docker-compose.yml
volumes:
      - ./php-conf/php.ini:/etc/php/7.3/fpm/php.ini
      - ./php-fpm-run:/var/run/php
      - ./php-pool/www.conf:/etc/php/7.3/fpm/pool.d/www.conf
      - ./laravel-product:/var/www/html/
```

まずはphp.iniを作っていきます。

```
$ mkdir php-conf
$ cd php-conf
$ nc php-conf
```

下記のようになります。

```/php-conf/php.ini
error_reporting = E_COMPILE_ERROR | E_RECOVERABLE_ERROR | E_ERROR | E_CORE_ERROR
max_input_time = 30
error_log = /var/log/php/error.log
file_uploads = On
allow_url_fopen = On
memory_limit = 256M
upload_max_filesize = 100M
max_execution_time = 360
date.timezone = Asia/Tokyo

```

次にwww.confを作りますが、ファイルやデータの永続化を行うには楽な方法があります。下記 にまとめてみました。
[Docker cp使って簡単永続化
](https://qiita.com/Nedward/items/d64749f447c9e11f411c)
こちらの方法で作っていきます。

```
$ docker-compose build
$ docker-compose up
$ docker ps
$ docker exec -it CONTAINER_ID /bin/bash
```
CONTAUNER_IDはphpコンテナのものを使用してみてください。
そうすると、コンテナの中に侵入できます。WORKDIRで設定した場所からプロンプトが始まるのがわかると思います。

```
root@8ade****512**:/var/www/html/project#
```

これで、confの設定ファイルを探します。

```
root@8ade****512**:/var/www/html/project# cd /etc/php/7.3/fpm/pool.d/www.conf
```

ここにあったので、コンテナからexitして、

```
$ mkdir php-pool
$ docker cp CONTAINER_ID:/etc/php/7.3/fpm/pool.d/www.conf ./php-pool
```

こうすれば、簡単に永続化したいものを引っ張ってこれます。コピーしてきたwww.confの下記の部分を編集してください。

```php-fool/www.conf
user = nginx
group = nginx
.
.
.
listen.owner = nginx
listen.group = nginx
```

**ここから先が個人的にハマったところです。sockファイルが消える問題です。**
UbuntuなどのLinuxは/var/run/以下のファイルって再起動かけると消えるんですね。故に、upしてもsockファイルが無くて立ち上がってくれません。exit code 0という最悪なエラーを吐き出して終わりです。さてこいつをどう永続化させたもんかという話なのですが、ここでだいぶ時間を食いました。結論としては、volumesの機能を利用します。ここでは先ほどのdocker cpも使えないので、これしか方法が見つかりませんでした。
先ほどのように、コンテナの中に入ってください。入ったコンテナの先で、php-fpmを起動させると、volumesがローカルにもsockファイルを作ってくれます。

```
root@8ade****512**:/var/www/html/project# service php7.3-fpm start
```

このように、立ち上げて、作ったものをローカルにもボリュームさせ、ボリュームしたものを再度コンテナへボリューム
してきて永続化する作業が挟まります。

下記が最後にprojectが来る場所を永続化させているところです。

```docker-compose.yml
- ./laravel-product:/var/www/html/
```

これでphpコンテナの定義完了です。

### mysqlコンテナを作ります。
新しくmysqlを動かすためのコンテナを記述します。

```larabel-test/docker-compose.yml
  mysql:
    image: mysql:8.0
    container_name: "laravel-mysql"
    restart: always
    environment:
      MYSQL_DATABASE: root
      MYSQL_ROOT_PASSWORD: root
    ports:
      - 13306:3306
```

これで、mysqlのコンテナは完成します。


## docker-compose build and up!!
コンテナが動いているかを確認してください。

```
$ docker-compose build
$ docker-compose up -d
$ docker-compose ps
```

続いて、コンテナの中に入り、そこで、laravelの新しいプロジェクトを作成してみてください。

```
$ docker-compose exec -it CONTAINER_ID /bin/bash
$ composer create-project --prefer-dist laravel/laravel project
$ chown -R nginx:nginx /var/www/html/project/
$ service php7.3-fpm start
```

testはlaravel newした時のプロジェクトのディレクトリで、nginx.confには、既にドキュメントルートをlaravelのpublicにまで設定してあり、ワークディレクトリとあわせて/var/www/html/publicとしています。

localhostにこれで接続すれば、laravelの画面が立ちあがります。

以上がdockerのコンテナをdocker-compose.ymlで作成し、それらを連携させ、ローカルにあるファイルをコンテナに反映させてdockerfileでコンテナを立ち上げて環境を構築させる一連の流れと仕組みになります。