Dockerでリバースプロキシ(nginx)構築+SSL対応

# はじめに
この記事は前回の記事の続きです。

前回の記事↓
[自宅鯖のDocker内でWordPressを構築する \- Qiita](https://qiita.com/daemokra/items/d23c0c6eb71ac4a49dbb)

# 記事一覧

- [(1) Dockerとは + Dockerインストール](https://qiita.com/daemokra/items/b5a628d92bbfe0c313c7) 
- [(2) Dockerで"WordPress+Apache+PHP" + "MySQL"を立ち上げる](https://qiita.com/daemokra/items/d23c0c6eb71ac4a49dbb)
- **▶(3) Dockerでリバースプロキシサーバー(nginx)＋SSL🐳＜今ココ**


# 対象とする読者
- 今までの記事を見た方
- リバースプロキシの知識が既にある方

# 前提
- SSL証明書を取得しておいてください
  - LetsEncryptを使用しています。
  - `/etc/letsencrypt`に証明書があることを前提に進めます。

```
/etc/letsencrypt
         ├── archive
         └── live # ←ここに最新の証明書が入ってます。

```



# 環境
- 自宅サーバー
  - OS: Ubuntu 20.04.2 LTS
- Docker
  - verison: 20.10.5, build 55c4c88
- Docker Compose
  - version: 1.28.5, build c4eb3a1
  - ファイルフォーマット: 3.9


# 本題

今回はnginxを使ってリバースプロキシを構築します。
作業自体は簡単です。

# 事前準備
リバースプロキシでフォワーディングする先の各種サーバーを構築し、Dockerの仮想ネットワークを作成しておいてください。
今回は`my_network`の仮想ネットワークに接続させました。




# ネットワーク図

- http://example.com にアクセスした場合
![概念図_nginx-Network.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1180204/43ab4491-123d-99eb-9069-b556def9e704.png)
- http://blog.example.com にアクセスした場合
![概念図_nginx-NetworkBlog.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1180204/c2705765-e088-a77c-4735-65778bdafcc9.png)




# ディレクトリ構造

WordPress側とリバースプロキシ側で`docker-compose.yml`を分けます。
**※この構成を推奨してるわけではありません。ご自身の環境にあった構成にしてください。**
**　Composeファイルをひとつにしても全く問題ありません。**
**　むしろ分けないほうが良いかもしれません。**

![概念図_nginx-Directory.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1180204/52e9ef40-e4d3-a7d6-2b9b-c7fc72441c72.png)


# docker-compose.yml

```yaml:docker-compose.yml
version: "3.9"

services:

  proxy:
    image: nginx:latest                             # 2020-03-25 latest=1.19.8
    volumes:
      - ./templates:/etc/nginx/templates            # nginxのconfファイルのテンプレート
      - ./conf/certpath.txt:/etc/nginx/certpath.txt # nginxでincludeさせるSSL関係の設定
      - /etc/letsencrypt:/etc/nginx/letsencrypt     # Lets Encryptの署名+秘密鍵
    restart: always
    ports:
      - "80:80"
      - "443:443"
    env_file:
      - ./conf/conf.env
    networks:
      - my_network

networks:
  my_network:
   name: my_network
```

各種オプションの説明は[こちら](https://qiita.com/daemokra/items/d23c0c6eb71ac4a49dbb#docker-composeyml%E3%81%AE%E4%BD%9C%E6%88%90)に書いてますので参考にしてください。

`templates`について説明します。

## templatesについて

[Docker Hub | nginx](https://hub.docker.com/_/nginx)
nginxの公式イメージ(ver.1.19.8)はテンプレートという便利機能がついてます。

訳：
>**nginxコンフィグでの環境変数の使用 (1.19からの新機能)**
>
>素のnginxだとコンフィグ内の環境変数をサポートしていませんが、このイメージにはnginxが起動する前に環境変数を抽出する関数があります。
>例のdocker-compose.ymlを示します。
>
>```yaml
>web:
>  image: nginx
>  volumes:
>   - ./templates:/etc/nginx/templates
>  ports:
>   - "8080:80"
>  environment:
>   - NGINX_HOST=foobar.com
>   - NGINX_PORT=80
>```
>
>デフォルトでは、この関数は`/etc/nginx/templates/*.template`のテンプレートファイルを読み込み、`envsubst`の実行結果を`/etc/nginx/conf.d`に出力します。
>
>
>`templates/default.conf.template`に環境変数を参照したファイルを置くと次のようになります。
>
>```
>listen       ${NGINX_PORT};
>```
>
>出力結果`/etc/nginx/conf.d/default.conf`の中身↓
>
>```
>listen       80;
>```
>

要するに`*.template`拡張子で保存してあげれば環境変数読み取っていい感じにconf.dファイルに変換してくれます。


今回は`./conf/conf.env`ファイルに環境変数を書き込んでそれを読ませてあげます。

```yaml:docker-compose.yml(抜粋)
    env_file:
      - ./conf/conf.env
```

```:./conf/conf.env
NGINX_HOST=example.com
NGINX_PORT=443 ssl
NGINX_WP_HOST=blog.example.com
NGINX_WP_PORT=443 ssl
```


テンプレートファイルは次のとおりです。最小構成です。

```nginx:./templates/default.conf.template
# HTTPをHTTPに飛ばす設定
server {
    listen 80;
    server_name ${NGINX_HOST};
    rewrite ^(.*)$ https://$host$1 permanent;
}


############## リバースプロキシ本体 ###################
server {
    listen       ${NGINX_PORT};
    server_name  ${NGINX_HOST};

    # アクセスログ 全部取る
    access_log  /var/log/nginx/host.access.log  main;

    # SSL設定
    include /etc/nginx/certpath.txt;

    # ロケーション設定
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }


}
```

```nginx:./templates/wordpress.conf.template
server {
    listen       ${NGINX_WP_PORT};
    server_name  ${NGINX_WP_HOST};

    access_log  /var/log/nginx/host.access.log  main;

    location / {
        proxy_pass http://wordpress;  # コンテナ名を指定
        proxy_redirect   off;
        proxy_set_header Host               $host;
        proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host   $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Real-IP          $remote_addr;
        proxy_set_header X-Forwarded-Proto  https;
    }
}
```

`wordpress.conf.template`の

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

の記述は、これがないとWordPressのCSSが崩れるので必要です。
申し訳ないです…設定の意味は理解できていません…。
気が向いたら調べて追記します。

# SSLについて
- `./conf/certpath.txt`にSSLの設定を書いてます。
いずれ変わることもあるかなと思って別ファイルに書きました。

- 2021年3月時点ですとTLS1,TLS1.1をサポートしてるとよろしくないようなので無効にしてます。
TLS1.2の接続のみだけ許可しています。
この設定であればSSLのテストをしてくれるサイト↓で「A」は取れると思います。（TLS1.2のみだとBになる）
https://www.ssllabs.com/ssltest/


- 証明書をコンテナ内に配置する方法ですが、「コンテナ内で取得」か「ホストで取得してコンテナにマウント」で迷いました。Lets EncryptのイメージもDocker Hubにあるのでそれを使おうかとも思いました。
結局「ホストで取得してコンテナにマウント」で対応しています。こっちのほうが簡単そうなので。あと、コンテナ技術的にたぶん証明書はコンテナ外管理のほうがいいですよね。
ただ、Lets Encryptの最新証明書が入ってる`live`フォルダって中身が`archive`フォルダへのシンボリックリンクなんですよね。`live`フォルダだけをマウントさせるのは無理っぽいので`/etc/letsencrypt`ごとマウントさせてます。
証明書以外の余計なものまでマウントさせてますがまあしょうがないです…。



```:./conf/certpath.txt
# SSL証明書の場所 (実際にある場所を入れてください。)
ssl_certificate     /etc/nginx/letsencrypt/live/<domain_name>/fullchain.pem;
ssl_certificate_key /etc/nginx/letsencrypt/live/<domain_name>/privkey.pem;


# ssl_prefer_server_ciphersをonに指定すると、利用する暗号を選ぶ際に、
# SSLv3やTLSではサーバが示した暗号スイートが優先されます。
# offにするとクライアントのものが優先されます。デフォルトはoffです。
# これはnginxの機能というよりはOpenSSLの機能で、
# SSL_CTX_set_options()にSSL_OP_CIPHER_SERVER_PREFERENCEを設定します。
# https://heartbeats.jp/hbblog/2012/06/nginx06.html
ssl_prefer_server_ciphers on;


# サーバー側で有効にするプロトコル
ssl_protocols TLSv1.2;


# サーバー側で有効にする暗号
# 参考：https://rms.ne.jp/sslserver/basis/forward_secrecy_apache_ngix/
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4";

```


# 実行
あとは`docker-compose up -d`すればOKです。
example.comでnginxのデフォルトページ、blog.example.comでWordPressのページにつながるはずです。



# トラブルシューティング
## nginxが起動できない
コンフィグファイルの構文ミスである場合が多いです。設定を見直してみてください。

もしくは、nginxは起動時にコンフィグファイルを評価し、名前解決を行います。
先にWordPressサーバーを起動しておかないと名前解決できません。先にWordPressを起動させてください。

この問題についての具体的な解決方法は出せていません…。
そもそもリバースプロキシと他のWEBサイトをCopmoseで分けるという発想が良くないのかもしれないです。
運営してみて構成を試行錯誤してみます。

## 意図しないページに飛ばされる
`proxy_pass https://wordpress:443`という記述にしていないですか？
私はこの記述の仕方でうまくいきませんでした。ポート番号外したら繋がるようになりました。


# 最後に
なんとかDockerでサイト構築することが出来ました。
今後もコンテナでサービス増やしていけたらいいなと思います。
フォルダ構成やComposeについては色々試行する余地がありそうです…。



Twitterもよかったらフォローしてください😊
https://twitter.com/daemokra

以上です。

# 参考にさせてもらったサイト
- [linux \- Letsencrypt \+ Docker \- the best way to handle symlink? \- Stack Overflow](https://stackoverflow.com/questions/47791396/letsencrypt-docker-the-best-way-to-handle-symlink)
- [Reverse Proxy SSL証明書の集中管理 \| アラコキからの Raspberry Pi 電子工作](https://arakoki70.com/?p=5765)
- [SSLに対応したNGINXリバースプロキシを構築する手順 \- Qiita](https://qiita.com/tnoce/items/ded6d3d298da5972ab63)
- [Let's Encrypt で Nginx にSSLを設定する \- Qiita](https://qiita.com/HeRo/items/f9eb8d8a08d4d5b63ee9)
- [Webサーバー nginx における SSL証明書設定の安全性向上 ～SSL Server Test で A\+ 判定を目指して～ \| SaintSouth\.NET](https://www.saintsouth.net/blog/safety-of-ssl-certificate-setting-improvements-in-web-server-nginx-to-get-rankaplus-from-ssl-server-test/)
- [nginx連載6回目: nginxの設定、その4 \- TLS/SSLの設定 \- インフラエンジニアway \- Powered by HEARTBEATS](https://heartbeats.jp/hbblog/2012/06/nginx06.html)