クローラーのアクセスによるサーバーCPU高負荷への対策とApache mod_dosdetector
はじめに
2024年7月、僕が運用保守を担当しているWebサーバーで、周期的にCPU負荷が高くなる現象が発生しました。
調査したところ、2つのクローラーによる集中アクセスが原因で、これらのクローラーによるアクセスをブロックする対策を行いました。
また、同様の集中アクセス発生に備え、Apache mod_dosdetectorを導入しました。
発生した現象
Webサーバーで、毎時20分~50分にかけてCPU負荷が高くなる。
CPU Load Averageが、通常時は0.5程度なのに、2~6程度まで上がる。
その際、Webページの表示も少し遅くなる。
調査
ネットワークインタフェースの転送量を確認したところ、CPU負荷と同じように増減しているため、何らかのWebアクセスが原因と推測しました。
7月22日9時台のアクセスログを10分ごとに切り出して確認すると、CPU負荷が高い時間帯に、以下の2種類のUser-Agentによるアクセスが多いことがわかりました。
(URLパスは省略しています。)
1つめは、Bytespiderというクローラーによるアクセスです。
47.128.44.52 - - [22/Jul/2024:09:05:01 +0900] "GET <url path> HTTP/2.0" 301 0 "-" "Mozilla/5.0 (Linux; Android 5.0) AppleWebKit/537.36 (KHTML, like Gecko) Mobile Safari/537.36 (compatible; Bytespider; spider-feedback@bytedance.com)"
Bytespiderは、TikTokで有名なByteDance社によるニュース配信サービスのクローラーとのことです。
(参考)
・【Tips】迷惑系クローラーBytespiderをブロックせよの巻 – ADV&TECH
https://www.advantech.jp/archives/3619
1時間で4,457件(1秒あたり1.24件)のリクエストがあり、それらすべてがアセットではなく、PV(ページビュー)に相当するアクセスでした。
2つめは、ClaudeBotというクローラーによるアクセスです。
3.16.47.65 - - [22/Jul/2024:09:21:03 +0900] "GET <url path> HTTP/2.0" 200 6263 "-" "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; +claudebot@anthropic.com)"
ClaudeBotは、Anthropicという生成AIサービスのクローラーとのことです。
(参考)
・Anthropic(アンスロピック)の生成AI「Claude(クロード)」とは何か? Google Bard との関係は? – 吉積情報コラボラボ
https://www.yoshidumi.co.jp/collaboration-lab/google-bard05
・Does Anthropic crawl data from the web, and how can site owners block the crawler? – Anthropic Support
https://support.anthropic.com/en/articles/8896518-does-anthropic-crawl-data-from-the-web-and-how-can-site-owners-block-the-crawler
9時18分から9時35分までの限定したアクセスで9,360件(1秒あたり8.67件)のリクエストがあり、こちらもすべて、アセットではなくPVに相当するアクセスでした。
9時台だけではなく、毎時15分から40分ぐらいにかけて、同じように集中アクセスがありました。
このサーバーのWebサイトはWordPress製で、キャッシュを導入していないため、ほぼすべてのリクエストで、DBアクセスが発生します。
低スペックのクラウドサーバーのため、
「1秒あたり1~2PVを超えるとパフォーマンスに影響が出るのではないか」
という想定でした。
それに比べるとかなり短い間隔でのアクセスです。
対策1
サーバースペックを鑑みると、クローラーのアクセス間隔は、(ざっくりですが)3秒以上開けてほしいと思います。
先ほど記載したAnthropicのサポートサイトでは
「robots.txt で Crawl-delay を指定してほしい」
とのことですが、robots.txt に従ってくれるかどうかは保証されていませんし、他の悪質なクローラーが ClaudeBot という名前を騙る可能性もあります。
Webサイトの性質上、ByteDanceやAnthropicのサービスで取り上げられることのメリットはないと判断して、これらのクローラーのアクセスをブロックすることにしました。
このサーバーでは、WebサーバーとしてNginxを使用しているため、Nginxで、User-Agentの文字列によりブロックする設定を追加しました。
# vim /etc/nginx/conf.d/drop.cnf # Crawler Access if ( $http_user_agent ~ "Bytespider" ) { set $deny_f 1; } if ( $http_user_agent ~ "ClaudeBot" ) { set $deny_f 1; } ## deny if ( $deny_f = 1) { return 403; }
※drop.cnf では、これ以外にも「URLパス+アクセス元IPアドレス」「リファラー」などの条件でブロックするルールを記載しています。
※アクセス元IPアドレスによるクローラーのブロックについては、クローラーが使用するIPアドレスは公開されないことが多いですし、変更、追加される可能性が高いので効果が薄いと思います。
Apacheであれば、以下のような感じでしょうか。
SetEnvIf User-Agent "Bytespider" bot SetEnvIf User-Agent "ClaudeBot" bot <RequireAll> Require all granted Require not env bot </RequireAll>
これでひとまず、Bytespider と ClaudeBot によるアクセスはなくなり、サーバーのCPU負荷は落ち着きました。
対策2
同じような悪質なクローラーや不正アクセスに備え、Webサーバーソフトウェアでアクセス頻度によるアクセス制限を導入しました。
このWebサーバーは、実は「Nginxリバースプロキシ+バックエンドApache」という、同じサーバー内でWebサーバーソフトウェア2段の構成となっています。
(関連記事)
・Apacheの前段にNginxリバースプロキシサーバーを設置 – 稲葉サーバーデザイン
https://inaba-serverdesign.jp/blog/20230411/nginx_reverseproxy_apache_wordpress.html
この構成の場合、アクセス頻度によるアクセス制限としては、以下の候補があります。
- Nginx ngx_http_limit_conn_module
- Nginx ngx_http_limit_req_module
- Apache mod_dosdetector
本来は、上位のNginxで設定できるとよかったのですが、アクセス頻度の判定ルールが、
ngx_http_limit_conn_module は、接続数(同時接続数?)ベース
ngx_http_limit_req_module は、リクエスト数ベース
となっており、やや扱いにくいと感じたため、使い慣れている Apache mod_dosdetector を採用しました。
※「接続数」といっても、最近のブラウザは、1ブラウザでも並列で複数の接続を試みているようで、どれぐらいだと「悪質」か判断しにくい。
※「リクエスト数」だと、各Webページによってアセット分リクエスト数が異なり、どれぐらいだと「悪質」か判断しにくい。
DoS攻撃対策ツール Apache mod_dosdetector は、Apacheの公式モジュールではありませんが、一定時間あたりのPV数を想定した以下のような設定が可能です。
- 同一IPアドレスからx秒間にy回アクセスがあればz秒ブロックする
- アクセスカウント対象のMIME type(またはファイル拡張子)の指定
このため、柔軟性に優れ、本番環境に導入しやすいと考えています。
この Apache mod_dosdetector ですが、改良版含め、3種類存在します(もっとあるかもしれませんが)。
- オリジナル
- mod_dosdetector_fork(1の改良版)
- mod_dosdetector_syslog(1の改良版)
このうち、僕は、オリジナルからの改良が少ないことと、設定の柔軟性から、2のmod_dosdetector_fork を愛用していたのですが、いつのまにか、この mod_dosdetector_fork は、Githubでの公開が削除されてしまいました。
しょうがないので、他のサーバーにインストールしたときのソースコードをコピーして導入しました。
Apache mod_dosdetectorの導入、設定については、以下の記事などが詳しいので、そちらを参照してください。
(参考記事)
・mod_dosdetectorを使ってみましょうよ。~挫折を乗り越え~ – DENET 技術ブログ
https://blog.denet.co.jp/mod_dosdetector_tukao/
・Apache Mod Dosdetector でDOS攻撃対策を行う – A-frontierプレスルーム
https://www.a-frontier.jp/technology/mod-dosdetector1/
・DoS対策としてEC2のapacheにmod_dosdetectorを入れる – COLORI
https://colo-ri.jp/develop/2022/01/ec2-apache-mod-dosdetector.html
ここでは、当該Webサーバーの環境に導入したときの要点のみ記載します。
まず、「Apacheの前段にNginxリバースプロキシ」という構成のため、デフォルトでは、Apacheのアクセス元IPアドレスは、Nginxの「127.0.0.1」となっています。
Apache mod_dosdetector で、ユーザーのアクセス元IPアドレスを扱うために、Apache mod_remotip モジュールを使用します。
このサーバーは Rocky Linux 8で、Rocky LinuxのリポジトリからインストールしたApacheには mod_remoteip が含まれており、デフォルトで有効となっています。
mod_remoteip で、
「接続元が RemoteIPTrustedProxy で指定したNginxのIPアドレスの場合、X-Forwarded-For の値をアクセス元IP(RemoteIP)とみなす」
という設定を行います。
# vim /etc/httpd/conf.d/remoteip.conf RemoteIPHeader X-Forwarded-For RemoteIPTrustedProxy 127.0.0.1
(mod_dosdetectorの動作とは無関係ですが)ついでに、Apacheのログにユーザーのアクセス元IPアドレスを記録するよう、ログフォーマットを変更します。
LogFormatの %h を %a に置き換えるのがポイントです。
# vim /etc/httpd/conf/httpd.conf <IfModule logio_module> # You need to enable mod_logio.c to use %I and %O #LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined </IfModule>
mod_dosdetectorの設定は以下のような感じ。
# vim /etc/httpd/conf.d/dosdetector.conf # Usualy apxs will add this line automatically in httpd.conf LoadModule dosdetector_module modules/mod_dosdetector.so # Exclude images, stylesheets and javascript files <IfModule setenvif_module> #SetEnvIf Request_URI "\.(gif|jpe?g|ico|js|css|png)$" NoCheckDoS SetEnvIf Request_URI "\.(gif|jpe?g|ico|js|css|png|svg|webp|woff|woff2)$" NoCheckDoS </IfModule> # Mod_DoSDetector configuration DoSDetection on DoSPeriod 10 DoSThreshold 35 DoSHardThreshold 50 DoSBanPeriod 600 DoSTableSize 1000
PVベースのカウントで判定したいので、SetEnvIf Request_URI で、カウントしないアセット系のファイル拡張子を指定するのですが、デフォルト設定は少し古いので、svg, webp, woff などを追記しています。
DoSxxx で、
「10秒間(DoSPeriod)に35回以上(DoSThreshold)のアクセスでソフトエラー(SuspectDoS=1)とみなし、600秒間Ban(DoSBanPeriod)する」
というブロック判定ルールを設定しています。
DoSTableSizeは、デフォルトは少なく感じたので増やしています。
実際にブロックする設定は、VirtualHost configで指定しています。
「SuspectDoS=1 のときは、429 Too Many Requests エラーを返す」
# mod_dosdetector RewriteEngine On RewriteCond %{ENV:SuspectDoS} =1 RewriteCond %{REMOTE_ADDR} !^127\.0\.0\.1$ RewriteRule .* - [R=429,L]
なお、1.オリジナル版と、2.mod_dosdetector_fork は、
「DoSHardThreshold を超えた(SuspectHardDoS=1)あとの動作は、DoSBanPeriod の時間が経過しなくてもBan状態から解除されてしまう」
「DoSThreshold を超えた(SuspectDoS=1)あとの動作は、DoSBanPeriod の時間が経過したのちBan状態から解除する」
という仕様?となっています。
このため、ここでは、ブロックする条件として、DoSThreshold(SuspectDoS=1) のほうを使用しています。
3.mod_dosdetector_syslog は、この点が改良されて、DoSBanPeriod 間、Ban状態を維持するそうです。
(参考)
・mod_dosdetector でDoS対策, BANが予定よりも早く解除される – teratail
https://teratail.com/questions/15606″
・mod_dosdetector_syslog – Github
https://github.com/gure-suzuki/mod_dosdetector_syslog
2.mod_dosdetector_fork では、DoS攻撃と判定したときは、Apacheのエラーログ(VirtualHostごとにエラーログを設定していれば、VirtualHostのエラーログ)に、以下のように記録されます。
[Sun Sep 08 16:49:08.980452 2024] [dosdetector:notice] [pid 3777397:tid 139867027605248] [client <IPアドレス>:0] '<IPアドレス>' is suspected as DoS attack! (counter: 36)
エラーログを「DoS」で検索すると見つけやすいです。
設定後は、意図したとおりに動作するかどうか、十分な動作確認をしましょう。
とくに、
「特定のIPアドレスがブロックされたとき、他のIPアドレスからは問題なくアクセスできるかどうか」
が重要です。
クローラーのように、複数のIPアドレスからアクセスされる場合は、一度そのIPアドレスをブロックしても他のIPアドレスからまたアクセスがきてしまいますが、それでもCPU負荷が高くなるのを緩和する効果はあります。
おわりに
Webサーバーで、クローラによる集中アクセスが発生してCPUが高負荷な状態となったため、Nginxで、これらのUser-Agentによるアクセスをブロックする設定を行いました。
また、このサーバーは「Nginxリバースプロキシ+バックエンドApache」という構成のため、Apache mod_dosdetectorを導入し、一定期間に一定以上のアクセスがあった場合、一定期間そのIPアドレスからのアクセスをブロックする設定を追加しました。
近年、生成AIによるクローラーアクセスが増えているようで、Cloudflareでもこれらのアクセスをブロックする機能が追加されたそうです。
(参考)
・Cloudflare、すべての生成AIによるクローラーをワンクリックでブロックする機能を無料で提供開始 – Publickey
https://www.publickey1.jp/blog/24/cloudflareai.html
生成AIにより便利な世の中になる面もあるかと思いますが、節度あるアクセスを望みます。
(関連記事)
・Apacheの前段にNginxリバースプロキシサーバーを設置 – 稲葉サーバーデザイン
https://inaba-serverdesign.jp/blog/20230411/nginx_reverseproxy_apache_wordpress.html