PostKubernetes, sự phức tạp và những điều tôi học được từ homelab
Articles
Kubernetes, sự phức tạp và những điều tôi học được từ homelab
Những suy nghĩ và kinh nghiệm cá nhân khi đưa Kubernetes vào homelab: từ sức hấp dẫn của trạng thái mong muốn đến chi phí vận hành, các kỳ vọng sai và thời điểm nên chọn một giải pháp đơn giản hơn.
Kubernetes đến với tôi không phải vì một bài toán đủ lớn
Tôi biết đến Kubernetes từ khá lâu trước khi thật sự cần nó. Khi ấy, Kubernetes xuất hiện trong gần như mọi cuộc trò chuyện về backend, DevOps và cloud. Người ta nói về khả năng tự phục hồi, mở rộng tự động, rolling update và hàng loạt khái niệm nghe rất giống lời hứa rằng hệ thống rồi sẽ tự chăm sóc chính nó.
Trong khi đó, những ứng dụng của tôi vẫn sống khá yên ổn trong vài container Docker. Một file docker-compose.yml, một reverse proxy và một buổi cuối tuần là đủ để đưa phần lớn ý tưởng lên mạng.
Vì vậy, lý do đầu tiên tôi cài Kubernetes không thật sự cao siêu. Tôi chỉ tò mò.
Tôi muốn biết điều gì nằm sau những sơ đồ có nhiều ô vuông, vì sao một file YAML có thể dài hơn cả phần cấu hình ứng dụng và tại sao mọi người vừa yêu vừa phàn nàn về cùng một công nghệ.
“Kubernetes không giải quyết sự phức tạp. Nó tổ chức sự phức tạp, đặt tên cho nó và buộc mình phải nhìn thẳng vào nó.”
Sự hấp dẫn của một hệ thống luôn cố sửa mọi thứ
Điều khiến tôi thích Kubernetes không phải số lượng tính năng. Đó là ý tưởng về trạng thái mong muốn.
Tôi không ra lệnh cho hệ thống theo kiểu: chạy container này, nếu lỗi thì khởi động lại, nếu đổi phiên bản thì dừng cái cũ rồi chạy cái mới. Tôi chỉ mô tả điều mình muốn:
yaml
spec: replicas: 2 containers: - name: web
image: ghcr.io/helios/web:2.0.0
Phần còn lại là một cuộc đối thoại liên tục giữa điều tôi khai báo và điều đang thực sự tồn tại. Nếu chỉ còn một Pod, controller tạo thêm một Pod. Nếu tôi đổi image, Deployment tìm cách đưa hệ thống từ phiên bản cũ sang phiên bản mới.
Nghe qua, đó chỉ là automation. Nhưng với tôi, nó thay đổi cách suy nghĩ: thay vì nhớ một chuỗi thao tác, tôi bắt đầu mô tả một kết quả có thể kiểm tra được.
Note
Giá trị lớn nhất của Kubernetes không nằm ở việc chạy container; nó nằm ở reconciliation, tức khả năng liên tục kéo trạng thái thực tế về gần trạng thái đã khai báo.
Ý tưởng ấy đẹp một cách nguy hiểm. Nó khiến tôi dễ tin rằng chỉ cần đưa ứng dụng vào Kubernetes thì ứng dụng sẽ trở nên “chuẩn production”. Thực tế không hào phóng như vậy.
Một ứng dụng không có health check vẫn không biết cách báo rằng nó đang hỏng. Một ứng dụng ghi toàn bộ dữ liệu vào filesystem tạm vẫn sẽ mất dữ liệu. Một service thiếu log vẫn im lặng đúng như trước đây. Kubernetes có thể khởi động lại một tiến trình, nhưng nó không thể tự hiểu ý định mà tôi chưa từng diễn đạt.
Cái giá không nằm trong hoá đơn máy chủ
Kubernetes nhẹ hay nặng phụ thuộc vào thứ mình đem ra đo. Một cụm k3s nhỏ không tiêu tốn quá nhiều CPU hoặc RAM. Nhưng nó tiêu tốn một tài nguyên khác: sự chú ý.
Trước đây, khi một container không chạy, tôi nhìn vào container. Sau này, tôi phải tự hỏi:
Deployment có tạo đúng ReplicaSet không?
Pod đang Pending, CrashLoopBackOff hay chỉ chưa Ready?
Service có chọn đúng label không?
Endpoint có tồn tại không?
Ingress có chuyển đúng host và path không?
DNS, CNI hoặc certificate có đang góp phần làm mọi thứ thú vị hơn mức cần thiết không?
Mỗi lớp đều có lý do tồn tại. Vấn đề là mỗi lớp cũng tạo thêm một nơi có thể hiểu sai.
Kỳ vọng ban đầu
Điều tôi gặp trong thực tế
Tự phục hồi
Chỉ phục hồi tốt khi health check phản ánh đúng trạng thái
Đừng nhầm khả năng tạo lại Pod với khả năng phục hồi toàn bộ hệ thống. Database, queue, storage và dữ liệu người dùng không trở nên an toàn chỉ vì workload đang chạy trong Kubernetes.
Đây cũng là lúc tôi hiểu vì sao một công cụ có thể rất mạnh nhưng vẫn không phù hợp với mọi dự án. Sức mạnh không miễn phí. Nó được trả bằng kiến thức, quy trình, thời gian nâng cấp và khả năng chấp nhận rằng đôi khi chính platform cũng là một sản phẩm cần được chăm sóc.
Homelab là nơi tốt để phạm sai lầm
Tôi dựng cụm k3s trong homelab không phải để giả lập một công ty lớn. Tôi muốn một nơi đủ thật để Pod có thể chết, DNS có thể sai và một lần sửa YAML bất cẩn có thể làm buổi tối dài hơn dự kiến.
Homelab cho tôi quyền thất bại mà không phải viết postmortem lúc ba giờ sáng.
Tôi từng có một Service không chuyển traffic đến đâu cả. Pod vẫn Running, Deployment vẫn đủ replica và Ingress nhìn qua không có gì đáng ngờ. Sau một lúc đi vòng quanh, nguyên nhân chỉ là hai label khác nhau:
yaml
# Podlabels: app: helios-blog# Serviceselector: app: blog
Lỗi rất nhỏ, nhưng bài học khá lớn. Kubernetes không “hiểu gần đúng”. Nó làm chính xác điều được khai báo, kể cả khi khai báo ấy không phải điều tôi nghĩ mình đã viết.
Từ đó, tôi hình thành thói quen đi theo dòng chảy thay vì đoán:
bash
kubectl get ingress,service,endpoints,pods -n homelabkubectl describe pod <pod-name> -n homelabkubectl logs <pod-name> -n homelab --tail=100
Ba lệnh này không giải quyết mọi vấn đề, nhưng chúng nhắc tôi nhìn vào bằng chứng trước khi xoá Pod và hy vọng lần khởi động tiếp theo sẽ tốt đẹp hơn.
Tip
Khi debug, hãy đi từ request vào trong: Ingress, Service, Endpoint rồi Pod. Nếu bắt đầu bằng việc thay đổi ngẫu nhiên nhiều resource, mình thường chỉ tạo thêm biến số.
Những câu hỏi tôi tự hỏi trước khi sửa cluster
Trạng thái nào đang khác với trạng thái tôi mong muốn?
Event gần nhất nói gì?
Đây là lỗi ứng dụng, cấu hình hay hạ tầng?
Thay đổi sắp làm có thể hoàn tác không?
Tôi đang sửa nguyên nhân hay chỉ đang làm triệu chứng biến mất?
Tôi thích mở terminal cạnh tài liệu, dùng Ctrl + K để tìm nhanh resource cần đọc, rồi ghi lại những điều vừa phát hiện. Kiến thức vận hành rất dễ tan biến nếu nó chỉ tồn tại trong lịch sử shell.
Kubernetes khiến tôi viết ứng dụng khác đi
Sau một thời gian, ảnh hưởng lớn nhất của Kubernetes lại nằm ngoài Kubernetes.
Tôi bắt đầu suy nghĩ nghiêm túc hơn về cách ứng dụng khởi động và dừng. Tôi thêm endpoint health check có ý nghĩa thay vì luôn trả 200 OK. Tôi để cấu hình đi qua biến môi trường, đưa log ra stdout và tự hỏi điều gì xảy ra khi tiến trình nhận SIGTERM.
Những điều đó đều hữu ích ngay cả khi ứng dụng quay về chạy bằng Docker Compose.
Readiness không phải một ô để đánh dấu
Trước đây, tôi xem readiness probe như một đoạn YAML cần có cho đủ bộ. Sau đó tôi nhận ra probe đang trả lời một câu hỏi quan trọng: ứng dụng này có nên nhận traffic ngay lúc này không?
Một process đang sống chưa chắc đã sẵn sàng. Nó có thể chưa kết nối được database, chưa tải xong cache hoặc đang ở giữa một quá trình shutdown.
Important
Health check nên mô tả khả năng phục vụ thật sự của ứng dụng, không chỉ xác nhận rằng process vẫn tồn tại.
Kubernetes không làm tôi trở thành kỹ sư giỏi hơn chỉ sau một đêm. Nhưng nó liên tục đặt những câu hỏi mà trước đây tôi có thể trì hoãn:
Ứng dụng cần bao nhiêu tài nguyên?
Khi nào nó sẵn sàng nhận request?
Điều gì xảy ra khi một instance biến mất?
Dữ liệu nào phải tồn tại lâu hơn container?
Tôi sẽ nhận ra lỗi bằng cách nào?
Đó là những câu hỏi đáng giá hơn việc thuộc lòng mọi tham số của kubectl.
Không phải dự án nào cũng cần một cluster
Tôi vẫn dùng Docker Compose. Thậm chí, sau khi học Kubernetes, tôi càng trân trọng sự đơn giản của Compose hơn.
Nếu tôi có một ứng dụng cá nhân nhỏ, ít thay đổi, chạy trên một máy và thời gian khôi phục vài phút là chấp nhận được, Compose thường là lựa chọn tốt. Nó ít lớp hơn, dễ quan sát hơn và cho phép tôi dành sự chú ý vào sản phẩm thay vì platform.
Kubernetes bắt đầu hợp lý hơn khi có nhiều workload, cần deploy thường xuyên, muốn chuẩn hoá cách vận hành hoặc cần một môi trường chung cho nhiều người. Nhưng ngay cả lúc đó, “có thể dùng” không đồng nghĩa với “nên tự vận hành”. Managed Kubernetes đôi khi là cách mua lại thời gian và sự bình yên.
Tình huống
Lựa chọn tôi thường nghiêng về
Một ứng dụng nhỏ trên một VPS
Docker Compose
Homelab để học vận hành
k3s
Nhiều service, nhiều lần deploy
Kubernetes
Đội nhỏ, không có người chăm platform
Managed service
Chưa hiểu ứng dụng cần gì
Bắt đầu đơn giản
Caution
Đưa Kubernetes vào một dự án chỉ vì nó phổ biến có thể biến platform thành phần phức tạp nhất của hệ thống, dù sản phẩm chưa có người dùng đầu tiên.
Tôi từng có xu hướng chọn công nghệ như chọn một phiên bản tương lai của bản thân: dùng thứ mà “một ngày nào đó” mình sẽ cần. Bây giờ tôi cố chọn cho bài toán đang tồn tại, đồng thời chừa một con đường nâng cấp đủ rõ ràng.
Một vài lời khuyên tôi muốn nghe sớm hơn
Nếu bắt đầu lại, tôi sẽ không lao ngay vào Helm chart, operator, service mesh hay GitOps. Những thứ đó hữu ích, nhưng chúng dễ che mất mô hình cơ bản.
Tôi sẽ học chậm hơn theo thứ tự:
Pod tồn tại và biến mất như thế nào.
Deployment quản lý Pod ra sao.
Label và selector nối các resource với nhau thế nào.
Service tạo một điểm truy cập ổn định bằng cách nào.
ConfigMap khác Secret ở đâu và giới hạn của cả hai là gì.
Event, log và metrics kể ba câu chuyện khác nhau ra sao.
Sau đó tôi mới thêm công cụ khi cảm thấy rõ nỗi đau mà công cụ đó giải quyết.
Hiểu Deployment, Service và Ingress trước.
Khai báo resource request và readiness probe.
Đưa manifest vào Git.
Tự động hoá backup và kiểm tra restore.
Thêm monitoring sau khi xác định metric cần quan sát.
Chỉ dùng operator khi chấp nhận vận hành cả operator đó.
Tôi cũng khuyên nên giữ một ứng dụng thật nhỏ trong cluster. Một workload mình hiểu từ đầu đến cuối hữu ích hơn mười chart cài bằng lệnh copy từ README. Khi nó lỗi, mình biết trạng thái đúng phải trông như thế nào.
Kubernetes không còn là mục tiêu đối với tôi. Nó là một môi trường để học về ranh giới trách nhiệm, trạng thái, lỗi và khả năng phục hồi.
Tôi vẫn thích cảm giác nhìn một Deployment tự thay Pod lỗi. Nhưng tôi cũng biết phía sau khoảnh khắc có vẻ kỳ diệu đó là hàng loạt giả định: probe phải đúng, image phải chạy được, network phải thông, storage phải còn và người vận hành phải đủ tỉnh táo để hiểu chuyện gì đang xảy ra.
Điều Kubernetes dạy tôi rõ nhất không phải cách quản lý một cluster. Đó là cách tôn trọng độ phức tạp. Không phủ nhận nó. Không thần thánh hoá nó. Và đặc biệt, không mang nó vào một hệ thống nếu mình chưa sẵn sàng chịu trách nhiệm cho phần phức tạp vừa tạo ra.
Tôi vẫn sẽ tiếp tục dùng Kubernetes trong homelab, tiếp tục làm hỏng vài thứ và tiếp tục ghi lại những điều học được. Nhưng với dự án tiếp theo, câu hỏi đầu tiên của tôi sẽ không còn là “triển khai nó lên Kubernetes thế nào?”. Câu hỏi sẽ là: hệ thống này thật sự cần điều gì để được vận hành tử tế?
Đôi khi câu trả lời là Kubernetes. Đôi khi chỉ là một file Compose được viết cẩn thận.
Kubernetes, sự phức tạp và những điều tôi học được từ homelab | Helios Blog