Mở đầu — vì sao bài này quan trọng
Nếu bạn làm Operations trong một công ty công nghệ hiện đại, gần như chắc chắn bạn sẽ chạm tay vào Kubernetes (thường viết tắt là K8s). Từ các startup fintech ở TP.HCM đến những ông lớn thương mại điện tử như Tiki, Shopee hay Grab, Kubernetes đã trở thành lớp nền tảng để chạy hàng trăm, thậm chí hàng nghìn container một cách có kiểm soát. Nhưng đây cũng chính là chỗ khiến nhiều kỹ sư Ops mới vào nghề "ngợp": Kubernetes có rất nhiều khái niệm, thuật ngữ lạ, và nếu không hiểu đúng bản chất, bạn sẽ thao tác theo kiểu học vẹt — copy lệnh từ Stack Overflow rồi cầu nguyện nó chạy.
Bài học này tập trung vào ba resource nền tảng nhất mà bất kỳ ai vận hành K8s cũng phải nắm vững: Pod, Deployment, và Service. Đây là bộ ba "xương sống" — hiểu chúng, bạn hiểu được cách một ứng dụng thực sự sống, được nhân bản, và được truy cập bên trong cụm Kubernetes. Tôi cố ý giữ phạm vi ở mức operations cơ bản: mục tiêu của bạn sau bài này không phải là trở thành chuyên gia Kubernetes, mà là có thể tự tin đọc một manifest YAML, deploy một ứng dụng, kiểm tra tình trạng của nó, và xử lý những sự cố cơ bản. Những chủ đề nâng cao hơn như GitOps, service mesh hay auto-scaling sẽ được các bài khác trong khóa học đảm nhận. Ở đây, chúng ta xây móng cho chắc.
Khái niệm cốt lõi
Trước khi đi vào từng resource, hãy nắm một tư duy nền tảng: Kubernetes là hệ thống khai báo (declarative). Bạn không ra lệnh "hãy khởi động container này" theo kiểu mệnh lệnh (imperative). Thay vào đó, bạn mô tả trạng thái mong muốn (desired state) — ví dụ "tôi muốn có 3 bản sao của ứng dụng này chạy liên tục" — và Kubernetes tự động làm mọi thứ để đưa thực tế về đúng trạng thái đó. Nếu một container chết, K8s tự tạo lại. Đây là điểm khác biệt căn bản so với việc bạn tự chạy docker run trên một server.
Pod — đơn vị nhỏ nhất
Pod là đơn vị triển khai nhỏ nhất mà Kubernetes quản lý. Một điều nhiều người hiểu sai: Pod không phải là container. Pod là một lớp bọc quanh một hoặc nhiều container chia sẻ chung tài nguyên.
Cụ thể, các container trong cùng một Pod chia sẻ:
- Network namespace: chúng có chung địa chỉ IP và cổng. Container A trong Pod có thể gọi container B qua
localhost. Điều này giải thích tại sao pattern "sidecar" (ví dụ một container ứng dụng chính + một container thu thập log chạy kèm) lại phổ biến. - Storage volumes: các volume được mount vào Pod có thể được nhiều container cùng truy cập.
Điểm quan trọng về mặt Ops: Pod là thứ phù du (ephemeral). Nó có thể chết bất cứ lúc nào — do node bị lỗi, do bạn scale down, do rolling update. Mỗi lần Pod được tạo lại, nó nhận một địa chỉ IP mới. Vì vậy, bạn không bao giờ trỏ ứng dụng khác trực tiếp vào IP của một Pod. Đây chính là lý do Service tồn tại (ta sẽ nói ở dưới).
Vì Pod phù du như vậy, bạn cũng gần như không nên tạo Pod "trần" (bare Pod) trong môi trường production. Nếu bạn kubectl run ra một Pod đơn lẻ và nó chết, sẽ không có gì tạo lại nó. Đó là lý do ta cần Deployment.
Deployment — quản lý vòng đời của Pod
Deployment là resource bạn sẽ dùng nhiều nhất khi vận hành ứng dụng stateless (không lưu trạng thái cục bộ) — tức phần lớn các web service, API backend.
Deployment quản lý một thứ gọi là ReplicaSet, và ReplicaSet đảm bảo luôn có đúng số lượng Pod bạn yêu cầu đang chạy. Chuỗi phân cấp là: Deployment → ReplicaSet → Pod. Bạn khai báo replicas: 3, Deployment tạo một ReplicaSet, ReplicaSet duy trì đúng 3 Pod. Một Pod chết? ReplicaSet lập tức tạo Pod mới thay thế.
Sức mạnh lớn nhất của Deployment nằm ở rolling update. Khi bạn đổi image từ v1 sang v2, Deployment không tắt hết Pod cũ rồi mới bật Pod mới (điều đó gây downtime). Thay vào đó, nó thay thế từng phần: tạo Pod mới, chờ Pod mới sẵn sàng (healthy), rồi mới xóa Pod cũ, lặp lại cho đến khi toàn bộ đã lên phiên bản mới. Hai tham số điều khiển tốc độ này là maxSurge (số Pod thừa được phép tạo trong quá trình update) và maxUnavailable (số Pod được phép tạm ngưng).
Một tính năng vàng cho Ops: rollback. Nếu bản deploy mới lỗi, bạn chỉ cần kubectl rollout undo deployment/tên-app là quay về phiên bản trước, vì Deployment lưu lịch sử các ReplicaSet.
Service — địa chỉ ổn định để truy cập Pod
Như đã nói, Pod có IP thay đổi liên tục. Vậy làm sao frontend gọi được backend khi backend gồm nhiều Pod luôn thay đổi? Câu trả lời là Service.
Service cung cấp một địa chỉ IP ảo ổn định (ClusterIP) và một tên DNS cố định, đứng trước một nhóm Pod. Nó dùng cơ chế label selector để biết Pod nào thuộc về mình — ví dụ Service chọn tất cả Pod có nhãn app: backend. Khi có request đến Service, nó tự động cân bằng tải (load balance) đến các Pod đang khỏe mạnh.
Có mấy loại Service chính bạn cần biết:
- ClusterIP (mặc định): chỉ truy cập được từ bên trong cụm. Dùng cho giao tiếp nội bộ giữa các service.
- NodePort: mở một cổng trên mọi node, cho phép truy cập từ bên ngoài. Thường chỉ dùng để test.
- LoadBalancer: tạo một load balancer thật của nhà cung cấp cloud (ví dụ ELB của AWS, hoặc Load Balancer của Google Cloud) để expose ra internet.
Tình huống thực tế
Ví dụ 1: Startup fintech ở TP.HCM và bài học "đừng trỏ vào IP Pod"
Một công ty fintech có khoảng 40 nhân sự chuyển hệ thống thanh toán lên Kubernetes trên EKS (AWS). Đội backend viết một service notification gọi sang service wallet. Ban đầu, để cho nhanh, một bạn dev hardcode địa chỉ IP của Pod wallet vào config của notification.
Mọi thứ chạy tốt trong hai tuần. Rồi một đêm, node chứa Pod wallet bị AWS thu hồi (spot instance). Kubernetes tạo lại Pod wallet trên node khác với IP hoàn toàn mới. Ngay lập tức, toàn bộ thông báo giao dịch ngừng hoạt động — notification vẫn cố gọi vào IP cũ đã chết. Đội Ops mất gần 45 phút mới lần ra nguyên nhân vì log chỉ báo "connection refused".
Bài học: Pod là phù du, IP của nó không phải là hợp đồng ổn định. Giải pháp đúng là để notification gọi qua DNS của Service — ví dụ http://wallet.default.svc.cluster.local. Sau khi sửa, dù Pod wallet bị tạo lại bao nhiêu lần, Service vẫn định tuyến chính xác. Đây là lỗi kinh điển của người mới, và nó dạy cho cả đội rằng: trong K8s, luôn giao tiếp qua Service, không bao giờ qua Pod IP.
Ví dụ 2: Sàn thương mại điện tử và cú rolling update suýt gây sự cố
Một sàn TMĐT khu vực Đông Nam Á chạy service product-catalog với replicas: 12 để chịu tải mùa sale. Trong một lần deploy phiên bản mới, đội Ops cấu hình rolling update nhưng quên thiết lập readiness probe cho Pod.
Vấn đề là ứng dụng cần khoảng 15 giây để khởi động (load cache sản phẩm vào bộ nhớ). Không có readiness probe, Kubernetes coi Pod là "sẵn sàng" ngay khi container chạy, và Service lập tức gửi traffic vào. Kết quả: trong lúc rolling update, khách hàng gặp lỗi 502 rải rác vì Service định tuyến request tới các Pod mới còn đang load cache, chưa phục vụ được.
Con số cụ thể: trong 4 phút của quá trình deploy, tỷ lệ lỗi tăng từ 0,1% lên 3,8%, tương đương khoảng vài nghìn request thất bại trong giờ cao điểm.
Bài học: Rolling update chỉ "zero-downtime" khi Kubernetes biết chính xác lúc nào một Pod thực sự sẵn sàng nhận traffic. Readiness probe chính là tín hiệu đó. Sau sự cố, đội thêm một readinessProbe kiểm tra endpoint /healthz (chỉ trả về OK khi cache đã load xong), và những lần deploy sau đó mượt mà, không còn lỗi 502.
Ví dụ 3: Đội DevOps ở Hà Nội và bài học về resource limits
Một công ty SaaS ở Hà Nội chạy nhiều service trên cùng một cụm K8s dùng chung. Một service phân tích dữ liệu bị memory leak, và vì Deployment của nó không đặt resource limits, Pod này ngốn hết RAM của node. Kết quả là các Pod khác trên cùng node bị OOMKilled (bị hệ thống giết vì hết bộ nhớ) một cách dây chuyền, kéo theo cả một service thanh toán không liên quan gì bị sập.
Bài học: Trong môi trường multi-tenant (nhiều team dùng chung cụm), luôn khai báo resources.requests và resources.limits cho mỗi container trong Deployment. requests giúp scheduler đặt Pod vào node phù hợp, còn limits ngăn một Pod "ăn hại" tài nguyên của cả node. Đây là kỷ luật vận hành bắt buộc, không phải tùy chọn.
Hướng dẫn từng bước
Hãy đi qua quy trình deploy một ứng dụng web đơn giản với đầy đủ ba resource.
Bước 1 — Viết Deployment manifest. Tạo file deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web
image: myregistry/web-app:v1
ports:
- containerPort: 8080
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
Chú ý ba điểm quan trọng đã học ở trên: replicas: 3 (Deployment duy trì 3 Pod), khối resources (giới hạn tài nguyên), và readinessProbe (đảm bảo rolling update an toàn). Label app: web-app là mấu chốt để Service tìm được các Pod này.
Bước 2 — Apply Deployment. Chạy kubectl apply -f deployment.yaml. Kubernetes tạo ReplicaSet và 3 Pod.
Bước 3 — Kiểm tra trạng thái. Dùng kubectl get pods để xem các Pod. Cột STATUS phải là Running và READY phải là 1/1. Nếu bạn thấy Pending, CrashLoopBackOff hay ImagePullBackOff, đó là dấu hiệu cần điều tra (xem phần lỗi thường gặp).
Bước 4 — Tạo Service. Tạo file service.yaml:
apiVersion: v1
kind: Service
metadata:
name: web-app
spec:
selector:
app: web-app
ports:
- port: 80
targetPort: 8080
type: ClusterIP
selector: app: web-app khớp với label của Pod. port: 80 là cổng Service lắng nghe, targetPort: 8080 là cổng của container.
Bước 5 — Apply và kiểm tra Service. Chạy kubectl apply -f service.yaml, rồi kubectl get svc để xem ClusterIP được cấp. Dùng kubectl get endpoints web-app để xác nhận Service đã "thấy" đúng 3 Pod — nếu danh sách endpoints trống, chắc chắn selector không khớp label.
Bước 6 — Thực hiện rolling update. Khi có phiên bản mới: kubectl set image deployment/web-app web=myregistry/web-app:v2. Theo dõi tiến trình bằng kubectl rollout status deployment/web-app. Nếu có vấn đề: kubectl rollout undo deployment/web-app để quay về ngay.
Bước 7 — Điều tra khi cần. Ba lệnh Ops bạn sẽ dùng hằng ngày: kubectl describe pod <tên> (xem sự kiện và lý do lỗi), kubectl logs <tên-pod> (xem log ứng dụng), và kubectl get events --sort-by=.lastTimestamp (xem toàn bộ sự kiện gần nhất của namespace).
Lỗi thường gặp & mẹo
Lỗi 1 — Selector của Service không khớp label của Pod. Đây là lỗi phổ biến nhất. Service tạo thành công nhưng không route được request nào. Mẹo: luôn chạy kubectl get endpoints <service> để xác nhận. Endpoints trống = selector sai.
Lỗi 2 — Quên readiness probe, gây lỗi khi rolling update. Như ví dụ sàn TMĐT ở trên. Mẹo: mọi service phục vụ traffic đều phải có readiness probe. Đừng nhầm với liveness probe — readiness quyết định có nhận traffic không, liveness quyết định có restart Pod không.
Lỗi 3 — Không đặt resource requests/limits. Gây tranh chấp tài nguyên và OOMKilled dây chuyền. Mẹo: luôn đặt cả hai. Đặt requests hợp lý để scheduler bố trí tốt, đặt limits để giới hạn thiệt hại.
Lỗi 4 — Tạo bare Pod trong production. Pod trần không tự phục hồi khi chết. Mẹo: luôn dùng Deployment cho ứng dụng stateless.
Lỗi 5 — Đọc sai trạng thái Pod. CrashLoopBackOff nghĩa là container khởi động rồi chết liên tục — thường do bug ứng dụng hoặc config sai; hãy xem kubectl logs. ImagePullBackOff nghĩa là không kéo được image — thường do sai tên image hoặc thiếu credential registry. Pending nghĩa là chưa có node nào đủ tài nguyên để đặt Pod — thường do requests quá lớn hoặc cụm hết chỗ.
Mẹo tổng quát: Dùng kubectl apply (declarative) thay vì kubectl create (imperative) để có thể quản lý manifest bằng Git. Và luôn đặt manifest YAML vào version control — đây là nền tảng cho các thực hành nâng cao hơn về sau.
Bài tập thực hành
- Deploy cơ bản: Trên một cụm local (minikube hoặc kind), viết và apply một Deployment chạy image
nginxvớireplicas: 3. Dùngkubectl get podsxác nhận đủ 3 Pod ở trạng thái Running.
- Kiểm chứng tính tự phục hồi: Xóa thủ công một Pod bằng
kubectl delete pod <tên>. Chạy lạikubectl get podsvà quan sát — bao lâu thì một Pod mới xuất hiện thay thế? Giải thích tại sao điều này xảy ra (gợi ý: ReplicaSet).
- Tạo Service và kiểm tra endpoints: Tạo một Service ClusterIP trỏ vào Deployment trên. Cố tình đặt sai selector (ví dụ
app: nginx-wrong), apply, rồi chạykubectl get endpoints. Quan sát danh sách trống. Sau đó sửa lại selector cho đúng và xác nhận endpoints hiện đủ 3 địa chỉ Pod.
- Rolling update và rollback: Cập nhật image nginx sang một phiên bản khác bằng
kubectl set image. Theo dõi bằngkubectl rollout status. Sau đó thực hiệnkubectl rollout undovà xác nhận đã quay về phiên bản cũ. Viết ra 2-3 câu mô tả điều bạn quan sát được về thứ tự Pod bị thay thế.
Tóm tắt
Ba resource nền tảng của Kubernetes tạo thành một bộ ba gắn kết chặt chẽ. Pod là đơn vị triển khai nhỏ nhất, bọc quanh một hoặc nhiều container chia sẻ network và storage — nhưng nó phù du, IP thay đổi, nên không bao giờ được trỏ trực tiếp vào. Deployment đứng trên, quản lý ReplicaSet để luôn duy trì đúng số Pod mong muốn, cung cấp khả năng tự phục hồi, rolling update không downtime, và rollback nhanh gọn. Service dùng label selector để gom các Pod lại và cấp một địa chỉ IP cùng tên DNS ổn định, đồng thời cân bằng tải giữa các Pod khỏe mạnh.
Tư duy cốt lõi cần khắc sâu: Kubernetes là hệ thống khai báo — bạn mô tả trạng thái mong muốn, nó lo phần còn lại. Ba kỷ luật vận hành quan trọng nhất từ các tình huống thực tế là: luôn giao tiếp qua Service chứ không qua Pod IP, luôn cấu hình readiness probe để rolling update thực sự an toàn, và luôn đặt resource requests/limits để tránh sự cố dây chuyền. Nắm chắc ba resource này, bạn đã có nền tảng vững để tiến tới những chủ đề vận hành K8s nâng cao hơn trong các bài sau.