dockerでnginx導入と複数サービスの展開


dockerでのnginx導入から外部に複数サービスのを公開するやり方のメモです。
ローカル、サーバ(VPS)ともにUbuntuです。

ローカルで起動

ひとまず何も設定せずnginxコンテナを作成して起動できることを確認します。
以下のように記述してdocker-compose up -dします。

docker-compose.yml
version: "2"
services:
  app:
    image: nginx:latest
    container_name: "app"
    ports:
      - "8080:80"

ローカルの開発環境で動かしていますが、ブラウザでlocalhost:8080にアクセスしてみると初期ページが表示されました。

独自ページを表示

デフォルトの設定では初期ページが表示されるようになっているので、別途confファイルを用意して指定したHTMLファイルが表示されるように設定します。
この設定ファイルは/etc/nginx/nginx.conf.d以下に置くことで機能します。

default.conf
server {
    listen       80;
    listen       443;
    server_name  localhost;

    location / {
        root   /app;
        index  index.html index.htm;
    }

    error_page  404 /404.html;
    location = /40x.html {
        root   /usr/share/nginx/html;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

表示用のHTMLファイルを用意します。

index.html
<html>
    <body>
        This is index.html.<br />
        <a href="pageA.html">next</a>
    </body>
</html>
pageA.html
<html>
    <body>
        This is pageA.html.
    </body>
</html>

docker-compose.ymlvolumesの記述を追加して、上記の設定ファイルとHTMLファイルをマウントします。
先述したとおり、設定ファイルは/etc/nginx/nginx.conf.d以下に配置されるようします。
HTMLファイルについては、deafult.confで指定したrootのパスに置いてください。

docker-compose.yml
version: "3"
services:
  app:
    image: nginx:latest
    container_name: "app"
    ports:
      - "8080:80"
    volumes:
      - ./nginx/html:/app
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf 

これでlocalhost:8080にアクセスすると先程のHTMLが表示されるようになりました。


ドメインの取得

外部公開に向けて事前にどこかのレジストラでドメインの取得をしておきます。
それぞれの紐付けとして以下の作業が必要ですが、利用サービスによって違うので各々のマニュアルを参照してください。

  • ドメインにVPSのネームサーバを設定
  • VPS側でドメイン、DNSレコードの登録

リバースプロキシとSSL

今回は1つのサーバ上で複数サービスを稼働させる想定をしています。
構成としてはリバースプロキシを用意してアクセスしてきたリクエストを各々のDockerコンテナに振り分けます。
また例のごとくLet's Encryptで無料のSSL対応も行います。

詳しくはここらへんのサイトを参考していただければ分かると思うのですが、要点だけ書きます。

サブドメインの設定

事前にVPS側でDNSレコードを追加しサブドメインの設定を行います。
Aレコードの設定でいけると思います。
これもVPS等のマニュアルに書いてあると思います。

ポート開放

ポートの80443が開いていない場合、開放しておきます。

iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables-save > /etc/iptables.rules

リバースプロキシの設定

Dockerで行うに当たって以下の2つのイメージを使うことで実現します。
前者がnginxのリバースプロキシで、後者はLet's EncyptのSSLサーバ証明書を自動取得・更新してくれるものです。
SSLサーバ証明書は手動でもできますがせっかくなので楽なやり方を採用します。

以下のようなdocker-compose.ymlになりました。
基本的には公式のGitHubに書いてある通りです。

なおvolumes_fromを使っていますが、version: "3"から廃止されたそうなので、version: "2"にしておかないと動きません。(今後のことを考えると何らかの対策が必要ですが、今回は試験的な試みなのでスルーします・・・)

補足(2019/09/08)
最近思ったのですがリバースプロキシをするnginxはわざわざDockerコンテナである必要はないかもしれません。
この時は複数のサブドメインの証明書の取得するのが面倒で全部面倒見てくれる上記のコンテナ2つを使いました。しかしワイルドカード証明書を使えば証明書は1組で済むし自動更新はcertbotやlegoを使えばそこまで手間ではないです。
またリバースプロキシのためにDockerネットワークの記述をそれぞれのdocker-compose.ymlに記述するのは本質的ではない気がするし外部のライブラリにがっつり依存するのもどうかなという思いがあります(分かっていて使うのであれば問題ない)。
そういうわけで今自分がやるのであればサブドメインに割り当てるサービスは独立したコンテナとして立ち上げてリバースプロキシはホスト側にnginxを建てて127.0.0.1:{PORT}を見に行くのが良いかなという感じです。

proxy/docker-compose.yml
version: "2"
services:
  nginx-proxy:
    image: jwilder/nginx-proxy
    container_name: nginx-proxy
    privileged: true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /srv/docker/nginx-proxy-with-encrypt/certs:/etc/nginx/certs:ro
      - /etc/nginx/vhost.d
      - /usr/share/nginx/html
      - /var/run/docker.sock:/tmp/docker.sock:ro
    restart: always

  letsencrypt-nginx:
    image: jrcs/letsencrypt-nginx-proxy-companion
    container_name: letsencrypt-nginx
    privileged: true
    volumes:
      - /srv/docker/nginx-proxy-with-encrypt/certs:/etc/nginx/certs:rw
      - /var/run/docker.sock:/var/run/docker.sock:ro
    volumes_from:
      - nginx-proxy
    restart: always

networks:
  default:
    external:
      name: shared

Docker内で通信に使うネットワークを作成します。上で設定しているnetworksで使います。

$ docker network create --driver bridge shared

アプリ作成

振り分け先としてサイトAとサイトB用のdocker-compose.ymlを用意します。

サイトA

ベースは一番最初に作成したdocker-compose.ymlです。

app1/docker-compose.yml
version: "2"
services:
  app1:
    image: nginx:latest
    container_name: "app1"
    environment:    
        VIRTUAL_HOST: app1.example.com
        LETSENCRYPT_HOST: app1.example.com
        LETSENCRYPT_EMAIL: [email protected]
    restart: always
    volumes:
      - ./app/html:/app
      - ./app/default.conf:/etc/nginx/conf.d/default.conf

networks:
  default:
    external:
      name: shared

portの設定がなくなり、代わりにVIRTUAL_HOST, LETSENCRYPT_HOST, LETSENCRYPT_EMAILを追加しました。
VIRTUAL_HOSTには用意したサブドメインを設定します。
LETSENCRYPT_HOST, LETSENCRYPT_EMAILにはLet's EncryptでSSLを取得する時に使用する情報です。
またnetworksも上で作ったものを設定して通信を疎通させます。

サイトB

ほぼ同様です。app1の部分をapp2に変えています。

app2/docker-compose.yml
version: "2"
services:
  app1:
    image: nginx:latest
    container_name: "app2"
    environment:    
        VIRTUAL_HOST: app2.example.com
        LETSENCRYPT_HOST: app2.example.com
        LETSENCRYPT_EMAIL: [email protected]
    restart: always
    volumes:
      - ./app/html:/app
      - ./app/default.conf:/etc/nginx/conf.d/default.conf

networks:
  default:
    external:
      name: shared

起動してみる

順番にdocker-compose up -dして起動していきます。
volumeの設定で/var/run/docker.sockをマウントすることによって、Dockerコンテナの起動/終了等を検知して自動的に振り分けの設定を行ってくれます。具体的にはnginxのdefault.confを都度書き換えているようです。

またSSLサーバ証明書の取得に多少時間がかかるので少し待ったほうがいいと思います。

cd proxy
$ docker-compose up -d
cd app1
$ docker-compose up -d
cd app2
$ docker-compose up -d

さて、これでapp1.example.comなどでアクセスし、ページが振り分けられていれば成功です。
またhttps通信が成功していればリダイレクトされ、ブラウザの保護されていない通信という表示も消えていると思います(Chromeの場合)。

その他、困ったこと

  • カーネルのバージョン
    • 3.1以上じゃないとdockerが動かない(uname -rで確認)
    • 古いバージョンだった場合に利用しているVPSがOpenVZ方式だったら利用者側でアップデートできないので詰み。
  • dockerおよびdocker-composeは公式サイトに従って入れる。
    • apt-getしたら古かったので動かなかった。
  • sudo: unable to resolve host hostnameで怒られた。
    • /etc/hosts127.0.1.1 hostnameを追加で消える。