Route 53 Resolver、マルチVPC環境で半年運用して踏んだ落とし穴まとめ
「DNSは後回しでいい」と思ってませんか?オンプレ連携後にホスト名が解決できず深夜対応した経験をもとに、ハイブリッドDNS設計で本当に詰まるポイントを共有します。
「DNSなんて設定すれば動く」と思ってた頃の話
オンプレとAWSを繋ぐハイブリッド構成を担当することになったのが約1年前。その時、正直DNSはインフラの「添え物」くらいの認識で、Route 53 Resolverの設計にほとんど時間をかけなかった。結果として、VPN接続後にオンプレのホスト名が解決できないとか、RDSのエンドポイントが特定のVPCから引けないとか、謎のDNSタイムアウトが深夜に発生するとか、そういう地味だけどじわじわ痛い問題が続出した。
今回は、その半年の運用を通じて学んだRoute 53 Resolver DNS設計の実務知見をまとめる。特に2025〜2026年頃にマルチアカウント環境が当たり前になってきた文脈での設計パターンを中心に話す。公式ドキュメントに書いてある教科書的な内容より、「ここで詰まった」「これ最初から知りたかった」を優先していく。
Route 53 Resolverの基本構成をおさらい(でも運用視点で)
まず整理のために基本から。Route 53 Resolverは大きく3つのコンポーネントで成り立っている。
- インバウンドエンドポイント: オンプレやVPN越しの外部リゾルバーからVPC内のRoute 53にDNSクエリを転送する受け口
- アウトバウンドエンドポイント: VPC内のリソースが外部(オンプレDNS等)にクエリを転送するための出口
- フォワーディングルール: 特定のドメイン(例:
corp.example.local)に対してどこに転送するかを定義するルール
図で見るとこうなる。
flowchart TB
subgraph OnPremise["オンプレミス"]
DC_DNS["社内DNSサーバー\n(corp.example.local)"]
DC_Client["社内端末・サーバー"]
end
subgraph AWS_Account["AWSアカウント"]
subgraph VPC_Hub["Hub VPC (10.0.0.0/16)"]
subgraph AZ_A["AZ-a"]
Inbound_A["インバウンド\nエンドポイント\n10.0.1.10"]
Outbound_A["アウトバウンド\nエンドポイント\n10.0.1.20"]
end
subgraph AZ_C["AZ-c"]
Inbound_C["インバウンド\nエンドポイント\n10.0.2.10"]
Outbound_C["アウトバウンド\nエンドポイント\n10.0.2.20"]
end
R53_Resolver["Route 53 Resolver\n(VPC内デフォルト)"]
end
subgraph VPC_App["App VPC (10.1.0.0/16)"]
App_Server["アプリサーバー"]
RDS["RDS\n(xxx.rds.amazonaws.com)"]
end
RAM["Resource Access Manager\n(ルール共有)"]
end
VPN_GW["VPN / Direct Connect"]
DC_Client -->|"corp.example.local?"| DC_DNS
DC_DNS -->|"DNS転送"| Inbound_A
DC_DNS -->|"DNS転送(冗長)"| Inbound_C
Inbound_A --> R53_Resolver
Inbound_C --> R53_Resolver
App_Server -->|"corp.example.local?"| Outbound_A
Outbound_A -->|"転送"| DC_DNS
VPC_Hub <-->|"VPC Peering / TGW"| VPC_App
VPC_Hub <--> VPN_GW
VPN_GW <--> OnPremise
RAM -->|"フォワーディングルール共有"| VPC_App
この図、実際に設計書で最初に書いたものに近い。ただし最初のバージョンは「インバウンドとアウトバウンドを同じサブネットに置いてた」というミスをやらかした。これは後述するが、AZを跨いだ冗長構成を考えると最低2AZに各エンドポイントを配置するのが必須だと痛感した。
実際に踏んだ「エンドポイントIP固定の罠」
インバウンドエンドポイントを作成すると、ENIが各AZに生成されてIPが払い出される。このIPをオンプレのDNSサーバーに「転送先」として登録するわけだが、エンドポイントを削除して再作成するとIPが変わる。当たり前といえば当たり前なんだけど、最初はそれを知らずにエンドポイントを作り直したとき、オンプレDNSの設定を更新し忘れて全社から「AWS環境に繋がらない」という障害になった。
IPを固定したい場合は、エンドポイント作成時に「IPアドレスを手動指定」する必要がある。CLIだとこう。
# インバウンドエンドポイントのIPを明示的に指定して作成
aws route53resolver create-resolver-endpoint \
--creator-request-id "inbound-hub-2026" \
--name "inbound-hub-endpoint" \
--security-group-ids sg-0abc123def456789 \
--direction INBOUND \
--ip-addresses \
SubnetId=subnet-0aaaa1111bbbb2222,Ip=10.0.1.10 \
SubnetId=subnet-0cccc3333dddd4444,Ip=10.0.2.10
これを最初からやっておけばよかった、というのが正直なところ。インフラ変更のたびにオンプレチームへの連絡コストが発生するので、IPを固定するのは初期設計の段階で決めておくべきだった。
マルチアカウント構成でのフォワーディングルール共有設計
2026年現在、AWS Organizationsを使ったマルチアカウント構成がほぼデファクトになってきた。DNS設計でも「どのアカウントにエンドポイントを置いて、どのアカウントがそれを使うか」を最初に整理しないと後で詰む。
うちのチームが採用したのはHub & Spoke型のDNS集約パターン。ネットワーク専用の共有サービスアカウント(Shared Services Account)にインバウンド・アウトバウンドエンドポイントを集約して、各スポークVPCはRAM(Resource Access Manager)経由でフォワーディングルールを共有する構成だ。
graph TB
subgraph SharedSvc["Shared Services Account"]
subgraph HubVPC["Hub VPC (10.0.0.0/16)"]
TGW_Attach_Hub["TGW Attachment"]
Inbound_EP["Inbound Endpoint\n(10.0.1.10 / 10.0.2.10)"]
Outbound_EP["Outbound Endpoint\n(10.0.1.20 / 10.0.2.20)"]
FwdRules["Forwarding Rules\n(corp.example.local)"]
end
end
subgraph ProdAccount["Production Account"]
subgraph ProdVPC["Prod VPC (10.1.0.0/16)"]
TGW_Attach_Prod["TGW Attachment"]
ProdApp["App Server"]
ProdRDS["RDS"]
end
end
subgraph DevAccount["Dev Account"]
subgraph DevVPC["Dev VPC (10.2.0.0/16)"]
TGW_Attach_Dev["TGW Attachment"]
DevApp["Dev Server"]
end
end
TGW["Transit Gateway"]
RAM["RAM\n(ルール共有)"]
OnPremDNS["オンプレDNS\n192.168.0.53"]
TGW_Attach_Hub <--> TGW
TGW_Attach_Prod <--> TGW
TGW_Attach_Dev <--> TGW
FwdRules -->|"RAMで共有"| RAM
RAM -->|"Prod VPCに関連付け"| ProdVPC
RAM -->|"Dev VPCに関連付け"| DevVPC
Outbound_EP -->|"クエリ転送"| OnPremDNS
OnPremDNS -->|"応答"| Outbound_EP
この構成の最大のメリットは、スポーク側のアカウントがエンドポイントを持たなくていいこと。エンドポイント1個あたり月$150〜200程度のコストがかかるので(ENI2個×時間単価)、アカウントが増えるほど集約の恩恵が大きくなる。うちは10アカウントあるので、集約しなかった場合と比べて月$1,500〜2,000くらい変わってくる計算だった。地味に大きい。
RAMでの共有、実際のCLI操作
フォワーディングルールを他のアカウントと共有するRAMの操作はこんな感じ。
# フォワーディングルールのARNを確認
aws route53resolver list-resolver-rules \
--query 'ResolverRules[?Name==`corp-example-local-rule`].Arn' \
--output text
# Resource Shareを作成してルールを共有
aws ram create-resource-share \
--name "dns-forwarding-rules-share" \
--resource-arns arn:aws:route53resolver:ap-northeast-1:111122223333:resolver-rule/rslvr-rr-xxxxxxxx \
--principals arn:aws:organizations::111122223333:organization/o-xxxxxxxxxx
# スポーク側で共有されたルールをVPCに関連付け(スポークアカウントで実行)
aws route53resolver associate-resolver-rule \
--resolver-rule-id rslvr-rr-xxxxxxxx \
--vpc-id vpc-0prod11112222prod
--principalsにOrganization全体のARNを指定するか、個別アカウントIDを指定するかは運用ポリシー次第。うちはOUレベルで管理してるので、OU単位でRAM共有するようにした。CDKでやる場合はaws-cdk-lib/aws-ramを使えば管理しやすい。
「なぜかDNSが遅い」問題、原因はSGとNACLだった
これが地味に時間を溶かした問題だった。アウトバウンドエンドポイントを設定したあと、特定のEC2インスタンスからオンプレのホスト名を引くと2〜3秒かかることがあった。通常は数十ms程度なのに。
調査してわかった原因は2つある。
原因1: セキュリティグループがUDPとTCPの両方を許可していなかった
DNSはUDP 53が基本だけど、レスポンスが512バイトを超える場合(大きなTXTレコードやDNSSECなど)はTCP 53にフォールバックする。最初、アウトバウンドエンドポイントのSGでUDP 53しか許可していなかったため、フォールバックが発生するたびにタイムアウトを待ってからリトライする動きになっていた。
# 正しいセキュリティグループ設定(アウトバウンドエンドポイント用)
インバウンドルール:
- UDP 53 from 0.0.0.0/0 (VPC CIDRに絞るべき)
- TCP 53 from 0.0.0.0/0 (VPC CIDRに絞るべき)
アウトバウンドルール:
- UDP 53 to 192.168.0.53/32 (オンプレDNS IP)
- TCP 53 to 192.168.0.53/32 (オンプレDNS IP)
原因2: NACLでエフェメラルポートを許可していなかった
NACLはステートレスなので、DNSの応答パケット(エフェメラルポート 1024-65535からのUDP応答)を明示的に許可する必要がある。SGだけで完結させようとしてNACLを疎かにしていたのが問題だった。
NACLでハマる話はVPCフローログの運用記事でも触れたけど、DNS設計においても同じ落とし穴がある。フローログを有効にして「REJECTがないか」を確認するのが最速のデバッグ方法だった。
DNS解決レイテンシの変化(実測値)
設定修正前後のDNS解決時間の実測値をまとめた。オンプレドメインが2,800ms→38msというのは正直「本当にこんなに変わるの?」と思ったくらいの差だったが、TCPフォールバックのタイムアウト待ちがなくなる効果はそれだけ大きかった。
xychart-beta
title "DNS解決レイテンシ比較(ms)"
x-axis ["内部ドメイン(修正前)", "内部ドメイン(修正後)", "オンプレDNS(修正前)", "オンプレDNS(修正後)", "外部ドメイン(修正前)", "外部ドメイン(修正後)"]
y-axis "レイテンシ (ms)" 0 --> 3000
bar [45, 42, 2800, 38, 120, 115]
2026年時点でやっておくべき設計プラクティス
半年の運用を経て「最初からこうしておけ」と言えるポイントが4つある。正直まだ全部完璧に実装できてるわけじゃないけど、少なくともこれを知ってるかどうかで設計の質が変わると思う。
1. DNSSEC対応を考慮したエンドポイント設計
2025年以降、Route 53のDNSSEC検証がより一般的になってきた。特に金融・医療系のシステムではDNSSEC対応が求められるケースが増えている。DNSSEC検証を有効にした場合、DNSレスポンスサイズが大幅に増加するため、先述のTCP 53フォールバック対応は必須になる。
# DNSSEC検証の有効化(ホストゾーンに対して)
aws route53 enable-hosted-zone-dnssec \
--hosted-zone-id Z1234567890ABCDEF
# KMSキーでDNSSEC署名キーを作成
aws route53 create-key-signing-key \
--hosted-zone-id Z1234567890ABCDEF \
--key-management-service-arn arn:aws:kms:us-east-1:111122223333:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
--name "my-ksk-2026" \
--status ACTIVE \
--caller-reference "ksk-$(date +%s)"
ひとつ地味にわかりにくい制約があって、KMSのキーはus-east-1に作成する必要がある。リージョンを間違えてエラーが出て数十分溶かした経験があるので先に言っておく。
2. Resolver Query Loggingは必ず有効化する
2024年後半から、Resolver Query LoggingがS3・CloudWatch Logs・Kinesis Data Firehoseに対応しているが、2026年現在はCloudWatch Logs Insights + Athenaのハイブリッドで分析するのが使いやすいと感じている。
# Query Loggingの設定(CloudWatch Logsへ)
aws route53resolver create-resolver-query-log-config \
--name "hub-vpc-query-log" \
--destination-arn arn:aws:logs:ap-northeast-1:111122223333:log-group:/aws/route53resolver/query-logs
# VPCに関連付け
aws route53resolver associate-resolver-query-log-config \
--resolver-query-log-config-id rqlc-xxxxxxxx \
--resource-id vpc-0hub11112222hub
Query Loggingのログは「どのIPがどのドメインを何回引いたか」がわかるので、セキュリティ異常検知にも使える。うちのチームはこのログをGuardDutyのDNS検知と組み合わせて、不審なドメインへのクエリを検知する仕組みを入れている。GuardDutyの運用についてはGuardDutyのFalse Positive対応記事が参考になるかもしれない。
3. フォワーディングルールの優先順位設計
フォワーディングルールには「最長一致」のマッチングが適用される。たとえば、こういう構成にした場合:
| ルール名 | ドメイン | 転送先 | 優先用途 |
|---|---|---|---|
| rule-corp-global | example.local | 192.168.0.53 | 全社ドメイン |
| rule-corp-db | db.example.local | 192.168.1.53 | DBチーム専用DNS |
| rule-corp-dev | dev.example.local | 192.168.2.53 | 開発環境DNS |
| rule-aws-internal | amazonaws.com | (Route53に委任) | AWSエンドポイント |
db.example.localへのクエリはexample.localよりdb.example.localのルールが優先される。これを使うと、特定のサブドメインだけ異なるDNSサーバーに転送するといった細かい制御ができる。うちはDBチームが自前のDNSサーバーでサービスディスカバリをやっていたので、このパターンで分岐させた。
注意点として、amazonaws.comへのフォワーディングルールを設定してしまうと、VPCエンドポイント経由での解決がうまくいかなくなることがある。基本的にamazonaws.comはRoute 53のデフォルトリゾルバーに任せておいて、明示的なルールを作らないほうが安全だ。
4. エンドポイントのヘルスモニタリング
インバウンド・アウトバウンドエンドポイントのENIは、特定の条件下(サブネットのIPが枯渇するなど)で期待通りに動かないことがある。CloudWatchで以下のメトリクスを監視しておくと安心できる。
# Resolver エンドポイントのメトリクス確認
aws cloudwatch get-metric-statistics \
--namespace AWS/Route53Resolver \
--metric-name InboundQueryVolume \
--dimensions Name=VpcId,Value=vpc-0hub11112222hub \
--start-time 2026-05-06T00:00:00Z \
--end-time 2026-05-07T00:00:00Z \
--period 300 \
--statistics Sum
InboundQueryVolumeが急激にゼロになったらエンドポイント障害のサインなので、アラームを設定しておくといい。実際に1回、NACLの誤設定でエンドポイントへの通信が完全に遮断されたことがあって、このアラームがなかったら気づくのが数時間遅れていたと思う。
まとめ
半年の運用で痛みながら整理したRoute 53 Resolver設計の要点をまとめておく。
| # | ポイント | やらかした場合のリスク |
|---|---|---|
| 1 | インバウンドエンドポイントのIPは初期設計で固定する | 再作成のたびにオンプレDNS設定変更が必要になり障害につながる |
| 2 | SGはUDP/TCP両方53番を許可、NACLはエフェメラルポートも設定 | DNSタイムアウトという診断が難しい問題になる |
| 3 | マルチアカウント環境はHub & Spoke + RAM共有でエンドポイントを集約 | アカウント数×$150〜200/月のコスト増 |
| 4 | Resolver Query Loggingを有効にしてCloudWatchで監視 | セキュリティ異常・パフォーマンス劣化・障害デバッグが全部つらくなる |
| 5 | amazonaws.comへの明示的なフォワーディングルールは作らない | VPCエンドポイント経由の名前解決が壊れる |
次のアクションとしては、まずQuery Loggingを有効化してからベースラインを取るのがおすすめだ。「今どんなDNSクエリが飛んでるか」を把握せずに設計を変えると、変更後の影響範囲が読めない。ハイブリッドDNS構成のセキュリティ強化を考えている方は、AWS Network Firewallのネットワーク保護設計と合わせて読むと、DNSトラフィックをFirewallで検査する構成も検討できると思う。
皆さんのRoute 53 Resolver設計で「これ詰まった」「こうしたらうまくいった」という経験があれば、ぜひコメントかTwitter/Xで教えてもらえると嬉しい。DNS設計は「やってみないとわからない」部分が多くて、まだ自分も学びの途中だと感じてる。