Tóm tắt. Bài báo cáo việc dựng một cụm Kubernetes 1.33 gồm bốn máy (ba control plane HA, cả bốn node chạy workload) bằng kubeadm, kèm Calico, MetalLB, Traefik và Longhorn, sau đó chạy một website tin tức WordPress và đưa ra Internet qua Cloudflare Tunnel. Bốn thí nghiệm được đo từ phía người dùng: mất đột ngột một node, tăng tải, cập nhật phiên bản, và bảo trì node. Kết quả: lớp ứng dụng không trạng thái đạt mục tiêu (cập nhật không gián đoạn, co giãn 3→10 pod trong 53 giây, không có request lỗi dưới tải). Riêng cơ sở dữ liệu chạy một bản là điểm lỗi đơn: gián đoạn 206 giây khi mất node và 41 giây khi bảo trì có kế hoạch.
1. Mô hình
| Lớp | Thành phần | Lựa chọn thiết kế |
|---|---|---|
| Hạ tầng | 4 VM Ubuntu 24.04, 64 vCPU, 16 GB RAM | Cùng một LAN; node1–3 là control plane, cả 4 node đều chạy pod |
| Runtime / K8s | containerd 2.2, kubeadm 1.33 | cgroup v2, SystemdCgroup=true, không có swap |
| API HA | kube-vip (static pod, ARP) | VIP 192.168.10.10:6443 làm controlPlaneEndpoint; etcd 3 thành viên |
| Mạng pod | Calico 3.32, VXLANCrossSubnet | Pod CIDR 10.244.0.0/16 |
| LoadBalancer | MetalLB L2 | Dải IP LAN 192.168.10.20–29 |
| Ingress | Traefik 3.7 (2 bản, khác node) | ingress-nginx đã ngừng phát triển từ 3/2026 |
| Lưu trữ | Longhorn 1.12 | 3 bản sao mỗi volume; RWO cho DB, RWX cho wp-content |
| Ứng dụng | WordPress 6.8 (PHP 8.3), MariaDB 11.4 | HPA 3–10 pod, PDB minAvailable: 2; DB là StatefulSet 1 bản |
| Công khai | cloudflared (2 bản, khác node) | Kết nối ra ngoài, không mở cổng trên router |
2. Triển khai: các điểm dễ sai
Phần lớn quá trình theo tài liệu chính thức. Dưới đây chỉ ghi lại những chỗ mặc định sẽ gây lỗi hoặc làm giảm độ sẵn sàng.
2.1. kube-vip trên node đầu tiên. Từ Kubernetes 1.29, admin.conf chưa có quyền trong lúc kubeadm init chạy. Vì vậy manifest kube-vip trên node đầu phải trỏ tạm vào super-admin.conf, và trả lại admin.conf sau khi init xong. Các control plane join sau dùng thẳng admin.conf. Manifest phải được đặt sau khi join, vì bước kiểm tra trước khi join yêu cầu thư mục manifests/ trống.
sed 's#/etc/kubernetes/admin.conf#/etc/kubernetes/super-admin.conf#' kube-vip.yaml \
> /etc/kubernetes/manifests/kube-vip.yaml
kubeadm init --config kubeadm.yaml --upload-certs # controlPlaneEndpoint: 192.168.10.10:6443
cp kube-vip.yaml /etc/kubernetes/manifests/kube-vip.yaml
2.2. Pod CIDR của Calico. Giá trị mặc định 192.168.0.0/16 trùng với hầu hết các LAN gia đình và văn phòng. Cần khai báo pod CIDR riêng và chỉ định card mạng, để Calico không chọn nhầm các card VPN như tailscale0 hay wg0. Từ bản 3.30, CRD của operator nằm trong file riêng operator-crds.yaml và phải cài trước.
spec:
calicoNetwork:
nodeAddressAutodetectionV4: {interface: ens33}
ipPools:
- {cidr: 10.244.0.0/16, encapsulation: VXLANCrossSubnet, natOutgoing: Enabled, blockSize: 26}
2.3. metrics-server không cần --kubelet-insecure-tls. Cách làm đúng là bật serverTLSBootstrap: true cho kubelet (trong ConfigMap kubelet-config và /var/lib/kubelet/config.yaml), rồi duyệt các CSR kubernetes.io/kubelet-serving sau khi đã kiểm tra requestor đúng là system:node:<tên>. Khi đó metrics-server xác thực kubelet bằng CA của cụm. Cần lưu ý: CSR gia hạn hằng năm cũng phải được duyệt; có thể dùng kubelet-csr-approver để tự duyệt theo quy tắc.
2.4. Longhorn. Cần tắt multipathd, vì nó chiếm các thiết bị khối mà Longhorn tạo ra. Volume RWX cần gói nfs-common trên mọi node. Và mặc định, pod StatefulSet trên node chết không bao giờ được tạo lại; phải bật chính sách sau:
kubectl -n longhorn-system patch settings.longhorn.io node-down-pod-deletion-policy \
--type=merge -p '{"value":"delete-both-statefulset-and-deployment-pod"}'
2.5. Rút ngắn thời gian dời pod. Mặc định pod chỉ bị dời khỏi node unreachable sau 300 giây. Với các workload cần phục hồi nhanh, đặt toleration riêng:
tolerations:
- {key: node.kubernetes.io/unreachable, operator: Exists, effect: NoExecute, tolerationSeconds: 30}
- {key: node.kubernetes.io/not-ready, operator: Exists, effect: NoExecute, tolerationSeconds: 30}
2.6. HTTPS phía sau tunnel. TLS kết thúc tại Cloudflare, và cloudflared gửi request tới Traefik bằng HTTP. Để WordPress biết người dùng đang dùng HTTPS (tránh vòng lặp chuyển hướng), Traefik phải tin header X-Forwarded-Proto đến từ dải pod: ports.web.forwardedHeaders.trustedIPs: [10.244.0.0/16]. Thêm middleware retry (3 lần) để che khoảng thời gian endpoint của pod vừa chết chưa kịp bị gỡ.
2.7. Khởi tạo volume RWX dùng chung. Image WordPress chính thức tự chép mã nguồn vào volume trong lần chạy đầu tiên. Nếu 3 pod cùng khởi động, chúng sẽ chép chồng lên nhau trên volume chung. Nên chạy 1 pod, cài đặt xong, rồi mới bật HPA.
3. Phương pháp đo
Tính sẵn sàng được đo từ phía người dùng: một máy gọi URL công khai (đi qua Cloudflare → tunnel → Traefik → WordPress → MariaDB) mỗi 0,5–1 giây, timeout 3–5 giây, ghi lại mã HTTP. Song song, một tiến trình ghi lại trạng thái cụm mỗi 2 giây: ai giữ VIP, trạng thái node, pod DB nằm ở đâu. Tải được tạo bằng ab -c 60 -t 180 từ một pod bên trong cụm. WordPress không có cache trang, nên con số hiệu năng phản ánh việc render PHP và truy vấn DB thực sự.
4. Kết quả
| Thí nghiệm | Kết quả đo |
|---|---|
| K1. Rút cáp mạng node đang giữ VIP API, MariaDB và 1 pod WordPress | VIP chuyển sau 23 s; node bị đánh dấu NotReady sau 74 s; pod WordPress được tạo lại sau 107 s; DB sẵn sàng trên node khác sau 206 s. Người dùng thấy 17 lần timeout và 104 lần HTTP 500. Không mất dữ liệu; khi cắm lại cáp, node tự nhập cụm và Longhorn tự dựng lại bản sao |
K2. Tải ab -c 60, 180 s |
3→6 pod sau 32 s, 3→10 pod sau 53 s; 18.239 request, 0 lỗi, 101 req/s, trung bình 592 ms; thu về 3 pod sau khoảng 5 phút hết tải |
| K3. Rolling update đổi image WordPress | Hoàn tất trong 134 s; 240/240 request trả 200 |
K4. kubectl drain node đang chạy MariaDB |
Drain xong trong 33 s; DB sẵn sàng sau 41 s; 51/400 request trả 500 |
5. Thảo luận
Phân rã thời gian phục hồi của K1:
phat hien node chet (node-monitor-grace-period) ~74 s
toleration unreachable 30 s
Longhorn xoa pod + gan volume sang node moi ~60 s
MariaDB khoi dong + kiem tra InnoDB ~36 s
------------------------------------------------------------
tong ~206 s (mat DB = website loi)
Lớp WordPress không trạng thái đã đạt mục tiêu: nhờ PDB, maxUnavailable: 0 và 3 pod rải trên 3 node, việc cập nhật và mất một pod web đều không làm gián đoạn dịch vụ. Toàn bộ gián đoạn quan sát được trong K1 và K4 đến từ cơ sở dữ liệu chạy một bản. Lưu trữ phân tán chỉ giải quyết được bài toán dữ liệu không mất, không giải quyết được bài toán dịch vụ không ngừng. Longhorn phải tháo volume ra khỏi node cũ và gắn sang node mới, và mọi bước đó đều nằm trên đường găng của thời gian phục hồi.
Có ba hướng cải thiện, xếp theo tác động:
- Nhân bản ở tầng ứng dụng, ví dụ MariaDB Galera 3 node (qua mariadb-operator) hoặc primary/replica có tự chuyển đổi. Khi đó mất một node DB chỉ còn là vài giây chuyển kết nối, và bảo trì theo kế hoạch không còn gây gián đoạn.
- Cache trang ở WordPress hoặc tại CDN. Cách này vừa nâng thông lượng (101 req/s là mức render PHP thuần), vừa cho phép phục vụ nội dung cũ trong lúc DB phục hồi.
- Rút ngắn thời gian phát hiện node chết. Có thể giảm
node-monitor-grace-periodvà toleration, nhưng đổi lại sẽ dễ dời pod nhầm khi mạng chỉ chập chờn.
Hai quan sát phụ. Thứ nhất, khi thu pod về, HPA không cân bằng lại vị trí pod: 3 pod còn lại nằm trên 2 node, vì ScheduleAnyway chỉ có tác dụng lúc xếp pod mới; cần descheduler nếu muốn cân bằng lại. Thứ hai, drain node bị PDB của Longhorn chặn tạm thời, đó là hành vi đúng: Longhorn không cho gỡ một bản sao đang là bản duy nhất khoẻ mạnh.
6. Kết luận
Bốn máy chủ phổ thông đủ để dựng một cụm Kubernetes có control plane chịu lỗi, lưu trữ nhân bản và cổng công khai không cần mở cổng trên router. Kết quả đo cho thấy độ sẵn sàng của hệ thống bị quyết định bởi thành phần có trạng thái yếu nhất, chứ không phải bởi số node. Với ứng dụng web điển hình, việc tiếp theo nên làm không phải thêm node, mà là nhân bản cơ sở dữ liệu ở tầng ứng dụng và thêm cache.
