부하 테스트 통합

허동빈·2026년 1월 27일

docker

목록 보기
7/8
post-thumbnail

Chapter 07: K6 부하 테스트 통합

K6, Prometheus, Grafana를 사용하여 컨테이너화된 애플리케이션의 부하 테스트 및 모니터링 환경을 구축.


1. 부하 테스트 환경 개요

🎯 목표

Docker Compose로 리소스가 제한된 컨테이너 환경에서 AWS EC2 t3.micro 스펙을 시뮬레이션하고, 부하 테스트를 통해 성능을 측정합니다.

📊 전체 아키텍처

┌─────────────────────────────────────────────────┐
│              Host Machine (맥북)                 │
│                                                 │
│  ┌───────────────────────────────────────────┐ │
│  │ Docker Compose (리소스 제한 적용)          │ │
│  │  ├─ auction-app (980MB, 2.0 CPU)          │ │
│  │  ├─ auction-mysql (980MB, 2.0 CPU)        │ │
│  │  └─ auction-redis (510MB, 2.0 CPU)        │ │
│  └───────────────────────────────────────────┘ │
│              ↓ 메트릭 수집                      │
│  ┌───────────────────────────────────────────┐ │
│  │ Prometheus (호스트 실행)                   │ │
│  │  - Spring Boot 메트릭 수집                │ │
│  │  - K6 메트릭 수집                          │ │
│  └───────────────────────────────────────────┘ │
│              ↓ 데이터 시각화                    │
│  ┌───────────────────────────────────────────┐ │
│  │ Grafana (호스트 실행)                      │ │
│  │  - 실시간 대시보드                         │ │
│  │  - 성능 분석                               │ │
│  └───────────────────────────────────────────┘ │
│              ↑ 부하 생성                        │
│  ┌───────────────────────────────────────────┐ │
│  │ K6 (호스트 실행)                           │ │
│  │  - 가상 유저 생성                          │ │
│  │  - API 호출                                │ │
│  └───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘

🔧 구성 요소

도구역할실행 위치포트
K6부하 생성호스트-
Prometheus메트릭 수집호스트9090
Grafana시각화호스트3000
Spring Boot테스트 대상컨테이너8080

2. Prometheus 설정

📝 Prometheus 설치 확인

# Prometheus 버전 확인
prometheus --version

# 출력 예시:
# prometheus, version 2.45.0

설치 안 되어 있다면:

macOS

brew install prometheus

Windows

# Chocolatey 사용
choco install prometheus

🔧 prometheus.yml 설정

파일 위치

# Prometheus 디렉토리로 이동
cd ~/Desktop/prometheus

# 설정 파일 확인
ls -la prometheus.yml

설정 파일 내용

현재 설정 확인:

global:
  scrape_interval: 15s
  evaluation_interval: 15s

# Remote Write 활성화 (K6가 직접 전송)
# --web.enable-remote-write-receiver 옵션 필요

scrape_configs:
  # Spring Boot Actuator (Docker 컨테이너)
  - job_name: 'spring-boot'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['localhost:8080']
        labels:
          application: 'auction-app'
          environment: 'docker-compose'

  # Prometheus 자기 자신
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

확인 포인트:

  • targets: ['localhost:8080']: Docker 컨테이너 포트 매핑으로 접근 가능
  • environment: 'docker-compose': 환경 구분 라벨
  • ✅ Remote Write는 K6가 직접 전송 (추가 설정 불필요)

🚀 Prometheus 실행

macOS

# Prometheus 디렉토리로 이동
cd ~/Desktop/prometheus

# 백그라운드 실행
prometheus --config.file=prometheus.yml \
  --web.enable-remote-write-receiver \
  --storage.tsdb.path=./data &

# 프로세스 확인
ps aux | grep prometheus

Windows (PowerShell)

# Prometheus 디렉토리로 이동
cd C:\Users\YourName\Desktop\prometheus

# 백그라운드 실행
Start-Process prometheus -ArgumentList "--config.file=prometheus.yml","--web.enable-remote-write-receiver","--storage.tsdb.path=./data" -WindowStyle Hidden

✅ Prometheus 동작 확인

# 웹 UI 접속
open http://localhost:9090

# 또는 브라우저에서
# http://localhost:9090

Targets 확인:
1. Status → Targets 메뉴 클릭
2. spring-boot job 확인

쿼리 테스트:

# JVM 메모리 사용량
jvm_memory_used_bytes{application="auction-app"}

# HTTP 요청 수
http_server_requests_seconds_count{application="auction-app"}

3. Grafana 설정

📝 Grafana 설치 확인

# Grafana 버전 확인
grafana-server -v

# 출력 예시:
# Version 10.0.0

설치 안 되어 있다면:

macOS

brew install grafana

Windows

choco install grafana

🚀 Grafana 실행

macOS

# 서비스 시작
brew services start grafana

# 또는 직접 실행
grafana-server --homepath /opt/homebrew/opt/grafana/share/grafana &

Windows

# 서비스 시작
net start grafana

🔧 Grafana 초기 설정

1. 로그인

URL: http://localhost:3000
ID: admin
PW: admin

첫 로그인 시 비밀번호 변경 요구 → Skip 또는 변경


2. Prometheus 데이터 소스 추가

경로: Configuration (⚙️) → Data Sources → Add data source

Name: Prometheus
Type: Prometheus
URL: http://localhost:9090
Access: Browser

[Save & Test] 클릭

성공 메시지: ✅ Data source is working


3. 대시보드 생성

방법 1: K6 기본 대시보드 Import

Dashboards (📊) → Import → Dashboard ID 입력

K6 공식 대시보드 ID: 2587

[Load] → Prometheus 데이터 소스 선택 → [Import]

방법 2: Spring Boot 대시보드 Import

Dashboard ID: 4701 (JVM Micrometer)

또는

Dashboard ID: 12900 (Spring Boot 2.1 Statistics)

📊 대시보드 패널 추가

새 대시보드 생성:
1. Create → Dashboard → Add new panel
2. 아래 쿼리 추가

JVM 메모리 사용량

jvm_memory_used_bytes{application="auction-app", area="heap"}

HTTP 요청 처리율

rate(http_server_requests_seconds_count{application="auction-app"}[1m])

응답 시간 (95 percentile)

histogram_quantile(0.95, 
  rate(http_server_requests_seconds_bucket{application="auction-app"}[5m])
)

컨테이너 메모리 사용률

# Docker Stats를 Prometheus로 수집하려면 cAdvisor 필요
# 현재는 docker stats 명령어로 확인

4. K6 테스트 스크립트 작성

📝 테스트 시나리오

  1. 경매 목록 조회 (읽기 부하)
  2. 경매 상세 조회 (캐시 테스트)
  3. 입찰 생성 (쓰기 부하 + 동시성 테스트)

🔧 테스트 스크립트

파일 생성

# K6 스크립트 디렉토리로 이동
cd ~/Desktop/k6-1

# 새 스크립트 생성
touch load-test-docker.js

스크립트 내용

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

// 커스텀 메트릭
const errorRate = new Rate('errors');
const auctionDetailDuration = new Trend('auction_detail_duration');
const bidCreateDuration = new Trend('bid_create_duration');

// 테스트 옵션
export const options = {
  stages: [
    { duration: '1m', target: 10 },   // Warm-up: 10 VUs
    { duration: '2m', target: 50 },   // Ramp-up: 50 VUs
    { duration: '2m', target: 50 },   // Steady: 50 VUs
    { duration: '1m', target: 0 },    // Cool-down
  ],
  thresholds: {
    'http_req_duration': ['p(95)<500', 'p(99)<1000'],
    'errors': ['rate<0.1'],
    'auction_detail_duration': ['p(95)<200'],
    'bid_create_duration': ['p(95)<500'],
  },
};

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

// Setup: 테스트 유저 로그인
export function setup() {
  const users = [
    { username: 'user1', password: '1234' },
    { username: 'user2', password: '1234' },
    { username: 'user3', password: '1234' },
  ];

  const authList = [];

  for (const user of users) {
    const loginRes = http.post(
      `${BASE_URL}/api/v1/members/login`,
      JSON.stringify(user),
      { headers: { 'Content-Type': 'application/json' } }
    );

    if (loginRes.status === 200) {
      const data = loginRes.json('data');
      authList.push({
        apiKey: data.apiKey,
        accessToken: data.accessToken,
      });
    }
  }

  console.log(`${authList.length}명 로그인 완료`);
  return { authList };
}

// 메인 테스트 시나리오
export default function (data) {
  // 랜덤 유저 선택
  const auth = data.authList[Math.floor(Math.random() * data.authList.length)];
  const headers = {
    'Authorization': `Bearer ${auth.apiKey} ${auth.accessToken}`,
  };

  // 1️⃣ 경매 목록 조회 (70% 확률)
  if (Math.random() < 0.7) {
    const listRes = http.get(`${BASE_URL}/api/v1/auctions`, { headers });
    
    check(listRes, {
      '경매 목록 조회 성공': (r) => r.status === 200,
    }) || errorRate.add(1);
  }

  // 2️⃣ 경매 상세 조회 (90% 확률)
  if (Math.random() < 0.9) {
    const auctionId = Math.floor(Math.random() * 20) + 1; // 1-20
    const startTime = Date.now();
    
    const detailRes = http.get(
      `${BASE_URL}/api/v1/auctions/${auctionId}`,
      { headers }
    );
    
    auctionDetailDuration.add(Date.now() - startTime);
    
    check(detailRes, {
      '경매 상세 조회 성공': (r) => r.status === 200,
    }) || errorRate.add(1);
  }

  // 3️⃣ 입찰 생성 (20% 확률)
  if (Math.random() < 0.2) {
    const auctionId = Math.floor(Math.random() * 20) + 1;
    const bidAmount = Math.floor(Math.random() * 100000) + 200000; // 200k-300k
    const startTime = Date.now();
    
    const bidRes = http.post(
      `${BASE_URL}/api/v1/auctions/${auctionId}/bids`,
      JSON.stringify({ price: bidAmount }),
      { headers: { ...headers, 'Content-Type': 'application/json' } }
    );
    
    bidCreateDuration.add(Date.now() - startTime);
    
    check(bidRes, {
      '입찰 성공 또는 검증 실패': (r) => r.status === 200 || r.status === 400,
    }) || errorRate.add(1);
  }

  sleep(1); // 사용자 think time
}

📊 테스트 옵션 설명

stages<: [
  { duration: '1m', target: 10 },   // 1분간 10명까지 증가
  { duration: '2m', target: 50 },   // 2분간 50명까지 증가
  { duration: '2m', target: 50 },   // 2분간 50명 유지 (피크)
  { duration: '1m', target: 0 },    // 1분간 0명으로 감소
]

총 소요 시간: 6분


🎯 임계값 (Thresholds)

thresholds: {
  'http_req_duration': ['p(95)<500', 'p(99)<1000'],  // 95%는 500ms 이하
  'errors': ['rate<0.1'],                            // 에러율 10% 미만
  'auction_detail_duration': ['p(95)<200'],          // 상세 조회 200ms 이하
  'bid_create_duration': ['p(95)<500'],              // 입찰 500ms 이하
}

5. 부하 테스트 실행

🚀 실행 절차

1. Docker Compose 실행

cd ~/Desktop/DevCource_2
docker-compose up -d

# 상태 확인
docker-compose ps

# 로그 확인 (BaseInitData 실행 확인)
docker-compose logs -f app | grep BaseInitData

2. Prometheus 실행 확인

# 프로세스 확인
ps aux | grep prometheus

# 웹 UI 확인
open http://localhost:9090

3. Grafana 대시보드 준비

# 브라우저에서 열기
open http://localhost:3000

# 대시보드 확인
# - K6 대시보드
# - Spring Boot 대시보드

4. K6 테스트 실행

# K6 디렉토리로 이동
cd ~/Desktop/k6-1

# Prometheus Remote Write로 메트릭 전송하며 테스트 실행
K6_PROMETHEUS_RW_SERVER_URL=http://localhost:9090/api/v1/write \
k6 run -o experimental-prometheus-rw load-test-docker.js

실행 중 출력 예시:

     ✓ 경매 목록 조회 성공
     ✓ 경매 상세 조회 성공
     ✓ 입찰 성공 또는 검증 실패

     █ VUs: 50/50  Duration: 3m30s / 6m0s

     http_req_duration...........: avg=120ms min=10ms med=80ms max=2s p(95)=350ms
     http_reqs...................: 12,450
     errors......................: 1.2%
     auction_detail_duration.....: p(95)=150ms
     bid_create_duration.........: p(95)=420ms

5. 동시에 리소스 모니터링

새 터미널 열어서:

# Docker 컨테이너 리소스 사용량 실시간 모니터링
docker stats

출력 예시:

CONTAINER              CPU %    MEM USAGE / LIMIT    MEM %
auction-app            85.23%   850MiB / 980MiB      86.73%  ← 거의 한계!
auction-mysql-compose  45.15%   720MiB / 980MiB      73.47%
auction-redis-compose  12.84%   45MiB / 510MiB       8.82%

6. 결과 분석

📊 K6 테스트 결과

성공 기준

✅ http_req_duration p(95) < 500ms
✅ errors rate < 10%
✅ auction_detail_duration p(95) < 200ms
✅ bid_create_duration p(95) < 500ms

분석 포인트

1. 응답 시간 (Response Time)

http_req_duration:
  avg=120ms     ← 평균 응답 시간
  p(95)=350ms   ← 95%가 350ms 이하 ✅
  p(99)=850ms   ← 99%가 850ms 이하 ✅
  max=2s        ← 최대 2초

판단:

  • ✅ 평균 120ms: 양호
  • ✅ p(95) 350ms: 임계값(500ms) 통과
  • ⚠️ max 2s: 일부 요청에서 지연 발생 (원인 분석 필요)

2. 에러율 (Error Rate)

errors: 1.2% (150 out of 12,450 requests)

판단:

  • ✅ 10% 미만: 통과
  • 에러 원인 확인 필요:
    • 입찰 검증 실패 (정상)
    • 동시성 충돌 (409 Conflict)
    • 타임아웃

3. 처리량 (Throughput)

http_reqs: 12,450 (약 34 req/s)

판단:

  • 50명 가상 유저 → 34 req/s 처리
  • AWS EC2 t3.micro 수준에서 적절한 성능

📈 Grafana 대시보드 분석

1. JVM 메모리 사용량

확인 사항:

Heap Memory Used: 750MB / 750MB (100%) ← 거의 한계!
GC Count: 120회 (6분간)
GC Time: 총 3.5초

판단:

  • ⚠️ 메모리 사용량 거의 100%
  • ⚠️ GC 빈번 발생 → 성능 저하 원인
  • 💡 해결책: 메모리 증가 또는 JVM 튜닝

2. HTTP 요청 처리율

확인 사항:

Request Rate: 30-40 req/s (피크 시)
Success Rate: 98.8%

판단:

  • ✅ 안정적인 처리율 유지
  • ✅ 높은 성공률

3. Redis 캐시 효율

확인 사항:

Cache Hit Rate: 85%
Cache Miss: 15%

판단:

  • ✅ 캐시 효율 높음
  • 경매 상세 조회 성능 향상에 기여

🐛 Docker Stats 분석

docker stats --no-stream

# 출력:
CONTAINER              CPU %    MEM USAGE / LIMIT    MEM %
auction-app            85.23%   850MiB / 980MiB      86.73%
auction-mysql-compose  45.15%   720MiB / 980MiB      73.47%
auction-redis-compose  12.84%   45MiB / 510MiB       8.82%

판단:

컨테이너CPU 사용률메모리 사용률판단
auction-app85%87%⚠️ 거의 한계, 여유 없음
auction-mysql45%73%✅ 양호
auction-redis13%9%✅ 여유 있음

💡 개선 제안

1. Spring Boot 메모리 증가

# docker-compose.yml
services:
  app:
    deploy:
      resources:
        limits:
          memory: 1280M  # 980M → 1280M

2. JVM 힙 메모리 조정

# Dockerfile
ENV JAVA_OPTS="-Xms700m -Xmx1000m ..."  # 기존: 512m-750m

3. Redis 캐시 TTL 증가

# application.yml
spring:
  cache:
    redis:
      time-to-live: 900000  # 10분 → 15분

7. 트러블슈팅

🐛 문제 1: K6 테스트 중 타임아웃

증상

WARN[0120] Request Failed  error="context deadline exceeded"

원인

  • Spring Boot 응답 지연
  • 메모리 부족으로 GC 빈번 발생

해결

# 1. 리소스 사용량 확인
docker stats

# 2. 메모리 부족 시 증가
# docker-compose.yml 수정 후 재시작
docker-compose down
docker-compose up -d

🐛 문제 2: Prometheus가 메트릭 수집 안 함

증상

Prometheus → Targets → spring-boot: DOWN

원인 확인

# Spring Boot Actuator 엔드포인트 확인
curl http://localhost:8080/actuator/prometheus

# 응답이 없으면: Spring Boot 문제
# 응답이 있으면: Prometheus 설정 문제

해결

# 1. Spring Boot 재시작
docker-compose restart app

# 2. Prometheus 재시작
pkill prometheus
prometheus --config.file=prometheus.yml --web.enable-remote-write-receiver &

🐛 문제 3: Grafana 대시보드에 데이터 없음

원인

  • Prometheus 데이터 소스 연결 안 됨
  • K6가 메트릭 전송 안 함

해결

# 1. Prometheus 데이터 소스 테스트
# Grafana → Configuration → Data Sources → Prometheus → Test

# 2. K6 실행 시 Remote Write 옵션 확인
K6_PROMETHEUS_RW_SERVER_URL=http://localhost:9090/api/v1/write \
k6 run -o experimental-prometheus-rw load-test-docker.js

🎯 핵심 명령어 정리

환경 준비

# Docker Compose 시작
cd ~/Desktop/DevCource_2
docker-compose up -d

# Prometheus 시작
cd ~/Desktop/prometheus
prometheus --config.file=prometheus.yml \
  --web.enable-remote-write-receiver &

# Grafana 시작
brew services start grafana

테스트 실행

# K6 테스트
cd ~/Desktop/k6-1
K6_PROMETHEUS_RW_SERVER_URL=http://localhost:9090/api/v1/write \
k6 run -o experimental-prometheus-rw load-test-docker.js

# 동시에 리소스 모니터링
docker stats

결과 확인

# Prometheus 메트릭 확인
open http://localhost:9090

# Grafana 대시보드 확인
open http://localhost:3000

# Docker 리소스 사용량
docker stats --no-stream

📚 참고 자료

profile
백엔드 공부

0개의 댓글