k6で始める負荷テスト
はじめに
APIに対する負荷試験を実施している際、Jmeterとk6で実装し負荷試験を実施していたのですが、k6が非常に便利だったので紹介してみようと思います。
k6について
K6はオープンソースの負荷生成、測定ツールです。
公式サイト参照ですが、以下のような特徴があります。
- CLIツールであること
- 負荷スクリプトはJavascript ES2015/ES6で記述でき、モジュールをサポートしている
-
Checks
とThresholds
を備えている- Checks: 成功失敗のチェックを記述することができる
- Thresholds: 閾値を設定できる
またJmeterと比較して軽量で、ローカル環境でRPS(Request per second)を増してもレスポンスの処理や結果の正しさは良好な印象でした。
ただし、注意点としてスクリプトはJavascriptで記載可能ですがNode.jsではないので、npm module
やNodeJS API
を使用するにはモジュールを作成して、ファイルからインポートしてやる必要があります。
これは後ほど作成方法など紹介しようと思います。
今回使用したコードは次のレポジトリにあります。
環境
- WSL2: Ubuntu 20
k6のインストール
Brew経由でインストールをします。Macならhomebrew, Linux, WSLならlinuxbrewで以下のコマンドを実行します。
brew install k6
実際に負荷をかけてみる
import { check } from "k6"
import http from "k6/http"
export const options = {
thresholds: {
http_req_failed: ["rate<0.01"],
http_req_duration: ["p(90)<2000"]
}
}
export default function () {
const headers = {
"Content-Type": "application/json"
}
const res = http.post(
"https://test.k6.io",
{ headers: headers }
)
check(res, {
'is_status_200': (r) => r.status === 200
})
}
上記だけで、一回のAPIリクエストについて記述できました。非常に簡単ですね。例では、単純なHTTPリクエストですが、GraphQLへのリクエストや、特定のシナリオに沿った負荷測定実行も可能です。
export const options = {
thresholds: {
http_req_failed: ["rate<0.01"],
http_req_duration: ["p(90)<2000"]
}
}
thresholdsにて、リクエストに対するレスポンスの閾値を設定することができます。
- http_req_failed: リクエストに失敗する割合。上記の例だと失敗リクエストが1%以下。
- http_req_duration: レスポンスタイムの閾値。上記の例だと90パーセンタイルで2000ms以内であること。この条件としては複数設定が可能です。
- http_req_duraion: ['p(90) < 400', 'p(95) < 800', 'p(99.9) < 2000']
上記の例だと90パーセンタイルで400ms、95パーセンタイルで800msそして99.9パーセンタイルで2000msを条件にしています。
- http_req_duraion: ['p(90) < 400', 'p(95) < 800', 'p(99.9) < 2000']
check(res, {
'is_status_200': (r) => r.status === 200
})
k6にあらかじめ用意されているcheckモジュールをインポートすることで、レスポンスに対するチェックを実施することが可能です。
上記例だと、レスポンスのステータスが200であることをチェックしており、こちらも複数条件設定することが可能です。
それでは、実際に負荷をかけてみようと思います。実行には次のようなコマンドを実行します。
k6 run simple_http.js -u 100 --rps 20 -d 1h --out json=result.json
-
-u
: ユーザー数 -
--rps
: Rquest per second -
-d
: 継続時間で、1h30m55sのような形式で入力する -
--out
: 結果の出力先。主につかうのは、csv, jsonなど。上記の例では、jsonで出力しています。出力形式のあとに=
でつないで、ファイル名を指定することで、結果を保存できます。
実行結果としては以下のようなものがCLIに表示されます。
/\ |‾‾| /‾‾/ /‾‾/
/\ / \ | |/ / / /
/ \/ \ | ( / ‾‾\
/ \ | |\ \ | (‾) |
/ __________ \ |__| \__\ \_____/ .io
execution: local
script: simple_http.js
output: json (test.json)
scenarios: (100.00%) 1 scenario, 10 max VUs, 1m0s max duration (incl. graceful stop):
* default: 10 looping VUs for 30s (gracefulStop: 30s)
running (0m32.0s), 00/10 VUs, 160 complete and 0 interrupted iterations
default ✓ [======================================] 10 VUs 30s
✓ is_status_200
checks.........................: 100.00% ✓ 160 ✗ 0
data_received..................: 1.9 MB 59 kB/s
data_sent......................: 38 kB 1.2 kB/s
http_req_blocked...............: avg=22.31ms min=6.9µs med=9.04µs max=388.91ms p(90)=26.05µs p(95)=348.49ms
http_req_connecting............: avg=11ms min=0s med=0s max=183.83ms p(90)=0s p(95)=173.45ms
✓ http_req_duration..............: avg=209.15ms min=171.78ms med=179.73ms max=781.71ms p(90)=348.89ms p(95)=355.6ms
{ expected_response:true }...: avg=209.15ms min=171.78ms med=179.73ms max=781.71ms p(90)=348.89ms p(95)=355.6ms
✓ http_req_failed................: 0.00% ✓ 0 ✗ 160
http_req_receiving.............: avg=17.34ms min=70.3µs med=163.14µs max=606.11ms p(90)=485.47µs p(95)=172.05ms
http_req_sending...............: avg=84.98µs min=30.2µs med=78.7µs max=217.3µs p(90)=131.74µs p(95)=137.63µs
http_req_tls_handshaking.......: avg=11.15ms min=0s med=0s max=191.88ms p(90)=0s p(95)=174.66ms
http_req_waiting...............: avg=191.72ms min=171.53ms med=178.78ms max=443.99ms p(90)=209.34ms p(95)=257.88ms
http_reqs......................: 160 5.001738/s
iteration_duration.............: avg=1.94s min=571.01ms med=1.99s max=2.6s p(90)=2.05s p(95)=2.17s
iterations.....................: 160 5.001738/s
vus............................: 6 min=6 max=10
vus_max........................: 10 min=10 max=10
この中で重要なのは次が挙げられます。
checks.........................: 100.00% ✓ 160 ✗ 0
-> checkで定義した項目が成功しているか
http_req_duration..............: avg=209.15ms min=171.78ms med=179.73ms max=781.71ms p(90)=348.89ms p(95)=355.6ms
-> レスポンスに関するメトリクス
✓ http_req_failed................: 0.00% ✓ 0 ✗ 160
-> thresholdで定義したエラー率
http_reqs......................: 160 5.001738/s
-> 結果の左はトータルに投げられたリクエストで、右がRPSの実測値です。
k6からAWSモジュールを使用する
k6の負荷スクリプトは、Javascriptで記述することは可能ですが、実行環境としてNode.jsではありません。そのため、npmモジュールなどをインストールしてそれを使うなどができません。例えば、AWSのAPI GWへのリクエストを投げようとすると、署名バージョン4の署名プロセスを利用して、認証情報を付与してやる必要がありますが、aws4使えば、比較的簡単に実装できますが、自作で署名を付与するとなると些か面倒です。
そこで、browserifyを利用してnode_moduleにインストールした、AWS4をk6でも実行可能な形式に変換します。
- 必要モジュールのインストール
npm install aws4 browserify
- AWS4の変換
./node_modules/browserify/bin/cmd.js ./node_modules/aws4/index.js > ./aws4.js
これで、aws4.js
というファイルがカレントディレクトリに生成されます。 - 実際にk6のスクリプトでインポートする
import aws4 from "./aws4.js"
インポートするのは非常に簡単です。
外部npmモジュールを用いた負荷測定スクリプト
import { check } from "k6"
import http from "k6/http"
import aws4 from "./aws4.js"
const awsKey = ""
const awsSecretKey = ""
const awsSessionToken = ""
export default function () {
const serviceName = "execute-api"
const baseUrl = "some_domain.com"
const path = "/api/v1/sampleEndpoint"
const options = {
headers: {
"Content-Type": "application/json"
},
service: serviceName
}
const parts = path.split("?")
options.host = baseUrl
options.path = path
options.region = "ap-northeast-1"
options.method = "GET"
aws4.sign(options, {
accessKeyId: awsKey,
secretAccessKey: awsSecretKey,
sessionToken: awsSessionToken
})
const res = http.get(`https://${baseUrl}${path}`, {headers: options.headers})
check(res, {
'is_status_200': (r) => r.status === 200
})
}
モジュールのインポート方法が通常とは異なりローカルのファイルを使うという違いを除けば、使用感などは一緒です。
ダッシュボードに結果を表示する
k6は非常に簡単で、軽量な負荷測定ツールですが、一点だけ欠点があります。デフォルトの結果表示では、レスポンス等の時系列変化がみれない。という点です。これは、似たような負荷測定ツールで、JmeterをベースとしたTaurusと比べると劣っている点であるとは思います。
そこで、k6のextensionである、xk6-dashboardをインストールしたk6をビルドして使用しようと思います。
xk6-jdashboardのビルド
事前にGoがインストールされていることが前提となります。
- xk6のダウンロード
go install go.k6.io/xk6/cmd/xk6@latest
- バイナリのビルド
xk6 build --with github.com/szkiba/xk6-dashboard@latest
実行方法としてはほぼほぼ一緒です。ただ、k6についてはローカルで生成されたものを利用することには注意が必要です。
./k6 run simple_http.js -u 10 --rps 3 -d 30s --out dashboard
ダッシュボード自体は、http://localhost:5665
にアクセスすることで確認可能です。
終わりに
k6は軽量かつ、実装するのが非常に簡単でちょっと面倒な点もありますが、ダッシュボードによる可視化や外部npmモジュールを使用することが可能なため、強力なツールです。
また、CLIで実行するオプションが豊富であったり、結果自体をPrometheusに保存することが可能なため、CICDパイプラインで負荷テストを実装して、結果をGrafanaで確認するなども可能になり、CICDとも非常に親和性が高いツールとなっているので、GitHub Actionsなどとも組み合わせてみてもよいかもと思いました。
Discussion