Dockerでnginxを使ってみた

# この記事の内容
Dockerでnginxを使用する際に気をつけたポイントについて書いてみた。
他コンテナとの連携方法や、nginxの設定などについて触れる。

# サンプルコード
本記事で解説したコードを公開しています。
https://github.com/fukky21/nginx-sample

# 全体の構成
今回は以下のような構成を考えてみる。
<img width="700" alt="全体構成" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/657374/7215e90b-db82-9fc3-682a-9510d68aaeb2.png">

# 環境構築
```yml:docker-compose.yml
version: '3.9'
services:
  proxy:
    image: nginx:1.21.0
    container_name: nginx-sample_proxy
    volumes:
      - ./proxy/nginx.conf:/etc/nginx/nginx.conf
      - ./proxy/html:/etc/nginx/html
      - ./proxy/log:/var/log/nginx
    ports:
      - '80:80'
    depends_on:
      - api
      - web

  api:
    build:
      context: ./api
      dockerfile: Dockerfile
    container_name: nginx-sample_api
    command: yarn dev
    volumes:
      - ./api:/app
    tty: true

  web:
    build:
      context: ./web
      dockerfile: Dockerfile
    container_name: nginx-sample_web
    command: yarn dev
    volumes:
      - ./web:/app
    tty: true
    environment:
      PORT: 3000
      HOST: 0.0.0.0

```

いくつかポイントがあるので解説する。
Dockerfileの内容については、[サンプルコード](https://github.com/fukky21/nginx-sample)をご覧ください。

## proxyの起動を最後にする
`depends_on`でwebとapiを指定することで、proxyよりも先にその2つが起動する。
`nginx.conf`で、プロキシの転送先に`nginx-sample_api`と`nginx-sample_web`を指定するので、最後に起動する必要がある。

## webとapiのポートをホスト側に公開しない
webとapiには`ports`を指定しない。こうすることで、ホストから直接webとapiにアクセスすることはできず、必ずproxyを経由することになる。

## webの環境変数にPORTとHOSTを指定する
webの`environment`で`PORT`と`HOST`を指定している。
これを指定しないと`502 Bad Gateway`と表示され、Webサイト(Nuxt.js)に接続できない。
原因に関しては、この記事に分かりやすくまとめられている。
https://qiita.com/amuyikam/items/01a8c16e3ddbcc734a46

## container_nameを指定する
後ほど紹介する`nginx.conf`で、ここで指定した`container_name`を使用する。

# nginxの設定
```conf:nginx.conf
worker_processes auto;

events {
  worker_connections 16;
}

http {
  server {
    listen 80 default_server;
    server_name _;
    return 444;
  }

  server {
    listen 80;
    server_name localhost;

    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 Connection '';

    error_page 404 /404.html;
    location = /404.html {
      root /etc/nginx/html;
      internal;
    }

    location /app/ {
      allow 172.24.0.1;
      deny all;
      proxy_pass http://nginx-sample_web:3000;
    }
    location /api/ {
      proxy_pass http://nginx-sample_api:3080;
    }
  }
}
```

## worker_processesとworker_connections
[こちらの記事](https://qiita.com/yuse/items/fe05cec1a331306eac19)によると、

> nginxにおける同時クライアントの最大数は、worker_processes × worker_connectionsの数で決まる。

ということらしい。
`worker_processes auto`とすると、ワーカープロセス数はCPUのコア数と同じになる。
Macの場合、CPUコア数は下のコマンドで確認できる。

```command
$ system_profiler SPHardwareDataType | awk '/Total Number of Cores/ { print $NF }'
```

`worker_connections`の値を色々いじってみると、接続できるクライアント数が変わることを確認できる。

## IPの直打ちアクセスを弾く
```
server {
  listen 80 default_server;
  server_name _;
  return 444;
}
```

http://127.0.0.1/app のように、IPアドレスを直打ちしてアクセスしてきた場合に接続を切ることで、攻撃を防ぐことができる。
`444`は、接続を閉じるためのnginxの特別な標準外コードで、詳しくは[こちら](http://nginx.org/en/docs/http/request_processing.html)を参照。

## ヘッダーを書き換える
```
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 Connection '';
```
ヘッダーの書き換えを行わない場合、webやapi側にはプロキシからリクエストが来たことしか伝わらない。ヘッダー情報を、プロキシにアクセスしてきたクライアントの情報に書き換えることで、webやapi側でもクライアントの情報を扱えるようになる。

## エラーページをカスタマイズする
```
error_page 404 /404.html;
location = /404.html {
  root /etc/nginx/html;
  internal;
}
```
デフォルトのエラーページでは以下のように表示される。

<img width="500" alt="デフォルトエラーページ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/657374/653a9f1a-1eb3-c3a4-0615-1cd3c814573c.png">

nginxを使用していることがクライアントにバレてしまうのが嫌な場合は、エラーページを独自に設定すると良い。
今回の例では、`404(Not Found)`のときだけオリジナルのエラーページが表示されるようにしてあるが、`403(Forbidden)`や`500(Internal Server Error)`のときも個別に指定できる。
`internal`を指定することで、内部からのリクエストのためだけに使用されることになり、外部からは直接アクセスできなくなる。

## IPアドレス制限をかける
```
allow 172.24.0.1;
deny all;
```

管理者サイトなど、特定のIPアドレスからのアクセスのみ許可したい場合は`allow`で指定する。その後ろに`deny all`を記述することで、`allow`で指定したIPアドレス以外のアクセスを弾くことができる。