Kubernetes untuk Backend Engineer

14 min readAdvanced
KubernetesContainerOrkestrasiDevOps

Container mengubah cara kita mengemas aplikasi, tetapi menjalankan container saja tidak cukup untuk produksi. Bayangkan Anda punya lima mikroservis, masing-masing tiga replika, yang harus di-restart saat crash, di-scale saat trafik naik, dan di-sebar ke beberapa mesin. Mengelola itu semua manual adalah resep bencana. Di sinilah Kubernetes (biasa disingkat K8s) berperan: sebuah orkestrator — sistem yang mengatur siklus hidup container secara otomatis.

Artikel ini untuk backend engineer yang sudah nyaman dengan Docker dan ingin memahami cara aplikasi (khususnya Spring Boot) di-deploy ke cluster Kubernetes: dari konsep Pod, Deployment, Service, hingga Ingress, plus praktik yang sering membuat orang tersandung.

Mengapa Perlu Orkestrasi Container

Docker menjalankan satu container dengan sangat baik. Tetapi Docker tidak menangani hal-hal berikut:

Masalah di produksiYang dilakukan DockerYang dilakukan Kubernetes
Banyak container dari banyak aplikasiAnda mengelola sendiri (docker run satu-satu)Mengatur semuanya sebagai workload
Container crash / berhentiTetap mati sampai Anda perbaikiOtomatis di-restart dan diganti
Trafik tinggiAnda menambah instance manualAuto scaling otomatis
Load balancing antar containerTidak ada (perlu solusi manual)Service mendistribusikan trafik
Container tersebar di banyak mesinTidak tahu apa-apaMenjadwalkan ke node yang tersedia
node mati mendadakContainer ikut hilangContainer dipindahkan ke node lain (failover)

Analogi sederhana: Docker itu seperti memiliki satu mobil — Anda tahu cara mengendarainya dan merawatnya. Kubernetes seperti memiliki sebuah pabrik armada truk: ada yang mengatur jadwal, memperbaiki truk yang mogok, mengalihkan rute, dan menambah truk saat pengiriman membludak. Tugas Anda bukan lagi menyetir, melainkan mendeklarasikan berapa truk yang Anda mau — sisanya dikerjakan sistem.

Kubernetes disebut declarative: Anda menulis keadaan yang diinginkan (ingin 3 pod berjalan, image app:1.2.0, port 8080), lalu control plane bekerja keras mewujudkannya dan menjaganya tetap begitu. Anda tidak memberi perintah "jalankan ini sekarang", melainkan "ini kondisi yang saya mau — jaga terus".

Konsep Inti Kubernetes

Sebelum menulis YAML, pahami dulu peta objek-objek utama Kubernetes dan hubungannya. Konsep inti ini adalah fondasi dari semua hal lain.

                   ┌───────────────────────────────────────────┐
                   │              CLUSTER K8s                  │
                   │                                           │
   ┌───────────────┴───────────────┐     ┌─────────────────────┤
   │         Control Plane         │     │     Worker Node 1   │
   │  (pengambil keputusan)        │     │   ┌───────────────┐ │
   │   API Server │ Scheduler      │     │   │  Pod A        │ │
   │   Controller Manager          │     │   │  Pod B        │ │
   └───────────────┬───────────────┘     │   └───────────────┘ │
                   │                     └─────────────────────┤
                   │   mengatur          ┌─────────────────────┤
                   └──────────────────▶ │     Worker Node 2   │
                                        │   ┌───────────────┐ │
                                        │   │  Pod A        │ │
                                        │   │  Pod C        │ │
                                        │   └───────────────┘ │
                                        └─────────────────────┘
ObjekAnalogiFungsi inti
ClusterSeluruh pabrikKumpulan node + control plane yang dikelola sebagai satu unit
NodeSatu mesin (fisik/VM)Tempat pod berjalan; ada worker node dan control plane
PodSatu peti kemas berisi 1+ kontainerUnit terkecil yang dijadwalkan; punya IP sendiri
Deployment"Cetak biru + pengawas"Menjaga jumlah replica & melakukan update tanpa downtime
ServiceResepsionis & papan nama internalLoad balancer abstrak + DNS internal yang stabil
IngressGerbang utama gedungPintu masuk trafik dari luar ke dalam cluster

Hubungannya berlapis: Deployment mengelola sekelompok Pod, Pod berjalan di atas Node, Service mengarahkan trafik ke Pod-pod milik Deployment, dan Ingress menjadi satu-satunya jalan masuk dari internet. Satu Service dan satu Ingress tidak terikat pada satu Deployment — ia menyeleksi Pod berdasarkan label.

Node: Mesin di Dalam Cluster

Node adalah mesin tempat workload berjalan. Ada dua peran besar:

  • Control plane node (master) — otak cluster. Menjalankan API server (satu-satunya gerbang interaksi, dipakai kubectl), scheduler (memutuskan pod ditaruh di mana), dan controller manager (menjaga kondisi agar sesuai deklarasi).
  • Worker node — otot cluster. Menjalankan kubelet (agen yang berkomunikasi dengan control plane) dan container runtime (misal containerd) yang benar-benar menjalankan container.

Pod: Unit Terkecil yang Dieksekusi

Pod adalah unit komputasi terkecil di Kubernetes. Berbeda dengan bayangan umum, Anda tidak pernah me-deploy container langsung — selalu melalui pod. Satu pod bisa berisi satu container (kasus paling umum) atau beberapa container yang saling berbagi network dan storage (pola sidecar, misal container aplikasi + container envoy untuk proxy).

Deployment: Mengelola Pod

Deployment mendeklarasikan: image apa, berapa jumlah pod, dan bagaimana cara update. Ia akan selalu mengejar kondisi tersebut — jika satu pod mati, deployment membuat pod baru; jika Anda menaikkan replicas dari 3 ke 5, ia membuat dua pod lagi.

Service: Abstraksi Jaringan

Pod bersifat ephemeral (sementara) dan IP-nya berubah setiap kali diganti. Service memberi nama dan IP yang stabil sehingga klien tidak perlu tahu pod mana yang melayani. Ia juga berperan sebagai load balancer internal.

Ingress: Jalan Masuk dari Luar

Service dengan tipe tertentu masih butuh pintu masuk dari internet. Ingress menyediakan routing berbasis host dan path, plus TLS — di baliknya bekerja Ingress Controller (misalnya NGINX) yang menerjemahkan aturan Ingress menjadi konfigurasi reverse proxy nyata.

Pod & Kontainer

Pod adalah "kandang" bagi container. Di dalam satu pod:

  • Semua container berbagi IP, network namespace, dan (bila didefinisikan) volume yang sama — mereka saling memanggil lewat localhost.
  • Satu container utama menjalankan aplikasi (misalnya JAR Spring Boot di atas JRE), container lain bertindak sebagai sidecar pendukung (log shipper, proxy, atau pemuat konfigurasi).

Pod itu ephemeral: ketika mati atau dihapus, pod tidak dihidupkan kembali — yang membuat penggantinya adalah controller (seperti Deployment) dengan pod baru, IP baru. Itulah mengapa Anda tidak boleh bergantung pada alamat IP pod secara langsung.

Deployment: Mendeklarasikan Keadaan

Contoh Deployment lengkap untuk aplikasi Spring Boot:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: production
  labels:
    app: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: registry.example.com/order-service:1.4.2
          ports:
            - containerPort: 8080
          env:
            - name: SPRING_PROFILES_ACTIVE
              value: "production"
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: password
          resources:
            requests:
              cpu: 250m
              memory: 512Mi
            limits:
              cpu: 1000m
              memory: 1Gi
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 20
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 40
            periodSeconds: 15

Bagian-bagian penting:

FieldPeran
apiVersion: apps/v1API group + versi; untuk Deployment gunakan apps/v1
kind: DeploymentJenis objek yang dibuat
spec.replicasJumlah pod yang diinginkan
spec.selector.matchLabelsCara Deployment menemukan dan mengelola pod-nya
spec.templateCetakan pod; wajib berisi labels yang cocok dengan selector
containers[].imageImage + tag yang akan dijalankan
containers[].resourcesrequests (jaminan) dan limits (batas) CPU/memori

Aturan emas: selector.matchLabels harus menyertakan label yang sama dengan template.metadata.labels. Jika tidak, kubectl apply menolak karena Deployment tidak tahu pod mana yang harus diawasi.

Rolling Update: Deploy Tanpa Downtime

Strategi update default Kubernetes adalah RollingUpdate: pod lama diganti secara bertahap sehingga layanan tidak pernah kehilangan semua instance sekaligus.

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
ParameterArtiEfek
maxSurge: 1Paling banyak 1 pod tambahan melebihi jumlah yang diinginkanMenjamin ada kapasitas ekstra saat update
maxUnavailable: 0Tidak ada pod lama yang boleh nonaktif pada saat yang samaMenjamin ketersediaan penuh 100%

Dengan maxSurge: 1 dan maxUnavailable: 0, urutannya kira-kira: pod baru (versi baru) dibuat → setelah ready, satu pod lama dihentikan → pod baru berikutnya dibuat, dan seterusnya. Anda juga bisa menetapkan minReadySeconds agar pod baru diberi waktu stabil sebelum dianggap sukses.

Service: Menghubungkan Trafik

Service menyeleksi pod berdasarkan label dan menjadi ujung nama yang stabil. Ini contoh Service untuk Deployment di atas:

apiVersion: v1
kind: Service
metadata:
  name: order-service
  namespace: production
spec:
  selector:
    app: order-service
  ports:
    - port: 80
      targetPort: 8080
      protocol: TCP
  type: ClusterIP

selector mencocokkan label pod. Request yang masuk ke port 80 akan diteruskan ke port targetPort (8080) pada salah satu pod yang berlabel app: order-service — itulah load balancing internal. Setiap Service mendapat nama DNS internal (order-service.production.svc.cluster.local) sehingga pod lain bisa memanggil tanpa tahu IP.

Tiga tipe Service yang wajib dikenal:

TipeJangkauanKapan dipakaiBisa dari internet?
ClusterIPInternal clusterKomunikasi antar pod / internalTidak
NodePortIP setiap node + port tertentuUji coba kecil / debuggingYa, lewat IP-node:port
LoadBalancerCloud (AWS/GCP/Azure)Ekspos langsung dengan load balancer cloudYa, lewat IP/domain ELB

Di produksi, NodePort dan LoadBalancer jarang diekspos langsung. Pola standar: Service tipe ClusterIP + Ingress sebagai satu-satunya jalan masuk.

Ingress: Pintu Masuk Tunggal

Ingress memetakan host/path ke Service di dalam cluster. Contoh: semua trafik api.example.com dikirim ke service order-service, sementara example.com/ dikirim ke service web-frontend.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-ingress
  namespace: production
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.example.com
      secretName: api-tls-cert
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: order-service
                port:
                  number: 80

Ingress sendiri bukan proxy — ia hanya aturan. Yang menjalankan aturan itu adalah Ingress Controller (misalnya NGINX Ingress Controller): sebuah aplikasi di dalam cluster yang membaca semua objek Ingress dan mengonfigurasi reverse proxy-nya. Tanpa controller, objek Ingress hanya diam. TLS ditangani dengan membuat Secret berisi sertifikat, lalu menyebutnya di spec.tls.

ConfigMap & Secret: Memisahkan Konfigurasi

Tidak bijak meng-hardcode konfigurasi atau kredensial di image. Kubernetes memisahkan keduanya menjadi dua objek:

ObjekIsiAman?
ConfigMapKonfigurasi non-rahasia (URL, profil aktif, threshold)Ya, tapi bukan rahasia
SecretData rahasia (password, API key, JWT signing key)Hanya disimpan obfuscated

Contoh ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: production
data:
  SPRING_PROFILES_ACTIVE: "production"
  KAFKA_BOOTSTRAP: "kafka-broker:9092"
  MAX_RETRY: "3"

Contoh Secret:

apiVersion: v1
kind: Secret
metadata:
  name: db-secret
  namespace: production
type: Opaque
data:
  password: U3VwZXJTZWNyZXQh

Peringatan penting: nilai Secret hanya di-base64, bukan dienkripsi. Base64 itu encoding, bukan encryption — siapa pun yang bisa membaca YAML-nya bisa membaliknya. Untuk rahasia sungguhan gunakan external secret manager (Vault, AWS Secrets Manager, Sealed Secrets) yang diintegrasikan dengan operator.

Menyuntikkan ke dalam pod ada dua cara:

spec:
  containers:
    - name: order-service
      image: registry.example.com/order-service:1.4.2
      envFrom:
        - configMapRef:
            name: app-config
      env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: password
  • envFrom dengan configMapRef — memasukkan semua pasangan key-value ConfigMap sebagai environment variable.
  • valueFrom.secretKeyRef — mengambil satu nilai dari Secret (yang lebih eksplisit dan mudah diaudit).

Setiap kali nilai Secret/ConfigMap berubah, pod lama tidak otomatis mendapatkan nilainya — Anda perlu rollout baru (misalnya kubectl rollout restart deployment/order-service).

Health Check: Liveness vs Readiness

Kubernetes mengecek kesehatan aplikasi lewat probe. Ada dua yang paling penting, dan keduanya punya tujuan berbeda:

ProbePertanyaanJika gagal
livenessProbe"Apakah proses masih hidup?"Pod di-restart oleh kubelet
readinessProbe"Apakah sudah siap menerima trafik?"Pod dikeluarkan dari Service, tapi tidak di-restart

Pelajaran terpenting soal probe: jangan gabungkan keduanya di endpoint yang sama. Saat aplikasi lambat karena database sibuk, readiness boleh gagal (trafik dialihkan, aplikasi pulih sendiri). Kalau liveness ikut gagal, Kubernetes malah me-restart pod — Anda membayar biaya cold start di atas masalah yang sudah ada.

Untuk Spring Boot, aktifkan Actuator dan kelompokkan health-nya (spring-boot-actuator) lalu gunakan endpoint yang sesuai:

readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  initialDelaySeconds: 20
  periodSeconds: 10
livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  initialDelaySeconds: 40
  periodSeconds: 15

initialDelaySeconds memberi waktu aplikasi booting sebelum dicek — Spring Boot yang sudah men-download konfigurasi jauh dari ponsel tidak akan langsung "sehat". periodSeconds mengatur interval pengecekan. Sebagai tambahan, aktifkan spring-boot-starter-actuator dengan property management.endpoint.health.probes.enabled=true agar kedua endpoint di atas tersedia.

Scaling: HorizontalPodAutoscaler

Scaling manual (mengubah replicas lewat kubectl scale) hanya untuk darurat. Untuk produksi, biarkan Kubernetes menghitung: HorizontalPodAutoscaler (HPA) menambah/mengurangi pod berdasarkan metrik seperti penggunaan CPU, memori, atau metrik kustom dari Prometheus.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

Aturan mainnya: target 70% rata-rata pemakaian CPU. Jika rata-rata semua pod menyentuh 70%, HPA menambah pod; jika turun dan stabil di bawah target, HPA menguranginya. Jangan pernah pakai HPA tanpa resources.requests — tanpa requests, HPA tidak punya basis untuk menghitung utililasi.

JVM penting untuk diperhatikan: di dalam container, JVM tidak otomatis menghormati cgroup. Pastikan -XX:MaxRAMPercentage=75 (lihat artikel Dockerizing) agar heap mengikuti memory limit pod — kalau tidak, JVM memakai memori host dan pod kena OOMKilled di mesin yang sehat.

requests dan limits adalah syarat wajib di pod produksi:

FieldMaknaEfek jika diabaikan
requests.cpu / requests.memoryJaminan minimum yang disediakan schedulerPod bisa dijadwalkan ke node yang kelebihan beban
limits.cpu / limits.memoryBatas maksimum yang boleh dipakaiSatu pod bisa menghabiskan seluruh node (memory leak = bencana)

Alur Deploy Aplikasi Spring Boot ke Cluster

Proses dari kode sampai melayani trafik bisa digambarkan sebagai pipa:

┌─────────┐   ┌───────────────┐   ┌──────────────────┐   ┌────────────┐
│ Java    │   │ Docker build  │   │ Push image       │   │ kubectl    │
│ source  │──▶│ (multi-stage) │──▶│ ke registry      │──▶│ apply      │
└─────────┘   └───────────────┘   │ (GHCR/DockerHub) │   │ deployment │
                                  └──────────────────┘   └────────────┘
                                      │                        │
                                      ▼                        ▼
                            ┌──────────────────┐   ┌────────────────────┐
                            │ Deployment       │──▶│ Service (ClusterIP)│
                            │ memakai image    │   │ menyeleksi pod     │
                            │ dari registry    │   └─────────┬──────────┘
                            └──────────────────┘             ▼
                                                 ┌────────────────────┐
                                                 │ Ingress → Ingress  │
                                                 │ Controller (Nginx) │
                                                 └────────────────────┘

Langkah praktisnya:

  1. Build image dengan Dockerfile multi-stage (JDK untuk compile, JRE untuk runtime) seperti di artikel Dockerizing Java.
  2. Push image ke registry dengan tag versi yang unik (misal 1.4.2 atau SHA commit), bukan latest.
  3. Tulis dan terapkan YAML: kubectl apply -f deployment.yaml -f service.yaml -f ingress.yaml.
  4. Tunggu rollout: kubectl rollout status deployment/order-service.
  5. Verifikasi: kubectl get pods, lalu uji endpoint publik lewat Ingress.

Di lingkungan nyata, langkah 1–3 dijalankan oleh CI/CD pipeline — bukan oleh manusia.

Jebakan & Praktik Baik

Praktik berikut mencegah insiden yang paling sering terjadi:

JebakanMengapa berbahayaSolusi
Tag image latestTidak bisa dilacak; deploy bisa memakai image berbeda dari yang diujiSelalu pakai tag versi/SHA yang immutable
kubectl exec untuk perbaikan manualPerbaikan tidak terdokumentasi dan hilang saat pod digantiPerbaiki di image/kode, biarkan rollout yang menyebar
Config drift antar nodeTiap pod berperilaku beda, sulit di-debugPakai GitOps (Argo CD/Flux): YAML di git, diterapkan otomatis
Secret tertulis di YAML yang di-commitBase64 bukan enkripsi; bocor ke git historyExternal secret manager + jangan pernah commit secret
Aplikasi stateful di-scaled asal-asalanData di pod hilang saat pod digantiPisahkan state ke database eksternal, buat aplikasi stateless

Satu prinsip menyatukan semuanya: pod adalah sapi, bukan hewan peliharaan. Pod boleh (dan akan) mati kapan saja — desain aplikasi yang bisa lahir ulang tanpa kehilangan data dan tanpa butuh perbaikan manual. State (database, cache) selalu dipisah ke layanan eksternal.

Kapan TIDAK Perlu Kubernetes

Kubernetes itu hebat, tetapi bukan jawaban untuk semua masalah. Jika situasinya:

  • Aplikasi kecil dengan satu instance dan tidak akan bertumbuh cepat,
  • Tim kecil tanpa dedicated ops,
  • Budget kecil dan trafik masih bisa ditangani satu server,

...maka Kubernetes justru menambah biaya operasional: cluster butuh dirawat, control plane perlu di-upgrade, dan debugging jadi lebih rumit. Mulai dari container saja dengan docker compose, dan pindah ke Kubernetes ketika Anda benar-benar butuh scaling otomatis, failover, dan multi-node — yaitu ketika biaya downtime sudah melebihi biaya kompleksitas.

Ringkasan

Kubernetes mengubah cara berpikir Anda dari "menjalankan container" menjadi "mendeklarasikan keadaan". Pod adalah unit terkecil yang dieksekusi, Deployment menjaga jumlah pod dan melakukan rolling update tanpa downtime, Service memberi nama stabil dan load balancing internal, sedangkan Ingress menjadi pintu masuk tunggal dari internet. Konfigurasi dipisahkan lewat ConfigMap (non-rahasia) dan Secret (rahasia — ingat, base64 bukan enkripsi). Health check dibedakan tegas: liveness untuk restart, readiness untuk trafik. Scaling otomatis ditangani HorizontalPodAutoscaler dengan syarat resources.requests didefinisikan. Dan yang terpenting: perlakukan pod sebagai sapi, pisahkan state, hindari latest dan kubectl exec — lalu gunakan Kubernetes hanya ketika kompleksitasnya benar-benar terbayar.

Lanjut membaca

← Back to technical articles