---
# System prepended metadata

title: Complete Grafana k6 Load Testing Tutorial
tags: ["#Grafana\_", "\_#Prometheus", '#K6', '#CloudNative']

---

# Complete Grafana k6 Load Testing Tutorial


> **Project Repository**: [https://github.com/Yang92047111/Grafana_k6_Load_Testing_Quick_Start](https://github.com/Yang92047111/Grafana_k6_Load_Testing_Quick_Start)

This tutorial provides a comprehensive guide to setting up and using Grafana, Prometheus, and k6 for monitoring and load testing cloud-native applications.

## Table of Contents
1. [Introduction to k6](#Introduction-to-k6)
2. [How k6 Works](#How-k6-Works)
3. [Project Structure](#Project-Structure)
4. [Quick Start Guide](#Quick-Start-Guide)
5. [Setting Up the Environment](#Setting-Up-the-Environment)
6. [Creating a Go Backend Service](#Creating-a-Go-Backend-Service)
7. [Dockerizing the Application](#Dockerizing-the-Application)
8. [Writing k6 Load Tests](#Writing-k6-Load-Tests)
9. [Running k6 Tests](#Running-k6-Tests)
10. [Monitoring and Analysis](#Monitoring-and-Analysis)
11. [CI/CD Pipeline](#CI/CD-Pipeline)
12. [Troubleshooting](#Troubleshooting)

## Introduction to k6

k6 is a developer-centric, free and open-source load testing tool built for making performance testing a productive and enjoyable experience. It's designed to test the reliability and performance of systems under load.

### Key Features:
- **JavaScript-based**: Write tests in modern JavaScript (ES6+)
- **Developer-friendly**: CLI tool with great developer experience
- **Cloud and on-premise**: Run locally or in the cloud
- **Rich metrics**: Built-in metrics and custom metrics support
- **Integrations**: Works with CI/CD pipelines and monitoring systems

## How k6 Works

k6 follows a straightforward architecture for load testing:

```
┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   k6 CLI Tool   │    │  Test Scripts    │    │   Target API    │
│                 │    │  (JavaScript)    │    │   (Go Service)  │
│ ┌─────────────┐ │    │                  │    │                 │
│ │Load Testing │ │───▶│ ┌──────────────┐ │───▶│ ┌─────────────┐ │
│ │   Engine    │ │    │ │HTTP Requests │ │    │ │Endpoints    │ │
│ └─────────────┘ │    │ │Checks & Thresholds│ │ │Handlers     │ │
│                 │    │ │Virtual Users │ │    │ │             │ │
│ ┌─────────────┐ │    │ └──────────────┘ │    │ └─────────────┘ │
│ │  Metrics    │ │    │                  │    │                 │
│ │ Collection  │ │    └──────────────────┘    └─────────────────┘
│ └─────────────┘ │              │                       │
└─────────────────┘              │                       │
         │                       │                       │
         ▼                       ▼                       ▼
┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│    Results      │    │   Monitoring     │    │    Logs &       │
│   & Reports     │    │   (Grafana)      │    │   Responses     │
└─────────────────┘    └──────────────────┘    └─────────────────┘
```

### k6 Test Lifecycle:
1. **Init**: Code runs once per VU (Virtual User) to set up test data
2. **Setup**: Runs once before the test starts
3. **VU Code**: Main test function that runs for each iteration
4. **Teardown**: Runs once after the test completes

## Project Structure

This tutorial implements a complete cloud-native load testing solution with the following structure:

```
grafana-k6-quick-start/
├── .dockerignore              # Docker build optimization
├── .github/workflows/         # CI/CD pipeline (GitHub Actions)
├── backend/                   # Go REST API service
│   ├── handlers/             # HTTP request handlers + tests
│   ├── middleware/           # HTTP middleware + tests
│   ├── models/              # Data models and structures
│   ├── main.go              # Application entry point
│   ├── Dockerfile           # Container configuration
│   └── go.mod/go.sum        # Go module dependencies
├── monitoring/                # Observability stack
│   ├── grafana/             # Dashboards and provisioning
│   └── prometheus/          # Prometheus configuration
├── tests/                     # k6 load testing scripts
│   ├── smoke-test.js        # Basic functionality validation
│   ├── load-test.js         # Standard load testing
│   ├── stress-test.js       # High load stress testing
│   ├── spike-test.js        # Sudden load spike testing
│   └── comprehensive-test.js # Full CRUD testing
├── docker-compose.yml         # Local development stack
├── Makefile                   # Build and deployment commands
└── README.md                 # Project documentation
```

## Quick Start Guide

### Option A: Local Development (API only)
```bash
# Clone the repository (if needed)
git clone <repository-url>
cd grafana-k6-quick-start

# Quick setup - downloads dependencies
make dev-setup

# Start the API server
make run

# In another terminal, test the API
curl http://localhost:8080/health

# Run a quick smoke test
make k6-smoke
```

### Option B: Full Setup with Monitoring
```bash
# Complete development environment with Docker
make dev-setup-full

# Or step by step:
make deps tidy          # Setup Go dependencies
make docker-build       # Build Docker image
make compose-up         # Start monitoring stack

# Access services:
# API: http://localhost:8080
# Grafana: http://localhost:3000 (admin/admin)
# Prometheus: http://localhost:9090
```

### Available Make Commands
```bash
make help              # Show all available commands
make test              # Run unit tests
make build             # Build Go application
make docker-build      # Build Docker image
make k6-smoke          # Run smoke test
make k6-load           # Run load test
make monitoring-up     # Start monitoring stack
make dev-teardown      # Clean up environment
```

## Setting Up the Environment

### Prerequisites:
- Docker and Docker Compose
- Go 1.21+
- k6

### Installation Commands:

```bash
# Install k6 (macOS)
brew install k6

# Install k6 (Linux)
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

# Verify installation
k6 version
```

## Creating a Go Backend Service

Let's create a simple REST API service in Go that we'll use as our testing target.

### Project Structure:
```
backend/
├── main.go
├── handlers/
│   └── handlers.go
├── models/
│   └── models.go
├── go.mod
└── Dockerfile
```

### main.go
```go
package main

import (
    "log"
    "net/http"
    "os"
    "time"

    "github.com/gorilla/mux"
    "your-project/handlers"
)

func main() {
    r := mux.NewRouter()
    
    // Routes
    r.HandleFunc("/health", handlers.HealthCheck).Methods("GET")
    r.HandleFunc("/api/users", handlers.GetUsers).Methods("GET")
    r.HandleFunc("/api/users", handlers.CreateUser).Methods("POST")
    r.HandleFunc("/api/users/{id}", handlers.GetUser).Methods("GET")
    r.HandleFunc("/api/users/{id}", handlers.UpdateUser).Methods("PUT")
    r.HandleFunc("/api/users/{id}", handlers.DeleteUser).Methods("DELETE")
    
    // Middleware
    r.Use(handlers.LoggingMiddleware)
    r.Use(handlers.CORSMiddleware)
    
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }
    
    srv := &http.Server{
        Handler:      r,
        Addr:         ":" + port,
        WriteTimeout: 15 * time.Second,
        ReadTimeout:  15 * time.Second,
    }
    
    log.Printf("Server starting on port %s", port)
    log.Fatal(srv.ListenAndServe())
}
```

### handlers/handlers.go
```go
package handlers

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strconv"
    "time"
    
    "github.com/gorilla/mux"
    "your-project/models"
)

var users = make(map[int]models.User)
var userID = 1

func HealthCheck(w http.ResponseWriter, r *http.Request) {
    response := map[string]interface{}{
        "status":    "healthy",
        "timestamp": time.Now().Unix(),
        "service":   "user-api",
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(response)
}

func GetUsers(w http.ResponseWriter, r *http.Request) {
    // Simulate some processing time
    time.Sleep(50 * time.Millisecond)
    
    userList := make([]models.User, 0, len(users))
    for _, user := range users {
        userList = append(userList, user)
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(userList)
}

func CreateUser(w http.ResponseWriter, r *http.Request) {
    var user models.User
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }
    
    // Simulate processing time
    time.Sleep(100 * time.Millisecond)
    
    user.ID = userID
    user.CreatedAt = time.Now()
    users[userID] = user
    userID++
    
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(user)
}

func GetUser(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id, err := strconv.Atoi(vars["id"])
    if err != nil {
        http.Error(w, "Invalid user ID", http.StatusBadRequest)
        return
    }
    
    user, exists := users[id]
    if !exists {
        http.Error(w, "User not found", http.StatusNotFound)
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}

func UpdateUser(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id, err := strconv.Atoi(vars["id"])
    if err != nil {
        http.Error(w, "Invalid user ID", http.StatusBadRequest)
        return
    }
    
    existingUser, exists := users[id]
    if !exists {
        http.Error(w, "User not found", http.StatusNotFound)
        return
    }
    
    var updatedUser models.User
    if err := json.NewDecoder(r.Body).Decode(&updatedUser); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }
    
    // Simulate processing time
    time.Sleep(75 * time.Millisecond)
    
    updatedUser.ID = id
    updatedUser.CreatedAt = existingUser.CreatedAt
    updatedUser.UpdatedAt = time.Now()
    users[id] = updatedUser
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(updatedUser)
}

func DeleteUser(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id, err := strconv.Atoi(vars["id"])
    if err != nil {
        http.Error(w, "Invalid user ID", http.StatusBadRequest)
        return
    }
    
    if _, exists := users[id]; !exists {
        http.Error(w, "User not found", http.StatusNotFound)
        return
    }
    
    delete(users, id)
    w.WriteHeader(http.StatusNoContent)
}

// Middleware
func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

func CORSMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        
        if r.Method == "OPTIONS" {
            w.WriteHeader(http.StatusOK)
            return
        }
        
        next.ServeHTTP(w, r)
    })
}
```

### models/models.go
```go
package models

import "time"

type User struct {
    ID        int       `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email"`
    Age       int       `json:"age"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at,omitempty"`
}
```

### go.mod
```go
module your-project

go 1.21

require github.com/gorilla/mux v1.8.0
```

## Dockerizing the Application

### Dockerfile
```dockerfile
# Build stage
FROM golang:1.21-alpine AS builder

WORKDIR /app

# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download

# Copy source code
COPY . .

# Build the application
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

# Final stage
FROM alpine:latest

RUN apk --no-cache add ca-certificates
WORKDIR /root/

# Copy the binary from builder stage
COPY --from=builder /app/main .

# Expose port
EXPOSE 8080

# Command to run
CMD ["./main"]
```

### docker-compose.yml
```yaml
version: '3.8'

services:
  user-api:
    build: ./backend
    ports:
      - "8080:8080"
    environment:
      - PORT=8080
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    restart: unless-stopped
```

## Writing k6 Load Tests

### Basic Load Test (load-test.js)
```javascript
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Counter, Rate, Trend } from 'k6/metrics';

// Custom metrics
const errorRate = new Rate('error_rate');
const customTrend = new Trend('custom_duration');
const httpReqFailed = new Counter('http_req_failed');

// Test configuration
export const options = {
  stages: [
    { duration: '30s', target: 10 },   // Ramp up to 10 users
    { duration: '1m', target: 10 },    // Stay at 10 users
    { duration: '30s', target: 50 },   // Ramp up to 50 users
    { duration: '2m', target: 50 },    // Stay at 50 users
    { duration: '30s', target: 0 },    // Ramp down to 0 users
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'], // 95% of requests must be below 500ms
    http_req_failed: ['rate<0.05'],   // Error rate must be below 5%
    error_rate: ['rate<0.1'],         // Custom error rate
  },
};

// Base URL - adjust based on your setup
const BASE_URL = 'http://localhost:8080';
// const BASE_URL = 'http://user-api.local'; // For ingress

// Test data
const users = [
  { name: 'John Doe', email: 'john@example.com', age: 30 },
  { name: 'Jane Smith', email: 'jane@example.com', age: 25 },
  { name: 'Bob Johnson', email: 'bob@example.com', age: 35 },
];

export function setup() {
  // This runs once before all VUs start
  console.log('Starting load test setup...');
  
  // Health check
  const healthResponse = http.get(`${BASE_URL}/health`);
  if (healthResponse.status !== 200) {
    throw new Error('Health check failed');
  }
  
  return { message: 'Setup complete' };
}

export default function(data) {
  // Main test function - runs for each VU iteration
  
  // Health check
  let response = http.get(`${BASE_URL}/health`);
  const healthCheckPassed = check(response, {
    'health check status is 200': (r) => r.status === 200,
    'health check has status field': (r) => JSON.parse(r.body).status === 'healthy',
  });
  
  if (!healthCheckPassed) {
    errorRate.add(1);
    httpReqFailed.add(1);
  }
  
  // Get all users
  response = http.get(`${BASE_URL}/api/users`);
  check(response, {
    'get users status is 200': (r) => r.status === 200,
    'get users response time < 500ms': (r) => r.timings.duration < 500,
  });
  
  // Create a new user
  const userData = users[Math.floor(Math.random() * users.length)];
  userData.email = `${userData.name.replace(' ', '').toLowerCase()}-${Date.now()}@example.com`;
  
  const createUserParams = {
    headers: {
      'Content-Type': 'application/json',
    },
  };
  
  response = http.post(`${BASE_URL}/api/users`, JSON.stringify(userData), createUserParams);
  const createUserPassed = check(response, {
    'create user status is 201': (r) => r.status === 201,
    'create user has id': (r) => JSON.parse(r.body).id > 0,
    'create user response time < 1000ms': (r) => r.timings.duration < 1000,
  });
  
  if (createUserPassed && response.status === 201) {
    const createdUser = JSON.parse(response.body);
    
    // Get the created user
    response = http.get(`${BASE_URL}/api/users/${createdUser.id}`);
    check(response, {
      'get user by id status is 200': (r) => r.status === 200,
      'get user by id returns correct user': (r) => JSON.parse(r.body).id === createdUser.id,
    });
    
    // Update the user
    const updatedUserData = {
      ...userData,
      age: userData.age + 1,
    };
    
    response = http.put(`${BASE_URL}/api/users/${createdUser.id}`, JSON.stringify(updatedUserData), createUserParams);
    check(response, {
      'update user status is 200': (r) => r.status === 200,
      'update user age changed': (r) => JSON.parse(r.body).age === updatedUserData.age,
    });
    
    // Delete the user
    response = http.del(`${BASE_URL}/api/users/${createdUser.id}`);
    check(response, {
      'delete user status is 204': (r) => r.status === 204,
    });
  } else {
    errorRate.add(1);
  }
  
  // Record custom metrics
  customTrend.add(response.timings.duration);
  
  // Sleep between 1-3 seconds
  sleep(Math.random() * 2 + 1);
}

export function teardown(data) {
  // This runs once after all VUs have finished
  console.log('Load test teardown complete');
}
```

### Spike Test (spike-test.js)
```javascript
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '10s', target: 10 },   // Below normal load
    { duration: '1m', target: 10 },    // Normal load
    { duration: '10s', target: 100 },  // Around the breaking point
    { duration: '3m', target: 100 },   // Beyond the breaking point
    { duration: '10s', target: 10 },   // Scale down. Recovery stage.
    { duration: '3m', target: 10 },    // Recovery stage.
    { duration: '10s', target: 0 },    // Scale down to 0 users.
  ],
  thresholds: {
    http_req_duration: ['p(99)<1500'], // 99% of requests must be below 1.5s
    http_req_failed: ['rate<0.1'],     // Error rate must be below 10%
  },
};

const BASE_URL = 'http://localhost:8080';

export default function() {
  const response = http.get(`${BASE_URL}/health`);
  check(response, {
    'spike test - status is 200': (r) => r.status === 200,
    'spike test - response time OK': (r) => r.timings.duration < 2000,
  });
  sleep(1);
}
```

### Stress Test (stress-test.js)
```javascript
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 100 },  // Ramp up to 100 users
    { duration: '5m', target: 100 },  // Stay at 100 users
    { duration: '2m', target: 200 },  // Ramp up to 200 users
    { duration: '5m', target: 200 },  // Stay at 200 users
    { duration: '2m', target: 300 },  // Ramp up to 300 users
    { duration: '5m', target: 300 },  // Stay at 300 users
    { duration: '2m', target: 400 },  // Ramp up to 400 users
    { duration: '5m', target: 400 },  // Stay at 400 users
    { duration: '10m', target: 0 },   // Ramp down to 0 users
  ],
  thresholds: {
    http_req_duration: ['p(99)<2000'], // 99% of requests must be below 2s
    http_req_failed: ['rate<0.1'],     // Error rate must be below 10%
  },
};

const BASE_URL = 'http://localhost:8080';

export default function() {
  const responses = http.batch([
    ['GET', `${BASE_URL}/health`],
    ['GET', `${BASE_URL}/api/users`],
  ]);
  
  check(responses[0], {
    'stress test - health check status is 200': (r) => r.status === 200,
  });
  
  check(responses[1], {
    'stress test - users endpoint status is 200': (r) => r.status === 200,
  });
  
  sleep(1);
}
```

### Smoke Test (smoke-test.js)
```javascript
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 1,        // 1 user looping for 1 minute
  duration: '1m',
  thresholds: {
    http_req_duration: ['p(99)<1500'], // 99% of requests must be below 1.5s
    http_req_failed: ['rate<0.01'],    // Error rate must be below 1%
  },
};

const BASE_URL = 'http://localhost:8080';

export default function() {
  const response = http.get(`${BASE_URL}/health`);
  check(response, {
    'smoke test - status is 200': (r) => r.status === 200,
    'smoke test - service is healthy': (r) => JSON.parse(r.body).status === 'healthy',
  });
  sleep(1);
}
```

## Running k6 Tests

### Test Execution Commands:

```bash
# Run smoke test (quick validation)
k6 run smoke-test.js

# Run basic load test
k6 run load-test.js

# Run stress test
k6 run stress-test.js

# Run spike test
k6 run spike-test.js

# Run test with custom VUs and duration
k6 run --vus 50 --duration 30s load-test.js

# Run test and output results to file
k6 run --out json=results.json load-test.js

# Run test with custom thresholds
k6 run --threshold http_req_duration=p(95)<200 load-test.js
```

### k6 in Docker:

```bash
# Run k6 tests in Docker
docker run --rm -i grafana/k6:latest run - <load-test.js

# Run with volume mount for test files
docker run --rm -v "$PWD:/tests" grafana/k6:latest run /tests/load-test.js

# Run with network access to local services
docker run --rm --network=host -v "$PWD:/tests" grafana/k6:latest run /tests/load-test.js
```

### k6 in Kubernetes:

#### k6-job.yaml
```yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: k6-load-test
spec:
  template:
    spec:
      containers:
      - name: k6
        image: grafana/k6:latest
        command: ["k6", "run", "--vus", "50", "--duration", "5m", "/scripts/load-test.js"]
        volumeMounts:
        - name: k6-scripts
          mountPath: /scripts
        env:
        - name: K6_OUT
          value: "json=/tmp/results.json"
      volumes:
      - name: k6-scripts
        configMap:
          name: k6-scripts
      restartPolicy: Never
  backoffLimit: 1
```

#### Create ConfigMap for test scripts:
```bash
kubectl create configmap k6-scripts --from-file=load-test.js

kubectl apply -f k6-job.yaml

# Check job status
kubectl get jobs
kubectl logs job/k6-load-test
```

## Monitoring and Analysis

### Setting up Grafana and Prometheus with Docker Compose:

The monitoring stack is already configured in the `docker-compose.yml` file with Grafana and Prometheus services.

### Start the monitoring stack:
```bash
# Start all services including monitoring
docker compose up -d

# Access the services:
# Grafana: http://localhost:3000 (admin/admin)  
# Prometheus: http://localhost:9090
```

### Run k6 with Prometheus output:
```bash
# Run k6 with Prometheus remote write output (when using docker-compose)
k6 run --out experimental-prometheus-rw=http://localhost:9090/api/v1/write load-test.js

# Alternative: Run k6 in Docker with Prometheus output
docker compose exec k6 k6 run --out experimental-prometheus-rw=http://prometheus:9090/api/v1/write /tests/load-test.js
```

### k6 Grafana Dashboard Configuration:

1. **Access Grafana**: http://localhost:3000 (admin/admin)

2. **Add Prometheus Data Source**:
   - URL: http://localhost:9090
   - Access: Server (default)
   - HTTP Method: GET

3. **Import k6 Dashboard**:
   - Go to + → Import
   - Use dashboard ID: 19665 (k6 Prometheus dashboard)
   - Or create custom dashboard with these queries:

#### Key Grafana Queries (PromQL):
```promql
# HTTP Request Duration (95th percentile)
histogram_quantile(0.95, rate(k6_http_req_duration_bucket[5m]))

# HTTP Request Rate
rate(k6_http_reqs_total[5m])

# Error Rate (percentage)
rate(k6_http_req_failed_total[5m]) / rate(k6_http_reqs_total[5m]) * 100

# Virtual Users
k6_vus

# Data Received Rate
rate(k6_data_received_total[5m])

# Data Sent Rate  
rate(k6_data_sent_total[5m])

# HTTP Request Duration Average
rate(k6_http_req_duration_sum[5m]) / rate(k6_http_req_duration_count[5m])

# Checks Pass Rate
rate(k6_checks_passed_total[5m]) / (rate(k6_checks_passed_total[5m]) + rate(k6_checks_failed_total[5m])) * 100
```

## Advanced k6 Testing Patterns

### Data-Driven Testing:

#### users-data.json
```json
[
  {"name": "Alice Johnson", "email": "alice@example.com", "age": 28},
  {"name": "Bob Smith", "email": "bob@example.com", "age": 34},
  {"name": "Charlie Brown", "email": "charlie@example.com", "age": 22},
  {"name": "Diana Prince", "email": "diana@example.com", "age": 31},
  {"name": "Eve Davis", "email": "eve@example.com", "age": 26}
]
```

#### data-driven-test.js
```javascript
import http from 'k6/http';
import { check } from 'k6';

const userData = JSON.parse(open('./users-data.json'));

export const options = {
  vus: 10,
  duration: '2m',
};

const BASE_URL = 'http://localhost:8080';

export default function() {
  // Pick a random user from the dataset
  const user = userData[Math.floor(Math.random() * userData.length)];
  
  // Modify email to make it unique
  user.email = `${user.name.replace(' ', '').toLowerCase()}-${__ITER}@example.com`;
  
  const response = http.post(`${BASE_URL}/api/users`, JSON.stringify(user), {
    headers: { 'Content-Type': 'application/json' },
  });
  
  check(response, {
    'user created successfully': (r) => r.status === 201,
    'user has correct name': (r) => JSON.parse(r.body).name === user.name,
  });
}
```

### Session-Based Testing:

#### session-test.js
```javascript
import http from 'k6/http';
import { check, group, sleep } from 'k6';

export const options = {
  vus: 20,
  duration: '5m',
};

const BASE_URL = 'http://localhost:8080';

export default function() {
  // Simulate user session
  group('User Registration Flow', () => {
    const userData = {
      name: `User ${__VU}-${__ITER}`,
      email: `user-${__VU}-${__ITER}@example.com`,
      age: Math.floor(Math.random() * 50) + 18,
    };
    
    // Create user
    let response = http.post(`${BASE_URL}/api/users`, JSON.stringify(userData), {
      headers: { 'Content-Type': 'application/json' },
    });
    
    const userId = check(response, {
      'user created': (r) => r.status === 201,
    }) ? JSON.parse(response.body).id : null;
    
    if (userId) {
      sleep(1);
      
      // Get user profile
      group('Profile Operations', () => {
        response = http.get(`${BASE_URL}/api/users/${userId}`);
        check(response, {
          'profile retrieved': (r) => r.status === 200,
        });
        
        sleep(2);
        
        // Update user profile
        userData.age += 1;
        response = http.put(`${BASE_URL}/api/users/${userId}`, JSON.stringify(userData), {
          headers: { 'Content-Type': 'application/json' },
        });
        
        check(response, {
          'profile updated': (r) => r.status === 200,
        });
      });
      
      sleep(1);
      
      // Clean up - delete user
      response = http.del(`${BASE_URL}/api/users/${userId}`);
      check(response, {
        'user deleted': (r) => r.status === 204,
      });
    }
  });
  
  sleep(Math.random() * 5 + 1);
}
```

### Performance Testing Best Practices:

#### comprehensive-test.js
```javascript
import http from 'k6/http';
import { check, group, sleep, fail } from 'k6';
import { Rate, Trend, Counter } from 'k6/metrics';

// Custom metrics
const errorRate = new Rate('errors');
const responseTimeTrend = new Trend('response_time');
const customCounter = new Counter('custom_counter');

export const options = {
  stages: [
    { duration: '1m', target: 20 },
    { duration: '3m', target: 20 },
    { duration: '1m', target: 0 },
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],
    http_req_failed: ['rate<0.05'],
    errors: ['rate<0.1'],
    response_time: ['p(95)<600'],
  },
  ext: {
    loadimpact: {
      name: 'Comprehensive API Test',
      projectID: 123456,
    },
  },
};

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

export function setup() {
  // Setup phase - run once
  console.log(`Testing API at: ${BASE_URL}`);
  
  const response = http.get(`${BASE_URL}/health`);
  if (response.status !== 200) {
    fail('API is not healthy');
  }
  
  return {
    baseUrl: BASE_URL,
    testData: generateTestData(100),
  };
}

export default function(data) {
  const testUser = data.testData[Math.floor(Math.random() * data.testData.length)];
  
  group('API Health Check', () => {
    const response = http.get(`${data.baseUrl}/health`);
    const success = check(response, {
      'health check passes': (r) => r.status === 200,
      'has healthy status': (r) => JSON.parse(r.body).status === 'healthy',
    });
    
    if (!success) {
      errorRate.add(1);
    }
    
    responseTimeTrend.add(response.timings.duration);
  });
  
  group('User CRUD Operations', () => {
    // Create
    let response = http.post(`${data.baseUrl}/api/users`, JSON.stringify(testUser), {
      headers: { 'Content-Type': 'application/json' },
    });
    
    const createSuccess = check(response, {
      'user created successfully': (r) => r.status === 201,
      'response has user id': (r) => JSON.parse(r.body).id > 0,
    });
    
    if (!createSuccess) {
      errorRate.add(1);
      return; // Skip remaining operations if creation fails
    }
    
    const userId = JSON.parse(response.body).id;
    customCounter.add(1);
    
    sleep(0.5);
    
    // Read
    response = http.get(`${data.baseUrl}/api/users/${userId}`);
    check(response, {
      'user retrieved successfully': (r) => r.status === 200,
      'correct user returned': (r) => JSON.parse(r.body).id === userId,
    }) || errorRate.add(1);
    
    sleep(0.5);
    
    // Update
    testUser.age += 1;
    response = http.put(`${data.baseUrl}/api/users/${userId}`, JSON.stringify(testUser), {
      headers: { 'Content-Type': 'application/json' },
    });
    
    check(response, {
      'user updated successfully': (r) => r.status === 200,
      'age was updated': (r) => JSON.parse(r.body).age === testUser.age,
    }) || errorRate.add(1);
    
    sleep(0.5);
    
    // Delete
    response = http.del(`${data.baseUrl}/api/users/${userId}`);
    check(response, {
      'user deleted successfully': (r) => r.status === 204,
    }) || errorRate.add(1);
  });
  
  sleep(Math.random() * 2 + 1);
}

export function teardown(data) {
  console.log('Test completed');
  console.log(`Base URL used: ${data.baseUrl}`);
}

function generateTestData(count) {
  const names = ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve', 'Frank', 'Grace', 'Henry'];
  const domains = ['example.com', 'test.com', 'demo.com'];
  
  const users = [];
  for (let i = 0; i < count; i++) {
    users.push({
      name: `${names[i % names.length]} ${i}`,
      email: `user${i}@${domains[i % domains.length]}`,
      age: Math.floor(Math.random() * 50) + 18,
    });
  }
  
  return users;
}
```

## Continuous Integration Integration

### GitHub Actions Workflow:

#### .github/workflows/load-test.yml
```yaml
name: Load Testing with k6

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 2 * * *'  # Run daily at 2 AM

jobs:
  load-test:
    runs-on: ubuntu-latest
    
    services:
      api:
        image: user-api:latest
        ports:
          - 8080:8080
        env:
          PORT: 8080
        options: >-
          --health-cmd "wget --quiet --tries=1 --spider http://localhost:8080/health"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Go
      uses: actions/setup-go@v4
      with:
        go-version: '1.21'
    
    - name: Build Docker image
      run: |
        cd backend
        docker build -t user-api:latest .
    
    - name: Install k6
      run: |
        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
    
    - name: Wait for API to be ready
      run: |
        timeout 300 bash -c 'until curl -f http://localhost:8080/health; do sleep 5; done'
    
    - name: Run smoke test
      run: k6 run tests/smoke-test.js
    
    - name: Run load test
      run: k6 run tests/load-test.js --out json=results.json
    
    - name: Upload test results
      uses: actions/upload-artifact@v3
      if: always()
      with:
        name: k6-test-results
        path: results.json
    
    - name: Comment PR with results
      if: github.event_name == 'pull_request'
      uses: actions/github-script@v6
      with:
        script: |
          const fs = require('fs');
          try {
            const results = fs.readFileSync('results.json', 'utf8');
            const metrics = JSON.parse(results);
            // Process and comment results...
          } catch (error) {
            console.log('No results file found');
          }
```

## CI/CD Pipeline

The project includes a comprehensive GitHub Actions pipeline that automates testing, building, and deployment. Here's what the pipeline includes:

### Pipeline Overview
```yaml
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:           # Unit tests with coverage
  build:          # Docker image build and push
  load-test:      # Automated k6 testing
  helm-test:      # Helm chart validation
  security-scan:  # Security vulnerability scanning
  deploy-staging: # Deploy to staging environment
  deploy-production: # Deploy to production
  post-deploy-tests: # Production smoke tests
```

### Pipeline Features
- **Unit Testing**: Go test suite with coverage reporting
- **Security Scanning**: Gosec (static analysis) + Trivy (image scanning)
- **Load Testing**: Automated k6 tests against built containers
- **Helm Validation**: Chart linting and templating verification
- **Multi-stage Deployment**: Staging → Production with approvals
- **Post-deployment Testing**: Smoke tests in production

### Using the Pipeline
The pipeline automatically triggers on:
- **Pull Requests**: Runs tests and validation only
- **Develop Branch**: Deploys to staging environment
- **Main Branch**: Deploys to production environment

## Troubleshooting

### Common Issues and Solutions

#### Docker Build Problems
```bash
# Test Docker connectivity
make docker-test

# Clean Docker cache
make docker-clean

# Test network connectivity
make network-test

# If Docker issues persist, use local development
make dev-setup
make run
```

#### API Not Starting
```bash
# Check if port is in use
lsof -i :8080

# Check API logs
make compose-logs

# Test API directly
go run backend/main.go
```

#### k6 Tests Failing
```bash
# Verify API is healthy
curl http://localhost:8080/health

# Test with verbose output
k6 run --http-debug tests/smoke-test.js

# Check if base URL is correct
export BASE_URL=http://localhost:8080
k6 run tests/smoke-test.js
```

#### Kubernetes Deployment Issues
```bash
# Check pod status
kubectl get pods
kubectl describe pod <pod-name>

# Check service connectivity
kubectl get svc
kubectl port-forward service/user-api 8080:80

# Check Helm deployment
helm status user-api
helm list
```

#### Monitoring Stack Issues
```bash
# Check Prometheus connection
kubectl port-forward -n monitoring service/prometheus 9090:9090
curl http://localhost:9090/-/healthy

# Verify Grafana datasource
# Login to Grafana → Configuration → Data Sources

# Check k6 output format
k6 run --out experimental-prometheus-rw=http://localhost:9090/api/v1/write tests/smoke-test.js
```

### Make Commands for Troubleshooting
```bash
make help              # Show all available commands
make docker-test       # Test Docker connectivity
make network-test      # Test network connectivity
make test-coverage     # Run tests with coverage report
make k8s-status        # Show Kubernetes resource status
make monitoring-up     # Restart monitoring stack
```

## Conclusion and Next Steps

This comprehensive tutorial has covered:

1. **k6 Fundamentals**: Understanding how k6 works and its architecture
2. **Environment Setup**: Local development with Docker
3. **Backend Service**: Go-based REST API with proper endpoints
4. **Container Orchestration**: Docker containerization
5. **Load Testing Scenarios**: Various test types (smoke, load, stress, spike)
6. **Monitoring Integration**: Grafana and Prometheus for metrics visualization
7. **Advanced Patterns**: Data-driven and session-based testing
8. **CI/CD Integration**: Automated testing in GitHub Actions

### Next Steps:

1. **Scale Testing**: Experiment with distributed k6 testing across multiple nodes
2. **Advanced Monitoring**: Integrate with Prometheus, Jaeger for distributed tracing
3. **Security Testing**: Add authentication and authorization testing scenarios
4. **Database Integration**: Include database load testing scenarios
5. **Cloud Deployment**: Deploy to cloud providers (AWS EKS, GCP GKE, Azure AKS)
6. **Performance Tuning**: Use k6 results to optimize application performance
7. **Custom Extensions**: Develop k6 extensions for specific testing needs

### Key Takeaways:

- **Start Small**: Begin with smoke tests before running intensive load tests
- **Monitor Everything**: Always collect metrics and monitor system behavior
- **Test Early**: Integrate performance testing into your development workflow
- **Realistic Scenarios**: Design tests that mirror real user behavior
- **Infrastructure Matters**: Ensure your test environment resembles production
- **Continuous Improvement**: Use test results to drive performance optimizations

This setup provides a solid foundation for comprehensive load testing with k6, enabling you to ensure your applications can handle production traffic effectively.