Nginxでproxy_passにホスト名を書いた時の名前解決のタイミング

Nginx 1.4.2で試しました。

ネームサーバーは、ローカルのunboundをlocal-zone, local-dataを使って簡易コンテンツサーバーにして試しました。

  local-zone: "oreno." static
  local-data: "api.oreno.   30 IN A 192.0.2.11"
#  local-data: "api.oreno.   30 IN A 192.0.2.12"

proxy_passにホスト名を書くと→名前解決は一度だけ

このように Nginx の設定を書いた場合、

location /api {
  proxy_pass http://api.oreno:9999;
}
  • 「api.oreno」の名前解決は、nginxの起動時に行われます
    • 名前解決できない場合は、nginxは起動しません
    • 名前解決できた場合は、ずっとそのIPアドレスにreverse proxyします
      • DNS応答のTTLは一切影響しません
      • 再度名前解決させるには、SIGHUPを送ればよいです
      • もちろん、SIGTERMで再起動してもよいです

という挙動になります。

つまり、ホスト名に紐づくIPアドレスがDNS的に変更されても、nginxは(HUPを受けるまでは)古いIPアドレスにreverse proxyし続けます。

定期的に名前解決させるにはどうすればよいか?

このように、setディレクティブで変数にホスト名をセットし、proxy_passではその変数を参照すれば、定期的に名前を引き直してくれるようになります。バッドな感じがしますが、公式ドキュメントにもこの挙動はちゃんと記されています。

location /api {
  resolver 127.0.0.1;
  set $api_backend "api.oreno";
  proxy_pass http://$api_backend:9999;
}

この時、resolverディレクティブもあわせて指定しないと、「no resolver defined to resolve api.oreno」とエラーログに記され、クライアントには 502 Bad Gateway が返されるので注意してください。

デフォルトではDNS応答のTTLが切れたら再度問い合わせに行きますが、

location /api {
  resolver 127.0.0.1 valid=2s;
  set $api_backend "api.oreno";
  proxy_pass http://$api_backend:9999;
}

と、resolverディレクティブにvalidオプションを指定することによって、TTLの値に関わらず、2秒経過したらまた再度DNS問い合わせするようにもできます。

upstreamディレクティブでserverを羅列してい場合に、定期的に名前解決させるには?

upstream api_upstream {
  resolver 127.0.0.1;

  set $api1 "api1.oreno";
  server $api1;
  set $api2 "api2.oreno";
  server $api2;
}

location /api {
  proxy_pass http://api_upstream:9999;
}

と書けばできそうに思えますがダメです。なぜなら、setディレクティブはupstreamコンテキストでは指定できないからです。

upstreamディレクティブでserverを羅列してい場合に、定期的に名前解決させる方法はないように思えるのですが、なにかよい方法があったら教えてもらえるとうれしいです><

追記

1.5.12以降なら、serverディレクティブにresolveパラメータをつければhttpブロックのresolverの設定の通りに名前を引き直してくれるようです。

http {
    resolver 127.0.0.1;

    upstream api_upstream {
        server api.oreno resolve;
    }
}

やったー!!!!と思って試してみたのですが「nginx: [emerg] invalid parameter "resolve" in ...」と怒られる。。と、よくドキュメント読んだら、

Additionally, the following parameters are available as part of our commercial subscription:

と書いてありました><

追記の追記

2024-11-26 にリリースされた 1.27.3 で resolve が使えるようになりました。

追記2

を使えばできました。nginx 1.8.0で確認。nginx 1.4.1ではビルドができませんでした。

これでTTL後に引き直し、

http {
    resolver 127.0.0.1;

    upstream api_upstream {
        dynamic_server api.oreno;
    }
}

これでTTLに関係なく2秒後に引き直してくれました!

http {
    resolver 127.0.0.1 valid=2s;

    upstream api_upstream {
        dynamic_server api.oreno;
    }
}

追記3

でも紹介されている https://github.com/wdaike/ngx_upstream_jdomain/ を使ったほうがよさそうです。
Nginx 1.14ではコンパイルできなかったので fork の
https://github.com/mwhipple/ngx_upstream_jdomain を試しました。ディレクティブが jdomain から hostname に変わっている点に注意してください。

http {
    resolver 127.0.0.1 valid=2s;

    upstream api_upstream {
        hostname api.oreno;
    }
}

追記4

ApacheだとProxyPassにdisablereuse=Onをつければいいっぽい。

なぞの「DNS error (32: Unknown error), query id:XXXXX」

まだ再現方法がわからないのですが、proxy_passで変数参照している場合にこのエラーが出て数秒〜十数秒間reverse proxyできなくなる現象が起きています。

こちら何か情報お持ちの方は教えていただけるとうれしいです><

おまけ: nginxのdebug log

configureするときに --with-debug した上で、

error_log /path/to/file debug;

とすると、nginxがdebug logを吐くようになります。resolverの挙動もこれで確認できます。ただ、かなりの量を吐くので注意してください。

あと、コンテキストによって吐かれるログが違うので、serverやhttpコンテキストだけでなく、一番外側のmainコンテキストにもerror_logディレクティブを書いた方がよいです。