재배포 없이 자동 반영? Spring Cloud Config 파일 변경 흐름 파헤치기

2025. 5. 18. 18:48· Spring
목차
  1. Spring Cloud Config란?
  2. Config 파일 변경 적용 흐름
  3. Config Server는 어떤 서버인가요?
  4. 모든 애플리케이션이 리프레시 될까요?
  5. 설정 파일을 변경 후 Git 저장소로 Push
  6. Server의 /monitor 엔드포인트로 요청
  7. Config Server에서의 /monitor 엔드포인트 요청 처리
  8. Spring Cloud Bus와 이벤트 전파
  9. 정리
  10. 변경 흐름 요약
  11. 마무리

Spring Cloud Config란?

각 서비스는 자체적인 설정 정보를 가지고 있으며, 보통 application.yml 또는 application.properties 파일에서 관리합니다. 하지만 설정 정보 하나를 변경하기 위해 애플리케이션을 재배포해야 하는 경우가 많습니다. 또한 여러 서비스의 동일한 설정을 변경해야 할 때는 각 서비스마다 개별적으로 수정해야 하는 번거로움이 있습니다.

 

이런 상황에서 Spring Cloud Config를 사용하면 여러 서비스의 설정 정보를 중앙에서 관리하고, 변경된 설정을 실시간으로 각 서비스에 적용할 수 있습니다.

 

Spring Cloud Config는 분산 환경에서 애플리케이션 설정을 중앙화하고, 설정 변경 시 실시간으로 이를 반영할 수 있는 기능을 제공합니다. 이 글에서는 Spring Cloud Config에서 Git 저장소의 Webhook과 Config Server의 /monitor 엔드포인트를 통해 설정 정보를 변경하는 방식을 설명하며, 설정 파일 변경이 애플리케이션에 반영되는 흐름과 관련 코드를 통해 동작 원리를 살펴보겠습니다. 메시지 브로커로는 Spring Cloud Bus와 함께 Kafka를 사용했습니다.

 

 

Config 파일 변경 적용 흐름

Spring Cloud Config 환경에서 Config 파일을 변경하는 흐름은 다음과 같습니다.

  1. Git 저장소에 변경 파일을 Push
    Config 파일이 저장된 Git 저장소에 원하는 변경 사항을 커밋하고 Push합니다.
  2. Webhooks를 통해 Config Server에 알림 전송
    Git 저장소에 설정된 Webhooks가 Config Server의 /monitor 엔드포인트를 호출하여 변경 사항을 알립니다.
  3. Config Server에서 이벤트 생성
    Config Server는 Spring Cloud Bus를 통해 이벤트를 생성하여 변경 사항을 관련 애플리케이션에 전달합니다.
  4. 애플리케이션에서 이벤트 수신 및 변경 사항 반영
    Spring Cloud Bus를 통해 전달된 이벤트를 애플리케이션이 수신하고, 변경된 설정을 반영합니다.

 

 

Config Server는 어떤 서버인가요?

Spring Cloud Config Server는 분산 환경에서 애플리케이션의 설정을 중앙 집중화하여 관리할 수 있도록 도와주는 Spring Cloud 프로젝트의 구성 요소입니다. 각 서비스의 설정을 개별적으로 관리하는 것이 아니라 Config Server에서 중앙에서 설정을 제공하고, 클라이언트 애플리케이션들이 이를 가져가도록 합니다.

 

 

모든 애플리케이션이 리프레시 될까요?

Git 저장소에서 Config 파일을 변경하면 관련된 모든 애플리케이션이 리프레시되는 것일까요? Spring 공식 문서에서는 다음과 같이 설명합니다.

by default, it looks for changes in files that match the application name (for example, foo.properties is targeted at the foo application, while application.properties is targeted at all applications).

즉, 기본적으로 애플리케이션 이름과 일치하는 파일의 변경 사항만 해당 애플리케이션에 적용됩니다. 따라서 변경된 파일과 관련된 애플리케이션만 리프레시가 발생합니다.

 

그러면 변경되는 대상으로만 리프레시가 일어난다고 하는데 어떤 과정이 일어나는지 코드로 직접 확인해보겠습니다.

먼저 hello와 test-hello라는 서비스가 있다고 가정해봅시다.

  • hello와 test-hello라는 두 개의 서비스가 있습니다.
  • 서비스 이름은 spring.application.name으로 설정됩니다.
  • Config Repository에 각 서비스의 환경별 yml 파일이 존재합니다.
  • test-hello-dev.yml 파일을 수정했다고 가정합니다.

 

설정 파일을 변경 후 Git 저장소로 Push

개발자는 test-hello-dev.yml 파일을 변경한 후 Git 저장소로 푸시합니다. 이때 Git 저장소는 Config Server가 참조하고 있는 저장소를 말하며, GitHub, GitLab 등 어떤 것이든 사용 가능합니다.

 

Server의 /monitor 엔드포인트로 요청

Git 저장소에는 웹훅을 설정할 수 있습니다. Config Repository의 main 브랜치로 푸시하는 경우 {config server 주소}/monitor로 웹훅을 설정합니다. 여기서는 GitLab을 사용했습니다.

 

Config Server에서의 /monitor 엔드포인트 요청 처리

Config Server에서 /monitor 엔드포인트 요청을 처리하는 코드는 PropertyPathEndpoint 클래스에 정의되어 있습니다. 

PropertyPathEndpoint의 notifyByPath 메서드가 요청을 받습니다. 먼저 this.extractor의 extract 메서드를 살펴봅시다. this.extractor는 CompositePropertyPathNotificationExtractor 클래스의 인스턴스입니다.

CompositePropertyPathNotificationExtractor는 여러 등록된 PropertyPathNotificationExtractor를 순회하며 result가 null이 아닌 PropertyPathNotificationExtractor를 찾습니다.

등록된 PropertyPathNotificationExtractor는 총 7개 있으며, GitLab을 사용했으므로 GitlabPropertyPathNotificationExtractor가 활성화됩니다.

추출한 result를 보면 path에 test-hello-dev.yml이 담겨있는데, 이는 우리가 수정한 파일명입니다.

다시 처음 /monitor 엔드포인트를 처리하는 notifyByPath 메서드로 돌아가보면, extractor에서 추출한 notification에는 변경한 파일명이 있습니다. notification이 null이 아니므로 조건문 안으로 진입합니다.

이때 guessServiceName이라는 내부 메서드를 호출하게 됩니다. 이 메서드는 파일 경로에서 서비스명으로 추측되는 이름들을 추출합니다. 먼저 파일 확장자를 제거하고, 뒤에서부터 '-'로 구분되는 문자열을 제거하면서 서비스명을 추출합니다.

따라서 test-hello-dev.yml에서는 test-hello-dev, test-hello, test 이렇게 3개의 서비스명이 추출됩니다.

 

뒤에서부터 '-'로 구분되는 문자열을 제거하기 때문에 test-dev, hello-dev와 같은 서비스명은 추출되지 않습니다!

 

다시 notifyByPath 메서드로 돌아와서 추출한 서비스명 test-hello-dev, test-hello, test에 대해 반복하면서 스프링 이벤트를 발행합니다. 이때 발행되는 이벤트는 RefreshRemoteApplicationEvent입니다.

RefreshRemoteApplicationEvent는 RemoteApplicationEvent를 상속받습니다. PropertyPathEndpoint는 RefreshRemoteApplicationEvent로 자기 자신과 busId, 그리고 추출한 서비스명을 전달합니다.

이때 생성된 스프링 이벤트들은 총 3개입니다. 추출한 서비스명마다 이벤트를 생성하기 때문입니다.

 

참고로 Config Server의 /monitor를 호출하면 아래와 같이 응답이 옵니다.

[
    "test-hello-dev",
    "test-hello",
    "test"
 ]

 

 

Spring Cloud Bus와 이벤트 전파

Kafka 메시지 발행

Config Server는 생성된 이벤트를 메시지 브로커에 발행합니다. 이를 통해 변경된 설정 정보를 애플리케이션에 전달합니다.

 

RemoteApplicationEventListener가 앞서 발행한 이벤트를 처리합니다. isFromSelf 메서드는 스스로부터 발행한 이벤트인지 확인합니다. 이벤트 속 오리진 서버와 이 이벤트를 처리하는 서버를 비교해서 스스로부터 나온 것인지 확인합니다. 해당 이벤트는 AckRemoteApplicationEvent가 아닌 RefreshRemoteApplicationEvent이므로 조건문 안으로 진입합니다.

이때 busBridge를 통해 이벤트를 send 합니다. busBridge 정보에서 중요한 것은 destination과 id입니다. destination은 Kafka 토픽명이고 id는 오리진 서버 값입니다.

this.busBridge.send를 호출하면 실제 Kafka 토픽에 메시지가 발행됩니다.

  1. test-hello-dev 이름을 담은 이벤트 발행 → RemoteApplicationEventListener에서 처리하여 Kafka 토픽 발행
  2. test-hello 이름을 담은 이벤트 발행 → RemoteApplicationEventListener에서 처리하여 Kafka 토픽 발행
  3. test 이름을 담은 이벤트 발행 → RemoteApplicationEventListener에서 처리하여 Kafka 토픽 발행

총 3개의 토픽이 발행됩니다.

 

실시간 메시지 확인

Kafka 토픽에 발행된 메시지는 다음 명령어를 통해 실시간으로 확인할 수 있습니다.

kafka-console-consumer.sh --bootstrap-server ${broker-host} --topic ${topic-name} --from-beginning

여기까지 정리하면, Config Server는 /monitor 엔드포인트로 요청을 받아서 설정 파일 이름에서 서비스명을 추출하고(대시를 기준으로), 추출한 서비스명마다 RefreshRemoteApplicationEvent 이벤트를 발행합니다. 이벤트 핸들러가 이를 받아서 Kafka 메시지를 발행합니다.

 

RefreshRemoteApplicationEvent 이벤트를 처리하는 핸들러는 RemoteApplicationEventListener 외에도 RefreshListener가 있습니다.

RefreshListener에서는 this.serviceMatcher.isForSelf 메서드를 호출합니다. 이 메서드는 해당 이벤트가 현재 서비스를 대상으로 하는지 확인합니다. isForSelf 메서드는 이벤트에서 얻은 목적지 서비스명을 추출해서 값이 있는지 확인하고 현재 서비스와 비교합니다.

 

Config Server 자신은 변경 대상 서비스가 아니므로 false를 반환합니다.

다시 RefreshListener를 보면 isForSelf 메서드에서 false를 받아서 리프레시를 수행하지 않고 로그만 출력합니다.

[http-nio-8888-exec-2] INFO  |o.s.cloud.bus.event.RefreshListener: [/] Refresh not performed, the event was targeting test-hello-dev:**
[http-nio-8888-exec-2] INFO  |o.s.cloud.bus.event.RefreshListener: [/] Refresh not performed, the event was targeting test-hello:**
[http-nio-8888-exec-2] INFO  |o.s.cloud.bus.event.RefreshListener: [/] Refresh not performed, the event was targeting test:**

 

지금까지가 Config Server에서의 처리입니다. 이 다음엔 애플리케이션 동작을 살펴보겠습니다. 

 

 

애플리케이션의 이벤트 처리

BusConsumer에서 Config Server가 발행한 토픽을 받습니다. 이때 3개의 토픽 중 매칭된 하나의 토픽에 대해서만 이벤트를 발행합니다.

그리고 RefreshListener에서 받아서 리프레시를 수행합니다.

Config Server의 RefreshListener와 마찬가지로, 클라이언트 애플리케이션에서도 RemoteApplicationEvent를 상속받은 RefreshRemoteApplicationEvent 이벤트를 처리합니다.

 

RemoteApplicationEventListener에서 이벤트를 받지만, isFromSelf 메서드가 false이므로 아무 동작도 하지 않습니다.

그 다음에는 RefreshListener가 이벤트를 처리합니다. serviceMatcher.isForSelf 메서드를 통해 변경 대상 서비스인지 확인하고, 대상 서비스라면 리프레시를 수행합니다. 대상 서비스가 아니라면 false를 반환하고 로그만 남깁니다.

 

 

정리

Spring Cloud Config는 설정 변경 사항을 효율적으로 관리하며, 변경된 파일과 관련된 애플리케이션만 리프레시를 수행합니다.

변경 흐름 요약

  1. 변경된 파일 이름에서 서비스 이름 추론
  2. 서비스별로 Spring Event 생성 및 메시지 브로커로 전파
  3. 관련 애플리케이션에서 이벤트 수신 후 설정 리프레시

이 과정을 통해 불필요한 리프레시를 방지하고 효율적으로 설정 변경을 반영할 수 있습니다. Spring Cloud Config와 Spring Cloud Bus의 연동으로 분산 환경에서 안정적이고 실시간으로 설정 변경을 관리할 수 있습니다!

 

 

마무리

Spring Cloud Config의 변경 흐름을 확인하면서 스프링의 이벤트 처리 메커니즘에 대해서도 자세히 살펴볼 수 있었습니다. 모든 과정이 이벤트를 발행하고 그 이벤트를 리스너에서 처리하는 방식으로 동작하여, 확장성과 유연성을 제공합니다.

 

 

[참고]

  • https://docs.spring.io/spring-cloud-config/docs/current/reference/html/

 

  1. Spring Cloud Config란?
  2. Config 파일 변경 적용 흐름
  3. Config Server는 어떤 서버인가요?
  4. 모든 애플리케이션이 리프레시 될까요?
  5. 설정 파일을 변경 후 Git 저장소로 Push
  6. Server의 /monitor 엔드포인트로 요청
  7. Config Server에서의 /monitor 엔드포인트 요청 처리
  8. Spring Cloud Bus와 이벤트 전파
  9. 정리
  10. 변경 흐름 요약
  11. 마무리
'Spring' 카테고리의 다른 글
  • Spring Cloud Gateway에서 X-Forwarded-Prefix 헤더가 경로에 미치는 영향
  • 포인트 지급 로직 어떻게 처리할까? 직접 메소드 호출 vs 이벤트 처리
yiseull
yiseull
느리지만 멈추지 않고, 코드 위에 경험과 신뢰를 쌓아갑니다. 작았던 눈덩이는 오늘도 한 줄의 코드와 함께 성장 중입니다. ⛄️🏂
yiseull
코드 위의 스노우볼
yiseull
  • 분류 전체보기 (17)
    • Java (1)
    • Spring (3)
    • 프로그래머스 백엔드 데브코스 (5)
    • 오늘의 배움 (3)
    • 회고 (3)
    • 트러블슈팅 (2)

최근 글

인기 글

hELLO · Designed By 정상우.v4.2.2
yiseull
재배포 없이 자동 반영? Spring Cloud Config 파일 변경 흐름 파헤치기
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.