nginxでIPアドレスをキャッシュしないようにするために 

# 概要・経緯
nginxはstart or reload時にドメインの名前解決を行い、そのIPをTTLに関係なく次のrestart or reload時まで保持する挙動となっています。
そのためドメインに紐づくIPアドレスを変更されると途端にnginxから接続できなくなるといったことが発生します。（実際に業務で発生しました）
そこで、本記事ではnginxが次の起動までIPアドレスを保持してしまう挙動を回避し、ドメインアクセス時に都度名前解決するような設定を紹介します。
同じような記事は他ブログ等でも書かれていますので、そちらも参考に。

# 解決法
結論から言うとやるべきことは2つだけ。

1. resolverパラメータでネームサーバのIPを指定する
2. ドメインをsetを使って変数化する

# 1.resolverパラメータでネームサーバのIPを指定する

```nginx
# resolver <name server>
resolver 8.8.8.8;
```

このようにすると指定したname serverに名前解決の問い合わせを行うようになります。
ただしname serverを指定する必要があり、`/etc/resolv.conf`を見なくなってしまうのでname serverの自動管理ができなくなってしまうの難点かなとは思いますが。
resolverパラメータを指定できるコンテキストはhttp, server, locationです。

# 2. ドメインをsetを使って変数化する
1でresolverを設定しましたが、resolverの設定だけではドメインアクセス時に名前解決してくれません。必ずドメインを一度setパラメータを使って変数化する必要があります。

```nginx
server {
    location /proxy {
        set $backend_server google.com;
        proxy_pass https://$backend_server;
    }
}
```

このようにすることでnginxはTTLを超えていた場合、ドメインアクセス時に名前解決してくれるようになります。
また、nginx側でTTLを制御することも可能です。
resolverパラメータにvalidパラメータも一緒に付与することでドメイン名を、指定したTTLが切れた後に名前解決してくれるようになります。

```nginx
resolver 8.8.8.8 valid=10s;
```

上記の例だとnginxはドメインアクセス時にTTLが10秒超えていると名前解決を行います。

# 注意：upstreamコンテキストを利用する場合
nginxがドメインを都度名前解決するためにはsetパラメータを使ってドメインを変数化する必要がありました。
ただsetはserver, location, ifコンテキスト内でしか利用できないため、upstreamとの併用ができません。
これの回避策はないかググっていると以下のような設定での回避策が見つかりました。やることは主に3点です。

- Unixドメインソケットを立ててそれをServerとして宣言する
- そのServerコンテキストの中でsetを使ってドメインを変数化する
- upstream先のserverに上記のServerを設定する

ご参考までに。

```nginx:nginx.conf
http {
  upstream backend {
    server unix:/var/run/nginx_backend1.sock weight=9 max_fails=1 fail_timeout=20s;
    server unix:/var/run/nginx_backend2.sock weight=1;
  }

  server {
    listen unix:/var/run/nginx_backend1.sock;
    set $backend_server google.com;
    location /proxy {
        proxy_pass https://$backend_server;
    }
  }

  server {
    listen unix:/var/run/nginx_backend2.sock;
    set $backend_server yahoo.co.jp;
    location /proxy {
        proxy_pass https://$backend_server;
    }
  }

  server {
    server_name localhost;
    listen 80;
    location /proxy {
        proxy_pass http://backend;
    }
  }
}
```

# おまけ
nginxが上記の動作を本当にするのか気になり、tcpdumpを用いてパケットレベルで確認したので一応載せておきます。

## 検証環境
Vagrantを用いてUbuntu 18.04のVMを立ち上げ、その上にnginxをインストールしました。

|検証ツール/ミドルウェア|バージョン|
|:-:|:-:|
|Vagrant|2.2.6|
|VirtualBox|6.0.14|
|Vagrant Box|ubuntu/bionic64|
|nginx|1.14.0|

## ソース
以下のようなlocationコンテキストを用意することで、`/proxy`にアクセスしたときにproxy_passで設定したドメインがどのタイミングで名前解決されるか確認します。

```nginx
location /proxy {
    set $domain google.com;
    proxy_pass https://$domain;
}
```

## 検証用コマンド
どのタイミングで名前解決されるか確認するために、1secずつ`/proxy`に対してcurlするスクリプトを用意しました。
これを実行中にDNS問い合わせのタイミングを確認します。
192.168.3.21はnginxが起動しているインスタンスのIPです。

```bash
$ while true; do curl -sS http://192.168.3.21/proxy -o /dev/null -w "%{http_code}\n"; sleep 1; done
```

## 1. resolverを設定しない場合は起動時に名前解決されるだけであることの確認
まず、resolverやsetを使わない場合にどのタイミングで名前解決するか確認します。
この場合、proxy_passに設定すべきドメインはsetを使ってはいけません。setを使うとresolverの設定が必須になり、resolverがないと/proxyアクセス時に名前解決に失敗してエラーになります。

```nginx:nginx.conf
location /proxy {
    proxy_pass https://google.com;
}
```

以下起動時のtcpdumpの結果です。

```
$ sudo tcpdump port 53 -vvv
tcpdump: listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes
05:30:35.258585 IP (tos 0x0, ttl 64, id 24234, offset 0, flags [DF], proto UDP (17), length 67)
    ubuntu-bionic.53629 > 10.0.2.3.domain: [bad udp cksum 0x1852 -> 0xb9e5!] 7978+ [1au] A? google.com. ar: . OPT UDPsize=512 (39)
05:30:35.269026 IP (tos 0x0, ttl 64, id 45044, offset 0, flags [none], proto UDP (17), length 83)
    10.0.2.3.domain > ubuntu-bionic.53629: [udp sum ok] 7978 q: A? google.com. 1/0/1 google.com. [2m47s] A 172.217.25.78 ar: . OPT UDPsize=4096 (55)
```

このログからでは分からないですが、検証したところAレコードのTTLが切れた後でもDNS問い合わせがされることはありませんでした。
よって起動時に名前解決するだけのことが分かりました。
最初に解決したIPをずっと保持していそうです。

## 2. resolverを設定する場合はAレコードのTTLに従って名前解決されることの確認
resolverパラメータを設定することで本当にTTLが切れるたびに名前解決するようになるか確認しました。

```nginx:nginx.conf
resolver 8.8.8.8 ipv6=off;
```

以下tcpdumpの結果です。

```
$ sudo tcpdump port 53 -vvv
tcpdump: listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes
04:44:20.256619 IP (tos 0x0, ttl 64, id 61932, offset 0, flags [DF], proto UDP (17), length 56)
    ubuntu-bionic.57268 > dns.google.domain: [bad udp cksum 0x1c54 -> 0x4800!] 42980+ A? google.com. (28)
04:44:20.264318 IP (tos 0x0, ttl 64, id 29087, offset 0, flags [none], proto UDP (17), length 72)
    dns.google.domain > ubuntu-bionic.57268: [udp sum ok] 42980 q: A? google.com. 1/0/0 google.com. [2m55s] A 172.217.25.110 (44)
04:47:16.258094 IP (tos 0x0, ttl 64, id 12872, offset 0, flags [DF], proto UDP (17), length 56)
    ubuntu-bionic.57268 > dns.google.domain: [bad udp cksum 0x1c54 -> 0x3bce!] 46102+ A? google.com. (28)
04:47:16.300846 IP (tos 0x0, ttl 64, id 31003, offset 0, flags [none], proto UDP (17), length 72)
    dns.google.domain > ubuntu-bionic.57268: [udp sum ok] 46102 q: A? google.com. 1/0/0 google.com. [4m59s] A 172.217.25.110 (44)
04:52:16.187167 IP (tos 0x0, ttl 64, id 48012, offset 0, flags [DF], proto UDP (17), length 56)
    ubuntu-bionic.57268 > dns.google.domain: [bad udp cksum 0x1c54 -> 0xde9e!] 4422+ A? google.com. (28)
04:52:16.228961 IP (tos 0x0, ttl 64, id 34262, offset 0, flags [none], proto UDP (17), length 72)
    dns.google.domain > ubuntu-bionic.57268: [udp sum ok] 4422 q: A? google.com. 1/0/0 google.com. [4m59s] A 172.217.25.110 (44)
```

まず04:44:20のDNS問い合わせのレスポンスを見てみます。TTLが2分55秒だということが分かります。
そして次のDNS問い合わせ時間を見てみると、04:47:16に問い合わせており、TTLの2分55秒が切れた後に問い合わせていることが分かります。この問い合わせで返ってきたAレコードのTTLは4分59秒です。
さらに次のDNS問い合わせ時間を見てみると、04:52:16に問い合わせており、TTLの4分59秒が切れた後に問い合わせていることが分かります。
よってAレコードのTTLに従ってnginxが名前解決していることが分かりました。

## 3. resolverパラメータのvalid設定が効いていることの確認
次に、resolverパラメータのvalidパラメータがあるとAレコードのTTLに関係なく名前解決するようになるか確認しました。
validは30秒で設定してあります。

```nginx:nginx.conf
resolver 8.8.8.8 valid=30s ipv6=off;
```

以下tcpdumpの結果です。

```
$ sudo tcpdump port 53 -vvv
tcpdump: listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes
05:07:46.253469 IP (tos 0x0, ttl 64, id 7328, offset 0, flags [DF], proto UDP (17), length 56)
    ubuntu-bionic.51617 > dns.google.domain: [bad udp cksum 0x1c54 -> 0x619d!] 42074+ A? google.com. (28)
05:07:46.294356 IP (tos 0x0, ttl 64, id 43277, offset 0, flags [none], proto UDP (17), length 72)
    dns.google.domain > ubuntu-bionic.51617: [udp sum ok] 42074 q: A? google.com. 1/0/0 google.com. [4m59s] A 172.217.25.110 (44)
05:08:17.913016 IP (tos 0x0, ttl 64, id 9394, offset 0, flags [DF], proto UDP (17), length 56)
    ubuntu-bionic.51617 > dns.google.domain: [bad udp cksum 0x1c54 -> 0x3e03!] 51188+ A? google.com. (28)
05:08:17.954836 IP (tos 0x0, ttl 64, id 43632, offset 0, flags [none], proto UDP (17), length 72)
    dns.google.domain > ubuntu-bionic.51617: [udp sum ok] 51188 q: A? google.com. 1/0/0 google.com. [4m59s] A 172.217.25.110 (44)
05:08:48.661111 IP (tos 0x0, ttl 64, id 14607, offset 0, flags [DF], proto UDP (17), length 56)
    ubuntu-bionic.51617 > dns.google.domain: [bad udp cksum 0x1c54 -> 0x2689!] 57198+ A? google.com. (28)
05:08:48.668321 IP (tos 0x0, ttl 64, id 43974, offset 0, flags [none], proto UDP (17), length 72)
    dns.google.domain > ubuntu-bionic.51617: [udp sum ok] 57198 q: A? google.com. 1/0/0 google.com. [3m57s] A 172.217.25.110 (44
```

まず05:07:46のDNS問い合わせのレスポンスを見てみます。TTLが4分59秒だということが分かります。
そして次のDNS問い合わせ時間を見てみると、05:08:17に問い合わせています。AレコードのTTLとは関係なくvalidに設定した30秒後に問い合わせていることが分かります。この問い合わせで返ってきたAレコードのTTLは4分59秒です。
さらに次のDNS問い合わせ時間を見てみると、05:08:48に問い合わせており、これもTTLの4分59秒と関係なく30秒後に問い合わせていることが分かります。
よってvalidを設定した場合はAレコードのTTLに関係なくvalidに設定した時間ごとにnginxが名前解決していることが分かりました。

# 参考
- [Nginxの名前解決についてまとめ - Engineering blog at ktrysmt](https://ktrysmt.github.io/blog/name-specification-of-nginx/)
- [DNS for Service Discovery with NGINX and NGINX&nbsp;Plus](https://www.nginx.com/blog/dns-service-discovery-nginx-plus/)
- http://nginx.org/en/docs/http/ngx_http_core_module.html#resolver
- http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#set
- [超絶初心者むけtcpdumpの使い方 - Qiita](https://qiita.com/tossh/items/4cd33693965ef231bd2a)