Kubernetes trong platform engineering GDev
Xem thửTình huống: một đêm gdev-api sập mà không ai biết
gdev-api của đội GDev đang chạy bằng Docker Compose trên một máy ảo duy nhất, với restart: unless-stopped. Lúc 02:10 nhà cung cấp cloud khởi động lại máy ảo để vá host. Docker khởi động lại cùng máy, nhưng container database chưa sẵn sàng nên gdev-api thoát với lỗi kết nối và không bao giờ thử lại. Sáng ra, khách hàng đã gặp lỗi gần sáu tiếng.
Ba tuần sau, đội muốn thêm môi trường staging, đặt gdev-api sau một load balancer với hai bản chạy, và deploy mà không cắt kết nối đang xử lý. Mỗi việc đó cần một đoạn script mới: SSH vào máy nào, chạy docker run thế nào, bản nào đang sống. Đây chính là bài toán mà Kubernetes sinh ra để giải: giữ cho hệ thống đúng như mô tả, kể cả khi bạn đang ngủ.
Sau bài này bạn làm được các việc sau:
- Kể ra ba vấn đề xuất hiện khi vượt khỏi Compose trên một máy, và Kubernetes xử lý từng vấn đề thế nào.
- Giải thích trạng thái mong muốn (desired state) và vòng lặp reconcile bằng một lab xóa Pod.
- Quyết định có nên dùng Kubernetes cho một đội nhỏ hay không, và biết nên bỏ qua những gì ở ngày đầu.
Bạn cần có sẵn: biết chạy container với Docker (xem chương Docker). Chưa cần biết Kubernetes.
Ba mô hình cần nhớ
- Bạn khai báo kết quả, hệ thống lo cách đạt được: bạn viết "luôn có 3 bản
gdev-api", không viết "SSH vào máy 2 và chạy lại container". - Vòng lặp reconcile: các controller liên tục so sánh trạng thái thực tế với trạng thái mong muốn rồi sửa chênh lệch, không chờ ai ra lệnh.
- Kubernetes là một cái giá phải trả: đổi lấy khả năng tự phục hồi và rollout an toàn, bạn trả bằng độ phức tạp vận hành. Câu hỏi đúng là "vấn đề của tôi đã đủ lớn để trả giá đó chưa".
Chuẩn bị môi trường lab
Cả chương dùng chung một cluster kind (Kubernetes chạy trong container Docker) tên gdev và namespace gdev-staging. Namespace là một vùng tên để nhóm và cô lập tài nguyên, sẽ học kỹ ở bài Namespace.
kind create cluster --name gdev
kubectl create namespace gdev-staging
kubectl config set-context --current --namespace=gdev-staging
kubectl get nodes
Kết quả minh họa:
NAME STATUS ROLES AGE VERSION
gdev-control-plane Ready control-plane 45s v1.xx.x
Một node Ready là đủ. Phiên bản bạn thấy phụ thuộc bản kind đã cài. Từ đây mọi lệnh kubectl chạy trong namespace gdev-staging.
Bài kubectl là bài thứ tư của chương, nhưng ngay từ các bài đầu bạn đã cần nó. Năm lệnh dưới đây đủ để theo các lab trước bài đó:
| Lệnh | Việc nó làm | Khi nào dùng |
|---|---|---|
kubectl get pods | Liệt kê đối tượng, thêm -o wide để xem IP và node | Xem hiện trạng |
kubectl describe pod TÊN | Mô tả chi tiết kèm mục Events ở cuối | Tìm nguyên nhân khi có lỗi |
kubectl logs TÊN | In log của container | Ứng dụng chạy sai hoặc crash |
kubectl apply -f file.yaml | Gửi trạng thái mong muốn lên cluster | Tạo hoặc cập nhật |
kubectl rollout status deployment/TÊN | Chờ và báo tiến độ triển khai | Sau mỗi lần deploy |
Thay TÊN bằng tên thật của đối tượng. Phần còn lại, gồm lọc, định dạng đầu ra và kubectl debug, nằm ở bài kubectl.
Vòng lặp reconcile: cách Kubernetes giữ lời hứa
Hãy dự đoán trước: bạn khai báo 3 bản gdev-api, rồi xóa tay một Pod. Mười giây sau có mấy Pod, và ai đã quyết định tạo thêm?
Đang tải sơ đồ…
Cách đọc sơ đồ:
- Desired state là thứ bạn gửi bằng
kubectl apply, được lưu ở API server. Đây là nguồn sự thật duy nhất. - Controller (chương trình đảm nhận một loại đối tượng, như Deployment controller) liên tục đọc trạng thái thực tế và đặt câu hỏi ở hình thoi: hai bên có khớp không.
- Nếu khớp, controller chờ sự kiện kế tiếp. Nếu lệch, nó tạo hoặc xóa Pod để thu hẹp khoảng cách, rồi đọc lại.
- Vòng lặp không có điểm kết thúc và không nhớ "ai đã ra lệnh gì". Vì vậy nó trả lời được câu hỏi ở trên: bạn xóa Pod, controller thấy 2 khác 3 và tạo Pod mới, bất kể Pod biến mất vì lý do gì.
Kết luận quan trọng: ở Kubernetes bạn không "chạy lệnh để Pod sống lại". Bạn sửa mô tả, hoặc để controller lo phần còn lại.
Lab: xem vòng lặp reconcile bằng tay
Đây là file mô tả gdev-api. Ở bài Deployment ta sẽ đọc từng trường, bây giờ chỉ cần để ý dòng replicas: 3, tức là "luôn có 3 bản". Image nginx:1.27-alpine đóng vai gdev-api trong cả chương.
apiVersion: apps/v1
kind: Deployment
metadata:
name: gdev-api
namespace: gdev-staging
spec:
replicas: 3
selector:
matchLabels:
app: gdev-api
template:
metadata:
labels:
app: gdev-api
spec:
containers:
- name: api
image: nginx:1.27-alpine
ports:
- containerPort: 80
Lưu thành gdev-api.yaml rồi áp dụng:
kubectl apply -f gdev-api.yaml
kubectl rollout status deployment/gdev-api
kubectl get pods
Kết quả minh họa (hậu tố tên Pod của bạn sẽ khác):
deployment.apps/gdev-api created
deployment "gdev-api" successfully rolled out
NAME READY STATUS RESTARTS AGE
gdev-api-6d8f9c7b5d-4kx2p 1/1 Running 0 12s
gdev-api-6d8f9c7b5d-h7q9w 1/1 Running 0 12s
gdev-api-6d8f9c7b5d-zt5mn 1/1 Running 0 12s
Bây giờ phá hoại: xóa một Pod, rồi liệt kê ngay.
kubectl delete $(kubectl get pods -l app=gdev-api -o name | head -n 1)
kubectl get pods
Kết quả minh họa:
pod "gdev-api-6d8f9c7b5d-4kx2p" deleted
NAME READY STATUS RESTARTS AGE
gdev-api-6d8f9c7b5d-4kx2p 1/1 Terminating 0 2m
gdev-api-6d8f9c7b5d-h7q9w 1/1 Running 0 2m
gdev-api-6d8f9c7b5d-n2c8v 0/1 ContainerCreating 0 1s
gdev-api-6d8f9c7b5d-zt5mn 1/1 Running 0 2m
Phân tích từng dòng:
- Pod bị xóa đang
Terminating: nó chưa biến mất ngay vì còn thời gian dừng êm. - Một Pod mới tên
n2c8vxuất hiện sau 1 giây. Bạn không hề ra lệnh tạo nó. Deployment controller đếm thấy chỉ còn 2 Pod hoạt động trong khi mong muốn là 3, nên tạo thêm. - Pod mới có tên và IP khác. Kubernetes không "hồi sinh" Pod cũ, nó thay bằng một Pod mới giống hệt về mô tả.
Đối chiếu với tình huống mở đầu: nếu gdev-api chạy bằng cách này và máy chứa nó chết, các Pod sẽ được tạo lại trên máy khác trong cluster. Cơ chế chọn máy và phát hiện máy chết nằm ở bài Kiến trúc.
Hãy xem vòng lặp này khi bị tác động từ hai phía. Trước khi bấm, dự đoán: nếu thay vì xóa Pod, bạn sửa số bản mong muốn từ 3 lên 5, controller làm gì?
Đang tải Animation…
Hiểu lầm lớn nhất: "Kubernetes là Docker chạy trên cluster"
Nghe thì hợp lý, nhưng sai ở điểm quan trọng nhất. Bảng dưới đặt hai thế giới cạnh nhau với cùng một việc:
| Việc cần làm | Docker hoặc Compose trên một máy | Kubernetes |
|---|---|---|
| Chạy 3 bản | Ba lệnh docker run, hoặc --scale | Khai báo replicas: 3, hệ thống tự giữ |
| Container chết | Chỉ chạy lại nếu có restart policy và máy còn sống | Controller tạo lại Pod, kể cả trên máy khác |
| Máy chết | Mọi thứ trên máy dừng cho tới khi người can thiệp | Pod được xếp lịch sang máy còn lại |
| Deploy bản mới | Dừng cũ, chạy mới, tự lo downtime | Rolling update có kiểm soát và rollback |
| Tìm nhau | Tên container trong một network của một máy | Service có DNS ổn định trên cả cluster |
| Ai được làm gì | Quyền truy cập Docker socket thường ngang với root | RBAC theo namespace và loại đối tượng |
Có ba điểm cần sửa trong đầu:
- Kubernetes không điều khiển Docker. Nó điều khiển container qua giao diện CRI, thường với containerd hoặc CRI-O. Docker Engine chỉ dùng được khi có thêm một bộ chuyển đổi bên ngoài (cri-dockerd), vì dockershim đã bị gỡ khỏi Kubernetes từ bản 1.24.
- Đơn vị nhỏ nhất không phải container mà là Pod, và đơn vị bạn quản lý hằng ngày thường là Deployment, Service, Ingress, tức là các đối tượng API.
- Image bạn build bằng Docker vẫn chạy được nguyên xi, vì cả hai cùng theo chuẩn OCI. Phần công việc cũ của bạn không bị bỏ đi.
Một điều Kubernetes không làm, theo tài liệu chính thức: nó không build mã nguồn, không dựng pipeline CI/CD, không cung cấp database hay hệ thống giám sát có sẵn. Đó là lý do chương CI/CD và gdev-ci vẫn cần thiết.
Có cần Kubernetes không
Trả lời trung thực: nhiều đội nhỏ chưa cần. Câu hỏi đáng hỏi là hệ thống của bạn đã có bao nhiêu vấn đề mà Kubernetes giải. Hãy tự đi qua sơ đồ trước khi đọc phần giải thích:
Đang tải sơ đồ…
Cách đọc sơ đồ: mỗi câu hỏi loại bỏ một nhóm hệ thống.
- Nếu mọi thứ vừa trên một máy và bạn chấp nhận vài phút gián đoạn khi máy khởi động lại, Compose với restart policy và giám sát đã giải đủ. Thêm Kubernetes chỉ thêm thứ phải học và phải vá.
- Nhu cầu nhiều máy nhưng không cần tự phục hồi hay deploy không gián đoạn có thể đáp ứng bằng dịch vụ container được quản lý của nhà cung cấp cloud, nơi họ lo phần điều phối.
- Chỉ khi cần cả hai nhu cầu trên, và có người chịu trách nhiệm nâng cấp cluster, Kubernetes mới đáng giá. Theo trang phát hành chính thức, Kubernetes ra khoảng ba bản mỗi năm, nên việc nâng cấp là công việc lặp lại, không phải việc làm một lần.
Nếu quyết định dùng, một đội nhỏ nên chọn control plane được quản lý của nhà cung cấp cloud lớn, ví dụ GKE của Google Cloud, EKS của AWS hay AKS của Azure, thay vì tự dựng, và bỏ qua những thứ sau ở ngày đầu:
| Chưa nên đưa vào | Vì sao |
|---|---|
| Service mesh | Thêm một lớp proxy cho mọi Pod, khó gỡ lỗi; chỉ cần khi bạn đã có nhu cầu mTLS hoặc chia traffic tinh vi |
| Operator hoặc CRD tự viết | Bạn phải bảo trì cả code lẫn đối tượng API mới |
| Nhiều cluster | Nhân đôi việc nâng cấp và quan sát, trong khi một cluster với nhiều namespace thường đủ |
| Hàng chục add-on cài sẵn | Mỗi add-on là một thứ có thể hỏng và cần nâng cấp |
Bộ khởi đầu hợp lý là Deployment, Service, Ingress hoặc Gateway, ConfigMap, Secret, probe và resource requests. Đó cũng là nội dung của chương này.
Failure lab: lệnh tay bị file ghi đè
Giả sử đồng đội thấy tải tăng và chạy một lệnh thủ công. Hãy tái hiện để thấy hậu quả:
kubectl scale deployment/gdev-api --replicas=5
kubectl get deployment gdev-api
Kết quả minh họa:
deployment.apps/gdev-api scaled
NAME READY UP-TO-DATE AVAILABLE AGE
gdev-api 5/5 5 5 9m
Có 5 bản chạy, nhưng gdev-api.yaml trong Git vẫn ghi replicas: 3. Một tuần sau pipeline gdev-ci chạy lại lệnh apply:
kubectl apply -f gdev-api.yaml
kubectl get deployment gdev-api
Kết quả minh họa:
deployment.apps/gdev-api configured
NAME READY UP-TO-DATE AVAILABLE AGE
gdev-api 3/3 3 3 1w
Triệu chứng là số bản tự giảm về 3 giữa lúc tải cao. Nguyên nhân: file YAML mới là trạng thái mong muốn, còn lệnh scale chỉ là một thay đổi tạm không được ghi lại ở đâu. Cách kiểm chứng là kubectl diff -f gdev-api.yaml trước khi apply, nó in ra các trường khác nhau giữa file và cluster. Cách sửa là đổi replicas trong file, commit rồi apply, hoặc giao việc co giãn theo tải cho HorizontalPodAutoscaler thay vì lệnh tay.
Dọn dẹp sau lab (giữ cluster cho các bài sau):
kubectl delete -f gdev-api.yaml
Lỗi thường gặp
| Triệu chứng | Nguyên nhân hay gặp | Cách kiểm chứng |
|---|---|---|
| Thay đổi tay biến mất sau lần deploy sau | Sửa trực tiếp trên cluster thay vì sửa file trong Git | kubectl diff -f file.yaml |
| Dùng Kubernetes cho hai service trên một máy | Chọn công cụ trước khi có bài toán | Đi lại sơ đồ quyết định ở trên |
| Kỳ vọng Kubernetes tự build và deploy code | Nhầm vai trò với CI/CD | Kubernetes chỉ nhận image đã build |
Tin rằng Pod Running là xong | Bỏ qua readiness và log ứng dụng | kubectl describe pod, kubectl logs |
| Cài mọi add-on ngay ngày đầu | Sợ thiếu, kết quả là quá nhiều thứ cần vá | Chỉ thêm khi gặp vấn đề thật |
Tránh hai thói quen cũ: dùng thẻ latest cho image (không biết đang chạy bản nào, rollback khó) và mọi thứ nằm chung namespace default (không tách quyền và quota theo môi trường).
Tự kiểm tra
Hãy trả lời trước khi đọc đáp án ngay bên dưới mỗi câu.
Câu 1. Bạn xóa một Pod của Deployment replicas: 3 và thấy một Pod mới xuất hiện sau vài giây. Ai đã tạo Pod đó, và dựa vào đâu?
Đáp án: Deployment controller tạo, dựa vào việc thấy số Pod thực tế (2) khác trạng thái mong muốn (3) lưu ở API server.
Câu 2. Một đội có hai service chạy trên một máy, chấp nhận vài phút gián đoạn khi bảo trì. Bạn khuyên gì, và vì sao?
Đáp án: Giữ Compose với restart policy và giám sát. Chưa có nhu cầu nhiều máy, tự phục hồi liên máy hay deploy không gián đoạn, nên chi phí vận hành Kubernetes lớn hơn lợi ích.
Câu 3. Câu "Kubernetes chạy Docker trên nhiều máy" sai ở những điểm nào?
Đáp án: Kubernetes điều khiển container qua CRI (containerd hoặc CRI-O), không qua Docker. Đơn vị quản lý là Pod và các đối tượng API khai báo, không phải lệnh docker run.
Nếu chỉ nhớ 3 điều
- Bạn khai báo trạng thái mong muốn, controller liên tục so sánh với thực tế rồi sửa chênh lệch.
- File mô tả trong Git là nguồn sự thật; lệnh sửa tay là thay đổi tạm sẽ bị ghi đè.
- Kubernetes giải bài toán nhiều máy, tự phục hồi và deploy an toàn; nếu chưa có các bài toán đó, chưa cần dùng.
Tài liệu chính thức: Tổng quan Kubernetes, Thành phần của Kubernetes và lịch phát hành.
Bài tiếp theo giới thiệu hệ sinh thái CNCF: những mảnh ghép nào quanh Kubernetes và khi nào mới cần đến chúng.