
3-1편에서 Docker의 개념을 학습했으니, 이제 실제로 로드밸런싱 테스트를 위한 4개의 웹서버를 Docker로 구축해보겠습니다
각 서버는 서로 다른 성능 특성을 가지도록 설정하여 나중에 로드밸런싱 알고리즘들의 성능을 명확하게 비교할 수 있도록 만들 예정입니다.
[사용자 요청]
↓
[Spring Boot 로드밸런서 :8080] ← 다음 편에서 구현
↓ 요청 분산
┌──────────────────────────────────────────────────────────────────────┐
│ [Docker 서버1] [Docker 서버2] [Docker 서버3] [Docker 서버4]
│ :5001 :5002 :5003 :5004 │
└──────────────────────────────────────────────────────────────────────┘
package com.loadbalancer;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.HashMap;
@RestController
public class TestController {
@GetMapping("/")
public Map<String, Object> home() {
// 환경변수에서 서버 설정 읽기
String serverId = System.getenv("SERVER_ID");
String responseDelay = System.getenv("RESPONSE_DELAY");
// 응답 지연 시뮬레이션
if (responseDelay != null) {
try {
Thread.sleep(Integer.parseInt(responseDelay));
} catch (Exception e) {
// 무시
}
}
Map<String, Object> response = new HashMap<>();
response.put("server", serverId != null ? serverId : "unknown");
response.put("timestamp", System.currentTimeMillis());
response.put("responseDelay", responseDelay != null ? responseDelay + "ms" : "0ms");
return response;
}
@GetMapping("/health")
public Map<String, String> health() {
return Map.of(
"status", "healthy",
"server", System.getenv("SERVER_ID")
);
}
}
메인 엔드 포인트 (/ )
SERVER_ID 환경변수 읽기RESPONSE_DELAY 환경변수 처리헬스체크 엔드포인트(/health )
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY . .
RUN chmod +x gradlew && ./gradlew clean build -x test --no-daemon
RUN find build/libs -name "*.jar" ! -name "*plain.jar" -exec cp {} app.jar \;
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
베이스이미지
FROM openjdk:17-jdk-slim
slim 태그로 불필요한 패키지 제거하여 이미지 크기 최적화작업 디렉토리
WORKDIR /app
/app 디렉토리를 작업 공간으로 설정빌드 과정 - 소스코드 복사
COPY . .
/app 으로 복사빌드과정 - Gradle 빌드
RUN chmod +x gradlew && ./gradlew clean build -x test --no-daemon
gradlew 실행 권한 부여clean build 이전 빌드 결과 삭제 후 새로 빌드-x test 테스트 실행 제외(빌드 시간 단축)--no-deamon Gradle 데몬 사용 안함JAR 파일 처리
RUN find build/libs -name "*.jar" ! -name "*plain.jar" -exec cp {} app.jar \;
build/libs 디렉토리에서 JAR 파일 검색*plain.jar 제외 (Spring Boot의 실행 가능한 JAR 만 선택)app.jar 로 이름 변경포트 노출 및 애플리케이션 실행
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
멀티스테이지 빌드
FROM 을 사용해 단계별로 이미지를 만드는 방식코드
# 빌드 스테이지
FROM openjdk:17-jdk-slim AS builder
WORKDIR /app
# Gradle 래퍼와 빌드 스크립트만 먼저 복사 (의존성 캐싱을 위해)
COPY gradlew .
COPY gradle gradle
COPY build.gradle .
COPY settings.gradle .
# 실행 권한 부여 및 의존성 다운로드
RUN chmod +x gradlew
RUN ./gradlew dependencies --no-daemon
# 소스 코드 복사 및 빌드
COPY src src
RUN ./gradlew clean build -x test --no-daemon
# 런타임 스테이지 - Eclipse Temurin 사용 (OpenJDK 기반)
FROM eclipse-temurin:17-jre
WORKDIR /app
# 빌드 스테이지에서 JAR 파일만 복사
COPY --from=builder /app/build/libs/*.jar app.jar
# 애플리케이션용 사용자 생성 (보안 강화)
RUN addgroup --system spring && adduser --system spring --ingroup spring
USER spring:spring
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
개선점
COPY . . → 소스코드 1줄만 바껴도 전체 재빌드 새버전: 의존성 파일들을 먼저 복사 → 의존성은 캐시되고, 소스코드 변경시에만 해당 레이어만 재빌드spring 전용 사용자 생성하여 실행services:
# 서버 1: 빠르고 안정적
web-server-1:
build: .
container_name: web-server-1
ports:
- "5001:8080"
environment:
- SERVER_ID=server-1
- RESPONSE_DELAY=50 # 50ms 지연
# 서버 2: 보통 성능
web-server-2:
build: .
container_name: web-server-2
ports:
- "5002:8080"
environment:
- SERVER_ID=server-2
- RESPONSE_DELAY=150 # 150ms 지연
# 서버 3: 느린 서버
web-server-3:
build: .
container_name: web-server-3
ports:
- "5003:8080"
environment:
- SERVER_ID=server-3
- RESPONSE_DELAY=300 # 300ms 지연
# 서버 4: 매우 느리고 불안정
web-server-4:
build: .
container_name: web-server-4
ports:
- "5004:8080"
environment:
- SERVER_ID=server-4
- RESPONSE_DELAY=500 # 500ms 지연
각각 서버 특성
RESPONSE_DELAY 가 달라 성능이 다름개별 포트 할당
curl localhost:5001 vs curl localhost:5004 비교 가능환경 변수로 설정 제어
SERVER_ID : 어느 서버가 응답했는지 식별RESPONSE_DELAY : 인위적 지연으로 서버 성능 차이 시뮬레이션 가능server-1

server-2

server-3

server-4

# 각 서버별 응답시간 측정
for port in 5001 5002 5003 5004; do
echo "Testing server on port $port:"
time curl -s http://localhost:$port/ > /dev/null
echo ""
done
time
real (실제 경과 시간), user (CPU 사용시간), sys (시스템 호출 시간) 출력curl -s
-s : silent 모드 (진행률 표시 숨김)/d /dev/null : 응답 내용은 버리고 시간만 측정결과 분석


설정이 올바르게 작동하는 것을 볼 수 있음
추가 오버헤드의 원인들?
일관된 오버헤드

증상: pipe/dockerDesktopLinuxEngine 에러
error during connect: Get
"http://%2F%2F.%2Fpipe%2FdockerDesktopLinuxEngine/v1.49/containers/json":
open //./pipe/dockerDesktopLinuxEngine: The system cannot find the file specified.
WSL2 설치 확인
# PowerShell 관리자 권한으로 실행
wsl --list --verbose
WSL2 재시작
# PowerShell 관리자 권한으로
wsl --shutdown
# 30초 대기 후 확인
wsl --list --verbose
정상상태
NAME STATE VERSION
* Ubuntu-22.04 Running 2
docker-desktop Running 2
docker-desktop-data Running 2
Docker 종료 후 재실행
증상: cp: target 'app.jar' is not a directory
여러 JAR 파일 생성으로 인한 충돌
해결: find 명령어로 plain.jar 제외
# 문제가 있던 방식
RUN cp build/libs/*.jar app.jar
# 해결된 방식
RUN find build/libs -name "*.jar" ! -name "*plain.jar" -exec cp {} app.jar \;
4개의 서로 다른 성능 특성을 가진 웹서버를 Docker로 성공적으로 구축했습니다.
각 서버별로 50ms부터 500ms까지 응답시간 차이를 확인할 수 있었고,
이제 로드밸런싱 테스트를 위한 기반이 완성되었습니다.
다음 편에서는 6가지 로드밸런싱 알고리즘의 동작 원리와 특징을 살펴보겠습니다.
Phase 1: 계획 및 이론
Phase 2: 환경 구축 및 설계
Phase 3: 구현 및 테스트
Phase 4: ML 확장 및 마무리