Phần trước đo được ClusterIP chỉ là luật iptables. Bài này đo bước trước đó: cái tên web-svc biến thành địa chỉ IP bằng cách nào, và vì sao nó tốn hơn bạn nghĩ.
resolv.conf trong mọi pod
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
nameserver trỏ tới CoreDNS. search là ba hậu tố sẽ được thử. Và ndots:5 là dòng gây tốn kém.
ndots:5 nghĩa là: tên có ít hơn 5 dấu chấm thì thử lần lượt cả ba hậu tố trước, rồi mới thử chính cái tên đó.
Với web-svc, đó là điều bạn muốn — web-svc.default.svc.cluster.local khớp ngay ở lần thử đầu.
Với api.doitac.com — hai dấu chấm, ít hơn 5 — bộ phân giải thử:
api.doitac.com.default.svc.cluster.local NXDOMAIN
api.doitac.com.svc.cluster.local NXDOMAIN
api.doitac.com.cluster.local NXDOMAIN
api.doitac.com tìm thấy
Ba lần hỏng trước một lần đúng.
Đếm số truy vấn thật
Đọc coredns_dns_requests_total trước và sau 20 lần phân giải:
| Truy vấn | Mỗi lần phân giải | |
|---|---|---|
google.com |
74 | 3,7 |
google.com. |
26 | 1,3 |
Khác biệt duy nhất: một dấu chấm ở cuối.
Dấu chấm cuối nói với bộ phân giải rằng đây là tên tuyệt đối — đừng thử hậu tố nào cả. Kết quả là 2,8 lần ít truy vấn hơn, cho mọi tên miền ngoài cụm.
Con số 1,3 thay vì 1,0 là do bộ phân giải hỏi cả A lẫn AAAA.
Độ trễ tương ứng
50 lần phân giải mỗi tên:
| Tên | Độ trễ |
|---|---|
web-svc |
0,49 ms |
web-svc.default |
0,22 ms |
web-svc.default.svc.cluster.local |
0,36 ms |
web-svc.default.svc.cluster.local. |
0,11 ms |
google.com |
0,44 ms |
google.com. |
0,10 ms |
Tên ngoài cụm với dấu chấm cuối nhanh gấp 4,4 lần.
Tên nội bộ ngắn vẫn khá rẻ, vì hậu tố đầu tiên đã khớp — chỉ một truy vấn hỏng trước đó là không có. Tên ngoài cụm mới là chỗ trả giá, vì nó phải trượt cả ba hậu tố.
Nửa mili giây nghe nhỏ. Nhân với mỗi lời gọi HTTP tới dịch vụ bên ngoài, trong một hệ thống gọi hàng nghìn lần mỗi giây, và nó thành tải thật lên CoreDNS — vốn chỉ có hai bản mặc định.
Ba cách chữa
Thêm dấu chấm cuối cho mọi tên ngoài cụm trong cấu hình:
API_URL=https://api.doitac.com./v1
Rẻ nhất, và không cần đụng vào cụm. Nhược điểm: trông lạ, và một số thư viện xử lý không đúng.
Hạ ndots cho pod gọi nhiều ra ngoài:
dnsConfig:
options:
- {name: ndots, value: "2"}
Khi ấy api.doitac.com (2 dấu chấm) được thử nguyên dạng trước. Đổi lại, web-svc (0 dấu chấm) vẫn dùng search list bình thường, nên dịch vụ nội bộ không ảnh hưởng.
NodeLocal DNSCache cho cụm lớn — một bộ đệm DNS chạy trên mỗi node, nên truy vấn không phải đi qua mạng tới CoreDNS.
Bốn dạng tên trong cụm
web-svc cùng namespace
web-svc.default khác namespace
web-svc.default.svc.cluster.local đầy đủ
db-0.db-svc.default.svc.cluster.local một pod cụ thể (headless)
Dạng cuối chỉ có với Headless Service (phần 56). Đó là cách StatefulSet cho mỗi bản một tên DNS ổn định.
Ngoài ra còn bản ghi SRV cho cổng đã đặt tên:
_http._tcp.web-svc.default.svc.cluster.local
CoreDNS chỉ có hai bản
kubectl -n kube-system get deployment coredns
Mặc định là 2 bản cho cả cụm, bất kể cụm có 3 node hay 300. Với cụm lớn và ứng dụng gọi DNS nhiều, đó là nút thắt thật.
Ba dấu hiệu CoreDNS đang quá tải:
coredns_dns_request_duration_secondsp99 tăng- lỗi
i/o timeoutkhi phân giải trong log ứng dụng - pod CoreDNS bị
OOMKilled(phần 54)
Cách xử lý theo thứ tự: sửa ndots ở phía ứng dụng trước, rồi mới tăng số bản CoreDNS, rồi mới tới NodeLocal DNSCache.
Cái bẫy khi đo
dig web-svc trả về NXDOMAIN, còn ứng dụng gọi web-svc thì chạy tốt.
Không mâu thuẫn: dig không dùng danh sách search — nó hỏi đúng cái tên bạn gõ. Bộ phân giải của hệ điều hành (getaddrinfo) thì có dùng.
Nên khi gỡ lỗi DNS trong Kubernetes, dig cho bạn câu trả lời của máy chủ DNS, không phải câu trả lời ứng dụng sẽ nhận được. Muốn cái sau thì dùng getent hosts hoặc nslookup, hoặc một đoạn mã gọi thẳng getaddrinfo.
Tôi vấp đúng chuyện này khi dựng phép đo cho bài: dig cho NXDOMAIN trong khi mọi thứ vẫn chạy, và mất một lúc mới nhận ra công cụ đang trả lời một câu hỏi khác.
Thử ba mươi giây
Đo cái giá của ndots trên cụm của bạn:
kubectl run t --image=python:3.12-alpine --restart=Never --rm -it -- python3 -c "
import socket, time
for n in ['api.github.com','api.github.com.']:
t=time.time()
for _ in range(50): socket.getaddrinfo(n,443)
print(f'{n:20} {(time.time()-t)/50*1000:6.2f} ms')
"
Nếu dòng có dấu chấm nhanh hơn nhiều lần, mọi lời gọi ra ngoài của bạn đang trả đúng cái giá đó.
Phần sau đo Ingress: cùng một yêu cầu, đi thẳng vào Service hay qua bộ điều khiển Ingress, chênh nhau bao nhiêu.