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:5Ba 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à:
payment.svc.cluster.local— namespace của Pod hiện tạisvc.cluster.local— fallback nếu không có namespacecluster.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ớidnsPolicy: 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ướcpayment-service.payment(1 dấu chấm) → thử search domains trướcapi.example.com(2 dấu chấm) → thử search domains trướcmy.service.ns.svc.cluster(4 dấu chấm) → thử search domains trướcmy.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:
spec:
dnsConfig:
options:
- name: ndots
value: "2" # Hoặc "1" tùy pattern namingVớ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:
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:
# 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.comOutput 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.