Mumpung hari Sabtu, enaknya ngebahas hal-hal yang lumayan kompleks. Mungkin tulisan ini sedikit panjang, semoga cocok untuk menemani satnight mu yang #dirumahaja.

Oke, jadi ceritanya dapet credit gratis sebesar $100 (pengen dapet juga? Daftar Linode sekarang!) untuk nyobain LKE (Linode Kubernetes Engine) yang masih closed beta. Sejauh ini udah nyaman banget sama Docker, tapi karena sedang mempelajari tentang Distributed System, mempelajari Kubernetes sepertinya hal yang menarik.

Tulisan ini menggunakan sudut pandang seorang Fariz, yang tau nya Docker. Jika kamu sudah tau Docker, mungkin tulisan ini sedikit bisa lebih mudah dipahami. Gue gak akan ngebahas tentang apa itu k8s, sejarahnya, dsb karena blog ini bukanlah Wikipedia, jadi mari kita langsung bahas sedikit tentang kenapa pakai k8s.

Container Orchestrator

Di Docker, ada namanya mode Swarm. Yang kita tau, Docker hanyalah Container Runtime aja, sebuah "program" yang berguna untuk menjalankan Container (program/aplikasi) berdasarkan suatu image.

Gambaran Container

Biasanya kita menggunakan Docker via CLI untuk berkomunikasi dengan Docker ini seperti menjalankan (docker run), mematikan (docker stop), dsb.

Kita membiarkan tangan kita kotor untuk melakukan operasional tersebut, dan meskipun kita menggunakan Docker Compose, kegunaan Compose pada dasarnya hanyalah untuk bisa menjalankan container lebih dari satu secara lebih mudah. Tidak perlu perintah docker run berulang-ulang, cukup dibungkus dengan berkas yml.

Lalu berkenalan lah dengan Docker Swarm, sebuah container orchestrator. Ketika menggunakan mode Swarm, nantinya ada konsep Manager & Node. Yang intinya, si Manager ini yang bertanggung jawab atas node-node yang berjalan pada suatu jaringan. Jika kamu tidak menggunakan mode Swarm, anggap mesin yang kamu sekarang gunakan tersebut adalah Manager.

Oke sebenarnya apa sih tugas Container Orchestrator ini?

Konsep container membuat proses delivery menjadi lebih cepat. Namun software bukan hanya tentang me-release, namun juga me-maintain. Jika dengan konsep Container kita bisa mengautomasi berbagai tugas dengan jaminan program tersebut akan berjalan secara konsisten (remember "it works on my machine?"), kita juga perlu "sesuatu" untuk bisa mengatur, meng-scale, dan me-maintain container kita tersebut.

Dan "sesuatu" tersebut bernama Container Orchestrator.

Docker Swarm & Kubernetes adalah salah dua contoh dari orchestrator ini, dengan kekurangan & kelebihannya masing-masing.

Mengapa kita butuh orchestrator? Karena mengatur sesuatu itu bukanlah hal yang mudah, terlebih mengatur sesuatu yang saling bersebaran. Apalagi banyak.

Disini, kita akan fokus di Kubernetes, atau k8s agar lebih singkat. Ada satu hal yang membuat k8s terlihat ribet, dan akan kita bahas dibawah!

Kubernetes Engine

Ingat ada konsep Manager & Node di Swarm? Di k8s pun sama, namanya Master & Node (minion). Ketika kita menggunakan mode Swarm di Docker, setiap mesin yang terhubung harus ter-install Docker, bukan? Begitupula dengan k8s.

Mode swarm & k8s adalah tentang komputer yang ter-distribusi, tentang membuat cluster, yang singkatnya adalah kumpulan komputer yang saling terhubung dan bekerja sama.

Jika kamu ingin membuat cluster dengan 3 node, kamu harus melakukan instalasi k8s, konfigurasi, dsb secara manual. Begitupula ketika menambah node lagi.

Disini bagian ribetnya.

Sama seperti ketika kita terbiasa "deployment" menggunakan FTP, lalu dihadapkan dengan Docker. Beruntungya, Docker yang mungkin biasa kita gunakan masih untuk 1 server, jadi gak ribet-ribet banget.

Oke lanjut, k8s terdiri dari beberapa komponen. Kurang lebih begini:

Hahaha ribet, kan? Begitulah kalau kita ingin membuat "cluster" k8s kita sendiri, belum termasuk di biaya usaha pemeliharaan ya.

Bukannya menyelesaikan masalah, malah menambah masalah, ya?

Sekarang begini, bagaimana kalau kita lupakan bagian ribet tersebut (hello abstraction!) melainkan kita fokus ke benefit dari orchestrator: untuk bisa mengatur, meng-scale, dan me-maintain container kita tersebut, secara otomatis.

Itulah mengapa ehm ada sebuah layanan yang bernama "managed" kubernetes yang tugasnya melakukan hal-hal ribet itu semua.

Layanan nya sudah banyak, ada GKE (Google Kubernetes Engine) nya Google, EKS (Amazon Elastic Container Service for Kubernetes), AKS (Azure Kubernetes Service) dan masih banyak termasuk LKE (Linode Kubernetes Engine).

Seperti layanan cloud lainnya, kita harus pintar dalam menghitung harga. Google, Amazon, dan Azure adalah pemain besar di industri cloud, mereka menjual nama & integritas sebagai nilai tambahnya.

Disini kita akan pakai LKE, gue salah satu fans berat nya Linode. Dari penawaran yang ada lumayan menarik, terlebih Linode lumayan terpercaya untuk level developer & di industri cloud.

Tapi kita tidak akan langsung masuk ke bagian praktik, kita akan berkenalan terlebih dahulu dengan objek-objek yang ada di k8s seperti Nodes, Pods, dan Services.

Nodes

Nodes adalah mesin yang ada di cluster, yang bertindak sebagai "worker". Ini sudah jelas, sama seperti konsep Node di Swarm.

Pods

Belum nemu "gambaran di dunia nyata" yang sesuai dengan Pods, tapi kurang lebih Pods ini adalah "sebuah wadah" yang menjalankan container. Docker termasuk container runtime yang digunakan di Pods, yang berarti Pods bisa menjalankan 1 container ataupun lebih

Begitu kira-kira gambaran nya.

Services

Sebelumnya, mari kita bandingkan dahulu bila kita menggunakan Docker.

Biasanya prosesnya client mengirim request ke server lalu di handle sama reverse proxy, dari reverse proxy ini yang meneruskan request tersebut ke service yang bersangkutan.

Biasanya kita membatasi port mana saja yang terbuka ke "dunia luar"—bahkan di server gue cuma port 80 & 443 (oke dan port SSH yang udah diubah)—untuk alasan keamanan.

Container bisa berkomunikasi dengan container lain jika berada di 1 jaringan yang sama, bukan? Anggap seluruh container berada di 1 jaringan bernama network1.

Setiap container meng-expose port nya masing-masing agar bisa saling berkomunikasi. Disini misal gue pakai Traefik untuk reverse proxy, Traefik tau harus nerusin ke service yang mana (plus ke port berapa) berkat bantuan label.

Sederhana, bukan?

Itu untuk ukuran di 1 server, sedangkan kita sedang berbicara tentang cluster, kan?

Oke intinya service adalah tentang "meng-expose" aplikasi/container kita yang ada di pods. Ada beberapa cara untuk meng-expose nya, bisa via ClusterIP; NodePort, LoadBalancer dan ExternalName.

Bingung?

Mari kita sedikit-sedikit bahas.

  • ClusterIP: Ini adalah default nya, hanya bisa diakses oleh cluster itu sendiri. Kecuali melakukan port forwarding.
  • NodePort: Ini kita bisa mengakses nya via ip:port yang terlihat familiar bila kita tidak menggunakan reverse proxy. Ip & port tersebut mengarah ke node bila ingin diakses oleh luar cluster (bila IP dari node tersebut 6.6.6.6, berarti bisa diakses dari luar (hp lo misalnya) dengan cara 6.6.6.6:666)
  • LoadBalancer: Service di expose via external (bukan dari cluster itu sendiri), biasanya dari Load Balancer yang ditawarkan oleh cloud provider yang kita pakai. Load balancer tersebut akan mengerahkan ke NodePort dan ClusterIP yang mengarah ke aplikasi kita. Ini yang murah, mudah, dan aman.
  • ExternalName: Ini kita bisa mengakses nya menggunakan nilai dari externalName, belum pernah coba jadi kita skip aja ya hampura.

Berdasarkan dari hasil yang gue baca, untuk pemula bisa dimulai dari menggunakan LoadBalancer. Nanti kita bahas selain via LoadBalancer kalau gue udah gak pemula haha.

Oke selain via Service, untuk bisa meng-expose aplikasi kita bisa via Ingress yang tentunya tidak akan kita bahas sekarang. Sejauh ini mungkin sudah lumayan jelas pusing lah ya konsep Kubernetes ini, waktunya kita praktik!

Membuat Cluster

Kita pakai k3d nya Rancher untuk development. k3d ini versi k3s (kubernetes versi ringan) tapi lebih stabil di berbagai platform. Silahkan install terlebih dahulu k3d nya.

HARUS BANGET PAKAI TUTORIAL INSTALL NIH???

Ok ok, harusnya kalau udah punya Docker di mesin nya udah ada program kubectl ya untuk bisa berkomunikasi dengan kubernetes manager. Kalau memang ternyata belum ke-install, silahkan install terlebih dahulu.

Oke bos udah nih, cobain jalanin kubectl di terminal buat mastiin kalau udah bener-bener ter-install.

Sekarang waktunya bikin cluster, k3d ini kita bikin cluster tapi di Docker. Caranya sederhana banget, tinggal eksekusi k3d create maka cluster sudah jadi (ikutin dulu langkah-langkah yang ada di layar setelah eksekusi perintah tersebut).

Karena k3d (k3s) by default menggunakan Traefik sebagai ingress controller nya, mari kita buat si k3d nge-expose port 80 yang kita pakai buat berkomunikasi dengan Traefik.

$ k3d create --workers 3 --publish="80:80"

Perintah diatas untuk membuat cluster kita plus dengan 3 worker & meng-expose port 80.

Oke sekarang kita sudah punya cluster, lalu apa?

Kita setting PVC terlebih dahulu. Nanti kita akan bahas lebih lanjut seputar PVC/Persistent Volume Controller di tulisan selanjutnya, untuk sekarang kita gunakan manifest default dari rancher aja. Dan untuk testing di development, kita akan pakai local-path aja.

$ kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml

Verifikasi dengan menjalankan perintah kubectl get storageclass, jika muncul si local-path berarti sudah yoi.

Oke, terus apa?

Kita akan deploy sebuah ehm blog yakni Ghost. Kenapa deploy Ghost? Karena udah ada image nya di local gue hahaha. Gue belum tau "best practices" untuk membuat berkas manifest di k8s, jadi mari kita buat berkas nya per-aplikasi dulu. Buat berkas dengan nama ghost.yml lalu isi dengan nilai berikut:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: blog
  labels:
    app: blog
spec:
  replicas: 1
  selector:
    matchLabels:
      app: blog
  template:
    metadata:
      labels:
        app: blog
    spec:
      containers:
      - name: blog
        image: ghost:3.12-alpine
        imagePullPolicy: Always
        volumeMounts:
          - mountPath: /var/lib/ghost/content
            name: content
        ports:
        - containerPort: 2368
        env:
        - name: url
          value: http://my-awesome-blog.com
      volumes:
        - name: content
          persistentVolumeClaim:
          claimName: content
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: content
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi
  storageClassName: local-path

Sekarang mari kita deploy, jalankan kubectl apply -f ghost.yml dan tunggu beberapa detik. Manifest diatas intinya kita membuat deployment & membuat volume untuk nyimpen data si blog ini.

Mari kita cek apakah sudah ter-deploy atau belum, bisa cek dengan kubectl get pods. Jika status sudah Running, berarti sudah yoi. Karena kita belum membuat service untuk meng-expose si blog ini, jadi mari kita pakai port forwarding terlebih dahulu.

$ kubectl port-forward <pod_name> 2368

Jika berhasil, harusnya bisa diakses di localhost:2368

Nice, sekarang mari kita buat service buat nge-expose aplikasi ini via ingress.

apiVersion: v1
kind: Service
metadata:
  name: blog
  namespace: default
spec:
  ports:
  - port: 80
    targetPort: 2368
    protocol: TCP
  selector:
    app: blog
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: blog
  annotations:
    kubernetes.io/ingress.class: "traefik"
spec:
  rules:
  - host: my-awesome-blog.com
    http:
      paths:
      - path: /
        backend:
          serviceName: blog
          servicePort: 80

Ini sebagai contoh aja dulu, nantinya akan kita bahas seputar Ingress & (Cloud) Load Balancer. Sekarang kita apply perubahan tersebut menggunakan kubectl apply -f ghost.yml, lalu kita coba akses!

Oke sudah berjalan, ya? Biar lebih oke kita buat my-awesome-blog.com arahin ke localhost kita. Tambahkan baris 127.0.0.1 my-awesome-blog.com ke /etc/hosts dan jika sudah mari kita lihat di browser!

It is done?

Rekap

Mari kita gambarkan ilustrasinya terlebih dahulu.

M,mmm

Dan untuk berkas lengkapnya dari ghost.yml tersebut adalah seperti ini:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: blog
  labels:
    app: blog
spec:
  replicas: 1
  selector:
    matchLabels:
      app: blog
  template:
    metadata:
      labels:
        app: blog
    spec:
      containers:
      - name: blog
        image: ghost:3.12-alpine
        imagePullPolicy: Always
        volumeMounts:
          - mountPath: /var/lib/ghost/content
            name: content
        ports:
        - containerPort: 2368
        env:
        - name: url
          value: http://my-awesome-blog.com
      volumes:
        - name: content
          persistentVolumeClaim:
            claimName: content
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: content
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi
  storageClassName: local-path
---
apiVersion: v1
kind: Service
metadata:
  name: blog
  namespace: default
spec:
  ports:
  - protocol: TCP
    targetPort: 2368
    port: 80
  selector:
    app: blog
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: blog
  annotations:
    kubernetes.io/ingress.class: "traefik"
spec:
  rules:
  - host: my-awesome-blog.com
    http:
      paths:
      - path: /
        backend:
          serviceName: blog
          servicePort: 80

Masih banyak hal lain yang belum kita bahas secara detail, khususnya seputar ingress & services, persistensi, dsb. Dan juga kita belum melakukan deployment cluster kita ke "cloud" beneran (via Managed Kubernetes), mungkin akan kita bahas di tulisan selanjutnya.

Satu hal yang paling gue seneng dari menggunakan Managed Kubernetes adalah gue gak perlu melakukan SSH ke mesin gue yang ada di entah dimana untuk melakukan deployment, dan ya, mereka biasanya menawarkan fitur Auto Scaling, jadi, goodbye untuk 503 service unavailable!

Terima kasih sudah mampir.