"Chia thành nhiều cụm" nghe như một quyết định kiến trúc. Bài này đo xem nó tốn gì cụ thể — trên hai cụm thật chạy cạnh nhau.

Chi phí cố định mỗi cụm, sự trùng dải IP, và bảng đo độ trễ

Chi phí cố định của mỗi cụm

Một cụm ba node, không chạy ứng dụng nào đáng kể. Đọc RSS thật của từng tiến trình:

kube-apiserver            298 MB
kube-controller-manager   113 MB
kube-scheduler             69 MB
etcd                       56 MB
                          ------
                          536 MB

Nửa gigabyte RAM trước khi có ứng dụng nào. Kèm 12 pod hệ thống phải giám sát, và một bộ chứng chỉ phải xoay vòng.

Con số này không lớn với một máy chủ hiện đại. Nhưng nó là chi phí cố định nhân với số cụm, và nó tăng khi cụm bận: cụm bốn node có nhiều việc hơn của tôi dùng 1,4 GB cho chính node control plane.

Chi phí đáng kể hơn không nằm ở RAM mà ở phần con người: mỗi cụm là một phiên bản để nâng cấp, một bộ RBAC để đồng bộ, một chỗ nữa để cấu hình lệch đi.

Hai cụm mặc định dùng chung một dải IP

Đây là chỗ tôi không lường trước.

cụm sc   cluster-cidr = 10.244.0.0/16
cụm up   cluster-cidr = 10.244.0.0/16

Y hệt nhau. Đó là mặc định của kubeadm, nên mọi cụm dựng theo mặc định đều dùng chung dải này.

Hậu quả đo được ngay:

pod bên up   10.244.1.7
pod bên sc   10.244.1.77

Hai địa chỉ trong cùng một dải, thuộc hai cụm khác nhau. Không có bảng định tuyến nào phân biệt được chúng.

gọi thẳng IP pod qua cụm  ->  TimeoutError (3001 ms)
phân giải tên Service kia ->  Name does not resolve

Cả hai tầng đều tách biệt: mạng không định tuyến, DNS không biết gì về nhau. CoreDNS của mỗi cụm chỉ phục vụ vùng cluster.local của chính nó.

Điều rút ra thực dụng: nếu bạn có thể sẽ nối hai cụm với nhau, hãy cấp dải IP khác nhau ngay từ khi tạo. Đổi --pod-network-cidr sau đó nghĩa là dựng lại cụm. Đây là quyết định năm phút lúc đầu, hoặc một dự án di trú về sau.

Đường đi được: NodePort

NodePort 32190 trên node IP 172.21.0.9  ->  KẾT NỐI ĐƯỢC

Cách duy nhất chạy được ngay là đi qua địa chỉ của node, thứ nằm ngoài dải pod và có thật trên mạng chung.

Trong thực tế đó là NodePort, LoadBalancer, hoặc Ingress — và đi kèm mọi thứ bạn phải tự lo: địa chỉ cố định, TLS, xác thực, phát hiện dịch vụ. Cái mà trong một cụm là http://web thì qua cụm trở thành một điểm cuối phải quản lý.

Đo độ trễ, và một lần tôi tự đo sai

trong cụm, tên DNS      p50 0,64 ms
trong cụm, ClusterIP    p50 0,17 ms
trong cụm, IP pod       p50 0,15 ms
qua cụm,  NodePort      p50 0,17 ms

Lần đầu tôi chỉ đo hai dòng: "trong cụm bằng tên DNS" (0,64 ms) và "qua cụm bằng NodePort" (0,17 ms). Kết quả trông như gọi qua cụm nhanh hơn gọi trong cụm, và tôi suýt viết ra điều đó.

Sai. Tôi so một tên DNS với một IP trần. Thư viện HTTP phân giải tên ở mỗi kết nối, và 0,47 ms chênh lệch là chi phí của DNS chứ không phải của mạng.

Đo lại bằng ClusterIP thì hai bên bằng nhau — 0,17 ms cả hai. Hai cụm này nằm trên cùng một cầu mạng Docker nên qua cụm gần như không tốn gì; giữa hai vùng địa lý thì con số sẽ khác hẳn, và đó là điều phép đo này không nói được.

Bài học đo đạc: khi so hai đường đi, hãy giữ mọi thứ khác giống nhau. Một tên miền lẫn vào là đủ để đảo ngược kết luận.

Nhân tiện, dòng đầu và dòng hai đo lại đúng điều đã thấy ở phần về DNS: phân giải tên tốn gần gấp bốn lần phần mạng. Với dịch vụ gọi nhau hàng nghìn lần mỗi giây, dùng lại kết nối là cách tiết kiệm rẻ nhất.

Khi nào thật sự cần nhiều cụm

Bốn lý do đứng vững:

  • Ranh giới sự cố. Một cụm hỏng không kéo theo cụm kia. Đây là lý do mạnh nhất, và cũng là lý do duy nhất mà namespace không thay thế được.
  • Ranh giới địa lý. Độ trễ giữa các vùng khiến một cụm trải rộng là ý tồi; etcd đặc biệt nhạy với chuyện này.
  • Yêu cầu tuân thủ. Dữ liệu phải nằm trong một quốc gia, hoặc phải cách ly vật lý.
  • Giới hạn quy mô. Kubernetes hỗ trợ tới 5.000 node, nhưng etcd và số đối tượng thường chạm trần sớm hơn nhiều.

Và lý do không đứng vững: tách theo môi trường, tách theo đội, tách theo ứng dụng. Ba việc đó namespace + RBAC + ResourceQuota làm được, với chi phí bằng không.

Ranh giới thực dụng: thêm một cụm khi bạn cần một ranh giới sự cố; đừng thêm cụm khi bạn chỉ cần một ranh giới quyền hạn.

Nếu buộc phải nối

Nhu cầu Cách
Một kubectl cho nhiều cụm Nhiều context trong kubeconfig
Triển khai cùng thứ ra mọi cụm Argo CD ApplicationSet, Flux
Gọi dịch vụ qua cụm Ingress/LoadBalancer công khai, hoặc service mesh đa cụm
Tên DNS dùng chung Multi-cluster Services, hoặc DNS ngoài
Chuyển đổi khi hỏng Cân bằng tải toàn cục ở tầng DNS

Dòng thứ ba và thứ tư là chỗ tốn công nhất, và cũng là chỗ dễ đánh giá thấp nhất — vì trong một cụm chúng miễn phí.

Thử ba mươi giây

Kiểm xem các cụm của bạn có dải IP trùng nhau không, trước khi ai đó cần nối chúng lại:

for c in $(kubectl config get-contexts -o name); do
  echo -n "$c: "
  kubectl --context "$c" get node -o jsonpath='{.items[0].spec.podCIDR}' 2>/dev/null
  kubectl --context "$c" get svc kubernetes -n default \
    -o jsonpath='  svc={.spec.clusterIP}{"\n"}' 2>/dev/null
done

Trùng nhau là bình thường — đó là mặc định. Nhưng biết trước thì rẻ hơn nhiều so với phát hiện ra vào ngày ai đó nói "nối hai cụm này lại đi".

Phần sau: chi phí và tối ưu tài nguyên — đo chỗ tiền thật sự rò ra.