Scheduling trên Kubernetes (phần 1)

1787
20-06-2023
Scheduling trên Kubernetes (phần 1)

Mở đầu

Bài viết này sẽ giới thiệu về một chủ đề trong đó là lập lịch trên kubernetes. Khi tạo ra một workload (pod/deployment..) thì làm sao để có thể biết được rằng các Pod tạo ra sẽ được chạy trên node nào? Bài viết này sẽ giải thích cơ chế lập lịch của k8s và ứng dụng trong tình huống thực tế.

Các tham số quyết định tới lập lịch (scheduling)

Khi tạo ra một workload, kube-scheduler của kubernetes có nhiệm vụ lập lịch thực thi các workload đó trên các node phù hợp.

Vậy để xác định được node nào phù hợp để chạy một workload, thì kube-scheduler có thể xem xét các tham số ảnh hưởng tới việc chọn node cho workload như sau:

  • Static Pod
  • nodeName
  • nodeSelector
  • Taint & Toleration
  • Node/Pod Affinity
  • Resource Request/Limits

Về cơ bản nhiệm vụ của kube-scheduler là tìm ra một node phù hợp nhất để thực thi một Pod. Kết quả cuối cùng là sẽ cập nhật tham số "nodeName" trong thông tin của Pod.

Các phương thức có thể sử dụng khi khai báo Pod

  • static pod: Đây là chạy một Pod trên một node cụ thể
  • nodeName: Đây là cách gán trực tiếp thông tin của Node mà ta mong muốn sẽ chạy Pod của chúng ta (Bản chất của lập lịch sử dụng các phương thức khác thì cũng đưa về kết quả là gán được một node vào trong tham số nodeName này)
  • nodeSelector: Cách này lựa chọn Node cho Pod theo một label cụ thể của Node
  • Taint/Toleration: Đây là một loại đánh dấu đặc biệt dành cho các Node, có tác dụng chỉ cho phép những Pod có đặc tính tương đương (toleration) được chạy trên các Node có taint tương ứng.
  • Affinity: Cho phép cấu hình Pod trên node với điều kiện node/pod đó có label thỏa mãn một điều kiện cho trước.
  • Resource request/limits: Node sẽ chỉ thích hợp (eligible) để được chọn nếu phần tài nguyên của nó còn đủ đáp ứng tài nguyên yêu cầu (resource requests) của Pod, ví dụ như CPU/RAM..

Trong bài viết này sẽ nói về một số phương thức cơ bản như static pod, nodeName, nodeSelectortaint/tolerations.

Sử dụng BKE để tạo ra cluster kubernetes 1.25.6 với 3 node worker

Scheduling trên Kubernetes (phần 1) - Ảnh 1.

Sử dụng static pod

Static pod được quản lý trực tiếp bởi service kubelet trên các node, không cần giám sát bởi API-server. Static Pod luôn được gán vào một Kubelet ở một node cụ thể.

Khi tạo ra một static pod thì kubelet cũng sẽ tạo ra một bản sao Pod đó trên kubernetes API server. Tên của các Static Pod sẽ có thêm hậu tố là hostname của node mà nó đang chạy theo format: [pod-name]-[node-hostname]

Cách tạo static Pod

Để tạo một static pod trên một node cụ thể thì cần tạo file định nghĩa (manifest) cho Pod tại thư mục mặc định /etc/kubernetes/manifests/. Service kubelet trên node sẽ định kỳ scan thư mục này và nếu phát hiện có file manifest mới thì nó sẽ tạo ra Pod từ file manifest đó.

Truy cập vào node là pool-qnnzooev-u1dz1w2wxm9hz9fg-node-kbseozza và tạo file manifest mynginx.yaml ở đường dẫn /etc/kubernetes/manifests/ có nội dung như sau:

Scheduling trên Kubernetes (phần 1) - Ảnh 2.

Lưu ý: Chỉ cần tạo file manifest thôi, không cần chạy lệnh kubectl apply! Kubelet sẽ tự động scan thư mục này và tạo Pod

Kiểm tra kết quả:

Scheduling trên Kubernetes (phần 1) - Ảnh 3.

Có thể thấy tên Pod lúc này là mynginx-pool-qnnzooev-u1dz1w2wxm9hz9fg-node-kbseozza chính là [pod-name]-[node-hostname].

Lưu ý: Khi tạo các static pod thì sẽ không quan tâm tới các giá trị Taint đang có ở node đó.

Sửa/xóa static Pod

Trạng thái của static pod chính là trạng thái tồn tại file manfinest của nó trong thư mục chứa static pod.

  • Để stop/delete static pod: Xóa file manifest của Pod ra khỏi thư mục static pod (/etc/kubernetes/manifests/)
  • Để create/start static pod: Tạo file manifest vào thư mực static pod (/etc/kubernetes/manifests/)

Xem thông tin cấu hình thư mục chứa static pod của kubelet

Để verify chính xác thư mục chứa static pod của kubelet thì các bạn có thể check theo cách sau: Lấy thông tin config của service kubelet:

Scheduling trên Kubernetes (phần 1) - Ảnh 4.

Ta sẽ xem config của kubelet ở file /var/lib/kubelet/kubelet-config.yaml

Scheduling trên Kubernetes (phần 1) - Ảnh 5.

Mọi người có thể thấy tham số staticPodPath chính là tham số khai báo thư mục chứa các static pod của node chạy kubelet này.

Sử dụng nodeName

Tham số nodeName có thể được sử dụng trong khai báo Pod để chỉ định một node cụ thể mà muốn dùng để chạy Pod này. Ví dụ muốn chạy một pod nginx trên node có tên pool-qnnzooev-u1dz1w2wxm9hz9fg-node-lxvdwep2 thì sẽ thực hiện tạo file manifest pod-nodename.yaml như sau:

Scheduling trên Kubernetes (phần 1) - Ảnh 6.

Sau đó apply file này vào hệ thống để tạo Pod:

Scheduling trên Kubernetes (phần 1) - Ảnh 7.

Như vậy Pod mới tạo ra đã được allocate vào node pool-qnnzooev-u1dz1w2wxm9hz9fg-node-lxvdwep2

Tiếp tục sẽ thử tạo ra một deployment để tạo ra nhiều Pod có cùng cấu hình nodeName. Ta sẽ tạo file deployment-nodename.yaml có nội dung như sau:

Scheduling trên Kubernetes (phần 1) - Ảnh 8.

Sau đó ta apply file này vào hệ thống và kiểm tra:

Scheduling trên Kubernetes (phần 1) - Ảnh 9.

Scheduling trên Kubernetes (phần 1) - Ảnh 10.

Bây giờ dù có scale số lượng Pod của deployment này là bao nhiêu thì toàn bộ Pod sinh ra bởi deployment sẽ đều có tham số nodeName: pool-qnnzooev-u1dz1w2wxm9hz9fg-node-rqforlu5 do đó sẽ đều được tạo trên node này.

Sử dụng nodeSelector

Ý tưởng của việc sử dụng tham số nodeSelector này đó là mong muốn cấu hình các Pod chỉ chạy trên một tập các node có lable nhất định.

Ví dụ trên hệ thống của chúng ta có 3 worker node mà được gán nhãn như sau:

Scheduling trên Kubernetes (phần 1) - Ảnh 11.

Các bạn có thể gán nhãn (label) cho node bằng cú pháp sau:

kubectl label nodes [node-name] [key]=[value]

Hoặc nếu dùng BKE để tạo label theo node pool (Nên sử dụng cách này vì khi scale thì label được gán thủ công theo cách trên sẽ không lưu trữ lại được). Một node pool là tập hợp các node có cùng cấu hình.

Tạo thêm node pool mới có 2 nodes, với label

Scheduling trên Kubernetes (phần 1) - Ảnh 12.

Scheduling trên Kubernetes (phần 1) - Ảnh 13.

Khi có nhu cầu một Pod cần chạy trên node có cấu hình lớn (tương ứng nhãn

size=large 

) thì sử dụng tham số nodeSelector.

Scheduling trên Kubernetes (phần 1) - Ảnh 14.

Ta sẽ định nghĩa Pod bằng file manifest pod-nodeselector.yaml có nội dung như sau:

Scheduling trên Kubernetes (phần 1) - Ảnh 15.

Apply file manifest trên vào hệ thống và kiểm tra:

Scheduling trên Kubernetes (phần 1) - Ảnh 16.

Scheduling trên Kubernetes (phần 1) - Ảnh 17.

Scheduling trên Kubernetes (phần 1) - Ảnh 18.

Tiếp tục kiểm chứng, tạo một file deployment manifest có tên deployment-nodeselector.yaml có nội dung như sau:

Scheduling trên Kubernetes (phần 1) - Ảnh 19.

Ta apply file manifest này vào hệ thống và kiểm tra kết quả:

Scheduling trên Kubernetes (phần 1) - Ảnh 20.

Như vậy thì có 10 Pod của deployment này được tạo ra và đều chạy trên nodepool pool-12wq4rcc, lý do là vì các node của pool này có label thỏa mãn điều kiện nodeSelector của Pod (size=large).

Lưu ý: Chỉ có 2 node pool-12wq4rcc-u1dz1w2wxm9hz9fg-node-vxd8ie08pool-12wq4rcc-u1dz1w2wxm9hz9fg-node-agg1jnr0 thỏa mãn điều kiện nodeSelector của deployment trên. Nên dù có scale deployment này thành 20 hay 100 Pod thì sẽ vẫn chỉ chạy trên 2 node thuộc pool trên (nếu có đủ tài nguyên).

Taint & Toleration

Taint là một loại nhãn đặc biệt dùng để đánh dấu node có những đặc trưng nào đó, xác định bởi một bộ key-value và một action khi Pod không thỏa mãn điều kiện key-value đó.

Gán Taint vào một node giống như việc chúng ta khóa một node bằng một cái ổ khóa (coi taint là ổ khóa), và (ổ khóa) này đặc trưng bởi một label key=value và một action kèm theo (gọi là effect nếu không có (chìa khóa).

Action này có thể là:

  • NoSchedule ==> Không có (chìa khóa) thì sẽ không được lên lịch chạy trên node này.
  • NoExecute ==> Không có (chìa khóa) thì sẽ có thể được lên lịch trên node này nhưng Pod sẽ không được thực thi.

Có (khóa) thì phải có (chìa). Để mở khóa (Taint) cho phép Pod có thể được chạy trên Node có Taint, thì Pod đó phải khai báo Tolerations (chìa khóa) đặc trưng với một label key=value và một Action kèm theo tương tự như với Taint.

Lưu ý: Một node có thể có nhiều Taint, khi đó Pod được gán vào Node nếu có tất cả các Toleration tương ứng với các Taint của node.

Scheduling trên Kubernetes (phần 1) - Ảnh 21.

Trong ví dụ ở hình trên pool pool-dxzy94ue đang có gán 1 Taint là app=db:NoSchedule.

Pod1 có khai báo Tolerations có key-value (app=db) tương ứng với Taint khai báo trên 2 pool do đó có thể được chạy trên các node thuộc pool pool-dxzy94ue Với pool pool-ugrgeq9xkhông có Taint thì mặc nhiên Pod1 cũng có thể được lập lịch chạy trên node này. Do đó Pod1 có thể được chạy trên cả 2 pool

Pod2 không có khai báo Tolerations do đó nó không thể được lập lịch chạy trên các node có Taint. Do đó nó chỉ có thể được chạy trên node không có taint còn lại là pool-ugrgeq9x.

Cú pháp lênh với taint:

Scheduling trên Kubernetes (phần 1) - Ảnh 22.

Ta sẽ gắn taint ở trên BKE khi tạo 1 node pool mới, tương tự như gán label, ta gắn theo pool thì sẽ không bị mất khi scale node

Scheduling trên Kubernetes (phần 1) - Ảnh 23.

Trước tên ta sẽ tạo một file manifest pod-without-toleration.yaml để định nghĩa Pod không có Toleration và cấu hình nodeSelector để cho Pod này cố tình chạy trên node có Taint sử dụng nodeSelector theo giá trị taint-yes:

Scheduling trên Kubernetes (phần 1) - Ảnh 24.

Chúng ta apply file trên vào và kiểm tra kết quả:

Scheduling trên Kubernetes (phần 1) - Ảnh 25.

Scheduling trên Kubernetes (phần 1) - Ảnh 26.

Scheduling trên Kubernetes (phần 1) - Ảnh 27.

Như vậy Pod này ở trạng thái Pending vì đang cố gắng lựa chọn 2 node pool-dxzy94ue-u1dz1w2wxm9hz9fg-node-p6ar0prspool-dxzy94ue-u1dz1w2wxm9hz9fg-node-tcbwxmzj để chạy theo cấu hình nodeSelector tuy nhiên 2 node này đều đang có Taint mà Pod không khai báo Toleration tương ứng. Do đó Pod sẽ không thể được allocate vào bất cứ node nào trong 2 node này dẫn tới trạng thái Pending

Để tạo Pod mới có thể chạy được trên Node có Taint thì ta phải khai báo tham số tolerations tương ứng với Taint của node. Ta sẽ tạo file manifest pod-with-toleration.yaml để định nghĩa Pod mới có toleration như sau:

Scheduling trên Kubernetes (phần 1) - Ảnh 28.

Sau đó apply vào hệ thống và kiểm tra:

Scheduling trên Kubernetes (phần 1) - Ảnh 29.

Scheduling trên Kubernetes (phần 1) - Ảnh 30.

Như vậy là Pod mới này đã chạy được cả trên Node có gán taint.

Tổng kết

**StaticPod** sẽ khai báo trong file /etc/kubernetes/manifest và sẽ được quản lý bới kubelet, bản copy của pod sẽ được ghi lên apiserver để theo dõi

Khi gán trực tiếp tham số **nodeName** cho Pod thì k8s sẽ không quan tâm node đó có đang có taint hay không mà sẽ gán ngay và luôn node đó cho Pod

Khi sử dụng **nodeSelector** thì các bạn cần lưu ý các node được chọn (theo rule của selector)

Khi sử dụng Taint thì sẽ cần toleration tương ứng

>> Xem tiếp: Scheduling trên Kubernetes (phần 2)

SHARE