5 분 소요

개요

Running Microservices

마이크로 서비스를 운영하는 방식에는 아래 보이는 여섯가지가 있다.
스크린샷 2022-10-17 오후 3 02 35

이 중에서, 이번 글에서는 [Docker + Local]을 다뤄볼 것이다.
나중에 다른 환경에 배포한다고 해도, Docker 이미지를 그대로 컨테이너화 시켜 사용할 수 있다.

Running Microservices in Local

스크린샷 2022-10-17 오후 3 15 29
지금까지 꽤 많은 마이크로 서비스들을 개발했다.
이번에는 해당하는 마이크로 서비스들을 이미지로 만들 것이고, 회색으로 보이는 서비스들에 대해서도 도커 컨테이너 환경에서 구동해 볼 것이다.

그 전에 다양한 서비스들이 서로 통신할 때 무리가 없도록 하나의 가상의 네트워크를 가질 수 있도록 구축하자.

Docker Container

0. Create Bridge Network

  • Bridge network
    호스트 pc와 별도의 가상의 네트워크를 만들고, 그 가상의 네트워크에서 컨테이너들을 배치해 사용하는 방식이다.
    $ docker network create --driver bridge [브릿지 이름]
    
  • Host network
    - 네트워크를 호스트로 설정하면 호스트의 네트워크 환경을 그대로 사용
    - 포트 포워딩 없이 내부 어플리케이션 사용
  • None network
    - 네트워크를 사용하지 않음
    - Io 네트워크만 사용, 외부와 단절

이제 터미널로 네트워크를 생성해보자.

그 전에 불필요한 리소스(중지되어있는 컨테이너, 사용되고 있지 않은 네트워크, 이전단계의 이미지와 캐시되어 있는 값)를 모두 삭제하는 커맨드를 통해 삭제하고 진행하자.

$ docker system prune

스크린샷 2022-10-17 오후 3 32 52

이제 네트워크를 확인해보면, 기본적으로 제공되는 세 가지 네트워크(bridge, host, null)가 존재할 것이다.

$ docker network ls

스크린샷 2022-10-17 오후 3 34 00

이제 네트워크를 생성해보자.

# 네트워크 생성
$ docker network create --gateway 172.18.0.1 --subnet 172.18.0.0/16 ecommerce-network

# 네트워크 목록 확인
docker network ls

# 특정 네트워크 상세 보기
$ docker network inspect ecommerce-network

스크린샷 2022-10-17 오후 3 40 25
게이트웨이(--gateway 172.18.0.1)와 서브넷 마스크(--subnet 172.18.0.0/16)를 지정하지 않고 만들 수도 있지만,
그랬을 때는, 자동으로 컨테이너의 ip가 할당되게 하는 옵션 말고
직접 ip address를 지정해서 컨테이너를 띄울 수도 있는데, 이 경우에 오류가 발생할 수 있다.

docker network inspect ecommerce-network 커맨듣로 방금 생성한 네트워크를 확인해보면,
subnet과 gateway가 지정되었음을 알 수 있고, 현재 해당 네트워크에 추가되어진 컨테이너가 아무것도 없음을 확인할 수 있다.

이런식으로 네트워크를 직접 생성해서 사용하게 되면 좋은 점은,
일반적인 컨테이너들끼리는 IP Address를 이용해 통신을 하게 되는데,
같은 네트워크에 포함되어있는 컨테이너들끼리는 IP Address 외에도, 컨테이너 ID 혹은 이름을 이용해 통신할 수 있다.

이것이 장점인 이유는, 도커 컨테이너를 생성할 때에는 비어있는 IP부터 순차적으로 할당되기 때문에
어떤 환경에서 각각의 마이크로 서비스들이 배포되었을 때 IP Address가 유동적으로 변경될 수 있고,
IP Address로만 통신을 하게 되면, 변경된 IP에 따라 다른 서비스들에 각각 다시 설정을 해주어야하는 불편함이 있기 때문이다.

이 때, --name 옵션을 통해 지정한 컨테이너의 이름을 이용해 통신하게 되면,
IP Address가 변경되더라도 다른 쪽에 있는 마이크로 서비스 혹은 서버들에는 변경 사항이 없을 것이다.
스크린샷 2022-10-17 오후 3 53 02

1. RabbitMQ

https://rabbitmq.com/

도커 컨테이너로 변환시킬 첫 번째 항목은 configuration service 이다.
그런데, configuration service에서 변경된 설정 파일의 내용을 모든 마이크로 서비스들에게 한꺼번에 업데이트 시켜주기 위해 사용했던 것이 Spring Cloud Bus 였다.
Spring Cloud Bus 에서 사용할 수 있는 messaging queueing server로써 RabbitMQ를 사용했다.

기존에는 RabbitMQ를 로컬에 직접 설치헤서 사용했다면, 이를 도커 컨테이너화 시켜서 기동해보자.

위 레빗엠큐 공식 홈페이지에 접속해 [Get Started] > [Download + Installation]를 클릭하자.
그리고 아래로 내리다보면 레빗엠큐를 도커로 어떻게 설치하면 좋은지` 나와있다.
스크린샷 2022-10-17 오후 4 09 57
헤딩 링크를 클릭하게 되면 도커 허브 사이트로 이동하는데, 여기서 사용하고자하는 태그를 선택해 이미지를 pull 받을 수 있다.

$ docker pull rabbitmq:management

그리고 아래와 같이 컨테이너를 생성하고 기동할 수 있다.
(미리 pull 받지 않고 run만 해도 pull을 받아오기 때문에 나는 아래 커맨드만 실행했다.)

$ docker run -d --name rabbitmq --network ecommerce-network \
-p 15672:15672 -p 5672:5672 -p 15671:15671 -p 5671:5671 -p 4369:4369 \
-e RABBITMQ_DEFAULT_USER=guest \
-e RABBITMQ_DEFAULT_PASS=guest \
rabbitmq:management

만약 해당 포트가 이미 사용중이라면?

해당 포트를 사용 중인 프로세스를 찾아 kill 한 뒤에 다시 기동한다.
(이 때 이미 생성된 rabbitmq 컨테이너도 삭제한 뒤에 다시 run 하자.)

$ sudo lsof -i tcp:4369
$ sudo kill -9 42938

스크린샷 2022-10-17 오후 4 19 55

그리고 rabbimq 컨테이너가 아까 생성한 ecommerce-network를 사용 중인지 확인해야 한다!

$ docker network inspect ecommerce-network

스크린샷 2022-10-17 오후 4 33 12
172.18.0.2이라는 IP Address가 할당된 것을 확인할 수 있다.
앞으로 ecommerce-network를 사용하는 컨테이너들은 RabbitMQ 컨테이너를 호출할 때,
172.18.0.2라는 IP Address로 호출하거나 rabbitmq라는 이름으로 호출할 수 있다.

이제 웹 브라우저로 접속해보자.
스크린샷 2022-10-17 오후 4 38 16
정상적으로 접속할 수 있고, username과 password는 모두 guest이다.

이렇게 첫 번째로 RabbitMQ 서비스를 도커 컨테이너화 시켜서 기동해보았다.

2. config-service

이제 Configuration Service를 도커 이미지화 한 뒤, 컨테이너를 통해 기동해보자.

그런데, 이전에 configuration server 안에 있는 민감 정보를 암호화하기 위해서 keytools를 통해 키 파일을 생성했었다.
이따가 이 키 파일을 컨테이너에 복사할 예정이기 때문에, 코드에서도 키 파일의 위치를 변경하자.

1) 키 파일 위치 변경

키 파일을 프로젝트 안으로 복사/붙여넣기

Finder에서 키 파일(apiEncryptionKey.jks)의 위치로 이동하자.
그리고 키 파일을 복사 한 뒤에 config-service 프로젝트 안에 붙여넣자.

스크린샷 2022-10-17 오후 5 01 53

스크린샷 2022-10-17 오후 5 03 51

bootstrap.yml에서 키 파일 위치 변경

컨테이너에서는 더이상 키파일의 위치가 /study/msa/...가 아니기 때문에 아래와 같이 변경하자.

encrypt:
  key-store:
    # location: file://${user.home}/study/msa/keystore/apiEncryptionKey.jks
    location: file:/apiEncryptionKey.jks # 🌟 컨테이너 안에 복사될 키 파일의 위치로 변경
    password: test1234
    alias: apiEncryptionKey

application.yml에서 구성 파일 위치 변경

지금까지는 native file repo에 있는 설정 파일(.yml)을 이용했는데,
이제부터는 remote repository에 있는 설정 파일을 사용하자.

server:
  port: 8888

spring:
  application:
    name: config-service
  rabbitmq:
    host: 127.0.0.1 # 🌟 docker run 에서 옵션으로 변경한다.
    port: 5672
    username: guest
    password: guest
  profiles:
    active: native # 🌟 docker run 에서 옵션으로 변경한다.
  cloud:
    config:
      server:
        native:
          search-locations: file://${user.home}/study/msa/native-file-repo
        git:
#          uri: file:///Users/minju/study/msa/git-local-repo # local git repository 위치
          uri: https://github.com/minju412/spring-cloud-config # 🌟 remote git repository 주소
#          username: ## remote git repository 가 private 이라면 username 과 password 도 입력한다.
#          password:
management:
  endpoints:
    web:
      exposure:
        include: health, busrefresh

2) Dockerfile을 통해 이미지 만들기

config-service를 Dockerfile을 통해 이미지화 할 수 있다.
Dockerfile

FROM openjdk:17-ea-11-jdk-slim

VOLUME /tmp

COPY apiEncryptionKey.jks apiEncryptionKey.jks

COPY target/config-service-0.0.1-SNAPSHOT.jar ConfigService.jar

ENTRYPOINT ["java","-jar","ConfigService.jar"]

이 때, 중요한건 configuration server 안에 키 값이 저장되어 있었다는 것이다.
(이전에 configuration server 안에 있는 민감 정보를 암호화하기 위해서 keytools를 통해 생성했었다.)
이 파일 역시 컨테이너 안에 포함되어 있어야 한다.
따라서 Dockerfile 안에 COPY apiEncryptionKey.jks apiEncryptionKey.jks 커맨드를 포함하여
host에 존재하는 파일을 도커 컨테이너 안으로 복사하자.

3) 이미지 빌드 및 도커 허브에 업로드

인텔리제이 터미널을 사용해도 되고, iTerm을 사용해도 된다.

# 최신 버전으로 컴파일
$ mvn clean compile package -DskipTests=true 
# clean은 기존 파일을 지운다.
# package 옵션으로 jar 파일까지 생성할 수 있다.
# 테스트 코드는 skip

# 이미지 파일 빌드
$ docker build -t ln8847/config-service:1.0  .

# 생성된 이미지 확인
$ docker images | grep config-service

# 도커 허브에 업로드
$ docker push ln8847/config-service:1.0

스크린샷 2022-10-17 오후 5 12 09
이미지가 생성되었기 때문에, 이 이미지를 이용해 컨테이너를 실행해보자.

🌟중요!
현재 config-serviceapplication.yml에는 rabbitmq의 host127.0.0.1로 되어있다.
이를 rabbitmq 도커 컨테이너의 IP Address로 변경해주어야 한다!

변경하는 방법에는 application.yml을 직접 수정할 수도 있지만 config-servicerun 할 때 옵션으로 지정할 수도 있다.
이번에는 코드는 변경하지 않고 옵션으로 지정해볼 것이다.
그리고 IP Address를 직접 사용하지 말고 “컨테이너의 이름”을 사용하자! (IP Address는 변경될 수 있기 때문이다.)

(당연히 rabbitmq 컨테이너도 실행중이어야 한다.)

4) 도커 컨테이너 실행

$ docker run -d -p 8888:8888 --network ecommerce-network \
-e "spring.rabbitmq.host=rabbitmq" \
-e "spring.profiles.active=default" \
--name config-service ln8847/config-service:1.0

스크린샷 2022-10-17 오후 5 30 49

이전과 마찬가지로 네트워크를 확인해보자.

$ docker network inspect ecommerce-network

스크린샷 2022-10-17 오후 5 32 07
방금 기동한 config-service와 이전에 기동한 rabbitmq가 둘 다 ecommerce-network안에 잘 포함되어 있는 것을 확인할 수 있다.

로그를 확인해보자.

$ docker logs [CONTAINER ID 혹은 이름]

스크린샷 2022-10-17 오후 5 34 48

🧐 참고
만약, config-service의 소스 코드 혹은 application.yml가 변경되었을 때에는,
[컴파일 -> 이미지 빌드] 과정을 다시 진행해야한다!🌟

$ mvn clean compile package -DskipTests=true 
$ docker build -t ln8847/config-service:1.0  . # 태그를 변경하지 않으면 기존 이미지에 override 된다.

그리고 docker images 커맨드를 실행해보면 이전 이미지는 <none>으로 표시되어 있을 건데,
컨테이너 공간 확보를 위해 이런 이미지들은 삭제하자.

이제, 마지막으로 구성 정보를 잘 읽어왔는지 웹 브라우저에서 확인해보자.
127.0.0.1:8888/ecommerce/default에 접속하자.
스크린샷 2022-10-17 오후 5 54 00
원하던대로 native repository가 아닌, 깃 허브(remote repository)에서 구성 정보를 가져오고 있음을 확인할 수 있다👍

마찬가지로 user-service도 확인할 수 있다.
스크린샷 2022-10-17 오후 5 55 58



💛 개인 공부 기록용 블로그입니다. 👻

맨 위로 이동하기