EC2における単位時間あたりの名前解決制限の対応

f:id:ngsw:20191129132719p:plain この記事はBASE Advent Calendar 2019の2日目の記事です

SREの ngsw です。

BASEはAWSを採用しており、その中でもEC2に大きく依存する部分があります。EC2においては以下の制約があります。

DNS の制限
各 Amazon EC2 インスタンスは Amazon が提供する DNS サーバーへ送信できるパケット数を
ネットワークインターフェイスあたり最大 1024 パケット/秒に制限しています。
この制限を増やすことはできません。

AWSのマネージドサービスはエンドポイントの提供になり、そのエンドポイントへのアクセスに名前解決は必要となります。BASE全体へのアクセス数は増加傾向にありますので、名前解決試行回数も同じく増加傾向にあると考えて良いでしょう。

「考えて良いでしょう」というのは平常時にはこの制約は顕在化せず問題視されないからです。 整理するとここでの課題は

  • 突発的なアクセス増にも耐えうる名前解決方法を持ちましょう
  • アクセス増時のスループットが名前解決制約で頭打ちにならないようにしましょう

ということになります。

AWS における名前解決制約回避のベストプラクティス

実際の対応方法は上記の記事がすべてといっても過言ではありません。しかしそれらがそのまま適用できたわけでもありません。その差分や行間をあわせてお伝えできたら幸いです。

そもdnsmasqってなに?

具体的にはEC2インスタンス内部にDNSキャッシュサーバを持つことができます。簡潔に申し上げれば dnsmasqを起動させ 127.0.0.1:53 で応答させることにより当該の名前解決制約が回避できるということです。

念の為回避できないかもしれない状況を(推測ではありますが)上げておきます。

  • 個別のFQDN 1024個超の秒間名前解決
  • IPv6名前解決 有効な状態で個別のFQDN 512個超の秒間名前解決(都度AレコードとAAAAレコードを解決しようとする)
    • 今回お伝えした手順では dnsmasq はIPv6に関して問い合わせしないことを確認しました。

主題は「dnsmasq がAWSリゾルバ問い合わせを秒間1024回超行ってしまう事自体は依然として制約されている」になります。ですからCNAMEレコードの都度問い合わせやTXTレコードの include 記述なども関わってくる問題と考えます。

dnsmasq 設定内容

以下にどのように設定したかを書きました。

起動プロセス名 dnsmasq
実行User dnsmasq
dnsmasq UID 5353
dnsmasq GID 5353
dnsmasq設定ファイル /etc/dnsmasq.conf
最大キャッシュTTL 2
最小キャッシュTTL 1
キャッシュレコード数 500
ネガティブキャッシュTTL 60
dnsmasq用resolvファイル /etc/resolv.dnsmasq
リゾルバIP 169.254.169.253 VPC での DNS の使用 - Amazon Virtual Private Cloud
dhclient設定ファイル /etc/dhcp/dhclient.conf
timeout 300
retry 60
dhclientが設定するリゾルバIP 127.0.0.1, 169.254.169.253 supersede domain-name-servers

以下の観点を持っています。

  • UID / GID はわかりやすいものを
  • dnsmasq自身のキャッシュ時間をいたずらに長くしない
  • dnsmasqプロセスが死んだ場合であってもAWS外部リゾルバをバックアップとして利用しない

UID / GIDについて

僕自身、UID / GID は明示しておきたいと考えるので明示した次第です。が、ここで現行環境適用時に問題が置きました。

サーバ構成は chef で管理されているのですが、dnsmasq導入以前に設定された既存ユーザにおいては UID / GID が明示されていない状況でした。

dnsmasq はサーバ設定の早い段階で入れておくのがよいだろうと考えたため、いの一番にインストールして設定するようレシピを書き換えたのですが、現在使用しているOSの仕様からか「UID / GIDを明示指定すると、それ以後に作成したユーザやグループは明示指定IDからのインクリメントになる」ということが起きてしまいました。つまり hoge 5354:5354 fuga 5355:5355のような形です。

これでは既存のEC2と新規のEC2で差分がでてしまいます。構築時期によってサーバの状況が違うことになってしまうのはあまりいい気持ちではありませんので、レシピ自体を見直してすべてのユーザにUID / GIDを明記するよう変更しました。

dnsmasq自身のキャッシュ時間について

「キャッシュ時間をいたずらに長くしない」ということについてです。今回のdnsmasq導入目的は「名前解決制限という制約の解決」でした。ですので「1秒間だけキャッシュできればよく、それ以外の副作用を極力排除する」というスタンスでいました。キャッシュ時間を長くしたとしてもおそらくはTTLなりをみてdnsmasqはよしなに動作してくれると信じてはいるのですが、検証工数を最小にするために短い値を設定しています。

懸念を具体的に申し上げると「dnsmasqのキャッシュ時間を長くすることで、AWSマネージド・サービスがオートスケールした場合のALIASレコードを無視する時間が長くなるのではないか」でした。

dnsmasqプロセスが死んだ場合について

本来であればバックアップのDNSを外部に用意したいものですが、今回はその対応を行っていません。理由は「VPCオプションによるプライベートホストゾーンに依存しているから」となります。外部のDNSはこの情報を知り得ませんので、問い合わせができたところで期待した名前解決は行われず正常に動作しません。

先にも書きましたが今回の目的は「名前解決制限という制約の解決」に絞っていますので、完璧を目指すのではなく「よりよいものをなるだけ少ない工数で」という割り切りをしています。

「うまく機能しているのか」確かめよう

少なくともAWSリゾルバへの問い合わせ回数が激減しているか確かめられればいいかと思います。僕は tcpdump で確認しました。

#!/bin/bash
while :
do
  dig +short 01endpoint.example.com >/dev/null 2>&1 &
  dig +short 02endpoint.example.com >/dev/null 2>&1 &
  dig +short 03endpoint.example.com >/dev/null 2>&1 &
  dig +short 04endpoint.example.com >/dev/null 2>&1 &
done

上記スクリプト dig.sh 程度でも効果はみてとれました。

chmod +x dig.sh

# dnsmasq導入前
./dig.sh
tcpdump -s 0 -i any port 53 -w dnsmasq_before.pcap

# dnsmasq導入後
./dig.sh
tcpdump -s 0 -i any port 53 -w dnsmasq_after.pcap

ここで取得できた *.pcapwiresharkCocoa Packet Analyzer で比較してみましょう。

dnsmasq before

  • 172.31.0.2 がAWSリゾルバ
    • 都度問い合わせが発生していることがわかる

f:id:ngsw:20191126181245p:plain

dnsmasq after

  • 169.254.169.253 がAWSリゾルバ
  • 127.0.0.1 DOMAIN が dnsmasq
    • この画像ではAWSリゾルバが存在しない程度にdnsmasqで完結していることがわかる

f:id:ngsw:20191126181352p:plain

対応を終えて

前職もAWSを利用していたのですが、dnsmasqではなく外部DNSをリゾルバ指定する対応でした。今思えば「dnsmasqの恩恵でもあるレイテンシ軽減」を手放していたのだなとすら感じます。今回は採用例(AWSベストプラクティスだから当然なのですが)でしたがSREという立場を考えるともっとたくさんの不採用例を持たなくてはならないなと考えます。

「手段と目的を取り違えるな」という言葉があります。その反対側には「手段の数とその質こそが、何を目的にできるかを制約している」とも個人的には考えていますので、技術職としていろいろなツール/ソフトウェアを触って手数そのものを増やしていきたい、その経験でもってプロダクトに貢献していきたいともあわせて考えた次第です。