--- id: backend-service-discovery title: Service Discovery — DNS / Consul / K8s category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [backend, service-discovery, vibe-coding] tech_stack: { language: "TS / K8s / Consul", applicable_to: ["Backend"] } applied_in: [] aliases: [service discovery, service mesh, DNS-SD, Consul, Eureka, K8s service] --- # Service Discovery > 마이크로서비스 = 어떻게 서로 찾지? **K8s Service (DNS) / Consul / Eureka / Service Mesh**. Client-side vs server-side discovery. ## 📖 핵심 개념 - DNS-based: 가장 단순. K8s 가 사용. - Server-side: LB 가 routing. AWS ALB. - Client-side: client 가 instance 선택. Eureka. - Service mesh: sidecar 가 처리. Istio. ## 💻 코드 패턴 ### K8s Service (DNS) ```yaml apiVersion: v1 kind: Service metadata: name: orders namespace: prod spec: selector: app: orders ports: - port: 80 targetPort: 3000 ``` → DNS: `orders.prod.svc.cluster.local`. ```ts // Client const r = await fetch('http://orders.prod.svc.cluster.local/api/list'); // 또는 same-namespace const r = await fetch('http://orders/api/list'); ``` → K8s 가 자동 LB + health check. ### Headless service (직접 IP list) ```yaml apiVersion: v1 kind: Service metadata: name: redis spec: clusterIP: None # headless selector: { app: redis } ports: [{ port: 6379 }] ``` → DNS 가 모든 pod IP 반환. Client 가 선택 — Redis cluster 같은 stateful. ### Consul ```bash # Consul agent 설치 consul agent -dev ``` ```ts // Service 등록 import Consul from 'consul'; const consul = new Consul(); await consul.agent.service.register({ name: 'orders', id: `orders-${hostname}`, address: '10.0.0.5', port: 3000, check: { http: 'http://10.0.0.5:3000/health', interval: '10s', timeout: '5s', }, }); // Service 찾기 const services = await consul.health.service('orders'); const healthy = services.filter(s => s.Checks.every(c => c.Status === 'passing')); const target = healthy[Math.floor(Math.random() * healthy.length)].Service; const r = await fetch(`http://${target.Address}:${target.Port}/api/list`); ``` ### Consul DNS interface ```bash # Consul 가 자동 DNS server (port 8600) dig @127.0.0.1 -p 8600 orders.service.consul # Or 시스템 DNS forwarding 설정 후 dig orders.service.consul ``` ### Service Mesh (Istio / Linkerd) discovery ``` Sidecar proxy 가 자동: - Service registry - Health check - Traffic split - Retry / circuit breaker App 코드는 그냥 "http://orders" — mesh 가 routing. ``` → 위 [[DevOps_Service_Mesh_Deep]]. ### AWS ECS / App Runner / ALB ```hcl resource "aws_service_discovery_service" "orders" { name = "orders" dns_config { namespace_id = aws_service_discovery_private_dns_namespace.main.id dns_records { ttl = 10 type = "A" } } health_check_custom_config { failure_threshold = 1 } } # ECS service resource "aws_ecs_service" "orders" { name = "orders" service_registries { registry_arn = aws_service_discovery_service.orders.arn } } ``` → `orders.acme.local` DNS. ### Eureka (Netflix, Java/Spring) ```java // Spring Cloud @EnableDiscoveryClient public class App { ... } // Application.yml eureka: client: serviceUrl: defaultZone: http://eureka:8761/eureka/ ``` → Spring 사용자. ### Health check ```ts app.get('/healthz', (req, res) => { // Liveness — process 살아있나 res.status(200).end(); }); app.get('/readyz', async (req, res) => { // Readiness — 준비됐나 (DB OK, deps OK) try { await db.query('SELECT 1'); await redis.ping(); res.status(200).end(); } catch { res.status(503).end(); } }); app.get('/startupz', (req, res) => { // Startup — 처음 시작 OK? if (initialized) res.status(200).end(); else res.status(503).end(); }); ``` ```yaml # K8s livenessProbe: httpGet: { path: /healthz, port: 3000 } periodSeconds: 10 failureThreshold: 3 readinessProbe: httpGet: { path: /readyz, port: 3000 } periodSeconds: 5 failureThreshold: 2 startupProbe: httpGet: { path: /startupz, port: 3000 } failureThreshold: 30 periodSeconds: 2 ``` ### 패턴: Liveness vs Readiness 차이 ``` Liveness 실패 → pod restart. Readiness 실패 → traffic 차단 (그러나 살아있음). Use case: - DB connection 끊김 → Readiness fail (try reconnect) - Memory leak / deadlock → Liveness fail (restart) - 시작 중 (DB migration) → Startup probe ``` ### Client-side load balancing (round-robin) ```ts class ServicePool { private instances: Instance[] = []; private idx = 0; async refresh() { const services = await consul.health.service('orders'); this.instances = services.filter(s => s.Checks.every(c => c.Status === 'passing')); } next(): Instance { if (this.instances.length === 0) throw new Error('no instances'); const inst = this.instances[this.idx % this.instances.length]; this.idx++; return inst; } } // 매 30초 refresh setInterval(() => pool.refresh(), 30000); ``` ### gRPC built-in resolver ``` gRPC = DNS resolver 자동. Round-robin LB built-in. xDS protocol (Envoy) 통합. ``` ```ts // gRPC client const client = new OrderServiceClient('dns:///orders.prod.svc.cluster.local:50051', grpc.credentials.createInsecure(), { 'grpc.lb_policy_name': 'round_robin', }); ``` ### Service Mesh discovery 의 장점 ``` - 자동 mTLS - 자동 retry / CB - Traffic split - Observability built-in - Multi-cluster discovery ``` → Istio / Linkerd / Linkerd / Consul Connect. ### External services ```yaml # K8s ExternalName service apiVersion: v1 kind: Service metadata: { name: stripe } spec: type: ExternalName externalName: api.stripe.com ``` → App 가 `http://stripe` 호출. ### Dynamic config (env vs DNS) ``` Env var: 배포 시 정해짐. DNS: runtime 변경 가능. → 자주 변경 = DNS / discovery. ``` ## 🤔 의사결정 기준 | 환경 | 추천 | |---|---| | K8s | Service (DNS) | | 비-K8s + 다중 instance | Consul / Eureka | | AWS ECS | Service Discovery | | Service mesh 전체 | Istio / Linkerd | | 단일 service | DNS / env var 충분 | | 매우 dynamic | xDS / Consul | ## ❌ 안티패턴 - **IP hardcode prod**: 변경 시 깨짐. - **DNS TTL 길음 (3600s)**: stale endpoint. 10-60s. - **Health check 없음**: dead instance 트래픽. - **Liveness = Readiness 같음**: restart 무한. - **Discovery 의존 + cache 없음**: registry 다운 시 모두 dead. - **Single-zone**: AZ 다운 = 모두. - **Manual scale**: K8s HPA / AWS auto-scaling. ## 🤖 LLM 활용 힌트 - K8s = Service + DNS 자동. - Consul = 비-K8s 표준. - Liveness ≠ Readiness. - Service mesh = 큰 cluster 의 답. ## 🔗 관련 문서 - [[Backend_Health_Check_Patterns]] - [[DevOps_Service_Mesh_Deep]] - [[DevOps_Kubernetes_Basics]]