Kubernetes thường được giới thiệu bằng sơ đồ kiến trúc. Bài này bắt đầu khác: dựng cùng một ứng dụng bằng Docker thuần và bằng Kubernetes, rồi đo cả hai — kể cả cái giá.

Chi phí của cụm, tự chữa, và cập nhật không gián đoạn

Cùng một việc, hai cái giá

Chạy 3 bản nginx:

Thời gian Bộ nhớ
docker run × 3 0,5 giây ~14 MB mỗi container
kubectl create deployment 10,6 giây cộng 743 MB cho chính cụm

Và một cụm Kubernetes rỗng — chưa chạy ứng dụng nào — đã có 9 pod hệ thống:

etcd                     kube-apiserver
kube-controller-manager  kube-scheduler
kube-proxy               kindnet
coredns × 2              local-path-provisioner

Docker thắng rõ ràng ở mọi chỉ số trong bảng trên: nhanh hơn hai mươi lần, không tốn bộ nhớ nền nào.

Vậy 743 MB đó mua được gì?

Giết một thứ đang chạy

docker kill w2
w2: Exited (137) 39 seconds ago

Nằm im. Mãi mãi. Cho tới khi có người để ý.

kubectl delete pod web-6d689fbfdf-gtgxc
3 pod chạy lại sau 1,1 giây

Và nếu tiến trình bên trong pod chết — mô phỏng ứng dụng sập chứ không phải container bị xoá:

kubectl exec <pod> -- sh -c 'kill 1'
pod vẫn Running,  cột RESTARTS = 1

Đây là khác biệt cơ bản nhất, và nó không phải chuyện kỹ thuật mà là chuyện mô hình:

Docker chạy thứ bạn bảo nó chạy. Kubernetes giữ thứ bạn khai báo là phải có.

Bạn nói "tôi muốn ba bản nginx". Kubernetes so trạng thái thật với lời khai đó, liên tục, và sửa chênh lệch. Không quan trọng vì sao chênh — container chết, máy sập, ai đó xoá nhầm.

Docker Compose có restart: unless-stopped và nó xử lý được trường hợp container chết. Nó không xử lý được máy chết.

Cập nhật phiên bản trong lúc đang phục vụ

kubectl set image deployment/web nginx=nginx:1.27-alpine

Trong lúc đó, gọi HTTP liên tục vào dịch vụ:

200 lời gọi:  200 thành công,  0 thất bại

Không mất một yêu cầu nào trong khi cả ba pod được thay bằng phiên bản mới.

Kubernetes dựng pod mới, đợi nó sẵn sàng, rồi mới xoá pod cũ — từng cái một, và luôn giữ đủ số bản đang phục vụ.

Làm việc này bằng tay với Docker cần một kịch bản: dựng container mới, kiểm tra sức khoẻ, thêm vào bộ cân bằng tải, bỏ container cũ ra, chờ kết nối đang mở đóng lại, xoá. Rồi xử lý trường hợp bản mới hỏng. Rồi xử lý trường hợp kịch bản bị ngắt giữa chừng.

Kịch bản đó chính là thứ Kubernetes thay thế. Nếu bạn đã từng viết nó, bạn biết nó dài bao nhiêu và hỏng theo bao nhiêu cách.

Bốn thứ khác Kubernetes làm sẵn

Khám phá dịch vụ. web-svc là một tên DNS trong cụm, tự trỏ tới các pod đang khoẻ. Pod chết thì tên đó tự bỏ nó ra. Với Docker, đây là chỗ người ta dựng Consul hoặc sửa tay tệp cấu hình nginx.

Xếp chỗ theo tài nguyên. Khai requests: cpu: 500m thì bộ lập lịch chỉ đặt pod lên node còn đủ chỗ. Với Docker, việc quyết định "máy nào chạy cái gì" là của con người.

Cấu hình và bí mật tách khỏi ảnh. ConfigMap và Secret gắn vào pod lúc chạy, nên cùng một ảnh chạy được ở mọi môi trường.

Tự mở rộng. Tải tăng thì tăng số bản, tải giảm thì giảm. Cần chỉ số, cần cấu hình, nhưng cơ chế có sẵn.

Ngưỡng đáng dùng

Con số 743 MB và 10,6 giây ở đầu bài là thật, và chúng là lý do Kubernetes không phải lựa chọn mặc định.

Dùng Docker Compose khi:

  • Một máy, và bạn chấp nhận máy đó là điểm hỏng duy nhất.
  • Vài container, ít khi thay đổi.
  • Đội nhỏ, không có ai chuyên vận hành.

Với hoàn cảnh đó, 743 MB và toàn bộ độ phức tạp đi kèm không mua lại được gì tương xứng.

Dùng Kubernetes khi:

  • Nhiều máy, và bạn cần ứng dụng sống sót khi một máy chết.
  • Cập nhật thường xuyên và không được gián đoạn.
  • Nhiều đội cùng chạy dịch vụ trên chung hạ tầng.
  • Tải thay đổi mạnh và bạn muốn tự co giãn.

Nói cách khác: Kubernetes trả lời câu hỏi "chuyện gì xảy ra khi có thứ hỏng". Nếu bạn chưa cần trả lời câu đó, bạn chưa cần Kubernetes.

Một hiểu nhầm phổ biến

Kubernetes không thay thế Docker. Nó điều phối container, và container vẫn được chạy bởi một runtime — containerd trong cụm tôi vừa dựng, như kubectl get nodes -o wide cho thấy.

Bạn vẫn viết Dockerfile, vẫn xây ảnh, vẫn đẩy lên registry. Cái đổi là ai quyết định chạy chúng ở đâu và giữ chúng sống.

Thử ba mươi giây

Dựng một cụm và tự xem nó tự chữa:

kind create cluster --name thu
kubectl create deployment web --image=nginx:alpine --replicas=3
kubectl delete pod $(kubectl get pods -l app=web -o jsonpath='{.items[0].metadata.name}')
kubectl get pods -l app=web -w

Bạn sẽ thấy pod thứ tư xuất hiện trước khi pod bị xoá kịp biến mất. Đó là toàn bộ ý tưởng của Kubernetes, gói trong một dòng đầu ra.

Phần sau dựng cụm nhiều node bằng kind và đo: node chết thì mất bao lâu pod mới được chuyển đi.