Karpenter trên Amazon EKS

Ngày nay, Kubernetes đã trở thành một công cụ không thể thiếu trong việc quản lý các ứng dụng container hóa. Tuy nhiên, một trong những thách thức lớn khi vận hành Kubernetes là quản lý và mở rộng tài nguyên tính toán (compute resources) một cách hiệu quả. Trên Amazon EKS, AWS đã giới thiệu Karpenter, một công cụ autoscaler mã nguồn mở, linh hoạt và hiệu suất cao, giúp đơn giản hóa việc quản lý node trong cluster Kubernetes. Trong bài blog này, chúng ta sẽ cùng tìm hiểu Karpenter là gì, tại sao nên sử dụng nó trên EKS, và cách bắt đầu triển khai.
Karpenter là gì?
Karpenter là một dự án mã nguồn mở được phát triển bởi AWS, ra mắt lần đầu tại AWS re:Invent 2021. Không giống như Kubernetes Cluster Autoscaler (CA) truyền thống, vốn dựa vào các nhóm node (node groups) được định nghĩa trước để mở rộng hoặc thu hẹp cluster, Karpenter hoạt động trực tiếp với các yêu cầu tài nguyên của pod và tự động cung cấp các node phù hợp mà không cần quản lý node group. Điều này mang lại sự linh hoạt và hiệu quả vượt trội, đặc biệt với các workload có nhu cầu tài nguyên biến động lớn.
Karpenter có một số đặc điểm nổi bật:
Tự động cung cấp node: Dựa trên nhu cầu thực tế của pod, Karpenter chọn loại instance EC2 phù hợp và khởi chạy chúng trong vài giây.
Tối ưu chi phí: Hỗ trợ sử dụng Spot Instances và thay thế các node đắt đỏ bằng các lựa chọn rẻ hơn khi có thể.
Tích hợp chặt chẽ với AWS: Hoạt động mượt mà với EKS và các dịch vụ AWS khác như EC2, IAM, và CloudWatch.
Tại sao nên sử dụng Karpenter trên Amazon EKS?
Trước khi có Karpenter, người dùng EKS thường dựa vào Cluster Autoscaler kết hợp với Amazon EC2 Auto Scaling Groups (ASG) hoặc Managed Node Groups (MNG). Tuy nhiên, cách tiếp cận này có một số hạn chế:
Độ trễ cao: Cluster Autoscaler cần thời gian để tương tác với ASG, dẫn đến việc mở rộng cluster có thể mất vài phút.
Thiếu linh hoạt: ASG yêu cầu định nghĩa trước loại instance và cấu hình, không phù hợp với các workload đa dạng hoặc thay đổi nhanh.
Quản lý phức tạp: Việc duy trì nhiều node group cho các loại workload khác nhau tạo thêm gánh nặng vận hành.
Karpenter giải quyết những vấn đề này bằng cách:
Loại bỏ khái niệm node group, cho phép provision node trực tiếp dựa trên yêu cầu của pod.
Giảm thời gian mở rộng xuống còn vài giây thay vì vài phút.
Tối ưu hóa chi phí bằng cách chọn instance phù hợp nhất (bao gồm cả Spot Instances) và hợp nhất workload để giảm lãng phí tài nguyên.
Bắt đầu với Karpenter trên Amazon EKS
Dưới đây là hướng dẫn cơ bản để triển khai Karpenter trên một cluster EKS. Trước khi bắt đầu, bạn cần chuẩn bị:
Một cluster EKS đã hoạt động (có thể tạo bằng eksctl hoặc AWS Console).
Các công cụ: AWS CLI, kubectl, eksctl, và Helm đã được cài đặt.
Bước 1: Tạo cluster EKS (nếu chưa có)
Nếu bạn chưa có cluster EKS, có thể tạo nhanh bằng eksctl:
eksctl create cluster --name my-eks-cluster --region us-west-2 --version 1.28
Sau khi cluster được tạo, đảm bảo bạn có thể kết nối bằng kubectl:
aws eks update-kubeconfig --name my-eks-cluster --region us-west-2 kubectl get nodes
Bước 2: Cài đặt Karpenter bằng Helm
Karpenter được cài đặt thông qua Helm chart. Trước tiên, thêm Helm repository của Karpenter:
helm repo add karpenter
https://charts.karpenter.sh/
helm repo update
Tạo namespace cho Karpenter và cài đặt:
kubectl create namespace karpenter
helm install karpenter karpenter/karpenter --namespace karpenter --version v0.33.1 \
--set settings.clusterName=my-eks-cluster \
--set settings.interruptionQueue=my-eks-cluster \
--wait
Bước 3: Cấu hình IAM Role cho Karpenter
Karpenter cần quyền để quản lý EC2 instances. Sử dụng IAM Roles for Service Accounts (IRSA) để cấp quyền:
Tạo IAM policy với các quyền cần thiết (dựa trên tài liệu chính thức của Karpenter).
Gắn policy vào một IAM role và liên kết với service account của Karpenter:
eksctl create iamserviceaccount \
--name karpenter \
--namespace karpenter \
--cluster my-eks-cluster \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonEKSClusterPolicy \
--approve
Bước 4: Tạo NodePool và EC2NodeClass
Karpenter sử dụng hai tài nguyên chính để định nghĩa cách provision node:
NodePool: Xác định các yêu cầu và giới hạn cho node (ví dụ: loại instance, capacity type).
EC2NodeClass: Cung cấp thông tin cụ thể về AWS (AMI, subnet, security group).
Ví dụ cấu hình NodePool:
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand", "spot"]
- key: node.kubernetes.io/instance-type47
operator: In
values: ["t3.large", "t3a.large"]
nodeClassRef:
name: default
disruption:
consolidationPolicy: WhenEmpty
consolidateAfter: 30s
Ví dụ cấu hình EC2NodeClass:
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
name: default
spec:
amiFamily: AL2
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: my-eks-cluster
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: my-eks-cluster
Áp dụng các file này:
kubectl apply -f nodepool.yaml
kubectl apply -f ec2nodeclass.yaml
Bước 5: Kiểm tra hoạt động của Karpenter
Tạo một deployment để kiểm tra khả năng autoscaling:
apiVersion: apps/v1
kind: Deployment
metadata:
name: inflate
spec:
replicas: 10
selector:
matchLabels:
app: inflate
template:
metadata:
labels:
app: inflate
spec:
containers:
- name: inflate
image: public.ecr.aws/amazonlinux/amazonlinux:latest
command: ["sleep", "3600"]
resources:
requests:
cpu: "1"
memory: "1Gi"
Áp dụng và quan sát:
kubectl apply -f inflate.yaml kubectl get nodes -w
Bạn sẽ thấy Karpenter tự động khởi chạy các node mới để đáp ứng nhu cầu của pod.
Ví dụ 1: Sử dụng Labels để Chỉ định Loại Instance và Vùng
Trong ví dụ này, chúng ta sẽ cấu hình một NodePool để chỉ sử dụng các instance loại t3.large hoặc t3a.large trong một Availability Zone (AZ) cụ thể, đồng thời gắn nhãn (labels) cho các node được tạo ra.
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: web-app
spec:
template:
spec:
requirements:
- key: node.kubernetes.io/instance-type
operator: In
values: ["t3.large", "t3a.large"]
- key: topology.kubernetes.io/zone
operator: In
values: ["us-west-2a"]
labels:
app: web-app
environment: production
nodeClassRef:
name: default
disruption:
consolidationPolicy: WhenEmpty
consolidateAfter: 30s
---
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
name: default
spec:
amiFamily: AL2
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: my-eks-cluster
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: my-eks-cluster
Giải thích:
requirements: Giới hạn loại instance và AZ mà Karpenter có thể sử dụng.
labels: Gắn nhãn app: web-app và environment: production cho các node được tạo. Các pod có thể sử dụng nodeSelector để nhắm đến các node này.
Pod ví dụ sử dụng nodeSelector:
apiVersion: v1 kind: Pod metadata: name: web-pod spec: containers: - name: nginx image: nginx nodeSelector: app: web-app environment: production
Ví dụ 2: Sử dụng Taints và Tolerations
Trong ví dụ này, chúng ta sẽ cấu hình một NodePool để tạo các node với taints, chỉ cho phép các pod có tolerations tương ứng được chạy trên đó.
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: gpu-workload
spec:
template:
spec:
requirements:
- key: node.kubernetes.io/instance-type
operator: In
values: ["g4dn.xlarge"] # Instance có GPU
taints:
- key: workload
value: gpu
effect: NoSchedule
labels:
workload: gpu-enabled
nodeClassRef:
name: default
disruption:
consolidationPolicy: WhenEmpty
consolidateAfter: 30s
---
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
name: default
spec:
amiFamily: AL2
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: my-eks-cluster
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: my-eks-cluster
Giải thích:
taints: Các node được tạo sẽ có taint workload=gpu:NoSchedule, nghĩa là chỉ các pod có toleration phù hợp mới được chạy trên node này.
labels: Gắn nhãn workload: gpu-enabled để dễ dàng nhắm mục tiêu bằng nodeSelector.
Pod ví dụ với toleration:
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
containers:
- name: gpu-app
image: nvidia/cuda:11.0-base
resources:
limits:
nvidia.com/gpu: 1
tolerations:
- key: workload
operator: Equal
value: gpu
effect: NoSchedule
nodeSelector:
workload: gpu-enabled
Ví dụ 3: Kết hợp Labels, Taints, và Capacity Type (Spot vs On-Demand)
Ví dụ này tạo một NodePool ưu tiên sử dụng Spot Instances cho các workload không quan trọng, với labels và taints để phân loại.
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: batch-jobs
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"] # Chỉ dùng Spot Instances
- key: node.kubernetes.io/instance-type
operator: In
values: ["m5.large", "m5a.large"]
taints:
- key: workload
value: batch
effect: NoExecute
labels:
role: batch-processing
nodeClassRef:
name: default
disruption:
consolidationPolicy: WhenUnderutilized
---
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
name: default
spec:
amiFamily: AL2
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: my-eks-cluster
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: my-eks-cluster
Giải thích:
capacity-type: Chỉ định sử dụng Spot Instances để tiết kiệm chi phí.
taints: Taint workload=batch:NoExecute đảm bảo chỉ các pod chịu được taint này mới chạy trên node.
labels: Nhãn role: batch-processing để phân loại node.
Pod ví dụ:
apiVersion: v1
kind: Pod
metadata:
name: batch-pod
spec:
containers:
- name: batch-job
image: busybox
command: ["sleep", "3600"]
tolerations:
- key: workload
operator: Equal
value: batch
effect: NoExecute
nodeSelector:
role: batch-processing
Ví dụ 4: Phân bổ Node cho Workload Đa Dạng với Weight
Trong trường hợp bạn muốn ưu tiên một số loại instance hơn các loại khác, bạn có thể sử dụng weight trong NodePool.
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: mixed-workload
spec:
template:
spec:
requirements:
- key: node.kubernetes.io/instance-type
operator: In
values: ["t3.medium", "t3.large", "c5.large"]
weight: 10 # Ưu tiên thấp hơn
- key: node.kubernetes.io/instance-type
operator: In
values: ["m5.large", "m5a.large"]
weight: 20 # Ưu tiên cao hơn
labels:
purpose: mixed
nodeClassRef:
name: default
disruption:
consolidationPolicy: WhenEmpty
consolidateAfter: 30s
Giải thích:
weight: Karpenter sẽ ưu tiên các instance m5.large và m5a.large (weight 20) hơn t3.medium, t3.large, c5.large (weight 10) khi có thể.
Pod không cần toleration vì không có taint, nhưng có thể dùng nodeSelector với purpose: mixed.
EC2NodeClass là gì?
EC2NodeClass là một Custom Resource Definition (CRD) trong Karpenter, được thiết kế để cung cấp thông tin cụ thể về cách Karpenter tương tác với hạ tầng AWS EC2 khi tạo các node trong cluster Kubernetes (trên EKS). Nếu như NodePool định nghĩa các yêu cầu chung về tài nguyên (như loại instance, capacity type, labels, taints), thì EC2NodeClass tập trung vào các chi tiết liên quan đến AWS, chẳng hạn như AMI (Amazon Machine Image), subnet, security group, và các cấu hình khác liên quan đến EC2.
Nói cách khác:
NodePool: Quyết định "node sẽ trông như thế nào" (loại instance, yêu cầu tài nguyên, v.v.).
EC2NodeClass: Quyết định "node sẽ được triển khai như thế nào trên AWS" (dùng AMI nào, chạy trong subnet nào, áp dụng security group nào).
Vai trò của EC2NodeClass
Khi Karpenter cần tạo một node mới để đáp ứng nhu cầu của pod, nó sẽ:
Nhìn vào NodePool để biết yêu cầu tài nguyên (instance type, capacity type, v.v.).
Tham chiếu đến EC2NodeClass được liên kết trong NodePool để lấy thông tin cụ thể về cách triển khai node trên AWS.
Gọi API của AWS EC2 để khởi chạy instance với các thông số từ cả hai tài nguyên này.
Vì vậy, EC2NodeClass đóng vai trò như một cầu nối giữa logic của Karpenter và hạ tầng AWS, đảm bảo các node được tạo ra phù hợp với môi trường EKS.
Các thành phần chính của EC2NodeClass
Dưới đây là các trường quan trọng trong một EC2NodeClass (dựa trên phiên bản v1beta1 của Karpenter):
amiFamily:
Xác định loại AMI (Amazon Machine Image) được sử dụng cho node.
Các giá trị phổ biến:
AL2: Amazon Linux 2 (mặc định cho EKS).
Bottlerocket: Một hệ điều hành tối ưu cho container.
Ubuntu: Dùng AMI dựa trên Ubuntu.
Custom: Nếu bạn muốn dùng AMI tùy chỉnh.
Ví dụ: amiFamily: AL2.
subnetSelectorTerms:
Chỉ định subnet mà Karpenter sẽ sử dụng để khởi chạy EC2 instance.
Dùng các tag để lọc subnet (thường là subnet được gắn tag bởi EKS).
Ví dụ:
subnetSelectorTerms: - tags: karpenter.sh/discovery: my-eks-cluster
securityGroupSelectorTerms:
Chỉ định security group áp dụng cho EC2 instance.
Tương tự như subnetSelectorTerms, dùng tag để lọc.
Ví dụ:
securityGroupSelectorTerms: - tags: karpenter.sh/discovery: my-eks-cluster
role (tùy chọn):
Chỉ định IAM Instance Profile gắn vào EC2 instance. Instance Profile này cần có quyền để node tham gia cluster EKS (thường được quản lý bởi EKS).
Nếu không khai báo, Karpenter sẽ dựa vào cấu hình mặc định từ EKS.
Ví dụ: role: my-eks-node-role.
amiSelectorTerms (tùy chọn):
Dùng khi bạn muốn chỉ định AMI cụ thể thay vì dựa vào amiFamily.
Hỗ trợ lọc AMI bằng tag hoặc ID.
Ví dụ:
amiSelectorTerms: - tags: custom-ami: my-ami
userData (tùy chọn):
Cho phép bạn cung cấp script hoặc dữ liệu khởi tạo tùy chỉnh khi instance được khởi chạy (ví dụ: cài đặt thêm phần mềm).
Thường dùng với amiFamily: Custom.
userData: | #!/bin/bash echo "Custom setup" > /tmp/setup.log
tags (tùy chọn):
Gắn các tag tùy chỉnh vào EC2 instance để dễ quản lý hoặc theo dõi chi phí.
Ví dụ:
tags: environment: production owner: team-a
Ví dụ 1: Cấu hình cơ bản với Amazon Linux 2
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
name: default
spec:
amiFamily: AL2
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: my-eks-cluster
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: my-eks-cluster
tags:
purpose: general-workload
Giải thích:
Dùng AMI Amazon Linux 2.
Chọn subnet và security group dựa trên tag karpenter.sh/discovery (thường được EKS gắn sẵn).
Gắn tag purpose: general-workload cho instance.
Ví dụ 2: Sử dụng Bottlerocket và IAM Role
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
name: bottlerocket
spec:
amiFamily: Bottlerocket
role: my-eks-node-role
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: my-eks-cluster
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: my-eks-cluster
Giải thích:
Dùng Bottlerocket làm hệ điều hành (tối ưu cho container).
Chỉ định IAM Instance Profile my-eks-node-role để node có quyền tham gia cluster.
Ví dụ 3: AMI tùy chỉnh với UserData
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
name: custom-ami
spec:
amiFamily: Custom
amiSelectorTerms:
- id: ami-0123456789abcdef0
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: my-eks-cluster
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: my-eks-cluster
userData: |
#!/bin/bash
yum install -y httpd
systemctl start httpd
Giải thích:
Dùng AMI tùy chỉnh với ID cụ thể.
Thêm script userData để cài đặt và khởi động Apache HTTP Server khi instance khởi chạy.
Một số lưu ý quan trọng
-
- Đây là tag mặc định mà EKS sử dụng để đánh dấu subnet và security group. Đảm bảo cluster của bạn đã được cấu hình với tag này (thường tự động khi dùng eksctl).
IAM Role:
- Nếu không khai báo role trong EC2NodeClass, Karpenter sẽ dựa vào cấu hình mặc định của EKS. Tuy nhiên, bạn nên kiểm tra IAM Instance Profile để đảm bảo node có quyền cần thiết.
Subscribe to my newsletter
Read articles from Kilo directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
