| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- spring cloud config
- kaniko
- ssh
- argocd
- Spring Config Bus
- cloud controller manager
- RDS
- Kubernetes
- spring
- ssafy #싸피 #ssafy 12기 #싸피 12기 #ssafy 합격 #싸피 합격 #합격 후기
- k8s
- 인프런강의
- model context protocol
- nginx
- sshtunneling
- AWS
- port forawrding
- PLG
- Docker
- helm
- ChatGPT
- EC2
- helm-chart
- Tunneling
- springboot
- promtail
- Helm Chart
- 생성형AI
- redis oss
- elasticahe
- Today
- Total
처누
[Spring] Spring Cloud Config(2) (Feat. RabbitMQ, Watcher) 본문
[Spring] Spring Cloud Config(1) (Feat. Actuator API)
이전의 프로젝트들에서 환경변수 값과 설정을 관리하기 쉽도록 Config Server를 따로 띄운 후 관리했다. 별도의 서버에서 Docker로 띄운 후 Config repo에서 해당 서버 ip, rabbitmq 및 다양한 설정들을 구현
cheonu.tistory.com
이전 포스팅에서 Spring Cloud Confing server를 구축하고 actuator API 호출을 통해 변경된 config를 적용했다. 하지만 이러한 방식은 설정이 변경될 때마다 actuator API를 호출해야 하고, 모든 서비스마다 refresh를 해주어야 하는 문제점이 발생한다. 즉, 사이즈가 커질수록 수동으로 하는 것에는 한계가 생긴다는 것이다. 이번에는 Spring Cloud Bus를 사용하여 RabbitMQ와 연결하는 방법과 Watcher를 통한 방법을 알아보려 한다.
0. Spring Cloud Bus? RabbitMQ?
Spring Cloud Bus란?
Spring Cloud Bus는 공식문서에서 다음과 같이 소개하고있다.
Spring Cloud Bus는 분산 시스템 노드들을 경량 메시지 브로커로 연결해, 설정 변경이나 관리 명령을 전체 서비스에 전파하는 역할을 한다. 핵심 개념은 "확장된 Spring Boot 애플리케이션의 분산 Actuator”이고, 단순히 관리용뿐 아니라 애플리케이션 간 메시징 채널로도 쓸 수 있다. 전송 계층으로는 AMQP(RabbitMQ) 또는 Kafka를 지원한다.
Introduction :: Spring Cloud Bus
Spring Cloud Bus links the nodes of a distributed system with a lightweight message broker. This broker can then be used to broadcast state changes (such as configuration changes) or other management instructions. A key idea is that the bus is like a distr
docs.spring.io
즉, Spring Cloud Bus는 분산 시스템 환경에서 이벤트를 전파하고 브로드캐스트 하는 역할을 하고, 각 서비스 및 구성 서버가 Spring Cloud Bus를 사용하려면 메시지 브로커에 연결되어 있어야 한다는 뜻이다.
RabbitMQ란?
RabbitMQ는 AMQP를 구현하여 메시지 브로커, 즉 메시지를 중개하는 역할을 하는 프로그램이다. 두 개 이상의 프로그램이 서로 데이털르 주고받을 때, 직접 연결하지 않고 RabbitMQ를 거쳐 통신한다. 쉽게 말해, 택배 회사 같은 역할이라고 생각하면 된다.
보내는 쪽(Sender) : 물건을 택배 회사에 맡김 -> 메시지를 RabiitMQ에 보냄
RabbitMQ : 물건을 분류하고 창고(큐)에 보관 -> 메시지를 큐에 저장
받는 쪽(Receiver) : 필요할 때 택비를 수령 -> 메시지를 RabbitMQ에서 꺼냄
❓AMQP(Advenced Message Queuing Protocol)/MOM(Message Oriented Middleware)
- AMQP : 메시지 지향 미들웨어를 위한 개방형 표준 응용 체계 프로토콜
- MOM : 메시지 지향 미들웨어는 비동기 메시지를 사용하는 응용프로그램들 사이에서 데이터를 송수신하는 것을 의미하며, 이러한 시스템을 구현한 솔류션을 메시지 큐라고 한다. RabbitMQ는 MOM에 속한다.
기본 개념

- Producer(생산자) : 메시지를 보내는 주체(ex. 주문 서비스)
- Consumer(소비자) : 메시지를 받는 주체(ex. 결제 서비스)
- Queue(큐) : 메시지를 잠시 저장하는 곳, Reciever가 꺼내가기 전까지 보관된다.
- Exchange(교환기) : 메시지를 어디 큐에 보낼지 결정하는 분류기 역할
- Binding(바인딩) : Exchange와 Queue를 연결하는 규칙(ex. " 주문.* " 패턴의 메시지는 주문 큐로 보냄)
장점
- 안정성 : Receiver가 잠시 다운돼 있어도, 메시지는 큐에 안전하게 쌓여있다가 나중에 전달된다.
- 비동기 처리 : Sender는 메시지만 보내고 바로 자기 일을 할 수 있다. Receiver가 나중에 메시지를 처리해도 상관없음.
- 확장성 : 메시지를 여러 Receiver가 동시에 소비하면, 자동으로 로드 밸런싱 효과가 난다.
- 유연한 통신 패턴 : 1:1, 1:N, 토픽 기반(pub/sub) 등 다양한 전달 방식을 지원함.
1. RabbitMQ(Docker) 실행
RabbitMQ Docker image 실행
docker run -d \
--name rabbitmq \
-p 5672:5672 \
-p 15672:15672 \
rabbitmq:management
2. Config Server
의존성 추가
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-bus-amqp'
application.yml 수정
spring:
application:
name: config
rabbitmq://rabbitmq 설정 추가
host: 127.0.0.1
port: 5672
username: guest
password: guest
3. Config Client
의존성 추가
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-bus-amqp'
application.yml 수정
spring:
application:
name: test
profiles:
active: dev
rabbitmq: //rabbitmq 설정 추가
host: 127.0.0.1
port: 5672
username: guest
password: guest
config:
import: optional:configserver:http://localhost:8888
management:
endpoints:
web:
exposure:
include: refresh, busrefresh //busrefresh 추가
설정 후, POST 요청으로 actuator/busrefresh를 보내면

아래와 같이 변경된 설정이 로그에 남는다. 테스트용이라 서비스가 하나밖에 없지만 서비스가 여러개일 경우 config client에 해놓은 설정값 똑같이 해놓는다면 모두 반영될 것이다.

변경된 설정값이 반영됐는지 확인하기위해 API 호출해보면 content가 spring-cloud-bus-test로 변경된 것을 볼 수 있다.

0. Watcher?
Watcher는 특정 자원의 변경을 감시하고, 변경이 일어나면 알림이나 후속 동작을 트리거하는 기능이다. 즉, 어떤 대상이 변했는지 계속 지켜보다가 바뀌면 알려주는 역할이다. 다음과 같은 상황에서 주로 사용된다.
- Linux inotify → 파일 시스템 이벤트 감지
- Kubernetes watch API → ConfigMap/Secret 변경 이벤트 감지
- Spring Cloud Config Watcher → Config Server 저장소(Git)의 변경을 감지
Spring Cloud Config에서의 Watcher의 역할은 다음과 같다.
- Config Server 저장소(Git) 변경 감시 : Git webhook, polling, 파일 시스템 이벤트 등을 통해 변경 발생을 탐지한다.
- Refresh 이벤트 발생 : 애플리케이션 내부적으로 RefreshEvent를 발행한다. @RefreshScope 또는 ConfigurationProperties Bean이 다시 로드된다.
- 전체 노드 반영 : Bus를 사용하지 않는 대신, Watcher가 각 애플리케이션 프로세스에서 동작해 자기 자신을 refresh하거나, API 호출을 통해 클러스터 노들에 refresh 요청을 전달한다.
1. Watcher 구현하기
Watcher를 사용하면 자동으로 변경을 감지하기 때문에 부담이 없어 좋다. 하지만 문제는 git으로 설정 파일을 관리하는 경우 Spring Cloud Config에서 제공하는 Watcher로는 변경 감지가 불가능 하다는 것이다.
이유는 Config Server는 클라이언트에게 state와 version 두 가지 값을 내려주는데, Spring Cloud Config가 구현해둔 ConfigCloudWatcher에서의 기본 구현은 state 값만 비교해서 변경 여부를 판단한다.
- state : Config Server의 내부 상태값(주로 EnviromentRepository 구현체가 정의)
- version : Git 저장소를 사용할 경우, 현재 HEAD commit의 checksum에 해당
@Scheduled(initialDelayString = "${spring.cloud.config.watch.initialDelay:180000}",
fixedDelayString = "${spring.cloud.config.watch.delay:500}")
public void watchConfigServer() {
if (this.running.get()) {
String newState = this.environment.getProperty("config.client.state");
String oldState = ConfigClientStateHolder.getState();
// only refresh if state has changed
if (stateChanged(oldState, newState)) {
ConfigClientStateHolder.setState(newState);
this.refresher.refresh();
}
}
}
하지만 Git 환경에서는 state가 항상 null로 내려오거나 일정한 값으로 내려와서 변경 여부 감지가 불가능하다. 즉, Git의 HEAD 변경 여부(version)는 Watcher가 확인하지않는다. 이 문제는 ConfigCloudWatcher를 수정하여 version 값의변경 여부를 체크하는 구현체를 만들어 해결할 수있다.
CustomCloudClientWatch
public class CustomConfigClientWatch implements Closeable, EnvironmentAware {
private static Log log = LogFactory.getLog(ConfigServicePropertySourceLocator.class);
private final AtomicBoolean running = new AtomicBoolean(false);
private final AtomicReference<String> version = new AtomicReference<>();
private final ContextRefresher refresher;
private final ConfigServicePropertySourceLocator locator;
private Environment environment;
public CustomConfigClientWatch(ContextRefresher refresher, ConfigServicePropertySourceLocator locator) {
this.refresher = refresher;
this.locator = locator;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@PostConstruct
public void start() {
this.running.compareAndSet(false, true);
}
@Scheduled(initialDelayString = "${spring.cloud.config.watch.initialDelay:10000}",
fixedDelayString = "${spring.cloud.config.watch.delay:5000}")
public void watchConfigServer() {
if (this.running.get()) {
String newVersion = fetchNewVersion();
if (newVersion == null) {
return;
}
String oldVersion = version.get();
if (versionChanged(oldVersion, newVersion)) {
version.set(newVersion);
refresher.refresh();
}
}
}
private String fetchNewVersion() {
try {
CompositePropertySource propertySource = (CompositePropertySource) locator.locate(environment);
return (String) propertySource.getProperty("config.client.version");
} catch (NullPointerException e) {
System.out.println("Cannot fetch from Cloud Confg Server: " + e);
}
return null;
}
boolean versionChanged(String oldState, String newState) {
return (!hasText(oldState) && hasText(newState))
|| (hasText(oldState) && !oldState.equals(newState));
}
@Override
public void close() {
this.running.compareAndSet(true, false);
}
}
@Scheduled 부분에 보면 initialDelayString과 fixedDelayString 두 가지가 있는데 각각 10초와 5초로 변경했다.
- initialDelayString : 애플리케이션 구동 후 Watcher 실행을 지연시킬 시간(ms)
- fixedDelayString : 설정 서버로부터 변경 여부를 확인할 시간(ms)
@Scheduled를 사용하려면 메인 클래스 @EnableScheduling를 추가해야한다.
@SpringBootApplication
@EnableConfigurationProperties(MyConfig.class)
@EnableScheduling
public class SpringClientConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(SpringClientConfigClientApplication.class, args);
}
}
2. Watcher를 등록할 설정 클래스 구현
Watcher를 구현했다면 빈으로 등록해주어야 한다. 이것도 기존에 작성된 클래스를 따라 다음과 같이 구현할 수 있다.
@Configuration
public class CustomClientWatchConfiguration {
@Bean
@ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)
@ConditionalOnProperty(name = ConfigClientProperties.PREFIX + ".enabled", matchIfMissing = true)
public ConfigServicePropertySourceLocator configServicePropertySource(ConfigClientProperties properties) {
return new ConfigServicePropertySourceLocator(properties);
}
@Bean
@ConditionalOnProperty(name = ConfigClientProperties.PREFIX + ".enabled", matchIfMissing = true)
public CustomConfigClientWatch configGitClientWatch(ConfigDataContextRefresher refresher, ConfigServicePropertySourceLocator locator) {
return new CustomConfigClientWatch(refresher, locator);
}
}
이제 Git에서 설정 변경 후 자동으로 반영되는지 확인해보자. [config-file] - [test-dev.yml]에서 content를 수정 후 커밋한다.
cheonwooo:
profile: dev
content: spring-cloud-config-watch-test-2
변경이 감지 되어 변경됐다는 로그를 확인 후 API 요청을 보내보면 반영이 된 것을 볼 수 있다.

이전 포스팅에서 다룬 actuator API와 이번 포스팅에서 다룬 Spring Cloud Bus + RabbitMQ, Watcher를 통해 환경 변수를 git으로 관리하고 반영하는 방법을 적용해봤다. 확실히 환경 변수를 git으로 관리하며 이를 자동으로 반영한다는 것은 CI/CD를 하는 것과 비슷하다는 느낌을 받았다. 역시 편한게 최고............ MSA 혹은 멀티 모듈 구조의 서비스라면 무조건 도입 1순위라고 생각한다. 점점 더 편한 것만 찾는 것은 개발자의 숙명인가보다......
참고
https://sjh9708.tistory.com/124
https://anythingis.tistory.com/192
https://mangkyu.tistory.com/254
'Spring' 카테고리의 다른 글
| [Spring] Spring Cloud Config(1) (Feat. Actuator API) (3) | 2025.08.17 |
|---|