【docker-compose】nginxでリバースプロキシを実装する

# 背景
[これ](https://qiita.com/not75743/items/2358229fc6f83718e7bf)の続きとして、docker-registryへhttpsで接続できるようにしたい。
nginxでリバースプロキシを構築して上記を実現する予定ですが、そもそもリバースプロキシを使ったことがないため、実際に導入してイメージをつかんでみます。
docker-composeを使います。

# 環境
- AWSのEC2
- 使ったAMIは**amzn2-ami-hvm-2.0.20210126.0-x86_64-gp2**

# dockerバージョン
```bash
$ docker --version
Docker version 20.10.4, build d3cb89e
$ docker-compose --version
docker-compose version 1.27.4, build 40524192
```

# 構成図
![docker-compose_reverseproxy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1464314/7c431587-718a-7deb-cc81-65ac60950351.png)


# 前提
- https接続のための証明書が取得済みであること。  
  本環境ではletsencryptで発行したSSL証明書を使います。
- docker,docker-composeが使えること

# フォルダ構成
```bash:フォルダ構成
.
|-- cert
|   |-- fullchain.pem # サーバ証明書
|   `-- privkey.pem # 秘密鍵
|-- docker-compose.yaml
|-- reverseproxy
|   `-- nginx.conf
|-- web1
|   `-- index.html
|-- web2
|   `-- index.html
`-- web3
    `-- index.html
```
webサーバのindex.htmlは↓です。

```bash:各フォルダのindex.html
$ cat web{1..3}/index.html
<h1>this is web1</h1>
<h1>this is web2</h1>
<h1>this is web3</h1>
```
# docker-compose.yaml
以下を使用しました

```yaml:docker-compose.yaml
version: "3.9"
services:
  reverse-proxy:
    image: nginx:1.20
    extra_hosts:
      - "host.docker.internal:host-gateway" # 注1
    ports:
      - 5043:443
    volumes:
      - ./cert/fullchain.pem:/etc/nginx/conf.d/fullchain.pem:ro
      - ./cert/privkey.pem:/etc/nginx/conf.d/privkey.pem:ro
      - ./reverseproxy/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./reverseproxy/.htpasswd:/etc/nginx/.htpasswd:ro
  web1:
    image: nginx:1.20
    extra_hosts:
      - "host.docker.internal:host-gateway"　# 注1
    ports:
      - 6043:80
    volumes:
      - ./web1/index.html:/usr/share/nginx/html/index.html
  web2:
    image: nginx:1.20
    extra_hosts:
      - "host.docker.internal:host-gateway" # 注1
    ports:
      - 7043:80
    volumes:
      - ./web2/index.html:/usr/share/nginx/html/index.html
  web3:
    image: nginx:1.20
    extra_hosts:
      - "host.docker.internal:host-gateway" # 注1
    ports:
      - 8043:80
    volumes:
      - ./web3/index.html:/usr/share/nginx/html/index.html
```

# nginx.conf
リバースプロキシとして動作させるnginxのconfファイルは以下です。

```bash:nginx.conf
events { # 注2
    worker_connections  1024;
}

http {
  server {
      listen 443 ;
      ssl on ; # 注3
      ssl_protocols TLSv1.3; # 注4
      ssl_certificate /etc/nginx/conf.d/fullchain.pem;
      ssl_certificate_key /etc/nginx/conf.d/privkey.pem;
      server_name  localhost;

      location /web1 {
          proxy_pass http://host.docker.internal:6043/; # 注1
          proxy_set_header Host $host;
          proxy_set_header X-Forwarded-for $remote_addr;
          auth_basic_user_file /etc/nginx/.htpasswd; # 注5
          auth_basic "basic ninnsyou";
    }
      location /web2 {
          proxy_pass http://host.docker.internal:7043/; # 注1
          proxy_set_header Host $host;
          proxy_set_header X-Forwarded-for $remote_addr;
          auth_basic_user_file /etc/nginx/.htpasswd;
          auth_basic "basic ninnsyou";
    }
      location /web3 {
          proxy_pass http://host.docker.internal:8043/; # 注1
          proxy_set_header Host $host;
          proxy_set_header X-Forwarded-for $remote_addr;
          auth_basic_user_file /etc/nginx/.htpasswd;
          auth_basic "basic ninnsyou";
    }
      error_page   500 502 503 504  /50x.html;
      location = /50x.html {
          root   /usr/share/nginx/html;
      }
  }
}
```
注を付けた箇所の説明をします。

### 注1 host.docker.internal について
[ここ](https://qiita.com/skobaken/items/03a8b9d0e443745862ac)を参考にさせていただきました。

```bash
extra_hosts:
  - "host.docker.internal:host-gateway"
```
とすることで、コンテナからホストへのアクセスを実現しています。　　

extra_hostsはドメインとIPアドレスに紐づけを行います。/etc/hostsのようなものです。
host.docker.internal はホストにアクセスするための特別なドメインであり、host-gatewayを選択すればホストにアクセスできる様です。
[githubのページ](https://github.com/moby/moby/pull/40007)から手順の参考にしましたが、公式のドキュメントには現時点でlinux向けの記載はなさそうです。
(docker for Windows , docker for MACについては記載があります)

### 注1 localhostの指定はOK？
localhost:6043と指定してもうまくいきません。
リバースプロキシコンテナのlocalhost:6043に転送され、ホストの6043に転送されるわけではないからです。
上記のhost.docker.internalや、ホストのIPアドレスを指定しましょう。

dockerのネットワーク、DNSについてはいずれまとめて調べてみたいですね。

### 注2 nginx.conf eventsブロックについて
ここの記載がないとリバースプロキシコンテナが立ち上がりません。（そもそもnginxが動作しない）
何らかの値を入れれば問題なく通りそうです。

```bash:events記載なし
$ head -n 3 reverseproxy/nginx.conf
#events {
#    worker_connections  1024;
#}
$ docker-compose up -d
$ docker-compose logs reverse-proxy
...
reverse-proxy_1  | /docker-entrypoint.sh: Configuration complete; ready for start up
reverse-proxy_1  | 2021/05/22 06:01:11 [emerg] 1#1: no "events" section in configuration
reverse-proxy_1  | nginx: [emerg] no "events" section in configuration
$
```
```bash:events記載あり
$ head -n 3 reverseproxy/nginx.conf
events {
    worker_connections  1024;
}
$ docker-compose up -d
$ docker-compose logs reverse-proxy
...
reverse-proxy_1  | /docker-entrypoint.sh: Configuration complete; ready for start up
$
```
知らなかったです。

### 注3 nginx.conf SSLの記載箇所
最初以下の様にしていました。
起動すると非推奨だと怒られました。動作はします。

```bash:nginx.conf
  server {
      listen 443 ;
      ssl on ;
```
```bash
[docker] $ docker-compose logs reverse-proxy
...
reverse-proxy_1  | 2021/05/22 06:18:48 [warn] 1#1: the "ssl" directive is deprecated, use the "listen ... ssl" directive instead in /etc/nginx/nginx.conf:8
reverse-proxy_1  | nginx: [warn] the "ssl" directive is deprecated, use the "listen ... ssl" directive instead in /etc/nginx/nginx.conf:8
reverse-proxy_1  | /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
reverse-proxy_1  | /docker-entrypoint.sh: Configuration complete; ready for start up
```
過去のバージョンと記法が異なっているのでしょうか。
[ドキュメント](http://nginx.org/en/docs/http/configuring_https_servers.html)にある通り、以下の記載にしましょう

```bash:nginx.conf
server {
    listen 443 ssl;
```

### 注4 TLS1.3に限定する
安全性を考慮してTLS1.3の通信に限定しました。
以下の様に記載したらTLS1.3を使わない通信は弾いてくれます。

```bash:nginx.conf
  server {
      ssl_protocols TLSv1.3;
```
```bash:疎通確認
[test@test-server ~]$ curl --tlsv1.2 -u test:test https://example.test:5043/web3
curl: (35) Peer reports incompatible or unsupported protocol version.
[test@test-server ~]$ curl --tlsv1.3 -u test:test https://example.test:5043/web3
<h1>this is web3</h1>
```

### 注5 nginxにbasic認証を付ける
[ドキュメント](http://nginx.org/en/docs/http/ngx_http_auth_basic_module.html)を参考にしました。
以下の記載で認証を実現しています。htpasswdファイルをあらかじめ用意しておきましょう。

```bash:nginx.conf
      location /web1 {
          auth_basic_user_file /etc/nginx/.htpasswd;
          auth_basic "basic ninnsyou"; 
```

# コンテナ起動、疎通確認
それでは起動してみます。
docker-compose.yamlがあるディレクトリで行います。

```bash
$ docker-compose up -d
Creating network "reverse-proxy_ssl_default" with the default driver
Creating reverse-proxy_ssl_web1_1          ... done
Creating reverse-proxy_ssl_web3_1          ... done
Creating reverse-proxy_ssl_reverse-proxy_1 ... done
Creating reverse-proxy_ssl_web2_1          ... done
$ docker-compose ps
              Name                             Command               State               Ports
----------------------------------------------------------------------------------------------------------
reverse-proxy_ssl_reverse-proxy_1   /docker-entrypoint.sh ngin ...   Up      0.0.0.0:5043->443/tcp, 80/tcp
reverse-proxy_ssl_web1_1            /docker-entrypoint.sh ngin ...   Up      0.0.0.0:6043->80/tcp
reverse-proxy_ssl_web2_1            /docker-entrypoint.sh ngin ...   Up      0.0.0.0:7043->80/tcp
reverse-proxy_ssl_web3_1            /docker-entrypoint.sh ngin ...   Up      0.0.0.0:8043->80/tcp
```
問題なく起動していそうです。別ホストからcurlで確認してみましょう

```bash
$ curl --tlsv1.3 -u test:test https://example.test:5043/web1
<h1>this is web1</h1>
$ curl --tlsv1.3 -u test:test https://example.test:5043/web2
<h1>this is web2</h1>
$ curl --tlsv1.3 -u test:test https://example.test:5043/web3
<h1>this is web3</h1>

# -u basic認証を行う。ユーザとパスワードを指定
```
問題なさそうです。アクセスログも確認してみます。
(見やすくするために出力を並び替えています。)

```bash
$ docker-compose logs | grep curl
web1_1           | 172.27.0.1 - test [22/May/2021:07:14:19 +0000] "GET / HTTP/1.0" 200 22 "-" "curl/7.29.0" "<クライアントIP>"
web2_1           | 172.27.0.1 - test [22/May/2021:07:14:22 +0000] "GET / HTTP/1.0" 200 22 "-" "curl/7.29.0" "<クライアントIP>"
web3_1           | 172.27.0.1 - test [22/May/2021:07:07:12 +0000] "GET / HTTP/1.0" 200 22 "-" "curl/7.29.0" "<クライアントIP>"
reverse-proxy_1  | <クライアントIP> - test [22/May/2021:07:14:23 +0000] "GET /web3 HTTP/1.1" 200 22 "-" "curl/7.29.0"
```
うまくいっていますね！ちなみにアクセス元の172.27.0.1は前述したgatewayです。
確認は以下の様にできますが、説明については別の機会にしたいと思います。

```bash
$ docker network inspect reverse-proxy_ssl_default | jq ".[] | {IPAM}[].Config[].Gateway"
"172.27.0.1"

# reverse-proxy_ssl_defaultは今回のdocker-composeで作ったネットワークです。
```

# 最後に

以上となります。
docker-registryも無事に構築できるとうれしいです。

# 参考
- 全体的に参考にさせていただきました  
https://qiita.com/zawawahoge/items/d58ab6b746625e8d4457
- dockerのhost.docker.internalについて参考にさせていただきました  
https://qiita.com/skobaken/items/03a8b9d0e443745862ac
- nginxでのSSL設定について参考にさせていただきました  
https://qiita.com/tnoce/items/ded6d3d298da5972ab63!