Pengalamatan Pod dan Service Di Kubernetes
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.
Setup
Sebagai contoh, saya akan mulai dengan membuat cluster Kubernetes di Docker dengan memberikan perintah ini:
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:
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:
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:
Saya dapat menggunakan perintah minikube ssh
untuk masuk ke dalam salah satu node, misalnya untuk melakukan troubleshooting sementara, seperti pada contoh berikut ini:
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:
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
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:
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.
Multiple Containers
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:
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 mengotori container tersebut.
Karena tidak bisa menambah container baru melalui perintah kubectl
, saya akan melakukannya melalui file manifest seperti latihan.yaml
yang memiliki isi:
Saya kemudian menghapus p1
dan membuat ulang pod berdasarkan file konfigurasi di atas:
Sekarang, saya akan memiliki dua pod p1
dan p2
dimana masing-masing pod memiliki dua container, seperti yang terlihat pada hasil berikut ini:
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:
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:
Terlihat bahwa IP pod yang sama seperti di p1-c1
juga dipakai oleh p1-c2
, seolah-olah mereka berada di komputer yang sama.
Pod To Pod (Same Node)
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:
Pod To Pod (Different Node)
Bagaimana bila pod berada di node yang berbeda? Untuk mencobanya, saya akan menambahkan baris berikut ini pada latihan.yaml
:
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:
Sekarang, saya akan menemukan p3
yang di-deploy di node berbeda dari p1
and p2
seperti yang terlihat di hasil perintah berikut ini:
Apakah p1
dapat menghubungi p3
yang berada di node berbeda? Yup, bisa, seperti yang dibuktikan oleh perintah berikut ini:
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).
Host Network
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:
Bila saya memberikan perintah berikut ini:
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:
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:
Setelah itu, saya menjalankan perintah berikut ini mengaplikasikan perubahan yang saya buat:
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:
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):
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:
NodePort
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:
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:
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:
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:
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
.
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:
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:
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:
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.