들어가기 전에
기존에는 아래와 같이 AWS Beanstalk을 사용하여 개발용 SpringBoot(Backend)을 관리하였고, DB는 AWS RDS를 이용하였습니다.
AWS 초심자다보니, 운영 어플리케이션을 AWS에 올리기 전에 미리 AWS를 사용해보자는 취지로 위와 같은 구조로 진행했었습니다. 하지만, 비용 이슈로 인해 개발용은 각자 로컬에서 띄우는 것이 맞다는 생각이 들었습니다. 이에 docker-compose
를 통해 SpringBoot와 DB를 하나로 묶어서 띄워서 테스트할 수 있는 환경을 구성해 보았습니다.
- Front-End에서 Back-End 어플리케이션을 띄워 API 테스트가 필요했던 터라,
docker-compose
로 어플리케이션과 DB를 함께 묶어 손쉽게 테스트를 진행할 수 있도록 했습니다.
이제부터, Docker-Compose
를 이용한 SpringBoot와 PostgreSQL 컨테이너를 한 번에 띄워보도록 하겠습니다.
하기 작업은 SpringBoot 어플리케이션이 Git Repository에 존재하며, docker는 이미 PC에 설치되어 있다고 가정합니다.
Docker-Compose를 이용한 SpringBoot와 PostgreSQL 이미지 한 번에 관리하기
Docker-Compose란?
Docker-Compose
는 여러 컨테이너로 실행되는 어플리케이션을 정의하고 관리하기 위한 도구입니다. Docker-Compose
를 이용하면 여러 컨테이너를 project
와 service
단위로 관리할 수 있습니다.
project
및 service
설정은 docker-compose.yml
파일에 의해 정의되며, Docker-Compose
실행 시 해당 YAML
파일을 읽어 컨테이너를 조작합니다.
Docker-Compose 커맨드
커맨드 | 의미 |
---|---|
docker-compose build | 서비스 빌드(혹은 재구축) |
docker-compose bundle | compose 파일로 docker 번들 만들기 |
docker-compose config | compose 파일 확인 후 보기 |
docker-compose create | 서비스 만들기 |
docker-compose down | 컨테이너를 중지하고 관리하는 자원 제거 |
docker-compose events | 컨테이너에 발생한 이벤트 보기 |
docker-compose exec | 실행 중인 컨테이너에서 커맨드 실행 |
docker-compose help | 커맨드 도움말 보기 |
docker-compose images | 이미지 목록 보기 |
docker-compose kill | 컨테이너 시그널 송신 |
docker-compose logs | 컨테이너의 출력 보기 |
docker-compose pause | 서비스 일시 정지 |
docker-compose port | 공개된 포트 할당 보기 |
docker-compose ps | 컨테이너 목록 보기 |
docker-compose pull | 서비스의 이미지 pull |
docker-compose push | 서비스의 이미지 push |
docker-compose restart | 서비스 다시 시작 |
docker-compose rm | 정지 중인 컨테이너 삭제 |
docker-compose run | 한 번만 작동하는 커맨드 실행 |
docker-compose scale | 서비스 컨테이너 수 변경 |
docker-compose start | 서비스 시작 |
docker-compose stop | 서비스 정지 |
docker-compose top | 실행 중인 프로세스 정보 보기 |
docker-compose unpause | 일시 정지 중인 컨테이너 다시 시작 |
docker-compose up | 서비스를 만들고 시작 |
docker-compose version | Docker Compose 버전 정보 보기 |
디렉토리 구조
필자가 SpringBoot와 PostgreSQL을 Docker-Compose
로 관리하기 위해 적용한 구조는 아래와 같습니다.
dev-docker
│
└───docker-compose.yml
│
└───.env
│
└───app/
│ │
│ └───bread-map-backend/
│ └───해당 디렉토리 하위에 git clone을 통해 sringboot 프로젝트 레포를 받아주세요!
│
└───db/
│
└───Dockerfile-postgres
│
└───data/
│
└───init.d/
└───init-user-db.sh
- SpringBoot 어플리케이션 레포지토리 이름이 bread-map-backend여서 위와 같은 구조로 진행했으며, 다른 이름 역시 사용 가능합니다.
- database는 postgreSQL을 사용했으며, docker에 올라와 있는 공식 postgreSQL 이미지를 이용했습니다.
- 이 경우 postgreSQL에 대한
Dockerfile
은 필요하지 않지만, 필자의 경우 postgreSQL 이미지를 띄운 후 database 생성 및 extension 설치가 필요하여 위와 같이Dockerfile-postgres
를 추가로 작성하였습니다. - database 생성 및 extension 설치는
init-user-db.sh
에 작성하였습니다.
- 이 경우 postgreSQL에 대한
위 디렉토리 구조에 맞추어 각각의 파일들에 어떤 내용이 작성되어 있는지 알아보도록 하겠습니다.
.env 파일
DB_USER_ID=postgres
DB_USER_PASSWORD=postgres
APP_DB_USER=dev
APP_DB_PASS=postgres
APP_DB_NAME=dev_db
DB_BUILD_CONTEXT=./db
POSTGRES_HOME_DIR=./db
APP_BUILD_CONTEXT=./app/bread-map-backend
S3_ACCESSKEY=자신의 AWS S3 ACCESSKEY
S3_SECRETKEY=자신의 AWS S3 SECRETKEY
S3_REGION=자신의 AWS S3 REGION
S3_BUCKET=자신의 AWS S3 BUCKET 이름(이미지가 저장될 버킷 디렉토리)
.env
파일 안에는docker-compose.yml
에서 사용될 환경변수를 작성하게 됩니다.S3_*
환경변수들의 경우 필자의 SpringBoot 어플리케이션 내 AWS S3에 이미지 업로드하는 API가 존재하여 작성한 것으로, 필요하지 않다면 삭제해도 무방합니다.
docker-compose.yml
version: "3" # 사용하고자 하는 docker-compose 버전 명시(현재 최신 버전은 3.8)
services:
db:
container_name: postgres # 컨테이너 이름(docker dashboard에 뜨는 이름)
restart: always # 항상 재시작(명시되어 있지 않을 경우 no가 default이며 어떤 상황에서도 restart 안함)
build: # build할 때 필요한 속성 지정
context: "${DB_BUILD_CONTEXT}" # Dockerfile이 들어있는 경로 지정
dockerfile: Dockerfile-postgres # 빌드 시 사용할 Dockerfile 이름으로, Dockerfile이 아닌 다른 이름으로 생성할 경우 명시 필요
ports:
- "5432:5432" # HOST_PORT:CONTAINER_PORT로 host의 5432 포트를 container의 5432 포트와 매핑
environment:
POSTGRES_USER: "${DB_USER_ID}" # 선택 속성으로 POSTGRES_PASSWORD와 함께 사용되며, POSTGRES_USER명으로 superuser가 생성됨(작성하지 않을 경우 postgres 이름으로 superuser 생성)
POSTGRES_PASSWORD: "${DB_USER_PASSWORD}" # 필수 속성으로 postgreSQL의 superuser password(비어있거나 선언되지 않으면 안됨)
APP_DB_NAME: "${APP_DB_NAME}"
APP_DB_USER: "${APP_DB_USER}"
APP_DB_PASS: "${APP_DB_PASS}"
volumes:
- ${POSTGRES_HOME_DIR}/data:/var/lib/postgresql/data # ${POSTGRES_HOME_DIR}/data 부분이 실제 PostgreSQL 저장 경로로 컨테이너 종료 또는 삭제되었을 때 내부 데이터 날림 방지하기 위한 설정
app:
container_name: daedong-bread-backend
build:
context: "${APP_BUILD_CONTEXT}"
dockerfile: Dockerfile-dev
ports:
- "8080:8080"
environment: # springboot의 application-*.yml에 주입 필요한 정보
S3_ACCESSKEY: "${S3_ACCESSKEY}"
S3_SECRETKEY: "${S3_SECRETKEY}"
S3_REGION: "${S3_REGION}"
S3_BUCKET: "${S3_BUCKET}"
DB_HOSTNAME: db
DB_PORT: 5432
DB_DBNAME: "${APP_DB_NAME}"
DB_USERNAME: "${APP_DB_USER}"
DB_PASSWORD: "${APP_DB_PASS}"
depends_on:
- db # db가 뜬 후에 app이 뜰 수 있도록 depends_on 설정
- 먼저,
docker-compose.yml
생성할 때에는version
을 명시하여 어떤 버전의docker-compose
를 사용할지 지정합니다. services
는 실질적인 컨테이너 집합 요소로,services
하위에 입력하는 내용을 기반으로 컨테이너가 각각 생성됩니다.- 위
docker-compose.yml
의 경우db
와app
이라는 컨테이너가 생성된다고 볼 수 있습니다. - 현재
dev-docker
라는 디렉토리 내docker-compose.yml
을 구성하고,db
와app
이라는 이름으로 컨테이너를 설정했기 때문에 docker dashboard에dev-docker
가 생성되고 해당 집합 내dev-docker_db
와dev-docker_app
이라는 컨테이너가 뜨게 됩니다.
- 위
- docker-compose.yml과 동일한 depth에 작성된 .env 파일을 읽어 환경변수를 주입할 수 있습니다.
- 위
docker-compose.yml
내"${~~}"
로 되어 있는 부분이.env
파일을 읽어 주입한 부분입니다.
- 위
- 기본적으로 docker의
service
를 작성할 때image
속성을 작성합니다. 다만, 필자의 경우 각service
별로Dockerfile
이 존재하여 각Dockerfile
내 이미지명을 작성해두었기 때문에 여기서는image
속성을 작성하지 않았습니다.Dockerfile
은FROM
명령부터 시작해야 하며, FROM 커맨드는 베이스 이미지를 지정하는 것이라Dockerfile
이 있다면 해당 컨테이너에 대한docker-compose.yml
설정에서는 이미지를 넣지 않습니다.- 이때, 어느 위치에 있는
Dockerfile
를 읽어야할지 알려주기 위해build
속성의context
를 작성했습니다. - 또한, 기본적으로
Dockerfile
을 읽기 때문에 읽을Dockerfile
이름을 기입해주었습니다.
db
의environment
속성 내APP_DB_NAME
,APP_DB_USER
,APP_DB_PASS
의 경우, SpringBoot 어플리케이션에서 사용할 DB 계정 및 database 이름입니다.service
의app
은 SpringBoot 이미지를 띄우기 위한 정보를 작성해둔 부분으로, 필자가 가진 SpringBoot 어플리케이션에는 운영용 Dockerfile이 존재하여 개발용과 운영용을 분리하기 위해Dockerfile-dev
를 읽도록build
속성에 설정하였습니다.build
속성의context
위치에서dockerfile-dev
를 찾아서 읽어들인다고 보면 됩니다.
app
의environment
속성에는application-*.yml
에서 필요한 속성들을 작성하였습니다.- 기본적으로, DB 정보를 담게 되며 이 외의 정보는 자신이 필요한 부분에 대해 기입하면 됩니다.
app
에는db
와 다르게depends_on
속성이 존재하고, 해당 속성에db
라고 작성해두었습니다. 이는,db
가 올라간 이후에app
이 떠야 정상 작동을 하기 때문에 의존성을 설정한 것입니다.
Dockerfile-postgres
FROM postgres:12.8
COPY ./init.d /docker-entrypoint-initdb.d
FROM
커맨드에는 어떤 이미지를 사용할지를 작성합니다. 필자의 경우, docker hub에 올라가 있는 postgreSQL을 사용할 것이기 때문에 위와 같이 작성하였습니다.- 기존에 사용하던 버전이 12.8이라, 위와 같이 작성한 것으로 만약 다른 버전을 사용하고 싶다면 해당 버전에 맞는 이미지를 넣어주면 됩니다.
- postgreSQL을 띄울 때, 초기 세팅을 위해
init.d
디렉토리 하위에init-user-db.sh
를 작동시켜야 하기 때문에init.d
디렉토리 하위 파일을/docker-entrypoint-initdb.d
디렉토리에 넣도록 COPY 작업을 진행합니다.init-user-db.sh
에서는 database 생성 및 application에서 사용할 db user 계정을 생성합니다. 또한 설치가 필요한extension
이 있어 extension을 생성해주었습니다.
- 기본적으로,
docker-compose.yml
내volume
속성을 이용하여init.d
디렉토리 내용을/docker-entrypoint-initdb.d
디렉토리로 보낼 수 있다고 알려져 있으나, 필자의 경우 제대로 동작하지 않아 따로Dockerfile
을 생성하였습니다.
왜 postgreSQL의 initial script를 docker-entrypoint-initdb.d/에 넣었을까?
필자가 사용한 postgreSQL의 docker image는 docker hub에 올라온 이미지입니다. docker hub에 존재하는 postgreSQL doc을 보면 하기와 같이 추가적인 initialize 작업이 필요할 경우의 지시사항이 나와있습니다.
- postgreSQL를 처음 시작할 때 추가로 해주어야 하는 작업이 있다면
*.sql
,*.sql.gz
또는*.sh
파일을/docker-entrypoint-initdb.d/
에 넣으면 됩니다. - docker hub에 존재하는
init-user-db.sh
예제를 이용하여 필자의init-user-db.sh
를 생성했습니다.
init-user-db.sh
#!/bin/bash
set -e
echo "START INIT-USER-DB";
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE USER $APP_DB_USER WITH PASSWORD '$APP_DB_PASS';
CREATE DATABASE $APP_DB_NAME;
GRANT ALL PRIVILEGES ON DATABASE $APP_DB_NAME TO $APP_DB_USER;
\connect $APP_DB_NAME $POSTGRES_USER
BEGIN;
CREATE EXTENSION CUBE;
CREATE EXTENSION EARTHDISTANCE;
COMMIT;
EOSQL
init-user-db.sh 스크립트 내용을 간단하게 살펴보겠습니다.
- 먼저, postgreSQL의 superuser가 존재하는지 체크합니다. 만약, 존재하지 않다면 error가 발생합니다.
- 그 후, application에서 사용할 계정을 생성하고 application용 database를 생성합니다. 이때 모든 권한을 주어 작업을 원할하게 할 수 있도록 했습니다.
- database 생성이 완료된 후에는 superuser로 application용 database에 접속하여 필요한 extension을 설치해주었습니다.
- 필자의 경우, postgreSQL의
earthdistance
를 사용하여 위도, 경도 계산하는 로직이 존재하여cube
와earthdistance
extension
을 설치했습니다. - superuser로 접속한 이유는 extension 설치 시 superuser 권한이 필요했기 때문입니다.
- 필자의 경우, postgreSQL의
Dockerfile-dev
SpringBoot용 Dockerfile
로 docker-compose.yml
에서 따로 Dockerfile
이름을 명시하지 않았다면 Dockerfile
로 작성하면 됩니다.
FROM adoptopenjdk:8-hotspot
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=dockerdev","/app.jar"]
ARG
커맨드는 빌드 시에만 사용되는 변수를 설정할 때 사용합니다.ENTRYPOINT
커맨드는 컨테이너 시작 시 실행할 커맨드를 설정합니다.- 필자의 경우, springboot의
active-profile
을dockerdev
로 지정했음을 알려야 하기 때문에 위와 같이 jar 실행 시active-profile
속성을 주도록 작성했습니다.
- 필자의 경우, springboot의
application-dockerdev.yml
필자의 경우 Dockerfile-dev
내에 active-profile
을 dockerdev
로 설정하였기 때문에 application.yml
이름이 application-dockerdev.yml
인 것입니다. 따라서, 자신의 active-profile
에 맞게 설정하면 됩니다.
spring:
datasource:
url: jdbc:postgresql://${db.hostname}:${db.port}/${db.dbname}
username: ${db.username}
password: ${db.password}
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: none
database: postgresql
properties:
hibernate:
format_sql: true
generate-ddl: true
servlet:
multipart:
max-file-size: 5MB
max-request-size: 5MB
swagger-info:
title: Daedong BreadMap REST API
description: Depromeet 10th Team 7
version: 1.0.0
logging:
level:
org.hibernate.SQL: debug
cloud:
aws:
credentials:
access-key: ${s3.accesskey}
secret-key: ${s3.secretkey}
region:
static: ${s3.region}
s3:
bucket: ${s3.bucket}
stack:
auto: false
docker-compose.yml
에서environment
속성으로 넣어주었던 값이application-dockerdev.yml
에서 사용되는 것을 확인할 수 있습니다.
Docker-Compose로 SpringBoot와 PostgreSQL 서비스 시작하기
docker-compose
실행 전에는 docker dashboard에 아무런 container가 떠있지 않고, SpringBoot 어플리케이션의 swagger 페이지가 정상적으로 뜨지 않는 것을 확인할 수 있습니다.
이때, 하기 명령어를 통해 Docker-Compose
를 시작할 수 있습니다. docker-compose
명령어는 docker-compose.yml
파일이 존재하는 위치에서 진행합니다.
docker-compose up
# docker-compose up -d 진행 시, 로그를 콘솔에 보이지 않게 할 수 있습니다. 이 경우, Docker Dashboard에서 컨테이너를 클릭하여 로그를 볼 수 있습니다.
필자의 경우, docker-compose
명령어에 --build
옵션을 추가로 줬으며 해당 옵션은 rebuild
를 할 수 있는 옵션으로 기존 build
가 존재할 경우에도 build
를 다시 진행합니다.
위와 같이 docker-compose
명령어 사용 시 -d
명령어를 넣으면 서비스가 뜨면서 생긴 로그는 콘솔에 남지 않습니다. 해당 로그는 docker dashboard의 각 서비스를 클릭하면 확인할 수 있습니다.
추가적으로 알면 좋은 Docker 개념
ENTRYPOINT와 CMD 차이점
ENTRYPOINT
ENTRYPOINT
는 하기와 같이 2가지 형태를 가지고 있으며, 컨테이너에서 가장 먼저 실행할 커맨드를 설정합니다.
ENTRYPOINT ["executable", "param1", "param2"]
(exec form - JSON 표기)ENTRYPOINT command param1 param2
(shell form)
CMD
CMD
커맨드는 두 가지 역할을 수행합니다. 아래와 같이 ENTRYPOINT
커맨드가 설정되어 있는지 유무에 따라 역할이 바뀝니다.
Dockerfile
에는 오직 하나의 CMD
커맨드만 수행될 수 있기 때문에 CMD가 여러 번 들어간다면 마지막 CMD
만 작동(override)합니다.
ENTRYPOINT
가 설정되어 있지 않은 경우, 컨테이너로 실행하는 커맨드 설정(ENTRYPOINT
와 동일)CMD ["executable", "param1", "param2"]
CMD command param1 param2
ENTRYPOINT
가 설정되어 있는 경우, 제공하는 매개 변수의 기본값(default) 설정합니다.CMD ["param1", "param2"]
ENTRYPOINT
와 CMD
사용해보기
FROM ubuntu
ENTRYPOINT ["/bin/echo", "Hello World"]
CMD ["!"]
위와 같이 Dockerfile
을 작성한 후 docker 이미지를 생성한 후 하기와 같이 테스트를 진행합니다.
$ docker build -t <image-name> # docker 이미지 build
$ docker run -it --rm <image-name> # CMD의 default 파라미터 사용 O
Hello World!
$ docker run -it --rm <image-name> everyone # CMD의 default 파라미터 사용 X
Hello World everyone
ENTRYPOINT
와 CMD
의 차이점을 간단하게 정리한 것으로, 좀 더 알아보고자 한다면 ENTRYPOINT와 CMD의 dockerfile 공식 문서를 확인하길 바랍니다.
참고 자료
'OPEN SOURCE > DOCKER' 카테고리의 다른 글
[Docker, Ubuntu] docker 환경에서 한글 안 깨지도록 설정하기 (2) | 2022.05.22 |
---|