본문 바로가기!

Vault/Spring

[Vault] (Spring Cloud Config - 3 ) Client에서 Config 변경 사항 반영 방안 정리

728x90
반응형

Spring Cloud Config를 활용하여 Vault와 연동한 애플리케이션을 구성할 때, Vault KV 설정 값이 변경되었을 때 이를 클라이언트에서 즉시 반영하는 방안에 대한 정리입니다.

 

Spring Cloud Config Client는 실행 시점에 Config Server를 통해 설정 값을 가져옵니다.

그러나 이후 Vault 또는 Config Server에서 설정 값이 변경되더라도, Client는 실행 시점의 값을 계속 유지하게 됩니다.

 

즉, Config Server에서 새로운 값을 제공하더라도 Client는 기존 값을 유지하며 자동으로 변경 사항을 감지하여 반영하지 않습니다!

 

변경 사항을 즉시 반영하는 3가지 방안은 다음과 같습니다.

  1. /actuator/refresh API를 활용한 수동 반영
  2. ContextRefresher.refresh()를 활용한 자동 감지
  3. Spring Cloud Bus 와 RabbitMQ 또는 Kafka를 활용한 변경 사항 전파

 

 

1.   /actuator/refresh API를 활용한 수동 반영

Spring Boot Actuator의 /actuator/refresh 엔드포인트를 활용하면 클라이언트에서 Config Server로부터 최신 설정 값을 다시 로드할 수 있습니다.

 

1-1)  테스트 진행

  • Client는 다음과 같이 구성하여 Config Server으로부터 Vault KV 값을 가져와 호출하는 코드를 작성하였습니다.
package com.example.kotlin_gradle_vault

import org.springframework.boot.CommandLineRunner
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component

@Component
class VaultConfigRunner(private val vaultConfigProperties: VaultConfigProperties) : CommandLineRunner {

    override fun run(vararg args: String?) {
        println("🔥🔥🔥 VaultConfigRunner : 최초 Vault KV 확인!!!")
        printCurrentConfig("🔹 최초 Vault 설정 값:")
    }

    // 10초마다 Vault의 값을 출력하여 변경 사항 확인
    @Scheduled(fixedRate = 10000)
    fun printVaultConfig() {
        printCurrentConfig("🔄 현재 Vault 설정 값:")
    }

    private fun printCurrentConfig(header: String) {
        println(header)
        println("🔹 db_url: ${vaultConfigProperties.db_url}")
        println("🔹 username: ${vaultConfigProperties.username}")
        println("🔹 password: ${vaultConfigProperties.password}")
    }
}

 

 

  • 1. Client를 BootRun 명령어로 애플리케이션을 실행합니다.

  • 2. 애플리케이션 동작 후 로그를 통해 Vault 의 KV 값을 확인합니다.

 

  • 3. Vault KV 값을 변경합니다.

 

  • 3. /actuator/refresh 엔드포인트를 활용하여 Config Server로부터 최신 설정 값을 다시 불러옵니다.

 

  • 4. 로그를 통해 Config Server로부터 최신 설정 값인 Vault 의 KV 값을 확인합니다.

 

 

 

 

2. ContextRefresher.refresh()를 활용한 자동 감지

Spring Boot에서는 ContextRefresher.refresh() 메서드를 활용하면 자동으로 변경 사항을 감지하여 클라이언트에서 설정 값을 반영할 수 있습니다.

 

해당 과정은 이전 블로그 내용에서 확인할 수 있습니다.

 

 

 

 

3. Spring Cloud Bus + RabbitMQ를 활용한 변경 사항 전파

여러 개의 Config Client가 존재할 경우, 각 클라이언트에서 /actuator/refresh를 호출하는 것이 비효율적일 수 있습니다.

 

이를 해결하기 위해 Spring Cloud Bus + RabbitMQ를 활용하면 한 번의 설정 변경으로 모든 클라이언트에게 변경 사항을 자동 반영할 수 있습니다.

 

3-1)  RabbitMQ 설치 (mac)

  • RabbitMQ를 설치합니다.
$ brew update
$ brew install rabbitmq
...
...
...
==> Checking for dependents of upgraded formulae...
==> No broken dependents found!
==> Caveats
==> rabbitmq
Management UI: http://localhost:15672
Homebrew-specific docs: https://rabbitmq.com/install-homebrew.html

To start rabbitmq now and restart at login:
  brew services start rabbitmq
Or, if you don't want/need a background service you can just run:
  CONF_ENV_FILE="/opt/homebrew/etc/rabbitmq/rabbitmq-env.conf" /opt/homebrew/opt/rabbitmq/sbin/rabbitmq-server

 

  • RabbitMQ를 실행합니다.
$ rabbitmq-server
=INFO REPORT==== 13-Mar-2025::14:20:21.820711 ===
    alarm_handler: {set,{system_memory_high_watermark,[]}}
2025-03-13 14:20:23.402995+09:00 [notice] <0.45.0> Application syslog exited with reason: stopped
2025-03-13 14:20:23.404832+09:00 [notice] <0.215.0> Logging: switching to configured handler(s); following messages may not be visible in this log output

  ##  ##      RabbitMQ 4.0.7
  ##  ##
  ##########  Copyright (c) 2007-2024 Broadcom Inc and/or its subsidiaries
  ######  ##
  ##########  Licensed under the MPL 2.0. Website: https://rabbitmq.com

  Erlang:      27.2.4 [jit]
  TLS Library: OpenSSL - OpenSSL 3.4.1 11 Feb 2025
  Release series support status: see https://www.rabbitmq.com/release-information

  Doc guides:  https://www.rabbitmq.com/docs
  Support:     https://www.rabbitmq.com/docs/contact
  Tutorials:   https://www.rabbitmq.com/tutorials
  Monitoring:  https://www.rabbitmq.com/docs/monitoring
  Upgrading:   https://www.rabbitmq.com/docs/upgrade

  Logs: /opt/homebrew/var/log/rabbitmq/rabbit@localhost.log
        <stdout>

  Config file(s): (none)

  Starting broker... completed with 7 plugins.

 

 

 

3-2)  Config Server 설정

  • Gradle 의존성 (dependencies 설정)
// Spring Cloud Bus
implementation("org.springframework.cloud:spring-cloud-bus")
// Spring Cloud Bus (RabbitMQ 사용)
implementation("org.springframework.cloud:spring-cloud-starter-bus-amqp")
// Spring Boot Actuator
implementation("org.springframework.boot:spring-boot-starter-actuator")

 

  • application.yml
server:
  port: 8888

spring:
  application:
    name: spring-config-server
  profiles:
    active: vault
  cloud:
    config:
      server:
        vault:
          fail-fast: true
          host: {Vault Hostname 기입}
          port: 8200
          namespace: admin
          authentication: token
          token: {Vault Token 기입}
          kv-version: 2
          backend: spring-config-kv
          profile-separator: '/'
          scheme: https
    rabbitmq:
      host: 127.0.0.1
      port: 5672
      username: guest
      password: guest

management:
  endpoints:
    web:
      exposure:
        include: "busrefresh"
  • Spring Cloud Bus를 활용한 메시지 브로커RabbitMQ를 사용하기 위해 필요한 설정 추가
  • Spring Boot의 Actuator 엔드포인트busrefresh 엔드포인트를 활성화하는 설정 추가

 

 

3-3)  Config Client 설정

  • Gradle 의존성 (dependencies 설정)
// Spring Cloud Bus (RabbitMQ 메시지 수신)
implementation("org.springframework.cloud:spring-cloud-starter-bus-amqp")
// Actuator 추가
implementation("org.springframework.boot:spring-boot-starter-actuator")

 

  • JAVA 코드는 1-1) TEST 진행 에서 진행한 코드를 그대로 사용합니다.

 

 

3-4)  Spring Cloud Bus를 활용한 Vault 설정 변경 자동 반영 테스트

Spring Cloud BusRabbitMQ를 연동하여 Config Server가 Spring Cloud Bus를 통해 메시지를 발행하고, 이를 통해 모든 Config Client가 변경 사항을 감지하고 설정을 업데이트합니다.

 

 

(1)  클라이언트 애플리케이션 기동 후 Vault 값 확인

  • 클라이언트 애플리케이션이 기동되면서 Config Server에서 값을 가져와 Vault 설정 값을 출력합니다.
  • 초기 값은 아래와 같습니다.
    • db_url: jdbc:mysql://dev-db-url
    • username: dev-username
    • password: dev-password

 

 

(2)  Vault에서 설정 값 변경

  • Vault UI를 통해 spring/dev 경로에 있는 값을 수정했습니다.
    • username: dev-usernamedev-username-busrefresh
    • password: dev-passworddev-password-busrefresh

 

 

(3)  Config Server - actuator/busrefresh 호출

  • busrefresh를 사용하여 Config Server가 변경 사항을 감지하도록 트리거를 실행합니다.

 

(4)  Config Client에서 변경 사항 자동 반영 확인

  • Config Client의 로그를 확인한 결과, 자동으로 변경된 설정이 반영됨을 확인할 수 있습니다.
  • 로그 확인
    • Received remote refresh request.
    • Keys refreshed [password, username]
      • 변경된 키(password, username)를 감지했음을 확인할 수 있습니다.
  • 변경 후 값
    • username: dev-username-busrefresh
    • password: dev-password-busrefresh
  • Config Client가 Config Server를 통해 변경 사항을 감지하고, 최신 설정 값을 가져왔음을 알 수 있습니다.

 

 

개인 정리

처음에는 단순히 Vault에서 값을 가져와 사용하면 끝이라고 생각했지만.....

Vault의 설정 값이 변경되더라도 클라이언트 애플리케이션이 이를 즉시 인식하지 못하기 때문에, 변경 사항을 자동으로 반영하는 다양한 방법을 고민해야 했다...

 

변경 사항을 자동 반영하는 방법 비교

  • /actuator/refresh API 호출 (수동 반영)
    • 클라이언트에서 직접 /actuator/refresh API를 호출하여 설정을 갱신하는 방식
    • 가장 간단한 방법이지만, 클라이언트 애플리케이션이 여러 개일 경우 각각의 클라이언트 애플리케이션을 개별적으로 호출해야 하는 비효율적인 단점
  • ContextRefresher.refresh() 활용 (주기적 반영)
    • 일정 시간마다 설정 값을 새로고침하는 방식
    • Config Server의 API를 직접 호출하지 않아도 변경 사항을 반영할 수 있지만, 설정 값이 자주 바뀌지 않는 경우에도 주기적으로 새로고침이 실행되기 때문에 불필요한 리소스 소비가 발생 가능
  • Spring Cloud Bus + RabbitMQ 활용 (메시지 브로커 기반 반영)
    • Config Server에서 /actuator/busrefresh API를 호출하면 메시지 브로커(RabbitMQ)가 이를 받아 Config Client들에게 변경 사항을 자동으로 전파하는 방식
    • 개별 클라이언트를 호출하지 않아도 Config 서버의 한 번의 호출로 여러 애플리케이션에 변경 사항을 반영할 수 있다는 장점이 있지만, 메시지 브로커를 추가로 설정하고 유지 관리해야 하는 부담 존재

 

좀 더 찾아보니... Spring Cloud Stream 과 Kafka를 활용하여 이벤트 기반 실시간 반영도 가능할 것 같음...

이벤트 브로커(Kafka)를 활용하여 Vault의 audit log? 등 변경 사항을 Config Server가 실시간으로 감지하고, 이를 클라이언트들에게 전파하는 방식으로....???

728x90
반응형