php nginx mysqlをDockerで構築する

## はじめに
最近LaravelのDocker環境を考える機会がありました。
その時の知見を活かして、昔考えたDocker PHP環境をアップグレードしたいと思います。
今回の構成の特徴は`healthcheck`です。 `depends_on`で実行順序を保証するだけではなく、対象のコンテナの準備が完了(healthcheckに合格)してから依存するコンテナが構築を開始するようにしています。
適宜コメントを入れているので、参考にしてください。

私は学生エンジニアです~~あと2ヶ月くらい...~~。
至らない点もあるかもしれません。
その点ご了承ください。
## 前提条件
Docker, Docker Composeの知識

## ファイル構成

```
docker/
  db/
    conf.d/
      - my.cnf
    init/
      - init.sql
     .env
  php/
    - .env
    - Dockerfile
    - php.ini
  nginx/
    - default.conf
app/
  src/
    - index.php
  vendor/
  - composer.json
  - composer.lock
data/
.dockerignore
.docker-ignore
docker-compose.yml
```

## 各種ファイルの詳細

docker/db/conf.d/my.cnf
```
[mysql]
default-character-set=utf8mb4
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_bin
[client]
default-character-set=utf8mb4
```
docker/db/init/init.sql
```sql
# 実行したいsqlを記述
# volumeのマウント先の*.sqlはentrypointにimportされて全て実行される仕組み
```
docker/db/.env
```
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=test
MYSQL_USER=test
MYSQL_PASSWORD=test
```

docker/nginx/default.conf
```shell
server {
  listen       80;
  listen  [::]:80;
  server_name  localhost;

  # DocumentRoot (default: "/usr/share/nginx/html")
  root   /usr/src/myapp/src; # rootのページがある場所に指定する
  # "/"の時に返却するファイルを指定
  index  index.html index.htm index.php;

  # 特定のURIやパターンに対するサーバーの動作を指定する
  location  / {
    # 左から順に評価
    # 存在しなければ最後の指定に従ってindex.phpを返す
    try_files $uri $uri/ /index.php$is_args$args;
  }

  location ~ \.php$ {
    # php-fpmのservice名:9000
    fastcgi_pass  php:9000; # (Dockerのservice名を使用していることに注意) (php-pfmはwebサーバを9000で受け付けている)
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
    include       fastcgi_params;
  }

  # status pageの設定
  location /status {
    access_log off;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass php:9000; # (Dockerのservice名を使用していることに注意) (php-pfmはwebサーバを9000で受け付けている)
    fastcgi_index status.html;
  }
}
```

docker/php/Dockerfile
```Dockerfile
FROM php:8.3.2-fpm-bullseye

WORKDIR /usr/src/myapp

# rootでのcomposer installを許可する環境変数
ENV COMPOSER_ALLOW_SUPERUSER=1

COPY ./app/composer.json ./app/composer.lock /usr/src/myapp/

# composer use git zip
# apt may change the interface. apt-get would be better now.
# docker-phpext-install pdo_mysql provide driver of mysql
RUN apt-get update && apt-get install -y git zip zlib1g-dev lsof \
    && docker-php-ext-install pdo_mysql

# enable status code page
RUN echo pm.status_path = /status >> /usr/local/etc/php-fpm.d/www.conf

# Composer Install
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# package install
RUN composer install

COPY ./app /usr/src/myapp/
```
docker/php/.env
```shell
# 指定したい環境変数(外部にされしたくない)があればここで指定する
```
docker/php/php.ini
```
[Date]
date.timezone = "Asia/Tokyo"
[opcache]
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=1
```

docker-compose.yml
```yml
version: "3.8"
services:
  php: # phpコンテナはnginxが実行できないphpの処理をするために存在する # webサーバーからのリクエストは9000で受け付ける
    build:
      context: .
      dockerfile: ./docker/php/Dockerfile
    env_file: ./docker/php/.env
    tty: true
    healthcheck:
      test: ["CMD", "lsof", "-i:9000"]
      start_period: 5s
      timeout: 20s
      retries: 10
    depends_on:
      db: # dbコンテナの状態がhealthyになるまで待つ (dbコンテナの準備が完全に終了するまで)
        condition: service_healthy
    volumes:
      - ./app/:/usr/src/myapp
      - ./docker/php/php.ini:/usr/local/etc/php/php.ini
    environment:
      - TZ=Asia/Tokyo
  nginx:
    image: nginx:latest
    depends_on: # nginxはdbとphpコンテナがhealthyになるまで待つ
      db:
        condition: service_healthy
      php:
        condition: service_healthy
    ports:
      - 8080:80
    volumes:
      - ./app/:/usr/src/myapp
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf # 設定ファイルをnginxの指定位置にマウント
  db:
    image: mysql:8.0.31
    platform: linux/amd64
    env_file: ./docker/db/.env
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    volumes:
      - ./data:/var/lib/mysql
      - ./docker/db/conf.d/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./docker/db/init:/docker-entrypoint-initdb.d # 初回コンテナ起動時に実行したいsql
    healthcheck: # コンテナの構築が完了したことをチェック
      test:
        ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot"]
      start_period: 5s
      timeout: 20s
      retries: 10
    ports:
      - 3306:3306
    environment:
      TZ: Asia/Tokyo

  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    platform: linux/amd64
    environment:
      - PMA_ARBITRARY=1
      - PMA_HOST=db
    depends_on: # phpmyadminはdbが準備できればコンテナの構築を始めて良い
      db:
        condition: service_healthy
    ports:
      - 8000:80

```
.dockerignore
```
**/vender
data
```
.gitignore
```
data
vendor
.DS_Store
.env
```

## おわりに
Dockerの環境づくりは何度かやったことがあるのですがhealthcheckを利用したの初めてでした(存在は知ってた)
今回腰を据えて環境構築をすることになったので、healthcheckは絶対に導入したいと考えていました。~~Laravelでだけどね~~
慣れないうちはDockerで環境構築をするに当たり様々な試行錯誤と調査が必要で大変ですが、完成した時の達成感は大きなものがありました。
この記事を作成するにあたり多くの有益な情報源を頼らせていただきました。
ありがとうございます。
参考URLを貼っておきますので確認してください。

## 参考

https://zenn.dev/sun_asterisk/articles/b4b17681d08018

https://koni.hateblo.jp/entry/2016/01/12/184800

https://www.php.net/manual/ja/fpm.status.php

https://beriki.hatenadiary.org/entry/20130125/1359137755

https://zenn.dev/sun_asterisk/articles/b4b17681d08018

https://tech.actindi.net/2022/02/21/083000

https://papakoletter.com/docker_healthcheck/

https://serverfault.com/questions/928983/php-fpm-status-page-not-found-on-docker