Kubernetes untuk Backend Engineer
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 produksi | Yang dilakukan Docker | Yang dilakukan Kubernetes |
|---|---|---|
| Banyak container dari banyak aplikasi | Anda mengelola sendiri (docker run satu-satu) | Mengatur semuanya sebagai workload |
| Container crash / berhenti | Tetap mati sampai Anda perbaiki | Otomatis di-restart dan diganti |
| Trafik tinggi | Anda menambah instance manual | Auto scaling otomatis |
| Load balancing antar container | Tidak ada (perlu solusi manual) | Service mendistribusikan trafik |
| Container tersebar di banyak mesin | Tidak tahu apa-apa | Menjadwalkan ke node yang tersedia |
node mati mendadak | Container ikut hilang | Container 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 │ │
│ └───────────────┘ │
└─────────────────────┘
| Objek | Analogi | Fungsi inti |
|---|---|---|
Cluster | Seluruh pabrik | Kumpulan node + control plane yang dikelola sebagai satu unit |
Node | Satu mesin (fisik/VM) | Tempat pod berjalan; ada worker node dan control plane |
Pod | Satu peti kemas berisi 1+ kontainer | Unit terkecil yang dijadwalkan; punya IP sendiri |
Deployment | "Cetak biru + pengawas" | Menjaga jumlah replica & melakukan update tanpa downtime |
Service | Resepsionis & papan nama internal | Load balancer abstrak + DNS internal yang stabil |
Ingress | Gerbang utama gedung | Pintu 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, dipakaikubectl),scheduler(memutuskan pod ditaruh di mana), dancontroller manager(menjaga kondisi agar sesuai deklarasi). - Worker node — otot cluster. Menjalankan
kubelet(agen yang berkomunikasi dengan control plane) dancontainer 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:
| Field | Peran |
|---|---|
apiVersion: apps/v1 | API group + versi; untuk Deployment gunakan apps/v1 |
kind: Deployment | Jenis objek yang dibuat |
spec.replicas | Jumlah pod yang diinginkan |
spec.selector.matchLabels | Cara Deployment menemukan dan mengelola pod-nya |
spec.template | Cetakan pod; wajib berisi labels yang cocok dengan selector |
containers[].image | Image + tag yang akan dijalankan |
containers[].resources | requests (jaminan) dan limits (batas) CPU/memori |
Aturan emas:
selector.matchLabelsharus menyertakan label yang sama dengantemplate.metadata.labels. Jika tidak,kubectl applymenolak 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
| Parameter | Arti | Efek |
|---|---|---|
maxSurge: 1 | Paling banyak 1 pod tambahan melebihi jumlah yang diinginkan | Menjamin ada kapasitas ekstra saat update |
maxUnavailable: 0 | Tidak ada pod lama yang boleh nonaktif pada saat yang sama | Menjamin 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:
| Tipe | Jangkauan | Kapan dipakai | Bisa dari internet? |
|---|---|---|---|
ClusterIP | Internal cluster | Komunikasi antar pod / internal | Tidak |
NodePort | IP setiap node + port tertentu | Uji coba kecil / debugging | Ya, lewat IP-node:port |
LoadBalancer | Cloud (AWS/GCP/Azure) | Ekspos langsung dengan load balancer cloud | Ya, 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:
| Objek | Isi | Aman? |
|---|---|---|
ConfigMap | Konfigurasi non-rahasia (URL, profil aktif, threshold) | Ya, tapi bukan rahasia |
Secret | Data 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
Secrethanya 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
envFromdenganconfigMapRef— memasukkan semua pasangankey-valueConfigMap 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:
| Probe | Pertanyaan | Jika 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,
readinessboleh gagal (trafik dialihkan, aplikasi pulih sendiri). Kalaulivenessikut 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 mengikutimemory limitpod — kalau tidak, JVM memakai memori host dan pod kenaOOMKilleddi mesin yang sehat.
requests dan limits adalah syarat wajib di pod produksi:
| Field | Makna | Efek jika diabaikan |
|---|---|---|
requests.cpu / requests.memory | Jaminan minimum yang disediakan scheduler | Pod bisa dijadwalkan ke node yang kelebihan beban |
limits.cpu / limits.memory | Batas maksimum yang boleh dipakai | Satu 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:
- Build image dengan Dockerfile multi-stage (JDK untuk compile, JRE untuk runtime) seperti di artikel Dockerizing Java.
- Push image ke registry dengan tag versi yang unik (misal
1.4.2atau SHA commit), bukanlatest. - Tulis dan terapkan YAML:
kubectl apply -f deployment.yaml -f service.yaml -f ingress.yaml. - Tunggu rollout:
kubectl rollout status deployment/order-service. - 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:
| Jebakan | Mengapa berbahaya | Solusi |
|---|---|---|
Tag image latest | Tidak bisa dilacak; deploy bisa memakai image berbeda dari yang diuji | Selalu pakai tag versi/SHA yang immutable |
kubectl exec untuk perbaikan manual | Perbaikan tidak terdokumentasi dan hilang saat pod diganti | Perbaiki di image/kode, biarkan rollout yang menyebar |
| Config drift antar node | Tiap pod berperilaku beda, sulit di-debug | Pakai GitOps (Argo CD/Flux): YAML di git, diterapkan otomatis |
| Secret tertulis di YAML yang di-commit | Base64 bukan enkripsi; bocor ke git history | External secret manager + jangan pernah commit secret |
| Aplikasi stateful di-scaled asal-asalan | Data di pod hilang saat pod diganti | Pisahkan 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
- Kubernetes Concepts — pintu masuk dokumentasi resmi
- Overview of Kubernetes — gambaran arsitektur cluster
- Deployments — referensi lengkap Deployment & rolling update
- Services — tipe Service dan cara kerjanya
- Secrets — cara aman menyimpan rahasia
- Sebelumnya: Dockerizing Java Applications — membangun image yang siap dideploy ke cluster
- Selanjutnya: CI/CD dengan GitHub Actions — mengotomasi build, push image, dan deploy