msa와 docker는 뗄레야 뗄 수 없는 관계에 있다고 생각한다. 도커를 기반으로 하여 AWS fargate 혹은 kubernetes등을 통해 이루어지는 컨테이너 오케스트레이션은 microservice 별로 scale-out이 가능하다는 MSA의 장점과 특히 잘어울린다. 이번 장에서는 MSA로 만든 어플리케이션을 도커라이징하는 과정을 설명하려 한다.
이번 실습에서는 아래와 같이 총 8개의 컨테이너를 만든다. (프론트까지 추가하면 9개)
MSA를 구성하는 각각의 service들은 모두 같은 환경(java & gradle)에서 돌아가기 때문에 도커파일의 생김새 자체는 모두 똑같다.
FROM gradle:6.2.2-jdk11 AS build
COPY --chown=gradle:gradle . /home/gradle/src
WORKDIR /home/gradle/src
RUN gradle clean build --no-daemon -x test
FROM openjdk:11-jre-slim
EXPOSE 8080
RUN mkdir /app
COPY --from=build /home/gradle/src/build/libs/*.jar /app/spring-boot-application.jar
ENTRYPOINT ["java", "-XX:+UnlockExperimentalVMOptions", "-Djava.security.egd=file:/dev/./urandom","-Dspring.profiles.active=test", "-jar","/app/spring-boot-application.jar"]
의존성 관리 모듈로 gradle을 사용하였기 때문에 베이스 이미지를 gradle로 했는데, 이미지의 크기를 줄이기 위해 멀티 스테이지 빌드를 적용했다.
이번 실습은 가능하다면 MSA로 작성한 앱을 쿠버네티스로 배포하는 것이 최종적인 목표지만, 우선은 로컬 환경에서 테스트가 가능하게 docker-compose를 이용해 하나의 호스트 위에 모든 컨테이너를 띄워본다.
version: '3'
services:
mysql:
image: mysql:5.7
container_name: mysql-db
environment:
MYSQL_ROOT_PASSWORD: "password"
ports:
- 3306:3306
networks:
- msa-network
config:
image: dvmflstm/config
depends_on:
- mysql
environment:
MYSQL_CONNECTION_URI: "jdbc:mysql://mysql:3306/test_db?serverTimezone=UTC&characterEncoding=UTF-8&autoReconnection=true"
MYSQL_ROOT_PASSWORD: "password"
ports:
- 8888:8888
networks:
- msa-network
registry:
image: dvmflstm/registry
depends_on:
- config
- mysql
environment:
CONFIG_SERVER_URI: "http://config:8888"
ports:
- 8761:8761
networks:
- msa-network
gateway:
image: dvmflstm/gateway
depends_on:
- config
- registry
- mysql
environment:
CONFIG_SERVER_URI: "http://config:8888"
EUREKA_SERVER_URI: "http://registry:8761/eureka"
MYSQL_CONNECTION_URI: "jdbc:mysql://mysql:3306/test_db?serverTimezone=UTC&characterEncoding=UTF-8&autoReconnection=true"
MYSQL_ROOT_PASSWORD: "password"
ports:
- 8080:8080
networks:
- msa-network
auth:
image: dvmflstm/auth
depends_on:
- config
- registry
- mysql
environment:
CONFIG_SERVER_URI: "http://config:8888"
EUREKA_SERVER_URI: "http://registry:8761/eureka"
MYSQL_CONNECTION_URI: "jdbc:mysql://mysql:3306/test_db?serverTimezone=UTC&characterEncoding=UTF-8&autoReconnection=true"
MYSQL_ROOT_PASSWORD: "password"
JWT_SECRET_KEY: "geunyoung_jwt_secret"
ports:
- 8081:8080
networks:
- msa-network
diet:
image: dvmflstm/diet
depends_on:
- config
- registry
- mysql
environment:
CONFIG_SERVER_URI: "http://config:8888"
EUREKA_SERVER_URI: "http://registry:8761/eureka"
MYSQL_CONNECTION_URI: "jdbc:mysql://mysql:3306/test_db?serverTimezone=UTC&characterEncoding=UTF-8&autoReconnection=true"
MYSQL_ROOT_PASSWORD: "password"
ports:
- 8082:8080
networks:
- msa-network
exercise:
image: dvmflstm/exercise
depends_on:
- config
- registry
- mysql
environment:
CONFIG_SERVER_URI: "http://config:8888"
EUREKA_SERVER_URI: "http://registry:8761/eureka"
MYSQL_CONNECTION_URI: "jdbc:mysql://mysql:3306/test_db?serverTimezone=UTC&characterEncoding=UTF-8&autoReconnection=true"
MYSQL_ROOT_PASSWORD: "password"
ports:
- 8083:8080
networks:
- msa-network
statistics:
image: dvmflstm/statistics
depends_on:
- config
- registry
- mysql
environment:
CONFIG_SERVER_URI: "http://config:8888"
EUREKA_SERVER_URI: "http://registry:8761/eureka"
MYSQL_CONNECTION_URI: "jdbc:mysql://mysql:3306/test_db?serverTimezone=UTC&characterEncoding=UTF-8&autoReconnection=true"
MYSQL_ROOT_PASSWORD: "password"
ports:
- 8084:8080
networks:
- msa-network
networks:
msa-network:
external: true
주의해서 봐야할 부분은 각 마이크로서비스에 config server의 uri와 eureka server의 uri를 환경변수로 주입해주는 부분인데, docker-compose에서 제공하는 service discovery 기능의 도움을 받아 간단하게 docker-compose가 생성하는 service의 이름으로 name resolution이 가능하다.
spring cloud에서는 각각의 마이크로서비스가 컨피그 서버와 유레카 서버의 도움을 받아야 하므로 이들이 구동된 후에 시작되어야 한다. 위에서 작성한 docker-compose.yml
에서는 depends_on으로 각 서비스의 시작 순서를 정해주었지만, 이것이 이전 서비스가 완전히 구동된 후에 다음 서비스를 시작시키는 것은 아니다. 그래서 완벽하게 구동 순서를 제어하기 위해서는 아래와 같은 방법을 활용해야 한다.
컨테이너가 비정상적으로 종료되었을 때 무조건 재시작시키는 옵션이다. 가장 간단한 해결 방법이지만, 컨테이너가 종료되는 것이 구동 순서 때문이 아니라 예기치 못한 다른 원인 때문이라면 의도치 않게 컨테이너는 무한히 시작-종료-시작-.. 을 반복하게 될 가능성이 있으며, 그에 따라 메모리 사용량 등도 높아질 수 있을 것 같다.
dockerize 명령의 -wait 옵션을 이용하여 원하는 컨테이너가 정상적으로 준비될 때 까지 기다리도록 할 수 있다.
예를 들어 8888번 포트의 config server가 준비될 때 까지 기다리도록 하는 명령은 아래와 같다.
dockerize -wait http://config:8888 -timeout 20s
아무튼 우여곡절 끝에 아래와 같이 모든 컨테이너를 정상적으로 구동시킬 수 있었다.
그리고 유레카서버에 등록되어 있는 서비스들의 목록도 잘 확인할 수 있었다.