[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
---
|
||||
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]]
|
||||
Reference in New Issue
Block a user