Mengisi cluster Swarm dengan service: hubungan Service-Task-Container, deployment lewat docker service dan docker stack, overlay network lintas node, Swarm secrets & configs, hingga rolling update dan rollback zero-downtime dengan kontrol parallelism dan delay.

Setelah di episode 16 sebelumnya kita berhasil menyatukan beberapa server menjadi satu cluster Swarm — manager dengan Raft consensus, worker, quorum, dan TLS antar node — pada episode kali ini kita mulai menggunakannya: mendefinisikan apa yang boleh berjalan di cluster, berapa banyak, di mana, dan bagaimana memperbaruinya tanpa membuat pengguna turun. Cluster tanpa service hanyalah kumpulan server yang saling menyapa; service-lah yang mengubahnya menjadi platform.
Pergeseran mental yang paling penting di episode ini: selama 15 episode, kalian berpikir dalam container — "saya menjalankan container web". Di Swarm, kalian berpikir dalam service — "saya menginginkan 3 replika web yang selalu ada". Container hanyalah hasil sampingan; yang kalian deklarasikan adalah keadaan yang diinginkan (desired state), dan Swarm yang bertugas mewujudkannya terus-menerus. Ini bukan sekadar gaya penulisan — ini filosofi orkestrasi yang membedakan "menjalankan aplikasi" dari "mengoperasikan aplikasi". Jika satu container mati, docker run tidak peduli; service Swarm akan segera membuat task pengganti.
Di episode ini kita akan membedah hubungan Service-Task-Container, men-deploy dengan docker service dan docker stack, menghubungkan kontainer lintas node dengan overlay network, menyimpan rahasia lewat Swarm secrets, dan menutup dengan yang paling bernilai di produksi: rolling update dan rollback zero-downtime. Siapkan cluster kalian dari episode 16 — semua contoh di sini berjalan di atasnya.
Sebelum menulis perintah, tetap ketiga tingkatan ini karena seluruh episode ini berdiri di atasnya:
New → Assigned → Preparing → Running → Complete/Shutdown/Failed.Analogi yang pas: service adalah blueprint perumahan (3 rumah, tipe A), task adalah lembar kerja untuk membangun satu rumah, dan container adalah rumah yang berdiri. Kalau satu rumah roboh, kontraktor (Swarm) mengambil lembar kerja itu dan membangunnya ulang — tanpa perlu blueprint baru.
Perintah pertama untuk mengisi cluster — dan perhatikan kemiripannya dengan docker run, hanya saja diakhiri dengan keinginan tentang jumlah replika:
docker service create \
--name web \
--replicas 3 \
-p 80:80 \
nginx:1.27-alpineSekarang periksa statusnya dari tiga sisi — service, task, dan log:
docker service ls
docker service ps webID NAME IMAGE NODE DESIRED STATE CURRENT STATE
v9... web.1 nginx:1.27-alpine node-1 Running Running about a minute ago
p2... web.2 nginx:1.27-alpine node-2 Running Running about a minute ago
q7... web.3 nginx:1.27-alpine node-3 Running Running about a minute agoLihat kolom NODE: ketiga task tersebar di tiga node berbeda — Swarm otomatis menyeimbangkan beban. Kini mari uji kekuatan orkestrasi: hentikan satu container dengan paksa, lalu perhatikan Swarm menggantikannya:
docker ps --filter name=web -q | head -1 | xargs docker kill
docker service ps webDalam hitungan detik, task baru web.4 muncul di node lain — dan jumlah replika kembali 3. Ini self-healing: karena kalian mendeklarasikan --replicas 3, Swarm menjadikannya kenyataan tanpa kalian campur tangan. Bayangkan harus melakukan ini manual dengan docker run di tengah malam — inilah alasan orkestrasi dibangun.
Perintah lain yang akan sering dipakai: docker service logs web (log semua task ter-agregat), docker service scale web=5 (ubah jumlah replika kapan saja), dan docker service rm web (hapus total):
docker service scale web=5
docker service logs --tail 50 web
docker service rm webTask web di node-1, task web di node-2, task web di node-3 — bagaimana mereka dan service lain saling berbicara? Jawabannya adalah overlay network: jaringan virtual yang membentang di atas semua node, tempat kontainer lintas node bisa saling memanggil seolah-olah berada di satu switch. Ingat kilasan driver overlay di episode 9 — kini ia menjadi nyata.
docker network create -d overlay my-net
docker service create \
--name api \
--network my-net \
--replicas 2 \
my-api:1.0Dua fakta penting tentang overlay: (1) ia bekerja hanya dalam lingkup Swarm — service non-swarm tidak bisa memakainya; (2) DNS berjalan lintas node, sehingga api bisa di-resolve dari task mana pun di node mana pun. Trafik antar node dienkapsulasi VXLAN lewat port 4789/udp (yang kita buka di episode 16). Kombinasi overlay + embedded DNS adalah alasan arsitektur microservice multi-host terasa "semudah localhost".
Menulis service satu per satu dengan docker service create terasa bertele-tele untuk aplikasi yang punya web, api, dan database. Solusinya: stack — deploy file Compose ke Swarm, dan Swarm menerjemahkan setiap service menjadi Swarm service. File Compose untuk Swarm memakai kunci deploy yang tidak berlaku di Compose biasa:
version: "3.9"
services:
web:
image: ghcr.io/arman/my-web:1.0.0
ports:
- "80:3000"
networks:
- app-net
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 20s
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
order: start-first
failure_action: rollback
rollback_config:
parallelism: 1
delay: 5s
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
api:
image: ghcr.io/arman/my-api:1.0.0
networks:
- app-net
deploy:
replicas: 2
resources:
limits:
cpus: "0.50"
memory: 256M
reservations:
cpus: "0.25"
memory: 128M
placement:
constraints:
- node.role == worker
restart_policy:
condition: on-failure
db:
image: postgres:16-alpine
volumes:
- db-data:/var/lib/postgresql/data
networks:
- app-net
environment:
POSTGRES_DB: appdb
POSTGRES_USER: app
secrets:
- db_password
deploy:
placement:
constraints:
- node.labels.db == true
restart_policy:
condition: on-failure
networks:
app-net:
driver: overlay
volumes:
db-data:
secrets:
db_password:
external: trueBedah kunci deploy — inilah yang membedakan stack dari compose biasa:
replicas — jumlah task yang diinginkan.update_config — cara service di-update: parallelism: 1 (satu task per satu), delay: 10s (jeda antar batch), order: start-first (task baru sehat dulu baru yang lama dimatikan — kunci zero-downtime), failure_action: rollback (otomatis kembali ke versi lama jika update gagal).rollback_config — cara rollback berjalan (mirip update, tapi mundur).restart_policy — kapan dan berapa kali task di-restart setelah gagal (on-failure, max_attempts).resources — limit dan reservasi CPU/memory per task (setara --memory/--cpus episode 25).placement — constraint di node mana task boleh dijadwalkan: node.role == worker atau label kustom (mis. node.labels.db == true, yang di-set dengan docker node update --label-add db=true node-3).Deploy dan kelola stack:
docker stack deploy -c docker-stack.yml my-stack
docker stack ls
docker stack ps my-stack
docker stack services my-stack
docker stack rm my-stackNama stack menjadi prefix service: my-stack_web, my-stack_api, my-stack_db. Untuk memperbarui service di dalam stack, cukup perbarui file dan jalankan docker stack deploy lagi — Swarm mendeteksi perbedaan dan hanya mengubah yang berubah.
Menyimpan password di environment file compose adalah kebocoran menunggu waktu. Swarm menawarkan secrets — data rahasia yang disimpan terenkripsi di state store, dikirim hanya ke node yang mendapat task, dan di-mount sebagai file di /run/secrets/<nama> di dalam container. Dua pendekatan pembuatan:
printf "P@ssw0rd-super-rahasia" | docker secret create db_password -
docker secret create tls_cert tls_cert.pemdocker service create \
--name api \
--secret db_password \
--secret source=tls_cert,target=cert.pem,mode=0400 \
my-api:1.0Di dalam container, aplikasi membaca password dari file /run/secrets/db_password — bukan dari environment variable. File secrets di-mount ke RAM, bukan disk container, dan tidak pernah terlihat di docker inspect. Perhatikan opsi target untuk mengganti nama file dan mode untuk mengontrol izinnya.
Configs adalah pasangan secrets untuk data yang tidak rahasia — misalnya konfigurasi nginx atau daftar service discovery:
docker config create nginx_conf nginx.conf
docker service create --name web --config nginx_conf nginx:1.27-alpineSekali lagi, file ter-mount di /run/secrets/nginx_conf di dalam container. Bedanya: config tidak dienkripsi di state store, karena isinya memang bukan rahasia. Aturan praktis: rahasia → secrets, bukan rahasia → configs.
Inilah momen puncaknya. Kalian punya my-stack_web berjalan 3 replika di versi 1.0.0, dan ingin naik ke 2.0.0 tanpa pengguna menyadarinya. Dengan docker service update, Swarm mengganti task satu per satu sesuai update_config — task baru menunggu healthcheck hijau sebelum task lama dihentikan (order: start-first):
docker service update \
--image ghcr.io/arman/my-web:2.0.0 \
--update-parallelism 1 \
--update-delay 10s \
my-stack_webBacanya: ganti image ke 2.0.0, satu task per batch, dengan jeda 10 detik antar batch. Load balancer Swarm secara otomatis hanya mengirim traffic ke task yang sehat — selama rotasi berjalan, selalu ada replika lama yang melayani sampai yang baru siap. Jika healthcheck task baru gagal berulang (ingat episode 15: HEALTHCHECK menentukan sehat-tidaknya), failure_action: rollback mengembalikan service ke image sebelumnya secara otomatis:
docker service rollback my-stack_webGunakan docker service ps my-stack_web untuk memantau setiap rotasi: setiap task yang gagal atau di-rollback meninggalkan jejak di kolom ERROR — sumber informasi emas saat update berantakan.
Tip
Kebiasaan yang menyelamatkan banyak tim: biasakan mendeploy dengan strategy yang sudah teruji di stack file (order: start-first, failure_action: rollback, parallelism: 1) daripada mengetik flag docker service update manual di production. Perintah manual rawan typo dan sulit direproduksi; file stack adalah dokumentasi sekaligus eksekutor yang konsisten.
Memakai depends_on di stack Swarm. depends_on (episode 10) tidak didukung dengan benar di Swarm mode — Swarm menangani urutan via healthcheck dan restart policy, bukan depends_on. Hapus depends_on dari stack file kalian.
docker run di dalam cluster. Kontainer docker run tidak dijadwalkan oleh Swarm dan tidak bisa memakai overlay network — ia hanya hidup di satu node. Semua workload yang "harus tersebar" wajib lewat service.
Update langsung menuju image yang tidak ada. Swarm tidak menarik image sebelum men-drain task lama. Task baru gagal, healthcheck gagal, dan failure_action: rollback menyelamatkan hari — tapi kalian tidak akan melihat warning ini kalau update_config tidak disetel. Selalu definisikan update config di stack.
Secret dibaca lewat env. Secret yang di-mount di /run/secrets tidak otomatis menjadi env var. Aplikasi harus membaca file, bukan membaca process.env. Pastikan kode aplikasi disesuaikan saat migrasi dari env ke secrets.
Lupa docker stack rm saat demo selesai. Stack yang tidak di-rm meninggalkan service, network, dan secret yang terus berjalan. Bersihkan dengan docker stack rm <nama> agar cluster tidak penuh resource.
Pada episode 17 ini kita telah mengisi cluster dengan sesuatu yang berharga: memahami hierarki Service → Task → Container dan pergeseran dari mentalitas docker run ke deklarasi desired state, men-deploy dengan docker service create/ls/ps/logs/scale/rm, menghubungkan service lintas node lewat overlay network dengan DNS lintas node, mendeploy aplikasi utuh lewat docker stack deploy -c docker-stack.yml dengan kunci deploy (replicas, resources, placement, update_config, restart_policy), menyimpan rahasia dengan Swarm secrets dan konfigurasi dengan configs (di-mount di /run/secrets), dan melepaskan versi baru tanpa downtime lewat rolling update (--update-parallelism, --update-delay, failure_action) serta rollback otomatis maupun manual.
Inti yang harus kalian bawa:
deploy: di stack file adalah kunci produksi: update order, failure action, resources, placement.start-first + healthcheck = zero-downtime; failure_action: rollback = jaring pengaman otomatis.Cluster sekarang hidup, berisi service, dan bisa di-update tanpa downtime. Tapi ada satu lengan yang belum kita kerahkan: pembangunan image itu sendiri masih memakai builder lama yang lambat dan terbatas. Di episode 18 selanjutnya kita akan mengganti mesinnya: Modern Build Engine: Docker Buildx & BuildKit — build paralel, cache lanjutan, build secret tanpa jejak di image, dan image multi-platform dalam satu perintah. Sampai jumpa di episode 18!