[V1] Docker - 100-hours-a-week/KTB4-10th-Cloud GitHub Wiki

- Dockerfile
→ Spring Boot Backend 이미지를 어떻게 만들지 정의
- docker-compose.yml
→ Backend 이미지 + DB 이미지를 EC2에서 어떻게 같이 실행할지 정의

[ 1. Docker File ]

1.1. 작성시 고려사항

  1. 빌드에는 필요하지만, 최종 컨테이너 이미지에는 필요 없는 환경을 제거할 수 있도록 단계를 2개로 나눠 이미지 만드는 방법

    • BUILD PHASE / RUN PHASE

    → 사용이유 : 가벼운 크기의 이미지 사용가능

  2. 도커 파일의 순서 == 실행 순서

    • 도커가 명령어의 결과를 레이어로 저장
    • 해당 레이어가 쌓여 도커 이미지를 만듦
    • 만약 하단 레이어의 코드가 변경 → 그 이후는 전부 재실행

    → 변경이 없는 것들을 최대한 먼저 실행

    • “도커 레이어 캐시”

      • 위에서 부터 실행하며 만들어진 결과를 재사용해서 다음 빌드를 빠르게 하는 기능
    • “캐시 마운트”

      https://docs.docker.com/build/cache/optimize/

      • 일반적인 도커
        • 명령어와 파일이 레이어 구축이후 변경 → 레이어 무효화 및 빌드 과정에서 레이어 재구축

      ⇒ 캐시마운트 : 빌드 중에 사용할 영구 캐시 위치 지정

      • 여러번 읽고 쓰기 가능
      • 레이어 재구성시에도 새 패키지나 변경된 패키지만 다운하면 됨
  3. Daemon

    • 백그라운드에서 계속 살아있으면서 작업 요청을 기다리는 프로세스
    • ex) gradle daemon
      • 첫 시행에서 gradle 프로세스 띄우고 종료하지않고 백그라운드에 유지 (빌드 속도에 도움)
    • 빌드에서 no daemon이유
      • jar만들고 끝내는 일회성 작업에 계속 띄워둘 필요가 없음
  4. 사용자

    • 별다른 설정이 없으면 도커파일 → root 권한 실행의 가능성
      • 만약 컨테이너 탈취되면 호스트에 접근이 되는 보안 취약점

    ⇒ 실행권한 최소화가 필요

    --system : 일반 사용자용 그룹X, 애플리케이션, 서비스 실행용 시스템 그룹
    
    useradd --system --gid spring spring
    -> spring이라는 그룹의 spring이라는 사람
    

1.2. 도커 파일

# 1. Build(멀티스테이지 빌드)
# 빌드를 위해 도구 모음 jdk 제공
FROM eclipse-temurin:21-jdk AS builder

WORKDIR /app

COPY gradlew .
COPY gradle gradle
COPY build.gradle.kts .
COPY settings.gradle.kts .

#의존성을 먼저 다운로드
# cache mount를 이용해 Gradle 캐시를 빌드 간 재사용

RUN --mount=type=cache,target=/root/.gradle \
		./gradlew dependencies --no-daemon

COPY src src

#jar생성도 cache mount
RUN --mount=type=cache,target=/root/.gradle \
    ./gradlew bootJar --no-daemon

# 2. Run
# 자바 21 환경 jre 제공
FROM eclipse-temurin:21-jre

WORKDIR /app

# 애플리케이션 실행용 Non-root 사용자/그룹 생성
RUN groupadd --system kgb \
    && useradd --system --gid kgb kgbUser

#위의 빌드단계에서 생성된 jar만 복사
#jar의 소유권도 지정
COPY --from=builder --chown=kgbUser:kgb /app/build/libs/*.jar app.jar

#사용자 지정
USER kgbUser

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]

[ 2. Docker Compose ]

- 고려사항 (변경할 사항)
    - 현재 환경변수는 백엔드 env.example 기반
        - 배포 DB 생성 이후 password 추가 필요
    - 실제 구성되는 백엔드에 따라 환경도 추가하거나 삭제가 필요
        - ai
  • 도커 컴포즈
    • 여러개의 도커 컨테이너를 하나의 파일로 정의하고 동시에 실행 및 관리할 수 있는 도구
  • 고려사항
    • 로컬에서 도커파일 이용해 직접 빌드하는 방식은 제외

      → GHCR에서 이미지 pull 해옴

      → 이미지 태그는 CD 단계에서 주입

      • ci-cd 하나의 yaml 사용으로 못찾는 문제 방지
services:
  backend:
    # CI에서 GHCR에 Push한 Backend 이미지 사용
    # GHCR_OWNER, IMAGE_TAG는 CD에서 동적으로 전달
    image: ghcr.io/${GHCR_OWNER}/ktb4-10-be:${IMAGE_TAG}

    # Host -> Backend Container
    ports:
      - "8080:8080"

    environment:
      # Docker 내부에서 db 접근(컴포즈 서비스명 사용)
      DB_URL: jdbc:mysql://db:3306/kgb
      DB_USERNAME: ${DB_USERNAME}
      DB_PASSWORD: ${DB_PASSWORD}

      # S3
      S3_BUCKET_NAME: kgb-s3-storage
      AWS_REGION: ap-northeast-2
      
      # AI
      AI_SERVER_URL: ${AI_SERVER_URL}

    # MySQL 상태가 확인 후 Backend 시작
    depends_on:
      db:
        condition: service_healthy

    # Container 비정상 종료 또는 EC2 재부팅 시 자동 재시작
    restart: unless-stopped

  
  db:
    image: mysql:8.4
    environment:
      # 최초 DB 생성 시 생성할 Database
      MYSQL_DATABASE: kgb
      MYSQL_USER: ${DB_USERNAME}
      MYSQL_PASSWORD: ${DB_PASSWORD}
			MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      
    # DB 데이터를 Container와 분리하여 영속화 
    volumes:
      - db-data:/var/lib/mysql

    # MySQL이 실제 연결 가능한 상태인지 확인
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"
        ]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

    # Container 비정상 종료 또는 EC2 재부팅 시 자동 재시작
    restart: unless-stopped

# DB Container가 삭제/재생성되어도 DB 데이터 유지
volumes:
  db-data: