こんにちは、インフラエンジニアのhogeです。本記事はiimonアドベントカレンダー12日目の記事となります。今回は負荷テストツールのk6に入門してみたので、記事にしてみようと思います。
k6を触ろうと思ったモチベーション
負荷テストツールを探していました。負荷テストツールとして真っ先に名前がでてくるのは、Apache JMeterです。最初は歴史が長く、最も情報量が多いのでApache JMeterを使おうと思っていましたが、個人的には以下の難点がありました。
上記の難点になっている部分を解消できるシナリオをプログラムで書けるツールを探していたところ、javascriptで書けるk6を発見したので、使ってみることにしました。
k6とは?
実装言語
- Go
シナリオ実装言語
特徴
- リソース消費を最小限に抑え絵得るように最適化されており、パフォーマンスが高い
- 別のツールを使えば負荷テスト結果をグラフで表示できる
- Saas版もある
社内で使う利点
- Javascriptでシナリオを記述できるので実装しやすそう。読める人が多そう。
k6でできないこと
- k6が実行するJavaScriptのランタイムはNode.jsではないのでNode Modulesが利用できない
- master/slave構成で並列実行をする仕組みは公式には提供されていない
k6をmacにインストールする
単一のバイナリ実行ファイルだけなので、プラットフォーム別のバイナリファイルを配備すれば動く
brew install k6
スクリプトを作成する
test_script.js
import http from 'k6/http'; import { sleep,check } from 'k6'; export default function () { const res = http.get('https://www.google.com/'); check(res, { 'success': (r) => r.status === 200}) }
単一URLを連打するスクリプト
実行すると以下の出力が得られる
代表的な出力メトリクス
- checks:コード内のcheck()の成功率 上記例では100%
- http_req_failed:失敗リクエスト数(エラー率・可用性) 上記例では0%
- http_req_duration:全リクエストの合計レイテンシー 上記例では平均125.83ms
- http_reqs: リクエスト数 上記例では1リクエスト
- iterations: イテレーション数 上記例では1回
オプションでユーザ数(vus)とduration(期間)を指定することができる 10ユーザ並列で30秒間リクエストを繰り返す場合、以下のようにコマンドを作る
k6 run --vus 10 --duration 30s script.js
テスト結果の詳細を出力することができる
バイナリをgithubのreleaseから取得できるので、解凍して使う
https://github.com/grafana/xk6-dashboard/releases/tag/v0.6.1
tar -zxvf xk6-dashboard_v0.6.1_darwin_amd64.tar.gz
実行する
./k6 run test-script.js --vus 10 --duration 30s --out dashboard=open
http://127.0.0.1:5665/にアクセスすると、ダッシュボードが表示される
先ほどのコマンドの実行結果も表で見やすく表示される
シナリオの自動生成
jsでシナリオを実装するということはクライアント側のコードを再実装することになりそうだと懸念していましたが、k6のコミュニティではシナリオ作成を柔軟にするため、Postman,Swagger,OpenAPI等で定義されているAPIを変換することができる機能が提供されていました。
今回の例では、postmanのcollectionからk6スクリプトを作成します。
postmanのcollectionからk6に変換するツールをインストールします。
npm install -g @apideck/postman-to-k6
collectionをjsonにエクスポートする
exportしたcollectionのjsonを引数に指定し、scriptを作成します
postman-to-k6 test-api.json -e env.json -o k6-script.js
k6-script.jsには以下のようなコードが出力されます(必要に応じて加工して使う必要はあると思いますが)
import './libs/shim/core.js'; import './libs/shim/urijs.js'; import URI from './libs/urijs.js'; import { group } from 'k6'; export const options = { maxRedirects: 4, duration: '1m', vus: 100, }; const Request = Symbol.for('request'); postman[Symbol.for('initial')]({ options, collection: { BASE_URL: 'https://test-api.k6.io/', }, environment: { USERNAME: 'test@example.com', PASSWORD: 'superCroc2020', FIRSTNAME: 'John', LASTNAME: 'Doe', EMAIL: 'test@example.com', ACCESS: null, REFRESH: null, CROCID: null, }, }); export default function () { group('Public APIs', function () { postman[Request]({ name: 'List all public crocodiles', id: '3ddd46c4-1618-4883-82ff-1b1e3a5f1091', method: 'GET', address: '{{BASE_URL}}/public/crocodiles/', }); postman[Request]({ name: 'Get a single public crocodile', id: '9625f17a-b739-4f91-af99-fba1d898953b', method: 'GET', address: '{{BASE_URL}}/public/crocodiles/1/', }); });
先ほどは、コマンドの引数で指定していましたが、コード内からオプションを実行のオプションを指定することもできます
export const options = { maxRedirects: 4, duration: '1m', vus: 100, };
実行すると以下のような結果を得ることができます
$ k6 run k6-script.js /\ |‾‾| /‾‾/ /‾‾/ /\ / \ | |/ / / / / \/ \ | ( / ‾‾\ / \ | |\ \ | (‾) | / __________ \ |__| \__\ \_____/ .io execution: local script: k6-script.js output: - scenarios: (100.00%) 1 scenario, 100 max VUs, 1m30s max duration (incl. graceful stop): * default: 100 looping VUs for 1m0s (gracefulStop: 30s) █ Public APIs data_received..............: 7.1 MB 117 kB/s data_sent..................: 3.0 MB 50 kB/s group_duration.............: avg=700.83ms min=349.43ms med=485.62ms max=3.9s p(90)=1.43s p(95)=1.8s http_req_blocked...........: avg=4.78ms min=0s med=2µs max=486.97ms p(90)=5µs p(95)=7µs http_req_connecting........: avg=2.16ms min=0s med=0s max=208.22ms p(90)=0s p(95)=0s http_req_duration..........: avg=344.91ms min=172.24ms med=212.48ms max=2.11s p(90)=717.42ms p(95)=1.12s http_req_failed............: 100.00% ✓ 17184 ✗ 0 http_req_receiving.........: avg=99.78µs min=6µs med=43µs max=22.4ms p(90)=110.7µs p(95)=178.84µs http_req_sending...........: avg=36.18µs min=3µs med=20µs max=9.27ms p(90)=41µs p(95)=58µs http_req_tls_handshaking...: avg=2.52ms min=0s med=0s max=275.32ms p(90)=0s p(95)=0s http_req_waiting...........: avg=344.77ms min=172.2ms med=212.36ms max=2.11s p(90)=717.37ms p(95)=1.12s http_reqs..................: 17184 284.158078/s iteration_duration.........: avg=700.88ms min=349.47ms med=485.71ms max=3.9s p(90)=1.43s p(95)=1.8s iterations.................: 8592 142.079039/s vus........................: 27 min=27 max=100 vus_max....................: 100 min=100 max=100 running (1m00.5s), 000/100 VUs, 8592 complete and 0 interrupted iterations default ✓ [======================================] 100 VUs 1m0s
まとめ
k6の触りの部分を紹介しました。JavaScriptで簡単に実装できて、Postman CollectionやOpenAPI等の既存の資産からコードを生成できるため、サクッと負荷テストを行うことができそうです。 今後は
- 実用的なシナリオテストを作成する
- 負荷テスト分析データをInfluxDBやcloudwatchに入れて可視化する
- 実行環境を考える
等を調べていずれ記事にしたいと思います。
最後に
この記事を読んで興味を持って下さった方がいらっしゃればカジュアルにお話させていただきたく、是非ご応募をお願いします。
次のアドベントカレンダーの記事は白水さんです!どんな記事を書くのかとても楽しみです!
参考
https://techblog.kayac.com/tonamel-installing-stress-test
https://speakerdeck.com/fujiwara3/k6niyorufu-he-shi-yan-ru-men-karashi-jian-made