Mua một chiếc xe có túi khí và phanh ABS không tự làm bạn an toàn hơn — nếu bạn không thắt dây và vẫn phóng ẩu, mấy hệ thống đó chỉ là trọng lượng thừa và một hoá đơn cao hơn. Chúng là cơ chế, không phải kết quả: chúng cho bạn khả năng sống sót một cú va chạm, với điều kiện bạn dùng đúng. Kubernetes so với Docker Compose đúng là câu chuyện túi khí ấy, và bài này dựng cùng một ứng dụng hai container bằng cả hai cách rồi đo song song thay vì trả lời bằng niềm tin.

Bốn chỉ số đầu: gần như hoà

Compose Kubernetes
Số dòng cấu hình 10 42
Khởi động lần đầu 0,9 giây 1,2 giây
RAM container (web / api) 12,9 / 20,9 Mi 12 / 12 Mi
Ứng dụng tự thoát → sống lại 0,4 giây 2,1 giây

Kubernetes không chậm hơn khi khởi động, và không tốn thêm RAM ở tầng container. Hai điều đó ngược với ấn tượng phổ biến. Bốn lần số dòng cấu hình là thật, nhưng 42 dòng vẫn là 42 dòng — nó không phải rào cản mà người ta hay mô tả.

Chỗ Kubernetes thua rõ là khởi động lại: 2,1 giây so với 0,4. Compose nhanh hơn vì Docker daemon thấy container chết và dựng lại ngay tại chỗ; Kubernetes phải đi qua kubelet, API server, và vòng lặp đối chiếu.

Nhưng Kubernetes mang theo phần không nằm trong ứng dụng

control plane                 536 MB   (đo ở phần 52)
kubelet + containerd mỗi node 207 MB   (đo ở phần 55)

Compose không có gì tương đương — nó dùng chính Docker daemon đã chạy sẵn. Đây mới là cái giá thật, và nó cố định: 743 MB cho một cụm một node, dù bạn chạy một container hay một trăm. Với ứng dụng chiếm 33 MB như trong phép đo, phần hạ tầng lớn gấp hai mươi lần phần ứng dụng. Tỷ lệ đó đảo ngược rất nhanh khi số dịch vụ tăng, và đó chính là chỗ ranh giới nằm.

Cập nhật phiên bản: chỗ khác biệt thật

Tôi bắn liên tục vào dịch vụ rồi đổi image giữa chừng.

Số lỗi
Compose, đổi image 2 / 408 yêu cầu
Kubernetes, 1 bản, không probe, không preStop 1 / 446
Kubernetes, 3 bản + readinessProbe + preStop 0 / 533

Dòng thứ hai là dòng đáng dừng lại: Kubernetes trần trụi không tốt hơn Compose. Một bản sao, không probe, không preStop — vẫn mất yêu cầu, đúng như Compose. Cái cho không đứt quãng là nhiều bản + readinessProbe + preStop, đúng ba thứ đã đo ở phần 51. Kubernetes không tặng bạn thời gian hoạt động; nó tặng bạn cơ chế để đạt được nó — đúng cái túi khí — và Compose không có cơ chế nào trong ba cái đó.

Đây là cách trả lời câu hỏi ban đầu chính xác nhất: Kubernetes không tốt hơn, nó có thể tốt hơn nếu bạn cấu hình đủ.

Một chỗ Compose không làm như quảng cáo

docker kill + restart: unless-stopped  ->  exited, restarts 0
docker kill + restart: always          ->  exited, restarts 0

Lặp ba lần, đều vậy. Cả hai chính sách khởi động lại đều không dựng container lên sau docker kill. Lý do: Docker coi docker kill và docker stop là yêu cầu tường minh của người vận hành, và đánh dấu container là "đã dừng có chủ đích". Chính sách khởi động lại không đè lên chủ đích đó.

Nhưng khi ứng dụng tự thoát — tôi chạy nginx -s stop bên trong — thì nó dựng lại sau 0,4 giây, RestartCount từ 0 lên 1. Chính sách hoạt động đúng cho trường hợp nó được thiết kế. Điều đáng nhớ là cách người ta hay kiểm thử: docker kill để "thử xem nó có tự sống lại không", thấy nó không sống lại, rồi kết luận sai — hoặc tệ hơn, sửa cấu hình cho tới khi phép thử sai đó cho kết quả đúng. Trong Kubernetes không có sự phân biệt này: pod bị xoá thì ReplicaSet dựng lại, bất kể ai xoá và vì sao.

Chọn thế nào

Từ các con số ở trên, ranh giới khá rõ:

Dùng Compose khi: một máy chủ là đủ; dừng vài giây lúc triển khai chấp nhận được; số dịch vụ đếm trên một bàn tay; và không ai trong đội muốn học Kubernetes.

Dùng Kubernetes khi: cần nhiều hơn một máy; cần triển khai không đứt quãng; có nhiều dịch vụ giống nhau cần vận hành theo cùng một cách; hoặc cần thứ chỉ Kubernetes có — tự mở rộng, tự chữa, phân quyền theo namespace.

Và một lựa chọn thứ ba ít được nhắc: Compose trên nhiều máy với một bộ cân bằng tải phía trước. Nó xử lý được phần lớn nhu cầu "không đứt quãng" mà không cần control plane, đổi lại là bạn tự lo việc điều phối.

Chuyển từ Compose sang Kubernetes

Nếu quyết định chuyển, thứ tự này ít đau nhất:

  1. Chuyển máy móc trước. kompose convert dựng bộ khung; đừng kỳ vọng nó đúng, chỉ cần nó đỡ phải gõ.
  2. Thêm readinessProbe ngay. Không có nó thì mọi thứ khác vô nghĩa — đã đo ở trên.
  3. Đặt requests. Không có requests thì pod là BestEffort và bị đuổi đầu tiên (phần 12).
  4. Bỏ depends_on. Kubernetes không có khái niệm tương đương và cố ý không có; ứng dụng phải tự thử lại. Đây là thay đổi tư duy lớn nhất, và nó thuộc về mã ứng dụng chứ không thuộc về YAML.
  5. Volume tính sau cùng. Đây là phần khác nhau nhiều nhất và dễ mất dữ liệu nhất — xem phần 54.

Mẫu số chung: nền tảng cho cơ chế, không cho kết quả

Cái làm "Kubernetes trần trụi không tốt hơn Compose" là một sự thật đúng với mọi nền tảng và framework: chúng cho bạn cơ chế (mechanism), không cho bạn kết quả (outcome). Kubernetes có sẵn nhiều bản sao, probe, rolling update — nhưng chỉ khi bạn nối chúng lại thì mới có triển khai không đứt quãng; để trần thì nó mất yêu cầu y như Compose, đúng cái túi khí không thắt dây. Và nó đi kèm một chi phí cố định (743 MB) chỉ đáng khi khấu hao trên đủ nhiều dịch vụ.

Trong thế giới Java, đây đúng là Spring cho bạn @Transactional, một thư viện circuit breaker (Resilience4j), một connection pool — nhưng thêm dependency vào classpath không tự làm hệ chịu lỗi; bạn phải bật và cấu hình từng cơ chế, và một @Transactional đặt sai chỗ (gọi nội bộ trong cùng bean) im lặng không có tác dụng. Hệ thống kiểu (type system) cũng vậy: nó chặn được lỗi nếu bạn thật sự mô hình hoá bằng kiểu, không phải bằng Object/any. Ở tầng nào, có công cụ trong tay khác với đạt được mục tiêu công cụ hứa.

Mẫu số chung: một nền tảng cấp cơ chế và một chi phí cố định để đổi lấy khả năng — nhưng khả năng chỉ thành kết quả khi bạn cấu hình đúng, và chi phí cố định chỉ đáng khi có đủ khối lượng để khấu hao. Câu hỏi đúng khi chọn không phải "Kubernetes có tốt hơn Compose không" mà là "tôi có đủ dịch vụ để khấu hao 743 MB, và tôi có sẵn lòng nối đúng các cơ chế nó cho — hay tôi chỉ đang trả tiền cho túi khí rồi không thắt dây". Với ít dịch vụ và một máy, Compose thắng; với nhu cầu không-đứt-quãng thật và nhiều dịch vụ, Kubernetes thắng sau khi bạn cấu hình đủ.

Nếu đang dùng Compose, muốn tự kiểm ứng dụng có sống sót qua kiểu hỏng thật hay không:

# KHÔNG dùng docker kill — nó bị coi là chủ đích của bạn
docker compose exec <dich-vu> sh -c 'kill -TERM 1' || \
docker compose exec <dich-vu> <lenh-dung-ung-dung>

sleep 5
docker compose ps
docker inspect <container> -f 'restarts={{.RestartCount}}'

RestartCount tăng nghĩa là chính sách khởi động lại đang hoạt động. Vẫn 0 và container exited nghĩa là bạn vừa kiểm bằng cách Docker coi là chủ đích — thử lại bằng cách làm chính ứng dụng thoát.

Phần cuối: gom mọi phép đo của sáu mươi phần thành một danh sách kiểm.