Amazon EC2から負荷テストを行うときの落とし穴と対策
ども、大瀧です。
ここのところ新機能を追いかける記事ばかりだったので、今回は少し毛色の異なるノウハウ系を書いてみます。
負荷テストの前置き(読み飛ばし可)
「Webサイトがテレビ番組で紹介されることになった!大幅なアクセス増がやってくる!」という場合に、ロードバランササービスのElastic Load Balancing(ELB)やCDNのCloudFrontなどスケールするサービスを組み合わせ乗り切るというのは、クラウドらしい柔軟性の高さを活かせる典型な例かと思います。実際、弊社の事例でも多くのお客様に提供し、ご好評をいただいています。
これらのサービスを構成するにあたり、実際のアクセス増に耐えられるか試すため負荷テストを実施することも多いと思いますが、大規模なケースになってくると難しいのが負荷テストを実施するマシンの確保です。これについてもAmazon EC2であれば、Auto ScalingやCloudFormationなどを組み合わせて短時間で大量のマシンを準備することが可能です。こんなブログ記事を公開していたりもします。
しかし、単純にマシンを多く用意するだけでは、様々なボトルネックにより思うような負荷掛けのパフォーマンスが出ないこともあります。その原因の筆頭として挙げられるのが、DNSのキャッシュです。
DNSキャッシュはAWSでの負荷テストの大敵
例えばELBでは、ロードバランサのノードが高負荷に耐えられるよう複数のノードでロードバランサが構成されます。ノードはELBへのアクセス状況により自動でスケールアウト、スケールインされ、ELBのDNS名に対するラウンドロビンのレコードがノード数と対応して増減します。AWSの様々なサービスでこのようなDNSをギミックとする負荷分散手法が用いられています。
ところが、負荷をかけるクライアントでDNSレコードをキャッシュしてしまうと、ノードのIPアドレスが固定されてしまいます。せっかくスケールアウトしてELBのノードが複数あったとしても、そのうちの1台にアクセスが集中してしまい負荷に耐えられなくなる恐れがあります。そこで、負荷をかけるクライアントではなるべくDNSレコードをキャッシュしない工夫が必要です。
クライアントでのDNSキャッシュ対策として、レコードのTTL値を短くすることが考えられます(ELBなどAWSサービスで提供されるレコードはTTL値が固定のものがあるため、短く出来ない場合もあります)。また、クライアントによってはTTL値を無視するもの(有名どころでJava JVMなど。マッツォさんのブログ記事が詳しいです)もあるので、負荷をかけるクライアントアプリケーションの仕様を確認しておきましょう。
DNSキャッシュを持つのはローカルマシンだけではない
DNSキャッシュを保持するのは、負荷をかけるマシンだけとは限りません。各マシンがDNSの問い合わせを行う、DNSキャッシュサーバーでもDNSキャッシュが保持されることを考慮する必要があります。EC2インスタンスで負荷掛けのアプリケーションを実行する場合、DNSキャッシュサーバーはEC2インスタンスが接続するVPC(Virtual Private Cloud)のDNSサーバー(IPアドレス: <VPCのネットワークアドレス>.2)になります。大量のリクエストを発行するために複数のインスタンスを実行したとしても、同一VPCで実行するインスタンスは同一DNSキャッシュサーバーにDNS問い合わせを行いレコードがキャッシュされるため、TTLが切れるまでDNSキャッシュサーバーは全インスタンスに同じレコードを返してしまいます。
ローカルDNSキャッシュサーバー(Unbound)の活用
そこで、EC2インスタンスで負荷掛けを行うときは、インスタンスごとにDNSキャッシュサーバーを持つ構成がオススメです。VPCのキャッシュサーバーを経由せず、インスタンスごと別々にコンテンツサーバーに問い合わせることで、インスタンス間でレコードを分散させることができます。
今回は、ユーザーデータでDNSキャッシュサーバーのUnboundを構成する例を示します。以下のテキストデータをEC2インスタンス起動時に読み取るユーザーデータに入力してください。
動作確認環境
- Amazon Linux AMI 2014.03.2 (HVM) - ami-29dc9228
ユーザーデータ
#!/bin/bash yum install -y --enablerepo=epel unbound service unbound start chkconfig unbound on sed -i 's/PEERDNS=yes/PEERDNS=no/g' /etc/sysconfig/network-scripts/ifcfg-eth0 sed -i 's/nameserver .*/nameserver 127.0.0.1/g' /etc/resolv.conf
Unboundは既定の設定でサービス起動と有効化、/etc/resolv.confでローカルのUnboundにDNSサーバーを向けています。6行目はDHCPクライアントによる/etc/resolv.confの書き換えを抑制しています。
これで、各インスタンスはローカルのDNSキャッシュを別々に持つため、インスタンスごとに異なるレコードの保持が期待できます。もちろん、先に紹介しましたアプリケーションのキャッシュ設定も合わせて確認しましょう。
今回のインストール方法だとUnboundをデフォルト設定で動作させるため、VPCの内部向け(<リージョン名>.compute.internal)の名前解決ができなくなります。必要に応じてVPCのDNSサーバーへのフォワード設定を追加しましょう。
適切に動作しているかどうかのデバッグは、実際のDNS問い合わせのトラフィックをtcpdumpコマンドなどで確認するのがよいでしょう。
キャッシュ対策有効: DNSコンテンツサーバーと通信している
[ec2-user@ip-172-31-19-127 ~]$ sudo tcpdump port 53 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes 23:54:02.870989 IP 172.31.19.127.37832 > G.ROOT-SERVERS.NET.domain: 44401% [1au] PTR? 152.77.146.59.in-addr.arpa. (55) 23:54:02.871803 IP 172.31.19.127.54687 > c.root-servers.net.domain: 47974% [1au] PTR? 4.36.112.192.in-addr.arpa. (54) 23:54:02.988219 IP c.root-servers.net.domain > 172.31.19.127.54687: 47974- 0/8/13 (642) 23:54:02.988302 IP 172.31.19.127.34330 > c.in-addr-servers.arpa.domain: 30494% [1au] PTR? 4.36.112.192.in-addr.arpa. (54) 23:54:02.988426 IP 172.31.19.127.51017 > k.root-servers.net.domain: 25426% [1au] DNSKEY? in-addr.arpa. (41) 23:54:02.988489 IP 172.31.19.127.34853 > d.root-servers.net.domain: 26515% [1au] NS? in-addr.arpa. (41) [ec2-user@ip-172-31-19-127 ~]$
キャッシュ対策無効: 通信内容がVPC内同士(ホスト名が.compute.internal)
[ec2-user@ip-172-31-19-127 ~]$ sudo tcpdump port 53 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes 23:55:13.593671 IP ip-172-31-19-127.ap-northeast-1.compute.internal.42296 > ip-172-31-0-2.ap-northeast-1.compute.internal.domain: 10051+ A? yahoo.com. (27) 23:55:13.593897 IP ip-172-31-19-127.ap-northeast-1.compute.internal.55755 > ip-172-31-0-2.ap-northeast-1.compute.internal.domain: 45846+ PTR? 2.0.31.172.in-addr.arpa. (41) 23:55:13.594103 IP ip-172-31-0-2.ap-northeast-1.compute.internal.domain > ip-172-31-19-127.ap-northeast-1.compute.internal.42296: 10051 3/0/0 A 98.139.183.24, A 206.190.36.45, A 98.138.253.109 (75) 23:55:13.594438 IP ip-172-31-19-127.ap-northeast-1.compute.internal.59651 > ip-172-31-0-2.ap-northeast-1.compute.internal.domain: 12685+ AAAA? yahoo.com. (27) [ec2-user@ip-172-31-19-127 ~]$
まとめ
負荷テスト時の落とし穴として、DNSキャッシュのはたらきとEC2での対策としてローカルキャッシュ(Unbound)の活用をご紹介しました。このほかにもALIASレコードとCNAMEレコードの動きの違いやELB/CloudFrontのラウンドロビンレコードの上限数など話すネタは尽きませんので、気が向いたらまた書きますね。