--- title: The tale of devbox tags: - blog slug: the-tale-of-devbox date: 2022-12-17T06:17:22.000Z date_updated: 2022-12-17T11:48:57.000Z --- Ada 20 devbox yang berjalan di tempat kerja gue sekarang per tulisan ini dibuat. Mesin paling minimum yang digunakan adalah `e2-standard-2` yang seharga $42/bulan. Berarti, untuk 20 devbox dengan mesin tersebut saja setidaknya memakan $840/bulan. Tidak semua menggunakan `e2-standard-2`, beberapa ada yang `e2-standard-4` dan juga ada VM Windows, tapi itu cerita lain. Untuk block storage, paling minimum adalah 30 GB. Ini masih relatif murah, sekitar $0,187/GB/bulan. Untuk 20 devbox, berarti sekitar 600 GB, yang mana memakan $112/bulan. Beberapa ada yang menggunakan 50-60 GB, tapi gue ambil angka yang paling kecilnya aja. Terakhir, static IP. Ini relatif murah juga, $0,004/IP/bulan harusnya. Untuk 20 devbox, berarti seharusnya hanya $0,08/bulan. Total biaya untuk 20 devbox tersebut setidaknya sekitar $952,08/bulan alias $31,74/hari. Dengan $950/bulan, setidaknya bisa mendapatkan `e2-highmem-16` (16 vCPU, 128 GB) seharga $709, hemat ~$240/bulan atau $8/hari untuk hitungan 1 bulan = 30 hari. Penghematan 3,7jt IDR/bulan adalah angka yang relatif kecil, namun topik utama dari tulisan ini bukanlah tentang penghematan. ## Utilization trend Beberapa devbox ada yang normal, ada yang under dan ada yang over utilization. Jika mengambil threshold 75% untuk status "normal", gampangnya, dengan `e2-standard-2`, itu berarti rata-rata hanya menggunakan 1500 mCPU dan 9 GiB memory (plus swap). Untuk under/over berarti dibawah/diatas itu. Beberapa kali GCP (Google Cloud Provider) menyarankan untuk melakukan something yang bisa menghemat cost untuk devbox tertentu. Di lain hari, devbox tersebut malah menjadi over utilization atau bahkan menjadi normal. Dalam sebulan, setidaknya devbox efektif digunakan selama 186 jam (31/4*24), yang umumnya, jika work-life balance (LOL) seharusnya hanya 62 jam (31/4*8). Jika ambil nilai tengah, safe to say sekitar 78 jam jika sehari mendedikasikan waktu selama 10 jam. Problem pertama yang ingin gue coba solve adalah mencari jalan tengah untuk tidak over/under utilization, dan opsi pertama gue adalah dengan cara menjadikannya satu. Dengan mesin `e2-highmem-16`, setidaknya ada 16000 mCPU dan 192 GiB memory (plus swap) pool yang bisa digunakan. Ambil threshold 75%, berarti 12000 mCPU dan 144 GiB memory. Untuk 20 developers (1 devbox=1 developer), minimum resource yang bisa dialokasi adalah 600 mCPU dan 7,2 GiB memory. Jika rata-rata penggunaan resource **setiap developer **adalah sebagaimana yang disebutkan, seharusnya dalam 30 hari, mesin `e2-highmem-16` tidak akan over/under utilization. ## Maintenance nightmare Disamping itu gue rasa tidak semua 20 developers peduli dengan maintenance VM. I mean, upaya untuk memelihara VM mereka selain melakukan penghematan sumber daya seperti CPU/memory/block storage. Ada kernel yang harus dipelihara, keamanan, dependensi, apapun. Rata-rata devbox sudah berjalan selama 4752 jam (198 hari), untuk gue pribadi yang agak sedikit rajin memelihara VM, per tulisan ini dibuat saja ada 77 updates yang harus dilakukan dan kernel gue menggunakan versi 5.15 (LTS). Untuk keamanan, setup firewall di level VPC (Virtual Private Cloud) sudah agak membantu. Hanya inbound connection ke 22/tcp, 80/tcp, 443/tcp yang diperbolehkan, tapi untuk outbound, ya semua jenis. Ada beberapa kasus yang bisa membuat setup ini menjadi rentan, tapi kita bahas di lain waktu saja. Selain itu, tidak ada rotation terkait SSH key. Berdoa saja tidak ada private key yang tercuri. ## RFC 1: gigantic devbox Ambil contoh mesin `e2-highmem-16`, setiap developer secara teori akan mendapatkan maksimal 800 mCPU dan 9,6 GiB memory. Untuk workload gue rasa paling banyak di I/O bound, dan maksimal CPU yang bisa digunakan dalam satu cycle anggap 75% dari total yakni 12 vCPU. Dengan 1 gigantic devbox, berarti hanya 1 mesin dan 1 static IP. Mungkin bisa menggunakan 1 block storage juga. Gampangnya, gue bisa hanya memelihara dan *hardening *1 mesin dan 1 IP. Dan tentunya bisa lebih mudah untuk beriterasi mencari angka yang tepat untuk tidak over/under utilization. Problemnya, GCP melakukan charge menggunakan model pay per use (which is good). Dilemanya, jika 1 mesin raksasa tersebut tidak pernah mati, kita perlu membayar full per bulannya. Jika mesin tersebut dimatikan secara periodic, mungkin akan mengganggu alur kerja beberapa developer. Penghematan ~3jt tetaplah penghematan, tapi at what cost? Challenge lain, gue perlu membuat abstraksi khususnya untuk melakukan port forwarding. Rencanannya setiap developer akan mendapatkan "container khusus", dan gue perlu melakukan port forwarding di level host. Seperti, jika container B publish port 80, gue harus make sure port 80 tersebut tidak di publish di host, tapi bisa diakses via `container-b-80.box.dev` misalnya. ## RFC 2: Kubernetes (uh) Alih-alih menggunakan mesin raksasa, mungkin bisa menggunakan 25% dari mesin tersebut. Misal, mesin `e2-highmem-8` yang memiliki 8 vCPU dan 64 GiB memory. Untuk 20 developer, berarti setidaknya ada 4 node yang berjalan. Biaya untuk `e2-highmem-8` adalah $177/bulan, jika ada empat, setidaknya berarti seharga $708, lebih murah $1 dari yang sebelumnya lol. Problem di Kubernetes kita tidak bisa membuat swap, tapi setidaknya kita bisa mengalokasikan 14-15 GB memory dan ~1800 mCPU per container. Tantangannya tentu ada. Pertama, gue perlu make sure—harus bener-bener yakin—apakah container tersebut memang benar-benar sedang tidak digunakan. Misal, jika tidak ada aktivitas selama 2 jam (waktu yang relatif pas) berarti container tersebut akan dimatikan. Tantangannya, gue perlu cari tahu untuk mengetahui ada/tidak adanya aktivitas tersebut. Kedua, akan lebih mudah bila menggunakan node pool yang spesifikasinya sama dengan devbox mereka sebelumnya, misal `e2-standard-2`, alias, 1 container = 1 devbox. Ini secara teori tidak akan menganggu workload lain, dan hal-hal lain seperti port forwarding relatif lebih mudah dilakukan. Problemnya adalah jika resource yang digunakan melebihi jumlah resource yang dialokasikan, yang seharusnya hanya masalah memory (OOM thing). Mungkin dengan mesin `e2-highmem-2` masalah tersebut bisa diselesaikan, tapi perbedaan antara 4 `e2-highmem-8` dengan 20 `e2-highmem-2` adalah ~$1000, jika hitung-hitungan gue benar. Yang bahkan lebih besar dari biaya 1 vm = 1 developer, jika container tersebut somehow tidak pernah mati. Tertantang jadi orang DevOps? ### Different pools for different beasts Since gue punya kontrol terhadap apa yang akan berjalan, memiliki node pool yang bervariasi mungkin bisa menjadi solusi. Misal, untuk warga frontend dan PM bisa menggunakan `e2-highmem-2` dan/atau `e2-highmem-4` dan sisanya menggunakan `e2-standard-2`, misalnya. Ini secara teori bisa menyelesaian masalah perbedaan ~$1000 tersebut, problemnya, adalah, what if, si pool `e2-standard-2` pada suatu waktu membutuhkan lebih dari 8 GiB memory? Pertanyaan *what if* memang selalu menyebalkan. ## Stick with Kubernetes? Anggap gue memilih Kubernetes, dan berikut pool yang ada: - frontend: e2-highmem-4 - pm: e2-highmem-2 - backend: e2-standard-2 Konsumsi memori backend berdasarkan yang gue lakukan tidak terlalu besar karena tidak menjalankan ehm Node.js dan/atau Cypress, but YMMV. Setiap "devbox" menggunakan container yang gue maintain, dan setiap reboot, doi akan update tanpa menyentuh persistent storage yang ada. Ini gue rasa bisa menghemat space, seperti, bayangkan setiap "devbox" memiliki `/var/lib/docker/image` dan mungkin `/var/lib/docker/buildkit` dan atau `/var/lib/docker/overlay2` yang sama? Problemnya perlu make sure masalah permission (e.g `docker system/image prune`), tapi itu cerita lain. Dan yang paling penting, hanya memiliki 1 IP dan tidak perlu maintain apapun yang berkaitan dengan OS dari kernel dan versi docker. Mungkin gambarannya seperti ini: ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ Ingress ├───────────►│ Service ├─────────►│ Workload │ └───────────────┘ └───────────────┘ └───────────────┘ *.box.dev username-port.box.dev username.svc.cluster.local:port Jadi gue harus buat proxy ketika developer: - ssh user@username.box.dev - Handle request dari `fariz-8080.box.dev` ke `fariz.svc.cluster.local:8080` Seems promising, meskipun sudah ada solusi lain dari [Coder](https://coder.com) dan [Gitpod](https://www.gitpod.io) misalnya. ## Scheduling Jika belum terlalu familiar dengan container orchestration seperti Kubernetes dan Nomad, pada dasarnya yang mereka lakukan adalah scheduling. Seperti, setiap 5s cek apakah perlu melakukan replica, setiap 3m apakah perlu menurunkan replica, atau bahkan sampai 0. Dalam kasus devbox ini, ini agak menantang. Metriks nya mungkin "kapan terakhir command dijalankan oleh developer?" jika ternyata 2 jam yang lalu, maka matikan si container. Yang paling menantang adalah untuk PM. Mungkin mereka akan ada mengubah something seperti berkas `docker-compose.yml`, masalahnya, jika di kemudian waktu tidak ada yang diubah dan container dinyalakan karena ada traffic masuk, bagaimana cara mematikannya di kemudian hari? Of course bisa melihat ke traffic, misal, jika dalam 2 jam hanya menerima 120 traffic, matikan container. Iterasi berlanjut perlu dilakukan, seperti biasa. Hadeh. ## Persistent storage Ini agak tricky juga. Tapi yang paling aman adalah dengan menggunakan 1 container = 1 PV. Dengan block storage 30 GB sebelumnya, setidaknya ~2.5 GB digunakan untuk OS itu sendiri (Ubuntu) dan ~500 MB untuk Docker. 10% overhead berasal dari OS. Yang paling banyak berkontribusi adalah Docker image. Dan bila Docker image tersimpan di penyimpanan khusus, gue rasa ini bisa membantu. Sumber kode dari satu repositori per tulisan ini dibuat gue rasa adalah ~3GB, sudah termasuk `.next` dan `node_modules`, you know lah yang mana. Yang berarti, 30 GB per container gue rasa cukup untuk menyimpan semuanya termasuk data-data di "sub container" yang dijalankan. Bahkan mungkin 10-20 GB saja cukup. ## Objektif & key results Tentu saja yang ingin gue capai adalah mengurangi maintenance overhead. Setiap developer tidak perlu melakukan maintenance terhadap VM yang digunakan. Selain itu, cost, tentu saja. Jika asumsinya sebelumnya menghabiskan $840/bulan untuk mesin (spoiler: aslinya lebih dari itu), dengan setup: - e2-highmem-4: 4 - e2-highmem-2: 3 - e2-standard-2: 13 Setidaknya berubah menjadi $1729/bulan—lah kok makin besar? Jika selama sebulan hanya berjalan 200 jam (10 jam/hari alias 50 jam/minggu), berarti harusnya menjadi $477/bulan, dari yang sebelumnya yang setidaknya $840/bulan khusus untuk VM. Turun sekitar 43.3% alias hemat 5,6jt yang mana mungkin bisa dialokasikan ke hal lain yang lebih produktif seperti ke salary gue misalnya hehehe (hehe he). Selain itu bisa berkurang juga biaya lain dari persistent storage dan static IP. Concern gue sebenarnya hanya di static IP, dan ini bukan masalah cost. Memiliki lebih sedikit yang harus di maintenance adalah ultimate goals yang ingin dicapai. Dan yang paling penting adalah produktifitas developer. Tentu langkah ini tidak menambah produktivitas developer, namun setidaknya jangan membuatnya menjadi turun. Per hari ini, upaya gue dalam membuat developer melupakan adanya server somewhat works. Atau setidaknya, tidak ada yang resign hanya karena hal yang gue lakukan lol. Developer sudah tidak lagi perlu memikirkan terkait server di lingkungan production dan staging, atau gampangnya, tidak ada akses SSH kesana. Segala hal yang ingin dilakukan harus eksplisit, jika sebelumnya melakukan SSH hanya untuk melihat log dari container, sekarang ada cara lain untuk melakukan hal serupa. Dari perspektif keamanan, ini mengurangi attack surface. Dengan akses SSH, bukan hanya siapapun yang memiliki akses dapat melihat log container, tapi bisa shutdown server & menghapus data production juga. Who knows. Selain itu, ini memudahkan untuk memelihara kepercayaan untuk setiap pihak. Setiap developer yang ingin mengakses lingkungan production, harus memiliki tujuan jelas, yang kemungkinan besar hanya untuk keperluan bisnis. Setiap akses ke layanan internal yang berhubungan langsung dengan lingkungan production akan direkam dan dilindungi SSO, ini membantu untuk menjaga data pengguna karena dari perspektif keamanan, ancaman yang paling besar adalah dari internal. Dan kita menerapkan Zero Trust™ untuk selalu trust but verify. Kembali ke pembahasan, jika ini berhasil diterapkan, artinya, devbox sudah tidak perlu lagi diperlakukan sebagai VM. Yang mana seharusnya hanya do one thing and do it well: menyelesaikan pekerjaan. Terakhir, tujuan paling penting yang ingin dicapai adalah membuat semua orang happy. Developer happy, manajemen happy, DevOps happy. Mungkin ini akan mempersulit gue dalam membuat laporan tren utilitas mengingat sistem "on-demand" yang diterapkan, jika RFC 1 tidak gue pilih. Tapi tidak masalah, selagi tidak ada laporan "overutilized" yang terjadi.