ทำไม Kubernetes ถึงเป็นจุดบอดด้านต้นทุนคลาวด์ที่ใหญ่ที่สุดของหลายองค์กร
ถ้าคุณกำลังรันแอปพลิเคชันบน Kubernetes อยู่ ไม่ว่าจะเป็น Amazon EKS, Azure AKS หรือ Google GKE มีเรื่องหนึ่งที่คุณต้องรู้ — องค์กรส่วนใหญ่กำลัง จ่ายค่า Kubernetes มากกว่าที่จำเป็นถึง 2-4 เท่า โดยไม่รู้ตัว
พูดตรงๆ เลยนะครับ ตัวเลขนี้ไม่ใช่การเดา จากการศึกษาล่าสุดในเดือนมกราคม 2026 ที่วิเคราะห์ข้อมูลจาก 3,042 production clusters ของกว่า 600 บริษัท พบว่า 68% ของ Pods ทั้งหมดตั้งค่า memory request ไว้มากกว่าที่ใช้จริงถึง 3-8 เท่า ลองคิดดูดีๆ ครับ — คุณจ่ายค่า RAM ที่ไม่เคยถูกแตะเลย ทุกนาที ทุกชั่วโมง ทุกวัน เงินก้อนนี้ "ระเหย" ไปเฉยๆ
ตัวเลขที่ชัดกว่านั้น? บริษัทโดยเฉลี่ยสูญเสียเงินไปกับ memory over-provisioning อย่างเดียวถึง $847 ต่อเดือน และมีกรณีที่บริษัทหนึ่ง "เลือดไหล" ถึง $2.1 ล้านต่อปี จากทรัพยากรที่ไม่ได้ใช้
ทำไมถึงเป็นแบบนี้? เพราะ Kubernetes สร้าง ชั้นของความซับซ้อน (Layer of Abstraction) ที่ทำให้การติดตามต้นทุนยากกว่า Virtual Machine แบบดั้งเดิมมาก ในโลกของ VM คุณเห็นชัดว่า Instance ตัวนี้ราคาเท่าไหร่ ใครใช้อยู่ แต่ใน Kubernetes? ค่าใช้จ่ายถูกแบ่งระหว่าง Nodes, Pods, Namespaces, Controllers — มันซ้อนกันหลายชั้นจนแกะไม่ออกจริงๆ
บทความนี้จะพาคุณเจาะลึกทุกมิติของการ optimize ค่าใช้จ่าย Kubernetes ตั้งแต่พื้นฐานจนถึงเทคนิคระดับสูง พร้อมตัวอย่างโค้ดที่เอาไปใช้ได้ทันที เราจะครอบคลุมทั้ง Right-Sizing, Autoscaling (VPA/HPA/Karpenter), เครื่องมือ Cost Visibility อย่าง OpenCost และ Kubecost, รวมถึงกลยุทธ์ลด waste ที่ได้ผลจริงในปี 2026
ทำความเข้าใจโครงสร้างต้นทุน Kubernetes: เงินหายไปตรงไหนบ้าง
ก่อนจะ optimize อะไรได้ เราต้องเข้าใจก่อนว่าค่าใช้จ่าย Kubernetes มาจากไหนบ้าง มาแยกดูทีละชั้นกันครับ
ชั้นที่ 1: Compute (Node Level)
นี่คือค่าใช้จ่ายก้อนใหญ่ที่สุด Kubernetes ต้องการ Nodes (ซึ่งก็คือ Virtual Machines นั่นแหละ) เพื่อรัน workloads ค่า Node รวมถึง CPU, Memory, และ Disk ที่จัดสรรให้ ไม่ว่า Pods ภายในจะใช้จริงแค่ไหนก็ตาม คุณยังคงจ่ายค่า Node เต็มจำนวน
ชั้นที่ 2: Pod Resources (Requests vs Limits)
ตรงนี้แหละครับที่ waste เกิดขึ้นมากที่สุด Kubernetes ใช้ระบบ Requests (จำนวนทรัพยากรขั้นต่ำที่รับประกันให้ Pod) และ Limits (จำนวนทรัพยากรสูงสุดที่ Pod ใช้ได้) Scheduler จะจัดวาง Pod บน Node โดยดูจาก Requests — ถ้า Pod ขอ 2 CPU / 4GB RAM แม้จะใช้จริงแค่ 0.3 CPU / 500MB RAM ก็ยังจองพื้นที่ไว้ 2 CPU / 4GB RAM บน Node นั้น
และนี่คือปัญหาหลักเลย: 64% ของทีมวิศวกรรมยอมรับว่าเพิ่ม headroom "เผื่อไว้ให้ปลอดภัย" ถึง 2-4 เท่า หลังจากเจอ OOM (Out of Memory) Kill แค่ครั้งเดียว แทนที่จะนั่งวิเคราะห์ว่าต้องการเท่าไหร่จริงๆ ผมเข้าใจนะครับ มันเป็นเรื่องปกติที่โดน OOM Kill แล้วจะตกใจ แต่การเพิ่มแบบ "สุ่มสี่สุ่มห้า" มันทำให้เสียเงินมากเกินจำเป็น
ชั้นที่ 3: Networking และ Storage
ค่า Load Balancer, Ingress Controller, Persistent Volumes, data transfer ข้าม AZ — ค่าใช้จ่ายเหล่านี้มักถูกมองข้ามแต่รวมกันแล้วไม่น้อยเลย โดยเฉพาะ data transfer ข้าม AZ ใน Kubernetes ที่ Pods อาจอยู่คนละ AZ กัน
ชั้นที่ 4: Kubernetes Control Plane
ค่า Managed Kubernetes service เอง เช่น EKS ($0.10/ชั่วโมง = ~$73/เดือน), AKS (ฟรีสำหรับ standard tier), GKE ($0.10/ชั่วโมง = ~$73/เดือน) ค่าตรงนี้ไม่มากต่อ cluster แต่ถ้าคุณมีหลาย cluster ก็สะสมได้เหมือนกัน
Right-Sizing: ศิลปะของการตั้งค่า Resource Requests ให้พอดี
Right-sizing ใน Kubernetes หมายถึงการตั้งค่า CPU และ Memory requests/limits ให้ใกล้เคียงกับการใช้งานจริงมากที่สุด ฟังดูง่ายใช่ไหมครับ? แต่ทำจริงยากกว่าที่คิดเยอะ เพราะ workloads ส่วนใหญ่มีการใช้ทรัพยากรที่ผันแปรตามเวลาและ traffic patterns
ขั้นตอนที่ 1: วัดการใช้งานจริง
ก่อนจะปรับอะไรได้ คุณต้องรู้ก่อนว่า Pods ของคุณใช้ทรัพยากรจริงๆ เท่าไหร่ ใช้ Prometheus + Grafana ดึงข้อมูลมาดูได้ตามนี้:
# PromQL: ดู CPU usage จริงเทียบกับ requests (ระดับ namespace)
# ค่ายิ่งต่ำ = ยิ่ง over-provisioned
sum(rate(container_cpu_usage_seconds_total{namespace="production"}[5m])) by (pod)
/
sum(kube_pod_container_resource_requests{namespace="production", resource="cpu"}) by (pod)
# PromQL: ดู Memory usage จริงเทียบกับ requests
sum(container_memory_working_set_bytes{namespace="production"}) by (pod)
/
sum(kube_pod_container_resource_requests{namespace="production", resource="memory"}) by (pod)
ถ้าค่าที่ได้ต่ำกว่า 0.5 (หรือ 50%) อย่างสม่ำเสมอ แสดงว่า Pod นั้น over-provisioned อยู่ และมีพื้นที่ให้ optimize ได้เยอะเลย
ขั้นตอนที่ 2: ตั้งค่า Requests ตาม P95/P99 Usage
แนวทางที่ดีที่สุดคือตั้ง requests ไว้ที่ P95 หรือ P99 ของ actual usage บวก buffer อีกสัก 10-20% อย่าดูแค่ค่าเฉลี่ยนะครับ เพราะค่าเฉลี่ยจะทำให้ตั้ง request ต่ำเกินไป แล้วคุณจะเจอปัญหา CPU throttling หรือ OOM Kill ได้
# PromQL: คำนวณ P95 CPU usage ย้อนหลัง 7 วัน
quantile_over_time(0.95,
rate(container_cpu_usage_seconds_total{
namespace="production",
pod=~"api-server-.*"
}[5m])[7d:5m]
)
# PromQL: คำนวณ P99 Memory usage ย้อนหลัง 7 วัน
quantile_over_time(0.99,
container_memory_working_set_bytes{
namespace="production",
pod=~"api-server-.*"
}[7d:5m]
)
ขั้นตอนที่ 3: ปรับ Deployment manifest
สมมติจากข้อมูล monitoring พบว่า API server ใช้ CPU จริงอยู่ที่ P95 = 250m, Memory P99 = 384Mi ก็ตั้งค่าประมาณนี้ครับ:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: api-server
template:
metadata:
labels:
app: api-server
spec:
containers:
- name: api-server
image: api-server:v2.5.0
resources:
requests:
cpu: "300m" # P95 (250m) + 20% buffer
memory: "460Mi" # P99 (384Mi) + 20% buffer
limits:
cpu: "500m" # ให้ burst ได้ถึง 500m
memory: "512Mi" # hard limit ป้องกัน OOM
เคล็ดลับสำคัญเรื่อง CPU Limits: มีแนวคิดที่ได้รับความนิยมมากขึ้นเรื่อยๆ ในปี 2026 คือ ไม่ตั้ง CPU limits เลย (ตั้งแค่ requests) เหตุผลก็ตรงไปตรงมา — CPU เป็นทรัพยากรที่ compressible ถ้า Pod ใช้ CPU เกิน limit มันจะถูก throttle ไม่ใช่ถูก kill ซึ่ง throttling ทำให้ latency สูงขึ้นโดยไม่จำเป็น ในขณะที่ถ้าไม่มี limit Pod สามารถ burst ใช้ CPU ที่ว่างอยู่บน Node ได้เลย ตราบที่ Node มี capacity เหลือ
# แนวทาง: ไม่ตั้ง CPU limits (Best Practice สำหรับ workloads ส่วนใหญ่)
resources:
requests:
cpu: "300m"
memory: "460Mi"
limits:
# ไม่ตั้ง cpu limit — ให้ burst ได้ตาม Node capacity
memory: "512Mi" # Memory limit ยังจำเป็น เพราะ memory ไม่ compressible
Vertical Pod Autoscaler (VPA): Right-Sizing แบบอัตโนมัติ
การ right-size ด้วยมือนั้นเหมาะกับ workloads ไม่กี่ตัว แต่ถ้าคุณมี 50-100 microservices ล่ะ? ทำด้วยมือไม่ไหวแน่นอนครับ ตรงนี้ VPA เข้ามาช่วยได้
Vertical Pod Autoscaler (VPA) เป็นคอมโพเนนต์ของ Kubernetes ที่ วิเคราะห์ historical usage ของแต่ละ Pod แล้วแนะนำหรือปรับ resource requests/limits ให้อัตโนมัติ ซึ่งจริงๆ แล้วมันทำงานได้ดีกว่าที่หลายคนคิดมาก
VPA มี 3 โหมดการทำงาน
- Off: VPA คำนวณ recommendations แต่ไม่ apply อะไรเลย เหมาะสำหรับการ "ดูก่อน" ว่า VPA แนะนำอะไร
- Initial: VPA ตั้ง requests ให้เฉพาะตอนสร้าง Pod ใหม่ ไม่แตะ Pod ที่รันอยู่แล้ว
- Auto: VPA ปรับ requests ให้อัตโนมัติโดย evict Pod แล้วสร้างใหม่ด้วยค่า requests ที่ปรับแล้ว — ตรงนี้ต้องระวังนิดนึงเพราะอาจทำให้เกิด downtime ได้
คำแนะนำสำคัญ: เริ่มจากโหมด Off เสมอ
อย่าเพิ่งเปิด Auto mode ทันทีนะครับ ความผิดพลาดที่พบบ่อยมากคือกระโดดไปใช้ Auto เลยโดยไม่ดู recommendations ก่อน เริ่มจาก "Off" ตรวจสอบว่า recommendations สมเหตุสมผลหรือไม่ แล้วค่อยปรับเป็น Auto ทีหลัง:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-server-vpa
namespace: production
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: api-server
updatePolicy:
updateMode: "Off" # เริ่มจาก Off เพื่อดู recommendations ก่อน
resourcePolicy:
containerPolicies:
- containerName: api-server
minAllowed:
cpu: "100m"
memory: "128Mi"
maxAllowed:
cpu: "2"
memory: "4Gi"
controlledResources: ["cpu", "memory"]
ดู recommendations ที่ VPA คำนวณได้ง่ายมาก:
# ดู VPA recommendations
kubectl describe vpa api-server-vpa -n production
# ตัวอย่าง output:
# Recommendation:
# Container Recommendations:
# Container Name: api-server
# Lower Bound:
# Cpu: 100m
# Memory: 200Mi
# Target:
# Cpu: 280m
# Memory: 420Mi
# Uncapped Target:
# Cpu: 280m
# Memory: 420Mi
# Upper Bound:
# Cpu: 800m
# Memory: 1Gi
ข้อจำกัดสำคัญของ VPA ที่ต้องรู้
- ไม่ควรใช้ VPA และ HPA บน metric เดียวกัน: ถ้า HPA scale ตาม CPU อย่าให้ VPA ปรับ CPU requests ด้วย เพราะมันจะขัดแย้งกัน แนวทางที่ดีคือให้ VPA จัดการ memory ส่วน HPA จัดการ scaling ตาม CPU หรือ custom metrics
- VPA ต้อง restart Pod: ใน Auto mode VPA จะ evict Pod แล้วสร้างใหม่เพื่อ apply ค่าใหม่ ซึ่งอาจทำให้เกิด brief disruption ได้ (แม้จะมี PodDisruptionBudget ช่วยควบคุมก็ตาม)
- GKE In-Place Pod Resize: ข่าวดีสำหรับผู้ใช้ GKE! ตั้งแต่ Kubernetes 1.35 GKE รองรับ In-Place Pod Resize ที่สามารถปรับ CPU/Memory ได้โดยไม่ต้อง restart Pod เลย — นี่เป็นฟีเจอร์ที่ทำให้ VPA น่าใช้ขึ้นมากครับ
Horizontal Pod Autoscaler (HPA): Scale ตาม Traffic จริง
ถ้า VPA ปรับ "ขนาด" ของ Pod ให้พอดี HPA ก็ปรับ "จำนวน" ของ Pod ให้พอดีกับ load ที่เข้ามา สองตัวนี้ทำงานคนละมิติกัน และเมื่อใช้ร่วมกันอย่างถูกวิธี จะช่วย optimize ต้นทุนได้อย่างมีประสิทธิภาพมาก
HPA พื้นฐาน: Scale ตาม CPU
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-server-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # Scale เมื่อ CPU usage เฉลี่ยเกิน 70%
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # รอ 5 นาทีก่อน scale down
policies:
- type: Percent
value: 25 # Scale down ครั้งละไม่เกิน 25%
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 30 # Scale up เร็วเมื่อ traffic พุ่ง
policies:
- type: Percent
value: 100 # Scale up ได้เร็วถึง 2 เท่า
periodSeconds: 60
- type: Pods
value: 4 # หรือเพิ่มครั้งละ 4 pods
periodSeconds: 60
selectPolicy: Max
HPA ขั้นสูง: Scale ตาม Custom Metrics
การ scale ตาม CPU อย่างเดียวไม่เหมาะกับทุก workload ยกตัวอย่างเช่น ถ้าแอปของคุณเป็น queue worker ที่ไม่ได้ใช้ CPU มากแต่มี message ค้างอยู่เป็นพันๆ ตัว ควร scale ตามจำนวน messages ใน queue แทน — ตรงนี้เป็นจุดที่หลายคนมองข้ามครับ:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: queue-worker-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: queue-worker
minReplicas: 1
maxReplicas: 50
metrics:
- type: External
external:
metric:
name: sqs_queue_messages_visible
selector:
matchLabels:
queue: "order-processing"
target:
type: AverageValue
averageValue: "10" # ต้องการให้แต่ละ Pod รับผิดชอบ ~10 messages
รวม VPA + HPA อย่างถูกวิธี
ส่วนนี้สำคัญมากครับ เพราะถ้ารวมผิดวิธีจะทำให้ทั้งสองตัวขัดแย้งกัน แนวทางที่แนะนำคือ:
- VPA จัดการ Memory requests/limits (เพราะ HPA ไม่ค่อย scale ตาม memory)
- HPA scale จำนวน Pods ตาม CPU หรือ custom metrics เช่น requests per second
- ตั้ง VPA ให้
controlledResources: ["memory"]เท่านั้น เพื่อไม่ให้ขัดแย้งกับ HPA
# VPA ที่จัดการแค่ Memory เพื่อไม่ขัดกับ HPA ที่ scale ตาม CPU
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-server-vpa
namespace: production
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: api-server
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: api-server
controlledResources: ["memory"] # จัดการแค่ memory
minAllowed:
memory: "128Mi"
maxAllowed:
memory: "4Gi"
Karpenter: Node Autoscaling ยุคใหม่ที่เร็วและฉลาดกว่า Cluster Autoscaler
Right-sizing Pods อย่างเดียวไม่พอนะครับ ถ้า Pods เล็กลงแต่ Nodes ยังใหญ่เท่าเดิม คุณก็ยังจ่ายค่า Node เต็มอยู่ดี
Karpenter เป็น open-source Kubernetes node provisioner ที่ AWS ดูแล ออกแบบมาเพื่อแทนที่ Cluster Autoscaler แบบเดิม ด้วยแนวคิดที่แตกต่างอย่างสิ้นเชิง และจากประสบการณ์ที่เห็นมา มันเปลี่ยนเกมจริงๆ ครับ:
Cluster Autoscaler vs Karpenter
| คุณสมบัติ | Cluster Autoscaler | Karpenter |
|---|---|---|
| การเลือก Instance Type | ต้องกำหนด Node Group ล่วงหน้า (เช่น m5.xlarge pool) | เลือก Instance Type อัตโนมัติตาม Pod requirements |
| ความเร็วในการ Scale Up | ช้า (2-5 นาที) ต้องรอ ASG provision | เร็ว (ไม่กี่วินาที) เรียก EC2 API โดยตรง |
| การ Consolidate Nodes | จำกัด — ทำได้แค่ scale down node ที่ว่าง | ฉลาด — ย้าย Pods แล้ว terminate node เพื่อรวมลง node ที่เล็กกว่า |
| Spot Instance Support | ต้องตั้ง Mixed Instance Policy เอง | จัดการ Spot/On-Demand mix อัตโนมัติ |
| Multi-Architecture | ต้องแยก Node Group สำหรับ ARM/x86 | รองรับ ARM (Graviton) และ x86 ใน pool เดียวกัน |
ตั้งค่า Karpenter NodePool
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: general-purpose
spec:
template:
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ["amd64", "arm64"] # ใช้ทั้ง x86 และ Graviton
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"] # ผสม Spot + On-Demand
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r"] # ยอมรับ compute, general, memory optimized
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ["5"] # ใช้ instance generation 6 ขึ้นไปเท่านั้น
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
limits:
cpu: "200" # จำกัด total CPU ของ pool ไม่เกิน 200 cores
memory: "400Gi" # จำกัด total memory ไม่เกิน 400Gi
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s # รวม node ภายใน 30 วินาทีเมื่อมี underutilization
---
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
role: KarpenterNodeRole-my-cluster
Karpenter Consolidation: ฟีเจอร์ลดต้นทุนที่ทรงพลังที่สุด
ฟีเจอร์ที่ทำให้ Karpenter โดดเด่นที่สุดคือ Node Consolidation — ซึ่งพูดง่ายๆ คือเมื่อ Karpenter ตรวจพบว่า Pods บน node หนึ่งสามารถย้ายไป node อื่นที่มีที่ว่างได้ มันจะ:
- หา node ที่มีขนาดเล็กกว่าที่สามารถรองรับ Pods ทั้งหมดได้
- ย้าย Pods ไปยัง node ใหม่หรือ node เดิมที่มีที่ว่าง
- Terminate node เดิมที่ไม่จำเป็นแล้ว
ยกตัวอย่างให้เห็นภาพชัดๆ ครับ — ถ้าคุณมี 3 nodes ขนาด m6i.xlarge (4 CPU, 16GB) แต่ Pods ทั้งหมดรวมกันใช้แค่ 6 CPU, 20GB Karpenter อาจ consolidate เหลือแค่ 2 nodes หรือเปลี่ยนเป็น instance type ที่เล็กกว่าที่เหมาะสมกว่า ช่วยประหยัดได้ทันทีโดยที่คุณไม่ต้องทำอะไรเลย
เครื่องมือ Cost Visibility: มองเห็นต้นทุนทุกมิติด้วย OpenCost และ Kubecost
คุณจะ optimize สิ่งที่วัดไม่ได้ไม่ได้หรอกครับ นี่คือหลักการพื้นฐานเลย เครื่องมือ cost visibility สำหรับ Kubernetes จึงเป็นสิ่งจำเป็นอย่างยิ่ง มาดูตัวเลือกหลักๆ กัน
OpenCost: มาตรฐานเปิดจาก CNCF
OpenCost เป็นโปรเจกต์ open-source ภายใต้ CNCF (Cloud Native Computing Foundation) ระดับ Incubation ที่ให้ cost monitoring แบบ vendor-neutral ติดตั้งง่ายและที่สำคัญคือ ฟรี:
# ติดตั้ง OpenCost ด้วย Helm
helm install opencost opencost/opencost --namespace opencost --create-namespace --set opencost.prometheus.internal.enabled=true --set opencost.ui.enabled=true
# เปิด port-forward เพื่อเข้าถึง UI
kubectl port-forward -n opencost svc/opencost 9090:9090
# ใช้ kubectl cost plugin ดูต้นทุนตาม namespace
kubectl cost namespace --show-cpu --show-memory --show-efficiency
ตัวอย่าง output จาก kubectl cost namespace:
+----------------+--------+----------+----------+-----------+------------+
| NAMESPACE | CPU | CPU EFF | MEMORY | MEM EFF | TOTAL COST |
+----------------+--------+----------+----------+-----------+------------+
| production | $245.30| 72% | $189.20 | 65% | $434.50 |
| staging | $120.15| 31% | $98.40 | 28% | $218.55 |
| development | $89.70 | 22% | $67.30 | 19% | $157.00 |
| monitoring | $45.20 | 85% | $38.90 | 78% | $84.10 |
+----------------+--------+----------+----------+-----------+------------+
จากตัวอย่างนี้ เห็นได้ชัดเลยว่า staging (efficiency 31%/28%) และ development (22%/19%) มี over-provisioning สูงมาก เป็นจุดที่ควรจัดการเป็นลำดับแรก
Kubecost: ฟีเจอร์เชิงพาณิชย์ที่ครบครัน
Kubecost สร้างบน OpenCost core engine แต่เพิ่มฟีเจอร์เชิงพาณิชย์อีกมาก ถ้าองค์กรคุณต้องการมากกว่าแค่ basic visibility Kubecost ก็น่าสนใจ:
- Real-time cost allocation แยกตาม cluster, namespace, deployment, pod, container, label
- Savings recommendations แนะนำการ right-size, ลด idle resources, ใช้ Spot Instances
- Budget alerts แจ้งเตือนเมื่อค่าใช้จ่ายเกินงบประมาณที่ตั้งไว้
- Unified cost view รวมค่าใช้จ่าย Kubernetes กับ cloud services อื่นๆ (S3, RDS ฯลฯ) ไว้ที่เดียว
# ติดตั้ง Kubecost
helm install kubecost cost-analyzer --repo https://kubecost.github.io/cost-analyzer/ --namespace kubecost --create-namespace --set kubecostToken="YOUR_TOKEN"
# ใช้ Kubecost API ดึงข้อมูลต้นทุนตาม namespace (7 วันย้อนหลัง)
curl -s "http://localhost:9090/model/allocation?window=7d&aggregate=namespace" | jq '.data[0] | to_entries[] | {
namespace: .key,
totalCost: .value.totalCost,
cpuCost: .value.cpuCost,
ramCost: .value.ramCost,
cpuEfficiency: .value.cpuEfficiency,
ramEfficiency: .value.ramEfficiency
}'
GKE Cost Allocation: ฟีเจอร์ native จาก Google Cloud
สำหรับผู้ใช้ GKE มีข่าวดีครับ GKE Cost Allocation เป็น GA แล้ว สามารถดู cost breakdown ตาม cluster, namespace และ labels ได้ใน Cloud Billing Console โดยไม่ต้องติดตั้งเครื่องมือเพิ่มเติมเลย ฟีเจอร์นี้ทำให้ FinOps teams มองเห็น Kubernetes spend ได้อย่างแม่นยำโดยไม่ต้องพึ่ง third-party tools
กลยุทธ์ลด Waste ที่ได้ผลทันที
นอกจากเรื่อง right-sizing และ autoscaling แล้ว ยังมีอีกหลายกลยุทธ์ที่ช่วยลดค่าใช้จ่ายได้แบบเห็นผลเร็วครับ มาดูกัน
1. ปิด Non-Production Environments นอกเวลาทำการ
อันนี้เป็น quick win ที่ดีที่สุดเลย Staging และ Development environments ไม่จำเป็นต้องรัน 24/7 จริงไหมครับ? การปิดนอกเวลาทำการ (เช่น 19:00-07:00 และวันหยุด) ช่วยประหยัดได้ถึง 65-70% ของค่าใช้จ่าย non-production:
# CronJob สำหรับ scale down dev/staging namespace ตอน 19:00
apiVersion: batch/v1
kind: CronJob
metadata:
name: scale-down-dev
namespace: kube-system
spec:
schedule: "0 19 * * 1-5" # จันทร์-ศุกร์ 19:00
jobTemplate:
spec:
template:
spec:
serviceAccountName: namespace-scaler
containers:
- name: kubectl
image: bitnami/kubectl:latest
command:
- /bin/sh
- -c
- |
# Scale down ทุก deployment ใน dev namespace
for deploy in $(kubectl get deploy -n development -o name); do
kubectl scale $deploy --replicas=0 -n development
done
for deploy in $(kubectl get deploy -n staging -o name); do
kubectl scale $deploy --replicas=0 -n staging
done
restartPolicy: OnFailure
---
# CronJob สำหรับ scale up dev/staging namespace ตอน 07:00
apiVersion: batch/v1
kind: CronJob
metadata:
name: scale-up-dev
namespace: kube-system
spec:
schedule: "0 7 * * 1-5" # จันทร์-ศุกร์ 07:00
jobTemplate:
spec:
template:
spec:
serviceAccountName: namespace-scaler
containers:
- name: kubectl
image: bitnami/kubectl:latest
command:
- /bin/sh
- -c
- |
# Scale up deployments กลับมา
kubectl scale deploy --all --replicas=2 -n development
kubectl scale deploy --all --replicas=2 -n staging
restartPolicy: OnFailure
2. ตั้ง Resource Quotas ป้องกัน Namespace Sprawl
ResourceQuotas ช่วยกำหนดขอบเขตค่าใช้จ่ายในแต่ละ namespace ป้องกันไม่ให้ทีมใดทีมหนึ่งใช้ทรัพยากรมากเกินไปจนกระทบทีมอื่น:
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-quota
namespace: development
spec:
hard:
requests.cpu: "20" # จำกัด total CPU requests ไม่เกิน 20 cores
requests.memory: "40Gi" # จำกัด total memory requests ไม่เกิน 40Gi
limits.cpu: "40" # จำกัด total CPU limits ไม่เกิน 40 cores
limits.memory: "80Gi" # จำกัด total memory limits ไม่เกิน 80Gi
pods: "100" # จำกัดจำนวน pods ไม่เกิน 100
persistentvolumeclaims: "20" # จำกัด PVC ไม่เกิน 20
---
# LimitRange บังคับให้ทุก Pod ต้องตั้ง requests/limits
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: development
spec:
limits:
- default:
cpu: "500m"
memory: "512Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
max:
cpu: "4"
memory: "8Gi"
min:
cpu: "50m"
memory: "64Mi"
type: Container
3. ลดค่า Data Transfer ข้าม AZ
ค่า data transfer ระหว่าง Availability Zones เป็นค่าใช้จ่ายที่ถูกมองข้ามบ่อยมากใน Kubernetes (ผมเจอบ่อยมากจริงๆ) AWS คิด $0.01/GB สำหรับ cross-AZ traffic ซึ่งฟังดูไม่เยอะ แต่สำหรับแอปที่มี traffic สูงอาจรวมเป็นหลักพันดอลลาร์ต่อเดือนได้สบาย
วิธีลด cross-AZ traffic ก็ไม่ยากครับ:
# ใช้ Topology Aware Routing เพื่อให้ traffic อยู่ใน AZ เดียวกัน
apiVersion: v1
kind: Service
metadata:
name: api-server
namespace: production
annotations:
service.kubernetes.io/topology-mode: Auto
spec:
selector:
app: api-server
ports:
- port: 80
targetPort: 8080
Topology Aware Routing จะพยายามส่ง traffic ไปยัง Pod ที่อยู่ใน AZ เดียวกับ client ก่อน ลด cross-AZ data transfer ได้อย่างมีนัยสำคัญโดยแทบไม่ต้องเปลี่ยนแปลง application code เลย
4. ใช้ Spot Instances กับ Kubernetes อย่างฉลาด
Kubernetes เป็น workload ที่เหมาะกับ Spot Instances มากเป็นพิเศษ เพราะ Kubernetes มี orchestration ในตัวอยู่แล้ว — เมื่อ Spot node ถูก terminate Kubernetes จะ reschedule Pods ไปยัง node อื่นให้อัตโนมัติ ไม่ต้องทำอะไรเพิ่ม:
# แยก workloads ด้วย taints/tolerations
# Node pool สำหรับ Spot instances
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: spot-workloads
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
- key: kubernetes.io/arch
operator: In
values: ["amd64", "arm64"]
taints:
- key: spot
value: "true"
effect: NoSchedule
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
---
# Deployment ที่ tolerate spot taint
apiVersion: apps/v1
kind: Deployment
metadata:
name: batch-processor
spec:
replicas: 10
template:
spec:
tolerations:
- key: spot
operator: Equal
value: "true"
effect: NoSchedule
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: batch-processor
containers:
- name: processor
image: batch-processor:latest
resources:
requests:
cpu: "500m"
memory: "1Gi"
5. ใช้ ARM (Graviton/Ampere) เพื่อลดต้นทุนและเพิ่มประสิทธิภาพ
AWS Graviton, Azure Ampere Altra และ GCP Tau T2A instances ให้ price-performance ratio ที่ดีกว่า x86 ถึง 20-40% สำหรับ workloads ส่วนใหญ่ และ Kubernetes ทำให้การ migrate ไป ARM ง่ายขึ้นมากเพราะสามารถรัน multi-architecture images ได้:
# สร้าง multi-arch container image ด้วย Docker Buildx
docker buildx build --platform linux/amd64,linux/arm64 --tag myregistry/api-server:v2.5.0 --push .
เมื่อสร้าง multi-arch image แล้ว Karpenter จะสามารถเลือก Graviton instances ได้โดยอัตโนมัติเมื่อมีราคาถูกกว่า x86 ไม่ต้องเปลี่ยน deployment manifest เลยครับ สะดวกมาก
การสร้าง FinOps Dashboard สำหรับ Kubernetes
ข้อมูลทั้งหมดที่กล่าวมาจะมีประโยชน์สูงสุดเมื่อถูกรวบรวมไว้ใน dashboard ที่ทุกคนในทีมเข้าถึงได้ ไม่ใช่แค่ทีม DevOps แต่รวมถึง engineering leads และ management ด้วย นี่คือแนวทางการสร้าง Grafana dashboard สำหรับ Kubernetes cost monitoring:
# PromQL queries สำหรับ Grafana Dashboard
# 1. Total Monthly Cost by Namespace (ใช้กับ OpenCost metrics)
sum(
increase(opencost_allocation_cost_total[30d])
) by (namespace)
# 2. CPU Efficiency Score by Deployment
avg(
rate(container_cpu_usage_seconds_total[1h])
/
kube_pod_container_resource_requests{resource="cpu"}
) by (deployment) * 100
# 3. Memory Waste (Requested - Used) in GB
sum(
(
kube_pod_container_resource_requests{resource="memory"}
-
container_memory_working_set_bytes
)
) by (namespace) / 1024 / 1024 / 1024
# 4. Node Utilization Score
(
sum(rate(node_cpu_seconds_total{mode!="idle"}[5m])) by (node)
/
sum(kube_node_status_capacity{resource="cpu"}) by (node)
) * 100
Alerts ที่ควรตั้ง
Dashboard จะไม่มีประโยชน์ถ้าไม่มีคนดู (ซึ่งเป็นเรื่องปกตินะครับ ไม่มีใครนั่งจ้อง dashboard ทั้งวัน) ดังนั้นต้องตั้ง alerts ด้วย:
# PrometheusRule สำหรับ cost alerts
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: cost-alerts
namespace: monitoring
spec:
groups:
- name: kubernetes-cost-alerts
rules:
# Alert เมื่อ namespace มี CPU efficiency ต่ำกว่า 30%
- alert: LowCPUEfficiency
expr: |
(
sum(rate(container_cpu_usage_seconds_total[1h])) by (namespace)
/
sum(kube_pod_container_resource_requests{resource="cpu"}) by (namespace)
) < 0.3
for: 24h
labels:
severity: warning
team: finops
annotations:
summary: "Namespace {{ $labels.namespace }} has CPU efficiency below 30%"
description: "Consider right-sizing pods in namespace {{ $labels.namespace }}"
# Alert เมื่อมี Pod ที่ไม่มี resource requests
- alert: MissingResourceRequests
expr: |
count(
kube_pod_container_info
unless on(pod, namespace, container)
kube_pod_container_resource_requests{resource="cpu"}
) > 0
for: 1h
labels:
severity: critical
team: platform
annotations:
summary: "Pods found without CPU resource requests"
Checklist สำหรับ Kubernetes Cost Optimization
เอาล่ะครับ มาถึงส่วนที่หลายคนรอ เพื่อให้คุณนำไปใช้ได้ทันที นี่คือ checklist สรุปสิ่งที่ควรทำ เรียงตามลำดับความง่ายและผลลัพธ์:
Quick Wins (ทำได้ทันที ผลลัพธ์ภายใน 1 สัปดาห์)
- ติดตั้ง OpenCost หรือ Kubecost เพื่อ visibility พื้นฐาน
- ตรวจสอบ Pods ที่ไม่มี resource requests/limits แล้วเพิ่มให้ครบ
- ปิด Dev/Staging นอกเวลาทำการด้วย CronJob
- ลบ Pods/Deployments ที่ไม่ได้ใช้แล้ว (orphaned resources)
- ตั้ง LimitRange เพื่อบังคับ default requests/limits
Medium-Term (2-4 สัปดาห์)
- ตั้ง VPA ในโหมด Off เพื่อดู recommendations สำหรับทุก workload
- ปรับ resource requests ตาม P95 usage + buffer
- ตั้ง ResourceQuotas สำหรับทุก namespace
- เปิดใช้ Topology Aware Routing ลด cross-AZ traffic
- ตั้ง HPA สำหรับ workloads ที่มี traffic ผันแปร
Long-Term (1-3 เดือน)
- Migrate จาก Cluster Autoscaler ไป Karpenter
- เริ่มใช้ Spot Instances สำหรับ fault-tolerant workloads
- สร้าง multi-arch images แล้วย้ายไป ARM (Graviton/Ampere)
- สร้าง FinOps dashboard ใน Grafana พร้อม cost alerts
- ตั้ง Chargeback/Showback model ตาม namespace/team
- เปิด VPA Auto mode สำหรับ workloads ที่เหมาะสม
กรณีศึกษา: ลดค่าใช้จ่าย Kubernetes ลง 55% ใน 3 เดือน
เพื่อให้เห็นภาพชัดขึ้นว่าเทคนิคเหล่านี้ได้ผลจริง ลองดูตัวอย่างการ optimize ของบริษัท e-commerce แห่งหนึ่งที่รัน Kubernetes workloads บน AWS EKS:
สถานการณ์เริ่มต้น
- ค่าใช้จ่าย Kubernetes: $42,000/เดือน
- จำนวน Nodes: 35 x m5.2xlarge (On-Demand ทั้งหมด)
- CPU Efficiency เฉลี่ย: 28%
- Memory Efficiency เฉลี่ย: 22%
- ไม่มี autoscaling ใดๆ (fixed replicas ทั้งหมด)
พูดตรงๆ สถานการณ์แบบนี้ไม่ได้แปลกเลยนะครับ หลายบริษัทเป็นแบบนี้
สิ่งที่ทำ (ตามลำดับ)
เดือนที่ 1:
- ติดตั้ง Kubecost — พบ 8 deployments ที่ไม่ได้ใช้แล้ว (legacy services ที่ไม่มีใครกล้าลบ) → ลบทิ้ง ประหยัด $3,200/เดือน
- ปิด staging ตอนกลางคืนและวันหยุด → ประหยัด $4,800/เดือน
- ปรับ resource requests ตาม P95 usage → ลด node จาก 35 เหลือ 25 ประหยัด $5,700/เดือน
เดือนที่ 2:
- ตั้ง HPA สำหรับ API services → ลด over-provisioning ตอน off-peak ประหยัด $2,800/เดือน
- ย้ายไป Karpenter → consolidation อัตโนมัติช่วยประหยัดอีก $1,500/เดือน
เดือนที่ 3:
- ใช้ Spot instances สำหรับ batch processing, CI/CD, และ staging → ประหยัด $3,200/เดือน
- ย้าย stateless workloads ไป Graviton (ARM) → ประหยัดอีก $1,800/เดือน
ผลลัพธ์
- ค่าใช้จ่าย Kubernetes ลดจาก $42,000 เหลือ $19,000/เดือน (ลด 55%)
- CPU Efficiency เพิ่มจาก 28% เป็น 68%
- Memory Efficiency เพิ่มจาก 22% เป็น 61%
- ไม่มี performance degradation — latency P99 เท่าเดิมหรือดีขึ้นด้วยซ้ำ
สรุป: Kubernetes Cost Optimization เป็นการเดินทาง ไม่ใช่จุดหมาย
การ optimize ค่าใช้จ่าย Kubernetes ไม่ใช่สิ่งที่ทำครั้งเดียวแล้วจบ มันเป็น กระบวนการต่อเนื่อง ที่ต้องทำซ้ำแล้วซ้ำเล่า เพราะ workloads เปลี่ยนแปลงตลอดเวลา ทีมใหม่เข้ามา services ใหม่ถูกสร้างขึ้น traffic patterns เปลี่ยนไป
สิ่งที่สำคัญที่สุด (และเป็นสิ่งที่หลายทีมมองข้าม) คือการสร้าง วัฒนธรรม cost awareness ในทีม ไม่ใช่แค่ติดตั้งเครื่องมือแล้วจบ ทุกคนที่ deploy workloads บน Kubernetes ควรเข้าใจว่าทรัพยากรที่ตัวเองใช้มีต้นทุนเท่าไหร่ ไม่ใช่แค่เรื่องของทีม FinOps
จากสถิติของ FinOps Foundation องค์กรที่มีโปรแกรม cost optimization อย่างเป็นระบบสามารถลดค่าใช้จ่ายคลาวด์ได้เฉลี่ย 25-30% ต่อเดือน และสำหรับ Kubernetes โดยเฉพาะ ที่มี resource headroom สูงถึง 20-30% ในสถานะปกติ โอกาสในการประหยัดจึงมีมากเป็นพิเศษ
เริ่มจาก visibility (ติดตั้ง OpenCost/Kubecost) ทำ right-sizing (VPA + manual analysis) ตั้ง autoscaling (HPA + Karpenter) แล้วค่อยๆ เพิ่มเทคนิคขั้นสูงอย่าง Spot instances และ ARM migration ทำทีละขั้นอย่างเป็นระบบ แล้วคุณจะเห็นบิลคลาวด์ลดลงอย่างมีนัยสำคัญ โดยที่ performance ยังคงดีเหมือนเดิมครับ