۱۱. فشردهسازی و پشتیبانگیری¶
بکاپی که بازیابیاش را تست نکردهاید، بکاپ نیست؛ امید است. این فصل هم ابزارها و هم رویهٔ اعتمادپذیر کردن بکاپ را پوشش میدهد.
راهنمای سریع¶
| فرمان | کاربرد | ریسک |
|---|---|---|
tar -tzf / unzip -l / sha256sum -c |
بررسی آرشیو و صحت | بدون تغییر |
tar -czf / zip / rsync -n |
ساخت آرشیو / تست همگامسازی | تغییر |
rsync -a |
کپی و همگامسازی | تغییر |
tar -x / unzip / rsync به مسیر زنده |
بازیابی | تغییر (روی دادهٔ جاری: مخرب) |
rsync --delete |
همگامسازی با حذف | مخرب |
restic forget --prune / borg prune |
حذف نسخههای قدیمی | مخرب |
قانون ۳-۲-۱¶
- ۳ نسخه از داده (اصلی + دو بکاپ)
- روی ۲ نوع رسانه/سامانهٔ متفاوت
- ۱ نسخه بیرون از سایت (و ترجیحاً غیرقابلتغییر / append-only تا باجافزار نتواند پاکش کند)
مفاهیم: RPO = حداکثر دادهای که میپذیرید از دست بدهید (فاصلهٔ بکاپها)؛ RTO = حداکثر زمانی که بازیابی طول میکشد. هر دو را بنویسید و اندازه بگیرید.
tar و gzip¶
umask 077 # بکاپ فقط برای مالک
tar --xattrs --acls --numeric-owner -czpf backup-etc-$(date +%F).tar.gz /etc
tar -tzf backup-etc-2026-01-01.tar.gz | head
tar -xzpf backup.tar.gz -C /restore/path
tar --exclude='*.tmp' --exclude='cache' -czf app.tar.gz /opt/app
gzip -k file.log # -k: فایل اصلی را نگه دار
gunzip file.log.gz
--numeric-ownerمالکیت را بهصورت UID عددی ذخیره میکند (در بازیابی روی سیستم دیگر، نامها اشتباه نگاشت نمیشوند)؛-pمجوزها را حفظ میکند؛--acls --xattrsACL و SELinux را هم.- بکاپ
/etcحاوی کلیدها و رمزهاست؛ مجوز آن را600و رمزگذاریشده نگه دارید.
قبل از استخراج آرشیو ناشناس، محتویات آن را با tar -tzf بررسی کنید (مسیرهای / یا ../) و از استخراج به مسیرهای حساس خودداری کنید.
zip و unzip¶
رمز zip -e استاندارد ضعیف است؛ برای محرمانگی از age/gpg استفاده کنید.
rsync¶
rsync -aHAXn --numeric-ids /srv/data/ /backup/data/ # -n: خشک (dry-run)
rsync -aHAX --numeric-ids /srv/data/ /backup/data/
rsync -avh --delete --dry-run /srv/data/ /backup/data/
-aHAX = آرشیو + hardlink + ACL + xattr. --dry-run تغییرات احتمالی را نشان میدهد. --delete فایلهایی را که در مبدأ نیستند از مقصد حذف میکند؛ ابتدا dry-run و بررسی مسیرها ضروری است.
مرگبارترین اشتباه rsync --delete
اگر مبدأ mount نبود یا خالی بود (مثلاً NFS قطع شد)، rsync --delete بکاپ را هم خالی میکند. در اسکریپت قبل از اجرا حتماً بررسی کنید: mountpoint -q /srv/data || exit 1 و [ -n "$(ls -A /srv/data)" ] || exit 1. ترجیحاً از ابزار نسخهدار (restic/borg) یا --link-dest استفاده کنید تا حذف، نسخهٔ قدیمی را نابود نکند.
snapshot چرخشی با hardlink (فضا فقط برای تغییرات):
today=$(date +%F)
rsync -aHAX --numeric-ids --link-dest=/backup/latest /srv/data/ /backup/$today/ \
&& ln -sfn /backup/$today /backup/latest
کپی از راه دور¶
حساب پشتیبان را با حداقل مجوز لازم تنظیم کنید و دسترسی حذف/بازنویسی را محدود سازید (command= در authorized_keys یا حالت append-only). مهاجمی که سرور اصلی را بگیرد نباید بتواند بکاپ را هم پاک کند: سرور بکاپ باید بکاپ را «بکشد» (pull) یا فقط اجازهٔ افزودن داشته باشد.
ابزارهای نسخهدار و رمزگذاریشده: restic و borg¶
نسبت به tar/rsync: deduplication، رمزگذاری، نسخههای متعدد، بررسی صحت و حذف سیاستمحور.
# restic
export RESTIC_REPOSITORY=/backup/restic-repo # یا sftp:user@host:/path ، s3:...
export RESTIC_PASSWORD_FILE=/root/.restic-pass # مجوز 600
restic init
restic backup /srv/data /etc --exclude-caches
restic snapshots
restic check # صحت ساختار
restic check --read-data-subset=5% # بخشی از داده را هم بخوان
restic restore latest --target /restore/test
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
# borg
borg init --encryption=repokey-blake2 /backup/borg-repo
borg create --stats /backup/borg-repo::'{hostname}-{now:%Y-%m-%d}' /srv/data /etc
borg list /backup/borg-repo
borg check /backup/borg-repo
borg prune --keep-daily 7 --keep-weekly 4 --keep-monthly 6 /backup/borg-repo
رمز/کلید مخزن را جدا از سرور نگه دارید
بدون رمز restic یا کلید borg، بکاپ بیمصرف است. نسخهٔ رمز را در گاوصندوق رمز یا جایی خارج از سرور نگه دارید و بازیابی با آن را آزمایش کنید.
بکاپ پایگاهداده¶
کپی خام فایلهای دیتابیس زنده معمولاً ناسازگار است. از ابزار خود دیتابیس استفاده کنید:
# PostgreSQL
sudo -u postgres pg_dump -Fc -f /backup/db-$(date +%F).dump DBNAME
sudo -u postgres pg_dumpall --globals-only > /backup/globals-$(date +%F).sql # نقشها
pg_restore --list /backup/db-2026-01-01.dump | head # بررسی خوانا بودن
# MySQL / MariaDB
mysqldump --single-transaction --routines --triggers --events DBNAME | gzip > /backup/db-$(date +%F).sql.gz
gzip -t /backup/db-2026-01-01.sql.gz
رمز را در خط فرمان نگذارید؛ از ~/.my.cnf (600) یا ~/.pgpass (600) استفاده کنید. برای حجم بزرگ: snapshot LVM/ZFS یا replica + dump از replica.
رمزگذاری¶
age -r AGE_PUBLIC_KEY -o backup.tar.gz.age backup.tar.gz # با کلید عمومی
age -d -i key.txt backup.tar.gz.age > backup.tar.gz
gpg --symmetric --cipher-algo AES256 backup.tar.gz # با رمز
بررسی پشتیبان¶
sha256sum backup.tar.gz > SHA256SUMS
sha256sum -c SHA256SUMS
tar -tzf backup.tar.gz >/dev/null && echo "آرشیو سالم"
وجود فایل پشتیبان به معنی قابلبازیابی بودن آن نیست. بازیابی آزمایشی، ثبت مدت بازیابی و بررسی سازگاری برنامه/دیتابیس ضروری است.
تمرین بازیابی (ماهانه، مستند):
- یک بکاپ تصادفی را روی ماشین/پوشهٔ جدا بازیابی کنید.
- دیتابیس را بالا بیاورید؛ چند رکورد/فایل مشخص را بررسی کنید.
- زمان صرفشده را ثبت کنید (RTO واقعی).
- نتیجه را در گزارش بنویسید؛ اگر شکست خورد، تیکت فوری بزنید.
اسکریپت نمونهٔ بکاپ (قابلاتکا)¶
#!/usr/bin/env bash
# /usr/local/sbin/backup-data.sh (نمونه — مسیرها را تطبیق دهید)
set -Eeuo pipefail
umask 077
SRC=/srv/data
DEST=/backup/data
LOCK=/var/lock/backup-data.lock
TAG=backup-data
log() { logger -t "$TAG" -p user.notice -- "$*"; echo "$(date -Is) $*"; }
fail() { logger -t "$TAG" -p user.err -- "FAILED: $*"; echo "$(date -Is) FAILED: $*" >&2; exit 1; }
trap 'fail "خطا در خط $LINENO"' ERR
exec 9>"$LOCK"
flock -n 9 || fail "نمونهٔ دیگری در حال اجراست"
mountpoint -q "$SRC" || fail "$SRC mount نیست" # جلوگیری از بکاپ/--delete روی مبدأ خالی
[ -n "$(ls -A "$SRC")" ] || fail "$SRC خالی است"
mountpoint -q "$DEST" || fail "$DEST mount نیست"
log "شروع"
rsync -aHAX --numeric-ids --link-dest="$DEST/latest" "$SRC/" "$DEST/$(date +%F)/"
ln -sfn "$DEST/$(date +%F)" "$DEST/latest"
KEEP=14 # آخرین ۱۴ نسخه نگه داشته میشود، نه «نسخههای جدیدتر از ۱۴ روز»:
# اگر چند هفته بکاپ شکست بخورد، با حذف بر اساس سن همهٔ نسخههای سالم پاک میشدند
mapfile -t old < <(find "$DEST" -maxdepth 1 -type d -name '20*' | sort | head -n -"$KEEP")
[ "${#old[@]}" -eq 0 ] || rm -rf -- "${old[@]}"
log "پایان موفق"
# هشدار «مرگضربانی»: اگر این پیام نیاید، مانیتورینگ باید هشدار بدهد
# curl -fsS -m 10 https://monitoring.example.internal/ping/backup-data >/dev/null || true
sudo install -m 750 backup-data.sh /usr/local/sbin/
sudo /usr/local/sbin/backup-data.sh && echo "OK"
# زمانبندی: cron یا systemd timer ([زمانبندی](13-scheduling.md))
نظارت: شکست بکاپ بیصدا رایجترین خطاست. «نبود سیگنال موفقیت» را هشدار کنید (heartbeat)، نه فقط «وجود خطا».
تست خودکار بازیابی و تازگی بکاپ (restic)¶
بکاپ بیصدا شکست میخورد؛ این اسکریپت قدیمیبودن بکاپ و قابلبازیابی بودن آن را هر شب میسنجد (آزمودهشده با restic 0.16 در سه حالت: سالم، snapshot قدیمی، مخزن خالی). jq لازم است.
#!/usr/bin/env bash
# /usr/local/sbin/restic-verify.sh (نمونه — مسیرها و آستانه را تطبیق دهید)
set -Eeuo pipefail
export RESTIC_REPOSITORY=/backup/restic-repo
export RESTIC_PASSWORD_FILE=/root/.restic-pass
MAX_AGE_H=26 # بکاپ شبانه + کمی تلورانس
TARGET=$(mktemp -d /var/tmp/restic-verify.XXXXXX)
trap 'rm -rf "$TARGET"' EXIT
fail() { logger -t restic-verify -p user.err -- "FAILED: $*"; echo "FAILED: $*" >&2; exit 1; }
last=$(restic snapshots --json | jq -r 'map(.time) | max // empty')
[ -n "$last" ] || fail "هیچ snapshot ای وجود ندارد"
age_h=$(( ( $(date +%s) - $(date -d "$last" +%s) ) / 3600 ))
[ "$age_h" -le "$MAX_AGE_H" ] || fail "آخرین snapshot ${age_h} ساعت پیش است"
restic check --read-data-subset=5% >/dev/null || fail "restic check"
SECONDS=0
restic restore latest --target "$TARGET" --include /etc/hostname --verify >/dev/null || fail "restore --verify"
[ -s "$TARGET/etc/hostname" ] || fail "فایل شاهد در بازیابی نیست"
logger -t restic-verify -p user.notice -- "OK age=${age_h}h sample_restore=${SECONDS}s"
- restic مسیر کامل را زیر
--targetبازسازی میکند ($TARGET/etc/hostname)؛ برای همین فایل شاهد با مسیر مطلق بررسی میشود. - زمان نمونهبازیابی، RTO واقعی نیست؛ بازیابی کامل ماهانه را هم مطابق «تمرین بازیابی» انجام دهید.
--read-data-subset=5%فقط بخشی از داده را میخواند. برای پوشش کامل و قابلپیشبینی، از شکل--read-data-subset=N/20استفاده کنید وNرا با روز ماه بچرخانید تا دادهها در یک چرخه کامل خوانده شوند.
بکاپ فقطافزودنی (append-only)¶
هدف: مهاجمی که سرور اصلی را بگیرد نتواند بکاپها را پاک کند. روی سرور بکاپ، کلید SSH کلاینت را به یک فرمان ثابت محدود کنید:
# ~backup/.ssh/authorized_keys (روی سرور بکاپ)
command="borg serve --append-only --restrict-to-path /backup/borg/server1",restrict ssh-ed25519 AAAA... backup@server1
در این حالت borg prune از سمت کلاینت هم چیزی را واقعاً پاک نمیکند؛ حذف نسخههای قدیمی (prune/compact) را از یک ماشین مدیریتی مورد اعتماد با دسترسی کامل انجام دهید. معادل restic: rest-server --append-only --private-repos. این بخش در sandbox آزموده نشده؛ بعد از راهاندازی، تلاش برای پاک کردن از سمت کلاینت را عمداً امتحان کنید و شکستخوردنش را ببینید.
زمانبندی با systemd و هشدار شکست¶
نسخهٔ service/timer برای اسکریپت بالا (با systemd-analyze verify بررسی شد):
# /etc/systemd/system/backup-data.service
[Unit]
Description=Data backup
OnFailure=backup-failed@%n.service
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/backup-data.sh
Nice=10
IOSchedulingClass=idle
# /etc/systemd/system/backup-failed@.service — بهجای logger، ایمیل/وبهوک واقعی بگذارید
[Unit]
Description=Alert: %i failed
[Service]
Type=oneshot
ExecStart=/usr/bin/logger -t backup-alert -p user.crit "unit %i failed"
# /etc/systemd/system/backup-data.timer
[Unit]
Description=Daily data backup
[Timer]
OnCalendar=*-*-* 02:30:00
RandomizedDelaySec=10m
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now backup-data.timer
systemctl list-timers backup-data.timer
sudo systemctl start backup-data.service && journalctl -u backup-data -n 20 # یک بار دستی امتحان کنید
OnFailure= فقط وقتی اجرا شود که سرویس شکست بخورد؛ اگر timer اصلاً اجرا نشود (غیرفعال، ساعت اشتباه) هشداری نمیآید. به همین دلیل heartbeat یا بررسی «تازگی بکاپ» (اسکریپت بالا) هم لازم است.
دیتابیس: بازیابی آزمایشی و بازیابی نقطهای¶
این بخش در sandbox آزموده نشده؛ روی ماشین آزمایشی امتحان کنید.
# تست بازیابی dump روی دیتابیس موقت (نه روی production)
createdb restore_test
pg_restore -d restore_test --no-owner /backup/db-2026-01-01.dump
psql -d restore_test -c 'SELECT count(*) FROM important_table;'
dropdb restore_test
# بکاپ پایه برای بازیابی تا لحظهٔ دلخواه (PITR) — همراه با آرشیو WAL (archive_mode / archive_command)
pg_basebackup -D /backup/pgbase -Ft -z -X stream -P
dump منطقی فقط تا لحظهٔ خودش بازمیگردد. اگر RPO شما از «شبانه» کمتر است، آرشیو WAL (PostgreSQL) یا binlog (MySQL/MariaDB) لازم است و بازیابی تا یک لحظهٔ مشخص باید تمرین شود.
جدول RPO/RTO سرویسها¶
این جدول را برای هر سرویس مهم پر کنید و ماهانه بازبینی کنید؛ «آخرین تست» خالی یعنی آن سرویس بکاپ ندارد.
| سرویس | RPO | RTO | روش | نسخهٔ بیرونی | آخرین تست بازیابی | مالک |
|---|---|---|---|---|---|---|
| (نام سرویس) | ۱ ساعت | ۲ ساعت | (مثلاً WAL + dump شبانه) | (محل) | (تاریخ) | (نام) |
خطاهای رایج¶
| نشانه | علت احتمالی | روش بررسی |
|---|---|---|
| بکاپ بیخطا ولی ناقص | مبدأ mount نبود | findmnt ؛ شرط mountpoint -q در اسکریپت |
| بازیابی دیتابیس خراب | کپی خام فایل زنده | pg_dump/mysqldump --single-transaction |
tar: Removing leading '/' |
عادی؛ مسیر مطلق نسبی ذخیره شد | بازیابی با -C |
| مالکیت بعد از بازیابی اشتباه | نام کاربر ≠ UID در سیستم جدید | --numeric-owner |
| فضای بکاپ پر شد | نگهداری/prune نیست | سیاست retention ؛ restic forget --prune |
| بکاپ هست ولی رمز گم شد | کلید مخزن جدا نگهداری نشده | گاوصندوق رمز؛ تست بازیابی |
| cron بکاپ اجرا نمیشود | PATH/مجوز/ساعت | زمانبندی |
| همهٔ نسخههای قدیمی پاک شدهاند | retention بر اساس سن + چند هفته شکست | نگهداری بر اساس تعداد؛ هشدار تازگی |
prune از کلاینت کار میکند |
بکاپ append-only نیست | authorized_keys با --append-only |
سیاست پشتیبانگیری¶
- حداقل یک نسخه جدا از سرور اصلی (و یک نسخه بیرون از سایت) نگهداری شود.
- نسخهها رمزگذاری و دسترسی آنها محدود شود.
- دوره نگهداری و RPO/RTO مشخص باشد.
- از دیتابیس زنده با روش سازگار با همان دیتابیس پشتیبان بگیرید.
- بازیابی را بهصورت دورهای تمرین و مستند کنید.
- بکاپ در برابر حذف تصادفی/باجافزار محافظت شود (append-only، immutable، حساب جدا).
/etc، فهرست بستهها، کرونها و پیکربندی سرویسها هم بکاپ شوند، نه فقط «داده».
خلاصهٔ اجرایی¶
- ۳-۲-۱ + RPO/RTO نوشتهشده.
--delete/prune فقط با بررسی mount و dry-run.- دیتابیس با ابزار خودش؛ رمز مخزن بیرون از سرور.
- ماهانه بازیابی را واقعاً تمرین و زمان بگیرید.
- عدمدریافت سیگنال موفقیت = هشدار.
- نگهداری بر اساس «تعداد نسخه» نه «سن»؛ تازگی و قابلبازیابی بودن بکاپ را هر شب خودکار بسنجید.