オンプレミスのDNSサーバをRoute 53へ移行する際に既存IPアドレスを流用したい場合を考えてみる

オンプレミスのDNSサーバをRoute 53へ移行する際に既存IPアドレスを流用したい場合を考えてみる

Clock Icon2025.05.21

こんにちは、なおにしです。

オンプレミス環境にあるDNSサーバをRoute 53 へ移行する場合について検討をする機会がありましたのでご紹介します。

はじめに

Route 53 では、AWS Site-to-Site VPNやAWS Direct Connect といった拠点間接続のためのサービスと組み合わせることで、オンプレミス環境におけるプライベートゾーンに対する名前解決についても対応することができます(※)。

より具体的には、Route 53 Resolver インバウンドエンドポイントをVPCのサブネットに設置し、当該VPCと拠点間接続のためのサービスを連携させます。

20250521_naonishi_onpremise-dns-to-route53-migration-with-existing-ip_1.png

※ 現行環境でActive Directory環境でDNSの動的更新を使用していたり、アプライアンス機器特有の機能などを利用していたりする場合は、事前に実現可能性を検討する必要があります。

既存DNSサーバのIPアドレスを流用したい場合

移行する際、クライアント機器にはDHCPを使用してDNSサーバのIPアドレスを配布しているのであれば、新しいDNSサーバのIPアドレスに参照先を一括変更することは比較的容易かと思いますが、管理の都合上でDHCPを利用しておらず、クライアント機器には個別にDNSサーバのIPアドレスを指定しており、さらに機器の台数が多かったりアクセスの悪い場所に設置されていたりする場合は、新DNSサーバへの移行難易度が高くなってしまいます。

このような場合、既存のオンプレミスDNSサーバのIPアドレスをそのままRoute 53 への移行後にも使用したいところですが、Route 53 Resolver インバウンドエンドポイントのIPアドレスに直接同じIPアドレスを指定するというような対応は非推奨です。

同じIPアドレスを指定する、つまりオンプレミス環境とVPCのCIDRを同じにしたSite-to-Site VPNやDirect Connect 構成は、IPアドレスの重複が発生するため、もしどうしてもCIDRを合わせる必要がある場合はAWS PrivateLink を間に挟むといったような工夫が必要となり、構成が複雑化します。

オンプレミス環境とVPCのCIDRを合わせずに既存DNSサーバのIPアドレスを使用して名前解決をしたい場合は、オンプレミス側でDNSフォワーダを設置したり、インバウンドエンドポイントのIPアドレスにDNAT可能なバーチャルIP(VIP)アドレスを準備したりして、既存DNSサーバのIPアドレスを割り当てるという方法が考えられます。

料金

Route 53 Resolver インバウンドエンドポイントに関しては以下のとおりドキュメントに記載があります。

Route 53 Resolver インバウンドエンドポイントを作成する場合、Route 53 では、ネットワーク上の DNS リゾルバーによるクエリ転送先の IP アドレスを少なくとも 2 つ作成しておく必要があります。冗長性を確保するために、少なくとも 2 つのアベイラビリティーゾーンで IP アドレスを指定する必要があります。

インバウンドエンドポイントの料金は「ENI ごとに 0.125 USD/時間」となっていますが、上記のとおり最低でも2つのENIを作成する必要がある(= 1つだけにすることはできません)ことから、インバウンドエンドポイントを設定するだけで以下の料金が発生します。

  • 2 ENI x 0.125 USD x 730 時間 (1 か月) = 182.50 USD/月

また、冒頭の図のとおりSite-to-Site VPN構成でVGWによってVPCと接続した形であれば、Site-to-Site VPNの料金として以下が発生します。

  • 1 接続 x 0.048 USD x 730 時間/月 = 35.04 USD/月

Direct Connect やTGWを利用する場合はさらにその料金が加算されます。また、1ホストゾーンあたり「0.50 USD/月」も発生しますので、上記構成にする場合は最低でも以下の料金が発生し、さらにデータ通信量やクエリ数に応じた従量課金が追加されます。

  • 218.04 USD/月 〜

このように一定の月額料金は発生しますが、オンプレミスの物理サーバの管理/運用/保守にかかる費用と比較してメリットがある場合は移行先の選択肢としてはありだと思います。

もちろん、移行前とはレコードの登録方法が変わりますし、Site-to-Site VPNについては以下のようなトンネル置換えの通知が来たりもするので、運用が変わる部分については慣れが必要です。

https://dev.classmethod.jp/articles/aws-site-to-site-vpn-lifecycle-control/

それでは実際にRoute 53にプライベートホストゾーンを登録し、Site-to-Site VPN経由で名前解決ができることを確認してみます。

やってみた

事前準備

Route 53 Resolver インバウンドエンドポイント側の設定はこちらの記事がステップバイステップで記載されていて分かりやすいのでご参照ください。

Site-to-Site VPNの設定についてはこちらをご参照ください。

AWS側の環境設定は他の記事にアウトソースできてしまったので、本記事ではオンプレミス側でバーチャルIPとDNSフォワーダを設置して名前解決できることを確認する部分に焦点を当てようと思います。

具体的には以下のように環境を準備しました。

20250521_naonishi_onpremise-dns-to-route53-migration-with-existing-ip_2.png

バーチャルIP(NAT)設定パターン

こちらはオンプレミスで使用しているNW機器の設定に依存するので、一例としてFortiGateで設定する場合のご紹介となります。

検証に利用できるFortiGateは60D(ファームウェア: v5.4.4)で、既にEOSを迎えて久しいくらい古いものですが、バーチャルIP機能を利用することでDNATが可能です。

例えば以下のように設定することで、宛先192.168.24.53への通信をNATして172.30.0.53に転送します。53番ポート以外を転送したくない場合はファイアウォールポリシーで制限可能です。

config firewall vip
    edit "AWS-Route53-Inbound-Endpoint"
        set uuid 12b3b394-2731-51f0-a814-ef561c695fa7
        set extip 192.168.24.53
        set extintf "internal"
        set mappedip "172.30.0.53"
    next
end

上記ではVIPを一つしか設定していませんが、せっかくインバウンドエンドポイントを2つ設置しているので、本来であればセカンダリDNSサーバとしてもう1つVIPを設定した方が良いと思います。

MacBookからdigコマンドで名前解決を実行した結果は以下のとおりです。対象インスタンスのIPアドレスを問題なく取得できています。

$ dig private.naonishi.example @192.168.24.53  

; <<>> DiG 9.10.6 <<>> private.naonishi.example @192.168.24.53
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 55885
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;private.naonishi.example.		IN	A

;; ANSWER SECTION:
private.naonishi.example.	300	IN	A	172.30.0.236

;; Query time: 47 msec
;; SERVER: 192.168.24.53#53(192.168.24.53)
;; WHEN: Tue May 20 20:19:32 JST 2025
;; MSG SIZE  rcvd: 66

ちなみに、登録しているプライベートホストゾーン以外のパブリックな名前解決も可能です。

$ dig google.com @192.168.24.53 

; <<>> DiG 9.10.6 <<>> google.com @192.168.24.53
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 21874
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;google.com.			IN	A

;; ANSWER SECTION:
google.com.		140	IN	A	172.217.174.110

;; Query time: 35 msec
;; SERVER: 192.168.24.53#53(192.168.24.53)
;; WHEN: Tue May 20 20:19:52 JST 2025
;; MSG SIZE  rcvd: 55

Route 53のクエリログも有効化してみたので、CloudWatch Logsに記録された内容は以下のとおりです。各項目についてはドキュメントをご参照ください。

{
    "version": "1.100000",
    "account_id": "012345678912",
    "region": "ap-northeast-1",
    "vpc_id": "vpc-0723d08c7f809209b",
    "query_timestamp": "2025-05-20T11:19:32Z",
    "query_name": "private.naonishi.example.",
    "query_type": "A",
    "query_class": "IN",
    "rcode": "NOERROR",
    "answers": [
        {
            "Rdata": "172.30.0.236",
            "Type": "A",
            "Class": "IN"
        }
    ],
    "srcaddr": "192.168.24.111",
    "srcport": "58998",
    "transport": "UDP",
    "srcids": {
        "resolver_endpoint": "rslvr-in-fed07f520bdc46669",
        "resolver_network_interface": "rni-3b1d98327291420da"
    }
}

resolver_endpointresolver_network_interfaceはインバウンドエンドポイントの設定にあるとおりです。

20250521_naonishi_onpremise-dns-to-route53-migration-with-existing-ip_3.jpg

google.comを名前解決したログももちろん記録されています。

{
    "version": "1.100000",
    "account_id": "012345678912",
    "region": "ap-northeast-1",
    "vpc_id": "vpc-0723d08c7f809209b",
    "query_timestamp": "2025-05-20T11:19:52Z",
    "query_name": "google.com.",
    "query_type": "A",
    "query_class": "IN",
    "rcode": "NOERROR",
    "answers": [
        {
            "Rdata": "172.217.174.110",
            "Type": "A",
            "Class": "IN"
        }
    ],
    "srcaddr": "192.168.24.111",
    "srcport": "49717",
    "transport": "UDP",
    "srcids": {
        "resolver_endpoint": "rslvr-in-fed07f520bdc46669",
        "resolver_network_interface": "rni-3b1d98327291420da"
    }
}

MacBookのDNSサーバのIPアドレスを設定すればEC2のプライベートIPアドレスを名前解決して直接データを取得できました。

$ curl http://private.naonishi.example
<!DOCTYPE html><html lang=ja><head><meta charset=UTF-8><title>テストページ</title></head><body><header><h1>こんにちは</h1></header></body></html>

Chromeでアクセスしてももちろん応答があります。

20250521_naonishi_onpremise-dns-to-route53-migration-with-existing-ip_4.png

DNSフォワーダ設置パターン

今回はUbuntu Server 22.04 LTS がインストールされたRaspberry Pi 4 をDNSフォワーダに設定しました。

今回はBIND等のソフトウェアはインストールせず、OS標準のsystemd-resolvedを使用してDNSフォワーダとして機能するようにしてみます。

現状の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: 192.168.24.254
       DNS Servers: 192.168.24.254

Link 3 (wlan0)
Current Scopes: none
     Protocols: -DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported

resolvectlコマンドについてはこちらのドキュメントをご参照ください。

対象のRaspberry Pi はIPアドレスを192.168.24.150で固定していますが、Raspberry Pi 側の設定ではなくFW側のDHCP設定でMACアドレス指定をすることで固定しています。このため、以下のとおりRaspberry Pi のnetplanでも特にDNSの設定はなく、単純にDHCPクライアントとして割り当てられたDNSサーバを指定している状態です。

$ cat /etc/netplan/50-cloud-init.yaml 
# This file is generated from information provided by the datasource.  Changes
# to it will not persist across an instance reboot.  To disable cloud-init's
# network configuration capabilities, write a file
# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following:
# network: {config: disabled}
network:
    ethernets:
        eth0:
            dhcp4: true
            optional: true
    version: 2

それでは、/etc/systemd/resolved.conf設定してDNSフォワーダとして機能するように設定します。resolved.confの設定項目についてはこちらのドキュメントをご参照ください。

まず、設定前のポート53のListen状況ですが、以下のようになっています。

$ ss -tulpn | grep ":53 "
udp   UNCONN 0      0            127.0.0.53%lo:53         0.0.0.0:*    users:(("systemd-resolve",pid=647,fd=13))
tcp   LISTEN 0      4096         127.0.0.53%lo:53         0.0.0.0:*    users:(("systemd-resolve",pid=647,fd=14))

こちらは出力結果のとおりsystemd-resolvedサービスによるものです。ドキュメントにも以下の記載があります。

systemd-resolved provides a local DNS stub listener on IP address 127.0.0.53 on the local loopback interface. Programs issuing DNS requests directly, bypassing any local API may be directed to this stub, in order to connect them to systemd-resolved.

以下のとおり設定します。

/etc/systemd/resolved.conf
[Resolve]
+DNS= 172.30.0.53 172.30.1.53
+FallbackDNS=8.8.8.8
+Domains=~naonishi.example
#DNSSEC=no
#DNSOverTLS=no
#MulticastDNS=no
#LLMNR=no
#Cache=no-negative
#CacheFromLocalhost=no
#DNSStubListener=yes
+DNSStubListenerExtra=192.168.24.150
#ReadEtcHosts=yes
#ResolveUnicastSingleLabel=no

設定した項目についてはドキュメントに記載のとおりですが、Domainsについては少し分かりづらかったので補足します。

ドメイン名の先頭に~(チルダ)を設定すると、対象のドメインはルーティングドメインとして機能します。この場合、対象のドメインに関連するDNSクエリは、設定したDNSサーバーを優先して使用します。

一方、~(チルダ)を設定しない場合は検索ドメインとして機能します。この場合、例えばprivate.naonishi.example ではなくping privateでも名前解決ができるようになります。

今回は前者の機能が意図したものだったので~naonishi.exampleを設定しています。もし全てのドメインをルーティングしたい場合は~.と設定することができます。

設定を適用するためにsystemd-resolvedを再起動します。

# systemctl restart systemd-resolved.service

Listen状況は以下のようになりました。192.168.24.150のTCP/UDPポート53番で待受状態になっています。

$ ss -tulpn | grep ":53 "
udp   UNCONN 0      0           192.168.24.150:53         0.0.0.0:*    users:(("systemd-resolve",pid=761753,fd=15))
udp   UNCONN 0      0            127.0.0.53%lo:53         0.0.0.0:*    users:(("systemd-resolve",pid=761753,fd=13))
tcp   LISTEN 0      4096         127.0.0.53%lo:53         0.0.0.0:*    users:(("systemd-resolve",pid=761753,fd=14))
tcp   LISTEN 0      4096        192.168.24.150:53         0.0.0.0:*    users:(("systemd-resolve",pid=761753,fd=16))

まず、Raspberry Pi上で名前解決をしてみると、問題なく応答があることを確認できました。

$ dig private.naonishi.example @192.168.24.150       

; <<>> DiG 9.10.6 <<>> private.naonishi.example @192.168.24.150
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26476
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;private.naonishi.example.		IN	A

;; ANSWER SECTION:
private.naonishi.example.	300	IN	A	172.30.0.236

;; Query time: 180 msec
;; SERVER: 192.168.24.150#53(192.168.24.150)
;; WHEN: Tue May 20 20:17:14 JST 2025
;; MSG SIZE  rcvd: 66

念のため登録されていないレコードを指定してみても問題ありませんでした。

$ dig google.com @192.168.24.150  

; <<>> DiG 9.10.6 <<>> google.com @192.168.24.150
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 53396
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;google.com.			IN	A

;; ANSWER SECTION:
google.com.		89	IN	A	142.250.198.14

;; Query time: 64 msec
;; SERVER: 192.168.24.150#53(192.168.24.150)
;; WHEN: Tue May 20 20:18:03 JST 2025
;; MSG SIZE  rcvd: 55

クエリログは以下のとおりです。

{
    "version": "1.100000",
    "account_id": "012345678912",
    "region": "ap-northeast-1",
    "vpc_id": "vpc-0723d08c7f809209b",
    "query_timestamp": "2025-05-20T11:17:14Z",
    "query_name": "private.naonishi.example.",
    "query_type": "A",
    "query_class": "IN",
    "rcode": "NOERROR",
    "answers": [
        {
            "Rdata": "172.30.0.236",
            "Type": "A",
            "Class": "IN"
        }
    ],
    "srcaddr": "192.168.24.150",
    "srcport": "53368",
    "transport": "UDP",
    "srcids": {
        "resolver_endpoint": "rslvr-in-fed07f520bdc46669",
        "resolver_network_interface": "rni-3b1d98327291420da"
    }
}
{
    "version": "1.100000",
    "account_id": "012345678912",
    "region": "ap-northeast-1",
    "vpc_id": "vpc-0723d08c7f809209b",
    "query_timestamp": "2025-05-20T11:18:03Z",
    "query_name": "google.com.",
    "query_type": "A",
    "query_class": "IN",
    "rcode": "NOERROR",
    "answers": [
        {
            "Rdata": "142.250.198.14",
            "Type": "A",
            "Class": "IN"
        }
    ],
    "srcaddr": "192.168.24.150",
    "srcport": "49752",
    "transport": "UDP",
    "srcids": {
        "resolver_endpoint": "rslvr-in-fed07f520bdc46669",
        "resolver_network_interface": "rni-3b1d98327291420da"
    }
}

MacBookのDNSサーバのIPアドレスを設定してEC2にアクセスできることはバーチャルIP設定パターンと同様に確認できましたが、結果については全く同じため記載を割愛します。補足としては、MacBookからDNSフォワーダを経由して名前解決した場合もクエリログに記録されるsrcaddrはDNSフォワーダのIPアドレスになります。

まとめ

Route 53 というよりはオンプレ側の設定に関する内容がメインになりましたが、実現可能性の解像度は上がった気がします。DNSフォワーダを準備するパターンについては、最初はBIND等のソフトウェアを追加インストールする必要があるのかと思っていましたが、今回のような単純なフォワーダであればOS標準の機能でも実装できるようになっていたことは勉強になりました。

本記事がどなたかのお役に立てれば幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.

OSZAR »