はじめに:なぜ、EC2の中の「169.254.169.253」や「ベースVPC + 2」を知る必要があるのか
インフラエンジニアとして現場を渡り歩いていると、避けて通れないのが「名前解決(DNS)」にまつわるトラブルだ。
「staging環境から本番のWeb APIへ接続できない」「コンテナが突如として Temporary failure in name resolution を吐いて死ぬ」「なぜか特定のマイクロサービスだけ名前解決に2秒もかかっている」――。
こうした夜間障害の対応で、パケットキャプチャ(tcpdump)を開き、DNSクエリがどこで迷子になっているかを突き止める瞬間は、SREとしての腕の見せ所である。そして、その原因の多くは、AWSのVPC内部で静かに、しかし圧倒的な信頼性で稼働している AmazonProvidedDNS(AmazonDNS) の仕組みを誤解していることに起因する。
教科書には「VPCのベースIPに2を足したアドレスがDNSサーバーです」とサラリと書かれている。しかし、実務でWeb APIを設計し、K8sクラスターを運用する私たちにとって、その背後でパケットがどのようにルーティングされ、どのようにスケーリング限界を突破しているのかを知ることは、強固なクラウドアーキテクチャを組む上で必須の教養だ。
今回は、このAWS VPC内部におけるDNS解決のメカニズムを、パケットの挙動と現場のトラブルシューティングの視点から徹底的に紐解いていこう。
—
1. AmazonProvidedDNS(AmazonDNS)の正体と「ベースVPC + 2」の仕組み
VPC内IPアドレスの魔術
AWSでVPCを作成すると、必ずCIDRブロック(例えば 10.0.0.0/16)を割り当てる。この時、AWSのネットワーク仮想化レイヤー(Nitroシステムや従来のホスト)は、そのVPCのネットワークアドレスの先頭から2番目のIPアドレスを、内部DNSリゾルバー用の予約アドレスとして強制的に割り当てる。
これが俗に言う 「ベースVPC + 2」 だ。
- VPC CIDRが
10.0.0.0/16の場合:DNSサーバーIPは10.0.0.2 - VPC CIDRが
192.168.10.0/24の場合:DNSサーバーIPは192.168.10.2
このIPアドレスは、実体がどこかのEC2インスタンスとして動いているわけではない。AWSの物理ホスト群に分散配置された、可用性・スケーラビリティともに極めて高いハイパーバイザー直結のバーチャルリゾルバーである。
さらに、AWSはすべてのEC2インスタンスに対して、OSのネットワーク設定(DHCP)を通じて、この「ベースVPC + 2」のIPアドレスをプライマリDNSサーバーとして自動的に強制配布している。また、これとは別に、すべてのVPCの各アベイラビリティーゾーン(AZ)の予約IPアドレスのベースからも、常に *.2 がDNSとして応答するようルーティングされている。
もう一つの顔:169.254.169.253
AWSインフラに親しんだエンジニアなら、リンクローカルアドレスである 169.254.169.253 もお馴染みだろう。
実は、AmazonProvidedDNSはこのマジックIPアドレス(リンクローカルアドレス)経由でもヒットする。インスタンスメタデータサービス(IMDS)と同じ発想で、インスタンスが自身のVPC内のネットワーク設定を意識することなく、常に同じIPアドレスへ向けてDNSクエリを投げれば、AWSがよしなに自AZ内のAmazonDNSリゾルバーへルーティングしてくれる仕組みになっている。
/etc/resolv.conf を覗いてみてほしい。大抵の場合、以下のように記述されているはずだ。
nameserver 10.0.0.2
search ap-northeast-1.compute.internal
※環境によっては nameserver 169.254.169.253 が設定されているケースや、systemd-resolved経由でローカルスタブリゾルバー(127.0.0.53)を挟みつつ、最終的に 10.0.0.2 へ転送しているケースもある。
—
2. パケットはどこを走る?:VPC内部の名前解決フロー
では、アプリケーションから api.internal.mycompany.com のようなホスト名に対してリクエストが飛んだ時、パケットとDNSリゾルバーの間で何が起きているのか。そのシーケンスを追ってみよう。
[App / Client] [OS Resolver] [AmazonDNS (Base+2)] [Route 53 / Resolver]
| | | |
|-- getaddrinfo() ------->| | |
| (名前解決要求) |-- UDP/53 クエリ送信 ------->| |
| | (to 10.0.0.2) | |
| | |-- (1) VPC内ホスト名か判定 |
| | |-- (2) Private Hosted Zone|
| | |-- (3) 外部ドメインなら ---->|
| | | インターネットへ |
| |<-- UDP/53 レスポンス -------|<-------------------------|
|<-- IPアドレス返却 ------| | |
| | | |
1. アプリケーション層からのクエリ発火
例えば、Pythonの requests や Node.jsの fetch、あるいは単なる curl コマンドが実行され、宛先ホスト名が指定されると、OSのCライブラリが持つ標準リゾルバー(getaddrinfo など)が呼び出される。
2. AmazonDNSへのパケット到達
OSは /etc/resolv.conf に従って、宛先ポート 53(UDP/TCP)へDNSクエリパケットを送信する。
このパケットは、VPCの仮想ルーターを経由して、インスタンスの恩恵を受けることなく一瞬で 10.0.0.2(または 169.254.169.253)へ到達する。この間のレイテンシーは通常、数ミリ秒(多くの場合 1ms 未満)だ。
3. AmazonDNSの判断ロジック(ここが重要)
AmazonDNS(10.0.0.2)は、受け取ったクエリに対して以下の優先順位で名前解決を試みる。
1. VPCのカスタムホスト名 / DHCPオプションセットのドメイン名
インスタンスに付与されたプライベートDNS名(例: ip-10-0-1-50.ap-northeast-1.compute.internal)や、DHCPオプションセットで指定したドメイン名に一致するかをチェックする。
2. Amazon Route 53 プライベートホストゾーン(Private Hosted Zone)
VPCにアタッチされているRoute 53のプライベートホストゾーンにレコードが存在するか確認する。ここが、マイクロサービス間の内部APIルーティングの要となる。
3. インターネット上のパブリックDNS名前解決
上記いずれにもヒットしない場合、AmazonDNSは内部フォワーダーとして機能し、インターネット上の権威DNSサーバーへ再帰的クエリ(Recursive Query)を投げに行き、結果をキャッシュしてクライアントに返す。
—
3. 実務で知るべき「DNSパラメータ」とコンテナ時代の罠
クラウドインフラを運用する上で、AmazonProvidedDNSの仕様上の制限や、OS・ランタイム側の設定パラメータを正しく把握していないと、痛い目を見る。
1. DNSのスケーリング制限(QPS制限)
AmazonProvidedDNSは非常に堅牢だが、EC2インスタンスあたりのクエリ処理能力には制限が存在する。
公式ドキュメントによると、AmazonProvidedDNSはデフォルトで、インスタンスのネットワークインターフェイス(ENI)あたり、最大1,000 Pkts/sec(またはQuery/sec) の制限がかかっている(インスタンスのサイズによってスケーリングするが、単一の巨大なK8sクラスターなどでが一斉に名前解決を始めると、この上限にヒットすることがある)。
【現場の教訓】
Kubernetesクラスター内のCoreDNSが、外部APIや内部サービスへの名前解決をすべて単一のノード経由でAmazonDNSへ丸投げしていると、秒間リクエスト数が跳ね上がった際にスロットリング(パケット破棄)が発生する。これが「原因不明のDNSタイムアウト」の正体だ。
対策として、K8s側で NodeLocal DNSCache を有効化し、各ノードのローカルでキャッシュを持たせる設計が現代のデファクトスタンダードとなっている。
2. /etc/resolv.conf の options チューニング
Linux上のアプリでHTTPリクエストを頻発させる場合、DNSのタイムアウトとリトライの挙動を理解しておく必要がある。
# /etc/resolv.conf の例
nameserver 10.0.0.2
options timeout:2 attempts:3
플
timeout:2: DNSサーバーからの応答がない場合、2秒待ってリトライする。attempts:3: 最大3回試行する。
もしAmazonDNSとの間でパケットロスや一時的な負荷スパイクが発生していると、このデフォルト設定のせいで、名前解決だけで最悪 2秒 × 3回 = 6秒 のブロッキングが発生し、Web APIのクライアント側でコネクションプールの枯渇やGateway Timeout(504)を引き起こす。実務では、アプリケーション側のタイムアウト設計とOSのDNS設定を常にセットで検証すべきだ。
—
4. 実装例:PythonおよびcURLにおけるDNS解決の挙動確認とテスト
それでは、実際にPythonやcURLを用いて、VPC内からAmazonDNSを正しく利用できているか、また名前解決の挙動をテスト・確認するためのコードスニペットを見ていこう。
A. cURLを用いた名前解決のデバッグと速度計測
まずは手元のEC2やコンテナ内から、DNS解決にかかっている時間や、どのIPにヒットしているかをワンライナーで検証する。
# -v (verbose) をつけて、どのIPアドレス(AmazonDNS経由で引いた結果)に対してTCP接続を試みるか確認する
curl -Iv https://api.internal.mycompany.com/healthz
# DNSの解決時間だけを正確に計測したい場合(curlのカスタムフォーマット)
curl -so /dev/null -w "DNS Lookup Time: %{time_namelookup}s\nTotal Time: %{time_total}s\n" https://api.internal.mycompany.com/healthz
*解説*: time_namelookup が異常に長い場合(例: 0.5秒以上など)、AmazonDNSへの負荷や、OS側のリゾルバー(glibc等)の検索ドメイン補完のタイムアウト(searchドメインに存在しないドメインを順番に総当たりで問い合わせているケース)が疑われる。
B. Python (requests / socket) による内部DNSの検証スクリプト
次に、Pythonスクリプトを用いて、明示的にシステムのリゾルバーを通じた名前解決の動作と、ソケットレベルでの接続テストを行う実用的なコードだ。
import socket
import time
import requests
# ターゲットとなる内部APIのホスト名
TARGET_HOST = "api.internal.mycompany.com"
TARGET_URL = f"https://{TARGET_HOST}/healthz"
def check_dns_and_connectivity():
print(f"[*] Target Host: {TARGET_HOST}")
# 1. socket.getaddrinfo を用いたDNS解決テスト
# これにより、AmazonProvidedDNS (10.0.0.2等) を経由した名前解決が正常か確認できる
start_time = time.time()
try:
# AF_INET は IPv4, SOCK_STREAM は TCP を指定
addr_info = socket.getaddrinfo(TARGET_HOST, 443, socket.AF_INET, socket.SOCK_STREAM)
elapsed_dns = time.time() - start_time
# 解決されたIPアドレスのリストを抽出
resolved_ips = [item[4][0] for item in addr_info]
print(f"[+] DNS Resolution SUCCESS ({elapsed_dns:.4f}s)")
print(f" Resolved IPs: {resolved_ips}")
except socket.gaierror as e:
print(f"[-] DNS Resolution FAILED: {e}")
return
# 2. 実際にHTTPリクエストを送信して疎通確認
start_time = time.time()
try:
# timeoutを設定し、ハングアップを防ぐのが実運用における鉄則
response = requests.get(TARGET_URL, timeout=3.0)
elapsed_http = time.time() - start_time
print(f"[+] HTTP Request SUCCESS ({elapsed_http:.4f}s)")
print(f" Status Code: {response.status_code}")
print(f" Response Body: {response.text.strip()}")
except requests.exceptions.Timeout:
print(f"[-] HTTP Request TIMEOUT (Check security groups or network ACLs)")
except requests.exceptions.RequestException as e:
print(f"[-] HTTP Request FAILED: {e}")
if __name__ == "__main__":
check_dns_and_connectivity()
このスクリプトをVPC内のプライベートサブネットに配置されたEC2やECSタスク上で実行することで、DNS解決のレイテンシーと実際の疎通性を同時にテストできる。CI/CDパイプラインのデプロイ後テストや、インフラのキッティング検証によく組み込まれる手法だ。
—
5. まとめ:SREが知るべきAmazonProvidedDNSの要点
AWS VPCの「ベースVPC + 2」で提供されるAmazonProvidedDNSは、私たちが普段意識することのない黒衣のような存在だが、クラウドインフラのパフォーマンスと信頼性を左右する極めて重要な心臓部である。
今回のポイントを最後に振り返っておこう。
1. 「ベースVPC + 2」および 169.254.169.253 はAWSの仮想化レイヤーが生み出す高速な内部DNSリゾルバーであり、高可用性が担保されている。
2. DNSのクエリは、VPC内のカスタムホスト名 → Route 53プライベートホストゾーン → 外部パブリックDNS の順で解決される。
3. コンテナ環境(Kubernetes等)や高負荷なWeb APIサーバー群では、DNSのQPS制限やタイムアウト設定(リトライ・検索ドメインの罠)が障害の原因になり得るため、NodeLocal DNSCacheなどの適切なアーキテクチャ設計が必要不可欠である。
インフラにトラブルが起きたとき、パケットがどこを通り、どのDNSサーバーに問いかけているのかを解剖学的に想像できるかどうかが、プロのエンジニアとそうでない者を分ける分かれ道となる。ぜひ日々の設計やデバッグにこの知見を役立ててほしい。
コメント