안녕하세요 독자님들.
오늘은 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. 컨테이너와 컨테이너 사이에서의 통신
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는 싱글 컨테이너를 관리하고 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를 사용한 경험을 공유해드렸습니다.
글에 내용에서 빠진 부분이나 궁금한점은 댓글로 달아주세요 !
긴 글 읽어주셔서 감사합니다.
댓글