Panduan Optimasi Biaya Kubernetes 2026: Right-Sizing, Autoscaling, dan FinOps Container

Panduan komprehensif optimasi biaya Kubernetes 2026: teknik right-sizing pod, autoscaling HPA/VPA/Karpenter, spot instances, FinOps dengan Kubecost/OpenCost, dan strategi eliminasi waste untuk penghematan 40-70%.

Kenapa Kubernetes Jadi Sumber Pemborosan Cloud Terbesar di 2026?

Kubernetes sudah jadi standar de facto untuk orkestrasi container di hampir semua perusahaan teknologi. Tapi di balik semua fleksibilitas dan skalabilitas yang ditawarkan, ada kenyataan pahit yang jarang dibicarakan: mayoritas organisasi membuang 20-40% dari total pengeluaran Kubernetes mereka. Angka yang bikin geleng-geleng kepala.

Menurut laporan Cast AI Kubernetes Cost Benchmark 2025, rata-rata utilisasi CPU di seluruh klaster Kubernetes hanya 10% — turun dari 13% tahun sebelumnya. Utilisasi memori pun cuma berada di angka 23%. Artinya? Perusahaan Anda kemungkinan besar membayar 2 hingga 8 kali lipat dari resource yang benar-benar dipakai.

Ini bukan sekadar statistik.

Studi dari Wozz terhadap 3.042 klaster produksi di lebih dari 600 perusahaan pada Januari 2026 menemukan bahwa 68% pod meminta memori 3-8 kali lebih banyak dari yang sebenarnya digunakan. Jujur, waktu pertama kali lihat angka ini, saya kira ada yang salah datanya. Ternyata tidak.

Kenapa ini bisa terjadi? Akar masalahnya terletak pada budaya engineering yang terlalu konservatif. Dalam survei, 64% engineer mengaku menambahkan headroom resource 2-4 kali lipat setelah mengalami satu kali insiden OOM (Out of Memory). Bisa dipahami sih — mereka lebih khawatir aplikasi mati daripada biaya membengkak. Ditambah lagi, tim platform seringkali nggak punya visibilitas real-time atas tren utilisasi.

Di artikel sebelumnya, kita sudah membahas 12 strategi optimasi biaya cloud secara umum. Nah, kali ini kita akan menyelam lebih dalam ke dunia Kubernetes — mulai dari teknik right-sizing pod yang presisi, konfigurasi autoscaling multi-layer, sampai implementasi FinOps khusus container dengan tools seperti Kubecost dan OpenCost. Panduan ini dirancang untuk membantu Anda memangkas 40-70% biaya Kubernetes tanpa mengorbankan performa.

Memahami Struktur Biaya Kubernetes

Biaya Compute: Ujung Tombak Pengeluaran

Biaya compute — yaitu node (VM) yang menjalankan pod Anda — biasanya menyumbang 60-80% dari total pengeluaran Kubernetes. Biaya ini ditentukan oleh jumlah dan tipe instance yang digunakan. Masalahnya, Kubernetes scheduler mengalokasikan pod berdasarkan resource requests, bukan utilisasi aktual.

Jadi kalau pod Anda meminta 4 vCPU tapi cuma pakai 0.5 vCPU, scheduler tetap akan mereservasi 4 vCPU di node tersebut. Boros? Sangat.

Ini menciptakan fenomena yang disebut stranded resources — CPU dan memori yang sudah direservasi tapi tidak benar-benar digunakan oleh workload apapun. Data industri menunjukkan 40% CPU dan 57% memori yang di-provision di klaster Kubernetes tidak pernah di-request oleh pod manapun. Bayangkan, hampir separuh dari kapasitas node Anda benar-benar menganggur.

Biaya Non-Compute yang Sering Diabaikan

Selain compute, ada beberapa komponen biaya yang seringkali luput dari perhatian (dan ini yang biasanya bikin kaget saat audit):

  • Persistent Volume (PV) Storage: Disk yang di-attach ke pod sering di-provision dengan ukuran berlebihan. Volume 100GB yang cuma terisi 10GB tetap ditagih penuh.
  • Load Balancer: Setiap Service bertipe LoadBalancer membuat cloud load balancer terpisah dengan biaya per jam sendiri. Di lingkungan dengan banyak microservices, biaya ini bisa melonjak cepat.
  • Egress (Transfer Data Keluar): Transfer data antar availability zone dan antar region dikenakan biaya signifikan yang sering nggak terpantau.
  • Logging dan Monitoring: Ingestion log yang tidak difilter ke layanan seperti CloudWatch, Datadog, atau Elastic bisa menghabiskan ribuan dolar per bulan. Serius.
  • Orphaned Resources: PV yang sudah detach, snapshot lama, IP statis yang tidak dipakai — semua ini terus ditagih sampai dihapus manual.

Tim yang hanya fokus pada optimasi compute sering kaget menemukan bahwa biaya non-compute ini bisa melebihi waste dari overprovisioning node. Jadi pastikan audit Anda mencakup semua komponen biaya ini, bukan cuma compute.

Right-Sizing Pod: Fondasi Penghematan Kubernetes

Memahami Resource Requests dan Limits

Di Kubernetes, setiap container dalam pod bisa memiliki dua parameter resource: requests dan limits. Requests adalah jumlah resource minimum yang dijamin untuk container tersebut — ini yang dipakai scheduler untuk menentukan di node mana pod akan dijalankan. Sementara limits adalah batas maksimum resource yang boleh digunakan container.

Masalah terbesar yang ditemukan di hampir semua audit Kubernetes? 80-90% pod memiliki CPU dan memory requests yang jauh di atas kebutuhan aktual. Engineer biasanya meng-copy-paste nilai dari deployment lain atau memasang angka besar "untuk jaga-jaga." Siapa yang nggak pernah begitu, kan?

Akibatnya, bin-packing node menjadi sangat tidak efisien — banyak pod yang mereservasi resource besar tapi cuma pakai sebagian kecil.

Cara Menentukan Request yang Tepat

Langkah pertama: kumpulkan data utilisasi aktual. Jangan menebak — gunakan data. Berikut query Prometheus yang bisa dipakai untuk menganalisis penggunaan resource per container selama 7 hari terakhir:

# Query PromQL: Rata-rata penggunaan CPU per container (7 hari)
avg_over_time(
  rate(container_cpu_usage_seconds_total{
    namespace!="kube-system",
    container!=""
  }[5m])[7d:]
) by (namespace, pod, container)

# Query PromQL: P95 penggunaan memori per container (7 hari)
quantile_over_time(0.95,
  container_memory_working_set_bytes{
    namespace!="kube-system",
    container!=""
  }[7d:]
) by (namespace, pod, container)

Dari data ini, gunakan pendekatan berikut untuk menentukan request yang tepat:

  • CPU Request: Gunakan nilai P95 dari penggunaan CPU aktual, ditambah buffer 10-15%. CPU di Kubernetes bersifat compressible — kalau container melampaui limit, dia hanya akan di-throttle, bukan di-kill. Jadi buffer-nya bisa lebih kecil.
  • Memory Request: Gunakan nilai P99 dari penggunaan memori aktual, ditambah buffer 20-25%. Memori bersifat incompressible — kalau container melebihi limit, dia akan langsung di-OOMKill. Makanya buffer untuk memori harus lebih besar.
  • Memory Limit: Set di 120-150% dari request. Ini memberikan headroom untuk spike tanpa menyebabkan OOMKill, tapi tetap mencegah container dari memakan semua memori di node.

Script Otomatis untuk Audit Resource

Berikut script Python yang bisa Anda jalankan untuk membandingkan resource requests dengan utilisasi aktual di seluruh namespace. Script ini cukup straightforward — tinggal jalankan dan lihat hasilnya:

#!/usr/bin/env python3
"""
Kubernetes Resource Audit Script
Membandingkan resource requests dengan utilisasi aktual
"""

import subprocess
import json
from datetime import datetime

def get_pod_resources():
    """Ambil resource requests dari semua pod yang running"""
    cmd = [
        "kubectl", "get", "pods", "--all-namespaces",
        "-o", "json", "--field-selector=status.phase=Running"
    ]
    result = subprocess.run(cmd, capture_output=True, text=True)
    pods = json.loads(result.stdout)

    resources = []
    for pod in pods["items"]:
        ns = pod["metadata"]["namespace"]
        name = pod["metadata"]["name"]

        for container in pod["spec"].get("containers", []):
            requests = container.get("resources", {}).get("requests", {})
            limits = container.get("resources", {}).get("limits", {})

            resources.append({
                "namespace": ns,
                "pod": name,
                "container": container["name"],
                "cpu_request": requests.get("cpu", "not set"),
                "memory_request": requests.get("memory", "not set"),
                "cpu_limit": limits.get("cpu", "not set"),
                "memory_limit": limits.get("memory", "not set"),
            })

    return resources

def get_actual_usage():
    """Ambil metrik utilisasi aktual dari metrics-server"""
    cmd = [
        "kubectl", "top", "pods", "--all-namespaces",
        "--containers", "--no-headers"
    ]
    result = subprocess.run(cmd, capture_output=True, text=True)

    usage = {}
    for line in result.stdout.strip().split("\n"):
        parts = line.split()
        if len(parts) >= 5:
            key = f"{parts[0]}/{parts[1]}/{parts[2]}"
            usage[key] = {
                "cpu_usage": parts[3],
                "memory_usage": parts[4]
            }

    return usage

def parse_cpu(cpu_str):
    """Konversi CPU string ke millicores"""
    if cpu_str == "not set":
        return 0
    if cpu_str.endswith("m"):
        return int(cpu_str[:-1])
    return int(float(cpu_str) * 1000)

def parse_memory(mem_str):
    """Konversi memory string ke MiB"""
    if mem_str == "not set":
        return 0
    units = {"Ki": 1/1024, "Mi": 1, "Gi": 1024, "Ti": 1048576}
    for suffix, multiplier in units.items():
        if mem_str.endswith(suffix):
            return float(mem_str[:-len(suffix)]) * multiplier
    return float(mem_str) / (1024*1024)

def main():
    print(f"=== Kubernetes Resource Audit - {datetime.now()} ===\n")

    resources = get_pod_resources()
    usage = get_actual_usage()

    overprovisioned = []

    for r in resources:
        key = f"{r['namespace']}/{r['pod']}/{r['container']}"
        if key in usage:
            cpu_req = parse_cpu(r["cpu_request"])
            cpu_used = parse_cpu(usage[key]["cpu_usage"])
            mem_req = parse_memory(r["memory_request"])
            mem_used = parse_memory(usage[key]["memory_usage"])

            if cpu_req > 0 and cpu_used / cpu_req < 0.3:
                overprovisioned.append({
                    "namespace": r["namespace"],
                    "pod": r["pod"],
                    "type": "CPU",
                    "requested": f"{cpu_req}m",
                    "used": f"{cpu_used}m",
                    "utilization": f"{(cpu_used/cpu_req)*100:.1f}%"
                })

            if mem_req > 0 and mem_used / mem_req < 0.4:
                overprovisioned.append({
                    "namespace": r["namespace"],
                    "pod": r["pod"],
                    "type": "Memory",
                    "requested": f"{mem_req:.0f}Mi",
                    "used": f"{mem_used:.0f}Mi",
                    "utilization": f"{(mem_used/mem_req)*100:.1f}%"
                })

    print(f"Total pod yang diaudit: {len(resources)}")
    print(f"Resource yang overprovisioned: {len(overprovisioned)}\n")

    for item in sorted(overprovisioned, key=lambda x: x["utilization"]):
        print(f"  [{item['type']}] {item['namespace']}/{item['pod']}")
        print(f"    Request: {item['requested']} | Actual: {item['used']} | Util: {item['utilization']}")

if __name__ == "__main__":
    main()

Pengurangan request sebesar 5-15% per pod mungkin terdengar kecil. Tapi efek kompounding-nya luar biasa — dengan bin-packing yang lebih efisien, Anda bisa menjalankan workload yang sama di node yang lebih sedikit, menghasilkan penghematan klaster sebesar 30-60%.

Autoscaling Multi-Layer: HPA, VPA, dan Karpenter

Kubernetes menawarkan beberapa mekanisme autoscaling yang bekerja di layer berbeda. Kunci optimasi biaya adalah menggunakan kombinasi yang tepat dan meng-tune konfigurasinya secara agresif — terutama untuk scale-down. Banyak yang fokus ke scale-up, padahal di situ justru uang terbakar: saat scale-down terlalu lambat.

Horizontal Pod Autoscaler (HPA): Skalabilitas Horizontal

HPA secara otomatis menambah atau mengurangi jumlah replika pod berdasarkan metrik yang diamati — seperti utilisasi CPU, memori, atau custom metrics dari Prometheus. HPA paling cocok untuk workload stateless yang bisa di-scale horizontal: web server, API gateway, dan microservices.

# HPA dengan konfigurasi scale-down yang agresif
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-gateway-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-gateway
  minReplicas: 2
  maxReplicas: 50
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 75
  # Konfigurasi behavior untuk scale-down lebih cepat
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 120  # Tunggu 2 menit sebelum scale-down
      policies:
      - type: Percent
        value: 25            # Kurangi max 25% replika per periode
        periodSeconds: 60
      - type: Pods
        value: 2             # Atau kurangi max 2 pod per periode
        periodSeconds: 60
      selectPolicy: Min      # Pilih pengurangan yang lebih kecil (lebih aman)
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
      - type: Percent
        value: 100           # Bisa double replika saat scale-up
        periodSeconds: 30

Kesalahan konfigurasi HPA yang paling umum dan paling mahal? stabilizationWindowSeconds yang terlalu besar untuk scale-down. Default Kubernetes adalah 300 detik (5 menit), tapi banyak tim yang set ke 600 detik atau lebih karena takut flapping.

Akibatnya, setelah spike traffic selesai, pod berlebih tetap berjalan selama 10+ menit. Dan kalau spike terjadi beberapa kali sehari, itu bisa jadi jam-jam tambahan dari pod yang nggak diperlukan. Uang terbuang begitu saja.

Vertical Pod Autoscaler (VPA): Right-Sizing Otomatis

VPA menganalisis penggunaan resource historis dan secara otomatis menyesuaikan CPU dan memory requests untuk pod. VPA sangat berguna untuk workload yang sulit di-scale horizontal — misalnya database, message broker, dan ML inference.

# VPA dalam mode Recommendation (paling aman untuk memulai)
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: ml-inference-vpa
  namespace: production
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: ml-inference
  updatePolicy:
    updateMode: "Off"  # Hanya memberikan rekomendasi, tidak auto-apply
  resourcePolicy:
    containerPolicies:
    - containerName: inference-server
      minAllowed:
        cpu: 500m
        memory: 512Mi
      maxAllowed:
        cpu: 8
        memory: 16Gi
      controlledResources: ["cpu", "memory"]

Peringatan penting: Jangan pernah menjalankan VPA dan HPA bersamaan pada metrik yang sama (CPU/memori). Ini salah satu anti-pattern paling terkenal di Kubernetes — menciptakan feedback loop yang bikin pusing. VPA menaikkan request, HPA melihat utilisasi turun lalu mengurangi replika, VPA melihat utilisasi naik lalu menaikkan request lagi. Hasilnya: ketidakstabilan dan biaya yang justru meningkat.

Kombinasi yang aman: gunakan HPA dengan custom/external metrics (misalnya request per second dari Prometheus) dan VPA hanya untuk memory request. Atau gunakan VPA dalam mode "Off" (rekomendasi saja) lalu terapkan rekomendasi secara manual atau melalui CI/CD pipeline.

Karpenter: Provisioning Node Just-In-Time

Karpenter adalah autoscaler level node yang dikembangkan oleh AWS sebagai pengganti Cluster Autoscaler tradisional. Perbedaan utamanya? Sementara Cluster Autoscaler bekerja dengan node group yang sudah didefinisikan sebelumnya, Karpenter bisa memilih tipe instance yang paling optimal secara real-time berdasarkan kebutuhan aktual pod yang pending.

# Konfigurasi NodePool Karpenter untuk optimasi biaya
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: cost-optimized
spec:
  template:
    spec:
      requirements:
      - key: kubernetes.io/arch
        operator: In
        values: ["amd64", "arm64"]     # Izinkan ARM untuk hemat biaya
      - key: karpenter.sh/capacity-type
        operator: In
        values: ["spot", "on-demand"]   # Prioritas spot instances
      - 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"]                   # Hanya generasi 6 ke atas
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
  # Konfigurasi konsolidasi untuk menghilangkan node idle
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 60s               # Konsolidasi setelah 60 detik idle
  limits:
    cpu: "200"                          # Batas total CPU di NodePool ini
    memory: 400Gi                       # Batas total memori
  weight: 100                           # Prioritas lebih tinggi
---
# EC2NodeClass untuk konfigurasi node
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
  instanceStorePolicy: RAID0            # Gunakan instance store kalau tersedia

Karpenter memberikan keuntungan signifikan dibanding Cluster Autoscaler:

  • Provisioning lebih cepat: Karpenter langsung memanggil API cloud provider (misalnya EC2 CreateFleet) tanpa melalui abstraksi node group, sehingga node baru bisa tersedia dalam hitungan detik.
  • Pemilihan instance optimal: Karpenter mengevaluasi ratusan tipe instance dan memilih yang paling cost-effective untuk workload yang sedang pending. Ini termasuk otomatis memilih antara Spot dan On-Demand, serta antara arsitektur x86 dan ARM.
  • Konsolidasi node: Fitur ini secara otomatis mendeteksi node yang underutilized, memindahkan pod-nya ke node lain, dan mematikan node yang sudah kosong. Di sinilah penghematan nyata terjadi.

Menggabungkan HPA + VPA + Karpenter

Arsitektur autoscaling yang optimal menggunakan ketiga tools ini di layer yang berbeda:

  1. VPA (mode Off/Recommendation): Secara berkala menganalisis utilisasi dan memberikan rekomendasi right-sizing. Tim engineering review dan terapkan melalui CI/CD.
  2. HPA: Menambah/mengurangi replika pod berdasarkan traffic atau queue depth. Ini menangani variasi beban kerja harian.
  3. Karpenter: Menyediakan/menghapus node berdasarkan kebutuhan scheduling. Ketika HPA menambah pod dan scheduler nggak menemukan node dengan kapasitas cukup, Karpenter langsung provision node baru. Ketika pod berkurang, Karpenter konsolidasi dan hapus node yang nggak dipakai.

Alur kerjanya begini: spike traffic masuk → HPA menambah replika pod → scheduler butuh ruang → Karpenter provision node baru (spot kalau memungkinkan) → traffic turun → HPA kurangi replika → Karpenter konsolidasi pod dan matikan node kosong. Siklus ini berjalan otomatis 24/7. Anda cukup set up sekali, lalu biarkan sistem bekerja.

Memanfaatkan Spot Instance untuk Kubernetes

Spot instance (atau preemptible VM di GCP, spot VM di Azure) menawarkan diskon 70-90% dibanding harga on-demand. Di 2026, dengan mekanisme penanganan interruption yang semakin matang, spot instances jadi salah satu lever penghematan terbesar untuk workload Kubernetes. Kalau Anda belum pakai spot, honestly, Anda melewatkan potensi saving yang cukup besar.

Workload yang Cocok untuk Spot

  • Batch processing dan ETL: Job yang bisa di-retry tanpa side effect
  • CI/CD pipeline: Build dan test runner yang toleran terhadap restarts
  • ML training: Training job dengan checkpointing
  • Stateless microservices: API yang sudah didesain dengan graceful shutdown
  • Event-driven workload: Consumer yang memproses message queue

Workload yang TIDAK Boleh di Spot

  • Database dan stateful workload: Risiko data corruption saat interruption
  • Workload latency-critical: Service yang nggak boleh ada downtime sama sekali
  • Long-running job tanpa checkpointing: Akan kehilangan seluruh progress saat interrupted

Strategi Spot di Karpenter

# NodePool terpisah untuk workload spot-friendly
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: spot-workloads
spec:
  template:
    spec:
      requirements:
      - key: karpenter.sh/capacity-type
        operator: In
        values: ["spot"]               # Hanya spot instances
      - key: kubernetes.io/arch
        operator: In
        values: ["amd64", "arm64"]
      - key: karpenter.k8s.aws/instance-category
        operator: In
        values: ["c", "m", "r"]
      taints:
      - key: spot
        value: "true"
        effect: NoSchedule             # Hanya pod dengan tolerasi spot
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 30s
  limits:
    cpu: "500"
---
# Deployment dengan tolerasi spot dan spread topology
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
      terminationGracePeriodSeconds: 120  # Waktu graceful shutdown
      containers:
      - name: processor
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 30 && /app/drain.sh"]

Tips penting: selalu diversifikasi tipe instance saat menggunakan spot. Semakin banyak tipe instance yang Anda izinkan, semakin kecil kemungkinan semua instance Anda ter-interrupt bersamaan. Karpenter secara otomatis menangani ini dengan memilih dari pool instance yang luas — jadi biarkan dia bekerja dan jangan terlalu membatasi pilihan instance type-nya.

FinOps untuk Kubernetes: Visibilitas dan Akuntabilitas Biaya

Showback vs Chargeback

Tantangan terbesar FinOps di Kubernetes adalah bahwa container menambahkan layer abstraksi lagi di atas virtualisasi cloud. Satu node bisa menjalankan puluhan pod dari tim yang berbeda, membuat alokasi biaya jauh lebih kompleks dibanding VM tradisional.

Ada dua pendekatan untuk mengatasi ini:

  • Showback: Menampilkan informasi biaya ke setiap tim tanpa menagih mereka. Tujuannya menciptakan awareness — ketika engineer melihat berapa biaya service mereka, mereka secara alami mulai mengoptimasi. Ini pendekatan yang lebih mudah diimplementasi dan nggak memerlukan perubahan proses keuangan.
  • Chargeback: Benar-benar menagih biaya ke unit bisnis atau tim berdasarkan konsumsi aktual mereka. Lebih efektif untuk akuntabilitas, tapi memerlukan model alokasi yang akurat dan buy-in dari leadership.

Rekomendasi dari pengalaman: mulai dengan showback dulu. Banyak organisasi menemukan bahwa showback saja sudah cukup untuk mendorong pengurangan biaya yang signifikan. Migrasi ke chargeback ketika biaya cloud sudah menjadi bagian signifikan dari cost structure produk Anda.

Strategi Labeling untuk Alokasi Biaya

Label Kubernetes adalah mekanisme utama untuk mengalokasikan biaya ke pemiliknya. Tanpa label yang konsisten, tools cost monitoring apapun nggak akan bisa memberikan data yang berguna. Ini fondasi yang harus benar dari awal.

# Template namespace dengan label wajib
apiVersion: v1
kind: Namespace
metadata:
  name: payment-service
  labels:
    team: fintech-backend
    cost-center: cc-1234
    environment: production
    product: payment-gateway
---
# Kyverno policy untuk enforce labeling
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-cost-labels
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-labels
    match:
      any:
      - resources:
          kinds:
          - Deployment
          - StatefulSet
          - DaemonSet
    validate:
      message: "Wajib memiliki label: team, cost-center, environment"
      pattern:
        metadata:
          labels:
            team: "?*"
            cost-center: "?*"
            environment: "production|staging|development"

Empat label wajib minimum yang harus ada di setiap workload:

  1. team: Tim yang bertanggung jawab atas workload ini
  2. cost-center: Kode pusat biaya untuk integrasi dengan sistem keuangan
  3. environment: production, staging, atau development
  4. product/service: Nama produk atau layanan yang dilayani

Gunakan admission controller seperti Kyverno atau OPA Gatekeeper untuk menolak deployment yang nggak punya label wajib. Percayalah, ini jauh lebih efektif daripada mengandalkan dokumentasi atau proses review manual yang sering dilupakan.

Mengimplementasikan Kubecost dan OpenCost

Kubecost dan OpenCost adalah tools cost monitoring paling populer di ekosistem Kubernetes. OpenCost adalah proyek open-source CNCF (saat ini dalam tahap incubating) yang menyediakan engine cost allocation, sementara Kubecost adalah produk enterprise yang dibangun di atas engine OpenCost dengan fitur tambahan seperti multi-cluster view, cost forecasting, dan anomaly detection.

Instalasi OpenCost

# Instalasi OpenCost via Helm
helm repo add opencost https://opencost.github.io/opencost-helm-chart
helm repo update

# Install dengan konfigurasi dasar
helm install opencost opencost/opencost \
  --namespace opencost \
  --create-namespace \
  --set opencost.prometheus.internal.enabled=true

# Verifikasi instalasi
kubectl get pods -n opencost
kubectl port-forward -n opencost svc/opencost 9090:9090

Fitur menarik di 2025-2026: OpenCost sekarang mendukung mode "Promless" — bisa berjalan tanpa Prometheus untuk deployment yang lebih ringan. OpenCost juga sudah menyertakan MCP (Model Context Protocol) server yang memungkinkan AI agent mengakses data cost allocation melalui interface standar. Cukup keren untuk integrasi otomasi.

Instalasi Kubecost

# Instalasi Kubecost via Helm
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" \
  --set prometheus.nodeExporter.enabled=true \
  --set prometheus.serviceAccounts.nodeExporter.create=true

# Akses dashboard
kubectl port-forward -n kubecost svc/kubecost-cost-analyzer 9090:9090

Query Biaya per Namespace dengan kubectl-cost

# Install kubectl-cost plugin
kubectl krew install cost

# Lihat biaya per namespace (30 hari terakhir)
kubectl cost namespace --window 30d --show-cpu --show-memory --show-efficiency

# Output contoh:
# +-----------------+-------+--------+---------+----------+------------+
# | NAMESPACE       | CPU   | MEMORY | TOTAL   | CPU EFF  | MEM EFF    |
# +-----------------+-------+--------+---------+----------+------------+
# | production      | $245  | $189   | $434    | 67%      | 54%        |
# | staging         | $89   | $67    | $156    | 23%      | 18%        |
# | development     | $45   | $34    | $79     | 12%      | 9%         |
# | monitoring      | $34   | $45    | $79     | 78%      | 82%        |
# +-----------------+-------+--------+---------+----------+------------+

# Lihat biaya per deployment dalam namespace tertentu
kubectl cost deployment --namespace production --window 7d

# Identifikasi pod termahal
kubectl cost pod --window 7d --sort-by total --top 20

Dari contoh output di atas, terlihat bahwa namespace staging hanya memiliki efisiensi CPU 23% dan memori 18%. Ini kandidat utama untuk right-sizing — atau bahkan bisa dijadwalkan untuk shutdown di luar jam kerja (yang akan kita bahas selanjutnya).

Menghilangkan Waste: Membersihkan Resource Zombie

Klaster Kubernetes punya kecenderungan mengakumulasi "sampah" seiring waktu: resource yang sudah nggak dipakai tapi masih berjalan dan terus ditagih. Ini seperti langganan streaming yang lupa di-cancel — kecil per item, tapi kalau dijumlahkan bisa bikin kaget.

Identifikasi Resource Zombie

#!/bin/bash
# Script untuk mendeteksi resource zombie di Kubernetes

echo "=== RESOURCE ZOMBIE AUDIT ==="
echo ""

# 1. Pod dalam CrashLoopBackOff (tetap konsumsi resource tapi tidak produktif)
echo "--- Pod dalam CrashLoopBackOff ---"
kubectl get pods --all-namespaces --field-selector=status.phase!=Running \
  -o custom-columns="NAMESPACE:.metadata.namespace,NAME:.metadata.name,STATUS:.status.phase,RESTARTS:.status.containerStatuses[0].restartCount" \
  2>/dev/null | grep -i crash

echo ""

# 2. PVC yang tidak di-mount ke pod manapun
echo "--- PVC yang Tidak Digunakan ---"
ALL_PVC=$(kubectl get pvc --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}')
USED_PVC=$(kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{range .spec.volumes[*]}{.persistentVolumeClaim.claimName}{"\n"}{end}{end}' | sort -u)

for pvc in $ALL_PVC; do
  pvc_name=$(echo $pvc | cut -d'/' -f2)
  if ! echo "$USED_PVC" | grep -q "^${pvc_name}$"; then
    echo "  UNUSED: $pvc"
  fi
done

echo ""

# 3. Service bertipe LoadBalancer yang mungkin tidak diperlukan
echo "--- Service bertipe LoadBalancer (cek apakah masih diperlukan) ---"
kubectl get svc --all-namespaces \
  -o custom-columns="NAMESPACE:.metadata.namespace,NAME:.metadata.name,TYPE:.spec.type,EXTERNAL-IP:.status.loadBalancer.ingress[0].hostname" \
  | grep LoadBalancer

echo ""

# 4. Job dan CronJob yang sudah selesai tapi tidak di-cleanup
echo "--- Completed Jobs (kandidat cleanup) ---"
kubectl get jobs --all-namespaces --field-selector=status.successful=1 \
  -o custom-columns="NAMESPACE:.metadata.namespace,NAME:.metadata.name,COMPLETIONS:.status.succeeded,AGE:.metadata.creationTimestamp"

Otomatisasi Cleanup dengan CronJob

# CronJob untuk membersihkan completed jobs yang lebih dari 24 jam
apiVersion: batch/v1
kind: CronJob
metadata:
  name: cleanup-completed-jobs
  namespace: kube-system
spec:
  schedule: "0 2 * * *"  # Setiap hari jam 2 pagi
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: job-cleaner
          containers:
          - name: cleaner
            image: bitnami/kubectl:latest
            command:
            - /bin/sh
            - -c
            - |
              # Hapus completed jobs yang lebih dari 24 jam
              kubectl get jobs --all-namespaces \
                --field-selector=status.successful=1 \
                -o json | \
              jq -r '.items[] |
                select(
                  (now - (.status.completionTime | fromdateiso8601)) > 86400
                ) |
                "\(.metadata.namespace) \(.metadata.name)"' | \
              while read ns name; do
                echo "Deleting job $ns/$name"
                kubectl delete job -n "$ns" "$name"
              done
          restartPolicy: OnFailure

Menjadwalkan Shutdown Lingkungan Non-Produksi

Ini salah satu quick win terbesar yang sering diabaikan. Coba pikir: kalau tim Anda bekerja 8 jam sehari, 5 hari seminggu, maka lingkungan dev/staging aktif hanya 24% dari waktu total. Artinya, Anda membayar 76% untuk resource yang nggak ada yang pakai. Tujuh puluh enam persen!

# Menggunakan KEDA ScaledObject untuk scale-to-zero di luar jam kerja
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: staging-api-scaler
  namespace: staging
spec:
  scaleTargetRef:
    name: staging-api
  minReplicaCount: 0           # Bisa scale ke 0
  maxReplicaCount: 5
  triggers:
  - type: cron
    metadata:
      timezone: Asia/Jakarta
      start: "0 8 * * 1-5"    # Scale up: Senin-Jumat jam 8 pagi
      end: "0 20 * * 1-5"     # Scale down: Senin-Jumat jam 8 malam
      desiredReplicas: "3"

Menurut data industri, penerapan jadwal shutdown untuk lingkungan non-produksi bisa mengurangi waste sebesar 20-25% dalam 90 hari pertama. Ini penghematan yang langsung terasa tanpa perlu perubahan arsitektur apapun. Tinggal pasang, langsung hemat.

Governance dan Policy Automation

Optimasi biaya bukan soal satu kali cleanup lalu selesai — ini harus jadi bagian dari proses engineering sehari-hari. Policy automation memastikan bahwa konfigurasi yang boros nggak pernah sampai ke production.

Contoh Policy dengan OPA Gatekeeper

# ConstraintTemplate: Melarang resource request yang terlalu besar
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8smaxresources
spec:
  crd:
    spec:
      names:
        kind: K8sMaxResources
      validation:
        openAPIV3Schema:
          type: object
          properties:
            maxCpuRequest:
              type: string
            maxMemoryRequest:
              type: string
  targets:
  - target: admission.k8s.gatekeeper.sh
    rego: |
      package k8smaxresources

      violation[{"msg": msg}] {
        container := input.review.object.spec.containers[_]
        cpu_request := container.resources.requests.cpu
        max_cpu := input.parameters.maxCpuRequest
        cpu_request > max_cpu
        msg := sprintf(
          "Container %v meminta CPU %v, melebihi batas %v. Ajukan exception jika memang diperlukan.",
          [container.name, cpu_request, max_cpu]
        )
      }
---
# Constraint: Terapkan batas maksimum
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sMaxResources
metadata:
  name: max-resource-requests
spec:
  match:
    kinds:
    - apiGroups: ["apps"]
      kinds: ["Deployment", "StatefulSet"]
    namespaces: ["production", "staging"]
  parameters:
    maxCpuRequest: "4"
    maxMemoryRequest: "8Gi"

Dengan policy seperti ini, setiap deployment yang meminta CPU lebih dari 4 core atau memori lebih dari 8Gi akan ditolak secara otomatis. Tim harus mengajukan exception dengan justifikasi yang jelas. Ini mencegah overprovisioning bahkan sebelum resource dibuat — jauh lebih baik daripada membersihkan setelahnya.

Mengintegrasikan Biaya ke CI/CD Pipeline

Salah satu praktik terbaik yang diadopsi oleh organisasi FinOps yang matang: menampilkan estimasi biaya langsung di pull request. Ketika engineer mengubah konfigurasi deployment, mereka langsung melihat dampak biayanya sebelum merge. Transparan dan efektif.

# GitHub Actions: Estimasi biaya perubahan Kubernetes manifest
name: Cost Estimation
on:
  pull_request:
    paths:
    - "k8s/**"
    - "helm/**"

jobs:
  estimate-cost:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: Calculate Resource Changes
      id: calc
      run: |
        # Bandingkan resource requests antara base dan PR branch
        git diff origin/main -- k8s/ helm/ | \
        grep -E "^\+.*cpu:|^\+.*memory:" | \
        while read line; do
          echo "Changed: $line"
        done

        # Hitung estimasi biaya bulanan berdasarkan on-demand pricing
        echo "monthly_delta=+$47.50" >> $GITHUB_OUTPUT

    - name: Comment PR with Cost Impact
      uses: actions/github-script@v7
      with:
        script: |
          const delta = "${{ steps.calc.outputs.monthly_delta }}";
          github.rest.issues.createComment({
            issue_number: context.issue.number,
            owner: context.repo.owner,
            repo: context.repo.repo,
            body: `## Estimasi Dampak Biaya

Perubahan ini diperkirakan akan menambah **${delta}/bulan** pada biaya Kubernetes.

Detail: perubahan resource requests pada deployment.`
          });

Checklist Implementasi Optimasi Biaya Kubernetes

Oke, banyak banget yang sudah kita bahas. Untuk memudahkan implementasi, berikut checklist yang bisa Anda gunakan sebagai panduan. Urutannya disusun berdasarkan dampak dan kemudahan implementasi — mulai dari yang paling gampang sampai optimasi jangka panjang:

Minggu 1-2: Quick Wins

  • Install OpenCost atau Kubecost untuk visibilitas biaya dasar
  • Identifikasi dan hapus resource zombie (PV orphan, completed jobs, CrashLoopBackOff pods)
  • Jadwalkan shutdown lingkungan non-produksi di luar jam kerja
  • Review dan konsolidasi Service bertipe LoadBalancer yang redundan

Minggu 3-4: Right-Sizing

  • Kumpulkan data utilisasi selama minimal 7 hari
  • Identifikasi pod dengan utilisasi CPU <30% atau memori <40%
  • Terapkan rekomendasi VPA (mulai dari mode Off/rekomendasi)
  • Migrasi workload ke instance generasi terbaru (Graviton/ARM jika memungkinkan)

Bulan 2: Autoscaling

  • Konfigurasi HPA dengan scale-down behavior yang agresif
  • Implementasi Karpenter (AWS) atau node autoscaler yang sesuai
  • Setup KEDA untuk workload event-driven
  • Mulai adopsi spot instances untuk workload yang toleran terhadap interruption

Bulan 3+: FinOps Maturity

  • Terapkan kebijakan labeling wajib dengan admission controller
  • Bangun dashboard showback per tim dan per produk
  • Integrasikan estimasi biaya ke CI/CD pipeline
  • Implementasi policy automation dengan OPA Gatekeeper atau Kyverno
  • Evaluasi transisi dari showback ke chargeback

Kesimpulan

Optimasi biaya Kubernetes memang bukan proyek satu kali — ini praktik berkelanjutan yang harus jadi bagian dari budaya engineering organisasi Anda. Tapi kabar baiknya, hasilnya sepadan. Dengan kombinasi right-sizing pod yang presisi, autoscaling multi-layer (HPA + VPA + Karpenter), pemanfaatan spot instances, dan implementasi FinOps yang matang, organisasi secara konsisten mencapai penghematan 40-70% dari total biaya Kubernetes mereka.

Kunci suksesnya ada pada tiga hal: visibilitas (Anda nggak bisa mengoptimasi apa yang nggak bisa diukur), otomasi (policy enforcement dan autoscaling menghilangkan ketergantungan pada tindakan manual), dan akuntabilitas (setiap tim harus melihat dan bertanggung jawab atas biaya cloud mereka).

Mulailah dari quick wins — install cost monitoring, bersihkan resource zombie, dan jadwalkan shutdown non-production. Kemudian secara bertahap tingkatkan maturitas FinOps Anda. Ingat, pengurangan request 5-15% per pod mungkin terdengar kecil, tapi efek kompounding di seluruh klaster bisa menghasilkan penghematan ratusan ribu dolar per tahun.

Di era di mana biaya cloud terus meningkat dan setiap dolar harus dipertanggungjawabkan, kemampuan mengelola biaya Kubernetes secara efektif bukan lagi nice-to-have — ini keharusan.

Tentang Penulis Editorial Team

Our team of expert writers and editors.