Teori desired state dan reconciliation loop baru terasa nyata ketika kita melihatnya bekerja pada mesin sungguhan. Tiga VPS Budi sudah menyala dengan Docker Engine 29.9.0 di masing-masing mesin, dan kabar terbaiknya: tidak ada perangkat lunak baru yang perlu dipasang. Swarm mode hidup di dalam daemon yang sejak Section Instalasi dan Konfigurasi kita kenal — satu flag saat inisialisasi mengubah kumpulan mesin menjadi satu cluster dengan penjadwal, service discovery, dan load balancing bawaan. Chapter ini membangun cluster manager-01, worker-01, dan worker-02 dari nol, menjalankan service pertama, membuktikan routing mesh dengan tiga curl, lalu mendeploy stack toko versi ringkas dan me-rolling-nya dari 1.5.0 ke 1.6.0 tanpa downtime.
Membangun Cluster Tiga Node #
Inisialisasi di manager-01 #
Swarm dibangunkan dari satu node yang mengangkat dirinya sebagai manager pertama. Budi masuk ke manager-01 dan menjalankan satu perintah:
$ ssh [email protected]
$ docker swarm init --advertise-addr 192.168.1.10Output yang Dihasilkan:
Swarm initialized: current node (owjil8f4rrsa8t9dtc5fyht0y) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-vdbsfa6lpr1x9r8e05ywzpzkh65n7sl5u9kay5in0xn5qcp63-6s2fptnklepx18byw5k8qfg3n 192.168.1.10:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.Mari kita telaah keluaran tersebut:
--advertise-addr 192.168.1.10— manager kerap punya lebih dari satu antarmuka jaringan, dan cluster wajib tahu alamat mana yang boleh dihubungi node lain; kita menunjuknya secara eksplisit supaya Swarm tidak menebak.- Node ID
owjil8f4rrsa8t9dtc5fyht0ysepanjang 25 karakter — identitas node di cluster; nama host tetap dipakai untuk tampilan, tetapi pembukuan internal memakai id ini. - Port
2377— pintu manajemen cluster (join, pembukuan Raft); port container yang kita publish tidak pernah lewat sini. - Token dengan format
SWMTKN-1-<49 karakter>-<25 karakter>— bagian pertama mengidentifikasi cluster, bagian kedua adalah secret pendaftaran; token versiworkerini hanya mendaftarkan node sebagai worker. - Satu manager berarti satu titik kegagalan cluster — cukup untuk latihan; produksi yang serius menambahkan manager hingga jumlah ganjil (tiga atau lima) demi quorum yang kita bahas di chapter sebelumnya.
Token join adalah kredensial: siapa pun yang memilikinya bisa mendaftarkan mesin ke cluster kita. Simpan sebagaimana kita menyimpan password — jangan ditempel di ruang obrolan tim — dan bila mencurigakan kebocoran, rotasikan dengan docker swarm join-token --rotate worker yang langsung membatalkan token lama.
Menjadi Anggota: join di worker #
Dua node lain mendaftarkan diri dengan perintah dari keluaran tadi:
$ ssh [email protected]
$ docker swarm join --token SWMTKN-1-vdbsfa6lpr1x9r8e05ywzpzkh65n7sl5u9kay5in0xn5qcp63-6s2fptnklepx18byw5k8qfg3n 192.168.1.10:2377Output yang Dihasilkan:
This node joined a swarm as a worker.Perintah yang sama dijalankan di worker-02 (192.168.1.12), dan cluster pun beranggota tiga. Kembali ke manager-01, Budi memeriksa keadaan cluster:
$ docker node lsOutput yang Dihasilkan:
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE
owjil8f4rrsa8t9dtc5fyht0y * manager-01 Ready Active Leader 29.9.0
lrumis21glhi3tt92ckyfdup5 worker-01 Ready Active 29.9.0
pwg1xjxwth3000zfk67wkbe7q worker-02 Ready Active 29.9.0Mari kita telaah tabel node tersebut:
- Tanda
*danLeader— perintah manajemen cluster dijalankan darimanager-01; node worker tidak bisa menjalankandocker node lssama sekali, karena pembukuan cluster hanya hidup di manager. STATUS Readypada ketiganya — node sehat dan terhubung; node yang kehilangan kontak akan tampilDownsetelah masa tenggang.AVAILABILITY Active— node bersedia menerima task;Drainmengosongkan node untuk maintenance, dan reconciliation loop yang memindahkan task-tasknya — persis skenario maintenance di chapter sebelumnya.ENGINE 29.9.0seragam — cluster paling sehat saat seluruh node menjalankan versi Engine yang sama; selisih versi besar antar node adalah sumber kejutan yang tidak kita cari.
Service: Deklarasi Replika #
Di atas cluster berdiri konsep service: deklarasi desired state, bukan container. Budi melahirkan service pertama — tiga replika nginx yang menerbitkan port 8080:
$ docker service create --name web --replicas 3 -p 8080:80 nginxOutput yang Dihasilkan:
overall progress: 3 out of 3 tasks
1/3: running [=================================================>]
2/3: running [=================================================>]
3/3: running [=================================================>]
verify: Service convergedMari kita telaah empat baris kemajuan tersebut:
--replicas 3adalah deklarasi, bukan perintah eksekusi — kita tidak menyebut node mana pun; penjadwal di manager yang memutuskan penempatan tiap task.1/3,2/3,3/3— masing-masing task dijadwalkan, ditarik imagenya, dan dihidupkan di node pilihan penjadwal; kemajuan ini adalah reconciliation loop yang bekerja di depan mata.verify: Service converged— keadaan aktual menyamai keadaan yang diinginkan; inilah kalimat resmi bahwa declarative sudah terpenuhi.-p 8080:80berlaku untuk seluruh cluster — port diterbitkan di semua node lewat routing mesh yang kita bedah segera setelah ini, bukan hanya di node tempat task berada.
Ringkasan cluster dibaca dari dua perintah bertingkat:
$ docker service lsOutput yang Dihasilkan:
ID NAME MODE REPLICAS IMAGE PORTS
ejh2g587bb61 web replicated 3/3 nginx:latest *:8080->80/tcp$ docker service ps webOutput yang Dihasilkan:
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
a1l6auui6mg5 web.1 nginx:latest manager-01 Running Running 4 minutes ago
fyoftjbokusv web.2 nginx:latest worker-01 Running Running 4 minutes ago
zr6atz51jyf2 web.3 nginx:latest worker-02 Running Running 4 minutes agoMari kita telaah dua tabel tersebut bersama:
MODE replicated— jumlah replika ditentukan angka; mode kembarannyaglobalmenjalankan tepat satu task di setiap node, cocok untuk agent monitoring yang memang harus hadir di semua mesin.REPLICAS 3/3— pembilang adalah task yang hidup, penyebut adalah desired state;2/3berarti loop sedang bekerja mencarikan pengganti.*:8080->80/tcp— tanda bintang menandakan port diterbitkan di seluruh node cluster, bukan di satu alamat tertentu.web.1sampaiweb.3tersebar di tiga node — penjadwal sengaja tidak menumpuk task satu service di satu node, karena satu node tumbang tidak boleh membawa serta seluruh service.- Kolom
DESIRED STATEdanCURRENT STATEberdampingan — reconciliation loop yang abstrak tadi kini punya dua kolom nyata; bilaworker-01mati, barisweb.2akan berakhirShutdowndan baris pengganti muncul di node lain dengan statusRunning.
Log seluruh replika juga bisa dibaca dari satu titik:
$ docker service logs web --tail 5Output yang Dihasilkan:
web.1.a1l6auui6mg5@manager-01 | 2026/11/02 08:41:12 [notice] 1#1: start worker processes
web.2.fyoftjbokusv@worker-01 | 192.168.1.10 - - [02/Nov/2026:08:43:51 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.14.1" "-"
web.3.zr6atz51jyf2@worker-02 | 192.168.1.10 - - [02/Nov/2026:08:43:55 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.14.1" "-"
web.2.fyoftjbokusv@worker-01 | 192.168.1.11 - - [02/Nov/2026:08:44:02 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.14.1" "-"
web.3.zr6atz51jyf2@worker-02 | 192.168.1.12 - - [02/Nov/2026:08:44:07 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.14.1" "-"Prefiks web.N.<task>@<node> adalah pembeda utamanya: docker service logs menggabungkan log seluruh task lintas node menjadi satu aliran, dan setiap baris menandai asalnya. Kelima baris itu juga menyimpan bukti untuk bagian berikutnya — permintaan dari tiga alamat berbeda dijawab oleh dua task yang berbeda.
Routing Mesh: Port yang Hidup di Semua Node #
Tiga curl dari mesin Budi menutup pembuktian routing mesh:
$ curl -fsS -o /dev/null -w "%{http_code}\n" http://192.168.1.10:8080/
$ curl -fsS -o /dev/null -w "%{http_code}\n" http://192.168.1.11:8080/
$ curl -fsS -o /dev/null -w "%{http_code}\n" http://192.168.1.12:8080/Output yang Dihasilkan:
200
200
200Ketiga jawaban 200 itu bukan datang dari mesin yang kebetulan memegang task. Swarm membentuk satu network overlay bernama ingress yang membentang ke seluruh node, dan setiap node yang menerima trafik pada port 8080 memasukkannya ke jaringan itu; di dalamnya, load balancing berbasis IPVS di kernel membagi trafik ke task mana pun yang sedang hidup, di node mana pun. Buktinya terbaca pada log di atas: permintaan dari 192.168.1.10 dijawab oleh task di worker-01 dan worker-02 — pintu 8080 terbuka untuk service, bukan untuk mesin tertentu.
Konsekuensinya perlu dicatat jujur: port yang diterbitkan service menjadi milik cluster. Service kedua yang mencoba menerbitkan 8080 akan ditolak dengan galat alokasi yang kita kenal — Bind for 0.0.0.0:8080 failed: port is already allocated — karena satu pintu 8080 milik cluster hanya boleh dipegang satu service. Pertemuan dua service pada satu port harus diselesaikan di lapisan aplikasi, misalnya lewat path di belakang satu reverse proxy, persis pola satu pintu yang toko kita pakai sejak Chapter Reverse Proxy.
Stack Deploy untuk Toko #
Service demi service dideklarasikan dengan perintah tunggal akan melelahkan; docker stack deploy memakai compose.yaml yang sudah kita kuasai. Budi menyiapkan versi ringkas stack toko untuk cluster:
services:
web:
image: registry.example.com/toko/backend:1.5.0
environment:
DATABASE_URL: "postgres://toko:rahasia123@db:5432/toko"
networks:
- toko-net
deploy:
replicas: 3
resources:
limits:
cpus: "0.50"
memory: 256M
db:
image: postgres:18-alpine
environment:
POSTGRES_USER: toko
POSTGRES_PASSWORD: rahasia123
POSTGRES_DB: toko
volumes:
- db-data:/var/lib/postgresql/data
networks:
- toko-net
volumes:
db-data:
networks:
toko-net:Mari kita telaah keputusan-keputusan berkas tersebut:
image: registry.example.com/toko/backend:1.5.0— image yang sama persis yang dibangun pipeline CI dan berjalan disrv-toko; tidak ada build ulang, tidak ada image khusus cluster.deploy.replicas: 3— desired state tiga replika backend; kunci ini hanya dibaca mode Swarm.deploy.resources.limits— pagarcpus 0.50danmemory 256Mper task ditegakkan cgroups di node tempat task mendarat, persis pembagian kerjalimitsversusreservationsyang dibedah Chapter Resource di Swarm.networks: toko-nettanpa definisi driver — padadocker stack deploy, network ini otomatis lahir sebagai overlay lintas node; nama servicedbkembali bisa dipecahkan DNS, kali ini oleh DNS internal cluster.volumes: db-data— volume menempel di node tempat task db mendarat; memindahkan data lintas node adalah pekerjaan tersendiri yang tidak kita dalami hari ini.
Deploy berlangsung dari manager-01:
$ docker stack deploy -c compose.yaml tokoOutput yang Dihasilkan:
Creating network toko_toko-net
Creating service toko_web
Creating service toko_dbNama stack toko menjadi prefix seluruh identitas — network toko_toko-net, service toko_web dan toko_db. Cluster kini memegang dua keluarga service:
$ docker service lsOutput yang Dihasilkan:
ID NAME MODE REPLICAS IMAGE PORTS
ejh2g587bb61 web replicated 3/3 nginx:latest *:8080->80/tcp
lly2zr7jf60x toko_db replicated 1/1 postgres:18-alpine
e1zkyg5p0apb toko_web replicated 3/3 registry.example.com/toko/backend:1.5.0Satu kejujuran sebelum melanjutkan: POSTGRES_PASSWORD: rahasia123 yang polos di environment memang cara paling cepat untuk latihan, tetapi Chapter Secret Management sudah menunjukkan harganya — nilai environment dapat dibaca docker inspect oleh siapa pun yang memegang socket. Produksi di cluster seharusnya memakai docker secret create, yang mendistribusikan rahasia terenkripsi ke node-node yang membutuhkan dan mengeksposnya sebagai berkas di /run/secrets/.
Rolling Update dan Scaling #
Rilis 1.6.0 tiba dari pipeline CI, dan di cluster ia menjadi perintah satu baris:
$ docker service update --image registry.example.com/toko/backend:1.6.0 toko_webOutput yang Dihasilkan:
toko_web
overall progress: 3 out of 3 tasks
1/3: running [=================================================>]
2/3: running [=================================================>]
3/3: running [=================================================>]
verify: Service convergedUpdate berjalan selang-seling: satu task dimatikan dan diganti task baru, trafik routing mesh mengalir ke replika lain selama pergantian, lalu giliran task berikutnya — tidak ada momen ketika seluruh service kosong sekaligus. Ritmenya diatur update_config, dan nilai bawaannya bisa kita pertimbangkan ulang di compose.yaml:
services:
web:
image: registry.example.com/toko/backend:1.6.0
networks:
- toko-net
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
order: start-firstMari kita telaah tiga kunci tersebut: parallelism: 1 mengganti satu task pada satu waktu — makin besar angkanya makin cepat sekaligus makin berisiko; delay: 10s memberi jeda observasi antar penggantian sehingga galat terdeteksi sebelum menyebar; order: start-first menghidupkan pengganti sebelum mendiamkan yang lama, kebalikan dari bawaan stop-first yang lebih hemat resource. Bila rilis baru ternyata buruk, docker service rollback toko_web memundurkan service ke versi image sebelumnya — padanan cepat dari ritual rollback Chapter Update dan Rollback.
Promosi memuncak, dan kapasitas ditambah dengan mengubah desired state:
$ docker service scale toko_web=5Output yang Dihasilkan:
toko_web scaled to 5
overall progress: 5 out of 5 tasks
1/5: running [=================================================>]
2/5: running [=================================================>]
3/5: running [=================================================>]
4/5: running [=================================================>]
5/5: running [=================================================>]
verify: Service convergedDua task baru dijadwalkan penjadwal ke node yang masih longgar, dan docker service ls kini menampilkan 5/5 — diubah kembali ke 3 setelah promo, loop juga yang menghentikan dua task ekstra. Inilah cabang “aktual lebih dari desired” pada diagram reconciliation di chapter sebelumnya, dikerjakan sungguhan.
Tabel Perintah Swarm #
| Perintah | Peran |
|---|---|
docker swarm init --advertise-addr <ip> | membentuk cluster dan mengangkat node ini sebagai manager pertama |
docker swarm join --token <token> <ip>:2377 | mendaftarkan node ke cluster sebagai worker atau manager |
docker swarm join-token worker | menampilkan ulang token join — tambahkan --rotate untuk membatalkan yang lama |
docker node ls | daftar node beserta status, availability, dan peran manajemennya |
docker node update --availability drain <node> | mengosongkan node untuk maintenance; task-nya dipindahkan otomatis |
docker service create | mendeklarasikan service beserta replika dan portnya |
docker service ls / docker service ps <svc> | memeriksa service dan task-tasknya per node |
docker service logs <svc> | menggabungkan log seluruh task lintas node |
docker service update / scale / rollback | mengubah desired state: image baru, jumlah replika, atau mundur |
docker stack deploy -c compose.yaml <nama> | mendeploy sekumpulan service dari compose.yaml |
docker stack ls / docker stack rm <nama> | daftar dan pembongkaran stack |
docker secret create / docker secret ls | menyimpan dan memeriksa rahasia terdistribusi cluster |
Cluster kita kini hidup: tiga node, satu stack toko, dan dua manuver produksi — rolling update dan scaling — yang dulu berdurasi runbook kini berdurasi satu perintah. Tetapi pasar kerja, penyedia cloud, dan sebagian besar percakapan industri menyebut nama lain. Kosakata yang kita bangun hari ini — desired state, service, task, reconciliation — tidak hangus; semuanya menanti terjemahannya. Chapter Konsep Kubernetes adalah kamus itu: pod, deployment, dan kubectl dibaca sebagai padanan langsung dari apa yang baru saja kita jalankan.
Referensi #
- Docker Swarm mode overview — arsitektur Swarm, node, service, dan routing mesh.
- Deploy services to a swarm
— service, task, dan perilaku
--replicasserta--update-*. - Deploy a stack to a swarm
— pembacaan kunci
deploydan pembentukan network overlay. - Compose file reference
— definisi resmi blok
deploybesertaupdate_config.