DNSサーバーへの負荷対策

この記事は アイスタイル [Advent Calendar 2023](https://qiita.com/advent-calendar/2023/istyle) 6日目の記事です。

# はじめに

こんにちは。
アイスタイルでサービスインフラの運用をしている土居です。

イベントに向けて負荷試験を行ったところ、他のシステムの応答速度が劣化してしまいました。
その時に効果が出た対策と効果測定方法をまとめます。

<br>
対象のシステムの構成や特徴は以下の通りです。

## システム構成
* 負荷掛け対象システムはAWS上に設置されている（概ねEC2で稼働している）
* 該当のEC2インスタンスのOSはLinuxベース
* 負荷掛け対象システムはDirect Connectで接続されたオンプレミス環境のDNSサーバーを参照している（AWSが用意しているDNSやパブリックなDNSは使用していない）

[Direct Connectとは(AWSドキュメント)](https://docs.aws.amazon.com/ja_jp/directconnect/latest/UserGuide/Welcome.html)

## 事情
* 他システムの応答速度の劣化具合とDNSサーバーへの着信クエリ数の増加具合に相関関係がある
* 1インスタンスからのDNSサーバーへのアクセスが多い
* DNSアクセスが増加するシステムの種類は多くない（2つのシステム、4か5のオートスケーリンググループ）

## 問題と分析
負荷を掛けたところ、負荷掛け対象以外のシステムでWebアクセスの応答速度が劣化してしまいました。

<img width="60%" alt="応答速度のグラフ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/0cf42df2-296c-4374-f076-77578f9cfc0d.png">

少ない負荷を掛けて調査していくと、環境内に設置しているDNSサーバーへの負荷が原因であることが分かってきました。

<img width="100%" alt="DNSのグラフ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/11888a2f-30e2-84f5-4696-f8f5a55015b8.png">
<font>${\scriptsize ↑負荷掛け時のDNS着信クエリ増量分は1.5M〜2M（約500K→2M〜2.5M）}$</font>


## 対策
DNSサーバーへの負荷が原因であることが分かってきたため、DNSキャッシュを導入して対策しました。

DNSキャッシュの仕組みとしては、

* 問い合わせ元のサーバーでDNS問い合わせ結果をキャッシュする
* DNSサーバーの手前にさらにDNSサーバーを用意して、そのサーバーで問い合わせ結果をキャッシュする

が考えれ、両方の対応を検討していましたが、結果的に問い合わせ元のサーバーでDNS問い合わせ結果をキャッシュすることで大幅な改善が見られたため、これが対策となりました。
なお、後者のDNSキャッシュサーバーの導入は試験が難航し、イベントに間に合いませんでした。

問い合わせ元のサーバー（負荷掛け対象システムの各インスタンス）でDNS問い合わせ結果をキャッシュするために、Dnsmasqを導入しました。

## Dnsmasqとは

[Wikipedia](https://ja.wikipedia.org/wiki/Dnsmasq)によると、

>軽量で比較的容易に設定できるDNSサーバのフォワーダとDHCPサーバをもつソフトウェア

とのことでした。

* 急ぎの対策が必要であったこと
* DNS問い合わせ元にDNSキャッシュを導入する場合のナレッジが多そうであったもの

という基準で選定されたのではないかと推測しています。

## 導入コスト

1インスタンス当たりの問い合わせ数が多く、導入すべきインスタンス数自体が多い訳ではないため、クライアント側で対策をすることが比較的容易でした。

オートスケーリングで使用するAMIに導入し、インスタンスを展開した後に負荷試験を再度実施しました。

## 結果
負荷掛け時のDNS着信クエリ増加量が大幅に減りました。

<img width="100%" alt="改善したDNSのグラフ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/1620bcfd-186f-959b-25b3-c7e00a5a4117.png">
<font>${\scriptsize ↑負荷掛け時のDNS着信クエリ増量分は100K程度（約500K→600K）}$</font>

DNS着信クエリ量が大幅に減ったことが確認でき、DNSキャッシュの導入は成功と言える結果となりました。
ただ、DNSキャッシュを導入したインスタンスからIPアドレスが動的に変わるAWSリソースへアクセスすることがあるため、キャッシュすることで無効なIPアドレスにアクセスしてしまう懸念がありました。
それについては、以下の2点の準備と調査をしました。

* DNSキャッシュを消すコマンドを把握しておく


```
$ systemctl force-reload dnsmasq
```
<font>${\scriptsize サービスをリロードすることで、DNSキャッシュがクリアされる}$</font>

* DNS問い合わせ先の主要なAWSリソース（今回の場合はRDS）エンドポイント名のTTLを確認する

```
$ dig xxxxxxdb.cluster-xxxxxxx.ap-northeast-1.rds.amazonaws.com
```

```:出力結果
（略）
;; ANSWER SECTION:
xxxxxxdb.cluster-xxxxxxx.ap-northeast-1.rds.amazonaws.com. 5 IN CNAME xxx-xxxxxxdb-instance-1.xxxxxxx.ap-northeast-1.rds.amazonaws.com.
xxx-xxxxxxdb-instance-1.xxxxxxx.ap-northeast-1.rds.amazonaws.com. 5	IN A xxx.xxx.xxx.xxx
（略）
```
<font>${\scriptsize ↑TTLが5秒（万一、RDSエンドポイントのIPアドレスが変わっても最大影響時間は5秒）であることが確認できた}$</font>

ちなみに、上記のようなRDSエンドポイントの名前解決もオンプレミスのDNSサーバーに問い合わせる構成です。

## 効果の深掘り

検証の精度を高めるために、DNSサーバーが着信したクエリの内訳を確認することにしました。

負荷掛け中にDNSサーバーでパケットキャプチャを取り、問い合わせ元IPアドレス別のパケット数を集計します。

```
$ sudo tcpdump -n -i eth0 dst port 53 and src net xxx.xxx.xxx.xxx mask xxx.xxx.xxx.xxx -w yyyymmdd_001.pcap
```
<font>${\scriptsize ※net}$</font><font>${\scriptsize xxx.xxx.xxx.xxx}$</font><font>${\scriptsize mask}$</font><font>${\scriptsize はAWS環境のプライベートネットワークアドレスとmaskを指定}$</font>
```
$ tcpdump  -r yyyymmdd_001.pcap -nn dst port 53 | awk -F " " '{print $3}' | awk -F "." -v 'OFS=.' '{print $1,$2,$3,$4}' | uniq -c | sort -r
```

```:出力結果
reading from file 20221124_001.pcap, link-type EN10MB (Ethernet)
   101 xxx.xxx.xxx.xxx
    98 xxx.xxx.xxx.xxx
    95 xxx.xxx.xxx.xxx
（略）
```

ちなみに、パケットキャプチャを別の視点で確認したところ、RDSエンドポイントなどのAWSリソースに対する名前解決ではなく、オンプレミス環境上のサーバーや同環境内でホストするWebシステムのドメインに対する名前解決（これらのTTLはDNSキャッシュのキャッシュ時間より長い）の方が多かったです。

EC2インスタンスのIPアドレスおよびインスタンス名のリストを取得し、負荷掛け対象の各インスタンス毎のDNSサーバーへの問い合わせ量を確認しました。
```
$ aws ec2 describe-instances --output=table --query 'Reservations[].Instances[].{InstanceId: InstanceId, PrivateIp: join(`, `, NetworkInterfaces[].PrivateIpAddress), GlobalIP: join(`, `, NetworkInterfaces[].Association.PublicIp), Name: Tags[?Key==`Name`].Value|[0]}'
```

```:出力結果
------------------------------------------------------------------------------------------------------
|                                                    DescribeInstances                               |
+----------------+----------------------+----------------------------------------+-------------------+
|    GlobalIP    |     InstanceId       |                   Name                 |    PrivateIp      |
+----------------+----------------------+----------------------------------------+-------------------+
|                |  i-xxxxxxxxxxxxxxxxx |  xxxxxx-xxx                            |  xxx.xxx.xxx.xxx  |
|                |  i-xxxxxxxxxxxxxxxxx |  xxxxxx-xxx                            |  xxx.xxx.xxx.xxx  |
|                |  i-xxxxxxxxxxxxxxxxx |  xxxxxx-xxx                            |  xxx.xxx.xxx.xxx  |
（略）
```

負荷試験のたびにインスタンスの終了と開始を繰り返したため、同じ役割のインスタンスのIPアドレスが変わります。
問い合わせ元のIPアドレスだけでは、負荷掛け対象のインスタンスからの問い合わせであることを確認できないので、インスタンス名も取得しました。

表示している画像ではインスタンス名をマスクしているので分かりづらいですが、インスタンス単位でもDNSキャッシュ導入前後の変化を確認できました。（実際には、導入前のインスタンス名は取得できていなかったので正確ではありませんが、水色背景の列と紫背景の列のそれぞれの数値に数倍程度の差があるように見えました）

この表では、DNSキャッシュ導入前の60秒当たりのDNS系パケット数が762,205、導入後の60秒当たりのDNS系パケット数が13〜14万程度という集計結果になっています。

<img width="100%" alt="集計結果" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/25cad6b0-6832-7118-6a86-28fb8116c127.png">
<font>${\scriptsize 上記の表には、負荷掛け対象のシステムとVPCを共有する他のシステムのインスタンスのデータも混じっている}$</font>

## 他に効果として考えられること

計測はしていませんが、今回のDNSキャッシュを導入したことで、Direct Connectやオンプレミスネットワークの通信量の減少も期待できると思います。

また、同じ結果が返ってくる問い合わせを何千回も実施することがなくなり、スマートになったと感じます。

ちなみに、どうやらLinuxではDNSの結果をキャッシュしませんが、MacやWindowsではするようです。
今回のDNS問い合わせ元サーバーがLinuxだったので効果が出たのだと思います。

## 感想

この件では、監視データや手動取得した情報を使って明確に効果を確認できました。
実際にイベント本番もDNS関連の問題なくシステムを稼働させることができて良かったです。

ちなみにここでのイベントとは、[@cosme BEAUTY DAY](https://www.cosme.com/campaign/beautyday/)のことですが、実はこの調査をしたのは1年前のBEAUTY DAY前です。

そして、この記事を公開するのは今年のBEAUTY DAY後になりますが、執筆自体はBEAUTY DAY前にしています。

なので、去年と同じ仕組みを使ったDNS周りの機能が今年も無事に動いてBEAUTY DAYを終えられていることを願っています。