Tantangan Upgrade In-Place Daemon Manajemen Server
Daemon web UI seperti panel administrasi infrastruktur (misalnya arsitektur panel 3x-ui) menggabungkan binary server, aset web statis, dan embedded database seperti SQLite dalam satu lingkungan eksekusi host. Melakukan pembaruan langsung pada host aktif (in-place upgrade) membawa risiko tinggi: binary baru bisa mengalami crash-loop, migrasi skema database gagal di tengah jalan, atau aset web baru tidak cocok dengan versi API backend.
Jika pembaruan gagal tanpa mekanisme recovery otomatis, akses administratif server akan terputus seketika. Pola safe rollback & state backup mengatasi masalah ini dengan memastikan status sistem lama selalu dicadangkan sebelum perubahan dieksekusi, status baru diuji secara otomatis melalui endpoint health check, dan seluruh operasi dikembalikan ke status awal jika pengujian tidak lolos verifikasi.
Arsitektur State, Binary, dan Konfigurasi
Pemisahan direktori dan pemanfaatan symlink adalah fondasi dari in-place update yang aman. Jangan pernah menimpa binary yang sedang berjalan secara langsung. Terapkan pemisahan stateful data dan stateless binaries:
- Stateless Binary & Assets: Tempatkan rilis dalam direktori berversi, misalnya
/opt/ui-daemon/releases/vX.Y.Z, lalu arahkan ke service runtime melalui symlink/opt/ui-daemon/current. - Stateful Data: Database SQLite dan file konfigurasi disimpan di luar direktori rilis, misalnya
/etc/ui-daemon/atau/var/lib/ui-daemon/, dan diakses oleh binary melalui path persisten atau symlink.
Penanganan SQLite Schema Migration
SQLite rentan terkunci atau mengalami corrupt sebagian jika proses dihentikan mendadak saat menjalankan ALTER TABLE atau transaksi DDL. Sebelum service versi baru dijalankan, dua langkah wajib dilakukan:
- Snapshot file database menggunakan API backup SQLite atau utilitas CLI
sqlite3 .backupguna mencegah partial write saat proses daemon lama sedang ditutup (flush WAL). - Pemeriksaan integritas database menggunakan pragma:
PRAGMA integrity_check;.
Implementasi Skrip Upgrade Idempoten dan Otomatis
Skrip berikut mengotomatiskan alur: backup database, ekstraksi rilis baru, perpindahan symlink, restart unit systemd, pengecekan health check HTTP via cURL, serta rollback instan jika health check gagal.
#!/usr/bin/env bash
set -euo pipefail
APP_NAME="ui-daemon"
BASE_DIR="/opt/${APP_NAME}"
DATA_DIR="/var/lib/${APP_NAME}"
DB_FILE="${DATA_DIR}/${APP_NAME}.db"
CURRENT_LINK="${BASE_DIR}/current"
BACKUP_DIR="/var/backups/${APP_NAME}"
NEW_VERSION="$1" # Argumen: versi baru, misal: v2.1.0
NEW_RELEASE_DIR="${BASE_DIR}/releases/${NEW_VERSION}"
HEALTH_CHECK_URL="http://127.0.0.1:2053/api/health"
if [ -z "${NEW_VERSION}" ]; then
echo "Error: Argumen versi rilis diperlukan." >&2
exit 1
fi
mkdir -p "${BACKUP_DIR}" "${NEW_RELEASE_DIR}"
# 1. Simpan target rilis sebelumnya untuk target rollback
PREV_TARGET=""
if [ -L "${CURRENT_LINK}" ]; then
PREV_TARGET=$(readlink -f "${CURRENT_LINK}")
fi
# 2. Backup database SQLite menggunakan online backup
TIMESTAMP=$(date +%Y%m%d%H%M%S)
DB_BACKUP_PATH="${BACKUP_DIR}/${APP_NAME}_${TIMESTAMP}.db"
if [ -f "${DB_FILE}" ]; then
echo "[*] Memvalidasi dan mencadangkan SQLite database..."
INTEGRITY_CHECK=$(sqlite3 "${DB_FILE}" "PRAGMA integrity_check;" 2>&1 || true)
if [ "${INTEGRITY_CHECK}" != "ok" ]; then
echo "[!] Database korup sebelum upgrade. Batalkan proses! Detail: ${INTEGRITY_CHECK}" >&2
exit 1
fi
sqlite3 "${DB_FILE}" ".backup '${DB_BACKUP_PATH}'"
fi
# Definisi fungsi rollback instan
rollback() {
echo "[!] Upgrade gagal. Menginisiasi rollback otomatis..." >&2
if [ -n "${PREV_TARGET}" ] && [ -d "${PREV_TARGET}" ]; then
ln -sfn "${PREV_TARGET}" "${CURRENT_LINK}"
fi
if [ -f "${DB_BACKUP_PATH}" ]; then
echo "[*] Memulihkan database SQLite dari snapshot..."
systemctl stop "${APP_NAME}" || true
cp -f "${DB_BACKUP_PATH}" "${DB_FILE}"
fi
systemctl restart "${APP_NAME}" || true
echo "[!] Rollback selesai. Menampilkan log eror terakhir:" >&2
journalctl -u "${APP_NAME}" -n 20 --no-pager >&2
exit 1
}
# Pasang trap: jika skrip terminated sebelum selesai, panggil rollback
trap rollback ERR
# 3. Download/Unpack binary dan aset baru (simulasi ekstraksi direktori)
echo "[*] Mempersiapkan rilis ${NEW_VERSION}..."
# Asumsi binary di-download atau dipindahkan ke NEW_RELEASE_DIR
# tar -xzf /tmp/${NEW_VERSION}.tar.gz -C "${NEW_RELEASE_DIR}"
# 4. Atomic switch symlink binary
echo "[*] Mengarahkan symlink ke versi baru..."
ln -sfn "${NEW_RELEASE_DIR}" "${CURRENT_LINK}"
# 5. Restart daemon melalui systemd
echo "[*] Memulai ulang systemd unit..."
systemctl daemon-reload
systemctl restart "${APP_NAME}"
# 6. Observasi dan Health Check pasca upgrade
echo "[*] Menjalankan validasi health check..."
HEALTHY=false
for i in {1..10}; do
STATUS_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${HEALTH_CHECK_URL}" || true)
if [ "${STATUS_CODE}" -eq 200 ]; then
HEALTHY=true
break
fi
sleep 2
done
if [ "${HEALTHY}" = false ]; then
echo "[!] Service gagal merespons HTTP 200 pada ${HEALTH_CHECK_URL}" >&2
rollback
fi
# Upgrade tuntas tanpa kesalahan: bersihkan trap
trap - ERR
echo "[+] Upgrade ke ${NEW_VERSION} sukses divalidasi."Konfigurasi Systemd untuk Mencegah Crash-Loop
File unit systemd harus dirancang agar membatasi restart tak terbatas saat binary crash-loop dan mereferensikan symlink yang konsisten:
[Unit]
Description=Server Management UI Daemon
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/ui-daemon/current
ExecStart=/opt/ui-daemon/current/ui-daemon --config /etc/ui-daemon/config.json
Restart=on-failure
RestartSec=3s
StartLimitIntervalSec=60s
StartLimitBurst=3
LimitNOFILE=65535
[Install]
WantedBy=multi-user.targetPengaturan StartLimitIntervalSec=60s dan StartLimitBurst=3 memastikan systemd berhenti mencoba restart jika daemon mengalami crash berulang kali dalam kurun 60 detik. Hal ini mencegah resource starvation pada CPU dan I/O disk.
Observasi Log dan Debugging
Saat upgrade gagal atau health check timeout, debugging instan dilakukan menggunakan journalctl dengan parameter terfilter:
- Lihat log error spesifik sejak percobaan boot terakhir:
journalctl -u ui-daemon.service -b -p err --no-pager - Streaming log daemon secara realtime:
journalctl -u ui-daemon.service -f -n 50 - Cek SQLite journal yang tertinggal (WAL/SHM): jika terdapat
.db-walsetelah service berhenti paksa, pastikan binary versi lama mampu membaca WAL tersebut atau lakukan checkpoint manual sebelum me-restore backup.
Catatan: Jika aplikasi backend menerapkan migrasi skema yang bersifat backward-incompatible (misal: menghapus kolom yang dibutuhkan versi lama), proses rollback binary ke versi sebelumnya tanpa me-restore file database fisik dapat memicu runtime error seketika. Selalu sertakan restore file database saat skema telah tersentuh migrator.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!