docker で nginx & php-fpm の PHP 実行環境を構築する(TCP/UNIX domain socket)

この記事は個人ブログと同じ内容です

www.ritolab.com


nginx と php-fpm の構成で docker コンテナの PHP アプリケーション実行環境を構築してみます。

(開発環境構築の話ではないので docker-compose は使いません)

nginx と php-fpm の通信(How)が今回の話のメインです。

ディレクトリ構成など

docker イメージとコンテナの作成に入る前に、今回は php-fpm と nginx のコンテナを作成するため、それぞれ php-fpm 用に php というディレクトリを作成し、nginx 用に web というディレクトリを切って進めます。

そして、アプリケーション(の代わり)として index.phpphp-fpm 側に配置しておきます。

docker/
├─ php/
│   └ src/
│       └─ index.php
└─ web/

なお、index.php は phpinfo を表示させるだけの簡単なものになっています。

php/src/index.php

<?php phpinfo(); ?>

php-fpm の Dockerfile を作成

Dockerfile を作成して、まずは最もベースとなる部分の定義を行います。

php/ 配下に Dockerfile を作成し以下を記述します。

php/Dockerfile

FROM php:8.0-fpm-alpine

# install configure file
RUN cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini

# settings
COPY settings/php.ini /usr/local/etc/php/conf.d/php.ini

# app sources
COPY src /usr/share/nginx/html
  1. ベースイメージには PHP の公式イメージを指定しています。
  2. PHP の設定ファイルである php.ini を作成します。
  3. 追加で設定するための PHP 設定ファイルを設置しています。
  4. アプリケーションをドキュメントルートにコピーしています。

PHP の設定ファイル php.ini について

工程 2 では、PHP に予め用意されているテンプレートからそのまま php.ini を作成し設置しています。

設定値をデフォルトから変更したいものについては、予め用意した php.ini にその設定を記述し、工程 3 で conf.d/ 配下に設置する事で設定値を上書きしています。

php/settings/php.ini

[PHP]
date.timezone = "Asia/Tokyo"

なので、変更が不要なら工程 3 の記述は不要です。

nginx の Dockerfile を作成

次に、nginx 側の Dockerfile を作成します。

web/ 配下に Dockerfile を作成し以下を記述します。

web/Dockerfile

FROM nginx:1.19-alpine

# settings
COPY settings/default.conf /etc/nginx/conf.d/default.conf
  1. ベースイメージには nginx の公式イメージを指定しています。
  2. nginx の設定ファイルを設置します。

nginx - Docker Official Images

hub.docker.com

nginx と php-fpm の通信を定義する

nginx と php-fpm の通信方式は 2 通りあって、それぞれで設定ファイルや Dockerfile への記述が異なります。

どちらを採用すべきか?については検証と結論に至れていないので、今回は両方で構築してみます。

TCP で通信を行う

TCP で通信する場合は、nginx の設定ファイルを以下のように定義します。

web/settings/default.conf

server {
    listen 80;
    root   /usr/share/nginx/html;

    location / {
        index          index.php index.html index.htm;
        fastcgi_pass   php-sample:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
}

設定のもろもろは条件によって書き方が異なるためここの記述自体はかなり簡略化してますが、ポイントは fastcgi_pass の値です。

fastcgi_pass php-sample:9000;

  • php-sample というのは、今回作成する php-fpm のコンテナ名です。この後、php-fpm のコンテナを立ち上げる際にこの名前で起動させるので、それをここに指定します。

  • :9000 はポート番号です。php-fpm のデフォルトの待受ポートは 9000 なのでこれを指定しています。

前項で php-fpm と nginx の、ベースの定義を行いましたが、TCP で通信する場合は nginx の設定ファイルの定義のみで実現できます。

動作確認

ではイメージの作成とコンテナの起動を行いアプリケーションを動作させてみます。

以下のコマンドを実行して環境を構築します。

# 1. PHP のイメージを作成
docker build --no-cache -t php/sample:20210109 .

# 2. nginx のイメージを作成
docker build --no-cache -t nginx/sample:20210109 .

# 3. ネットワークを作成
docker network create --driver bridge sample_nw

# 4. PHP のコンテナを起動
docker run --net=sample_nw --name php-sample php/sample:20210109

# 5. nginx のコンテナを起動
docker run -p 80:80 --net=sample_nw --name nginx-sample nginx/sample:20210109

ブラウザからアクセスします。

f:id:ro9rito:20210112202912p:plain

nginx と php-fpm を TCP で通信させてアプリケーションが実行できました。

最終的なファイル構成

docker/
├── php/
│   ├ Dockerfile
│   ├ settings/
│   │   └─ php.ini
│   └ src/
│       └─ index.php
└── web/
    ├ Dockerfile
    └ settings/
        └─ default.conf

Docker コンテナ・ネットワークについて

先程の環境立ち上げの際に、イメージとコンテナ以外に「ネットワーク」を作成しました。

1つのネットワークを作成しそのネットワークに各コンテナを接続する事で、コンテナ間が互いを識別できるようにしています。

作成したネットワークの詳細を確認すると、コンテナが2つ接続されている事がわかります。

# ネットワークを作成
docker network create --driver bridge sample_nw

# ネットワーク一覧
% docker network ls
NETWORK ID     NAME             DRIVER    SCOPE
9326dc6f3546   sample_nw        bridge    local

# 作成したネットワークを確認
% docker network inspect sample_nw
[
  {
      "Name": "sample_nw",
      "Id": "9326dc6f35463041ec9511fba0426bd81311a704f757da755a81a42fd5da51ef",
      "Created": "2021-01-09T04:31:03.73817688Z",
      "Scope": "local",
      "Driver": "bridge",
      "EnableIPv6": false,
      "IPAM": {
          "Driver": "default",
          "Options": {},
          "Config": [
              {
                  "Subnet": "172.18.0.0/16",
                  "Gateway": "172.18.0.1"
              }
          ]
      },
      "Internal": false,
      "Attachable": false,
      "Ingress": false,
      "ConfigFrom": {
          "Network": ""
      },
      "ConfigOnly": false,
      "Containers": {
          "61d2d36499511f76ea2e64cd3124da3fc0fc05aa5b8af115bd70a3b937258e31": {
              "Name": "nginx-sample",
              "EndpointID": "27a848cb6d4e39121a2a8fcd0ec69c0929411e6bf353b868c7dc70354a351ef9",
              "MacAddress": "08:19:ac:14:00:06",
              "IPv4Address": "172.18.0.3/16",
              "IPv6Address": ""
          },
          "acbc9a2bf88caeb1fad3a8d0d0e5a409642ea2651487abede28157ad883b027d": {
              "Name": "php-sample",
              "EndpointID": "b1fe75cad1c2e6d9541cfca82577a1e02e93107b6e026ecb476e9309456ae1ce",
              "MacAddress": "08:19:ac:14:00:05",
              "IPv4Address": "172.18.0.2/16",
              "IPv6Address": ""
          }
      },
      "Options": {},
      "Labels": {}
  }
]

Docker コンテナ・ネットワークの理解

docs.docker.jp

ちなみに、ネットワークを作成せずに、 nginx 側のコンテナ起動時に --link オプションを使って php-fpm 側のコンテナを識別させる方法もありますが、現在では非推奨となっています。

コンテナ・リンク機能(古い機能)

docs.docker.jp

UNIX ドメインソケット で通信を行う

UNIX ドメインソケットで通信する場合に TCP の時と違うのは、通信の為にソケットを指定する事と、nginx 側からソケットを参照できるようにする事です。

php-fpm

php/Dockerfile

FROM php:8.0-fpm-alpine

# add user
RUN addgroup -S nginx && adduser -S nginx -G nginx

# install setting
RUN cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini

# settings
COPY settings/zzz-docker.conf /usr/local/etc/php-fpm.d/zzz-docker.conf
COPY settings/php.ini /usr/local/etc/php/conf.d/app-php.ini

# unix socket
RUN mkdir /var/run/php-fpm
VOLUME ["/var/run/php-fpm"]

# app sources
COPY src /usr/share/nginx/html
  1. ベースイメージには PHP の公式イメージを指定しています。
  2. nginx ユーザを作成します。
  3. PHP の設定ファイルである php.ini を作成します。
  4. php-fpm の設定ファイル(更新分)を設置します。
  5. PHP の設定ファイル(更新分)を設置します。
  6. ソケットを設置するディレクトリを作成し、ボリュームマウントします。
  7. アプリケーションをドキュメントルートにコピーします。

php-fpm の設定ファイルはデフォルトで設置されていますが、UNIX ドメインソケットを利用するために値を変更したい(工程 5 のところ)ので、設定値を上書きするための設定ファイルを作成します。

php/settings/zzz-docker.conf

[www]
listen = /var/run/php-fpm/php-fpm.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0660

unix ドメインソケットを使うように指定したのと、ソケットを使用するユーザ(パーミッション)を設定しています。

owner/group は、nginx 側からソケットを参照するユーザを指定しています。

nginx

web/Dockerfile

FROM nginx:1.19-alpine

# settings
COPY settings/default.conf /etc/nginx/conf.d/default.conf
  1. ベースイメージには nginx の公式イメージを指定しています。
  2. nginx の設定ファイルを設置します。

Dockerfile は TCP の時と変わりません。

nginx の設定ファイルを以下に定義します。

web/settings/default.conf

server {
    listen       80;
    server_name  localhost;
    root         /usr/share/nginx/html;

    location / {
        index          index.php index.html index.htm;
        fastcgi_pass   unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
}

TCP の時との違いは、fastcgi_pass に unix ドメインソケットを指定している点です。

動作確認

ではイメージの作成とコンテナの起動を行いアプリケーションを動作させてみます。

以下のコマンドを実行して環境を構築します。

# 1. PHP のイメージを作成
docker build --no-cache -t php/sample:20210109 .

# 2. nginx のイメージを作成
docker build --no-cache -t nginx/sample:20210109 .

# 3. PHP のコンテナを起動
docker run --name php-sample php/sample:20210109

# 4. nginx のコンテナを起動
docker run -p 80:80 --volumes-from php-sample --name nginx-sample nginx/sample:20210109

ブラウザからアクセスします。

f:id:ro9rito:20210112203405p:plain

nginx と php-fpm を UNIX ドメインソケット で通信させてアプリケーションが実行できました。

最終的なファイル構成

docker/
├ php/
│   ├ Dockerfile
│   ├ settings/
│   │   ├─ php.ini
│   │   └─ zzz-docker.conf
│   └ src/
│       └─ index.php
└ web/
    ├ Dockerfile
    └ settings/
        └── default.conf

まとめ

Laravel や CakePHP 等の PHP フレームワークを動かす際も基本的には同じ要領で、nginx の設定ファイル側を調整したり、必要なパッケージを PHP 側に入れたりなどすれば動作させる事ができます。

実行環境なのでアプリケーションごとイメージに固めていますが、これらの dockerfile と docker-compose を併せて役割を分ければ開発環境の構築も可能です。