広大な海を渡るには、星だけでなく海図がいる
안녕하신게라!パナソニック コネクト株式会社クラウドソリューション部の加賀です。
昔々、といっても1993年の話。世界的に普及した最初期のグラフィカルWebブラウザ「NCSA Mosaic」が登場しました。そのアイコンは地球儀でWorld Wideを指し、バラバラのテキストと画像をモザイクタイルに見立てそれらが繋がった先に世界が見える、と。そのMosaicを作ったマーク・アンドリーセンが次に送り出したのが、1994年の「Netscape Navigator」。起動時のスプラッシュ画面は灯台や舵といった航海モチーフをあしらったデザインで、大航海時代の船乗りが星を頼りに現在地を知る天測航法を思わせるものでした。後年お馴染みの「星空を背景にした N」ロゴへと姿を変えていくわけですが、地球を見渡すフェーズから、いよいよ舵を握り星を頼りにその海を航海するフェーズへ移った、というわけです。(その後、Windows95とInternetExplorerの登場で利用者が一気に増えたのは御存知の通り)
星を読み舵取れども、目的地が分からねば船は進まじ。広大な海を往くには「海図」が必要です。
前回の「SSL/TLSと証明書の基本の"キ"」では、証明書を使って通信相手のFQDN(完全修飾ドメイン名)が本物であることを確認し、安全にデータをやり取りする仕組みを解説しました。
しかし、時計が合い、相手が本物だと信じられても、そもそも「その相手がネットワーク上のどこにいるのか」が分からなければ、実際にデータを送り届けることはできません。
今回はインターネットという広大な海から、たった1台のサーバを見つけ出すための「海図」にあたる、DNSとIPアドレスの基本の"キ"を解説します。
タイトルの「浸透」という言葉に心がチクリとなる世代も減り、最近ではとんと聞かなくなりました、とても良い傾向です。これからも皆でdigを投げよう!
ネットワーク上の「住所」がIPアドレス
手紙を届けるには住所がいるように、ネットワークの住所が「IPアドレス(Internet Protocol Address)」です。
192.168.1.1 のような4つの数字の羅列(IPv4)を見たことがあるかと思います。IPv4アドレスは2011年にIANAの中央在庫(未割当プール)が枯渇しており、現在は 2001:db8::1 のような128ビットのIPv6への移行が進んでいます。
IPアドレスは大きく分けて2種類です。グローバルIPアドレスは、インターネットという世界共通の巨大な街で使われる「世界で唯一の住所」。一方のプライベートIPアドレスは、自宅のLANや社内ネットワーク、AWS VPCの中など「閉じた世界」でのみ使えるローカルな住所で、マンションの「101号室」のように世界中で被っていても問題ありません。
私たちがWebサイトにアクセスするとき、最終的にはそのサーバのグローバルIPアドレスに向かって通信しなければなりません。
人間にやさしい名前「FQDNとDNS」
IPアドレスさえ分かれば通信はできます。ただ、人間にとって xx.xx.xx.xx のような数字の羅列を覚えるのは無理がありますよね。
そこで活躍するのが、前回のTLSの回でも登場した「FQDN(完全修飾ドメイン名)」です。
FQDNとはインターネット上での絶対的な名前のことで、例えば api.qiita.com であれば、qiita.com の部分が組織やサービスが取得する縄張りであるドメイン名、api の部分がその縄張りの中の特定のサーバや役割を示すホスト名、それらを組み合わせたものがFQDNです。
人間が覚えやすい「FQDN」と、コンピュータが処理する「IPアドレス」。この2つを紐付ける電話帳のようなシステムが必要になりました。
それが DNS(Domain Name System)です。FQDNからIPアドレスを割り出す作業のことを「名前解決」と呼びます。
DNSレコードにはいろいろな種類がありますが、よく見る主要なものを一覧にしておきます。
| レコード種別 | 用途 |
|---|---|
| A | FQDN → IPv4アドレス |
| AAAA | FQDN → IPv6アドレス |
| CNAME | FQDN → 別のFQDN(エイリアス) |
| NS | そのゾーンを管理する権威DNSサーバ |
| SOA | ゾーンの管理情報(シリアル、TTL等) |
| MX | そのドメイン宛メールの受信サーバ |
| TXT | 文字列を任意に格納(SPF、DKIM、ドメイン所有証明等で多用) |
| PTR | IPアドレス → FQDN(逆引き) |
| CAA | そのドメインの証明書を発行してよい認証局を制限 |
レコード表の中で地味に大事なのが逆引き(PTRレコード)。通常のFQDN→IPとは逆に、IPからFQDNを引く仕組みです。メールサーバの接続元検査では「逆引きで得たFQDNを正引きしたら同じIPに戻ってくるか」を確認する FCrDNS(Forward-Confirmed reverse DNS) が広く使われています。ログ分析でIPをホスト名に変換する用途でもおなじみですね。なお、SPFやDKIMといった送信ドメイン認証は同じ「送信元の正当性を確かめる」別の仕組みですが、こちらはTXTレコードで実現される独立の認証レイヤーで、PTRとは別物です。
【コラム】FQDNの末尾には「見えないドット」がある?
実はFQDNの正式な表記では、末尾に見えないドット . が隠れています。つまり api.qiita.com. が正確な形。
この最後のドットは「ルート(根源)」を意味していて、パスの最初の /(ルートディレクトリ)のようなものです。TerraformでRoute 53の設定を書いていて末尾に . が付いていて「なんだこれ?」となった経験はありませんか? あれはDNSの世界における「絶対パス(FQDNですよ)」という宣言です。
省略した場合は相対パスとなるため、サブドメイン名を管理しているときは特に意識する必要があります。
名前解決とは「たらい回し」
では、ブラウザのURLバーに qiita.com と打ち込んだとき、裏側では何が起きているのでしょうか?
実は、あなたのパソコンは世界中のDNSサーバを巻き込んだ地味に長い伝言ゲームをやっています。
登場人物は大きく2種類です。「権威DNSサーバ」は、自分が担当するゾーン(例えば qiita.com)の答えを持っているサーバ。「フルリゾルバ(再帰リゾルバ/キャッシュDNSサーバ)」は、答えを持っていない代わりに権威DNSを順に辿って答えを探し回る代行屋さんです。普段あなたのPCが直接話す相手は後者のフルリゾルバ側です。
- まず近くの「フルリゾルバ」に聞きます。プロバイダなどが用意してくれている問い合わせ代行サーバです。
- リゾルバは「.(ルート)」→「.com」→「qiita.com」と、階層構造のトップから順番に権威DNSサーバへたらい回しにされながら答えを探し出します。
ただし、実際にはフルリゾルバへ問い合わせる前に、OS側でいくつかチェックが走ります。まずローカルDNSキャッシュ(Windowsなら ipconfig /displaydns で確認できます)と hosts ファイル(Linux/macOSは /etc/hosts、Windowsは C:\Windows\System32\drivers\etc\hosts)を参照します。両者の参照順や優先度はOSやディストリビューションの設定(Linuxなら /etc/nsswitch.conf の hosts: 行)で決まりますが、いずれにせよ「ローカルで完結するなら外には聞きに行かない」のが共通です。開発中に 127.0.0.1 local.example.com を hosts に書いて動作確認した経験もあるのでは。
レジストラと権威DNSの関係
ところで、このリレーが成立するには前提条件があります。「.comのDNSサーバは、qiita.comの権威DNSサーバがどこにあるか知っている」必要がある、という点です。
ドメインを取得するとき、私たちはお名前.comやRoute 53といった「レジストラ」を通じてドメインを登録します。このときレジストラ経由で上位のレジストリ(.comならVerisign)に「qiita.comの権威DNSはここですよ」とNSレコードを登録してもらうことで、初めてインターネット全体から名前解決できるようになります。
「ドメインを買ったのに名前解決できない」「権威DNSをRoute 53に移したのに反映されない」というトラブルは、たいていこのレジストラ側のNS設定が正しく向いていないことが原因です。権威DNSサーバ側のゾーン設定だけ変えてレジストラ側を忘れるのはあるあるです。
なお、qiita.com の権威DNSを ns1.qiita.com のように そのドメイン自身のサブドメインで指定する場合、ちょっとした鶏卵問題が生まれます。ns1.qiita.com のIPを引くには qiita.com の権威DNSに聞きに行く必要があり、その権威DNSが ns1.qiita.com 自身……という無限ループです。これを解決するのが「グルーレコード(glue record)」で、上位レジストリ(.comならVerisign)側にネームサーバのIPアドレスを直接登録しておく特殊なA/AAAAレコードです。レジストラの管理画面で「ホスト登録」「Glue Record」「ネームサーバホストの作成」といったメニューがそれにあたります。自前で ns1.example.com を立てるときに踏みがちな罠なので、頭の片隅にでも。
digで名前解決を自分の手で確かめる
名前解決の仕組みを理解したら、実際にコマンドで確認してみましょう。dig(主にLinux/macOS、WindowsもBIND同梱ツールやwinget等で導入可能)や nslookup(Windows/Linux/macOS)を使えば、DNSの問い合わせ結果をそのまま見ることができます。
# Aレコードを問い合わせ
dig qiita.com A
# Windowsの場合
nslookup qiita.com
dig を打つと、こんな感じの応答が返ってきます。
;; ANSWER SECTION:
qiita.com. 60 IN A xx.xx.xx.xx
;; SERVER: 192.168.1.1#53(192.168.1.1)
;; Query time: 12 msec
ANSWER SECTION に「FQDN / TTL(秒) / クラス(IN=Internet)/ レコード種別 / 値」が並びます。応答した権威DNSサーバや問い合わせにかかった時間も併せて確認でき、トラブルシューティングの第一歩になります。
さらに強力なのが dig +trace です。これは先ほどのシーケンス図で見た「ルートから順にたらい回しにされる」過程をそのまま再現してくれるオプションで、どの階層で問題が起きているかを一発で切り分けられます。
# ルートDNSから順に辿る過程を表示
dig +trace qiita.com
なお、より厳密に「実際のフルリゾルバと同じ挙動」でトレースしたい場合は delv +ns +rtrace qiita.com という選択肢もあります。delv は BIND 9.10 以降に同梱される検証用リゾルバで、dig +trace が各階層へ独立にクエリを投げる擬似トレースなのに対し、delv は内部リゾルバ実装を使って本物の名前解決と同じ手順を踏み、ついでにDNSSEC検証まで行ってくれます。環境に delv がない場合(macOS標準やWindows等)は素直に dig +trace で十分です。
インフラエンジニアにとって dig は最も頻繁に使うコマンドの一つです。DNSで困ったらまず dig を投げる、これを条件反射にしておくと障害対応のスピードがまるで違います。
【コラム】そもそもルートDNSの場所はどうやって知るのか?(ルートヒント)
上の図には「リゾルバはまずルートDNSに聞く」と書きましたが、ではフルリゾルバはルートDNSのIPアドレスをどうやって知るのでしょうか。「ルートヒント(root hints)」として用意しており、13系統(a.root-servers.net 〜 m.root-servers.net)のルートサーバのIPv4/IPv6アドレスをあらかじめ書いたファイルが同梱されています。
BINDなら named.root(ディストリビューションによっては named.ca)、Unboundなら root.hints、Windows DNS Serverなら cache.dns といったファイルで持ちます。配布元はIANAで、dig . NS @a.root-servers.net でも取得できます。たった13系統とはいえ、Anycast(同一IPを世界中の拠点で広告し、クライアントから最寄りの拠点に到達させる技術)によって世界中で1,900以上の拠点(root-servers.org 統計)に分散配置されており、実際に応答するのは物理的に最も近いサーバです。
なお一般PCのスタブリゾルバはルートに直接問い合わせないので、ルートヒントを持っていません(systemd-resolved等もこちらに分類されます)。
インフラエンジニアを悩ませる「浸透いうな」問題
冒頭で「浸透という言葉は聞かなくなった」と触れましたが、正直に告白すると私も昔に「DNS浸透待ちですねー」と言っちゃいました。日本のDNS界隈では長らく「浸透いうな」と啓発が続けられてきた、知る人ぞ知るネタでもあります。
さて、浸透とは何ぞや。
ドメインを新しいサーバのIPアドレスに向け直したのに、「一部のユーザには新しいサイトが見えて、別のユーザには古いサイトが見える」という現象に対して、昔は「DNSの変更がまだインターネットに浸透していないからだ」と表現していました。
でも、ここまで読んだ皆さんならお分かりの通り、DNSに「浸透」という概念はありません。あるのは「キャッシュ(TTL)」だけです。
先ほどの図を思い出してください。フルリゾルバは毎回ルートから調べ直すと大変なので、一度調べた答えを一定時間メモ(キャッシュ)しておく機能を持っています。
このメモの有効期限が「TTL(Time To Live)」です。
例えばTTLが 86400秒(24時間) に設定されていれば、権威DNSでIPアドレスを変更しても世界中のリゾルバは最長24時間、古いメモを信じ続けることになります。だからこそ「DNS切り替えの旧TTL以上前(24時間TTLなら丸1日以上前)から事前にTTLを短く(例えば60秒に)設定しておく」のが、インフラ運用の鉄則です。
ただしTTL短縮にはトレードオフがあります。TTLが短いということは、キャッシュがすぐ期限切れになるということ。つまり権威DNSサーバへのクエリ数がそのぶん増大します。Route 53のようなマネージドサービスなら勝手にスケールしてくれますが、自前でBINDを運用しているなら要注意。トラフィックの多いドメインでTTLを極端に短くすると権威DNS自体がクエリの洪水で溢れます。そういう場合はセカンダリDNS(ゾーン転送)で冗長化しつつ負荷を分散させるか、Anycast構成で複数拠点にクエリを散らしてやる必要があります。
ネガティブキャッシュの罠
さらに厄介なのが、応答が存在しない(NXDOMAIN)場合にもキャッシュが効く「ネガティブキャッシュ」の存在です。レコードを新規追加する直前に間違って問い合わせてしまうと、「このFQDNは存在しない」という否定の答えがSOAレコードの MINIMUM 値(RFC 2308でネガティブキャッシュTTLとして再定義された値。実効値はSOA自身のTTLと MINIMUM の小さい方)の期間、世界中のリゾルバにキャッシュされます。「レコード作ったのに引けない!」という事故の多くはこれが原因なので、事前確認はギリギリまで控えるか、SOAの MINIMUM を短くしておくのが安全です。
クラウド時代の「TTL 60秒すら長すぎる」問題
昔ながらのオンプレ環境の引っ越しなら「TTL 60秒」で十分安全でした。けれど、オートスケールや障害時の自動フェイルオーバーが当たり前のクラウド環境では、IPアドレスが秒単位で変わることも珍しくありません。
障害検知からDNSフェイルオーバーが走った場合、TTLが60秒あると最大60秒間は古い(死んでいる)IPに通信が向き、ユーザにエラーが出続けることになります。さらに悪いことに、DNSのTTLとは別に、OS標準のスタブリゾルバやブラウザの内部キャッシュ、JVM(networkaddress.cache.ttl のデフォルトはSecurityManager有効時 -1 =無期限、無効時でも 30 秒)といったアプリケーション層にも独自のキャッシュが存在し、権威DNSのTTLどおりに更新されない場合があります。金融系や医療系のシステムでは、この「最大60秒のエラー」すら許されないケースがあります。
そこで現在では、DNSのTTL変更に頼らず、Anycastでトラフィックを受け止め、裏側のクラウドWAN内で動的に経路を振り分けるアプローチが主流です。AWS Global AcceleratorやCloudflareのCDNがまさにこれ。「DNSキャッシュ問題」というプロトコルの限界を、クラウドベンダーが物理インフラの力技でぶん殴って解決した形です。
【コラム】Kubernetesと ndots:5 の落とし穴
コンテナ環境、特にKubernetes(CoreDNS)でDNSのパフォーマンス問題に出会ったことはありませんか? 原因の常連が /etc/resolv.conf のオプション ndots:5 です。
これは「FQDNにドットが5個未満なら、まず各検索ドメイン(svc.cluster.local 等)を順番に付けて問い合わせ、最後の手段としてようやく元の名前を絶対FQDNで引きにいく」という設定で、外部の api.qiita.com(ドット2個)を引くだけでも api.qiita.com.default.svc.cluster.local→api.qiita.com.svc.cluster.local→…と数回の余計な問い合わせが走ります。
クラスタDNSへの負荷増・レイテンシ増の元凶になりがちなので、外部通信が多いPodでは dnsConfig で ndots:2 に下げる、もしくは末尾に . を付けて絶対FQDN化する、といった対策が定石です。
Zone Apexの壁と「ALIASレコード」
DNSレコードにはIPを直接返す「Aレコード」以外に、別の名前に転送する「CNAMEレコード」があります。例えば www.qiita.com を qiita.com のCNAMEとして設定すれば、www付きでアクセスしてもqiita.comと同じIPに辿り着けます。
ただし、DNSの仕様(RFC 1034/2181)上、Zone Apex(ドメイン名そのもの。例では qiita.com 自身)にはSOAやNSレコードが必須で、CNAMEは設定できません。さらにCNAMEは仕様上「同じ名前で他のレコード種別(MX、TXT等)と共存できない」制約もあり(DNSSECのRRSIG/NSEC等は例外)、これも現場でよく踏みます。
一方、AWS ALBやCloudFrontといったサービスはIPが動的に変わるため、利用者に払い出されるのはFQDN(xxx.ap-northeast-1.elb.amazonaws.com 等)だけです。これだと qiita.com(Zone Apex)でALBを受けたくてもCNAMEが使えず、Aレコードに書くべき固定IPも存在しません。歴史的にもこのCNAME制約を避けるために www.qiita.com のようにwww付きで運用するのが定番でしたが、「Zone Apexを直接ALBに向けたい」というニーズはクラウド時代になって一気に増えました。
この問題を解決するのが、Route 53の「ALIASレコード」、Cloudflareの「CNAME Flattening」、DNSimpleの「ANAMEレコード」といったDNSプロバイダ独自の拡張です。裏側でプロバイダが名前解決を代行し、最終的なIPアドレスをAレコードとしてクライアントに返してくれます。DNS標準にはない各社の力技ですが、知らないとTerraformの alias ブロックで「なんぞこれ」と首を傾げることになります。
【コラム】Private Hosted Zoneと「スプリットホライズンDNS」
オンプレでは社内DNSサーバを立てて「内部だけで引けるFQDN」を運用するのが当たり前でしたが、クラウドのマネージドDNSにも同等の機能が用意されています。VPC内部だけに存在するプライベートDNSゾーン(AWS Route 53 Private Hosted Zone、Azure Private DNS、Google Cloud DNS Private Zoneなど)がそれです。同じFQDN api.example.com でも、VPC内から引けばプライベートIP、インターネットから引けばパブリックIP(あるいはNXDOMAIN)が返るような構成を「スプリットホライズンDNS」と呼びます。
これにより、社外向けのAPIエンドポイントと社内向けの管理用エンドポイントをFQDNで統一しつつ、IP境界はネットワーク側で安全に分離できます。マルチアカウント・マルチVPC構成では権威ゾーンの共有や転送ルール(Route 53 Resolver Rule、Azure Private DNS Resolverなど)の設計が腕の見せ所となります。これが管理地獄への入口でもあったりしますが、それはまた別のお話。
DNSのセキュリティ
で、ここからセキュリティのお話。伝統的なDNS(UDP/53、TCP/53)の通信は平文(暗号化なし)で行われています。改ざんも盗聴もすり替えもやりたい放題。
「え、じゃあDNS改ざんされたら偽サーバに繋がれて終わりじゃない?」と思うかもしれません。実際には、前回解説したTLSが最後の砦になります。攻撃者が偽のIPへ誘導できたとしても、正規ドメインの有効な証明書を持っていないので、TLSハンドシェイクの時点で証明書検証エラーが出てブラウザが止めてくれる。証明書の「信頼の鎖」がここで活きるわけです。
ただし、ユーザが「この接続は安全ではありません」の警告を無視して突き進んだり、攻撃者がDNS検証ベースでDV証明書を不正取得できた場合は、TLSすら突破されます。TLSだけに頼るのではなく、DNS応答そのものを水際で守る仕組みも要るよね、という話です。
DNSSEC(DNS Security Extensions, RFC 4033-4035)がその代表で、権威DNSの応答にデジタル署名を付けて改ざんを検知します。前回のTLS回で解説した電子署名と同じ発想をDNSの世界に持ち込んだもので、「DNSの応答自体が本物か」を確認できます。
一方、DoH(DNS over HTTPS, RFC 8484)やDoT(DNS over TLS, RFC 7858)、さらに近年はDoQ(DNS over QUIC, RFC 9250)も登場しており、DNSの問い合わせそのものを暗号化します。DNSSECが「応答の中身が改ざんされていないか」を守るものだとすれば、DoH/DoT/DoQは「何を問い合わせたかを第三者に見られないようにする」ものです。それぞれ守る層が違うのを揃えて初めて安全となります。「どのサイトを見ているかISPに知られたくない」というプライバシーの観点から主要ブラウザやOSで対応が進んでいます。
【コラム】DNSSECを世界に急がせた「2008年カミンスキー攻撃」
DNSSECの議論自体は1990年代からありましたが、実装と普及が一気に進んだ決定打が2008年にDan Kaminskyが公表した「DNSキャッシュポイズニング」の新手法(カミンスキー攻撃)です。
従来のDNSはトランザクションIDが16ビット(最大65,536通り)しかなく、フルリゾルバが権威DNSに問い合わせを出している隙に、攻撃者が偽の応答パケットを大量に投げつけてIDを的中させれば、リゾルバのキャッシュを偽IPで上書きできてしまう、という攻撃です。カミンスキーは、存在しないサブドメイン(random123.example.com 等)を次々に問い合わせることで攻撃機会を無制限に作れる手法を示し、「TTLが切れるのを待つ必要すらない」と業界を震撼させました。
暫定対策として送信元ポートのランダム化(実効的にエントロピーを32ビット相当に拡張)が世界中のDNS実装に緊急パッチされ、根本対策としてDNSSECの導入が加速。「DNSの応答に署名を付けて検証する」という発想が一気に現実味を帯びた瞬間でした。
【コラム】DNSが落ちると世界が止まる「2016年Dyn攻撃」
DNSがいかに重要なインフラかを世に知らしめたのが、2016年10月21日のDyn DNS攻撃です。Mirai(IoT機器に感染するマルウェアが形成したボットネット)が大手DNSプロバイダDynを標的に大規模なDDoS攻撃を実行し、Twitter(現X)、Netflix、Reddit、GitHub、Spotifyといった名だたるサービスが数時間にわたり名前解決できなくなりました。
サーバそのものは無事なのに、住所を引けないだけで世界中のユーザがアクセスできなくなる。DNSの怖さを体現した事件です。この一件以降、ミッションクリティカルなサービスでは権威DNSを複数プロバイダで冗長化する「マルチDNSプロバイダ構成」が当たり前になりました。
DNSは縁の下のロードバランサー
DNSは「FQDNからIPを引く海図」だけではありません。問い合わせ元やサーバ状態に応じて返す答えを動的に変える、分散制御装置としても使われています。
Route 53やAzure Traffic Managerを触ったことがある方なら馴染み深いはず。加重ルーティング(重み付け比率でIPを振り分け、カナリアリリースに活用)、位置情報ルーティング(日本からのクエリには東京のIP、米国からはバージニアのIP)、レイテンシルーティング(最も遅延の少ないリージョンを返す)、フェイルオーバー(ヘルスチェックでダウン検知→予備系に切替)等と、並べ出すと終わらないのでこのへんで。これらをまとめてGSLB(Global Server Load Balancing)と呼びます。L7ロードバランサと組み合わせ、「リージョン間はDNSで振り分け、リージョン内はALB(AWS)やApplication Gateway(Azure)で振り分け」という二段構えの可用性設計が定番です。
ただしDNSベースの振り分けは、前述のTTL問題と無関係ではいられません。フェイルオーバーを「1分以内に切り替えたい」場合、TTLは限界まで短くした上で、それでも残るキャッシュ起因の遅延を許容する設計判断が必要になります。
おわりに
URLバーに文字を打ち込むだけでサイトに辿り着ける。その当たり前を支えているのが、世界中のDNSサーバによるたらい回しリレーです。地味ですが、これがないとインターネットは始まりません。
前回までの「NTPと時刻同期」・「SSL/TLSと証明書」と同じく、DNSも「動いてて当たり前」と思われている縁の下のインフラです。障害が起きてから初めて存在に気付かれる宿命だからこそ、「基本の"キ"」を押さえておくのが大切です。
お断り
記事内容は個人の見解であり、所属組織の立場や戦略・意見を代表するものではありません。
あくまでエンジニアとしての経験や考えを発信していますので、ご了承ください。