Skip to main content

3.2. Scheduling: Resource Requirements, DaemonSets and Static Pods

Mục lục​


1. Resource Requirements và Limits​

Bắt đầu với một Kubernetes cluster ba node. Mỗi node có một tập hợp tài nguyên CPU và memory khả dụng. Mỗi pod đòi hỏi một tập hợp tài nguyên để chạy — ví dụ, pod này cần 2 CPU và 1 đơn vị memory. Khi một pod được đặt lên một node, nó tiêu thụ tài nguyên khả dụng trên node đó. Chính kube-scheduler là thành phần quyết định pod nào đi đến node nào. Scheduler xem xét lượng tài nguyên pod cần và tài nguyên khả dụng trên các node để xác định node phù hợp nhất.

Trong trường hợp này, scheduler lập lịch pod mới lên node 2 vì có đủ tài nguyên khả dụng trên node đó. Nếu các node không có đủ tài nguyên khả dụng, scheduler tránh đặt pod lên những node đó và thay vào đó đặt pod lên node có đủ tài nguyên. Và nếu không có đủ tài nguyên trên bất kỳ node nào, scheduler sẽ giữ pod lại, không lập lịch, và sẽ thấy pod ở trạng thái Pending. Nếu xem các event bằng lệnh kubectl describe pod, sẽ thấy thông báo insufficient CPU.

Scheduler Places Pod by Available ResourcesNew Podneeds 2 CPUnot enough CPUfits hereNode 11 CPU freeOther Pods usingmost of the CPUNode 23 CPU freeNew Pod lands hereNode 30.5 CPU freeToo littleCPU leftScheduler compares Pod's resource request against what each Node has free.

Resource requests​

Có thể chỉ định lượng CPU và memory cần cho một pod khi tạo — ví dụ 1 CPU và 1 GiB memory. Đây được gọi là resource request cho một container — tức lượng CPU hoặc memory tối thiểu mà container yêu cầu. Khi scheduler cố đặt pod lên một node, nó dùng các con số này để xác định node có đủ tài nguyên khả dụng.

💡 Hình dung: Giống việc đặt bàn cố định tại một quán cà phê học nhóm — request là chiếc bàn luôn thuộc về người đặt dù người đó có ngồi kín chỗ hay không, quán phải giữ trống sẵn trước khi nhận thêm khách khác. Còn limit (sẽ nói ở phần sau) là quy định được phép kéo ghế lấn sang bao nhiêu không gian trống xung quanh nếu quán chưa đông — hào phóng khi quán vắng, nhưng không bao giờ vượt quá giới hạn đó dù quán có vắng đến đâu.

Trong pod definition file mẫu, chỉ cần thêm một section gọi là resources, dưới đó thêm requests và chỉ định giá trị cần cho memory và CPU:

resources:
requests:
memory: "4Gi"
cpu: 2

Khi scheduler nhận request đặt pod này, nó tìm một node có đủ lượng tài nguyên này. Và khi pod được đặt lên một node, pod nhận được lượng tài nguyên được đảm bảo (guaranteed) cho nó.

Vậy một count CPU thực sự nghĩa là gì? Có thể chỉ định giá trị thấp nhất là 0.1 CPU. 0.1 CPU cũng có thể diễn đạt là 100m, trong đó m là viết tắt của milli, và có thể xuống thấp đến 1m, nhưng không thấp hơn được nữa. Một count CPU tương đương với một vCPU — một vCPU trên AWS, hay được gọi là một core trên GCP hoặc Azure, hoặc một hyperthread trên các hệ thống khác. Có thể yêu cầu số CPU cao hơn cho container, miễn là node đủ lớn. Ví dụ trong bài giảng có set giá trị 0.5.

Tương tự với memory, có thể chỉ định 256Mi dùng suffix Mi, hoặc chỉ định cùng giá trị đó bằng con số đầy đủ (whole number), hoặc dùng suffix M. Cũng có thể dùng suffix G cho gigabyte. Lưu ý sự khác biệt giữa G và Gi: G là đơn vị thập phân (decimal, cơ số 10), còn Gi là đơn vị nhị phân (binary, cơ số 2) — 1Gi = 1024Mi, trong khi 1G = 1000M. Vì hai hệ đơn vị khác gốc, không nên quy đổi thẳng Gi sang MB — 1Gi thực chất lớn hơn 1GB một chút (xấp xỉ 1.074 GB). Điều tương tự áp dụng cho Mi/M và Ki/K.

Resource limits​

Xét một container chạy trên một node — mặc định, container không có giới hạn về tài nguyên nó có thể tiêu thụ trên node. Ví dụ một container thuộc một pod bắt đầu với 1 CPU trên node, nó có thể tăng lên và tiêu thụ bất kỳ lượng tài nguyên nào nó cần, và điều đó khiến các process gốc trên node hoặc các container khác thiếu tài nguyên.

Tuy nhiên, có thể set một limit cho việc sử dụng tài nguyên trên các pod này. Ví dụ, nếu set limit 1 vCPU cho container, container sẽ bị giới hạn chỉ tiêu thụ tối đa 1 vCPU từ node đó. Tương tự với memory — ví dụ có thể set limit 512Mi trên một container. Các limit này được chỉ định dưới section limits, bên trong section resources trong pod definition file:

resources:
requests:
memory: "4Gi"
cpu: 2
limits:
memory: "512Mi"
cpu: 1

Khi pod được tạo, Kubernetes set các limit này cho container. Và nhớ rằng requests và limits được set cho từng container riêng trong một pod — nếu có nhiều container, mỗi container có thể có request hoặc limit riêng cho việc sử dụng của nó.

Điều gì xảy ra khi một pod cố vượt quá tài nguyên vượt quá limit đã chỉ định? Với CPU, hệ thống sẽ throttle CPU để không vượt quá limit đã chỉ định — container không thể dùng nhiều CPU hơn limit của nó. Tuy nhiên, điều này không đúng với memory. Một container có thể dùng nhiều memory hơn limit của nó. Nếu một pod liên tục cố tiêu thụ nhiều memory hơn limit, pod sẽ bị terminate, và sẽ thấy pod bị terminate với lỗi OOM trong log hoặc trong output của lệnh describe khi chạy nó — OOM là viết tắt của out of memory kill.

Cấu hình mặc định​

Mặc định, Kubernetes không set CPU hoặc memory request hoặc limit. Nghĩa là bất kỳ pod nào cũng có thể tiêu thụ bao nhiêu tài nguyên tùy ý trên bất kỳ node nào, và làm các pod hoặc process khác đang chạy trên node đó thiếu tài nguyên. Đây là điểm rất, rất quan trọng cần lưu ý.

Trường hợp CPU: giả sử có hai pod cạnh tranh tài nguyên CPU trên cluster (khi nói pod, nghĩa là tất cả container trong một pod).

  • Không có request hoặc limit set: một pod có thể tiêu thụ toàn bộ tài nguyên CPU trên node và khiến pod thứ hai thiếu tài nguyên cần thiết. Đây không phải kịch bản lý tưởng.
  • Không set request nhưng có set limit: Kubernetes tự động set request bằng đúng limit. Ví dụ requests và limits được giả định là 3, mỗi pod được đảm bảo 3 vCPU và không hơn, vì limits cũng set giống vậy.
  • Set cả requests và limits: mỗi pod được đảm bảo số CPU request, là 1 vCPU, và có thể tăng lên đến limit đã định nghĩa, là 3 vCPU, nhưng không hơn. Nhìn qua có vẻ là kịch bản lý tưởng nhất. Tuy nhiên, vấn đề là nếu pod 1 cần nhiều CPU cycle hơn vì một lý do nào đó và pod 2 không thực sự tiêu thụ nhiều CPU cycle, thì không muốn giới hạn pod 1 về CPU. Muốn cho phép pod 1 dùng các CPU cycle khả dụng miễn là pod 2 không thực sự cần đến. Nếu có đủ CPU cycle khả dụng trên hệ thống, tại sao không để pod dùng chúng — không muốn giới hạn tài nguyên hoặc CPU cycle một cách không cần thiết. Vậy đây cũng chưa phải kịch bản lý tưởng.
  • Set requests nhưng không set limits: đây là kịch bản lý tưởng nhất. Vì requests được set, mỗi pod được đảm bảo 1 vCPU. Tuy nhiên, vì limits không được set, khi khả dụng, bất kỳ pod nào cũng có thể tiêu thụ bao nhiêu CPU cycle tùy ý đang có sẵn. Nhưng tại bất kỳ thời điểm nào, nếu pod 2 cần thêm CPU cycle theo yêu cầu của nó, pod đó sẽ được đảm bảo lượng CPU cycle đã yêu cầu.

Tất nhiên, có những trường hợp thực sự muốn giới hạn tài nguyên của một pod — khi đó có thể set limits. Ví dụ, một use case tốt cho việc set limits chính là các lab trong khóa học này — tất cả các lab được host dưới dạng container trên một cluster, và vì được công khai cho mọi người truy cập, người dùng có thể chạy bất kỳ loại workload nào họ muốn, nên set limits để ngăn người dùng lạm dụng infrastructure, ví dụ để đào Bitcoin (Bitcoin mining) hoặc các hoạt động tiêu tốn tài nguyên khác.

Nhưng trong trường hợp của người dùng, nếu không muốn giới hạn application tiêu thụ thêm CPU khi cần, có thể cân nhắc không set limits. Nhưng nhớ, nếu làm vậy, cần đảm bảo tất cả các pod đều có set requests, vì đó là cách duy nhất để một pod được đảm bảo tài nguyên khi không có request nào được set cho các pod khác. Nếu có bất kỳ pod nào không có request set, và không có limit nào set cho tất cả các pod khác, thì có khả năng bất kỳ pod nào cũng có thể tiêu thụ toàn bộ CPU khả dụng trên node và làm các pod không có request bị đói tài nguyên. Vậy hãy đảm bảo đã set request cho tất cả các pod.

Một vài lưu ý: requests và limits có thể khác nhau cho mỗi pod, nhưng để đơn giản, các ví dụ trên giả định chúng giống nhau cho tất cả các pod. Nhưng hoàn toàn có thể có requests hoặc limits khác nhau cho mỗi container, trong mỗi pod. Cũng lưu ý các khuyến nghị này chỉ dành cho CPU.

Trường hợp memory: tương tự.

  • Hai pod cạnh tranh tài nguyên memory trên cluster, không set resource limit — một pod có thể tiêu thụ toàn bộ memory trên node và khiến pod thứ hai thiếu tài nguyên cần thiết. Không lý tưởng.
  • Không set requests nhưng có set limits — Kubernetes tự động set requests bằng limits. Ví dụ requests và limits giả định là 3 GiB, mỗi pod được đảm bảo 3 GiB và không hơn.
  • Requests và limits đều được set — mỗi pod được đảm bảo lượng memory đã định, 1 GiB, và có thể tăng lên đến limit định nghĩa, 3 GiB, nhưng không hơn.
  • Set requests nhưng không set limits — mỗi pod được đảm bảo 1 GiB. Tuy nhiên, vì limits không set, khi khả dụng, bất kỳ pod nào cũng có thể tiêu thụ bao nhiêu memory tùy ý. Và nếu pod 2 yêu cầu thêm memory, để giải phóng memory, lựa chọn duy nhất là kill nó. Vì không giống CPU, không thể throttle memory. Một khi memory đã được gán cho một pod, cách duy nhất để thu hồi là kill pod và giải phóng toàn bộ memory nó dùng.

LimitRange​

Như đã nói, mặc định Kubernetes không cấu hình resource requests hoặc limits cho pod. Vậy làm sao đảm bảo mọi pod được tạo đều có giá trị mặc định được set? Điều này khả thi nhờ LimitRanges. LimitRanges giúp định nghĩa giá trị mặc định để set cho container trong Pod được tạo mà không có request hoặc limit chỉ định trong pod definition file. Điều này áp dụng ở cấp namespace.

Đây là một object, tạo một definition file với apiVersion set là v1, kind set là LimitRange:

apiVersion: v1
kind: LimitRange
metadata:
name: cpu-resource-constraint
spec:
limits:
- default:
cpu: 500m
defaultRequest:
cpu: 500m
max:
cpu: "1"
min:
cpu: 100m
type: Container

Đặt tên là "CPU Resource Constraint". Set default limit là 500m, default request cũng là 500m. Cũng chỉ định max CPU là 1 và min là 100m. max là giới hạn tối đa có thể set trên một container trong một pod, và min là request tối thiểu mà một container trong một pod có thể yêu cầu — đây tất nhiên chỉ là các giá trị ví dụ, không phải khuyến nghị — cần set giá trị phù hợp nhất cho application của mình.

Tương tự với memory, dùng memory thay cho cpu và chỉ định default, max, min theo dạng này. Lưu ý các limit này được áp dụng khi một pod được tạo. Nếu tạo hoặc thay đổi một LimitRange, nó không ảnh hưởng đến các pod hiện có — chỉ ảnh hưởng đến các pod mới được tạo sau khi LimitRange được tạo hoặc cập nhật.

Sau khi tạo, có thể xác nhận lại LimitRange đã áp dụng đúng bằng lệnh kubectl describe:

kubectl describe limitrange cpu-resource-constraint

# Output (minh họa):
Name: cpu-resource-constraint
Namespace: default
Type Resource Min Max Default Request Default Limit Max Limit/Request Ratio
---- -------- --- --- --------------- ------------- -----------------------
Container cpu 100m 1 500m 500m -

Cột Default Request và Default Limit chính là hai giá trị sẽ tự động gán cho container nào không khai báo resources trong pod definition file, còn Min/Max là biên chặn cho container nào có khai báo nhưng vượt ngoài khoảng cho phép — pod đó sẽ bị từ chối tạo.

ResourceQuota​

Cuối cùng, có cách nào để giới hạn tổng lượng tài nguyên có thể được tiêu thụ bởi các application deploy trong một Kubernetes cluster không? Ví dụ, nếu muốn nói rằng tất cả các pod cộng lại không được tiêu thụ nhiều hơn một lượng CPU hoặc memory nhất định, có thể tạo quotas ở cấp namespace.

Một ResourceQuota là một object cấp namespace có thể tạo để set giới hạn cứng (hard limit) cho tổng requests và tổng limits của tất cả pod trong namespace đó:

apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-quota
namespace: dev
spec:
hard:
requests.cpu: "4"
requests.memory: 4Gi
limits.cpu: "10"
limits.memory: 10Gi

Trong ví dụ này, resource quota giới hạn tổng CPU request của namespace dev là 4 và tổng memory request là 4 GiB. Nó cũng định nghĩa giới hạn tối đa của CPU tiêu thụ bởi tất cả các pod cộng lại là 10 và memory là 10 GiB.

Tạo quota rồi dùng kubectl describe để xem đã dùng hết bao nhiêu so với hard limit:

kubectl create -f compute-quota.yaml
kubectl describe resourcequota compute-quota -n dev

# Output (minh họa):
Name: compute-quota
Namespace: dev
Resource Used Hard
-------- ---- ----
limits.cpu 6 10
limits.memory 6Gi 10Gi
requests.cpu 2 4
requests.memory 2Gi 4Gi

Cách đọc output: mỗi dòng ứng với đúng một key trong spec.hard ở file trên, cột Hard chính là giá trị vừa khai báo, còn cột Used là tổng các giá trị mà pod trong namespace đã khai báo — không phải mức tiêu thụ thực tế đo được. Ví dụ dòng requests.cpu đang là 2 / 4: các pod trong namespace dev cộng lại khai báo 2 CPU request trên hard limit 4, nên chỉ còn chỗ cho 2 CPU request nữa; dù các pod đó thực tế chỉ dùng 0.1 CPU thì Used vẫn là 2. Tương tự, dòng limits.cpu là 6 / 10 nghĩa là tổng limit khai báo của các pod là 6.

Khi một pod mới làm Used vượt quá Hard, API server từ chối ngay request tạo pod (lỗi exceeded quota) — pod không bao giờ được tạo nên cũng không đi tới bước scheduler.

Hai lưu ý thực tế:

  • Khi namespace đã có quota cho requests.* / limits.*, mọi container tạo trong namespace đó bắt buộc phải khai báo trường tương ứng, nếu không pod bị từ chối. Đây chính là lúc LimitRange ở phần trên phát huy tác dụng: nó tự điền default cho container không khai báo, nhờ vậy pod vẫn qua được quota.
  • Quota chỉ tính các pod chưa kết thúc — pod ở trạng thái Succeeded hoặc Failed không còn chiếm quota nữa.

Lưu ý khi chỉnh sửa Pod và Deployment​

Nhớ rằng không thể chỉnh sửa specification của một Pod đang tồn tại, ngoại trừ các trường sau:

spec.containers[*].image
spec.initContainers[*].image
spec.activeDeadlineSeconds
spec.terminationGracePeriodSeconds
spec.tolerations
spec.schedulingGates

Ví dụ, không thể chỉnh sửa environment variables, service accounts, và resource limits (những phần sẽ được thảo luận sau) của một pod đang chạy. Nhưng nếu thực sự muốn, có hai cách:

Cách 1 — Chạy lệnh kubectl edit pod. Lệnh này sẽ mở pod specification trong một editor (vi editor). Chỉnh sửa các thuộc tính cần thiết. Khi lưu, sẽ bị từ chối, vì đang cố chỉnh sửa một trường trên pod không cho phép chỉnh sửa. Một bản sao của file với các thay đổi được lưu tại một vị trí tạm thời (temporary location), ví dụ như đường dẫn được hiển thị lúc lỗi xảy ra. Sau đó có thể xóa pod hiện có bằng lệnh:

kubectl delete pod webapp

Rồi tạo pod mới với các thay đổi bằng file tạm đó:

kubectl create -f /tmp/kubectl-edit-ccvrq.yaml

Cách 2 — Trích xuất pod definition ra dạng YAML vào một file bằng lệnh:

kubectl get pod webapp -o yaml > my-new-pod.yaml

Sau đó chỉnh sửa file đã export bằng editor (vi editor), lưu lại:

vi my-new-pod.yaml

Sau đó xóa pod hiện có:

kubectl delete pod webapp

Rồi tạo pod mới với file đã chỉnh sửa:

kubectl create -f my-new-pod.yaml

Chỉnh sửa Deployment: với Deployments, có thể dễ dàng chỉnh sửa bất kỳ trường/thuộc tính nào của pod template. Vì pod template là con (child) của deployment specification, mỗi khi có thay đổi, deployment sẽ tự động xóa và tạo pod mới với các thay đổi đó. Vậy nếu được yêu cầu chỉnh sửa một thuộc tính của một Pod thuộc một deployment, chỉ cần chạy lệnh:

kubectl edit deployment my-deployment

Demo: chẩn đoán một Pod bị OOMKilled​

Tình huống rất hay gặp trong vận hành thực tế: Pod chạy được một lúc rồi tự restart, không có lỗi image, không có lỗi config rõ ràng — nghi ngờ đầu tiên nên là memory limit.

Xác nhận Pod đang bị kill vì hết memory.

kubectl describe pod elephant

# Output (rút gọn):
Containers:
elephant:
Limits:
memory: 10Mi
Last State: Terminated
Reason: OOMKilled
Exit Code: 137

Last State: Terminated / Reason: OOMKilled là bằng chứng trực tiếp — container đã cố dùng nhiều memory hơn Limits.memory cho phép (10Mi), và bị kernel kill. Container này chạy lệnh stress mô phỏng tiêu thụ 15Mi, vượt xa giới hạn 10Mi.

Tăng memory limit lên 20Mi. Vì limit không sửa được trên Pod đang chạy, kubectl edit sẽ báo lỗi ngay khi lưu — nhưng nó vẫn lưu lại bản đã sửa vào 1 file tạm, dùng file đó để tạo lại Pod bằng replace --force:

kubectl edit pod elephant
# (sửa memory: 10Mi -> 20Mi, lưu — báo lỗi "field is immutable",
# nhưng in ra đường dẫn file tạm, ví dụ /tmp/kubectl-edit-ccvrq.yaml)

kubectl replace --force -f /tmp/kubectl-edit-ccvrq.yaml
kubectl get pod elephant

# Output:
NAME READY STATUS RESTARTS AGE
elephant 1/1 Running 0 8s

Với 20Mi, process stress (15Mi) giờ nằm trong giới hạn, Pod chạy ổn định. Đây cũng là quy trình chung cho MỌI trường hợp cần sửa field immutable của Pod (image, resources,...): sửa → edit báo lỗi nhưng để lại file tạm → replace --force với file đó.

Request/limit kiểm soát được 1 Pod dùng bao nhiêu tài nguyên trên 1 node. Nhưng nếu cần đúng 1 bản sao của Pod chạy trên mọi node trong cluster — không phải "n replica rải đều", mà đúng 1-1 với từng node, kể cả khi cluster scale thêm node mới — thì ReplicaSet lẫn Deployment đều bó tay, vì cả hai chỉ quan tâm tổng số replica, không quan tâm placement theo node. Đó là lúc cần DaemonSet.


2. DaemonSets​

Từ trước đến giờ đã deploy nhiều pod khác nhau lên các node khác nhau trong cluster. Với sự trợ giúp của ReplicaSets và Deployments, đã đảm bảo nhiều bản sao của application luôn khả dụng trên nhiều worker node khác nhau.

DaemonSets giống ReplicaSets ở chỗ chúng giúp deploy nhiều instance của pod, nhưng nó chạy đúng một bản sao của pod trên mỗi node trong cluster. Bất cứ khi nào một node mới được thêm vào cluster, một bản sao của pod tự động được thêm vào node đó. Và khi một node bị gỡ bỏ, pod đó cũng tự động bị gỡ bỏ. DaemonSet đảm bảo luôn có một bản sao của Pod trên tất cả các node trong cluster.

💡 Hình dung: Giống việc lắp đúng một máy báo khói cho mỗi tầng của tòa nhà — không phải "lắp 5 máy rồi rải đều", mà là một quy tắc gắn liền với chính khái niệm tầng: xây thêm tầng mới, máy báo khói tự động được lắp theo; phá bỏ một tầng, máy đó cũng gỡ theo. Không ai cần đếm hay chỉ định con số replicas cụ thể, vì số lượng máy luôn tự khớp đúng số tầng hiện có.

DaemonSet: Exactly One Pod per NodeNode 1DaemonSetPodNode 2DaemonSetPodNode 3 (new)Pod addedautomaticallyNode joins cluster → DaemonSet controller schedules one Pod there right away.Node leaves cluster → its Pod is removed automatically, no manual cleanup needed.Common use cases: monitoring agent, log collector, kube-proxy, CNI plugin (e.g. Calico).

Một vài use case của DaemonSets:

  • Muốn deploy một monitoring agent hoặc log collector trên mỗi node trong cluster, để giám sát cluster tốt hơn. DaemonSet phù hợp hoàn hảo cho việc này vì nó có thể deploy monitoring agent dưới dạng một Pod trên tất cả các node trong cluster. Sau đó không cần lo lắng về việc thêm hoặc gỡ monitoring agent khỏi các node này khi có thay đổi trong cluster, vì DaemonSet sẽ tự lo việc đó.
  • Trước đây khi bàn về kiến trúc Kubernetes, đã học rằng một trong các thành phần worker node cần thiết trên mọi node trong cluster là kube-proxy. Đó là một use case tốt của DaemonSets — component kube-proxy có thể được deploy dưới dạng một DaemonSet trong cluster.
  • Một use case khác là cho networking. Các giải pháp networking như Calico — một CNI (Container Network Interface) plugin phổ biến, chịu trách nhiệm cấp phát network và định tuyến giữa các pod — yêu cầu một agent được deploy trên mỗi node trong cluster.

Tạo một DaemonSet tương tự quy trình tạo ReplicaSet — có nested pod specification dưới section template và selectors để liên kết DaemonSet với các Pod. Một DaemonSet definition file có cấu trúc tương tự — bắt đầu với apiVersion, kind, metadata, và spec:

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: monitoring-daemon
labels:
app: monitoring-agent
spec:
selector:
matchLabels:
app: monitoring-agent
template:
metadata:
labels:
app: monitoring-agent
spec:
containers:
- name: monitoring-agent
image: monitoring-agent

apiVersion là apps/v1. kind là DaemonSet thay vì ReplicaSet. Đặt tên là monitoring-daemon. Dưới spec, có selector và pod specification template — gần như y hệt ReplicaSet definition, chỉ khác là kind là DaemonSet. Đảm bảo labels trong selector (app: monitoring-agent) khớp với labels trong pod template, nếu không API server sẽ từ chối tạo object.

Điểm khác biệt đáng chú ý nhất so với ReplicaSet: không có field replicas. Không cần khai báo số lượng, vì số Pod luôn được suy ra từ số node.

Khi sẵn sàng, tạo DaemonSet bằng kubectl create -f (không có lệnh kubectl create daemonset — đây là một trong số ít object không hỗ trợ tạo bằng imperative command, sẽ xử lý ở phần Demo bên dưới):

kubectl create -f daemon-set-definition.yaml

# Output (minh họa):
daemonset.apps/monitoring-daemon created

Để xem DaemonSet đã tạo, chạy kubectl get daemonsets (viết tắt kubectl get ds):

kubectl get daemonsets

# Output (minh họa):
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
monitoring-daemon 3 3 3 3 3 <none> 10s

Và để xem thêm chi tiết, chạy kubectl describe daemonset:

kubectl describe daemonset monitoring-daemon

# Output (minh họa):
Name: monitoring-daemon
Selector: app=monitoring-agent
Node-Selector: <none>
Labels: app=monitoring-agent
Annotations: deprecated.daemonset.template.generation: 1
Desired Number of Nodes Scheduled: 3
Current Number of Nodes Scheduled: 3
Number of Nodes Scheduled with Up-to-date Pods: 3
Number of Nodes Scheduled with Available Pods: 3
Number of Nodes Misscheduled: 0
Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
Labels: app=monitoring-agent
Containers:
monitoring-agent:
Image: monitoring-agent
Port: <none>
Host Port: <none>
Environment: <none>
Mounts: <none>
Volumes: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 10s daemonset-controller Created pod: monitoring-daemon-8k4tq
Normal SuccessfulCreate 10s daemonset-controller Created pod: monitoring-daemon-hd2xz
Normal SuccessfulCreate 10s daemonset-controller Created pod: monitoring-daemon-q7lmv

Ba dòng đáng chú ý trong output này:

  • Desired Number of Nodes Scheduled: 3 — chỗ này nói rõ vì sao DaemonSet không cần replicas: nó đếm node, không đếm replica. Con số 3 không nằm trong file YAML mà do controller tự tính = số node đủ điều kiện chạy Pod (sau khi loại các node bị taint chặn hoặc không khớp nodeSelector). Join thêm node thứ 4 vào cluster thì dòng này tự thành 4 và một Pod mới xuất hiện, không cần sửa file.
  • Number of Nodes Misscheduled: 0 — số node đang chạy Pod của DaemonSet dù lẽ ra không nên chạy, thường gặp ngay sau khi vừa đổi nodeSelector hoặc vừa thêm taint. Khác 0 nghĩa là controller đang trong quá trình dọn các Pod thừa.
  • Cột From trong Events là daemonset-controller, mỗi node một dòng SuccessfulCreate. Đây là chỗ để phân biệt: controller tạo Pod object, còn việc chọn node cho Pod đó vẫn do default scheduler làm (từ v1.12 trở đi — xem ngay đoạn dưới).

Vậy DaemonSet hoạt động như thế nào? Nó lập lịch Pod trên từng node ra sao? Và làm sao nó đảm bảo mỗi node đều có một Pod? Nếu được yêu cầu lập lịch một pod trên mỗi node trong cluster, sẽ làm thế nào? Ở một trong các phần trước, đã bàn rằng có thể set thuộc tính nodeName trên Pod để bỏ qua scheduler và đặt Pod trực tiếp lên một node. Đó là một cách tiếp cận — trên mỗi pod, set thuộc tính nodeName trong specification trước khi nó được tạo, và khi được tạo, chúng tự động đến đúng node tương ứng. Đó là cách nó hoạt động cho đến Kubernetes phiên bản 1.12.

Từ phiên bản 1.12 trở đi, DaemonSet dùng scheduler mặc định và node affinity rule đã học ở một trong các phần trước để lập lịch pod lên các node.

Demo: xem DaemonSet có sẵn và tự tạo một cái mới​

Liệt kê DaemonSet ở mọi namespace — chúng thường "ẩn" trong kube-system, dễ bỏ sót nếu chỉ kubectl get daemonsets mặc định (namespace default):

kubectl get daemonsets --all-namespaces

# Output:
NAMESPACE NAME DESIRED CURRENT READY AGE
kube-system kube-flannel-ds 1 1 1 10d
kube-system kube-proxy 1 1 1 10d

kube-proxy (đã nói ở Part 2.1) và kube-flannel-ds (CNI plugin lo pod networking) chính là 2 ví dụ thật của DaemonSet đang chạy ngay trong mọi cluster kubeadm — không phải khái niệm lý thuyết suông. Cột DESIRED/CURRENT/READY đều bằng đúng số node trong cluster (ở đây là 1).

Tạo DaemonSet mới cho 1 logging agent (fluentd). Như đã nói ở trên, không có lệnh kubectl create daemonset trực tiếp — nhưng cấu trúc DaemonSet gần giống hệt Deployment, nên mẹo nhanh nhất trong phòng thi là generate ra một Deployment rồi sửa lại:

kubectl create deployment fluentd-elasticsearch -n kube-system --image=fluent/fluentd --dry-run=client -o yaml > fluentd.yaml

Sửa fluentd.yaml: đổi kind: Deployment thành kind: DaemonSet, xóa hẳn field replicas và strategy (DaemonSet không có khái niệm số lượng cố định — số Pod luôn bằng số node):

kubectl create -f fluentd.yaml

# Output:
daemonset.apps/fluentd-elasticsearch created

Kiểm tra lại — DESIRED bằng đúng số node trong cluster (ở đây là 1), dù trong file không hề có dòng replicas nào:

kubectl get daemonset fluentd-elasticsearch -n kube-system

# Output:
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
fluentd-elasticsearch 1 1 1 1 1 <none> 18s

Đủ đồ nghề để quản lý DaemonSet trong vận hành bình thường — có scheduler, có api server, mọi thứ đủ đầy. Nhưng nếu kubelet phải tự vận hành một node hoàn toàn đơn độc, không có api server, không có scheduler, không có gì cả — nó còn làm được gì? Câu trả lời chính là static pod.


3. Static Pods​

Ở phần trước trong khóa học đã bàn về kiến trúc và cách kubelet hoạt động như một trong nhiều control plane component trong Kubernetes. Kubelet dựa vào kube-apiserver để nhận chỉ dẫn về những pod nào cần load lên node của nó — quyết định này dựa trên quyết định của kube-scheduler, được lưu trong etcd data store.

Nếu không có kube-apiserver, kube-scheduler, không có controllers, không có etcd cluster thì sao? Nếu không có master nào cả? Nếu không có node nào khác? Nếu chỉ có một mình giữa biển khơi, không thuộc bất kỳ cluster nào? Có gì mà kubelet, với vai trò là thuyền trưởng của con tàu, có thể làm? Nó có thể hoạt động như một node độc lập không? Nếu vậy, ai sẽ cung cấp chỉ dẫn cần thiết để tạo các pod đó?

Kubelet có thể quản lý một node một cách độc lập. Trên host, có kubelet được cài đặt, và tất nhiên có containerd để chạy container. Không có Kubernetes cluster nào ở đây — không có kube-apiserver hay bất cứ thứ gì tương tự. Điều duy nhất kubelet biết làm là tạo pod. Nhưng lại không có api server để cung cấp chi tiết về pod. Đến giờ đã biết rằng để tạo một pod, cần chi tiết của pod trong một pod definition file. Nhưng làm sao cung cấp pod definition file cho kubelet khi không có kube-apiserver?

💡 Hình dung: Kubelet lúc này giống một thuyền trưởng chèo thuyền một mình giữa biển khơi, không có trung tâm điều phối (kube-apiserver) nào ra lệnh từ đất liền. Thuyền trưởng vẫn phải tự vận hành con tàu — nhưng thay vì chờ lệnh qua radio, ông đọc chỉ dẫn từ một cuốn sổ nhật ký để sẵn trong cabin (thư mục static pod path): sổ ghi gì thì làm nấy, sổ đổi trang thì làm lại theo trang mới, xé mất một trang thì việc đó dừng ngay. Không có ai trên đất liền kiểm tra hay điều phối — thuyền trưởng tự mình đọc, tự mình quyết, tự mình duy trì.

Có thể cấu hình kubelet để đọc pod definition file từ một thư mục trên server, được chỉ định để lưu thông tin về pod. Đặt pod definition file vào thư mục này. Kubelet định kỳ kiểm tra thư mục này để tìm file, đọc các file này, và tạo pod trên host. Không chỉ tạo pod, nó còn đảm bảo pod luôn hoạt động — nếu application crash, kubelet sẽ cố restart nó. Nếu có thay đổi trong bất kỳ file nào trong thư mục này, kubelet sẽ tạo lại pod để những thay đổi đó có hiệu lực. Nếu xóa một file khỏi thư mục này, pod tương ứng sẽ tự động bị xóa.

Những pod được tạo bởi kubelet theo cách này — không có sự can thiệp của kube-apiserver hay các thành phần còn lại của Kubernetes cluster — được gọi là static pods. Chỉ có thể tạo pod theo cách này — không thể tạo ReplicaSets, Deployments, hay Services bằng cách đặt definition file vào thư mục được chỉ định. Tất cả những khái niệm này là một phần của toàn bộ kiến trúc Kubernetes, đòi hỏi các control plane component khác như replication controller và deployment controller, v.v. Kubelet chỉ hoạt động ở cấp pod và chỉ hiểu pod — đó là lý do nó có thể tạo static pod theo cách này.

Vậy thư mục được chỉ định đó là gì, và cấu hình nó thế nào? Có thể là bất kỳ thư mục nào trên host. Vị trí của thư mục đó được truyền vào kubelet dưới dạng một flag khi chạy service. Flag này tên là --pod-manifest-path, và ví dụ set là /etc/kubernetes/manifests — lưu ý flag này hiện đã deprecated. Cách được khuyến nghị thay thế là cung cấp đường dẫn tới một file config khác qua option --config, và định nghĩa đường dẫn thư mục là staticPodPath trong file đó. Các cluster thiết lập bằng công cụ kubeadm dùng cách tiếp cận này.

Nếu đang kiểm tra một cluster có sẵn, nên kiểm tra option này của kubelet để xác định đường dẫn tới thư mục — khi đó sẽ biết nơi để đặt definition file cho static pod của mình. Vậy hãy nhớ điều này khi làm bài lab: cần biết cách xem và cấu hình option này, bất kể phương pháp nào được dùng để thiết lập cluster. Trước tiên, kiểm tra option pod-manifest-path trong file kubelet service. Nếu không có ở đó, tìm option config, xác định file được dùng làm file config. Rồi bên trong file config, tìm option staticPodPath. Một trong hai cách này sẽ cho biết đường dẫn đúng.

Khi các static pod đã được tạo, có thể xem chúng bằng lệnh docker ps. Vì sao không dùng lệnh kubectl như thường lệ? Nhớ rằng chưa có phần còn lại của Kubernetes cluster ở đây — tiện ích kubectl hoạt động với kube-apiserver. Vì chưa có api server, không có kubectl, nên dùng lệnh docker.

Vậy nó hoạt động thế nào khi node đó là một phần của cluster? Khi có một api server yêu cầu kubelet tạo pod, kubelet có thể tạo cả hai loại pod cùng lúc không? Cách kubelet hoạt động là nó có thể nhận request để tạo pod từ các nguồn đầu vào khác nhau. Thứ nhất là qua pod definition file từ thư mục static pod, như vừa thấy. Thứ hai là qua một HTTP API endpoint — đó là cách kube-apiserver cung cấp đầu vào cho kubelet. Kubelet có thể tạo cả hai loại pod, static pod và các pod từ api server, cùng một lúc.

Vậy trong trường hợp đó, api server có biết về các static pod do kubelet tạo không? Có. Nếu chạy lệnh kubectl get pods trên master node, các static pod sẽ được liệt kê như bất kỳ pod nào khác. Điều này hoạt động như thế nào? Khi kubelet tạo một static pod, nếu nó là một phần của cluster, nó cũng tạo một mirror object trong kube-apiserver. Những gì thấy từ kube-apiserver chỉ là một bản mirror chỉ đọc (read-only) của pod. Có thể xem chi tiết về pod, nhưng không thể chỉnh sửa hay xóa nó như các pod thông thường — chỉ có thể xóa chúng bằng cách chỉnh sửa file từ thư mục manifest của node.

Lưu ý rằng tên của pod tự động được thêm hậu tố tên node vào cuối. Trong trường hợp này, ví dụ static-web-node01.

Static Pod Creation FlowNode (worker or master)/etc/kubernetes/manifests/*.yamlreadskubeletruns static Podcreates mirror objectkube-apiserverMirror Pod objectread-onlykubectl get podsshows it like anyother PodDelete or edit only works on the manifest file — the mirror object rejects both.No manifests folder, no api server → kubelet still creates the Pod, just no mirror.

Vậy tại sao muốn dùng static pods? Vì static pod không phụ thuộc vào Kubernetes control plane, có thể dùng static pod để deploy chính các control plane component dưới dạng pod trên một node. Bắt đầu bằng việc cài đặt kubelet trên tất cả master node. Sau đó tạo pod definition file dùng Docker image của các control plane component khác nhau, như API server, controller, etcd, đặt các definition file vào thư mục manifest được chỉ định, và kubelet sẽ lo việc deploy chính các control plane component đó dưới dạng pod trên cluster. Cách này không cần tải các binary, cấu hình service, hay lo lắng về việc service bị crash — nếu bất kỳ service nào trong số này bị crash, vì đó là một static pod, nó sẽ tự động được kubelet restart.

Gọn gàng và đơn giản — đó là cách công cụ kubeadm thiết lập một Kubernetes cluster. Đó là lý do khi liệt kê các pod trong namespace kube-system, sẽ thấy các control plane component xuất hiện dưới dạng pod trong cluster thiết lập bằng công cụ kubeadm.

Trước khi kết thúc, một câu hỏi thường gặp là về sự khác biệt giữa static pods và DaemonSets. DaemonSets, như đã thấy trước đó, được dùng để đảm bảo một instance của application luôn khả dụng trên tất cả node trong cluster — được xử lý bởi DaemonSet controller thông qua kube-apiserver. Trong khi đó, static pods, như đã thấy, được tạo trực tiếp bởi kubelet, không có sự can thiệp từ kube-apiserver hay phần còn lại của Kubernetes control plane component. Static pod có thể được dùng để deploy chính các Kubernetes control plane component. Cả static pods và các pod được tạo bởi DaemonSet đều bị kube-scheduler bỏ qua — kube-scheduler không có tác động lên các pod này.

Demo: nhận diện, tạo, và xóa static Pod​

Phân biệt static Pod với Pod thường. Cách nhanh nhất là nhìn tên — static Pod luôn có hậu tố tên node. Cách chắc chắn hơn là xem ownerReferences:

kubectl get pod kube-apiserver-controlplane -n kube-system -o yaml

# Output (rút gọn):
metadata:
ownerReferences:
- kind: Node
name: controlplane

ownerReferences.kind: Node xác nhận đây là static Pod — do kubelet trên chính node đó sở hữu. Pod thường (do ReplicaSet quản lý) sẽ có ownerReferences.kind: ReplicaSet thay vào đó. Áp dụng cách này để liệt kê toàn bộ static Pod trong cluster: etcd, kube-apiserver, kube-controller-manager, kube-scheduler đều là static Pod — còn CoreDNS và kube-proxy thì không (CoreDNS là Deployment, kube-proxy là DaemonSet, như đã thấy ở mục 2).

Tìm đúng thư mục static Pod path trước khi tạo mới — đừng đoán, luôn tra trong kubelet config:

cat /var/lib/kubelet/config.yaml | grep staticPodPath

# Output:
staticPodPath: /etc/kubernetes/manifests

Tạo static Pod mới — generate YAML bằng kubectl run --dry-run=client, rồi thả thẳng file vào đúng thư mục đó, không cần kubectl create hay apply gì cả:

kubectl run static-busybox --image=busybox --dry-run=client -o yaml --command -- sleep 1000 > static-busybox.yaml
cp static-busybox.yaml /etc/kubernetes/manifests/
kubectl get pods

# Output:
NAME READY STATUS RESTARTS AGE
static-busybox-controlplane 1/1 Running 0 6s

⚠️ Lưu ý cú pháp: với --command, mọi thứ sau -- được hiểu là command chạy bên trong container, không phải flag của kubectl — nên --dry-run=client -o yaml luôn phải đặt trước --, còn sleep 1000 đặt sau cùng.

kubelet tự phát hiện file mới trong thư mục và tạo Pod ngay, không cần lệnh nào khác.

Sửa static Pod — chỉ cần sửa trực tiếp file YAML trong thư mục manifest rồi lưu, kubelet tự nhận diện thay đổi và tạo lại Pod:

sed -i 's/image: busybox/image: busybox:1.28.4/' /etc/kubernetes/manifests/static-busybox.yaml
kubectl get pods

# Output:
NAME READY STATUS RESTARTS AGE
static-busybox-controlplane 1/1 Running 0 4s

Xóa static Pod — kubectl delete không đủ, vì kubelet sẽ tạo lại ngay khi thấy file manifest vẫn còn. Phải xóa đúng file trên đúng node:

kubectl delete pod static-greenbox-node01
kubectl get pods -w

# Output:
static-greenbox-node01 1/1 Running 0 1s # tự sống lại ngay lập tức

Pod sống lại vì file manifest vẫn còn — phải SSH vào đúng node (tên static Pod tiết lộ node nào: node01), tìm file trong đúng staticPodPath của node đó, rồi xóa file:

ssh node01
cat /var/lib/kubelet/config.yaml | grep staticPodPath

# Output:
staticPodPath: /etc

⚠️ staticPodPath không phải lúc nào cũng giống nhau giữa các node — node này có thể trỏ tới /etc/kubernetes/manifests như thường lệ, trong khi node khác bị chỉnh sang đường dẫn khác. Luôn tra lại kubelet config trên đúng node đang thao tác, đừng giả định.

rm /etc/static-greenbox.yaml
exit
kubectl get pods -w

# Output:
static-greenbox-node01 1/1 Terminating 0 5m
# (rồi biến mất hẳn)

Static Pod giải quyết bài toán "chạy được khi không có gì cả" — kể cả khi mất trắng api server. Nhưng trong một cluster bình thường, đủ đầy control plane, vẫn còn một bài toán khác: khi tài nguyên khan hiếm, Pod nào được ưu tiên lập lịch trước? Đó là việc của PriorityClasses.


4. Priority Classes​

Đã biết Kubernetes chạy các application khác nhau dưới dạng pod với các priority (độ ưu tiên) khác nhau. Ví dụ, chính các Kubernetes control plane component cũng chạy dưới dạng pod trong cluster, và dù thế nào những workload đó cũng cần luôn chạy được. Tương tự, có thể có database ưu tiên cao, các application quan trọng chạy trên cluster, cũng như các workload ưu tiên thấp hơn như background job.

Cần một cách để đảm bảo các workload ưu tiên cao luôn được lập lịch mà không bị gián đoạn bởi các workload ưu tiên thấp hơn. Đây là lúc PriorityClasses phát huy tác dụng. PriorityClasses giúp định nghĩa priority cho các workload khác nhau, để workload ưu tiên cao luôn được ưu tiên hơn workload ưu tiên thấp. Nếu một pod ưu tiên cao không thể được lập lịch, scheduler sẽ cố terminate một workload ưu tiên thấp hơn để pod đó có thể được lập lịch.

💡 Hình dung: Giống khu vực tiếp nhận cấp cứu ở bệnh viện — bệnh nhân nguy kịch (pod priority cao) được đưa thẳng vào phòng mổ trước, kể cả khi phải tạm dừng ca đang mổ cho một ca ít khẩn cấp hơn (preemption). Nhưng nếu bệnh nhân đó mang thẻ "ưu tiên nhẹ, không chen ngang" (preemptionPolicy: Never), họ chỉ được gọi trước những người đến sau trong hàng chờ, chứ không bao giờ được phép làm gián đoạn ca đang diễn ra.

PriorityClasses là các object không thuộc namespace (non-namespaced), nghĩa là chúng không được tạo trong một namespace cụ thể, không gắn với bất kỳ namespace nào. Một khi được tạo, chúng khả dụng để cấu hình trên bất kỳ Pod nào trong bất kỳ namespace nào.

Priority được định nghĩa bằng một dải số: có thể định nghĩa priority cao đến 1 tỷ và thấp đến âm 2 tỷ (hoặc xung quanh con số đó). Số càng lớn thì priority càng cao. Dải số này dành cho các application và workload được deploy trên cluster — dải từ dương 1 tỷ đến âm 2 tỷ là dành cho application hoặc workload triển khai dưới dạng pod trên cluster. Nhưng có một dải riêng dành cho các pod system-critical nội bộ, như chính các Kubernetes control plane component — chúng luôn nhận priority cao nhất, với priority lên đến 2 tỷ.

Để liệt kê PriorityClasses hiện có, chạy lệnh kubectl get priorityclasses — hiện tại liệt kê các class system-cluster-critical và system-node-critical, và đúng như đã nói, giá trị của chúng gần với con số 2 tỷ hoặc xung quanh dải đó.

kubectl get priorityclasses

# Output (minh họa):
NAME VALUE GLOBAL-DEFAULT AGE
system-cluster-critical 2000000000 false 12d
system-node-critical 2000001000 false 12d
high-priority 1000000 false 2m

Hai class system-* luôn có sẵn ngay khi cluster được tạo — không ai cần tự khai báo chúng — và cột VALUE xác nhận đúng dải "trên 2 tỷ" đã nói ở trên, tách biệt hẳn khỏi dải giá trị mà người dùng tự định nghĩa (tối đa 1 tỷ).

Tạo một PriorityClass mới:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
description: "..."

Sau khi tạo, gán PriorityClass này cho một pod bằng thuộc tính priorityClassName bên trong pod definition:

spec:
priorityClassName: high-priority

Khi pod được tạo, nó được xem như có priority được gán từ PriorityClass đó. Vậy nếu không chỉ định priorityClassName cho một pod thì sao? Mặc định, pod được xem như có giá trị priority bằng 0. Tuy nhiên, nếu muốn thay đổi điều đó, phải tạo một PriorityClass mới và set thuộc tính globalDefault thành true. Đây là một trường tùy chọn, dùng để định nghĩa priority mặc định cho tất cả các pod nếu không có priorityClassName được định nghĩa rõ ràng trong configuration file của pod đó. Cũng lưu ý thuộc tính này chỉ có thể được định nghĩa trong một PriorityClass duy nhất — vì không thể có nhiều giá trị mặc định, tức không thể có nhiều hơn một PriorityClass với thuộc tính globalDefault set là true.

Ảnh hưởng của pod priority. Giả sử có hai workload với hai priority chuẩn bị được lập lịch: một critical app với priority cao hơn là 7, và một jobs app với priority thấp hơn là 5. Vì critical app có priority cao hơn, nó được đặt trước. Và nếu vẫn còn tài nguyên khả dụng, workload với priority thấp hơn cũng được đặt tiếp theo, sau workload priority cao.

Giả sử có một job priority cao hơn đến với priority 6, và không còn tài nguyên khả dụng trên cluster. Điều gì xảy ra? Có evict các workload hiện có, hay chờ đợi? Hành vi này được định nghĩa bởi preemptionPolicy được định nghĩa trong PriorityClass được gán cho workload mới. Nếu preemption policy không được set, giá trị mặc định của nó là PreemptLowerPriority. Nghĩa là nó sẽ kill pod priority thấp hơn hiện có và chiếm chỗ.

Nhưng nếu không muốn kill hoặc evict workload hiện có, mà muốn chờ tài nguyên cluster được giải phóng, phải set preemptionPolicy thành Never. Vậy preemptionPolicy set là Never khiến các pod trong PriorityClass đó không preempting — nghĩa là chúng không thể preempt (chiếm chỗ) các pod khác, và sẽ chờ để được lập lịch trong hàng đợi lập lịch (scheduling queue). Tuy nhiên, chúng vẫn nhận priority cao hơn khi lập lịch so với các pod ưu tiên thấp hơn khác cũng đang chờ được lập lịch.

preemptionPolicy Decides What Happens NextNew high-priority Podno free resources on clusterPreemptLowerPriorityNeverDefault behaviorLower-priority Pod evictedNew Pod takes its placeright awaypreemptionPolicy: NeverNo Pod is evictedNew Pod waits in queue,but ahead of lower priority onesPriority always decides scheduling order — preemptionPolicy only decides whether existing Pods get killed for it.

Nguồn tham khảo​

Nguồn gốc: Khóa "Certified Kubernetes Administrator (CKA)" — module "3. Scheduling", nền tảng KodeKloud.

Ghi chú cập nhật/đính chính:

  • Pod update cho phép thêm 2 trường spec.terminationGracePeriodSeconds và spec.schedulingGates ngoài 4 trường đã nêu — 2026-09, Kubernetes Pods docs
  • 1Gi = 1024Mi (binary), khác gốc với 1G = 1000M (decimal) — không quy đổi thẳng Gi sang MB — 2026-09, Kubernetes resource units
  • Flag cấu hình static pod path trên kubelet là --pod-manifest-path (đã deprecated, camelCase podManifestPath không tồn tại), thay thế bằng staticPodPath trong kubelet config file — 2026-09, kubernetes/website commit 8a0a983
  • Registry k8s.gcr.io đã ngừng cập nhật từ 4/2023, khuyến nghị dùng registry.k8s.io — 2026-09, Kubernetes blog: image registry redirect
  • system-cluster-critical = 2,000,000,000 và system-node-critical = 2,000,001,000 (đều trên mốc 2 tỷ) — 2026-09, Pod Priority and Preemption