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.

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.

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 GGi: G là Gigabyte, tương ứng 1000 MB, trong khi Gi là GiB, tương đương 1024 MB. Điều tương tự áp dụng cho MB/MiBKiB.

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.
  • Không 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 không đượ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.

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 requests và limits. Trong ví dụ này, resource quota giới hạn tổng CPU request trong namespace hiện tại là 4 và memory 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.

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.tolerations

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

Lab: Thực hành Resource Requests và Limits

Một pod tên rabbit đã được deploy. Xác định CPU request đã set trên pod này. Kiểm tra bằng kubectl get pod để chắc chắn pod rabbit tồn tại, rồi kubectl describe pod rabbit. Trong section limits, thấy cả CPU limits và CPU requests — CPU request được yêu cầu là 1.

Xóa pod rabbit.

kubectl delete pod rabbit

Một pod khác tên elephant đã được tạo, và thất bại khi khởi động (fails to get to a running state). Kiểm tra pod này và xác định lý do vì sao nó không chạy. Chạy kubectl describe pod elephant. Trong section Last State, lý do được ghi là OOMKilled — chỉ ra đây là vấn đề về memory. Trạng thái OOMKilled cho biết pod thất bại vì hết memory (ran out of memory).

Xác định memory limit set trên pod. Memory limit là 10Mi.

Pod elephant chạy một process tiêu thụ 15Mi memory. Tăng limit của pod elephant lên 20Mi, xóa và tạo lại pod nếu cần. Command dùng để tạo pod này là lệnh stress, khiến pod dùng 15 megabyte memory, nhưng limit chỉ là 10Mi — đó là lý do nó bị kill.

Cần đổi limit thành 20Mi. Một cách là dùng kubectl edit pod elephant, nhưng có thể sẽ không thể thay đổi limit bằng edit — thử vẫn được, đặt là 20Mi và lưu — báo lỗi rằng không thể chỉnh sửa limit đã set trên một pod đang chạy. Thoát ra, nhưng các thay đổi đã được lưu vào một file tạm thời. Có thể dùng file đó để tạo lại pod — xóa pod và tạo lại, hoặc cách dễ hơn là dùng lệnh kubectl replace với --force, để xóa pod hiện có và tạo lại theo file đó:

kubectl replace --force -f <file-tạm>

Kiểm tra trạng thái pod — Running. Xác nhận limit vừa set đã phản ánh đúng.

Nhiệm vụ cuối cùng là xóa pod, chờ vài giây để process terminate.


2. DaemonSets

Phần này nói về DaemonSets trong Kubernetes. 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.

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 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. apiVersionapps/v1. kindDaemonSet 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 khớp với labels trong pod template.

Khi sẵn sàng, tạo DaemonSet bằng lệnh kubectl create daemonset. Để xem DaemonSet đã tạo, chạy kubectl get daemonset. Và để xem thêm chi tiết, chạy kubectl describe daemonset.

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.

Lab: Thực hành DaemonSets

Có bao nhiêu DaemonSet được tạo trong cluster, ở tất cả namespace? Chạy kubectl get daemonsets — namespace default không có DaemonSet nào. Nhưng nếu xét tất cả namespace: kubectl get daemonsets --all-namespaces — thấy có hai DaemonSet trong tất cả namespace: kube-flannel-dskube-proxy. Đáp án: 2.

DaemonSets được tạo trong namespace nào? Xem cột namespace — chúng nằm trong namespace kube-system.

Cái nào dưới đây là một DaemonSet? kube-flannel-dskube-proxy là DaemonSets; scheduler, CoreDNS, etcd-master thì không.

Có bao nhiêu node mà pod của DaemonSet kube-proxy được lập lịch lên? Xem kube-proxy — các số desired, current, và ready đều là 1. Xem chi tiết hơn với kubectl describe daemonset kube-proxy (namespace kube-system) — desired number of nodes scheduled là 1, current number of nodes scheduled là 1. Vì cluster này chỉ có một node.

Image nào được dùng bởi pod triển khai bởi DaemonSet kube-flannel-ds? Xem chi tiết hơn bằng kubectl describe daemonset kube-flannel-ds -n kube-system — image thuộc coreos/flannel.

Deploy một DaemonSet cho fluentd logging theo specification cho trước. Một cách là vào trang tài liệu Kubernetes để lấy specification. Nhưng vì cấu trúc rất giống một Deployment, cách dễ hơn là dùng option dry-run cho một Deployment để tạo một file rồi chỉnh sửa cần thiết. Tại thời điểm ghi hình bài giảng, không có lệnh create daemonset trực tiếp, nên phải tạo bằng cách giả lập một deployment. Tên là fluentd-elasticsearch, namespace là kube-system, image theo yêu cầu:

kubectl create deployment fluentd-elasticsearch -n kube-system --image=<image-được-cho> --dry-run=client -o yaml > fluentd.yaml

Chỉnh sửa file này: đổi kind thành DaemonSet. Không cần replicas vì DaemonSets không có replicas — so sánh, thấy không có replicas nên gỡ bỏ. Cũng gỡ bỏ strategy. Phần còn lại giữ nguyên. Lưu file và tạo:

kubectl create -f fluentd.yaml

Namespace là kube-system, và DaemonSet đã được tạo (chưa sẵn sàng ngay, nhưng ổn). Đây chỉ là một ví dụ đơn giản, không cung cấp thêm bất kỳ option hay tham số nào cho DaemonSet này.


3. Static Pods

Phần này nói về static pods trong Kubernetes. Ở 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?

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 option khi chạy service. Option này tên là podManifestPath, và ví dụ set là /etc/kubernetes/manifests. Cũng có một cách khác để cấu hình điều này — thay vì chỉ định option trực tiếp trong file kubelet.service, có thể 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.

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.

Lab: Thực hành Static Pods

Có bao nhiêu static pod tồn tại trong cluster, ở tất cả các namespace? Liệt kê pod ở tất cả namespace bằng kubectl get pods -A. Một cách để phân biệt static pod với các pod khác là nhìn vào tên — ở cuối tên sẽ có tên node được thêm vào. Có bốn static pod, tất cả đều được tạo trên controlplane.

Một cách khác để chắc chắn là xem chi tiết YAML của một pod bằng kubectl get pod <tên> -n kube-system -o yaml. Từ đây, cần xem section ownerReferences — nếu cuộn xuống, sẽ thấy owner của pod là node, kindNode, và namecontrolplane. Đó là một cách để chắc chắn liệu một pod có phải static pod hay không. Ngược lại, với một pod không phải static pod, xem ownerReferences sẽ thấy owner là một ReplicaSet.

Cái nào dưới đây không được deploy dưới dạng static pod? etcd, kube-apiserver, kube-controller-manager, và kube-scheduler đều là static pod. CoreDNS không phải static pod.

Cái nào dưới đây không được deploy? Tất cả đều là static pod ngoại trừ kube-proxy — kube-proxy không phải static pod (nó là DaemonSet).

Static pod hiện đang được tạo trên node nào? Tất cả đều trên controlplane.

Đường dẫn của thư mục chứa static pod definition file là gì? Một cách để xác định là xem kubelet config, tại /var/lib/kubelet/config.yaml. Trong file này, đây chính là kubelet configuration, và có section staticPodPath, hiện đang set là thư mục /etc/kubernetes/manifests. Kiểm tra thư mục đó — thấy các file được dùng để tạo static pod. Đây cũng là đường dẫn cần kiểm tra cho static pod path configuration trên bất kỳ node nào.

Có bao nhiêu pod definition file trong thư mục manifests? 4 file — khớp với số static pod đã thấy.

Docker image nào được dùng để deploy kube-apiserver dưới dạng static pod? Xem kube-apiserver.yaml trong thư mục manifests, image là k8s.gcr.io/kube-apiserver:1.20.0 (tại thời điểm ghi hình bài giảng — cần dùng đúng version thực tế trên hệ thống của mình khi trả lời).

Tạo một static pod tên static-busybox dùng image busybox và lệnh sleep. Lệnh để tạo một pod là kubectl run, tên static-busybox, image busybox, lệnh sleep 1000. Nhưng không muốn tạo pod này ngay, chỉ muốn lấy pod definition file và đặt nó vào static pod path của node. Dùng dry-run:

kubectl run static-busybox --image=busybox --dry-run=client -o yaml --command -- sleep 1000 > static-busybox.yaml

Lưu ý quan trọng: khi dùng command option (--command), luôn nhớ không đặt bất kỳ option nào của kubectl sau nó — bất cứ gì đặt sau hai dấu gạch ngang (--) sẽ được coi là option cho lệnh sẽ chạy bên trong container, không phải cho kubectl. Vậy command luôn phải đặt ở cuối cùng. Đặt --dry-run=client -o yaml trước command option. Chuyển output này vào file static-busybox.yaml, rồi di chuyển file vào thư mục /etc/kubernetes/manifests. Pod được tạo, trạng thái static-busybox-controlplane, Running.

Đổi image của static pod sang busybox:1.28.4. Để thay đổi một static pod, chỉ cần chỉnh sửa file và lưu. Vào /etc/kubernetes/manifests, chỉnh sửa file pod tương ứng, đổi image thành busybox:1.28.4, lưu lại. Pod chuyển sang Pending, có vấn đề pull image (do gõ nhầm tag). Sửa lại đúng và lưu — luôn xác nhận lại sau khi thực hiện thay đổi, đặc biệt trong kỳ thi, vì có thể gõ nhầm ở nhiều chỗ khác nhau.

Một static pod mới tên static-greenbox đã được tạo, tìm và xóa nó. Có một pod static-greenbox cần xóa. Thử xóa bằng lệnh delete thông thường — báo đã xóa, nhưng nếu kiểm tra lại sẽ thấy pod được tạo lại, vì đây là pod do kubelet quản lý. Không thể xóa một static pod chỉ bằng lệnh delete — nó sẽ bị xóa nhưng sẽ được tạo lại ngay. Cần tìm manifest file cho static pod đó và xóa nó. Kiểm tra thư mục /etc/kubernetes/manifests trên controlplane — không có file cho greenbox. Vì tên static pod cho biết nó được tạo trên node01, cần vào node01 để xóa.

Lấy địa chỉ IP các node bằng kubectl get nodes -o wide — control plane ở 10.38.102.6, node01 ở 10.38.102.8. SSH vào node01 (đôi khi có thể SSH trực tiếp bằng tên node01 tùy DNS, nếu không thì dùng địa chỉ IP nội bộ). Trên node01, tìm static pod manifest path — vào /etc/kubernetes/manifests, nhưng không thấy static-greenbox ở đó.

Quay lại kiểm tra kubelet configuration để tìm ra vấn đề: file tại /var/lib/kubelet/config.yaml. Trong file này, static pod path không phải/etc/kubernetes/manifests mà đã bị đổi thành /etc — một thay đổi cố ý để thử thách người học suy nghĩ. Và đúng vậy, static-greenbox.yaml nằm ở đó. Chỉ cần xóa file này khỏi vị trí đó, xác nhận đã xóa. Thoát SSH, quay lại control plane.

Liệt kê pod theo node và theo dõi (watch) chờ pod bị terminate — pod đang bị terminate, rồi biến mất.


4. Priority Classes

Phần này nói về PriorityClasses trong Kubernetes. Đã 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.

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-criticalsystem-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 đó.

Tạo một PriorityClass mới:

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

Với apiVersionscheduling.k8s.io/v1, kindPriorityClass, metadata với name, rồi định nghĩa value — đó là con số đã đề cập ở trên. Cuối cùng có description — đây là trường tùy chọn.

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.

Vậy priority vẫn áp dụng chỉ khi nó sắp được lập lịch — nhưng liệu nó có kill pod hiện có không? Sẽ preempt hay evict workload hiện có hay không? Điều đó được định nghĩa bởi preemptionPolicy.


Nguồn tham khảo

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