Service #

Sebagaimana kita ketahui dari pembahasan model jaringan Kubernetes, setiap Pod mendapatkan alamat IP unik tersendiri. Namun, kita juga harus sadar akan satu realitas penting: Pod di Kubernetes bersifat mudah mati (ephemeral). Pod dapat dihancurkan dan dibuat ulang kapan saja akibat kegagalan node, autoscaling, atau pembaruan aplikasi (rolling updates). Setiap kali Pod baru lahir, ia akan membawa alamat IP baru yang tidak dapat diprediksi.

Situasi ini menimbulkan tantangan besar. Jika kita memiliki sekumpulan Pod backend penerima transaksi (misalnya 3 replika) dan sekumpulan Pod frontend yang perlu mengirimkan permintaan ke backend tersebut, bagaimana frontend tahu alamat IP mana yang harus dihubungi? Kita tidak mungkin memperbarui file konfigurasi frontend setiap kali salah satu Pod backend mati atau berganti IP.

Di sinilah Service hadir sebagai solusi pemecahan masalah. Service adalah objek abstraksi Kubernetes yang mendefinisikan cara mengakses sekumpulan Pod secara stabil. Service menyediakan satu alamat IP virtual yang tetap (ClusterIP) dan satu nama DNS internal yang tidak akan pernah berubah selama objek Service tersebut ada.

Artikel ini akan membedah secara mendalam cara kerja Service, mekanisme pencocokan label, perbedaan mendetail dari empat tipe Service utama, konsep Headless Service, serta metode debugging jaringan Service.


Cara Service Menemukan Pod: Label Selector #

Service tidak terhubung langsung ke nama Pod tertentu secara fisik. Sebagai gantinya, Service menggunakan mekanisme pencari dinamis yang disebut Label Selector (Pencocok Label).

Saat kita membuat Service, kita menentukan daftar label yang harus dimiliki oleh Pod target. Service kemudian akan memonitor seluruh Pod di dalam namespace yang sama, dan secara otomatis menyaring Pod mana saja yang memiliki label yang cocok untuk dimasukkan ke dalam daftar distribusi lalu lintas datanya.

Mari kita visualisasikan bagaimana objek Service menjembatani lalu lintas ke beberapa Pod melalui diagram berikut:

flowchart TD
    Client["Frontend Pod (Request)"] --> Svc["Service: backend-svc (10.96.0.10:80)"]
    Svc -. Selector: app=backend .-> Pod1["Pod backend-0 (10.244.1.4:8080)"]
    Svc -. Selector: app=backend .-> Pod2["Pod backend-1 (10.244.2.7:8080)"]
    Svc -. Selector: app=backend .-> Pod3["Pod backend-2 (10.244.1.9:8080)"]

Endpoints dan EndpointSlices #

Di balik layar, ketika kita membuat Service dengan selector, Kubernetes API Server secara otomatis membuat objek pendamping yang bernama Endpoints. Objek Endpoints ini menyimpan daftar mentah alamat IP dan port dari seluruh Pod aktif yang cocok dengan selector Service kita.

# Perintah untuk memeriksa Endpoints dari sebuah Service
kubectl get endpoints backend-service

Output yang dihasilkan:

NAME              ENDPOINTS
backend-service   10.244.1.4:8080,10.244.1.9:8080,10.244.2.7:8080

Jika salah satu Pod backend mengalami crash atau gagal dalam pemeriksaan kesehatan (readiness probe), Kubernetes Controller Manager akan langsung mendeteksi kegagalan tersebut dan menghapus alamat IP Pod yang rusak dari daftar Endpoints. Dengan demikian, Service tidak akan mengirimkan lalu lintas data ke Pod yang sedang tidak sehat.

[!NOTE] Pada kluster besar dengan ribuan Pod, objek Endpoints tunggal dapat membengkak sangat besar, memicu masalah performa etcd akibat transfer data manifest yang besar untuk setiap perubahan IP. Untuk mengatasi hal ini, Kubernetes modern (versi 1.21 ke atas) memperkenalkan EndpointSlice. Objek ini memecah daftar alamat IP Pod menjadi beberapa segmen kecil (slice) berukuran maksimal 100 endpoint per objek untuk menjaga skalabilitas kluster.


Empat Tipe Service Utama #

Kubernetes menyediakan empat tipe Service yang disesuaikan dengan kebutuhan eksposur lalu lintas jaringan aplikasi kita.

flowchart TD
    Types["KUBERNETES SERVICE TYPES"]
    Types --> ClusterIP["ClusterIP (Akses internal kluster - Default)"]
    Types --> NodePort["NodePort (Membuka port fisik di setiap node worker)"]
    Types --> LoadBalancer["LoadBalancer (Membuat load balancer eksternal cloud)"]

1. ClusterIP (Default) #

Ini adalah tipe Service bawaan jika kita tidak menentukan properti type secara eksplisit. ClusterIP mengalokasikan alamat IP virtual internal kluster yang hanya dapat dijangkau dari dalam kluster itu sendiri (oleh Pod lain atau dari dalam node worker).

Contoh Manifes ClusterIP #

apiVersion: v1
kind: Service
metadata:
  name: auth-service
  namespace: production
spec:
  type: ClusterIP
  selector:
    app: auth
  ports:
    - name: http
      protocol: TCP
      port: 80 # Port virtual Service
      targetPort: 3000 # Port kontainer aplikasi kita

Aplikasi internal kluster dapat menghubungi layanan ini menggunakan nama DNS instan: http://auth-service.production.svc.cluster.local atau cukup http://auth-service jika berada di namespace yang sama.


2. NodePort #

NodePort digunakan ketika kita ingin membuka akses ke layanan kluster dari jaringan luar tanpa bantuan load balancer cloud. Kubernetes akan membuka satu port statis khusus dari rentang port default 30000-32767 di seluruh node worker fisik kluster kita.

Contoh Manifes NodePort #

apiVersion: v1
kind: Service
metadata:
  name: web-nodeport-service
  namespace: production
spec:
  type: NodePort
  selector:
    app: frontend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 30080 # Port fisik node (rentang wajib 30000-32767)

Alur Kerja Jaringan NodePort #

Ketika client luar menghubungi alamat IP fisik dari node worker mana pun di port 30080 (misalnya http://192.168.1.10:30080), kernel node worker tersebut akan menangkap paket tersebut, mengarahkannya ke ClusterIP Service virtual web-nodeport-service, dan meneruskannya ke Pod frontend target.


3. LoadBalancer #

Tipe LoadBalancer adalah metode standar industri yang digunakan untuk mengekspos aplikasi internal kluster langsung ke internet publik pada lingkungan cloud provider (seperti AWS, GCP, atau Azure).

Contoh Manifes LoadBalancer #

apiVersion: v1
kind: Service
metadata:
  name: public-ingress-service
  namespace: production
spec:
  type: LoadBalancer
  selector:
    app: ingress-nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

Alur Kerja Jaringan LoadBalancer #

Saat kita menerapkan manifes ini di AWS EKS, driver cloud provider di EKS secara otomatis akan membuat objek Load Balancer fisik baru di akun AWS kita (misalnya AWS Network Load Balancer). AWS akan memberikan alamat DNS publik atau IP eksternal yang stabil. Lalu lintas dari luar akan masuk ke Load Balancer fisik cloud, didistribusikan ke port NodePort otomatis kluster, dan akhirnya mendarat di Pod aplikasi kita.


4. ExternalName #

Tipe ini tidak melakukan load balancing virtual, pembuatan port node, ataupun alokasi IP virtual. ExternalName bertindak murni sebagai alias DNS (CNAME record) internal kluster untuk mengarahkan lalu lintas ke layanan eksternal di luar kluster.

Contoh Manifes ExternalName #

apiVersion: v1
kind: Service
metadata:
  name: cloud-database-alias
  namespace: production
spec:
  type: ExternalName
  externalName: my-prod-db.c123456789.ap-southeast-1.rds.amazonaws.com

Ketika aplikasi di dalam kluster mencoba menghubungi cloud-database-alias.production.svc.cluster.local, DNS resolver internal Kubernetes (CoreDNS) langsung merespon dengan mengembalikan nilai CNAME RDS AWS tersebut, sehingga aplikasi kita dapat langsung melakukan handshaking database ke AWS RDS tanpa melalui perantara proxy kluster.


Headless Service (clusterIP: None) #

Kadang kala, kita tidak menginginkan adanya IP virtual ClusterIP tunggal yang bertindak sebagai load balancer. Kita mungkin sedang menjalankan database terdistribusi cluster (seperti Cassandra, MongoDB, atau Redis) di mana setiap replika database harus dapat dihubungi secara langsung oleh replika lainnya untuk keperluan replikasi data internal dan sinkronisasi state.

Untuk memfasilitasi kebutuhan ini, kita menggunakan Headless Service dengan menyetel nilai clusterIP: None.

Contoh Manifes Headless Service #

apiVersion: v1
kind: Service
metadata:
  name: database-headless
  namespace: production
spec:
  clusterIP: None # Wajib: Menjadikan Service ini Headless
  selector:
    app: postgres
  ports:
    - port: 5432
      targetPort: 5432

Bagaimana Headless DNS Bekerja? #

Jika kita melakukan kueri DNS lookup biasa terhadap ClusterIP Service, CoreDNS akan mengembalikan satu IP virtual ClusterIP. Namun, jika kita melakukan kueri DNS lookup terhadap Headless Service database-headless, CoreDNS akan langsung mengembalikan seluruh daftar alamat IP Pod asli yang berada di belakang Service tersebut.

# Melakukan kueri DNS lookup terhadap Headless Service
nslookup database-headless.production.svc.cluster.local

Hasil DNS response:

Name:   database-headless.production.svc.cluster.local
Address: 10.244.1.4  # IP Pod postgres-0
Address: 10.244.1.9  # IP Pod postgres-1
Address: 10.244.2.7  # IP Pod postgres-2

Jika digabungkan dengan StatefulSet, Headless Service memungkinkan kita memiliki subdomain DNS terpisah yang stabil untuk setiap indeks ordinal Pod:

  • Pod ke-0: postgres-0.database-headless.production.svc.cluster.local
  • Pod ke-1: postgres-1.database-headless.production.svc.cluster.local

Service Tanpa Selector #

Terdapat skenario khusus di mana kita ingin menggunakan abstraksi Service Kubernetes, tetapi target layanannya tidak berada di dalam kluster (misalnya database server legacy lama di VM on-premise). Kita dapat membuat Service tanpa mendefinisikan parameter .spec.selector, lalu membuat objek Endpoints secara manual dengan nama yang persis sama.

# 1. Manifes Service tanpa selector
apiVersion: v1
kind: Service
metadata:
  name: legacy-mysql-service
  namespace: production
spec:
  ports:
    - port: 3306
      targetPort: 3306
---
# 2. Manifes Endpoints manual (Nama wajib sama dengan Service)
apiVersion: v1
kind: Endpoints
metadata:
  name: legacy-mysql-service
  namespace: production
subsets:
  - addresses:
      - ip: 192.168.10.150 # IP database fisik di luar Kubernetes
    ports:
      - port: 3306

Dengan cara ini, aplikasi di dalam kluster cukup menghubungi legacy-mysql-service seolah-olah itu adalah database internal kluster biasa. Jika di masa mendatang database tersebut dimigrasikan ke dalam kluster, kita cukup menambahkan selector ke Service tanpa perlu mengubah konfigurasi kode aplikasi kita.


Sticky Sessions: Session Affinity #

Secara default, Service Kubernetes mendistribusikan lalu lintas data ke Pod target secara acak menggunakan algoritma load balancing internal (biasanya iptables random mode). Namun, jika aplikasi kita membutuhkan pengguna agar selalu terhubung ke kontainer yang sama demi menjaga session state lokal, kita dapat menyalakan properti sessionAffinity.

spec:
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800 # Pertahankan session selama 3 jam (10800 detik)

[!WARNING] Fitur sessionAffinity: ClientIP bekerja pada layer 4 jaringan (berdasarkan alamat IP asal client). Jika client luar kita berada di belakang satu gerbang proxy NAT kantor yang sama, maka seluruh pengguna dari kantor tersebut akan dialirkan ke satu Pod kontainer yang sama, memicu ketidakseimbangan beban kerja (load imbalance) pada kluster kita.


Anti-Pattern vs Solusi Jaringan Service #

Mari kita pelajari beberapa kesalahan paling umum yang sering dilakukan saat merancang Service beserta cara mengatasinya.

Anti-Pattern 1: Membuat LoadBalancer Service untuk Setiap Layanan Mikro Internal #

Kita memiliki arsitektur microservices yang terdiri dari 15 layanan backend internal. Kita mengonfigurasi seluruh 15 Service backend tersebut dengan tipe LoadBalancer agar tim developer dapat dengan mudah mengakses masing-masing layanan secara instan dari luar.

Konsekuensi Buruk #

Setiap satu LoadBalancer Service akan menginstruksikan cloud provider untuk membuat satu unit Load Balancer fisik baru di cloud (misalnya AWS NLB). Jika kita membuat 15 LoadBalancer Service, kita akan menerima tagihan 15 load balancer fisik yang sangat mahal setiap bulannya. Selain itu, mengekspos database atau API internal langsung ke internet luar membuka celah keamanan yang sangat berbahaya.

Kode Solusi (ClusterIP + Single Ingress Controller) #

Kita harus mengonfigurasi seluruh Service backend internal menggunakan tipe default ClusterIP. Untuk mengekspos API publik ke luar kluster, kita mendeploy satu unit Ingress Controller (yang hanya membutuhkan satu LoadBalancer Service), kemudian membuat aturan routing berbasis domain/path untuk diteruskan ke ClusterIP internal kita.

# SOLUSI: Contoh routing Ingress tunggal menuju banyak backend ClusterIP internal
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-gateway-ingress
  namespace: production
spec:
  ingressClassName: nginx
  rules:
    - host: api.mycompany.com
      http:
        paths:
          - path: /users
            pathType: Prefix
            backend:
              service:
                name: user-service # Service internal ClusterIP
                port:
                  number: 80
          - path: /payments
            pathType: Prefix
            backend:
              service:
                name: payment-service # Service internal ClusterIP
                port:
                  number: 80

Anti-Pattern 2: Kehilangan IP Asli Pengirim pada LoadBalancer Service #

Kita mendeploy Service LoadBalancer untuk menerima lalu lintas web. Saat menganalisis log akses web NGINX kita, kita mendapati bahwa seluruh alamat IP pengirim yang tercatat di log akses NGINX adalah alamat IP internal dari node worker host, bukan IP asli client dari internet luar.

Konsekuensi Buruk #

Ini terjadi karena secara default, Kubernetes menggunakan kebijakan lalu lintas externalTrafficPolicy: Cluster. Jika paket data mendarat di Node A, tetapi Pod aplikasi kita berada di Node B, Node A akan melakukan Source NAT (SNAT) untuk meneruskan paket ke Node B. Proses SNAT ini menimpa alamat IP asli client dengan IP internal Node A. Kita kehilangan kemampuan untuk melakukan audit keamanan, pembatasan akses geografis (geo-blocking), atau pencegahan spamming IP.

Kode Solusi (Menggunakan externalTrafficPolicy: Local) #

Kita harus memaksa Service agar hanya meneruskan lalu lintas langsung ke Pod yang berada di node yang sama dengan tempat pendaratan paket dari Load Balancer, dengan menyetel externalTrafficPolicy: Local.

# SOLUSI: Mempertahankan IP Client Asli
apiVersion: v1
kind: Service
metadata:
  name: web-ingress-service
  namespace: production
spec:
  type: LoadBalancer
  externalTrafficPolicy: Local # Mempertahankan Source IP asli client luar
  selector:
    app: frontend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  • Dengan menyetel Local, Load Balancer fisik cloud hanya akan mengirimkan lalu lintas ke node worker yang secara aktif menjalankan Pod target kita. Tidak ada SNAT lintas node yang terjadi, sehingga IP asli client dapat dibaca secara utuh oleh kontainer aplikasi kita.

Panduan Praktis Debugging Layanan Service #

Jika aplikasi kita gagal menghubungi sebuah Service, kita dapat mendiagnosis kendalanya secara terarah menggunakan perintah CLI berikut:

1. Memverifikasi Alamat IP Virtual (ClusterIP) #

Periksa apakah objek Service berhasil mendapatkan alokasi IP virtual internal kluster:

kubectl get svc -n production

Jika kolom CLUSTER-IP bernilai None (dan itu bukan Headless Service yang disengaja), atau kosong, berarti kluster kehabisan rentang alokasi IP internal (Service CIDR pool).

2. Memverifikasi Ketersediaan Endpoints #

Periksa apakah ada Pod sehat yang terdaftar di belakang Service tersebut:

kubectl describe svc <nama-service> -n <namespace>

Tinjau baris output Endpoints:. Jika baris tersebut bernilai kosong atau <none>, periksa hal berikut:

  • Apakah label selector di Service sudah ditulis dengan benar dan cocok dengan label yang tercantum pada Pod?
  • Apakah Pod target kita sedang dalam status Running?
  • Apakah Pod target kita gagal melewati pemeriksaan readinessProbe? (Pod yang gagal readiness probe otomatis dikeluarkan dari daftar Endpoints).

3. Menguji Resolusi DNS dari dalam Kluster #

Luncurkan pod debugger sementara dan lakukan tes nslookup untuk memastikan CoreDNS berfungsi normal:

kubectl run dns-test --rm -i --tty --image=tutum/dnsutils -- nslookup auth-service

Jika nslookup gagal mengembalikan IP ClusterIP, periksa log pod CoreDNS di namespace kube-system.


Ringkasan #

  • Service menjamin kestabilan titik akses: Menyediakan alamat IP virtual (ClusterIP) dan nama DNS internal yang tetap untuk menghubungkan Pod-Pod yang dinamis dan mudah berubah (ephemeral).
  • Selector menghubungkan Service dan Pod secara dinamis: Service menggunakan label selector untuk menyaring Pod target secara otomatis dan mendaftarkan IP mereka ke objek Endpoints/EndpointSlice.
  • Pilih tipe Service sesuai kebutuhan: Gunakan ClusterIP untuk backend internal, NodePort untuk eksposur port lokal fisik, LoadBalancer untuk integrasi otomatis cloud, dan ExternalName untuk alias CNAME eksternal.
  • Headless Service untuk cluster database: Nonaktifkan ClusterIP virtual (clusterIP: None) untuk memungkinkan kueri DNS mengembalikan IP asli seluruh Pod secara langsung; krusial untuk kluster database stateful.
  • Pertahankan IP client asli dengan externalTrafficPolicy: Local: Ubah setelan policy bawaan ke Local pada LoadBalancer Service guna mencegah IP client luar tertimpa oleh proses Source NAT (SNAT) node worker host.
  • Gunakan describe dan dnsutils untuk debugging: Langkah pertama saat koneksi Service bermasalah adalah memeriksa kecocokan tabel Endpoints dan memastikan kelancaran resolusi kueri DNS internal.

← Sebelumnya: Pod-to-Pod Communication   Berikutnya: DNS & Service Discovery →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact