Spring Cloud Kubernetes — 쿠버네티스 네이티브 서비스 디스커버리와 설정 관리
Kubernetes가 이미 서비스 목록을 알고 있는데, 왜 Eureka 같은 서비스 레지스트리를 또 띄워야 할까요?
Kubernetes는 자체적으로 Service라는 리소스를 통해 서비스 디스커버리를 제공합니다. 그런데 Spring Cloud 애플리케이션을 K8s 위에서 실행하면서도 Eureka를 별도로 운영하는 경우가 많습니다. Spring Cloud Kubernetes는 이 중복을 제거하고, K8s의 네이티브 기능을 Spring Cloud의 추상화에 직접 연결해줍니다.
Spring Cloud Kubernetes란
Spring Cloud Kubernetes는 Kubernetes의 네이티브 기능(Service, ConfigMap, Secret)을 Spring Cloud의 표준 인터페이스(DiscoveryClient, PropertySource)로 매핑하는 프로젝트입니다.
핵심 기능:
- **서비스 디스커버리 **: K8s Service → Spring
DiscoveryClient - ** 설정 관리 **: K8s ConfigMap/Secret → Spring
PropertySource - ** 리더 선출 **: K8s 네이티브 리더 선출 메커니즘 지원
- ** 헬스 체크 **: K8s의 liveness/readiness 프로브와 Spring Actuator 연동
의존성
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-client-all</artifactId>
</dependency>
kubernetes-client-all은 디스커버리 + 설정 + 리더 선출을 모두 포함합니다. 필요한 것만 쓰고 싶다면 개별 스타터를 선택할 수 있습니다:
spring-cloud-starter-kubernetes-client-config— ConfigMap/Secret 연동만spring-cloud-starter-kubernetes-client— 디스커버리만
DiscoveryClient: K8s Service를 자동 매핑
Spring Cloud의 DiscoveryClient 인터페이스를 구현하여, K8s Service를 Spring의 ServiceInstance로 변환합니다. 기존에 Eureka용으로 작성된 코드가 수정 없이 동작합니다.
@Service
@RequiredArgsConstructor
public class ServiceRegistry {
private final DiscoveryClient discoveryClient;
public List<String> getAllServices() {
// K8s Service 목록을 그대로 반환
return discoveryClient.getServices();
}
public List<ServiceInstance> getInstances(String serviceId) {
// 특정 서비스의 Pod 인스턴스 목록
return discoveryClient.getInstances(serviceId);
}
}
이 코드는 Eureka를 쓸 때와 ** 완전히 동일 **합니다. 의존성만 바꾸면 구현 코드를 수정할 필요가 없다는 게 Spring Cloud 추상화의 장점입니다.
설정 옵션
spring:
cloud:
kubernetes:
discovery:
enabled: true
all-namespaces: false # true면 모든 네임스페이스 검색
namespaces: # 특정 네임스페이스만 검색
- production
- staging
service-labels: # 라벨 기반 필터링
environment: production
team: backend
공부하면서 가장 실수하기 쉬운 부분이 RBAC 권한 설정이었습니다. Spring Cloud Kubernetes가 K8s API 서버에 접근하려면 적절한 ServiceAccount와 Role/ClusterRole이 필요합니다:
# K8s RBAC 설정
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: service-discovery-role
rules:
- apiGroups: [""]
resources: ["services", "endpoints", "pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: service-discovery-binding
subjects:
- kind: ServiceAccount
name: my-app-sa
namespace: default
roleRef:
kind: ClusterRole
name: service-discovery-role
apiGroup: rbac.authorization.k8s.io
이 권한이 없으면 애플리케이션 기동 시 403 에러가 나는데, 처음 세팅할 때 가장 많이 마주치는 문제입니다.
ConfigMap/Secret 연동
K8s ConfigMap과 Secret을 Spring의 PropertySource로 자동 매핑하여, application.yml과 동일하게 @Value나 @ConfigurationProperties로 접근할 수 있습니다.
ConfigMap 기반 설정
# K8s ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: order-service # 애플리케이션 이름과 일치시키기
namespace: default
data:
application.yml: |
order:
max-items: 50
timeout-seconds: 30
feature:
new-payment: true
@Component
@ConfigurationProperties(prefix = "order")
@Getter @Setter
public class OrderProperties {
private int maxItems;
private int timeoutSeconds;
}
Spring Cloud Kubernetes는 기본적으로 spring.application.name과 일치하는 ConfigMap을 자동으로 찾습니다. 다른 이름의 ConfigMap을 사용하려면 명시적으로 지정합니다:
spring:
cloud:
kubernetes:
config:
enabled: true
sources:
- name: order-service-config
namespace: default
- name: shared-config # 공통 설정 ConfigMap
namespace: common
Secret 연동
# K8s Secret
apiVersion: v1
kind: Secret
metadata:
name: order-service-secrets
type: Opaque
data:
db-password: cGFzc3dvcmQxMjM= # base64 인코딩
spring:
cloud:
kubernetes:
secrets:
enabled: true
sources:
- name: order-service-secrets
@Value("${db-password}")
private String dbPassword; // 자동으로 base64 디코딩
동적 설정 갱신
ConfigMap이 변경되면 애플리케이션을 재배포하지 않고도 설정을 갱신할 수 있습니다:
spring:
cloud:
kubernetes:
config:
enabled: true
reload:
enabled: true
mode: polling # polling 또는 event
period: 15000 # polling 주기 (ms)
strategy: refresh # refresh 또는 restart_context
@RefreshScope를 적용한 Bean은 ConfigMap 변경 시 자동으로 갱신됩니다:
@RefreshScope
@Component
@ConfigurationProperties(prefix = "feature")
@Getter @Setter
public class FeatureFlags {
private boolean newPayment;
}
mode를 event로 설정하면 K8s API의 Watch 기능을 사용하여 변경 즉시 감지합니다. 다만 이 방식은 K8s API 서버와의 연결을 계속 유지하므로, Pod 수가 많으면 API 서버에 부하를 줄 수 있습니다.
네임스페이스와 라벨 기반 필터링
실무에서는 하나의 K8s 클러스터에 여러 환경(dev, staging, production)이 공존하는 경우가 많습니다. 이때 네임스페이스와 라벨로 서비스 디스커버리 범위를 제어합니다.
spring:
cloud:
kubernetes:
discovery:
# 특정 네임스페이스만
namespaces:
- production
# 라벨 조건 추가
service-labels:
app.kubernetes.io/part-of: order-system
# 특정 서비스 포함/제외
filter:
includes:
- payment-service
- inventory-service
라벨 기반 필터링을 쓰면, 같은 네임스페이스에 있더라도 관련 없는 서비스는 디스커버리 대상에서 제외됩니다. 대규모 클러스터에서 불필요한 서비스 목록을 줄이는 데 유용합니다.
Discovery Server
모든 Pod에 K8s API 접근 권한을 주는 건 보안상 바람직하지 않을 수 있습니다. Discovery Server는 이 문제를 해결합니다.
[Discovery Server] ← K8s API 접근 권한 (유일)
↑
HTTP 엔드포인트
↑
[Pod A] [Pod B] [Pod C] ← K8s API 권한 불필요
Discovery Server 설정:
<!-- Discovery Server 의존성 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-discoverserver</artifactId>
</dependency>
클라이언트 측 설정:
# 클라이언트 Pod에서 Discovery Server를 사용
spring:
cloud:
kubernetes:
discovery:
discovery-server-url: http://discovery-server.default.svc:8761
이렇게 하면 RBAC 설정을 Discovery Server 한 곳에만 집중할 수 있어 보안 관리가 훨씬 간단해집니다.
Spring Cloud 2025.0.0 (Northfields) 업데이트
최신 릴리스인 Spring Cloud 2025.0.0에서 달라진 점:
- Fabric8 Kubernetes Client 7.3.1 로 업그레이드 — K8s 1.32+ API 호환
- Spring Boot 3.5.0 호환
- GraalVM Native Image 공식 지원 — 네이티브 컴파일로 기동 시간 단축
- 개선된 ConfigMap/Secret 감시 메커니즘
GraalVM Native Image 빌드:
# 네이티브 이미지 빌드
mvn -Pnative spring-boot:build-image
네이티브 이미지로 빌드하면 기동 시간이 수 초에서 수십 밀리초로 줄어들어, K8s 환경에서의 스케일링 속도가 크게 개선됩니다.
Istio와의 호환성
Istio 같은 서비스 메시를 이미 사용하고 있다면, Spring Cloud Kubernetes와의 역할 중복이 걱정될 수 있습니다.
역할 분담 정리:
- ** 서비스 디스커버리 **: Istio가 처리 → Spring Cloud Kubernetes 디스커버리는 비활성화 가능
- ** 로드밸런싱 **: Istio의 Envoy 프록시가 처리
- ** 설정 관리 **: Spring Cloud Kubernetes의 ConfigMap 연동은 Istio와 무관하게 유용
- ** 서킷 브레이커 **: Istio의 Outlier Detection 또는 Resilience4j 중 선택
# Istio 환경에서 디스커버리만 비활성화
spring:
cloud:
kubernetes:
discovery:
enabled: false # Istio가 처리
config:
enabled: true # ConfigMap 연동은 유지
공부하면서 느낀 건, Istio와 Spring Cloud Kubernetes는 경쟁 관계가 아니라 ** 보완 관계 **라는 것입니다. 네트워크 레벨은 Istio에, 애플리케이션 레벨 설정은 Spring Cloud Kubernetes에 맡기는 패턴이 가장 자연스럽습니다.
Eureka vs Spring Cloud Kubernetes 선택 기준
| 기준 | Eureka | Spring Cloud Kubernetes |
|---|---|---|
| 실행 환경 | K8s + VM + 베어메탈 모두 가능 | K8s 전용 |
| 추가 인프라 | Eureka 서버 운영 필요 | K8s 자체 기능 활용 (추가 없음) |
| 설정 관리 | Spring Cloud Config 별도 필요 | ConfigMap/Secret 네이티브 연동 |
| 헬스 체크 | Eureka heartbeat | K8s liveness/readiness 프로브 |
| 멀티 클러스터 | Eureka Peer Replication | Federation 또는 별도 구성 필요 |
정리하면:
- K8s 전용 환경 → Spring Cloud Kubernetes가 운영 부담이 적음
- K8s + 비K8s 혼합 환경 → Eureka가 환경 독립적이라 유리
- ** 이미 Eureka를 잘 쓰고 있다면** → 굳이 마이그레이션할 필요 없음
- ** 새로 시작한다면** → K8s 환경이라면 Spring Cloud Kubernetes로 시작하는 것을 권장
정리
- Spring Cloud Kubernetes는 K8s의 Service, ConfigMap, Secret을 Spring Cloud 추상화에 직접 매핑
DiscoveryClient코드가 Eureka 때와 완전히 동일하므로, 의존성 교체만으로 전환 가능- ConfigMap 변경 시
@RefreshScope와 함께 사용하면 재배포 없이 설정 갱신 가능 - Discovery Server를 사용하면 RBAC 권한 관리를 단순화할 수 있음
- K8s 전용 환경이라면 Eureka 대신 Spring Cloud Kubernetes로 운영 복잡도를 줄이는 것이 합리적