TT Lab
はじめる
学ぶ 学習パス コース

Linuxネットワーク診断

getentとdigは別の道を行く

TT Labで続きを見る

一言でいうと

getentはネームサービススイッチを経由し、digは経由しません。2つの結果が違えば、問題はDNSではなく、スタブリゾルバーの区間です。

なぜ必要なのか

「DNSが使えません」という報告を受けました。接続してdig api.example.comを打ってみると、きちんと出ます。ところが、アプリケーションは、ずっと名前が見つからないと言っています。

ここで「digが通るからDNSは正常」と結論づけると、数時間を失います。digは、アプリケーションが使う道を通らないからです。

アプリケーションはglibcのgetaddrinfo()を呼び出し、glibcは/etc/nsswitch.confのhosts:行を見て、どの順序で尋ねるかを決めます。通常はfiles dnsなので、/etc/hostsを先に見て、その次がDNSです。一方、digは、これらをすべて飛ばして、/etc/resolv.confのネームサーバーに直接尋ねます。

そのため、/etc/hostsに誤ったエントリがあると、digは正常なのに、アプリだけが間違った場所に行きます。逆に、hostsにだけある名前は、アプリではできるのに、digでは見つけられません。同じ名前を尋ねても、2つのツールの答えが違いうるという事実そのものが、診断ツールです。

どう動くのか

名前解決は、1つのシステムではなく、性格の異なる5つの区間がつながった経路です。

  1. アプリケーション + スタブリゾルバー: /etc/hosts、/etc/nsswitch.conf、ランタイム自体のキャッシュ
  2. リゾルバーの選択: /etc/resolv.conf
  3. 再帰リゾルバーのキャッシュ: ほとんどのクエリがここで終わります
  4. 委任の追跡: ルート → TLD → 権威サーバー
  5. 権威サーバーの応答

区間を分ける理由は、各区間を別々に尋ねられるからです。getentは1つ目を含めて見て、digは2つ目から、dig @서버 +norecurseは、そのリゾルバーのキャッシュだけを(プレースホルダーはサーバーです)、dig +traceは4つ目を直接歩きます。「+traceは正常なのにアプリは失敗」なら、犯人は権威サーバーではなく、リゾルバーの区間です。

/etc/resolv.confは、遅延の算数を決めます。

ここから、2つの計算が出てきます。最初のネームサーバーが死んでいると、ユーザーは、デフォルト値を基準に5秒をそのまま体感します。そして、ndotsが大きいと、外部の名前1つを解決するのに、失敗するクエリが何回も先に出ていきます。コンテナで名前解決が遅い典型的な原因です。解決は意外と簡単です。名前の末尾にドットを付けて、絶対名にすることです。

TTLとネガティブキャッシュ。DNSにはプッシュがありません。変更が広がるのではなく、キャッシュが期限切れになるのです。そして、「ない」という答えもキャッシュされます。NXDOMAIN(名前自体がない)とNODATA(名前はあるが、そのタイプだけがない)は別の出来事で、誤字のあったレコードを直したのに、半日の間ずっと失敗し続ける状況の原因は、たいていネガティブキャッシュの時間です。

現場での姿

digのstatusを先に読みます。NOERRORなのに答えの区画が空なら、NODATAです。SERVFAILは、「名前がない」ではなく、答えを作れなかったという意味で、上位への到達失敗や、DNSSEC検証の失敗を指します。REFUSEDは、ポリシー上の拒否です。

flagsのaaの1文字。これがなければ、権威のある答えではなく、キャッシュから来た答えです。権威サーバーは常に元のTTLを返し、リゾルバーは残りの時間を返します。2つが違うのは正常です。

スプリットホライズン。社内DNSと外部DNSが、同じ名前に違う答えを返す構成では、どこで尋ねたかが答えを変えます。障害の報告を受けるときに、「VPNに接続した状態か」を先に確認する必要がある理由です。

最後の原則を1つ。名前が正常に解決されたら、その時点でDNSの調査を終了して、接続の層に移ります。名前解決ができているのにDNSを掘り続けるのが、時間の無駄の代表的な形です。

Linuxで名前が解決される実際の経路

digはできるのにプログラムはできない状況が、よくあります。2つが別の道を通るからです。digはDNSサーバーに直接尋ね、プログラムはgetaddrinfo()を経由します。

順序を決めるのは、/etc/nsswitch.confです。

hosts: files mdns4_minimal [NOTFOUND=return] dns

filesが/etc/hostsで、それがDNSより前にあります。そのため、hostsファイルに残っている古いエントリ1つが、DNSをまるごと覆い隠します。mdns4_minimalの後の[NOTFOUND=return]は、そこで見つからなければ、DNSへ行かずに失敗せよという意味なので、.localドメインがDNSに渡りません。

プログラムが見る答えは、getentで見ます。

getent hosts api.example.com      # nsswitch 를 그대로 따른다
getent ahostsv4 api.example.com   # IPv4 만

/etc/resolv.confのsearchが、クエリを増やします。短い名前を1つ探すとき、searchドメインごとに1回ずつ尋ねます。ndots:5なら、ドットが5個未満の名前は、先にsearchを付けて試すので、api.example.comのような名前も、searchを経由した後で初めて、そのまま照会されます。Kubernetes Podで外部ドメインの照会が遅い理由が、これです。名前の末尾にドットを打つと(api.example.com.)、その過程を飛ばします。

systemd-resolvedを使うと、もう1層あります。/etc/resolv.confが127.0.0.53を指しており、実際のサーバーはその中にあります。そのときは、状態を別に見ます。

resolvectl status
resolvectl query api.example.com

キャッシュは、複数の場所にあります。リゾルバーのデーモン、アプリケーション(特にJVMのnetworkaddress.cache.ttl)、そしてブラウザーです。レコードを変えたのに、一方だけが新しい値を見ている状況は、たいていアプリケーションのキャッシュです。JVMは、デフォルト値が永久キャッシュの場合があるので、フェイルオーバーのとき、古いアドレスをつかみ続けます。

次のラボですること

/etc/resolv.confと/etc/nsswitch.confを直接読んで、スタブリゾルバーがどう動作するかを確認し、/etc/hostsにエントリを追加して、getentは見つけるのにdigは見つけられない状況を自分で作ります。同じ名前を2行で書いて、どちらが勝つかを確認し、最後に、名前解決の結果を終了コードで知らせるヘルパーを作成します。