Posted at

Linuxのresolv.confのベストプラクティスを探す

More than 3 years have passed since last update.

最近でもないですが、RDSとかを使うとリゾルバが死ぬと影響でますよねー。

その一方で、意外とちゃんと設定されていないresolv.confについて詳しく。


まずデフォルトってどんなだっけ?

最小限で書くと、こんな感じになっていると思います。


resolv.conf

nameserver 192.168.33.10  #1号機

nameserver 192.168.33.11 #2号機

以後、IPだとわかりにくいので、192.168.33.10を1号機、192.168.33.11を2号機と呼びます。


1号機がノードまるごと落ちた(IP的に死亡)

めんどくさいので、1号機のIPを存在しない192.168.33.101に変えて試してみます。

[root@localhost ~]# strace -tt route

06:18:35.238838 execve("/sbin/route", ["route"], [/* 17 vars */]) = 0
(途中省略)
06:18:35.243618 connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.101")}, 16) = 0
06:18:35.243646 poll([{fd=4, events=POLLOUT}], 1, 0) = 1 ([{fd=4, revents=POLLOUT}])
06:18:35.243666 sendto(4, "\n\251\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:18:35.243689 poll([{fd=4, events=POLLIN}], 1, 5000) = 0 (Timeout)
06:18:40.248851 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 5
06:18:40.248894 connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.11")}, 16) = 0
06:18:40.248923 poll([{fd=5, events=POLLOUT}], 1, 0) = 1 ([{fd=5, revents=POLLOUT}])
06:18:40.248944 sendto(5, "\n\251\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:18:40.249154 poll([{fd=5, events=POLLIN}], 1, 5000) = 1 ([{fd=5, revents=POLLIN}])
06:18:40.249475 ioctl(5, FIONREAD, [116]) = 0
06:18:40.249521 recvfrom(5, "\n\251\201\203\0\1\0\0\0\1\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.11")}, [16]) = 116
(途中省略)
06:18:40.249813 +++ exited with 0 +++

1号機は5秒でタイムアウト(06:18:35.243689)して、2号機に接続しにいって結果を得て(06:18:40.249521)います。


1号機がサービスだけ死んだ(ポートだけ死亡)

IPは元に戻して、サービスを落として試します。

06:29:52.984867 execve("/sbin/route", ["route"], [/* 17 vars */]) = 0

(途中省略)
06:29:52.989791 connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.10")}, 16) = 0
06:29:52.989818 poll([{fd=4, events=POLLOUT}], 1, 0) = 1 ([{fd=4, revents=POLLOUT}])
06:29:52.989838 sendto(4, "\325|\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:29:52.989904 poll([{fd=4, events=POLLIN}], 1, 5000) = 1 ([{fd=4, revents=POLLERR}])
06:29:52.990147 close(4) = 0
06:29:52.990166 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 4
06:29:52.990184 connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.11")}, 16) = 0
06:29:52.990208 poll([{fd=4, events=POLLOUT}], 1, 0) = 1 ([{fd=4, revents=POLLOUT}])
06:29:52.990226 sendto(4, "\325|\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:29:52.990311 poll([{fd=4, events=POLLIN}], 1, 5000) = 1 ([{fd=4, revents=POLLIN}])
06:29:53.110923 ioctl(4, FIONREAD, [116]) = 0
06:29:52.989904 recvfrom(4, "\325|\201\203\0\1\0\0\0\1\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.11")}, [16]) = 116
(途中省略)
06:29:53.111217 +++ exited with 0 +++

ポートが開いていないので1号機は即座にPOLLERR(06:29:52.989904)、2号機に聞きに行って(06:29:52.989904)事なきを得ています。


そしてそもそもどんな挙動だっけ?

resolv.confのマニュアルを見てみましょう。

主要部分を抜粋するとこんな感じです。


nameserver

複数のサーバが指定された場合、レゾルバライブラリは リストされた順に問い合わせを行う。

options timeout:n

「レゾルバが他のネームサーバで問い合わせをリトライする前に、 リモートネームサーバからの応答を待つ時間」を設定する。 単位は秒で、デフォルトは RES_TIMEOUT である (現状では 5 秒、 を参照)。


というわけで、ここまでのテストだと必ず1号機に先に問い合わせして、タイムアウトを待つ場合は5秒ということでマニュアル通りの挙動です。


耐障害性のためにresolv.confで出来ること

結論から言うと、実は大してないんですよね、これ。。。

近しいところでやれることはこのあたりです。

まず、先ほどのtimeoutを縮めると、次のサーバへのリトライが早くなります。

あとは、エラーで諦めることを目的とするのであれば、attemptsを小さくすると、早々に諦めてエラー処理してくれます。これは nameserverが複数ある場合は、それら一式に適用 されます。(なので1にしても、1セットはやってくれる)


options attempts:n

「レゾルバが諦めて呼び出し元のアプリケーションにエラーを返すまでに、 ネームサーバに問い合わせを行う回数」を設定する。 デフォルトは RES_DFLRETRY 回である (現状では 2 回、 を参照)。 このオプションの値の上限は 5 であり、黙ってこの値まで切り詰められる。


その他、rotateすると問い合わせの順番を変えてくれるので、障害機でないものに行ってくれるかもしれません。


options rotate

リストされているネームサーバから選ぶときに、ラウンドロビン (round-robin) 選択を行わせる。リストされている全てのサーバで問い合わせの負荷を分散する効果があり、最初にリストされたサーバに全てのクライアントが毎回最初に問い合わせを行うわけではなくなる。


というわけで、これらを試しにセットしてstraceしてみます。(わかりやすく、options timeout:3 attempts:5 rotateにしました)

[root@localhost ~]# strace -tt route

06:57:36.523619 execve("/sbin/route", ["route"], [/* 17 vars */]) = 0
(途中省略)
06:57:36.528604 connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.10")}, 16) = 0
06:57:36.528633 poll([{fd=4, events=POLLOUT}], 1, 0) = 1 ([{fd=4, revents=POLLOUT}])
06:57:36.528654 sendto(4, "n\314\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:57:36.529175 poll([{fd=4, events=POLLIN}], 1, 3000) = 1 ([{fd=4, revents=POLLERR}])
06:57:36.529200 close(4) = 0
06:57:36.529256 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 4
06:57:36.529276 connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.111")}, 16) = 0
06:57:36.529301 poll([{fd=4, events=POLLOUT}], 1, 0) = 1 ([{fd=4, revents=POLLOUT}])
06:57:36.529321 sendto(4, "n\314\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:57:36.529340 poll([{fd=4, events=POLLIN}], 1, 2000) = 0 (Timeout)
06:57:38.531494 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 5
06:57:38.531535 connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.100")}, 16) = 0
06:57:38.531569 poll([{fd=5, events=POLLOUT}], 1, 0) = 1 ([{fd=5, revents=POLLOUT}])
06:57:38.531590 sendto(5, "n\314\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:57:38.531615 poll([{fd=5, events=POLLIN}], 1, 4000) = 0 (Timeout)
06:57:42.535756 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 6
06:57:42.535796 connect(6, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.10")}, 16) = 0
06:57:42.535826 poll([{fd=6, events=POLLOUT}], 1, 0) = 1 ([{fd=6, revents=POLLOUT}])
06:57:42.535847 sendto(6, "n\314\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:57:42.535931 poll([{fd=6, events=POLLIN}], 1, 3000) = 1 ([{fd=6, revents=POLLERR}])
06:57:42.536214 close(6) = 0
06:57:42.536256 close(4) = 0
06:57:42.536282 close(5) = 0
06:57:42.536306 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 4
06:57:42.536352 connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.111")}, 16) = 0
06:57:42.536390 poll([{fd=4, events=POLLOUT}], 1, 0) = 1 ([{fd=4, revents=POLLOUT}])
06:57:42.536435 sendto(4, "n\314\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:57:42.536463 poll([{fd=4, events=POLLIN}], 1, 2000) = 0 (Timeout)
06:57:44.538183 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 5
06:57:44.538225 connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.100")}, 16) = 0
06:57:44.538255 poll([{fd=5, events=POLLOUT}], 1, 0) = 1 ([{fd=5, revents=POLLOUT}])
06:57:44.538276 sendto(5, "n\314\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:57:44.538300 poll([{fd=5, events=POLLIN}], 1, 4000) = 0 (Timeout)
06:57:48.543322 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 6
06:57:48.543363 connect(6, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.10")}, 16) = 0
06:57:48.543393 poll([{fd=6, events=POLLOUT}], 1, 0) = 1 ([{fd=6, revents=POLLOUT}])
06:57:48.543414 sendto(6, "n\314\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:57:48.543490 poll([{fd=6, events=POLLIN}], 1, 3000) = 1 ([{fd=6, revents=POLLERR}])
06:57:48.543728 close(6) = 0
06:57:48.543751 close(4) = 0
06:57:48.543768 close(5) = 0
06:57:48.543784 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 4
06:57:48.543801 connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.111")}, 16) = 0
06:57:48.543821 poll([{fd=4, events=POLLOUT}], 1, 0) = 1 ([{fd=4, revents=POLLOUT}])
06:57:48.543838 sendto(4, "n\314\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:57:48.543858 poll([{fd=4, events=POLLIN}], 1, 2000) = 0 (Timeout)
06:57:50.545999 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 5
06:57:50.546040 connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.100")}, 16) = 0
06:57:50.546069 poll([{fd=5, events=POLLOUT}], 1, 0) = 1 ([{fd=5, revents=POLLOUT}])
06:57:50.546090 sendto(5, "n\314\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:57:50.546115 poll([{fd=5, events=POLLIN}], 1, 4000) = 0 (Timeout)
06:57:54.550286 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 6
06:57:54.550328 connect(6, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.10")}, 16) = 0
06:57:54.550358 poll([{fd=6, events=POLLOUT}], 1, 0) = 1 ([{fd=6, revents=POLLOUT}])
06:57:54.550378 sendto(6, "n\314\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:57:54.550464 poll([{fd=6, events=POLLIN}], 1, 3000) = 1 ([{fd=6, revents=POLLERR}])
06:57:54.550716 close(6) = 0
06:57:54.550739 close(4) = 0
06:57:54.550755 close(5) = 0
06:57:54.550770 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 4
06:57:54.550788 connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.111")}, 16) = 0
06:57:54.550806 poll([{fd=4, events=POLLOUT}], 1, 0) = 1 ([{fd=4, revents=POLLOUT}])
06:57:54.550823 sendto(4, "n\314\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:57:54.550842 poll([{fd=4, events=POLLIN}], 1, 2000) = 0 (Timeout)
06:57:56.552982 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 5
06:57:56.553023 connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.100")}, 16) = 0
06:57:56.553052 poll([{fd=5, events=POLLOUT}], 1, 0) = 1 ([{fd=5, revents=POLLOUT}])
06:57:56.553073 sendto(5, "n\314\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:57:56.553097 poll([{fd=5, events=POLLIN}], 1, 4000) = 0 (Timeout)
06:58:00.558031 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 6
06:58:00.558074 connect(6, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.10")}, 16) = 0
06:58:00.558104 poll([{fd=6, events=POLLOUT}], 1, 0) = 1 ([{fd=6, revents=POLLOUT}])
06:58:00.558125 sendto(6, "n\314\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:58:00.558212 poll([{fd=6, events=POLLIN}], 1, 3000) = 1 ([{fd=6, revents=POLLERR}])
06:58:00.558429 close(6) = 0
06:58:00.558455 close(4) = 0
06:58:00.558471 close(5) = 0
06:58:00.558487 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 4
06:58:00.558505 connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.111")}, 16) = 0
06:58:00.558525 poll([{fd=4, events=POLLOUT}], 1, 0) = 1 ([{fd=4, revents=POLLOUT}])
06:58:00.558543 sendto(4, "n\314\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:58:00.558563 poll([{fd=4, events=POLLIN}], 1, 2000) = 0 (Timeout)
06:58:02.560695 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 5
06:58:02.560736 connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.33.100")}, 16) = 0
06:58:02.560766 poll([{fd=5, events=POLLOUT}], 1, 0) = 1 ([{fd=5, revents=POLLOUT}])
06:58:02.560786 sendto(5, "n\314\1\0\0\1\0\0\0\0\0\0\0012\0012\0010\00210\7in-addr\4ar"..., 39, MSG_NOSIGNAL, NULL, 0) = 39
06:58:02.560811 poll([{fd=5, events=POLLIN}], 1, 4000) = 0 (Timeout)
06:58:06.564969 close(4) = 0
06:58:06.565010 close(5) = 0
06:58:06.565045 write(1, "default 10.0.2.2 "..., 77default 10.0.2.2 0.0.0.0 UG 0 0 0 eth0
) = 77
06:58:06.565069 read(3, "", 1024) = 0
06:58:06.565089 close(3) = 0
06:58:06.565109 munmap(0x7f6dab081000, 4096) = 0
06:58:06.565152 exit_group(0) = ?
06:58:06.565226 +++ exited with 0 +++

長いですが、ポイントは大してありません



  • poll()POLLINしている行が3000ms -> 2000ms -> 4000msを1セットとして5回(attempts分)


  • connect()で接続しているinet_addrがランダムに

というわけで、 3台のnameserverに対しtimeout:1 attempts:1 rotate:1と設定すると、最速の3秒で諦めてくれ、ランダムなのでもしかすると障害機でないものにあたってくれるかもしれない、というのが最大限です。対策になっていないですよね。。。でも、resolv.confの精一杯はここです。(3台というのは、nameserverで設定できる上限値が3台なので。)

ちなみに、POLLINしているところのタイムアウト値ですが、以下の記事が詳しいです。

/etc/resolv.confとタイムアウト秒


そんなのじゃ困るんだけど

というわけで、resolv.confに拘らず、なんとかする手段を考えます。


nameserverをVIPにする

お金持ちのところはこれが多いように思います。死活出来るロードバランサ配下にnameserverをぶら下げ、VIPのnameserverとして提供します。安心だけれども、ELBだとこれ出来ないですね。。。


サーバ自身を強化する

resolv.confで出来ないなら、何か入れちゃいましょうということで、パッケージで入れられるunboundを使うとかでしょうか。Linux自身の仕組みを使わないことで、resolv.confの制約は受けなくなります。

参考

DNS unboundサーバ構築手順(yumでインストール)