ClusterIP

Service tipe ClusterIP dengan IP virtual yang stabil sebagai dasar service discovery di dalam kluster.

Daftar isi

Service tipe ClusterIP memberikan satu alamat IP virtual yang stabil untuk sekelompok Pod di dalam kluster. Pod dapat dibuat dan dihapus kapan saja, sedangkan alamat IP Pod berubah setiap kali itu terjadi, sehingga klien memerlukan titik akses yang tidak bergeser. Topik ini membahas masalah yang dipecahkan oleh ClusterIP, struktur manifest Service berbasis selector, cara objek Endpoints terbentuk, serta simulasi distribusi trafik yang dapat dijalankan di sandbox. Setelah membacanya, pembaca memahami alasan ClusterIP menjadi dasar service discovery di dalam kluster Kubernetes.

IP Pod yang tidak stabil#

Setiap Pod memperoleh alamat IP dari rentang alamat Pod yang dikonfigurasi pada kluster. Alamat tersebut hanya hidup selama Pod itu ada. Apabila Pod dihapus karena replika diskalakan turun, Node mengalami masalah, atau Deployment melakukan rollout baru, Pod penggantinya hampir pasti memperoleh alamat yang berbeda.

Keadaan ini tidak mengganggu komunikasi searah seperti pengiriman log, tetapi menjadi masalah serius bagi klien yang memanggil satu layanan secara berulang. Klien yang menyimpan alamat Pod di konfigurasinya akan gagal setiap kali alamat berubah. Dengan tiga replika, klien juga perlu memilih satu dari tiga alamat, sedangkan daftar alamat tersebut terus berubah mengikuti penjadwalan.

ClusterIP menyelesaikan kedua persoalan itu sekaligus. Service menyediakan satu alamat virtual yang tidak berubah selama Service ada, dan Service itulah yang mengikuti daftar Pod terkini yang cocok dengan selector-nya. Klien cukup mengetahui satu alamat dan satu port, tanpa perlu memantau kehidupan Pod satu per satu.

Manifest Service dan selector#

Manifest Service berbasis ClusterIP hanya memerlukan selector dan satu daftar port. Bidang spec.selector berisi label yang dicocokkan terhadap label Pod, sedangkan setiap entri pada spec.ports mendeklarasikan port yang diekspos Service dan targetPort yang dituju di dalam Pod. Port Service inilah yang dipanggil klien, sedangkan targetPort adalah port proses di dalam container.

Bidang metadata.name menentukan identitas Service di seluruh kluster dan menjadi dasar nama DNS-nya. Nama ini harus unik di dalam satu namespace, sehingga konvensi penamaan yang konsisten memudahkan pemanggilan dari komponen lain. Bidang labels pada metadata tidak wajib, tetapi membantu pengelompokan saat jumlah objek di kluster bertambah.

Berkas service.yaml di bawah ini mendefinisikan Service bernama service-aplikasi yang memilih Pod berlabel app: aplikasi-catatan dan mengekspos port 80 ke arah port 8080 pada Pod.

yaml service.yaml
apiVersion: v1
kind: Service
metadata:
  name: service-aplikasi
  labels:
    app: aplikasi-catatan
spec:
  type: ClusterIP
  selector:
    app: aplikasi-catatan
  ports:
    - name: http
      port: 80
      targetPort: 8080

Perintah berikut menerapkan manifest tersebut lalu menampilkan Service yang terbentuk bersama objek Endpoints-nya.

bash
# tidak-dijalankan
# terapkan manifest Service ke kluster
kubectl apply -f service.yaml
# tampilkan Service dan Endpoints yang terbentuk
kubectl get service,endpoints
text Contoh keluaran
NAME               TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
service-aplikasi   ClusterIP   10.96.42.17   <none>        80/TCP    5s
NAME               ENDPOINTS                                          AGE
service-aplikasi   10.244.1.5:8080,10.244.2.11:8080,10.244.3.7:8080   5s

Cara kerja Endpoints dan kube-proxy#

Service tidak menyimpan daftar Pod secara langsung. Controller pada control plane terus memantau Pod yang cocok dengan selector, lalu mengisi objek Endpoints dengan pasangan alamat IP dan port dari Pod yang siap menerima trafik. Apabila satu Pod mati dan Pod baru muncul, isi objek Endpoints berubah otomatis, sedangkan alamat virtual Service tetap.

Pada setiap Node, kube-proxy menerjemahkan alamat virtual Service menjadi aturan penerusan paket. Mekanismenya bergantung pada implementasi, umumnya berupa aturan iptables atau program eBPF yang memilih salah satu endpoint untuk setiap koneksi baru. Alamat virtual tidak pernah dipasang pada antarmuka jaringan mana pun, sehingga alamat tersebut hanya bermakna di dalam jaringan kluster.

Diagram berikut merangkum alur permintaan dari Pod klien menuju salah satu endpoint.

Pod klien10.244.1.9Service ClusterIP10.96.42.17:80iptables/eBPFEndpointsPod 10.244.1.5:8080Pod 10.244.2.11:8080Pod 10.244.3.7:8080permintaanditeruskanIP virtual tetap stabil, daftar endpoint mengikuti pod yang cocok dengan selector
Alur permintaan melalui Service ClusterIP

Perintah describe memperlihatkan selector, alamat virtual, dan daftar endpoint yang terbentuk untuk service-aplikasi.

bash
# tidak-dijalankan
# tampilkan detail Service beserta endpoint yang ditemukan selector
kubectl describe service service-aplikasi
text Contoh keluaran
Name:              service-aplikasi
Namespace:         default
Selector:          app=aplikasi-catatan
Type:              ClusterIP
IP:                10.96.42.17
Port:              http  80/TCP
TargetPort:        8080/TCP
Endpoints:         10.244.1.5:8080,10.244.2.11:8080,10.244.3.7:8080

Simulasi distribusi trafik#

Service meneruskan setiap koneksi baru ke salah satu endpoint yang tersedia, dan kube-proxy umumnya memilih endpoint secara bergilir dengan pola round-robin. Skrip berikut membaca service.yaml menggunakan PyYAML, mencetak selector dan port yang terdaftar, lalu mensimulasikan sepuluh permintaan yang dibagikan ke tiga endpoint secara deterministik melalui pembagian berputar berbasis modulo. Pembagian berbasis modulo dipilih agar hasilnya dapat dihitung ulang persis, berbeda dari pemilihan endpoint pada kube-proxy yang dapat bergantung pada detail implementasinya.

python simulasi-clusterip.py
"""Simulasi distribusi trafik Service ClusterIP secara round-robin deterministik."""
import yaml

with open("service.yaml", encoding="utf-8") as berkas:
    manifest = yaml.safe_load(berkas)

spec = manifest["spec"]
# daftar endpoint yang dilaporkan objek Endpoints untuk selector di atas
endpoints = ["10.244.1.5", "10.244.2.11", "10.244.3.7"]

print(f"Nama Service : {manifest['metadata']['name']}")
print(f"Tipe         : {spec['type']}")
print(f"Selector     : {spec['selector']}")
for port in spec["ports"]:
    print(f"Port         : {port['port']} -> targetPort {port['targetPort']}")

print()
print(f"Distribusi 10 permintaan ke {len(endpoints)} endpoint (pembagian berputar):")
rekap = {ep: 0 for ep in endpoints}
for urutan in range(10):
    tujuan = endpoints[urutan % len(endpoints)]
    rekap[tujuan] += 1
    print(f"  permintaan {urutan + 1:2d} -> {tujuan}")

print()
print("Rekapitulasi:")
for ep, jumlah in rekap.items():
    print(f"  {ep} : {jumlah} permintaan")
text Keluaran
Nama Service : service-aplikasi
Tipe         : ClusterIP
Selector     : {'app': 'aplikasi-catatan'}
Port         : 80 -> targetPort 8080

Distribusi 10 permintaan ke 3 endpoint (pembagian berputar):
  permintaan  1 -> 10.244.1.5
  permintaan  2 -> 10.244.2.11
  permintaan  3 -> 10.244.3.7
  permintaan  4 -> 10.244.1.5
  permintaan  5 -> 10.244.2.11
  permintaan  6 -> 10.244.3.7
  permintaan  7 -> 10.244.1.5
  permintaan  8 -> 10.244.2.11
  permintaan  9 -> 10.244.3.7
  permintaan 10 -> 10.244.1.5

Rekapitulasi:
  10.244.1.5 : 4 permintaan
  10.244.2.11 : 3 permintaan
  10.244.3.7 : 3 permintaan

Sepuluh permintaan menghasilkan pembagian 4, 3, dan 3. Pembagian tidak sepenuhnya rata apabila jumlah permintaan tidak habis dibagi banyaknya endpoint, dan permintaan ke-10 kembali ke endpoint pertama. Prinsip yang sama berlaku pada Service asli, yaitu setiap endpoint menerima bagian yang hampir merata dalam jangka panjang. Nilai modulo pada skrip memilih endpoint dari sisa pembagian nomor permintaan, sehingga urutan pemilihan berputar penuh sebelum kembali ke awal. Pada kluster nyata, kube-proxy bekerja per koneksi, bukan per permintaan, sehingga koneksi HTTP yang panjang tetap terikat pada satu endpoint sampai koneksi ditutup.

Kesalahan umum#

Kesalahan konfigurasi ClusterIP hampir selalu bermanifestasi sebagai Endpoints yang kosong atau koneksi yang ditolak. Tiga kasus berikut paling sering ditemui.

  • Selector tidak cocok dengan Pod mana pun. Penyebabnya paling sering salah ketik pada nilai label, misalnya app: aplikasiCatatan padahal Pod berlabel aplikasi-catatan. Service tetap dibuat, tetapi Endpoints kosong dan permintaan ditolak. Perbaikannya adalah menyamakan nilai label pada Pod dan selector, lalu memeriksa kubectl get endpoints.
  • targetPort tidak sesuai port yang mendengarkan di dalam container. Service meneruskan paket ke port yang dituju, tetapi tidak ada proses yang mendengarkan di sana, sehingga koneksi ditolak. Perbaikannya adalah menyamakan targetPort dengan port yang dideklarasikan pada spec.containers[].ports Pod.
  • ClusterIP diakses dari luar kluster. Alamat virtual hanya berlaku di dalam jaringan kluster, sehingga panggilan dari komputer di luar kluster menuju alamat tersebut tidak berfungsi. Apabila akses eksternal diperlukan, gunakan NodePort atau LoadBalancer.

Ketiga kasus ini berbagi satu pola perbaikan, yaitu mengamati hasil nyata sebelum mengubah manifest. Perintah kubectl get endpoints menampilkan hasil pencocokan selector pada objek Endpoints, sedangkan kubectl describe memperlihatkan target port yang terdaftar, sehingga kedua perintah tersebut menutup sebagian besar diagnosis ClusterIP. Dengan urutan itu, kebanyakan kasus ClusterIP selesai tanpa menelusuri komponen lain.

Ringkasan#

  • ClusterIP memberikan satu IP virtual yang stabil sepanjang hidup Service, sedangkan alamat Pod berubah setiap kali Pod dibuat ulang.
  • Controller mengisi objek Endpoints dari Pod yang cocok dengan selector, dan kube-proxy menerjemahkan alamat virtual menjadi aturan penerusan paket pada setiap Node.
  • Kesalahan selector atau targetPort bermanifestasi sebagai Endpoints kosong atau koneksi yang ditolak, sehingga kubectl get endpoints menjadi pemeriksaan pertama yang paling berguna.

Referensi#