Docker를 활용한 배포 - Docker Compose와 Docker Network

Jihyeon Yun·2024년 11월 15일
0

1. docker-compose.yml을 활용한 배포

docker-compose.yml 작성

Docker를 활용해 MySQL 이미지를 가져와 실행하고, 이를 spring 이미지에서 이용하도록 docker-compose.yml 파일을 작성했다.

services:
  mysql:
    image: mysql:latest
    container_name: mysql-example
    environment:
      MYSQL_ROOT_PASSWORD: ${db_root_password}
      MYSQL_DATABASE: ${db_name}
      MYSQL_USER: ${db_username}
      MYSQL_PASSWORD: ${db_password}
    ports:
      - "3306:3306"

  spring-boot-app:
    image: ${생성한_Docker_image_이름}
    container_name: spring-boot-container
    ports:
      - "8080:8080"
    depends_on:
      - mysql
    environment:
      DB_URL: jdbc:mysql://mysql:3306/${db_name}
      DB_USERNAME: ${db_username}
      DB_PASSWORD: ${db_password}
      SPRING_PROFILES_ACTIVE: prod

오류 발생


MySQL 컨테이너는 계속해서 실행되지만 spring 이미지가 계속해서 종료 되는 문제가 발생했다.

로그를 살펴보니 MySQL이 제대로 시작되기 전에 Spring 컨테이너가 실행이 되면서 DB 연결을 시도해서 자꾸 멈추는 것을 확인했다.

찾아보니 depends_on의 경우는 Docker compose에서 '단순히 서비스의 시작 순서를 설정하는 것이지 서비스의 준비상태까지 보장하는 것은 아니' 라고 한다.

따라서 정상적으로 실행시키기 위해서는 mysql이 실제로 통신가능한 상태인지 확인하는 과정이 필요하다.

해결 방법

1) wait-for-it 사용

wait-for-it은 docker에서 여러 Docker Container들을 동시에 실행해야 할 때 각각의 실행 순서를 정해준다.
https://github.com/vishnubob/wait-for-it

github에서 wait-for-it.sh 다운로드

curl -o wait-for-it.sh https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh
chmod +x wait-for-it.sh

docker-compose.yml 또는 Dockerfile 수정

Dockerfile.yml
spring-boot-app:
  image: elice_aws_docker_practice
  container_name: spring-boot-container
  ports:
    - "8080:8080"
  depends_on:
    - mysql
  environment:
    DB_URL: jdbc:mysql://mysql:3306/elice
    DB_USERNAME: elice
    DB_PASSWORD: elice1234
    SPRING_PROFILES_ACTIVE: prod
  entrypoint: ["wait-for-it.sh", "mysql:3306", "--", "java", "-jar", "app.jar"]
Dockerfile
FROM openjdk:17-jdk-slim

# wait-for-it.sh 복사
COPY wait-for-it.sh /usr/local/bin/wait-for-it.sh
RUN chmod +x /usr/local/bin/wait-for-it.sh

# 앱 실행 명령어를 wait-for-it.sh로 래핑
CMD ["wait-for-it.sh", "mysql:3306", "--", "java", "-jar", "app.jar"]
  • MySQL 컨테이너가 완전히 실행되고 난 뒤에 jar 파일을 실행한다.

2) HealthCheck를 사용한 컨테이너 상태 확인

services:
  mysql:
    image: mysql:latest
    container_name: mysql-example
    environment:
      MYSQL_ROOT_PASSWORD: ${db_root_password}
      MYSQL_DATABASE: ${db_name}
      MYSQL_USER: ${db_username}
      MYSQL_PASSWORD: ${db_password}
    ports:
      - "3306:3306"
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 3

  spring-boot-app:
    image: ${생성한_Docker_image_이름}
    container_name: spring-boot-container
    ports:
      - "8080:8080"
    depends_on:
      mysql:
        condition: service_healthy
    environment:
      DB_URL: jdbc:mysql://mysql:3306/${db_name}
      DB_USERNAME: ${db_username}
      DB_PASSWORD: ${db_password}
      SPRING_PROFILES_ACTIVE: prod
  • HealthCheck를 사용하는경우 MySQL이 정상 작동 상태를 반환하기 전까지 spring-boot-app을 시작하지 않는다

결과

나는 HealthCheck를 활용하는 방법을 선택했다.

위의 사진을 살펴보면 처음 실행했을 때는 mysql-examplespring-boot-container의 로그가 번갈아 가며 찍히다가 종료되었는데, 이번에는 mysql이 전부 실행된 뒤에 spring이 실행되는 모습을 볼 수 있다.

localhost를 통해서 접근도 잘 되는 것을 확인할 수 있다.


2. Docker Network를 활용한 배포

이 방법은 이미 MySQL이나 다른 서비스가 Docker를 이용해 배포되고 있을 때 사용하면 좋을 것 같다. docker-compose를 통해서 docker 컨테이너를 생성 및 실행하는 경우 docker network가 새로 생성되기 때문!

Docker Network 생성

docker network create ${network_name}
docker network ls

MySQL 컨테이너 생성

docker pull mysql
docker run -d --name mysql-container --network ${위에서_설정한_네트워크명} -e MYSQL_ROOT_PASSWORD=${password} mysql:latest
docker ps
docker network inspect ${네트워크명}

MySQL 컨테이너에 접속해 DB 생성

docker exec -it <mysql-container-이름> bash # bash 터미널 실행
mysql -u root -p

위의 명령어를 사용하면 MySQL 컨테이너의 터미널에 접근이 가능하다.
이후 터미널에서 mysql을 실행하고, DB를 생성해준다.

CREATE DATABASE <DB명>

docker-compose.yml 파일 수정

1) 이미 생성된 Docker image를 사용하는 경우

services:
  spring-app:
    image: your-existing-image:latest  # 사용하려는 Docker 이미지 이름과 태그
    container_name: spring-app-container
    ports:
      - "8080:8080"
    nvironment:
      SPRING_DATASOURCE_URL: jdbc:mysql://${mysql_container_이름}:3306/${db_이름}
      SPRING_DATASOURCE_USERNAME: ${db_username}
      SPRING_DATASOURCE_PASSWORD: ${db_password}
    networks:
      - docker-net-prac

networks:
  docker-net-prac:
    external: true

2) Docker image를 생성하는 경우

services:
  spring-app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: spring-app-container
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://${mysql_container_이름}:3306/${db_이름}
      SPRING_DATASOURCE_USERNAME: ${db_username}
      SPRING_DATASOURCE_PASSWORD: ${db_password}
    ports:
      - "8080:8080"
    networks:
      - docker-net-prac

networks:
  docker-net-prac:
    external: true

docker-compose.yml 파일 설명

context의 역할

  1. Docker 빌드 컨텍스트 경로 지정
    • Docker 데몬이 해당 디렉토리를 기준으로 Dockerfile과 빌드에 필요한 파일을 찾음
  2. COPY 명령어에서 사용
    • Dockerfile 내에서 COPY나 ADD 명령어를 사용할 때, context로 지정된 디렉토리 기준으로 파일 경로를 참조
  3. 빌드 파일 전송
    • Docker는 context에서 지정한 경로에 있는 모든 파일을 Docker 데몬에게 전송
    • .dockerignore 파일을 사용하면 불필요한 파일들을 제거 가능

external option

  • 이미 존재하는 네트워크를 사용하는 경우 true로 설정함
  • Docker compose는 기본적으로 고유한 네트워크를 생성하기 때문에 external: true를 통해 이 동작을 막는다.
  • 동일한 네트워크를 생성하려고 하면 충돌 에러가 발생할 수 있기 때문에 Compose가 네트워크를 새로 생성하지 않도록 한다.

[참고자료]

profile
Better than Yesterday, Further than Before

0개의 댓글