
K6, Prometheus, Grafana를 사용하여 컨테이너화된 애플리케이션의 부하 테스트 및 모니터링 환경을 구축.
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 |
# Prometheus 버전 확인
prometheus --version
# 출력 예시:
# prometheus, version 2.45.0
설치 안 되어 있다면:
brew install prometheus
# Chocolatey 사용
choco install prometheus
# 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': 환경 구분 라벨# Prometheus 디렉토리로 이동
cd ~/Desktop/prometheus
# 백그라운드 실행
prometheus --config.file=prometheus.yml \
--web.enable-remote-write-receiver \
--storage.tsdb.path=./data &
# 프로세스 확인
ps aux | grep prometheus
# 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
# 웹 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"}
# Grafana 버전 확인
grafana-server -v
# 출력 예시:
# Version 10.0.0
설치 안 되어 있다면:
brew install grafana
choco install grafana
# 서비스 시작
brew services start grafana
# 또는 직접 실행
grafana-server --homepath /opt/homebrew/opt/grafana/share/grafana &
# 서비스 시작
net start grafana
URL: http://localhost:3000
ID: admin
PW: admin
첫 로그인 시 비밀번호 변경 요구 → Skip 또는 변경
경로: Configuration (⚙️) → Data Sources → Add data source
Name: Prometheus
Type: Prometheus
URL: http://localhost:9090
Access: Browser
[Save & Test] 클릭
성공 메시지: ✅ Data source is working
방법 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_memory_used_bytes{application="auction-app", area="heap"}
rate(http_server_requests_seconds_count{application="auction-app"}[1m])
histogram_quantile(0.95,
rate(http_server_requests_seconds_bucket{application="auction-app"}[5m])
)
# Docker Stats를 Prometheus로 수집하려면 cAdvisor 필요
# 현재는 docker stats 명령어로 확인
# 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: {
'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 이하
}
cd ~/Desktop/DevCource_2
docker-compose up -d
# 상태 확인
docker-compose ps
# 로그 확인 (BaseInitData 실행 확인)
docker-compose logs -f app | grep BaseInitData
# 프로세스 확인
ps aux | grep prometheus
# 웹 UI 확인
open http://localhost:9090
# 브라우저에서 열기
open http://localhost:3000
# 대시보드 확인
# - K6 대시보드
# - Spring Boot 대시보드
# 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
새 터미널 열어서:
# 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%
✅ 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초
판단:
2. 에러율 (Error Rate)
errors: 1.2% (150 out of 12,450 requests)
판단:
3. 처리량 (Throughput)
http_reqs: 12,450 (약 34 req/s)
판단:
확인 사항:
Heap Memory Used: 750MB / 750MB (100%) ← 거의 한계!
GC Count: 120회 (6분간)
GC Time: 총 3.5초
판단:
확인 사항:
Request Rate: 30-40 req/s (피크 시)
Success Rate: 98.8%
판단:
확인 사항:
Cache Hit Rate: 85%
Cache Miss: 15%
판단:
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-app | 85% | 87% | ⚠️ 거의 한계, 여유 없음 |
| auction-mysql | 45% | 73% | ✅ 양호 |
| auction-redis | 13% | 9% | ✅ 여유 있음 |
# docker-compose.yml
services:
app:
deploy:
resources:
limits:
memory: 1280M # 980M → 1280M
# Dockerfile
ENV JAVA_OPTS="-Xms700m -Xmx1000m ..." # 기존: 512m-750m
# application.yml
spring:
cache:
redis:
time-to-live: 900000 # 10분 → 15분
WARN[0120] Request Failed error="context deadline exceeded"
# 1. 리소스 사용량 확인
docker stats
# 2. 메모리 부족 시 증가
# docker-compose.yml 수정 후 재시작
docker-compose down
docker-compose up -d
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 &
# 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