技術(tech)

k6実行中のメトリクスをdatadogに送信して連携する

はじめに

負荷試験ツールとして単体でも便利なk6ですが、外部のロギングツールと連携することで、更に使い勝手が増します。

特に、負荷試験中は、負荷試験が正常に回っているのかウォッチしつつ、何か異常があればすぐに試験を中止するのが、試験の効率化の上で重要です。

そこで今回は、k6とdatadogを連携して、k6実行中のメトリクスをdatadogに送信する方法を紹介します。

結論を知りたい方は以下のPRをご参照ください。
PR: https://github.com/gonkunkun/k6-template/pull/6

前提

今回の記事内容の前提は以下の通りです

  • dockerの環境構築済み
  • k6をinstall済み
  • goの環境構築済み

手順

まずはDatadogのAPIキーを入手しましょう。
今回はトライアルで登録します。

まずはdatadogのサイトへ。
datadog: https://www.datadoghq.com/ja/

「無料で始める」を選択。

適宜情報を入力しましょう。

利用環境としてDockerを選択します。

手順に従って、datadog agentを起動します。

~/D/g/template-of-k6 ❯❯❯ docker run -d --name dd-agent \                                                                                          main
-e DD_API_KEY=xxxxx \
-e DD_SITE="ap1.datadoghq.com" \
-e DD_APM_ENABLED=true \
-e DD_APM_NON_LOCAL_TRAFFIC=true \
-e DD_APM_RECEIVER_SOCKET=/opt/datadog/apm/inject/run/apm.socket \
-e DD_DOGSTATSD_SOCKET=/opt/datadog/apm/inject/run/dsd.socket \
-v /opt/datadog/apm:/opt/datadog/apm \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-v /proc/:/host/proc/:ro \
-v /sys/fs/cgroup/:/host/sys/fs/cgroup:ro \
-v /var/lib/docker/containers:/var/lib/docker/containers:ro \
gcr.io/datadoghq/agent:7
fc6fa398a85a1b8000a3d8bfea2ea1e2b3b4fb98a1f5ccc46f84dfe9af9091e3

Datadog側でdatadog-agentのデータの受信を確認出来たら、チュートリアルは完了です。

Integrationから、k6のIntegartionをinstallします。

k6で検索。

Install integrationを選択。

後は表示される手順に従って、datadog-agentを再度起動します。

~/D/g/template-of-k6 ❯❯❯ DOCKER_CONTENT_TRUST=1 \                                                                                             ✘ 1 main
docker run -d \
    --name datadog \
    -v /var/run/docker.sock:/var/run/docker.sock:ro \
    -v /proc/:/host/proc/:ro \
    -v /sys/fs/cgroup/:/host/sys/fs/cgroup:ro \
    -e DD_SITE="ap1.datadoghq.com" \
    -e DD_API_KEY=${DD_API_KEY} \
    -e DD_DOGSTATSD_NON_LOCAL_TRAFFIC=1 \
    -p 8125:8125/udp \
    datadog/agent:latest

datadog agentの起動を確認。

~/D/g/template-of-k6 ❯❯❯ docker ps                                                                                                                main
CONTAINER ID   IMAGE                      COMMAND                CREATED          STATUS                    PORTS                NAMES
fc6fa398a85a   gcr.io/datadoghq/agent:7   "/bin/entrypoint.sh"   43 seconds ago   Up 41 seconds (healthy)   8125/udp, 8126/tcp   dd-agent

ログ上も特に問題は無さそう。

~/D/g/template-of-k6 ❯❯❯ docker logs -f datadog                                                                                                   main
[s6-init] making user provided files available at /var/run/s6/etc...exited 0.
[s6-init] ensuring user provided files have correct perms...exited 0.
[fix-attrs.d] applying ownership & permissions fixes...
...

ここから、k6側の設定を行います。
必須なのはxk6-output-statsdの拡張機能です。
k6コマンドをビルドしましょう。

なお、xk6コマンドが無い方は以下の記事を参考にinstallしましょう。

k6 - 環境変数を扱うにはxk6-dotenvがオススメなので使い方を紹介開発を進めていると管理しなければならない環境変数が多くなってきます。 k6でも同じ話が起きがちなのです。 本記事では環境変数の管理をする...
xk6 build \
--with github.com/LeonAdato/xk6-output-statsd@latest \
--with github.com/grafana/xk6-dashboard@latest \
--with github.com/szkiba/xk6-ts@latest \
--with github.com/szkiba/xk6-dotenv@latest

k6実行時にdatadog-agent経由でメトリクスを送信出来るように、実行時のコマンドに環境変数とオプションを追加します。
参考: https://grafana.com/docs/k6/latest/results-output/real-time/datadog/

~/D/g/template-of-k6 ❯❯❯ K6_STATSD_ENABLE_TAGS=true XK6_TS=false ./k6 run ./dist/loadTest.js --out output-statsd -e CONFIG_PATH=../src/sample-product/configs/smoke.json

          /\      |‾‾| /‾‾/   /‾‾/   
     /\  /  \     |  |/  /   /  /    
    /  \/    \    |     (   /   ‾‾\  
   /          \   |  |\  \ |  (‾)  | 
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: ./dist/loadTest.js
     output: statsd (localhost:8125)

  scenarios: (100.00%) 2 scenarios, 2 max VUs, 1m30s max duration (incl. graceful stop):
           * sampleScenario1: 1 iterations for each of 1 VUs (maxDuration: 1m0s, exec: sampleScenario1, gracefulStop: 30s)
           * sampleScenario2: 1 iterations for each of 1 VUs (maxDuration: 1m0s, exec: sampleScenario2, gracefulStop: 30s)

INFO[0001] 0se: == setup() BEGIN ===========================================================  source=console
INFO[0001] 0se: Start of test: 2024-04-07 14:24:34       source=console
INFO[0001] 0se: Test environment: local                  source=console
INFO[0001] 0se: == Check scenario configurations ======================================================  source=console
INFO[0001] 0se: Scenario: sampleScenario1()              source=console
INFO[0001] 0se: Scenario: sampleScenario2()              source=console
INFO[0001] 0se: == Check scenario configurations FINISHED ===============================================  source=console
INFO[0001] 0se: == Initialize Redis ======================================================  source=console
INFO[0001] 0se: == setup() END ===========================================================  source=console
INFO[0007] 5se: Scenario sampleScenario1 is initialized. Lens is 10000  source=console
INFO[0007] 5se: Scenario sampleScenario2 is initialized. Lens is 10000  source=console
INFO[0007] 5se: == Initialize Redis FNISHED ===============================================  source=console
INFO[0007] 5se: sampleScenario2() start ID: 2, vu iterations: 1, total iterations: 0  source=console
INFO[0007] 5se: sampleScenario1() start ID: 2, vu iterations: 1, total iterations: 0  source=console
INFO[0007] 6se: sampleScenario2() end ID: 2, vu iterations: 1, total iterations: 0  source=console
INFO[0007] 6se: sampleScenario1() end ID: 2, vu iterations: 1, total iterations: 0  source=console
INFO[0007] 0se: == All scenarios FINISHED ===========================================================  source=console
INFO[0007] 0se: == Teardown() STARTED ===========================================================  source=console
INFO[0007] 0se: == Initialize Redis ======================================================  source=console
INFO[0007] 0se: == Teardown() FINISHED ===========================================================  source=console
INFO[0007] 0se: == Initialize Redis FINISHED ===============================================  source=console

     █ setup

     █ sampleScenario2

       ✓ Status is 200

     █ sampleScenario1

       ✓ Status is 200

     █ teardown

     checks.........................: 100.00% ✓ 2        ✗ 0  
     data_received..................: 152 kB  25 kB/s
     data_sent......................: 939 kB  153 kB/s
     group_duration.................: avg=609.27ms min=608.9ms  med=609.27ms max=609.65ms p(90)=609.57ms p(95)=609.61ms
     http_req_blocked...............: avg=421.83ms min=421.75ms med=421.83ms max=421.91ms p(90)=421.89ms p(95)=421.9ms 
     http_req_connecting............: avg=193.51ms min=193.32ms med=193.51ms max=193.71ms p(90)=193.67ms p(95)=193.69ms
     http_req_duration..............: avg=186.93ms min=186.7ms  med=186.93ms max=187.15ms p(90)=187.11ms p(95)=187.13ms
       { expected_response:true }...: avg=186.93ms min=186.7ms  med=186.93ms max=187.15ms p(90)=187.11ms p(95)=187.13ms
     http_req_failed................: 0.00%   ✓ 0        ✗ 2  
     http_req_receiving.............: avg=117.5µs  min=117µs    med=117.5µs  max=118µs    p(90)=117.9µs  p(95)=117.95µs
     http_req_sending...............: avg=83.49µs  min=45µs     med=83.49µs  max=122µs    p(90)=114.3µs  p(95)=118.15µs
     http_req_tls_handshaking.......: avg=215.37ms min=215.24ms med=215.37ms max=215.51ms p(90)=215.48ms p(95)=215.5ms 
     http_req_waiting...............: avg=186.73ms min=186.54ms med=186.73ms max=186.91ms p(90)=186.87ms p(95)=186.89ms
     http_reqs......................: 2       0.325413/s
     iteration_duration.............: avg=1.68s    min=906.15µs med=611.63ms max=5.52s    p(90)=4.05s    p(95)=4.79s   
     iterations.....................: 2       0.325413/s
     vus............................: 2       min=0      max=2
     vus_max........................: 2       min=2      max=2

running (0m06.1s), 0/2 VUs, 2 complete and 0 interrupted iterations
sampleScenario1 ✓ [======================================] 1 VUs  0m00.6s/1m0s  1/1 iters, 1 per VU
sampleScenario2 ✓ [======================================] 1 VUs  0m00.6s/1m0s  1/1 iters, 1 per VU

後はdatadogのダッシュボードからメトリクスの受信を確認。

無事にk6のメトリクスをdatadogに連携出来ました。

使い勝手の向上

負荷試験へのtag付け

メトリクス送信時に、カスタムタグも一緒に付与するのがオススメです。
どの環境で、いつテストを実行したのか、これらの情報が分かるだけで後からでも負荷試験の結果確認がしやすくなります。

tags: https://k6.io/docs/using-k6/tags-and-groups/

k6の実行オプションに以下のようにtagsを追加するだけです。

    tags: {
      env: 'local',
      datetime: formatDate(new Date()),
    },

参考PR: https://github.com/gonkunkun/k6-template/pull/6/files

Datadogのダッシュボード上から、envとdatetimeでフィルタリングできるようになりました。

docker-composeで起動

毎回dockerの実行コマンドを記載するのも面倒です。

docker-composeでまとめて定義しちゃいましょう。

ここではdatadog-agentとRedisをdocker-composeで起動するようにします。

version: '3.8'

services:
  datadog:
    image: datadog/agent:latest
    container_name: datadog
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /proc/:/host/proc/:ro
      - /sys/fs/cgroup/:/host/sys/fs/cgroup:ro
    environment:
      - DD_SITE=${DD_SITE}
      - DD_API_KEY=${DD_API_KEY}
      - DD_DOGSTATSD_NON_LOCAL_TRAFFIC=1
    ports:
      - '8125:8125/udp'

  redis:
    image: redis:latest
    ports:
      - '6379:6379'

環境変数を設定。

export DD_API_KEY=xxx
export DD_SITE=ap1.datadoghq.com

後は起動するだけです。

docker compose up

おわりに

今回はk6実行中のメトリクスをdatadogに連携する方法を消化しました。
負荷試験は長い時間行うこともあり、試験が正しく回っているのかを確認するためにも、メトリクスの監視は必須です。

よいk6ライフを。