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:

bash
$ ssh [email protected]
$ docker swarm init --advertise-addr 192.168.1.10

Output yang Dihasilkan:

text
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:

  1. --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.
  2. Node ID owjil8f4rrsa8t9dtc5fyht0y sepanjang 25 karakter — identitas node di cluster; nama host tetap dipakai untuk tampilan, tetapi pembukuan internal memakai id ini.
  3. Port 2377 — pintu manajemen cluster (join, pembukuan Raft); port container yang kita publish tidak pernah lewat sini.
  4. Token dengan format SWMTKN-1-<49 karakter>-<25 karakter> — bagian pertama mengidentifikasi cluster, bagian kedua adalah secret pendaftaran; token versi worker ini hanya mendaftarkan node sebagai worker.
  5. 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:

bash
$ ssh [email protected]
$ docker swarm join --token SWMTKN-1-vdbsfa6lpr1x9r8e05ywzpzkh65n7sl5u9kay5in0xn5qcp63-6s2fptnklepx18byw5k8qfg3n 192.168.1.10:2377

Output yang Dihasilkan:

text
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:

bash
$ docker node ls

Output yang Dihasilkan:

text
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.0

Mari kita telaah tabel node tersebut:

  1. Tanda * dan Leader — perintah manajemen cluster dijalankan dari manager-01; node worker tidak bisa menjalankan docker node ls sama sekali, karena pembukuan cluster hanya hidup di manager.
  2. STATUS Ready pada ketiganya — node sehat dan terhubung; node yang kehilangan kontak akan tampil Down setelah masa tenggang.
  3. AVAILABILITY Active — node bersedia menerima task; Drain mengosongkan node untuk maintenance, dan reconciliation loop yang memindahkan task-tasknya — persis skenario maintenance di chapter sebelumnya.
  4. ENGINE 29.9.0 seragam — 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:

bash
$ docker service create --name web --replicas 3 -p 8080:80 nginx

Output yang Dihasilkan:

text
overall progress: 3 out of 3 tasks
1/3: running   [=================================================>]
2/3: running   [=================================================>]
3/3: running   [=================================================>]
verify: Service converged

Mari kita telaah empat baris kemajuan tersebut:

  1. --replicas 3 adalah deklarasi, bukan perintah eksekusi — kita tidak menyebut node mana pun; penjadwal di manager yang memutuskan penempatan tiap task.
  2. 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.
  3. verify: Service converged — keadaan aktual menyamai keadaan yang diinginkan; inilah kalimat resmi bahwa declarative sudah terpenuhi.
  4. -p 8080:80 berlaku 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:

bash
$ docker service ls

Output yang Dihasilkan:

text
ID             NAME      MODE         REPLICAS   IMAGE          PORTS
ejh2g587bb61   web       replicated   3/3        nginx:latest   *:8080->80/tcp
bash
$ docker service ps web

Output yang Dihasilkan:

text
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 ago

Mari kita telaah dua tabel tersebut bersama:

  1. MODE replicated — jumlah replika ditentukan angka; mode kembarannya global menjalankan tepat satu task di setiap node, cocok untuk agent monitoring yang memang harus hadir di semua mesin.
  2. REPLICAS 3/3 — pembilang adalah task yang hidup, penyebut adalah desired state; 2/3 berarti loop sedang bekerja mencarikan pengganti.
  3. *:8080->80/tcp — tanda bintang menandakan port diterbitkan di seluruh node cluster, bukan di satu alamat tertentu.
  4. web.1 sampai web.3 tersebar di tiga node — penjadwal sengaja tidak menumpuk task satu service di satu node, karena satu node tumbang tidak boleh membawa serta seluruh service.
  5. Kolom DESIRED STATE dan CURRENT STATE berdampingan — reconciliation loop yang abstrak tadi kini punya dua kolom nyata; bila worker-01 mati, baris web.2 akan berakhir Shutdown dan baris pengganti muncul di node lain dengan status Running.

Log seluruh replika juga bisa dibaca dari satu titik:

bash
$ docker service logs web --tail 5

Output yang Dihasilkan:

text
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:

bash
$ 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:

text
200
200
200

Ketiga 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:

yaml
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:

  1. image: registry.example.com/toko/backend:1.5.0 — image yang sama persis yang dibangun pipeline CI dan berjalan di srv-toko; tidak ada build ulang, tidak ada image khusus cluster.
  2. deploy.replicas: 3 — desired state tiga replika backend; kunci ini hanya dibaca mode Swarm.
  3. deploy.resources.limits — pagar cpus 0.50 dan memory 256M per task ditegakkan cgroups di node tempat task mendarat, persis pembagian kerja limits versus reservations yang dibedah Chapter Resource di Swarm.
  4. networks: toko-net tanpa definisi driver — pada docker stack deploy, network ini otomatis lahir sebagai overlay lintas node; nama service db kembali bisa dipecahkan DNS, kali ini oleh DNS internal cluster.
  5. 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:

bash
$ docker stack deploy -c compose.yaml toko

Output yang Dihasilkan:

text
Creating network toko_toko-net
Creating service toko_web
Creating service toko_db

Nama stack toko menjadi prefix seluruh identitas — network toko_toko-net, service toko_web dan toko_db. Cluster kini memegang dua keluarga service:

bash
$ docker service ls

Output yang Dihasilkan:

text
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.0

Satu 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:

bash
$ docker service update --image registry.example.com/toko/backend:1.6.0 toko_web

Output yang Dihasilkan:

text
toko_web
overall progress: 3 out of 3 tasks
1/3: running   [=================================================>]
2/3: running   [=================================================>]
3/3: running   [=================================================>]
verify: Service converged

Update 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:

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-first

Mari 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:

bash
$ docker service scale toko_web=5

Output yang Dihasilkan:

text
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 converged

Dua 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 #

PerintahPeran
docker swarm init --advertise-addr <ip>membentuk cluster dan mengangkat node ini sebagai manager pertama
docker swarm join --token <token> <ip>:2377mendaftarkan node ke cluster sebagai worker atau manager
docker swarm join-token workermenampilkan ulang token join — tambahkan --rotate untuk membatalkan yang lama
docker node lsdaftar node beserta status, availability, dan peran manajemennya
docker node update --availability drain <node>mengosongkan node untuk maintenance; task-nya dipindahkan otomatis
docker service createmendeklarasikan 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 / rollbackmengubah 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 lsmenyimpan 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 #