1. はじめに
こんにちは、グリーホールディングス株式会社に2025年に新卒入社した高田倫太朗です。
本記事では、Google Kubernetes Engine (GKE)上で運用しているAPIサーバーに対して、負荷テストを実施した記録をまとめます。
今回の負荷テストは、リクエスト数増加を見越して、最適なpod数、リソース量を設定するために行います。
自分が運用しているサービスでは、短時間の間にリクエスト量が倍以上増加することがあります。 そのような状況に対処できるか調べるためにも、急激なリクエスト量増加を負荷テストのシナリオに組み込む必要がありました。
また、grpcサーバーも運用しているため、状況によってはgrpcサーバーへの負荷テストを実施する必要があります。
2. なぜk6を選んだか
k6の概要
Grafana k6 は、負荷試験・パフォーマンステスト専用のOSSツールです。2016年にLoad Impactによって開発が開始され、2021年にGrafana Labsによって買収されてからは、同社の監視・可観測性プラットフォームの一部として積極的な開発が続けられています。
k6は「開発者ファースト」の思想で設計されており、従来のGUIベースの負荷テストツールとは大きく異なるアプローチを採用しています。テストシナリオをJavaScriptで記述することで、開発者が慣れ親しんだプログラミング言語を使用して直感的にテストを作成できます。また、Go言語で実装されたコアエンジンにより、高いパフォーマンスと軽量性を実現しており、大規模な負荷テストでもリソース効率よく実行することが可能です。
モダンなDevOpsワークフローに適応するため、CI/CDパイプラインとの統合、Infrastructure as Code(IaC)との親和性、クラウドネイティブ環境での実行サポートなど、現代的なソフトウェア開発に必要な機能を標準で提供しています。
k6の強み
k6の強みとして以下が挙げられると考えました。
開発チームとの親和性
- JavaScriptでのシナリオ記述 : インフラ担当だけでなくアプリ開発者でも扱いやすい
- バージョン管理 : テストスクリプトをGitで管理し、レビューやCI/CDに組み込みやすい
- テストの再現性 : コードベースなので環境に依存しない
技術的優位性
- Go製による軽量性 : 他の負荷試験ツール(JMeterなど)と比べてメモリ消費が少なくスケールしやすい
- プロトコル対応 : HTTP/REST, gRPC, WebSocketなどに対応しており、GKEのように複数のマイクロサービスをテストする場合に適している
- クラウドネイティブ : Kubernetesでの実行に最適化されている
運用面でのメリット
- コンテナ化 : 公式Dockerイメージが提供されており、Kubernetes Jobとして簡単に実行可能
- 監視統合 : PrometheusやGrafanaとの連携が標準でサポート
- 拡張性 : プラグインやカスタムメトリクスで機能拡張が容易
k6の特徴と要件への適合性
今回の要件に対してk6が適している理由は以下の通りです:
今回の要件
- GKE環境でのAPIサーバー負荷テスト
- gRPCサーバーの負荷分散確認
- 段階的な負荷増加テスト
- 継続的なテスト実行環境の構築
k6の適合性
- Kubernetes統合 : Kubernetes Jobとしての実行が標準サポートされており、GKE環境に適している
- 開発効率 : JavaScriptでの設定により、チーム内での保守性が高い
- プロトコル対応 : gRPCプロトコルにネイティブ対応しており、将来的な拡張に対応可能
- 柔軟な負荷パターン : ramping-arrival-rateなど段階的な負荷増加が設定可能
これらの内容を踏まえて、今回のAPIサーバーの負荷テストという用途においては、k6の特徴が要件に適合していると判断しました。
3. 環境の説明
負荷テストを行う環境についての詳細は以下です。
テスト対象システム
- プラットフォーム : Google Kubernetes Engine (GKE)
- アプリケーション : REST API サーバー
- Pod構成 : 複数のPodでリクエストを分散処理
- Auto Scaling : HPAによる自動スケーリング設定
負荷テスト実行環境
- k6実行場所 : 同一GKEクラスター内でKubernetes Jobとして実行
- k6バージョン : 0.46.0
- テスト方式 : ramping-arrival-rate パターンで段階的負荷増加
- 最大負荷 : 100 RPS(Requests Per Second)
4. 実践例
ここでは、実際にGKE上でk6を使用した負荷テストの実装例を紹介します。
ファイル構成
まず、負荷テスト用のファイル構成を整理しました:
kubernetes/
├── app # アプリケーション関連のディレクトリ
│ └── ...
├── k6 # k6関連のディレクトリ
│ ├── k6-job.yaml # Kubernetes Job定義
│ └── scripts
│ ├── load-test.js # k6負荷テストスクリプト
│ └── sample_request.json # テスト用リクエストデータ
└── Makefile # デプロイ・実行用コマンド
Kubernetes Job設定
k6をKubernetes Job として実行するための設定ファイルです。環境変数を使用して柔軟にテスト条件を変更できるようにしています:
apiVersion: batch/v1
kind: Job
metadata:
name: k6-load-test
spec:
template:
spec:
containers:
- name: k6
image: grafana/k6:0.46.0
command: ["k6", "run", "/scripts/load-test.js"]
env:
- name: TARGET_URL
value: <your_target_url>
- name: K6_VUS
value: "100"
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "1000m"
memory: "1.5Gi"
volumeMounts:
- name: k6-scripts
mountPath: /scripts
restartPolicy: Never
volumes:
- name: k6-scripts
configMap:
name: k6-scripts
backoffLimit: 0
設定のポイント:
-
TARGET_URL
とK6_VUS
を環境変数で指定可能 - リソース制限を設定してクラスター全体への影響を制御
-
backoffLimit: 0
でJob失敗時の再試行を無効化
k6テストスクリプト
段階的に負荷を増加させる ramping-arrival-rate パターンを採用しました:
import http from 'k6/http';
import { check } from 'k6';
const payload = JSON.parse(open('./sample_request.json'));
const TARGET_URL = __ENV.TARGET_URL || '<your_target_url>';
const MAX_VUS = parseInt(__ENV.K6_VUS) || 100;
export const options = {
scenarios: {
ramping: {
executor: 'ramping-arrival-rate',
startRate: 10,
timeUnit: '1s',
preAllocatedVUs: 50,
maxVUs: MAX_VUS,
stages: [
// 多段階で負荷を変化させる
{ target: 40, duration: '2m' },
{ target: 40, duration: '3m' },
{ target: 80, duration: '1m' },
{ target: 80, duration: '4m' },
{ target: 40, duration: '1m' },
{ target: 40, duration: '4m' },
{ target: 100, duration: '1m' },
{ target: 100, duration: '4m' },
{ target: 10, duration: '2m' },
],
},
},
};
export default function () {
const url = `${TARGET_URL}`;
const params = {
headers: {
'Content-Type': 'application/json',
},
};
const res = http.post(url, JSON.stringify(payload), params);
check(res, {
'status is 200': (r) => r.status === 200,
});
}
スクリプトのポイント:
- ramping-arrival-rate : 一定時間あたりのリクエスト数を段階的に変更
- 多段階負荷パターン : 実際のトラフィック変動を想定した負荷曲線
- 外部ファイル読み込み :
sample_request.json
からテストデータを読み込み
実行方法
負荷テストの実行を効率化するため、Makefileにコマンドを定義しました:
k6-create-configmap:
make stg
kubectl create configmap k6-scripts \
--from-file=load-test.js=./k6/scripts/load-test.js \
--from-file=sample_request.json=./k6/scripts/sample_request.json \
-n <your_namespace>
k6-run-test:
make stg
kubectl apply -f k6/k6-job.yaml -n <your_namespace>
k6-status:
make stg
kubectl get job k6-load-test -n <your_namespace>
kubectl get pods -l job-name=k6-load-test -n <your_namespace>
k6-logs:
make stg
kubectl logs -l job-name=k6-load-test -n <your_namespace>
k6-cleanup:
make stg
kubectl delete configmap k6-scripts -n <your_namespace> --ignore-not-found=true
kubectl delete job k6-load-test -n <your_namespace> --ignore-not-found=true
make stg コマンドは k8sのコンテキストを検証用のステージング環境に向けるためのコマンドです。
実行手順:
-
ConfigMapの作成 (スクリプトファイルをクラスターに配置)
make k6-create-configmap
-
負荷テストの実行
make k6-run-test
-
実行状況の確認
make k6-status
-
ログの確認
make k6-logs
-
リソースのクリーンアップ
make k6-cleanup
テスト結果の分析
上のグラフは負荷テスト実行中のCPU使用率の推移を示しています。
結果の考察:
- 負荷の段階的増加に応じてCPU使用率も比例して上昇
- 最大負荷(100 RPS)時においてもcpuリソースを使い切ることなくリクエストを捌けている
- レスポンス時間は全体を通して安定しており、システムの健全性を確認
得られた知見:
- 現在の設定で100 RPS程度の負荷に対応可能
- オートスケーリングが適切に動作し、負荷増加に対応してPod数が調整された
5. まとめ
本記事では、GKE環境でk6を使用した負荷テストの実装から実行まで一連の流れを紹介しました。
今回の取り組みで確認できたこと:
- k6 + Kubernetes Jobの組み合わせによる効率的な負荷テスト環境の構築
- 段階的負荷増加パターンによる現実的なテストシナリオの実現
今後の改善点:
- より詳細な監視メトリクスの収集
- 異なる負荷パターンでのテスト実施
負荷テストは継続的に実施することで、システムの健全性を保ち、サービス品質向上に繋がります。 今回構築した環境をベースに、定期的な負荷テストを行っていこうと考えています。
最後までご覧いただきありがとうございました!