K6 — это современный, дружественный к разработчикам инструмент нагрузочного тестирования, разработанный для тестирования производительности и надёжности API, микросервисов и веб-сайтов. Построенный на Go и скриптуемый на JavaScript, K6 приносит performance тестирование в DevOps workflow с бесшовной интеграцией CI/CD и cloud-native возможностями.

Почему K6 для Современного Load Testing?

K6 решает ограничения традиционных инструментов performance тестирования, фокусируясь на опыте разработчика и современной инфраструктуре:

  • JavaScript API: Знакомый скриптинг для frontend и backend разработчиков
  • CLI-first дизайн: Идеально для автоматизации и CI/CD пайплайнов
  • Производительность: Эффективное использование ресурсов, может генерировать высокую нагрузку с одной машины
  • Cloud & Local: Запускать локально во время разработки, масштабировать в облако для production-level тестирования
  • Современные протоколы: Поддержка HTTP/1.1, HTTP/2, WebSockets, gRPC
  • Observability: Метрики в реальном времени, интеграция Prometheus, дашборды Grafana

K6 vs Традиционные Инструменты Load Testing

ФункцияK6JMeterGatlingLocust
Язык СкриптингаJavaScriptGUI/XMLScalaPython
Эффективность РесурсовОтличнаяСредняяХорошаяСредняя
Интеграция CI/CDНативнаяПлагиныХорошаяХорошая
Кривая ОбученияНизкаяСредняяВысокаяНизкая
Поддержка CloudНативная (K6 Cloud)ПлагиныGatling EnterpriseНет нативной
Поддержка ПротоколовHTTP/2, WS, gRPCОбширнаяHTTP/2, WSHTTP
Результаты в Реальном ВремениОтличныеОграниченныеХорошиеХорошие

Начало Работы с K6

Установка

# macOS (Homebrew)
brew install k6

# Windows (Chocolatey)
choco install k6

# Linux (Debian/Ubuntu)
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \
  --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | \
  sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6

# Docker
docker pull grafana/k6

Ваш Первый K6 Тест

// simple-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 10, // Virtual users (виртуальные пользователи)
  duration: '30s', // Длительность теста
};

export default function () {
  const response = http.get('https://test-api.k6.io');

  check(response, {
    'статус 200': (r) => r.status === 200,
    'время ответа < 500ms': (r) => r.timings.duration < 500,
  });

  sleep(1);
}

Запустить тест:

k6 run simple-test.js

Конфигурация Тестов и Профили Нагрузки

Виртуальные Пользователи и Длительность

export const options = {
  // Простая конфигурация
  vus: 50,
  duration: '5m',

  // Или использовать stages для ramping
  stages: [
    { duration: '2m', target: 100 }, // Подъём до 100 пользователей
    { duration: '5m', target: 100 }, // Удержание 100 пользователей
    { duration: '2m', target: 0 },   // Спуск до 0 пользователей
  ],
};

Сценарии Load Testing

export const options = {
  scenarios: {
    // Постоянная нагрузка
    constant_load: {
      executor: 'constant-vus',
      vus: 50,
      duration: '5m',
    },

    // Ramping VUs
    ramping_load: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '1m', target: 50 },
        { duration: '3m', target: 50 },
        { duration: '1m', target: 0 },
      ],
    },

    // Постоянная частота запросов
    constant_rps: {
      executor: 'constant-arrival-rate',
      rate: 1000, // 1000 запросов в секунду
      timeUnit: '1s',
      duration: '5m',
      preAllocatedVUs: 50,
      maxVUs: 200,
    },

    // Spike тест
    spike_test: {
      executor: 'ramping-arrival-rate',
      startRate: 100,
      timeUnit: '1s',
      preAllocatedVUs: 50,
      maxVUs: 500,
      stages: [
        { duration: '30s', target: 100 },  // Нормальная нагрузка
        { duration: '1m', target: 1000 },  // Spike
        { duration: '30s', target: 100 },  // Обратно к нормальной
      ],
    },
  },
};

Thresholds для Критериев Pass/Fail

export const options = {
  thresholds: {
    // HTTP ошибки должны быть менее 1%
    http_req_failed: ['rate<0.01'],

    // 95% запросов должны быть ниже 500ms
    http_req_duration: ['p(95)<500'],

    // 99-й перцентиль должен быть ниже 1s
    'http_req_duration{type:api}': ['p(99)<1000'],

    // Thresholds кастомных метрик
    checks: ['rate>0.99'], // 99% проверок должны пройти
  },
};

Продвинутое HTTP Тестирование

HTTP Методы и Request Bodies

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

export default function () {
  // GET запрос
  const getResponse = http.get('https://api.example.com/users');

  // POST с JSON
  const payload = JSON.stringify({
    name: 'John Doe',
    email: 'john@example.com',
  });

  const params = {
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer token123',
    },
  };

  const postResponse = http.post(
    'https://api.example.com/users',
    payload,
    params
  );

  check(postResponse, {
    'создан успешно': (r) => r.status === 201,
    'есть user id': (r) => r.json('id') !== undefined,
  });

  // PUT запрос
  const updatePayload = JSON.stringify({ name: 'Jane Doe' });
  http.put(`https://api.example.com/users/${postResponse.json('id')}`, updatePayload, params);

  // DELETE запрос
  http.del(`https://api.example.com/users/${postResponse.json('id')}`, null, params);
}

Batch Запросы

import http from 'k6/http';

export default function () {
  // Выполнить запросы параллельно
  const responses = http.batch([
    ['GET', 'https://api.example.com/users'],
    ['GET', 'https://api.example.com/products'],
    ['GET', 'https://api.example.com/orders'],
  ]);

  // Проверить все ответы
  responses.forEach((response, index) => {
    check(response, {
      [`запрос ${index} статус 200`]: (r) => r.status === 200,
    });
  });
}

Аутентификация и Управление Сессиями

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

export function setup() {
  // Логин один раз за прогон теста
  const loginResponse = http.post('https://api.example.com/auth/login', {
    username: 'testuser',
    password: 'testpass',
  });

  return { token: loginResponse.json('token') };
}

export default function (data) {
  // Использовать токен из setup
  const params = {
    headers: {
      'Authorization': `Bearer ${data.token}`,
    },
  };

  const response = http.get('https://api.example.com/protected', params);

  check(response, {
    'аутентифицирован': (r) => r.status === 200,
  });
}

Параметризация Данных

Использование CSV Данных

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

const csvData = new SharedArray('users', function () {
  return papaparse.parse(open('./users.csv'), { header: true }).data;
});

export default function () {
  // Получить случайного пользователя
  const user = csvData[Math.floor(Math.random() * csvData.length)];

  const response = http.post('https://api.example.com/login', {
    username: user.username,
    password: user.password,
  });

  check(response, {
    'логин успешен': (r) => r.status === 200,
  });
}

Использование JSON Данных

import { SharedArray } from 'k6/data';

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

export default function () {
  const product = testData[__VU % testData.length]; // Round-robin выбор

  http.post('https://api.example.com/cart/add', JSON.stringify({
    productId: product.id,
    quantity: 1,
  }), {
    headers: { 'Content-Type': 'application/json' },
  });
}

Кастомные Метрики и Tags

Кастомные Метрики

import http from 'k6/http';
import { Trend, Counter, Rate, Gauge } from 'k6/metrics';

const myTrend = new Trend('waiting_time');
const myCounter = new Counter('my_counter');
const myRate = new Rate('my_rate');
const myGauge = new Gauge('my_gauge');

export default function () {
  const response = http.get('https://api.example.com');

  // Добавить кастомные метрики
  myTrend.add(response.timings.waiting);
  myCounter.add(1);
  myRate.add(response.status === 200);
  myGauge.add(response.timings.duration);
}

Tags для Организации Метрик

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

export default function () {
  // Тегировать отдельные запросы
  const response = http.get('https://api.example.com/users', {
    tags: {
      type: 'api',
      endpoint: 'users',
    },
  });

  check(response, {
    'статус 200': (r) => r.status === 200,
  }, { type: 'api' }); // Тегировать проверки
}

export const options = {
  thresholds: {
    // Threshold для конкретных тегов
    'http_req_duration{type:api}': ['p(95)<500'],
    'http_req_duration{endpoint:users}': ['p(99)<1000'],
  },
};

Реальные Сценарии Тестирования

Пользовательский Поток E-commerce

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

export const options = {
  stages: [
    { duration: '2m', target: 50 },
    { duration: '5m', target: 50 },
    { duration: '2m', target: 0 },
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],
    'http_req_duration{page:home}': ['p(95)<300'],
    'http_req_duration{page:product}': ['p(95)<400'],
  },
};

export default function () {
  let response;

  group('Домашняя Страница', function () {
    response = http.get('https://ecommerce.example.com', {
      tags: { page: 'home' },
    });
    check(response, { 'homepage загружена': (r) => r.status === 200 });
    sleep(1);
  });

  group('Поиск Продуктов', function () {
    response = http.get('https://ecommerce.example.com/search?q=laptop', {
      tags: { page: 'search' },
    });
    check(response, { 'результаты поиска': (r) => r.status === 200 });
    sleep(2);
  });

  group('Просмотр Продукта', function () {
    response = http.get('https://ecommerce.example.com/products/123', {
      tags: { page: 'product' },
    });
    check(response, { 'детали продукта': (r) => r.status === 200 });
    sleep(3);
  });

  group('Добавить в Корзину', function () {
    response = http.post(
      'https://ecommerce.example.com/cart',
      JSON.stringify({ productId: 123, quantity: 1 }),
      {
        headers: { 'Content-Type': 'application/json' },
        tags: { page: 'cart' },
      }
    );
    check(response, { 'добавлено в корзину': (r) => r.status === 200 });
    sleep(1);
  });

  group('Checkout', function () {
    response = http.post(
      'https://ecommerce.example.com/checkout',
      JSON.stringify({
        payment: 'card',
        address: '123 Main St',
      }),
      {
        headers: { 'Content-Type': 'application/json' },
        tags: { page: 'checkout' },
      }
    );
    check(response, { 'checkout успешен': (r) => r.status === 200 });
  });
}

Нагрузочный Тест API с Обработкой Ошибок

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

const errorRate = new Rate('errors');

export const options = {
  scenarios: {
    stress_test: {
      executor: 'ramping-arrival-rate',
      startRate: 10,
      timeUnit: '1s',
      preAllocatedVUs: 50,
      maxVUs: 500,
      stages: [
        { duration: '1m', target: 10 },
        { duration: '2m', target: 50 },
        { duration: '2m', target: 100 },
        { duration: '1m', target: 10 },
      ],
    },
  },
  thresholds: {
    errors: ['rate<0.1'], // Частота ошибок должна быть менее 10%
    http_req_duration: ['p(95)<2000', 'p(99)<5000'],
  },
};

export default function () {
  const response = http.get('https://api.example.com/data');

  const success = check(response, {
    'статус 200': (r) => r.status === 200,
    'время ответа OK': (r) => r.timings.duration < 1000,
    'нет ошибок в body': (r) => !r.body.includes('error'),
  });

  errorRate.add(!success);

  if (response.status !== 200) {
    console.error(`Запрос не удался: ${response.status} ${response.body}`);
  }

  sleep(1);
}

Интеграция CI/CD

GitHub Actions

name: Load Test

on:
  push:
    branches: [main]
  schedule:
    - cron: '0 2 * * *' # Ежедневно в 2 AM

jobs:
  k6_load_test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Run K6 Load Test
        uses: grafana/k6-action@v0.3.1
        with:
          filename: tests/load-test.js
          cloud: true
          token: ${{ secrets.K6_CLOUD_TOKEN }}

      - name: Upload Results
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: k6-results
          path: results/

Jenkins Pipeline

pipeline {
    agent any

    stages {
        stage('Load Test') {
            steps {
                sh '''
                    k6 run --out json=results.json \
                           --out influxdb=http://influxdb:8086/k6 \
                           tests/load-test.js
                '''
            }
        }

        stage('Check Thresholds') {
            steps {
                script {
                    def exitCode = sh(
                        script: 'k6 run --quiet tests/load-test.js',
                        returnStatus: true
                    )
                    if (exitCode != 0) {
                        error("Thresholds нагрузочного теста провалились")
                    }
                }
            }
        }
    }

    post {
        always {
            archiveArtifacts artifacts: 'results.json', fingerprint: true
        }
    }
}

GitLab CI

k6-test:
  stage: test
  image: grafana/k6:latest
  script:
    - k6 run --summary-export=summary.json tests/load-test.js
  artifacts:
    when: always
    paths:
      - summary.json
    reports:
      junit: summary.json

Observability и Анализ Результатов

Опции Output

# JSON вывод
k6 run --out json=results.json script.js

# CSV вывод
k6 run --out csv=results.csv script.js

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

# Prometheus Remote Write
k6 run --out experimental-prometheus-rw script.js

# K6 Cloud
k6 run --out cloud script.js

# Множественные выводы
k6 run --out json=results.json --out influxdb=http://localhost:8086/k6 script.js

Чтение Результатов

# Статистика резюме
k6 run script.js

# Детальный вывод
k6 run --verbose script.js

# Тихий режим (только ошибки)
k6 run --quiet script.js

# Сохранить резюме в файл
k6 run --summary-export=summary.json script.js

Кастомное Резюме

import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js";
import { textSummary } from "https://jslib.k6.io/k6-summary/0.0.1/index.js";

export function handleSummary(data) {
  return {
    "summary.html": htmlReport(data),
    "summary.json": JSON.stringify(data),
    stdout: textSummary(data, { indent: " ", enableColors: true }),
  };
}

Лучшие Практики

Эффективность Ресурсов

// ❌ Плохо: Создаёт новый объект каждую итерацию
export default function () {
  const headers = { 'Content-Type': 'application/json' };
  http.get('https://api.example.com', { headers });
}

// ✓ Хорошо: Переиспользовать объекты
const params = {
  headers: { 'Content-Type': 'application/json' },
};

export default function () {
  http.get('https://api.example.com', params);
}

Think Time и Pacing

import { sleep } from 'k6';

export default function () {
  // Симулировать время размышления пользователя
  http.get('https://api.example.com/page1');
  sleep(Math.random() * 3 + 2); // 2-5 секунд

  http.get('https://api.example.com/page2');
  sleep(Math.random() * 2 + 1); // 1-3 секунды
}

Модульные Тестовые Скрипты

// modules/auth.js
export function login(username, password) {
  const response = http.post('https://api.example.com/login', {
    username,
    password,
  });
  return response.json('token');
}

// modules/users.js
export function getUser(token, userId) {
  return http.get(`https://api.example.com/users/${userId}`, {
    headers: { Authorization: `Bearer ${token}` },
  });
}

// main-test.js
import { login } from './modules/auth.js';
import { getUser } from './modules/users.js';

export default function () {
  const token = login('user', 'pass');
  getUser(token, 123);
}

Заключение

K6 представляет собой смену парадигмы в performance тестировании, принося дружественный разработчикам инструментарий и современные практики в load testing. Его JavaScript API, эффективное использование ресурсов и бесшовная интеграция с CI/CD пайплайнами делают его идеальным для DevOps команд, практикующих непрерывное тестирование.

Ключевые преимущества:

  • Дружественный разработчикам JavaScript API
  • Эффективное использование ресурсов для генерации высокой нагрузки
  • Нативная интеграция CI/CD
  • Поддержка современных протоколов (HTTP/2, WebSockets, gRPC)
  • Гибкие режимы выполнения (локальный, облачный, гибридный)
  • Богатая экосистема и опции observability

Независимо от того, тестируете ли вы микросервисы, API или полные веб-приложения, K6 предоставляет инструменты и гибкость для обеспечения надёжной работы ваших систем под нагрузкой. Интегрируя performance тестирование рано в цикл разработки, команды могут обнаружить проблемы до того, как они достигнут production, и поддерживать высококачественные, производительные приложения.