Grafana k6 上手實踐

張晉濤發表於2021-12-21

大家好,我是張晉濤。

本篇我將為你介紹一個工具 - k6 ,它和 K8s 並沒有什麼直接的關係,它是一款開源的效能壓測工具。

k6 背後的故事

2016 年 8 月,k6 在 GitHub 上釋出了第一個版本,至此,一個出色的開源負載壓測工具進入了人們的視野。

2021 年的 6 月,對於 Grafana 和 k6 來講是個大日子,Grafana Labs 收購了 k6 。

而事實上, Grafana 與 k6 的緣分還要追溯到更早的 2 年前。

2019 年,在進行 Grafana 6.0 的短期令牌重新整理行為的壓測時,Grafana Labs 進行了一系列的技術選型。

由於 Grafana Labs 的大部分後端軟體是使用 Go 來實現的,恰巧 k6 滿足 OSS 和 Go 需求,並且負載測試是使用 JS 編寫(Grafana 前端框架及 UI 都在使用)。這使得 k6 自 Grafana 6.0 版本開始,不斷地為 Grafana 開發者及測試者完成追蹤 bug 的使命。

img

圖 1 ,k6 加入 Grafana Labs

多樣的壓測工具

一個稱心應手的自動化負載壓測工具會極大的提升程式開發人員的程式碼質量及效率。

下圖中是一些比較常見的用於負載壓測的工具,我們可以在 GitHub 上看到,目前,更新比較頻繁、活躍的專案主要有:Gatling, Jmeter 和 k6 。

img

圖 2 ,壓測工具們

如何從中選擇,簡單的講就是工具效率的比拼。主要從以下兩個方面來考量:

  • 工具效能
  • 工具使用體驗

下圖對以上工具進行了一些簡單的對比。

img

這裡我主要對比下其中較為活躍的 3 個專案。

  • JMeter - 熟悉 Java 的小夥伴可能比較瞭解這個工具。由於存在時間久,JMeter 的功能是這之中最全面的,並且整合、附加元件做的較好。基於它構建的 SaaS 服務 Blazemeter,相信大家也都熟識。這也導致了一個極大的問題,使用的複雜性高及不夠輕量級;
  • Gatling - Gatling 也有著 SaaS 產品 Gatling Frontline。就使用門檻來講,JS 要比 Scala 要低很多;
  • k6 - k6 最初是由 SaaS 服務 Load Impact 的幾名員工開發維護。使用門檻低(JS),引數化更簡單,並且 “負載測試即程式碼” 的理念也讓他的維護成本更低。未來可期。

img

圖 3 ,3 種熱門工具比一比

執行效果

img

或者這樣:

img

安裝 k6

k6 是用 Go 語言開發的,要安裝 k6 步驟很簡單,只要直接在其 GitHub 的 Release 頁面下載二進位制檔案即可。比如:

(MoeLove) ➜ wget -q https://github.com/grafana/k6/releases/download/v0.35.0/k6-v0.35.0-linux-amd64.tar.gz 
(MoeLove) ➜ tar -xzf k6-v0.35.0-linux-amd64.tar.gz 
(MoeLove) ➜ ls
k6-v0.35.0-linux-amd64  k6-v0.35.0-linux-amd64.tar.gz
(MoeLove) ➜ mv ./k6-v0.35.0-linux-amd64/k6 ~/bin/k6
(MoeLove) ➜ k6 version
k6 v0.35.0 (2021-11-17T09:53:18+0000/1c44b2d, go1.17.3, linux/amd64)

或者也可以直接使用它的 Docker 映象:

➜  ~ docker run  --rm loadimpact/k6  version   
k6 v0.35.0 (2021-11-17T09:53:03+0000/1c44b2d, go1.17.3, linux/amd64)

核心概念

在 k6 中並沒有太多的概念。其中最主要的就是用來執行測試的 virtual users (VUs) ,它的本質就是併發執行任務的次數。

在使用 k6 執行測試的時候,可以通過 --vus或者 -u進行指定,預設是 1 。

上手實踐

我個人感覺 k6 在目前的這些主流壓測工具中算使用者體驗比較好的一個。它使用 JS(ES6)作為配置語言,還是比較方便的,我們來做一些示例。

簡單請求

如果對於進行 HTTP 請求的時候,我們只需要從 k6/http 匯入 http即可。

注意在 k6 中,預設情況下必須得有個作為入口的 default函式,這類似我們常用的 main函式。

import http from "k6/http";

export default function(){
  http.get("https://test-api.k6.io/public/crocodiles/")
}

執行後效果如下:

(MoeLove) ➜ k6 run simple_http_get.js 

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

  execution: local
     script: simple_http_get.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.1s), 0/1 VUs, 1 complete and 0 interrupted iterations
default ✓ [======================================] 1 VUs  00m01.1s/10m0s  1/1 iters, 1 per VU

     data_received..................: 6.3 kB 5.7 kB/s
     data_sent......................: 634 B  578 B/s
     http_req_blocked...............: avg=848.34ms min=848.34ms med=848.34ms max=848.34ms p(90)=848.34ms p(95)=848.34ms
     http_req_connecting............: avg=75.59µs  min=75.59µs  med=75.59µs  max=75.59µs  p(90)=75.59µs  p(95)=75.59µs 
     http_req_duration..............: avg=247.46ms min=247.46ms med=247.46ms max=247.46ms p(90)=247.46ms p(95)=247.46ms
       { expected_response:true }...: avg=247.46ms min=247.46ms med=247.46ms max=247.46ms p(90)=247.46ms p(95)=247.46ms
     http_req_failed................: 0.00%  ✓ 0        ✗ 1  
     http_req_receiving.............: avg=455.24µs min=455.24µs med=455.24µs max=455.24µs p(90)=455.24µs p(95)=455.24µs
     http_req_sending...............: avg=103.77µs min=103.77µs med=103.77µs max=103.77µs p(90)=103.77µs p(95)=103.77µs
     http_req_tls_handshaking.......: avg=848.07ms min=848.07ms med=848.07ms max=848.07ms p(90)=848.07ms p(95)=848.07ms
     http_req_waiting...............: avg=246.9ms  min=246.9ms  med=246.9ms  max=246.9ms  p(90)=246.9ms  p(95)=246.9ms 
     http_reqs......................: 1      0.911502/s
     iteration_duration.............: avg=1.09s    min=1.09s    med=1.09s    max=1.09s    p(90)=1.09s    p(95)=1.09s   
     iterations.....................: 1      0.911502/s
     vus............................: 1      min=1      max=1
     vus_max........................: 1      min=1      max=1

k6 預設會將執行後的結果輸出到終端。同時它自帶了一些指標會同時輸出。

這些指標基本上都是語義化的,看名字就可以理解其含義,這裡就不一一介紹了。

帶檢查的請求

我們可以在請求中同時增加一些測試,判斷介面的響應值是否符合我們的預期。如下:

import http from "k6/http";
import { check, group } from "k6";

export default function() {

    group("GET", function() {
        let res = http.get("http://httpbin.org/get?verb=get");
        check(res, {
            "status is 200": (r) => r.status === 200,
            "is verb correct": (r) => r.json().args.verb === "get",
        });
    });
}

通過引入了 check函式,來執行一些判斷的邏輯,當然上述的 ==> 其實是 ES6 中的一種簡寫,將其展開為正常的函式也可以。比如:

import http from "k6/http";
import { check, group } from "k6";

export default function() {

    group("GET", function() {
        let res = http.get("http://httpbin.org/get?verb=get");
        check(res, {
          "status is 200": function(r){
             return r.status === 200
          },
            "is verb correct": (r) => r.json().args.verb === "get",
        });
    });
}

使用 k6 執行此指令碼後,得到的輸出相比之前的多瞭如下內容:

     █ GET

       ✓ status is 200
       ✓ is verb correct

     checks.........................: 100.00% ✓ 2        ✗ 0

從這裡可以看到我們當前請求介面的測試是否通過(也可以用來判斷當前介面是否能正常提供服務)。

自定義指標輸出

接下來我們嘗試下在壓測過程中定義一些自己定的指標。只需要從 k6/metrics中匯入一些不同型別的指標即可。這和在 Prometheus 中的型別基本一致。

這裡我增加了兩個 metric。一個 testCounter用於統計一共執行了多少次測試, passedRate計算通過率。

import http from "k6/http";
import { Counter, Rate } from "k6/metrics";
import { check, group } from "k6";


let testCounter = new Counter("test_counter");
let passedRate = new Rate("passed_rate");

export default function() {

    group("GET", function() {
        let res = http.get("http://httpbin.org/get?verb=get");
        let passed = check(res, {
            "status is 200": (r) => r.status === 200,
            "is verb correct": (r) => r.json().args.verb === "get",
        });

        testCounter.add(1);
        passedRate.add(passed);
    });
}

這裡我們設定了 2 個 VU, 以及設定了執行過程為 10s 執行後的輸出如下:

(MoeLove) ➜ k6 run -u 2 -d 10s  simple_custom_metrics.js
...
  execution: local
     script: simple_custom_metrics.js
     output: -

  scenarios: (100.00%) 1 scenario, 2 max VUs, 40s max duration (incl. graceful stop):
           * default: 2 looping VUs for 10s (gracefulStop: 30s)


running (10.4s), 0/2 VUs, 36 complete and 0 interrupted iterations
default ✓ [======================================] 2 VUs  10s

     █ GET

       ✓ status is 200
       ✓ is verb correct

     checks.........................: 100.00% ✓ 72       ✗ 0  
     data_received..................: 18 kB   1.7 kB/s
     data_sent......................: 3.9 kB  372 B/s
     group_duration.................: avg=567.35ms min=440.56ms med=600.52ms max=738.73ms p(90)=620.88ms p(95)=655.17ms
     http_req_blocked...............: avg=266.72µs min=72.33µs  med=135.14µs max=776.66µs p(90)=644.4µs  p(95)=719.96µs
     http_req_connecting............: avg=170.04µs min=45.51µs  med=79.9µs   max=520.69µs p(90)=399.41µs p(95)=463.55µs
     http_req_duration..............: avg=566.82ms min=439.69ms med=600.31ms max=738.16ms p(90)=620.52ms p(95)=654.61ms
       { expected_response:true }...: avg=566.82ms min=439.69ms med=600.31ms max=738.16ms p(90)=620.52ms p(95)=654.61ms
     http_req_failed................: 0.00%   ✓ 0        ✗ 36 
     http_req_receiving.............: avg=309.13µs min=122.4µs  med=231.72µs max=755.3µs  p(90)=597.95µs p(95)=641.92µs
     http_req_sending...............: avg=80.69µs  min=20.47µs  med=38.91µs  max=235.1µs  p(90)=197.87µs p(95)=214.79µs
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=566.43ms min=439.31ms med=600.16ms max=737.8ms  p(90)=620.19ms p(95)=654.18ms
     http_reqs......................: 36      3.472534/s
     iteration_duration.............: avg=567.38ms min=440.62ms med=600.53ms max=738.75ms p(90)=620.89ms p(95)=655.2ms 
     iterations.....................: 36      3.472534/s
     passed_rate....................: 100.00% ✓ 36       ✗ 0  
     test_counter...................: 36      3.472534/s
     vus............................: 2       min=2      max=2
     vus_max........................: 2       min=2      max=2

可以看到在輸出中多了兩行:

     passed_rate....................: 100.00% ✓ 36       ✗ 0  
     test_counter...................: 36      3.472534/s

與我們的預期相符。

不過這樣看起來不夠直觀,我們可以嘗試使用 k6 Cloud 來展示結果。登陸後,只要在執行 k6 時,通過 -o cloud的方式將輸出指定到 cloud 就可以在 cloud 上看到所有的指標了

img

總結

本篇主要是在介紹一個現代化的使用者體驗相對較好的壓測工具 k6 。我目前正在計劃將其引入到我們專案的 CI 中,以便了解每次核心部分的變更對專案效能的影響。

後續推進順利的話,會再分享 k6 如何應用到 CI 環境中,敬請期待。


歡迎訂閱我的文章公眾號【MoeLove】

相關文章