[DockerSwarm + SpringCloud] 1시간 안에 가장 간단한 MSA 구성하기
DockerSwarm은 Kubernetes와 같은 오케스트레이션 툴입니다.
Kubernetes에 비해 생소한 DockerSwarm의 기본 개념과 유용성에 대해 다루고,
DockerSwarm과 SpringCloud를 이용해 간단한 형태의 MSA를 구현해 보겠습니다.
프로젝트 github링크: https://github.com/alska95/simple-msa-project
목차
왜 쿠버네티스 대신 DockerSwarm인가?
오케스트레이션 툴로 가장 널리 알려진 Kubernetes 대신 DockerSwarm을 택한 이유가 궁금하실 것 같습니다.
DockerSwarm을 선택한 가장 큰 이유는 그 간단함과 빠른 학습 곡선입니다.
docker만 설치되어 있다면 바로 이용할 수 있고, 명령어가 간단합니다.
또한 docker compose를 사용 중이라면, docker compose의 스크립트를 그대로 사용 가능합니다.
명령어가 간단한 만큼 Kubernetes보다 제공하는 기능은 적지만, 기본적인 오케스트레이션 툴의 역할에 충실하여
기본 개념을 익히는대에 부족함이 없습니다.
이러한 장점을 이용하여 1시간 안에 간단한 MSA를 구성할 수 있습니다.
가장 간단한 형태의 MSA
제가 정의 내린 가장 간단한 형태의 MSA란
[오케스트레이션 툴 + 서비스 디스커버리 + 게이트웨이 + 마이크로서비스]로 구성된 형태입니다.
아래와 같은 구조를 가지며,
가장 기본적인 기능을 수행하는 것을 목표로 합니다.
1. 사용자 요청을 게이트웨이에서 수신
2. 서비스 디스커버리에 마이크로서비스의 IP 주소 요청
3. 서비스 디스커버리가 마이크로서비스의 IP 주소를 반환
4. 서비스 호출
5. 서비스 응답
6. 사용자에게 응답 전달
각 요소들에 대한 간략한 설명
각 요소들은 얼마든지 다른 어플리케이션으로 대체될 수 있습니다. 기호에 따라 원하는 어플리케이션을 선택하면 될 것 같습니다.
1. 오케스트레이션 툴
Docker Swarm을 사용합니다.
DNS, 로드 밸런싱, 인스턴스 확장, 인그래스 네트워크, overlay 네트워크 등 오케스트레이션 툴의 기본적인 기능을 제공합니다.
2. 게이트웨이
Spring Cloud Gateway를 사용합니다.
필요에 따라 nginx나 다른 어플리케이션으로 대체될 수 있습니다.
게이트웨이는 외부 요청을 서비스 디스커버리를 사용하여 적절한 서비스로 전달합니다.
3. 서비스 디스커버리
서비스 디스커버리는 DNS의 역할을 포함하여 여러 역할을 수행합니다.
서비스 디스커버리의 DNS를 이용하여 게이트웨이에서 요청을 각 서비스로 라우팅 하고,
각 마이크로서비스들끼리 통신이 가능합니다.
따로 구현은 하지 않고 dockerSwarm에서 제공하는 overlay network의 DNS를 사용하여 서비스 디스커버리를 대체할 예정입니다.
Docker Swarm에서는 각 서비스의 이름을 DNS로 사용하여 자동으로 IP 주소를 해석해 줍니다. 여러 인스턴스가 있는 경우 라운드 로빈 방식으로 트래픽을 분배합니다.
만약에 포트 번호를 동적으로 할당하고 싶거나
서비스 디스커버리의 다른 기능이 필요 하다면 spring cloud eureka로 간단히 구현 가능합니다.
4. 마이크로서비스
간단한 형태의 스프링 부트 프로젝트를 사용할 계획입니다.
이미 구현해 둔 프로젝트가 있다면, 별도의 추가 구현 없이 그대로 마이크로서비스로서 이용할 수 있습니다.
가장 간단한 형태의 MSA 구현
모든 기본 요소들에 대한 설명을 마쳤으니 구현을 해보도록 하겠습니다.
github링크의 완성된 프로젝트를 참고해 주시면 좋을 것 같습니다.
프로젝트의 전체적인 구조는 아래와 같습니다.
1. docker swarm 구성
1-1. docker, docker compose 설치 및 로그인
docker과 docker compose를 설치합니다.
docker swarm은 docker만 설치되어있으면 바로 이용 가능합니다.
docker-compose는 여러 도커 컨테이너를 같이 운용할 때 빌드/배포를 매우 편안하게 해주는 툴입니다.
맥북 환경에서
brew install --cask docker를 터미널에 입력하면 docker, docker-compose, docker-desktop이 같이 설치됩니다.
다른 환경이라면 인터넷을 참고하시어 docker과 docker-compose를 설치해 주시면 될 것 같습니다.
설치 이후에는
터미널에서 docker login을 사용하여 로그인을 진행합니다.
1.2. docker swarm init
터미널에 docker swarm init을 입력하여 docker swarm 클러스터를 초기화합니다.
docker swarm을 init한 호스트는 마스터 노드이자 워커 노드로 swarm에 참여하게 됩니다.
다른 노드들을 추가하고 싶다면 init시에 노출된 토큰을 이용하여 참여시키면 됩니다.
1-3. docker swarm 스크립트 구현
docker swarm을 이용하기 위해서 아래와 같은 스크립트를 구성합니다.
파일 이름은 docker-swarm-simple-service.yml로 하겠습니다.
gateway-service와 micro-service 두 가지 서비스를 docker-swarm에 등록합니다.
gateway-service의 8080 포트를 열어두어 gateway에서 유저의 요청을 처리할 것입니다.
이미지 이름에는 자신의 "docker계정 id/서비스 이름" 형태로 입력해주시면 됩니다.
services:
gateway-service:
image: yourid/gateway-service
ports:
- "8080:8080"
deploy:
replicas: 1
restart_policy:
condition: on-failure
networks:
- base-network
build:
context: ../gateway
micro-service:
image: yourid/micro-service
deploy:
replicas: 2
restart_policy:
condition: on-failure
networks:
- base-network
environment:
- SPRING_PROFILES_ACTIVE=alpha
build:
context: ../micro-service
args:
MAVEN_PROFILE: alpha
스크립트 설명을 더보기 란에 넣어두었습니다.
가볍게 읽고 넘어가시면 괜찮을 것 같습니다.
- networks 도커 스웜의 네트워크를 구성합니다. 아래와 같은 형식을 가집니다.
networks:
네트워크 이름:
driver: 네트워크드라이버
예시 스크립트에서는 base-network라는 이름을 가지는 overlay 드라이버를 사용하는 네트워크를 구성합니다. - services 도커 스웜의 서비스들을 구성합니다. 아래와 같은 형식을 가집니다.
services:
서비스 이름:
image: 서비스가 사용하게될 이미지 이름을 입력합니다. 도커 컴포즈가 해당 이미지 이름으로 빌드를 해주니 아무 이름이나 입력하시면 됩니다.
ports:
- "외부에 공개하게 될 포트:컨테이너 안에서 사용하는 포트(host port)"
environment:
- 환경변수이름=환경변수
deploy:
replicas: 복제할 인스턴스 개수입니다. 예시에서는 2개의 마이크로서비스 인스턴스를 생성합니다.
restart_policy:
condition: 컨테이너를 재시작하는 조건을 정의합니다. 예시에서는 실행 실패 시 컨테이너를 재시작합니다.
networks:
- 사용할 네트워크 이름을 입력합니다. 예시에서는 위에서 정의한 base-network를 이용합니다.
build: 컨테이너 빌드/배포를 효율적으로 하기 위해 docker compose의 build기능을 추가했습니다.
context: 빌드할 도커파일의 루트 경로를 입력합니다.
args: 환경 변수를 입력합니다.
1-4. docker-swarm 실행 쉘 스크립트 구현
docker-swarm 명령어들을 일일이 치기는 불편하기 때문에 미리 정의된 간단한 쉘 스크립트를 구현합니다.
파일 이름은 docker-swarm.sh로 하겠습니다.
#!/bin/bash
COMMAND=$1
# Docker Swarm 파일의 경로를 입력합니다.
SERVICE_FILE_PATH=docker-swarm-simple-service.yml
STACK_NAME=base-stack
# 명령에 따라 동작을 수행합니다.
case $COMMAND in
"start")
# Docker Swarm에서 서비스를 시작합니다.
echo "Deploying all services to the stack..."
docker-compose -f $SERVICE_FILE_PATH build
docker-compose -f $SERVICE_FILE_PATH push
docker stack deploy -c $SERVICE_FILE_PATH $STACK_NAME
;;
"stop")
# 전체 스택을 중지합니다.
echo "Removing all services from the stack..."
docker stack rm $STACK_NAME
;;
*)
echo "Invalid command. Please use 'start', or 'stop'."
;;
esac
- sh docker-swarm.sh start를 입력하면
docker-compose로 이미지를 빌드/푸쉬 하고, docker-swarm을 이용하여 배포하게 됩니다. - sh docker-swarm.sh stop을 입력하면
docker-swarm의 서비스들을 모두 종료시킵니다. - 스크립트에서는 다루지 않았지만 update로 무중단 배포 기능을 추가할 수 있습니다.
2. 게이트웨이 서비스 구현
외부 요청을 각 서비스로 전달하기 위한 게이트웨이를 구현합니다.
게이트웨이 프로젝트를 생성하고
라우팅 하고자 하는 서비스에 대한 설정을 추가합니다.
2-1. gateway 프로젝트 생성
기본적인 스프링 프로젝트를 생성하고, 아래 spring cloud gateway 의존성을 추가합니다.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2-2. 라우팅 설정 추가
application.yml에 라우팅 설정을 추가합니다.
가장 기본적인 라우팅 설정만 진행합니다.
spring:
application:
name: gateway-service
cloud:
gateway:
routes:
- id: micro-service
predicates:
- Path=/micro-service/**
uri: http://micro-service:8080
- id: 해당 라우트의 id입니다.
- predicates: 해당 라우트로 보내기 위한 요청인지 판단을 합니다.
위와 같은 설정에서는 /micro-service/**로 들어오는 모든 요청을 uri로 보냅니다. - uri: 라우팅 하게 될 uri를 의미합니다. dns를 이용하여 호출합니다.
2-3. 프로젝트를 배포하기 위한 Dockerfile을 작성하고 pom.xml에서 버전을 설정합니다.
프로젝트의 루트 경로에 Dockerfile을 추가합니다.
주석으로 간단한 설명을 달아두었습니다.
FROM maven:3.8.4-openjdk-17-slim as build
WORKDIR /app
COPY . /app
# Maven 빌드 프로필을 변수로 전달
ARG MAVEN_PROFILE=local
# Docker 이미지 내에서 애플리케이션의 패키징이 이루어진다.
RUN mvn clean package -P${MAVEN_PROFILE} -DskipTests
FROM amazoncorretto:17-al2023-jdk
WORKDIR /app
# 빌드 단계에서 생성된 JAR 파일이 복사된다.
COPY --from=build /app/target/gateway-1.0.jar gateway-1.0.jar
# java -jar gateway-1.0.jar 명령을 통해 애플리케이션이 시작된다.
ENTRYPOINT ["java","-jar","gateway-1.0.jar"]
pom.xml의 버전을 1.0으로 수정해야 합니다.
3. 마이크로서비스
요청이 들어오면 "hello + 호스트 이름" 을 반환하는 서비스를 구성해 보겠습니다.
기본적인 형태의 스프링 부트 프로젝트입니다.
기존 스프링 부트 프로젝트가 있다면 해당 프로젝트를 그대로 사용하셔도 무방합니다.
3-1. 스프링 웹 프로젝트를 생성합니다.
3-2. "hello + 호스트 이름" 을 반환하는 컨트롤러를 구성합니다.
3-3. Gateway와 마찬가지로 Dockerfile을 작성하고 pom.xml에서 버전을 설정합니다.
pom.xml의 버전을 1.0으로 수정해야 합니다.
마이크로서비스 테스트
위에서 마이크로서비스의 구성과 구현에 대한 설명을 마쳤습니다.
이제 sh docker-swarm.sh start 명령어로 gateway, docker-swarm, 마이크로서비스를 실행시키고 테스트를 할 수 있습니다.
서비스가 잘 동작하는지 테스트를 진행해 보겠습니다.
1. docker-swarm.sh이 있는 경로에서 sh docker-swarm.sh start를 입력합니다.
아래는 docker-desktop의 화면입니다.
정상적으로 실행된 것을 확인할 수 있습니다.
2. 서비스에 요청을 보내어 요청이 잘 처리가 되는지, 도커 스웜의 round-robin 로드벨런싱이 잘 동작하는지 테스트합니다.
두 번의 요청을 보냈는데
첫 번째 요청은 첫 번째 인스턴스에서 처리되었고,
두 번째 요청은 라운드 로빈에 의해 두 번째 인스턴스에서 정상적으로 처리되었음을 확인할 수 있습니다.
마치며
DockerSwarm과 SpringCloudGateway를 이용하여 간단한 MSA를 구성해 보았습니다.
1시간 정도의 투자를 통해 MSA의 핵심 요소를 직접 경험하고,
프로젝트에서 활용할 수 있는 기초 지식을 습득하는 기회가 되었길 기대합니다.
앞으로 이 간단한 MSA 구조를 기반으로, 보안, 성능 최적화, 모니터링 등의 추가 요소를 다루어 더욱 발전된 MSA를 구현해 보겠습니다.
추가적인 의견이나 개선점이 있다면 언제든지 공유 부탁드립니다.
부족한 글 끝까지 읽어주셔서 감사드립니다.