docker で nginx + php-fpm の構成で UNIX ソケットで連携させる

先日、[作成した nginx + php-fpm 環境](https://qiita.com/centipede/items/2b6f1c0c7de9225c026a)は TCP で連携させていた。この実行環境で nginx と php-fpm 間を UNIX Socket で連携できるようにする。

## 手順の流れ

1. php-fpm を UNIX ソケットで listen させるには
1. php-fpm を UNIX ソケットで連携させる dokcer を用意する
1. コンテナを稼働させて、設定の反映を確認する

### php-fpm を UNIX ソケットで listen させるには

大まかには次の設定が必要になる。

1. php-fpm イメージに登録されているユーザーを nginx のイメージに追加する
1. php-fpm への接続を TCP から UNIX Socket に変更する設定ファイルを追加する
1. nginx が php-fpm に接続する際の設定ファイルを追加する

### php-fpm イメージに登録されているユーザーを nginx のイメージに追加する

php-fpm イメージに登録されているユーザーを nginx イメージに追加する理由は、php-fpm の UNIX ソケットを操作する権限を追加する必要があるためだ。この権限を追加しないと、nginx からのリクエストを php-fpm に渡すことができない。

#### php-fpm コンテナを起動し、php-fpm イメージに登録されているユーザーを確認する

```zsh
$ docker run --detach --net=sample_nw --name php-sample php/sample:20220327
8ffaac4a194dbfee7cd214f4a84fb613b3dbc929dca0f46305d4550f9129acda
$ docker ps
CONTAINER ID   IMAGE                 COMMAND                  CREATED          STATUS          PORTS      NAMES
8ffaac4a194d   php/sample:20220327   "docker-php-entrypoi…"   31 seconds ago   Up 29 seconds   9000/tcp   php-sample
$ docker exec -i -t 8ffaac4a194d /bin/sh

# cat /etc/passwd | grep nginx
# cat /etc/passwd | grep www-data
www-data:x:82:82:Linux User,,,:/home/www-data:/sbin/nologin
/var/www/html # id www-data
uid=82(www-data) gid=82(www-data) groups=82(www-data),82(www-data)
```

WEB サーバー関連のユーザーとして、`uid=82(www-data) gid=82(www-data) groups=82(www-data),82(www-data)`が登録されているので、このユーザーを nginx のイメージに登録する。

### nginx イメージに登録されているユーザーを確認する

nginx のコンテを起動して、登録ユーザーを確認する。

www_data グループは登録されているが、www-data ユーザーは登録されていない。php イメージには、www-data のグループとユーザー登録されているので、nginx のイメージには www-data ユーザを登録する。

```zsh
$ docker ps
CONTAINER ID   IMAGE                     COMMAND                  CREATED          STATUS          PORTS                NAMES
95a89d5cac6d   php-socket_nginx-sample   "/docker-entrypoint.…"   18 seconds ago   Up 17 seconds   0.0.0.0:80->80/tcp   php-socket_nginx-sample_1
d82e7e343c79   php-socket_php-sample     "docker-php-entrypoi…"   19 seconds ago   Up 17 seconds   9000/tcp             php-socket_php-sample_1
$ docker exec -i -t bde24e5cbe3d /bin/sh
# cat www-data /etc/group
www-data:x:82:www-data
# cat nginx /etc/group
nginx:x:101:nginx
# grep www-data /etc/passwd
# grep nginx /etc/passwd
nginx:x:101:101:nginx:/var/cache/nginx:/sbin/nologin
# id nginx
uid=101(nginx) gid=101(nginx) groups=101(nginx),101(nginx)
# head /etc/nginx/nginx.conf

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
# exit
```

www-data と nginx のグループは登録されているが、ユーザーが登録されているのは、nginx ユーザーだけなのが上記で確認できた。

また、`/etc/nginx/nginx.conf` に登録されている nginx プロセスのユーザーは nginx で登録されているので、php-fpm の UNIX ソケットを操作する権限を付与するために、`/etc/nginx/nginx.conf` の設定を変更する。

### php-fpm への接続を TCP から UNIX Socket に変更する設定ファイルを追加する

php-fpm がリクエストを待ち受ける設定ファイルは、下記のディレクトリ下にある。

```zsh
$ docker run --detach --net=sample_nw --name php-sample php/sample:20220327
8ffaac4a194dbfee7cd214f4a84fb613b3dbc929dca0f46305d4550f9129acda
$ docker ps
CONTAINER ID   IMAGE                 COMMAND                  CREATED          STATUS          PORTS      NAMES
8ffaac4a194d   php/sample:20220327   "docker-php-entrypoi…"   31 seconds ago   Up 29 seconds   9000/tcp   php-sample
$ docker exec -i -t 8ffaac4a194d /bin/sh
# ls -l /usr/local/etc/php-fpm.d/
total 56
-rw-r--r--    1 root     root           357 Mar 23 19:57 docker.conf
-rw-r--r--    1 root     root         20876 Mar 23 19:57 www.conf
-rw-r--r--    1 root     root         20876 Mar 23 19:56 www.conf.default
-rw-r--r--    1 root     root            45 Mar 23 19:57 zz-docker.conf
# cat /usr/local/etc/php-fpm.d/zz-docker.conf
[global]
daemonize = no

[www]
listen = 9000
```
上記の設定ファイル `/usr/local/etc/php-fpm.d/zz-docker.conf` はなかなかの曲者で、`Dockerfile の COPY や docker-compose.yaml で、ファイルを利用して UNIX ソケットを listen する設定に置き換えても、変更点が反映されない`。

調べてみると、公式の php-fpm イメージの Dokcerfile の記述で、ユーザーの変更を公式の Dockerfile が上書きしていたことが原因だった。

* 参照: [php/Dockerfile at a41dc76cb007ac64177d6999f7be91aae149ee9e · docker-library/php · GitHub](https://github.com/docker-library/php/blob/a41dc76cb007ac64177d6999f7be91aae149ee9e/8.1/bullseye/fpm/Dockerfile)

```Dockerfile
	} | tee php-fpm.d/docker.conf; \
	{ \
		echo '[global]'; \
		echo 'daemonize = no'; \
		echo; \
		echo '[www]'; \
		echo 'listen = 9000'; \
	} | tee php-fpm.d/zz-docker.conf
```

#### 参考

* [Documentation regarding configuration. #241](https://github.com/docker-library/php/issues/241)
* [PHPの公式DockerイメージでUNIXソケット通信しようとして罠にハマる](https://yoshinorin.net/articles/2017/03/06/php-official-docker-image-trap/)
* [docker-composeでunixソケットを使った、Nginx、php-fpmコンテナを作る](https://qiita.com/yCroma/items/c22d948b4393fbc96f53)

では、php-fpm を UNIX ソケットで listen させるにはどうしたら良いのかというと、

* php-fpm の設定は、後から読み込まれる設定で上書きされる
* 設定ファイルは、ファイル名の昇順で読み込まれる
* この仕様を利用して、`/usr/local/etc/php-fpm.d/zz-docker.conf` よりも後に設定ファイルを読み込ませて、TCP で listen する設定を、UNIX ソケットで listen する設定で上書きする

ことが必要になる。

この回避策を記載している WEB ページはいくつかあったが、上記の理由は記載されていなかった。そのため、ファイル名を `socket.conf` にして設定変更を試みても反映されない理由がわからず、原因の確定に時間がかなりかかった。

### php-fpm を UNIX ソケットで連携させる dokcer を用意する

docker は先日、[作成した設定](https://qiita.com/centipede/items/2b6f1c0c7de9225c026a)を流用する。

#### 前回作成した docker の設定を複製する

作成した docker の設定が下記ディレクトリ `$HOME/works/docker-sample/php-tcp` にあるとする。それを複製する。

```zsh
$ pwd
$HOME/works/docker-sample
$ ls -l
total 0
drwxr-xr-x  5 centipede  centipede  160  3 27 13:36 php-tcp
$ cp -r php-tcp
$ ls -l
total 0
drwxr-xr-x  7 centipede  centipede  224  3 28 15:19 php-socket
drwxr-xr-x  5 centipede  centipede  160  3 27 13:36 php-tcp
```

#### docker-compose.yaml を編集する

php-fpm が生成する UNIX ソケットを、nginx のコンテナから参照できるようにする。

```zsh
$ cp -p docker-compose.yaml docker-compose.yaml.orig
$ vim docker-compose.yaml
$ cat docker-compose.yaml
version: '3'

# nginx と php-fpm のコンテナ間で共有されるボリューム名を設定
volumes:
  php-fpm-sock:

services:
  php-sample:
    restart: always
    build:
      context: .
      dockerfile: ./php/Dockerfile
    volumes:
      # 共有されるボリュームを次のディレクトリにマウントさせる
      - php-fpm-sock:/var/run/php-fpm

  nginx-sample:
    restart: always
    ports:
      - 80:80
    build:
      context: .
      dockerfile: ./nginx/Dockerfile
    volumes:
      # 共有されるボリュームを次のディレクトリにマウントさせる
      - php-fpm-sock:/var/run/php-fpm
      # ngixn のプロセスを実行するユーザーを変更するために、設定ファイルを置き換える
      - ./nginx/settings/nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - php-sample
```

変更点は次の通り。

```diff
$ diff -uw docker-compose.yaml.orig docker-compose.yaml
--- docker-compose.yaml.orig	2022-03-27 13:51:28.000000000 +0900
+++ docker-compose.yaml	2022-03-28 15:20:51.000000000 +0900
@@ -1,11 +1,16 @@
 version: '3'

+volumes:
+  php-fpm-sock:
+
 services:
   php-sample:
     restart: always
     build:
       context: .
       dockerfile: ./php/Dockerfile
+    volumes:
+      - php-fpm-sock:/var/run/php-fpm

   nginx-sample:
     restart: always
@@ -14,3 +19,8 @@
     build:
       context: .
       dockerfile: ./nginx/Dockerfile
+    volumes:
+      - php-fpm-sock:/var/run/php-fpm
+      - ./nginx/settings/nginx.conf:/etc/nginx/nginx.conf
+    depends_on:
+      - php-sample
```

#### nginx の Dockerfile を変更する

php-fpm の UNIX ソケットを操作するために、nginx のイメージにユーザーを追加する。

この時に、php のイメージに登録されている www-data ユーザーのユーザー ID と一致させること。今回は ID が 82 だったので、ユーザー ID が 82 をもつ www-data ユーザーを nginx のイメージに作成する。

```zshr
$ cp nginx/Dockerfile nginx/Dockerfile.tcp
$ vim nginx/Dockerfile
$ cat nginx/Dockerfile
FROM nginx:1.21.6-alpine

# add nginx group and nginx user
RUN adduser -S www-data -G www-data -u 82 \
    && echo "www-data ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers \
    && echo 'www-data:www-data' | chpasswd

COPY nginx/settings/default.conf /etc/nginx/conf.d/default.conf
```

変更点は次の通り。

```diff
$ diff -uw nginx/Dockerfile.bak nginx/Dockerfile
--- nginx/Dockerfile.tcp	2022-03-27 17:50:49.000000000 +0900
+++ nginx/Dockerfile	2022-03-28 15:22:08.000000000 +0900
@@ -1,3 +1,8 @@
 FROM nginx:1.21.6-alpine

-COPY settings/default.conf /etc/nginx/conf.d/default.conf
+# add nginx group and nginx user
+RUN adduser -S www-data -G www-data -u 82 \
+    && echo "www-data ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers \
+    && echo 'www-data:www-data' | chpasswd
+
+COPY nginx/settings/default.conf /etc/nginx/conf.d/default.conf
\ No newline at end of file
```

#### UNIX ソケットを介して nginx と php-fpm の連携するように変更をする

nginx を実行するユーザーを nginx から www-data に変更するために、変更を加えた `nginc.conf` を用意する。

```zsh
$ touch nginx/settings/nginx.conf
$ vim nginx/settings/nginx.conf
$ cat nginx/settings/nginx.conf
# change nginx user to www-data
user  www-data;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}
```

php-fpm の UNIX ソケットとやりとりするように、`default.conf` の設定を変更する。php-fpm の UNIX ソケットは、`/var/run/php-fpm/php-fpm.sock` に生成する。

```zsh
$ cp nginx/settings/default.conf nginx/settings/default.conf.orig
$ vim nginx/settings/default.conf
$ cat nginx/settings/default.conf
server {
    listen 80;
    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;
    }
}
```

変更点は次の通り。

```diff
$ diff -uw nginx/settings/default.conf.orig nginx/settings/default.conf
--- nginx/settings/default.conf.orig	2022-03-27 18:00:38.000000000 +0900
+++ nginx/settings/default.conf	2022-03-27 18:31:28.000000000 +0900
@@ -4,7 +4,7 @@

     location / {
         index          index.php index.html index.htm;
-        fastcgi_pass   php-sample:9000;
+        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;
```

#### php-fpm の Dockerfile を変更する

php-fpm が TCP 9000 ポートを listen させる設定 `/usr/local/etc/php-fpm.d/zz-dcoker.conf` を読み込んだ後に、その設定を UNIX ソケットを listen する設定に上書きするために、ファイル名 `/usr/local/etc/php-fpm.d/zz-www.conf` を php-fpm のイメージに取り込ませる。

```zsh
$ cp php/Dockerfile php/Dockerfile.tcp
$ vim php/Dockerfile
$ cat php/Dockerfile
FROM php:8.1.4-fpm-alpine

RUN cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini

COPY php/settings/php.ini /usr/local/etc/php/conf.d/php.ini
# import the conf file ordred by file name to overwrite listening TCP port setting to unix socket
COPY php/settings/php-fpm.d/zz-www.conf /usr/local/etc/php-fpm.d/zz-www.conf

COPY php/src /usr/share/nginx/html
```

変更点は次の通り。

```diff
$ diff -uw php/Dockerfile.tcp php/Dockerfile
--- php/Dockerfile.tcp	2022-03-27 17:51:59.000000000 +0900
+++ php/Dockerfile	2022-03-28 15:36:56.000000000 +0900
@@ -1,5 +1,9 @@
 FROM php:8.1.4-fpm-alpine

 RUN cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini
+
 COPY php/settings/php.ini /usr/local/etc/php/conf.d/php.ini
+# import the conf file to overwrite listening TCP port setting to unix socket
+COPY php/settings/php-fpm.d/zz-www.conf /usr/local/etc/php-fpm.d/zz-www.conf
+
 COPY php/src /usr/share/nginx/html
```

### コンテナを稼働させて、設定の反映を確認する

念のため、現在のディレクトリを確認する。

```zsh
$ pwd
$HOME/works/docker-sample/php-socket
$ ls -l
$ ls -l
total 16
-rw-r--r--  1 centipede  centipede  471  3 28 15:20 docker-compose.yaml
-rw-r--r--  1 centipede  centipede  248  3 27 13:51 docker-compose.yaml.orig
drwxr-xr-x  6 centipede  centipede  192  4  3 18:50 nginx
drwxr-xr-x  7 centipede  centipede  224  3 28 15:20 php
drwxr-xr-x  2 centipede  centipede   64  3 28 13:29 src
$ tree
$ tree
.
├── docker-compose.yaml
├── docker-compose.yaml.orig
├── nginx
│   ├── Dockerfile
│   ├── Dockerfile.tcp
│   └── settings
│       ├── default.conf
│       ├── default.conf.orig
│       └── nginx.conf
└── php
    ├── Dockerfile
    ├── Dockerfile.tcp
    ├── settings
    │   ├── php-fpm.d
    │   │   └── zz-www.conf
    │   └── php.ini
    └── src
        └── index.php

6 directories, 12 files
```

コンテナを稼働させる。

```zsh
$ docker-compose up -d
$ docker ps
CONTAINER ID   IMAGE                     COMMAND                  CREATED      STATUS      PORTS                NAMES
95a89d5cac6d   php-socket_nginx-sample   "/docker-entrypoint.…"   6 days ago   Up 6 days   0.0.0.0:80->80/tcp   php-socket_nginx-sample_1
d82e7e343c79   php-socket_php-sample     "docker-php-entrypoi…"   6 days ago   Up 6 days   9000/tcp             php-socket_php-sample_1
```

php のコンテナにログインして、状態を確認する。

```zsh
$ docker exec -i -t d82e7e343c79 /bin/sh
/var/www/html # ls -l /usr/local/etc/php-fpm.d/
docker.conf       www.conf          www.conf.default  zz-docker.conf    zz-www.conf
/var/www/html # ls -l /usr/local/etc/php-fpm.d/zz-www.conf
-rw-r--r--    1 root     root           173 Mar 28 06:20 /usr/local/etc/php-fpm.d/zz-www.conf
/var/www/html # cat /usr/local/etc/php-fpm.d/zz-www.conf
[global]
daemonize = no

[www]

user = www-data
group = www-data

listen = /var/run/php-fpm/php-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode  = 0660
/var/www/html # ls -l /var/run/php-fpm/
total 0
srw-rw----    1 www-data www-data         0 Mar 28 06:23 php-fpm.sock
/var/www/html # netstat -natp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.11:44299        0.0.0.0:*               LISTEN      -
/var/www/html # exit
```

php-fpm の設定ファイルが取り込まれ、UNIX ソケットが想定通りに生成されている。`netstat -natp` を実行した結果、php-fpm が ポートを listen していないことが確認できた。

次に、nginx の状態を確認する。

```zsh
$ docker exec -i -t 95a89d5cac6d /bin/sh
/ # ls -l /var/run/php-fpm/
total 0
srw-rw----    1 www-data www-data         0 Mar 28 06:23 php-fpm.sock
/ # netstat -natp | grep nginx
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      1/nginx: master pro
/ # exit
```

nginx のコンテナから php-fpm の UNIX ソケットが見えていることが確認できた。

ホストマシンから curl で接続をしてみる。

```zsh
$ curl -I http://localhost/
HTTP/1.1 200 OK
Server: nginx/1.21.6
Date: Sun, 03 Apr 2022 09:58:42 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/8.1.4
```

[http://localhost/](http://localhost/) にも接続をして、phpinfo() の実行結果が表示されることを確認した。