Spring Config 서버를 구축하여 여러 서버에서 활용되는 공통된 설정값을 관리할 수 있다.
공통된 설정 파일들은 로컬 파일 시스템, 로컬 git 레포지토리 또는 remote git 레포지토리에 올려서 관리 할 수 있음
https://docs.spring.io/spring-cloud-config/docs/current/reference/html/
현재 진행 중인 프로젝트에서는 remote git private 레포지토리에 설정파일을 올려 관리하고 있다.
민감 정보에 대한 보안을 위해 private 레포지토리로 구성하였고, 대칭키를 이용하여 설정값을 암호화
Refresh
설정 파일이 외부에서 관리되기 때문에 서버가 실행되고 있는 도중 설정 값이 변경될 수 있다. 하지만 설정 파일이 변경되어도 설정 클라이언트(Spring Cloud Config Client)에 반영되지 않는다. 설정 서버(Spring Cloud Config Server)에 부하를 줄이도록 애플리케이션을 실행 시점에 1번 설정 정보를 읽고 로컬에 캐싱
해두기 때문이다.
그렇다고 설정 파일이 변경될 때 마다 여러 서버들을 다시 실행하여 설정 값을 적용시키는 것은 매우 번거로운 작업이다.
1. actuator - refresh
변경 사항을 반영하는 가장 심플한 방법은 클라이언트에 spring-boot-actuator 의존성을 추가하고 POST로 /actuator/refresh 요청을 보내는 것이다. 그러면 actuator가 설정 정보를 다시 읽고 값을 refresh한다.
하지만 여러 대의 서버에 적용하기에는 여전히 번거롭다.
2. spring-cloud-bus
spring-cloud-bus는 서비스 노드를 연결해 설정 정보 등의 변경을 전파해주기 위한 경량화된 메세지 브로커 프로젝트이다. 해당 프로젝트는 기본적으로 AMQP를 메세지 브로커로 사용하는데, 이것을 사용하면 변경 메세지를 받아서 값을 자동으로 갱신시킬 수 있다.
하지만 spring-cloud-bus 역시 새롭게 운영해야 되고, 여전히 설정 파일 변경 후 한 번은 refresh를 해줘야 되는 진정한 자동화가 이루어지지 않는다.
3. ConfigClientWatch
Watcher는 설정 서버에게 변경 여부를 지속적으로 물어보고 확인하는 컴포넌트이다. 클라이언트 서버에 ConfigClientWatch 클래스를 생성 및 @Bean으로 쉽게 등록이 가능하고, Spring Scheduler로 일정 주기마다 자동으로 설정 파일을 적용한다.
즉, 서버 재실행이나 API 요청과 같은 작업 없이 실시간으로 설정 파일 적용이 가능한 것이다.
spring-cloud-config-client-watch
state → version
하지만 ConfigClientWatch클래스를 클라이언트 서버에 생성하고, 스케줄러 bean이 등록될 수 있도록 @EnableScheduling 어노테이션을 선언해주어도 refresh가 발생하지 않았다.
Application.java
@SpringBootApplication
@EnableScheduling
public class NuguriMemberApplication {
public static void main(String[] args) {
SpringApplication.run(NuguriMemberApplication.class, args);
}
}
ConfigClientWatch.java
/**
* @author Spencer Gibb
*/
public class ConfigClientWatch implements Closeable, EnvironmentAware {
...
@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();
}
}
}
...
}
문제는 변경을 감지하는 state값이 null이기 때문이다.
{
"name":"member",
"profiles":[
"prod"
],
"label":null,
"version":"47e006778f3cc0a52c9f26c2d9aa68932104c335",
"state":null,
"propertySources":[
{
"name":"git@github.com:heptris/nuguri-msa-config.git/...\\member-prod.yml",
"source":{
"spring.datasource.url":"{cipher}5c4af82cf00c7526f65f74c1bf850d07cd31291bbabed7311d2f21b1e9fb07739a649020437fd131ebb1ea60bb2143264245285480169660d54d6267846f517fb026f0291dcca2d82840a0fccf13da17c850843d11879b4cb45bf281193832bddb8e9d9124a19557b966db2f7dfe86283e6ed2680a0e43c88d99debe9dc7377e117dd93a3f857797f172ee40ab92248f",
"spring.datasource.username":"{cipher}bd14239f5d2253d6fd54be621b24ade263e9e6665d197c4ab4232eb76896bd56",
...
}
}
]
}
ConfigClientWatch에서는 config.client.state값을 비교하여 변경을 감지하지만 config 서버로부터 null값을 반환 받는다.
config 서버는 설정 클라이언트에게 state와 version을 내려주는데, Git은 version이 git HEAD의 checksum에 해당한다. 그러므로 state가 아닌 version으로 변경감지를 하도록 ConfigGitCloudWatcher 구현체를 만들어 해결할 수 있다.
ConfigGitClientWatch.java
@Component
@Slf4j
public class ConfigGitClientWatch implements Closeable, EnvironmentAware {
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 ConfigGitClientWatch(
ContextRefresher refresher, ConfigServicePropertySourceLocator locator) {
this.refresher = refresher;
this.locator = locator;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@PostConstruct
public void start() {
running.compareAndSet(false, true);
}
@Scheduled(
initialDelayString = "${spring.cloud.config.watch.git.initialDelay:10000}",
fixedDelayString = "${spring.cloud.config.watch.git.delay:10000}"
)
public void watchConfigServer() {
if (running.get()) {
String newVersion = fetchNewVersion();
String oldVersion = version.get();
if (versionChanged(oldVersion, newVersion)) {
version.set(newVersion);
refresher.refresh();
}
}
}
private String fetchNewVersion() {
CompositePropertySource propertySource = (CompositePropertySource) locator.locate(environment);
return (String) propertySource.getProperty("config.client.version");
}
private static boolean versionChanged(String oldVersion, String newVersion) {
return !hasText(oldVersion) && hasText(newVersion)
|| hasText(oldVersion) && !oldVersion.equals(newVersion);
}
@Override
public void close() {
running.compareAndSet(true, false);
}
}
git 레포지토리에 파일을 수정하여 push하면 다른 version값을 응답받고 refresh가 되는 것을 확인할 수 있다.
'Spring' 카테고리의 다른 글
Spring Cloud Config URL 조회 시 보안 (0) | 2023.11.04 |
---|---|
Embedded Redis로 테스트 환경 구축하기 (0) | 2023.10.30 |
Spring Gateway 에러 핸들링 (0) | 2023.09.19 |
MSA에서 로그인 되어 있는 회원 정보 조회 (0) | 2023.09.18 |
Spring MVC DispatcherServlet 분석 (0) | 2023.07.27 |