Skip to content

✍️ 필사 모드: Service Discovery 완전 가이드 2025: Consul, Eureka, Kubernetes DNS, 로드 밸런싱 통합

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

TL;DR

  • Service Discovery = 마이크로서비스의 필수: IP/포트 하드코딩 X, 동적 발견
  • 2가지 모델: Client-side (Eureka, Ribbon), Server-side (K8s, AWS ALB)
  • 3대 옵션: Consul (가장 인기), Kubernetes DNS (K8s 환경), etcd (인프라용)
  • Health Check 필수: 죽은 인스턴스 자동 제거
  • Service Mesh 통합: Istio, Linkerd가 디스커버리 + 로드 밸런싱 + observability

1. Service Discovery가 필요한 이유

1.1 모놀리스의 단순함

# 모놀리스
def get_user(user_id):
    return database.query(...)  # 같은 프로세스

함수 호출. 끝.

1.2 마이크로서비스의 복잡함

# 마이크로서비스
def get_user(user_id):
    response = requests.get(f"http://???/users/{user_id}")

**???**에 무엇을 넣을까?

옵션 1: 하드코딩

USER_SERVICE = "http://10.0.1.5:8080"

문제:

  • IP가 변경되면? (재배포, 스케일링)
  • 여러 인스턴스 중 어디로?
  • 인스턴스가 죽으면?
  • 새 인스턴스가 추가되면?

해결: Service Discovery.

1.3 동적 환경의 현실

Kubernetes 환경:

  • Pod이 죽고 재생성됨 (다른 IP)
  • HPA로 스케일링 (인스턴스 수 변화)
  • 롤링 업데이트 (옛 + 새 인스턴스 공존)
  • 노드 장애 (다른 노드로 이동)

필요한 것:

  • 동적 인스턴스 추적
  • 자동 등록/제거
  • 로드 밸런싱
  • 헬스 체크

2. Service Discovery 모델

2.1 Client-Side Discovery

[Client]
"users 서비스 어디?"
[Service Registry]
"[10.0.1.5, 10.0.1.6, 10.0.1.7]"
[Client] → 직접 선택 → [Server]

: Netflix Eureka + Ribbon

장점:

  • 더 적은 hop (직접 호출)
  • 클라이언트가 로드 밸런싱 전략 선택
  • 인프라 단순

단점:

  • 모든 언어에 클라이언트 라이브러리 필요
  • 클라이언트가 디스커버리 로직 처리
  • 결합도 증가

2.2 Server-Side Discovery

[Client]
"/users"
[Load Balancer / Proxy]
    (디스커버리 + 로드 밸런싱)
[Service Registry]
[Server]

: Kubernetes Service, AWS ALB

장점:

  • 클라이언트는 단일 endpoint만 알면 됨
  • 언어 무관
  • 중앙 관리

단점:

  • 추가 hop (load balancer)
  • 인프라 복잡도

2.3 Self-Registration vs Third-Party Registration

Self-Registration:

  • 서비스 시작 시 스스로 등록
  • 종료 시 스스로 해제
  • 예: Spring Cloud + Eureka
@SpringBootApplication
@EnableDiscoveryClient  // 자동 등록
public class UserServiceApplication {
    // ...
}

Third-Party Registration:

  • 외부 시스템이 등록 관리
  • 서비스는 모름
  • 예: Kubernetes (kubelet이 등록), Registrator + Docker

3. 주요 도구 비교

3.1 Consul

HashiCorp의 service mesh + 디스커버리.

# 서비스 등록
service {
  name = "users"
  port = 8080
  check {
    http     = "http://localhost:8080/health"
    interval = "10s"
  }
}

기능:

  • Service discovery (DNS + HTTP API)
  • Health checking
  • KV store
  • Multi-datacenter
  • Service mesh (Connect)

장점:

  • 풍부한 기능
  • 멀티 DC 지원
  • 강력한 health check
  • 활발한 커뮤니티

단점:

  • 운영 복잡 (Raft 클러스터)
  • 학습 곡선

3.2 Eureka (Netflix)

Netflix가 만든 client-side 디스커버리.

# application.yml
eureka:
  client:
    serviceUrl:
      defaultZone: http://eureka:8761/eureka/

장점:

  • Spring Cloud와 완벽 통합
  • 단순
  • AP (가용성 우선)

단점:

  • Java 중심
  • Netflix가 유지보수 줄임 (2018~)
  • 새 프로젝트는 Consul/K8s 추천

3.3 etcd

Kubernetes의 기반 데이터 저장소.

용도:

  • Kubernetes 상태 저장
  • 분산 락
  • 설정 관리
  • (덜 일반적) Service discovery

장점:

  • Raft 합의로 강력한 일관성
  • gRPC API
  • Kubernetes 생태계

단점:

  • 직접 service discovery로는 자주 사용 안 함
  • K8s 위에서는 K8s API 사용

3.4 Apache ZooKeeper

가장 오래된 분산 코디네이션 시스템.

역사:

  • Hadoop 생태계
  • Kafka가 사용 (KRaft로 대체 중)
  • HBase, Solr

장점:

  • 매우 성숙
  • 검증됨

단점:

  • 운영 복잡
  • 새 프로젝트는 etcd/Consul 선호
  • Java 의존

3.5 Kubernetes DNS

K8s 내장 디스커버리. 추가 도구 불필요.

apiVersion: v1
kind: Service
metadata:
  name: users
spec:
  selector:
    app: users
  ports:
    - port: 8080

클라이언트:

response = requests.get("http://users:8080/api/users/123")
# K8s DNS가 자동으로 IP로 해결

작동:

  1. CoreDNS가 K8s API 모니터링
  2. 서비스 생성/변경 시 DNS 레코드 자동 업데이트
  3. users.namespace.svc.cluster.local 형식

장점:

  • 추가 도구 불필요
  • 표준 DNS (모든 언어 지원)
  • 자동 등록/제거

단점:

  • K8s 안에서만
  • DNS 캐싱 문제 (TTL 짧게)

3.6 비교표

ConsulEurekaetcdZooKeeperK8s DNS
모델BothClient-sideServerBothServer-side
언어AnyJava 중심AnyAnyAny
CAPCPAPCPCPCP
Health Check강력단순외부외부강력
Multi-DC제한적외부
운영 난이도보통낮음보통높음매우 낮음

4. Health Checking

4.1 왜 중요한가?

서비스 등록 ≠ 건강함. 등록되었지만 응답 못 하는 인스턴스를 거르기.

[Client][Load Balancer][Healthy Instance][Dead Instance]    ❌ ← 제거 필요!

4.2 Health Check 종류

1. HTTP:

GET /health
HTTP/1.1 200 OK
{"status": "healthy"}

가장 흔함. 단순 endpoint.

2. TCP:

TCP connection to port 8080

연결만 확인. HTTP보다 약함.

3. gRPC:

service Health {
  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
}

gRPC 표준.

4. Script:

#!/bin/bash
# 커스텀 로직
if [ $(check_db) ]; then exit 0; fi
exit 1

복잡한 검증 로직.

4.3 Health Check 깊이

Liveness vs Readiness (Kubernetes 용어):

Liveness: "이 컨테이너가 살아있나?"

  • 죽었으면 → 재시작
  • 단순 체크
livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 10

Readiness: "이 컨테이너가 트래픽을 받을 수 있나?"

  • 아니면 → 로드 밸런서에서 제외
  • DB 연결, 의존 서비스 확인
readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5

Startup (K8s 1.16+): "초기화 완료했나?"

  • 느린 시작 앱용 (Java)
  • liveness/readiness보다 먼저

4.4 Health Check 설계

Shallow Check:

@app.route('/health')
def health():
    return {'status': 'ok'}, 200

문제: 앱은 살아있지만 DB 연결 끊겼을 수 있음.

Deep Check:

@app.route('/health')
def health():
    if not db.ping():
        return {'status': 'db_down'}, 503
    if not cache.ping():
        return {'status': 'cache_down'}, 503
    return {'status': 'ok'}, 200

문제: DB가 잠시 느리면 모든 인스턴스가 unhealthy → cascade failure.

균형:

  • Liveness: shallow (앱 자체)
  • Readiness: deep (의존성 포함)
  • 의존성 장애 시 readiness만 fail → 트래픽 차단, 재시작 X

4.5 Cascade Failure 방지

@app.route('/ready')
def ready():
    # DB 다운 → 503 → 트래픽 차단
    # 모든 인스턴스가 동시에 503 → 모든 트래픽 차단 → 더 큰 장애!
    pass

해결: Bulkhead, Circuit Breaker 패턴.


5. Kubernetes Service Discovery 깊이

5.1 Service 종류

ClusterIP (기본):

  • 클러스터 내부에서만 접근
  • 가상 IP (kube-proxy가 처리)

NodePort:

  • 모든 노드의 특정 포트로 노출
  • 외부 접근 가능

LoadBalancer:

  • 클라우드 로드 밸런서 자동 생성
  • AWS ALB, GCP LB, Azure LB

ExternalName:

  • 외부 DNS 이름으로 매핑
  • 외부 서비스 통합

Headless (clusterIP: None):

  • 가상 IP 없음
  • DNS는 직접 Pod IP 반환
  • StatefulSet, 클라이언트 사이드 LB

5.2 DNS 형식

<service>.<namespace>.svc.cluster.local

예시:

  • users (같은 네임스페이스)
  • users.production (다른 네임스페이스)
  • users.production.svc.cluster.local (전체)

5.3 kube-proxy

각 노드에서 실행. Service IP → Pod IP 변환.

모드:

  • iptables (기본): iptables 규칙 사용
  • IPVS: 더 빠름, 큰 클러스터 권장
  • eBPF (Cilium): 가장 빠름

5.4 Endpoint와 EndpointSlice

kubectl get endpoints users
# users  10.0.1.5:8080,10.0.1.6:8080,10.0.1.7:8080

Service가 가리키는 Pod들의 IP 목록. Pod 추가/제거 시 자동 업데이트.

EndpointSlice (K8s 1.21+):

  • 큰 클러스터 (수천 endpoint)에서 더 효율적
  • API 부하 감소

5.5 Service Mesh와 통합

Istio, Linkerd 같은 service mesh:

  • K8s service discovery 위에 추가 기능
  • mTLS
  • 트래픽 분할 (canary)
  • 분산 트레이싱
  • 관찰성
# Istio VirtualService
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: users
spec:
  hosts:
    - users
  http:
    - route:
        - destination:
            host: users
            subset: v1
          weight: 90
        - destination:
            host: users
            subset: v2
          weight: 10  # 10% 카나리

6. 분산 환경의 일관성

6.1 CAP 정리 적용

Eureka (AP):

  • 가용성 우선
  • 일시적으로 stale data 허용
  • "최선의 노력" 디스커버리

Consul (CP):

  • 일관성 우선
  • 네트워크 분할 시 일부 노드 거부
  • Raft 합의

선택:

  • AP: 디스커버리 가용성이 우선 (대부분의 경우)
  • CP: 강한 일관성 필요 (드문 경우)

6.2 Eventually Consistent 디스커버리

대부분의 시스템:

  • 새 인스턴스 등록 → 다른 클라이언트가 알기까지 약간의 지연 (수 초)
  • 죽은 인스턴스 제거 → 동일

현실:

  • 100% 즉시 알림은 불가능
  • 클라이언트는 retry + circuit breaker로 보호
  • Eventually consistency 받아들임

6.3 Split Brain 방지

네트워크 분할 시 두 그룹이 자기가 master라 생각.

해결: Quorum.

3노드 클러스터:

  • 1+1 분할: 어느 쪽도 quorum 못 만듦
  • 2+1 분할: 2 쪽이 quorum, 1 쪽은 거부

5노드 클러스터:

  • 2+3 분할: 3 쪽이 quorum

홀수 개 노드 권장.


7. 보안

7.1 mTLS

서비스 간 통신 암호화 + 신원 검증.

[Client] ←─ mTLS ─→ [Server]
   (둘 다 인증서 제시)

Service Mesh가 제공:

  • Istio: 자동 mTLS
  • Linkerd: 자동 mTLS
  • Consul Connect: 자동 mTLS

효과:

  • 도청 방지
  • MITM 공격 방지
  • Zero-trust 네트워크

7.2 ACL (Access Control)

Consul ACL:

service "users" {
  policy = "read"
}

서비스별 권한 정의. 어떤 클라이언트가 어떤 서비스를 호출 가능?

7.3 Service Identity

SPIFFE/SPIRE: 표준 service identity 프레임워크.

spiffe://example.com/services/users

각 서비스에 unique ID. 인증서에 포함.


8. 멀티 클러스터 / 멀티 리전

8.1 왜 필요한가?

  • 글로벌 분산: 사용자에 가까이
  • 재해 복구: 한 region 다운 시
  • 격리: 환경별 (dev/staging/prod)

8.2 Consul Mesh Gateway

[DC1]                    [DC2]
[Client] ─→ [MGW] ─→ [MGW] ─→ [Server]

DC 간 트래픽이 mesh gateway를 통해. 보안 + 라우팅.

8.3 Istio Multi-Cluster

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  values:
    global:
      multiCluster:
        clusterName: cluster-1
      network: network-1

모드:

  • Replicated control plane: 각 클러스터에 Istio
  • Single control plane: 한 곳에서 모두 관리

8.4 Submariner

K8s 클러스터 간 네트워크 연결. 다른 service mesh와 결합.


9. 실전 패턴

9.1 Sidecar 패턴

Service mesh가 사용. 각 service 옆에 proxy:

[App] ←─→ [Envoy Proxy] ←─→ [Network]
       (sidecar)

장점:

  • 앱은 모름
  • 모든 언어 지원
  • 일관된 정책

단점:

  • 추가 latency (~1ms)
  • 리소스 사용

9.2 DNS 캐싱 문제

# 첫 호출
ip = dns.resolve("users")  # 10.0.1.5
# IP 캐시
# 이후 user-service Pod 재시작 → 새 IP (10.0.1.6)
# 하지만 캐시는 옛 IP 그대로
# → 연결 실패

해결:

  • TTL 짧게 (K8s DNS는 5초)
  • HTTP 클라이언트가 매번 DNS 해결
  • Service Mesh 사용 (Envoy가 자동)

9.3 Graceful Shutdown

잘못된 종료:

[Pod 종료 신호]
[즉시 종료]
[진행 중 요청 손실]

Graceful:

[Pod 종료 신호]
[Readiness fail] → 새 트래픽 차단
[진행 중 요청 완료 대기]
[종료]
import signal

def handle_term(signum, frame):
    app.shutdown_started = True
    # Readiness가 503 반환
    # 진행 중 요청 완료
    sys.exit(0)

signal.signal(signal.SIGTERM, handle_term)

K8s에서 terminationGracePeriodSeconds 설정.

9.4 Connection Draining

로드 밸런서가 인스턴스 제거 시:

  1. 새 연결 차단
  2. 기존 연결 완료 대기 (보통 30초)
  3. 강제 종료

10. 도구 선택 가이드

10.1 의사결정 트리

Kubernetes 사용 중?
├─ YES
│  ├─ 단순 (basic discovery만 필요)
│  │   → K8s DNS (기본)
│  └─ Service Mesh 필요 (mTLS, traffic, observability)
│     → Istio, Linkerd
└─ NO
   ├─ HashiCorp 스택?
   │   → Consul
   ├─ Java + Spring?
   │   → Eureka (legacy) 또는 Consul
   └─ 일반 솔루션
Consul

10.2 Consul vs K8s DNS vs Istio

Consul:

  • ✅ K8s 외부 환경
  • ✅ 멀티 데이터센터
  • ✅ KV 스토어 등 추가 기능
  • ❌ K8s만 사용한다면 과도

K8s DNS:

  • ✅ K8s 환경, 단순
  • ✅ 추가 도구 불필요
  • ❌ K8s 밖 서비스 통합 어려움

Istio:

  • ✅ K8s + service mesh 필요
  • ✅ mTLS, 트래픽 분할, observability
  • ❌ 복잡, 러닝 커브

퀴즈

1. Client-side와 Server-side discovery의 차이는?

: Client-side: 클라이언트가 service registry에서 인스턴스 목록을 받아 직접 선택 (Eureka + Ribbon). 장점: hop 적음, 유연. 단점: 모든 언어에 라이브러리 필요. Server-side: 클라이언트는 단일 endpoint(load balancer)에 요청 → LB가 디스커버리 + 라우팅 (K8s, AWS ALB). 장점: 언어 무관, 클라이언트 단순. 단점: 추가 hop. 현대 시스템은 server-side가 우세 (K8s 표준).

2. Liveness, Readiness, Startup probe의 차이는?

: Liveness: "컨테이너가 살아있나?" 실패 시 → 재시작. 단순 체크 (앱 프로세스 응답). Readiness: "트래픽을 받을 준비가 됐나?" 실패 시 → 로드 밸런서에서 제외 (재시작 X). 의존성 (DB, 캐시) 포함. Startup (K8s 1.16+): "초기화 완료?" 실패 시 → liveness/readiness 미실행. 느린 시작 앱(Java)에 적합. 잘못 구성하면 cascade failure 발생 — DB 다운 → 모든 readiness fail → 모든 트래픽 차단.

3. Eureka vs Consul vs K8s DNS — 언제 어떤 것?

: Eureka: Spring Boot/Java + AP (가용성 우선). 새 프로젝트는 비추천 (Netflix 유지보수 감소). Consul: HashiCorp 스택, 멀티 DC, K8s 외부 환경, 풍부한 기능 (KV store, mesh). K8s DNS: K8s 환경의 기본. 추가 도구 불필요, 단순. 대부분의 K8s 워크로드에 충분. 결정: K8s 안 → DNS, K8s + 고급 → Istio, K8s 외부 → Consul, Java + Spring legacy → Eureka.

4. DNS 캐싱이 service discovery에 일으키는 문제는?

: 클라이언트가 DNS를 캐시하면 → Pod이 재시작하여 IP가 바뀌어도 옛 IP로 계속 시도 → 연결 실패. 해결: (1) TTL 짧게 (K8s DNS는 5초), (2) HTTP 클라이언트가 매번 DNS 해결 (Java의 networkaddress.cache.ttl=0), (3) Service Mesh 사용 — Envoy가 endpoint를 직접 모니터링하여 캐싱 문제 회피, (4) Headless service + 클라이언트 LB. 가장 흔한 함정 중 하나입니다.

5. mTLS가 service mesh의 핵심 기능인 이유는?

: 마이크로서비스는 수많은 서비스 간 통신이 발생합니다. 각 통신을 평문으로 두면 (1) 도청 위험, (2) MITM 공격, (3) 신원 위조 가능. mTLS (mutual TLS): 양쪽이 서로 인증서를 교환하여 신원 검증 + 트래픽 암호화. Zero-trust 네트워크의 기반. Istio, Linkerd, Consul Connect가 자동으로 mTLS 제공 — 앱 코드 변경 없이. SPIFFE/SPIRE는 표준 service identity를 정의합니다.


참고 자료

현재 단락 (1/438)

- **Service Discovery = 마이크로서비스의 필수**: IP/포트 하드코딩 X, 동적 발견

작성 글자: 0원문 글자: 10,449작성 단락: 0/438