EKS Cost Optimization
Phần này trình bày các chiến lược tối ưu chi phí khi vận hành EKS: từ lựa chọn compute (Spot, Graviton, Fargate), right-sizing resource, đến các công cụ giám sát và phân bổ chi phí.
📌 1. Tổng quan chi phí EKS
1.1. Cấu trúc chi phí
| Thành phần | Chi phí | Ghi chú |
|---|---|---|
| EKS Control Plane | $0.10/giờ (~$73/tháng) | Cố định, không phụ thuộc số Node |
| EC2 Worker Nodes | Theo instance type | Phần lớn chi phí nằm ở đây |
| EBS Volumes | Theo GB + IOPS | PersistentVolume cho stateful workload |
| NAT Gateway | $0.045/giờ + data transfer | Tốn kém nếu traffic outbound lớn |
| Load Balancer | ALB ~$22/tháng + LCU | Mỗi Ingress/Service tạo một LB |
| CloudWatch / Logging | Theo data ingested | Dễ bị phình nếu không kiểm soát |
| Data Transfer | Theo GB cross-AZ, cross-Region | Thường bị bỏ quên |
1.2. Nguyên tắc tối ưu
- Right-size - Chọn đúng lượng resource cần thiết
- Right-type - Chọn đúng loại compute (Spot, Graviton, Fargate)
- Right-time - Scale theo nhu cầu, tắt khi không dùng
- Visibility - Theo dõi chi phí liên tục, phân bổ theo team/service
💰 2. Tối ưu Compute (EC2 Worker Nodes)
2.1. Spot Instances
Spot Instances giúp tiết kiệm lên đến 90% so với On-Demand. Phù hợp cho workload stateless, fault-tolerant.
Cấu hình Managed Node Group với Spot:
resource "aws_eks_node_group" "spot" {
cluster_name = aws_eks_cluster.main.name
node_group_name = "spot-workers"
node_role_arn = aws_iam_role.node.arn
subnet_ids = var.private_subnet_ids
capacity_type = "SPOT"
instance_types = ["m5.large", "m5a.large", "m5d.large", "m6i.large"]
scaling_config {
desired_size = 3
max_size = 10
min_size = 1
}
labels = {
"capacity-type" = "spot"
}
taint {
key = "spot"
value = "true"
effect = "NO_SCHEDULE"
}
}
Schedule workload lên Spot nodes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: batch-processor
spec:
replicas: 5
template:
spec:
tolerations:
- key: "spot"
operator: "Equal"
value: "true"
effect: "NoSchedule"
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: capacity-type
operator: In
values:
- spot
containers:
- name: processor
image: my-batch-app:latest
resources:
requests:
cpu: 500m
memory: 512Mi
Xử lý Spot Interruption:
Spot Instance có thể bị thu hồi bất cứ lúc nào (cảnh báo trước 2 phút). Cần cài AWS Node Termination Handler:
helm repo add eks https://aws.github.io/eks-charts
helm install aws-node-termination-handler eks/aws-node-termination-handler \
--namespace kube-system \
--set enableSpotInterruptionDraining=true \
--set enableScheduledEventDraining=true
- Luôn khai báo nhiều instance types (ít nhất 4-5 loại) để tăng khả năng có capacity.
- Dùng
topologySpreadConstraintsđể spread Pod qua nhiều AZ. - Không dùng Spot cho stateful workload (database, message queue).
- Kết hợp On-Demand (baseline) + Spot (burst traffic).
2.2. Graviton (ARM) Instances
Graviton instances (C6g, M6g, R6g, M7g,...) tiết kiệm ~20% chi phí và hiệu năng tốt hơn so với x86 cùng tier.
Cấu hình Node Group Graviton:
resource "aws_eks_node_group" "graviton" {
cluster_name = aws_eks_cluster.main.name
node_group_name = "graviton-workers"
node_role_arn = aws_iam_role.node.arn
subnet_ids = var.private_subnet_ids
capacity_type = "ON_DEMAND"
instance_types = ["m7g.large", "m6g.large"]
ami_type = "AL2023_ARM_64_STANDARD"
scaling_config {
desired_size = 2
max_size = 8
min_size = 1
}
labels = {
"arch" = "arm64"
}
}
Schedule workload lên Graviton nodes:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values:
- arm64
- Container image phải hỗ trợ multi-arch (amd64 + arm64).
- Build image với
docker buildx build --platform linux/amd64,linux/arm64. - Kiểm tra tất cả dependencies (native libraries) tương thích ARM.
2.3. Karpenter Consolidation
Karpenter tự động gom Pod từ nhiều Node underutilized về ít Node hơn, tiết kiệm chi phí (xem thêm bài Scaling).
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: cost-optimized
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r"]
- key: kubernetes.io/arch
operator: In
values: ["amd64", "arm64"]
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s
limits:
cpu: 100
memory: 400Gi
2.4. Fargate cho workload nhỏ
Fargate tính phí theo vCPU + Memory thực dùng (tính theo giây). Phù hợp cho workload nhỏ, không muốn quản lý Node.
resource "aws_eks_fargate_profile" "batch" {
cluster_name = aws_eks_cluster.main.name
fargate_profile_name = "batch-jobs"
pod_execution_role_arn = aws_iam_role.fargate.arn
subnet_ids = var.private_subnet_ids
selector {
namespace = "batch"
labels = {
compute = "fargate"
}
}
}
| So sánh | EC2 | Fargate |
|---|---|---|
| Quản lý Node | Tự quản lý | AWS quản lý |
| Chi phí | Rẻ hơn khi utilization cao | Rẻ hơn khi workload nhỏ/burst |
| Spot support | ✔ | ❌ |
| DaemonSet | ✔ | ❌ |
| GPU | ✔ | ❌ |
📐 3. Right-sizing Resources
3.1. Vấn đề over-provisioning
Phần lớn cluster EKS bị over-provisioned — request nhiều CPU/Memory hơn thực dùng. Điều này gây lãng phí vì:
- Kubernetes schedule Pod dựa trên
requests(không phải actual usage) - Node phải đủ lớn để chứa tổng
requestscủa tất cả Pod - Thực tế Pod chỉ dùng 10-30% resource đã request
3.2. Dùng VPA Recommender
Cài VPA ở mode Off để thu thập recommendation mà không tự động thay đổi (xem thêm bài Scaling):
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: my-app-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
updatePolicy:
updateMode: "Off"
Xem recommendation:
kubectl describe vpa my-app-vpa
3.3. Dùng Kubecost
Kubecost là công cụ phân tích chi phí K8s phổ biến, giúp hiển thị chi phí theo namespace, deployment, label.
helm repo add kubecost https://kubecost.github.io/cost-analyzer/
helm repo update
helm install kubecost kubecost/cost-analyzer \
--namespace kubecost \
--create-namespace \
--set kubecostToken="your-token"
Kubecost cung cấp:
- Chi phí theo namespace, deployment, Pod
- Right-sizing recommendations
- Savings recommendations (idle resources)
- Allocation reports cho chargeback
3.4. Prometheus metrics hữu ích
Một số PromQL queries để phát hiện over-provisioning:
CPU request vs actual usage:
# Tỷ lệ CPU thực dùng / CPU request (theo namespace)
sum(rate(container_cpu_usage_seconds_total{container!=""}[5m])) by (namespace)
/
sum(kube_pod_container_resource_requests{resource="cpu"}) by (namespace)
Memory request vs actual usage:
# Tỷ lệ Memory thực dùng / Memory request (theo namespace)
sum(container_memory_working_set_bytes{container!=""}) by (namespace)
/
sum(kube_pod_container_resource_requests{resource="memory"}) by (namespace)
Nếu tỷ lệ < 0.3 (30%), workload đang bị over-provisioned.
🌐 4. Tối ưu Networking Cost
4.1. Giảm chi phí NAT Gateway
NAT Gateway tốn $0.045/giờ + $0.045/GB data processed. Với traffic lớn, đây là khoản chi phí đáng kể.
Giải pháp:
- Dùng VPC Endpoints cho AWS services (S3, ECR, CloudWatch, STS,...) — miễn phí data transfer
resource "aws_vpc_endpoint" "s3" {
vpc_id = var.vpc_id
service_name = "com.amazonaws.ap-southeast-1.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = var.private_route_table_ids
}
resource "aws_vpc_endpoint" "ecr_api" {
vpc_id = var.vpc_id
service_name = "com.amazonaws.ap-southeast-1.ecr.api"
vpc_endpoint_type = "Interface"
subnet_ids = var.private_subnet_ids
security_group_ids = [var.vpc_endpoint_sg_id]
private_dns_enabled = true
}
VPC Endpoints cần thiết cho EKS:
| Service | Type | Lý do |
|---|---|---|
| S3 | Gateway | ECR image layers, logs |
| ECR (api + dkr) | Interface | Pull container images |
| STS | Interface | IRSA authentication |
| CloudWatch Logs | Interface | Logging |
| EC2 | Interface | Karpenter provisioning |
4.2. Giảm chi phí Cross-AZ traffic
Cross-AZ data transfer tốn $0.01/GB mỗi chiều. Với microservices gọi nhau nhiều, chi phí này tích lũy nhanh.
Giải pháp:
- Dùng
topologySpreadConstraintskết hợp topology-aware routing:
apiVersion: v1
kind: Service
metadata:
name: my-service
annotations:
service.kubernetes.io/topology-mode: Auto
spec:
selector:
app: my-app
ports:
- port: 80
Khi bật topology-aware routing, traffic ưu tiên gửi đến Pod cùng AZ trước.
4.3. Giảm chi phí Load Balancer
Mỗi LoadBalancer Service tạo một NLB/ALB riêng (~$22/tháng). Với nhiều services, chi phí nhân lên.
Giải pháp: Dùng IngressGroup để share một ALB cho nhiều services:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-a
annotations:
alb.ingress.kubernetes.io/group.name: shared-alb
spec:
rules:
- host: app-a.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-a-svc
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-b
annotations:
alb.ingress.kubernetes.io/group.name: shared-alb
spec:
rules:
- host: app-b.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-b-svc
port:
number: 80
📉 5. Tối ưu Storage Cost
5.1. Chọn đúng EBS Volume Type
| Volume Type | Chi phí | IOPS | Phù hợp |
|---|---|---|---|
| gp3 | $0.08/GB | 3000 baseline (free) | Đa số workload |
| gp2 | $0.10/GB | 3 IOPS/GB | Legacy, nên chuyển sang gp3 |
| io2 | $0.125/GB + IOPS | Lên đến 64,000 | Database high-performance |
| st1 | $0.045/GB | Throughput optimized | Log storage, batch |
Luôn dùng gp3 thay gp2 — rẻ hơn 20% và IOPS cao hơn:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3
provisioner: ebs.csi.aws.com
parameters:
type: gp3
fsType: ext4
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
5.2. Dọn dẹp PVC không sử dụng
# Liệt kê PVC Bound nhưng không được Pod nào tham chiếu
pods_json="$(kubectl get pods --all-namespaces -o json)"
kubectl get pvc --all-namespaces -o json | \
jq -r --argjson pods "$pods_json" '
($pods.items
| map(. as $p
| $p.spec.volumes[]?
| select(has("persistentVolumeClaim"))
| "\($p.metadata.namespace)/\(.persistentVolumeClaim.claimName)")
| unique) as $used
| .items[]
| select(.status.phase == "Bound")
| "\(.metadata.namespace)/\(.metadata.name)" as $pvc
| select(($used | index($pvc)) == null)
| "\($pvc) - \(.spec.resources.requests.storage // "N/A")"
'
5.3. EFS vs EBS
| Tiêu chí | EBS | EFS |
|---|---|---|
| Access mode | ReadWriteOnce | ReadWriteMany |
| Chi phí | $0.08/GB (gp3) | $0.30/GB (Standard) |
| Performance | Cao, consistent | Tùy throughput mode |
| Phù hợp | Database, single Pod | Shared storage, multi-Pod |
Dùng EFS Infrequent Access cho data ít truy cập — tiết kiệm ~92%:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: efs-ia
provisioner: efs.csi.aws.com
parameters:
provisioningMode: efs-ap
fileSystemId: fs-xxxxxxxx
directoryPerms: "700"
basePath: "/dynamic"
# Tự động chuyển file sang IA sau 30 ngày không truy cập
uid: "1000"
gid: "1000"
⏰ 6. Scheduling & Off-hours Savings
6.1. Scale-to-zero ngoài giờ làm việc
Với môi trường dev/staging, có thể tắt hoàn toàn ngoài giờ:
Dùng KEDA CronScaler:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: office-hours-scaler
spec:
scaleTargetRef:
name: my-dev-app
minReplicaCount: 0
maxReplicaCount: 3
triggers:
- type: cron
metadata:
timezone: Asia/Ho_Chi_Minh
start: 0 8 * * 1-5
end: 0 19 * * 1-5
desiredReplicas: "3"
Ngoài giờ 8h-19h (thứ 2 - thứ 6), Pod scale về 0 — tiết kiệm ~65% chi phí dev/staging.
6.2. Tắt Node Group dev/staging
# Tắt cuối ngày
aws eks update-nodegroup-config \
--cluster-name dev-eks \
--nodegroup-name dev-workers \
--scaling-config minSize=0,maxSize=0,desiredSize=0
# Bật đầu ngày
aws eks update-nodegroup-config \
--cluster-name dev-eks \
--nodegroup-name dev-workers \
--scaling-config minSize=2,maxSize=5,desiredSize=2
💵 7. Savings Plans & Reserved Instances
7.1. Compute Savings Plans
Cam kết sử dụng compute trong 1 hoặc 3 năm, tiết kiệm lên đến 66%:
| Loại | Flexibility | Tiết kiệm |
|---|---|---|
| Compute Savings Plans | Mọi instance family, region, OS | ~40-50% |
| EC2 Instance Savings Plans | Cố định instance family + region | ~50-60% |
| Reserved Instances | Cố định instance type + AZ | ~55-66% |
Khuyến nghị:
- Dùng Compute Savings Plans cho baseline capacity (On-Demand nodes luôn chạy)
- Phần burst traffic dùng Spot Instances
7.2. Tính toán Savings Plans
Ví dụ: Cluster chạy 5x m6i.xlarge On-Demand 24/7
On-Demand: 5 × $0.192/giờ × 730 giờ = $700.8/tháng
Savings Plans (1yr, No Upfront): 5 × $0.121/giờ × 730 giờ = $441.65/tháng
Tiết kiệm: $259.15/tháng (~37%)
Savings Plans (3yr, All Upfront): 5 × $0.076/giờ × 730 giờ = $277.4/tháng
Tiết kiệm: $423.4/tháng (~60%)
📊 8. Công cụ theo dõi chi phí
8.1. AWS Cost Explorer
- Xem chi phí theo service, tag, thời gian
- Tạo Cost Allocation Tags để phân bổ chi phí theo team/project
Tag resources cho cost tracking:
resource "aws_eks_node_group" "workers" {
# ...
tags = {
Environment = "production"
Team = "backend"
Project = "order-service"
CostCenter = "CC-1234"
}
}
8.2. Kubecost
Kubecost hiển thị chi phí bên trong Kubernetes — theo namespace, deployment, label:
helm install kubecost kubecost/cost-analyzer \
--namespace kubecost \
--create-namespace \
--set kubecostToken="your-token" \
--set prometheus.server.persistentVolume.size=32Gi
8.3. AWS Budgets
Tạo budget alert để cảnh báo khi chi phí vượt ngưỡng:
resource "aws_budgets_budget" "eks_monthly" {
name = "eks-monthly-budget"
budget_type = "COST"
limit_amount = "1000"
limit_unit = "USD"
time_unit = "MONTHLY"
cost_filter {
name = "Service"
values = ["Amazon Elastic Kubernetes Service"]
}
notification {
comparison_operator = "GREATER_THAN"
threshold = 80
threshold_type = "PERCENTAGE"
notification_type = "FORECASTED"
subscriber_email_addresses = ["devops-team@example.com"]
}
}
🏗️ 9. Chiến lược tối ưu tổng thể
Quick Wins (thực hiện ngay)
| Hành động | Tiết kiệm ước tính | Độ khó |
|---|---|---|
| Chuyển gp2 → gp3 | ~20% storage cost | Thấp |
| Tạo VPC Endpoints (S3, ECR) | ~30-50% NAT cost | Thấp |
| Share ALB bằng IngressGroup | ~$22/tháng mỗi LB bớt | Thấp |
| Right-size resource (VPA recommendation) | ~20-40% compute | Trung bình |
| Dùng Spot cho non-critical workload | ~60-90% compute | Trung bình |
Trung hạn (1-3 tháng)
| Hành động | Tiết kiệm ước tính | Độ khó |
|---|---|---|
| Chuyển sang Graviton instances | ~20% compute | Trung bình |
| Dùng Karpenter consolidation | ~20-30% compute | Trung bình |
| Topology-aware routing | ~30-50% cross-AZ cost | Trung bình |
| Scale-to-zero dev/staging ngoài giờ | ~65% dev/staging | Thấp |
Dài hạn (3-12 tháng)
| Hành động | Tiết kiệm ước tính | Độ khó |
|---|---|---|
| Compute Savings Plans | ~40-60% baseline | Thấp (cam kết tài chính) |
| Kubecost chargeback | Visibility | Trung bình |
| Multi-arch images (amd64 + arm64) | ~20% sau khi dùng Graviton | Cao |
Checklist Cost Optimization
- ✔ VPC Endpoints cho S3, ECR, STS, CloudWatch đã tạo
- ✔ StorageClass mặc định là gp3 (không phải gp2)
- ✔ Spot Instances cho stateless/batch workload
- ✔ VPA mode
Offđang thu thập recommendation - ✔ IngressGroup để share ALB
- ✔ Cost Allocation Tags trên tất cả resources
- ✔ AWS Budget alert đã cấu hình
- ✔ Dev/Staging scale-to-zero ngoài giờ
- ✔ Kubecost hoặc CloudWatch cost dashboard đã cài
10. Tổng kết
Tối ưu chi phí EKS là quá trình liên tục, tập trung vào:
- Compute - Spot + Graviton + Karpenter consolidation
- Networking - VPC Endpoints + IngressGroup + topology-aware routing
- Storage - gp3 + EFS IA + dọn PVC không dùng
- Scheduling - Scale-to-zero ngoài giờ cho non-production
- Commitment - Savings Plans cho baseline capacity
- Visibility - Kubecost + Cost Explorer + Budget alerts
Tham khảo: