Hateburo: kazeburo hatenablog

SRE / 運用系小姑 / Goを書くPerl Monger

買ってよかった2023

年末恒例

kazeburo.hatenablog.com

今年は家を買いました。

新築マンションですが、早い時期から相談していたのでオーダーメイドで間取りを変更して、書庫をつくりました。

仕事机まわり

いくつかのプロジェクトで出社しての打ち合わせが多い時期もあったけど、今年も基本リモートワーク。

引っ越して机も買い替えで KANADEMONOさんの リノリウム天板にした

kanademono.design

机の下にはサンワダイレクト ケーブルトレーを設置している。横90cmあるのでいろいろ収められて便利

モニタは Intehill のモバイルモニター U16NAってのものを買った。

16" 4K+ Monitor with P3 98% and 10Bit Color [U16NA]www.intehill.com

そこまで机が広くないところにMacbook 2台置いているので小さめのモニタを探していた。これは16インチとモバイルサイズでありながら画面が16:10の解像度が4Kより少し縦が長い3840x2400でエクセル作業も楽。

しかも、背面が平らな綺麗なデザインでVESAマウント用の穴付き。

ただし、今は入手が不可能になっている。残念

www.amazon.co.jp

アームはコンパクトに畳めそうなもので安価なものを選択

ミーティング用のカメラは蟹バサミクランプで固定

クランプは短いものを使っているのでカメラに対して斜めに顔が映るので、真っ直ぐにも対応ができるようもう少し長いやつに買い換えようかなと思ってる。

会社用Macbook Proからモニタ、カメラ、キーボード、電源とケーブルがたくさん生えていると移動の時に不便なので、Thunerbolt 4の Hub を購入

机の下のケーブルトレーに設置して、Macとはケーブル一本。トラブルなどもなく動くし、スッキリしたのでよかった

ランニング

今年はハーフマラソン2回、フルマラソン1回、会社のチームで駅伝大会に参加できた。

記録はこんな感じ

  • 1/8 ハイテクハーフは 1時間32分
  • 4/16 東日本国際親善マラソン(ハーフ) 1時間36分
  • 10/29 横浜マラソン(フル)は4時間2分
  • 11/19 日本ITチャリティ駅伝(3km) 11分35秒

フルマラソンはまたチャレンジしたいところ!

靴は2足購入

前半が ASICS SUPERBLAST

これで横浜マラソンも走った

後半は ASICS MAGIC SPEED 3

初カーボンプレート入りシューズ。今年も走ったハイテクハーフをこのMAGIC SPEED 3で走る予定。

MAGIC SPEED 3はすごく軽いと感じていて、たまにSUPERBLASTに戻すと、柔らかくて跳ねるけど重いという感覚がある。

フルマラソンを走ったときに、Apple Watchの電池が最後までもたなかったのでApple Watch Ultra 2を購入。シリーズ6からの買い替えでした。ガーミンと悩んで末に、走る時と普段と使い分けるのが面倒だということでこちらに

初見デカっ!と思ったけど、重さは気にならないし画面綺麗だし、まじ電池が長持ちでいい。今年一のライフチェンジングアイテムだった

12月のここまでの走行距離はこんな感じ

去年のエントリからすると1kmあたり14秒ほど速く走ってますね

ポンポさんはNHKで映画を観てから漫画を購入。映像研も好きな漫画ですが、作品をつくる人のストーリーは良い

会社で読書会でふりかえりガイドブックを扱った。同じシリーズのスクラムブートキャンプアジャイルプラクティスガイドブックもおすすめです

さくらインターネットではITパスポートを取得することが推奨されているので勉強しました。プロジェクト管理などの用語はスコシヤクニタッテイル

個人的な今年のおすすめ技術系の本は GitLab本、スタッフエンジニアです。

GitLab本は年明けから読書会をやっていく予定なので楽しみ。チームのパフォーマンスをあげてさくらインターネットで今取り組んでいる大きな2つのプロジェクトをやっていきたい

来年は何買うかな~

ISUCON13のベンチマーカーのDNS水責め攻撃について

この記事はさくらインターネット Advent Calendar 2023の12月3日の記事になります。

先日行われました ISUCON13 の作問を担当しました。参加者の皆様、スタッフの皆様ありがとうございました。

このエントリではISUCON13のDNSに関わる要素とベンチマーカーから行われたDNS水責めについて紹介します。

ISUCON13の問題の講評と解説は以下のエントリーでも行っていますので読んでいただけると嬉しいです

isucon.net

こんいす〜

ISUCON13における名前解決

上記のエントリーにもある通り、今回のISUCONではDNSが問題の一部として出てきます。

これまでポータルから参加者は割り振られたサーバの中から負荷をかけるサーバ1台選択し、ポータルはそのサーバに対して負荷走行を行うことが多くありましたが、今回はサーバ1台を選択したら、ベンチマーカーはそのサーバの UDP/53 で起動しているDNSサーバ(PowerDNS MySQL backendを使用)に対して名前解決のリクエストを行い、得られた結果のIPアドレスに対して接続をし、負荷走行を行います。

もちろん、得られたIPアドレスが参加者に割り振られたサーバでなかった場合、エラーとなります。

ベンチマーカーの実装で利用しているGo言語の標準的なHTTPクライアントでは、TCP接続を行う関数を差し替えることができ、そこで独自の名前解決をいれています。

transport := &http.Transport{
    DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
        // ここに独自の名前解決を実装する
        return dialer.DialContext(ctx, network, addr)
    },
}
return &http.Client{
    Transport: transport,
}

名前解決にはGo言語標準の net.Resolver ではなく、レスポンスコードなどを確認するため、miekg/dns を使っています。

github.com

ISUCON13におけるDNS水責め攻撃

ISUCON13でDNSを導入した狙いの一つがDNS水責め攻撃を負荷走行中に再現することでした。さくらインターネットでもDNS水責め攻撃をうけてさまざまな対策をしています。実際の攻撃や対策の内容は以下のスライドを参考にしてください。

speakerdeck.com

当日のマニュアルにもDNSへの攻撃は記載しています。

負荷走行中、DNSサーバに対していわゆる「DNS水責め攻撃」が行われます。 DNS水責め攻撃はランダムなサブドメインを生成し、大量のアクセスを行うことでDNSサーバの応答に影響を与えることを目的とする攻撃手法です。 ベンチマーカーはDNS水責め攻撃およびスクレイピングを行い、名前解決ができると HTTPS によりアクセスを試みる動作を行います。失敗(NXDOMAINや応答なし)では来ません。

ISUCON13のDNS水責め攻撃はスコアに影響をしない、負荷走行中のサブ要素となっています。あまりにDNS名前解決やスクレイピング的な動作が負荷とならないよう、最大 3000 QPS以上にならないようにしてありました。また、一定のパフォーマンスを満たしている状態で徐々に並列数が上がるようにしてあるため、負荷が上がりすぎず、負荷走行中には通常の名前解決と合わせて最大15万から18万クエリ程度が最大だったと思われます。

DNSおよびDNS水責め攻撃への対応

ISUCON13におけるDNSへの対応ですが、以下のものを想定していました

  1. MySQLスキーマでわざと消してあるインデックスを追加
  2. DNSをアプリケーションのホストと切り離す
  3. TTLおよびPowerDNSのチューニング
  4. MySQLからBINDゾーンファイルバックエンドに切り替え、ワイルドカードレコードを設定
  5. DNSサーバの自作、dnsdistや自作DNSサーバで水責め攻撃の応答を遅延させる

1. インデックスの追加

データベースのログ解析、プロファイリングを行なったチームはインデックスが不足しているのを見つけることができたのではないかと思います。

ISUCON13の初期実装では全体的にインデックスが「ない」状態ですが、こちらもインデックスも欠けています。レコードを管理するrecordsテーブルにおいて、name に対してあるべきインデックスがなく、作成する必要があります。

CREATE TABLE records (
  id                    BIGINT AUTO_INCREMENT,
  domain_id             INT DEFAULT NULL,
  name                  VARCHAR(255) DEFAULT NULL,
  type                  VARCHAR(10) DEFAULT NULL,
  content               VARCHAR(64000) DEFAULT NULL,
  ttl                   INT DEFAULT NULL,
  prio                  INT DEFAULT NULL,
  disabled              TINYINT(1) DEFAULT 0,
  ordername             VARCHAR(255) BINARY DEFAULT NULL,
  auth                  TINYINT(1) DEFAULT 1,
  PRIMARY KEY (id)
) Engine=InnoDB CHARACTER SET 'latin1';

ALTER TABLE records ADD INDEX idx_name (records);

PowerDNSのMySQLバックエンドのスキーマはこちらでも確認できます。

doc.powerdns.com

2. DNSを別サーバに移動する

今回のベンチマーカーではDNSから返すIPを変更することで負荷走行を行うサーバを変更できるので、それを利用してDNSサーバとアプリケーションサーバを別のサーバにする構成変更ができます。制限されているとはいえ、3000 QPSはそれなりの規模であり、負荷を分散する意味は十分にあります。

上記のインデックスと構成変更を行うことで一旦はDNSについては傍においておけるようになっていたのではないかと思います。

3. TTLとPowerDNSの設定

DNSは分散システムであり、キャッシュを適切に利用することで成り立っているシステムです。ところがISUCON13の初期状態ではまったくキャッシュをしないように設定しているため、これを変更するだけでもサーバに対する負荷は削減できます。

(なぜキャッシュを無効化していたかは、DNSの反映を可能な限り高速にするためというストーリーと考えていただけると幸いです)

PowerDNSのゾーン情報はアプリケーションの初期化フェーズでゾーンファイルから読み込まれるので、ここのTTLを変更します。

diff --git a/webapp/pdns/u.isucon.dev.zone b/webapp/pdns/u.isucon.dev.zone
index bd387a1..e1fa270 100644
--- a/webapp/pdns/u.isucon.dev.zone
+++ b/webapp/pdns/u.isucon.dev.zone
@@ -10,7 +10,7 @@ $TTL 3600
 @        0 IN NS ns1.u.isucon.dev.
 @        0 IN A  <ISUCON_SUBDOMAIN_ADDRESS>
 ns1      0 IN A  <ISUCON_SUBDOMAIN_ADDRESS>
-pipe     0 IN A  <ISUCON_SUBDOMAIN_ADDRESS>
+pipe     60 IN A  <ISUCON_SUBDOMAIN_ADDRESS>
 test001  0 IN A  <ISUCON_SUBDOMAIN_ADDRESS>
 
 www              0 IN A  <ISUCON_SUBDOMAIN_ADDRESS>

マニュアルでもDNSの動作としてTTLを設定すれば、適切にキャッシュされるとしていました。

pipe.u.isucon.dev が主にベンチマーカーからアクセスされるドメインであり、これだけTTLを長めに設定すれば十分な効果が得られます。ただ、DNSラウンドロビンなどDNSによる負荷分散を行う場合、この設定をしてしまうと狙った効果が得られなかったかもしれません。

また、PowerDNSの設定ファイル /etc/powerdns/pdns.conf においてもデフォルトから意図的に変更されているものがあり、いくつか変更した方がいい項目があります。

negquery-cache-ttl=0
query-cache-ttl=0

あたりは設定しておいても良さそうです。PowerDNSのパフォーマンスチューニングについては以下のドキュメントが参考になります。自分もよくみました。

doc.powerdns.com

PowerDNSの設定などはデフォルトであるべきものがなかったりと、インフラよりのやや謎解き要素になっていたかもしれません。

4. BIND zone file backendとワイルドカード

さくらのクラウドではDNS水責め攻撃の対策として、MySQL backendからBIND zone file backendに切り替えています。

水責め攻撃ではキャッシュは有効に働きませんので、MySQL backendではDNS問い合わせの都度SQLが発行されてしまい、パフォーマンスに大きな影響がでます。一方BIND zone file backendでは、静的な設定ファイルを読み込むことでPowerDNSのメモリ内だけで処理を行うので、レスポンスが高速になることが期待できます。

ただ、BIND zone file backendでは動的にレコードを追加変更することができません。今回の問題ではユーザが増えるたびにDNSのレコードを追加する必要があるので、このままでは使えません(ユーザ追加の都度ゾーンファイルを読み込み直す対応はあったようです)。

そこで、DNSレコードとして、ワイルドカードレコードを追加しておくことで、ユーザ追加ごとにDNSレコード操作の必要をなくすという手が考えられます。

diff --git a/webapp/pdns/u.isucon.dev.zone b/webapp/pdns/u.isucon.dev.zone
index bd387a1..55a2fe4 100644
--- a/webapp/pdns/u.isucon.dev.zone
+++ b/webapp/pdns/u.isucon.dev.zone
@@ -10,8 +10,9 @@ $TTL 3600
 @        0 IN NS ns1.u.isucon.dev.
 @        0 IN A  <ISUCON_SUBDOMAIN_ADDRESS>
 ns1      0 IN A  <ISUCON_SUBDOMAIN_ADDRESS>
-pipe     0 IN A  <ISUCON_SUBDOMAIN_ADDRESS>
+pipe     60 IN A  <ISUCON_SUBDOMAIN_ADDRESS>
 test001  0 IN A  <ISUCON_SUBDOMAIN_ADDRESS>
+*        60 IN A  <ISUCON_SUBDOMAIN_ADDRESS>

ただこれに行うと、DNS水責め攻撃のクエリに対してもIPアドレスが返ってしまい、マニュアルにもあるHTTPSによるスクレイピング(という設定の余計なアクセス)が発生し、アプリケーションが十分に高速化されていないとかえって負荷になるという状態になります。

5. DNSサーバの自作、あるいは意図的な遅延

DNS水責め攻撃はDoS攻撃手法の一種です。大量のクエリを投げつけて相手のサービスに影響を与えることが狙いだとされています。

水責め攻撃への対応としては攻撃を受け付けるだけの容量、パフォーマンスを備えるというのもありますが、検知してレートリミットをかけたり、フィルタリングする対策も当然あります。

ISUCON13の水責め攻撃でも検知しての対策ができます。名前解決が行われるドイメインは username.u.isucon.dev となっており、usernameが存在していなかった場合、それは水責め攻撃のクエリとみなして、遅延をいれるという手があります。

遅延をいれることで、水責め攻撃のスループットを落とすことでき、DNSサーバのみならず、ベンチマーカー側のCPU負荷をも抑えることができます。他の処理に回せるCPUが増え、結果として全体のスコアが上昇する可能性もありました。

遅延を実現する方法として以下のような方法が考えられます。

  • アプリケーションの中にDNSサーバを組み込み、アプリケーション中でユーザの存在確認をし、遅延をいれる
  • PowerDNSのリモートバックエンドを利用する。アプリケーション中にDNS名前解決用のAPIエンドポイントを追加
  • dnsdistをPowerDNSの前に置き、NXDOMAIN(ドメインが見つからない応答)の場合に遅延挿入

DNSproxyサーバであるdnsdistではさまざまな条件でリクエストやレスポンスに処理を加えることができますが、以下のように設定でレスポンスがNXDOMAINであった場合に遅延挿入が実現できます。dnsdistを起動する前にPowerDNSは1053ポートで動くように変更しておきます。

addLocal("0.0.0.0:53", {reusePort=true,tcpListenQueueSize=4096})
newServer({address="127.0.0.1:1053",useClientSubnet=true,name="backend1"})
addACL("0.0.0.0/0")
addACL("::0/0")

addResponseAction(
  RCodeRule(DNSRCode.NXDOMAIN),
  DelayResponseAction(1000)
)

DNSの遅延をいれることで、DNS水責めの回転数は圧倒的に落ち、名前解決数も減ります。もし、名前解決で使用するCPUがボトルネックになっている場合はスコアが上がるかもしれません。

さくらインターネットの企業賞の条件は、「DNS名前解決数の最も多かったチーム」であり名前解決のパフォーマンスも出しながら、スコアのアップも目指したチームへの賞とさせていただいています。

まとめ

ISUCON13のDNSに関わる要素とベンチマーカーから行われたDNS水責めについて紹介しました。今ではDNSサーバを運用する機会はほぼなく、今後ISUCONでDNSが題材にあがることはないかもしれませんが、DNSはインターネットの大事なコンポーネントであり、参加者の皆様の今後の開発のなんらかの参考になれば幸いです。

resolv.conf におけるDNS名前解決のタイムアウト

resolv.confに記述したDNSサーバへの名前解決のタイムアウトとリトライ回数について改めて確認したので忘備録

CentOS7系およびRocky Linux 9系で確認しています。

/etc/resolv.confDNSサーバが次のように指定されている場合、

nameserver 198.51.100.3
nameserver 198.51.100.4

以下の順で利用されます。

  1. 198.51.100.3タイムアウト 5秒
  2. 198.51.100.45秒 ✖️ 2 ➗ サーバ台数 (小数点切り捨て)のタイムアウト=5 秒

これを2回繰り返し、両方接続不可能な場合、合計20秒間名前解決しようとします。

3台ある場合、

nameserver 198.51.100.3
nameserver 198.51.100.4
nameserver 198.51.100.5
  1. 198.51.100.3タイムアウト 5秒
  2. 198.51.100.45秒 x 2 ➗ サーバ台数 (小数点切り捨て)のタイムアウト=3 秒
  3. 198.51.100.52. の倍のタイムアウト=6秒

これを2回繰り返し、5秒→3秒→6秒を2回の28秒間試行します。

3台以上DNSサーバがある場合は、4台目以降は無視されます。

タイムアウトoptions timeout:N 、試行回数は options attempts:M 指定できます。それぞれ一般的なデフォルトは 5秒2回 となります。

タイムアウトを短くして試行回数を増やすのであれば、次のようにするのが良さそうです。

nameserver 198.51.100.3
nameserver 198.51.100.4
options timeout:1 attempts:3

デフォルト5秒はWebサーバ用途では長すぎるかもしれません。

また、ubuntu 22.04などでは systemd-resolvedが動作しています。こちらではローカルでキャッシュをもっていたり、接続できないDNSサーバにしばらくのアクセスをしない機能があるようです。

straceで確認

2台の場合

11:56:02.580141 socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 4
11:56:02.580190 setsockopt(4, SOL_IP, IP_RECVERR, [1], 4) = 0
11:56:02.580237 connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("198.51.100.3")}, 16) = 0
11:56:02.580294 poll([{fd=4, events=POLLOUT}], 1, 0) = 1 ([{fd=4, revents=POLLOUT}])
11:56:02.580337 sendto(4, "tC\1\0\0\1\0\0\0\0\0\0\6example\2com\0\0\1\0\1", 30, MSG_NOSIGNAL, NULL, 0) = 30
11:56:02.580428 poll([{fd=4, events=POLLIN}], 1, 5000) = 0 (Timeout)
# 5秒
11:56:07.585638 socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 5
11:56:07.585726 setsockopt(5, SOL_IP, IP_RECVERR, [1], 4) = 0
11:56:07.585766 connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("198.51.100.4")}, 16) = 0
11:56:07.585807 poll([{fd=5, events=POLLOUT}], 1, 0) = 1 ([{fd=5, revents=POLLOUT}])
11:56:07.585837 sendto(5, "tC\1\0\0\1\0\0\0\0\0\0\6example\2com\0\0\1\0\1", 30, MSG_NOSIGNAL, NULL, 0) = 30
11:56:07.585924 poll([{fd=5, events=POLLIN}], 1, 5000) = 0 (Timeout)
# 5秒
11:56:12.591058 poll([{fd=4, events=POLLOUT}], 1, 0) = 1 ([{fd=4, revents=POLLOUT}])
11:56:12.591182 sendto(4, "tC\1\0\0\1\0\0\0\0\0\0\6example\2com\0\0\1\0\1", 30, MSG_NOSIGNAL, NULL, 0) = 30
11:56:12.591282 poll([{fd=4, events=POLLIN}], 1, 5000) = 0 (Timeout)
# 5秒
11:56:17.596483 poll([{fd=5, events=POLLOUT}], 1, 0) = 1 ([{fd=5, revents=POLLOUT}])
11:56:17.596563 sendto(5, "tC\1\0\0\1\0\0\0\0\0\0\6example\2com\0\0\1\0\1", 30, MSG_NOSIGNAL, NULL, 0) = 30
11:56:17.596629 poll([{fd=5, events=POLLIN}], 1, 5000) = 0 (Timeout)
# 5秒
11:56:22.601778 close(4)                = 0
11:56:22.601824 close(5)                = 0

3台の場合

12:14:57.572565 socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 4
12:14:57.572597 setsockopt(4, SOL_IP, IP_RECVERR, [1], 4) = 0
12:14:57.572626 connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("198.51.100.3")}, 16) = 0
12:14:57.572682 poll([{fd=4, events=POLLOUT}], 1, 0) = 1 ([{fd=4, revents=POLLOUT}])
12:14:57.572731 sendto(4, "\366\237\1\0\0\1\0\0\0\0\0\0\6example\2com\0\0\1\0\1", 30, MSG_NOSIGNAL, NULL, 0) = 30
12:14:57.572795 poll([{fd=4, events=POLLIN}], 1, 5000) = 0 (Timeout)
# 5秒
12:15:02.577947 socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 5
12:15:02.578009 setsockopt(5, SOL_IP, IP_RECVERR, [1], 4) = 0
12:15:02.578045 connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("198.51.100.4")}, 16) = 0
12:15:02.578101 poll([{fd=5, events=POLLOUT}], 1, 0) = 1 ([{fd=5, revents=POLLOUT}])
12:15:02.578138 sendto(5, "\366\237\1\0\0\1\0\0\0\0\0\0\6example\2com\0\0\1\0\1", 30, MSG_NOSIGNAL, NULL, 0) = 30
12:15:02.578216 poll([{fd=5, events=POLLIN}], 1, 3000) = 0 (Timeout)
# 3秒
12:15:05.581337 socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 6
12:15:05.581406 setsockopt(6, SOL_IP, IP_RECVERR, [1], 4) = 0
12:15:05.581443 connect(6, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("198.51.100.5")}, 16) = 0
12:15:05.581484 poll([{fd=6, events=POLLOUT}], 1, 0) = 1 ([{fd=6, revents=POLLOUT}])
12:15:05.581512 sendto(6, "\366\237\1\0\0\1\0\0\0\0\0\0\6example\2com\0\0\1\0\1", 30, MSG_NOSIGNAL, NULL, 0) = 30
12:15:05.581568 poll([{fd=6, events=POLLIN}], 1, 6000) = 0 (Timeout)
# 6秒
12:15:11.587696 poll([{fd=4, events=POLLOUT}], 1, 0) = 1 ([{fd=4, revents=POLLOUT}])
12:15:11.587759 sendto(4, "\366\237\1\0\0\1\0\0\0\0\0\0\6sakura\2ad\2jp\0\0\1\0\1", 30, MSG_NOSIGNAL, NULL, 0) = 30
12:15:11.587830 poll([{fd=4, events=POLLIN}], 1, 5000) = 0 (Timeout)
# 5秒
12:15:16.592954 poll([{fd=5, events=POLLOUT}], 1, 0) = 1 ([{fd=5, revents=POLLOUT}])
12:15:16.593015 sendto(5, "\366\237\1\0\0\1\0\0\0\0\0\0\6example\2com\0\0\1\0\1", 30, MSG_NOSIGNAL, NULL, 0) = 30
12:15:16.593077 poll([{fd=5, events=POLLIN}], 1, 3000) = 0 (Timeout)
# 3秒
12:15:19.596238 poll([{fd=6, events=POLLOUT}], 1, 0) = 1 ([{fd=6, revents=POLLOUT}])
12:15:19.596327 sendto(6, "\366\237\1\0\0\1\0\0\0\0\0\0\6example\2com\0\0\1\0\1", 30, MSG_NOSIGNAL, NULL, 0) = 30
12:15:19.596422 poll([{fd=6, events=POLLIN}], 1, 6000) = 0 (Timeout)
# 5秒
12:15:25.602566 close(4)                = 0
12:15:25.602637 close(5)                = 0
12:15:25.602668 close(6)                = 0

「さくらのクラウドシェル」でWebサーバを起動する(ngrok編)

さくらインターネットから新しいサービス、「さくらのクラウドシェル」がリリースされました。 ブラウザからワンクリックで利用できるオンラインのシェル環境が起動できます。無料で使えます!!

こちら無料で試すボタンを押すと、次のモーダルが現れます。

ここで「会員IDで利用する」を選んだ場合、アウトバウンド向けの各ポート(22/53/80/443/1024-65535)の通信が利用できます。会員IDでログインしない場合は通信ができない環境が起動します。

ボタンを押すとすぐにシェルが立ち上がります。

フォントや文字色、背景画像が選べたりします。

シェルとしてはZSHが起動し、Go、PythonRuby、Node.js などの開発言語のほかに、VimEmacs、tmux、Git、Ansible、Terraform、さくらのクラウドコマンドラインから操作できる usacloud があらかじめ導入されています。

さくらのクラウドシェルでWebサーバを起動する

さくらのクラウドシェルは外から通信を受けることはできないので、ちょっとしたツールを試したりすることはできますが、Webサーバなどサーバとして利用することは通常できません。 そこで、Ngrokというトンネルサービスを使用して、Webサーバを起動してみます。

ngrok.com

ソースコードgistにおきました。

gist.github.com

このコードではGo言語とNgrokのライブラリを使っています。

ngrok.com

クラウドシェルを起動後、

sakura@cloud-shell% mkdir server
sakura@cloud-shell% cd server
sakura@cloud-shell% curl -LO https://gist.githubusercontent.com/kazeburo/795c8602e26aca66301e0142bcc024ea/raw/b9b22dbb4b23d38d37e59dadceeec418d69ed499/main.go
sakura@cloud-shell% go mod init server
sakura@cloud-shell% go mod tidy
sakura@cloud-shell% go run main.go

でサーバが起動します。

表示されたURLがクリッカブルになっている(便利!)なのでクリックして動作確認ができます。

さくらのクラウドシェルでは20分操作しないと自動終了されるので、Webサーバも20分で終了します。はい。

dnsdist のパフォーマンスを引き出すネットワーク設定

YAPC::Kyoto 2023、JANOG51 MeetingではDNSへの水責めの攻撃とその対策について話をさせていただきました。その中で DNS攻撃をフィルタリングするために利用している dnsdist についてチューニングにより大きくパフォーマンス向上できることがわかってきたので紹介します。

YAPCの記事 kazeburo.hatenablog.com

JANOG51 Meetingについての記事 knowledge.sakura.ad.jp

Linuxのネットワークパラメータのチューニング

Linuxのチューニングでよくある設定ですが、sysctl.conf で以下のカーネルパラメータをチューニングをします。

net.core.somaxconn = 65535
net.core.netdev_max_backlog = 16384
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728

ここでは rmem_maxwmem_max だけを設定しています。 net.core.rmem_defaultnet.core.wmem_default については後述します。

dnsdistのチューニング

listenするスレッド数を増やす

dnsdistでは addLocal を増やすことでlistenerスレッドの数を増やせます。

addLocal("0.0.0.0:53", {reusePort=true})
addLocal("0.0.0.0:53", {reusePort=true})
addLocal("0.0.0.0:53", {reusePort=true})
addLocal("0.0.0.0:53", {reusePort=true})

この際に reusePort を有効にするとそれぞれのスレッドにListen Queueが分散されるので高速化が見込めます。

また、CPU affinityを固定するのも良いでしょう

addLocal("0.0.0.0:53", {reusePort=true,cpus={0}})
addLocal("0.0.0.0:53", {reusePort=true,cpus={1}})
addLocal("0.0.0.0:53", {reusePort=true,cpus={2}})
addLocal("0.0.0.0:53", {reusePort=true,cpus={3}})

さらにTCPの接続が多い場合、キューのサイズを増やしておくと良いかもしれません

addLocal("0.0.0.0:53", {reusePort=true,cpus={0},tcpListenQueueSize=4096})
addLocal("0.0.0.0:53", {reusePort=true,cpus={1},tcpListenQueueSize=4096})
addLocal("0.0.0.0:53", {reusePort=true,cpus={2},tcpListenQueueSize=4096})
addLocal("0.0.0.0:53", {reusePort=true,cpus={3},tcpListenQueueSize=4096})

UDPのバッファサイズの調整

UDPの読み書きのバッファのサイズを大きくします。デフォルトはカーネルパラメータの net.core.rmem_defaultnet.core.wmem_default になります。なのでsysctlで設定することもできます。

setUDPSocketBufferSizes(8388608,8388608)

カーネルパラメータの net.core.rmem_maxnet.core.wmem_maxが設定できる最大値となります。

複数のUDPによるクエリを1度のシステムコールで読み込む

recvmmsg(2) を使うことで1度のシステムコールで複数のUDPによるクエリを読み込むことができます。 setUDPMultipleMessagesVectorSize はその最大個数を指定します。

setUDPMultipleMessagesVectorSize(100)

デフォルトは1で、通常の recvmsg(2) を使います。

ベンチマークの効果

さくらのクラウドの8コアの仮想サーバにて自作のDNS水責めベンチマークツールを使い検証を行いました。

dnsdistのみの検証のため、設定に

addAction(
  AllRule(),
  RCodeAction(DNSRCode.REFUSED)
)

を設定し、すべてのクエリに即時 REFUSED を返すように設定してあります。

初期状態

Listenerスレッドが1個の状態です。

# GOGC=500 ./prsd-bench4 -P 53 -H 192.168.10.50 --max-workers 1000 --max-length 8 --label 1 --zone example.com 2> /dev/null
2023-05-24 16:09:52.842209747 +0900 JST m=+10.002875416 resolved: 0.000000 query/sec, refused 231067.300000 query/sec, failed 72.200000 query/sec
2023-05-24 16:10:02.842144106 +0900 JST m=+20.002809775 resolved: 0.000000 query/sec, refused 220423.400000 query/sec, failed 144.400000 query/sec
2023-05-24 16:10:12.842143685 +0900 JST m=+30.002809354 resolved: 0.000000 query/sec, refused 230264.600000 query/sec, failed 144.400000 query/sec
2023-05-24 16:10:22.84215706 +0900 JST m=+40.002822729 resolved: 0.000000 query/sec, refused 220053.900000 query/sec, failed 144.400000 query/sec
2023-05-24 16:10:32.842160142 +0900 JST m=+50.002825810 resolved: 0.000000 query/sec, refused 227706.300000 query/sec, failed 144.400000 query/sec
2023-05-24 16:10:42.840047568 +0900 JST m=+60.000713237 resolved: 0.000000 query/sec, refused 232174.600000 query/sec, failed 144.400000 query/sec

20万qps以上は出ていますが、エラーもちらほらあります。

チューニング後

listenerスレッドを仮想コア数と同じ8個として、紹介したチューニングを全て入れている状態です。

# GOGC=500 ./prsd-bench4 -P 53 -H 192.168.10.50 --max-workers 1000 --max-length 8 --label 1 --zone example.com 2> /dev/null
2023-05-24 16:21:29.396835468 +0900 JST m=+10.001673734 resolved: 0.000000 query/sec, refused 417461.100000 query/sec, failed 0.000000 query/sec
2023-05-24 16:21:39.396746836 +0900 JST m=+20.001585106 resolved: 0.000000 query/sec, refused 445043.800000 query/sec, failed 0.000000 query/sec
2023-05-24 16:21:49.396666734 +0900 JST m=+30.001505003 resolved: 0.000000 query/sec, refused 431644.600000 query/sec, failed 0.000000 query/sec
2023-05-24 16:21:59.396818157 +0900 JST m=+40.001656423 resolved: 0.000000 query/sec, refused 438897.200000 query/sec, failed 0.000000 query/sec
2023-05-24 16:22:09.396665403 +0900 JST m=+50.001503669 resolved: 0.000000 query/sec, refused 440108.300000 query/sec, failed 0.000000 query/sec
2023-05-24 16:22:19.395664578 +0900 JST m=+60.000502848 resolved: 0.000000 query/sec, refused 425201.300000 query/sec, failed 0.000000 query/sec

エラーなく40万qps以上処理できるようになりました。

まとめ

権威DNSサーバであるNSDでは、UDPのバッファサイズ、recvmmsgの利用などはデフォルトで行われており、dnsdistパフォーマンスの調査をするにあたり参考にしました。

www.nlnetlabs.nl

この記事がDNSサーバのパフォーマンスで困った際にお役に立てれば幸いです。

YAPC::Kyoto 2023でDNS水責め攻撃とGoによるベンチマーカの発表をしてきました #yapcjapan

YAPC::Kyoto 2023 に参加してきました!

数年ぶりに開かれたYAPCで、数年ぶりに会うエンジニアの同窓会みたいな雰囲気ありつつ、新しい参加者も多く最高でした。オフラインイベント楽しいです。スタッフの皆様ありがとうございました!! 京都まで行かせてくれた家族にも感謝

会場のKRPは2006年まで働いていた場所で、17年も経ってそこで発表する機会をいただいたのは個人的に感慨深いものがあります。はてなの大西さんの発表は自分にとってもとても懐かしく聞いておりました。

発表してきた

私の発表はこちら

speakerdeck.com

DNS水責め攻撃とその対策については1月に開催されたJANOG51 Meeting in 富士吉田でも紹介しております。発表について記事にしていただいているので詳しくはこちらを。

knowledge.sakura.ad.jp

YAPC::Kyotoではここから派生して、Goによるベンチマーカ作りと高速なベンチマーカをつくるためのチューニングポイントを紹介しています。ベンチマークをとる、プロファイルを行うなども簡単ですが詰め込んでいます。ベンチマーカ自体のコードは公開していませんが、この資料が何かの参考になれば幸いです。

資料中に出てくるISUCON本はこちら

Go言語のベンチマークやプロファイリングについては mattn さんが書かれた「Go言語プログラミングエッセンス」がわかりやすくおすすめです。参考にさせていただきました。

オフラインイベントの体験をもっとたくさんのエンジニアに

いやぁ、ほんと行ってよかった。

この体験をたくさんのエンジニアに、特にコロナ禍で一度も経験していない若いエンジニアに味わって欲しい。YAPCは次回広島開催の話もあるし、他のカンファレンスでもオフライン増えてきているので積極的に参加するよう背中を押していきたい。そのためさくらインターネットや自分がお手伝いできることがあればお声がけいただけると嬉しいです。

YAPC::Kyoto 2023で話します! そしてチケットを今すぐに購入しましょう!!

YAPC::Kyoto 2023の採択トークが決まったようですね。面白そうなトークが沢山あってすごいですね。

blog.yapcjapan.org

私のトークも採択されました。ありがとうございます。ありがとうございます!!

こういう話をします。

クラウドファースト、クラウドバイデフォルトなどと言われるようにクラウドサービス前提にシステムの構築運用がなっています。インターネットにおける重要な基盤技術のひとつであるDNSにおいてもクラウドサービスが使われるようになっています。

トークでは、さくらのクラウドDNSアプライアンスサービスに行われたDNS水責め攻撃と呼ばれるDDoS攻撃の内容およびその対策について紹介します。また、対策にあたって作成したDNSサーバへ負荷をかけるベンチマーカを題材にハイパフォーマンスなベンチマーカを作る上で必要な要素も紹介します。

アジェンダ
* さくらのクラウド DNSアプライアンスとは(Perlも使っているよ)
* DNS水責め攻撃とその実際
* GoによるハイパフォーマンスなDNSのベンチマーカ作成

この話は、JANOG51 Meetingにて発表して内容に関連しているものとなります。資料の公開もしていますので、ぜひご覧ください。

speakerdeck.com

発表のアーカイブ動画もこちらで公開されています。

www.janog.gr.jp

チケットを買ってくれ

それはそうとして、そんなYAPC::Kyoto 2023ですがチケット販売が今月1月の31日までとなっています。

passmarket.yahoo.co.jp

今月中にチケットを買わないと参加ができないのです! 今、まさにこの瞬間、すぐに買いましょう!!!!! 豪華ノベルティがついてくる個人スポンサーチケットは残席わずかとのこと!!

買いましたか?買いましたね。それでは会場でお会いいたしましょう!

yapcjapan.org

今回会場となる、京都リサーチパークは以前働いていた場所で、そちらに久しぶりに行って、エンジニア仲間と会い、またしゃべれる機会があるということで非常に楽しみにしております。YAPC::Kyoto今からワクワクが止まりません!!

オマージュ元

tamamemo.hatenablog.com