Scheduling trên Kubernetes (phần 2)

1526
15-06-2023
Scheduling trên Kubernetes (phần 2)

Mở đầu

Trong bài viết Scheduling trên Kubernetes (phần 1) đã giới thiệu một số cách thức sử dụng trong lập lịch thực hiện các workload như:

Static Pod Sử dụng nodeName Sử dụng nodeSelector Sử dụng Taint/Toleration

Bài viết này sẽ tiếp tục giới thiệu một số cách thức lập lịch trong Kubernetes được sử dụng trong cấu hình ứng dụng phục vụ các yêu cầu lập lịch khác nhau như:

NodeAffinity: Lựa chọn node thỏa mã các điều kiện về labels của node PodAffinity: Tạo Pod trên node mà trên đó phải có Pod khác có label thỏa mãn điều kiện cho trước PodAntiAffinity: Tạo Pod trên node mà trên đó phải KHÔNG CÓ Pod khác có label thỏa mãn điều kiện cho trước

Trong môi trường lab đang cài đặt kubernetest v1.25.6 có 3 worker pool như sau:

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

Sử dụng NodeAffinity

Có bài toán là cấu hình cho Pod chỉ chạy trên các Node có cấu hình lớn (size=large). Tuy nhiên với bài toán ngược lại: chỉ chọn các node có cấu hình vừa và nhỏ để chạy Pod (size != large) thì cách dùng nodeSelector sẽ không giải quyết được bài toán.

Bởi nodeSelector chỉ cho phép định nghĩa ra tập node theo một điều kiện label duy nhất. Vậy để giải quyết bài toán này thì sẽ phải dùng tới cấu hình **nodeAffinity**.

Ý tưởng của cấu hình nodeAffinity giống với nodeSelector đó là đều lựa chọn node để thực thi Pod, tuy nhiên điều kiện lựa chọn của nodeAffinity thì linh động và đa dạng hơn so với nodeSelector, có thể sử dụng nhiều toán tử so sánh hơn.

Khi sử dụng cấu hình NodeAffinity thì chúng ta có 2 loại:

requiredDuringSchedulingIgnoredDuringExecution (hard): Scheduler sẽ không thể lập lịch cho Pod cho đến khi rule của NodeAffinity được thỏa mãn. preferredDuringSchedulingIgnoredDuringExecution (soft): Với cấu hình này thì ta thông báo cho scheduler "cố gắng hết sức" để tìm ra các node thỏa mãn điều kiện. Tuy nhiên khi đã "cố gắng hết sức" rồi mà không tìm dc node nào thỏa mã thì Pod sẽ vẫn được lập lịch.

Sử dụng hard nodeAffinity

Tiếp tục trở lại bài toán ban đầu là làm sao để lựa chọn các node có cấu hình "không lớn" để chạy Pod. Lúc này sẽ sử dụng cấu hình NodeAffinity.

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

Mấu chốt ở đây nằm ở cấu hình affinity cho Pod như sau:

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

Lab1: Tạo file manifest deployment-nodeaffinity.yaml có nội dung như sau:

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

Thực hiện apply vào hệ thống và kiểm tra kết quả:

quanlm@quanlm-desktop:~$ kubectl apply -f deployment-nodeaffinity.yaml

deployment.apps/deployment-nodeaffinity created

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

Như vậy các Pod sinh ra của deployment đều chạy trên 2 node medium-u1dz1w2wxm9hz9fg-node-bpmidfczsmall-u1dz1w2wxm9hz9fg-node-t4xtgint, đây là 2 node có label size=mediumsize=small thỏa mãn điều kiện của nodeAffinity đã cấu hình cho deployment.

Tiếp tục scale 10 pod để kiểm tra:

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

Thì kết quả cho ra 10 Pod vẫn chỉ chạy trên 2 node medium-u1dz1w2wxm9hz9fg-node-bpmidfczsmall-u1dz1w2wxm9hz9fg-node-t4xtgint thỏa mãn điều kiện của nodeAffinity.

Lab2

Cấu hình để Pod chỉ chạy trên node có ổ SSD (disktype=ssd) và có cấu hình không nhỏ (size not small). 2 yêu cầu trên cho kết quả Pod chỉ được chạy trên pool pool-medium. Để thực hiện yêu cầu này vẫn dùng cấu hình NodeAffinity tuy nhiên sẽ phải dùng 2 điều kiện lọc.

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

Mấu chốt cho bài toán này nằm ở phần cấu hình nodeAffinity như sau:

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

Tạo file deployment-multi-nodeaffinity.yaml có nội dung như sau:

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

Thực hiện apply vào hệ thống và kiểm tra:

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

Lúc này kết quả cho thấy cả 3 Pod đều chạy trên node medium-u1dz1w2wxm9hz9fg-node-bpmidfcz đúng như mong muốn.

Lab3 Cấu hình để Pod chỉ chạy trên node có ổ SSD (disktype=ssd) và có cấu hình lớn (size=large). Thực tế với hiện trạng 3 node của chúng thì sẽ không có node nào thỏa mãn cả 2 điều kiện trên ==> Pod sinh ra sẽ ở trạng thái Pending.

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

 Để thực hiện yêu cầu này ta sẽ vẫn dùng cấu hình NodeAffinity. 

Tạo file deployment-multi-nodeaffinity.yaml có nội dung như sau:

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

Thực hiện apply vào hệ thống và kiểm tra:

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

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

Đúng như dự đoán, 3 Pod sinh ra bởi deployment này đều ở trạng thái Pending.

Nguyên nhân là đang cấu hình tham số requiredDuringSchedulingIgnoredDuringExecution do đó khi không tìm được node thỏa mãn thì Pod sẽ ở trạng thái Pending → Sử dụng soft nodeAffinity.

 Sử dụng soft nodeAffinity

Trong lab trên khi thiết lập các cấu hình nodeAffinity và không có node nào thỏa mãn thì ứng dụng của chúng sẽ không được lập lịch và thực hiện.

Do đó thay vì bắt buộc phải tuân theo rule đó thì có thể lựa chọn cấu hình "soft rule" tức là cố gắng hết sức để lựa chọn theo điều kiện nhưng nếu không thể tìm được node thỏa mãn thì sẽ vẫn lập lịch thực hiện.

Lab1: Ưu tiên cao nhất cho việc lập lịch cho Pod trên node có size=large, nếu không có thì tiếp tục ưu tiên node có ổ SSD disktype=ssd.

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

 Với yêu cầu và hiện trạng như trên thì các Pod sinh ra sẽ luôn được ưu tiên chạy trên node pool pool-large.

Tạo file deployment-prefer-nodeaffinity.yaml có nội dung như sau:

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

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

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

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

Đúng như lý thuyết đã phân tích, các Pod đều được lập lịch thực hiện trên node large-u1dz1w2wxm9hz9fg-node-lmedw5vq.

Sử dụng PodAffinity

Khi sử dụng nodeAffinity thì đang sử dụng các labels của node để làm điều kiện lựa chọn. Còn với PodAffinity thì nó vẫn là cách thức lựa chọn node nhưng là dựa vào labels của các Pod chạy trên node.

Cấu hình PodAffinity tưng tự với việc Pod-A chỉ ưu tiên/yêu cầu chạy trên một node nào đó mà đang có Pod-B đang chạy. Giúp chúng ta có thể tùy biến cho các Pod ứng dụng có giao tiếp nhiều với nhau thì ưu tiên chạy trên chung một node để tối ưu kết nối.

Và tương tự như nodeAffinity thì podAffinity cũng có 2 loại:

- `requiredDuringSchedulingIgnoredDuringExecution`

- `preferredDuringSchedulingIgnoredDuringExecution`

Xem xét một ví dụ như sau:

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

Trên hệ thống đã triển khai sẵn ứng dụng db, các Pod của DB có label là app=db. Muốn deploy các Pod BE của tôi lên nhưng node nào có đang chạy DB để tối ưu phần kết nối.

Để giải quyết yêu cầu trên chúng ta sẽ sử dụng cấu hình podAffinity. Đầu tiên ta sẽ setup 2 Pod chạy trên 2 node và có label là app=db để giả định DB đang chạy trên 2 node này.

Ta sẽ tạo 2 Pod sử dụng cấu hình nodeName để assign trực tiếp vào node:

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

 Lưu nội dung trên thành db-pod.yaml và apply vào hệ thống:

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

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

 Tiếp theo ta sẽ tạo một deployment từ file deployment-pod-affinity.yaml như sau:

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

 Tiến hành apply vào hệ thống và kiểm tra:

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

 Kết quả đúng như mong đợi, các Pod của BE chỉ chạy trên các node có Pod của DB

Tuy nhiên lúc này Pod BE đều chạy trên 1 node. Sẽ là tốt hơn nếu 2 Pod của BE lại chia đều trên 2 node có chạy Pod DB. Vấn đề này sẽ được giải quyết khi sử dụng cấu hình podAntiAffinity.

Sử dụng PodAntiAffinity

PodAntiAffinity tưng tự với việc Pod-A chỉ chấp nhận chạy trên một node nào đó mà đang không có Pod-B đang chạy.

Như trong ví dụ trên, đề bài muốn là 2 Pod của BE sẽ chạy trên 2 node có Pod DB.

Chúng ta đã giải quyết được 1 nửa bài toán là chỉ cho Pod BE chạy trên node có Pod DB

Còn lại ý tưởng giải quyết sẽ là "ưu tiên không chạy Pod Be trên node đã có Pod BE". Nghĩa là một node sẽ chỉ có tối đa 1 Pod Be được chạy, như vậy sẽ giải quyết hoàn toàn bài toán.

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

 Chúng ta sẽ cập nhật lại deployment bên trên để sử dụng thêm cấu hình podAntiAffinity nữa như sau:

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

 Thực hiện apply lại vào hệ thống vào kiểm tra:

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

 Như vậy là 2 Pod BE đã chia đều đúng trên 2 node có chạy Pod DB, cấu hình podAntiAffinity mình chỉ sử dụng mode là preferredDuringSchedulingIgnoredDuringExecution để ưu tiên cho các Pod chia đều ra các node.

Tuy nhiên thực tế sẽ có các bài toán số lượng replicas (số Pod) nhiều hơn số node do đó nên để tham số prefer (soft) để nếu có vi phạm thì Pod sẽ vẫn được lên lịch thực hiện chứ không bị pending

Tổng kết

Qua bài viết ta tổng kết lại 1 số kiến thức như sau:

NodeAffinity: sẽ lựa chọn node thỏa mã các điều kiện về labels của node

PodAffinity: sẽ tạo Pod trên node mà trên đó phải có Pod khác có label thỏa mãn điều kiện cho trước

PodAntiAffinity: sẽ tạo Pod trên node mà trên đó phải KHÔNG CÓ Pod khác có label thỏa mãn điều kiện cho trước

requiredDuringSchedulingIgnoredDuringExecution (hard): Scheduler sẽ không thể lập lịch cho Pod cho đến khi rule của Affinity được thỏa mãn.

preferredDuringSchedulingIgnoredDuringExecution (soft): Với cấu hình này thì ta thông báo cho scheduler "cố gắng hết sức" để tìm ra các node thỏa mãn điều kiện. Tuy nhiên khi đã "cố gắng hết sức" rồi mà không tìm dc node nào thỏa mã thì Pod sẽ vẫn được lập lịch.

>> Có thể bạn quan tâm: 9 Lợi ích chính của việc ứng dụng Kubernetes

SHARE