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:
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.
Mấu chốt ở đây nằm ở cấu hình affinity cho Pod như sau:
Lab1: Tạo file manifest deployment-nodeaffinity.yaml có nội dung như sau:
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
Như vậy các Pod sinh ra của deployment đều chạy trên 2 node medium-u1dz1w2wxm9hz9fg-node-bpmidfcz
và small-u1dz1w2wxm9hz9fg-node-t4xtgint
, đây là 2 node có label size=medium
và size=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:
Thì kết quả cho ra 10 Pod vẫn chỉ chạy trên 2 node medium-u1dz1w2wxm9hz9fg-node-bpmidfcz
và small-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.
Mấu chốt cho bài toán này nằm ở phần cấu hình nodeAffinity như sau:
Tạo file deployment-multi-nodeaffinity.yaml có nội dung như sau:
Thực hiện apply vào hệ thống và kiểm tra:
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.
Để 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:
Thực hiện apply vào hệ thống và kiểm tra:
Đú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
.
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:
Ta apply file trên vào hệ thống và kiểm tra kết quả:
Đú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:
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:
Lưu nội dung trên thành db-pod.yaml và apply vào hệ thống:
Tiếp theo ta sẽ tạo một deployment từ file deployment-pod-affinity.yaml như sau:
Tiến hành apply vào hệ thống và kiểm tra:
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.
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:
Thực hiện apply lại vào hệ thống vào kiểm tra:
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