Problem#

A 4000 VU load test failed immediately upon execution:

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

k6-operator logs:

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"}

Root Cause Analysis#

k6-operator execution flow#

1. TestRun CR created
   ↓
2. Initializer Pod runs
   - k6 inspect: analyzes script (determines maxVUs)  ← no env vars here!
   - k6 archive: compresses script
   ↓
3. Runner Pod created (VUS, DURATION env vars injected)
   ↓
4. Test runs

Problematic code#

// k6 script generated by scripts.go
const _vus = __ENV.K6_VUS ? parseInt(__ENV.K6_VUS) : (parseInt(__ENV.VUS) || 1);
//                                                                          ↑
//                                                              default value 1!
StageEnv vars_vus value
k6 inspect (Initializer)none1 (default)
k6 run (Runner)VUS=40004000

k6 inspect analyzes export const options in the script to determine maxVUs. Without env vars, the default value of 1 is used, so maxVUs is reported as 1.

Why are env vars missing?#

This is intentional by design:

The k6-operator analyzes the script to determine resource requirements before creating the Runner Pod. The Initializer Pod is analysis-only, so VUS env vars are not injected into it.

Fix#

Change#

In k6-controller/services/scripts.go, set the default value large enough to never be a limiting factor:

// 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);

How to apply#

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

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

# 3. Restart
kubectl rollout restart deployment/k6-controller -n k6-system

ConfigMap 1MB limit (15000+ VUs)#

When using large numbers of tokens (15000+), you can hit the ConfigMap 1MB size limit.

Fix: Split tokens into multiple ConfigMaps of 1000 each

  • createTokenConfigMaps(): splits tokens into chunks
  • BuildScriptWithMultipleTokenFiles(): loads tokens from multiple files
// Load tokens from multiple files
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;
});

References#