+1

Kubernetes resource management - Quản trị tài nguyên trong Kubernetes

Tại sao phải quản lý tài nguyên trong Kubernetes?

image.png

Cái gì muốn hoạt động hiệu quả đều cần phải được quản lý, Kubernetes đã có cơ chế tự động phân phối các pod ra đều các node tuy nhiên đó chỉ là phần của quản lý tài nguyên. Ngoài ra trên thực tế thì có khá nhiều các yếu tố các mà chúng ta sẽ phải suy nghĩ tới do Kubernetes mặc định chưa có các khả năng để làm việc đó. Việc quản lý tài nguyên trong Kubernetes hiệu quả sẽ giúp chúng ta:

  • Tiết kiệm chi phí hoạt động (Cost): Đây có lẽ là yếu tố nhiều người quan nhất 😄. Việc quản lý tài nguyên hiệu quả có thể giúp ta tiết kiệm rất nhiều chi phí hoạt động của cluster. Mình đã gặp nhiều trường hợp có thể tối ưu từ 30 đến 70% chi phí chỉ bằng việc quản lý tài nguyên hiệu quả hơn.
  • Tăng cường độ ổn định (Stability): Khi các ứng dụng được phân bổ tài nguyên hợp lý thì các tài nguyên này hoạt động sẽ ổn định hơn.

Trong bài viết này, mình sẽ giới thiệu đến các bạn những khái niệm giúp quản lý tài nguyên trong Kubernetes, cùng với đó là một vài kinh nghiệm cá nhân của bản thân trong từng khái niệm, công cụ này.

Allocate Resources (Node)

Allocate Resources là thông tin được thể hiện trên mỗi node của Kubernetes. Thông số này thể hiện lượng tài nguyên bạn có thể sử dụng cho mỗi node. Để có thể xem được thông tin này bạn chạy câu lệnh sau

kubectl describe node <node-name>

Ví dụ output sẽ có dạng như sau:

Name:               node-pool-1
Roles:              <none>
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    kubernetes.io/hostname=node-pool-1
                    node-role.kubernetes.io/worker=true
Annotations:        csi.volume.kubernetes.io/nodeid: {"ebs.csi.aws.com":"i-0a12345b67890cdef"}
                    volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp:  Mon, 10 Aug 2024 10:45:23 +0700
Taints:             node.kubernetes.io/unreachable:NoSchedule
Unschedulable:      false
Lease:
  HolderIdentity:  node-pool-1
  AcquireTime:     Mon, 13 Oct 2024 10:45:35 +0700
  RenewTime:       Mon, 13 Oct 2024 11:00:35 +0700

Capacity: # <= Cấu hình của node
  cpu:                4000mi
  ephemeral-storage:  100Gi
  hugepages-2Mi:      0
  memory:             16Gi
  pods:               110
Allocatable: # <= Lượng tài nguyên có thể sử dụng
  cpu:                3496mi
  ephemeral-storage:  95Gi
  hugepages-2Mi:      0
  memory:             15Gi
  pods:               110

System Info:
  Machine ID:                 1234567890abcdef
  System UUID:                98765432-1A2B-3C4D-5E6F-789012345678
  Boot ID:                    11223344-5566-7788-99AA-BBCCDDEEFF00
  Kernel Version:             5.11.0-1019-aws
  OS Image:                   Ubuntu 20.04.6 LTS
  Operating System:           linux
  Architecture:               amd64
  Container Runtime Version:  docker://20.10.8
  Kubelet Version:            v1.24.3
  Kube-Proxy Version:         v1.24.3

Conditions:
  Type             Status    LastHeartbeatTime                 LastTransitionTime                Reason             Message
  ----             ------    -----------------                 ------------------                ------             -------
  MemoryPressure   False     Mon, 13 Oct 2024 11:00:05 +0700    Mon, 10 Aug 2024 10:45:23 +0700   KubeletHasSufficientMemory   kubelet has sufficient memory available
  DiskPressure     False     Mon, 13 Oct 2024 11:00:05 +0700    Mon, 10 Aug 2024 10:45:23 +0700   KubeletHasNoDiskPressure     kubelet has no disk pressure
  PIDPressure      False     Mon, 13 Oct 2024 11:00:05 +0700    Mon, 10 Aug 2024 10:45:23 +0700   KubeletHasSufficientPID      kubelet has sufficient PID available
  Ready            True      Mon, 13 Oct 2024 11:00:05 +0700    Mon, 10 Aug 2024 10:45:23 +0700   KubeletReady                 kubelet is posting ready status

Addresses:
  InternalIP:  192.168.1.15
  Hostname:    node-pool-1

Non-terminated Pods:          (12 in total)
  Namespace                   Name                            CPU Requests  CPU Limits   Memory Requests  Memory Limits  AGE
  ---------                   ----                            ------------  ----------   ---------------  -------------  ---
  kube-system                 kube-proxy-abc123               100m (2%)     200m (5%)    64Mi (0%)        128Mi (0%)     45d
  kube-system                 coredns-12345abc                100m (2%)     100m (2%)    70Mi (0%)        70Mi (0%)      45d
  kube-system                 fluentd-abc123                  200m (5%)     200m (5%)    200Mi (1%)       400Mi (2%)     45d
  default                     nginx-deployment-abc123         500m (12%)    1 (25%)      512Mi (3%)       1Gi (6%)       10d

Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource           Requests     Limits
  --------           --------     ------
  cpu                1.6 (40%)    2.5 (62%)
  memory             846Mi (5%)   1.598Gi (10%)
  ephemeral-storage  0 (0%)       0 (0%)
  hugepages-2Mi      0 (0%)       0 (0%)

Events:
  Type    Reason                   Age                  From                     Message
  ----    ------                   ----                 ----                     -------
  Normal  NodeHasSufficientMemory   45d                  kubelet                  Node node-pool-1 status is now: NodeHasSufficientMemory
  Normal  NodeHasNoDiskPressure     45d                  kubelet                  Node node-pool-1 status is now: NodeHasNoDiskPressure
  Normal  NodeReady                 45d                  kubelet                  Node node-pool-1 status is now: NodeReady

Hãy chú ý đến 2 phần CapacityAllocatable.

  • Capacity để hiện tài nguyên thực tế của Node. Ví dụ trong hình là 1 node có cấu hình 4 vCPU - 16 GB Memory - 100 GB Disk. đây là cấu hình bạn yêu cầu từ Cloud provider nếu bạn sử dụng Cloud.
  • Tuy nhiên thực tế khi bạn thêm 1 node cấu hình như trên vào cụm K8s thì bạn không thể sử dụng hết 100% lượng tài nguyên này đâu. Lượng tài nguyên thực tế có thể sử dụng để chạy ứng dụng trong K8s trên node này được thể hiện ở Allocatable. Nhiều người hay lầm tưởng khi order 1 node 4 vCPU thì có thể sử dụng toàn bộ lượng tài nguyên này, tuy nhiên thực tế K8s sẽ để dư ra 1 phần để chạy các thành phần các như các process hệ thống hay chính những thành phần của K8s. Trong ví dụ này lượng tài nguyên có thể sử dụng sẽ thấp hơn Capacity3496mi CPU - 15GB Memory - 95GB Disk.

Theo mình biết thì không có 1 cách nào tính được lượng Allocatable dựa trên Capacity. Cái này mình sẽ phải tham khảo trực tiếp ở từng loại node. Tuy nhiên thông số Allocatable/Capacity sẽ càng lớn ở những node có cấu hình lớn, và ngược lại sẽ nhỏ hơn ở các node có cấu hình nhỏ. Vì vậy đôi khi sử dụng 1 vài node lớn sẽ chứa được nhiều resource hơn chạy ở nhiều node nhỏ có cùng tổng cấu hình CPU - Memory!

Lựa chọn loại node cho Kubernetes cluster cũng là một việc rất quan trọng.

  • Nếu cluster của bạn dùng để chạy các microservice có pod sử dụng tài nguyên ở mức nhỏ thì sử dụng nhiều node có cấu hình nhỏ sẽ phù hợp do thuận tiện hơn trong việc autoscale node và sử dụng được toàn bộ tài nguyên trong node đó.
  • Nếu cluster bạn chạy nhiều loại ứng dụng khác nhau như microservices + databases thì cluster bạn có thể tiếp cận theo hướng sử dụng nhiều Node Pool cho cluster này. Node pool 1 có cấu hình nhỏ chạy các microservice, Node pool 2 có cấu hình cao hơn để chạy database do thường database sẽ sử dụng 1 lượng lớn memory.

Resource Quota (namespace)

Resource Quota giúp giới hạn tổng tài nguyên mà một namespace có thể sử dụng. Điều này rất hữu ích khi bạn có nhiều nhóm làm việc trong cùng một cluster và muốn đảm bảo rằng không có nhóm nào chiếm dụng toàn bộ tài nguyên. Ví dụ đối với các cluster có nhiều team dev sử dụng, ta có thể phân quyền cho mỗi team dev sử dụng 1 namespace và giới hạn lượng resource có thể sử dụng ở mỗi namespace thông qua Resource Quota

Manifest cho Resource Quota sẽ có dạng:

# resource-quota.yml file
apiVersion: v1
kind: ResourceQuota
metadata:
  name: dev-team-quota
spec:
  hard:
    pods: "10"
    requests.cpu: "4"
    requests.memory: "8Gi"
    limits.cpu: "8"
    limits.memory: "16Gi"

Với ví dụ trên, toàn bộ pod trong namespace phải thỏa mãn các điều kiện đã định nghĩa trong file manifest:

  • Trong namespace có thể có tối đa 10 pods
  • Tổng lượng CPU request và limit trong namespace là 4 và 8
  • Tổng lượng Memory request và limit trong namespace là 8GB và 16 GB

Chạy command sau để apply Resource Quota:

kubectl apply -f resource-quota.yml --namespace dev-team-a

Resource request/limit

Resource request và limit là cấu hình được cấu hình cho container nằm trong pod khi triển khai trên Kuberntes. Cấu hình này định nghĩa cách tài nguyên sẽ được phân bổ cho container đó. Cấu hình Resource request và limit sẽ có dạng như sau:

apiVersion: v1
kind: Pod
metadata:
  name: resource-example
spec:
  containers:
    - name: app
      image: busybox
      resources:
        requests:
          memory: "256Mi"
          cpu: "500m"
        limits:
          memory: "512Mi"
          cpu: "1"
  • Resource Requests: Là lượng tài nguyên tối thiểu mà một container yêu cầu. Scheduler sẽ dựa trên thông số này để xác định node có đủ tài nguyên để chạy pod hay không.
  • Resource Limits: Là giới hạn tài nguyên tối đa mà một container có thể sử dụng. Nếu container vượt quá giới hạn này, nó sẽ bị giới hạn (throttle đối với CPU hoặc bị OOMKilled đối với bộ nhớ).

Trong ví dụ trên:

  • Container yêu cầu ít nhất 0.5 CPU và 256Mi Memory.
  • Container không được sử dụng quá 1 CPU và 512Mi Memory. Nếu nó sử dụng quá 512Mi Memory, Kubernetes sẽ kill container này.

Việc cấu hình resource request và limit là một vấn đề khá đau đầu khi cân bằng giữa chi phíhiệu năng vì chỉ cần cấu hình tài nguyên thấp 1 chút thôi có thể dẫn đến việc pod bị kill do sử dụng quá nhiều Memory.

Source: CAST AI

Trước khi đến với việc cấu hình resource request và limit thế nào cho phù hợp thì chúng ta trước hết cần phải biết đến khái niệm Quality of Service trong Kuberntes

Quality of Service (QoS)

Quality of Service (QoS) giúp Kubernetes ưu tiên việc phân bổ tài nguyên cho các pod dựa trên thông số resource request và limit mà chúng cấu hình. Kubernetes phân loại QoS thành ba nhóm: Guaranteed, Burstable và Best-Effort. Đại khái đây là cách chúng ta đánh độ quan trọng cho từng pod trong Kubernets. Trong trường hợp node bị quá tải tài nguyên nó sẽ kill những pod có mức độ ưu tiên thấp nhất để đẩy sang các node khác cho đến khi node có mức sử dụng ổn định thì dừng lại. Trường hợp này các pod bạn có thể thấy sẽ bị rơi vào trạng thái Evicted 😄

Vậy làm sao để định nghĩa được Quality of Service cho pod?

image.png

  • Guaranteed: Nếu cả request và limit của CPU và bộ nhớ đều bằng nhau, pod sẽ được gán QoS "Guaranteed". Đây là mức ưu tiên cao nhất khi hệ thống bị thiếu tài nguyên.
  • Burstable: Nếu request thấp hơn limit, pod sẽ thuộc nhóm "Burstable", có mức ưu tiên trung bình.
  • Best-Effort: Nếu pod không cấu hình request hoặc limit, nó sẽ được gán QoS "Best-Effort", có mức ưu tiên thấp nhất. Pod này sẽ bị ảnh hưởng nhiều nhất khi tài nguyên trở nên khan hiếm.

Những loại ứng dụng nào thì nên sử dụng Quality of Service thế nào?

  • Theo kinh nghiệm của mình những ứng dụng Stateful như database, message queue hoặc những ứng dụng critical như payment,... thì nên cấu hình sử dụng QoS là Guaranteed. Mức tài nguyên request = limit nên cấu hình ở ngưỡng resource mà pod sử dụng ở mức cao nhất trong vòng 1 tuần cộng thêm 10%.

Ví dụ: pod Redis sử dụng làm cache có ngưỡng sử dụng tài nguyên memory trong vòng 1 tuần như sau: Min: 1,1 GB, Avg: 1,4 GB Max: 1,9 GB thì mình sẽ cấu hình resource request = resource limit = 2,1 GB

  • Đối với QoS Burstable mình sẽ sử dụng cho các ứng dụng Stateless có mức độ chịu lỗi cao (Đơn giản là khi bị kill đột ngột thì ứng dụng vẫn gracefull shutdown, không sinh ra lỗi). Thường là các API, cronjob,...

Ví dụ: Pod API Python có ngưỡng sử dụng tài nguyên memory trong vòng 1 tuần như sau: Min: 300 MB, Avg: 350 MB Max: 440 MB Mình sẽ cấu hình memory request là 350 MB và memory limit là 450MB

Việc cấu hình memory request bằng mức tài nguyên sử dụng trung bình sẽ giúp pod được cung cấp đủ tài nguyên để hoạt động ở trạng thái bình thường. Memory limit sẽ cao hơn 1 chút so với điểm max memory để đảm bảo trong trường hợp peak hours pod cũng ít có khả năng bị kill, đảm bảo hệ thống ổn định hơn.

  • Còn lại QoS Best-Effort mình sẽ ưu tiên sử dụng cho các pod cung cấp tính năng phụ cho Kuberntes như: Logging, Tracing, Monitoring,... Do khi các pod này bị kill thì đen nhất là mất metric trong vài giây thôi chứ không có critical issues nào.

Kết

Tóm lại, việc quản lý tài nguyên trong Kubernetes là một yếu tố quan trọng giúp đảm bảo rằng các ứng dụng hoạt động hiệu quả và không gây ảnh hưởng đến các ứng dụng khác trong cùng một cluster. Bằng cách sử dụng Resource Requests và Limits, Resource Quotas, cùng với cơ chế QoS, bạn có thể kiểm soát chặt chẽ cách các tài nguyên được sử dụng, từ đó tối ưu hóa hiệu suất và tính ổn định của hệ thống.

Nếu thấy bài viết hay và đem lại giá trị cho bạn thì đừng ngại ngần cho mình 1 Upvote + 1 Follow nhé. Đó là động lực để mình chia sẻ thêm các kiến thức khác về Kubernetes, Devops ✌️

Have a nice day!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí