docker-compose挑戦

※この記事は初めてdocker-composeを使って
nginx+php+mariadb(mysql)+adminer
の開発環境を作成したときのメモ。
はまったことや、ポイントだったり、気づいたことなど。

#ディレクトリ構成

```
mydocker
├─ docker-compose.yml
├─ .env
│
├─hostos
│  └─nginx
│      └─conf.d
│    　  └─ vhosts.conf
├─ nginx
│  ├─ conf   ※3
│  │  ├─ conf.d
│  │  │  └─ vhosts.conf
│  │  └─ nginx.conf
│  └─ Dockerfile
│
├─ php
│  ├─ php-fpm.d
│  │  └─www.conf
│  ├─ unix-socket
│  ├─ php.ini 
│  └─ Dockerfile
│
├─ mariadb
│  ├─ data
│  └─ Dockerfile
│
└─ logs
   ├─ nginx
   └─ php-fpm
```
ちなみにmydockerと同階層にsourceディレクトリがありphp等のソースコードを設置。

#docker-compose.yml
まずはファイルの中身から。

```mydocker/#docker-compose.yml
version: '3.4'

services:
  nginx:
    # image: nginx
    container_name: nginx
    restart: always
    build:
      context: ./nginx/
      args:
          project: ${PROJECT}
          host_name: ${HOST_NAME}
          user: ${PRJ_USER}
          user_id: ${PRJ_USER_ID}
    volumes:
      # log
      - ./logs/nginx/${HOST_NAME}:/var/log/nginx/${HOST_NAME}
      # source(DocumentRoot)
      - ./../source/${HOST_NAME}/public:/var/www/html/${HOST_NAME}
      # unix-socket
      - ./php/unix-socket:/var/run/php-fpm
    ports:
      - 8082:80
    depends_on:
      - phpfpm
  phpfpm:
    # image: 7.4.3-fpm
    container_name: php
    restart: always
    build:
      context: ./php/
      args:
        host_name: ${HOST_NAME}
        user: ${PRJ_USER}
        user_id: ${PRJ_USER_ID}
    volumes:
      # log
      - ./logs/php-fpm/:/var/log/php-fpm
      # source
      - ./../source:/home/${PRJ_USER}/source
      # unix-socket
      - ./php/unix-socket:/var/run/php-fpm
    depends_on:
      - mariadb
  mariadb:
    # image: mariadb
    container_name: mariadb
    restart: always
    build:
      context: ./mariadb/
      args:
        user_id: ${PRJ_USER_ID}
        group_id: ${PRJ_GROUP_ID}
    volumes:
      # データ永続化
      - "./mariadb/data:/var/lib/mysql"
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
    ports:
      - 33060:3306
  adminer:
    image: adminer
    container_name: adminer
    restart: always
    ports:
      # コンテナ側は8080必須
      - 8080:8080
    depends_on:
      - mariadb
```

※build - Dockerfileから起動するのでimageの記述は不要。
※└─ context - Dockerfileの場所
※└─ args - Dockerfileに変数を渡せる
※restart: always - ホストの起動時やDockerデーモンの起動時の自動起動設定。ただし、コンテナの起動設 定に不備があると、再起動を繰り返すので注意。
※depends_on - buildする順番。volumeやらコンテナ間通信やら考慮する

公式はhttp://docs.docker.jp/compose/toc.html


#共通で使う変数
Dockerファイル、およびdocker-compose.yml内で使える共通の変数がほしい
=>
.envファイルを使う。

```mydocker/.env
PROJECT=hogeporject
PRJ_USER=developper
PRJ_USER_ID=1000
PRJ_GROUP_ID=1000
ENV=development
HOST_NAME=xxx.com
MYSQL_ROOT_PASSWORD=xxxxxxx
```

.envで設定した変数は、docker-compose.yml内で使える。
Dockerファイルでも利用したい場合は、argsに渡す。

```mydocker/docker-compose.yml
build:
 args:　　← Dockerファイルに渡す。
   project: ${PROJECT}
   host_name: ${HOST_NAME}
   user: ${PRJ_USER}
   user_id: ${PRJ_USER_ID}
 volumes:
   - ./logs/nginx/${HOST_NAME}:/var/log/nginx/${HOST_NAME}　　← docker-compose.yml内で使う。
```

受け取るDockerファイルでは初期値を書いておける

```Dockerfile:mydocker/nginx/Dockerfile
FROM ngin
ARG user="developper"　　← 初期値
RUN useradd -m -s /bin/bash -u $user_id $user
```

#コンテナ内の設定ファイルに変数を渡す
例えば、Nginxコンテナ内のnginx.conf等にホスト名などを変数で渡したい。

私が使った方法は、Dockerfile内で
ARGで設定した変数でそのままsedして書き換える。

コンテナにもっていく設定ファイル（テンプレート）

```/etc/nginx/conf.d/vhosts.conf
server {
    listen       80;
    server_name  host_name;
```

Dockerfileで「host_name」の部分を置換

```Dockerfile:mydocker/nginx/Dockerfile
ARG host_name="xxx.com"
ADD ./conf/conf.d /etc/nginx/conf.d/   ←ホスト側からテンプレートをもってくる
RUN sed -i -e "s/host_name/$host_name/g" /etc/nginx/conf.d/vhosts.conf
```

その他の人では、envsubstコマンドを使っている人もいるようです。


#timezoneをホストと合わせたい
ログとか、DBのtimestanmpとかとか、コンテナ内の時間をホストと合わせたい。

Dockerファイルで以下を設定

```Dockerfile:Dockerfile
# timezone
RUN rm -fr /etc/localtime && ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
```

#コンテナのファイルがホスト側で権限がなくて読めない
volumeで設定して、ホスト側と共有しても、コンテナ側の所有者設定になってしまい、ホスト側でいじれないという問題。

例えば、mariadbの場合の解決策。

```Dockerfile:mydocker/mysql/Dockerfile
ARG user_id=1000
ARG group_id=1000

# データの所有者をホストOSとあわせる
RUN usermod -u $user_id -o mysql
RUN groupmod -g $group_id mysql
```

※user_idとgroup_idに、ホスト側で作った、Docker操作ユーザのidを渡す。
※mariadbの場合は、mysqlユーザでデータを作るので、mysqlユーザのユーザ番号とグループ番号をあわせる

#コンテナ間で共有したいファイルは、共通ユーザを所有者にしたい
nginx+php(php-fpm)でunixソケット通信をしたかったので、それに沿って。

nginxコンテナの設定でこうしたい。

```mydocker\nginx\conf\conf.d\vhosts.conf
    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        if (!-f $document_root$fastcgi_script_name) {
            return 404;
        }
        include /etc/nginx/fastcgi_params;
        #fastcgi_pass 127.0.0.1:9000;  ←普通？ならコレ
        #fastcgi_pass php:9000;  ←コンテナ間通信としては、とりあえずこれでもできる。
        fastcgi_pass  unix:/var/run/php-fpm/php-fpm.sock;  ←こうしたい。
```
つまり
unix:/var/run/php-fpm/php-fpm.sock
をphpとnginxで共有する必要がある。
=>
php⇔ホスト⇔nginx
でvolume設定をし、所有者権限も同じにする。

- **volume設定**

docker-compose.ymlをこうする

```mydocker\docker-compose.yml（一部）
services:
  nginx:
    container_name: nginx
    build:
      context: ./nginx/
      args:
          user: ${PRJ_USER}
          user_id: ${PRJ_USER_ID}
    volumes:
      - ./php/unix-socket:/var/run/php-fpm
  phpfpm:
    container_name: php
    build:
      context: ./php/
      args:
        user: ${PRJ_USER}
        user_id: ${PRJ_USER_ID}
    volumes:
      # unix-socket
      - ./php/unix-socket:/var/run/php-fpm
```

php⇔ホスト
nginx⇔ホスト
ホストで同じ場所を指定し、そこにphp-fpm.sockを格納できるようにしておく

- **共通ユーザ**

php、nginx、ホストで共通のユーザを作成し
そのユーザをコンテナ内の実行ユーザにする。

まず、ホスト側で共通ユーザを作成し、ユーザIDを確認する。
`$ useradd developper`
`$ id developper`

このIDをDockerfileに渡して使っていく。

phpの設定

```Dockerfile:mydocker\php\Dockerfile（一部）
ARG user="developper"
ARG user_id=1000

# 共通ユーザ（ホスト+nginx+php）
RUN useradd -m -s /bin/bash -u $user_id $user

#copy php.ini
COPY ./php.ini /usr/local/etc/php/

#php-fpm unix-socket
Run mkdir -p /var/run/php-fpm && chown $user:$user /var/run/php-fpm ※1
COPY ./php-fpm.d/www.conf /usr/local/etc/php-fpm.d/www.conf ※2
Run sed -i -e "s/user_name/$user/g" /usr/local/etc/php-fpm.d/www.conf ※3
Run sed -i -e "s/listen = 9000/listen = \/var\/run\/php-fpm\/php-fpm\.sock/g" /usr/local/etc/php-fpm.d/zz-docker.conf ※4
```

※1 /var/run/php-fpmディレクトリを作成し、所有者変更をしておく
※2 ホストから設定ファイル（php-fpm.d/www.conf）をもってくる

```php-fpm.d/www.conf（一部）
user = user_name
group = user_name

;listen = 127.0.0.1:9000
listen = /var/run/php-fpm/php-fpm.sock

;listen.owner = nobody
listen.owner = user_name
;listen.group = nobody
listen.group = user_name
```
※3 設定ファイルを共通ユーザで書き換え。
※4 php-fpmの公式イメージ特有の問題。
www.confのlisten設定を上書きされているので対応。
/usr/local/etc/php-fpm.d/zz-docker.confのlistenを書き換える。
[こちら](https://yoshinorin.net/2017/03/06/php-official-docker-image-trap/)など参照。

Nginxの設定

```Dockerfile:mydocker\nginx\Dockerfile（一部）
# 共通ユーザ（ホスト+nginx+php）
RUN useradd -m -s /bin/bash -u $user_id $user

#copy conf
Run sed -i -e "s/user nginx;/user $user;/g" /etc/nginx/nginx.conf ※1

#php-fpm unix-socket
Run mkdir -p /var/run/php-fpm && chown $user:$user /var/run/php-fpm ※2
```

※1 実行ユーザを共通ユーザにする。

```/etc/nginx/nginx.conf
user nginx; ←これを書き換える
```
※2 phpと同様に、/var/run/php-fpmディレクトリを作成し、所有者変更をしておく

#Nginxでのホスト側からのリバースプロキシ
ホスト側でSSL接続でうけて、HTTPかつコンテナ側のポートにリバースプロキシして流す方法。

ホスト側のNginx設定

```mydocker\hostos\nginx\conf.d\vhosts.conf
server {
    listen 443 ssl;
    server_name xxx.com;

    location / {
        # docker's port
        proxy_pass http://localhost:8082; ※1
        # https -> http
        proxy_redirect http:// https://; ※2
        # inherit remote info
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr; ※3
        proxy_set_header X-Forwarded-Proto https; ※4
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
```

※1 Nginxコンテナに流す
※2 HTTPSからHTTPに
※3 クライアントの接続元IPを伝える
※4 クライアントのプロトコル（SSLであること）を伝える

ちなみに、このときはfuelphpをのせた。
https://qiita.com/uecoeco/items/b9a8460fac33d84be2f9


#PHP⇔Nginx間でのvolume設定
当たり前といえば、当たり前のことだけど、ぼーっとしてるとミスする。

nginxコンテナの設定を見直す

```mydocker\nginx\conf\conf.d\vhosts.conf
    root         /var/www/html/xxx.com; ※1

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        if (!-f $document_root$fastcgi_script_name) {
            return 404;
        }
        include /etc/nginx/fastcgi_params;
        #fastcgi_pass 127.0.0.1:9000;
        #fastcgi_pass php:9000;
        fastcgi_pass  unix:/var/run/php-fpm/php-fpm.sock;

        fastcgi_index index.php;
        fastcgi_read_timeout 30;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; ※2
    }
```

はまったのは、このまま
xxx.com/index.php
を表示したところ
File not found.

なぜか。理由とポイントは
※1ドキュメントルートは/var/www/html/ではない。
※2 設定では、ドキュメントルート上のPHPスクリプトを探す。

PHPスクリプトはPHPコンテナにあるので
$document_rootがPHPコンテナにないと、探すことが出来ない。

解決策は以下２つ

1. fastcgi_param SCRIPT_FILENAMEの$document_rootをPHPコンテナに合わせたパスに書き換えておく

2. $document_rootをPHPコンテナに作って、PHPソースの場所からシンボリックリンクを作っておく

私は後者を採用。

```Dockerfile:mydocker\php\Dockerfile（一部）
RUN mkdir -p /var/www/html
Run ln -s /home/$user/source/public /var/www/html/xxx.com
```


#mariadb(mysql)のデータ永続化
/var/lib/mysqlをvolume設定すればよい。
所有者については先述の通り。

```mydocker\docker-compose.yml
  mariadb:
    # image: mariadb
    container_name: mariadb
    restart: always
    build:
      context: ./mariadb/
      args:
        user_id: ${PRJ_USER_ID}
        group_id: ${PRJ_GROUP_ID}
    volumes:
      # データ永続化
      - "./mariadb/data:/var/lib/mysql"
```


#ポート固定のコンテナがある
コンテナ側のポートが固定されており、docker側からのオプションでポート指定してもうまく立ち上がらない
コンテナがある。
nginxコンテナのように指定したポートで受けてプロキシとかもする必要はない。（するとnginxでポート使用済みのエラーが発生する。）
例えば、adminerとか。

```mydocker\docker-compose.yml
  adminer:
    image: adminer
    container_name: adminer
    restart: always
    # build:
    #   context: ./adminer/
    ports:
      # コンテナ側は8080必須
      - 8080:8080
    depends_on:
      - mariadb
```

例えば、
- 8080:80
としても立ち上がらない。
特に支障はないので、従えばよいのだが。
（どうしても変えたい場合の手段はあるだろうけど、そんなことはあまりないので。）


#コンテナがうまく立ち上がらないとき。
`$ docker-compose up -d`
`$ docker-compose ps`
をしてもExitedやRestartingになって立ち上がっていないとき。

=>
とにかくログをみる。
`$ docker-compose logs -f`

コンテナを削除してしまうとみれないので注意。

#Nginxのログ
これも、当たり前の話。
Nginxのログはrootユーザで作成されるので、コンテナ側とvolume設定しても、やはりroot所有者である。
いじることはないので、特に変える必要はないけど。