본문 바로가기
Backend

docker-compose로 간단한 프로젝트 배포하기

by 조니 Johnny 2022. 4. 12.

이게 왜 안돼? 아니 이게 된다고?

 

안녕하세요 독자님들. 

오늘은 docker, docker-compose 를 이용해서 간단한 백엔드 애플리케이션 배포한 경험을 스토리에 담으려고 합니다.

 

이 글은 도커에 대한 기본적인 이해가 있는 분들을 대상을 작성했습니다.

잘못된 정보에 대한 지적이나 기술에 대한 건전한 토론은 환영입니다. 🤗

 

주접은 뒤로하고 본론으로 들어갈게요.

회사 과제를 하면서 도커로 백엔드 애플리케이션을 배포할 일이 생겼어요.

프로젝트를 할 때 redis나 mysql을 도커 컨테이너로 돌리면서 했었던 적이 잦아 쉽게 할 줄 알았는데..

docker 컨테이너 가상화에 3일을 들인거라면 저는 꽤나 애를 먹었다고 할 수 있을 것 같아요.

 

이 글을 읽는 독자님들은 3일을 꼭 세이브 하시길 바랍니다. 🙇🏻‍♂️

 

index

1. 문제 상황

2. 컨테이너와 컨테이너 사이에서의 통신 ?

3. docker와 docker-compose 적용 사례

4. 마무리

 

 

 

1. 문제상황

I. 과제는 언어에 관계없이 도커 컨테이너로 실행할 수 있도록 제출해주세요.

II. DB의 DDL 소스를 첨부해주세요.

 

- 개발환경

springboot 2.6.3

docker 20.10.8

MySQL 5.7

spring-data-redis 2.6.5

 

 

2. 컨테이너와 컨테이너 사이에서의 통신

container to container

Docker 컨테이너는 격리된 환경에서 돌아가기 때문에 기본적으로 다른 컨테이너와의 통신이 불가능합니다.

또한 컨테이너는 자신만의 프로세스, 네트워크, 가상머신을 가집니다.

 

ex) mysql 컨테이너 - springboot-app 컨테이너 사이에 통신이 가능하려면 별도의 네트워크를 구성해야 합니다.

네트워크 옵션은 도커 공식문서에 정리가 잘 되어 있으니 참고하세요.

 

나는 처음에 도커 컨테이너가 docker 내부 로직에 따라 다른 ip가 부여되는줄 모르고 (localhost 에서 돌아가는 줄 알았다)

docker run redis ... -> docker run mysql ... -> docker run spring-app 순으로 실행시키면 되겠다는 막연한 생각을 했습니다.

 

이 포스팅을 보고 컨테이너에 기본적인 동작원리를 알게 된 당신은 오늘의 목표에 반절 이상 도달했어요

 

 

그럼 컨테이너 커스텀 네트워크를 구성하면 되겠네요?

 

물론 네트워크를 생성/구성해서 해결할 수 있습니다.

네트워크를 생성해서 내가 Grouping 하고자 하는 container 옵션으로 추가하면 될 것 같아요!

 

네트워크를 생성하는 명령어는 아래와 같습니다.

docker network create {network-name}

 

컨테이너를 실행할 때 네트워크를 지정하는 방법은 아래와 같습니다.

docker network connect {network-name} {container-name}

 

네트워크를 직접 생성하여 공유하게 하는 방법은 제가 이용한 솔루션이 아니니 이 포스팅에서는 넘어가도록 하겠습니다.

추가적으로 docker (custom) network 대해서 알고 싶은 분들은 도커 공식 튜토리얼을 참고해주세요 🙂

 

왜 근본적인 해결방법이 아닐까요?

쉘 스크립트를 작성해두지 않는 이상 매번 env 옵션이 포함된 긴 docker run 명령어를 쳐야 합니다.

스프링 애플리케이션 하나에 의존하는 third party 애플리케이션이 늘어나면 늘어날수록 굉장히 번거로워지는 일입니다.

 

조금 더 읽어보세요!
docker-compose가 반복되는 command 와 group 네트워크 구성을 한번에 해결해 줄거예요.

 

 

3. docker 와 docker-compose  

docker-compose

docker는 싱글 컨테이너를 관리하고 command line에서 명령어를 실행할 수 있어요.

docker-compose는 .yml 파일 기반으로 멀티 컨테이너를 관리할 수 있고 .yml 파일에 명령어를 적어서 컨테이너를 정의하고 관리해요.

 

또한 docker-compose는 이번 포스팅의 논점인 컨테이너간 "네트워크의 불일치"를 해결해줘요.

docker-compose는 docker 와 다르게 하나의 디폴트 네트워크에 compose에 속하는 모든 컨테이너를 연결해요.

시작하기 

docker가 설치 돼 있다는 가정하에 진행할게요.

저는 macOS 환경에서 개발하기 때문에 homebrew 를 통해 설치해줄게요.

brew install docker-compose

 

내가 빌드 하고자 하는 project root 에 dockerfile과 docker-compose.yml을 생성해 주세요.

.
├── Dockerfile
├── docker-compose.yml

❗️ DockerFile 의 이름 spell-check 를 꼭 해주세요.

 

 

spirngboot 애플리케이션의 DockerFile 내용은 아래와 같아요.

# docker start with a base image containing java runtime
FROM openjdk:11

# Add Author information
LABEL repository="https://github.com/jyeonjyan"
LABEL maintainer="jyeonjyan"

# Add a volume to /tmp
VOLUME /tmp

# Make port 8080 available to the world outside this container
EXPOSE 8080

# The application's jar file
ARG JAR_FILE=build/libs/app-ex-0.0.1-SNAPSHOT.jar

# Add the application's jar to the container
ADD ${JAR_FILE} app-ex.jar

# Run the jar file
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app-ex.jar"]

 

DockerFile에 나온 옵션(FROM, LABEL)들은 꼭 알아야 하는 내용들이니 알아서 꼭 찾아보시길 권장합니다.

여기를 참고하세요.

 

 

해당 DockerFile은 스프링 애플리케이션을 이미지로 만드는 역할만 한다.

DockerFile 설정을 간단히 해석하자면..

이 이미지는 openjdk:11 베이스 이미지로 사용하고
이 DockerFile로 만들어진 도커 이미지는 8080 포트로 외부에 공개할 예정이다.

 

아직.

1. 앱이 동작할 때

2. MySQL, Redis 를 켜야하고 셋팅(DDL) 되어야

한다는 요구사항은 만족하지 못했어요.

 

이제 두가지 옵션을 만족하기 위해서 docker-compose.yml 을 작성해 볼게요. ⬇️

 

version: "3.8"

services:
  app:
    container_name: spring-app
    build:
      context: .
    restart: on-failure
    env_file:
      - ./.env
    ports:
    - $SPRING_LOCAL_PORT:$SPRING_DOCKER_PORT
    environment:
      SPRING_APPLICATION_JSON: '{
        "spring.datasource.url" : "jdbc:mysql://mysql:3306/test_db?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8&autoReconnection=true",
        "spring.datasource.username" : "$MYSQLDB_USER",
        "spring.datasource.password" : "$MYSQLDB_ROOT_PASSWORD",
        "spring.jpa.properties.hibernate.dialect" : "org.hibernate.dialect.MySQL57Dialect",
        "spring.jpa.hibernate.ddl-auto" : "update"
      }'
    stdin_open: true
    tty: true
    depends_on:
      - mysql
      - redis

  mysql:
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
    container_name: mysql-container
    image: mysql:5.7
    restart: unless-stopped
    env_file:
      - ./.env
    volumes:
      - ./schema:/docker-entrypoint-initdb.d
    environment:
      - MYSQL_ROOT_PASSWORD=$MYSQLDB_ROOT_PASSWORD
      - MYSQL_DATABASE=$MYSQLDB_DATABASE
    ports:
      - 3306:3306

  redis:
    container_name: redis-container
    image: redis:latest
    restart: unless-stopped
    ports:
      - 6379:6379
    command: redis-server

 

docker-compose 에도 다양한 옵션들이 존재합니다.

옵션에 대한 상세한 설명은 이 글의 내용에서 다루는게 적절하지 않아 제 세팅에 대한 설명만 해드리겠습니다.

 

1. app

2. mysql

3. redis 

 

순으로 설명하도록 하겠습니다.

 

app service

* app의 container name은 spring-app으로 지정하겠습니다.

* app의 빌드 context는 docker-compose.yml 이 존재하는 root/DockerFile 을 참조합니다.

* app이 오류 코드로 인해 실패한 경우에만 컨테이너를 restart 합니다.

* 별도의 env 파일을 만들어 민감한(DB username, password.. etc)를 저장합니다.

* 포트 또한 env 파일에 미리 지정한 값을 가져옵니다. 참고 (https://docs.docker.com/compose/environment-variables/)

* 해당 app 은 mysql, redis service에 의존하기 때문에 (mysql, redis 컨테이너가 선행되어야 합니다)

* environment 옵션을 추가적으로 사용하여 springboot app의 datasource-url의 host를 mysql service 이름으로 지정하였습니다.

 

 

mysql service

* 과제의 mysql 버전은 5.7로 제한돼 있었습니다.

* mysql은 추가적인 커맨드(utf8mb4 설정)를 추가하여 실행할 예정입니다.

* mysql 컨테이너는 unless-stopped 옵션을 사용하고 이 옵션은 always와 비슷합니다. (https://docs.docker.com/config/containers/start-containers-automatically/)

* volumes: ./schema:/ ... 는 해당 mysql 컨테이너가 실행될 때 실행시키고 싶은 sql 스크립트를 실행시킬 수 있게 하는 옵션입니다.
   저는 프로젝트 루트에 schema 디렉토리를 별도로 두고 하위에 sql 스크립트들을 관리했습니다.

 

redis

* 가장 최신버전의 redis 이미지를 받아 실행합니다.

 

 

이렇게 하면..

1. app service의 initdb.d 옵션을 통해 MySQL 컨테이너가 실행되는 시점에 DDL이 가능해졌습니다.

2. app service의 depends_on 옵션을 통해 MySQL 과 Redis 컨테이너를 선행 하였습니다.

 

 

확인해볼까요?

# 프로젝트 루트로 이동
cd ./myProject

# 빌드
docker-compose build --no-cache

# 도커 컴포즈 실행
docker compose up -d

 

마무리

어머! 이건! 꼭 적용시켜야해 ~

 

네 이렇게 해서 저는 Docker를 통해 springboot-app, MySQL, Redis 를 한꺼번에 동시에 실행 시켰습니다.

docker, docker-compose 가 제가 포스팅에서 다룬 역할만을 하는건 절대 아닙니다.

지금까지 제가 간단히 docker, docker-compose를 사용한 경험을 공유해드렸습니다.

 

글에 내용에서 빠진 부분이나 궁금한점은 댓글로 달아주세요 !

긴 글 읽어주셔서 감사합니다.

댓글