Docker + Nginx + Let's EncryptでHTTPS対応のプロキシサーバーを構築する

Docker上にNginxコンテナをプロキシサーバーとして構築し、Let's EncryptでHTTPS対応しました。構築にあたって **かなり苦戦した** ので、そのノウハウを記事としてまとめました。

## 「Nginx」とは

Apacheなどの従来のWebサーバーは、クライアントの数が多くなるとサーバーがパンクする **「C10K問題（クライアント1万台問題）」** を抱えていました。「Nginx」はこの問題を解決するために誕生した、静的コンテンツを高速配信するWebサーバーです。2017年10月現在、そのシェアは **Apacheとほぼ同等** となっています。

![wpid-wss-share13.png](https://qiita-image-store.s3.amazonaws.com/0/155486/19428ede-e7ce-63b6-5874-ed252db42a5d.png)

|Webサーバー|シェア|
|---|---|
|Micosoft IIS|49.44%|
|Apache|18.78%|
| **Nginx** | **18.40%** |

---

- [これから始める人のためのNginx（1）：高速・軽量・高機能……Nginxの基礎知識 - ＠IT](http://www.atmarkit.co.jp/ait/spv/1406/17/news013.html)
- [Web2.0の先にあるC10K問題 － ＠IT](http://www.atmarkit.co.jp/news/analysis/200701/09/c10k.html)
- [October 2017 Web Server Survey | Netcraft](https://news.netcraft.com/archives/2017/10/26/october-2017-web-server-survey-13.html)

## 「Let's Encrypt」とは

「Let's Encrypt」は **すべてのWebサーバへの接続を暗号化する** ことを目指し、SSL/TLSサーバ証明書を **無料** で発行する認証局（CA）です。シスコ、Akamai、電子フロンティア財団、モジラ財団などの大手企業・団体がスポンサーとして支援しています。

---

- [Let's Encrypt 総合ポータル](https://letsencrypt.jp/)
- [Let's Encrypt - Wikipedia](https://ja.wikipedia.org/wiki/Let%27s_Encrypt)

## 本稿が目指すシステム構成

本稿ではAmazon EC2、Dockerコンテナを使用して以下のようなシステムを構築することを目標とします。

![DockerでNgixのプロキシサーバーを構築する.png](https://qiita-image-store.s3.amazonaws.com/0/155486/77473e47-8101-d518-f374-879f41e4e1a8.png)


## 前提条件

- 独自ドメインを取得していること（本稿で使用するドメインは`example.com`とします）
- IPv4パブリックIP（Elastic IP）がEC2インスタンスに設定されていること
- EC2インスタンスにDocker、docker-composeがインストールされていること

## 事前に準備すること

DockerでHTTPS対応のプロキシサーバーを構築するにあたり、事前に以下の設定をしておく必要があります。

- EC2のインバウンドルールで443ポートを開放する
- DNSのAレコードを設定する
- プロキシ用のネットワークを構築する

### EC2のインバウンドルールで443ポートを開放する

インバウンドルールを以下のように設定し、443ポートを外部へ公開します。

|タイプ|プロトコル|ポート範囲|ソース|
|---|---|---|---|
|HTTPS|TCP|443|0.0.0.0/0|
|HTTPS|TCP|443|::/0|

---
- [セキュリティグループのルールのリファレンス - Amazon Elastic Compute Cloud](http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/WindowsGuide/security-group-rules-reference.html)

### DNSのAレコードを設定する

DNSの設定方法は利用しているドメイン取得サービスによって異なります。例えばバリュードメインの場合、DNSの設定方法は「[DNS情報・URL転送の設定 | VALUE-DOMAIN ユーザーガイド](https://www.value-domain.com/userguide/manual/moddns/)」に記載されています。

DNSのAレコードを以下のように設定します。`xx.xx.xx.xx`にはEC2インスタンスに割り当てられているIPv4パブリックIPを設定します。

```
a @ xx.xx.xx.xx
a www xx.xx.xx.xx
```

上記設定は以下を意味します。

- example.com（サブドメイン無し）をIPアドレス`xx.xx.xx.xx`にポイントする
- www.example.com をIPアドレス`xx.xx.xx.xx`にポイントする

### プロキシ用のネットワークを構築する

プロキシサーバーとWebサーバー間のネットワークは外部との通信を行う必要がありません。そこで
プロキシサーバーとWebサーバー間の **内部ネットワーク** を構築するため、EC2のインスタンスにログインし、以下のコマンドを入力します。

```
$ docker network create --internal sample_proxy_nw
```

上記コマンドは以下を意味します。

- `--internal`: ネットワーク外との通信が行えないネットワークを作成します。
- `sample_proxy_nw`: 任意のネットワーク名です。

以下のコマンドを入力し、ネットワークの設定情報がコンソールに出力されていることを確認しましょう。

```
$ docker network inspect sample_proxy_nw
```

---

- [network create — Docker-docs-ja 1.12.RC2 ドキュメント](http://docs.docker.jp/v1.12/engine/reference/commandline/network_create.html)

## Dockerコンテナの定義ファイルを作成する

事前準備が完了したら、Dockerコンテナの定義ファイルを作成しましょう。本稿におけるディレクトリ構成は以下のとおりです。

**/path/to/dir/**

```
.
├── docker-compose.yml // プロキシサーバーとWebサーバーのコンテナを定義するファイル
└── proxy
    ├── default.conf // プロキシサーバー上にあるNginxのデフォルト定義ファイル
    ├── Dockerfile // プロキシサーバーのイメージを構築するためのファイル
    └── entrypoint.sh // プロキシサーバーにSSL証明書を取得するためのファイル
```

以下では、各ファイルの内容を解説します。

### ./docker-compose.yml

docker-compose.ymlでは、以下のコンテナを定義しています。

- proxy: プロキシサーバー（Nginxベース）
- web1: Webサーバー（httpdベース）
- web2: Webサーバー（httpdベース）

```
version: '3'
services:
  proxy:
    build: ./proxy
    tty: true
    image: sample_proxy
    container_name: sample_proxy
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    ports:
      - "443:443"
    volumes:
      - '/srv/letsencrypt:/etc/letsencrypt'
    networks:
      - default
      - sample_proxy_nw
    depends_on:
      - "web1"
      - "web2"
    command: ["wait-for-it.sh", "sample_web1:80", "--", "./wait-for-it.sh", "sample_web2:80"]
  web1:
    image: httpd
    container_name: sample_web1
    tty: true
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    networks:
      - sample_proxy_nw
  web2:
    image: httpd
    container_name: sample_web2
    tty: true
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    networks:
      - sample_proxy_nw
networks:
  proxy_nw:
    external: true
```

上記コマンドは以下を意味します。

- サービス`proxy`の`ports`: 外部からのHTTPSアクセスとproxyサーバーの内部ポートを疎通させるため、`443:443`を定義します。
- サービス`proxy`の`volumes`: `/srv/letsencrypt:/etc/letsencrypt`を定義します。`/etc/letsencrypt`は **Let's Encryptで取得した証明書が生成されるディレクトリ** です。
- `networks`: 上述の説明で生成した`sample_proxy_nw`を各サービス（proxy, web1, web2）に定義します。
-  `depends_on`: コンテナの起動順序を制御するオプションです。 **Nginxのproxy_passに設定されているWebサーバーが起動していない状態でプロキシサーバーが起動した場合にエラーとなる** ため、web1, web2を設定します。

---

- [wait-for-it/README.md at master · vishnubob/wait-for-it · GitHub](https://github.com/vishnubob/wait-for-it/blob/master/README.md)
- [Control startup order in Compose | Docker Documentation](https://docs.docker.com/compose/startup-order/)

### ./proxy/default.conf

`./proxy/default.conf`はNginxのデフォルト定義ファイル（`/etc/nginx/conf.d/default.conf`）を書き換えるためのファイルです。

```
server{

    server_name example.com www.example.com;

    proxy_redirect off;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    location / {
        proxy_pass    http://sample_web1/;
    }

    location /example/ {
        proxy_pass    http://sample_web2/;
    }

}
```

上記設定は以下を意味します。

- `server_name`: ユーザーから要求されるHTTPリクエストのヘッダに含まれるHostフィールドと`server_name`が一致した場合、該当するサーバ設定を採用します。Nginxではキャッチオールサーバーとして`_`を定義することもできますが、 **certbot-autoがサーバー情報を正しく取得することができない** ため、上記のようにドメイン名を入力します。
- `location`: ルートディレクトリ（example.com/）とサブディレクトリ（example.com/example/）にアクセスした際の振り分け先URIを設定します。`proxy_pass`には、`http://[コンテナ名]/`を設定します。コンテナ名はdocker-compose.ymlの`container_name`で設定した名前となります。  
また、`http://sample_web1/`のように **末尾に`/`を入れる** ことに注意しましょう。例えば`location /example/`において、プロキシパスの末尾に`/`が含まれていない（`http://sample_web2`）場合、振り分け先は `http://sample_web2/example/`となってしまいます。  

---

- [Server names | Nginx](http://nginx.org/en/docs/http/server_names.html)
- [nginxのproxy_passの注意点 -- ぺけみさお](https://www.xmisao.com/2014/05/09/nginx-proxy-pass.html)

### ./proxy/Dockerfile

```
FROM nginx
COPY default.conf /etc/nginx/conf.d/default.conf
RUN apt-get update && apt-get install -y \
        wget && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
ADD https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh /usr/local/bin/wait-for-it.sh
RUN chmod +x /usr/local/bin/wait-for-it.sh
ADD https://dl.eff.org/certbot-auto /usr/local/bin/certbot-auto
RUN chmod a+x /usr/local/bin/certbot-auto
RUN certbot-auto --os-packages-only -n
COPY ./entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
```

上記設定は以下を意味します。

- `ADD https://dl.eff.org/certbot-auto /usr/local/bin/certbot-auto`: Let's Encryptが発行するSSL/TLSサーバ証明書を自動で取得・更新するツール「 **certbot-auto** 」をダウンロードします。

---

- [Certbot](https://certbot.eff.org/)

### ./proxy/entrypoint.sh

```
#!/bin/bash
certbot-auto --nginx -d example.com -d www.example.com -m your-account@gmail.com --agree-tos -n
certbot-auto renew
/bin/bash
```

上記設定は以下を意味します。

- `--nginx`: プロキシサーバーにNginxを使用する場合のオプションです。`default.conf`の設定を自動的に書き換えます。（2017年12月現在、アルファ版のプラグイン）
- `-d example.com -d www.example.com`: SSL/TLSサーバ証明書の取得を申請するドメイン名を指定します。
- `-m your-account@gmail.com`: アカウントの登録や回復などに使用する電子メールアドレスを指定します。
- `--agree-tos`: Let's Encryptの利用規約に同意します。
- `-n`: インタラクティブ設定をオフにします。
- `./certbot-auto renew`: **3ヶ月で失効する** SSL/TLSサーバ証明書を自動で更新します。

以下のコマンドを入力してentrypoint.shに **実行権限を付与する** ことを忘れないようにしましょう。

```
$ chmod +x entrypoint.sh
```

---

- [Nginx プラグイン | ユーザーガイド - Let's Encrypt 総合ポータル](https://letsencrypt.jp/docs/using.html#nginx)
- [コマンド解説 - Let's Encrypt 総合ポータル](https://letsencrypt.jp/command/)

## Dockerコンテナを起動する

それでは以下のコマンドを入力してDockerコンテナを起動しましょう。

```
docker-compose up -d
```

しばらく時間をおいてから、以下のコマンドを入力します。

```
docker-compose logs
```

以下のように出力されていれば成功です。

```
-------------------------------------------------------------------------------
Congratulations! You have successfully enabled https://example,com and
https://www.example.com

You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=example.com
https://www.ssllabs.com/ssltest/analyze.html?d=www.example.com
-------------------------------------------------------------------------------
```

## HTTPSでアクセスする

ブラウザを起動し、実際に以下のURLにアクセスしてみましょう。

- https://example.com
- https://example.com/example/
- https://www.example.com
- https://www.example.com/example/

Chromeブラウザの場合は`デベロッパーツール > Security > View certificate`からSSL/TLSサーバ証明書を確認することができます。

「発行元: Let's Encrypt Authority X3」となっているはずです。