こんにちは!生成AIの活用がどんどん進む中、社内システムやWebサービスに最新のAI機能を組み込む開発が増えてきましたよね。「うちのサービスでもAIを使いたい!」というワクワク感の一方で、セキュリティ担当としては、ちょっと冷や汗が出るポイントでもあるんです。
特に、AI特有の「重たい処理」を狙ったサイバー攻撃については、これまでのWebサイトを守る方法とは少し頭を切り替える必要があります。
今回は、新人のIT担当者や、セキュリティに初めて触れる開発者の方に向けて、AIシステムの命綱である「可用性(サービスを止めないこと)」を守るためのリソース制限、中でも「DoS(サービス妨害)攻撃からGPUを守るキューイングとスロットリング」について、身近な例えを交えながら一歩ずつ学んでいきましょう!
—
1. 家の鍵だけでは防げない? AIを狙う「泥棒」の手口
まずは、私たちが普段使っているWebサイトと、生成AIが組み込まれたシステムの「違い」を考えてみますね。
普通のWebサイト(例えば、ブログ記事を表示するページなど)は、サーバーにとって「お茶を淹れるくらい簡単な作業」の連続です。だから、少しくらいアクセスが急増しても、サーバーはケロリとしています。
一方で、生成AIの「推論リクエスト」は、例えるなら「超難解な論文をその場で一から執筆してもらう」ようなものなんです。裏側では、高価でパワフルなGPU(画像処理半導体)がフル回転し、膨大な電力を消費しながら必死に言葉を紡ぎ出しています。
身近な例え:ラーメン屋の「一席占領おじさん」
ここで、街の人気ラーメン屋を想像してみてください。
お店には席(GPUの処理枠)が10席しかありません。通常なら、お客さんがサッと食べてサッと帰り、回転率よくみんなが美味しいラーメンを食べられますよね。
しかし、そこに悪意を持った「泥棒(攻撃者)」がやってきて、こんな嫌がらせをしたとします。
- 「メニューに載っていない、めちゃくちゃ具だくさんで注文をつける特製ラーメンを、一晩中作り続けてくれ!」と、無限に注文票を出し続ける。
- 仲間を何百人も動員して、一斉にその重たい注文を浴びせる。
結果はどうなるでしょうか?
お店の調理場(GPU)はパンクし、本当にラーメンを食べたい一般のお客さんは、外で何時間も待たされるか、お店に入れなくなってしまいますよね。これが、AIシステムにおける DoS攻撃(サービス妨害攻撃) の正体です。
—
2. 攻撃を防ぐための二大巨頭:「スロットリング」と「キューイング」
では、この悪意ある「重たい注文の嵐」からAIシステムを守るにはどうすればよいのでしょうか?
セキュリティの世界では、防犯の仕組みとして大きく2つのアプローチを用意します。それが 「スロットリング(制限)」 と 「キューイング(順番待ち)」 です。
スロットリング(来場制限・速度制限)
これは、ラーメン屋の入口に「お一人様1時間に1杯まで!」というルールを設けるようなものです。
短時間に何回もリクエストを送ってくる怪しい人や、あきらかに不自然な大量のアクセス(ボットなど)を検知したら、「ちょっとペースを落としてくださいね」と入り口でピシャリと遮断します。
キューイング(整理券システム・順番待ち)
スロットリングだけだと、アクセスが集中したときに正規のユーザーまで弾かれてしまいます。そこで登場するのが「整理券」です。
調理場(GPU)のキャパシティを超えるリクエストが来たら、すぐエラーにするのではなく、「順番待ちの列(キュー)」に並べてもらいます。「ただいま混み合っております。あなたの番号は42番です。順番が来るまで少々お待ちください」と優しく待機してもらうわけですね。これなら、時間は少しかかっても、システムがクラッシュするのを防げます。
—
3. 実践!コードで見るリソース制限の仕組み
「考え方は分かったけれど、具体的にどうやってコードを書けばいいの?」という声が聞こえてきそうですね。
ここからは、実際の開発現場で使える簡単な防御のアイデアを、Python(FastAPIなど)をベースにした疑似コードで見ていきましょう。
今回は、リクエストが来すぎたときに「順番待ちの列(キュー)」に入れ、さらに「1人あたりの速度制限(スロットリング)」をかけるイメージです。
import time
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
import asyncio
app = FastAPI()
# 【スロットリング用】簡易的なIPアドレスごとのアクセス記録(本番ではRedisなどの外部キャッシュを使います)
request_counts = {}
# 【キューイング用】同時に処理できるAI推論の最大数(GPUのメモリや性能に合わせて調整!)
MAX_CONCURRENT_REQUESTS = 5
semaphore = asyncio.Semaphore(MAX_CONCURRENT_REQUESTS)
# 1分間に許可する最大リクエスト数(スパム防止)
RATE_LIMIT_PER_MINUTE = 10
@app.post("/api/v1/generate")
async def generate_text(request: Request, prompt_data: dict):
client_ip = request.client.host
current_time = time.time()
# 1. 【スロットリングのチェック】
# クライアントごとのリクエスト履歴を管理し、短期間の連打をブロックする
if client_ip not in request_counts:
request_counts[client_ip] = []
# 1分以上前の古い履歴を削除
request_counts[client_ip] = [t for t in request_counts[client_ip] if current_time - t < 60]
if len(request_counts[client_ip]) >= RATE_LIMIT_PER_MINUTE:
# 制限回数を超えている場合は、HTTP 429 (Too Many Requests)を返す
return JSONResponse(
status_code=429,
content={"error": "リクエストが多すぎます。1分後に再度お試しください。"}
)
# 今回のアクセス時刻を記録
request_counts[client_ip].append(current_time)
# 2. 【キューイングの適用】
# 同時実行数が上限に達している場合、ここで順番待ち(セマフォによるブロック)が発生します
async with semaphore:
try:
# ログ出力(現場での監視用)
print(f"[{client_ip}] AI推論処理を開始します...")
# ここに実際の重たいAI推論処理(LLMの呼び出しなど)が入ります
# 例: response = await heavy_ai_model.generate(prompt_data["prompt"])
await asyncio.sleep(2) # 重たい処理のシミュレーション
return {"status": "success", "result": "AIからの生成テキストがここに入ります。"}
except Exception as e:
# 万が一、処理中にエラーが起きた場合のハンドリング
raise HTTPException(status_code=500, detail="AIの処理中にエラーが発生しました。")
コードの解説と現場の知見
このコードでは、以下の2重の防壁を作っています。
1. RATE_LIMIT_PER_MINUTE で、悪意あるスクリプトが秒速で大量の負荷をかけるのを防ぎます(スロットリング)。
2. asyncio.Semaphore を使うことで、どれだけたくさんのリクエストが来ても、サーバーが同時に受け持つ作業を強制的に 5 件までに絞り込んでいます(キューイング)。
現場のインフラエンジニアとしては、このコードを直接アプリに書くだけでなく、手前のリバースプロキシ(Nginx や Cloudflare などのCDN)の段階でレートリミットを設定しておくのが、サーバーのCPU負荷すら逃がせる鉄則のテクニックになります。
—
4. セキュリティ担当からのメッセージ
AIシステムの可用性保護は、一朝一夕には完璧になりません。「どのくらいコストがかかる処理なのか」「正規のユーザーは1日に何回くらい使うものなのか」をビジネス部門やデータサイエンティストとしっかり話し合い、適切なバランスを見つけることが大切です。
「難しそうだな…」と感じたかもしれませんが、まずは小さな制限(スロットリング)をかけることから一歩ずつ始めてみましょう。あなたのその丁寧な実装が、会社の高価なGPUと、サービスを待っている大切なお客さんを守る盾になります。
それでは、次のセキュリティの冒険でお会いしましょう!安全で快適な開発ライフを!
コメント