Mở Đầu: Kubernetes Đang "Ngốn" Tiền Của Bạn Như Thế Nào?
Kubernetes đã trở thành tiêu chuẩn de facto cho việc triển khai ứng dụng trên cloud — điều đó thì ai cũng biết rồi. Nhưng đi kèm với sự phổ biến ấy là một thực tế khá đau: phần lớn doanh nghiệp đang lãng phí từ 28% đến 40% chi phí Kubernetes của mình. Theo nghiên cứu mới nhất từ Cast AI vào đầu năm 2026, trung bình chỉ có 13% CPU được yêu cầu (requested) thực sự được sử dụng, còn memory utilization cũng chỉ đạt mức 23%.
Nói thẳng ra: cứ mỗi 100 đồng bạn chi cho Kubernetes, có tới 30-40 đồng bị "đốt" vào tài nguyên không ai đụng tới.
Với một nghiên cứu phân tích 3.042 cluster production trên 600+ công ty vào tháng 1/2026, 68% pod đang yêu cầu bộ nhớ gấp 3-8 lần so với mức thực tế sử dụng. Trung bình mỗi công ty lãng phí khoảng 847 USD/tháng chỉ riêng cho memory over-provisioning. Con số này nghe không lớn lắm cho đến khi bạn nhân nó lên theo số cluster đang chạy.
Bài viết này sẽ hướng dẫn bạn từ A đến Z cách tối ưu chi phí Kubernetes trên ba nền tảng cloud lớn nhất — Amazon EKS, Azure AKS và Google GKE — với các chiến lược đã được kiểm chứng thực tế, code mẫu sẵn sàng áp dụng, và các công cụ mới nhất năm 2026. Mình đã áp dụng nhiều cách trong số này cho các dự án thực tế và kết quả thật sự đáng ngạc nhiên.
1. Hiểu Cấu Trúc Chi Phí Kubernetes Trên Từng Cloud Provider
1.1 Ba Thành Phần Chi Phí Chính
Trước khi tối ưu bất cứ gì, bạn cần hiểu tiền đang đi đâu đã. Chi phí Kubernetes bao gồm ba lớp chính:
- Control Plane (Mặt phẳng điều khiển): Đây là "bộ não" của cluster — bao gồm API server, etcd, scheduler. AWS EKS tính phí 0.10 USD/giờ (~73 USD/tháng). Azure AKS thì miễn phí hoàn toàn cho control plane cơ bản — và thành thật mà nói, đây là lợi thế cạnh tranh khá lớn của Azure. Google GKE miễn phí cho một cluster zonal, nhưng regional cluster sẽ tính 0.10 USD/giờ.
- Worker Nodes (Compute): Chiếm tới 60-80% tổng chi phí. Đây là các máy ảo (EC2, Azure VM, GCE) chạy workload thực tế của bạn. Chi phí phụ thuộc vào loại instance, số lượng, và mô hình mua (On-Demand, Reserved, Spot).
- Phụ phí khác: Networking (load balancer, data transfer giữa AZ), storage (persistent volumes), logging/monitoring, và các dịch vụ managed add-on. Phần này hay bị bỏ qua nhưng cộng lại cũng kha khá đấy.
1.2 So Sánh Chi Phí Cơ Bản EKS vs AKS vs GKE
Để bạn dễ hình dung, đây là so sánh chi phí cho một cluster điển hình với 3 worker node (4 vCPU, 16 GB RAM mỗi node) theo giá On-Demand tại khu vực Đông Nam Á:
- Amazon EKS: Control plane ~73 USD/tháng + 3x m5.xlarge (~461 USD/tháng) = ~534 USD/tháng
- Azure AKS: Control plane miễn phí + 3x Standard_D4s_v5 (~421 USD/tháng) = ~421 USD/tháng
- Google GKE: Control plane ~73 USD/tháng (regional) + 3x e2-standard-4 (~388 USD/tháng) = ~461 USD/tháng
Lưu ý rằng đây chỉ là chi phí compute cơ bản thôi nhé. Chi phí thực tế sẽ cao hơn nhiều khi tính thêm networking, storage, và các dịch vụ bổ sung. Và quan trọng hơn — chi phí thấp nhất không phải lúc nào cũng là lựa chọn tốt nhất. Hãy chọn provider phù hợp với hệ sinh thái hiện tại của bạn.
2. Right-Sizing: Chiến Lược Cho Kết Quả Nhanh Nhất
2.1 Tại Sao Hầu Hết Mọi Người Đều Set Resource Requests Sai?
Theo khảo sát, 64% đội ngũ kỹ thuật thừa nhận rằng họ thêm "buffer an toàn" gấp 2-4 lần sau chỉ một lần gặp sự cố OOMKilled. Nghe quen không? Mình chắc nhiều bạn cũng từng làm vậy. Điều đáng nói là: hầu hết team đặt resource requests tại thời điểm deploy rồi... không bao giờ quay lại xem xét lại.
Kết quả? Một pod chỉ cần 128MB RAM nhưng được cấp 1GB. Nhân lên 100 pod, bạn đang lãng phí gần 90GB RAM — tương đương hàng trăm đô la mỗi tháng chỉ vì... sợ.
2.2 Sử Dụng Vertical Pod Autoscaler (VPA) Ở Chế Độ Recommendation
VPA là công cụ chính thức của Kubernetes để tự động điều chỉnh resource requests. Tuy nhiên, lời khuyên chân thành của mình là: đừng bật auto mode ngay. Hãy bắt đầu với chế độ recommendation để thu thập dữ liệu trước, sau đó review và áp dụng thủ công. Tin mình đi, bạn sẽ cảm ơn bước thận trọng này.
Cài đặt VPA trên EKS:
# Cài đặt VPA trên EKS
git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler
./hack/vpa-up.sh
# Hoặc cài qua Helm (khuyến nghị)
helm repo add cowboysysop https://cowboysysop.github.io/charts/
helm install vpa cowboysysop/vertical-pod-autoscaler --namespace kube-system --set recommender.enabled=true --set updater.enabled=false --set admissionController.enabled=false
Tạo một VPA object ở chế độ "Off" (chỉ đưa ra đề xuất, không tự động thay đổi gì cả):
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: my-app-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
updatePolicy:
updateMode: "Off" # Chỉ recommendation, không tự động update
resourcePolicy:
containerPolicies:
- containerName: my-app
minAllowed:
cpu: "50m"
memory: "64Mi"
maxAllowed:
cpu: "2000m"
memory: "4Gi"
controlledResources: ["cpu", "memory"]
Sau 24-48 giờ, kiểm tra đề xuất của VPA:
# Xem VPA recommendations
kubectl get vpa my-app-vpa -n production -o jsonpath='{.status.recommendation}' | jq .
# Output mẫu:
# {
# "containerRecommendations": [{
# "containerName": "my-app",
# "lowerBound": {"cpu": "50m", "memory": "131072k"},
# "target": {"cpu": "120m", "memory": "262144k"},
# "upperBound": {"cpu": "400m", "memory": "524288k"},
# "uncappedTarget": {"cpu": "120m", "memory": "262144k"}
# }]
# }
Trong ví dụ trên, VPA đề xuất target là 120m CPU và 256MB memory. Nếu pod đang được set 500m CPU và 1GB memory, bạn có thể tiết kiệm hơn 75% tài nguyên chỉ bằng cách điều chỉnh theo đề xuất. Đơn giản vậy thôi mà hiệu quả thật sự lớn.
2.3 Script Tự Động Quét Và Báo Cáo Lãng Phí
Đây là script Python mình hay dùng để quét tất cả namespace trong cluster và tìm ra các deployment đang over-provisioned. Bạn có thể copy và chạy ngay:
#!/usr/bin/env python3
"""
Script quét Kubernetes cluster để tìm workload over-provisioned.
Yêu cầu: pip install kubernetes
"""
from kubernetes import client, config
from kubernetes.client.rest import ApiException
def get_resource_waste_report():
config.load_kube_config()
v1 = client.CoreV1Api()
apps_v1 = client.AppsV1Api()
custom_api = client.CustomObjectsApi()
report = []
# Lấy tất cả namespace (trừ system namespaces)
namespaces = v1.list_namespace()
skip_ns = {"kube-system", "kube-public", "kube-node-lease"}
for ns in namespaces.items:
ns_name = ns.metadata.name
if ns_name in skip_ns:
continue
# Lấy metrics cho các pod trong namespace
try:
pod_metrics = custom_api.list_namespaced_custom_object(
group="metrics.k8s.io",
version="v1beta1",
namespace=ns_name,
plural="pods"
)
except ApiException:
continue
for pod_metric in pod_metrics.get("items", []):
pod_name = pod_metric["metadata"]["name"]
try:
pod = v1.read_namespaced_pod(pod_name, ns_name)
except ApiException:
continue
for i, container in enumerate(pod.spec.containers):
if not container.resources.requests:
continue
requested_cpu = parse_cpu(
container.resources.requests.get("cpu", "0")
)
requested_mem = parse_memory(
container.resources.requests.get("memory", "0")
)
actual_cpu = parse_cpu(
pod_metric["containers"][i]["usage"]["cpu"]
)
actual_mem = parse_memory(
pod_metric["containers"][i]["usage"]["memory"]
)
cpu_util = (actual_cpu / requested_cpu * 100) if requested_cpu > 0 else 0
mem_util = (actual_mem / requested_mem * 100) if requested_mem > 0 else 0
if cpu_util < 50 or mem_util < 50:
report.append({
"namespace": ns_name,
"pod": pod_name,
"container": container.name,
"cpu_requested": f"{requested_cpu}m",
"cpu_actual": f"{actual_cpu}m",
"cpu_utilization": f"{cpu_util:.1f}%",
"mem_requested": f"{requested_mem}Mi",
"mem_actual": f"{actual_mem}Mi",
"mem_utilization": f"{mem_util:.1f}%",
})
report.sort(key=lambda x: float(x["cpu_utilization"].rstrip("%")))
print(f"
{'='*80}")
print(f"BÁO CÁO LÃNG PHÍ TÀI NGUYÊN KUBERNETES")
print(f"{'='*80}")
print(f"Tổng số container over-provisioned: {len(report)}")
print(f"{'='*80}
")
for item in report[:20]:
print(f"Namespace: {item['namespace']}")
print(f" Pod: {item['pod']} | Container: {item['container']}")
print(f" CPU: {item['cpu_requested']} requested -> "
f"{item['cpu_actual']} used ({item['cpu_utilization']})")
print(f" Memory: {item['mem_requested']} requested -> "
f"{item['mem_actual']} used ({item['mem_utilization']})")
print()
def parse_cpu(value):
if value.endswith("n"):
return int(value[:-1]) / 1_000_000
elif value.endswith("m"):
return int(value[:-1])
return int(value) * 1000
def parse_memory(value):
units = {"Ki": 1/1024, "Mi": 1, "Gi": 1024, "Ti": 1048576}
for unit, multiplier in units.items():
if value.endswith(unit):
return float(value[:-len(unit)]) * multiplier
return float(value) / (1024 * 1024)
if __name__ == "__main__":
get_resource_waste_report()
3. Autoscaling Thông Minh: HPA, VPA Và Karpenter
3.1 Horizontal Pod Autoscaler (HPA) — Làm Đúng Cách
HPA tự động tăng giảm số lượng pod dựa trên metric. Nhưng thực tế là nhiều team mắc lỗi khi cấu hình HPA — dẫn đến scaling quá chậm hoặc flapping (cứ scale up rồi lại scale down liên tục, rất phiền). Đây là cấu hình HPA tối ưu mà mình khuyến nghị:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60 # Scale khi CPU vượt 60%
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 70
behavior:
scaleUp:
stabilizationWindowSeconds: 60 # Chờ 60s trước khi scale up
policies:
- type: Percent
value: 50 # Tăng tối đa 50% mỗi lần
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300 # Chờ 5 phút trước khi scale down
policies:
- type: Percent
value: 25 # Giảm tối đa 25% mỗi lần
periodSeconds: 120
Một lưu ý quan trọng: Đừng chạy VPA và HPA trên cùng một metric (ví dụ cả hai đều dùng CPU). Đây là anti-pattern nổi tiếng sẽ tạo ra feedback loop gây mất ổn định hệ thống. Thay vào đó, hãy dùng VPA cho CPU/memory requests, và HPA cho custom metrics như request rate, queue depth, v.v.
3.2 Karpenter: Tương Lai Của Node Autoscaling
Nếu bạn đang dùng AWS EKS, thì Karpenter thật sự là một game-changer. Khác với Cluster Autoscaler truyền thống (vốn làm việc với node group cố định), Karpenter tự động chọn instance type tối ưu nhất cho pending pod, khởi động node trong 30-60 giây (so với vài phút của Cluster Autoscaler), và liên tục consolidate node để giảm lãng phí.
Minh chứng thực tế đáng chú ý: Salesforce đã migrate hơn 1.000 EKS cluster từ Cluster Autoscaler sang Karpenter vào đầu năm 2026, đạt mức tiết kiệm khoảng 5% trong FY2026 và dự kiến thêm 5-10% trong FY2027. Operational overhead giảm khoảng 80% nhờ tự động hóa thay thế quản lý node group thủ công. Những con số đó khó mà bỏ qua được.
Cấu hình Karpenter NodePool tối ưu cho chi phí:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: cost-optimized
spec:
template:
metadata:
labels:
workload-type: general
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"] # Ưu tiên Spot
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r"] # Cho phép nhiều instance family
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ["4"] # Chỉ dùng gen 5+
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 60s # Consolidate node rỗi sau 60s
limits:
cpu: "200"
memory: "800Gi"
weight: 10 # Ưu tiên NodePool này
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
amiSelectorTerms:
- alias: "al2023@latest"
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: my-cluster
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: my-cluster
blockDeviceMappings:
- deviceName: /dev/xvda
ebs:
volumeSize: 50Gi
volumeType: gp3
deleteOnTermination: true
Với cấu hình trên, Karpenter sẽ tự động ưu tiên Spot instance (tiết kiệm đến 70-90%), chọn instance type phù hợp nhất với kích thước pod, và consolidate các node có utilization thấp bằng cách di chuyển pod sang node khác rồi terminate node cũ. Khá thông minh phải không?
3.3 GKE Autopilot — Lựa Chọn "Zero Waste" Cho Google Cloud
Nếu bạn dùng Google Cloud, GKE Autopilot là lựa chọn đáng xem xét nghiêm túc. Khác với GKE Standard nơi bạn phải quản lý node, Autopilot tính phí theo pod resource requests — nghĩa là bạn chỉ trả tiền cho tài nguyên mà pod yêu cầu, không phải cho toàn bộ node.
# Tạo GKE Autopilot cluster
gcloud container clusters create-auto my-cluster --region=asia-southeast1 --release-channel=regular
# Deploy workload — GKE Autopilot tự quản lý node
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:latest
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "500m"
memory: "1Gi"
EOF
Với Autopilot, việc right-sizing resource requests trở nên cực kỳ quan trọng vì bạn trả tiền chính xác theo requests. Kết hợp với VPA recommendation mode, bạn có thể đạt mức tối ưu gần như lý tưởng.
4. Spot Instance Trên Kubernetes: Tiết Kiệm 60-90% Chi Phí Compute
4.1 Nguyên Tắc Vàng Khi Dùng Spot
Spot Instance (AWS), Spot VMs (Azure), và Preemptible/Spot VMs (GCP) cung cấp compute với giá giảm 60-90% so với On-Demand. Đổi lại, chúng có thể bị thu hồi bất cứ lúc nào khi cloud provider cần capacity.
Nghe đáng sợ đúng không? Nhưng với Kubernetes, việc xử lý Spot interruption trở nên dễ dàng hơn rất nhiều nhờ cơ chế rescheduling tự động. Mình đã chạy production workload trên Spot hơn một năm nay và (gõ lên gỗ) chưa gặp sự cố nghiêm trọng nào.
Nguyên tắc sử dụng Spot hiệu quả trên Kubernetes:
- Đa dạng hóa instance type: Sử dụng nhiều loại instance khác nhau để giảm rủi ro bị thu hồi đồng loạt. Ví dụ: thay vì chỉ dùng m5.xlarge, hãy cho phép m5.xlarge, m5a.xlarge, m5d.xlarge, m6i.xlarge.
- Duy trì On-Demand baseline: Luôn giữ một số node On-Demand cho critical workload. Tỷ lệ khuyến nghị: 20-30% On-Demand, 70-80% Spot.
- Xử lý graceful shutdown: Cấu hình terminationGracePeriodSeconds phù hợp và sử dụng preStop hooks để xử lý dữ liệu trước khi pod bị terminate.
- Tách biệt workload: Dùng node selector và taints/tolerations để đảm bảo workload phù hợp mới chạy trên Spot node.
4.2 Cấu Hình Spot Node Pool Trên EKS Với Karpenter
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: spot-workloads
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r"]
- key: karpenter.k8s.aws/instance-size
operator: In
values: ["large", "xlarge", "2xlarge"]
taints:
- key: spot
value: "true"
effect: NoSchedule
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s
limits:
cpu: "100"
---
# Deployment sử dụng Spot nodes
apiVersion: apps/v1
kind: Deployment
metadata:
name: batch-processor
spec:
replicas: 5
selector:
matchLabels:
app: batch-processor
template:
metadata:
labels:
app: batch-processor
spec:
tolerations:
- key: spot
operator: Equal
value: "true"
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
terminationGracePeriodSeconds: 120
containers:
- name: processor
image: batch-processor:latest
resources:
requests:
cpu: "500m"
memory: "1Gi"
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 30 && /app/graceful-shutdown.sh"]
4.3 Cấu Hình Spot Node Pool Trên AKS
# Thêm Spot node pool vào AKS cluster
az aks nodepool add --resource-group myResourceGroup --cluster-name myAKSCluster --name spotnodepool --priority Spot --eviction-policy Delete --spot-max-price -1 --node-count 3 --min-count 1 --max-count 10 --enable-cluster-autoscaler --node-vm-size Standard_D4s_v5 --labels workload-type=batch --node-taints "kubernetes.azure.com/scalesetpriority=spot:NoSchedule"
5. Giám Sát Chi Phí Với OpenCost Và Kubecost
5.1 OpenCost — Tiêu Chuẩn Mở Từ CNCF
OpenCost là dự án mã nguồn mở được CNCF hỗ trợ, cung cấp khả năng giám sát chi phí Kubernetes theo namespace, workload, và label. Nếu bạn chưa có giải pháp cost monitoring nào, đây là điểm khởi đầu tuyệt vời — miễn phí mà lại khá mạnh.
# Cài đặt OpenCost trên cluster
helm install opencost opencost/opencost --namespace opencost --create-namespace --set opencost.exporter.defaultClusterId="my-production-cluster" --set opencost.ui.enabled=true
# Truy vấn chi phí theo namespace qua API
curl -s http://localhost:9090/allocation/compute -d window=7d -d aggregate=namespace -d accumulate=true | jq '.data[] | {
namespace: .name,
totalCost: .totalCost,
cpuCost: .cpuCost,
memCost: .ramCost,
efficiency: .totalEfficiency
}'
5.2 Kubecost — Giải Pháp Toàn Diện Hơn
Kubecost xây dựng trên nền tảng OpenCost nhưng bổ sung nhiều tính năng enterprise đáng giá: multi-cluster view, tích hợp với billing API của cloud provider để reconcile chi phí thực tế (bao gồm Reserved Instance, Savings Plans), và đề xuất tối ưu tự động. Nếu công ty bạn đủ lớn, đầu tư vào Kubecost rất đáng.
# Cài đặt Kubecost
helm install kubecost cost-analyzer --repo https://kubecost.github.io/cost-analyzer/ --namespace kubecost --create-namespace --set kubecostToken="your-token" --set prometheus.server.global.external_labels.cluster_id="my-cluster"
Tạo Prometheus alert cho chi phí bất thường (bạn sẽ cảm ơn mình khi nó bắt được spike chi phí lúc 3 giờ sáng):
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: kubecost-cost-alerts
namespace: kubecost
spec:
groups:
- name: cost-alerts
rules:
- alert: NamespaceCostSpike
expr: |
sum(kubecost_cluster_costs{}) by (namespace)
> 1.5 * avg_over_time(
sum(kubecost_cluster_costs{}) by (namespace)[7d:1h]
)
for: 2h
labels:
severity: warning
annotations:
summary: "Chi phí namespace tăng đột biến"
description: "Chi phí hiện tại cao hơn 50% so với trung bình 7 ngày"
- alert: LowResourceEfficiency
expr: |
kubecost_container_cpu_usage_avg / kubecost_container_cpu_request_avg < 0.3
for: 24h
labels:
severity: info
annotations:
summary: "Container sử dụng CPU dưới 30%"
5.3 Xây Dựng Dashboard Chi Phí Với Grafana
Kết hợp OpenCost hoặc Kubecost với Grafana để tạo dashboard chi phí trực quan cho team. Theo kinh nghiệm của mình, khi mọi người nhìn thấy số tiền thực tế trên dashboard, họ tự giác tối ưu hơn rất nhiều. Đây là một số query PromQL hữu ích:
# Tổng chi phí theo namespace (24h qua)
sum(rate(kubecost_allocation_cpu_cost_total[24h])) by (namespace)
+ sum(rate(kubecost_allocation_memory_cost_total[24h])) by (namespace)
# CPU utilization efficiency theo deployment
sum(rate(container_cpu_usage_seconds_total{container!=""}[5m])) by (namespace, pod)
/
sum(kube_pod_container_resource_requests{resource="cpu"}) by (namespace, pod)
* 100
# Top 10 namespace tốn kém nhất
topk(10,
sum(kubecost_allocation_cpu_cost_total + kubecost_allocation_memory_cost_total)
by (namespace)
)
6. Cost Allocation Và Chargeback — Ai Đang Trả Tiền Cho Cái Gì?
6.1 Chiến Lược Label Cho Cost Allocation
Kubernetes labels là công cụ mạnh mẽ nhất để phân bổ chi phí. Nhưng chúng chỉ hiệu quả khi được áp dụng nhất quán trên toàn cluster. Nghe thì dễ, nhưng thực tế thì... đây là một trong những thứ khó enforce nhất trong tổ chức lớn. Đây là cấu trúc label mình khuyến nghị:
metadata:
labels:
# Bắt buộc - dùng cho cost allocation
app.kubernetes.io/name: "payment-service"
app.kubernetes.io/component: "api"
cost.company.com/team: "platform-engineering"
cost.company.com/project: "checkout-v2"
cost.company.com/environment: "production"
cost.company.com/cost-center: "CC-PLATFORM-2026"
# Tùy chọn - dùng cho reporting chi tiết
cost.company.com/business-unit: "e-commerce"
cost.company.com/tier: "critical"
6.2 Bắt Buộc Label Bằng OPA Gatekeeper
Để đảm bảo mọi workload đều có label chi phí (vì con người thì hay quên), sử dụng OPA Gatekeeper hoặc Kyverno để enforce policy:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("Pod thiếu label bắt buộc: %v", [missing])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-cost-labels
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet", "DaemonSet"]
namespaceSelector:
matchExpressions:
- key: "kubernetes.io/metadata.name"
operator: NotIn
values: ["kube-system", "kube-public", "monitoring"]
parameters:
labels:
- "cost.company.com/team"
- "cost.company.com/project"
- "cost.company.com/environment"
7. Tối Ưu Networking Và Storage — Chi Phí "Ẩn" Hay Bị Bỏ Qua
7.1 Giảm Chi Phí Data Transfer Giữa AZ
Trên AWS, data transfer giữa các Availability Zone tính phí 0.01 USD/GB mỗi chiều (0.02 USD/GB cho cả hai chiều). Con số này nghe nhỏ xíu, nhưng với microservices giao tiếp liên tục qua lại, chi phí có thể lên hàng ngàn USD mỗi tháng. Mình từng thấy một hệ thống tốn hơn 2.000 USD/tháng chỉ riêng cho cross-AZ traffic.
Giải pháp: sử dụng topology-aware routing để ưu tiên traffic trong cùng AZ:
apiVersion: v1
kind: Service
metadata:
name: my-service
annotations:
service.kubernetes.io/topology-mode: Auto
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
Bạn cũng nên cân nhắc sử dụng pod topology spread constraints để phân bổ pod đều trên các AZ, giảm thiểu cross-AZ traffic:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: my-app
7.2 Tối Ưu Persistent Volume
Storage trên Kubernetes cũng là nguồn lãng phí đáng kể mà nhiều người không để ý. Một số best practices:
- Dùng đúng loại storage: gp3 trên AWS rẻ hơn gp2 tới 20% và có IOPS baseline cao hơn. Chuyển từ gp2 sang gp3 gần như không có lý do gì để không làm. Trên Azure, Premium SSD v2 cho phép điều chỉnh IOPS và throughput độc lập.
- Resize thay vì tạo mới: Kubernetes hỗ trợ PVC expansion — tăng kích thước volume mà không cần tạo lại.
- Xóa PV không sử dụng: PersistentVolume với reclaimPolicy: Retain sẽ không bị xóa khi PVC bị delete. Nhớ quét và xóa các PV orphaned định kỳ nhé.
# Tìm PV không có PVC (orphaned)
kubectl get pv --no-headers | awk '$5 == "Released" || $5 == "Available" {print $1, $2, $5, $6}'
# Liệt kê tất cả PVC và dung lượng
kubectl get pvc --all-namespaces -o custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name,SIZE:.spec.resources.requests.storage,STATUS:.status.phase --sort-by='.spec.resources.requests.storage'
8. Tự Động Hóa Tối Ưu Chi Phí Với GitOps
8.1 Tích Hợp Cost Check Vào CI/CD Pipeline
Một trong những cách hiệu quả nhất để ngăn chặn lãng phí là kiểm tra chi phí ngay từ khâu code review. Đừng đợi đến khi bill đến rồi mới giật mình. Bạn hoàn toàn có thể tích hợp cost estimation vào CI/CD pipeline:
# .github/workflows/cost-check.yml
name: Kubernetes Cost Check
on:
pull_request:
paths:
- 'k8s/**'
- 'helm/**'
jobs:
cost-estimate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Estimate resource costs
run: |
# Parse tất cả Kubernetes manifests
for file in $(find k8s/ -name "*.yaml" -o -name "*.yml"); do
cpu=$(yq e '.spec.template.spec.containers[].resources.requests.cpu' "$file" 2>/dev/null)
mem=$(yq e '.spec.template.spec.containers[].resources.requests.memory' "$file" 2>/dev/null)
replicas=$(yq e '.spec.replicas // 1' "$file" 2>/dev/null)
if [ "$cpu" != "null" ] && [ -n "$cpu" ]; then
echo "File: $file | CPU: $cpu x $replicas | Memory: $mem x $replicas"
fi
done
8.2 Scheduled Right-Sizing Review
Tạo CronJob trong cluster để tự động gửi báo cáo right-sizing hàng tuần. Cái hay của cách này là bạn không cần nhớ phải check — nó tự nhắc bạn:
apiVersion: batch/v1
kind: CronJob
metadata:
name: weekly-cost-report
namespace: monitoring
spec:
schedule: "0 9 * * 1" # Mỗi thứ Hai lúc 9h sáng
jobTemplate:
spec:
template:
spec:
serviceAccountName: cost-reporter
containers:
- name: reporter
image: curlimages/curl:latest
command:
- /bin/sh
- -c
- |
REPORT=$(curl -s http://kubecost.kubecost:9090/savings -d window=7d)
curl -X POST "$SLACK_WEBHOOK_URL" -H "Content-Type: application/json" -d "{"text": "*Báo Cáo Chi Phí Kubernetes Tuần*
$REPORT"}"
env:
- name: SLACK_WEBHOOK_URL
valueFrom:
secretKeyRef:
name: slack-secrets
key: webhook-url
restartPolicy: OnFailure
9. Checklist Tối Ưu Chi Phí Kubernetes — Bắt Đầu Từ Đâu?
Okay, nhiều thông tin quá đúng không? Đừng lo, đây là checklist được sắp xếp theo thứ tự tác động từ cao đến thấp, giúp bạn biết nên bắt đầu từ đâu:
Tuần 1-2: Quick Wins (Tiết kiệm 20-30%)
- Cài đặt OpenCost hoặc Kubecost để có visibility về chi phí hiện tại
- Triển khai VPA ở chế độ recommendation cho tất cả workload production
- Xác định và xóa các resource không sử dụng: orphaned PV, idle Load Balancer, unused namespaces
- Chuyển EBS volume từ gp2 sang gp3 (tiết kiệm 20% ngay lập tức — đây là quick win dễ nhất)
Tuần 3-4: Autoscaling (Tiết kiệm thêm 15-25%)
- Áp dụng VPA recommendations để right-size resource requests
- Cấu hình HPA cho stateless workload với behavior policy phù hợp
- Triển khai Karpenter (EKS) hoặc cấu hình Cluster Autoscaler tối ưu
- Bật topology-aware routing để giảm cross-AZ data transfer
Tháng 2: Spot Và Commitment (Tiết kiệm thêm 20-40%)
- Thêm Spot node pool cho workload fault-tolerant
- Mua Reserved Instances hoặc Savings Plans cho On-Demand baseline
- Thiết lập cost allocation labels và enforce bằng OPA Gatekeeper
- Tích hợp cost check vào CI/CD pipeline
Tháng 3+: Văn Hóa FinOps (Duy trì và cải thiện liên tục)
- Thiết lập weekly cost review meeting
- Tạo chargeback/showback report cho từng team
- Đặt budget alerts và anomaly detection
- Đánh giá GKE Autopilot hoặc các giải pháp serverless container (Fargate, Azure Container Apps) cho workload phù hợp
Kết Luận
Tối ưu chi phí Kubernetes không phải là việc làm một lần rồi xong. Thật ra thì đó là một hành trình liên tục đòi hỏi sự kết hợp giữa công cụ phù hợp, quy trình rõ ràng, và (cái này quan trọng nhất) văn hóa FinOps trong tổ chức.
Tin tốt là với những chiến lược trong bài viết này — từ right-sizing với VPA, autoscaling với Karpenter, tận dụng Spot instance, đến giám sát bằng OpenCost/Kubecost — bạn hoàn toàn có thể cắt giảm 40-60% chi phí Kubernetes mà không ảnh hưởng đến hiệu năng ứng dụng.
Hãy bắt đầu từ những quick wins nhỏ, đo lường kết quả, và dần mở rộng phạm vi tối ưu. Chỉ riêng việc cài OpenCost và chạy VPA recommendation mode trong tuần đầu tiên, bạn đã có thể phát hiện ra hàng trăm — thậm chí hàng ngàn — USD đang bị lãng phí mỗi tháng. Và tin mình đi, đó mới chỉ là khởi đầu thôi.