WebサーバーのHTTP/3対応をNginxのリバースプロキシでするためのDockerイメージが出来ました

## やりたいこと
好きな**WebサーバーをHTTP/3に対応**させたいです。
HTTP/3はGoogleやCloudflareなどで使われ徐々に普及しています。HTTP/3では
UDPで通信が行われ、HTTP/1.xやHTTP/2やより高速に通信することができるようになります。詳しくは「[HTTP/3が出るらしいという話を雑に書く - Qiita](https://qiita.com/inductor/items/61b824289ac9b2f11b3e)」などが参考になります。

今回は**Nginx + HTTP/3をDockerイメージにしてすぐに手軽にHTTP/3対応**が出来るようにしました。Docker Composeで手軽にHTTP/3対応できるサンプルも載せたいと思います。

## Cloudflare Quiche
CDNや1.1.1.1やWarpなどで活躍しているCloudflareからQuicheというQUICやHTTP/3のためのOSSがリリースされています。Cloudflare公式が[NginxをHTTP/3対応する記事](https://blog.cloudflare.com/experiment-with-http-3-using-nginx-and-quiche/)があり、それを元にDockerイメージを作成しました。

## Nginx + HTTP/3のDockerイメージ

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/01ba0732-c625-87b2-6ff8-0ceb9f328565.png" width="500">

`docker pull nwtgck/nginx-http3`でDockerイメージが手に入ります。
Dockerfileの内容は<https://github.com/nwtgck/docker-nginx-http3/blob/master/Dockerfile>です。
Dockerfileは公式のGitHubにあるNginx + HTTP/3をする説明を（[quiche/extras/nginx at master · cloudflare/quiche](https://github.com/cloudflare/quiche/tree/cc98af06cb1240fb1b1be3557166ca443b1d8653/extras/nginx)）を元に作成しました。

Docker Hub上でのサイズは47MBで`nginx:latest`と同じくらいのサイズです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/8d2b59fc-29da-d2df-534d-03459d067c1c.png" width="500">

### 例: docker run

`docker run`で起動させる例についてです。
以下の`nginx.conf`を今いるディレクトリに作成します。

```conf:./nginx.conf
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    server {
        # Enable QUIC and HTTP/3.
        listen 443 quic reuseport;

        # Enable HTTP/2 (optional).
        listen 443 ssl http2;

        ssl_certificate      /etc/ssl/certs/server.crt;
        ssl_certificate_key  /etc/ssl/private/server.key;

        # Enable all TLS versions (TLSv1.3 is required for QUIC).
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;

        # Add Alt-Svc header to negotiate HTTP/3.
        add_header alt-svc 'h3-23=":443"; ma=86400';
    }
}
```

以下のようにHTTP/3ではUDPも使うため`-p 443:443/udp`でUDPも解放します。

```bash
docker run -it -p 443:443 -p 443:443/udp \
  -v $PWD/nginx.conf:/usr/local/nginx/conf/nginx.conf \
  -v /etc/letsencrypt/live/nwtgck-nginx-http3.tk/fullchain.pem:/etc/ssl/certs/server.crt \
  -v /etc/letsencrypt/live/nwtgck-nginx-http3.tk/privkey.pem:/etc/ssl/private/server.key \
  nwtgck/nginx-http3
```

上記はLet's Encryptの証明書を指定してます。`.../fullchain.pem`や`.../privkey.pem`は適切に存在しているものを指定してください。最初はローカル環境で自己証明書を使いたかったのですが、後述のChromeでHTTP/3対応の確認が出来なかったのでLet's Encryptを使いました。

### 好きなWebサーバーをリバースプロキシしてHTTP/3対応するには
上記のように好きに`nginx.conf`が書けるため`location / {proxy_pass ...}`などを使ってリバースプロキシの設定すれば良いと思います。
`docker run --net=host ...`を使って起動すればホスト環境で起動している他のWebサーバーのリバースプロキシを作れると思います。
Docker Composeでリバースプロキシする例は後述します。

## HTTP/3対応できているか確認する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/d6e141f6-d8cc-9615-18bc-16073c24a646.png)

このCloudflareの公式ブログ「[HTTP/3: the past, the present, and the future](https://blog.cloudflare.com/http3-the-past-present-and-future/)」で触れられている方法を使ってHTTP/3の対応を確認します。Chrome Canaryに`--enable-quic --quic-version=h3-23`フラグを付けて起動します。

Macユーザーは以下のコマンドで起動できます。

```bash
/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --enable-quic --quic-version=h3-23
```

以下のように開発者ツールのネットワークタブで`http/2+quic/99`となっていてHTTP/3で通信できていることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/470e15ea-3109-f387-0d9b-c70a9ebdd65d.png)

Chrome Canaryは`Version 80.0.3959.0 (Official Build) canary (64-bit)`を使いました。GUIのChrome CanaryではなくCLIで確認したいときは以下の方法が使えるかも知れません。
[http3-client - HTTP/3 Docs](https://developers.cloudflare.com/http3/intro/http3-client)

## Docker Composeを使った例

HTTP/3の対応するのはGhostというOSSのブログのプラットフォームです。<https://hub.docker.com/_/ghost/>でDockerイメージが配布されています。

このGitHubリポジトリ<https://github.com/nwtgck/ghost-nginx-http3-docker-compose>に例を作りました。

`docker-compose.yml`と`nginx.conf`があるだけのシンプルな構成で内容は以下のとおりです。

```docker-compose.yml
version: '3.1'
services:
  nginx:
    image: nwtgck/nginx-http3
    ports:
      - '80:80'
      - '443:443'
      - '443:443/udp'
    depends_on:
      - ghost
    restart: always
    volumes:
      - ./nginx.conf:/usr/local/nginx/conf/nginx.conf
      # 適切に証明書のパスを指定してください。
      - /etc/letsencrypt/live/nwtgck-nginx-http3.tk/fullchain.pem:/etc/ssl/certs/server.crt
      - /etc/letsencrypt/live/nwtgck-nginx-http3.tk/privkey.pem:/etc/ssl/private/server.key

  ghost:
    image: ghost
    restart: always
    expose:
      - "2368"
```


```nginx.conf
worker_processes  1;

events {
    worker_connections  1024;
}


http {
    server {
        listen       80;
        server_name  localhost;

        location / {
            proxy_pass http://ghost:2368;
        }
    }

    server {
        # Enable QUIC and HTTP/3.
        listen 443 quic reuseport;

        # Enable HTTP/2 (optional).
        listen 443 ssl http2;

        ssl_certificate      /etc/ssl/certs/server.crt;
        ssl_certificate_key  /etc/ssl/private/server.key;

        # Enable all TLS versions (TLSv1.3 is required for QUIC).
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;

        location / {
            proxy_pass http://ghost:2368;
        }

        # Add Alt-Svc header to negotiate HTTP/3.
        add_header alt-svc 'h3-23=":443"; ma=86400';
    }
}
```

### 自分の環境で試す方法

```bash
# リポジトリをクローンする
git clone https://github.com/nwtgck/ghost-nginx-http3-docker-compose.git
cd ghost-nginx-http3-docker-compose

<docker-compose.ymlの...fullchain.pemと...privkey.pemのパスを適切に書き換える>

# サーバーを起動する
docker-compose up
```

Chrome Canaryの開発者ツールのネットワークタブで確認すると`https://nwtgck-nginx-http3.tk/...`のリクエストが`http/2+quic/99`となっていてHTTP/3で通信されていることが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84451/9c83ba6b-8d79-fe26-6784-2b880a3c16b6.png)

## おまけ

以下にNginx + HTTP/3でビルドしたり、Dockerイメージにするために少し苦労したことなどを書きました。
[Nginx + HTTP/3 (Quiche)のDockerイメージを生成するためにしたこと - nwtgck / Ryo Ota](https://scrapbox.io/nwtgck/Nginx_+_HTTP%2F3_(Quiche)%E3%81%AEDocker%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AB%E3%81%97%E3%81%9F%E3%81%93%E3%81%A8)