[Docker] Webアプリをリバースプロキシで束ねる

  • このエントリーをはてなブックマークに追加

下記の図のように、ウェブブラウザからのアクセスを一つのNginxコンテナで待ち受け、各種アプリにリバースプロキシする構成を組み立てる例を紹介します。

PlantUML Syntax:<br />
node “Docker Host” {<br />
    folder “App Host” {<br />
        [Nginx:443]<br />
    }</p>
<p>    folder “App1 Container” {<br />
        [Nginx:80] — (App1)<br />
    }</p>
<p>    folder “App2 Container” {<br />
        [Node.js:3000] — (App2)<br />
    }</p>
<p>    folder “App3 Container” {<br />
        [Apache:80] — (App3)<br />
    }</p>
<p>    [Nginx:443] —> [Nginx:80]<br />
    [Nginx:443] —> [Node.js:3000]<br />
    [Nginx:443] —> [Apache:80]<br />
}</p>
<p>Client —> [Nginx:443]<br />


利点

  • 外部公開する設定を一つのコンテナにまとめられ、アプリコンテナはデフォルトのまま使える
  • すべてのサービスを強制的にHTTPSにすることが容易
  • Let’s EncryptのSSL証明書取得・更新が楽(ホストnginxの80番を常に固定パスに流せばいい)

やりかた

OSはCentOS7、Let’s Encryptのcertbotはインストールしてある前提です。
例としてRedmineのコンテナを作ることにします。

appsネットワーク作成

ホストコンテナからアプリコンテナへ橋渡しをするためのネットワークを作成します。

# docker network create --driver=bridge apps

ホストコンテナ作成

Nginx公式のイメージから作るためのdocker-compose.ymlを書きます。
networksには先ほど作成したappsを記入します。

version: '2'

services:
  nginx:
    image: nginx:alpine
    container_name: apps-host
    ports:
      - 80:80 # SSL証明書の取得時に使用
      - 443:443
    volumes:
      - ./config/conf.d:/etc/nginx/conf.d:ro # 設定が書き換えがしやすいように
      - /etc/letsencrypt:/etc/letsencrypt:ro # 取得したSSL証明書を読み込むため
      - ./data:/data:ro                      # SSL証明書の取得時に使用
    networks:
      - apps
    restart: unless-stopped

networks:
    apps:
      external: true

conf.dディレクトリのdefailt.confを下記のような感じで書きます。
ほぼSSL証明書取得のための記述だけです。

server {
        listen 80 default_server;

        # 証明書発行時は必ずここにアクセスが来るので、限定的に許可
        location ^~ /.well-known/acme-challenge/ {
                root /data/webroot;
        }

        # 上記以外はすべてHTTPSへリダイレクト
        location / {
                return 301 https://$host$request_uri;
        }
}

SSL証明書の取得

アプリの準備をする前にSSL証明書を取得します。
まずはホストコンテナを起動しましょう。

# docker-compose up -d

あとは下記のコマンドで一発です。/path/toは先にバインドしたディレクトリへのパスです。

# certbot certonly --webroot -w /path/to/data/webroot -d redmine.example.com

アプリコンテナ作成

同じくdocker-compose.ymlを書きます。こちらもappsのネットワークにリンクさせます。

version: '2'

services:
    app:
        image: redmine
        container_name: redmine # ホスト名を参照するために必ず書きます
        restart: unless-stopped
        expose:
            - 3000 # portsではなくexpose
        networks:
            - apps

networks:
    apps:
        external: true

アプリ用のserverディレクティブを書く

先ほどvolumesで指定したconf.d直下にredmine.confを作成します。
ここで、proxy_passredmineを直接指定すると、ホストコンテナより先にRedmineのコンテナが起動していないと、ホスト名解決ができなくなってしまいます。
つまりは複数のアプリコンテナを用意した場合に、すべて先に起動する必要がでてきてしまいます。
これを回避するために、リゾルバを指定して動的にホスト名を解決するようにします。

server {
        listen 443;
        server_name redmine.example.com;

    # SSL設定
        ssl                     on;
        ssl_certificate         /etc/letsencrypt/live/redmine.example.com/fullchain.pem;
        ssl_certificate_key     /etc/letsencrypt/live/redmine.example.com/privkey.pem;

        ssl_session_timeout     10m;
        ssl_protocols           TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers             ECDHE+RSAGCM:ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:!EXPORT:!DES:!3DES:!MD5:!DSS;

        ignore_invalid_headers off;

        location / {
                resolver 127.0.0.11 valid=2s;      # ここでリゾルバ指定。アドレスが違う場合もあるかも
                set $proxy_pass_host redmine:3000; # 動的に指定するには一旦変数に入れないといけない 
                proxy_pass http://$proxy_pass_host;

                # この辺はリバースプロキシのお約束
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
}

あとはホストコンテナの再起動をして、アプリコンテナを立ち上げて完了です。

# docker-compose restart
# docker-compose up -d
  • このエントリーをはてなブックマークに追加

SNSでもご購読できます。




コメントを残す