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

ネットワーク基礎

DNS — digは通るのにアプリケーションが失敗する理由

TT Labで続きを見る

一言でいうと

digとアプリケーションは、そもそも別の経路で名前を解決するので、digが成功したという事実は、アプリケーションが成功する根拠にはなりません。

なぜ必要なのか

障害の状況で、最も人を混乱させる組み合わせがあります。dig +short api.internal.example.comはアドレスをちゃんと返すのに、同じホストのアプリケーションは、名前が見つからないと言って落ちます。ここで、大半は「DNSは問題ないのに、アプリケーションがおかしい」という結論に進み、ライブラリを調べ始めます。この方向は、ほとんど常に徒労です。

どう動くのか

まず、DNS自体の構造です。リゾルバーは、ルートサーバーに尋ねてTLDサーバーを探り出し、TLDサーバーに尋ねて権威サーバーを探り出し、権威サーバーから最終的な答えを受け取ります(再帰クエリと反復クエリの組み合わせ)。各応答にはTTLが付いていて、その時間だけキャッシュされます。

レコードの種類も、知っておく価値があります。AはIPv4アドレス、AAAAはIPv6アドレス、CNAMEは別の名前へのエイリアス、MXはメールサーバー、NSは権威サーバー、TXTは任意の文字列、SRVはサービスのホストとポートです。

さて、核心です。アプリケーションが実際にたどる経路は違います。C、Python、Ruby、PHP、そしてLinuxのJavaまで、ほとんどがglibcのgetaddrinfo()を呼び出し、glibcは次の順序に従います。

  1. /etc/nsswitch.confのhosts行を読んで、どのソースをどの順序で使うかを決めます。
  2. filesなら、/etc/hostsを見ます。
  3. dnsなら、/etc/resolv.confのnameserver、search、optionsを適用して問い合わせます。
  4. resolveなら、systemd-resolvedに尋ねます。

dig、nslookup、hostは、この経路をまるごと飛ばします。nsswitch.confも/etc/hostsも見ず、resolv.confからnameserverのアドレスだけを持ってきて、UDP 53で問い合わせを投げます。そのため、/etc/hostsにしかない名前をdigは見つけられず、逆に、誰かがデバッグの際に残した古い/etc/hostsのエントリのせいで、アプリケーションだけが古いIPに接続する状況が生まれます。

アプリケーションと同じ経路を再現する道具は、getent hostsです。getent ahostsを使うと、IPv4とIPv6を、試す順序どおりに見せてくれます。まとめると、次のとおりです。digはDNSサーバーの答えを見せ、getentはアプリケーションが受け取る答えを見せます。2つの結果が違えば、その違い自体が、原因の場所です。

現場での姿

Kubernetes Podのresolv.confには、options ndots:5と4つのsearchドメインが入っています。ndotsは、「ドットの個数がこの値より少なければ、searchリストを先に付けてみよ」という意味です。そのおかげで、Podの中でapiやapi.prodのような短い名前を使えます。

問題は、外部ドメインです。api.stripe.comはドットが2つで、5より少ないので、searchドメイン4つを先に付けてみます。AとAAAAを一緒に尋ねるので、外部ドメイン1つを解決するために、クエリが10個出て、そのうち8個は、NXDOMAINを受け取るために出たものです。Podが数百個あれば、CoreDNSの負荷の大半が、失敗することが確定したクエリで占められます。

ここで、よく回っている処方を正す必要があります。「ndotsを1に下げればよい」というアドバイスは、クラスターを壊します。ndotsが1だと、ドットが1つのpayments.prodのようなクロスネームスペースの呼び出しが、searchを経由せず、すべて失敗します。安全な下限は2です。より確実な方法は、外部ドメインの末尾にドットを付けて、絶対名にすることです。末尾のドット1つが、searchドメイン4つをなくします。

応答コードも、ひとまとめにしてはいけません。NXDOMAINは、その名前がないという確定で、SERVFAILは、リゾルバーが答えを作れなかったという意味で、ANSWERが0のNOERRORは、名前はあるが、そのタイプのレコードがないという意味です。最後のものが、最も誤診されやすいです。AはあるのにAAAAがない場合が代表的です。

名前が解決されないとき、どこまで行ったかを見る

「DNSの問題のようだ」という言葉はよく聞きますが、実際にDNSである場合は、半分ほどです。どこで詰まったかは、1段階ずつ尋ねていくと表に出ます。

digはキャッシュを、dig +traceは連鎖を見ます。

dig example.com A +short          # 지금 내 리졸버가 아는 답
dig example.com A +trace          # 루트부터 권한 서버까지 따라간다
dig @8.8.8.8 example.com A        # 다른 리졸버는 뭐라고 하는가
dig example.com SOA +short        # 이 이름의 주인은 어느 서버인가

+shortではできるのにブラウザーでできないなら、DNSではなく、その次の段階です。@8.8.8.8ではできるのにデフォルトのリゾルバーではできないなら、自分側のリゾルバーやキャッシュです。

nslookup/digと、アプリケーションが、別の答えを見ることがあります。この2つはDNSを直接尋ねますが、ほとんどのプログラムは、getaddrinfo()を経由します。その経路には、/etc/hosts、/etc/nsswitch.conf、mDNSが挟まっています。プログラムが実際に何を見ているかは、getent hosts example.comのほうが正確です。

TTLは、「いつからできるか」ではなく、「いつまで古いものが残るか」です。レコードを変える前に、TTLを300秒に縮めておき、そのTTLが過ぎた後に変更し、安定したら再び上げます。この順序を守らないと、変更した瞬間ではなく、古いTTLがすべて抜ける時刻まで、人によって違う場所に行きます。

dig example.com A          # ANSWER SECTION 의 두 번째 열이 남은 TTL 이다

このコードブロックの韓国語コメントは、ANSWER SECTIONの2列目が残りのTTLだという意味です。

ない名前と、答えがないことは、違います。NXDOMAINは、その名前が存在しないことで、NOERRORなのにANSWERが0なら、名前はあるが、その種類のレコードがないことです。AAAAだけがなくてAはある状況で、IPv6を先に試すクライアントが遅くなる場面が、ここです。

検索ドメインが黙って付きます。/etc/resolv.confのsearchにドメインが複数あると、短い名前1つを照会するたびに、その数だけクエリが出ます。Kubernetes Podのndots:5のために、外部ドメインの照会が4回失敗した後で成功するのが、よく知られた事例です。最後にドットを打って、example.com.と書くと、検索を飛ばします。

続くクイズで確認すること

digとgetentの違いを説明できるか、ndotsが生む遅延のメカニズムを理解したかを確認します。