k6の拡張機能開発「xk6」入門
はじめに
k6で複雑なシナリオを書こうとすると、JavaScriptの組み込み関数(例:Math)だけでは実装が難しいことがあります。。
本来であればNode.jsやブラウザ上で動作するため、便利なライブラリが提供されていますが、これらをk6上で動作させることはできません。
自前で一から実装するのも簡単なのであれば良いのですが、非常に難しい場合もあります。そんなときに使うのがxk6
です。このコマンドラインツールとGoパッケージを利用することで、k6を簡単にカスタムビルドし、特定の拡張機能を組み込んだバイナリを作成することができます。
公式ドキュメント
Extensions | Grafana k6 documentation
GitHub
xk6
を使うメリット
xk6
を利用する最大のメリットは、k6の機能を拡張できることです。
既存の拡張機能を追加したり、新たに開発した拡張機能を組み込むことで、テストツールをより柔軟にカスタマイズできます。
具体的には以下のようなケース
-
プロトコルサポートの追加: 例えば、Kafkaメッセージングシステムをテストする際、
xk6-kafka
拡張機能を組み込むことで、Kafkaプロトコルを利用した負荷テストが可能になります。 - カスタムデータの取り扱い: 標準のk6では対応できないデータソースや出力先がある場合、そのための拡張機能を追加することで、特定の要件に対応したテストを実現できます。
既存の拡張機能は以下にあります↓
Explore k6 extensions | Grafana k6 documentation
xk6
の使い方
xk6
を活用する方法は多岐にわたりますが、ここでは主にDockerを利用したセットアップとローカル環境でのビルド方法について説明します。
Dockerを使った簡単セットアップ
例:カスタムk6バイナリのビルド
例えば、Linuxシステム上でxk6-kafka
とxk6-output-influxdb
拡張機能を組み込んだk6 v0.43.1バイナリを作成する場合、以下のコマンドを使用します:
docker run --rm -it -u "$(id -u):$(id -g)" -v "${PWD}:/xk6" grafana/xk6 build v0.43.1 \\
--with github.com/mostafa/xk6-kafka@v0.17.0 \\
--with github.com/grafana/xk6-output-influxdb@v0.3.0
現在のディレクトリにカスタムk6バイナリが作成されます。
他のOS向けバイナリのビルド
xk6
はクロスコンパイルもサポートしており、異なるOS向けのバイナリを簡単にビルドできます。例えば、macOS用のバイナリをビルドするには、以下のようにします:
docker run --rm -it -e GOOS=darwin -u "$(id -u):$(id -g)" -v "${PWD}:/xk6" \\
grafana/xk6 build v0.43.1 \\
--with github.com/mostafa/xk6-kafka@v0.17.0 \\
--with github.com/grafana/xk6-output-influxdb@v0.3.0
ローカルでのカスタムk6バイナリのビルド
Dockerを使わずにローカルでカスタムk6バイナリをビルドしたい場合、以下のコマンドでxk6
をGoを使用してインストールできます
go install go.k6.io/xk6/cmd/xk6@latest
このコマンドにより、xk6
バイナリがGoパスにインストールされ、すぐに使用可能になります。
例:カスタムビルドのコンパイル
次のコマンドを実行して特定の拡張機能を追加したk6バイナリを作成できます
xk6 build --with github.com/grafana/xk6-browser
このコマンドでは、ブラウザ自動化機能を持つxk6-browser
拡張機能が追加され、ブラウザ操作をテストできるk6バイナリが生成されます。
応用的な使い方
以下では、拡張機能の開発プロセスについて解説します。
k6拡張機能の開発
拡張機能のフォルダ内でxk6
をビルドサブコマンドなしで実行すると、拡張機能を含むk6をその場でビルドして実行できます。
xk6 run -u 10 -d 10s test.js
環境変数を使ったカスタマイズ
xk6
では、環境変数を使用してビルドや動作をさらにカスタマイズできます。例えば:
-
K6_VERSION
: 使用するk6のバージョンを指定します。 -
XK6_RACE_DETECTOR=1
: Goのレースディテクタを有効にして、並行処理の問題を検出します。 -
XK6_K6_REPO
: カスタムk6リポジトリのパスを設定します。フォークやカスタムバージョンを使用する際に便利です。
依存関係の管理
拡張機能を開発したり利用したりする際には、k6コアとの依存関係を同期させることが重要です。これにより、JSランタイムのバイナリ互換性が保証され、ビルドエラーの発生を防ぐことができます。go-depsync
ツールを使用すると、依存関係を自動的にチェックし、同期させるためのgo get
コマンドを生成できます。
実践編 - 拡張機能の開発
実際にxk6
を使って拡張機能を開発してみましょう。以下は、シンプルな「Hello World」拡張機能の例です。
ソースコードはこちら
1. 開発環境のセットアップ
まず、拡張機能の開発を行うためにGo開発環境をセットアップします。以下のツールが必要です:
-
Goのインストール:
xk6
はGoで開発されているため、Go 1.17以上が必要です。 -
xk6
のインストール:xk6
を使ってk6に新しい機能を追加し、カスタムビルドを作成することができます。以下のコマンドでインストールします。
go install go.k6.io/xk6/cmd/xk6@latest
2. Goモジュールの初期化
次に、拡張機能のプロジェクトディレクトリを作成し、Goモジュールを初期化します。以下のコマンドを使用します:
mkdir xk6-hello-world
cd xk6-hello-world
go mod init github.com/moko-poi/xk6-hello-world
ここでのモジュール名は、あなたのGitHubリポジトリに対応するパスに置き換えてください。
3. 拡張機能の実装
次に、xk6
拡張機能を実装します。以下は、カスタム比較機能を提供するシンプルな拡張機能の例です。
まず、hello.go
というファイルを作成し、次のコードを追加します:
package hello
import (
"go.k6.io/k6/js/modules"
)
func init() {
modules.Register("k6/x/hello", new(Hello))
}
type Hello struct{}
func (h *Hello) HelloWorld() string {
return "Hello World!"
}
xk6
を使ってビルドとテスト
4. 次に、この拡張機能を含むカスタムk6バイナリをビルドします。以下のコマンドを実行してください:
xk6 build --with github.com/moko-poi/xk6-hello-world=.
これにより、拡張機能が組み込まれたk6バイナリが作成されます。
次に、テストスクリプトtest.js
を作成し、以下のコードを追加します:
import hello from 'k6/x/hello';
export default function () {
console.log(`${hello.helloWorld()}`);
}
スクリプトを実行:
./k6 run test.js
このスクリプトを実行すると、コンソールに「Hello World!」と表示されるはずです。これで、xk6
を使った拡張機能の基本的な開発プロセスが理解できたかと思います。
Go-to-JS Bridge
) の仕組み
補足:GoからJavaScriptへの橋渡し (k6
やxk6
を利用していると、Goで実装した機能をJavaScriptから呼び出すことができます。これを可能にしているのがGo-to-JS Bridge
という仕組みです。この橋渡し機能は、GoのコードをJavaScriptのランタイムであるsobek
に渡す際に発生する様々な変換を担っています。
1. メソッドとフィールドの変換
Goで定義されたメソッドやフィールドは、そのままではJavaScriptから直接使用することはできません。これらは、Go-to-JS Bridge
によってJavaScript側で利用可能な形式に変換されます。
例: メソッドの変換
Goのメソッドは通常PascalCaseで書かれますが、JavaScriptではcamelCaseでアクセスされるように変換されます。以下の例を見てみましょう。
Goでの定義:
package compare
type Compare struct{}
// GoではPascalCase
func (c *Compare) IsGreater(a, b int) bool {
return a > b
}
JavaScriptでの使用:
import compare from 'k6/x/compare';
// JavaScriptではcamelCaseに変換される
if (compare.isGreater(5, 3)) {
console.log("5 is greater than 3");
}
この変換により、GoのメソッドはJavaScriptから直感的に呼び出すことができるようになります。
例: フィールドの変換
フィールド名も同様に、PascalCaseからsnake_caseへと変換されます。
Goでの定義:
type Compare struct {
ComparisonResult string
}
JavaScriptでの使用:
console.log(compare.comparison_result); // snake_caseに変換されている
js
タグを使ったフィールドの明示的な制御
2. Go-to-JS Bridge
では、フィールド名を明示的に指定したり、JavaScriptから非表示にしたりすることが可能です。
js
タグの使用
例: type Compare struct {
ComparisonResult string `js:"result"` // "result"として公開
HiddenField string `js:"-"` // 非公開
}
このように設定することで、JavaScript側ではresult
という名前でフィールドにアクセスでき、HiddenField
はJavaScriptからは見えなくなります。
3. 型変換とネイティブコンストラクタ
Go-to-JS Bridge
は、Goの型をJavaScriptの型に変換する機能も持っています。ただし、複雑な型の場合には変換がうまく行かない場合があります。例えば、Goのint64
型はJavaScriptのnumber
型に自動的に変換されますが、構造体や複雑なオブジェクトはsobek.Object
やsobek.Value
に変換する必要があります。
例: ネイティブコンストラクタの利用
sobek
を使って、Goの構造体をJavaScriptからnew
キーワードを使って生成することができます。
Goでの定義:
func (*Compare) XComparator(call sobek.ConstructorCall, rt *sobek.Runtime) *sobek.Object {
return rt.ToValue(&Compare{}).ToObject(rt)
}
JavaScriptでの使用:
const comparator = new compare.Comparator(); // Goで定義した構造体をnewでインスタンス化
この仕組みによって、JavaScriptのような直感的なオブジェクト指向プログラミングが可能になります。
参照:https://grafana.com/docs/k6/latest/extensions/explanations/go-js-bridge/
まとめ
k6でシナリオを書いていると、k6のビルドイン関数では実装が難しいと感じることがしばしばあります。複雑な計算や特殊なプロトコルへの対応が求められる場面では、既存の機能だけでは不十分なことも多いです。
そんなときにxk6
を利用することで、Go言語の強力な標準ライブラリを活用し、簡単にカスタム拡張機能を作成できるのは非常に便利です。
Goであれば、ほとんどのケースに標準ライブラリで対応でき、さらに書き慣れた環境でサクッと実装できました。
Discussion