クローラーのアクセスによるサーバーCPU高負荷への対策とApache mod_dosdetector

isdはじめに

2024年7月、僕が運用保守を担当しているWebサーバーで、周期的にCPU負荷が高くなる現象が発生しました。

調査したところ、2つのクローラーによる集中アクセスが原因で、これらのクローラーによるアクセスをブロックする対策を行いました。
また、同様の集中アクセス発生に備え、Apache mod_dosdetectorを導入しました。

isd発生した現象

Webサーバーで、毎時20分~50分にかけてCPU負荷が高くなる。
CPU Load Averageが、通常時は0.5程度なのに、2~6程度まで上がる。
その際、Webページの表示も少し遅くなる。

isd調査

ネットワークインタフェースの転送量を確認したところ、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を超えるとパフォーマンスに影響が出るのではないか」
という想定でした。
それに比べるとかなり短い間隔でのアクセスです。

isd対策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負荷は落ち着きました。

isd対策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種類存在します(もっとあるかもしれませんが)。

  1. オリジナル
  2. mod_dosdetector_fork(1の改良版)
  3. 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負荷が高くなるのを緩和する効果はあります。

isdおわりに

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
 

Follow me!