k6를 활용한 부하테스트

k6는 효율적인 부하 테스트 도구로, CI/CD 통합이 용이하여 현대 소프트웨어 개발에 필수적입니다.

k6를 활용한 부하테스트
Photo by Jr Korpa / Unsplash

개요

시스템의 안정성과 성능을 검증하는 부하 테스트는 현대 소프트웨어 개발에서 필수적인 과정입니다. 이를 위한 도구로 전통적인 Apache JMeter와 Grafana에서 지원하는 k6가 대표적입니다.

JMeter는 Java 기반의 GUI 도구로, 직관적인 인터페이스와 풍부한 플러그인 생태계가 장점입니다. 하지만 높은 리소스 사용량과 현대적인 CI/CD 통합의 한계가 있습니다.

k6는 Go로 작성된 새로운 부하 테스트 도구로, JavaScript로 테스트 스크립트를 작성합니다. 효율적인 리소스 사용과 클라우드 네이티브 환경 지원이 특징이며, CI/CD 통합이 용이합니다. 다만 CLI 기반으로 GUI가 없고 오랜 시간 사용되었던 JMeter에 대비하여 레퍼런스가 상대적으로 부족합니다.

해당 문서에는 간단한 테스트 및 Javascript에 친숙한 점에 있어 k6로 테스트를 진행하였으며, 테스트 방법을 정리하였습니다.

Prerequisite

첫 번째 테스트

테스트 기본 구조

  1. 기본 함수
    • 테스트 로직이 위치하는 곳입니다. 이 함수는 테스트가 수행할 작업과 실행 중의 동작을 정의합니다. 스크립트에서 기본 함수로 내보내야 합니다.
  2. Import
    • 추가적인 k6 모듈이나 JavaScript 라이브러리(jslibs)를 임포트하여 스크립트의 기능을 확장할 수 있습니다. 예를 들어 HTTP 요청을 하거나 브라우저 상호작용을 시뮬레이션하는 등의 작업을 할 수 있습니다. k6는 Node.js 위에 구축되지 않았으며, 자체 JavaScript 런타임을 사용합니다. 일부 npm 모듈과의 호환성은 다를 수 있습니다.
  3. 옵션(선택 사항)
    • 테스트의 실행을 구성할 수 있게 해줍니다. 예를 들어 가상 사용자 수, 테스트 기간 또는 성능 기준을 설정하는 등의 작업을 할 수 있습니다. 자세한 내용은 옵션을 참조하세요.
  4. 생애 주기 작업(선택 사항)
    • 테스트 로직 실행 전후에 코드를 실행해야 할 필요가 있을 수 있습니다. 예를 들어 파일에서 데이터를 파싱하거나 Amazon S3에서 객체를 다운로드하는 등의 작업을 수행할 수 있습니다. 생애 주기 작업을 사용하면 미리 정의된 함수나 특정 코드 범위 내에서 코드를 작성하여 테스트 실행의 다양한 단계에서 실행될 수 있습니다.
// HTTP 요청을 하기 위해 http 모듈을 임포트합니다. 
// 이 시점부터 `http` 메서드를 사용하여 HTTP 요청을 할 수 있습니다.
import http from 'k6/http';

// Delay를 구성하기 위해 sleep 함수를 임포트합니다.
// 이 시점부터 `sleep` 함수를 사용하여 테스트 스크립트에 지연을 도입할 수 있습니다.
import { sleep } from 'k6';

// 테스트가 실행될 때 실행될 내보내기 함수 정의.
export default function () {
  // 지정된 URL에 HTTP GET 요청을 보냅니다.
  const res = http.get('https://test.k6.io');

  // 응답 상태 코드를 로그에 기록합니다.
  console.log(res.status);

  // 1초의 지연을 도입합니다.
  sleep(1);
}

테스트 실행하기

테스트가 완료되면 다음과 같이 결과가 생성됩니다.

k6 run k6-test.js

-----------------------------------------------

data_received..................: 16 kB 9.6 kB/s
data_sent......................: 442 B 268 B/s
http_req_blocked...............: avg=434.9ms  min=434.9ms  med=434.9ms  max=434.9ms  p(90)=434.9ms  p(95)=434.9ms 
http_req_connecting............: avg=214.81ms min=214.81ms med=214.81ms max=214.81ms p(90)=214.81ms p(95)=214.81ms
http_req_duration..............: avg=215.23ms min=215.23ms med=215.23ms max=215.23ms p(90)=215.23ms p(95)=215.23ms
 { expected_response:true }...: avg=215.23ms min=215.23ms med=215.23ms max=215.23ms p(90)=215.23ms p(95)=215.23ms
http_req_failed................: 0.00% 0 out of 1
http_req_receiving.............: avg=101µs    min=101µs    med=101µs    max=101µs    p(90)=101µs    p(95)=101µs   
http_req_sending...............: avg=37µs     min=37µs     med=37µs     max=37µs     p(90)=37µs     p(95)=37µs    
http_req_tls_handshaking.......: avg=218.38ms min=218.38ms med=218.38ms max=218.38ms p(90)=218.38ms p(95)=218.38ms
http_req_waiting...............: avg=215.09ms min=215.09ms med=215.09ms max=215.09ms p(90)=215.09ms p(95)=215.09ms
http_reqs......................: 1     0.605518/s
iteration_duration.............: avg=1.65s    min=1.65s    med=1.65s    max=1.65s    p(90)=1.65s    p(95)=1.65s   
iterations.....................: 1     0.605518/s
vus............................: 1     min=1      max=1
vus_max........................: 1     min=1      max=1

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

각 메트릭에 대한 설명입니다.

1. 데이터 전송 관련

- `data_received`: 서버로부터 받은 총 데이터량 (16 kB)
- `data_sent`: 서버로 보낸 총 데이터량 (442 B)

2. 
HTTP 요청 단계별 시간

- `http_req_blocked`: 요청이 큐에서 대기한 시간 (평균 434.9ms)
- `http_req_connecting`: TCP 연결 수립하는데 걸린 시간 (평균 214.81ms)
- `http_req_duration`: 전체 요청 처리 시간 (평균 215.23ms)
  - `expected_response:true`: 성공적인 응답의 처리 시간
- `http_req_failed`: 실패한 요청의 비율 (0%)
- `http_req_receiving`: 서버로부터 응답을 받는데 걸린 시간 (평균 101µs)
- `http_req_sending`: 요청을 보내는데 걸린 시간 (평균 37µs)
- `http_req_tls_handshaking`: HTTPS 연결 수립 시간 (평균 218.38ms)
- `http_req_waiting`: 서버 처리 대기 시간 (TTFB, 평균 215.09ms)

3. 테스트 실행 관련
- `http_reqs`: 총 요청 수 (1개)
- `iteration_duration`: 각 반복 실행의 총 소요 시간 (평균 1.65초)
- `iterations`: 총 반복 실행 횟수 (1회)
- `vus`: 가상 사용자 수 (1명)
- `vus_max`: 최대 가상 사용자 수 (1명)


- `avg`: 평균값
- `min`: 최소값
- `med`: 중간값
- `max`: 최대값
- `p(90)`: 90번째 백분위수(전체 요청 중 90%)
- `p(95)`: 95번째 백분위수(전체 요청 중 95%)

CLI 동작에서 옵션 값을 설정하여 테스트 실행 또한 가능합니다

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

---------------------------------------------------------------

data_received..................: 16 kB 9.6 kB/s
data_sent......................: 442 B 268 B/s
http_req_blocked...............: avg=434.9ms  min=434.9ms  med=434.9ms  max=434.9ms  p(90)=434.9ms  p(95)=434.9ms 
http_req_connecting............: avg=214.81ms min=214.81ms med=214.81ms max=214.81ms p(90)=214.81ms p(95)=214.81ms
http_req_duration..............: avg=215.23ms min=215.23ms med=215.23ms max=215.23ms p(90)=215.23ms p(95)=215.23ms
{ expected_response:true }...: avg=215.23ms min=215.23ms med=215.23ms max=215.23ms p(90)=215.23ms p(95)=215.23ms
http_req_failed................: 0.00% 0 out of 1
http_req_receiving.............: avg=101µs    min=101µs    med=101µs    max=101µs    p(90)=101µs    p(95)=101µs   
http_req_sending...............: avg=37µs     min=37µs     med=37µs     max=37µs     p(90)=37µs     p(95)=37µs    
http_req_tls_handshaking.......: avg=218.38ms min=218.38ms med=218.38ms max=218.38ms p(90)=218.38ms p(95)=218.38ms
http_req_waiting...............: avg=215.09ms min=215.09ms med=215.09ms max=215.09ms p(90)=215.09ms p(95)=215.09ms
http_reqs......................: 1     0.605518/s
iteration_duration.............: avg=1.65s    min=1.65s    med=1.65s    max=1.65s    p(90)=1.65s    p(95)=1.65s   
iterations.....................: 1     0.605518/s
vus............................: 1     min=1      max=1
vus_max........................: 1     min=1      max=1
Notion Image

option을 추가하여 테스트 또한 가능합니다.

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

구체적인 시나리오를 지정하며 단계적인 시나리오 구성은 관련 링크에서 확인할 수 있습니다.

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

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

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

코드를 실행하기 위한 곳은 3가지에서 실행 가능하며 Local, Distributed, k6 Cloud에서 실행할 수 있습니다.

k8s 환경에서 실행하기 위한 YAML 구성은 다음과 같습니다.

---
apiVersion: k6.io/v1alpha1
kind: K6
metadata:
  name: k6-sample
spec:
  parallelism: 4
  script:
    configMap:
      name: 'k6-test'
      file: 'script.js'

출력

K6_WEB_DASHBOARD=true k6 run script.js

Dashboard, Report 모두 웹 형식으로 지원합니다.

Notion Image
Notion Image

Reference