Deploy pertama biasanya terasa mudah: salin beberapa berkas, restart service, selesai dalam lima menit. Masalah baru terasa pada deploy kedua puluh — satu langkah terlewat, satu berkas tercampur karena disalin saat aplikasi masih berjalan, dan tiga server diam-diam menjalankan tiga versi berbeda. Chapter ini merancang alur deployment yang dijalankan oleh mesin, bukan oleh ingatan manusia: build, transfer, activate, verify, dan rollback. Semuanya kita praktikkan dengan skrip yang menjalankan pipeline sungguhan di sandbox, memakai aplikasi contoh app-toko dan strategi rilis berbasis symlink dari Chapter Symbolic Links dan Hard Links .
Kenapa Deploy Manual Selalu Berujung Kecelakaan #
Deployment manual bergantung pada dua hal yang rapuh: ingatan admin dan konsistensi tangan. Ingatan terhadap lima belas langkah akan goyah pukul sebelas malam, dan salin-menempel manual menghasilkan server snowflake — server yang tampak sama tetapi berperilaku berbeda. Tabel berikut merangkum kecelakaan yang paling sering terjadi:
| Kecelakaan deploy manual | Akibat di lapangan |
|---|---|
| berkas disalin langsung ke folder aplikasi yang sedang jalan | pengguna menerima campuran dua versi selama proses salin |
| migrasi database terlupa dijalankan | aplikasi baru membaca kolom yang belum dibuat |
| urutan langkah berbeda antar admin | dua server “identik” berperilaku berbeda |
| tidak ada catatan versi yang terpasang | saat insiden, tidak ada yang tahu kode mana yang berjalan |
| rollback dilakukan sambil panik | kesalahan baru ditumpuk di atas kesalahan lama |
Karena itu deployment yang sehat punya tiga sifat: berulang (hasil sama siapa pun yang menjalankan), cepat dibatalkan (menit, bukan jam), dan berjejak (setiap rilis bisa ditunjuk foldernya). Ketiganya kita bangun bertahap di chapter ini.
Enam Tahap Pipeline Deployment #
Pipeline adalah urutan tahap yang sama setiap kali deploy dijalankan, tanpa tergantung siapa yang memencet tombolnya:
| Tahap | Isi pekerjaan | Kalau tahap ini gagal |
|---|---|---|
| build | hasilkan artifact dari kode sumber | tidak ada yang dikirim, server lama tetap aman |
| test | uji artifact (unit, smoke) | versi cacat tertahan sebelum menyentuh server |
| upload | kirim artifact ke server, belum diaktifkan | server tidak berubah sama sekali |
| activate | alihkan traffic ke versi baru | inilah satu-satunya momen berisiko |
| verify | health check pasca-activate | memicu rollback |
| rollback | kembali ke versi sebelumnya | tersedia sebagai jaring pengaman |
Ada satu prinsip penting di tabel itu: artifact yang diuji adalah artifact yang dipasang. Kita membangun sekali, lalu memindahkan berkas yang sama ke semua server — bukan membangun ulang di tiap server, karena hasil build bisa berbeda antar mesin. Tahap activate sengaja dipisah dari upload supaya ada jeda yang aman: artifact sudah utuh di server sebelum pengguna dialihkan.
Tiga Strategi Aktivasi Rilis #
Activate adalah momen paling kritis — detik ketika pengguna berpindah dari versi lama ke versi baru. Ada tiga strategi utama:
| Strategi | Cara kerja | Kebutuhan | Kecepatan rollback |
|---|---|---|---|
| blue-green | dua environment utuh; load balancer memilih yang aktif | server dua kali lipat | secepat switch balik |
| rolling | server diganti satu per satu dari dalam cluster | cluster dengan beberapa server | pelan: mundur satu-satu |
| symlink release | tiap versi punya folder; symlink current dipindah | satu server saja | secepat membalik symlink |
Blue-green paling boros tetapi paling aman: environment biru tetap melayani selama hijau disiapkan, dan berpindah kembali hanya satu konfigurasi load balancer. Rolling hemat karena tidak ada server menganggur, cocok untuk cluster web yang banyak. Symlink release — pola yang dipopulerkan Capistrano di dunia Ruby — paling sederhana dan menjadi fokus latihan kita, karena prinsipnya bisa diterapkan di satu server tanpa perangkat tambahan.
Rilis Berbasis Symlink: Folder releases dan current #
Strategi ini menyimpan setiap versi sebagai folder bernomor di bawah releases/, lalu satu symlink bernama current menunjuk versi yang sedang aktif:
app-toko/
├── releases/
│ ├── 1/
│ │ └── app.txt
│ └── 2/
│ └── app.txt
└── current -> releases/2Web server atau systemd service hanya pernah membaca lewat current — tidak pernah menyebut nomor versi secara langsung. Kita bangun strukturnya langkah demi langkah:
mkdir -p app-toko/releases/1
printf 'app-toko versi 1\n' > app-toko/releases/1/app.txt
ln -sfn releases/1 app-toko/current
readlink app-toko/current
cat app-toko/current/app.txtOutput yang Dihasilkan:
releases/1
app-toko versi 1Mari kita bedah baris demi baris:
- Baris 1 (
mkdir -p app-toko/releases/1): folder rilis pertama dibuat. Rilis berikutnya tinggal menambah folderreleases/2,releases/3, dan seterusnya — isi rilis lama tidak pernah disentuh. - Baris 2 (
printf ... > app-toko/releases/1/app.txt): menulis “artifact” versi 1. Di pipeline nyata, berkas ini adalah hasil build yang sudah lulus tahap test — bukan salinan acak dari laptop developer. - Baris 3 (
ln -sfn releases/1 app-toko/current): opsi-smembuat symlink,-fmenimpa bilacurrentsudah ada, dan-nmemperlakukancurrentsebagai berkas biasa — tanpa-n,lnbisa salah sasaran dengan menulis ke dalam folder tujuan symlink lama. Target ditulis relatif terhadap lokasicurrent, bukan direktori kerja kita. - Baris 4–5 (
readlinkdancat): tahap verify — symlink harus menunjukreleases/1, dan membaca lewatcurrentharus menghasilkan versi yang benar sebelum kita percaya rilis ini aktif.
Satu Skrip untuk Satu Siklus Penuh #
Bundle chapter ini membawa skrip deploy-app.sh yang merangkai tahap build → upload → activate → verify dalam satu perintah, plus subperintah rollback untuk jaring pengamannya. Kita jalankan satu siklus lengkap: dua rilis sukses, lalu simulasi health check versi 2 yang gagal:
bash deploy-app.sh 1
bash deploy-app.sh 2
echo "versi aktif : $(cat current/app.txt)"
echo "--- health check versi 2 gagal; rollback ---"
bash deploy-app.sh rollback
echo "versi aktif : $(cat current/app.txt)"Output yang Dihasilkan:
deploy versi 1: OK (current -> releases/1)
deploy versi 2: OK (current -> releases/2)
versi aktif : app-toko versi 2
--- health check versi 2 gagal; rollback ---
rollback: current -> releases/1
versi aktif : app-toko versi 1Skripnya layak dibedah karena polanya bisa dipindahkan ke pipeline nyata:
deploy(): empat tahap pipeline berurutan —printfmembangun artifact,cpmengunggahnya kereleases/$versi,ln -sfnmengaktifkan, dan blokifmemverifikasi. Kalau verify gagal, skripexit 1— pipeline berhenti, tidak melanjutkan dengan asumsi baik-baiknya.rollback():readlink currentmengetahui versi yang aktif, laluls releases | sort -n | awkmemilih nomor rilis terbesar yang lebih kecil dari versi sekarang. Ekspansi${sekarang#"$DIR"/}membuang prefiksreleases/sehingga tersisa angkanya.- Dispatch argumen: argumen
rollbackmasuk ke fungsi rollback, argumen kosong mencetak pemakaian, dan lainnya dianggap nomor versi. - Di pipeline nyata (GitLab CI, GitHub Actions, Jenkins): tahap yang sama dipicu oleh
git tag, artifact dikirim denganrsync, verify memanggil health check sungguhan — materi Chapter Uptime dan Health Checks .
Setelah beberapa rilis, folder releases/ menjadi jejak audit yang bisa diperiksa siapa saja:
bash deploy-app.sh 1
bash deploy-app.sh 2
bash deploy-app.sh 3
bash deploy-app.sh rollback
echo "== isi folder releases (jejak setiap rilis) =="
ls -R releases
echo "current menunjuk ke: $(readlink current)"Output yang Dihasilkan:
deploy versi 1: OK (current -> releases/1)
deploy versi 2: OK (current -> releases/2)
deploy versi 3: OK (current -> releases/3)
rollback: current -> releases/2
== isi folder releases (jejak setiap rilis) ==
releases:
1
2
3
releases/1:
app.txt
releases/2:
app.txt
releases/3:
app.txt
current menunjuk ke: releases/2Perhatikan hasilnya: tiga rilis terpasang lengkap, current sudah dibalik ke releases/2 oleh rollback, dan folder versi 1 tetap utuh — inilah yang membuat rollback di strategi ini nyaris instan. Di produksi, rilis yang lebih tua dari N terakhir biasanya dihapus otomatis agar disk tidak membengkak, tetapi beberapa rilis terakhir selalu disimpan sebagai jaring pengaman.
Migrasi Database dan Batas Rollback #
Symlink membalikkan kode dalam satu detik, tetapi migrasi database tidak mundur sendiri: kolom yang sudah ditambah dan data yang sudah ditulis tetap ada. Karena itu urutannya diberlakukan: migrasi dijalankan sebelum activate, dan skema dirancang backward-compatible — rilis N menambahkan kolom tanpa memakainya, rilis N+1 yang mulai menulis, dan penghapusan kolom menunggu rilis N+2. Pola expand-contract ini menjaga rollback selalu mungkin.
Dengan pipeline yang berulang, rollback yang cepat, dan jejak rilis yang rapi, perubahan kode sudah menjadi urusan menit. Pertanyaan berikutnya adalah bagaimana kita tahu bahwa rilis baru bermasalah sebelum pengguna membanjiri kotak masuk — jawabannya ada di log yang terkumpul dari semua server, yang kita bangun di Chapter Log Aggregation .
Referensi lanjut: halaman manual ln
untuk opsi -s, -f, -n, serta readlink
untuk membaca target symlink.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
deploy-app.sh
Unduh berkas#!/usr/bin/env bash
# deploy-app.sh — simulasi pipeline deployment berbasis symlink release.
# Pemakaian: bash deploy-app.sh <versi> → rilis versi baru
# bash deploy-app.sh rollback → kembali ke versi sebelumnya
APP=app-toko
DIR=releases
deploy() {
versi="$1"
# 1. build: hasilkan artifact dari "kode sumber"
printf '%s versi %s\n' "$APP" "$versi" > artifact.txt
# 2. upload: taruh artifact di folder release baru
mkdir -p "$DIR/$versi"
cp artifact.txt "$DIR/$versi/app.txt"
# 3. activate: alihkan symlink current ke rilis baru
ln -sfn "$DIR/$versi" current
# 4. verify: pastikan current melayankan versi yang benar
if [ "$(cat current/app.txt)" = "$APP versi $versi" ]; then
echo "deploy versi $versi: OK (current -> $DIR/$versi)"
else
echo "deploy versi $versi: GAGAL"
exit 1
fi
}
rollback() {
sekarang=$(readlink current)
versi_sekarang=${sekarang#"$DIR"/}
sebelumnya=$(ls "$DIR" | sort -n | awk -v v="$versi_sekarang" \
'($1+0) < (v+0) {akhir=$1} END {print akhir}')
if [ -z "$sebelumnya" ]; then
echo "rollback: tidak ada versi lebih lama"
exit 1
fi
ln -sfn "$DIR/$sebelumnya" current
echo "rollback: current -> $DIR/$sebelumnya"
}
if [ "${1:-}" = "rollback" ]; then
rollback
elif [ -z "${1:-}" ]; then
echo "pemakaian: bash deploy-app.sh <versi>|rollback" >&2
exit 0
else
deploy "$1"
fi