複数サイトをひとつのVPSでホスティングする場合、nginxなどのリバースプロキシをフロントに置き、リクエストをバックエンドのウェブサービスに振り分ける構成になります。
Dockerを使わなくてもこのような配置になりますが、Dockerで実現する場合はどうしたら良いでしょうか?Dockerでもこのトポロジーと同じようになりますが、本稿では具体的に、リバースプロキシコンテナのnginx-proxyとdocker-composeを使った、比較的管理が容易と思われる方法を紹介します。
ひとつのウェブサービスはひとつのdocker-composeにする
方針として、ウェブサービスの単位でdocker-compose.ymlを作るようにします。ひとつのdocker-compose.ymlに押し込む方法も考えられますが、そうしません。理由はウェブサービスを開設・閉鎖するたびにdocker-compose.ymlを変更しなければならず、変更ミスがサーバ全体に及ぶ危険性があるためと、サービス名の重複を気にしないとならないためです。
ウェブサービスごとにdocker-compose.ymlを作るようにしておけば、影響範囲をウェブサービスひとつに限定できます。ウェブサービスを追加するときも、新たにdocker-compose.ymlを追加するだけで良いので管理も単純です。
この方針をディレクトリ構成で表現する場合、僕は次のようにしています。app1, app2が各ウェブサービスのディレクトリで、ウェブサービスを追加するときは、このディレクトリを増やしていきます。sharedはウェブサービスをまたいで利用するサービスを管理します。例えば、nginx-proxyのようなリバースプロキシなどがここで管理されます。
├── app1
│ └── docker-compose.yml
├── app2
│ └── docker-compose.yml
└── shared
└── docker-compose.yml
入口となるnginx-proxyの設定
フロントに立てるリバースプロキシの設定をdocker-compose.ymlに起こすと次のようになります。
version: "2"
services:
proxy:
image: jwilder/nginx-proxy
container_name: proxy
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ./certs:/etc/nginx/certs:ro
restart: always
logging:
options:
max-size: 5m
max-file: "10"
networks:
default:
external:
name: shared
container_name: proxy
はコンテナ名の設定です。無くても構いません。省くとdocker-composeが成り行きでshared_proxy_1
といった名前をつけます。この設定は好みの問題です。サーバ全体で1つしか起動したくないので、コンテナ名をユニークにしたいというのが設定する動機ではあります。
ports
の部分はHTTPとHTTPSをリバースプロキシで受け止める設定です。nginx-proxyはSSL証明書を/etc/nginx/certs
に配置するとHTTPSも使えるようになるので、443でもリッスンするようにします。
volumes
の/var/run/docker.sock:/tmp/docker.sock
はnginx-proxyがDockerコンテナの起動・終了を検知するために必要です。起動を検知すると、nginxの/etc/nginx/conf.d/default.conf
を書き換えてupstream
を自動追加してくれます。./certs:/etc/nginx/certs
のマウントはSSL証明書置き場です。
restart: always
は万が一にもnginx-proxyが異常終了してもコンテナを自動再起動する設定です。
logging
の部分はログローテーションのための設定で、5MBごとにログファイルを分割し、10ファイルまで残す設定です。
networks
は、後述しますが、「shared」というブリッジネットワークにリバースプロキシを接続する設定です。docker-composeのv1書式ではdocker-composeをまたいでも同じネットワークに所属できたので、この設定は不要でした。v2書式からはdocker-compose.ymlごとに独立したネットワークが作られるようになったため、サービスをまたいで通信をする場合はどのネットワークに接続するかを明示的にする必要があります。
共有ネットワークを作る
リバースプロキシと各ウェブサービスが通信できるように、共有ネットワークを作ります。
docker network create --driver bridge shared
ウェブサービスの設定
新たにdocker-compose.ymlを作成し、ウェブサービスの設定をします。ここではWordPressを例にします。
version: "2"
services:
wordpress:
image: wordpress
environment:
VIRTUAL_HOST: wordpress.example.com
WORDPRESS_DB_PASSWORD: root
depends_on:
- mysql
restart: always
logging:
options:
max-size: 5m
max-file: "10"
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: root
restart: always
logging:
options:
max-size: 5m
max-file: "10"
networks:
default:
external:
name: shared
環境変数VIRTUAL_HOST
は、nginx-proxyのための設定です。ここで宣言したホスト名でnginx-proxyが自動的にアクセスをこのサービスに振り分けてくれます。
ここでもウェブサービスを共有ネットワークに乗せるために、networks
の設定が必要になります。これがないとnginx-proxyからの通信が行えなくなります。
ここまでの設定でトポロジーはこんな感じになります。
各サービスを起動する
リバースプロキシを起動します。
cd shared
docker-compose up -d
ウェブサービスを起動します。
cd app1
docker-compose up -d
データベースを共有のコンテナにする
先述のウェブサービスのdocker-compose.ymlではMySQLのコンテナはそのウェブサービスが専有するものにしていました。この方法はシンプルで、ウェブサービスごとにデータベースの設定ができたりと柔軟です。一方で、ウェブサービスが沢山できてくるとデータのバックアップが面倒だったり、SequelProなどのクライアントでつなぐために、ウェブサービスごとにMySQLのポート番号を決めなければならなかったりと、運用面が不便さがあります。
どちらもトレードオフだと思いますが、ひとつのVPSにMySQLをひとつだけ起動する方法についても触れておきます。データベースを共有のコンテナにした際のトポロジーはこんな感じです。
まず、shared/docker-compose.ymlのservices
にmysql
を追加します。
version: "2"
services:
proxy:
image: jwilder/nginx-proxy
container_name: proxy
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ./certs:/etc/nginx/certs:ro
restart: always
logging:
options:
max-size: 5m
max-file: "10"
mysql:
image: mysql:5.7
container_name: mysql
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: root
restart: always
logging:
options:
max-size: 5m
max-file: "10"
networks:
default:
external:
name: shared
mysqlはポート3306を開けておきます。これは、SequelProなどのMySQLクライアントで入れるようにするためのものです。DBをメンテするときにポートが空いていると、SSH越しにアクセスできるので便利です。
次にウェブアプリ側のdocker-compose.ymlからmysqlを取り除いて、代わりにexternal_links
で共有のmysqlコンテナにつながるようにします。
version: "2"
services:
wordpress:
image: wordpress
environment:
VIRTUAL_HOST: wordpress.example.com
WORDPRESS_DB_PASSWORD: root
external_links:
- mysql
restart: always
logging:
options:
max-size: 5m
max-file: "10"
networks:
default:
external:
name: shared
これでサーバひとつにつきMySQLがひとつにできます。
おわり
VPSで複数サイトをdockerで実現する方法を紹介しました。本稿で作成したdocker-composeのファイルはGitHubで公開しています。