Skip to content
Published on

DNSトラブルシューティング完全ガイド - 基礎からKubernetesまで

Authors
  • Name
    Twitter

1. DNSとは何か

DNS(Domain Name System)は、人間が読めるドメイン名(例:example.com)を、コンピュータが通信に使用するIPアドレス(例:93.184.216.34)に変換する分散階層型ネーミングシステムです。インターネットの電話帳とも呼ばれ、ほぼすべてのネットワーク通信の最初のステップを担っています。

1.1 DNSが重要な理由

  • Webブラウザの HTTP リクエスト、API呼び出し、メール送信など、ほぼすべてのネットワーク操作がDNSルックアップから始まります。
  • DNSの障害はサービス全体の障害に波及する可能性があります。
  • マイクロサービス環境では、サービスディスカバリの中核的な役割を果たします。

2. DNS名前解決プロセス

クライアントがドメイン名を入力すると、以下のステップでIPアドレスを取得します。

2.1 全体の流れ

1. クライアント → ローカルDNSキャッシュ確認(/etc/hostsを含む)
2. ローカルキャッシュミス → 再帰リゾルバ(ISPまたは設定されたDNSサーバ)に問い合わせ
3. 再帰リゾルバ → ルートネームサーバ(.)に問い合わせ
4. ルートNSTLDネームサーバ(.com、.netなど)を応答
5. TLD NS → 権威ネームサーバを応答
6. 権威NS → 最終的なIPアドレスを応答
7. 再帰リゾルバ → 結果をキャッシュしてクライアントに応答

2.2 再帰的(Recursive)vs 反復的(Iterative)問い合わせ

# dig +traceで再帰的な解決を追跡
$ dig +trace example.com

; <<>> DiG 9.18.18 <<>> +trace example.com
;; global options: +cmd
.                       518400  IN      NS      a.root-servers.net.
.                       518400  IN      NS      b.root-servers.net.
;; Received 239 bytes from 127.0.0.53#53(127.0.0.53) in 1 ms

com.                    172800  IN      NS      a.gtld-servers.net.
com.                    172800  IN      NS      b.gtld-servers.net.
;; Received 1170 bytes from 198.41.0.4#53(a.root-servers.net) in 23 ms

example.com.            172800  IN      NS      a.iana-servers.net.
example.com.            172800  IN      NS      b.iana-servers.net.
;; Received 356 bytes from 192.5.6.30#53(a.gtld-servers.net) in 15 ms

example.com.            86400   IN      A       93.184.216.34
;; Received 56 bytes from 199.43.135.53#53(a.iana-servers.net) in 78 ms

2.3 DNSレコードの種類

レコード説明
AIPv4アドレスマッピングexample.com → 93.184.216.34
AAAAIPv6アドレスマッピングexample.com → 2606:2800:220:1:...
CNAME正規名(エイリアス)www.example.com → example.com
MXメールサーバexample.com → mail.example.com
NSネームサーバ委任example.com → ns1.example.com
TXTテキストレコード(SPF、DKIMなど)v=spf1 include:...
SRVサービスロケータ_http._tcp.example.com
PTR逆引き(IP→ドメイン)34.216.184.93 → example.com
SOA権威開始ゾーン管理メタデータ

3. よくあるDNS問題

3.1 NXDOMAIN(存在しないドメイン)

問い合わせたドメインが存在しない場合に返される応答です。

$ dig nonexistent.example.com

;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 12345
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1

;; QUESTION SECTION:
;nonexistent.example.com.    IN      A

;; AUTHORITY SECTION:
example.com.            900     IN      SOA     ns1.example.com. admin.example.com. 2024010101 3600 900 604800 86400

原因分析:

  • ドメイン名のタイプミス
  • DNSレコードがまだ作成されていない
  • ドメイン登録の有効期限切れ
  • DNS伝播(propagation)が完了していない

3.2 DNSタイムアウト

$ dig @10.0.0.1 example.com +timeout=5

; <<>> DiG 9.18.18 <<>> @10.0.0.1 example.com +timeout=5
; (1 server found)
;; global options: +cmd
;; connection timed out; no servers could be reached

原因分析:

  • DNSサーバがダウンまたはアクセス不可
  • ファイアウォールがUDP/TCPポート53をブロック
  • ネットワーク接続の問題
  • DNSサーバの過負荷

3.3 古い・誤ったレコード(Stale/Wrong Records)

# 期待と異なるIPアドレスが返される場合
$ dig api.myservice.com +short
192.168.1.100    # 期待値: 10.0.1.50

# 複数のDNSサーバで比較確認
$ dig @8.8.8.8 api.myservice.com +short
10.0.1.50
$ dig @1.1.1.1 api.myservice.com +short
10.0.1.50
$ dig @192.168.1.1 api.myservice.com +short
192.168.1.100    # ローカルDNSキャッシュが古い値を返している

4. DNSデバッグツール

4.1 dig(Domain Information Groper)

最も強力で広く使われているDNSデバッグツールです。

# 基本的な問い合わせ
$ dig example.com

# 特定のレコードタイプを問い合わせ
$ dig example.com MX
$ dig example.com AAAA
$ dig example.com TXT

# 簡潔な出力
$ dig example.com +short
93.184.216.34

# 特定のDNSサーバを指定
$ dig @8.8.8.8 example.com

# 逆引き
$ dig -x 93.184.216.34

# すべてのレコードタイプを問い合わせ
$ dig example.com ANY

# 応答時間の確認(Query time)
$ dig example.com | grep "Query time"
;; Query time: 23 msec

# TCPを使用(UDPの代わりに)
$ dig +tcp example.com

# DNSSEC検証
$ dig +dnssec example.com

4.2 nslookup

対話型モードと非対話型モードの両方をサポートします。

# 基本的な問い合わせ
$ nslookup example.com
Server:         127.0.0.53
Address:        127.0.0.53#53

Non-authoritative answer:
Name:   example.com
Address: 93.184.216.34

# 特定のDNSサーバを使用
$ nslookup example.com 8.8.8.8

# 特定のレコードタイプ
$ nslookup -type=MX example.com

# 対話型モード
$ nslookup
> set type=NS
> example.com
Server:         127.0.0.53
Address:        127.0.0.53#53

Non-authoritative answer:
example.com     nameserver = a.iana-servers.net.
example.com     nameserver = b.iana-servers.net.
> exit

4.3 host

簡潔な出力を提供する軽量ツールです。

# 基本的な問い合わせ
$ host example.com
example.com has address 93.184.216.34
example.com has IPv6 address 2606:2800:220:1:248:1893:25c8:1946
example.com mail is handled by 0 .

# 逆引き
$ host 93.184.216.34
34.216.184.93.in-addr.arpa domain name pointer example.com.

# 特定のレコードタイプ
$ host -t NS example.com
example.com name server a.iana-servers.net.
example.com name server b.iana-servers.net.

# 詳細な出力
$ host -v example.com

4.4 drill

DNSSEC対応が強化されたツールです(ldnsパッケージ)。

# 基本的な問い合わせ
$ drill example.com
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 54321
;; QUESTION SECTION:
;; example.com.   IN      A

;; ANSWER SECTION:
example.com.      86400   IN      A       93.184.216.34

# DNSSEC追跡
$ drill -DT example.com

# 特定のサーバに問い合わせ
$ drill @8.8.8.8 example.com

5. DNSキャッシュの問題

5.1 TTL(Time To Live)

# TTL値の確認
$ dig example.com

;; ANSWER SECTION:
example.com.            86400   IN      A       93.184.216.34
#                       ^^^^^ TTL: 86400秒 = 24時間

TTLが長いと、DNSの変更が伝播するまでに時間がかかります。DNSマイグレーション前にはTTLを事前に短くしておくことをお勧めします。

# TTL戦略の例
# 1. マイグレーション24時間前: TTLを300秒(5分)に短縮
# 2. マイグレーション実行: IPアドレスを変更
# 3. 伝播完了を確認後: TTLを元の値に復元

5.2 ネガティブキャッシュ(Negative Caching)

NXDOMAIN応答もキャッシュされます。SOAレコードのMINIMUMフィールドがネガティブキャッシュTTLを決定します。

$ dig example.com SOA

;; ANSWER SECTION:
example.com.    86400   IN      SOA     ns1.example.com. admin.example.com. (
                                        2024010101 ; Serial
                                        3600       ; Refresh
                                        900        ; Retry
                                        604800     ; Expire
                                        86400 )    ; Minimum TTL(ネガティブキャッシュTTL)

5.3 ローカルDNSキャッシュの管理

# Linux: systemd-resolvedのキャッシュ統計を確認
$ resolvectl statistics
Current Cache Size: 152
Cache Hits: 1234
Cache Misses: 567

# Linux: systemd-resolvedのキャッシュをフラッシュ
$ sudo resolvectl flush-caches

# macOS: DNSキャッシュをフラッシュ
$ sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder

# Windows: DNSキャッシュをフラッシュ
> ipconfig /flushdns

6. resolv.confとnsswitch.confの設定

6.1 /etc/resolv.conf

$ cat /etc/resolv.conf
# DNSサーバ設定(最大3つ)
nameserver 8.8.8.8
nameserver 8.8.4.4
nameserver 1.1.1.1

# デフォルト検索ドメイン
search mycompany.com prod.mycompany.com

# オプション
options timeout:2        # タイムアウト2秒
options attempts:3       # リトライ3回
options ndots:5          # FQDN判定基準(下記で詳細説明)
options rotate           # DNSサーバのラウンドロビン
options edns0            # EDNS0を有効化

主要な設定の説明:

  • nameserver:使用するDNSサーバ(順番に試行、最大3つ)
  • search:短いホスト名に自動的に付加するドメインのリスト
  • domain:searchと似ているが、1つのドメインのみ指定
  • options ndots:n:ドット(.)がn個未満の名前は、searchドメインを先に試行

6.2 /etc/nsswitch.conf

名前解決の順序を制御します。

$ grep hosts /etc/nsswitch.conf
hosts:          files dns myhostname

# files = /etc/hostsファイルを最初に確認
# dns = DNSサーバに問い合わせ
# myhostname = ローカルホスト名を解決(systemd)
# /etc/hostsファイルの例
$ cat /etc/hosts
127.0.0.1       localhost
127.0.1.1       myserver
10.0.1.50       api.internal.mycompany.com api-internal
192.168.1.100   db-master.mycompany.com

6.3 systemd-resolvedの確認

# 現在のDNS設定を確認
$ resolvectl status
Global
       Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub

Link 2 (eth0)
    Current Scopes: DNS
         Protocols: +DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 8.8.8.8
       DNS Servers: 8.8.8.8 8.8.4.4

# 特定のドメインの名前解決テスト
$ resolvectl query example.com
example.com: 93.184.216.34           -- link: eth0
             2606:2800:220:1:248:1893:25c8:1946 -- link: eth0

7. KubernetesにおけるCoreDNSトラブルシューティング

7.1 CoreDNSアーキテクチャ

Kubernetesクラスタ内で、CoreDNSはサービスディスカバリを担当します。すべてのPodからのDNS問い合わせはCoreDNSを通じて処理されます。

# CoreDNS Podの状態確認
$ kubectl get pods -n kube-system -l k8s-app=kube-dns
NAME                       READY   STATUS    RESTARTS   AGE
coredns-5d78c9869d-abc12   1/1     Running   0          7d
coredns-5d78c9869d-def34   1/1     Running   0          7d

# CoreDNSサービスの確認
$ kubectl get svc -n kube-system kube-dns
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   30d

7.2 CoreDNS Corefileの確認

$ kubectl get configmap coredns -n kube-system -o yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health {
           lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        prometheus :9153
        forward . /etc/resolv.conf {
           max_concurrent 1000
        }
        cache 30
        loop
        reload
        loadbalance
    }

7.3 CoreDNSログの確認

# CoreDNSのログを確認
$ kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
[INFO] 10.244.0.15:45678 - 12345 "A IN my-service.default.svc.cluster.local. udp 54 false 512" NOERROR qr,aa,rd 106 0.000234s
[INFO] 10.244.0.15:45679 - 12346 "A IN external-api.com. udp 34 false 512" NOERROR qr,rd,ra 62 0.023456s

# logプラグインを有効化(Corefileにlogを追加)
# .:53 {
#     log
#     errors
#     ...
# }

7.4 Pod内でのDNSデバッグ

# デバッグ用Podを作成
$ kubectl run dns-debug --image=nicolaka/netshoot --rm -it --restart=Never -- bash

# Pod内でDNS解決をテスト
bash-5.1# nslookup kubernetes.default.svc.cluster.local
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   kubernetes.default.svc.cluster.local
Address: 10.96.0.1

# digで詳細確認
bash-5.1# dig kubernetes.default.svc.cluster.local

;; ANSWER SECTION:
kubernetes.default.svc.cluster.local. 30 IN A   10.96.0.1

;; Query time: 1 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)

# 外部ドメインの名前解決を確認
bash-5.1# dig example.com +short
93.184.216.34

# PodのDNS設定を確認
bash-5.1# cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

8. ndots設定とSearch Domain

8.1 ndotsの動作原理

ndotsオプションは、問い合わせ名に含まれるドット(.)の数がこの値未満の場合、searchドメインを先に付加して問い合わせます。

# Kubernetesのデフォルト設定: ndots:5
# search default.svc.cluster.local svc.cluster.local cluster.local

# "api.example.com" を問い合わせた場合(ドット2個 < ndots 5)
# 実際の問い合わせ順序:
1. api.example.com.default.svc.cluster.local    → NXDOMAIN
2. api.example.com.svc.cluster.local            → NXDOMAIN
3. api.example.com.cluster.local                → NXDOMAIN
4. api.example.com.                             → 成功!

これにより、外部ドメインの問い合わせ時に不要なDNSクエリが3回追加で発生します。

8.2 ndotsの最適化

# Pod specでdnsConfigからndotsを調整
apiVersion: v1
kind: Pod
metadata:
  name: optimized-pod
spec:
  containers:
    - name: app
      image: myapp:latest
  dnsConfig:
    options:
      - name: ndots
        value: '2'
# FQDNを使用して不要な問い合わせを回避(末尾にドットを追加)
# 非効率:
$ dig api.example.com   # ndotsにより複数回問い合わせ

# 効率的:
$ dig api.example.com.  # FQDNとして直接問い合わせ(trailing dot)

8.3 dnsPolicyオプション

# ClusterFirst(デフォルト): CoreDNSを最初に使用
apiVersion: v1
kind: Pod
spec:
  dnsPolicy: ClusterFirst

# Default: ノードのDNS設定をそのまま使用
spec:
  dnsPolicy: Default

# None: dnsConfigから直接設定
spec:
  dnsPolicy: None
  dnsConfig:
    nameservers:
    - 8.8.8.8
    - 1.1.1.1
    searches:
    - my-namespace.svc.cluster.local
    - svc.cluster.local
    options:
    - name: ndots
      value: "2"

9. 実践デバッグシナリオ

9.1 シナリオ1: サービス間通信の失敗

# 症状: Pod AからPod Bのサービスへの接続が失敗
$ kubectl exec pod-a -- curl http://my-service:8080
curl: (6) Could not resolve host: my-service

# Step 1: PodのDNS設定を確認
$ kubectl exec pod-a -- cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

# Step 2: CoreDNSに直接問い合わせ
$ kubectl exec pod-a -- dig @10.96.0.10 my-service.default.svc.cluster.local
;; status: NXDOMAIN

# Step 3: サービスの存在を確認
$ kubectl get svc my-service -n default
Error from server (NotFound): services "my-service" not found

# Step 4: 正しいネームスペースを確認
$ kubectl get svc --all-namespaces | grep my-service
production   my-service   ClusterIP   10.96.45.123   <none>   8080/TCP   5d

# 解決: ネームスペースを含むFQDNを使用
$ kubectl exec pod-a -- curl http://my-service.production.svc.cluster.local:8080

9.2 シナリオ2: 外部ドメイン名前解決の失敗

# 症状: Podから外部API呼び出しが失敗
$ kubectl exec my-pod -- curl https://api.external.com
curl: (6) Could not resolve host: api.external.com

# Step 1: CoreDNSが内部クエリで正常か確認
$ kubectl exec my-pod -- dig @10.96.0.10 kubernetes.default.svc.cluster.local +short
10.96.0.1    # 内部DNSは正常

# Step 2: CoreDNSのアップストリームフォワーディングを確認
$ kubectl exec my-pod -- dig @10.96.0.10 api.external.com
;; status: SERVFAIL

# Step 3: CoreDNSのログを確認
$ kubectl logs -n kube-system -l k8s-app=kube-dns | grep "api.external.com"
[ERROR] plugin/forward: no nameservers found

# Step 4: CoreDNSのforward設定を確認
$ kubectl get configmap coredns -n kube-system -o jsonpath='{.data.Corefile}'
# forward . /etc/resolv.conf を確認

# Step 5: CoreDNS Podのresolv.confを確認
$ kubectl exec -n kube-system coredns-5d78c9869d-abc12 -- cat /etc/resolv.conf
nameserver 169.254.169.253   # クラウドDNSにアクセスできない可能性

# 解決: Corefileでforward先を明示的に指定
# forward . 8.8.8.8 8.8.4.4

9.3 シナリオ3: 間欠的なDNSタイムアウト

# 症状: DNS問い合わせが間欠的に5秒以上かかる
$ time dig @10.96.0.10 example.com
;; Query time: 5003 msec    # 5秒タイムアウト後にリトライ

# 原因: Linux conntrackのレースコンディション(DNAT + UDP)
# UDPのDNSパケットがconntrackテーブルで衝突する可能性

# 確認: conntrackテーブルの状態
$ sudo conntrack -S
cpu=0           found=0 invalid=1523 insert=0 insert_failed=156 drop=156
#                                                ^^^^^^^^^^^^^ insert失敗があると問題

# 解決方法1: TCP DNSを使用
# CoreDNS Corefileにforce_tcpオプションを追加

# 解決方法2: NodeLocal DNSCacheをデプロイ
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml

# 解決方法3: Podでsingle-request-reopenオプションを使用
apiVersion: v1
kind: Pod
spec:
  dnsConfig:
    options:
    - name: single-request-reopen
      value: ""

9.4 シナリオ4: DNS伝播遅延の確認

# DNS変更後、複数のサーバで伝播状態を確認
$ for dns in 8.8.8.8 1.1.1.1 9.9.9.9 208.67.222.222; do
    echo "=== $dns ==="
    dig @$dns api.myservice.com +short +timeout=3
done

=== 8.8.8.8 ===
10.0.1.50
=== 1.1.1.1 ===
10.0.1.50
=== 9.9.9.9 ===
192.168.1.100    # まだ古いレコード
=== 208.67.222.222 ===
192.168.1.100    # まだ古いレコード

# TTLを確認してキャッシュの有効期限を予測
$ dig @9.9.9.9 api.myservice.com | grep -A1 "ANSWER SECTION"
;; ANSWER SECTION:
api.myservice.com.    1423    IN      A       192.168.1.100
#                     ^^^^ 残りTTL: 約24分後にキャッシュ期限切れ

10. 便利なDNSデバッグワンライナー集

# 1. DNS応答時間のベンチマーク
$ for i in $(seq 1 10); do dig example.com | grep "Query time"; done

# 2. 複数ドメインの一括問い合わせ
$ for domain in api.example.com web.example.com db.example.com; do
    echo "$domain: $(dig +short $domain)"
done

# 3. DNSレコード変更のモニタリング
$ watch -n 5 "dig +short api.myservice.com @8.8.8.8"

# 4. 逆引きDNSの一括確認
$ for ip in 10.0.1.{1..10}; do
    result=$(dig +short -x $ip)
    echo "$ip -> ${result:-NO PTR}"
done

# 5. DNSSEC検証状態の確認
$ dig +dnssec +short example.com
93.184.216.34
A 13 2 86400 20240315000000 20240301000000 12345 example.com. <base64_signature>

# 6. Kubernetesの全サービスのDNS解決を確認
$ kubectl get svc --all-namespaces -o jsonpath='{range .items[*]}{.metadata.name}.{.metadata.namespace}.svc.cluster.local{"\n"}{end}' | \
  while read fqdn; do
    result=$(kubectl exec dns-debug -- dig +short $fqdn 2>/dev/null)
    echo "$fqdn -> ${result:-FAILED}"
  done

11. まとめとチェックリスト

DNS問題が発生した場合、以下の順序で診断します。

  1. /etc/resolv.confの設定を確認(nameserver、search、ndots)
  2. digまたはnslookupで基本的なDNS問い合わせをテスト
  3. 特定のDNSサーバを指定して問い合わせ(dig @8.8.8.8
  4. +traceオプションで完全な名前解決パスを追跡
  5. TTLを確認してキャッシュの問題かどうかを判断
  6. Kubernetes環境であればCoreDNS Podの状態とログを確認
  7. ndotsとsearchドメインの設定がパフォーマンスに与える影響を検討
  8. conntrack関連の間欠的な問題はNodeLocal DNSCacheの導入を検討

DNSはネットワーク問題の根本原因であることが非常に多いです。体系的なデバッグの習慣を身につければ、障害対応の時間を大幅に短縮できます。