배경
현재 프로젝트의 Directory Tree 구조는 같습니다.
root
├── deploy
├── gradle
├── module-admin-api
│ ├── build
│ ├── out
│ └── src
├── module-business-api
│ ├── build
│ ├── out
│ └── src
├── module-core
│ ├── build
│ ├── out
│ └── src
├── module-user-api
│ ├── build
│ ├── out
│ └── src
└── scripts
Java
복사
Gradle의 Multi module을 사용하여 하나의 프로젝트에서 3가지 API 서버를 구동 중입니다.
이외에도 MySQL, Redis 등을 사용 중이고 Prometheus, Grafana를 이용해 비즈니스 지표에 대한 대시보드를 개발하는 과정에서 Local 환경의 불편함을 느껴 Docker로 이전을 생각하였습니다.
네트워크 설정
•
docker network
docker network create local-network
Java
복사
위 명령어를 통해 local-network 라는 이름을 가진 브릿지 네트워크를 만들어줍니다.
docker network local-network conatiner-name
Java
복사
위 명령어를 통해 커스텀 네트워크에 컨테이너를 등록해줍니다.
•
application.yml
#MySQL 설정
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://host.docker.internal:3306/db_name?serverTimezone=Asia/Seoul
username: username
password: password
hikari:
maximum-pool-size: 30
#Redis 설정
redis:
host: host.docker.internal
port: 6379
Java
복사
MySQL과 Redis에 대한 설정을 할 때 host를 host.docker.internal로 해주면
Docker Container로 띄운 MySQL과 Redis가 아니라 로컬 머신에서 사용하고 있는 서버를 사용할 수 있습니다.
나머지 컨테이너들도 커스텀 네트워크에 다 연결해주면 하나의 네트워크로 컨테이너 간의 통신이 가능해집니다.
이 때 통신할 컨테이너의 호스트는 컨테이너 이름으로 지정해줍니다.
도커 이미지
FROM amazoncorretto:17
WORKDIR /tmp
COPY *.jar /tmp/gocho-api.jar
EXPOSE 8080 9000
ENTRYPOINT java -Duser.timezone=Asia/Seoul -Dspring.profiles.active=local -jar gocho-api.jar
Java
복사
우선 위와 같이 Dockerfile을 만들어서 각 모듈에 넣어주었습니다.
docker compose up을 통해 컨테이너를 실행하기 전 선행 태스크로 이미지를 빌드하기 위한 도커파일입니다.
그 후 IntelliJ에서 각 Dockerfile을 빌드하기 위한 Run Configuration을 만들었습니다.
이 때 중요한 점은 Dockerfile에서 jar 파일을 COPY 해야하기 때문에 Before Launch로 Gradle의 bootJar Task를 선행해주어야 한다는 것입니다.
Dockerfile의 위치는 jar파일이 생성되는 /build/libs에 위치해두었습니다.
도커 컴포즈
version: '3'
networks:
default:
external:
name: local-network
services:
user-service:
image: user-service:latest
container_name: user-service
ports:
- 8080:8080
- 9000:9000
stdin_open: true
tty: true
restart: always
extra_hosts:
- user-service:127.0.0.1
business-service:
image: business-service:latest
container_name: business-service
ports:
- 8081:8080
- 9001:9000
stdin_open: true
tty: true
restart: always
extra_hosts:
- business-service:127.0.0.1
admin-service:
image: admin-service:latest
container_name: admin-service
ports:
- 8082:8080
- 9002:9000
stdin_open: true
tty: true
restart: always
extra_hosts:
- admin-service:127.0.0.1
Java
복사
위와 같은 docker-compose.yml을 작성하여 root 디렉토리에 위치시켜주었습니다.
이제 하나하나 뜯어서 살펴보겠습니다.
networks:
default:
external:
name: local-network
Java
복사
mysql, redis, prometheus, grafana 등의 컨테이너들은
docker network create local-network 를 통해 생성된 네트워크에서 실행 중이었기 때문에
외부의 네트워크에 도커 컴포즈의 컨테이너들이 실행되도록 설정해주었습니다.
services:
user-service:
image: user-service:latest
container_name: user-service
ports:
- 8080:8080
- 9000:9000
stdin_open: true
tty: true
business-service:
image: business-service:latest
container_name: business-service
ports:
- 8081:8080
- 9001:9000
stdin_open: true
tty: true
admin-service:
image: admin-service:latest
container_name: admin-service
ports:
- 8082:8080
- 9002:9000
stdin_open: true
tty: true
Java
복사
이제 3가지의 API 서버들을 정의해주었습니다.
ports에서 xxxx:yyyy 중 xxxx는 호스트에서 사용할 포트로 이 포트는 중복되어선 안됩니다.
그렇기 때문에 서비스별로 다른 포트를 설정 해준 후 컨테이너 내에서 사용할 포트인 yyyy는 통일시켜주었습니다.
docker run 명령 중 -i 옵션인 stdin_open을 통해 기본 입력을 연결시켜주었습니다.
docker run 명령 중 -t 옵션인 tty을 통해 터미널을 제공하게끔 하였습니다.
이제 IntelliJ에서 Run 커맨드 하나로 한 번에 실행되도록 하기 위해 Run Configuration을 설정해주었습니다.
재빌드 이슈
코드를 수정하고 다시 빌드 후 서버를 실행시켜야 하는 상황에서 문제가 발생하였습니다.
1.
서버를 재실행해도 docker compose down이 자동으로 되지 않습니다.
task dockerComposeDown(type: Exec) {
commandLine 'docker', 'compose', '-p', 'spring-boot', 'down', '--rmi', 'all'
}
Java
복사
Gradle Task에 강제로 docker compose를 종료하는 태스크를 추가하였습니다.
2.
코드가 수정되었어도 도커 이미지가 다시 빌드되지 않았습니다.
task dockerImageBuild {
doLast {
def workDirs = ['module-user-api/build/libs', 'module-business-api/build/libs', 'module-admin-api/build/libs']
def dockerfiles = ['Dockerfile-user', 'Dockerfile-business', 'Dockerfile-admin']
def imageNames = ['user-service', 'business-service', 'admin-service']
dockerfiles.eachWithIndex { dockerfile, idx ->
def imageName = imageNames[idx]
def workDir = workDirs[idx]
exec {
workingDir workDir
commandLine 'docker', 'build', '-t', imageName, '-f', dockerfile, '.'
}
}
}
}
Java
복사
Gradle Task에 도커 이미지들을 강제로 빌드하는 태스크를 추가하였습니다.
이를 docker compose up 하는 Run configuration에 Before Launch로 추가하였고
서버를 재실행하면 docker compose down 과 docker build 가 강제로 실행되게끔 하였습니다.
결과
이렇게 해서 docker run을 통해 따로 띄운 컨테이너와 docker compose up을 통해 띄운 컨테이너들이 정상적으로 작동하는걸 확인할 수 있습니다.
docker network inspect local-network
[
{
"Name": "local-network",
"Id": "1fe0e0e3c0e9c11f8f2480118e2c92b0921191ab9c093bb29fb08542771e8fc5",
"Created": "2023-07-26T05:34:00.485830591Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.19.0.0/16",
"Gateway": "172.19.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"24028ce23e35dc78d389d23daaaf22de72abe2225896d408c94e74ac7aab1eef": {
"Name": "redis",
"EndpointID": "a82623b7bcccf88d688766b3cfcda8944a5e9e2ca1c651f869da99d7ede576c9",
"MacAddress": "02:42:ac:13:00:03",
"IPv4Address": "172.19.0.3/16",
"IPv6Address": ""
},
"489dbdf1bcdd48fd52a7899235d8ccb3c9d50086f74c988337d366bb5b746ea6": {
"Name": "mysql",
"EndpointID": "e9271ca577130e4723997b49b5d312f24139a61e85832e3b5d1177f2df79b583",
"MacAddress": "02:42:ac:13:00:02",
"IPv4Address": "172.19.0.2/16",
"IPv6Address": ""
},
"6c830d81d01c05c26e25b0111523af3dd4f4e66404f79d59b11315482c800a96": {
"Name": "business-service",
"EndpointID": "9a9fba68b183be3ee73f8eefc78946931cb4bda8f36f5f04892179871d3f80a3",
"MacAddress": "02:42:ac:13:00:08",
"IPv4Address": "172.19.0.8/16",
"IPv6Address": ""
},
"7b047cdbe73a859cbcc1dbb55386ec83de6f205cdeb12e4c99dff8a5f3459571": {
"Name": "grafana",
"EndpointID": "eeaedd046262d7aa502a2bcb6262831ca8f81bb82da07c7ad86c50d1cc125e82",
"MacAddress": "02:42:ac:13:00:04",
"IPv4Address": "172.19.0.4/16",
"IPv6Address": ""
},
"9a8b56417c1ff425c70fdfa5cebac9f8736064b313847ee1bb8de5c6efdef5ab": {
"Name": "user-service",
"EndpointID": "60e3d16520aed70748ce61d954f6fa97ca4040e9c2f9ca472afd03408d768957",
"MacAddress": "02:42:ac:13:00:06",
"IPv4Address": "172.19.0.6/16",
"IPv6Address": ""
},
"d791a3e7848abfa6f4bbe4af1ae7cbb422154bb2fe8061c5cf9cc84bfe79cacc": {
"Name": "admin-service",
"EndpointID": "3eb783e338cea9f8e2c97c90035a31cedba7abfc1a355fde34d8eba077a96d72",
"MacAddress": "02:42:ac:13:00:07",
"IPv4Address": "172.19.0.7/16",
"IPv6Address": ""
},
"d854508255ad70b261a2a3f077fea1ddadf640f45235bac90b2ed80a53bd0e76": {
"Name": "prometheus",
"EndpointID": "7d99adbee7e0236e33a58380b36dffbc39445a29c673a30ad46b6f934333a03d",
"MacAddress": "02:42:ac:13:00:05",
"IPv4Address": "172.19.0.5/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
Java
복사
컨테이너들이 커스텀 네트워크에 잘 연결되어있는 모습 또한 docker network inspect local-network 를 통해 확인할 수 있습니다.