
Giới thiệu
Trong microservices, các service instances có thể scale up/down dynamically, IP thay đổi liên tục. Hardcode địa chỉ không khả thi. Service Discovery là cơ chế cho phép services tự động tìm thấy nhau.
1. Service Discovery Patterns
1.1 Client-Side Discovery
Client query Service Registry để lấy danh sách instances, tự chọn instance để gọi:
┌─────────┐ ┌──────────────────┐
│ Client │───▶│ Service Registry │
│ Service │ │ (Consul/etcd) │
│ │◀───│ │
│ │ │ Returns: │
│ │ │ - 10.0.1.5:8080 │
│ │ │ - 10.0.1.6:8080 │
│ │ │ - 10.0.1.7:8080 │
│ │ └──────────────────┘
│ │
│ Client-side │
│ Load Balancer│ ← Round Robin / Random / Least Connections
│ │
│ │───▶ 10.0.1.6:8080 (chosen instance)
└─────────┘
Ưu điểm: Không có proxy bottleneck, client tự quyết định routing Nhược điểm: Discovery logic nằm trong mỗi service (mỗi language cần library riêng)
1.2 Server-Side Discovery
Client gửi request đến Load Balancer/Router, router query registry và forward:
┌─────────┐ ┌──────────────┐ ┌──────────────────┐
│ Client │───▶│ Load Balancer│───▶│ Service Registry │
│ Service │ │ / Router │◀───│ │
│ │◀───│ │ └──────────────────┘
│ │ │ │
│ │ │ │───▶ 10.0.1.5:8080
└─────────┘ └──────────────┘
Ưu điểm: Client đơn giản, language-agnostic Nhược điểm: Load balancer là potential bottleneck và single point of failure
1.3 So sánh
| Tiêu chí | Client-Side | Server-Side |
|---|---|---|
| Client complexity | Cao (cần library) | Thấp |
| Hop count | 1 (direct) | 2 (qua LB) |
| Language support | Cần library per language | Language-agnostic |
| Load balancer | Không cần | Cần (potential SPOF) |
| Ví dụ | Netflix Eureka + Ribbon | Kubernetes Service, AWS ELB |
2. Service Registry
2.1 Consul
HashiCorp Consul cung cấp service discovery, health checking, và KV store:
┌────────────────────────────────────────────┐
│ Consul Cluster │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Server │ │ Server │ │ Server │ │
│ │ (Leader)│ │(Follower)│ │(Follower)│ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ Raft Consensus Protocol │
│ │
│ Service Catalog: │
│ ├── order-service │
│ │ ├── 10.0.1.5:8080 (passing) │
│ │ ├── 10.0.1.6:8080 (passing) │
│ │ └── 10.0.1.7:8080 (critical) │
│ ├── payment-service │
│ │ ├── 10.0.2.3:8080 (passing) │
│ │ └── 10.0.2.4:8080 (passing) │
│ └── inventory-service │
│ └── 10.0.3.1:8080 (passing) │
└────────────────────────────────────────────┘
Service Registration:
{
"service": {
"name": "order-service",
"id": "order-service-1",
"port": 8080,
"tags": ["v1", "production"],
"meta": {
"version": "1.2.0",
"protocol": "http"
},
"check": {
"http": "http://localhost:8080/health",
"interval": "10s",
"timeout": "3s",
"deregister_critical_service_after": "30s"
}
}
}
Service Discovery Query:
# DNS interface
dig @127.0.0.1 -p 8600 order-service.service.consul SRV
# HTTP API
curl http://consul:8500/v1/health/service/order-service?passing=true
# Response
[
{
"Service": {
"ID": "order-service-1",
"Address": "10.0.1.5",
"Port": 8080,
"Tags": ["v1", "production"]
},
"Checks": [{ "Status": "passing" }]
}
]
2.2 etcd
Distributed KV store, dùng bởi Kubernetes cho cluster state:
# Register service
etcdctl put /services/order-service/instances/1 \
'{"host":"10.0.1.5","port":8080,"status":"healthy"}'
# Discover service (prefix query)
etcdctl get /services/order-service/instances/ --prefix
# Watch for changes
etcdctl watch /services/order-service/instances/ --prefix
3. Kubernetes DNS-Based Discovery
3.1 CoreDNS
Kubernetes tích hợp sẵn service discovery qua CoreDNS:
┌───────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌──────────────┐ │
│ │ CoreDNS │ ← Watches Kubernetes API │
│ │ (kube-dns) │ │
│ └──────┬───────┘ │
│ │ │
│ DNS Records: │
│ ├── order-service.default.svc.cluster.local │
│ │ → ClusterIP: 10.96.45.32 │
│ ├── payment-service.default.svc.cluster.local │
│ │ → ClusterIP: 10.96.78.91 │
│ └── order-service.staging.svc.cluster.local │
│ → ClusterIP: 10.96.12.55 │
│ │
│ Format: <service>.<namespace>.svc.cluster.local│
└───────────────────────────────────────────────┘
3.2 Kubernetes Service Types
# ClusterIP (default) — internal only
apiVersion: v1
kind: Service
metadata:
name: order-service
namespace: default
spec:
type: ClusterIP
selector:
app: order-service
ports:
- port: 8080
targetPort: 8080
---
# Headless Service — returns Pod IPs directly (no load balancing)
apiVersion: v1
kind: Service
metadata:
name: order-service-headless
spec:
clusterIP: None # ← Headless
selector:
app: order-service
ports:
- port: 8080
# ClusterIP service → resolves to virtual IP
nslookup order-service.default.svc.cluster.local
# → 10.96.45.32
# Headless service → resolves to all Pod IPs
nslookup order-service-headless.default.svc.cluster.local
# → 10.0.1.5, 10.0.1.6, 10.0.1.7
# Within same namespace, short name works
curl http://order-service:8080/api/orders
# Cross-namespace
curl http://order-service.staging:8080/api/orders
3.3 EndpointSlices
# Kubernetes tự động tạo EndpointSlice cho mỗi Service
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: order-service-abc12
labels:
kubernetes.io/service-name: order-service
addressType: IPv4
endpoints:
- addresses: ["10.0.1.5"]
conditions:
ready: true
serving: true
- addresses: ["10.0.1.6"]
conditions:
ready: true
serving: true
- addresses: ["10.0.1.7"]
conditions:
ready: false # Not ready — excluded from routing
serving: false
ports:
- port: 8080
protocol: TCP
4. Health Checking
4.1 Health Check Types
┌─────────────────────────────────────────────────┐
│ Health Check Levels │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Liveness: "Is the process alive?" │ │
│ │ → Fail: Restart container │ │
│ │ → Check: process not deadlocked │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Readiness: "Can it handle requests?" │ │
│ │ → Fail: Remove from Service endpoints │ │
│ │ → Check: DB connected, cache warm │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Startup: "Has it finished starting?" │ │
│ │ → Fail: Keep waiting (don't kill early) │ │
│ │ → Check: initialization complete │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
4.2 Implementation
@RestController
public class HealthController {
@Autowired
private DataSource dataSource;
@Autowired
private RedisTemplate<String, String> redis;
// Liveness — lightweight, no dependency check
@GetMapping("/health/live")
public ResponseEntity<Map<String, String>> liveness() {
return ResponseEntity.ok(Map.of("status", "UP"));
}
// Readiness — check dependencies
@GetMapping("/health/ready")
public ResponseEntity<Map<String, Object>> readiness() {
Map<String, Object> checks = new HashMap<>();
boolean ready = true;
// Check database
try {
dataSource.getConnection().isValid(2);
checks.put("database", "UP");
} catch (Exception e) {
checks.put("database", "DOWN");
ready = false;
}
// Check Redis
try {
redis.getConnectionFactory().getConnection().ping();
checks.put("redis", "UP");
} catch (Exception e) {
checks.put("redis", "DOWN");
ready = false;
}
checks.put("status", ready ? "UP" : "DOWN");
return ready
? ResponseEntity.ok(checks)
: ResponseEntity.status(503).body(checks);
}
}
5. Load Balancing Algorithms
5.1 Các thuật toán phổ biến
Round Robin:
Request 1 → Instance A
Request 2 → Instance B
Request 3 → Instance C
Request 4 → Instance A (vòng lại)
Weighted Round Robin:
Instance A (weight: 3) → nhận 3/6 requests
Instance B (weight: 2) → nhận 2/6 requests
Instance C (weight: 1) → nhận 1/6 requests
Least Connections:
Chọn instance có ít connection nhất hiện tại
Phù hợp khi request có duration khác nhau
Random:
Chọn ngẫu nhiên — đơn giản, hiệu quả cho large pool
Consistent Hashing:
Hash request key → map đến instance cố định
Phù hợp cho sticky sessions, caching scenarios
Power of Two Choices:
Random chọn 2 instances, pick instance ít load hơn
Tốt hơn pure random, ít overhead hơn least connections
5.2 Kubernetes kube-proxy modes
iptables mode (default):
→ Random selection via iptables rules
→ No health-aware routing
IPVS mode:
→ Supports multiple algorithms
→ rr (Round Robin), lc (Least Connection),
dh (Destination Hashing), sh (Source Hashing)
→ Better performance at scale
# Enable IPVS mode
kubectl edit configmap kube-proxy -n kube-system
# mode: "ipvs"
# ipvs:
# scheduler: "lc" # Least Connections
6. Best Practices
1. Sử dụng Kubernetes DNS cho intra-cluster discovery
→ Không cần external registry
2. Headless Service cho stateful workloads
→ Client-side load balancing cho gRPC
3. Health checks luôn bao gồm cả liveness + readiness
→ Liveness: lightweight, không check dependencies
→ Readiness: check downstream dependencies
4. Graceful shutdown với preStop hook
→ Deregister trước khi shutdown
→ Drain existing connections
5. Service mesh thay thế client-side discovery
→ Sidecar proxy xử lý routing + load balancing
→ Application code không cần discovery logic
# Graceful shutdown example
spec:
terminationGracePeriodSeconds: 30
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"] # Wait for endpoint removal
Tóm tắt
- Client-Side Discovery: Client query registry, tự load balance
- Server-Side Discovery: Router/LB query registry, forward request
- Kubernetes DNS: Built-in discovery, đơn giản nhất cho K8s workloads
- Health checks: Liveness (alive?), Readiness (ready?), Startup (started?)
- Trong Kubernetes, Kubernetes Service + CoreDNS thường đủ, không cần Consul/etcd riêng
- Service Mesh (Istio/Linkerd) nâng cấp discovery với intelligent routing