문제 상황#

4000 VU 부하테스트 실행 시 즉시 실패:

Status: error (Pod: Succeeded)
Summary not available (test may have ended too quickly)

k6-operator 로그:

k6 inspect: {MaxVUs:1 ...}
ERROR: Parallelism argument cannot be larger than maximum VUs in the script
{"maxVUs": 1, "parallelism": 2, "error": "number of instances > number of VUs"}

원인 분석#

k6-operator 실행 흐름#

1. TestRun CR 생성
   ↓
2. Initializer Pod 실행
   - k6 inspect: 스크립트 분석 (maxVUs 확인)  ← 환경변수 없음!
   - k6 archive: 스크립트 압축
   ↓
3. Runner Pod 생성 (VUS, DURATION 환경변수 주입)
   ↓
4. 테스트 실행

문제 코드#

// scripts.go에서 생성되는 k6 스크립트
const _vus = __ENV.K6_VUS ? parseInt(__ENV.K6_VUS) : (parseInt(__ENV.VUS) || 1);
//                                                                          ↑
//                                                              기본값 1!
단계환경변수_vus 값
k6 inspect (Initializer)없음1 (기본값)
k6 run (Runner)VUS=40004000

k6 inspect는 스크립트의 export const options를 분석해서 maxVUs를 결정함. 환경변수가 없으면 기본값 1이 사용되어 maxVUs=1로 판단됨.

왜 환경변수가 없나?#

설계상 의도된 동작:

k6-operator는 스크립트를 분석해서 리소스 요구사항을 파악한 후 Runner Pod를 생성함. Initializer Pod는 분석 전용이라 VUS 환경변수가 주입되지 않음.

해결#

수정 내용#

k6-controller/services/scripts.go에서 기본값을 충분히 크게 설정:

// Before
const _vus = __ENV.K6_VUS ? parseInt(__ENV.K6_VUS) : (parseInt(__ENV.VUS) || 1);

// After
const _vus = __ENV.K6_VUS ? parseInt(__ENV.K6_VUS) : (parseInt(__ENV.VUS) || 50000);

적용 방법#

cd /path/to/k6-controller
docker build -t ghcr.io/goorm-gongbang/k6-controller:latest .

# 2. 푸시
docker push ghcr.io/goorm-gongbang/k6-controller:latest

# 3. 재시작
kubectl rollout restart deployment/k6-controller -n k6-system

관련 이슈#

ConfigMap 1MB 제한 (15000+ VU)#

대량의 토큰(15000+)을 사용할 때 ConfigMap 1MB 제한에 걸림.

해결: 토큰을 1000개씩 여러 ConfigMap으로 분할

  • createTokenConfigMaps(): 토큰을 청크로 분할
  • BuildScriptWithMultipleTokenFiles(): 여러 파일에서 토큰 로드
// 여러 파일에서 토큰 로드
const AUTH_TOKENS = new SharedArray('tokens', function() {
  let allTokens = [];
  try { allTokens = allTokens.concat(JSON.parse(open('/data/tokens-0/tokens.json'))); } catch(e) {}
  try { allTokens = allTokens.concat(JSON.parse(open('/data/tokens-1/tokens.json'))); } catch(e) {}
  // ...
  return allTokens;
});

참고#