suin.io

VPSにdockerで複数サイトをホスティングするには?

suin2017年1月3日

複数サイトをひとつの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に起こすと次のようになります。

shared/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を例にします。

app1/docker-compose.yml
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のservicesmysqlを追加します。

shared/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"

  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コンテナにつながるようにします。

app1/docker-compose.yml
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で公開しています。

RELATED POSTS