Chuyển đến nội dung chính

Bài 4: k6 — Load Testing Framework từ Grafana

k6 scripting, scenarios, thresholds, checks, custom metrics. k6 Cloud, k6 browser module, k6 operator cho Kubernetes.

🔒 DevSecOps — Bài 4 Bài 4: k6 — Load Testing Framework từ Grafana

Performance Testing & Pentest: Quy trình Chuẩn Doanh nghiệp 2026

Phần 1: Nền tảng Performance Testing

xdev.asia

1. Giới thiệu k6

k6 (Grafana k6) là open-source load testing tool viết bằng Go, sử dụng JavaScript/TypeScript cho test scripts. Ưu điểm: nhẹ, nhanh, developer-friendly, tích hợp tốt với Grafana ecosystem.

# Cài đặt
brew install k6                    # macOS
sudo apt install k6                # Ubuntu/Debian
docker run --rm -i grafana/k6 run  # Docker

# Chạy test
k6 run script.js
k6 run --vus 50 --duration 5m script.js

2. k6 Scripting Cơ bản

// tests/load/basic.js
import http from 'k6/http';
import { check, group, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';

// Custom metrics
const errorRate = new Rate('errors');
const productLatency = new Trend('product_latency');

export const options = {
  stages: [
    { duration: '2m', target: 50 },   // Ramp up
    { duration: '5m', target: 50 },   // Steady state
    { duration: '2m', target: 100 },  // Scale up
    { duration: '5m', target: 100 },  // Steady state
    { duration: '2m', target: 0 },    // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<300', 'p(99)<1000'],
    http_req_failed: ['rate<0.01'],
    errors: ['rate<0.05'],
    product_latency: ['p(95)<200'],
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000';

export default function () {
  group('Browse Products', () => {
    const listRes = http.get(`${BASE_URL}/api/products?page=1&limit=20`);
    check(listRes, {
      'list status 200': (r) => r.status === 200,
      'has products': (r) => JSON.parse(r.body).data.length > 0,
    });
    errorRate.add(listRes.status !== 200);

    // Get product detail
    const products = JSON.parse(listRes.body).data;
    if (products.length > 0) {
      const id = products[Math.floor(Math.random() * products.length)].id;
      const detailRes = http.get(`${BASE_URL}/api/products/${id}`);
      productLatency.add(detailRes.timings.duration);
      check(detailRes, {
        'detail status 200': (r) => r.status === 200,
      });
    }
  });

  sleep(Math.random() * 3 + 1); // Think time: 1-4s
}

3. Scenarios — Mô hình tải nâng cao

export const options = {
  scenarios: {
    // Scenario 1: Shared iterations (fixed total)
    smoke: {
      executor: 'shared-iterations',
      vus: 5,
      iterations: 100,
      maxDuration: '2m',
    },

    // Scenario 2: Constant arrival rate (fixed RPS)
    constant_load: {
      executor: 'constant-arrival-rate',
      rate: 100,           // 100 RPS
      timeUnit: '1s',
      duration: '10m',
      preAllocatedVUs: 50,
      maxVUs: 200,
      startTime: '2m',    // Bắt đầu sau 2 phút
    },

    // Scenario 3: Ramping arrival rate (thay đổi RPS)
    ramping_load: {
      executor: 'ramping-arrival-rate',
      startRate: 10,
      timeUnit: '1s',
      stages: [
        { duration: '5m', target: 200 },  // Ramp to 200 RPS
        { duration: '10m', target: 200 }, // Sustain
        { duration: '5m', target: 500 },  // Scale up
        { duration: '10m', target: 500 }, // Sustain peak
        { duration: '5m', target: 0 },    // Ramp down
      ],
      preAllocatedVUs: 100,
      maxVUs: 500,
      startTime: '12m',
    },

    // Scenario 4: Spike test
    spike: {
      executor: 'ramping-vus',
      stages: [
        { duration: '10s', target: 1000 },  // Spike!
        { duration: '1m', target: 1000 },    // Sustain
        { duration: '10s', target: 0 },      // Drop
      ],
      startTime: '35m',
    },
  },
};

4. Data-driven Testing

import { SharedArray } from 'k6/data';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';

// Load CSV data (shared across VUs — memory efficient)
const users = new SharedArray('users', function () {
  return papaparse.parse(open('./data/users.csv'), { header: true }).data;
});

const products = new SharedArray('products', function () {
  return JSON.parse(open('./data/products.json'));
});

export default function () {
  // Random user credentials
  const user = users[Math.floor(Math.random() * users.length)];
  
  // Login
  const loginRes = http.post(`${BASE_URL}/api/auth/login`, JSON.stringify({
    email: user.email,
    password: user.password,
  }), { headers: { 'Content-Type': 'application/json' } });

  const token = JSON.parse(loginRes.body).accessToken;
  const authHeaders = {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
  };

  // Browse with auth
  const product = products[Math.floor(Math.random() * products.length)];
  http.get(`${BASE_URL}/api/products/${product.id}`, authHeaders);

  sleep(2);
}

5. Output Integrations

# Output to Prometheus (remote write)
k6 run --out experimental-prometheus-rw script.js

# Output to InfluxDB
k6 run --out influxdb=http://localhost:8086/k6 script.js

# Output to JSON (for CI analysis)
k6 run --out json=results.json script.js

# Output to Grafana Cloud k6
K6_CLOUD_TOKEN=xxx k6 cloud script.js

# Multiple outputs
k6 run --out json=results.json --out experimental-prometheus-rw script.js

6. k6 Browser Module

// Hybrid: protocol + browser testing
import { browser } from 'k6/browser';
import { check } from 'k6';

export const options = {
  scenarios: {
    browser_test: {
      executor: 'constant-vus',
      vus: 5,
      duration: '5m',
      options: { browser: { type: 'chromium' } },
    },
  },
  thresholds: {
    browser_web_vital_lcp: ['p(95)<2500'],
    browser_web_vital_fid: ['p(95)<100'],
    browser_web_vital_cls: ['p(95)<0.1'],
  },
};

export default async function () {
  const page = await browser.newPage();
  try {
    await page.goto('https://example.com');
    
    // Measure Core Web Vitals
    await page.waitForSelector('.product-list');
    
    check(page, {
      'page loaded': (p) => p.locator('h1').textContent() !== '',
    });

    // User interaction
    await page.locator('.search-input').fill('laptop');
    await page.locator('.search-btn').click();
    await page.waitForSelector('.search-results');
  } finally {
    await page.close();
  }
}

7. k6 Operator cho Kubernetes

# k6-test.yaml - Distributed load test trên K8s
apiVersion: k6.io/v1alpha1
kind: TestRun
metadata:
  name: load-test-products
spec:
  parallelism: 10          # 10 pods chạy song song
  script:
    configMap:
      name: k6-test-scripts
      file: load-test.js
  arguments: --out experimental-prometheus-rw
  runner:
    resources:
      limits:
        cpu: "500m"
        memory: "256Mi"
# Deploy k6 operator
helm install k6-operator grafana/k6-operator

# Run distributed test
kubectl apply -f k6-test.yaml
kubectl get testrun -w  # Watch progress

8. Tổng kết

  • k6: JavaScript-based, Go runtime, developer-friendly
  • Scenarios: shared-iterations, constant-arrival-rate, ramping-arrival-rate
  • Thresholds: Auto pass/fail dựa trên SLO targets
  • Browser module: Front-end performance + Core Web Vitals
  • k6 Operator: Distributed testing trên Kubernetes
  • Integrations: Prometheus, Grafana, InfluxDB, Cloud

Bài tiếp theo sẽ so sánh Gatling và Artillery.