Các bạn có từng thắc mắc khi chúng ta tạo ra một workload(pod/deployment) thì các pod được sinh ra sẽ được chạy trên node nào chưa? Trong bài viết này, Stringee sẽ giải đáp cho các bạn về cơ chế lập lịch trong K8s và một số ứng dụng của nó.

1. Các tham số quyết định tới lập lịch trên K8s

Kubernetes (hay K8s) là một nền tảng mã nguồn mở tự động hóa việc quản lý, scaling và triển khai ứng dụng dưới dạng container hay còn gọi là Container orchestration engine. Nó loại bỏ rất nhiều các quy trình thủ công liên quan đến việc triển khai và mở rộng các containerized applications.

>>> Xem thêm: Monitoring trên K8s cluster với Prometheus và Grafana

Khi chúng ta tạo ra một workload thành phần kube-scheduler mặc định của K8s sẽ là đối tượng có nhiệm vụ lập lịch thực thi các workload đó trên các node phù hợp. Các tham số có sự ảnh hưởng tới lập lịch trong K8s gồm có: static pod, nodeName, nodeSelector, affinity, resource request/limits.

Nhiệm vụ của kube-scheduler là tìm ra một node phù hợp để thực thi 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 mà chúng ta có thể sử dụng khi khai báo Pod:

- static pod: Đây là cách chúng ta có thể chạy một Pod trên một node cụ thể mà ta mong muốn.

- nodeName: Đây là cách chúng ta 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 giúp chúng ta 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. Nó 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: Ý tưởng của nó là 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: Đây không phải là tham số chúng ta sử dùng để lựa Node cho Pod. Tuy nhiên nó là điều kiện cần để Node được chọn. 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, chúng ta sẽ cùng tìm hiểu các khái niệm cơ bản về: staticPod, nodeName, nodeSelector, Taint/Toleration, Affinity và Resource request/limits.

>>> Xem thêm bài viết:

- Alert manager: Cấu hình cảnh báo bằng Prometheus cho hệ thống K8s

- Monitoring trên K8s cluster với Prometheus và Grafana

- Hướng dẫn cài đặt Web server Apache trên CentOS 7

2. Sử dụng static pod

static pod được quản lý trực tiếp bởi service kubelet trên các node mà 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 chúng ta 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. Điều này để giúp cho việc chúng ta có thể thấy được thông tin của Static Pod thông qua API Server nhưng chúng ta không thể điều khiển được các Pod này (vì nó được quản lý bởi kubelet trên node).

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]

2.1. Tạo static pod như thế nào?

Để tạo một static pod trên một node cụ thể chúng ta cần tạo một file manifest cho Pod tại thư mục mặc định của K8s /etc/kubernetes/manifests.

Service kubelet trên node đó sẽ định kỳ scan thư mục và tạo Pod từ file đó nếu có manifest thay đổi.

Dưới đây là một ví dụ, chúng ta sẽ tạo một file nginx.yaml vào manifest với nội dung như sau:

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: mynginx
  name: mynginx
spec:
  containers:
  - image: nginx
    name: mynginx
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

Sau khi tạo xong file manifest này, chúng ta chỉ cần chờ cho đến khi scheduler của K8s quét được file và tạo pod mới, chúng ta không cần thực hiện lệnh kubectl apply đâu nhé.

2.2. Sửa/xóa static pod

Như trên các bạn cũng có thể thấy rằng, chúng ta không cần thực hiện lệnh kubectl apply để tạo ra các pod. Vậy thì với các nghiệp vụ sửa/xóa static pod thì chúng ta cần thực hiện làm sao? Câu trả lời đơn giản là trạng thái của static pod chính là trạng thái tồn tại file manifest của nó trong thư mục chứa static pod.

Do đó:

- Để stop/delete static pod: Move 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/

3. 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à ta muốn dùng để chạy Pod này. Ví dụ mình muốn chạy một pod nginx trên node có tên worker1 thì sẽ thực hiện tạo file manifest pod-nodename.yaml như sau:

apiVersion: v1
kind: Pod
metadata:  
  labels:
    app: be
  name: pod-nodename
spec:
  containers:
  - image: nginx
    imagePullPolicy: IfNotPresent
    name: mynginx
    resources: {}
  nodeName: worker1
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

Sau đó apply file này vào hệ thống

kubectl apply -f pode-nodename.yaml

Như vậy Pod mới tạo ra đã được allocate vào node viettq-worker1 đúng như ta mong muốn.

Để tiếp tục làm rõ ta sẽ thử tạo ra một deploy để sinh ra nhiều Pod có cùng cấu hình nodeName xem sao. Ta sẽ tạo file deployment-nodename.yaml có nội dung như sau:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: deployment-nodename
  name: deployment-nodename
spec:
  replicas: 1
  selector:
    matchLabels:
      app: deployment-nodename
  strategy: {}
  template:
    metadata:
      labels:
        app: deployment-nodename
    spec:
      containers:
      - image: nginx
        imagePullPolicy: IfNotPresent
        name: nginx
        resources: {}
      nodeName: worker1

Sau đó chúng ta cần apply file này và kiểm tra lại các pod đã được tạo đã đúng như chúng ta mong muốn hay chưa.

4. Sử dụng nodeSelector

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

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

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

Và có một pod cần chạy tên node có cấu hình high tức là có label size=high thì ta có thể sử dụng tham số nodeSelector

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

apiVersion: v1
kind: Pod
metadata:  
  labels:
    app: using-nodeselector
  name: pod-nodeselector
spec:
  nodeSelector:
    size: high
  containers:
  - image: nginx
    imagePullPolicy: IfNotPresent
    name: mynginx
    resources: {}  
  dnsPolicy: ClusterFirst
  restartPolicy: Always

Ta apply file manifest vào hệ thống và kiểm tra:

kubectl apply -f pode-nodeselector.yaml

Một lưu ý dành cho các bạn ở đây là khi sử dụng nodeSelector để lựa chọn node phù hợp cho Pod thì ta chỉ có thể lựa chọn theo một label duy nhất. Nếu chỉ có nodeSelector không thể giải quyết được bài toán của bạn, chúng ta sẽ cần sử dụng tới nodeAffinity và nó sẽ được trình bày ngay sau đây thôi.

5. 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 đó. Hiểu đơn giản hơn thì việc 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 (hiểu là 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.

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

#Gán taint vào node:

kubectl taint node [node-name] [taint-key]=[taint-value]:[effect]

#Bỏ gán taint vào node:

kubectl taint node [node-name] [taint-key]=[taint-value]:[effect]-

#Kiểm tra một node có Taint hay không:

kubectl describe node [node-name] |grep Taint

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:

apiVersion: v1
kind: Pod
metadata:
  name: pod-without-toleration
  labels:
    type: without-toleration
spec:
  nodeSelector:
    taint: exists
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent

Để 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:

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-toleration
  labels:
    type: with-toleration
spec:
  nodeSelector:
    taint: exists
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations:
  - key: "app"
    operator: "Equal"
    value: "db"
    effect: "NoSchedule"

Kết

Trên đây là một số phương thức cơ bản để cấu hình phần lập lịch thực thi workload. Trong phần sau mình sẽ tiếp tục giới thiệu một số kỹ thuật phức tạp hơn một chút liên quan tới Affinity.


Stringee Communication APIs là giải pháp cung cấp các tính năng giao tiếp như gọi thoại, gọi video, tin nhắn chat, SMS hay tổng đài CSKH cho phép tích hợp trực tiếp vào ứng dụng/website của doanh nghiệp nhanh chóng. Nhờ đó giúp tiết kiệm đến 80% thời gian và chi phí cho doanh nghiệp bởi thông thường nếu tự phát triển các tính năng này có thể mất từ 1 - 3 năm.

Bộ API giao tiếp của Stringee hiện đang được tin dùng bởi các doanh nghiệp ở mọi quy mô, lĩnh vực ngành nghề như TPBank, VOVBacsi24, VNDirect, Shinhan Finance, Ahamove, Logivan, Homedy,  Adavigo, bTaskee…

Quý bạn đọc quan tâm xin mời đăng ký NHẬN TƯ VẤN TẠI ĐÂY: