k6 - 現代負載測試工具

最近工作上用了這套工具幫產品做 Load testing,使用起來相當舒服,測試結果與實際狀況也很接近,上來分享一下

k6

k6 是一個 Open source load testing tool 和 Saas,用 Go 撰寫,有點可惜的是使用 JavaScript 來寫腳本,但有提供許多 Examples,對於 JavaScript 不熟的人撰寫起來也不困難。

使用起來很簡單,介面也相當乾淨,也有提供 testing API 讓你玩看看它。

安裝

方法很多種,這邊是直接用 homebrew

$ brew install k6

執行

先簡單寫了一個 test.js,情境為每一秒戳一次 http://test.k6.io

import http from 'k6/http';
import { sleep } from 'k6';

export default function () {
  http.get('http://test.k6.io');
  sleep(1);
}
$ k6 run test.js
          /\      |‾‾| /‾‾/   /‾‾/
     /\  /  \     |  |/  /   /  /
    /  \/    \    |     (   /   ‾‾\
   /          \   |  |\  \ |  (‾)  |
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: test.js
     output: -

  scenarios: (100.00%) 1 scenario, 1 max VUs, 10m30s max duration (incl. graceful stop):
           * default: 1 iterations for each of 1 VUs (maxDuration: 10m0s, gracefulStop: 30s)


running (00m01.9s), 0/1 VUs, 1 complete and 0 interrupted iterations
default ✓ [======================================] 1 VUs  00m01.9s/10m0s  1/1 iters, 1 per VU

    data_received..............: 11 kB 5.8 kB/s
    data_sent..................: 76 B  40 B/s
    http_req_blocked...........: avg=557.07ms min=557.07ms med=557.07ms max=557.07ms p(90)=557.07ms p(95)=557.07ms
    http_req_connecting........: avg=303.93ms min=303.93ms med=303.93ms max=303.93ms p(90)=303.93ms p(95)=303.93ms
    http_req_duration..........: avg=307.96ms min=307.96ms med=307.96ms max=307.96ms p(90)=307.96ms p(95)=307.96ms
    http_req_receiving.........: avg=518µs    min=518µs    med=518µs    max=518µs    p(90)=518µs    p(95)=518µs
    http_req_sending...........: avg=1.98ms   min=1.98ms   med=1.98ms   max=1.98ms   p(90)=1.98ms   p(95)=1.98ms
    http_req_tls_handshaking...: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s
    http_req_waiting...........: avg=305.46ms min=305.46ms med=305.46ms max=305.46ms p(90)=305.46ms p(95)=305.46ms
    http_reqs..................: 1     0.526921/s
    iteration_duration.........: avg=1.86s    min=1.86s    med=1.86s    max=1.86s    p(90)=1.86s    p(95)=1.86s
    iterations.................: 1     0.526921/s
    vus........................: 1     min=1 max=1
    vus_max....................: 1     min=1 max=1

執行後會直接在 stdout 顯示測試的結果,VU (virtual user) 為 1 ,代表這次測試只有模擬一個 user 在執行這個動作。

如果要模擬更多 VU 或持續時間只需要:

$ k6 run --vus 10 --duration 30s test.js

也可以定義在 script 裡面

import http from 'k6/http';
import { sleep } from 'k6';
export let options = {
  vus: 10,
  duration: '30s',
};
export default function () {
  http.get('http://test.k6.io');
  sleep(1);
}

Ramping up and down VUs

k6 提供 stages,可以模擬 user 持續增加或遞減

import http from 'k6/http';
import { check, sleep } from 'k6';

export let options = {
  stages: [
    { duration: '30s', target: 20 },
    { duration: '1m30s', target: 10 },
    { duration: '20s', target: 0 },
  ],
};

export default function () {
  let res = http.get('https://httpbin.org/');
  check(res, { 'status was 200': (r) => r.status == 200 });
  sleep(1);
}

以上面這個例子來說,情境就會是:

  1. user 在 30 秒內增加到 20 人
  2. 1 分 30 秒內降至 10 人
  3. 最後 20 秒逐漸退去到 0 人

check function 為確認回的 code 是 200,如果有非 200 的會在結束後統一顯示結果。

Test Types

並非每種測試都叫做壓力測試,官網上有寫出這些測試的差別和使用情境,這邊簡單介紹一下:

Smoke Testing

盡可能用最少的資源去測試,只是為了測試出有沒有 error

smoke-test

Load Testing

Load testing 也稱 Performance testing ,主要是關注當前系統的配置,在多少 user 或 RPS 時,performance 是如何

load-test

Stress Testing

和 Load testing 不同,主要是測試系統的極限在哪裡

勿在 production 測試

stress-test

Spike Testing

是 Stress testing 的變體,與之不同的是,流量不會慢慢進來,測試在短時間內大量的流量進來,系統還能不能撐住

勿在 production 測試

spike-test

Soak Testing

主要測試在長時間下,系統的可靠性和效能

soak-test

這次產品測試

這次產品有預估人數,所以設定了一個人數(假設為 5000 人),來做 Load testing

人數的情境:

  1. 5 min 到 5000 人
  2. 5 min 持平
  3. 2 min 5000 人退去到 0

scenario

打 API 的情境:

  1. login 拿到 token (只做一次)
  2. 之後每 10 秒戳一次某 API

寫成 script 大概會是這樣:

import http from 'k6/http';
import {check, group, sleep} from 'k6';

export let options = {
  stages: [
    { duration: '5m', target: 5000 },
    { duration: '5m', target: 5000 },
    { duration: '2m', target: 0 },
  ],
};

let isLogin = false

export default function () {
  group('login', () => {
    if (!isLogin) {
      let loginRes = http.post(`https://api.example.com/v1/login/`, {
        username: `user_${__VU}`,
        password: 'pwd',
      });
      isLogin = true

      check(loginRes, { 'logged in successfully': (resp) => resp.json('token') !== '' });
    }
  })

  group('get something', () => {
    let res = http.get(`https://api.example.com/v1/something`, {
      headers: {
        Authorization: `Bearer ${loginRes.json('token')}`,
      },
    }).json();
    check(res, { 'status was 204': (r) => r.status === 204 });
  })

  sleep(10);
}

不同的 VU 不會共用 variable

測試結果:

test

實際情況:

actual

以 CPU 使用率為例,可以看到在持平的時候測試結果維持在 18% 左右,而產品實際快到達該人數的時候是 17% 左右,很接近測試的數據。

後記

以往做 load testing 的時候都是用 vegeta 來測試,但測試的效果有限,沒辦法真正測出 End-User 實際使用的情境,所以只能大概多估算一下目前配置能乘載多少人。

k6 提供了許多豐富的功能,可以根據不同情境做不同測試,也支援了許多 Protocol,像是 WebSocket gRPC 等,也可以把 result output 到 JSON、CSV、AWS Cloud Watch 等。

k6 Cloud 提供了很豐富的資料分析和精美的介面,也能幫你做定時的測試,但是免費仔要用的話 VU 只能到 50 人,要上去就要再付錢了。

經過這次認識到測試類型有很多種,而不是每個都叫做壓力測試,讓自己又增長了不少知識,有興趣的可以去官網 docs 看看,收穫會更多。