Zenn
🐍

負荷テストツール『k6』をローカル環境でサクッと試してみた

2025/02/23に公開

はじめに

簡単なPHPアプリケーションとDockerコンテナを用意して、k6を試してみました!
k6は、軽量かつ高速なパフォーマンステストツールで、JavaScriptでテストスクリプトを記述できるのが特徴です。
今回は、特定のURLに対して負荷をかけるため、ローカル環境をDockerで構築しています。
「自分の環境でサクッと試したい」という方はぜひ参考にしてください!

Macにk6をインストールするには、以下のコマンドを実行します。

brew install k6

専用のJavaScriptファイルを用意すれば、すぐに動作させることができます。

構成

最低限の動作確認ができるシンプルなディレクトリ構成にしています。

├── docker-php-app
│   ├── Dockerfile
│   ├── docker-compose.yml
│   └── src
│       ├── calculate.php
│       ├── index.php
│       └── submit.php
└── script.js

環境作成

Dockerfile
# PHP + Apache の公式イメージを使用
FROM php:8.1-apache

# Apache のドキュメントルートを設定
COPY src/ /var/www/html

# ポートを公開
EXPOSE 80
docker-compose.yml
version: '3.8'
services:
  php-apache:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:80"
    volumes:
      - ./src:/var/www/html

実行処理

calculate.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>計算処理 - k6 Test App</title>
</head>
<body>
    <h1>計算処理</h1>
    <p>負荷テスト用の計算処理を実行しています...</p>

    <?php
    $result = 0;
    // より重い計算処理
    for ($i = 1; $i <= 1000; $i++) {
        for ($j = 1; $j <= 1000; $j++) {
            // 複雑な数学関数を利用
            $value = sqrt($i) * log($j + 1) + pow($i, 2) * sin($j);
            $result += $value;
        }
    }
    echo "<p>計算結果: " . number_format($result, 2) . "</p>";
    ?>
</body>
</html>

※ 画面表示

submit.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>フォーム送信 - k6 Test App</title>
</head>
<body>
    <h1>フォーム送信</h1>
    <form action="submit.php" method="post">
        <label>名前: <input type="text" name="name" required></label>
        <button type="submit">送信</button>
    </form>

    <?php
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $name = htmlspecialchars($_POST['name']);
        echo "<p>送信された名前: " . $name . "</p>";
    }
    ?>
</body>
</html>

※ 画面表示

負荷テストスクリプト

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

// 負荷テスト対象の URL
const BASE_URL = 'http://localhost:8080';
const CALCULATE_URL = `${BASE_URL}/calculate.php`;
const SUBMIT_URL = `${BASE_URL}/submit.php`;

// シナリオ設定
export const options = {
  stages: [
    { duration: '10s', target: 100 }, // 10 秒で VU を 100 まで急増
    { duration: '2m', target: 100 },  // 2 分間 維持
    { duration: '10s', target: 0 }    // 10 秒で 0 に急減
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'], // 95% のリクエストが 500ms 未満
    http_req_failed: ['rate<0.01'],   // 失敗率を 1% 未満に抑える
  }
};

// 負荷テストのメイン処理
export default function () {
  // 1. calculate.php への GET リクエスト
  const calculateRes = http.get(CALCULATE_URL);
  check(calculateRes, {
    'calculate.php: ステータスが 200': (res) => res.status === 200,
  });

  // 2. submit.php への POST リクエスト
  const payload = { name: `user_${Math.floor(Math.random() * 1000)}` };
  const submitRes = http.post(SUBMIT_URL, payload);
  check(submitRes, {
    'submit.php: ステータスが 200': (res) => res.status === 200,
  });

  // 1秒休憩
  sleep(1);
}

ステージの流れと処理内容

⭐️急増フェーズ (10秒間で VU を 100 に増加)

負荷テストを開始した最初の10秒間で、仮想ユーザー(VU)が 0 から 100 に急速に増加します。
この段階でサーバーが急激な負荷にどれだけ耐えられるかを確認します。

⭐️維持フェーズ (2分間で VU を 100 のままで維持)

2分間、100人の仮想ユーザーがサーバーにリクエストを送り続けます。
高負荷状態を維持して、サーバーがこの負荷に対してどれくらいのスケーラビリティと安定性を持つかを測定します。

⭐️急減フェーズ (10秒間で VU を 0 に急減)

最後の10秒間で仮想ユーザーが急速に減少します。
サーバーが負荷を急激に減少させた後にどれだけ速やかに安定するかを確認します。

しきい値の設定

SLAとして、シナリオ設定に以下の条件を thresholds に設定しています。

  • 95% のリクエストが 500ms 未満で応答すること
  • エラーレートが 1% 未満であること

結果

k6 の実行結果は以下の通りです。

% k6 run script.js


         /\      Grafana   /‾‾/  
    /\  /  \     |\  __   /  /   
   /  \/    \    | |/ /  /   ‾‾\ 
  /          \   |   (  |  (‾)  |
 / __________ \  |_|\_\  \_____/ 
 execution: local
        script: script.js
        output: -

     scenarios: (100.00%) 1 scenario, 100 max VUs, 2m50s max duration (incl. graceful stop):
              * default: Up to 100 looping VUs for 2m20s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)


     ✓ calculate.php: ステータスが 200
     ✓ submit.php: ステータスが 200

     checks.........................: 100.00% 21458 out of 21458
     data_received..................: 16 MB   117 kB/s
     data_sent......................: 2.9 MB  20 kB/s
     http_req_blocked...............: avg=9.85µs   min=0s    med=4µs     max=2.09ms p(90)=8µs      p(95)=10µs    
     http_req_connecting............: avg=3.98µs   min=0s    med=0s      max=687µs  p(90)=0s       p(95)=0s      
   ✓ http_req_duration..............: avg=108.24ms min=298µs med=59.08ms max=3.26s  p(90)=259.3ms  p(95)=349.85ms
       { expected_response:true }...: avg=108.24ms min=298µs med=59.08ms max=3.26s  p(90)=259.3ms  p(95)=349.85ms
   ✓ http_req_failed................: 0.00%   0 out of 21458
     http_req_receiving.............: avg=48.32µs  min=6µs   med=40µs    max=2.05ms p(90)=86µs     p(95)=107µs   
     http_req_sending...............: avg=17.04µs  min=2µs   med=15µs    max=1.08ms p(90)=28µs     p(95)=30µs    
     http_req_tls_handshaking.......: avg=0s       min=0s    med=0s      max=0s     p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=108.17ms min=270µs med=59ms    max=3.26s  p(90)=259.26ms p(95)=349.81ms
     http_reqs......................: 21458   152.328208/s
     iteration_duration.............: avg=1.21s    min=1.05s med=1.16s   max=4.27s  p(90)=1.36s    p(95)=1.5s    
     iterations.....................: 10729   76.164104/s
     vus............................: 7       min=7              max=100
     vus_max........................: 100     min=100            max=100

注目すべきポイントは以下の通りです。

  • http_req_duration の p(95) が 349.85ms なので、95% のリクエストが 500ms 未満で応答していることが確認できました。
  • http_req_failed が 0% であるため、エラーは発生していません。

これらの結果から、今回のPHPアプリケーションは 100 VU の負荷に対して十分に応答できていると言えます。

おわりに

初めてk6を触ってみましたが、ローカル環境でも簡単に試すことができました!
JavaScriptで実装できるため、条件分岐やループ、外部APIとの連携など、より複雑なシナリオを構築することも可能です。
ユーザーの行動パターンをシミュレーションしたり、認証が必要なフローをテストするなど、実際の利用状況に近い負荷テストを実施できるのが楽しみです!

Discussion

ログインするとコメントできます