Cost Optimization #

Kubernetes memudahkan scaling, tapi juga memudahkan pemborosan. Cluster yang dibuat untuk eksperimen dan tidak pernah dihapus, node yang dipakai 10% kapasitasnya, atau development environment yang berjalan 24/7 padahal hanya dipakai 8 jam — semuanya adalah biaya nyata yang sering tidak disadari sampai tagihan cloud datang. Artikel ini membahas cara sistematis mengidentifikasi dan mengurangi pemborosan tanpa mengorbankan reliabilitas.

Di Mana Biaya Kubernetes Berasal #

Komponen biaya cluster Kubernetes di cloud:

Node (terbesar, biasanya 70-80% total biaya):
  → Compute: CPU dan memory node
  → Spot/Preemptible: 60-80% lebih murah dari on-demand

Storage (10-20%):
  → PersistentVolumes (SSD/HDD)
  → Snapshot backup
  → Container registry storage

Networking (5-15%):
  → Load balancer per Service type LoadBalancer
  → Egress traffic keluar region/internet
  → NAT Gateway untuk private cluster

Managed control plane:
  → EKS: $0.10/jam per cluster (~$72/bulan)
  → GKE/AKS: gratis untuk control plane

Identifikasi Pemborosan #

# Lihat utilisasi node (apakah node terlalu banyak?)
kubectl top nodes

# Hitung request vs actual usage per namespace
kubectl top pods -A --containers | sort -k4 -rn | head -20

# Cari Pod dengan resource yang sangat over-provisioned
# (request jauh lebih besar dari actual usage)
# Pod dengan CPU usage < 10% dari request (over-provisioned)
(
  rate(container_cpu_usage_seconds_total{container!=""}[30m])
  / on(pod, namespace, container)
  kube_pod_container_resource_requests{resource="cpu", container!=""}
) < 0.1

# Namespace dengan utilisasi CPU rendah
sum by (namespace) (
  rate(container_cpu_usage_seconds_total{container!=""}[1h])
) /
sum by (namespace) (
  kube_pod_container_resource_requests{resource="cpu"}
) * 100

Spot/Preemptible Node: Penghematan Terbesar #

Spot Instance (AWS) atau Preemptible VM (GCP) adalah node yang 60-80% lebih murah dari on-demand, tapi bisa diambil kembali oleh provider dengan notice 2 menit (AWS) atau 30 detik (GCP).

Workload yang cocok untuk Spot/Preemptible:
  ✓ Stateless service yang bisa restart cepat
  ✓ Batch processing dan data pipeline
  ✓ CI/CD runner
  ✓ Development dan staging environment
  ✓ ML training job
  ✓ Worker yang bisa retry jika terganggu

Workload yang TIDAK cocok:
  ✗ Database dengan data lokal
  ✗ Service yang tidak punya replicas (single replica production)
  ✗ Workload dengan startup time sangat lama (>10 menit)
  ✗ Service yang tidak bisa gracefully handle SIGTERM
# Node pool Spot untuk GKE
gcloud container node-pools create spot-workers \
  --cluster production \
  --spot \
  --machine-type n2-standard-4 \
  --num-nodes 0 \
  --enable-autoscaling --min-nodes 0 --max-nodes 50

# Node pool Spot untuk EKS (managed node group)
eksctl create nodegroup \
  --cluster production \
  --name spot-workers \
  --instance-types m5.xlarge,m5a.xlarge,m4.xlarge \  # multiple types untuk ketersediaan
  --spot \
  --nodes-min 0 \
  --nodes-max 50
# Tolerasi Spot node di Deployment
spec:
  template:
    spec:
      tolerations:
      - key: "cloud.google.com/gke-spot"    # GKE
        operator: "Equal"
        value: "true"
        effect: "NoSchedule"
      # atau untuk EKS:
      # key: "eks.amazonaws.com/capacityType"
      # value: "SPOT"

      # Prefer Spot, tapi fallback ke on-demand jika tidak tersedia
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            preference:
              matchExpressions:
              - key: cloud.google.com/gke-spot
                operator: In
                values: ["true"]

Scale ke Nol untuk Non-Production #

Development dan staging environment yang berjalan 24/7 membuang 2/3 biaya untuk jam yang tidak dipakai:

Pola yang umum:
  Development environment: dipakai jam 9-18 (9 jam dari 24 jam = 37%)
  Staging environment: dipakai jam 8-22 saat testing berlangsung (58%)

  Sisa 42-63% waktu: cluster menyala tapi tidak ada yang pakai

Tools untuk scale ke nol:
  Kube-downscaler: scale Deployment ke 0 berdasarkan jadwal
  KEDA CronScaler: schedule scale
  Manual: CronJob yang jalankan kubectl scale
# Kube-downscaler: matikan semua di luar jam kerja
# Deploy kube-downscaler dengan konfigurasi:
# --default-downtime="Mon-Fri 18:00-08:00 Asia/Jakarta"
# --default-downtime="Sat-Sun 00:00-24:00 Asia/Jakarta"

# Annotation di Deployment untuk exclude dari downscaling
metadata:
  annotations:
    downscaler/exclude: "true"   # jangan scale down ini
    downscaler/force-uptime: "true"  # paksa tetap nyala
# Atau CronJob sederhana untuk scale down staging setiap malam
kubectl create cronjob scale-down-staging \
  --image=bitnami/kubectl \
  --schedule="0 20 * * 1-5" \
  -- kubectl scale --replicas=0 deployment --all -n staging

kubectl create cronjob scale-up-staging \
  --image=bitnami/kubectl \
  --schedule="0 8 * * 1-5" \
  -- kubectl scale --replicas=2 deployment --all -n staging

Right-Sizing: Hapus Padding yang Tidak Perlu #

Sumber padding yang sering tidak disadari:

1. "Safety margin" yang terlalu besar
   Orang sering set requests 2-3x lebih besar dari yang dibutuhkan
   "Lebih baik lebih dari kurang"
   → Biaya lebih besar, utilisasi rendah

2. Tidak pernah di-review setelah initial setup
   requests di-set saat awal, tidak pernah di-update
   meski usage actual sudah diketahui setelah beberapa bulan

3. Copy-paste dari template
   "Deployment sebelumnya pakai 512Mi, pakai yang sama aja"
   meski aplikasi baru jauh lebih ringan

Gunakan VPA untuk rekomendasi:
# Install VPA dan set mode Off untuk monitoring
kubectl apply -f https://github.com/kubernetes/autoscaler/releases/latest/download/vertical-pod-autoscaler.yaml

# Deploy VPA per Deployment dalam mode Off
# Setelah beberapa hari, lihat rekomendasi:
kubectl describe vpa api-vpa -n production | grep -A 10 "Recommendation"

# Terapkan rekomendasi ke values.yaml dan redeploy
# Review setiap bulan sekali sebagai bagian dari cost review rutin

Monitoring Biaya per Namespace dan Tim #

Tools untuk cost visibility:
  Kubecost: breakdown biaya per namespace, Pod, label
  OpenCost: open-source alternatif Kubecost
  Cloud native: AWS Cost Explorer, GCP Billing, Azure Cost Management

Label strategy untuk cost allocation:
  Semua resource harus punya label:
  team: backend
  environment: production
  project: checkout-service

  Dengan label ini, biaya bisa di-aggregate per tim dan proyek
# Enforcing cost labels via Kyverno policy
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-cost-labels
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-cost-labels
    match:
      any:
      - resources:
          kinds: ["Deployment", "StatefulSet", "DaemonSet"]
    validate:
      message: "Resource harus punya label: team, environment, project"
      pattern:
        metadata:
          labels:
            team: "?*"
            environment: "?*"
            project: "?*"

Ringkasan #

  • Spot/Preemptible untuk workload toleran — 60-80% lebih murah; gunakan untuk stateless service dengan replicas, batch job, CI runner, dan non-production environment.
  • Scale ke nol untuk development dan staging — environment yang tidak dipakai di luar jam kerja = 42-63% biaya terbuang; kube-downscaler atau CronJob bisa menghemat signifikan.
  • VPA dalam mode Off untuk rekomendasi — observasi penggunaan aktual selama 1-2 minggu, terapkan rekomendasi ke resource requests; hindari “safety margin” yang tidak berbasis data.
  • Label yang konsisten untuk cost allocation — tanpa label team dan project, tidak bisa tahu siapa yang menyebabkan lonjakan biaya; enforce via Kyverno policy.
  • Satu LoadBalancer per Ingress, bukan per Service — setiap LoadBalancer Service punya biaya tersendiri ($15-20/bulan per LB); gunakan satu Ingress Controller yang berbagi satu LB.
  • Review biaya bulanan secara regular — biaya cloud cluster tanpa review bulanan cenderung naik terus; alokasikan waktu untuk cost review dan right-sizing sebagai bagian operasional rutin.

← Sebelumnya: High Availability   Berikutnya: Disaster Recovery →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact