Theory
고급 2-3시간 이론 + 실습

Spring 19: 배포 - Docker, Profile

컨테이너화와 환경 분리

DockerProfile환경 설정CI/CD

1. 배포 개념

1.1 CI/CD 파이프라인

지속적 통합 (Continuous Integration)

개발자들이 코드 변경사항을 자주 중앙 저장소에 통합하는 개발 관행입니다. 각 통합은 자동화된 빌드와 테스트를 통해 검증됩니다.

CI의 핵심 요소:
  • 소스 코드 버전 관리 (Git)
  • 자동화된 빌드 프로세스
  • 자동화된 테스트 실행
  • 빠른 피드백 제공
  • 빌드 실패 시 즉시 알림

지속적 배포 (Continuous Deployment)

코드 변경사항이 자동으로 프로덕션 환경까지 배포되는 프로세스입니다. 모든 테스트를 통과한 코드는 수동 개입 없이 자동으로 배포됩니다.

CD의 핵심 요소:
  • 자동화된 배포 파이프라인
  • 환경별 설정 관리
  • 롤백 메커니즘
  • 모니터링 및 알림
  • 보안 검증

CI/CD 파이프라인 예제

# GitHub Actions CI/CD 파이프라인
name: Spring Boot CI/CD

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    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') }}
    
    - name: Run tests
      run: ./gradlew test
    
    - name: Generate test report
      uses: dorny/test-reporter@v1
      if: success() || failure()
      with:
        name: Gradle Tests
        path: build/test-results/test/*.xml
        reporter: java-junit

  build-and-deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Build Docker image
      run: |
        docker build -t myapp:latest .
        docker tag myapp:latest myapp:${{ github.sha }}
    
    - name: Deploy to staging
      run: |
        # 스테이징 환경 배포 스크립트
        echo "Deploying to staging..."
    
    - name: Run integration tests
      run: |
        # 통합 테스트 실행
        ./gradlew integrationTest
    
    - name: Deploy to production
      if: success()
      run: |
        # 프로덕션 환경 배포
        echo "Deploying to production..."

1.2 배포 전략

블루-그린 배포

두 개의 동일한 프로덕션 환경(블루/그린)을 유지하며, 한 번에 전체 트래픽을 새 버전으로 전환하는 방식입니다.

장점:
  • 즉시 롤백 가능
  • 다운타임 최소화
  • 완전한 환경 테스트
단점:
  • 리소스 비용 2배
  • 데이터베이스 동기화 복잡

롤링 배포

서버를 하나씩 또는 그룹별로 순차적으로 업데이트하는 방식입니다. 전체 서비스 중단 없이 점진적으로 배포합니다.

장점:
  • 리소스 효율적
  • 점진적 배포
  • 서비스 중단 없음
단점:
  • 배포 시간 길어짐
  • 버전 혼재 상황

카나리 배포

소수의 사용자에게만 새 버전을 배포하여 테스트한 후, 점진적으로 확대하는 방식입니다.

장점:
  • 위험 최소화
  • 실제 사용자 피드백
  • 점진적 확대
단점:
  • 복잡한 트래픽 관리
  • 모니터링 복잡성

A/B 테스트 배포

사용자를 그룹으로 나누어 서로 다른 버전을 제공하고 성능을 비교하는 방식입니다.

장점:
  • 데이터 기반 의사결정
  • 사용자 경험 최적화
  • 비즈니스 메트릭 검증
단점:
  • 복잡한 분석 필요
  • 장기간 운영 필요

Kubernetes 배포 전략 예제

# 롤링 업데이트 배포
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-app
spec:
  replicas: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  selector:
    matchLabels:
      app: spring-app
  template:
    metadata:
      labels:
        app: spring-app
    spec:
      containers:
      - name: spring-app
        image: spring-app:v2.0
        ports:
        - containerPort: 8080
        readinessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        livenessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 60
          periodSeconds: 30

---
# 카나리 배포를 위한 서비스
apiVersion: v1
kind: Service
metadata:
  name: spring-app-canary
spec:
  selector:
    app: spring-app
    version: canary
  ports:
  - port: 80
    targetPort: 8080
  type: ClusterIP

---
# Ingress를 통한 트래픽 분할
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: spring-app-ingress
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: spring-app-canary
            port:
              number: 80

1.3 환경 분리

환경별 특성과 목적

개발 환경 (Development)
  • • 개발자 개인 작업 환경
  • • 빠른 피드백과 디버깅
  • • 로컬 데이터베이스 사용
  • • 상세한 로깅 활성화
  • • 개발 도구 통합
테스트 환경 (Testing)
  • • 자동화된 테스트 실행
  • • CI/CD 파이프라인 통합
  • • 테스트 데이터 관리
  • • 성능 테스트 수행
  • • 보안 취약점 스캔
스테이징 환경 (Staging)
  • • 프로덕션과 동일한 구성
  • • 최종 사용자 테스트
  • • 통합 테스트 수행
  • • 배포 프로세스 검증
  • • 성능 및 부하 테스트
프로덕션 환경 (Production)
  • • 실제 사용자 서비스
  • • 고가용성 및 확장성
  • • 보안 강화
  • • 모니터링 및 알림
  • • 백업 및 재해복구

환경별 설정 관리

# application.yml (공통 설정)
spring:
  application:
    name: spring-deployment-demo
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
  endpoint:
    health:
      show-details: when-authorized

---
# application-dev.yml (개발 환경)
spring:
  config:
    activate:
      on-profile: dev
  datasource:
    url: jdbc:h2:mem:devdb
    driver-class-name: org.h2.Driver
    username: sa
    password: 
  jpa:
    hibernate:
      ddl-auto: create-drop
    show-sql: true
  h2:
    console:
      enabled: true

logging:
  level:
    com.example: DEBUG
    org.springframework.web: DEBUG

---
# application-test.yml (테스트 환경)
spring:
  config:
    activate:
      on-profile: test
  datasource:
    url: jdbc:postgresql://test-db:5432/testdb
    username: ${DB_USERNAME:testuser}
    password: ${DB_PASSWORD:testpass}
  jpa:
    hibernate:
      ddl-auto: create-drop

logging:
  level:
    com.example: INFO

---
# application-staging.yml (스테이징 환경)
spring:
  config:
    activate:
      on-profile: staging
  datasource:
    url: jdbc:postgresql://staging-db:5432/stagingdb
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
    hikari:
      maximum-pool-size: 10
      minimum-idle: 5

logging:
  level:
    com.example: WARN
  file:
    name: /var/log/app/staging.log

---
# application-prod.yml (프로덕션 환경)
spring:
  config:
    activate:
      on-profile: prod
  datasource:
    url: jdbc:postgresql://prod-db:5432/proddb
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
    hikari:
      maximum-pool-size: 20
      minimum-idle: 10
      connection-timeout: 30000

logging:
  level:
    com.example: ERROR
  file:
    name: /var/log/app/production.log

management:
  endpoints:
    web:
      exposure:
        include: health,info

환경 분리 베스트 프랙티스

설정 관리
  • • 환경변수를 통한 외부 설정
  • • 민감한 정보는 별도 관리
  • • 설정 파일 버전 관리
  • • 기본값 설정으로 안정성 확보
데이터 관리
  • • 환경별 독립적인 데이터베이스
  • • 테스트 데이터 자동 생성
  • • 프로덕션 데이터 보호
  • • 데이터 마이그레이션 스크립트
보안
  • • 환경별 접근 권한 관리
  • • 네트워크 분리
  • • 암호화 키 관리
  • • 감사 로그 유지
모니터링
  • • 환경별 모니터링 설정
  • • 알림 규칙 차별화
  • • 성능 메트릭 수집
  • • 로그 중앙화

1.4 배포 파이프라인 설계

파이프라인 단계별 구성

1
소스 코드 관리

Git 브랜치 전략, 코드 리뷰, 머지 정책

2
빌드 및 테스트

컴파일, 단위 테스트, 정적 분석, 보안 스캔

3
아티팩트 생성

Docker 이미지 빌드, 패키지 생성, 저장소 업로드

4
배포 및 검증

환경별 배포, 통합 테스트, 헬스 체크

5
모니터링 및 알림

성능 모니터링, 에러 추적, 알림 설정

⚠️ 배포 시 고려사항

기술적 고려사항
  • • 데이터베이스 마이그레이션
  • • 캐시 무효화
  • • 세션 관리
  • • 외부 서비스 의존성
  • • 리소스 사용량 모니터링
운영적 고려사항
  • • 배포 시간대 선택
  • • 롤백 계획 수립
  • • 팀 간 커뮤니케이션
  • • 사용자 공지
  • • 장애 대응 절차

2. Docker

2.1 Docker 기초 개념

컨테이너화의 이점

일관성

개발, 테스트, 프로덕션 환경에서 동일한 실행 환경 보장

격리성

애플리케이션과 의존성을 독립적으로 실행

이식성

어떤 환경에서든 동일하게 실행 가능

효율성

가상머신 대비 적은 리소스 사용

2.2 Dockerfile 작성

기본 Spring Boot Dockerfile

# 기본 Dockerfile
FROM openjdk:17-jdk-slim

# 작업 디렉토리 설정
WORKDIR /app

# JAR 파일 복사
COPY build/libs/*.jar app.jar

# 포트 노출
EXPOSE 8080

# 애플리케이션 실행
ENTRYPOINT ["java", "-jar", "app.jar"]

최적화된 Dockerfile

# 최적화된 Dockerfile
FROM openjdk:17-jdk-slim as builder

# 빌드 도구 설치
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

# 작업 디렉토리 설정
WORKDIR /app

# Gradle Wrapper와 빌드 파일 복사
COPY gradlew .
COPY gradle gradle
COPY build.gradle .
COPY settings.gradle .

# 의존성 다운로드 (캐시 최적화)
RUN ./gradlew dependencies --no-daemon

# 소스 코드 복사
COPY src src

# 애플리케이션 빌드
RUN ./gradlew bootJar --no-daemon

# 런타임 이미지
FROM openjdk:17-jre-slim

# 보안을 위한 사용자 생성
RUN groupadd -r spring && useradd -r -g spring spring

# 작업 디렉토리 설정
WORKDIR /app

# 빌드된 JAR 파일 복사
COPY --from=builder /app/build/libs/*.jar app.jar

# 파일 소유권 변경
RUN chown spring:spring app.jar

# 사용자 전환
USER spring

# 헬스체크 추가
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8080/actuator/health || exit 1

# 포트 노출
EXPOSE 8080

# JVM 옵션 설정
ENV JAVA_OPTS="-Xmx512m -Xms256m -XX:+UseG1GC -XX:+UseContainerSupport"

# 애플리케이션 실행
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

Dockerfile 베스트 프랙티스

이미지 최적화
  • • 경량 베이스 이미지 사용 (alpine, slim)
  • • 불필요한 패키지 제거
  • • 레이어 수 최소화
  • • .dockerignore 파일 활용
보안
  • • 비특권 사용자로 실행
  • • 최신 베이스 이미지 사용
  • • 민감한 정보 하드코딩 금지
  • • 취약점 스캔 수행

2.3 멀티 스테이지 빌드

멀티 스테이지 빌드의 장점

  • 최종 이미지 크기 대폭 감소
  • 빌드 도구와 런타임 환경 분리
  • 보안 향상 (빌드 도구 제거)
  • 빌드 캐시 최적화
  • 개발/프로덕션 환경 구분

고급 멀티 스테이지 Dockerfile

# 멀티 스테이지 빌드 Dockerfile
# Stage 1: 의존성 다운로드
FROM openjdk:17-jdk-slim as deps
WORKDIR /app
COPY gradlew .
COPY gradle gradle
COPY build.gradle .
COPY settings.gradle .
RUN ./gradlew dependencies --no-daemon

# Stage 2: 소스 빌드
FROM deps as builder
COPY src src
RUN ./gradlew bootJar --no-daemon

# Stage 3: 테스트 실행 (선택적)
FROM builder as tester
RUN ./gradlew test --no-daemon

# Stage 4: 프로덕션 이미지
FROM openjdk:17-jre-slim as production

# 시스템 업데이트 및 필수 패키지 설치
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        curl \
        dumb-init && \
    rm -rf /var/lib/apt/lists/*

# 사용자 생성
RUN groupadd -r spring && useradd -r -g spring spring

# 작업 디렉토리 설정
WORKDIR /app

# 빌드된 JAR 파일만 복사
COPY --from=builder /app/build/libs/*.jar app.jar

# 파일 소유권 변경
RUN chown spring:spring app.jar

# 사용자 전환
USER spring

# 헬스체크
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8080/actuator/health || exit 1

# 포트 노출
EXPOSE 8080

# 환경 변수 설정
ENV JAVA_OPTS="-Xmx512m -Xms256m -XX:+UseG1GC -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"

# dumb-init을 사용하여 신호 처리 개선
ENTRYPOINT ["dumb-init", "--"]
CMD ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

# Stage 5: 개발 이미지 (개발용)
FROM openjdk:17-jdk-slim as development
WORKDIR /app
COPY gradlew .
COPY gradle gradle
COPY build.gradle .
COPY settings.gradle .
COPY src src

# 개발 도구 설치
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        curl \
        vim \
        htop && \
    rm -rf /var/lib/apt/lists/*

# 개발 환경 설정
ENV SPRING_PROFILES_ACTIVE=dev
ENV JAVA_OPTS="-Xmx1g -Xms512m -XX:+UseG1GC -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"

EXPOSE 8080 5005

CMD ["./gradlew", "bootRun", "--no-daemon"]

빌드 타겟 선택

# 프로덕션 이미지 빌드
docker build --target production -t myapp:prod .

# 개발 이미지 빌드
docker build --target development -t myapp:dev .

# 테스트 포함 빌드
docker build --target tester -t myapp:test .

# 빌드 인자 사용
docker build --build-arg PROFILE=prod --target production -t myapp:latest .

2.4 Docker 이미지 최적화

레이어 캐싱 최적화

❌ 비효율적인 방법
# 소스 코드 변경 시 의존성도 재다운로드
COPY . .
RUN ./gradlew dependencies
RUN ./gradlew bootJar
✅ 효율적인 방법
# 의존성 먼저 다운로드 (캐시 활용)
COPY build.gradle settings.gradle ./
RUN ./gradlew dependencies
COPY src src
RUN ./gradlew bootJar

.dockerignore 파일

# .dockerignore
# 빌드 결과물
build/
target/
*.jar
*.war

# IDE 파일
.idea/
.vscode/
*.iml
*.ipr
*.iws

# 버전 관리
.git/
.gitignore
.gitattributes

# 문서
README.md
docs/
*.md

# 로그 파일
logs/
*.log

# 임시 파일
tmp/
temp/
.tmp

# OS 파일
.DS_Store
Thumbs.db

# 테스트 결과
test-results/
coverage/

# 환경 설정 (민감한 정보)
.env
.env.local
application-local.yml

이미지 크기 최적화 기법

1. 베이스 이미지 선택
# 크기 비교
openjdk:17-jdk        ~470MB
openjdk:17-jdk-slim   ~220MB
openjdk:17-jre-slim   ~185MB
openjdk:17-alpine     ~165MB
2. 패키지 정리
RUN apt-get update && \
    apt-get install -y --no-install-recommends curl && \
    rm -rf /var/lib/apt/lists/*
3. 레이어 결합
# 여러 명령을 하나로 결합
RUN apt-get update && \
    apt-get install -y curl && \
    curl -O https://example.com/file && \
    rm -rf /var/lib/apt/lists/*

Docker 빌드 최적화 명령어

# 빌드 캐시 사용
docker build --cache-from myapp:latest -t myapp:new .

# BuildKit 사용 (병렬 빌드)
DOCKER_BUILDKIT=1 docker build -t myapp:latest .

# 빌드 컨텍스트 최소화
docker build -f Dockerfile.prod -t myapp:prod .

# 이미지 크기 분석
docker images myapp:latest
docker history myapp:latest

# 이미지 레이어 분석 도구
dive myapp:latest

# 불필요한 이미지 정리
docker image prune -f
docker system prune -f

2.5 Docker 보안

🔒 보안 베스트 프랙티스

사용자 권한
  • • root 사용자로 실행 금지
  • • 전용 사용자 계정 생성
  • • 최소 권한 원칙 적용
  • • 파일 권한 적절히 설정
이미지 보안
  • • 신뢰할 수 있는 베이스 이미지
  • • 정기적인 이미지 업데이트
  • • 취약점 스캔 수행
  • • 민감한 정보 제거

보안 강화 Dockerfile

FROM openjdk:17-jre-slim

# 보안 업데이트 적용
RUN apt-get update && \
    apt-get upgrade -y && \
    apt-get install -y --no-install-recommends \
        curl \
        dumb-init && \
    rm -rf /var/lib/apt/lists/*

# 비특권 사용자 생성
RUN groupadd -r -g 1000 spring && \
    useradd -r -u 1000 -g spring -d /app -s /sbin/nologin spring

# 작업 디렉토리 설정 및 권한 부여
WORKDIR /app
RUN chown spring:spring /app

# 애플리케이션 파일 복사
COPY --chown=spring:spring build/libs/*.jar app.jar

# 실행 권한 설정
RUN chmod 500 app.jar

# 사용자 전환
USER spring

# 불필요한 권한 제거
RUN chmod -R go-rwx /app

# 보안 헤더 설정을 위한 환경 변수
ENV JAVA_OPTS="-Djava.security.egd=file:/dev/./urandom \
               -Dspring.security.require-ssl=true \
               -Dserver.use-forward-headers=true"

# 헬스체크
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8080/actuator/health || exit 1

EXPOSE 8080

ENTRYPOINT ["dumb-init", "--"]
CMD ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

3. Docker Compose

3.1 Docker Compose 개요

Docker Compose의 장점

다중 컨테이너 관리

여러 서비스를 하나의 파일로 정의하고 관리

환경 일관성

개발, 테스트, 프로덕션 환경 동일하게 구성

간편한 배포

한 번의 명령으로 전체 스택 실행

서비스 연결

컨테이너 간 네트워크 자동 구성

3.2 기본 Docker Compose 구성

기본 docker-compose.yml

version: '3.8'

services:
  # Spring Boot 애플리케이션
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - DB_HOST=postgres
      - DB_PORT=5432
      - DB_NAME=myapp
      - DB_USERNAME=postgres
      - DB_PASSWORD=password
    depends_on:
      - postgres
      - redis
    networks:
      - app-network

  # PostgreSQL 데이터베이스
  postgres:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - app-network

  # Redis 캐시
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    networks:
      - app-network

volumes:
  postgres_data:
  redis_data:

networks:
  app-network:
    driver: bridge

Docker Compose 기본 명령어

# 서비스 시작 (백그라운드)
docker-compose up -d

# 서비스 중지
docker-compose down

# 서비스 재시작
docker-compose restart

# 로그 확인
docker-compose logs -f app

# 특정 서비스만 시작
docker-compose up -d postgres redis

# 빌드 후 시작
docker-compose up --build

# 볼륨까지 삭제
docker-compose down -v

# 스케일링
docker-compose up -d --scale app=3

3.3 다중 컨테이너 구성

완전한 마이크로서비스 스택

version: '3.8'

services:
  # API Gateway (Nginx)
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - user-service
      - order-service
      - product-service
    networks:
      - frontend
      - backend

  # 사용자 서비스
  user-service:
    build: ./user-service
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - DB_HOST=user-db
      - REDIS_HOST=redis
      - EUREKA_URL=http://eureka:8761/eureka
    depends_on:
      - user-db
      - redis
      - eureka
    networks:
      - backend
    deploy:
      replicas: 2

  # 주문 서비스
  order-service:
    build: ./order-service
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - DB_HOST=order-db
      - KAFKA_BROKERS=kafka:9092
      - EUREKA_URL=http://eureka:8761/eureka
    depends_on:
      - order-db
      - kafka
      - eureka
    networks:
      - backend

  # 상품 서비스
  product-service:
    build: ./product-service
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - DB_HOST=product-db
      - ELASTICSEARCH_URL=http://elasticsearch:9200
      - EUREKA_URL=http://eureka:8761/eureka
    depends_on:
      - product-db
      - elasticsearch
      - eureka
    networks:
      - backend

  # 서비스 디스커버리 (Eureka)
  eureka:
    image: springcloud/eureka
    ports:
      - "8761:8761"
    networks:
      - backend

  # 사용자 DB
  user-db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=userdb
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    volumes:
      - user_db_data:/var/lib/postgresql/data
    networks:
      - backend

  # 주문 DB
  order-db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=orderdb
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    volumes:
      - order_db_data:/var/lib/postgresql/data
    networks:
      - backend

  # 상품 DB
  product-db:
    image: mongo:6
    environment:
      - MONGO_INITDB_DATABASE=productdb
    volumes:
      - product_db_data:/data/db
    networks:
      - backend

  # Redis 캐시
  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    networks:
      - backend

  # Kafka
  zookeeper:
    image: confluentinc/cp-zookeeper:latest
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000
    networks:
      - backend

  kafka:
    image: confluentinc/cp-kafka:latest
    depends_on:
      - zookeeper
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    networks:
      - backend

  # Elasticsearch
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.8.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
    volumes:
      - elasticsearch_data:/usr/share/elasticsearch/data
    networks:
      - backend

  # 모니터링 (Prometheus)
  prometheus:
    image: prom/prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    networks:
      - monitoring

  # 모니터링 (Grafana)
  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana_data:/var/lib/grafana
    networks:
      - monitoring

volumes:
  user_db_data:
  order_db_data:
  product_db_data:
  redis_data:
  elasticsearch_data:
  prometheus_data:
  grafana_data:

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
  monitoring:
    driver: bridge

3.4 네트워크 구성

네트워크 타입별 특성

Bridge Network
  • • 기본 네트워크 타입
  • • 같은 네트워크 내 컨테이너 통신
  • • 서비스명으로 DNS 해석
  • • 외부 접근 시 포트 매핑 필요
Host Network
  • • 호스트 네트워크 직접 사용
  • • 최고 성능
  • • 포트 충돌 주의
  • • 보안상 권장하지 않음
Overlay Network
  • • 다중 호스트 간 통신
  • • Docker Swarm에서 사용
  • • 암호화 지원
  • • 클러스터 환경에 적합
None Network
  • • 네트워크 연결 없음
  • • 완전 격리
  • • 보안이 중요한 작업
  • • 배치 작업에 적합

고급 네트워크 설정

version: '3.8'

services:
  web:
    image: nginx
    networks:
      frontend:
        aliases:
          - web-server
        ipv4_address: 172.20.0.10
      backend:
        aliases:
          - api-gateway

  app:
    build: .
    networks:
      - backend
    ports:
      - "8080"  # 동적 포트 할당

  db:
    image: postgres
    networks:
      backend:
        ipv4_address: 172.21.0.10
    # 외부 접근 차단 (포트 매핑 없음)

networks:
  frontend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16
          gateway: 172.20.0.1
    driver_opts:
      com.docker.network.bridge.name: frontend-br
      com.docker.network.bridge.enable_icc: "true"
      
  backend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.21.0.0/16
    internal: true  # 외부 인터넷 접근 차단
    
  # 외부 네트워크 사용
  external-net:
    external: true
    name: my-external-network

네트워크 보안 설정

# 네트워크 분리 예제
version: '3.8'

services:
  # DMZ 영역 (외부 접근 가능)
  nginx:
    image: nginx
    ports:
      - "80:80"
      - "443:443"
    networks:
      - dmz
      - frontend

  # 애플리케이션 영역
  app:
    build: .
    networks:
      - frontend
      - backend
    # 외부 포트 노출 없음

  # 데이터베이스 영역 (완전 격리)
  database:
    image: postgres
    networks:
      - backend
    # 외부 접근 완전 차단

networks:
  dmz:
    driver: bridge
    # 외부 접근 허용
    
  frontend:
    driver: bridge
    internal: false
    
  backend:
    driver: bridge
    internal: true  # 인터넷 접근 차단
    
# 네트워크 정책 (방화벽 규칙)
# iptables 또는 Docker 네트워크 정책 사용

3.5 볼륨 관리

볼륨 타입별 특성

Named Volume
  • • Docker가 관리
  • • 데이터 영속성
  • • 컨테이너 간 공유
  • • 백업 용이
Bind Mount
  • • 호스트 경로 직접 매핑
  • • 개발 시 편리
  • • 실시간 파일 동기화
  • • 보안 주의 필요
tmpfs Mount
  • • 메모리에 저장
  • • 임시 데이터
  • • 빠른 I/O
  • • 재시작 시 삭제

볼륨 설정 예제

version: '3.8'

services:
  # 데이터베이스 (Named Volume)
  postgres:
    image: postgres:15
    volumes:
      # 데이터 영속성
      - postgres_data:/var/lib/postgresql/data
      # 초기화 스크립트 (Bind Mount)
      - ./init-scripts:/docker-entrypoint-initdb.d:ro
      # 설정 파일 (Bind Mount)
      - ./postgres.conf:/etc/postgresql/postgresql.conf:ro
    environment:
      - POSTGRES_DB=myapp

  # 애플리케이션 (개발 환경)
  app-dev:
    build: .
    volumes:
      # 소스 코드 실시간 동기화
      - ./src:/app/src:ro
      - ./build:/app/build
      # 로그 파일
      - app_logs:/var/log/app
      # 임시 파일 (tmpfs)
      - type: tmpfs
        target: /tmp
        tmpfs:
          size: 100M
    environment:
      - SPRING_PROFILES_ACTIVE=dev

  # 애플리케이션 (프로덕션 환경)
  app-prod:
    build: .
    volumes:
      # 로그만 외부 저장
      - app_logs:/var/log/app:rw
      # 설정 파일 (읽기 전용)
      - ./config/application-prod.yml:/app/config/application.yml:ro
      # 업로드 파일
      - upload_data:/app/uploads
    environment:
      - SPRING_PROFILES_ACTIVE=prod

  # 로그 수집기
  filebeat:
    image: elastic/filebeat:8.8.0
    volumes:
      # 애플리케이션 로그 읽기
      - app_logs:/var/log/app:ro
      # Filebeat 설정
      - ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
      # Filebeat 데이터
      - filebeat_data:/usr/share/filebeat/data

volumes:
  postgres_data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /opt/postgres-data
      
  app_logs:
    driver: local
    
  upload_data:
    driver: local
    driver_opts:
      type: nfs
      o: addr=nfs-server,rw
      device: ":/path/to/uploads"
      
  filebeat_data:
    driver: local

볼륨 백업 및 복원

# 볼륨 백업
docker run --rm -v postgres_data:/data -v $(pwd):/backup \
  alpine tar czf /backup/postgres_backup.tar.gz -C /data .

# 볼륨 복원
docker run --rm -v postgres_data:/data -v $(pwd):/backup \
  alpine tar xzf /backup/postgres_backup.tar.gz -C /data

# 볼륨 정보 확인
docker volume ls
docker volume inspect postgres_data

# 사용하지 않는 볼륨 정리
docker volume prune

# 볼륨 복사 (컨테이너 간)
docker run --rm -v source_vol:/from -v dest_vol:/to \
  alpine cp -av /from/. /to/

3.6 환경별 설정

docker-compose.override.yml (개발 환경)

# docker-compose.override.yml (자동으로 적용됨)
version: '3.8'

services:
  app:
    build:
      context: .
      target: development
    volumes:
      - ./src:/app/src:ro
      - ./build:/app/build
    environment:
      - SPRING_PROFILES_ACTIVE=dev
      - DEBUG=true
    ports:
      - "8080:8080"
      - "5005:5005"  # 디버그 포트

  postgres:
    ports:
      - "5432:5432"  # 외부 접근 허용
    environment:
      - POSTGRES_DB=devdb
    volumes:
      - ./dev-data:/docker-entrypoint-initdb.d

  redis:
    ports:
      - "6379:6379"  # 외부 접근 허용

docker-compose.prod.yml (프로덕션 환경)

# docker-compose.prod.yml
version: '3.8'

services:
  app:
    build:
      context: .
      target: production
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - DB_HOST=${DB_HOST}
      - DB_PASSWORD=${DB_PASSWORD}
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  postgres:
    environment:
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
    # 외부 포트 노출 없음 (보안)

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx-prod.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - app

환경별 실행 명령어

# 개발 환경 (기본 + override)
docker-compose up -d

# 테스트 환경
docker-compose -f docker-compose.yml -f docker-compose.test.yml up -d

# 프로덕션 환경
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# 환경 변수 파일 사용
docker-compose --env-file .env.prod up -d

# 특정 서비스만 실행
docker-compose -f docker-compose.prod.yml up -d app postgres

# 설정 검증
docker-compose -f docker-compose.prod.yml config

# 로그 모니터링
docker-compose -f docker-compose.prod.yml logs -f --tail=100

4. Spring Profile

4.1 Spring Profile 개요

Profile의 필요성

환경별 설정 분리

개발, 테스트, 프로덕션 환경에 맞는 설정 적용

조건부 빈 등록

특정 환경에서만 필요한 빈을 선택적으로 등록

기능 토글

환경에 따라 특정 기능을 활성화/비활성화

보안 강화

민감한 설정을 환경별로 안전하게 관리

4.2 환경별 설정 파일

application.yml 구조

# application.yml (공통 설정)
spring:
  application:
    name: spring-profile-demo
  
  # 기본 데이터소스 설정
  datasource:
    driver-class-name: org.postgresql.Driver
    hikari:
      connection-timeout: 20000
      maximum-pool-size: 10
  
  # JPA 공통 설정
  jpa:
    hibernate:
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    properties:
      hibernate:
        format_sql: false
        use_sql_comments: false

# 로깅 기본 설정
logging:
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

# Actuator 기본 설정
management:
  endpoints:
    web:
      exposure:
        include: health,info
  endpoint:
    health:
      show-details: never

---
# 개발 환경 설정
spring:
  config:
    activate:
      on-profile: dev
      
  # H2 인메모리 데이터베이스
  datasource:
    url: jdbc:h2:mem:devdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    username: sa
    password: 
    driver-class-name: org.h2.Driver
    
  # H2 콘솔 활성화
  h2:
    console:
      enabled: true
      path: /h2-console
      
  # JPA 개발 설정
  jpa:
    hibernate:
      ddl-auto: create-drop
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        use_sql_comments: true

# 개발 환경 로깅
logging:
  level:
    com.example: DEBUG
    org.springframework.web: DEBUG
    org.hibernate.SQL: DEBUG
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE

# 개발 환경 Actuator
management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: always

---
# 테스트 환경 설정
spring:
  config:
    activate:
      on-profile: test
      
  # 테스트 데이터베이스
  datasource:
    url: jdbc:postgresql://localhost:5432/testdb
    username: ${DB_USERNAME:testuser}
    password: ${DB_PASSWORD:testpass}
    
  # JPA 테스트 설정
  jpa:
    hibernate:
      ddl-auto: create-drop
    show-sql: false

# 테스트 환경 로깅
logging:
  level:
    com.example: INFO
    org.springframework: WARN

---
# 스테이징 환경 설정
spring:
  config:
    activate:
      on-profile: staging
      
  # 스테이징 데이터베이스
  datasource:
    url: jdbc:postgresql://staging-db:5432/stagingdb
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
    hikari:
      maximum-pool-size: 15
      minimum-idle: 5
      
  # JPA 스테이징 설정
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false

# 스테이징 환경 로깅
logging:
  level:
    com.example: WARN
    root: INFO
  file:
    name: /var/log/app/staging.log

---
# 프로덕션 환경 설정
spring:
  config:
    activate:
      on-profile: prod
      
  # 프로덕션 데이터베이스
  datasource:
    url: jdbc:postgresql://prod-db:5432/proddb
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
    hikari:
      maximum-pool-size: 20
      minimum-idle: 10
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
      
  # JPA 프로덕션 설정
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false
    properties:
      hibernate:
        jdbc:
          batch_size: 20
        order_inserts: true
        order_updates: true

# 프로덕션 환경 로깅
logging:
  level:
    com.example: ERROR
    root: WARN
  file:
    name: /var/log/app/production.log
    max-size: 100MB
    max-history: 30

# 프로덕션 환경 Actuator (보안 강화)
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
      base-path: /actuator
  endpoint:
    health:
      show-details: when-authorized
  security:
    enabled: true

Profile 활성화 방법

# 1. application.yml에서 기본 프로파일 설정
spring:
  profiles:
    active: dev

# 2. 환경 변수로 설정
export SPRING_PROFILES_ACTIVE=prod

# 3. JVM 시스템 프로퍼티로 설정
java -Dspring.profiles.active=prod -jar app.jar

# 4. 프로그램 인수로 설정
java -jar app.jar --spring.profiles.active=prod

# 5. Docker 환경에서 설정
docker run -e SPRING_PROFILES_ACTIVE=prod myapp:latest

# 6. 여러 프로파일 동시 활성화
java -Dspring.profiles.active=prod,monitoring -jar app.jar

# 7. IDE에서 설정 (IntelliJ IDEA)
# Run Configuration > Environment variables > SPRING_PROFILES_ACTIVE=dev

4.3 외부 설정 관리

설정 우선순위

1
Command Line Arguments

--server.port=8081

2
JVM System Properties

-Dserver.port=8081

3
OS Environment Variables

SERVER_PORT=8081

4
application-{profile}.yml

Profile별 설정 파일

5
application.yml

기본 설정 파일

외부 설정 파일 사용

# 1. 외부 설정 파일 지정
java -jar app.jar --spring.config.location=classpath:/,/opt/config/

# 2. 추가 설정 파일 지정
java -jar app.jar --spring.config.additional-location=/opt/config/

# 3. 설정 파일 이름 변경
java -jar app.jar --spring.config.name=myapp

# 4. 여러 위치에서 설정 로드
java -jar app.jar \
  --spring.config.location=classpath:/,file:./config/,file:/opt/config/

# 5. 환경 변수로 외부 설정
export SPRING_CONFIG_LOCATION=/opt/config/application.yml
java -jar app.jar

# 6. Docker에서 외부 설정 마운트
docker run -v /host/config:/app/config \
  -e SPRING_CONFIG_LOCATION=/app/config/application.yml \
  myapp:latest

환경 변수 매핑

# application.yml에서 환경 변수 사용
spring:
  datasource:
    url: ${DATABASE_URL:jdbc:h2:mem:testdb}
    username: ${DB_USERNAME:sa}
    password: ${DB_PASSWORD:}
    
server:
  port: ${SERVER_PORT:8080}
  
app:
  jwt:
    secret: ${JWT_SECRET:default-secret}
    expiration: ${JWT_EXPIRATION:86400}
  
  redis:
    host: ${REDIS_HOST:localhost}
    port: ${REDIS_PORT:6379}
    password: ${REDIS_PASSWORD:}

# 환경 변수 설정 예제
export DATABASE_URL=jdbc:postgresql://prod-db:5432/proddb
export DB_USERNAME=produser
export DB_PASSWORD=securepassword
export SERVER_PORT=8080
export JWT_SECRET=super-secret-key
export REDIS_HOST=redis-cluster
export REDIS_PASSWORD=redis-password

# .env 파일 사용 (Docker Compose)
DATABASE_URL=jdbc:postgresql://postgres:5432/myapp
DB_USERNAME=postgres
DB_PASSWORD=password
SERVER_PORT=8080
JWT_SECRET=my-jwt-secret
REDIS_HOST=redis
REDIS_PORT=6379

4.4 비밀 정보 관리

🔒 보안 고려사항

❌ 피해야 할 방법
  • • 설정 파일에 비밀번호 하드코딩
  • • Git에 민감한 정보 커밋
  • • 로그에 비밀 정보 출력
  • • 프로덕션에서 기본값 사용
✅ 권장하는 방법
  • • 환경 변수 사용
  • • 외부 비밀 관리 시스템
  • • 암호화된 설정 파일
  • • 런타임 비밀 주입

Spring Cloud Config 사용

# Config Server 설정
# application.yml (Config Server)
server:
  port: 8888

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/myorg/config-repo
          search-paths: '{application}'
          username: ${GIT_USERNAME}
          password: ${GIT_PASSWORD}
        encrypt:
          enabled: true

# 암호화 키 설정
encrypt:
  key: myencryptionkey

---
# Config Client 설정
# bootstrap.yml (Application)
spring:
  application:
    name: myapp
  cloud:
    config:
      uri: http://config-server:8888
      profile: ${SPRING_PROFILES_ACTIVE:dev}
      label: main
      fail-fast: true
      retry:
        initial-interval: 1000
        max-attempts: 6

# 암호화된 설정 사용
# myapp-prod.yml (Config Repository)
spring:
  datasource:
    password: '{cipher}AQA1234567890abcdef...'
    
app:
  jwt:
    secret: '{cipher}BQB0987654321fedcba...'

Kubernetes Secrets 사용

# Kubernetes Secret 생성
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
data:
  database-password: cGFzc3dvcmQ=  # base64 encoded
  jwt-secret: bXktand0LXNlY3JldA==

---
# Deployment에서 Secret 사용
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-app
spec:
  template:
    spec:
      containers:
      - name: app
        image: myapp:latest
        env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: database-password
        - name: JWT_SECRET
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: jwt-secret
        # 또는 볼륨으로 마운트
        volumeMounts:
        - name: secret-volume
          mountPath: /etc/secrets
          readOnly: true
      volumes:
      - name: secret-volume
        secret:
          secretName: app-secrets

# 파일에서 Secret 읽기
spring:
  datasource:
    password: ${DB_PASSWORD:file:/etc/secrets/database-password}

HashiCorp Vault 통합

# Vault 의존성 추가
# build.gradle
implementation 'org.springframework.cloud:spring-cloud-starter-vault-config'

# Vault 설정
# bootstrap.yml
spring:
  cloud:
    vault:
      host: vault-server
      port: 8200
      scheme: https
      authentication: TOKEN
      token: ${VAULT_TOKEN}
      kv:
        enabled: true
        backend: secret
        profile-separator: '/'
        default-context: myapp
        application-name: myapp

# Vault에서 비밀 정보 조회
# Java 코드
@Configuration
public class VaultConfig {
    
    @Value("${database.password}")
    private String databasePassword;
    
    @Value("${jwt.secret}")
    private String jwtSecret;
    
    // 동적으로 비밀 정보 갱신
    @EventListener
    public void handleRefreshEvent(RefreshRemoteApplicationEvent event) {
        // 비밀 정보 갱신 로직
    }
}

# Vault CLI로 비밀 정보 저장
vault kv put secret/myapp/prod \
  database.password=securepassword \
  jwt.secret=super-secret-key

4.5 Profile 기반 빈 등록

@Profile 어노테이션 사용

// 개발 환경 전용 설정
@Configuration
@Profile("dev")
public class DevConfig {
    
    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .build();
    }
    
    @Bean
    public MailSender mailSender() {
        // 개발 환경에서는 콘솔에 메일 내용 출력
        return new ConsoleMailSender();
    }
}

// 프로덕션 환경 전용 설정
@Configuration
@Profile("prod")
public class ProdConfig {
    
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(environment.getProperty("spring.datasource.url"));
        config.setUsername(environment.getProperty("spring.datasource.username"));
        config.setPassword(environment.getProperty("spring.datasource.password"));
        config.setMaximumPoolSize(20);
        return new HikariDataSource(config);
    }
    
    @Bean
    public MailSender mailSender() {
        // 실제 SMTP 서버 사용
        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
        mailSender.setHost("smtp.gmail.com");
        mailSender.setPort(587);
        return mailSender;
    }
}

// 여러 프로파일 조건
@Configuration
@Profile({"dev", "test"})
public class NonProdConfig {
    
    @Bean
    public SecurityConfig securityConfig() {
        return SecurityConfig.builder()
            .disableCsrf()
            .permitAll()
            .build();
    }
}

// 프로파일 제외 조건
@Configuration
@Profile("!prod")
public class NonProdSecurityConfig {
    
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring()
            .requestMatchers("/h2-console/**")
            .requestMatchers("/actuator/**");
    }
}

// 복잡한 프로파일 조건
@Configuration
@Profile("prod & monitoring")
public class ProdMonitoringConfig {
    
    @Bean
    public MeterRegistry meterRegistry() {
        return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
    }
}

// 메서드 레벨 프로파일
@Configuration
public class DatabaseConfig {
    
    @Bean
    @Profile("dev")
    public DataSource devDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .build();
    }
    
    @Bean
    @Profile("prod")
    public DataSource prodDataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:postgresql://prod-db:5432/proddb")
            .build();
    }
}

조건부 빈 등록 고급 기법

// 커스텀 조건 클래스
public class ProductionCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment env = context.getEnvironment();
        return env.acceptsProfiles(Profiles.of("prod")) && 
               "true".equals(env.getProperty("app.production.enabled"));
    }
}

// 커스텀 조건 사용
@Configuration
@Conditional(ProductionCondition.class)
public class ProductionOnlyConfig {
    
    @Bean
    public ProductionService productionService() {
        return new ProductionService();
    }
}

// 프로퍼티 기반 조건
@Configuration
@ConditionalOnProperty(
    name = "app.feature.enabled", 
    havingValue = "true", 
    matchIfMissing = false
)
public class FeatureConfig {
    
    @Bean
    public FeatureService featureService() {
        return new FeatureService();
    }
}

// 클래스 존재 여부 기반 조건
@Configuration
@ConditionalOnClass(RedisTemplate.class)
public class RedisConfig {
    
    @Bean
    @ConditionalOnMissingBean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(jedisConnectionFactory());
        return template;
    }
}

// 프로파일 그룹 설정
# application.yml
spring:
  profiles:
    group:
      production: "prod,monitoring,security"
      development: "dev,debug,h2"
      testing: "test,testcontainers"

# 프로파일 그룹 활성화
java -jar app.jar --spring.profiles.active=production

5. Kubernetes 기초

5.1 Kubernetes 개요

Kubernetes의 핵심 개념

컨테이너 오케스트레이션

다수의 컨테이너를 자동으로 배포, 관리, 확장

선언적 설정

원하는 상태를 정의하면 자동으로 유지

자동 복구

장애 발생 시 자동으로 복구 및 재시작

확장성

부하에 따라 자동으로 스케일링

5.2 Deployment

기본 Deployment 설정

apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-app
  labels:
    app: spring-app
    version: v1.0
spec:
  replicas: 3
  selector:
    matchLabels:
      app: spring-app
  template:
    metadata:
      labels:
        app: spring-app
        version: v1.0
    spec:
      containers:
      - name: spring-app
        image: myregistry/spring-app:1.0.0
        ports:
        - containerPort: 8080
          name: http
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "prod"
        - name: DB_HOST
          value: "postgres-service"
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"

고급 Deployment 설정

apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-app-advanced
  labels:
    app: spring-app
    tier: backend
spec:
  replicas: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 2
  selector:
    matchLabels:
      app: spring-app
  template:
    metadata:
      labels:
        app: spring-app
        tier: backend
    spec:
      containers:
      - name: spring-app
        image: myregistry/spring-app:2.0.0
        ports:
        - containerPort: 8080
          name: http
        - containerPort: 8081
          name: management
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "prod"
        - name: JVM_OPTS
          value: "-Xmx768m -Xms512m"
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "1000m"
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8081
          initialDelaySeconds: 60
          periodSeconds: 30
          timeoutSeconds: 5
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8081
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 3
          failureThreshold: 3
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 15"]
      terminationGracePeriodSeconds: 30