Kubernetes bekerja pada sistem terdistribusi yang terdiri atas satu atau lebih server yang disebut node. Untuk mencobanya di komputer lokal, saya tidak perlu sampai membangun home networking. Saya bisa menggunakan minikube yang merupakan implementasi ringan dari Kubernetes yang memang ditujukan untuk dipakai di komputer lokal. Minikube dapat menggunakan hypervisor dan virtual machine untuk men-simulasi-kan node Kubernetes di komputer yang sama. Khusus untuk sistem operasi Linux, minikube juga dapat langsung menggunakan Docker untuk menciptakan node Kubernetes tanpa menggunakan VM. Walaupun konsep “memakai container Docker untuk menjalankan Kubernetes yang akan mengelola container Docker lainnya” terdengar rekursif, ia menawarkan kinerja yang lebih baik.

Sebagai contoh, saya akan mulai dengan membuat cluster Kubernetes di Docker dengan memberikan perintah ini:

$ minikube start --driver=docker --cni=calico --nodes=2 --profile=latihan

$ minikube profile latihan

Perintah di atas akan membuat sebuah cluster Kubernetes yang terdiri atas 2 node di profile dengan nama latihan dan mengaktifkannya. Dengan fasilitas profile, saya bisa memiliki lebih dari satu cluster virtual tanpa harus menghapus cluster yang sudah ada sebelumnya.

Bila saya memberikan perintah docker ps di komputer host, saya akan menemukan dua container Docker yang mewakili cluster Kubernetes lokal saya sudah dibuat secara otomatis oleh Minikube:

$ docker ps --format '{{.Image}} {{.Names}}'

gcr.io/k8s-minikube/kicbase:v0.0.28 latihan-m02
gcr.io/k8s-minikube/kicbase:v0.0.28 latihan

Hasil di atas menunjukkan bawah saya berhasil membuat dua node Kubernetes. Kedua node ini dijalankan dari dalam Docker (tanpa virtual machine) di komputer host (yang sedang saya pakai saat ini). Untuk melihat IP-nya, saya bisa menggunakan perintah seperti:

$ docker inspect -f '{{.Name}}: {{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' latihan latihan-m02

/latihan: 192.168.49.2
/latihan-m02: 192.168.49.3

Komputer host dapat menghubungi kedua container tersebut, yang dapat dibuktikan dengan memberikan perintah ping 192.168.49.2 dan ping 192.168.49.3 dari komputer host. Walaupun demikian, Docker di komputer host hanya sekedar mengurus siklus hidup container tersebut dan tidak tahu seperti apa “isi”-nya. Untuk mengetahui informasi lebih lanjut, saya harus menggunakan kubectl, seperti pada contoh berikut ini:

$ kubectl get nodes -o wide

NAME          STATUS   ROLES                  AGE   VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
latihan       Ready    control-plane,master   24m   v1.22.3   192.168.49.2   <none>        Ubuntu 20.04.2 LTS   5.4.0-91-generic   docker://20.10.8
latihan-m02   Ready    <none>                 19m   v1.22.3   192.168.49.3   <none>        Ubuntu 20.04.2 LTS   5.4.0-91-generic   docker://20.10.8

Saya dapat menggunakan perintah minikube ssh untuk masuk ke dalam salah satu node, misalnya untuk melakukan troubleshooting sementara, seperti pada contoh berikut ini:

$ minikube ssh -n latihan-m02

docker@latihan-m02:~$ ping host.minikube.internal

PING host.minikube.internal (192.168.49.1) 56(84) bytes of data.
64 bytes from host.minikube.internal (192.168.49.1): icmp_seq=1 ttl=64 time=0.060 ms
64 bytes from host.minikube.internal (192.168.49.1): icmp_seq=2 ttl=64 time=0.042 ms
64 bytes from host.minikube.internal (192.168.49.1): icmp_seq=3 ttl=64 time=0.052 ms
64 bytes from host.minikube.internal (192.168.49.1): icmp_seq=4 ttl=64 time=0.043 ms
64 bytes from host.minikube.internal (192.168.49.1): icmp_seq=5 ttl=64 time=0.040 ms

Salah satu alamat yang unik pada minikube adalah host.minikube.internal. Ini adalah alamat yang ditambahkan oleh minikube di setiap node yang ada. Sesuai dengan namanya, alamat ini merujuk pada komputer host. Programmer seharusnya tidak perlu sampai mengakses node atau melakukan konfigurasi di node secara langsung.

Pod

Di Kubernetes, satuan unit eksekusi paling kecil disebut sebagai pod. Sebuah pod dapat memiliki satu atau lebih container Docker. Sebagai contoh, saya akan membuat sebuah pod dengan nama p1 seperti berikut ini:

$ kubectl run p1 --image=nginx --port=80

Pod p1 diatas hanya terdiri atas sebuah container Docker yang dibuat berdasarkan image nginx (diambil dari Docker Hub). Bagaimana cara saya mengakses pod ini? Bila saya memberikan perintah berikut ini:

$ kubectl get pods -o wide

NAME   READY   STATUS    RESTARTS   AGE     IP             NODE          NOMINATED NODE   READINESS GATES
p1     1/1     Running   0          1m30s   10.244.255.1   latihan-m02   <none>           <none>

Saya menemukan bahwa p1 memiliki alamat IP 10.244.255.1 dan berjalan di node latihan-m02 yang memiliki IP 192.168.49.3. Pod tidak di-akan dipublikasikan oleh node sehingga bila saya memberikan perintah seperti curl 192.168.49.3, saya tetap tidak dapat mengakses container nginx milik p1! Saya juga tidak dapat menggunakan curl 10.244.255.1 karena IP tersebut adalah IP internal.

Namun, bila ingin melakukan troubleshooting dan perlu mengakses sistem operasi di dalam pod, saya dapat menggunakan perintah seperti berikut ini:

$ kubectl exec p1 -- bash

Perintah di atas akan memberikan akses ke shell sistem operasi yang dipakai oleh pod sehingga saya dapat memberikan perintah CLI seolah-olah seperti berada di komputer lokal.

Sebuah pod bisa memiliki lebih dari satu container. Namun, karena pod merupakan satuan terkecil di Kubernetes, tidak ada perintah untuk menambahkan container ke pod yang sudah ada. Sebagai contoh, bila saya mencoba menambahkan container baru ke pod p1, saya akan menemukan pesan kesalahan seperti:

$ kubectl patch pod p1 --patch '{"spec": {"containers": [{"name": "network-sidecar", "image": "amazon/amazon-ecs-network-sidecar"}]}}'

The Pod "p1" is invalid: spec.containers: Forbidden: pod updates may not add or remove containers

Ini menunjukkan bahwa struktur container di dalam pod sulit untuk berubah lagi. Bila container tambahan memiliki fungsi yang jauh berbeda dari yang sudah ada di pod, sangat disarankan untuk membuat pod baru yang berisi container tersebut. Ini akan membuat pengelolaan container tersebut menjadi lebih mudah. Lalu, pada kasus seperti apa sebuah pod disarankan untuk memiliki lebih dari satu container? Biasanya container tambahan di pod berhubungan dengan jaringan seperti proxy dan sebagainya. Sebagai contoh, container amazon-ecs-network-sidecar yang hendak saya tambahkan adalah sebuah container yang berisi perintah seperti traceroute, ifconfig, dan sebagainya yang sangat membantu dalam troubleshooting jaringan. Perintah tersebut tidak tersedia di container nginx karena container nginx berdasarkan debian:slim yang menghilangkan banyak file dan aplikasi yang tidak dibutuhkan. Tujuannya supaya ukuran kecil dan container juga tidak berat. Tentu saja saya juga bisa masuk ke dalam container dan memberikan perintah seperti apt update dan apt install, tapi ini akan men-“cemari” container tersebut.

Karena tidak bisa menambah container baru melalui perintah kubectl, saya akan melakukannya melalui file manifest seperti latihan.yaml yang memiliki isi:

apiVersion: v1
kind: Pod
metadata:
  name: p1
spec:
  restartPolicy: Never
  containers:
    - name: p1-c1
      image: nginx
    - name: p1-c2
      image: amazon/amazon-ecs-network-sidecar
---
apiVersion: v1
kind: Pod
metadata:
  name: p2
spec:
  restartPolicy: Never
  containers:
    - name: p2-c1
      image: nginx
    - name: p2-c2
      image: amazon/amazon-ecs-network-sidecar

Saya kemudian menghapus p1 dan membuat ulang pod berdasarkan file konfigurasi di atas:

$ kubectl delete pod p1

$ kubectl apply -f latihan.yaml

Sekarang, saya akan memiliki dua pod p1 dan p2 dimana masing-masing pod memiliki dua container, seperti yang terlihat pada hasil berikut ini:

$ kubectl get pods -o wide

NAME   READY   STATUS    RESTARTS   AGE   IP             NODE          NOMINATED NODE   READINESS GATES
p1     2/2     Running   0          63s   10.244.255.6   latihan-m02   <none>           <none>
p2     2/2     Running   0          63s   10.244.255.5   latihan-m02   <none>           <none>

Walaupun terdapat dua container di p1 dan p2, masing-masing pod hanya memiliki satu alamat IP. Oleh sebab itu, saya harus berhati-hati memastikan bahwa tidak ada container yang berusaha mendapatkan akses ke port yang sama di pod yang sama. Anggap saja p1-c1 dan p1-c2 adalah aplikasi yang berjalan di komputer yang sama sehingga jika mereka sama-sama berusaha memakai port yang sama, hanya salah satu dari mereka yang sukses akan dan yang satunya lagi akan gagal. Bila saya memberikan perintah:

$ kubectl exec p1 -c p1-c2 -- netstat -anp

Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      1/sshd              
tcp6       0      0 :::80                   :::*                    LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      1/sshd              

saya menemukan bahwa container p1-c2 melihat port yang dipakai oleh container p1-c1 seolah-olah mereka berada di dalam komputer yang sama. Bukan hanya itu, bila saya memberikan perintah:

$ kubectl exec p1 -c p1-c2 -- ifconfig

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1480
        inet 10.244.255.6  netmask 255.255.255.255  broadcast 10.244.255.6        

Terlihat bahwa IP pod yang sama seperti di p1-c1 juga dipakai oleh p1-c2, seolah-olah mereka berada di komputer yang sama.

Apakah pod yang satu dapat menghubungi pod yang lainnya? Yup, walaupun tidak ada bantuan dari DNS, asalkan tahu nama alamat IP pod yang hendak dituju, mereka dapat saling berkomunikasi, seperti yang ditunjukkan pada perintah berikut ini:

$ kubectl exec p1 -c p1-c2 -- traceroute 10.244.255.5

traceroute to 10.244.255.5 (10.244.255.5), 30 hops max, 60 byte packets
 1  latihan-m02.latihan (192.168.49.3)  0.055 ms  0.008 ms  0.007 ms
 2  10.244.255.5 (10.244.255.5)  0.055 ms  0.015 ms  0.013 ms

Bagaimana bila pod berada di node yang berbeda? Untuk mencobanya, saya akan menambahkan baris berikut ini pada latihan.yaml:

...
---
apiVersion: v1
kind: Pod
metadata:
  name: p3
spec:
  restartPolicy: Never
  containers:
    - name: p3-c1
      image: nginx
    - name: p3-c2
      image: amazon/amazon-ecs-network-sidecar
  nodeName: latihan

Pada deklarasi p3 di atas, saya menggunakan nodeName sehingga pod baru ini hanya akan di-deploy di node yang memiliki nama latihan (ini adalah node pertama). Untuk membuat pod baru tersebut, saya memberikan perintah berikut ini:

$ kubectl apply -f latihan.yaml

Sekarang, saya akan menemukan p3 yang di-deploy di node berbeda dari p1 and p2 seperti yang terlihat di hasil perintah berikut ini:

$ kubectl get pods -o wide

NAME   READY   STATUS    RESTARTS   AGE     IP             NODE          NOMINATED NODE   READINESS GATES
p1     2/2     Running   0          9m     10.244.255.6   latihan-m02   <none>           <none>
p2     2/2     Running   0          9m     10.244.255.5   latihan-m02   <none>           <none>
p3     2/2     Running   0          9m     10.244.103.3   latihan       <none>           <none>

Apakah p1 dapat menghubungi p3 yang berada di node berbeda? Yup, bisa, seperti yang dibuktikan oleh perintah berikut ini:

$ kubectl exec p1 -c p1-c2 -- traceroute 10.244.103.3

traceroute to 10.244.103.3 (10.244.103.3), 30 hops max, 60 byte packets
 1  latihan-m02.latihan (192.168.49.3)  0.050 ms  0.010 ms  0.008 ms
 2  10.244.103.0 (10.244.103.0)  0.105 ms  0.033 ms  0.030 ms
 3  10.244.103.3 (10.244.103.3)  0.067 ms  0.035 ms  0.031 ms

Hasil di atas juga menunjukkan adanya hop tambahan karena ini melibatkan jaringan yang berbeda yang berada di komputer berbeda (walaupun virtual bila dijalankan di minikube). Pod yang berada di node berbeda akan diakses melalui tunnel interface tunl0 yang telah dipersiapkan oleh Calico. tunl0 menggunakan jenis tunnel yang disebut sebagai IP over IP tunnel (IPIP tunnel).

Sekarang saya sudah berhasil membuat dua pod saling berkomunikasi. Lalu, bagaimana caranya supaya pod tersebut dapat diakses oleh pihak luar? Tidak ada gunanya membuat sebuah aplikasi yang hanya berkomunikasi secara internal namun tidak dapat dipanggil oleh pengguna, bukan? Untuk mejawab pertanyaan ini, Kubernetes memiliki apa yang disebut sebagai service.

Sebelum menggunakan service, pada kasus tertentu yang sangat jarang terjadi, pod dapat menggunakan jaringan di node secara langsung dengan mengisi nilai hostNetwork dengan true seperti pada konfigurasi berikut ini:

apiVersion: v1
kind: Pod
metadata:
  name: p1
spec:
  restartPolicy: Never
  hostNetwork: true
  containers:
    - image: nginx
      name: nginx
      ports:
        - containerPort: 80

Bila saya memberikan perintah berikut ini:

$ kubectl get pods -o wide

NAME   READY   STATUS    RESTARTS   AGE   IP             NODE           NOMINATED NODE   READINESS GATES
p1     1/1     Running   0          50s   192.168.49.3   minikube-m02   <none>           <none>

Saya dapat melihat bahwa pod tersebut dapat diakses melalui IP node (192.168.49.3) secara langsung. Bila saya memberikan perintah curl 192.168.49.3 dari komputer host, saya dapat langsung mengakses NGINX di pod tersebut. Cara ini adalah cara paling singkat untuk membiarkan sebuah pod dapat diakses secara langsung, namun juga merupakan cara yang paling berbahaya dan sebaiknya dihindari kecuali pada kasus-kasus tertentu.

Service

Untuk membuktikan bahwa pod tidak memiliki alamat yang tetap, saya akan me-restart minikube dengan minikube stop dan minikube start. Karena tidak ada deployment yang berusaha membuat ulang pod, saya akan menemukan pod saya berada dalam status Completed. Saya kemudian membuat ulang pod dengan perintah seperti:

$ kubectl delete -f latihan.yaml

$ kubectl apply -f latihan.yaml

$ kubectl get pods -o wide

NAME   READY   STATUS    RESTARTS   AGE   IP             NODE          NOMINATED NODE   READINESS GATES
p1     2/2     Running   0          48s   10.244.255.5   latihan-m02   <none>           <none>
p2     2/2     Running   0          48s   10.244.255.4   latihan-m02   <none>           <none>
p3     2/2     Running   0          48s   10.244.103.6   latihan       <none>           <none>

Terlihat bahwa walaupun pod dengan nama yang sama dibuat ulang, mereka memiliki IP yang berbeda. Bila pihak yang memanggil pod masih memakai IP lama, mereka tidak akan terhubung lagi (atau terhubung ke pod yang salah). Untuk mengatasi hal ini, Kubernetes memiliki konsep service. Sebagai contoh, saya mengubah file latihan.yaml menjadi seperti berikut ini:

apiVersion: v1
kind: Pod
metadata:
  name: p1
  labels:
    service: s1
spec:
  restartPolicy: Never
  containers:
    - name: p1-c1
      image: nginx
    - name: p1-c2
      image: amazon/amazon-ecs-network-sidecar
---
apiVersion: v1
kind: Pod
metadata:
  name: p2
  labels:
    service: s2
spec:
  restartPolicy: Never
  containers:
    - name: p2-c1
      image: nginx
    - name: p2-c2
      image: amazon/amazon-ecs-network-sidecar
---
apiVersion: v1
kind: Pod
metadata:
  name: p3
  labels:
    service: s1
spec:
  restartPolicy: Never
  containers:
    - name: p3-c1
      image: nginx
    - name: p3-c2
      image: amazon/amazon-ecs-network-sidecar
  nodeName: latihan
---
apiVersion: v1
kind: Service
metadata:
  name: s1
spec:
  selector:
    service: s1
  ports:
    - port: 80

Setelah itu, saya menjalankan perintah berikut ini mengaplikasikan perubahan yang saya buat:

$ kubectl apply -f latihan.yaml

$ kubectl get service

NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP   45m
s1           ClusterIP   10.108.241.53   <none>        80/TCP    80s

Konfigurasi di atas akan membuat sebuah service s1 yang terdiri atas pod p1 dan p3. Yup, walaupun pod berada di node yang berbeda, mereka tetap dapat dijadikan sebagai satu service yang sama. Sekarang, saya dapat mengakses p1 dan p3 berdasarkan IP milik s1, seperti yang terlihat pada contoh berikut ini:

$ kubectl exec p1 -c p1-c1 -- bash -c "echo Ini adalah p1 > /usr/share/nginx/html/index.html"

$ kubectl exec p3 -c p3-c1 -- bash -c "echo Ini adalah p3 > /usr/share/nginx/html/index.html"

$ kubectl exec p2 -c p2-c2 -- curl -s s1

Ini adalah p3

$ kubectl exec p2 -c p2-c2 -- curl -s s1

Ini adalah p3

$ kubectl exec p2 -c p2-c2 -- curl -s s1

Ini adalah p1

Mengapa DNS bisa mengenali nama s1? Bila saya masuk ke salah satu pod dengan kubectl exec -- bash dan melihat DNS yang dipakai dengan memberikan perintah cat /etc/resolv.conf, saya akan menemukan baris seperti nameserver yang merujuk ke CoreDNS. Berkat bantuan CoreDNS, nama seperti s1 akan diterjemahkan menjadi cluster IP 10.108.241.53. Lalu, bagaimana IP 10.108.241.53 bisa berubah menjadi IP untuk p1 atau p3? Pada saat melakukan request keluar ke 10.108.241.53, akan ada operasi DNAT, seperti yang ditunjukkan pada perintah berikut ini (dari container yang mewakili sebuah node):

$ sudo iptables -t nat -L

Chain KUBE-SERVICES (2 references)
target     prot opt source               destination         
...
KUBE-SVC-WXDZ2HVMUQ5BIO6Q  tcp  --  anywhere             10.108.241.53       /* default/s1 cluster IP */ tcp dpt:http
...
Chain KUBE-SVC-WXDZ2HVMUQ5BIO6Q (1 references)
target     prot opt source               destination         
KUBE-MARK-MASQ  tcp  -- !10.244.0.0/16        10.108.241.53       /* default/s1 cluster IP */ tcp dpt:http
KUBE-SEP-4KQ27AGTZ3LSVQQF  all  --  anywhere             anywhere             /* default/s1 */ statistic mode random probability 0.50000000000
KUBE-SEP-7GPWQRIYTMYP5CKW  all  --  anywhere             anywhere             /* default/s1 */
...
Chain KUBE-SEP-4KQ27AGTZ3LSVQQF (1 references)
target     prot opt source               destination         
KUBE-MARK-MASQ  all  --  10.244.103.4         anywhere             /* default/s1 */
DNAT       tcp  --  anywhere             anywhere             /* default/s1 */ tcp to:10.244.103.8:80
...
Chain KUBE-SEP-7GPWQRIYTMYP5CKW (1 references)
target     prot opt source               destination         
KUBE-MARK-MASQ  all  --  10.244.103.6         anywhere             /* default/s1 */
DNAT       tcp  --  anywhere             anywhere             /* default/s1 */ tcp to:10.244.255.8:80
...

Terlihat pada konfigurasi di atas, Kubernetes menggunakan -m statistic --mode random --probability 0.5 di iptables untuk menerjemahkan 10.108.241.53 menjadi salah satu dari 10.244.103.8 atau 10.244.255.8 (dimana masing-masing memiliki peluang 50%) untuk membuat sebuah load balancer sederhana. Hal ini teruji pada saat saya mengerjakan perintah curl secara berulang kali, dimana terkadang hasilnya merujuk pada pod p1 dan terkadang merujuk ke p3. Dengan demikian, di aplikasi yang harus memanggil p1 atau p3, saya tidak perlu menggunakan alamat IP p1 dan p3 secara langsung, melainkan cukup menggunakan s1.

Bila saya menghapus dan membuat ulang p1 dan p3, saya akan menemukan bahwa nilai IP untuk s1 akan diperbaharui sesuai secara otomatis sesuai dengan IP terbaru dari p1 dan p3 seperti yang terlihat pada percobaan berikut ini:

$ kubectl get endpoints s1

NAME   ENDPOINTS                         AGE
s1     10.244.103.8:80,10.244.255.8:80   3m35s

$ kubectl delete pod/p1 pod/p3

$ kubectl apply -f latihan.yaml

$ kubectl get endpoints s1

NAME   ENDPOINTS                         AGE
s1     10.244.103.9:80,10.244.255.9:80   4m27s

Terlihat bahwa service sudah menyelesaikan permasalahan alamat pod yang tidak kekal. Namun, ini masih belum menjawab pertanyaan bagaimana pengguna bisa mengakses layanan karena IP yang dipakai oleh s1 adalah IP internal yang tidak dapat diakses dari luar. Secara default, tipe service yang dibuat adalah ClusterIP yang menggunakan IP internal. Untuk service yang dapat diakses dari luar, saya bisa mencoba menggunakan tipe NodePort seperti pada contoh berikut ini:

$ kubectl delete service s1

$ kubectl expose pod p1 --port=80 --name=s1 --type=NodePort

$ kubectl get services

NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        116m
s1           NodePort    10.104.165.13   <none>        80:31686/TCP   5s

Hasil di-atas menunjukkan bahwa port 80 di p1 dan p3 dapat diakses melalui IP node di port 31686. Saya bisa mencobanya dengan memberikan perintah:

$ curl -s 192.168.49.2:31686 (untuk node pertama)

$ curl -s 192.168.49.3:31686 (untuk node kedua)

Pada saat mengakses 192.168.49.2, request akan diarahkan ke pod p3. Sementara itu, untuk 192.168.49.3, request akan diarahkan ke pod p1. Untuk mempermudah, minikube juga memiliki perintah untuk mendapatkan IP node dan port berdasarkan nama service:

$ minikube service --url s1

http://192.168.49.2:31686

NodePort akan mempublikasikan informasi service tersebut ke seluruh node yang ada. Setiap service akan diasosiasikan dengan port tertentu (secara default mulai dari port 30000 sampai 32767) di seluruh node yang ada. Sebagai contoh, bila saya mendeklarasikan dua pod NGINX yang memakai port 80 secara internal, versi service NodePort untuk kedua pod tersebut akan terlihat seperti:

$ kubectl get services

NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP        23m
service1     NodePort    10.104.155.201   <none>        80:30971/TCP   10m
service2     NodePort    10.99.118.194    <none>        80:31166/TCP   10m

Untuk mengakses service di atas, saya dapat menggunakan alamat http://<ipnode>:30971 untuk service1 dan http://<ipnode>:31166 untuk service2. Saya dapat menggunakan IP node apa saja karena NodePort dipublikasikan ke seluruh node yang ada. Bila saya mengakses IP untuk node1 dan ternyata pod tujuan saya tidak ada disana, request akan diteruskan ke node yang mengandung pod tujuan tersebut. Salah satu kelemahannya adalah bila node1 mengalami masalah dan aplikasi klien hanya akan memanggil IP node1, maka seluruh service tidak akan bisa diakses. Salah solusi untuk mengatasi masalah tersebut adalah dengan membuat service dengan tipe LoadBalancer. Service LoadBalancer membutuhkan sebuah load balancer eksternal yang tidak dikelola oleh Kubernetes. Load balancer ini bisa berbeda-beda tergantung pada instalasi Kubernetes yang dipakai, namun tugasnya selalu sama: mendistribusikan request langsung ke node yang memiliki pod tujuan.

Saya bisa membuat service dengan tipe LoadBalancer dengan menggunakan perintah seperti berikut ini:

$ kubectl delete service s1

$ kubectl expose pod p1 --port=80 --name=s1 --type=LoadBalancer

$ kubectl expose pod p2 --port=80 --name=s2 --type=LoadBalancer

$ kubectl get services -o wide

NAME         TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE    SELECTOR
kubernetes   ClusterIP      10.96.0.1        <none>        443/TCP        145m   <none>
s1           LoadBalancer   10.97.11.234     <pending>     80:30731/TCP   74s    service=s1
s2           LoadBalancer   10.102.179.113   <pending>     80:32361/TCP   65s    service=s2

Bila ini dilakukan di Kubernetes di cloud, nilai External-IP akan terisi alamat IP publik yang dapat saya akses dari mana saja. Sebagai contoh, Google Kubernetes Engine (GKE) akan menggunakan layanan Cloud Load Balancing (GLB) sebagai load balancer yang dipakai. Sementara itu, untuk percobaan lokal dengan minikube, saya dapat menjalankan perintah berikut ini:

$ minikube tunnel

Aplikasi tunnel ini harus tetap berjalan selama menguji load balancer dan tidak boleh dimatikan; sama seperti pada kasus produksi dimana load balancer eksternal harus tetap aktif. Tidak lama seperti perintah ini diberikan, bila saya memberikan perintah kubectl get services lagi, nilai External-IP akan terisi. Ini menunjukkan bahwa saya dapat mengakses s1 dan s2 dari klien (dalam hal ini adalah komputer host), seperti:

$ curl -s 10.97.11.234

$ curl -s 10.102.179.113

Setiap service LoadBalancer memiliki alamat IP publik masing-masing. Biasanya IP publik akan di-asosiasikan dengan sebuah domain, misalnya s1.jocki.me atau s2.jocki.me. Ada juga yang disebut sebagai wildcard DNS record, misalnya jika saya menetapkan *.service.jocki.me ke sebuah server, maka seluruh akses seperti a.service.jocki.me, b.service.jocki.me, www.service.jocki.me, dan sebagainya akan diarahkan ke server tersebut. Server tersebut kemudian bisa menentukan apa yang harus dikembalikan berdasarkan nama host yang diakses. Terlihat sangat sederhana bukan? Untuk menggunakan wildcard DNS record, saya dapat menggunakan fasilitas ingress controller di Kubernetes. Saat ini, karena memakai IP publik dari load balancer, saya harus mendaftarkan DNS record untuk setiap service secara manual.