k6
k6 는 서버에 일부러 부하를 걸어 보는 도구입니다. 가상의 사용자를 여럿 만들어 같은 대본을 동시에 반복시킵니다. 대본은 JavaScript 로 씁니다. 그 대본을 돌리는 실행 파일은 Go 로 만들어졌습니다.
쉽고 빠른 이해
서버에 일부러 부하를 걸어 보는 실행 파일입니다. 대본을 JavaScript 로 써서 가상 사용자를 여럿 띄우고, 같은 대본을 동시에 반복시켜 트래픽을 만듭니다.
실제 사용자가 몰렸을 때 서비스가 버티는지는 미리 걸어 보지 않으면 알 수 없습니다. 기준을 정해 두면 그 결과를 합격과 불합격으로 가를 수 있고, 자동화 파이프라인에 끼워 넣을 수 있습니다.
도는 순서는 셋입니다.
- 초기화 코드가 가상 사용자마다 한 번씩 먼저 돌며 테스트 옵션을 정합니다.
- 가상 사용자마다 대본의 반복 실행 부분을 정해진 횟수나 시간만큼 되풀이합니다.
- 정해 둔 기준을 넘기면 실패로 끝나고, 종료 코드가 0 이 아니게 됩니다.
기본 기능에 없는 프로토콜을 쓰려면 무언가를 더 붙이거나 실행 파일을 통째로 다시 만들어야 합니다. 한 대가 감당할 수 있는 부하를 넘기면 이 실행 파일 혼자서는 안 되고 다른 도구로 넘어가야 합니다.
상세
Grafana Labs 가 내는 오픈소스 성능 테스트 도구입니다. 공식 문서는 스스로를 성능 문제를 일찍 잡고 안정성을 미리 끌어올리도록 돕는 확장 가능한 성능 테스트 도구라고 적습니다. 저장소의 README 는 핵심 설계 목표를 개발자 경험이라고 밝힙니다.
공식 문서가 지금 최신 판으로 안내하는 것은 v2.2.x 입니다. 받아서 설치한 다음 명령 한 줄로 부릅니다. 실행 파일 하나가 대본 파일 하나를 받아 돕니다.
k6 run script.js
엔진은 Go, 대본은 JavaScript
k6 엔진은 Go 로 작성되었고 그 안에 JavaScript 가상 머신을 품고 있습니다. 그 가상 머신이 Sobek 입니다. 문서는 Sobek 을 Go 로 작성된 JavaScript 엔진이라고 적고, k6 실행 파일에 Sobek 이 박혀 있어서 테스트 대본을 JavaScript 로 쓸 수 있다고 설명합니다. Sobek 저장소는 goja 에서 갈라져 나온 갈래입니다.
flowchart TD
G["goja"] -- 포크 --> S
subgraph EXE["k6 실행 파일(Go)"]
S["Sobek(JavaScript 엔진)"]
end
앞서 말한 k6 엔진이 곧 이 k6 실행 파일입니다. Go 로 만들어진 실행 파일 자체가 그 안에 JavaScript 엔진인 Sobek 을 품고 있는 것이지, Sobek 이 별도 프로세스로 떠 있는 것이 아닙니다.
이 구조 때문에, k6 에 원래 없는 기능을 붙이는 확장을 Go 코드로 만들고 대본에서는 그것을 여느 JavaScript 모듈처럼 가져다 쓸 수 있습니다.
.ts 확장자를 가진 파일은 esbuild 가 JavaScript 코드로 바꿔 줍니다. 다만 문서는
TypeScript 지원이 부분적이라고 적습니다. 타입 정보를 떼어낼 뿐 타입 안전성을 주지는 않습니다.
가상 사용자와 대본의 두 부분
부하를 만드는 단위는 가상 사용자(Virtual User, VU)입니다. 용어집은 가상 사용자를 테스트 대본의
반복 실행을 각자 따로, 동시에 돌리는 모의 사용자라고 정의합니다. 입문 문서는 이를 더 거칠게
"본질적으로 병렬로 도는 while(true) 루프" 라고 적습니다. 일반적으로 가상 사용자가 많아질수록
만들어지는 트래픽도 많아집니다.
대본은 두 부분으로 나뉩니다. 하나는 테스트를 준비하는 초기화 코드이고, 다른 하나는 실제로
요청을 보내는 가상 사용자 코드입니다. 초기화 코드는 함수를 정의하고 테스트 옵션을 설정하는
자리입니다. 가상 사용자 코드는 default 함수 안에 들어갑니다.
flowchart TD
S["대본 파일"] --> V["가상 사용자 1"]
S --> V2["가상 사용자 2"]
V --> I["초기화 코드 · 한 번"]
V2 --> I2["초기화 코드 · 한 번"]
I --> D["default 함수를 반복"]
I2 --> D2["default 함수를 반복"]
초기화 코드는 가장 먼저 돌고 가상 사용자마다 한 번씩만 불립니다. default 코드는 테스트
옵션이 정한 횟수만큼, 또는 정한 시간 동안 반복해서 돕니다.
포기한 것
Node.js 가 아니다
대본이 JavaScript 라고 해서 Node.js 프로그램인 것은 아닙니다. 문서는 k6 가 브라우저에 가까운
모듈 해석 방식을 쓰고 Node.js 의 모듈 해석은 지원하지 않는다고 못 박습니다. 그래서 import
에 적는 파일 이름은 ./helpers.js 처럼 끝까지 다 적어야 합니다.
require() 도 이름만 같은 다른 물건입니다. 문서는 이것이 k6 가 따로 구현한 모듈 적재
방식이며 Node.js 의 require() 와 같게 동작하지 않는다고 적습니다. 다룰 수 있는 것은 셋뿐입니다.
| 적재할 수 있는 것 |
|---|
| k6 내장 모듈 |
| 로컬 파일 시스템에 있는 대본 |
| HTTP(HyperText Transfer Protocol) 또는 HTTPS 로 받아 오는 원격 대본 |
Node.js 의 모듈 해석 알고리즘은 지원하지 않습니다. 대신 얻은 것은 실행 파일 하나입니다. 문서는 성능을 위해 엔진을 Go 로 쓰고 그 안에 JavaScript 가상 머신을 품었다고 이 선택의 이유를 밝힙니다.
여러 대에 흩는 일은 이 실행 파일이 안 한다
이 실행 파일을 하나 띄우면 프로세스가 하나 뜹니다. 그 k6 프로세스 하나가 부하 생성기 — k6 가 실제로 도는 그 컴퓨터를 가리키는 말입니다 — 한 대의 모든 CPU(Central Processing Unit) 코어를 씁니다. 문서는 이 문서의 지침을 지키면 k6 한 대가 동시 가상 사용자 30,000~40,000 명을 돌릴 수 있고, 경우에 따라 초당 HTTP 요청 300,000 개까지 만들 수 있다고 적습니다. 초당 100,000~300,000 요청을 넘길 일이 없다면 한 대로 충분할 것 같다고도 덧붙입니다.
그 위로 넘어가는 일, 그러니까 여러 대가 하나의 테스트를 나눠 도는 일은 이 k6 코어 —
앞서 말한 그 실행 파일 하나 — 가 하지 않습니다. 그 자리를 받는 것은 k6 Operator 라는
별도 프로젝트입니다. 이것은 Kubernetes 의
오퍼레이터 패턴 구현이고, TestRun 이라는 커스텀 리소스 타입을 정의합니다. TestRun 객체
하나가 대본을 가리키고 환경을 설정하고 parallelism 으로 인스턴스 개수를 지정합니다.
여러 대가 필요한 경우로 문서가 드는 것은 셋입니다. 테스트 대상을 여러 IP(Internet Protocol) 주소에서 접근해야 할 때, 최적화를 다 마친 노드 하나로도 필요한 부하가 안 나올 때, 그리고 이미 Kubernetes 를 운영 환경으로 쓰고 있을 때입니다.
flowchart TD
A["k6 프로세스 한 대"] --> B{"한 대로 충분한가?"}
B -- "예 · 30,000~40,000 VU 안쪽" --> C["그대로 한 대에서 돈다"]
B -- 아니오 --> D["k6 Operator"]
D --> E["TestRun 리소스가 parallelism 개수만큼<br/>인스턴스로 흩는다"]
경계를 넘는 판단은 이 실행 파일 안에 없습니다.
프로토콜을 k6 코어에 다 넣지 않는다
k6 코어에 없는 기능은 확장으로 붙입니다. 문서가 나누는 확장은 네 종류입니다.
| 확장 종류 | 무엇을 하나 |
|---|---|
| JavaScript 확장 | 대본에서 쓸 수 있는 JavaScript API(Application Programming Interface) 를 늘립니다. 새 네트워크 프로토콜 지원을 붙이기도 합니다 |
| 출력 확장 | 메트릭을 별도 파일 형식이나 서비스로 보냅니다 |
| 시크릿 소스 확장 | 테스트에 시크릿을 공급합니다 |
| 서브커맨드 확장 | k6 x 이름공간 아래에 명령을 추가합니다 |
여기서 시크릿은 API 키·비밀번호처럼 대본에 그대로 적으면 안 되는 값을 뜻합니다.
이 넷은 확장이 무엇을 하느냐로 가른 분류입니다. 어디서 왔느냐 — 공식·커뮤니티가 만들었는지 직접 만들었는지 — 는 또 다른 기준입니다. 붙여서 돌리는 방법이 여기서 갈립니다.
붙여서 돌리는 방법은 둘입니다. 하나는 자동 해석입니다. 대본에서 확장 모듈을 import 하면 k6
가 알아서 찾아 적재합니다. 다만 제약이 붙습니다.
| 자동 해석의 제약 |
|---|
| 공식 확장과 커뮤니티 확장에만 걸립니다 |
| 출력 확장은 지원하지 않습니다 |
| 표준 입력으로 대본을 넘기는 방식은 지원하지 않습니다 |
.js · .ts · .tar 확장자 파일만 쓸 수 있습니다 |
나머지 하나는 xk6 로 k6 실행 파일을 새로 빌드하는 것입니다. xk6 는 그 커스텀 실행 파일을 만드는 빌드 도구입니다. 자동 해석이 안 되는 확장과 직접 만든 확장은 이 길을 거쳐야 합니다. 받아서 바로 쓰는 실행 파일 하나라는 성질을 그 순간 내려놓는 것입니다.
flowchart TD
A["대본이 쓰려는 확장"] --> B{"공식 또는 커뮤니티 확장인가?"}
B -- 예 --> C["k6 가 자동으로 찾아 적재 · 제약 있음"]
B -- "아니오 · 직접 만든 확장" --> D["xk6 로 실행 파일을 새로 빌드"]
예시
옵션을 대본에 박은 최소 형태
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);
}
vus 가 동시에 도는 가상 사용자 수, duration 이 테스트를 도는 총 시간입니다. default
함수가 가상 사용자 코드이고, 이 대본에서는 요청 하나를 보내고 1초 쉽니다. 같은 값을 명령줄로
줄 수도 있습니다.
k6 run --vus 10 --duration 30s script.js
임계값으로 합격과 불합격을 가르는 대본
import http from 'k6/http';
export const options = {
thresholds: {
http_req_failed: ['rate<0.01'], // http errors should be less than 1%
http_req_duration: ['p(95)<200'], // 95% of requests should be below 200ms
},
};
export default function () {
http.get('https://quickpizza.grafana.com');
}
thresholds 는 테스트 메트릭에 거는 합격·불합격 기준입니다. 여기서는 HTTP 오류 비율이 1퍼센트
미만일 것, 요청의 95퍼센트가 200밀리초 안에 끝날 것 두 가지를 겁니다. 문서는 테스터들이
서비스 수준 목표(Service Level Objective, SLO)를 코드로 못 박는 데 임계값을 자주 쓴다고
적습니다.
기준을 다 만족하면 k6 는 그 테스트를 합격으로 보고 종료 코드 0 으로 끝납니다. 하나라도
실패하면 0 이 아닌 종료 코드로 끝납니다. 파이프라인이 이 종료 코드를 보고 빌드를 떨어뜨립니다.
기다리지 않고 즉시 멈추게 할 수도 있습니다. abortOnFail 을 true 로 두면 임계값이 깨지는
즉시 테스트가 멈춥니다.
flowchart TD
T["임계값 검사"] -->|모두 만족| P["합격 · 종료 코드 0"]
T -->|하나라도 실패| F["불합격 · 0이 아닌 종료 코드"]
F --> A{"abortOnFail 을 켰나?"}
A -- 예 --> S["그 자리에서 테스트를 즉시 멈춘다"]
A -- 아니오 --> W["나머지 테스트를 마저 돌리고 끝난다"]
export const options = {
thresholds: {
http_req_duration: [{ threshold: 'p(99) < 10', abortOnFail: true }],
},
};
부하의 모양을 고르는 실행기
실행기는 k6 가 가상 사용자와 반복을 어떤 일정으로 돌릴지 정합니다. options 의 scenarios
객체 안에 이름을 붙여 넣는 설정 하나하나가 시나리오이고, 그 시나리오 객체의 executor 키에
실행기 이름을 적습니다.
export const options = {
scenarios: {
arbitrary_scenario_name: {
//Name of executor
executor: 'ramping-vus',
// more configuration here
},
},
};
| 이름 | 값 | 문서가 적는 것 |
|---|---|---|
| Shared iterations | shared-iterations |
정해진 반복 횟수를 여러 가상 사용자가 나눠 가집니다 |
| Per VU iterations | per-vu-iterations |
가상 사용자마다 정확히 정해진 횟수만큼 반복합니다 |
| Constant VUs | constant-vus |
고정된 수의 가상 사용자가 정해진 시간 동안 가능한 만큼 반복합니다 |
| Ramping VUs | ramping-vus |
변하는 수의 가상 사용자가 정해진 시간 동안 가능한 만큼 반복합니다 |
| Constant Arrival Rate | constant-arrival-rate |
정해진 기간 안에 고정된 수의 반복을 실행합니다 |
| Ramping Arrival Rate | ramping-arrival-rate |
정해진 기간 안에 변하는 수의 반복을 실행합니다 |
가상 사용자 수를 시간에 따라 올렸다 내리는 것은 stages 로도 적을 수 있습니다. 이것은 위
표의 ramping-vus 실행기 하나짜리 시나리오를 짧게 쓴 것입니다.
export const options = {
stages: [
{ duration: '30s', target: 20 },
{ duration: '1m30s', target: 10 },
{ duration: '20s', target: 0 },
],
};
운영
아무것도 안 적으면 어떻게 도나
| 옵션 | 환경 변수 | 명령줄 | 기본값 |
|---|---|---|---|
vus |
K6_VUS |
--vus, -u |
1 |
duration |
K6_DURATION |
--duration, -d |
null |
iterations |
K6_ITERATIONS |
--iterations, -i |
1 |
stages |
K6_STAGES |
--stage <duration>:<target> |
vus 와 duration 값으로 정해집니다 |
--no-thresholds |
K6_NO_THRESHOLDS |
--no-thresholds |
false |
아무것도 안 적으면 가상 사용자 하나가 default 함수를 정확히 한 번 돌립니다. vus 와
iterations 의 기본값이 둘 다 1이기 때문입니다. vus 와 duration 을 같이 주면
constant-vus 실행기 하나짜리 시나리오를 쓴 것과 같습니다. 예시에서 본 것처럼 stages
자체는 ramping-vus 실행기 하나짜리 시나리오를 짧게 쓴 것입니다. stages 를 따로 안
적으면 그 자리는 vus 와 duration 값으로 채워집니다.
한 대에서 어디까지 나오나
문서가 적는 수치는 동시 가상 사용자 30,000~40,000 명, 초당 HTTP 요청 300,000 개까지입니다. 그 선을 향해 갈 때 문서가 수치로 못박는 자리는 CPU 사용률 하나입니다.
| 볼 자리 | 문서가 적는 선 |
|---|---|
| CPU 사용률 | 80퍼센트 안에 머물게 합니다. 모든 코어가 100퍼센트로 차면 결과가 실제보다 나빠 보일 수 있습니다 |
메모리는 가상 사용자 수에 거의 비례합니다. 문서는 단순한 테스트가 가상 사용자당 1~5메가바이트
쯤 쓴다고 적습니다. 가상 사용자 1,000 명이면 1~5기가바이트입니다. 파일을 올리거나 큰
JavaScript 모듈을 불러오는 테스트는 가상 사용자당 수십 메가바이트까지 갑니다. 가상 사용자
하나하나가 테스트가 쓰는 모든 JavaScript 모듈의 사본을 들고 있기 때문입니다. 이걸 나눠 쓰려면
SharedArray 를 쓰거나 Redis 같은 외부 저장소를 두라고 문서는 적습니다.
응답 본문도 메모리를 먹습니다. k6 는 기본적으로 요청의 응답 본문을 메모리에 올립니다. 문서는
이것이 메모리 사용량을 훨씬 키우고 종종 불필요하다고 적습니다. 안 올리게 하는 손잡이가
discardResponseBodies 입니다.
export const options = {
discardResponseBodies: true,
};
운영체제 쪽 상한
현대 운영체제는 한 프로그램이 만들 수 있는 동시 네트워크 연결 수를 꽤 낮게 잡아 둡니다.
문서는 대부분의 프로그램이 k6 처럼 수천 개의 동시 TCP(Transmission Control Protocol) 연결을
열 필요가 없으므로 그것이 안전한 기본값이라고 적습니다. 그래서 큰 테스트 전에는 그 값을
올립니다. Linux 에서 root 로 다음을 실행하라고 문서가 적습니다.
sysctl -w net.ipv4.ip_local_port_range="1024 65535"
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_timestamps=1
ulimit -n 250000
응답이 안 올 때
k6 의 기본 타임아웃은 60초입니다. 요청은 나갔는데 대상 시스템이 그 안에 응답을 못 만들면 에러가 찍힙니다. 이것은 부하 생성기가 아니라 대상 쪽을 가리키는 신호입니다.
관련 항목
k6 가 다루는 테스트 기법과 개념
부하 테스트 · 가상 사용자 · 임계값 · 스파이크 테스트 · 스트레스 테스트 · 소크 테스트 · 합성 모니터링 · 카오스 테스트
k6 실행 파일을 이루는 구성 요소
Go · JavaScript · TypeScript · Sobek · goja · esbuild · ECMAScript 모듈
k6 가 부하를 거는 프로토콜
여러 대로 흩을 때 쓰는 기술
Kubernetes · 오퍼레이터 패턴 · 커스텀 리소스
k6 를 둘러싼 도구와 개념
다른 이름: Grafana k6