현대의 웹 애플리케이션 개발에서 Docker 컨테이너화와 리버스 프록시 설정은 필수적인 기술 스택이 되었습니다.
특히 Spring Boot 애플리케이션을 운영 환경에 배포할 때 Nginx를 리버스 프록시로 활용하면 성능 향상, 보안 강화, 로드 밸런싱 등 다양한 이점을 얻을 수 있습니다.
이번 포스팅에서는 Docker를 활용하여 Spring Boot 애플리케이션과 Nginx 리버스 프록시를 설정하는 방법을 단계별로 알아보겠습니다.
Docker 컨테이너 환경에서 Spring Boot 애플리케이션 구축하기
Spring Boot 애플리케이션을 Docker 컨테이너로 배포하기 위해서는 먼저 효율적인 Dockerfile을 작성해야 합니다.
멀티 스테이지 빌드를 활용하면 이미지 크기를 최적화하고 빌드 프로세스를 개선할 수 있습니다.
# 멀티 스테이지 빌드를 위한 빌드 스테이지
FROM gradle:7.6-jdk17 as builder
WORKDIR /app
COPY build.gradle settings.gradle ./
COPY src ./src
# Gradle 빌드 실행
RUN gradle clean build -x test
# 런타임 스테이지
FROM openjdk:17-jre-slim
WORKDIR /app
# 빌드된 JAR 파일 복사
COPY --from=builder /app/build/libs/*.jar app.jar
# 애플리케이션 포트 노출
EXPOSE 8080
# 애플리케이션 실행
ENTRYPOINT ["java", "-jar", "app.jar"]
이 Dockerfile은 빌드 의존성을 분리하여 최종 이미지 크기를 크게 줄여줍니다.
또한 JRE 슬림 버전을 사용하여 보안성과 효율성을 높였습니다.
Nginx 리버스 프록시 설정으로 성능 최적화하기
Nginx를 리버스 프록시로 설정하면 정적 파일 서빙, SSL 종료, 압축, 캐싱 등의 기능을 활용할 수 있습니다.
Spring Boot 애플리케이션 앞단에 Nginx를 배치하여 웹 서버의 역할을 분담시키는 것이 모범 사례입니다.
# nginx.conf
upstream spring-app {
server spring-boot-app:8080;
}
server {
listen 80;
server_name localhost;
# 정적 파일 서빙
location /static/ {
alias /var/www/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
# API 요청을 Spring Boot로 프록시
location /api/ {
proxy_pass http://spring-app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 타임아웃 설정
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# 기본 요청 처리
location / {
proxy_pass http://spring-app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Gzip 압축 활성화
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}
이 설정에서는 업스트림 서버 정의, 정적 파일 캐싱, 프록시 헤더 설정, Gzip 압축 등을 포함하고 있습니다.
Docker Compose를 활용한 멀티 컨테이너 오케스트레이션
Docker Compose를 사용하면 Spring Boot 애플리케이션과 Nginx를 하나의 스택으로 관리할 수 있습니다.
네트워크 설정, 볼륨 마운트, 환경 변수 등을 통합적으로 관리할 수 있어 개발 및 운영 효율성이 크게 향상됩니다.
# docker-compose.yml
version: '3.8'
services:
spring-boot-app:
build:
context: .
dockerfile: Dockerfile
container_name: spring-boot-container
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- SERVER_PORT=8080
networks:
- app-network
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
nginx:
image: nginx:alpine
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./static:/var/www/static:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- spring-boot-app
networks:
- app-network
restart: unless-stopped
networks:
app-network:
driver: bridge
volumes:
static-files:
driver: local
이 Docker Compose 설정에는 헬스체크, 네트워크 분리, 볼륨 마운트, 재시작 정책 등이 포함되어 있습니다.
SSL/TLS 인증서 적용과 HTTPS 보안 설정
운영 환경에서는 HTTPS 통신이 필수적입니다.
Let's Encrypt를 활용한 무료 SSL 인증서 적용 방법과 Nginx에서의 SSL 설정을 알아보겠습니다.
# ssl.conf
server {
listen 443 ssl http2;
server_name yourdomain.com;
# SSL 인증서 설정
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
# SSL 보안 설정
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# HSTS 헤더 추가
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 보안 헤더 추가
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
location / {
proxy_pass http://spring-app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
# HTTP에서 HTTPS로 리다이렉트
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}
이 설정은 최신 SSL/TLS 보안 표준을 준수하며 다양한 보안 헤더를 포함하고 있습니다.
로드 밸런싱과 고가용성 구현 전략
대규모 트래픽을 처리하기 위해서는 여러 Spring Boot 인스턴스를 운영하고 Nginx를 통해 로드 밸런싱을 구현해야 합니다.
다양한 로드 밸런싱 알고리즘과 헬스체크 설정을 통해 안정적인 서비스를 제공할 수 있습니다.
# docker-compose-ha.yml
version: '3.8'
services:
spring-boot-app-1:
build: .
container_name: spring-app-1
environment:
- SPRING_PROFILES_ACTIVE=prod
- SERVER_PORT=8080
networks:
- app-network
restart: unless-stopped
spring-boot-app-2:
build: .
container_name: spring-app-2
environment:
- SPRING_PROFILES_ACTIVE=prod
- SERVER_PORT=8080
networks:
- app-network
restart: unless-stopped
spring-boot-app-3:
build: .
container_name: spring-app-3
environment:
- SPRING_PROFILES_ACTIVE=prod
- SERVER_PORT=8080
networks:
- app-network
restart: unless-stopped
nginx:
image: nginx:alpine
container_name: nginx-lb
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx-lb.conf:/etc/nginx/nginx.conf:ro
depends_on:
- spring-boot-app-1
- spring-boot-app-2
- spring-boot-app-3
networks:
- app-network
restart: unless-stopped
networks:
app-network:
driver: bridge
로드 밸런싱을 위한 Nginx 설정:
# nginx-lb.conf
upstream spring-cluster {
least_conn;
server spring-app-1:8080 max_fails=3 fail_timeout=30s;
server spring-app-2:8080 max_fails=3 fail_timeout=30s;
server spring-app-3:8080 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
location / {
proxy_pass http://spring-cluster;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 연결 설정
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 버퍼링 설정
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
# 헬스체크 엔드포인트
location /nginx-health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
이 설정은 least_conn 알고리즘을 사용하여 연결 수가 가장 적은 서버로 요청을 분산시킵니다.
모니터링과 로깅 시스템 구축
운영 환경에서는 애플리케이션과 인프라의 상태를 지속적으로 모니터링해야 합니다.
Nginx 액세스 로그와 Spring Boot 액추에이터를 활용한 모니터링 설정을 구현해보겠습니다.
# 로깅 설정
log_format detailed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time';
server {
access_log /var/log/nginx/access.log detailed;
error_log /var/log/nginx/error.log warn;
# 나머지 설정...
}
Spring Boot 애플리케이션에 액추에이터 의존성 추가:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
}
액추에이터 설정:
# application.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
metrics:
export:
prometheus:
enabled: true
성능 최적화와 캐싱 전략
웹 애플리케이션의 성능을 최적화하기 위해서는 적절한 캐싱 전략이 필요합니다.
Nginx에서 제공하는 다양한 캐싱 기능과 Spring Boot의 캐싱 메커니즘을 함께 활용하면 응답 시간을 크게 단축할 수 있습니다.
# 캐싱 설정
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app_cache:10m max_size=1g inactive=60m use_temp_path=off;
server {
location /api/public/ {
proxy_pass http://spring-app;
proxy_cache app_cache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status;
}
# 정적 파일 캐싱
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary Accept-Encoding;
}
}
이 설정은 API 응답 캐싱과 정적 파일 캐싱을 구분하여 적용하고 있습니다.
보안 강화와 방화벽 설정
웹 애플리케이션 보안을 위해서는 다층 보안 전략이 필요합니다.
Nginx에서 제공하는 보안 기능과 Docker 네트워크 격리를 통해 보안을 강화할 수 있습니다.
# 보안 설정
server {
# DDoS 방어
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
# IP 화이트리스트 (관리자 페이지)
location /admin/ {
allow 192.168.1.0/24;
allow 10.0.0.0/8;
deny all;
proxy_pass http://spring-app;
}
# Rate limiting 적용
location /api/auth/login {
limit_req zone=login burst=5 nodelay;
proxy_pass http://spring-app;
}
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://spring-app;
}
# 보안 헤더
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
}
이 보안 설정은 DDoS 방어, IP 접근 제한, 보안 헤더 등을 포함하고 있습니다.
배포 자동화와 CI/CD 파이프라인 구축
DevOps 환경에서는 자동화된 배포 파이프라인이 핵심입니다.
GitHub Actions를 활용한 CI/CD 파이프라인 구축 방법을 알아보겠습니다.
# .github/workflows/deploy.yml
name: Docker Deploy
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Cache Gradle packages
uses: actions/cache@v3
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: ${{ runner.os }}-gradle
- name: Run tests
run: ./gradlew test
- name: Build with Gradle
run: ./gradlew build
- name: Build Docker image
run: docker build -t spring-boot-app:${{ github.sha }} .
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Push to Docker Hub
run: |
docker tag spring-boot-app:${{ github.sha }} ${{ secrets.DOCKER_USERNAME }}/spring-boot-app:latest
docker push ${{ secrets.DOCKER_USERNAME }}/spring-boot-app:latest
- name: Deploy to server
uses: appleboy/ssh-action@v0.1.5
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
script: |
cd /path/to/your/app
docker-compose pull
docker-compose up -d
docker system prune -f
이 CI/CD 파이프라인은 테스트 실행, Docker 이미지 빌드, 레지스트리 푸시, 자동 배포까지 전체 과정을 자동화합니다.
트러블슈팅과 성능 튜닝 가이드
운영 중 발생할 수 있는 일반적인 문제들과 해결 방법을 정리해보겠습니다.
가장 빈번하게 발생하는 문제들은 메모리 부족, 네트워크 연결 문제, 캐시 미스 등입니다.
Spring Boot 애플리케이션의 JVM 튜닝:
# Dockerfile JVM 최적화
FROM openjdk:17-jre-slim
ENV JAVA_OPTS="-Xms512m -Xmx2g -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:+UseStringDeduplication"
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
Nginx 성능 튜닝:
# nginx.conf 성능 최적화
worker_processes auto;
worker_connections 1024;
events {
use epoll;
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# 버퍼 크기 최적화
client_body_buffer_size 128k;
client_max_body_size 10m;
client_header_buffer_size 1k;
large_client_header_buffers 4 4k;
output_buffers 1 32k;
postpone_output 1460;
}
이러한 최적화 설정들은 높은 동시 접속자 수와 대용량 트래픽 처리에 도움이 됩니다.
마무리: 운영 환경 배포 체크리스트
Docker와 Nginx를 활용한 Spring Boot 애플리케이션 배포를 위한 최종 체크리스트입니다.
보안, 성능, 모니터링 측면에서 누락되기 쉬운 항목들을 정리했습니다.
보안 체크리스트:
- SSL/TLS 인증서 적용 및 갱신 자동화
- 보안 헤더 설정 완료
- Rate limiting 및 DDoS 방어 설정
- 불필요한 포트 및 서비스 차단
- 정기적인 보안 업데이트 적용
성능 체크리스트:
- JVM 메모리 설정 최적화
- Nginx 캐싱 설정 적용
- 정적 파일 CDN 연동
- 데이터베이스 연결 풀 튜닝
- 애플리케이션 성능 모니터링 구축
모니터링 체크리스트:
- 로그 수집 및 분석 시스템 구축
- 헬스체크 엔드포인트 구현
- 알람 및 알림 시스템 설정
- 메트릭 수집 및 대시보드 구성
- 백업 및 복구 절차 수립
이러한 체계적인 접근을 통해 안정적이고 확장 가능한 웹 애플리케이션 인프라를 구축할 수 있습니다.
Docker 컨테이너화와 Nginx 리버스 프록시를 활용한 아키텍처는 현대적인 웹 서비스 운영의 표준이 되었으며, 지속적인 학습과 개선을 통해 더욱 효율적인 시스템을 만들어갈 수 있습니다.
'DevOps' 카테고리의 다른 글
서버리스 아키텍처로 비용 효율적인 서비스 구축하기: 완벽한 가이드 (0) | 2025.05.25 |
---|---|
GitOps로 CI/CD 파이프라인 자동화하기: 현대적 DevOps 워크플로우 구축 가이드 (0) | 2025.05.25 |
EC2와 GitHub Actions를 활용한 배포 파이프라인 구축: 효율적인 CI/CD 구현 가이드 (0) | 2025.05.24 |
AWS 비용 최적화 전략: EC2, S3, RDS 중심으로 (0) | 2025.05.24 |
AWS EC2 + RDS 배포 실전 가이드 - 비용 최적화까지 완벽 정리 (1) | 2025.05.05 |