k6で極める負荷テスト:シンプルなスクリプトからCI/CD自動化まで

Development tutorial - IT technology blog
Development tutorial - IT technology blog

午前2時の電話と、そこから得た貴重な教訓

あなたがぐっすり眠っているとき、電話のベルが激しく鳴り響きます。ダッシュボードは真っ赤に染まり、サーバーのCPU使用率は100%に達し、データベース接続は飽和状態。ユーザーからはアプリがフリーズしているという苦情が殺到しています。眠い目をこすりながら3時間かけてデバッグした結果、犯人は昨日デプロイしたばかりの新しいAPIだったことが判明しました。たった50人が同時にアクセスしただけで、システム全体が完全に崩壊してしまったのです。

これはホラー映画のシナリオではなく、私が実際に経験した現実です。当時の最大の失敗は、ロジックのユニットテスト(Unit Test)だけに集中し、パフォーマンス・テスト(負荷テスト)を忘れていたことでした。同じような悲劇を繰り返さないために、今回私が皆さんにご紹介したい強力なツールが「k6」です。

なぜk6なのか?

JMeterのような重くて複雑なUIツールは一旦忘れましょう。k6ならJavaScriptでテストシナリオを書くことができます。これは非常に便利です。テストコードをリポジトリ内で管理でき、バージョン管理も行える上、既存のコーディングスキルをそのまま活かせるからです。

1. k6をあっという間にインストールする

Linux(Ubuntu/Debian)ユーザーなら、おなじみのコマンドを数行実行するだけです:

sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6

macOSユーザーなら、Homebrewでもっと早くインストールできます: brew install k6

2. 最初のスクリプト:APIへの挨拶

製品APIにリクエストを送信する test.js ファイルを作成してみましょう:

import http from 'k6/http';
import { sleep } from 'k6';

export default function () {
  http.get('https://api.example.com/products');
  sleep(1);
}

実行コマンド: k6 run test.js。非常にシンプルですね。これで1秒間に1リクエストを実行しました。しかし実際には、システムはこれよりもはるかに複雑な負荷に耐える必要があります。

k6を負荷テストの「仕事人」へアップグレードする

数千人の仮想ユーザー(Virtual Users – VUs)が同時にアクセスする状況をシミュレートするには、Stagesを設定する必要があります。コマンドラインの長いパラメータを入力する代わりに、コードの中に設定を直接記述することで、管理と再利用が容易になります。

export const options = {
  stages: [
    { duration: '30s', target: 20 }, // 最初の30秒で20ユーザーまで徐々に増やす(ウォームアップ)
    { duration: '1m', target: 20 },  // 安定性を確認するため1分間20ユーザーを維持
    { duration: '10s', target: 0 },  // 正常に終了させるため0まで徐々に減らす
  ],
};

この設定により、実際のユーザー行動を正確に模倣できます。アクセス数は通常、1秒間で突発的に増えるのではなく、徐々に増加していくものだからです。

安全性のしきい値を設定する(ChecksとThresholds)

画面に「Finished」と表示されるのを確認するだけでは不十分です。APIがステータスコード200を返しているか、レスポンスタイムがビジネス要件を満たしているかを知る必要があります。

import { check } from 'k6';

let res = http.get('https://api.example.com/products');
check(res, {
  'ステータスが200': (r) => r.status === 200,
  'p95レスポンスタイム < 500ms': (r) => r.timings.duration < 500,
});

特に重要なのが Thresholds(しきい値)です。例えば、「リクエストの10%が失敗した、あるいは95%のリクエスト(p95)が1秒以上かかった場合にテストを失敗(fail)とする」といったルールを定義できます。これは、不具合のあるコードが本番環境(production)にリリースされるのを防ぐ最後の砦となります。

実践的なシナリオのシミュレーション:認証とデータフロー

現在のほとんどのAPIはログインを必要とします。以下は、ログインしてトークンを取得してからデータにアクセスするフローを処理する方法です:

import http from 'k6/http';
import { check, sleep } from 'k6';

export default function () {
  const loginRes = http.post('https://api.example.com/login', {
    username: 'admin',
    password: 'password123',
  });
  const token = loginRes.json('token');

  const params = { headers: { Authorization: `Bearer ${token}` } };
  const res = http.get('https://api.example.com/private/data', params);
  
  check(res, { 'ステータスが200': (r) => r.status === 200 });

  // ユーザーがコンテンツを読むために1〜4秒間静止するのをシミュレート
  sleep(Math.random() * 3 + 1); 
}

Tips: JSONを処理したり、スクリプトを書くためにデータを素早くフォーマットしたい場合は、toolcraft.app をよく使います。VS Codeに拡張機能をインストールして試行錯誤するよりも、ずっと時間を節約できます。

負荷テストをCI/CDパイプラインに組み込む

時間がある時だけテストを実行するのではなく、GitHub Actionsに統合して、コードがプッシュされるたびにシステムが自動的にパフォーマンスをチェックするようにしましょう。新しいコードによってAPIが遅くなった場合、ビルドは即座に失敗します。

name: パフォーマンスチェック
on: [push]
jobs:
  k6_test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: k6を実行
        uses: grafana/[email protected]
        with:
          filename: test.js

負荷テストの現場で役立つ実戦経験

  • 本番環境(Production)では絶対にテストしない: どれほど自信があっても、必ずステージング(Staging)環境を構築してください。自分のシステムにうっかり自前でDDoS攻撃を仕掛けたくはないはずです。
  • データベースを綿密に調査する: APIのコードは最適化できても、ボトルネックの90%はインデックスが不足しているクエリにあります。k6の実行中はスロークエリログを有効にして、犯人を特定しましょう。
  • リアルなデータを使用する: 同じユーザー一人だけでテストし続けないでください。1,000人の異なるユーザーリストが入ったCSVファイルなどを使用し、データベースのキャッシュによる「偽の高速レスポンス」を避けましょう。
  • エラー率(Error Rate)を観察する: システムがHTTP 200を返していても、レスポンスボディの中身がロジックエラーを報告している場合があります。正確性を保証するために、返された内容を細かくチェックする必要があります。

わずか30分のk6セットアップで、枕を高くして眠れるようになります。真夜中に叩き起こされる心配も、予期せぬサーバーダウンの恐怖もありません。皆さんが本当に堅牢なシステムを構築できることを願っています!

Share: