Kubernetes Network Model #
Ketika kita merancang dan mengoperasikan sistem terdistribusi skala besar di Kubernetes, salah satu tantangan terbesar yang sering kita hadapi adalah bagaimana komponen-komponen aplikasi saling terhubung secara aman, andal, dan cepat. Di dalam kluster yang dinamis, ratusan hingga ribuan kontainer dapat dibuat, dimatikan, dan dipindahkan dalam hitungan detik.
Untuk mengatasi kompleksitas komunikasi ini, Kubernetes memilih pendekatan jaringan (networking) yang sangat spesifik, terstruktur, dan berbeda secara fundamental dari model jaringan Docker standar (docker-scoped networking).
Sebelum kita melangkah jauh membahas abstraksi yang lebih tinggi seperti Service, Ingress, atau NetworkPolicy, kita wajib memahami konsep dasar yang menjadi fondasi seluruh sistem jaringan di Kubernetes: Kubernetes Network Model. Model jaringan ini bukanlah sebuah saran implementasi, melainkan sebuah kontrak arsitektur absolut yang harus dipenuhi oleh setiap plugin jaringan yang berjalan di atas kluster Kubernetes kita.
Empat Aturan Fundamental (The Four Immutable Rules) #
Model jaringan Kubernetes dibangun di atas empat aturan utama yang tidak dapat dinegosiasikan. Setiap implementasi jaringan (baik menggunakan AWS VPC CNI, Calico, Cilium, maupun Flannel) wajib mematuhi aturan ini untuk memastikan seluruh sistem Kubernetes dapat berinteraksi dengan normal:
flowchart TD
Contract["KUBERNETES NETWORK CONTRACT"]
Contract --> Rule1["Aturan 1 (Setiap Pod memiliki IP unik sendiri di seluruh kluster)"]
Contract --> Rule2["Aturan 2 (Semua Pod saling terhubung secara direct tanpa NAT)"]
Contract --> Rule3["Aturan 3 (Node host dapat berkomunikasi dengan seluruh Pod tanpa NAT)"]
Aturan 1: Setiap Pod Mendapatkan Alamat IP Unik Sendiri #
Di Kubernetes, unit terkecil dari deployment kita bukanlah kontainer, melainkan Pod. Karena itu, alamat IP dialokasikan di tingkat Pod, bukan kontainer individual. Semua kontainer di dalam satu Pod berbagi alamat IP yang sama, ruang port yang sama, dan antarmuka jaringan (network interface) yang sama. Tidak diperlukan port mapping yang rumit (seperti docker run -p 8080:80) untuk menghubungkan kontainer-kontainer tersebut ke luar.
Aturan 2: Semua Pod Dapat Berkomunikasi Satu Sama Lain Tanpa NAT #
Pod apa pun di dalam kluster dapat menghubungi Pod lain mana pun secara langsung menggunakan alamat IP tujuannya. Komunikasi ini harus berjalan tanpa melalui proses Network Address Translation (NAT), tidak peduli apakah Pod tujuan berada di node worker yang sama (intra-node) atau di node worker yang berbeda (inter-node). Dari perspektif Pod, seluruh kluster terlihat seperti satu jaringan LAN virtual yang datar (flat network).
Aturan 3: Node Worker Dapat Menghubungi Semua Pod Tanpa NAT #
Setiap proses yang berjalan di tingkat sistem operasi node worker (host) harus dapat mengirimkan paket data ke Pod mana pun di node worker tersebut atau node worker lainnya secara langsung tanpa NAT. Aturan ini sangat penting agar agen sistem seperti Kubelet, sistem monitoring tingkat node, dan daemon log collector dapat memantau kesehatan dan status kontainer kita secara akurat.
Aturan 4: Alamat IP yang Dilihat oleh Pod adalah Alamat IP yang Sama yang Dilihat oleh Pihak Luar #
Jika sebuah Pod menanyakan sistem operasinya sendiri mengenai alamat IP internalnya (misalnya melalui kueri loopback), alamat IP yang dikembalikan harus persis sama dengan alamat IP yang dilihat oleh Pod lain saat menerima paket dari Pod tersebut. Tidak ada masking IP internal (IP masquerading) yang terjadi dalam komunikasi antar Pod. Hal ini sangat menyederhanakan proses penelusuran kesalahan (debugging) dan pencatatan log (logging) jaringan.
Flat Network: Satu Ruang Alamat IP Terpadu #
Aturan-aturan fundamental di atas melahirkan apa yang disebut sebagai Flat Network (Jaringan Datar). Di Kubernetes, kita memperlakukan seluruh Pod seolah-olah mereka terhubung ke satu switch jaringan fisik raksasa yang sama.
flowchart TD
subgraph DockerStandalone["Docker Standalone (Host-Scoped)"]
direction LR
subgraph NodeA["Node A (Host 1)"]
ContA["Container A<br>172.17.0.2"]
ContB["Container B<br>172.17.0.3"]
end
subgraph NodeB["Node B (Host 2)"]
ContC["Container C<br>172.17.0.2"]
ContD["Container D<br>172.17.0.3"]
end
NodeA -->|"NAT via Port 8080"| Network["External Network"]
NodeB -->|"NAT via Port 9090"| Network
end
subgraph KubernetesFlat["Kubernetes Flat Network (Cluster-Scoped)"]
direction LR
subgraph NodeK1["Node Worker 1 (10.0.0.10)"]
PodA["Pod A<br>10.244.1.5"]
PodB["Pod B<br>10.244.1.6"]
end
subgraph NodeK2["Node Worker 2 (10.0.0.11)"]
PodC["Pod C<br>10.244.2.3"]
PodD["Pod D<br>10.244.2.4"]
end
NodeK1 <-->|"Direct Routing (No NAT)"| NodeK2
end
Pada model Docker standalone tradisional, Container A di Host 1 dan Container C di Host 2 tidak memiliki rute langsung satu sama lain. Mereka bahkan dapat memiliki alamat IP internal yang tumpang tindih (misalnya sama-sama mendapat 172.17.0.2). Untuk menghubungkannya, kita dipaksa mengekspos port ke sistem operasi host (port forwarding) dan mengandalkan proses NAT di tingkat kernel host.
Sebaliknya, pada model Flat Network Kubernetes:
- Setiap Pod di kluster memiliki IP yang unik secara universal di dalam rentang Cluster CIDR (misalnya
10.244.0.0/16). - Pod A (
10.244.1.5) dapat melakukan ping atau mengirim data HTTP langsung ke Pod C (10.244.2.3) tanpa bantuan port mapping. - Aplikasi kita tidak perlu tahu di node worker mana Pod tujuan berada. Logika jaringan kita menjadi jauh lebih sederhana karena kita bisa memperlakukan Pod layaknya mesin server independen di jaringan lokal.
Bagaimana IP Pod Dialokasikan (Pod CIDR) #
Untuk memastikan tidak ada alamat IP yang tumpang tindih (IP conflict) di seluruh kluster, Kubernetes menggunakan strategi pembagian rentang IP yang terstruktur untuk setiap node worker.
Saat kluster diinisialisasi, administrator menentukan satu rentang alamat IP utama untuk seluruh Pod di kluster, yang disebut Cluster CIDR (misalnya 10.244.0.0/16). Rentang ini kemudian dibagi lagi menjadi segmen-segmen kecil yang tidak saling tumpang tindih untuk setiap node worker, yang disebut Pod CIDR.
Mari kita perhatikan visualisasi alokasi segmen IP ini:
flowchart TD
Cluster["Cluster CIDR: 10.244.0.0/16"] -. Alokasi Segmen .-> Node1["Node Worker A: 10.244.1.0/24"]
Cluster -. Alokasi Segmen .-> Node2["Node Worker B: 10.244.2.0/24"]
Cluster -. Alokasi Segmen .-> Node3["Node Worker C: 10.244.3.0/24"]
Node1 --> PodA1["Pod A1: 10.244.1.2"]
Node1 --> PodA2["Pod A2: 10.244.1.3"]
Node2 --> PodB1["Pod B1: 10.244.2.2"]
Node2 --> PodB2["Pod B2: 10.244.2.3"]
Ketika kita menambahkan node worker baru ke dalam kluster, control plane Kubernetes (melalui Controller Manager) akan mendeteksi node tersebut dan mengalokasikan satu blok subnet CIDR eksklusif (misalnya 10.244.1.0/24 untuk Node A yang mampu menampung hingga 254 Pod).
Setiap kali Kubelet di Node A diperintahkan membuat Pod baru:
- Kubelet akan berinteraksi dengan plugin CNI yang aktif di node tersebut.
- Plugin CNI mengambil satu alamat IP kosong yang tersedia dari blok CIDR milik node tersebut (misalnya
10.244.1.2). - Alamat IP tersebut ditempelkan pada Pod baru dan didaftarkan kembali ke API Server.
[!WARNING] Alamat IP Pod bersifat ephemeral (sementara). IP ini terikat pada siklus hidup Pod. Jika Pod kita mati, dihapus, atau dipindahkan ke node lain akibat proses pemeliharaan node, Pod baru yang menggantikannya akan mendapatkan IP yang sama sekali berbeda dari blok CIDR node baru tersebut. Oleh karena itu, kita tidak boleh sekali-kali menuliskan alamat IP Pod secara statis (hardcoded) di dalam kode aplikasi kita untuk keperluan komunikasi antar layanan.
Namespace Jaringan Pod (Linux Network Namespaces) #
Untuk memahami bagaimana kontainer-kontainer di dalam satu Pod dapat saling berkomunikasi via localhost, kita harus melihat ke bawah kap mesin sistem operasi Linux, tepatnya pada fitur Network Namespace (netns).
Network namespace adalah fitur isolasi kernel Linux yang menyediakan salinan virtual dari tumpukan jaringan (network stack) sistem operasi, yang terdiri dari antarmuka jaringan (interface), tabel routing (routing table), dan aturan firewall (iptables/nftables).
Secara default, setiap kontainer Docker yang berjalan di Linux akan mendapatkan satu network namespace uniknya sendiri. Namun, Kubernetes melakukan hal yang berbeda:
flowchart TD
subgraph PodNamespace["POD A NETWORK NAMESPACE"]
direction TB
lo["Loopback Interface (lo) -> 127.0.0.1"]
eth0["Ethernet Interface (eth0) -> 10.244.1.5"]
subgraph Containers["Containers sharing same namespace"]
direction LR
App["Container App (Port 80)"]
Sidecar["Container Sidecar (e.g. Envoy Proxy)"]
App <-->|"localhost"| Sidecar
end
end
- Kontainer Sandbox (Pause Container): Saat Pod dibuat, Kubernetes pertama-tama akan menghidupkan sebuah kontainer kecil khusus yang tidak melakukan apa-apa, yang disebut Pause Container. Kontainer ini dibuat semata-mata untuk menginisialisasi satu network namespace baru bagi Pod tersebut.
- Berbagi Namespace: Ketika kontainer aplikasi kita dan kontainer sidecar (misalnya log forwarder atau service mesh proxy) dihidupkan di dalam Pod yang sama, Kubernetes menginstruksikan container runtime (seperti containerd) agar menempatkan kontainer-kontainer tersebut ke dalam network namespace milik Pause Container tadi.
- Implikasi Komunikasi:
- Komunikasi Intra-Pod (Localhost): Karena kontainer-kontainer di dalam satu Pod berbagi namespace yang sama, mereka dapat berkomunikasi satu sama lain menggunakan alamat loopback
127.0.0.1(localhost) secara langsung. Sebagai contoh, aplikasi web kita di Port 80 dapat menghubungi sidecar db-proxy di Port 5432 vialocalhost:5432. - Isolasi Port: Dua kontainer di dalam satu Pod yang sama tidak boleh menggunakan port yang sama (misalnya tidak boleh sama-sama mencoba memperebutkan Port 80), karena hal ini akan memicu error Address already in use.
- Komunikasi Inter-Pod: Pod lain yang ingin menghubungi aplikasi kita harus menggunakan alamat IP eksternal Pod (
10.244.1.5) atau melalui perantara objek Service.
- Komunikasi Intra-Pod (Localhost): Karena kontainer-kontainer di dalam satu Pod berbagi namespace yang sama, mereka dapat berkomunikasi satu sama lain menggunakan alamat loopback
Peran CNI (Container Network Interface) sebagai Pelaksana Kontrak #
Kubernetes dirancang sebagai platform orkestrasi yang modular. Kubernetes API Server sendiri tidak memiliki kode untuk membuat kartu jaringan virtual, mengatur tabel routing di Linux host, atau membuat terowongan overlay (overlay tunnels). Kubernetes mendelegasikan tugas tersebut sepenuhnya ke Container Network Interface (CNI) plugin.
CNI adalah spesifikasi standar industri yang mendefinisikan bagaimana container runtime (seperti containerd atau CRI-O) berinteraksi dengan plugin jaringan pihak ketiga melalui berkas manifes konfigurasi JSON standar.
Kubelet berinteraksi dengan plugin CNI melalui dua perintah utama:
- ADD: Dipanggil oleh Kubelet saat Pod baru akan dihidupkan. CNI wajib membuat interface jaringan virtual (biasanya sepasang
vethpair), memasukkan salah satu ujungnya ke dalam network namespace Pod, mengalokasikan IP dari IPAM pool, dan memperbarui tabel routing host agar paket data tahu cara menuju ke Pod tersebut. - DEL: Dipanggil saat Pod dihapus. CNI harus membersihkan antarmuka virtual yang telah dibuat dan mengembalikan IP yang telah dialokasikan kembali ke pool agar bisa digunakan oleh Pod lain di masa mendatang.
Berikut adalah perbandingan beberapa plugin CNI terpopuler yang umum digunakan di industri:
| Nama CNI | Teknologi Routing | Dukungan NetworkPolicy | Fitur Observabilitas | Use Case Utama |
|---|---|---|---|---|
| Flannel | VXLAN (Overlay) | Tidak Mendukung | Sangat Dasar | Kluster kecil, pengembangan lokal (Kind/Minikube), ringan tanpa overhead CPU. |
| Calico | BGP (Direct/Routing) atau VXLAN | Sangat Kuat | Menengah | Kluster produksi on-premise atau cloud yang membutuhkan isolasi firewall mikro-segmentasi yang ketat. |
| Cilium | eBPF (Direct Linux Kernel Data Path) | Sangat Kuat (Layer 3-7) | Sangat Tinggi (Hubble UI) | Kluster skala besar modern, latency-sensitive applications, security auditing, service mesh tanpa sidecar. |
Penting untuk kita catat bahwa apa pun CNI yang kita pilih di tingkat infrastruktur, cara kita menulis manifes Kubernetes (Deployment, Service, ingress) akan tetap persis sama. CNI hanyalah detail implementasi teknis di balik layar.
Perbedaan Fundamental dengan Docker Standalone Networking #
Untuk memperjelas pemahaman kita, mari kita bandingkan perbedaan arsitektur jaringan antara Docker Standalone (yang sering kita gunakan di laptop saat masa development) dengan Kubernetes:
| Fitur Jaringan | Docker Standalone Networking | Kubernetes Network Model |
|---|---|---|
| Skala Alamat IP | Host-scoped (hanya unik di dalam satu mesin host). | Cluster-scoped (unik di seluruh kluster lintas host). |
| Akses Antar Node | Tidak bisa berkomunikasi langsung tanpa overlay network tambahan atau port forwarding manual. | Pod lintas node terhubung langsung via flat network tanpa NAT. |
| Publikasi Port | Wajib melakukan port mapping (hostPort:containerPort). |
Tidak butuh port mapping untuk komunikasi internal kluster. |
| Resolusi DNS | Mengandalkan internal embedded DNS server Docker (hanya di custom user bridge). | Mengandalkan CoreDNS terpusat yang terintegrasi dengan siklus hidup Service kluster. |
Anti-Pattern vs Solusi dalam Jaringan Kubernetes #
Mari kita pelajari beberapa kesalahan konfigurasi jaringan (anti-pattern) yang paling sering dilakukan oleh developer di Kubernetes beserta cara mengatasinya.
Anti-Pattern 1: Menggunakan Docker-Style hostPort pada Spec Pod
#
Kita mendeploy Pod aplikasi stateless (misalnya web server) dan menyertakan parameter hostPort pada spec kontainer agar port 80 kontainer langsung menempel ke port 80 fisik mesin worker node, mirip dengan perintah docker run -p 80:80.
Kode Manifest Salah (Mengkunci Port Host Node) #
# JANGAN LAKUKAN INI DI PRODUKSI: Bentrok port host fisik
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app-hostport-failure
namespace: production
spec:
replicas: 3 # Mencoba mendeploy 3 replika
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web-container
image: nginx:alpine
ports:
- containerPort: 80
hostPort: 80 # Mencegah Pod kedua dijadwalkan di node yang sama!
Konsekuensi Buruk #
Jika kluster kita hanya memiliki 2 node worker fisik, maka replika Pod ketiga akan stuck selamanya di status Pending. Hal ini terjadi karena port 80 pada kedua node fisik sudah habis diklaim oleh dua Pod pertama. Menggunakan hostPort membatasi skalabilitas kluster kita secara drastis dan membingungkan Kubernetes scheduler.
Kode Solusi (Menggunakan Abstraksi Service) #
Kita harus membiarkan port Pod berjalan di ruang jaringan virtualnya sendiri secara bebas tanpa hostPort, kemudian kita menggunakan objek Service untuk mengarahkan lalu lintas ke Pod-Pod tersebut secara merata.
# SOLUSI: Pisahkan urusan publikasi port ke objek Service
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app-clean
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web-container
image: nginx:alpine
ports:
- containerPort: 80 # Port virtual kontainer aman tanpa hostPort
---
apiVersion: v1
kind: Service
metadata:
name: web-app-service
namespace: production
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
selector:
app: web-app
Anti-Pattern 2: Menggunakan hostNetwork: true pada Pod Aplikasi Biasa
#
Kita menyalakan fitur hostNetwork: true pada manifes Pod aplikasi microservices kita dengan alasan ingin meningkatkan performa jaringan atau memudahkan akses langsung ke resource internal VM node worker.
Kode Manifest Salah (Bypassing Isolasi Jaringan Host) #
# JANGAN LAKUKAN INI: Merusak keamanan isolasi kluster
apiVersion: v1
kind: Pod
metadata:
name: backend-hostnetwork-risky
namespace: production
spec:
hostNetwork: true # Pod menggunakan namespace jaringan mesin VM host langsung!
containers:
- name: backend-app
image: my-backend:v1
ports:
- containerPort: 8080
Konsekuensi Buruk #
Dengan menyalakan hostNetwork, Pod tidak lagi berjalan di dalam namespace jaringan terisolasi. Pod tersebut dapat melihat seluruh antarmuka jaringan fisik dari mesin VM host, memantau lalu lintas data sistem operasi node worker, dan meningkatkan risiko keamanan secara dramatis. Jika kontainer berhasil dieksploitasi oleh hacker, hacker tersebut bisa langsung mengakses jaringan VPC cloud internal kita seolah-olah mereka adalah administrator server VM host tersebut.
Kode Solusi (Gunakan Namespace Terisolasi Default) #
Gunakan konfigurasi jaringan virtual bawaan Kubernetes dan manfaatkan NetworkPolicy untuk membatasi akses lalu lintas jaringan secara aman.
# SOLUSI: Biarkan Pod berjalan di namespace terisolasi (hostNetwork: false secara default)
apiVersion: v1
kind: Pod
metadata:
name: backend-app-secure
namespace: production
spec:
# hostNetwork: false (Secara default dinonaktifkan demi keamanan)
containers:
- name: backend-app
image: my-backend:v1
ports:
- containerPort: 8080
Ringkasan #
- Kubernetes Network Model adalah kontrak mutlak: Setiap Pod memiliki IP unik, seluruh Pod saling terhubung langsung tanpa NAT, node worker dapat menjangkau seluruh Pod tanpa NAT, dan alamat IP internal Pod sama dengan alamat IP yang dilihat dari luar.
- Flat Network menyederhanakan arsitektur: Seluruh Pod berada di satu ruang alamat IP datar yang unik di seluruh kluster, mengeliminasi kerepotan port forwarding tradisional.
- IP Pod bersifat ephemeral: Selalu gunakan abstraksi objek Service untuk berkomunikasi antar layanan daripada mengandalkan IP Pod yang mudah berubah saat Pod dibuat ulang.
- Pause Container menginisialisasi namespace: Kontainer di dalam satu Pod berbagi namespace jaringan yang sama via Pause Container, memungkinkan komunikasi localhost antar kontainer di satu Pod.
- CNI plugin menangani detail teknis: Plugin CNI seperti Calico, Cilium, atau Flannel bertugas mengimplementasikan model Flat Network ini tanpa memengaruhi cara kita menulis manifes Kubernetes.
- Hindari hostPort dan hostNetwork: Dua fitur ini merusak portabilitas kluster dan membuka celah keamanan serius. Selalu gunakan objek Service untuk eksposur port aplikasi secara aman.
← Sebelumnya: Storage Performance Berikutnya: Pod-to-Pod Communication →