📈

k6 の実行結果を Prometheus に保存して Grafana で可視化してみた

2024/02/07に公開

はじめに

負荷テストの結果をきれいに可視化したくなり、k6, Prometheus, Grafana の組み合わせで負荷テストの実行基盤を実装してみました。自分の理解を深めるためにも、その内容を記事にまとめておきます。

負荷テストとは

開発環境ではきちんと動いていたサービスをいざ本番環境にリリースしたとき、アクセス集中により予期せぬ問題が発生することがあります。このような問題への対策として、サービスに意図的に負荷をかけて擬似的にアクセス集中の状態を作り出し、システムの振る舞いを観測する負荷テストが用いられます。これによって潜在的な性能上の問題を特定し、サービスの信頼性を高めることができます。

k6とは

k6 はオープンソースの負荷テストツールです。実装には Go 言語が使われていますが、ユーザー側のテストシナリオは JavaScript で記述することができます。

Prometheusとは

Prometheus はオープンソースの監視システムです。サーバーの CPU 使用率・メモリ使用率などのメトリクスを収集し、時系列データベースに蓄積し、監視することができます。

収集したメトリクスは Web UI 上で閲覧することも可能ですが、UI は簡易的なもので機能が少ないため Grafana と組み合わせて使われることが多いです。

Grafanaとは

Grafana はオープンソースの監視ツールです。外部のデータソースと連携し、メトリクスの可視化・アラート・分析をすることができます。Prometheus, Loki, Elasticsearch, InfluxDB, Postgres など、様々なデータソースに対応しています。

実装してみる

環境

  • Docker 4.27.1
  • k6 0.49.0
  • Prometheus 2.49.1
  • Grafana 10.3.1

シナリオの作成

k6 で負荷テストを実行するために、シナリオを記述した JavaScript ファイルを作成します。

scripts/sample.js
import http from "k6/http";
import { check, sleep } from "k6";

// Test configuration
export const options = {
  thresholds: {
    // Assert that 99% of requests finish within 3000ms.
    http_req_duration: ["p(99) < 3000"],
  },
  // Ramp the number of virtual users up and down
  stages: [
    { duration: "30s", target: 15 },
    { duration: "1m", target: 15 },
    { duration: "30s", target: 0 },
  ],
};

// Simulated user behavior
export default function () {
  let res = http.get("https://test-api.k6.io/public/crocodiles/1/");
  // Validate response status
  check(res, { "status was 200": (r) => r.status == 200 });
  sleep(1);
}

このシナリオは、https://test-api.k6.io/public/crocodiles/1/ に対して下図のように仮想ユーザー数 (VUs) を増減させながら負荷テストを実行するものです。また、このシナリオでは 99% 以上のリクエストが 3000ms 以内に終了した場合のみ正常終了します。

続いて、このシナリオに対して負荷テストを実行するために k6 の実行環境を準備します。

k6 コンテナの定義

docker-compose.yml に k6 コンテナの定義を記述します。

docker-compose.yml
version: "3.8"

services:
  k6:
    image: grafana/k6:0.49.0
    ports:
      - "6565:6565"
    volumes:
      - type: bind
        source: "./scripts"
        target: "/scripts"

負荷テストの実行

先ほど作成したシナリオをもとに、負荷テストを実行してみます。

まず、定義したコンテナを作成して起動します。コンテナを起動した後に k6 コマンドを実行するので -d, --detach オプションをつけてバックグラウンドで実行しておきましょう。

docker compose up -d

コンテナが起動できたら k6 コマンドを叩いて負荷テストを実行します。

docker compose run k6 /scripts/sample.js

実行すると以下のような結果が得られます。

無事負荷テストを実行することができました。しかし、このままでは結果は標準出力に出力されるだけでどこにも保存されません。k6 の実行結果を Prometheus に保存するために Prometheus の環境を準備します。

Prometheus コンテナの定義

docker-compose.yml に Prometheus コンテナの定義を追記します。

docker-compose.yml
  version: "3.8"

   services:
     k6:
       image: grafana/k6:0.49.0
       ports:
         - "6565:6565"
       volumes:
         - type: bind
           source: "./scripts"
           target: "/scripts"
+
+    prometheus:
+      image: prom/prometheus:v2.49.1
+      ports:
+        - "9090:9090"
+      volumes:
+        - type: bind
+          source: "./prometheus.yml"
+          target: "/etc/prometheus/prometheus.yml"
+        - type: bind
+          source: "./prometheus-data"
+          target: "/prometheus"
+      command:
+        - "--config.file=/etc/prometheus/prometheus.yml"

また、Prometheus の設定ファイルとデータを格納しておくディレクトリを作成します。

touch prometheus.yml
mkdir prometheus-data

k6 の実行結果を Prometheus に保存する

k6 の実行結果を Prometheus に保存するには、それぞれのサービス(コンテナ)をネットワークに接続する必要があります。ここでは、 k6-prometheus という名前のネットワークを作成し、k6 サービスと prometheus サービスをネットワークに接続します。
また、k6 の実行結果を Prometheus に書き込むために Prometheus の Remote Write Receiver を有効にしておきます。

docker-compose.yml
  version: "3.8"

+ networks:
+   k6-prometheus:
+
  services:
    k6:
      image: grafana/k6:0.49.0
      ports:
        - "6565:6565"
      volumes:
        - type: bind
          source: "./scripts"
          target: "/scripts"
+     environment:
+       - K6_PROMETHEUS_RW_SERVER_URL=http://prometheus:9090/api/v1/write
+     networks:
+       - k6-prometheus

    prometheus:
      image: prom/prometheus:v2.49.1
      ports:
        - "9090:9090"
      volumes:
        - type: bind
          source: "./prometheus.yml"
          target: "/etc/prometheus/prometheus.yml"
        - type: bind
          source: "./prometheus-data"
          target: "/prometheus"
      command:
+       - "--web.enable-remote-write-receiver"
        - "--config.file=/etc/prometheus/prometheus.yml"
+     networks:
+       - k6-prometheus

更新した docker-compose.yml の内容を反映するため、コンテナを作成し直して起動します。

docker compose up -d

k6 を実行するときには、 -o, --out オプションに experimental-prometheus-rw を指定します。

docker compose run k6 -o experimental-prometheus-rw /scripts/sample.js

http://localhost:9090 にアクセスしてクエリを実行することで、負荷テストの結果として得られるメトリクスを簡単に可視化することができます。負荷テスト実行中の k6_http_reqs_total の推移を可視化すると、以下の画像のようになりました。

しかし、冒頭でも述べたように Prometheus の UI は簡易的で機能が少ないため、今回は得られたメトリクスを Grafana で可視化します。まずは Grafana の環境を準備しましょう。

Grafana コンテナの定義

docker-compose.yml に Grafana のコンテナ定義を追記します。

docker-compose.yml
  version: "3.8"

  networks:
    k6-prometheus:

  services:
    k6:
      image: grafana/k6:0.49.0
      ports:
        - "6565:6565"
      volumes:
        - type: bind
          source: "./scripts"
          target: "/scripts"
      environment:
        - K6_PROMETHEUS_RW_SERVER_URL=http://prometheus:9090/api/v1/write
      networks:
        - k6-prometheus

    prometheus:
      image: prom/prometheus:v2.49.1
      ports:
        - "9090:9090"
      volumes:
        - type: bind
          source: "./prometheus.yml"
          target: "/etc/prometheus/prometheus.yml"
        - type: bind
          source: "./prometheus-data"
          target: "/prometheus"
      command:
        - "--web.enable-remote-write-receiver"
        - "--config.file=/etc/prometheus/prometheus.yml"
      networks:
        - k6-prometheus
+
+   grafana:
+     image: grafana/grafana:10.3.1
+     ports:
+       - "3000:3000"
+     volumes:
+       - type: bind
+         source: "./grafana"
+         target: "/etc/grafana/provisioning"

また、Grafana のデータを格納しておくディレクトリを作成します。

mkdir grafana

Grafana のデータソースに Prometheus を追加する

Grafana のデータソースに Prometheus を追加するには、先ほどと同様にそれぞれのサービスをネットワークに接続する必要があります。ここでは、 prometheus-grafana という名前のネットワークを作成し、 prometheus サービスと grafana サービスをネットワークに接続します。

docker-compose.yml
  version: "3.8"

  networks:
    k6-prometheus:
+   prometheus-grafana:

  services:
    k6:
      image: grafana/k6:0.49.0
      ports:
        - "6565:6565"
      volumes:
        - type: bind
          source: "./scripts"
          target: "/scripts"
      environment:
        - K6_PROMETHEUS_RW_SERVER_URL=http://prometheus:9090/api/v1/write
      networks:
        - k6-prometheus

    prometheus:
      image: prom/prometheus:v2.49.1
      ports:
        - "9090:9090"
      volumes:
        - type: bind
          source: "./prometheus.yml"
          target: "/etc/prometheus/prometheus.yml"
        - type: bind
          source: "./prometheus-data"
          target: "/prometheus"
      command:
        - "--web.enable-remote-write-receiver"
        - "--config.file=/etc/prometheus/prometheus.yml"
      networks:
        - k6-prometheus
+       - prometheus-grafana

    grafana:
      image: grafana/grafana:10.3.1
      ports:
        - "3000:3000"
      volumes:
        - type: bind
          source: "./grafana"
          target: "/etc/grafana/provisioning"
+     networks:
+       - prometheus-grafana

更新した docker-compose.yml の内容を反映するため、コンテナを作成し直して起動します。

docker compose up -d

http://localhost:3000 にアクセスすると、 Grafana のログイン画面が表示されます。初期アカウントのユーザネームとパスワードはともに admin です。

ログインすると新規パスワードの設定を求められます。適当なパスワードを設定しましょう。

パスワードを設定したらデータソースを追加します。「Prometheus」で検索をかけて設定を行います。

Prometheus server URL には http://prometheus:9090 を指定します。

設定が完了したら画面下部の「Save & Test」をクリックして設定を完了します。

ダッシュボードの作成

今回は k6 Prometheus Dashboard を使ってダッシュボードを作成します。Grafana の画面からダッシュボードをインポートしましょう。

負荷テストを再度実行しながらダッシュボードをリロードすると各メトリクスが更新され、グラフが描画されることを確認できます。

Prometheus にはこの他にもメトリクスが保存されています。Grafana のダッシュボードをカスタマイズすることで、自分のサービスに必要なメトリクスだけを表示することができます。

おわりに

今回は Docker を使って k6 で実行した負荷テストの結果を Prometheus に保存し、Grafana で可視化してみました。今回の実装に使用したコードは GitHub で公開しています。参考になれば幸いです。

https://github.com/nozomu-y/introduction-to-k6-prometheus-grafana

GitHubで編集を提案

Discussion