こんにちは、インフラエンジニアのryuです。
アプリケーションのログに Name or service not known が出ている。サーバーにログインして dig を叩いてみると、ちゃんとIPアドレスが返ってくる。
DNSは正常に見える。でもアプリは名前を引けていない。こんな場面に出会ったことはないでしょうか。
このとき、DNSサーバーをいくら調べても答えは出ません。原因はたいてい、DNSに届く前の段階にあります。
dig とアプリケーションは、そもそも同じ経路を通っていません。ここを知らないまま切り分けを始めると、正常なDNSサーバーを何時間も疑うことになります。
今日は、Linuxが名前を引くときに何をどの順番で見ているのかを、上から順に追ってみます。登場するファイルは3つと、最近の環境ならもう1つだけです。
名前解決は、DNSサーバーに聞く前から始まっています¶
まず全体の流れを押さえます。
アプリが呼んでいるのは、DNSクライアントではありません¶
curl でも ping でも、Pythonのライブラリでも、ホスト名からIPアドレスを得るときに呼ぶのは getaddrinfo() という関数です。これはCライブラリ(多くのLinuxではglibc)の機能です。
つまりアプリケーションは「DNSサーバーに問い合わせて」いるわけではありません。「OSに名前を教えてくれと頼んで」いるだけです。
では、頼まれたglibcはどこを見るのか。それを決めているのが /etc/nsswitch.conf です。
身近なものにたとえると、社内で人を探すときの順番に似ています。まず自分のデスクに貼ったメモを見て、無ければ部署の内線表を見て、それでも分からなければ交換手に聞く。どこから順に当たるかを決めた社内ルールが、nsswitch.conf にあたります。
この仕組みは、ホスト名の管理がファイルからDNSやNISへ広がっていった時代に、参照先を切り替えられるようにするために生まれました。だからDNSは「候補のひとつ」でしかなく、必ず使われるとは限らないのです。
/etc/nsswitch.conf の hosts 行が、参照する順番を決める¶
nsswitch.conf は、glibcが各種の名前情報をどこから、どの順で取得するかを定義する設定ファイルです。ユーザー情報やグループ情報もここで制御されていて、ホスト名に関係するのが hosts 行です。
まず、自分のサーバーの設定を見てみましょう。
grep '^hosts' /etc/nsswitch.conf
ディストリビューションによって、返ってくる内容は違います。
# Debian / Ubuntu 系でよく見る形
hosts: files dns
# systemd-resolved を前提にした環境でよく見る形
hosts: files resolve [!UNAVAIL=return] dns myhostname
左から順に試して、答えが得られたところで止まる。これが基本の動きです。並んでいる語は、それぞれ次のような意味を持ちます。
| ソース | 見る場所 | 補足 |
|---|---|---|
files |
/etc/hosts |
最初に置かれることが多い。ここに書いてあれば勝ち |
dns |
/etc/resolv.conf の nameserver |
いわゆるDNS問い合わせ |
resolve |
systemd-resolved | nss-resolveモジュール経由で問い合わせる |
myhostname |
自ホスト名・ローカルアドレス | localhost や自分のホスト名を解決する |
mdns4_minimal |
mDNS(.local) |
.local 以外は解決を拒否して次へ渡す |
角かっこで囲まれた [!UNAVAIL=return] や [NOTFOUND=return] は、結果に応じた振る舞いの指定です。[NOTFOUND=return] が付いていれば、そのソースが「見つからない」と答えた時点で、後ろのソースを試さずに終わります。
[!UNAVAIL=return] は少しひねくれた書き方で、「結果がUNAVAIL(そのソースが使えない)以外だったら、ここで終わる」という意味です。つまりsystemd-resolvedが動いてさえいれば、resolvedが「そんな名前は無い」と答えた時点で確定し、後ろの dns は試されません。
ここが見落としポイントになります。後ろに dns が書いてあっても、前のソースで打ち切られていれば、DNSには一度も問い合わせが行きません。
resolvedが上流サーバーを1台も持っていない状態でこの設定になっていると、resolv.conf に正しいDNSサーバーが書かれていても、名前は引けません。設定ファイルを1つずつ見ていくと、どれも正しく見えるので厄介です。順番を知らないと、原因にたどり着けない類のトラブルです。
nsswitch.confはglibc 2.33以降、ファイルを変更すると自動的に読み直されます。それより古いバージョンでは、プロセスが起動したときに一度だけ読まれるため、設定を直しても動いているプロセスの挙動は変わりません。直したのに変わらないときは、アプリの再起動を試してみてください。
digとgetentは、同じ場所を見ていません¶
ここが冒頭の食い違いの正体です。
dig や nslookup は、DNSの問い合わせを組み立ててDNSサーバーへ直接投げるための道具です。/etc/resolv.conf の nameserver は参照しますが、nsswitch.conf は読みません。/etc/hosts も見ません。
一方、getent hosts はNSSの経路を通ります。これはアプリケーションが通る経路と同じです。
| コマンド | 通る経路 | 分かること |
|---|---|---|
dig / nslookup |
DNSサーバーへ直接 | DNSサーバーの応答そのもの |
getent hosts |
NSS(nsswitch.conf の順番) |
アプリと同じ結果 |
ping / curl |
NSS(getaddrinfo) |
実際のアプリの挙動 |
resolvectl query |
systemd-resolved | resolvedが何を返すか |
つまり dig が成功して getent hosts が失敗するなら、問題はDNSサーバーではなく、その手前のNSSの経路にあります。
dig が悪いわけではありません。DNSサーバーの応答を細かく見るには、これ以上の道具はありません。ただ、アプリが名前を引けるかどうかの証明には使えない。役割が違うだけです。
この区別を持っていないと、「DNSは正常です」という報告と「アプリは落ちています」という事実が、いつまでも噛み合いません。
切り分けは、getent hosts から始める¶
だから、名前解決の調査では最初に打つコマンドを変えるだけで、無駄な時間がだいぶ減ります。
# アプリと同じ経路で引けるか(これが本命)
getent hosts app.example.internal
# DNSサーバー自体は答えているか
dig +short app.example.internal
# 参照順とDNSサーバーの設定を確認
grep '^hosts' /etc/nsswitch.conf
cat /etc/resolv.conf
この4つで、どこが原因かがほぼ絞れます。両方失敗するならDNSかネットワークの問題、dig だけ成功するならNSSの経路の問題です。
層ごとに切り分ける発想そのものは、名前解決に限った話ではありません。通信が通らないときの順番はping・ss・tcpdumpで疎通トラブルを層ごとに切り分ける手順にまとめてあるので、あわせて読むと全体像がつながると思います。
resolv.conf は、DNSに落ちてきたときの設定書¶
NSSの並びをたどって dns に届いたとき、はじめて /etc/resolv.conf の出番になります。
nameserver が並んでいるのは見慣れていると思いますが、トラブルのもとになるのはむしろ search と ndots です。
search と ndots で、問い合わせる名前が変わります¶
search には、補完に使うドメインを並べます。ndots は、名前を絶対名として扱うかどうかの境目になるドットの数で、既定値は1です。
指定したドット数より少ないドットしか含まない名前は、search に並んだドメインを順に足しながら問い合わせが試されます。
search corp.example.com example.com
options ndots:2
この設定で api.internal を引くと、ドットが1個で ndots:2 より少ないため、まず api.internal.corp.example.com、次に api.internal.example.com が試されます。意図した api.internal そのものが問い合わされるのは、そのあとです。
補完先に別のレコードが存在すると、まったく違うIPアドレスが返ってきます。しかもDNSサーバー側のログには「正常に応答した」と記録されるので、サーバー側を見ているだけでは気づけません。
ndots を大きくすると、余計な問い合わせが増えるという副作用もあります。Kubernetes上のPodで名前解決が妙に遅い、という相談の原因がここだった、というのはよくある話です。
この補完を確実に止めたいときは、名前の末尾にドットを付けて絶対名として書きます。api.internal. と書けば、search のドメインは足されません。設定ファイルやアプリの接続先に書く名前で、意図をはっきりさせたい場合に使える手です。
逆に、社内のホスト名を短く書いて済ませている環境では、この補完が前提になっています。search の行を消すと、今まで動いていた設定が一斉に名前を引けなくなります。整理するときは、どの名前が補完に頼っているかを先に洗い出してからにしてください。
127.0.0.53 が書いてあるときは、systemd-resolved が前に立っています¶
最近のUbuntuなどで /etc/resolv.conf を開くと、こう書かれていることがあります。
nameserver 127.0.0.53
options edns0 trust-ad
search .
これは自分自身を指しています。systemd-resolvedが 127.0.0.53(と 127.0.0.54)のループバックアドレスでDNSのスタブリスナーを提供していて、そこへ来た問い合わせをresolvedが受け取り、本当の上流DNSサーバーへ中継する形です。
このとき、実際に使われている上流サーバーは resolv.conf には書かれていません。確認するにはresolvedに聞きます。
# 上流サーバー・検索ドメイン・インターフェースごとの設定を見る
resolvectl status
# resolved 経由で引いてみる
resolvectl query app.example.internal
ここで大事なのが、/etc/resolv.conf を手で書き換えても戻ってしまう、という挙動です。推奨される構成では、/etc/resolv.conf は /run/systemd/resolve/stub-resolv.conf へのシンボリックリンクになっています。resolvedが管理しているファイルなので、直接編集しても意味がありません。
ls -l /etc/resolv.conf
リンク先がどこを指しているかを見れば、誰がこのファイルを管理しているのか分かります。設定を変えたいなら、/etc/systemd/resolved.conf 側を直すのが筋です。NetworkManagerやcloud-initが管理している環境もあるので、まずは管理者が誰なのかを突き止めるのが先になります。
systemdが絡む設定の調べ方はsystemdの言い分をsystemctlとjournalctlで読み解く手順でも扱っています。考え方は同じで、自分が触っているファイルが本当に効いているのかを疑うところから始めます。
現場でよく出会う食い違いのパターン¶
ここまでの知識で説明がつく、典型的なケースを並べておきます。
/etc/hosts に古い行が残っている¶
いちばん多いのがこれです。移行作業のときに一時的に書いた行が消されずに残っていて、files が先に答えてしまう。
DNSのレコードを新しいIPアドレスに変えても、そのサーバーだけ旧環境を向き続けます。dig は新しいIPアドレスを返すので、余計に混乱します。
/etc/hosts の役割そのものが曖昧な方は、hostsファイルとは?概要や設定ファイルの場所を先に読んでおくと理解が早いはずです。
コンテナの中と外は、別の設定です¶
コンテナ内のプロセスが名前を引けないとき、ホスト側の /etc/resolv.conf を見ても答えは出ません。コンテナには、コンテナランタイムが用意した別の /etc/resolv.conf が入っています。
ホストでは引けるがコンテナでは引けない、という現象の多くはここが原因です。調べるときは、必ずコンテナの中で getent hosts を打ちます。
ややこしいのは、ホスト側がsystemd-resolvedを使っている場合です。127.0.0.53 はホストのループバックアドレスなので、これをそのままコンテナへ渡すと、コンテナの中の何も無い場所を指してしまいます。だからコンテナランタイムは、resolvedの上流サーバーを読み取って別の resolv.conf を組み立てます。
手元では動くのに本番のコンテナで落ちる、という話の裏には、こうしたファイルの入れ替わりが隠れていることが少なくありません。
名前解決だけが、やたらと遅い¶
ping を打つと数秒待たされてから応答が始まる、というケースもあります。これは失敗する問い合わせが先に試されて、タイムアウトを待っている状態です。
IPv6のAAAAレコードが引けずに待っている場合、search の補完が空振りしている場合、上流DNSサーバーの1台目が応答しない場合。どれも「引けるが遅い」という同じ症状になります。
疑うべきは応答の内容ではなく、何回問い合わせが投げられているかです。
確認したいときは resolvectl query に時間を測るオプションを添えるか、問い合わせの様子をパケットで見てしまうのが早いです。1回の名前解決で何本のクエリが飛んでいるかが見えると、遅さの理由はたいてい一目で分かります。
一度、自分の環境で順番を追ってみてください¶
この記事に出てきたことは、覚えるより一度手で追ってみたほうが身につきます。
nsswitch.conf の hosts 行を読んで、/etc/hosts を見て、resolv.conf を開いて、getent hosts と dig の結果を並べる。5分もかかりません。
そして、この順番を知っているかどうかで、障害対応の速さははっきり変わります。DNSサーバーの担当チームに問い合わせる前に、自分の側の経路を確認できるようになるからです。
こうした基礎を体系立てて学び直したい方には、InfraAcademyのハンズオン講座が向いていると思います。名前解決やサービスの起動まわりはLinuxのロードマップに沿って手を動かすと、断片的な知識がつながってきます。DNSの仕組みそのものを整理したい場合はDNS入門講座まとめから辿るのが早いです。
新人にこの順番を教える立場の方は、常駐という働き方に合わせた育成の組み立て方も参考になるかもしれません。切り分けの順番は、現場で自然に身につくものではなく、誰かが渡すものだからです。
まとめ¶
Linuxの名前解決について、順番を整理しておきます。
アプリケーションが呼ぶのは getaddrinfo() で、その参照先を決めるのが /etc/nsswitch.conf の hosts 行です。files で /etc/hosts を見て、resolve や dns に進む。[NOTFOUND=return] が書かれていれば、その時点で打ち切られます。
dig と nslookup はこの経路を通らず、DNSサーバーへ直接問い合わせます。だから、アプリと同じ結果を知りたいときは getent hosts を使います。
dns に落ちてきたあとの設定が /etc/resolv.conf で、search と ndots の組み合わせが意図しない名前を問い合わせることがあります。127.0.0.53 が書かれていれば、systemd-resolvedが前に立っているので resolvectl status で上流を確認します。
名前解決は、DNSの話だと思われがちです。実際にはOSの設定ファイルを順番に読む作業のほうが多い。そう捉え直せるようになると、切り分けはぐっと楽になります。次に Name or service not known を見かけたら、まず getent hosts から始めてみてください。



