背景と必要性:静的なresolv.confからの脱却
長年にわたり、LinuxはDHCPリースや静的にハードコードされたネームサーバーを記録したプレーンテキストファイル /etc/resolv.conf に依存してきました。しかし、そのシンプルな仕組みは現代のインフラでは通用しなくなっています。静的な構成ではクエリ結果をローカルにキャッシュできず、VPNトンネルを介したスプリットホライズンルーティングも識別できず、すべての名前解決クエリを暗号化されていない平文のUDPとして信頼できないネットワークへ送信してしまいます。
systemd-resolved はこれらのボトルネックを解消します。これは 127.0.0.53:53 にバインドされたローカルスタブリゾルバとして動作します。レコードをRAM上にキャッシュすることで、2回目以降の名前解決のレイテンシを実質ゼロにまで短縮します。さらに、DNSSEC検証の処理、インターフェース固有のトンネル(WireGuardやOpenVPNなど)へのクエリ振り分け、ポート853を使用したアップストリームトラフィックのTLSによるカプセル化などを担います。
DNS設定の変更は、容易にホストを孤立させるリスクを伴います。ある本番環境の12ノードへのステージング展開時、テスト不足のDoT変更によって外部APIの名前解決がブロックされ、数分でメトリクスエージェントが停止した事例がありました。リゾルバの変更は、環境全体へ展開する前に必ずカナリア環境(検証機)でテストしてください。
インストール:systemd-resolvedを確実に動作させる
Ubuntu(18.04以降)やDebian 12などのディストリビューションでは、systemd-resolved が標準でインストールされています。ただし、最小構成のクラウドイメージやLXCコンテナ、不要なパッケージが削られたベース環境では含まれていない場合があります。バイナリが存在しない場合はパッケージをインストールします:
# Debian/Ubuntu系システム
sudo apt-get update
sudo apt-get install systemd-resolved -y
# RHEL/Fedora/Rocky Linux
sudo dnf install systemd-resolved -y
デーモンを起動し、システムの起動時に自動実行されるよう設定します:
sudo systemctl enable --now systemd-resolved.service
標準Cライブラリの名前解決ルーチン(glibcの getaddrinfo)は、現在も /etc/resolv.conf を参照します。シンボリックリンクを作成して、このパスがsystemdの動的スタブファイルを直接指すように設定します:
sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
リンク先がスタブリゾルバを向いていることを確認します:
cat /etc/resolv.conf
ファイル内に nameserver 127.0.0.53 および options edns0 trust-ad が表示されていれば正常です。
設定:DNSキャッシュ、DoT、フォールバック
デーモンのデフォルトパラメータは /etc/systemd/resolved.conf に配置されています。このファイルを直接編集してはいけません。パッケージのアップデート時に変更が上書きされてしまうためです。代わりに、/etc/systemd/resolved.conf.d/ 配下にモジュール化されたオーバーライド用ファイルを配置します。
1. インメモリDNSキャッシュのセットアップ
ローカルキャッシュを使用することで、パブリックネットワーク経由のラウンドトリップを削減できます。マイクロサービスの各API呼び出しから40msを削るだけでも、累積リクエスト時間は大幅に短縮されます。専用のドロップイン設定ファイルを作成します:
sudo mkdir -p /etc/systemd/resolved.conf.d
sudo tee /etc/systemd/resolved.conf.d/01-cache.conf << 'EOF'
[Resolve]
DNSCache=yes
CacheFromLocalhost=no
EOF
DNSCache=yes は、権威DNSのTTL制限に基づいて、ポジティブおよびネガティブの名前解決結果をRAMに保持するようリゾルバに指示します。同一マシン上でdnsmasqやunboundなどの中間プロキシを実行している場合、CacheFromLocalhost=no を設定しておくことで二重キャッシュを防ぐことができます。
2. DNS-over-TLS(DoT)の強制
標準のDNSは、パブリックネットワーク上でホスト名を平文で送信するため、中継ルーターやISPから簡単に読み取られてしまいます。DNS-over-TLSは、TCPポート853上のTLSを利用して名前解決を安全に保護します。アップストリームリゾルバとそのSNI(Server Name Indication)ホスト名を指定します:
sudo tee /etc/systemd/resolved.conf.d/02-dot.conf << 'EOF'
[Resolve]
DNS=1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com 2606:4700:4700::1111#cloudflare-dns.com
FallbackDNS=9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net
DNSOverTLS=yes
DNSSEC=allow-downgrade
EOF
この構成の信頼性を維持するために、3つの主要なディレクティブが機能しています:
#cloudflare-dns.com: SNIホスト名を指定します。リゾルバはこの値をアップストリームサーバーのTLS証明書と照合して検証します。DNSOverTLS=yes: TLS通信を必須にします。ファイアウォールによってポート853が遮断されている場合、平文へのフォールバックは行われずクエリは完全に失敗します。一時的なネットワーク障害時に非暗号化通信へのフォールバックを許容する場合は、opportunisticに設定してください。DNSSEC=allow-downgrade: ドメインがDNSSEC署名を提供している場合は検証を行いますが、署名されていないドメインへの名前解決リクエストは破棄せずに処理を継続します。
新しい設定を適用します:
sudo systemctl restart systemd-resolved
検証とモニタリング:キャッシュの確認とトラブルシューティング
サービスが正常に再起動したからといって、DNS設定が正しく機能していると思い込まないでください。実際の稼働ステータスとパケットの流れを確認することが重要です。
アクティブなアップストリームとステータスの確認
resolvectl status を実行して、リンク設定とアクティブなネームサーバーを確認します:
resolvectl status
Global セクションを確認し、DNSOverTLS: yes がアクティブになっており、設定したアップストリームがリストに表示されているか確認します。
レイテンシとキャッシュヒットのテスト
dig コマンドを使用してキャッシュ効率をベンチマークします:
# 1回目のクエリ:キャッシュミス(WANを経由)
dig @127.0.0.53 kernel.org | grep "Query time"
# 2回目のクエリ:キャッシュヒット(ローカルRAMから即座に取得)
dig @127.0.0.53 kernel.org | grep "Query time"
1回目のリクエストでは、TLS経由でアップストリームプロバイダーに問い合わせるため、通常35msから75ms程度かかります。一方、2回目のリクエストは 0 msec または 1 msec で解決されます。
キャッシュ統計情報の確認
resolvectl statistics を実行してキャッシュの状態を把握します:
resolvectl statistics
このコマンドは、現在のアクティブなキャッシュエントリ数、トランザクション数、ヒット数、キャッシュミス数を出力します。健全なノードであれば、通常のWebトラフィックやバックグラウンドジョブの実行中に70%以上のヒット率を維持することが一般的です。
古いレコードのフラッシュ(破棄)
レコードの変更後やインフラの移行時、サービスを再起動せずにローカルキャッシュをパージします:
resolvectl flush-caches
実践的なトラブルシューティングシナリオ
名前解決が失敗した場合は、以下の的を絞ったチェック手順で問題を切り分けます:
1. 送信ポート853の疎通テスト
厳格なDoTの運用には、ポート853に対する送信TCPアクセスが必要です。クラウドのセキュリティグループ、企業ファイアウォール、ISPの制限によってこのポートがブロックされるケースは珍しくありません。接続性を直接テストします:
nc -zv -w3 1.1.1.1 853
3秒後にハンドシェイクがタイムアウトする場合は、ファイアウォールの外向き(Egress)ルールを緩和してください。アップストリーム側でポート853を開放できない場合は、ドロップイン設定ファイル内の DNSOverTLS=yes を DNSOverTLS=opportunistic に戻します。
2. 上書きされたresolv.confの修復
DHCPクライアント、netplan、NetworkManagerは、/etc/resolv.conf の制御権をめぐって競合することがよくあります。inodeの状態を確認します:
ls -la /etc/resolv.conf
/etc/resolv.conf が /run/systemd/resolve/stub-resolv.conf へのシンボリックリンクではなく通常の通常ファイルになっている場合、外部ツールによって上書きされています。シンボリックリンクを再作成し、/etc/NetworkManager/NetworkManager.conf の [main] セクションに dns=systemd-resolved を追記して、NetworkManagerがDNS制御をsystemdに委譲するよう設定します。
3. ライブクエリパケットのスニッフィング
journalctlを使用して名前解決のログをリアルタイムで追跡します:
journalctl -u systemd-resolved -f --no-tail
実際のネットワークトラフィックを確認するには、tcpdump を実行します。クエリが平文のUDPポート53ではなく、TCPポート853経由で外部インターフェースから送信されていることを確認します:
sudo tcpdump -ni any port 853 or port 53
内部アプリケーションはローカルの 127.0.0.53:53 に問い合わせを行います。一方、外部へのアウトバウンドトラフィックは、TLSセッションで保護されたポート853経由でアップストリームIPのみを対象としている必要があります。

