이 글에서 얻는 것
- 12-Factor App 설정 원칙을 이해합니다
- Spring Cloud Config로 중앙 설정 관리를 구현합니다
- Vault를 활용한 시크릿 관리를 알아봅니다
12-Factor App 설정 원칙
Config는 환경에 저장
flowchart TB
subgraph "❌ 하드코딩"
App1["application.yml\ndb.url=jdbc:mysql://prod-db:3306"]
end
subgraph "✅ 환경 변수"
Env["환경 변수\nDB_URL=jdbc:mysql://..."]
App2["application.yml\ndb.url=${DB_URL}"]
Env --> App2
end
원칙: 설정은 코드와 분리. 환경마다 다른 값을 환경 변수로 주입
Spring Boot 설정 우선순위
// 우선순위 (높은 순)
1. 명령줄 인자: --server.port=8080
2. SPRING_APPLICATION_JSON
3. 시스템 프로퍼티: -Dserver.port=8080
4. OS 환경 변수: SERVER_PORT=8080
5. application-{profile}.yml
6. application.yml
7. @PropertySource
8. 기본값
환경별 프로파일
# application.yml (공통)
spring:
profiles:
active: ${SPRING_PROFILES_ACTIVE:local}
server:
port: 8080
---
# application-local.yml
spring:
datasource:
url: jdbc:h2:mem:testdb
---
# application-prod.yml
spring:
datasource:
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
Spring Cloud Config
아키텍처
flowchart LR
subgraph "Config Server"
CS[Config Server]
GIT[(Git Repository)]
CS --> GIT
end
subgraph "Applications"
A1[Service A]
A2[Service B]
A3[Service C]
end
A1 -->|/config| CS
A2 -->|/config| CS
A3 -->|/config| CS
Config Server 설정
// Config Server Application
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
# Config Server application.yml
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://github.com/company/config-repo
default-label: main
search-paths: '{application}'
Git Repository 구조
config-repo/
├── application.yml # 공통 설정
├── order-service/
│ ├── application.yml # order-service 기본
│ ├── application-dev.yml # order-service dev
│ └── application-prod.yml # order-service prod
└── payment-service/
├── application.yml
└── application-prod.yml
Client 설정
# bootstrap.yml (Spring Boot 2.4 이전)
spring:
application:
name: order-service
cloud:
config:
uri: http://config-server:8888
profile: ${SPRING_PROFILES_ACTIVE:dev}
# Spring Boot 2.4+ (spring.config.import)
spring:
config:
import: configserver:http://config-server:8888
cloud:
config:
profile: ${SPRING_PROFILES_ACTIVE:dev}
동적 설정 갱신
@RefreshScope // 설정 갱신 시 빈 재생성
@Component
public class DynamicConfig {
@Value("${feature.new-checkout:false}")
private boolean newCheckoutEnabled;
public boolean isNewCheckoutEnabled() {
return newCheckoutEnabled;
}
}
// POST /actuator/refresh 호출 시 갱신
# actuator 설정
management:
endpoints:
web:
exposure:
include: refresh, health
Vault 시크릿 관리
왜 Vault인가?
flowchart TB
subgraph "❌ 평문 저장"
GIT1["Git\npassword=secret123"]
ENV1["ENV\nDB_PASSWORD=secret123"]
end
subgraph "✅ Vault"
App[Application]
Vault[(HashiCorp Vault)]
App -->|인증| Vault
Vault -->|암호화된 시크릿| App
end
Vault 장점:
- 시크릿 암호화 저장
- 동적 시크릿 (DB 자격증명 자동 생성)
- 감사 로그
- 시크릿 회전
Spring Cloud Vault 설정
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>
spring:
cloud:
vault:
uri: http://vault:8200
token: ${VAULT_TOKEN}
kv:
enabled: true
backend: secret
default-context: order-service
profile-separator: '/'
Vault에 시크릿 저장
# Vault CLI로 시크릿 저장
vault kv put secret/order-service \
db.username=admin \
db.password=super-secret-password \
api.key=abc123
# 개발/운영 환경 분리
vault kv put secret/order-service/dev db.password=dev-password
vault kv put secret/order-service/prod db.password=prod-password
사용
@Service
public class DatabaseConfig {
@Value("${db.username}") // Vault에서 자동 주입
private String username;
@Value("${db.password}") // Vault에서 자동 주입
private String password;
}
Kubernetes ConfigMap/Secret
ConfigMap
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: order-service-config
data:
application.yml: |
server:
port: 8080
feature:
new-checkout: true
# deployment.yaml
spec:
containers:
- name: order-service
volumeMounts:
- name: config
mountPath: /config
volumes:
- name: config
configMap:
name: order-service-config
Secret
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: order-service-secret
type: Opaque
data:
db-password: c3VwZXItc2VjcmV0 # base64 encoded
# 환경 변수로 주입
spec:
containers:
- name: order-service
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: order-service-secret
key: db-password
설정 검증
@ConfigurationProperties
@ConfigurationProperties(prefix = "app.feature")
@Validated
@Getter @Setter
public class FeatureProperties {
@NotBlank
private String name;
@Min(1) @Max(100)
private int maxConnections = 10;
@NotNull
private Duration timeout = Duration.ofSeconds(30);
private boolean enabled = false;
}
app:
feature:
name: checkout
max-connections: 50
timeout: 5s
enabled: true
시작 시 검증
@Component
public class ConfigValidator implements ApplicationRunner {
@Autowired
private FeatureProperties featureProperties;
@Override
public void run(ApplicationArguments args) {
// 필수 설정 검증
if (featureProperties.getName() == null) {
throw new IllegalStateException("app.feature.name is required");
}
log.info("Configuration validated: {}", featureProperties);
}
}
요약
설정 관리 체크리스트
| 항목 | 권장 |
|---|---|
| 환경별 분리 | Spring Profiles |
| 중앙 관리 | Spring Cloud Config |
| 시크릿 | Vault / K8s Secret |
| 동적 갱신 | @RefreshScope |
| 검증 | @ConfigurationProperties + @Validated |
핵심 원칙
- 코드와 설정 분리: 환경 변수 사용
- 시크릿은 암호화: Vault 또는 K8s Secret
- 중앙 집중 관리: Config Server
- 검증: 시작 시 필수 설정 확인
🔗 Related Deep Dive
- Kubernetes 기본: ConfigMap, Secret 활용.
- 피처 플래그: 동적 설정 기반 기능 제어.
💬 댓글