Fenomena Cruft pada Build Server dan Stob Cruft Index
Kegagalan pipeline CI/CD sering kali tidak bersumber dari regresi kode aplikasi, melainkan dari degradasi lingkungan build itu sendiri. Akumulasi layer Docker tak bertuan (dangling images), cache paket dependensi yang ditinggalkan runner, core dump, dan artifact kompilasi temporer secara perlahan menghabiskan kapasitas disk server. Kondisi degradasi inkremental ini merefleksikan konsep Stob Cruft Index, di mana sistem mengalami penurunan keandalan fungsional akibat menumpuknya artefak usang (cruft) yang tidak dibersihkan oleh siklus hidup perangkat lunak standar.
Ketika partisi penyimpanan mencapai titik 100% utilisasi di tengah proses deployment, daemon container engine berhenti berfungsi, I/O lock terjadi, dan deployment berhenti tanpa state yang bersih. Untuk mengatasi masalah ini, diperlukan pendekatan berlapis: observabilitas storage proaktif, pembersihan otomatis (automated hygiene), validasi pra-rilis, dan strategi fail-safe auto-rollback.
Arsitektur Observabilitas Storage dan Disk I/O
Monitoring kapasitas disk tidak cukup hanya mengamati persentase sisa ruang secara statis. Deteksi dini memerlukan kalkulasi laju konsumsi disk menggunakan metrik Prometheus dari node_exporter, khususnya node_filesystem_avail_bytes dan node_disk_io_time_seconds_total.
Prometheus Alerting Rules
Konfigurasi alert berikut mendeteksi dua kondisi: sisa storage di bawah threshold kritis dan laju penurunan disk space (burn rate) yang berpotensi menghabiskan volume build dalam kurun waktu 4 jam ke depan.
groups:
- name: build_node_storage_alerts
rules:
- alert: BuildNodeDiskFillingFast
expr: |
(
node_filesystem_free_bytes{mountpoint="/var/lib/docker"}
/
node_filesystem_size_bytes{mountpoint="/var/lib/docker"}
) < 0.20
and
predict_linear(node_filesystem_free_bytes{mountpoint="/var/lib/docker"}[1h], 4 * 3600) < 0
for: 5m
labels:
severity: warning
annotations:
summary: "Disk /var/lib/docker menipis cepat pada {{ $labels.instance }}"
description: "Diprediksi akan kehabisan ruang dalam waktu kurang dari 4 jam dengan laju I/O saat ini."
- alert: BuildNodeDiskCriticallyLow
expr: |
(
node_filesystem_avail_bytes{mountpoint="/"}
/
node_filesystem_size_bytes{mountpoint="/"}
) < 0.10
for: 2m
labels:
severity: critical
annotations:
summary: "Sisa disk kritis pada {{ $labels.instance }}"
description: "Kapasitas partisi root tersisa kurang dari 10%."
Otomasi Pembersihan Cruft: Systemd Timer dan Cleanup Script
Pembersihan manual menggunakan cron job sering kali tidak terpantau status kegagalannya. Penggunaan unit systemd service dan timer menyediakan isolasi eksekusi, dependensi boot yang jelas, serta logging terpusat via journalctl.
Script Pembersihan (/usr/local/bin/cruft-reaper.sh)
#!/usr/bin/env bash
set -euo pipefail
THRESHOLD_PERCENT=80
TARGET_MOUNT="/var/lib/docker"
CURRENT_USAGE=$(df --output=pcent "$TARGET_MOUNT" | tail -n 1 | tr -dc '0-9')
if [ "$CURRENT_USAGE" -ge "$THRESHOLD_PERCENT" ]; then
echo "[WARN] Penggunaan disk $CURRENT_USAGE% melebihi batas $THRESHOLD_PERCENT%. Memulai pembersihan."
# Bersihkan container yang sudah berhenti lebih dari 24 jam dan image yang tidak terpakai
docker system prune --force --filter "until=24h"
# Hapus volume dangling
docker volume prune --force
# Hapus temporary cache file CI runner yang lebih dari 7 hari
find /tmp -type f -atime +7 -delete 2>/dev/null || true
# Potong file log systemd yang membengkak
journalctl --vacuum-size=500M
echo "[INFO] Pembersihan selesai. Penggunaan saat ini: $(df --output=pcent "$TARGET_MOUNT" | tail -n 1)"
else
echo "[INFO] Utilisasi disk terkendali ($CURRENT_USAGE%). Pembersihan dilewati."
fi
Konfigurasi Unit Systemd (/etc/systemd/system/cruft-reaper.timer)
[Unit]
Description=Timer Pembersihan Cruft Build Server Berkala
RefuseManualStart=no
RefuseManualStop=no
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
Pre-flight Health-Check dan Pola Auto-Rollback Pipeline
Kegagalan proses build di tengah eksekusi akibat disk penuh dapat merusak integritas deployment pipeline. Lakukan pre-flight health check sebelum proses build dimulai, dan implementasikan handler trap untuk melakukan eksekusi rollback otomatis jika disk exhaustion terjadi saat kompilasi atau deployment berlangsung.
#!/usr/bin/env bash
set -Eeuo pipefail
MIN_REQUIRED_GB=15
DEPLOY_ENV="production"
ACTIVE_RELEASE_FILE="/app/current_release"
PREVIOUS_RELEASE_FILE="/app/previous_release"
rollback_handler() {
local exit_code=$?
if [ $exit_code -ne 0 ]; then
echo "[FATAL] Pipeline gagal dengan exit code $exit_code. Menjalankan auto-rollback..."
if [ -f "$PREVIOUS_RELEASE_FILE" ]; then
PREV_IMAGE=$(cat "$PREVIOUS_RELEASE_FILE")
echo "[ROLLBACK] Mengembalikan service ke image: $PREV_IMAGE"
docker compose -f /app/docker-compose.yml up -d --force-recreate
echo "$PREV_IMAGE" > "$ACTIVE_RELEASE_FILE"
echo "[ROLLBACK] Service berhasil dipulihkan ke versi stabil sebelumnya."
else
echo "[ERROR] File snapshot rilis sebelumnya tidak ditemukan. Intervensi manual diperlukan."
fi
# Prune darurat untuk membebaskan inode/blok yang terkunci
docker builder prune -f || true
fi
exit $exit_code
}
trap rollback_handler ERR EXIT
# 1. Pre-flight Check: Disk Space
FREE_DISK_GB=$(df -BG /var/lib/docker | awk 'NR==2 {print $4}' | tr -dc '0-9')
if [ "$FREE_DISK_GB" -lt "$MIN_REQUIRED_GB" ]; then
echo "[ABORT] Disk space tersisa ${FREE_DISK_GB}GB, dibutuhkan minimal ${MIN_REQUIRED_GB}GB."
exit 1
fi
echo "[PRE-FLIGHT] Disk space memadai: ${FREE_DISK_GB}GB tersedia."
# 2. Persiapan Deployment State
CURRENT_IMAGE=$(cat "$ACTIVE_RELEASE_FILE" 2>/dev/null || echo "")
echo "$CURRENT_IMAGE" > "$PREVIOUS_RELEASE_FILE"
# 3. Eksekusi Build & Deploy
NEW_IMAGE="registry.internal/api:${BUILD_ID:-latest}"
docker build -t "$NEW_IMAGE" .
docker compose -f /app/docker-compose.yml up -d
# Update state jika sukses
echo "$NEW_IMAGE" > "$ACTIVE_RELEASE_FILE"
# Matikan trap error pada akhir eksekusi sukses
trap - ERR EXIT
echo "[SUCCESS] Rilis versi $NEW_IMAGE berhasil diterapkan."
Postmortem Insiden: Disk-Pressure Akibat Cruft Builder
Kasus Nyata: Kegagalan Rilis Core API v2.14.0 (Severity: SEV-2)
Durasi Dampak: 42 menit (Pipeline CI/CD terhenti secara total)
Ringkasan Insiden: Deployment pipeline gagal pada tahap docker build dengan pesan error no space left on device. Daemon Docker pada build worker kehilangan akses tulis ke /var/lib/docker/overlay2, menyebabkan container runtime crash dan task rilis tergantung (hanging).
Akar Masalah (Root Cause): Buildkit cache tidak dikonfigurasi batas kapasitasnya (disk cache limit). Akumulasi multi-stage build artifacts selama 14 hari menghasilkan 120GB layer cruft temporer. Kondisi ini dipicu oleh commit besar yang mengubah layer dasar Dockerfile sehingga memaksa re-building tanpa cache hit pada 90% build instructions.
Tindakan Mitigasi yang Diterapkan:
- Limitasi BuildKit Cache: Menetapkan konfigurasi
/etc/docker/daemon.jsondengandefault-address-poolsdan batasan ukuranbuilder.gcmenjadi maksimal 30GB. - Automasi Pruning Reguler: Mengaktifkan
systemd-timeryang mengeksekusidocker builder prunedansystem prunemingguan pada seluruh fleet build node. - Alert Burn Rate Storage: Memasang metrik prediksi linear pada Alertmanager untuk memberi peringatan dini sebelum sisa kapasitas berada di bawah 20%.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!