Berbagi volume antar docker host

The power of decentralized

Sudah menjadi rahasia umum bahwasannya hal yang paling sulit di distribusi sistem salah satunya adalah hal terkait persistensi.

Ketika membuat aplikasi, biasanya kita butuh menyimpan data-data terkait pengguna ataupun aplikasi seperti foto profile, kredensial pengguna, daftar transaksi pengguna, dan sebagainya.

Pendekatan diatas lebih mudah ketika kita menggunakan bare metal ataupun teknologi virtualisasi: Data apapun tinggal disimpan di server kita sendiri, yang mana dikelola (juga) oleh kita.

Sekarang di era cloud, aplikasi rata-rata berjalan diatas teknologi bernama container. Selain membuat proses deployment menjadi lebih mudah, juga mempermudah dalam proses operasional.

Dan ya, bisa berjalan di berbagai cloud provider juga, tanpa harus berurusan dengan proprietary software mengingat teknologi container memiliki spesifikasi standar yang dipayungi oleh Open Container Initiative.

Proses transisi dari bare metal ke cloud ini lumayan mudah™: Hal-hal terkait aset statis disimpan di object storage yang disediakan oleh cloud provider, dan yang terkait database pun menggunakan layanan yang disediakan oleh cloud provider yang biasanya layanan tersebut kompatibel dengan teknologi terbuka yang sudah ada (dan yang sudah banyak digunakan).

Tapi (mungkin) tidak sedikit yang belum mau 100% ber-migrasi ke cloud, dengan alasan masing-masing.

Termasuk gue, bila berbicara pribadi.

Tapi gue ingin menggunakan (salah satunya) Docker karena fitur yang ditawarkannya, bukan hanya karena hype.

Docker berjalan mulus bila hanya menggunakan 1 mesin (1 host), dan akan menjadi sulit ketika kita sudah menggunakan n+1 host berdasarkan alasan dan pertimbangan masing-masing.

Jika berbicara tentang Docker, Docker memiliki mode swarm yang sederhananya memudahkan kita untuk menjalankan berbagai container di lebih dari 1 mesin, alias di komputer cluster.

Masalahnya, menggunakan mode swarm tidak semudah simsalabim out-of-the-box automusically kelar! Dan masalah yang paling sering ditemukan (atau gue aja?) adalah terkait volume.

Yang mana menjadi topik utama dari tulisan ini.

Docker volume

Docker memiliki solusi untuk melakukan persistensi data yang berada di container ke host.

Cara nya ada 2: Yang pertama yang umum adalah melakukan "binding" dari host ke container yang biasanya menggunakan sintaks -v /source:/dest yang mana source tersebut ada di host kita dan dest tersebut di container.

Perubahan data yang terjadi di /dest dalam container kita, akan disimpan di /source dalam host kita.

Sederhana dan mudah.

Cara kedua adalah dengan melakukan "mounting" yang mana hampir mirip mekanisme nya seperti diatas, namun volume diatur oleh Docker.

Yang singkatnya, bila /source diatas adalah berdasarkan konfigurasi yang kita lakukan, bila menggunakan mounting, kita hanya mendefinisikan volume apa yang akan kita gunakan, dan biar docker yang mengatur.

Ini adalah contoh untuk mounting volume:

Lihat bagaimana docker melakukan "mapping" dari volume dengan key konten ke /var/lib/docker/volumes/konten.

Salah satu keuntungan menggunakan volume adalah mengurangi terjadinya error ketika binding direktori dari host ke container.

Menggunakan Binding

Kita memiliki container yang menjalankan image Nginx.

Nginx "meng-serve" dokumen ke client biasanya via direktori /usr/share/nginx/html. Jika kita memiliki direktori curahatan di host kita yang berisi berkas index.html dan ingin si Nginx ini meng-serve nya, kita bisa binding dengan menggunakan opsi -v /home/curhatan/:/usr/share/nginx/html maka berkas index.html kita tadi akan diberikan ke client.

Menggunakan binding adalah cara yang paling sederhana untuk menjaga data yang ada di container kita tidak hilang misalnya jika kita secara iseng tidak sadar melakukan docker rm <id> yang pastinya terlalu nganggur untuk dilakukan.

Menggunakan Volume

Ini cara yang lebih "elegan" dibanding cara sebelumnya.

Pertama, tidak perlu khawatir akan terjadinya kesalahan dalam "membawa" sumber direktori dari host ke container, itu tanggung jawab Docker.

Kedua, jika kita menggunakan mode swarm, kita "setidaknya" tidak perlu khawatir akan terjadi error hanya karena host lain tidak memiliki direktori /source sebagaimana host pertama.

Ketiga, anggap docker volume ini sebagai abstraksi dalam sistem penyimpanan.

Ketika volume menjadi masalah (apalagi binding)

Anggap kita memiliki aplikasi "pastebin" yang misalnya data paste tersebut disimpan di file system. Kita menggunakan docker volume disini, demi mengurangi kemungkinan masalah yang ada.

Kita deploy aplikasi tersebut dalam mode replika, yang mana 3 berada di manager dan 2 berada di worker.

Sebagai ilustrasi saja.

Masalahnya, ketika kita membuat paste baru, misal dengan id s3nd-nud3s. Ketika pertama kali akses /s3nd-nud3s, kita mendapatkan paste yang kita inginkan. Ketika kedua kali, masih tidak ada error.

Sampai ke yang ke-empat kali.

Karena singkatnya, berkas s3nd-nud3s.txt hanya berada di host 1, sedangkan di request keempat container yang melayani request tersebut berasal dari host 2.

Ini sebagai contoh saja—yang mungkin aplikasi seperti ini umum dibuat—yang mana meng-ilustrasikan aplikasi ini "stateful" yang sederhananya bergantung dengan berkas yang ada di file system.

Network File System

Solusi pertama untuk menyelesaikan masalah diatas adalah menggunakan NFS (Network File System), dan layanan populer yang digunakan biasanya adalah GlusterFS (Red Hat) dan Ceph (Ceph Foundation).

Biaya yang lumayan mahal dari menggunakan NFS adalah I/O latensi.

Dan juga, kurang cocok untuk digunakan di lingkungan container.

Namun jika I/O Latency bukanlah hal yang besar untukmu, kamu bisa coba menggunakan NFS-based solution tersebut!

3rd-party solution

Beberapa cloud provider memberikan layanan untuk membuat hal diatas menjadi lebih mudah dan juga dapat diandalkan.

Misal, seperti EFS (Elastic File System) nya AWS ataupun Filestore nya GCP yang memudahkan kita untuk melakukan "mounting drive" ke berbagai instance yang kita gunakan.

Tentunya dengan latensi yang rendah, dan biaya yang ehm yaaa akan selalu ada harga yang harus dibayar untuk segala sesuatu, khususnya suatu kelebihan.

Oke, 2 solusi yang disebutkan diatas itu bergantung ke cloud provider. Dan kita tidak bisa menggunakan layanan (serupa) tersebut, bila cloud provider yang kita gunakan tidak menyediakannya.

Seperti yang terjadi oleh gue.

Rsync

Sebenarnya masalah yang ada dalam kasus ini adalah: Bagaimana data yang ada konsisten disetiap host.

Kita tidak akan berbicara seputar replicated ataupun striped (chunked) dalam teknik menyimpan berkas di cluster, karena masalah seputar konsistensi saja sudah bikin ribet.

Ada tool bernama rsync (Remote Sync) yang mana bertugas untuk melakukan singkronisasi antar host dengan cara yang efisien. Pikirkan seperti cara kerja Dropbox.

Jika dipikir-pikir, rsync bisa menyelesaikan masalah kita sebelumnya, karena tugasnya melakukan sinkronisasi, bukan?

Tapi, enggak mungkin dong masa setiap 1-5 detik kita mencoba melakukan sinkronisasi via cronjob misalnya meskipun yaa why not sih.

Juga, bila kita menggunakan protokol rsync://, maka proses sinkronisasi tidak berada dalam jaringan yang ter-enkripsi, kecuali via format hostname:/path yang mana menggunakan protokol ssh.

Kita jadikan PR terlebih dahulu seputar rsync, karena gue dapet ide gila™ lain.

What if

Kita menggunakan torrent?

Ada yang pernah bermain dengan torrent?

Jika sudah pernah, pasti tau sedikit dong cara kerja "torrenting" ini?

Sederhananya, dalam melakukan torrenting proses ini menggunakan jaringan decentralized ataupun distributed, yang singkatnya berarti cocok untuk sistem ter-distrbusi ini, kan?

Ya, that P2P thing.

Dalam melakukan torrenting, ada yang namanya seeder dan leecher. Singkatnya, leecher ini adalah si tukang "download" dan seeder ini si tukang "membuat download menjadi lebih efisien".

Seeder ini memiliki salinan berkas yang dimaksud, dan setiap leecher akan menjadi seeder juga (jika ingin) karena sederhananya, dia memiliki salinan berkasnya juga, kan?

Agar lebih mudah (dan lebih keren), kita buat visualisasinya:

Visualiasi diatas adalah menggambarkan tentang bagaimana PC-3 dan PC-5 ingin "memiliki" berkas bernama bandung_lautan_asmara.3gp.

Disitu, PC-1, PC-2, dan PC-7 memiliki berkasnya, jadi, PC-3 dan PC-5 mengunduh berkas tersebut se-efisien mungkin dengan mempertimbangkan letak geografis dan pembagian bandwidth ke 3 host tersebut.

Jika berkas tersebut berukuran 3GB, kasarnya, per-host terdekat akan masing-masing memberikan 1.5GB potongan berkas dan si PC-6 yang letaknya lumayan jauh dari Leecher kebagian 500MB sisanya.

Tentu ini bisa menghemat bandwidth antar host, dan juga proses download bisa menjadi lebih cepat. Yang singkatnya, semakin banyak host semakin efisien proses sinkronisasi tersebut (misal tiap host yang ada diatas memiliki berkas yang diinginkan).

Sekarang, mari kita pikirkan implementasinya di docker swarm misalnya:

Oh iya perlu dicatat bahwa di konsep "torrenting" ini ada yang bernama "tracker" yang singkatnya anggap saja sebagai "registry" terkait proses pertukaran informasi terkait berkas yang ingin diminta.

Oke, jika torrenting biasanya kita ingin mendapatkan berkas berdasarkan "magnet link" yang ada, dalam kasus ini sepertinya kurang cocok bila menggunakan magnet link tersebut.

Nah, bila NFS adalah tentang melakukan mounting storage ke berbagai komputer, rsync adalah tentang melakukan sinkronisasi antar host, si torrenting ini antar host juga namun lebih efisien (dan berjalan terus-menerus).

Kekurangan dari rsync adalah proses tidak berjalan secara terus menerus, dan torrenting hanya berlaku untuk berkas-berkas yang dimaksud.

Bagaimana bila kita gabungkan rsync dengan proses torrenting ini?

Atau, mau sekaligus dengan NFS juga?

Konsepnya begini bila rsync + torrenting, setiap host memiliki "container" yang berjalan untuk melakukan sinkronisasi direktori:

Yang mana bila salah satu host terjadi perubahan data yang ada di /var/something/shared, maka host lain akan mendapatkan "pemberitahuan" dan akan melakukan sinkronisasi sesegera mungkin.

Dan ya, biasanya bandwidth yang diberikan oleh cloud provider kita lumayan besar dengan koneksi internet yang lumayan cepat, mungkin menggunakan private IP bisa membuat proses ini menjadi lebih efisien lagi walau belum gue coba.

Bagaimana bila menggunakan "konsep" NFS?

Hampir serupa dengan gambaran sebelumnya, tapi, disini container connect nya ke container lain terlebih dahulu, baru container tersebut lah yang connect ke /var/something/shared yang dimaksud.

Mulai ber-eksperimen

Disini gue akan coba sinkronisasi berkas sebesar ~304M (isinya cuma hasil piping dari yes) dan akan melihat berapa waktu yang dibutuhkan untuk mengsingkronisasi berkas tersebut ke 3 host (2 memiliki public IP yang 1 dari laptop gue).

Pilihan bisa menggunakan bittorrent sync, tapi karena tidak open source mari kita gunakan syncthing yang keren banget ini.

Kita buat terlebih dahulu direktori mana yang ingin kita bagikan, lalu menjalankan container syncthing ini.

$ mkdir -p tmp/ya
$ docker run \
  -v ~/tmp/ya:/var/syncthing \
  -p 8384:8384 -p 22000:22000
  syncthing/syncthing:latest
$ yes > yes

Karena ini belum berarti apa-apa, mari kita jalankan ini di server gue juga dengan perintah diatas selain bagian yes kebawah.

Bagian "source" diatas (~/tmp/ya) bisa menggunakan apa saja, karena yang penting adalah isi dari /var/syncthing tersebut.

Kita harus menghubungkan mesin kita terlebih dahulu, via "secret key". Disini kita pakai GUI terlebih dahulu biar lebih gampang. Berdasarkan log yang ada di stdout, gue memiliki log berikut:

[start] 05:27:45 INFO: syncthing v1.5.0 "Fermium Flea" (go1.13.10 linux-amd64) docker@syncthing.net 2020-04-21 20:45:03 UTC
[MQXOD] 05:27:46 INFO: My ID: MQXODCY-L5JJTQ4-ALPLVCY-XSJ74AJ-GTXPK5K-B45JVBB-JVK2HU4-BFULTQP
[MQXOD] 05:27:47 INFO: Single thread SHA256 performance is 192 MB/s using minio/sha256-simd (170 MB/s using crypto/sha256).
[MQXOD] 05:27:48 INFO: Hashing performance is 150.30 MB/s
[MQXOD] 05:27:48 INFO: Overall send rate is unlimited, receive rate is unlimited
...redacated...

Kita fokus ke bagian My ID nya saja, karena itu yang dibutuhkan untuk menghubungkan antar host.

Tambahkan device tersebut ke host yang kita inginkan, dan disini kita bisa lakukan via GUI dengan akses localhost:8384. Jika menggunakan publik IP, silahkan ganti localhost tersebut.

Lalu di pojok kanan bawah, ada tombol Add Remote Device yang nantinya diisi dengan ID yang kita bahas sebelumnya, dan akan berada di mode "unused" karena kita tidak melakukan sharing/seeding apapun.

Sekarang kita aktifkan Sharing, bisa di cek di Default Folder > Edit > Sharing > Checklist host yang sudah terhubung.

Agar lebih mempersingkat waktu, mari kita mulai singkronisasi dengan mengetikkan perintah berikut:

$ mv yes ~/tmp/yes/Sync

Nantinya akan dapat prompt bila pertama kali bahwa si host A ingin "mengakses" host B sebagai konfirmasi.

Tergantung kecepatan antar host, gue pertama coba dari local ke server gue yang ada di singapore, dengan statistik seperti ini (di yang local gue):

  • Download Rate: 248 KiB/s (28.7 MiB)
  • Upload Rate: 77 B/s (20.7 KiB)

Kecepatan diatas dapat dimengerti mengingat ISP yang gue pakai itu...

Dan menghabiskan ~35m untuk singkronisasi dari server 1 gue ke laptop gue.

Untuk dari server 1 ke server 2 (sama-sama singapore), dengan statistik seperti ini (di server 1 gue):

  • Download Rate 0 B/s: gak ke capture! (290 MiB)
  • Upload Rate: 0 B/s: gak ke capture juga! (593 MiB)

Hanya memakan ~3s yang bahkan gue enggak sempet capture itu berapa bps nya hahaha.

Penutup

Pastinya ada latensi, dan pastinya juga sangat kecil.

Sepengelaman gue (sinkronisasi TLS certificates, appendonly.aof nya Redis ~80M, berkas-berkas konfigurasi) tidak terasa adanya latensi. Terlebih bukan data yang sifatnya banyak operasi read, jadi kemungkinan "keterbedaan" minim disini.

Dan gue rasa kalau perbedaannya cuma di appendonly.aof nya Redis enggak terlalu bermasalah, mengingat Redis biasanya digunakan untuk melakukan hal-hal terkait caching :))

Ini menarik, hal yang belum gue lakukan adalah berkomunikasi via private IP dan "mereplika" layanan minio gue yang memiliki total ukuran ~2.2G.

Masalah gue seputar per-volume-an di docker swarm terjawab sudah, setidaknya untuk saat ini. Disini gue tidak menggunakan etcd karena gue bukan hanya ingin "mendistribusikan" hal-hal terkait konfigurasi aja.

Gue memilih syncthing karena dia fokus ke keamanan, kemudahan, dan pastinya karena Open Source, tidak seperti Bittorrent Sync.

Terima kasih OSS!

Ini adalah salah satu cara yang gue pakai untuk melakukan replika terhadap penyimpanan, untuk orang miskin. Semoga bisa membantu kamu juga bila memiliki masalah (dan budget serta kebutuhan) yang sama.

Terima kasih!

Menikmati tulisan ini?

Blog ini tidak menampilkan iklan, yang berarti blog ini didanai oleh pembaca seperti kamu. Gabung bersama yang telah membantu blog ini agar terus bisa mencakup tulisan yang lebih berkualitas dan bermanfaat!

Pendukung

Kamu?