서비스가 10개인데, DB 비밀번호를 바꿔야 한다면 10개 서비스를 모두 다시 배포해야 할까요?

마이크로서비스 환경에서 각 서비스가 자체 설정을 관리하면, 설정 변경마다 모든 서비스를 재배포해야 합니다. Spring Cloud Config는 설정을 중앙 서버에서 관리하고, 재배포 없이 런타임에 설정을 변경할 수 있게 해줍니다.

Spring Cloud Config란

Spring Cloud Config는 분산 환경에서 애플리케이션 설정을 중앙에서 관리하는 서버-클라이언트 구조입니다.

  • Config Server: 설정 파일을 저장하고 제공하는 서버
  • Config Client: Config Server에서 설정을 가져오는 각 서비스

Config Server 구성

의존성

XML
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>

서버 설정

JAVA
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}
YAML
# application.yml (Config Server)
server:
  port: 8888

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/my-org/config-repo
          default-label: main
          search-paths: '{application}'  # 애플리케이션별 디렉토리
          clone-on-start: true           # 시작 시 clone

Git 저장소 구조

PLAINTEXT
config-repo/
├── application.yml          # 모든 서비스 공통 설정
├── order-service/
│   ├── application.yml      # order-service 기본 설정
│   └── application-prod.yml # order-service 운영 설정
├── payment-service/
│   ├── application.yml
│   └── application-prod.yml
└── user-service/
    ├── application.yml
    └── application-prod.yml

설정 조회 API

Config Server는 REST API로 설정을 제공합니다.

PLAINTEXT
GET /{application}/{profile}
GET /{application}/{profile}/{label}

예시:

  • GET /order-service/prod → order-service의 prod 프로파일 설정
  • GET /order-service/prod/main → main 브랜치의 설정

Config Client 구성

의존성

XML
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

클라이언트 설정

YAML
# application.yml (각 서비스)
spring:
  application:
    name: order-service  # Config Server에서 이 이름으로 설정을 찾음
  config:
    import: configserver:http://localhost:8888
  cloud:
    config:
      fail-fast: true    # Config Server 연결 실패 시 즉시 종료
      retry:
        initial-interval: 1000
        max-attempts: 5

서비스가 시작되면 Config Server에서 order-service의 설정을 가져와 로컬 설정과 병합합니다.

@RefreshScope — 재배포 없이 설정 갱신

Config Server의 설정이 변경되었을 때, 클라이언트가 재배포 없이 최신 설정을 반영하려면 @RefreshScope를 사용합니다.

JAVA
@RestController
@RefreshScope  // refresh 시 빈이 재생성됨
public class FeatureController {

    @Value("${feature.new-ui-enabled:false}")
    private boolean newUiEnabled;

    @Value("${app.notification.email:admin@example.com}")
    private String notificationEmail;

    @GetMapping("/feature-flags")
    public Map<String, Object> getFeatureFlags() {
        return Map.of(
                "newUiEnabled", newUiEnabled,
                "notificationEmail", notificationEmail
        );
    }
}

설정 갱신 트리거

BASH
# Actuator의 refresh 엔드포인트 호출
curl -X POST http://order-service:8080/actuator/refresh

이 요청이 들어오면 @RefreshScope 빈이 소멸 후 재생성되며, Config Server에서 최신 설정을 다시 주입합니다.

YAML
# Actuator 설정 (클라이언트)
management:
  endpoints:
    web:
      exposure:
        include: refresh, health

Spring Cloud Bus — 전체 인스턴스 일괄 갱신

서비스 인스턴스가 여러 개라면 각각에 refresh를 호출하는 것은 번거롭습니다. Spring Cloud Bus를 사용하면 메시지 브로커를 통해 모든 인스턴스에 설정 변경을 전파할 수 있습니다.

XML
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>  <!-- RabbitMQ -->
</dependency>
BASH
# 한 번의 요청으로 모든 인스턴스에 전파
curl -X POST http://any-instance:8080/actuator/busrefresh
PLAINTEXT
[Config Server] → Git 변경 감지

[/actuator/busrefresh 호출]

[RabbitMQ] → 이벤트 전파

[order-service-1] [order-service-2] [order-service-3]
  (설정 갱신)        (설정 갱신)        (설정 갱신)

Git Webhook 자동화

Git 저장소에 Webhook을 설정하면, 설정 파일이 push될 때 자동으로 busrefresh가 트리거됩니다.

PLAINTEXT
Git Push → Webhook → /monitor 엔드포인트 → Bus 이벤트 → 전체 인스턴스 갱신

암호화/복호화

DB 비밀번호, API 키 같은 민감한 설정을 암호화하여 저장할 수 있습니다.

대칭 키 설정

YAML
# Config Server
encrypt:
  key: my-secret-encryption-key

암호화 API 사용

BASH
# 암호화
curl -X POST http://config-server:8888/encrypt -d "db-password-123"
# → 682bc583f4641332b5bb3e7...

# 복호화
curl -X POST http://config-server:8888/decrypt -d "682bc583f4641332b5bb3e7..."
# → db-password-123

설정 파일에 암호화된 값 사용

YAML
# Git 저장소의 설정 파일
spring:
  datasource:
    password: '{cipher}682bc583f4641332b5bb3e7...'

{cipher} 접두사가 있는 값은 Config Server가 클라이언트에 전달할 때 자동으로 복호화합니다.

Vault 백엔드

더 강력한 시크릿 관리가 필요하면 HashiCorp Vault를 백엔드로 사용할 수 있습니다.

YAML
spring:
  cloud:
    config:
      server:
        vault:
          host: vault.example.com
          port: 8200
          scheme: https
          authentication: TOKEN
          token: s.my-vault-token

Vault는 Git과 달리 시크릿의 자동 만료, 동적 생성, 접근 감사 등 엔터프라이즈급 기능을 제공합니다.

프로파일별 설정 전략

YAML
# application.yml (기본, 모든 환경)
app:
  name: order-service
  log-level: INFO

# application-dev.yml (개발)
spring:
  datasource:
    url: jdbc:h2:mem:devdb

# application-prod.yml (운영)
spring:
  datasource:
    url: jdbc:mysql://prod-db:3306/orders
    password: '{cipher}...'

클라이언트의 spring.profiles.active에 따라 적절한 설정이 반환됩니다.

실무 팁

  • Config Server는 ** 고가용성 **을 위해 최소 2대 이상 운영하세요
  • fail-fast: true + retry 설정으로 Config Server 장애 시 서비스 시작 실패를 방지하세요
  • 민감한 설정은 반드시 ** 암호화 **하거나 Vault 를 사용하세요
  • 설정 변경 이력을 Git으로 관리하면 누가, 언제, 무엇을 변경했는지 추적할 수 있습니다
  • @RefreshScope는 ** 자주 변경되는 설정 **에만 사용하세요 (DB 커넥션 풀 등 무거운 빈에는 주의)

주의할 점

1. Config Server에 연결할 수 없으면 서비스 자체가 시작되지 않는다

fail-fast: true를 설정하면 Config Server 장애 시 서비스가 즉시 종료됩니다. 이 설정 없이도 설정값을 받아오지 못하면 빈 생성 실패로 이어질 수 있습니다. retry 설정을 함께 사용하고, Config Server의 고가용성을 반드시 확보하세요.

2. @RefreshScope 빈이 재생성될 때 일시적으로 요청 처리가 지연될 수 있다

/actuator/refresh를 호출하면 @RefreshScope 빈이 소멸 후 재생성됩니다. 이 과정에서 해당 빈을 사용하는 요청은 빈이 재생성될 때까지 대기하게 됩니다. DB 커넥션 풀이나 무거운 초기화 로직이 있는 빈에 @RefreshScope를 붙이면 갱신 시 서비스 지연이 발생할 수 있습니다.

3. Git 저장소에 평문 비밀번호를 저장하면 보안 사고로 이어진다

Config Server가 Git 기반이므로 설정 파일의 이력이 모두 남습니다. 평문 비밀번호를 커밋하면 나중에 삭제해도 Git 히스토리에 남아 있습니다. 민감한 값은 반드시 {cipher} 암호화를 사용하거나 Vault 같은 시크릿 관리 도구를 연동하세요.

정리

  • Spring Cloud Config는 Git 저장소 기반의 중앙 설정 관리 서버입니다
  • @RefreshScope로 ** 재배포 없이** 설정을 런타임에 갱신할 수 있습니다
  • Spring Cloud Bus 를 사용하면 한 번의 요청으로 모든 인스턴스에 설정 변경을 전파합니다
  • 민감한 설정은 {cipher} 암호화 또는 Vault 로 보호합니다
댓글 로딩 중...