[LG CNS AM Inspire Camp 1기] MSA (3) - Config Server & RabbitMQ

정성엽·2025년 3월 11일
0

LG CNS AM Inspire 1기

목록 보기
57/70
post-thumbnail

INTRO

이전 포스팅에서는 API Gateway에 대해서 알아봤다.

API Gateway는 MSA와 같은 분산 시스템 환경에서 요청을 적절하게 라우팅을 해주기 위해 사용했었다.

앞으로도 계속해서 MSA 아키텍처에서 필요한 내용을 하나씩 정리해볼건데, 이번에는 여러가지 서비스의 설정 정보를 한군데에서 관리하도록 도와주는 Config Server에 대해서 정리해보려고 한다 👀


1. Config Server란?

Config Server는 MSA 환경에서 여러 서비스 인스턴스의 설정 정보를 중앙에서 관리하기 위한 컴포넌트이다.

분산 시스템에서는 각 서비스마다 다양한 환경(개발, 테스트, 운영)에 맞는 설정 정보가 필요한데, 이를 각 서비스에 직접 포함시키면 관리가 복잡해지는 문제가 있다.

예를들어서, JWT 생성과 검증에 사용되는 Secret Key가 변경된 상황을 생각해보자

만약, 모든 서비스에서 JWT 필터를 적용하고 있다면 다른 모든 서비스에서도 Secret Key 관련 설정 정보를 변경하고 다시 빌드하고 도커 허브에 올리고 배포해야하는 번거로운 작업이 될 수 있는 것이다.

💡 Config Server의 필요성

12factors라는 클라우드 네이티브 애플리케이션 개발 원칙 중 하나는 다음과 같다.

"개발, 테스트, 운영 환경이 일치하되, 간단한 조작으로 유연하게 변경할 수 있어야 한다"

여기서 Config Server는 하나의 중앙화된 저장소에서 구성 요소를 관리하는 기능을 제공하며, 각 서비스는 시작 시 Config Server로부터 설정 정보를 가져와서 사용한다.

이렇게 하면 설정 정보의 변경이 필요할 때 서비스를 다시 빌드하지 않고도 쉽게 변경할 수 있다.

즉, Config Server는 이런 원칙을 구현하는 데 도움을 준다!


2. Config Server의 동작 방식

Config Server는 일반적으로 Git Repository를 백엔드 저장소로 사용한다.

설정 파일을 Git에 저장함으로써 버전 관리가 가능하고, 여러 환경에 대한 설정을 비교적 체계적으로 관리할 수 있다는 장점이 있기 때문이다.

💡 계층 구조

Config Server는 계층 구조 를 사용하여 설정 정보를 관리한다.

조금 더 자세히 정리하면 다음과 같은 우선순위로 설정 정보를 찾는다고 볼 수 있다.

계층 구조

  • application.yml
  • application-{profile}.yml
  • {application-name}.yml
  • {application-name}-{profile}.yml

따라서 user-service-dev.yml 가 없다면 user-service.yml 을 가져오고, 그것도 없다면 application-dev.yml 을, 마지막으로 application.yml 에서 데이터를 가져온다.

간단하게 생각하면 서비스와 가장 가까운 정보(구체적인 설정 정보)부터 찾아서 가져오려고 시도한다고 이해하면 된다!


3. Config Server 구현

우선 위와 같이 Config Server 관련 의존성을 추가해야 한다.

의존성이 추가되었다면 application.yml에서 config server 설정을 진행해주면 된다.

코드를 살펴보자

💡 Config Server 설정

Sample Code

server:
  port: 8888

spring:
#  profiles:
#    active: native
  application:
    name: config-service
  cloud:
    config:
      server:
        native:
          search-locations: file:///Users/yeop/Desktop/LG_CNS_AM_Inspire_Camp/MSA
        git: #default
          uri: # 설정 정보를 관리하는 Github 주소 입력
          default-label: master # 해당 Github의 브랜치 설정
#          username: <github-id>
#          password: <gihub-accessToken>
        bootstrap: true

management:
  endpoints:
    web:
      exposure:
        include: health, busrefresh, refresh, metrics

설정 정보를 정리해보면 다음과 같이 정리할 수 있다.

즉, native 설정을 켜주면 search-locations로 지정한 디렉토리에서 설정 파일을 찾고, native 설정이 꺼져있다면 git으로 설정한 디렉토리에 접근하여 설정 파일을 찾는 것이다.

다음으로 BootApplication에서 다음 코드와 같이 @EnableConfigServer 어노테이션을 추가해주면 우선은 마무리 된다.

Sample Code

@SpringBootApplication
@EnableConfigServer
public class ConfigServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServiceApplication.class, args);
    }
}

💡 Client 서비스 설정

Config Server를 사용하는 클라이언트 서비스는 다음과 같이 설정 정보를 작성해야 한다.

Sample Code

spring:
  config:
    import: optional:configserver:http://127.0.0.1:8888/
  cloud:
    config:
      name: user-service
      ...

위처럼 spring.config.import : ~~ 에서 Config Server를 찾아오는 모습이다.

여기서 필자는 configserver가 디스커버리 서비스에서 대상을 찾기 위해 사용하는 검색어인줄 알았으나 관련 내용을 정리해보면 다음과 같다.

optional:configserver:http://127.0.0.1:8888/ 정리


-optional:

  • Config Server에 연결할 수 없더라도 애플리케이션이 시작할 수 있게 해주는 Prefix이다.

configserver:

  • 이것은 Spring Cloud Config의 특별 구문으로, Config Server를 사용한다는 의미를 가진다.

http://127.0.0.1:8888/

  • 실제 Config Server의 URL이다.
  • 이전에 8888 포트를 Config Server로 지정했기 때문에 위와같은 URL을 사용한다.

즉, 여기서 configserver 는 Spring Cloud Config Client가 인식하는 특별한 접두사(prefix)인 것이다.

이 접두사는 Spring Boot가 Config Server에 연결해야 함을 알려주는 마커(marker)라고 이해하면 될 것 같다!

실제로 서비스를 실행시켜보면 Config Server에서 데이터를 패치받는 모습을 볼 수 있다.


4. 설정 변경 감지와 적용

Config Server의 한 가지 제한사항은 설정 정보가 변경되더라도 서비스를 재시작하지 않으면 변경 사항이 적용되지 않는다는 점이다.

즉, 편하게 사용하려고 Config Server를 사용하지만 위의 설정 그대로 사용한다면 설정 정보가 변경된 순간 서비스를 다시 실행해야하는 불편함이 그대로 존재하게 된다.

실제로 강의에서 실습하는 과정에서 대부분의 시간을 여기에 할애했다..
(MSA를 적용했기 때문에 서비스가 많아서 실습이 꽤 까다로웠다)

그래서 필자는 최종 결론만 정리해보려고 한다 😅

💡 Spring Cloud Bus를 이용한 자동 갱신

이전에 언급한 문제를 해결하기 위해 Spring Cloud Bus와 메시지 브로커(RabbitMQ, Kafka 등)를 사용할 수 있다.

설정이 변경되면 Config Server가 메시지 브로커를 통해 모든 서비스에 변경 사항을 알리고, 각 서비스는 자동으로 설정을 갱신하는 것이다.

우선 메세지 브로커를 이용하지 않았을 때의 구조를 살펴보면 다음과 같다.

위 사진과 같이 Github에서 토큰 정보를 변경하면 Config-Server에는 즉시 반영되지만, Config-Server에서 설정 정보를 받아오는 다른 서비스에는 refresh를 걸어주지 않는이상 기존 토큰 정보를 가지고 있게 된다.

그래서 이러한 문제를 해결하기 위해 메세지 브로커를 적용한 구조를 살펴보면 다음과 같다.

GitRepository에서 설정 정보가 변경되면 우선 Config-Server에는 바로 반영된다.

여기서 개발자는 /busrefresh 라는 엔드포인트를 호출하여 메세지 브로커에 메세지를 발행시킨다.

RabbitMQ에 메세지가 저장되면, 이를 구독하는 모든 서비스들에게 변경 내용을 알려주고 서비스들은 이 내용으로 설정 정보를 변경하게 된다.

(물론 설정이라는 특성상 모든 설정에 대하여 동적으로 변경이 가능한 것은 아니다.)

💡 Spring Cloud Bus & RabbitMQ 설정 추가

이제부터는 위에서 설명한 구조대로 동작할 수 있도록 설정 정보를 수정해보자

우선 메세지 브로커에 연결해야할 서비스들 (Config Server와 Config Server에서 설정 정보를 가져올 서비스들)에 한가지 의존성을 추가해야한다.

메세지 브로커를 사용할 서비스들에 위 의존성이 추가되었다면, 메세지 브로커로 사용할 RabbitMQ를 설치해야 한다.

간단하게 진행하기 위해 Docker Hub에 등록된 RabbitMQ 이미지를 사용하도록 하자

RabbitMQ 컨테이너 실행
docker run -d -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:4.0-management


  • RabbitMQ 이미지는 먼저 Pull을 해야한다.
  • 여기서 웹 서버 포트를 지정해줄 수 있는데 필자는 15672로 지정했다.

Config-Server 설정 추가

Sample Code

server:
  port: 8888

spring:
#  profiles:
#    active: native
  application:
    name: config-service
  cloud:
    config:
      server:
        native:
          search-locations: file:///Users/yeop/Desktop/LG_CNS_AM_Inspire_Camp/MSA
        git: #default
          uri: # Github 주소
          default-label: master
#          username: <github-id>
#          password: <gihub-accessToken>
        bootstrap: true
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest

management:
  endpoints:
    web:
      exposure:
        include: health, busrefresh, refresh, metrics

사진과 같이 rabbitmq 관련 정보를 추가해주자

우선 로컬 환경에서 진행하기 때문에 127.0.0.1을 사용하고 도커 컨테이너를 띄울때 사용한 5672 포트를 사용하여 바인딩을 시켜준다.

기본 아이디와 비밀번호는 guest 이므로 이 정보도 넣어준다.

여기서 이전에 설명하지 않고 넘어갔던 부분인 management 는 Actuator 기능을 사용하기 위한 설정이다.

우리는 busrefresh 엔드포인트를 호출하여 메세지를 발행해야하기 때문에 사용한다고 일단 알아두자!

마찬가지로 메세지 브로커를 사용할 서비스들에도 위와 같은 rabbitmq 설정을 추가해주면 된다.

💡 RabbitMQ 접근 & 팁?

이전에 컨테이너 실행시 설정했던 웹포트 15672로 접근하면 다음과 같은 화면을 볼 수 있다.

이후, busrefresh 엔드포인트를 호출하면 메세지 브로커를 구독하는 서비스에도 변경된 설정 정보가 반영되게 된다!

여기서 다음 사진을 살펴보자

즉, 8888(Config-Server)가 아니라 메세지 브로커를 구독하는 8000(User-Service)에서 actuator/busrefresh를 호출해도 정상적으로 동작한다.

Config Server에 직접 /actuator/busrefresh를 호출하지 않아도, 메시지 브로커(RabbitMQ)에 연결된 어떤 서비스에서든 /actuator/busrefresh를 호출하면 설정 변경이 모든 서비스에 전파된다.

동작 과정을 살펴보면 다음과 같다.

메세지 브로커 동작 과정
1. GitHub에서 설정 변경 발생

  1. Config Server는 변경 사항 감지
  1. 어떤 서비스든(ex: user-service) /actuator/busrefresh 엔드포인트 호출
  1. 해당 서비스는 Spring Cloud Bus를 통해 메시지 브로커에 메시지 발행
  1. 메시지 브로커는 동일한 버스에 연결된 모든 다른 서비스(Config Server 포함)에게 이 메시지 전달
  1. 각 서비스는 메시지를 받고 Config Server에서 새 설정을 가져와 적용

정리하면 동일한 메세지 버스에 각 서비스가 연결되어있기 때문에, busrefresh를 호출할 수 있으며, 한 서비스에서 보낸 메세지는 동일한 버스에 연결된 모든 서비스에 전달되기 때문에 user-service에서 호출해도 변경된 설정이 모두 반영될 수 있다는 것을 기억하자!


5. 암호화 / 복호화

우리는 Github Repository에서 비밀키와 같은 데이터를 관리한다면 아무래도 민감한 정보이기 때문에 암호화된 값으로 저장하는게 보안 측면에서 더 안전한 방법일 것이다.

따라서, Config Server에서 중요한 키들을 관리한다고 하면, 암호화 / 복호화 기능이 Config Server가 시작되는 초기 단계에서 제공되어야 할 것이다.

bootstrap.yml은 Spring Cloud 애플리케이션이 시작될 때 가장 먼저 로드되는 설정 파일이기 때문에, 이 파일을 별도로 생성해서 암호화 / 복호화를 진행해보도록 하자

💡 비대칭키 암호화

RSA 알고리즘을 사용하는 비대칭키 암호화를 구현하기 위해, JAVA의 keytool을 이용해서 key-store를 생성할 수 있다.

명령어는 다음과 같다.

KeyStore 생성
keytool -genkeypair -alias apiEncryptionKey -keyalg RSA -dname "CN=Config Server,OU=Spring Cloud,O=Organization" -keypass 1q2w3e4r -keystore apiEncryptionKey.jks -storepass 1q2w3e4r


-genkeypair : 공개키/개인키 쌍을 생성
-alias apiEncryptionKey : 키 쌍의 별칭 설정
-keyalg RSA : RSA 알고리즘 사용
-dname : 인증서 소유자 정보
-keypass 1q2w3e4r : 키 비밀번호
-keystore apiEncryptionKey.jks : 생성할 키스토어 파일 이름
-storepass 1q2w3e4r : 키스토어 비밀번호


위 명령어를 입력하면 사진과 같이 jks 확장자를 가진 파일이 하나 생성되는 모습을 볼 수 있다.

다음으로 Config Server에서 application.yml과 동일한 위치에 bootstrap.yml을 생성하고 다음과 같이 코드를 작성해주자

Sample Code

encrypt:
    key-store:
        location: file: # 방금 생성한 jks 파일 경로
        password: 1q2w3e4r
        alias: apiEncryptionKey

우리는 Config Server 설정 파일로 등록했기 때문에 8888번 포트에 encrypt 엔드포인트로 plain text를 날려주면 다음과 같은 사진을 볼 수 있다.

Result View


우리는 이 값을 깃허브에 저장해서 암호화된 키 값으로 사용하면 된다.

이렇게 사용할 수 있는 이유는 Config Server에서 Github에 저장된 암호화된 Key를 가져올 때, 이전에 생성한 KeyStore를 이용하여 복호화를 진행하기 때문에 결과적으로 decrypt된 값을 사용할 수 있다.

참고로 Github에 암호화된 값을 저장할 때는 {ciper} 라는 Prefix를 붙여서 저장하면 된다.


OUTRO

이번 포스팅에서는 Spring Cloud Config Server에 대해 자세히 알아봤다.

MSA 환경에서 여러 서비스의 설정을 중앙에서 관리하는 Config Server는 개발 생산성과 시스템 안정성을 크게 향상시키는 중요한 컴포넌트다.

Config Server와 메시지 브로커를 함께 사용하면 여러 서비스 간의 설정 정보를 효율적으로 관리하고 동기화할 수 있어, MSA의 복잡성을 줄이는 데 큰 도움이 된다는 것을 기억하자 👊

profile
코린이

0개의 댓글