DevOps Tutorial

Performance Testing กับ
JMeter และ
GitLab CI/CD

คู่มือฉบับสมบูรณ์สำหรับ DevOps Engineer ในการผสานการทดสอบประสิทธิภาพด้วย JMeter เข้ากับ Pipeline GitLab CI/CD

1. บทนำ: ทำไมต้อง JMeter + GitLab CI/CD?

ในยุคที่แอปพลิเคชันต้องรับมือกับผู้ใช้หลายพันจนถึงหลายล้านคน การทดสอบประสิทธิภาพ (Performance Testing) ไม่ใช่แค่เรื่องเสริมอีกต่อไป แต่กลายเป็นส่วนสำคัญของการพัฒนาซอฟต์แวร์

JMeter กับ GitLab CI/CD คือคู่หูที่สมบูรณ์แบบสำหรับ DevOps Team เพราะ JMeter เป็นเครื่องมือ Open Source ที่ทรงพลังสำหรับการทดสอบประสิทธิภาพ ในขณะที่ GitLab CI/CD ช่วยให้เราผสานการทดสอบนี้เข้ากับ Pipeline ได้อย่างราบรื่น

ข้อดีของการผสาน JMeter กับ GitLab CI/CD

Continuous Testing

ทดสอบอัตโนมัติทุกครั้งที่มีการเปลี่ยนแปลง code

Automated Reporting

สร้าง report และ artifact อัตโนมัติ

Fast Feedback

ได้ผลลัพธ์เร็วกว่าการทดสอบด้วยมือ

Cost Effective

ใช้เครื่องมือ Open Source ทั้งหมด

ภาพรวม Process

เมื่อเราผสาน development process ที่มีการผสาน JMeter เข้ากับ GitLab CI/CD เราจะได้ pipeline ที่ทำหน้าที่ดังนี้:

1. Push Code 2. Build 3. JMeter Test 4. Report

2. JMeter Fundamentals: พื้นฐานที่คุณต้องรู้

ก่อนที่เราจะผสาน JMeter เข้ากับ GitLab CI/CD เรามาทบทวนพื้นฐานของ JMeter กันก่อน

2.1 Architecture Overview

JMeter ใช้โครงสร้างแบบ Thread Group เพื่อจำลองผู้ใช้จำนวนมากร่วมเวลาเดียวกัน (concurrent users)

JMeter Core Components

  • Thread Group: กลุ่มผู้ใช้ที่จะทำการทดสอบ
  • HTTP Request: คำสั่ง HTTP ที่จะส่งไปยัง server
  • Sampler: ตัวส่งคำขอและรับคำตอบ
  • Listener: รับฟังผลลัพธ์และเก็บข้อมูล

2.2 คำสั่งสำคัญที่ใช้บ่อย

Terminal
# รัน JMeter script
jmeter -n -t "script.jmx" -l "results.jtl"

# รันพร้อมสร้าง HTML report
jmeter -n -t "script.jmx" -l "results.jtl" -e -o "report"

# ดูเวอร์ชัน
jmeter -v

คำอธิบาย-flag

  • -n = Non-GUI mode (ไม่เปิด GUI)
  • -t = Test script file (.jmx)
  • -l = Log file (.jtl)
  • -e = Generate report after test
  • -o = Output directory for report

3. Prerequisites: สิ่งที่ต้องเตรียมพร้อมก่อนเริ่ม

ก่อนที่จะเริ่มต้นผสาน JMeter กับ GitLab CI/CD คุณต้องมีสิ่งเหล่านี้พร้อม

Software

  • GitLab Account - GitLab CE/EE หรือ GitLab SaaS
  • JMeter - เวอร์ชัน 5.4.3 ขึ้นไป
  • Java - JDK 8 หรือสูงกว่า (แนะนำ 11+)
  • Docker - เวอร์ชัน 20+ (แนะนำ)

Environment

  • GitLab Runner - Self-hosted หรือ Shared
  • Test Server - Environment สำหรับ testing
  • Storage - พื้นที่เก็บ artifacts และ reports
  • Permissions - CI/CD Variables และ secrets

Checklist ก่อนเริ่มต้น

4. JMeter Installation: วิธีการติดตั้งและasetup

มีหลายวิธีในการติดตั้ง JMeter ซึ่งแต่ละวิธีมีข้อดีต่างกันขึ้นอยู่กับความต้องการของคุณ

Option 1: Docker (แนะนำ)

วิธีที่ง่ายที่สุดและเป็นมาตรฐานปัจจุบัน ใช้ JMeter image ที่มีอยู่แล้ว

docker run --rm jmeter

Option 2: Download

ดาวน์โหลดจากเว็บไซต์ทางการและติดตั้งด้วยตนเอง

wget https://dlcdn.apache.org/jmeter

Option 3: Homebrew

สำหรับ macOS ใช้ Homebrew เป็น package manager

brew install jmeter

ตัวอย่าง Docker Compose

docker-compose.yml
version: '3.8'
services:
  
    jmeter:
      image: alpine/jmeter:latest
    container_name: jmeter-test
    volumes:
      - ./tests:/tests
      - ./results:/results
    command: -n -t "/tests/test.jmx" -l "/results/report.jtl"

Tip: ตรวจสอบการติดตั้ง

หลังติดตั้งเสร็จตรวจสอบเวอร์ชันด้วยคำสั่ง jmeter -v หรือ docker run --rm jmeter -v

5. Writing JMeter Scripts: เขียนสคริปต์การทดสอบ

JMeter scripts ถูกเขียนในรูปแบบ XML และมีส่วนประกอบหลักอยู่ 3 ส่วน

5.1 Script Structure

สคริปต์ JMeter มีโครงสร้างดังนี้:

1. Test Plan

Container หลักของทั้ง test

2. Thread Group

จำนวน user และเวลา testing

3. HTTP Request

คำสั่ง actual ที่ส่งไปยัง API

4. Result Listeners

รับผลและสร้าง report

<?xml version="1.0" encoding="UTF-8"?>
<testPlan>
  <elementGroup>
    <threadGroup>
      <!-- User settings -->
      <stringProp>10</stringProp>
      <stringProp>60</stringProp>
      </threadGroup>
    <httpSampler>
      <!-- Request details -->
      <stringProp;name="HTTPSampler.path">/api/test</stringProp>
    </httpSampler>
    <resultCollector>
      <!-- Listener config -->
    </resultCollector>
  </elementGroup>
</testPlan>

ตัวอย่างสคริปต์จริง (simple_api_test.jmx)

simple_api_test.jmx JMeter XML
<?xml version="1.0" encoding="UTF-8"?>
<testPlan version="1.2" >
  <elementGrou>
    <threadGroup>
      <stringProp name="ThreadGroup.num_threads">50</stringProp>
      <stringProp name="ThreadGroup.ramp_time">10</stringProp>
      <stringProp name="ThreadGroup.duration">60</stringProp>
      <elementProp name="HTTPsampler.Arguments">
        <collectionProp name="Arguments.arguments">
          <elementProp name="">
            <stringProp name="Argument.name">endpoint</stringProp>
            <stringProp name="Argument.value">/api/health</stringProp>
          </elementProp>
        </collectionProp>
      </elementProp>
    </threadGroup>
  </elementGroup>
</testPlan>

สร้างสคริปต์ด้วย JMeter GUI

  1. เปิด JMeter GUI
    • Windows: bin/jmeter.bat
    • Linux/Mac: ./bin/jmeter
  2. Right-click Test Plan -> Add -> Threads (Users) -> Thread Group
  3. ตั้งค่า Thread Group (Num Threads, Ramp-up period, Duration)
  4. Right-click Thread Group -> Add -> Sampler -> HTTP Request
  5. ตั้งค่า HTTP Request (Server Name, Path, Method)
  6. Right-click Thread Group -> Add -> Listener -> Summary Report
  7. Save แล้ว export เป็น .jmx file

คำเตือน

อย่าใช้ GUI ในการ run test ใน production! GUI ใช้สำหรับสร้างและ debug เท่านั้น ห้ามใช้ใน GitLab CI/CD เพราะจะใช้ resources มากเกินไป

6. GitLab CI/CD Integration: ผสาน JMeter เข้ากับ Pipeline

ต่อไปเรามาดูวิธีการผสาน JMeter กับ GitLab CI/CD กัน

ไฟล์ .gitlab-ci.yml ตัวอย่าง

.gitlab-ci.yml YAML
image: "alpine/jmeter:latest"

stages:
  - test
  - report

variables:
  JMeter: "/opt/apache-jmeter/bin/jmeter"
  TEST_PLAN: "tests/api_test.jmx"
  REPORT_DIR: "results"

performance_test:
  stage: test
  script:
    - echo "Starting JMeter test..."
    - mkdir -p $REPORT_DIR
    - jmeter -n -t $TEST_PLAN -l $REPORT_DIR/report.jtl
    - echo "Test completed"
  artifacts:
    paths:
      - $REPORT_DIR/
    expire_in: 1 week
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

generate_report:
  stage: report
  script:
    - echo "Generating HTML report..."
    - jmeter -g $REPORT_DIR/report.jtl -o $REPORT_DIR/html
    - echo "Report generated at $REPORT_DIR/html"
  dependencies:
    - performance_test
  artifacts:
    paths:
      - $REPORT_DIR/html/
    expires_in: 1 week
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

stage: test

  • ใช้ Alpine JMeter docker image
  • สร้าง directory สำหรับ report
  • รัน JMeter ใน non-GUI mode
  • เก็บ artifacts เป็น .jtl file

stage: report

  • ใช้ .jtl file จาก stage แรก
  • สร้าง HTML report
  • เก็บ artifacts HTML report
  • แชร์ report กับ team

6.1 ตัวแปร Environment

ใช้ GitLab CI/CD Variables ในการจัดการ configuration

CI/CD Variables (Settings > CI/CD > Variables)

  • TEST_URL

    URL ของ server ที่ต้องการทดสอบ

  • NUM_THREADS

    จำนวน concurrent users

  • DURATION

    ระยะเวลา test (วิ)

  • APP_VERSION

    เวอร์ชันของแอป

Modified .gitlab-ci.yml
variables:
  TEST_URL: "https://api.example.com"
  NUM_THREADS: "100"
  DURATION: "300"

performance_test:
  script:
    - jmeter -n -t $TEST_PLAN
      -Jtest.url=$TEST_URL
      -Jnum.threads=$NUM_THREADS
      -Jduration=$DURATION
      -l $REPORT_DIR/report.jtl

Secret Variables (Protected)

สำหรับ API Keys หรือ credentials ให้ tick Protected และ Masked เพื่อความปลอดภัย แล้ว secrets จะไม่แสดงใน log

7. Advanced JMeter with GitLab: Techniques ขั้นสูง

เรากลับมาดูเทคนิคขั้นสูงที่ช่วยให้ testing มีประสิทธิภาพมากขึ้น

7.1 Parameterized Tests

ใช้ JMeter Properties เพื่อส่งค่าผ่าน command line

-Jtest.url=https://api.example.com
-Jnum.threads=100
-Jduration=300

7.2 GitLab Merge Request Checks

รัน test เฉพาะ MR เท่านั้น

rules:
  - if: $CI_PIPELINE_SOURCE == "merge_request_event"

7.3 Parallel Testing

รัน tests หลายแบบพร้อมกันเพื่อลดเวลา

Parallel Jobs in .gitlab-ci.yml
performance_test:
  stage: test
  script:
    - jmeter -n -t tests/login_test.jmx -l reports/login.jtl
    - jmeter -n -t tests/api_test.jmx -l reports/api.jtl
    - jmeter -n -t tests/checkout_test.jmx -l reports/checkout.jtl
  artifacts:
    paths:
      - reports/

parallel_job:
  stage: test
  script:
    - echo "This job runs in parallel"
  parallel: 4

7.4 Threshold Checks (Fail if Slow)

ตั้งเงื่อนไขให้ pipeline ล้มเหลวถ้า performance ไม่ดีพอ

Threshold Configuration

thresholds.json
{
  "maxResponseTime": 2000,
  "failureRate": 0.01,
  "minThroughput": 100
}
bash script
# ตรวจสอบผลลัพธ์ และ fail ถ้าเกิน threshold
if [ $avg_response_time -gt $MAX_RESPONSE_TIME ]; then
  echo "[FAIL] Response time too high: $avg_response_time > $MAX_RESPONSE_TIME"
  exit 1
fi

if [ $failure_rate -gt $FAILURE_RATE ]; then
  echo "[FAIL] Failure rate too high: $failure_rate > $FAILURE_RATE"
  exit 1
fi

Best Practice: Test Environment Considerations

Always test against a dedicated performance environment:

  • ไม่ใช้ production environment (production)
  • ใช้ข้อมูล sample/mock ที่ไม่ใช่ production data
  • คำนึงถึง network latency และ bandwidth
  • ใช้ dedicated resources สำหรับ performance testing
  • เก็บ baseline metrics สำหรับการเปรียบเทียบ

8. Best Practices: แนวทางปฏิบัติที่ดีที่สุด

เหล่านี้คือ best practices ที่รวมจากการใช้งานจริง

1

Test Design

  • - ใช้ Thread Group ที่สมจริง
  • - ใส่ think time เพื่อจำลอง user
  • - ใช้ CSV Data Set สำหรับ data-driven
  • - แบ่ง test เป็น logical groups
2

CI/CD Integration

  • - รันใน non-GUI mode เสมอ
  • - ใช้ Docker สำหรับ isolation
  • - Archive all test artifacts
  • - ใช้ parallel jobs เพื่อความเร็ว
3

Reporting

  • - สร้าง HTML report อัตโนมัติ
  • - เก็บ .jtl files สำหรับ later analysis
  • -ใช้ threshold checks
  • -ส่ง notification ถ้า fail

Checklist ก่อน Deploy

Tests กับ Production Data

ตรวจสอบว่าไม่ได้ใช้ production data

Environment Separation

ทดสอบใน environment แยกจาก production

Resource Allocation

มี resources เพียงพอสำหรับ testing

Thresholds

ตั้งค่า thresholds ที่สมเหตุสมผล

Common Mistakes

  • ใช้ GUI mode ใน CI/CD pipeline
  • ไม่เก็บ artifacts
  • ไม่มี threshold checks
  • รัน test ที่ไม่มี validation

Pro Tips

  • ใช้ JMeter Maven Plugin สำหรับการ run
  • ใช้ Distributed testing สำหรับ large scale
  • บันทึก baseline metrics
  • ใช้ monitoring ร่วมกับ Grafana/Prometheus

9. Real-World Example: ตัวอย่างใช้งานจริง

เราจะมาทำ example จริงให้เห็นภาพ

Project Structure

.
├── .gitlab-ci.yml
├── tests/
│   ├── api_test.jmx
│   ├── login_test.jmx
│   └── checkout_test.jmx
├── results/
│   ├── api.jtl
│   ├── api.html/
│   ├── login.jtl
│   └── login.html/
└── scripts/
    ├── threshold_check.sh
    └── notification.sh

9.1 Complete .gitlab-ci.yml

.gitlab-ci.yml
# JMeter & GitLab CI/CD Complete Example
image: "apache/jmeter:latest"

stages:
  - build
  - test
  - report
  - deploy

variables:
  APP_NAME: "my-api"
  TEST_ENV: "https://test.example.com"
  JMX_PATH: "tests/api_test.jmx"
  RESULTS_DIR: "results"

before_script:
  - echo "Setting up environment..."
  - mkdir -p $RESULTS_DIR
  - echo "Environment: $CI_ENVIRONMENT_NAME"

build:
  stage: build
  script:
    - echo "Building application..."
    - ./gradlew build
  artifacts:
    paths:
      - build/
  rules:
    - if: $CI_COMMIT_BRANCH

performance_test:
  stage: test
  script:
    - echo "Running JMeter performance test..."
    - jmeter -n -t $JMX_PATH
      -Jtest.url=$TEST_ENV
      -Jnum.threads=50
      -Jduration=120
      -l $RESULTS_DIR/performance.jtl
    - echo "Test completed successfully"
  artifacts:
    paths:
      - $RESULTS_DIR/
    expire_in: 2 weeks
  rules:
    - if: $CI_COMMIT_BRANCH == "develop"
    - if: $CI_COMMIT_BRANCH == "main"

generate_report:
  stage: report
  script:
    - echo "Generating HTML report..."
    - jmeter -g $RESULTS_DIR/performance.jtl
      -o $RESULTS_DIR/html_report
    - echo "Report URL: $CI_WEB_ENV_URL/html_report"
    - cat $RESULTS_DIR/html_report/index.html | grep -oP '<title>[^<]+</title>'
  dependencies:
    - performance_test
  artifacts:
    paths:
      - $RESULTS_DIR/html_report/
    expire_in: 1 month
  rules:
    - if: $CI_COMMIT_BRANCH == "develop"
    - if: $CI_COMMIT_BRANCH == "main"

deploy_to_staging:
  stage: deploy
  script:
    - echo "Deploying to staging..."
    - ./deploy.sh staging
  environment:
    name: staging
    url: https://staging.example.com
  only:
    - develop

deploy_to_prod:
  stage: deploy
  script:
    - echo "Deploying to production..."
    - ./deploy.sh prod
  environment:
    name: production
    url: https://www.example.com
  only:
    - main
  when: manual

Threshold Check Script (scripts/threshold_check.sh)

threshold_check.sh
#!/bin/bash
# Threshold check script for JMeter results

# Configuration
MAX_RESPONSE_TIME=2000
FAILURE_RATE=0.01
MIN_THROUGHPUT=100
RESULTS_FILE="${1:-results/performance.jtl}"

echo "🔍 Starting threshold check..."
echo "File: $RESULTS_FILE"
echo "Max Response Time: ${MAX_RESPONSE_TIME}ms"
echo "Max Failure Rate: ${FAILURE_RATE*100}%"
echo "Min Throughput: ${MIN_THROUGHPUT} req/s"
echo ""

# Get statistics from JTL file using xmllint or jtl
echo "Analyzing results..."
# This is a simplified example

# Check metrics
# Replace these with actual parsing from JTL
avg_response_time=1500  # Example value
failure_rate=0.005  # Example value
throughput=120  # Example value

# Threshold checks
errors=0

if [ $avg_response_time -gt $MAX_RESPONSE_TIME ]; then
  echo "[FAIL]  Avg Response Time ($avg_response_time ms) > Max ($MAX_RESPONSE_TIME ms)"
  errors=$((errors+1))
else
  echo "[PASS]  Avg Response Time ($avg_response_time ms) < Max ($MAX_RESPONSE_TIME ms)"
fi

if [ $failure_rate -gt $FAILURE_RATE ]; then
  echo "[FAIL]  Failure Rate ($failure_rate) > Max ($FAILURE_RATE)"
  errors=$((errors+1))
else
  echo "[PASS]  Failure Rate ($failure_rate) < Max ($FAILURE_RATE)"
fi

if [ $throughput -lt $MIN_THROUGHPUT ]; then
  echo "[FAIL]  Throughput ($throughput) < Min ($MIN_THROUGHPUT)"
  errors=$((errors+1))
else
  echo "[PASS]  Throughput ($throughput) > Min ($MIN_THROUGHPUT)"
fi

echo ""
if [ $errors -gt 0 ]; then
  echo "[FAIL] Threshold check FAILED with $errors error(s)"
  exit 1
else
  echo "[PASS] All threshold checks PASSED"
  exit 0
fi

Example JTL Statistics

Metric Value Threshold Status
Avg Response Time 1,250 ms < 2,000 ms [PASS]
Max Response Time 2,850 ms < 5,000 ms [PASS]
Error Rate 0.3% < 1% [PASS]
Throughput 145 req/s > 100 req/s [PASS]
Active Threads 50 = 50 [PASS]
Total Time 122s ~120s [PASS]

10. Troubleshooting: แก้ปัญหาเมื่อเกิดข้อผิดพลาด

เมื่อเกิดข้อผิดพลาดใน pipeline ลองใช้ขั้นตอนเหล่านี้

Common Issues & Solutions

[FAIL] JMeter not found

Error: jmeter: command not found

Solution:

  • ตรวจสอบว่าใช้ Docker image ที่มี JMeter ติดตั้งแล้ว
  • หรือ install JMeter ใน pipeline
  • หรือใช้ apache/jmeter:latest

[FAIL] Test script error

Error: FileNotFound: script.jmx

Solution:

  • ตรวจสอบ path ของ .jmx file
  • ดูว่า artifact มาถึง pipeline นี้หรือไม่
  • ตรวจสอบว่าไฟล์อยู่ใน path ที่ถูกต้อง

[FAIL] Out of memory

Error: Java heap space

Solution:

  • ลดจำนวน threads
  • ลด duration ของการทดสอบ
  • เพิ่ม memory ของ CI runner
  • ใช้ JVM_ARGS="-Xmx1g"

[FAIL] Connection refused

Error: Connection refused

Solution:

  • ตรวจสอบตัวแปร TEST_URL
  • ตรวจสอบว่า test server รันอยู่
  • ตรวจสอบ firewall/network policy
  • ลอง ping/test connectivity ก่อน

[FAIL] Report generation failed

Error: Cannot write to output directory

Solution:

  • ทำ mkdir -p output_dir
  • ตรวจสอบ permissions
  • ใช้ path ที่ไม่มี space

Debugging Steps

Debugging pipeline
# 1. Run locally ด้วย Docker
docker run --rm -v $(pwd)/tests:/tests \
  -v $(pwd)/results:/results \
  alpine/jmeter \
  -n -t /tests/test.jmx -l /results/report.jtl

# 2. ดู logs แบบ real-time
jmeter -n -t tests/test.jmx -l results/report.jtl \
  -e -o results/report \
  > 2>1 | tee jmeter.log

# 3. ตรวจสอบ JTL file
head -n 100 results/report.jtl
tail -n 100 results/report.jtl

# 4. ดูผลสรุป
jmeter -g results/report.jtl -l summary.csv

Automated Debugging Script

debug_jmeter.sh
#!/bin/bash
# JMeter debugging helper script

set -e

SCRIPT="${1:-tests/api_test.jmx}"
LOG_FILE="jmeter_debug.log"

echo "🔍 Starting JMeter debug..."
echo "Script: $SCRIPT"
echo "Log: $LOG_FILE"
echo ""

# Check if JMeter is available
if ! command -v jmeter &> /dev/null; then
  echo "⚠️  JMeter not found. Using Docker..."
  docker run --rm -v $(pwd):/work -w /work \
    alpine/jmeter jmeter -n -t "$SCRIPT" &> $LOG_FILE
else
  echo "🚀 Running JMeter..."
  jmeter -n -t "$SCRIPT" &> $LOG_FILE
fi

echo ""
echo "📊 View results:"
echo "   tail -n 100 $LOG_FILE"
echo "   cat $LOG_FILE"