Skip to main content

5.2. Application Lifecycle Management - Multi-container Pods, Init Containers and Autoscaling

Mục lục


1. Multi-container Pods

Ý tưởng tách một application monolithic lớn thành các subcomponent nhỏ hơn, gọi là microservices, cho phép phát triển và deploy một tập hợp code độc lập, nhỏ gọn và có thể tái sử dụng. Kiến trúc này giúp scale up/down cũng như sửa đổi từng service theo nhu cầu, thay vì phải sửa đổi toàn bộ application.

Tuy nhiên, đôi khi cần hai service phối hợp cùng nhau, ví dụ một web server và một logging service. Với main app, cần một web server cho mỗi app instance, đi kèm với nhau. Không muốn gộp và làm phình to code của hai service này, vì mỗi service nhắm tới các tính năng khác nhau và vẫn muốn chúng được phát triển và deploy riêng biệt — chỉ cần hai chức năng này hoạt động cùng nhau. Cần một web server hoặc helper cho mỗi main app instance, đi kèm nhau để chúng có thể scale up/down cùng lúc.

Đó là lý do có multi-container Pod — các container chia sẻ cùng lifecycle, nghĩa là chúng được tạo ra cùng nhau và bị destroy cùng nhau. Chúng chia sẻ cùng network namespace, nghĩa là có thể tham chiếu tới nhau qua localhost. Và chúng có quyền truy cập vào cùng storage volume. Nhờ vậy, không cần thiết lập volume sharing hay service giữa các pod để cho phép chúng giao tiếp với nhau.

Để tạo một multi-container Pod, thêm thông tin container mới vào pod definition file. Nhớ rằng, section containers dưới section spec trong pod definition file là một array, và lý do nó là array chính là để cho phép nhiều container trong một Pod duy nhất. Ví dụ, thêm một container mới tên webapp làm container một, và một container khác tên main-app làm container hai. Đây là một ví dụ rất đơn giản; các phần sau trong khoá học sẽ xem xét những ví dụ thực tế hơn.


2. Các Pattern thiết kế Multi-container Pod

Có nhiều pattern khác nhau cho multi-container pod. Điều vừa thấy ở phần trước chính là co-located containers — dạng nguyên thuỷ của multi-container Pod. Đơn giản là hai container chạy trong một Pod, cả hai container đều được thiết kế để tiếp tục chạy trong suốt vòng đời của Pod. Pattern này thường dùng khi hai service phụ thuộc lẫn nhau.

Pattern thứ hai là initContainers, hay còn gọi là regular init container ngày nay. Pattern này dùng khi có các bước khởi tạo (initialization steps) cần thực hiện lúc Pod khởi động, trước cả main application. Ví dụ, đây có thể là một init container chờ một database sẵn sàng. Trước khi main application khởi động, init container hoàn thành công việc của nó rồi kết thúc, sau đó main application mới khởi động.

Pattern thứ ba gọi là sidecar container. Một sidecar container được thiết lập giống init container — sidecar khởi động trước, làm công việc của nó, nhưng thay vì kết thúc, nó tiếp tục chạy trong suốt vòng đời của Pod. Main application khởi động sau khi sidecar container khởi động. Pattern này hữu ích khi có một log shipper kiểu như vậy, cần chạy cùng main application — nó cần khởi động trước main app, nhưng tiếp tục chạy chừng nào main app còn chạy, rồi kết thúc sau khi main app kết thúc.

💡 Hình dung: co-located giống hai đồng nghiệp bước vào phòng họp cùng lúc, không ai chờ ai — bắt đầu làm việc song song ngay. Init container giống đội dọn dẹp tới trước giờ họp, sắp bàn ghế xong thì rời đi — buổi họp chính thức chỉ bắt đầu sau khi họ đã ra khỏi phòng. Sidecar giống một phiên dịch viên đi cùng suốt buổi họp: có mặt trước khi diễn giả chính bước vào, đứng cạnh trong suốt buổi, và chỉ rời đi sau khi diễn giả đã kết thúc phần trình bày.

Vậy có ba pattern; init container được phân biệt rõ ràng, vì ta biết nó khởi động rồi dừng, sau đó main app mới chạy. Nhưng pattern đầu tiên và cuối cùng — co-located containers và sidecar containers — có thể hơi gây nhầm lẫn. Vì sao lại có hai pattern nếu chúng phục vụ cùng mục đích? Ở cả hai trường hợp, các Pod đều chạy suốt vòng đời của chúng. Vậy khác biệt giữa cái đầu và cái thứ hai là gì?

Khác biệt chính là: với co-located containers (pattern đầu tiên), không có khả năng định nghĩa container nào khởi động trước và thứ tự khởi động đó. Cả hai container được định nghĩa như các phần tử trong một array — không có sự phân biệt giữa chúng. Mọi thứ khởi động cùng lúc, và không có đảm bảo cái nào khởi động trước cái nào. Nếu đây không phải yêu cầu thực sự cho use case của mình, có thể dùng lựa chọn này. Tuy nhiên, lựa chọn sidecar containers cung cấp khả năng chỉ định thứ tự khởi động, shutdown, và hoàn tất, rồi tiếp tục chạy trong suốt vòng đời Pod.

Bắt đầu với pattern đầu tiên — pod definition file cho co-located containers nguyên thuỷ. Section containers là một array, có hai phần tử, tức hai container được chỉ định trong array. Khi Pod được tạo, cả hai đều khởi động, không có thứ tự cụ thể, và tiếp tục chạy.

Tiếp theo là regular containers (init container). Thay vì thêm một container khác làm phần tử thứ hai trong list containers, ta định nghĩa một thuộc tính riêng biệt gọi là initContainers. Dưới đó, theo cùng pattern như với main container, có name, image, và command. Init container này mặc định chạy lúc bắt đầu và kết thúc quá trình chạy của nó. Trong ví dụ, một khi script wait-for-DB-to-start kết thúc, main container mới khởi động. Có thể định nghĩa nhiều init container, để chúng chạy tuần tự — ví dụ có thể định nghĩa thêm một init container khác làm phần tử thứ hai trong list, gọi là "API checker", chờ một API khác khởi động. Điều xảy ra là: đầu tiên container "DB checker" chạy, container đó chạy rồi kết thúc; sau đó container thứ hai trong Pod khởi động, "API checker" khởi động rồi kết thúc; rồi main application khởi động và tiếp tục chạy cho tới hết vòng đời Pod. Đó là cách định nghĩa thứ tự khởi động cho các init container.

Pattern thứ ba là sidecar container. Vẫn dùng phương pháp sidecar container — code vẫn ở đó, đảm bảo init container khởi động trước. Sau khi sẵn sàng, main app khởi động. Tuy nhiên, init container tiếp tục chạy vì nó có restartPolicy set là Always. Điều này đảm bảo init container không bị terminate cho tới sau khi main application dừng. Nhờ vậy, log shipper có thể ghi lại được cả log lúc khởi động lẫn lúc termination của main container.

Multi-container Pod: 3 pattern theo timelinePod startsteady statePod endCo-locatedwebappmain-appInit containerinit-containermain-appSidecarsidecarmain-apppeer containermain applicationsidecar (sống lâu hơn main)Sidecar = init container với restartPolicy Always — không kết thúc, chạy cùng main app

Một ví dụ thực tế hơn là dùng Elasticsearch và Kibana stack — đây là một framework thu thập và tổng hợp log, trong đó Elasticsearch là component thu thập log từ các endpoint/application khác nhau, còn Kibana là visualizer, một dashboard mà user hoặc administrator xem. Trong trường hợp này, cùng với app, thêm một sidecar gọi là Filebeat, đi kèm với Elasticsearch. Filebeat khởi động trước main app để có thể capture log lúc khởi động của application, và kết thúc sau khi main app kết thúc để capture bất kỳ log termination nào. Trong trường hợp application kết thúc do bug, termination log cần được capture để chẩn đoán. Và đó chính là điều đạt được nhờ một sidecar container.


3. Init Container và Sidecar Container

Trong một multi-container Pod, mỗi container được kỳ vọng chạy một process sống suốt vòng đời của Pod. Ví dụ, trong một Pod có web application và một logging agent, cả hai container đều được kỳ vọng hoạt động xuyên suốt vòng đời của Pod. Process trong container logging agent phải sống chừng nào web application còn chạy. Nếu một main container fail và restartPolicy của Pod là Always hoặc OnFailure, toàn bộ Pod sẽ được restart.

Hành vi restart Pod trong multi-container Pod. Trong một multi-container Pod, restartPolicy áp dụng ở cấp container, không phải cấp Pod. Nếu một container thoát, kubelet sẽ restart đúng container đó tại chỗ theo policy — các container khác trong Pod không bị ảnh hưởng và tiếp tục chạy.

  • Always (mặc định): restart container sau bất kỳ lần thoát nào, bất kể exit code.
  • OnFailure: chỉ restart khi exit code khác 0.
  • Never: không bao giờ restart.

Policy này áp dụng cho app container và regular init container. Sidecar container (init container có restartPolicy: Always ở cấp container) luôn restart bất kể restartPolicy của Pod là gì.

Kubernetes không restart toàn bộ Pod khi một container fail. Pod chỉ được recreate bởi controller của nó khi node chết hoặc Pod bị xoá.

Init Container là gì? Init container là một container đặc biệt, chạy trước các main container trong một Pod. Mỗi init container phải chạy thành công (exit 0) trước khi init container kế tiếp bắt đầu. Sau khi tất cả init container hoàn tất, các container thường (regular containers) khởi động đồng thời.

Chúng được cấu hình tương tự các container khác nhưng đặt trong section initContainers của pod spec.

Nếu bất kỳ init container nào fail, toàn bộ Pod sẽ được restart và tất cả init container sẽ chạy lại từ đầu.

Ví dụ dùng Init Containers:

apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
initContainers:
- name: init-myservice
image: busybox:1.31
command: ["sh", "-c", "until nslookup myservice; do echo waiting for myservice; sleep 2; done;"]
- name: init-mydb
image: busybox:1.31
command: ["sh", "-c", "until nslookup mydb; do echo waiting for mydb; sleep 2; done;"]
containers:
- name: myapp-container
image: busybox:1.28
command: ["sh", "-c", "echo The app is running! && sleep 3600"]

Native Sidecar Containers (Kubernetes 1.33+). Bắt đầu từ Kubernetes v1.33, sidecar container được hỗ trợ native. Điều này cho phép sidecar container tuân theo một lifecycle được định nghĩa rõ ràng so với các main container trong Pod — mà không cần các "entrypoint hack".

Cách native sidecar hoạt động:

  • Khai báo bằng field restartPolicy: Always bên trong block initContainers.
  • Kubernetes coi các container này là sidecar, đảm bảo chúng:
    • Khởi động trước các main container.
    • Chạy song song với main container.
    • Shutdown sau khi main container hoàn tất.

Ví dụ: cấu hình Native Sidecar:

apiVersion: v1
kind: Pod
metadata:
name: sidecar-example
spec:
initContainers:
- name: sidecar-logger
image: busybox:1.31
restartPolicy: Always
command: ["sh", "-c", "while true; do echo Sidecar running; sleep 10; done"]
containers:
- name: main-app
image: busybox:1.31
command: ["sh", "-c", "echo Main app starting; sleep 60"]

Trong setup này: container sidecar-logger hoạt động như một sidecar, dù được khai báo trong initContainers. Nó dùng restartPolicy: Always để sống suốt vòng đời Pod. Kubernetes khởi động sidecar trước, chờ nó ready, rồi mới khởi động main app.

Xem thêm tài liệu chính thức: Kubernetes Docs: Init ContainersKubernetes Docs: Native Sidecar Containers.


4. Lab: Thực hành Init Containers

Xác định pod có cấu hình init container. Kiểm tra các pod đang có — có ba pod: red, green, blue. Nhìn qua tưởng pod green (có hai container) là pod có init container, nhưng kiểm tra chi tiết từng pod bằng kubectl describe pod. Nếu không chỉ định tên pod, lệnh này liệt kê mô tả của tất cả pod. Với pod red: có init containers, có một container red. Với pod green: có hai init container — green-container-1green-container-2 — nhưng đây thực ra không phải init container, chúng chỉ là container thường (init container sẽ nằm trong một section riêng gọi là init containers, còn đây không phải). Với pod blue: có một section init containers riêng, và một section containers riêng — đây chính là pod có cấu hình init container. Lưu ý, output của lệnh kubectl get pods không hiển thị chi tiết init container — nó chỉ tính các container chính của pod. Đáp án: pod blue.

Image nào được dùng bởi container trên pod blue? kubectl describe pod — init container tên init-myservice, image là busybox.

Trạng thái của init container trên pod blue là gì? Vì sao? Trạng thái là Terminated, lý do là Completed. Container được cấu hình với image busybox và một command đơn giản chạy sleep trong shell — cụ thể là sleep 5. Khi Pod được tạo, init container này kích hoạt, sleep 5 giây rồi terminate. Sau đó các container chính mới được khởi động. Đó là setup đơn giản cho container này — command sleep hoàn tất thành công, exit code bằng 0. Process không crash, không thể nói process không start được — process hoàn tất thành công, đó là lý do exit code là 0.

Có một pod mới tên purple. Có bao nhiêu init container? Xem chi tiết pod — có section init container với hai item, tức là hai init container.

Trạng thái của pod là gì? Dưới output của kubectl get pods, cột STATUS cho thấy trạng thái Pending.

Sau khi tạo bao lâu thì application sẽ lên và sẵn sàng phục vụ user? Xem setup pod: pod có một container purple (chính là app), nhưng có hai init container — init container đầu tiên có command set sleep 600 giây, init container thứ hai có command set sleep 1200 giây. Cái đầu chạy trước, cái sau chạy tiếp theo — có thể chain nhiều init container như vậy nếu cần. 600 giây tương đương 10 phút, 1200 giây tương đương 20 phút. Tổng cộng, trước khi thực sự lên, các init container sẽ sleep tổng cộng 30 phút. Chỉ sau 30 phút pod mới lên. Đây là use case ví dụ đơn giản, nhưng trong thực tế đây có thể là bất cứ thứ gì — chuẩn bị database, chuẩn bị một số startup script, v.v. — những việc cần chạy trước khi application thực sự lên.

Update pod red để dùng một init container. Pod red hiện có một container, đang Running. Cần update để dùng một init container dùng image busybox, sleep 20 giây. Chạy kubectl edit pod red, thêm section initContainers bên cạnh section containers, với image: busybox, name: busybox, và command sleep 20 giây. Lưu lại — không được phép lưu trực tiếp, thoát ra và dùng file đã chỉnh sửa:

kubectl replace --force -f <file>

Chờ pod terminate và tạo lại.

Application orange mới được deploy, có gì đó sai, xác định và fix. Kiểm tra application orange — có một container, đang ở trạng thái Init:CrashLoopBackOff. Xem chi tiết pod: có init container tên init-myservice, và một container tên orange-container — một app đơn giản. Xem event, thấy Back-off restarting failed container — một trong các init container đang fail và bị restart liên tục. Trạng thái Init:CrashLoopBackOff cho biết chính init container đang fail, đó là lý do nó bị restart.

Kiểm tra log bằng kubectl logs, chỉ định pod orange — nhưng vì container chính vẫn đang chờ khởi động (do pod vẫn ở trạng thái initializing), cần xem log của init container. Dùng option -c kèm tên container, init-myservice:

kubectl logs orange -c init-myservice

Log báo sleep: not found — có typo trong command sleep. Xem cách init container được cấu hình, thấy section command có lệnh sleep nhưng bị gõ sai — đó chính là nguyên nhân fail. Trạng thái là Terminated, nhưng lần này lý do là Error (thay vì Completed như trước), exit code là 127, và nó cứ restart liên tục cho tới khi được fix.

Fix bằng kubectl edit pod orange, vào phần init containers, sửa lại typo, lưu lại (biết trước sẽ không cho lưu trực tiếp), rồi:

kubectl replace --force -f <file>

Kiểm tra lại trạng thái — init container giờ có command đúng, đã Terminated với lý do Completed, exit code 0. Kiểm tra trạng thái pod — giờ đang Running.


5. Self-Healing Applications

Kubernetes hỗ trợ self-healing application thông qua ReplicaSets và Replication Controllers. Replication controller giúp đảm bảo một POD được tự động tạo lại khi application bên trong POD crash. Nó giúp đảm bảo luôn có đủ replica của application đang chạy tại mọi thời điểm.

Kubernetes cũng cung cấp thêm khả năng kiểm tra sức khoẻ (health) của application đang chạy bên trong POD và thực hiện các hành động cần thiết thông qua LivenessReadiness Probes. Tuy nhiên, các probe này không nằm trong yêu cầu của kỳ thi CKA, nên không được đề cập ở đây — đây là chủ đề dành cho kỳ thi Certified Kubernetes Application Developers (CKAD) và được bàn trong khoá CKAD.


6. Giới thiệu Autoscaling

Trong các phần tiếp theo sẽ bàn về autoscaling. Xét theo góc độ kỳ thi, sẽ bàn về horizontal pod autoscaling (HPA) và vertical pod autoscaling (VPA). Vì đây chỉ là một trong nhiều chủ đề của kỳ thi CKA, nội dung sẽ được giữ ở mức nhẹ nhàng nhất có thể. Tuy nhiên, đây là một chủ đề rất rộng, và KodeKloud có hẳn một khoá học riêng cho nó tên Kubernetes Autoscaling — nên tham khảo thêm nếu cần.

Trước khi xem xét autoscaling, hãy xem scaling nghĩa là gì. Tạm gác Kubernetes và container sang một bên, quay lại với server vật lý và application host trên đó. Ngày xưa, khi host application trên server vật lý với dung lượng CPU và memory được định trước, điều gì xảy ra khi load tăng lên và hết tài nguyên hiện có trên server? Cần scale up server — tắt application, thêm tài nguyên CPU hoặc memory, rồi bật lại. Đó gọi là vertical scaling — tăng kích thước của server hiện có theo chiều dọc, bằng cách thêm nhiều component CPU và memory vào server hiện có.

Nếu application hỗ trợ chạy nhiều instance, một cách khác là tránh phải tắt server — thay vào đó, thêm nhiều server và chia sẻ tải giữa chúng. Chạy nhiều instance của application bằng cách thêm nhiều server được gọi là horizontal scaling. Tóm lại: vertical scaling nghĩa là thêm tài nguyên vào một application hiện có, còn horizontal scaling nghĩa là thêm nhiều instance hoặc nhiều server vào hệ thống.

Áp dụng vào Kubernetes và thế giới container: một trong những mục đích chính của một container orchestrator như Kubernetes là host application dưới dạng container và scale up/down theo nhu cầu. Có hai loại scaling trong Kubernetes. Điều vừa nói ở trên là scaling workloads — scale bằng cách thêm hoặc bớt container/pod trên cluster. Cách tiếp cận khác là scaling cluster infrastructure — thêm hoặc bớt server/hạ tầng cho cluster.

Với cả hai, đều có horizontal và vertical scaling. Với cluster: horizontal scaling nghĩa là thêm nhiều node vào cluster; vertical scaling nghĩa là tăng tài nguyên trên các node hiện có. Với workload: horizontal scaling nghĩa là tạo nhiều pod hơn; vertical scaling nghĩa là tăng tài nguyên phân bổ cho các pod hiện có.

Về cách thực hiện: có cách thủ công (manual) và cách tự động (automated). Cách thủ công để horizontally scale cluster infra là tự provision node mới rồi dùng lệnh kubeadm join để thêm node vào cluster. Với vertical scaling, không phải cách tiếp cận phổ biến cho Kubernetes, vì nó đòi hỏi phải tắt server và application đang chạy trên đó rồi thêm tài nguyên rồi bật lại — điều không mong muốn. Vì hầu hết hạ tầng ngày nay là máy ảo, có thể dễ dàng provision một server với tài nguyên cao hơn, thêm vào cluster, rồi gỡ bỏ server cũ — nên vertical scaling không phải cách tiếp cận phổ biến.

Với horizontal scaling workload theo cách thủ công, dùng lệnh kubectl scale trên workload để scale up/down số lượng pod. Để vertically scale tài nguyên gắn với một pod, thường dùng lệnh kubectl edit để vào Deployment, StatefulSet, hay ReplicaSet và thay đổi resource limits/requests gắn với pod.

Cuối cùng, đến cách tự động. Để horizontally scale infra, có Kubernetes Cluster Autoscaler. Để horizontally scale workload, có Horizontal Pod Autoscaler (HPA). Và để vertically scale workload, có Vertical Pod Autoscaler (VPA). Đó chính là chủ đề của các phần tiếp theo.


7. Horizontal Pod Autoscaler (HPA)

Trước tiên xem cách scale một workload horizontally theo cách thủ công. Với vai trò Kubernetes administrator, ngồi trước máy tính nhìn vào một cluster, được giao nhiệm vụ đảm bảo luôn đủ workload để đáp ứng nhu cầu cho application. Từ góc độ cấu hình deployment, pod này request 250 mCPU và có limit 500 mCPU — nghĩa là 500 mCore là tối đa pod nhận được, sau đó không nhận thêm nữa, và capacity một pod duy nhất có thể xử lý là 500m CPU.

Nếu làm thủ công, sẽ chạy lệnh kubectl top pod để theo dõi resource consumption của pod. Nhớ rằng phải có metrics-server chạy trên cluster để monitor resource usage như vậy — đây là một add-on riêng (không có sẵn mặc định trên mọi cluster), có nhiệm vụ định kỳ kéo số liệu CPU/memory từ kubelet trên từng node, tổng hợp lại rồi expose qua Metrics API; cả kubectl top lẫn HPA đều đọc số liệu từ đúng API này. Khi đạt ngưỡng khoảng 450 milicore hoặc gần đó (ngưỡng tự định nghĩa), sẽ chạy lệnh kubectl scale để scale deployment, thêm pod. Đó là cách scale workload thủ công.

Vấn đề với cách này: phải ngồi trước máy tính liên tục theo dõi resource usage, phải tự chạy lệnh để scale up/down, và nếu có traffic spike đột ngột trong lúc muốn nghỉ ngơi, có thể không phản ứng đủ nhanh để scale up kịp thời. Để giải quyết, dùng Horizontal Pod Autoscaler.

💡 Hình dung: làm thủ công bằng kubectl top/kubectl scale giống việc tự đứng canh đồng hồ đo điện trong nhà rồi tự tay bật/tắt cầu dao mỗi khi kim đồng hồ chạm ngưỡng. HPA giống lắp một bộ điều nhiệt tự động — nó tự đọc đồng hồ liên tục và tự bật thêm/tắt bớt thiết bị theo ngưỡng đã cài, không cần ai đứng canh.

HPA liên tục monitor các metric giống như làm thủ công bằng lệnh top. Sau đó tự động tăng hoặc giảm số lượng pod trong một Deployment, StatefulSet, hoặc ReplicaSet dựa trên CPU, memory, hoặc custom metric. Nếu CPU hoặc memory usage lên quá cao, HPA tạo thêm pod để xử lý; nếu giảm xuống, nó bớt pod dư để tiết kiệm tài nguyên, nhờ đó cân bằng ngưỡng. HPA cũng có thể theo dõi nhiều loại metric khác nhau.

Xem cách hoạt động trong thực tế: với một NGINX deployment cho trước, có thể cấu hình một Horizontal Pod Autoscaler bằng lệnh kubectl autoscale, target deployment myapp, chỉ định ngưỡng CPU 50% với tối thiểu 1 và tối đa 10 pod. Khi chạy lệnh này, Kubernetes tạo một HPA cho deployment, đầu tiên đọc limit cấu hình trên pod, biết được nó là 500 milicore. Sau đó liên tục poll metrics-server để monitor usage. Khi usage vượt quá 50%, nó điều chỉnh số replica để scale up hoặc down tuỳ usage.

Để xem trạng thái HPA đã tạo, chạy kubectl get hpa — liệt kê HPA hiện có. Cột TARGETS hiển thị CPU usage hiện tại so với ngưỡng đã set, cùng với số tối thiểu, tối đa và số replica hiện tại.

kubectl get hpa

# Output minh họa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
myapp-hpa Deployment/myapp 78%/50% 1 10 4 6m12s

(Output minh họa — cấu trúc và tên cột đúng theo lệnh thật, số liệu cụ thể chỉ mang tính ví dụ.) Cột TARGETS đang là 78%/50% nghĩa là CPU usage thực tế đã vượt ngưỡng 50%, nên HPA đã scale lên 4 replica (REPLICAS) để kéo usage trung bình xuống gần ngưỡng.

Nó sẽ không bao giờ vượt quá số tối đa khi scale up, và không xuống dưới số tối thiểu khi scale down. Khi không cần HPA nữa, có thể xoá bằng lệnh kubectl delete hpa.

HPA architecture: vòng lặp giám sát và tự scalePods(CPU/memory usage)metrics-server(in-memory aggregation)HPAtarget CPU: 50%min:1 / max:10Deploymentreplicas: 4metricsMetrics APIscale up/downcreates/removes PodChu trình giám sát → so sánh → scale lặp lại liên tục, không phải one-time action

Đó là cách tiếp cận imperative để tạo HPA. Cũng có cách declarative: tạo một HPA definition file với apiVersion set là autoscaling/v2. kindHorizontalPodAutoscaler. Tên là myapp-hpa. Sau đó có scaleTargetRef — resource target mà HPA sẽ monitor, ở đây là Deployment tên myapp. Cũng có minReplicas/maxReplicas được định nghĩa. Và có cấu hình metrics resource cần monitor — trong trường hợp này, resource là CPU và target utilization là 50%.

Lưu ý rằng HPA đi kèm sẵn trong Kubernetes (không cần cài đặt riêng như VPA) — API autoscaling/v2 dùng trong ví dụ trên đã graduate lên stable từ phiên bản 1.23, thay cho autoscaling/v1 cũ chỉ hỗ trợ CPU. Cũng lưu ý nó phụ thuộc vào metrics-server — đây là một điều kiện tiên quyết.

Về metrics-server: HPA phụ thuộc vào metrics-server để lấy số liệu resource utilization hiện tại. Ở đây đang nói tới internal metrics-server, nhưng cũng có các resource khác có thể tham chiếu tới, như một custom metrics adapter có thể lấy thông tin từ các nguồn nội bộ khác, ví dụ một workload deploy trong cluster — tuy nhiên vẫn là nguồn nội bộ. Cũng có thể tham chiếu tới các nguồn bên ngoài, như một tool hay instance nằm ngoài Kubernetes cluster, ví dụ Datadog hoặc Dynatrace instance, thông qua một external adapter. Tuy nhiên, những điều này nằm ngoài phạm vi khoá học này — chi tiết và bài lab về các nguồn này có trong khoá Kubernetes Auto Scaling.


8. Resize Pod tại chỗ (In-place Resize)

Trước khi bàn về Vertical Pod Autoscaler, nên tìm hiểu trước về in-place resize của Pod resource. Điều này nghĩa là gì? Tính tới thời điểm ghi hình bài này (Kubernetes version 1.32), nếu thay đổi resource requirement của một Pod trong Deployment, hành vi mặc định là xoá Pod hiện có rồi tạo mới một Pod với thay đổi mới. Nghĩa là mọi thay đổi trên resource definition của Pod không diễn ra tại chỗ — Pod phải bị kill và một Pod mới với resource definition mới sẽ được tạo. Đó là hành vi mặc định.

Đã biết điều này có thể gây gián đoạn (disruptive), đặc biệt với các workload stateful. Vì vậy có một cải tiến đang được phát triển gọi là in-place update of Pod resources. Đây là tính năng hiện đang ở giai đoạn alpha kể từ Kubernetes release 1.27, và không được bật mặc định. Khi nó tiến tới giai đoạn beta hoặc stable trong tương lai, nó sẽ được bật mặc định — nhưng tại thời điểm ghi hình, chưa phải vậy. Nó vẫn có sẵn như một trong các feature thuộc Kubernetes cluster, chỉ cần bật lên.

Để bật tính năng này, phải set feature flag InPlacePodVerticalScaling thành true. Sau khi bật, pod definition hỗ trợ một tập tham số resizePolicy, sẽ xem tiếp bên dưới.

Các option resizePolicy mới cho phép chỉ định một restart policy cho từng resource. Trong ví dụ dưới đây, đã định nghĩa rằng thay đổi CPU resource sẽ không yêu cầu restart Pod, còn thay đổi memory sẽ yêu cầu restart Pod:

apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
spec:
containers:
- name: myapp-container
image: myapp
resources:
requests:
cpu: "500m"
memory: "256Mi"
limits:
cpu: "1"
memory: "512Mi"
resizePolicy:
- resourceName: cpu
restartPolicy: NotRequired
- resourceName: memory
restartPolicy: RestartContainer

Tiếp theo, thực hiện thay đổi một resource, ví dụ update CPU lên 1 — có thể thấy Pod không cần bị kill, mà đơn giản được update với resource mới, kích thước được tăng lên. Điều này giải thích cách resize một CPU hoặc memory resource gắn với một container mà không thực sự restart nó. Tính năng này graduate lên beta (bật mặc định) từ Kubernetes 1.33, rồi lên stable (luôn bật, không cần feature flag) từ Kubernetes 1.35.

Đổi resource của Pod: mặc định vs in-place resizeHành vi mặc định (không bật in-place resize)Pod hiện có(cpu: 500m)kubectl edit(đổi cpu)Pod bị xoáPod mới tạo(cpu: 1)In-place resize (đã bật InPlacePodVerticalScaling)Pod hiện có(cpu: 500m)Resize resource(in-place)không xoá PodCùng 1 Pod(cpu: 1)Pod UID không đổi ở hàng dưới — vẫn đúng Pod đó, chỉ resource thay đổi tại chỗ

Nhớ rằng đây vẫn đang nói về việc scale một Pod thủ công — chưa thực sự nói về vertical pod autoscaler (sẽ bàn ở phần tiếp theo). Nhưng cũng cần lưu ý một số giới hạn của in-place resizing:

  • Chỉ hoạt động với resource CPU và memory. Pod QoS class — hạng ưu tiên Kubernetes gán cho Pod dựa trên cách set requests/limits (Guaranteed nếu requests bằng limits, Burstable nếu có requests nhưng thấp hơn limits, BestEffort nếu không set gì), quyết định Pod nào bị evict trước khi node thiếu tài nguyên — không thể thay đổi bằng in-place resize.
  • Init container và ephemeral container — loại container tạm thời thêm vào một Pod đang chạy để debug (qua kubectl debug), không có trong pod spec gốc và không tự khởi động lại — không thể resize bằng cách này.
  • Resource requests và limits không thể được sửa đổi sau khi đã set.
  • Memory limit của một container có thể không giảm xuống dưới mức usage hiện tại. Nếu một lần resize khiến container rơi vào trạng thái này, trạng thái resize sẽ ở mức InProgress cho tới khi memory limit mong muốn trở nên khả thi.
  • Cuối cùng, Windows Pod hiện không thể resize.

9. Vertical Pod Autoscaler (VPA)

Trước tiên xem cách scale một workload vertically theo cách thủ công. Với vai trò Kubernetes administrator, nhìn vào một cluster, được giao nhiệm vụ đảm bảo luôn đủ workload để đáp ứng nhu cầu. Từ cấu hình Deployment, pod này request 250 mCPU và có limit 500 mCPU — nghĩa là 500 mCPU là capacity tối đa nó nhận được. Như đã làm trước đó, sẽ chạy lệnh kubectl top pod để monitor resource consumption của pod (vẫn cần metrics server chạy trên cluster, tương tự trước). Khi đạt tới một ngưỡng cụ thể, để scale pod vertically, chạy lệnh kubectl edit deployment — thay đổi resource requests và limits gắn với deployment, dưới pod template, dưới section container, rồi lưu lại. Điều xảy ra là Pod hiện tại sẽ bị kill và một Pod mới được tạo. Đó là cách làm thủ công.

Tất nhiên không muốn làm thủ công — dùng Vertical Pod Autoscaler cho việc này. Tương tự HPA, VPA liên tục monitor metric rồi tự động tăng/giảm resource gắn với các pod trong deployment, nhờ đó cân bằng workload.

Khác với HPA, Vertical Pod Autoscaler không đi kèm sẵn — phải tự deploy nó. Trước tiên, apply VPA definition file có sẵn trong GitHub repo. Sau đó, khi chạy lệnh kubectl get pods trong namespace kube-system và tìm VPA, sẽ thấy nhiều component được deploy: admission controller, recommender, và updater service.

VPA deployment gồm nhiều component: VPA admission controller, updater, và recommender.

💡 Hình dung: ba component VPA giống một đội bảo trì tòa nhà. Recommender là kỹ sư đi đo đạc và viết báo cáo đề xuất ("phòng này cần thêm công suất điều hòa X") nhưng không tự ý lắp đặt gì. Updater là người quản lý — đọc báo cáo đó, thấy phòng nào đang dùng thiết bị dưới chuẩn thì cho ngừng hoạt động phòng đó để thay mới. Admission controller là đội lắp đặt đứng chờ sẵn ở cửa: bất cứ phòng nào vừa mở ra (Pod mới tạo), họ lắp ngay đúng thiết bị theo báo cáo đã đo, không cần đợi kỹ sư quay lại đo lần nữa.

  • Recommender: liên tục monitor resource usage từ Kubernetes Metrics API, thu thập dữ liệu usage lịch sử và trực tiếp cho các pod, sau đó đưa ra khuyến nghị (recommendation) về giá trị CPU và memory tối ưu. Recommender không tự sửa pod trực tiếp — nó chỉ đề xuất thay đổi.
  • Updater: phát hiện các pod đang chạy với resource dưới mức tối ưu và evict chúng khi cần update. Nó lấy thông tin từ recommender và monitor pod, nếu pod cần được update thì evict nó — nghĩa là terminate pod đó.
  • Admission controller: can thiệp vào quá trình tạo pod, dùng recommendation từ recommender để mutate pod spec, áp dụng giá trị CPU/memory được khuyến nghị lúc startup. Điều này đảm bảo pod mới tạo khởi động với đúng resource request cần thiết.
VPA: 3 component phối hợp giám sát và điều chỉnh resourceKubernetes Metrics APImonitor usageUpdater(evict Pod cũ)Recommender(đề xuất CPU/mem)Admission Controller(mutate Pod mới)đề xuấtđề xuấtevict nếu vượt ngưỡngmutate specPod đang chạy(resource cũ)Pod mới tạo(resource khuyến nghị)Recommender chỉ đề xuất — Updater và Admission Controller mới thực sự thay đổi Pod

Tóm lại: VPA recommender thu thập thông tin, updater monitor hoặc lấy thông tin từ recommender, so sánh với Pod thực tế — nếu pod vượt ngưỡng, nó kill pod. Việc có kill hay không phụ thuộc vào policy sẽ bàn ngay sau đây. Nhưng thông thường nó sẽ kill pod, sau đó admission controller can thiệp vì khi một pod bị kill, Deployment sẽ tự động recreate pod — và khi đó, admission controller can thiệp và update resource để pod mới lên với kích thước mới.

Cách tạo VPA resource bằng definition file: không có lệnh imperative để tạo VPA vì nó không phải component built-in như HPA. apiVersionautoscaling.k8s.io/v1. kindVerticalPodAutoscaler. Tên là MyAppVPA. Target cần monitor là deployment MyApp. containerPolicies định nghĩa những gì được monitor — trong đó minAllowedmaxAllowed cho CPU được cấu hình. Cuối cùng có updatePolicy.mode.

Có bốn mode VPA hoạt động:

  • Off: chỉ đưa ra khuyến nghị mà không làm gì cả — chỉ recommender hoạt động, updater và admission controller không hoạt động.
  • Initial: chỉ thay đổi pod lúc tạo, không sau đó. Recommender đưa ra recommendation, và khi Deployment được scale vì lý do khác và pod mới lên, admission controller can thiệp và thay đổi resource definition của pod. Updater trong mode này không hoạt động để kill hay take down pod.
  • Recreate: updater component can thiệp và evict pod hiện có khi resource consumption vượt khoảng đã chỉ định.
  • Auto: update pod hiện có tới các con số được khuyến nghị. Hành vi này giống hệt Recreate — nếu một pod vượt khoảng cho phép, nó bị kill và tạo lại.

Mode Auto từng được kỳ vọng sẽ tự chuyển sang dùng in-place update một khi tính năng đó khả dụng ở bản stable của Kubernetes, thay vì phải kill pod như Recreate. Nhưng hướng phát triển thực tế không đi theo lối đó: Auto đã bị deprecated trong VPA, giờ chỉ còn là alias của Recreate, không có hành vi đặc biệt nào thêm. Thay vào đó, một mode mới hoàn toàn ra đời để làm đúng việc mà Auto từng được kỳ vọng — InPlaceOrRecreate: VPA thử update resource của pod tại chỗ trước (không kill pod, dùng chính cơ chế in-place resize đã nói ở phần trước), chỉ fallback về evict/recreate nếu không update tại chỗ được cho thay đổi resource đó.

VPA sẽ monitor resource usage và đề xuất điều chỉnh — có thể kiểm tra recommendation bằng lệnh kubectl describe vpa theo sau là tên VPA.

kubectl describe vpa myapp-vpa

# Output minh họa (rút gọn)
Status:
Recommendation:
Container Recommendations:
Container Name: myapp-container
Lower Bound:
Cpu: 500m
Memory: 256Mi
Target:
Cpu: 1500m
Memory: 512Mi
Upper Bound:
Cpu: 2
Memory: 1Gi

(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, số liệu cụ thể chỉ mang tính ví dụ.) Field Target chính là con số VPA khuyến nghị nên set — ở đây đề xuất tăng CPU lên 1.5 core so với mức đang request.

So sánh HPA và VPA — khi nào dùng cái nào?

  • Cách scale: VPA tăng CPU và memory cho pod hiện có, hoặc recreate pod đó với resource mới; HPA thêm/bớt pod theo nhu cầu. VPA tối ưu hiệu năng của từng pod riêng lẻ, còn HPA tập trung phân phối tải trên nhiều instance.
  • Hành vi Pod: VPA restart pod để áp dụng giá trị resource mới, nghĩa là có downtime khi scale up vertically. Ngược lại, HPA giữ nguyên các Pod hiện có đang chạy và chỉ đơn giản đưa thêm pod mới lên, đảm bảo tính khả dụng liên tục.
  • Xử lý traffic spike: HPA rõ ràng thắng thế — nó ngay lập tức thêm pod khi nhu cầu tăng, phù hợp cho application cần scale nhanh. VPA, ngược lại, không thể xử lý spike đột ngột hiệu quả vì thường phải restart pod, gây độ trễ.
  • Tối ưu chi phí: cả hai đều có ưu điểm riêng. VPA ngăn over-provisioning bằng cách điều chỉnh CPU/memory allocation khớp với usage thực tế. HPA tránh các pod idle không cần thiết, đảm bảo tài nguyên được dùng hiệu quả mà không giữ dư thừa instance.

Khi nào nên dùng cái nào? VPA phù hợp nhất cho stateful workload và các application nặng CPU/memory như database, ứng dụng JVM, AI, và các workload cần fine-tune resource. Ví dụ, một số application cần nhiều CPU lúc khởi động ban đầu nhưng sau đó không cần nhiều CPU nữa — có thể khởi động instance với nhiều CPU, và sau khi quá trình khởi tạo hoàn tất, khi cần giảm CPU/memory, đó là lúc VPA phát huy tác dụng. HPA lý tưởng cho web application, microservice, và stateless service như web server, message queue, và application dựa trên API cần scale nhanh để xử lý traffic biến động.

Tóm lại: VPA là về tối ưu resource allocation cho từng Pod riêng lẻ, còn HPA là về scale số lượng Pod động theo nhu cầu. Chọn autoscaler phù hợp phụ thuộc vào loại workload và cách cần scale application.


Nguồn tham khảo

Nguồn gốc: Khóa "Certified Kubernetes Administrator (CKA)" — phần "Application Lifecycle Management" (Multi-container Pods; các Pattern thiết kế Multi-container Pod; Init Container và Sidecar Container; Self-Healing Applications; Autoscaling — Horizontal Pod Autoscaler, In-place Resize, Vertical Pod Autoscaler), nền tảng KodeKloud. Giảng viên: Mumshad Mannambeth.

Repo ghi chú, link tài liệu, và đáp án các practice question của toàn bộ khóa học: kodekloudhub/certified-kubernetes-administrator-course.

Fact-check:

  • Native sidecar container (restartPolicy: Always trong initContainers): alpha từ v1.28, beta + bật mặc định từ v1.29, GA/stable từ v1.33 — 2026-09-13, Kubernetes: Native Sidecar Containers
  • In-place pod resize (InPlacePodVerticalScaling): alpha từ v1.27, beta + bật mặc định từ v1.33, stable (luôn bật) từ v1.35 — 2026-09-13, Kubernetes v1.33 blog: In-Place Pod Resize Beta
  • HPA (tính năng) có từ rất sớm trong Kubernetes; API autoscaling/v2 (hỗ trợ memory/custom metrics) graduate lên stable từ v1.23 — 2026-09-13, Kubernetes: Horizontal Pod Autoscaling
  • VPA updateMode: Auto đã deprecated, hiện là alias của Recreate; mode InPlaceOrRecreate là mode mới thử update resource tại chỗ trước khi fallback evict/recreate — 2026-09-13, Kubernetes: Vertical Pod Autoscaling