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 ที่ทำหน้าที่ดังนี้:
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 คำสั่งสำคัญที่ใช้บ่อย
# รัน 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
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 มีโครงสร้างดังนี้:
Container หลักของทั้ง test
จำนวน user และเวลา testing
คำสั่ง actual ที่ส่งไปยัง API
รับผลและสร้าง 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)
<?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
- เปิด JMeter GUI
- Windows:
bin/jmeter.bat - Linux/Mac:
./bin/jmeter
- Windows:
- Right-click Test Plan -> Add -> Threads (Users) -> Thread Group
- ตั้งค่า Thread Group (Num Threads, Ramp-up period, Duration)
- Right-click Thread Group -> Add -> Sampler -> HTTP Request
- ตั้งค่า HTTP Request (Server Name, Path, Method)
- Right-click Thread Group -> Add -> Listener -> Summary Report
- 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 ตัวอย่าง
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
เวอร์ชันของแอป
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 หลายแบบพร้อมกันเพื่อลดเวลา
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
{ "maxResponseTime": 2000, "failureRate": 0.01, "minThroughput": 100 }
# ตรวจสอบผลลัพธ์ และ 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 ที่รวมจากการใช้งานจริง
Test Design
- - ใช้ Thread Group ที่สมจริง
- - ใส่ think time เพื่อจำลอง user
- - ใช้ CSV Data Set สำหรับ data-driven
- - แบ่ง test เป็น logical groups
CI/CD Integration
- - รันใน non-GUI mode เสมอ
- - ใช้ Docker สำหรับ isolation
- - Archive all test artifacts
- - ใช้ parallel jobs เพื่อความเร็ว
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
# 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)
#!/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
# 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
#!/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"