docker-compose
를 통해 로컬 개발 환경을 설정해보자. 기본적으로 SpringBoot
을 사용할 것이고 MySQL
, Redis
를 컨테이너로 올려 SpringBoot 에서 사용할 예정이다. 이걸 인프라라고 말하기도 부끄럽지만, 구성도를 그려보자면 아래와 같이 나타낼 수 있다.
프로젝트 구조
개발환경을 설정하기 전에 해당 포스팅을 그대로 따라하면 최종적으로 아래와 같은 프로젝트 구조가 된다.
docker-compose.yml 작성
Spring 에서 MySQL
, Redis
Container 를 사용할것이기 때문에 우선적으로 MySQL, Redis 를 Container 로 띄워야 한다. 이를 위해 Container 를 한번에 관리할 수 있는 docker-compose
를 사용할 것이다. docker-compose 는 내부적으로 docker-compose.yml
이라는 파일을 참조하기 때문에 해당 파일을 작성해주어야 한다. 해당 파일의 내용은 아래와 같이 작성해준다.
Windows 기준 Docker 를 설치하면 docker-compose 커맨드라인 도구는 설치되어 있다.
각 Field 들의 의미는 아래와 같다.
Field | Description |
---|---|
services | 실행하려는 Container 들을 명시. docker-comopse 에서는 container 대신 services 라는 개념을 사용한다. |
image | 사용하고 싶은 image 를 명시. 로컬에 해당 image 가 없으면 Docker Hub 에서 Pull 하여 사용한다. |
container_name | image 를 통해 생성되는 Container 이름을 명시. |
restart | 컨테이너를 수동으로 끄기 전까지 항상 재시작 되도록 명시. |
expose | Container 가 노출되기 원하는 Port 정보를 명시. (ports 를 명시해주면 굳이 사용할 필요 X) |
ports | Container 가 노출한 Port 정보와 외부로 노출되기 원하는 Port 정보를 매핑. |
command | Container 가 실행될때 추가적으로 실행되는 옵션을 명시. |
environment | Image 에서 사용되는 환경변수를 명시. DockerHub 에서 환경변수를 볼 수 있다. |
volumes | Container 외부에 파일 시스템을 마운트할 때 사용. |
Note
volumes 에서 : 를 기준으로 오른쪽(Container 내부 파일시스템)이 왼쪽(로컬 파일시스템)에 마운트된다. 따라서 컨테이너가 죽어도 로컬에 있는 파일시스템으로 값을 동기화할 수 있다.
Volumes 파일 정보
앞서 언급했듯, volumes 를 통해 Container 내부의 특정한 파일시스템을 로컬로 Mount 시켜 데이터베이스를 동기화할 수 있다.
../db/mysql/data:/var/lib/mysql
- Container 내부
/var/lib/mysql
파일시스템을 로컬의docker-compose.yml
파일이 존재하는 경로를 기준으로../db/mysql/data
경로에 마운트한다. 첨언하자면 /var/lib/mysql 디렉터리는 MySQL이 데이터베이스 파일을 저장하는 곳으로 기본적으로 데이터 파일들이 위치하는 디렉터리이다. 해당 디렉터리 하위에는데이터베이스 파일, 테이블 데이터, 인덱스, 로그 파일
등이 저장된다.
../db/mysql/init:/docker-entrypoint-initdb.d
- Container 내부
/docker-entrypoint-initdb.d
파일시스템을 로컬의docker-compose.yml
파일이 존재하는 경로를 기준으로../db/mysql/init
경로에 마운트한다. 첨언하자면 /docker-entrypoint-initdb.d 디렉터리는 도커 컨테이너 내에서 MySQL 이미지를 사용할 때초기화 스크립트나 SQL 파일을 자동으로 실행
하는 데 사용되기 때문에 해당 폴더에 초기화 스크립트를 넣어 초기데이터를 넣기에 매우 적합하다. 뿐만 아니라 디렉터리에 있는SQL
파일들은 이름 순서대로 실행하기 때문에여러개의 초기화 스크립트
를 설정할수도 있다.
Warn
/docker-entrypoint-initdb.d 안의 초기화 스크립트들은 컨테이너가 제일 처음 실행될때 딱 한번만 실행된다.
초기 스크립트 작성
초기데이터를 넣기위해 로컬 docker-compose.yml
파일이 존재하는 경로를 기준으로 ../db/mysql/init
경로에 초기화 스크립트를 적어주어야 한다 (여러개를 작성하려면 사전순으로). 필자는 두개의 초기화 스크립트를 사전순으로 작성해주었다.
Container 생성
이제 아래의 명령어를 통해 docker-compose.yml
을 실행하여 Container 들을 생성 및 실행해주면 된다.
-d
옵션은 컨테이너를 실행시킨 후,Terminal
로 빠져나오게하는 옵션이다.-f
옵션은docker-compose.yml
파일이 위치하는 경로를 명시해주면 된다. (compose 파일의 위치와 현재 경로가 일치한다면 명시해주지 않아도 된다)
추가적으로 특정한 services
만 실행하고 싶다면 yml 에 작성한 services 이름을 명시해주면 된다.
컨테이너가 실행되는 로그를 보면 제일 먼저 docker-compose.yml
에 작성한 MySQL 의 MYSQL_DATABASE
환경변수에 의해 docker_build
데이터베이스가 먼저 생성되고, 이후에 로컬 ../db/mysql/init
위치에 작성했던 초기화 스크립트들이 사전순으로 실행되는 것을 확인할 수 있다.
아래 사진을 보면 container 가 잘 뜬 것을 확인할 수 있고
초기화 스크립트로 인해 DDL
문이 동작하여 DB 에 테이블이 생성된 것을 확인할 수 있다.
초기 스크립트가 실행되지 않을때
docker-compose -f ./docker/docker-compose.yml up -d
로 컨테이너를 생성하더라도 초기 스크립트가 실행되지 않을때가 있다. 이에 대해 도커에서는 /docker-entrypoint-initdb.d
라는 스크립트 파일은 컨테이너가 실행되는 시점에 데이터베이스가 이미 존재하면(데이터 디렉터리가 비어있지 않으면) 실행되지 않는다
고 한다.
우리는 컨테이너 내부의 /var/lib/mysql 경로를 로컬의 ../db/mysql/data 경로로 마운트시켰고, 이미 한번 컨테이너를 실행했다. 그렇기 때문에 그 다음 컨테이너를 실행할 시점에는 ../db/mysql/data 가 비어있지 않아 초기화 스크립트가 실행되지 않는다.
해결방법
해결방법은 간단하다. 로컬의 ../db/mysql/data 안의 내용들을 삭제해주고 컨테이너를 다시 시작해주면 초기화 스크립트가 다시 작동하게 된다. 혹시 다른 이유로 초기화 스크립트가 동작하지 않을때는 해당 링크 를 참고하도록 하자.
Spring 코드 작성
dependencies 확인
여기부터는 스프링의 영역이다. 우선 해당 포스팅에서 사용하는 의존성은 아래와 같다.
application.yml 설정
이제 application.yml
파일을 작성해준다.
애플리케이션 코드 작성
이제 환경설정이 끝났으니 애플리케이션 코드만 작성해주면 된다. 필자는 간단하게 DataSource
커넥션 정보, Redis
의 Key Value 셋팅 동작 여부, JPA 로 MySQL
에 select 가 잘 가는지만 테스트할 것이다.
우선 Persistence Layer 의 코드는 아래와 같다. Repository 는 생략한다.
또한, MYSQL 을 테스트하기 위해 초기데이터를 입력해준다.
기본적인 Controller 코드는 아래와 같다. 아래와 같이 Repository 계층을 바로 Controller 계층에 주입하면 좋지 않다. 하지만 테스트 용도이기 때문에 이 부분은 넘어간다.
API 동작 확인
이제 앞서 작성한 코드와 설정을 바탕으로 curl
로 API 를 테스트해보자. 우선 DataSource
커넥션 정보는 아래와 같이 잘 뜨는것을 확인할 수 있다.
또한, MySQL
에서도 모든 row 들을 잘 가져오는 것도 확인할 수 있다.
마지막으로 Redis
에서도 Key Value 셋팅이 잘 동작하는 것을 확인할 수 있다.
마치며
이렇게 Local 환경에서 MySQL 과 Redis Container 를 띄워 Spring 과 통신하는 개발환경을 설정해보았다. 추가적으로 알아두어야할 것은 MySQL, Redis, Spring 을 통합한 통합환경을 image
로 빌드하려면, application.yml
에 Redis 와 DataSoure 정보를 localhost 가 아닌 host.docker.internal
로 설정해야한다. (윈도우 기준)