Kubernetes DNS Spec — Service Discovery và DNS Record Types
Tại Sao Cần Hiểu DNS Spec Rõ Ràng
Developer thường nghĩ "gọi service bằng tên là xong". Nhưng khi gặp sự cố — query cross-namespace không work, StatefulSet pod không addressable bằng tên cố định, SRV record trả về sai — thì hiểu chính xác Kubernetes tạo ra những DNS records nào, với format gì, và trong điều kiện nào, là điều bắt buộc để debug.
Kubernetes có một spec rõ ràng cho DNS records. CoreDNS (và Cloud DNS for GKE) implement spec này. Hiểu spec trước, sau đó mới hiểu implementation.
Internal Model — Service DNS Records
A/AAAA Records Cho Regular Services
Với mọi Service có type khác ExternalName và clusterIP khác None, Kubernetes (thông qua CoreDNS kubernetes plugin) tạo một A record (IPv4) hoặc AAAA record (IPv6):
Format: <service-name>.<namespace>.svc.<cluster-domain>
Ví dụ: Service payment-api ở namespace backend:
payment-api.backend.svc.cluster.local → 10.96.45.200 (ClusterIP)ClusterIP là virtual IP — không có host nào thực sự gắn với IP này. kube-proxy (hoặc eBPF trong Dataplane V2) intercept traffic đến ClusterIP và DNAT đến Pod endpoints thực. Giá trị TTL mặc định: 30 giây.
Điều quan trọng: Record này luôn trỏ về ClusterIP, không phải Pod IP. Dù Pods đằng sau Service thay đổi, A record vẫn không đổi. Client không cần biết Pod IPs.
SRV Records — Service và Port Discovery
Kubernetes tạo SRV records để client có thể discover cả hostname lẫn port của một Service:
Format: _<port-name>._<protocol>.<service-name>.<namespace>.svc.<cluster-domain>
Ví dụ: Service payment-api có named port http (TCP, port 8080):
_http._tcp.payment-api.backend.svc.cluster.local → 8080 payment-api.backend.svc.cluster.localSRV record chứa hai thông tin: port number (8080) và hostname để lookup A record tiếp. Client dùng gRPC hoặc các protocol hỗ trợ SRV-based discovery có thể dùng SRV để tự động phát hiện port mà không cần hardcode.
Điều kiện bắt buộc: Port phải có tên trong Service spec:
spec:
ports:
- name: http # Tên này tạo SRV record
port: 8080
protocol: TCP
- port: 9090 # Không có tên → không có SRV record
protocol: TCPPTR Records — Reverse Lookup
Kubernetes tạo PTR records cho reverse DNS lookup (IP → hostname):
- Với Service IP:
<reversed-ip>.in-addr.arpa → <service>.<namespace>.svc.cluster.local - Với Pod IP:
<reversed-ip>.in-addr.arpa → <pod-ip-dashes>.<namespace>.pod.cluster.local
PTR records quan trọng cho logging và monitoring tools cần resolve IP thành readable hostname.
Pod DNS Records
A Record Cơ Bản
Mọi Pod có một A record dựa trên IP của nó:
Format: <pod-ip-with-dashes>.<namespace>.pod.cluster.local
Ví dụ: Pod có IP 10.0.1.15 ở namespace backend:
10-0-1-15.backend.pod.cluster.local → 10.0.1.15Record này được tạo bởi pods insecure directive trong CoreDNS Corefile. Nó ít được dùng trực tiếp nhưng hữu ích cho debugging (resolve IP về namespace).
hostname và subdomain — Addressable Pods
Kubernetes cho phép đặt hostname cố định cho Pod, kết hợp với headless Service để tạo DNS record stable:
apiVersion: v1
kind: Pod
spec:
hostname: db-primary
subdomain: postgres-headless # Phải khớp với tên headless Service
containers:
- name: postgres
image: postgres:15Kết hợp với headless Service postgres-headless trong cùng namespace data:
db-primary.postgres-headless.data.svc.cluster.local → Pod IPĐiều kiện tạo record này:
- Pod có
spec.hostnameđược set - Pod có
spec.subdomainđược set - Tồn tại headless Service (
clusterIP: None) trong cùng namespace với tên khớpsubdomain - Pod ở trạng thái Ready (hoặc
publishNotReadyAddresses: truetrên Service)
Nếu thiếu một trong bốn điều kiện, record không được tạo. Đây là nguồn gốc của nhiều bug "service không reachable".
setHostnameAsFQDN
Khi spec.setHostnameAsFQDN: true, kubelet cấu hình Pod sao cho lệnh hostname trả về FQDN đầy đủ thay vì chỉ phần hostname:
# Không có setHostnameAsFQDN
hostname # → db-primary
# Với setHostnameAsFQDN: true
hostname # → db-primary.postgres-headless.data.svc.cluster.localHữu ích cho applications cần self-identify bằng FQDN (ví dụ: database cluster members cần biết FQDN của mình để register với cluster coordinator).
Giới hạn: Linux kernel giới hạn hostname ở 64 ký tự. Nếu FQDN dài hơn 64 ký tự, Pod sẽ fail để start với error không rõ ràng.
Cross-Namespace Discovery — Cơ Chế Và Giới Hạn
Short Name vs FQDN
# Cùng namespace: có thể dùng short name
nameserver: payment-api
# Cross-namespace: phải dùng đầy đủ namespace
nameserver: payment-api.backend
# Hoặc FQDN đầy đủ (luôn an toàn)
nameserver: payment-api.backend.svc.cluster.localKhi Pod trong namespace frontend query payment-api, search domain expansion sẽ thử:
payment-api.frontend.svc.cluster.local→ NXDOMAIN (không có Service đó ở frontend namespace)payment-api.svc.cluster.local→ NXDOMAINpayment-api.cluster.local→ NXDOMAINpayment-api.(FQDN) → NXDOMAIN (không có)
Vì không có Service payment-api ở namespace frontend, query sẽ fail. Phải dùng payment-api.backend để CoreDNS biết đúng namespace:
payment-api.backend.frontend.svc.cluster.local→ NXDOMAINpayment-api.backend.svc.cluster.local→ thành công (khớp với search domainsvc.cluster.local)
StatefulSet DNS — Kết Hợp Headless Service
StatefulSet là use case phổ biến nhất của headless Services và per-Pod DNS. Kubernetes tự động gán hostname stable cho mỗi Pod trong StatefulSet:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: kafka
spec:
serviceName: kafka-headless # Tham chiếu đến headless Service
replicas: 3
selector:
matchLabels:
app: kafka
template:
spec:
containers:
- name: kafka
image: kafka:3.5apiVersion: v1
kind: Service
metadata:
name: kafka-headless
spec:
clusterIP: None
selector:
app: kafka
ports:
- port: 9092
name: brokerKết quả: mỗi Pod được assign hostname tự động là <statefulset-name>-<ordinal>:
kafka-0.kafka-headless.default.svc.cluster.local → IP của kafka-0
kafka-1.kafka-headless.default.svc.cluster.local → IP của kafka-1
kafka-2.kafka-headless.default.svc.cluster.local → IP của kafka-2Tại sao điều này quan trọng với stateful systems: Khi kafka-1 restart, nó sẽ có IP mới nhưng hostname vẫn là kafka-1.kafka-headless.default.svc.cluster.local. DNS record được cập nhật khi Pod ready. Các Kafka brokers khác có thể tiếp tục connect đến kafka-1 bằng DNS name mà không cần biết IP mới.
SRV Records Trong StatefulSet
Với cấu hình trên và named port broker, SRV records được tạo:
_broker._tcp.kafka-headless.default.svc.cluster.local → 9092 kafka-0.kafka-headless.default.svc.cluster.local
_broker._tcp.kafka-headless.default.svc.cluster.local → 9092 kafka-1.kafka-headless.default.svc.cluster.local
_broker._tcp.kafka-headless.default.svc.cluster.local → 9092 kafka-2.kafka-headless.default.svc.cluster.localKafka client có thể dùng SRV discovery để tìm tất cả brokers mà không cần hardcode danh sách.
Điều Kiện Tạo DNS Records — Sẵn Sàng Và Không Sẵn Sàng
ReadyAddresses vs NotReadyAddresses
Mặc định, CoreDNS chỉ tạo A records cho Pods ở trạng thái Ready. Pod chưa pass readiness probe sẽ không có DNS record (nếu là Pod được address trực tiếp qua headless Service).
Với regular Services, ClusterIP luôn có A record ngay khi Service được tạo. Nhưng traffic chỉ được forward đến Pods Ready — kube-proxy không DNAT đến Pods không Ready.
publishNotReadyAddresses
spec:
clusterIP: None
publishNotReadyAddresses: true # Tạo DNS records ngay cả khi Pod chưa ReadyDùng cho trường hợp đặc biệt: ví dụ Zookeeper ensemble cần reach nhau trước khi cả ensemble Ready để thực hiện leader election. Nếu chờ Ready mới có DNS, ensemble không bao giờ khởi động được.
Constraints và Failure Modes
DNS Record Propagation Delay
Khi Pod mới được tạo và trở thành Ready, CoreDNS cần nhận watch event từ API server và cập nhật internal cache trước khi DNS record available. Trong thực tế, delay này thường < 1 giây nhưng có thể lên đến vài giây trong cluster under load.
Implication: Application không nên assume DNS record available ngay lập tức sau khi Pod được scheduled. Retry với exponential backoff là pattern đúng khi khởi động.
Service Name Và Port Name Constraints
Theo Kubernetes DNS spec và GKE:
- Service name: lowercase, alphanumeric, dashes; không bắt đầu/kết thúc bằng dash
- Port name: cùng constraint
- GKE giới hạn: Service và port name tối đa 62 ký tự khi dùng Cloud DNS for GKE
Service name dài quá 62 ký tự sẽ không có SRV records hoặc có thể fail với Cloud DNS.
CNAME Chaining và ExternalName
Service type: ExternalName tạo CNAME record. Kubernetes không validate rằng CNAME target tồn tại — nếu target không resolvable, client nhận được CNAME response nhưng subsequent lookup fail.
CNAME chaining (CNAME trỏ đến CNAME khác) được support nhưng CoreDNS không follow chains — nó trả về CNAME record trực tiếp, client phải tự follow chain.
Minh Họa: Verify DNS Records
# Verify A record cho Service
kubectl run dns-test --image=busybox:1.36 --rm -it --restart=Never -- \
nslookup payment-api.backend.svc.cluster.local
# Verify SRV record
kubectl run dns-test --image=busybox:1.36 --rm -it --restart=Never -- \
nslookup -type=SRV _http._tcp.payment-api.backend.svc.cluster.local
# Verify Pod DNS record (headless StatefulSet)
kubectl run dns-test --image=busybox:1.36 --rm -it --restart=Never -- \
nslookup kafka-0.kafka-headless.default.svc.cluster.local
# Verify tất cả A records của headless Service (trả về multiple Pod IPs)
kubectl run dns-test --image=busybox:1.36 --rm -it --restart=Never -- \
nslookup kafka-headless.default.svc.cluster.local
# PTR record lookup (reverse DNS)
kubectl run dns-test --image=busybox:1.36 --rm -it --restart=Never -- \
nslookup 10.0.1.15