nginx-proxy と docker-compose でレガシーなLAMP(LEMP)環境を複数稼働できるようにした

この記事は、[Makuake Development Team Advent Calendar 2019](https://adventar.org/calendars/4716) 7日めの記事です。

yutakiと申します。
PRIME ORDERというWebシステム開発サービスのエンジニアリングマネージャをやっております。
https://prime-order.jp/

# 概要

最近、レガシーな案件のプレビューのために
十数年ずっと頑張ってきたオンプレの共有開発サーバが故障してしまい
急いでnginx-proxyとdocker-composeで複数案件の開発環境を復活させた話をします。

# やったこと(ざっくり)
※端末はmacです
基本的に下記のコンテナ構成でプロジェクトを作成

 - 新しめ(laravel系): [nginx] + [php] + [mysql] + [node(build専用)] の4台構成
 - 古め(その他): [apache & php] + [mysql] の2台構成

そしてnginx-proxyでローカルドメインを割り当てました。
たとえばhogeプロジェクトであれば ```http://hoge.localhost```で使えるようにした感じです。


## 1. 案件ごとの構成をdocker-composeで作成

### ベースimageの選定について
一般的な構成の時は、app以外はほぼ公式イメージで対応しました。

appについては、docker-php-ext-installを使えるので、エクステンションの管理も楽です。
一部、pecl経由でないと入れられないものもありますがそれはpeclで入れて有効にすればいいだけ。
(imagemagickとか)

```Dockerfile
FROM php:7.1-fpm-alpine

# lib
RUN apk add --no-cache --virtual build-dependencies gcc make autoconf libc-dev libtool \
 && apk add --no-cache --virtual zlib1g-dev libxml2-dev \
 && apk add --no-cache --virtual libmagickwand-dev libpng-dev imagemagick-dev

# composer
COPY --from=composer /usr/bin/composer /usr/bin/composer

# php extension
RUN docker-php-ext-install zip xml pdo_mysql gd

RUN pecl install imagick \
  && docker-php-ext-enable imagick

RUN pecl install mailparse \
  && docker-php-ext-enable mailparse

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

WORKDIR /var/www
```

こんな感じで書いたものを使いまわしていけました。

---

apacheが一緒になっている5.4系のイメージなどはこんな感じ。

```Dockerfile
FROM php:5.4.45-apache

# composer
COPY --from=composer /usr/bin/composer /usr/bin/composer

# lib
RUN apt-get update \
  && apt-get install -y git libmagickwand-dev libmcrypt-dev

# php extension
RUN docker-php-ext-install mcrypt pdo pdo_mysql zip \
  && pecl install imagick \
  && docker-php-ext-enable imagick

# apache module
RUN mv /etc/apache2/mods-available/rewrite.load /etc/apache2/mods-enabled/ \
  && mv /etc/apache2/mods-available/headers.load /etc/apache2/mods-enabled/

# conf, ini
COPY site.conf /etc/apache2/sites-enabled/site.conf
COPY php.ini /usr/local/etc/php/php.ini

WORKDIR /var/www/site
```

apacheのモジュールがconfファイルを移動するだけで有効になるので楽なのがポイントです。

---

あとは、ごく少数、PHP5.2みたいな古いバージョンが使われている社内システムもありました。
そちらは公式にはバージョンがないので、DockerHubで該当バージョンを使っているものを探して利用しました。

例えば下記など。
https://github.com/andres-ortiz/php5.2-apache2.2
セキュリティパッチ等当ててくれているので開発環境とはいえありがたいです。
ただこのimageは起動時にrun.shでhtdocs以下にログディレクトリを作成してしまうので
スクリプトを上書きするかDocumentrootを変更するなどすると良いと思います。

## 2. nginx-proxy
こちらを利用します。
https://github.com/jwilder/nginx-proxy

使い方の要点は下記です。

- どこでもいいのでnginx-proxyをdocker-compose.yml経由で起動しておく
- 各案件のdocker-composeの調整
    - 「VIRTUAL_HOST」という環境変数でホスト名を定義する
    - nginx-proxyと同じネットワークに所属させる

nginx-proxyを起動しっぱなしにしておけば特に他に設定が不要という神ツールです。
(dockerのプロセスを監視、各docker-composeのup、downを検知して勝手にプロキシしてくれます)

### nginx-proxy本体のdocker-compose.yml
docker-compose.ymlは最小構成ならこれだけ

```docker-compose.yml
version: '2'

services:
  nginx-proxy:
    image: jwilder/nginx-proxy
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
    restart: always
```

ちなみにネットワーク名は固定してもいいと思います。
nginx-proxyというディレクトリで起動しているなら「nginx-proxy_default」
がネットワーク名になります。

### 各案件のdocker-compose.yml

たとえばこう

```docker-compose.yml
version: '3'

services:
  hoge-web-php:
    image: ${WEB_PHP_IMAGE}
    build: docker/web-php
    ports:
    - 80
    volumes:
      - ./logs/apache2:/usr/local/apache2/logs:cached
      - ./server:/usr/local/apache2/app:cached
    environment:
      VIRTUAL_HOST: hoge.localhost
  hoge-db:
    image: ${MYSQL_IMAGE}
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DBNAME}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      TZ: ${MYSQL_TZ}
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
    - ./logs/mysql:/var/log/mysql:cached
    - ./docker/db/data:/var/lib/mysql:cached
    - ./docker/db/my.cnf:/etc/mysql/conf.d/my.cnf
    - ./docker/db/sql:/docker-entrypoint-initdb.d
    ports:
    - 3306
# 下記はnginx-proxyを利用するときのみ解放
networks:
  default:
    external:
      name: nginx-proxy_default
```
webサーバのコンテナのポートは80番にしてください。
あと、サービス名(hoge-web-php, hoge-dbなど)はネットワーク内でuniqueである必要があります。
案件名をprefixなどでつけておくといいと思います。

ポイントは、hoge-web-phpの

```docker-compose.yml
environment:
  VIRTUAL_HOST=hoge.localost
```

そして最下部の

```docker-compose.yml
networks:
  default:
    external:
      name: nginx-proxy_default
```

です。
なお、このnetworksセクションをコメントアウトしてしまえば通常通りポートフォワードでの利用もできます。
nginx-proxyにproxyさせたいときだけ記述すれば良いです。

これで、```http://hoge.localhost```で該当案件を処理することができます。

## その他

### proxyの設定をいじりたい場合

confを上書きすれば簡単です。

こんなふうに上書き

```docker-compose.yml
    volumes:
      - ./proxy.conf:/etc/nginx/proxy.conf
```

デフォルトの中身はこうです。
(必要なものは揃っている印象です)

```proxy.conf
# HTTP 1.1 support
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;

# Mitigate httpoxy attack (see README for details)
proxy_set_header Proxy "";
```

### SSL対応(リンク紹介だけ)

nginx-proxyをssl対応する場合は
https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion
を活用すれば死ぬほど簡単に対応できます。

双方の活用は、素晴らしい参考記事がありましたので貼っておきます
https://tech.quartetcom.co.jp/2017/04/11/multiple-ssl-apps-on-one-docker-host/


### Laravel Valetを利用する場合

80/443ポートの食い合いになるので使う時は```valet stop```してください。
Laravel Valetはローカルにnginxを立ち上げていて、dnsmasqと併用して似たようなことをしています。

valetと共存させようと欲張り、nginx-proxyのポートを```8080:80```
にして運用しようとしてみたところ、プロキシはされるのですが
ローカル -> nginxの間のX-Forwarded-＊系を伝搬させることができず
Laravelのrouteメソッドなどで適切なURLが作成できませんでした。
しばらく調べてみたんですが、案件ごとにごちゃごちゃ設定するしか手がなさそうだったので断念。

### これTCPのプロキシならMySQLもいけるんじゃ？->いけなかった

MySQLも```3306:3306```でプロキシして```hoge-db.localhost```でSequel Proとかでアクセスできるんじゃないのか？
と思ってやってみましたが、ホストは解決できるものの正常に通信はできませんでした。
超残念。こちらは大人しくサービスごとにport設定するなどしています。
issueでも似たこと話してました。
https://github.com/jwilder/nginx-proxy/issues/318