k6を使ったマルチリージョンな負荷試験について
メディア統括本部 サービスリライアビリティグループ(SRG)の小原(@No_oLimits)です。
#SRG(Service Reliability Group)は、主に弊社メディアサービスのインフラ周りを横断的にサポートしており、既存サービスの改善や新規立ち上げ、OSS貢献などを行っているグループです。
本記事では、とある海外向けサービスで実施したk6(k6-operator)を使ったマルチリージョンな負荷試験について紹介します
はじめに負荷試験環境アーキテクチャ概要詳細 - シナリオ実行詳細 - モニタリングトラブルシュートあれこれk6 起動しないk6 OOMで無事死亡Prometheus 負荷高騰k6を使ってみて良かったところ改善してほしいところ終わりに
はじめに
k6はgrafanaが開発している負荷試験ツールです
シナリオはJavaScript/TypeScriptで記述します
担当したサービスではAPI系のテストでしか使ってませんが、ブラウザテストも可能です
HTTPに加えてWebSocketやgRPCプロトコルにも対応
素の状態でも十分な機能が提供されていますが拡張機能も多数用意されていて、より複雑な負荷シナリオにも対応できます
負荷試験環境アーキテクチャ
概要
3つのリージョンにk6をデプロイし、対応するリージョンのターゲットシステムに負荷をかけます
k6 Operatorを動かすため、GKE Autopilot上で実行します
負荷をかけるターゲットとなるシステムの詳細は省きます
詳細 - シナリオ実行
k6-operatorをインストールすることで TestRunというリソースが使えます
TestRunをapplyすると / / が起動し、指定した台数のrunnerが起動するとテストが開始します
負荷シナリオは事前にGCSにアップロードしておきます
k6 Pod起動時にinitContainerがGCSからシナリオを取得し、emptyDirに置いてk6 containerから参照します
PodにはGSA/KSAのWorkload Identity連携することで、GCSへの参照権限を与えます
詳細 - モニタリング
k6はテスト完了時にstatsが表示されますが、分散実行している場合はメトリクスの集約が必要になります
今回はPrometheus + Grafanaでモニタリング環境を構築します
理由はk6はデフォルトでprometheus remote writeへのメトリクス送信に対応しているからです
他のバックエンドへのメトリクスの送信には拡張機能が必要になります
- 各リージョンにPrometheusをデプロイ
- k6はリージョン内のPrometheusにメトリクスを送信
- Grafanaのデータソースはthanos-query
- Prometheus APIにはThanos sidecarを経由してThanos Queryがリクエスト
- internal LBのクラスタ間アクセスを可能にする (*1
thanosを使ってクロスリージョンなprometheusメトリクスを集約しているところがミソです
thanosはprometheusをスケールさせるためのコンポーネントで、ほかにもさまざまな機能があります
これでマルチリージョンでもメトリクスを集約してモニタリングすることができます
*1) internal LBをクラスタ間でアクセス可能にする
Thanos QueryはLB IPに対してリクエストするように設定します
トラブルシュートあれこれ
k6 起動しない
特に初回実行時にハマります
基本的には何も起動してこないのでログも分かりません
でも実はk6-operator managerがログを吐いてます
何も起動しなかったらmanagerのログを確認しましょう
そして起動しないだいたいの理由は、マニフェストの指定が間違ってたりします
k6 OOMで無事死亡
今回、仮想ユーザ数 500,000 でテストを行なっていました
ユーザ毎に複数の属性を定義し、その属性によってテスト時のユーザとしての振る舞いを制御しようとしていたところ、テスト開始後しばらくしてOOMでPodが次々に停止していきました
この属性定義はファイル化していて、起動時にファイルを読み込んでからテストを実行する流れでしたが、500,000ユーザ分のファイルをk6プロセス内の全VUスレッドが読み込むことでOOMが発生したようでした
対応策として を利用しました
ShareArrayはVU間で共有できるメモリ空間で、ファイル読み込み時にこの配列に属性を設定することでメモリ使用量を大幅に削減することができ、OOMを回避しました
Prometheus 負荷高騰
負荷高騰するタイミングがおそらく2つあります
k6実行時と、Grafanaからのクエリ発行時です
k6実行時にprometheusへメトリクス送信過多となっている状態ではcpuやmemoryリソースを見直すのが定番でしょう
また、この状態ではk6側のログにも送信失敗のメッセージが大量に出力されているはずですので確認しましょう
Grafanaでグラフを表示する際にPrometheusが応答しなくなるパターンは原因の特定が難しいですが、私が経験した範囲で最も多かったのがk6が送信するメトリクスのグループ化が不十分だったことです
下記の記事の通り、k6ではリクエスト毎(このリクエストは負荷をかけるターゲットシステムに対してのリクエスト)にデフォルトでタグが付与されます
そしてそのタグ情報をメトリクスデータと合わせてPrometheusに送信しています
Prometheus側ではGrafanaからのクエリに対してtag情報をもとにグルーピングだったりソートしているはずなので、グルーピングされていない多数のメトリクスをクエリしていると負荷高騰につながると想定されます
今回のパターンではtagのname値をリクエスト毎に適切にグルーピングすることで解消しました
name にはデフォルトでリクエストURLが値として設定されますが、URLにユーザIDが含まれることでユーザ毎にユニークな値としてPrometheusが解釈し、nameでグループを行うようなGrafanaからのクエリに応答できなくなっていたものと推察されます
k6を使ってみて良かったところ
k6-operatorの機能が充実してます
当時、シナリオをk6 podにどうやって配置しようか悩んでいたところk6-operatorの新しいバージョンがリリースされTestRun(当時はK6リソース)にinitContainer specが実装されました
ほかにも、Prometheus Remote-Writeに対応するため拡張機能を追加してビルドする必要がありましたがこちらも現在はコア機能として提供されています
頻繁にアップデートされているので今後もどんどん機能が追加されるでしょう
改善してほしいところ
今回のマルチリージョンで負荷試験するにあたり、各リージョンのテストタイミングを同期したかったのですが同期する機能はどうやら無いようで、タイミングを見てデプロイして気合いで同期していました
実行時にオプションとしてテスト日時を設定できるようになれば完璧かなと思いました
終わりに
k6を使ったマルチリージョン負荷試験について紹介しました
この記事が皆様の参考になれば幸いです。
SRG では一緒に働く仲間を募集しています。
ご興味ありましたらぜひこちらからご連絡ください。