UnboundがSERVFAILを返す原因はinfraキャッシュだった


自宅の Proxmox を、約1年半ぶりに更新しました。有料版のリポジトリが有効なまま使っていたため、Proxmox 固有のパッケージがずっと更新されていなかったのです。

無償版のリポジトリに切り替えて更新し、再起動したところ、自宅の DNS で名前解決ができなくなりました。

この記事では、原因を切り分けた過程と、そこで分かった Unbound の仕様についてまとめます。

起きたこと

再起動後に確認すると、自動で起動していたのは DNS サーバーの LXC だけでした。仮想ルーターの VM を含め、ほかは自動起動の設定が入っていなかったのです。

ルーターと残りのサーバーを手動で起動しました。ところが、ルーターが動いているのに、名前解決ができません。

切り分け

ネットワークは生きていた

まず DNS の LXC で、起動に失敗したサービスを確認しました。

pct exec 101 -- systemctl --failed

systemd-networkd が失敗していたので、ネットワークが原因かと考えました。しかし IP アドレスもデフォルトルートも付いており、インターネット上のアドレスへの ping も通ります。このLXCのネットワークは別の仕組み(ifupdown)で設定しているため、networkd の失敗は通信に影響していませんでした。こちらは別件だったので、後で触れます。

Unbound 自身に聞いてみる

外には出られる。Unbound のプロセスも動いている。そこで、LXC の中から Unbound に直接問い合わせました。

pct exec 101 -- dig @127.0.0.1 example.com
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 40877
;; Query time: 2 msec

結果は SERVFAIL でした。ここで引っかかったのが、Query time: 2 msec です。

本当に外部の DNS サーバーへ問い合わせて失敗したのなら、少なくとも数十ミリ秒はかかるはずです。2ミリ秒で返ってくるということは、Unbound は外に問い合わせることすらせず、失敗を返していることになります。

原因は Unbound の infra キャッシュ

Unbound は、問い合わせ先のサーバーごとに「応答があったか」「どれくらいで返ってきたか」を記録しています。これを infra キャッシュと呼びます。

応答しなかったサーバーは「応答しない」と記録され、しばらくの間は問い合わせの対象から外されます。止まっているサーバーに何度も問い合わせて、時間を無駄にしないための仕組みです。記録の有効期間は infra-host-ttl で決まり、既定値は900秒(15分)です。

今回の流れに当てはめると、こうなります。

順番起きたこと
1DNS の LXC が先に起動し、Unbound が動き出す
2ルーターがまだ動いていないため、外部への問い合わせがすべてタイムアウトする
3Unbound が、問い合わせ先を「応答しない」と記録する
4ルーターを起動し、通信が回復する
5記録が残っているため、Unbound は外に問い合わせず、SERVFAIL を即座に返す

ネットワークは直っているのに、Unbound の記憶だけが障害中のままという状態でした。Unbound を再起動して記録を消すと、すぐに名前解決できるようになりました。

実務でもそういえばあった

ここで思い出したのが、少し前に実務で見たパケットキャプチャです。Unbound を使っている DNS サーバーで、クライアントに Server failure が返っている場面がありました。
そのときは原因を追い切れていませんでしたが、インターネット向けの通信ができなくなる構成変更をしていたりしたので、今回と同じだった可能性があります。

ただし、SERVFAIL は「最終的に答えが得られなかった」という結果を示すだけで、原因はいくつもあります。キャプチャで見分けるなら、応答までの時間と、その間に Unbound が外へ問い合わせているかが手がかりになります。

見えたもの疑う原因
数ミリ秒で SERVFAIL。外への問い合わせが出ていないinfra キャッシュ(今回と同じ)
数秒待って SERVFAIL。外への問い合わせに応答がない上位のサーバーまでの経路やファイアウォール
特定のドメインだけ SERVFAILDNSSEC の検証失敗など(dig +cd で成功するなら DNSSEC)

比較のため、DNSSEC の署名がわざと壊されているテスト用のドメイン(dnssec-failed.org)に問い合わせてみました。Debian の Unbound は、何も設定しなくても DNSSEC の検証が有効になっているため、このドメインは必ず SERVFAIL になります。

;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 24980
;; Query time: 1303 msec

同じ SERVFAIL でも、こちらは1,303ミリ秒かかっています。実際に外部の権威サーバーへ問い合わせ、署名を確かめたうえで失敗しているからです。今回の障害の2ミリ秒とは、桁が3つ違います。応答までの時間を見れば、Unbound が外に問い合わせたのかどうかを比較し、判断材料にできます。

どうしておくべきか

DNS より先にルーターを起動する

根本は起動順です。Proxmox では、自動起動と起動順を次のように設定できます。order の小さいものから順に起動し、up は次を起動するまでの待ち時間(秒)です。

qm set 100 --onboot 1 --startup order=1,up=30
pct set 101 --onboot 1 --startup order=2,up=10

復旧後は infra キャッシュだけを消す

Unbound 全体を再起動しなくても、infra キャッシュだけを消せます。通常のキャッシュは残るので、本番環境ではこちらの方が影響を抑えられます。

unbound-control flush_infra all

設定で備える

server:
    infra-keep-probing: yes
    log-servfail: yes

infra-keep-probing を有効にすると、「応答しない」と記録したサーバーにも問い合わせを試み続け、回復に早く気づけるようになります。ネットワークの瞬断が起こりうる環境では入れておく価値があります。log-servfail は、SERVFAIL を返した理由をログに残す設定です。原因の分からない SERVFAIL を追うときの足がかりになります。

もう1つの失敗は別件だった

最初に見つけた systemd-networkd の失敗は、ログに次のように出ていました。

Failed to set up mount namespacing: /run/systemd/unit-root/proc: Permission denied

ほかの LXC と設定を比べると、DNS の LXC だけ nesting が無効でした。Proxmox の更新で LXC への制限が厳しくなり、systemd がサービスを起動するときに使う仕組みが許可されなくなったようです。pct set 101 --features nesting=1 で有効にし、再起動して解消しました。

まとめ

今回の事象は、プロセスが動いていることと、サービスが使えることは違うという話でした。Unbound は起動していて、ネットワークも通っていた。それでも名前解決はできなかった。

自宅ネットワーク基本設計書では、監視項目に「名前解決の応答」を入れています。リンクの状態やプロセスの死活だけでは、今回のような障害を検知できないからです。設計で書いていた理由を、自分の環境で実際に確かめる形になりました。

もう1つの教訓は、どちらの問題もほかのサーバーとの設定の違いが原因だったことです。自動起動の設定も、nesting も、DNS の LXC だけが違っていました。普段は表に出ず、再起動や更新のときに初めて顔を出す種類の問題です。

\ 最新情報をチェック /

  • X

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

PAGE TOP