Skip to content

DNS Resolution trong Pod — /etc/resolv.conf, ndots:5, và Lookup Storm

Tại Sao Đây Là Foundation Của Mọi Vấn Đề DNS

Gần như mọi DNS issue trong Kubernetes đều bắt nguồn từ một cơ chế đơn giản: cách kubelet cấu hình /etc/resolv.conf bên trong mỗi Pod. Khi một microservice gọi http://payment-service/checkout, nó không hề biết rằng OS sẽ thực hiện tới 5 DNS queries riêng biệt trước khi có được IP. Mỗi query có thể mất vài milliseconds, và ở scale, chúng có thể cạn kiệt CoreDNS connection tracking table.

Phần lớn engineer debug DNS bằng cách nhìn vào CoreDNS logs hoặc Wireshark — nhưng nếu không hiểu tại sao /etc/resolv.conf được cấu hình như vậy và ndots:5 hoạt động thế nào, việc debug chỉ là đi theo triệu chứng mà không hiểu nguyên nhân.

Internal Model — kubelet và /etc/resolv.conf

Kubelet Là Người Viết /etc/resolv.conf

Khi kubelet tạo một Pod, một trong những việc đầu tiên nó làm là cấu hình DNS cho namespace mạng của Pod đó. Không phải CoreDNS, không phải container runtime, mà là kubelet. Kubelet đọc dnsPolicy từ Pod spec và tạo nội dung /etc/resolv.conf tương ứng.

Với dnsPolicy: ClusterFirst (mặc định), file /etc/resolv.conf trong một Pod ở namespace payment sẽ trông như sau:

nameserver 10.96.0.10
search payment.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

Ba dòng này là toàn bộ cơ chế DNS của Pod. Hiểu ba dòng này là hiểu 90% vấn đề DNS trong Kubernetes.

Dòng nameserver — Điểm Đến Của Mọi Query

10.96.0.10 là ClusterIP của Service kube-dns trong namespace kube-system. Đây là virtual IP được kube-proxy (hoặc eBPF trong Dataplane V2) chuyển tiếp đến CoreDNS pods thực.

Trong GKE với NodeLocal DNSCache được bật, nameserver sẽ là 169.254.20.10 — link-local IP của local cache agent thay vì ClusterIP của kube-dns. Chi tiết về NodeLocal DNSCache ở Chương 5 của chapter này.

Với GKE + Cloud DNS (không có NodeLocal DNSCache), Pod queries đến 169.254.169.254:53 — địa chỉ của metadata server, vốn được GKE tích hợp Cloud DNS vào đó.

Dòng search — Search Domain Expansion

search payment.svc.cluster.local svc.cluster.local cluster.local

Đây là danh sách các suffix được OS tự động thêm vào hostname khi DNS lookup. Thứ tự quan trọng: OS thử từ trái sang phải.

Với Pod ở namespace payment, search domains là:

  1. payment.svc.cluster.local — namespace của Pod hiện tại
  2. svc.cluster.local — fallback nếu không có namespace
  3. cluster.local — cluster domain

Kubelet tạo search list này dựa trên namespace của Pod. Pod ở namespace default sẽ có default.svc.cluster.local ở đầu tiên.

Theo tài liệu Kubernetes: kubelet truyền DNS search list vào container qua /etc/resolv.conf. Với dnsPolicy: ClusterFirst, search list bao gồm namespace của Pod, svc.cluster.local, và cluster domain.

Cơ Chế ndots:5 — Nguồn Gốc Của Lookup Storm

ndots Là Gì

ndots:N là option của resolver libc (glibc, musl) kiểm soát khi nào một tên được coi là absolute FQDN và khi nào nó được mở rộng với search domains.

Quy tắc: nếu tên có ít hơn N dấu chấm, resolver sẽ thử từng search domain trước khi thử tên đó như FQDN. Nếu tên có N dấu chấm hoặc nhiều hơn, resolver thử nó như FQDN trước.

Với ndots:5:

  • payment-service (0 dấu chấm) → thử search domains trước
  • payment-service.payment (1 dấu chấm) → thử search domains trước
  • api.example.com (2 dấu chấm) → thử search domains trước
  • my.service.ns.svc.cluster (4 dấu chấm) → thử search domains trước
  • my.service.ns.svc.cluster.local (5 dấu chấm) → thử như FQDN trực tiếp

Tại Sao Kubernetes Chọn ndots:5

Kubernetes DNS spec yêu cầu Service FQDN có dạng service.namespace.svc.cluster.local — đây là 4 dấu chấm. Nếu ndots:2 hoặc ndots:4, một query đến service.namespace.svc.cluster.local sẽ bị resolver thử như FQDN ngay lập tức.

Nhưng vấn đề: một developer muốn gọi payment-service (ngắn gọn, trong cùng namespace) — họ mong đợi Kubernetes tự động resolve thành payment-service.payment.svc.cluster.local. Để điều này hoạt động, ndots cần đủ cao để tên ngắn không bị coi là FQDN và được mở rộng với search domains.

Con số 5 được chọn vì nó đủ cao để bao phủ mọi short name (0-4 dấu chấm) nhưng không ảnh hưởng đến FQDN thực (service.namespace.svc.cluster.local có 5 chấm — vừa đúng ngưỡng để được xử lý như FQDN khi có đủ 5 dấu chấm).

Lookup Path Thực Tế — Đếm Từng Query

Giả sử Pod trong namespace payment muốn kết nối đến api.external-company.com:

Bước 1: api.external-company.com có 2 dấu chấm < 5 → resolver thử search domains trước.

Query 1: api.external-company.com.payment.svc.cluster.local → NXDOMAIN (không tồn tại)

Query 2: api.external-company.com.svc.cluster.local → NXDOMAIN

Query 3: api.external-company.com.cluster.local → NXDOMAIN

Query 4: api.external-company.com. (FQDN với trailing dot) → thành công, trả về 1.2.3.4

Kết quả: 4 DNS queries để resolve một external hostname đơn giản.

Nếu muốn gọi payment-service (cùng namespace):

Query 1: payment-service.payment.svc.cluster.local → trả về ClusterIP

Kết quả: 1 query, thành công ngay.

Negative Caching Amplification

Mỗi NXDOMAIN response trong ví dụ trên đều được DNS cache lưu lại. Với ndots:5 và external hostname có ít dấu chấm, mỗi Pod đều phải thực hiện 3 NXDOMAIN queries trước khi thành công. Ở scale 1000 Pods cùng resolve api.external-company.com:

  • 3000 NXDOMAIN queries gửi đến CoreDNS mỗi khi cache hết hạn
  • Mỗi NXDOMAIN được negative-cache cho TTL ngắn (thường 0-30 giây)
  • Sau TTL, cycle lặp lại

Đây là ndots:5 lookup storm — một anti-pattern phổ biến trong cluster với nhiều external service calls.

Tại Sao FQDN Không Phải Magic Bullet

Developer thường nghe lời khuyên "thêm trailing dot vào hostname để bypass search domains". Điều này đúng về mặt kỹ thuật: api.external-company.com. (có trailing dot) là FQDN tuyệt đối, resolver không mở rộng search domains.

Nhưng trailing dot:

  • Không được hỗ trợ trong mọi application framework
  • Có thể gây lỗi với một số HTTP clients (Host header không hợp lệ)
  • Không giải quyết vấn đề cơ bản: mọi external call vẫn cần DNS lookup, chỉ là ít hơn

Giải pháp thực sự là override ndots ở Pod level cho workloads có nhiều external calls:

yaml
spec:
  dnsConfig:
    options:
      - name: ndots
        value: "2"  # Hoặc "1" tùy pattern naming

Với ndots:2, query đến api.external-company.com (2 dấu chấm) được xử lý như FQDN ngay lập tức — không có search domain expansion.

dnsPolicy Options — Khi Nào Cần Override

Kubelet hỗ trợ bốn dnsPolicy:

ClusterFirst (mặc định)

Query đến nameserver cluster (CoreDNS). Query không match cluster domain được forward đến upstream. Search domains cluster được áp dụng. Đây là behavior mọi người mong đợi cho service discovery.

Default

Pod kế thừa cấu hình DNS của node thay vì cluster DNS. /etc/resolv.conf của Pod giống với /etc/resolv.conf của node worker. Không có search domains Kubernetes. Không có cluster service discovery.

Trường hợp dùng: Pod cần resolve DNS theo cách mà node resolve (ví dụ: tool monitoring cần lookup node-local DNS).

ClusterFirstWithHostNet

Bắt buộc khi Pod dùng hostNetwork: true. Nếu không chỉ định, Pod dùng hostNetwork: true sẽ fallback về Default policy — mất cluster DNS.

None

Bỏ qua toàn bộ cấu hình DNS mặc định. Phải kết hợp với dnsConfig để chỉ định nameserver và search domains thủ công:

yaml
spec:
  dnsPolicy: None
  dnsConfig:
    nameservers:
      - 8.8.8.8
    searches:
      - company.internal
    options:
      - name: ndots
        value: "2"

Constraints và Failure Modes

Search Domain Limit

Linux resolver giới hạn tối đa 6 search domains và tổng 256 ký tự cho search list. Kubernetes mặc định dùng 3 search domains, còn lại 3 slot cho custom domains qua dnsConfig.searches.

Nếu vượt giới hạn, kubelet sẽ truncate — không có warning rõ ràng. Custom search domains bị mất lặng lẽ.

FQDN Length Limit

Theo spec.setHostnameAsFQDN: true, hostname của Pod là FQDN đầy đủ. Linux kernel giới hạn hostname ở 64 ký tự. Nếu FQDN (bao gồm subdomain và cluster domain) vượt 64 ký tự, Pod sẽ fail ở startup với lỗi không rõ ràng.

Stale resolv.conf Sau Node Reload

Khi node reboot hoặc kubelet restart, /etc/resolv.conf trong container không được cập nhật tự động — nó là file được bind-mount vào container lúc start. Nếu ClusterIP của kube-dns thay đổi (hiếm nhưng xảy ra khi recreate cluster), Pods cần restart để nhận cấu hình mới.

ndots và NXDOMAIN Amplification ở Scale

Worst case: cluster có 500 Pods, mỗi Pod gọi 10 external hostnames với 2 chấm mỗi hostname. Với cache TTL 30 giây và 3 NXDOMAIN queries per hostname:

  • Queries/giây = 500 × 10 × 3 / 30 = 5000 NXDOMAIN queries/giây đến CoreDNS
  • CoreDNS mặc định chỉ handle ~10,000-20,000 queries/giây
  • Phần lớn capacity bị dùng cho NXDOMAIN, không phải actual resolution

Minh Họa Cơ Chế: Debug Lookup Path

Để quan sát lookup path thực tế từ trong Pod:

bash
# Xem resolv.conf
kubectl exec -it <pod> -- cat /etc/resolv.conf

# Trace toàn bộ DNS queries (bao gồm NXDOMAIN)
kubectl exec -it <pod> -- nslookup -debug api.external-company.com

# Dùng dig để thấy rõ từng query
kubectl exec -it <pod> -- dig api.external-company.com

# So sánh: FQDN với trailing dot (bypass search domains)
kubectl exec -it <pod> -- dig api.external-company.com.

# Đo số queries thực tế bằng strace
kubectl exec -it <pod> -- strace -e trace=network curl http://api.external-company.com

Output của nslookup -debug sẽ hiển thị từng query được thực hiện, bao gồm cả các NXDOMAIN failed attempts. Đây là cách nhanh nhất để xác nhận lookup path.

References