[Docker] Docker Compose (+ Spring Boot +MySQL + MongoDB)

늦잠·2024년 6월 27일
0

목표 : 여러 도커 컨테이너로 구성한 어플리케이션의 환경을 Docker Compose를 통해 한번에 설정하기.

Docker Compose는 여러 도커 컨테이너를 사용하는 어플리케이션 개발에 유용한 도구다.

도커 컨테이너로 MySQLMongoDB를 연동시켰던 Spring Boot 프로젝트를 이어서 사용한다. (시리즈 참고)

DockerFile

우선 어플리케이션의 이미지를 빌드하기 위한 DockerFile부터 생성한다.

DockerFile은 도커 이미지빌드하는 방법을 적어놓은 텍스트 파일이다. 빌드 과정에서 DockerFile에 적은 명령이 순서대로 실행된다.

jar 파일 빌드

./gradlew clean build

프로젝트 디렉토리에서 터미널을 열고, 위 명령어를 통해 프로젝트를 빌드하여 jar 파일을 생성한다. 중간에 clean을 넣으면 기존 jar 파일을 지운 후 다시 생성한다.

DockerFile 생성

FROM openjdk:21

ARG JAR_FILE=build/libs/SimpleProject-0.0.1-SNAPSHOT.jar

COPY ${JAR_FILE} app.jar

EXPOSE 8080

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

간단하게 구성한 DockerFile이다.
Spring Boot 프로젝트 최상단에 DockerFile 파일을 만들고 내용을 적는다.

각 명령어의 의미는 다음과 같다.

  • FROM :
    이미지의 기반이 될 이미지를 명시한다. 이 프로젝트에선 Java 21을 사용했으므로 openjdk:21 이미지를 사용하였다. 필요에 따라 jdk/jre, alpine 버전 여부 등을 선택해 사용하자.

  • ARG :
    빌드 때 사용할 변수를 선언한다. 위에선 build/libs에 있는 jar 파일을 JAR_FILE 변수에 저장하였다.

  • COPY :
    파일이나 폴더를 생성할 컨테이너 안으로 복사한다. 위에선 JAR_FILE 변수에 저장되어 있는 jar 파일을 app.jar 라는 이름으로 복사하였다.

  • EXPOSE :
    컨테이너 속 어플리케이션이 열어둘 포트 번호를 지정한다.

  • ENTRYPOINT :
    이미지가 빌드 된 이후, 컨테이너에서 처음으로 실행될 명령어를 지정한다.

    이외에 자주 사용되는 명령어로는 RUN, CMD, VOLUME 등이 있다.

  • RUN :
    다음과 같은 형식으로 명령문을 실행한다. 이미지를 빌드할 때 필요한 요소를 다운받을 때 등 자주 쓰인다.

    # Shell form:
    RUN [OPTIONS] <command> ...
    # Exec form:
    RUN [OPTIONS] [ "<command>", ... ]
  • CMD :
    ENTRYPOINT와 유사하게 이미지가 빌드 된 이후, 컨테이너에서 가장 먼저 명령어를 실행한다. ENTRYPOINT는 항상 실행이 되지만 CMD는 변경이 될 수 있다. (참고 확인)

DockerFile 명렁어 : https://docs.docker.com/reference/dockerfile/

DockerFile로 이미지 빌드

docker build -t {이미지 이름} . 

DockerFile이 있는 디렉토리, 즉 프로젝트 최상단에서 터미널을 열고 다음 명령어를 실행하여 도커 이미지를 빌드한다.
-t {이미지 이름} 부분은 이미지 이름을 위한 옵션이라 빼도 상관없다.
docker desktop이나 docker images 명령문을 통해 이미지 목록을 확인할 수 있다.

이후 docker run을 통해 컨테이너를 생성할 수 있지만 여기서 사용하는 프로젝트의 경우, 다른 컨테이너 속 MySQL이나 MongoDB와 연결되지 못해 에러가 발생한다.

Docker Compose

이제 Docker Compose를 사용해보자.

설치

링크 : https://docs.docker.com/compose/install/

공식 문서에서 소개하는 방법은 3가지가 있다.

1. Docker Desktop 설치 :
Docker Desktop에 포함되어 있으므로 이를 설치한다.

Docker Desktop 설치 관련 글 : https://velog.io/@woolzam/%EB%8F%84%EC%BB%A4-%EB%8D%B0%EC%8A%A4%ED%81%AC%ED%86%B1-%EC%84%A4%EC%B9%98

2. Compose 플러그인 설치 :
도커엔진과 도커 CLI가 설치되어 있을 경우.

https://docs.docker.com/compose/install/linux/#install-using-the-repository

3. Compose Standalone 설치:

https://docs.docker.com/compose/install/linux/#install-the-plugin-manually

1번이 docker 공식 문서에서 가장 추천하는 방법이며, 3번이 가장 덜 추천하는 방법이다.

docker-compose.yml

services:
  db_mysql:
    image: "mysql:latest"
    container_name: db_mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: "0000"
      MYSQL_DATABASE: "simpledb"
      MYSQL_ROOT_HOST: '%'
    networks:
      - simple_network

  db_mongodb:
    image: "mongo:latest"
    container_name: db_mongodb
    ports:
      - "27017:27017"
    networks:
      - simple_network

  simple-web:
    depends_on:
      - db_mysql
      - db_mongodb
    build: .
    ports:
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://db_mysql:3306/simpledb
      SPRING_DATASOURCE_USERNAME: "root"
      SPRING_DATASOURCE_PASSWORD: "0000"
    networks:
      - simple_network

networks:
  simple_network:

DockerFile을 만들었을 때와 마찬가지로, 프로젝트 최상단 디렉토리에 docker-compose.yml 파일을 생성한다.

docker-compose.yml를 통해 여러 개의 컨테이너를 구성하고 설정한다.

하나씩 짚어보자.

  • services :
    serviecs 속성에선 어플리케이션이 사용하는 각 service를 정의한다. 위에서 services 아래에 정의한 db_mysql, db_mongodb, simple-web이 service의 이름이다.
    service란 독립적인 기능 요소라고 생각하면 될 것 같다. 각 service는 컨테이너를 생성하게 된다.

    • db_mysql :
      MySQL db 기능이 구현될 서비스이다.
      • image :
        각 service의 기반이 되는 도커 이미지이다. db_mysql 서비스는 mysql:latest 이미지를 기반으로 생성된다.
      • container name :
        컨테이너가 생성될 때 붙여줄 이름이다.
      • ports :
        컨테이너 바깥, 호스트와 컨테이너 사이에서 포트 번호를 매핑한다. 왼쪽이 호스트의 포트 번호, 오른쪽이 컨테이너의 포트 번호이다.
      • environment :
        환경 변수를 지정한다. db_mysql서비스는 여기서 root 사용자의 비밀번호, 생성할 데이터베이스, root로 접속이 가능한 호스트를 지정하였다.
      • networks :
        가장 아래 networks에서 설명하겠따.
    • db_mongodb :
      mongodb 기능이 구현될 서비스.
      db_mysql 서비스와 딱히 다른 점은 없다.
    • simple-web :
      spring boot가 실행될 서비스이다.
      • depends_on :
        이 서비스를 실행하기 위해 필요한 서비스 목록이다. 이를 통해 서비스의 실행 및 종료 순서를 정할 수 있다.
      • build :
        소스로부터 이미지를 생성한다. 여기에선 같은 디렉토리를 의미하는 .를 적었으니, 같은 디렉토리에 있는 DockerFile로부터 이미지를 생성하여 이 이미지를 기반으로 서비스를 빌드한다.
      • environment :
        마찬가지로 환경 변수를 설정한다. 여기에서 중요한 건 MySQL db의 주소이다. 만약 이전에 db를 localhost에서 연결했다면 localhost 대신 db의 컨테이너 이름을 적어야한다.
        ex) SPRING_DATASOURCE_URL: jdbc:mysql://{mysql 컨테이너 이름}:{연결한 포트}/{데이터베이스 이름}
  • networks :
    서비스 간 통신하는 영역이다. 최상단 요소인 networks에서 network 목록을 만들어 두고, 각 서비스 아래의 networks에서 통신할 network 목록을 지정한다. 같은 network를 지정한 서비스끼리는 통신이 가능하다.

spring boot 설정 파일 변경

spring:
  #mysql
  datasource:
    #url: jdbc:mysql://db_mysql:3306/simpledb?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Seoul&zeroDateTimeBehavior=convertToNull&rewriteBatchedStatements=true
    #driver-class-name: com.mysql.cj.jdbc.Driver
    #username: root
    #password: '0000'

  data:
    mongodb:
      port: 27017
      #host: localhost
      host: db_mongodb
      database: simplemongodb
  jpa:
    database-platform: org.hibernate.dialect.MySQLDialect
    open-in-view: false
    show-sql: true
    hibernate:
      format_sql: true
      ddl-auto: update

더이상 localhost를 통해 db에 연결되는 것이 아니므로, 설정파일인 application.yml을 다음과 같이 수정하였다.

(application.yml보다 docker-compose.yml 설정이 우선된다는 글을 읽긴했는데 레퍼런스는 못찾았다.)

mongodb의 host도 mongodb 서비스의 컨테이너 이름으로 교체하였다.

실행 및 테스트

docker compose up

docker-compose.yml 파일이 있는 디렉토리에서 터미널을 열고 다음 명령어로 docker compose 작업을 실행한다.

(생기는 문제점은 아래 확인)

잘 되었다면 테스트했을 때 로컬의 8080 포트와 spring boot 서비스 컨테이너의 8080포트를 연결시켰기 때문에 기존 주소로도 이상없이 웹 페이지가 나온다.

테스트 함수로 각 db에 데이터를 삽입해본 후, 컨테이너 안에서 확인해보면 데이터가 잘 들어갔음을 확인할 수 있다.
끝!

문제점

끝..이라고 썼지만 문제점이 하나 있는데, docker compose up을 통해 처음 어플리케이션을 구동하면 다음과 같이 에러가 발생한다.

하지만 이후 생성된 컨테이너를 정지하였다가 다시 실행하면 잘 동작하는 것을 볼 수 있다.

예상되는 원인으로, mysql 서비스의 컨테이너가 제대로 구성되기 전에 웹 부분이 동작을 시도하다 오류가 났을 가능성이 있다.

해결하기 위한 방안으론 simple-web 서비스의 depends_on 속성에 service_healthy 같은 옵션을 활용하면 되지 않을까 싶다.

시도해보고 글을 수정하겠다.

이 밑에 더 글이 없다면면 귀찮아서 시도도 안 해본거임

문제점 수정

역시 MySQL 문제였던 것 같다.
depends_on 속성을 service_healthy로 하면 MySQL 서비스가 정상인지 체크한 후, 정상일 때 web 서비스가 시작된다.

정상인지 체크하는 방법은 MySQL 서비스 쪽에 명시해야 한다.

옵션은 다음과 같다.

db_mysql 서비스에 추가할 옵션 :

	healthcheck:
      test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
      timeout: 20s
      retries: 10

simple-web 서비스 쪽 옵션 :

 	depends_on:
      db_mysql:
        condition: service_healthy

최종 docker_compose.yml 파일

services:
  db_mysql:
    image: "mysql:latest"
    container_name: db_mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: "0512"
      MYSQL_DATABASE: "simpledb"
      MYSQL_ROOT_HOST: '%'
    healthcheck:
      test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
      timeout: 20s
      retries: 10
    networks:
      - simple_network

  db_mongodb:
    image: "mongo:latest"
    container_name: db_mongodb
    ports:
      - "27017:27017"
    networks:
      - simple_network

  simple-web:
    depends_on:
      db_mysql:
        condition: service_healthy
      db_mongodb:
        condition: service_started
    build: .
    ports:
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://db_mysql:3306/simpledb
      SPRING_DATASOURCE_USERNAME: "root"
      SPRING_DATASOURCE_PASSWORD: "0512"
    networks:
      - simple_network


networks:
  simple_network:

참고

profile
피카

0개의 댓글