
최근 포스팅한 내용들은 모두 같은 프로젝트에서 배우고 적용한 내용들이다. 해당 프로젝트는 클라우드 네이티브 아키텍처를 기반으로 MSA 구조를 채택한 프로젝트이다. 마이크로서비스 간의 결합도를 낮추고, 필요한 컴포넌트만 독립적으로 확장하거나 배포할 수 있도록 설계하였다. 다양한 기술들을 학습하고 이를 적용한 프로젝트여서 이야기할 내용이 매우 많은 프로젝트이다.
MSA 구조를 처음 도입하기도 하였고 나아가 대규모 트래픽 테스트까지 진행하고 테스트 커버리지도 90%이상을 유지하는 목표도 달성하였기에 많은 성장을 도와준 프로젝트라고 생각한다.
이번 포스팅에서는 설정 파일, 시크릿 관리에 대한 Spring Cloud Config, Vault, Spring Cloud Bus 등의 기술 스택을 소개하려고 한다. 이전에 Spring Cloud Config, Spring Cloud Bus에 대해서는 자세하게 다룬 적이 있어서 Vault를 위주로 다룰 것이지만 다른 두 내용이 빠질 순 없어서 그때 이야기했던 내용을 다시 복습하는 느낌으로 간단하게 소개할 내용이다.
Vault는 HashiCorp에서 제공하는 시크릿 관리 솔루션으로, 애플리케이션 운영 시 필요한 패스워드나 API 키 등의 민감 정보를 중앙 집중식으로 안전하게 저장·관리하고, 세분화된 접근 권한(ACL)과 토큰 기반 인증 방식을 통해 필요한 시점에만 시크릿을 안전하게 노출한다. 이러한 특성은 보안성을 크게 높이면서도 운영 편의성을 제공하여, 각각의 애플리케이션이 민감 정보를 직접 다루지 않고도 필요한 정보를 안전하게 주고받을 수 있게 해준다.
프로젝트 초기에는 Native Repo 방식을 활용하여, Config Service가 로컬 파일 시스템이나 별도의 경로에 위치한 설정 파일을 직접 읽어오는 방식을 사용하였다. 그러나 다음과 같은 한계가 있었다.
이러한 문제를 해결하기 위해 Vault를 도입하였다. Vault는 중앙 집중식으로 시크릿을 암호화하여 저장하고, 접근 권한을 세밀하게 제어할 수 있다. 본 프로젝트에서는 Vault를 활용해 다음과 같은 개선을 이루었다.
bootstrap.yaml)spring:
application:
name: config-service
cloud:
vault:
host: localhost
port: 8200
scheme: http
uri: http://localhost:8200
token: ${VAULT_TOKEN}
authentication: token
kv:
backend: config-service-secret
application-name: config-service
profiles: dev
위 설정은 부트스트랩 단계에서 Vault에 대한 접근 정보를 정의한다. spring.cloud.vault 하위 옵션으로 Vault 호스트, 토큰, 인증 방식을 명시한다. backend와 profiles를 활용해 어떤 경로에서 시크릿을 가져올지 세밀하게 조정할 수 있다.
token: Vault 서버 접근에 필요한 토큰이다. 실제 배포 시 운영 환경별로 다른 값을 주입하여 보안을 강화한다.backend: Vault 내 K/V 엔진의 경로를 설정한다. 여기서는 config-service-secret을 사용하였다.profiles: dev, prod 등 환경별로 Vault 내 경로를 분리해 운영할 수 있다.부트스트랩 파일을 추가하면 config service가 시작될 때 application.yaml보다 bootstrap.yaml을 먼저 확인하게 된다. application.yaml에는 아래에서 볼 수 있듯이 cloud bus를 위한 rabbitMQ 정보에 Vault값에서 가져온 시크릿값이 주입되어야 한다. 만약 application.yaml안에서 vault 연결이 이루어지게 되면 vault 값을 가져오기전에 시크릿 값을 주입해야하므로 값이 제대로 매핑되지 않았다. 따라서 Bootstrap.yaml을 두어서 애플리케이션이 로드될 때 가장 먼저 vault에 연결하여 시크릿 값들을 올바르게 가져오도록 한 것이다.
spring:
profiles:
active: git, vault
rabbitmq:
host: ${vault.rabbitmq_host}
port: ${vault.rabbitmq_port}
username: ${vault.rabbitmq_username}
password: ${vault.rabbitmq_password}
cloud:
config:
server:
vault:
order: 1
host: localhost
port: 8200
token: ${VAULT_TOKEN}
authentication: token
kv-version: 2
backend: config-service-secret
profile-separator: '/'
scheme: http
git:
uri: (Github Repo 주소)
search-paths:
- '{application}'
default-label: main
timeout: 5
clone-on-start: true
force-pull: true
management:
endpoints:
web:
exposure:
include: health, bus-refresh
spring.profiles.active: git, vault: Git과 Vault 프로파일을 동시에 활성화하여, Git Repo에서 일반 설정값을 가져오고 Vault에서 민감 정보를 가져올 수 있도록 한다.${vault.rabbitmq_xxx}로 지정함으로써, 실제 값은 Vault로부터 가져온다.cloud.config.server.git: Git Repo와 연동하여, 민감 정보가 아닌 일반 설정(예: 로그 레벨, 서비스별 Endpoint 등)을 버전 관리한다.아래는 실제로 내가 구현한 프로젝트의 Vault 저장소 구성을 도식화한 이미지이다. config service sercret 엔진안에는 폴더로 각 4개의 서비스가 있고 각 서비스마다 local, prod, test 설정에 대한 시크릿 값을 따로 두어서 프로파일 별로 설정 값을 다르게 가져오도록 구성하였다.

클라우드 네이티브 아키텍처로 설계된 마이크로서비스들은 대부분 공통되는 설정값(데이터베이스 접근 정보, 외부 API 인증 정보 등)을 공유하거나 환경별(dev, prod 등)로 구분된 다른 설정값을 필요로 한다. 이때 각 마이크로서비스는 Config Service를 통해 설정값을 전달받아야 한다.
edge-service의 application.yaml아래는 여러 서비스들 중 하나인 Edge Service에서, Config Service를 어떻게 참조하도록 설정했는지 보여주는 예시이다.
spring:
application:
name: edge-service
profiles:
active: local
config:
import: "configserver:"
cloud:
config:
uri: http://localhost:8888
request-connect-timeout: 5000
request-read-timeout: 5000
fail-fast: true
retry:
max-attempts: 6
initial-interval: 1000
max-interval: 2000
multiplier: 1.1
management:
endpoints:
web:
exposure:
include: refresh, health, bus-refresh
spring.application.name: 마이크로서비스의 이름을 정의한다. Config Service에서 {application} 구문으로 해당 마이크로서비스에 맞는 설정을 구분·관리할 수 있다.spring.profiles.active: 현재 서비스가 동작하는 프로파일을 명시한다. 개발 환경(local), 운영 환경(prod) 등에 따라 다른 설정을 받아올 수 있다. Vault에는 각 서비스에 대한 각각의 프로파일이 존재한다.config.import: "configserver:"를 통해 Config Service를 설정 소스로 사용하도록 한다. Spring Boot 2.4 이상에서 지원되는 방식이다.cloud.config.uri: Config Service가 동작하는 주소를 지정한다. 위의 예시에서는 로컬에서 config service를 실행하였기 때문에 로컬 호스트를 넣었다. 실제 배포에서는 config service의 배포 주소를 넣어야 한다.fail-fast, retry 옵션: Config Service에 연결이 실패하더라도 재시도 하도록 설정한다. 네트워크 혹은 일시적 장애 상황에서도 안정적으로 재연결을 시도한다.management.endpoints.web.exposure.include: Spring Actuator에서 노출할 엔드포인트를 지정한다. refresh, health, bus-refresh 등을 통해 동적 구성 갱신과 헬스 체크가 가능하다.Vault가 민감 정보를 안전하게 관리하더라도, 일반적인 설정값(로그 레벨, 특정 기능 토글 등)은 Git Repository를 통해 버전 관리하는 방식을 선택하였다. 그 이유는 아래와 같다.
management.endpoints.web.exposure.include에 health, bus-refresh, refresh 등을 포함하면, Spring Actuator를 통해 다음과 같은 이점이 있다.
이를 통해 프로젝트 운영 중 새로운 설정값이나 시크릿을 업데이트할 때, 재배포 없이 서비스 중단 시간을 최소화하면서 갱신이 가능해진다. 자세한 내용은 아래 포스팅에서 다루었다.
Spring Config Server에서 Config 파일들을 어떻게 쉽게 갱신할까?
기존에는 Github Secret을 활용해서 시크릿 관리를 하였는데 시크릿 값이 바뀔 때마다 다시 키 값을 지정하고 프로파일마다 다른 값을 설정하는 것에 대한 불편함이 존재했었다. 그러나 이번에 처음으로 Vault를 도입해서 사용해보니 왜 여러 기업들이 Vault를 사용하여 시크릿 값들을 관리하고 있는지 알게 되엇다. GUI가 매우 깔끔하였고 Json 형태로 키 값을 바로 넣을 수 있어서 수정도 편리하였다. 또한 시크릿 키를 버전 별로 관리할 수도 있어 향후 실제 서비스의 배포 및 운영에도 매우 큰 도움이 되었다.