Service cho bạn một cái tên và một địa chỉ IP ổn định trong khi pod đến rồi đi. Bài này mở nó ra xem địa chỉ đó thật sự là gì.
ClusterIP không tồn tại ở đâu cả
ClusterIP: 10.96.241.187
số socket đang nghe trên IP đó: 0
số giao diện mạng mang IP đó: 0
Không tiến trình nào nghe. Không card mạng nào có địa chỉ đó.
Nó chỉ tồn tại dưới dạng luật iptables. Gói tin gửi tới 10.96.241.187:80 bị bắt ở tầng nat và đổi địa chỉ đích (DNAT) sang IP của một pod thật:
-A KUBE-SEP-4URQXOD2OJVTCIBV -p tcp -m comment --comment "default/web-svc" \
-m tcp -j DNAT --to-destination 10.244.2.90:80
Điều này giải thích vài hành vi hay gây bối rối:
- Không
pingđược ClusterIP. Không có gì trả lời ICMP; luật chỉ áp cho TCP hoặc UDP tới đúng cổng đã khai. telnettới cổng không khai trong Service thì im lặng. Không luật nào khớp, gói tin đi vào hư không.tcpdumptrên node không thấy ClusterIP ở phía pod đích — địa chỉ đã bị đổi trước khi ra khỏi node.
Cách chọn pod: xác suất giảm dần
luật 1 --probability 0.33333 -> pod 1
luật 2 --probability 0.50000 -> pod 2
luật 3 không điều kiện -> pod 3
Con số trông không đều, nhưng kết quả thì đều: 33,3%, rồi 50% của 66,7% còn lại, rồi phần còn lại — mỗi pod đúng một phần ba.
Gọi 300 lần và đếm:
100 web-...-57dwk
94 web-...-6z7m6
106 web-...-pdrlj
Đủ đều.
Nhưng có một chi tiết quan trọng: cân bằng tải xảy ra ở mức kết nối, không ở mức yêu cầu. iptables chọn pod một lần khi kết nối TCP được mở, và mọi gói tin sau đó của kết nối ấy đi tới cùng pod.
Với HTTP keep-alive hoặc gRPC — vốn giữ một kết nối rất lâu — mọi yêu cầu trên kết nối đó về cùng một pod. Client mở 3 kết nối tới Service 10 pod thì chỉ chạm 3 pod, mãi mãi.
Đây là nguyên nhân phổ biến nhất của "tải không đều giữa các pod" mà không ai giải thích được. Cách chữa nằm ở tầng ứng dụng: proxy hiểu HTTP/2 (Ingress, lưới dịch vụ), hoặc client tự mở lại kết nối định kỳ.
Vấn đề quy mô của iptables
trước: 90 dòng luật NAT
thêm 20 service (mỗi cái 2 pod): 310 dòng (+220)
-> trung bình 11 dòng luật mỗi service
Quy mô thật: 5.000 service × 11 = 55.000 dòng iptables.
Và điểm đau nằm ở chỗ khác: mỗi lần một service đổi, kube-proxy ghi lại toàn bộ bảng. Với 55.000 luật, việc đó tốn hàng trăm mili giây tới vài giây — nhân với tần suất pod đến và đi trên một cụm bận.
Tra cứu cũng tuyến tính: gói tin phải đi qua chuỗi luật cho tới khi khớp.
Đây là lý do IPVS tồn tại:
iptables luật tuyến tính, ghi lại cả bảng khi đổi
IPVS bảng băm trong kernel, cập nhật từng mục, tra cứu O(1)
IPVS còn cho nhiều thuật toán cân bằng (rr, lc, sh) thay vì chỉ ngẫu nhiên. Bật bằng --proxy-mode=ipvs trên kube-proxy, và nó cần module kernel ip_vs.
Với cụm dưới vài trăm service, iptables hoàn toàn ổn và đơn giản hơn. Trên một nghìn service trở lên, IPVS là lựa chọn mặc định.
Kubernetes 1.31 trở lên còn có chế độ nftables, thay thế iptables với cùng ưu điểm về cập nhật gia tăng.
Bốn loại Service
ClusterIP — mặc định. Chỉ truy cập được từ trong cụm.
NodePort — mở một cổng (30000–32767) trên mọi node. Gọi vào node nào cũng tới được pod, kể cả node không chạy pod nào của service đó.
LoadBalancer — xin nhà cung cấp đám mây một bộ cân bằng tải. Trong kind nó nằm mãi ở <pending> (phần 42).
ExternalName — chỉ là một bản ghi DNS CNAME, không có ClusterIP, không có luật iptables. Dùng để trỏ tên trong cụm sang dịch vụ bên ngoài.
Service không có selector
Một dạng ít biết nhưng hữu ích: tạo Service không khai selector, rồi tự tạo EndpointSlice trỏ tới IP bất kỳ — kể cả IP ngoài cụm.
Kết quả là một tên DNS trong cụm trỏ tới cơ sở dữ liệu chạy trên máy vật lý, và ứng dụng không cần biết nó nằm ngoài. Khi nào chuyển cơ sở dữ liệu vào cụm, chỉ cần thêm selector và ứng dụng không đổi gì.
Headless Service
clusterIP: None bỏ hẳn phần iptables. DNS trả về danh sách IP của mọi pod thay vì một ClusterIP ảo.
Client tự chọn, và đó chính là điều cần cho:
StatefulSet— mỗi pod có tên DNS riêng (db-0.db-svc), thứ mà ClusterIP không cho.- Client tự cân bằng tải — gRPC làm việc này tốt hơn nhiều so với để iptables chọn một lần khi mở kết nối.
Thử ba mươi giây
Xem Service của bạn thành bao nhiêu luật:
# trên một node
iptables-save -t nat | grep -c "<namespace>/<ten-service>"
# tổng luật NAT của cả node
iptables-save -t nat | wc -l
# chế độ kube-proxy đang chạy
kubectl -n kube-system get cm kube-proxy -o jsonpath='{.data.config\.conf}' | grep mode
Nếu tổng luật NAT vượt vài chục nghìn và mode rỗng (tức iptables), đó là lúc nên đo thời gian kube-proxy đồng bộ — chỉ số kubeproxy_sync_proxy_rules_duration_seconds.
Phần sau đo DNS trong cụm: web-svc biến thành 10.96.241.187 bằng cách nào, và mất bao lâu.