Tự động co giãn Kubernetes với Karpenter: Rút ngắn thời gian cấp phát EKS từ 5 phút xuống 45 giây

Vấn đề với cơ chế Autoscaling truyền thống trên EKS

Lưu lượng truy cập tăng đột biến trên AWS Elastic Kubernetes Service (EKS) thường làm lộ rõ một điểm nghẽn khó chịu: thời gian khởi động node mới quá chậm. Khi tải bất ngờ tăng cao, các pod rơi vào trạng thái Pending từ 3 đến 7 phút để chờ có thêm tài nguyên tính toán. Nếu bạn vận hành các API nhạy cảm về độ trễ, vài phút chậm trễ này đồng nghĩa với việc kết nối bị rớt, mã lỗi 504 xuất hiện và trải nghiệm người dùng bị ảnh hưởng tiêu cực.

Điểm nghẽn: Gánh nặng từ Auto Scaling Group

Trong khi việc scale workload ở tầng pod có thể dựa vào các giải pháp tự động co giãn theo sự kiện với KEDA, thì ở tầng hạ tầng node, Kubernetes Cluster Autoscaler (CAS) từng là công cụ mở rộng quy mô tiêu chuẩn trong nhiều năm. Tuy nhiên, CAS không thể tự mình khởi chạy các instance điện toán. Thay vào đó, nó phải dựa vào một lớp trung gian: AWS EC2 Auto Scaling Groups (ASG).

Lớp trung gian này tạo ra độ trễ đáng kể. Khi các pod không tìm thấy node khả dụng, CAS sẽ quét các node group tĩnh, cập nhật số lượng mong muốn (desired capacity) trên một ASG phù hợp rồi đợi EC2 phản hồi. Nếu cụm của bạn chạy nhiều loại workload khác nhau—chẳng hạn như các node GPU cho machine learning, instance ARM64 Graviton cho web worker, GitHub Actions Runner tự động co giãn và Spot instance cho xử lý batch—bạn sẽ nhanh chóng phải quản lý từ 15 đến 25 ASG riêng biệt. Mô hình này dẫn đến hai hạn chế lớn:

  • Độ trễ cấp phát cao: Việc chờ đợi tín hiệu vòng đời của ASG, quá trình khởi tạo EC2 instance và bootstrap kubelet thường mất từ 3 đến 6 phút.
  • Lãng phí chi phí đám mây: Các pod hiếm khi lấp đầy hoàn toàn các kích thước instance cố định. Một dịch vụ yêu cầu 3.2 vCPU trên một instance cố định 4 vCPU sẽ để lãng phí 20% dung lượng máy chủ, nhưng AWS vẫn tính tiền cho toàn bộ server đó.

Cách Karpenter giải quyết triệt để vấn đề

Karpenter loại bỏ hoàn toàn Auto Scaling Group khỏi quy trình. Nó theo dõi trực tiếp các pod đang chờ xử lý (pending) thông qua Kubernetes API server và gọi thẳng lệnh ec2:CreateFleet để khởi chạy các instance khớp chính xác với yêu cầu của pod. Các node được đăng ký và sẵn sàng hoạt động chỉ trong khoảng 45 giây. Bằng cách gom pod với mật độ tối ưu trên các loại instance phù hợp nhất, Karpenter thường xuyên giúp giảm từ 30% đến 50% hóa đơn tài nguyên tính toán (bổ trợ đắc lực cho quy trình ước tính chi phí Cloud trong CI/CD).

Bắt đầu nhanh (5 phút)

Làm theo các bước sau để triển khai Karpenter trên cụm EKS hiện có bằng Helm và cấu hình cấp phát node động.

1. Cài đặt Karpenter qua Helm

Xác nhận cụm EKS của bạn đã bật OpenID Connect (OIDC) hoặc EKS Pod Identity cùng các IAM role cần thiết (thường được cấu hình sẵn thông qua mã nguồn Terraform). Sau đó tiến hành cài đặt Karpenter chart:

export KARPENTER_VERSION="1.0.0"
export CLUSTER_NAME="production-eks-cluster"

helm install karpenter oci://public.ecr.aws/karpenter/karpenter \
  --version "${KARPENTER_VERSION}" \
  --namespace karpenter \
  --create-namespace \
  --set "settings.clusterName=${CLUSTER_NAME}" \
  --set "serviceAccount.annotations.eks\.amazonaws\.com/role-arn=arn:aws:iam::123456789012:role/KarpenterControllerRole" \
  --wait

2. Định nghĩa EC2NodeClass và NodePool

Karpenter v1 hoạt động dựa trên hai Custom Resource Definition (CRD) chính:

  • EC2NodeClass: Cấu hình các thiết lập ở tầng AWS, bao gồm subnet, security group, họ AMI và ổ cứng lưu trữ.
  • NodePool: Thiết lập phạm vi lập lịch pod, kiến trúc vi xử lý của instance, hình thức mua (purchase options) và chính sách gián đoạn (disruption).

Áp dụng manifest sau để bật tính năng cấp phát node động:

apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: default
spec:
  amiFamily: AL2023
  role: KarpenterNodeInstanceRole
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: production-eks-cluster
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: production-eks-cluster
---
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: general-compute
spec:
  template:
    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64", "arm64"]
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["c", "m", "r"]
  limits:
    cpu: 1000
    memory: 2000Gi
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 1m

3. Kiểm tra khả năng mở rộng quy mô động

Tạo một deployment thử nghiệm yêu cầu 10 bản sao (replicas) với mức phân bổ CPU đáng kể:

kubectl create deployment inflate --image=public.ecr.aws/eks-distro/kubernetes/pause:3.7 --replicas=10
kubectl set resources deployment inflate --requests=cpu=1,memory=1Gi

Để debug Kubernetes và kiểm tra quá trình cấp phát, hãy theo dõi log của Karpenter controller để xem cách nó đánh giá các yêu cầu đang chờ, truy vấn AWS Fleet API và khởi chạy ngay lập tức node tối ưu nhất:

kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller

Phân tích chuyên sâu: Karpenter so với Cluster Autoscaler

Tại sao Karpenter lại nhanh hơn nhiều so với Cluster Autoscaler? Sự khác biệt cốt lõi nằm ở mô hình lập lịch (scheduling model).

Cách thức Karpenter lập lịch node

Cluster Autoscaler hoạt động theo kiểu phản ứng (reactive) bên trong các ranh giới nhóm cứng nhắc. Khi một pod ở trạng thái pending, CAS kiểm tra xem có ASG hiện có nào chứa được nó không. Nếu khớp nhóm, CAS tăng số lượng instance của ASG đó và đợi AWS EC2 Auto Scaling hoàn tất vòng đời khởi chạy.

Karpenter lại đóng vai trò như một trợ lý lập lịch chủ động (proactive). Vòng lặp điều khiển của nó thực hiện qua bốn giai đoạn rõ ràng:

  1. Gom nhóm (Grouping): Karpenter tập hợp các pod đang pending dựa trên affinity, taint, toleration và tài nguyên yêu cầu.
  2. Lựa chọn Instance (Instance Selection): Controller kiểm tra giá EC2 và năng lực của từng loại instance theo thời gian thực. Từ đó tính toán cấu hình instance tiết kiệm chi phí nhất để chứa trọn vẹn nhóm pod đó.
  3. Cấp phát trực tiếp (Direct Provisioning): Karpenter gọi trực tiếp ec2:CreateFleet. Cơ chế này bỏ qua launch template của ASG, độ trễ từ warm pool và chi phí kiểm tra định kỳ (polling).
  4. Tự động đăng ký (Automatic Registration): EC2 instance gia nhập control plane của EKS và các pod được gắn vào ngay khi node chuyển sang trạng thái Ready.

Tự động đóng gói tài nguyên (Bin-Packing) và gom cụm node

Các node group truyền thống thường chỉ chạy ở mức 30% đến 45% công suất CPU. Việc thu hồi dung lượng dư thừa thường đòi hỏi các kịch bản drain tùy chỉnh hoặc bộ điều khiển từ bên thứ ba.

Karpenter giải quyết bài toán này thông qua tính năng gom cụm chủ động (consolidation). Với thiết lập consolidationPolicy: WhenEmptyOrUnderutilized, nó liên tục theo dõi mật độ của cụm. Ví dụ: khi lưu lượng giảm vào ban đêm và 3 pod đang nằm trên một máy m6i.2xlarge giá 0,38 USD/giờ có thể vừa vặn trên một máy c6i.large giá 0,096 USD/giờ, Karpenter sẽ thực hiện di chuyển theo hình thức cuốn chiếu:

  1. Khởi chạy node c6i.large mới để thay thế.
  2. Drain và cordon node m6i.2xlarge một cách an toàn.
  3. Hủy instance lớn hơn sau khi tất cả các pod đã chạy ổn định trên instance mới.

Kỹ thuật nâng cao

1. Ưu tiên tối ưu chi phí với Spot và Graviton

Kết hợp vi xử lý AWS Graviton (ARM64) với EC2 Spot instance có thể giảm chi phí hạ tầng từ 60% đến 80% so với việc dùng các node x86 On-Demand. Chẳng hạn, một Spot instance c7g.xlarge thường chỉ tốn khoảng 0,05 USD/giờ so với 0,17 USD/giờ của một instance On-Demand c6i.xlarge.

Bạn có thể áp dụng chiến lược này trực tiếp bên trong NodePool:

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: cost-optimized
spec:
  template:
    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
        - key: kubernetes.io/arch
          operator: In
          values: ["arm64"]
        - key: karpenter.k8s.aws/instance-generation
          operator: Gt
          values: ["5"]
      expireAfter: 720h
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 30s
    budgets:
      - nodes: "20%"

2. Phát hiện sai lệch cấu hình (Drift Detection) và nâng cấp node tự động

Việc cập nhật AMI cho worker node trong mô hình EKS thông thường đòi hỏi bạn phải cập nhật launch template, điều chỉnh ASG và xoay vòng instance thủ công. Karpenter hỗ trợ xử lý lệch cấu hình một cách tự nhiên. Khi bạn cập nhật tag AMI hoặc security group trong EC2NodeClass, Karpenter sẽ đánh dấu các node cũ là bị lệch cấu hình (drifted). Sau đó, nó dần dần thay thế chúng trong khi vẫn tuân thủ disruption budget, giúp hệ thống luôn hoạt động liên tục trong suốt quá trình triển khai.

Kinh nghiệm thực tiễn cho môi trường Production

Dưới đây là bốn nguyên tắc an toàn vận hành được đúc kết từ việc quản trị các cụm EKS có lưu lượng lớn:

  • Đa dạng hóa các loại Spot Instance: Tránh cố định Karpenter vào một dòng instance duy nhất như c5.xlarge. Hãy cho phép nhiều dòng máy như c6i, c7g, m6i và m7g. Việc lựa chọn phạm vi rộng sẽ ngăn ngừa lỗi khởi chạy khi một Availability Zone của AWS rơi vào tình trạng khan hiếm tài nguyên Spot.
  • Thiết lập Pod Disruption Budget (PDB): Karpenter thực hiện gom cụm rất quyết liệt. Nếu thiếu PDB, việc drain node quá nhanh có thể gây gián đoạn dịch vụ tạm thời. Hãy luôn cấu hình PDB với minAvailable: 1 hoặc maxUnavailable: 20% cho các dịch vụ quan trọng.
  • Cô lập Controller trên các node On-Demand: Chạy pod controller của Karpenter trên một managed node group dùng On-Demand hoặc trên AWS Fargate. Nếu Karpenter chạy trên một Spot node bị thu hồi, cụm sẽ không thể cấp phát tài nguyên thay thế cho đến khi controller được khởi động lại ở nơi khác.
  • Rà soát kỹ các tag trên Subnet và Security Group: Karpenter tìm kiếm VPC subnet và security group thông qua các AWS tag như karpenter.sh/discovery: <cluster-name>. Đảm bảo các tag này đã có đầy đủ trên tất cả các private subnet và security group mục tiêu trong mã Terraform hoặc OpenTofu của bạn.
Share: