Skip to content

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 ExternalNameclusterIP 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.local

SRV 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:

yaml
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: TCP

PTR 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.15

Record 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:

yaml
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:15

Kế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:

  1. Pod có spec.hostname được set
  2. Pod có spec.subdomain được set
  3. Tồn tại headless Service (clusterIP: None) trong cùng namespace với tên khớp subdomain
  4. Pod ở trạng thái Ready (hoặc publishNotReadyAddresses: true trê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:

bash
# Không có setHostnameAsFQDN
hostname  # → db-primary

# Với setHostnameAsFQDN: true
hostname  # → db-primary.postgres-headless.data.svc.cluster.local

Hữ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.local

Khi Pod trong namespace frontend query payment-api, search domain expansion sẽ thử:

  1. payment-api.frontend.svc.cluster.local → NXDOMAIN (không có Service đó ở frontend namespace)
  2. payment-api.svc.cluster.local → NXDOMAIN
  3. payment-api.cluster.local → NXDOMAIN
  4. payment-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:

  1. payment-api.backend.frontend.svc.cluster.local → NXDOMAIN
  2. payment-api.backend.svc.cluster.localthành công (khớp với search domain svc.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:

yaml
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.5
yaml
apiVersion: v1
kind: Service
metadata:
  name: kafka-headless
spec:
  clusterIP: None
  selector:
    app: kafka
  ports:
  - port: 9092
    name: broker

Kế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-2

Tạ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.local

Kafka 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

yaml
spec:
  clusterIP: None
  publishNotReadyAddresses: true  # Tạo DNS records ngay cả khi Pod chưa Ready

Dù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

bash
# 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

References