دانيلا (⁦Dayfing⁩)
العودة إلى المقالات
2,528 كلمة13 د

النسخ الاحتياطي 3-2-1 باستخدام restic: نسخة خارجية وتمارين استعادة

النسخة الاحتياطية التي يمكن الاستعادة منها تعمل وفق جدول دون تدخل منك، وتحتفظ بنسخة واحدة خارج الموقع حيث لا يستطيع خادم مخترق حذفها، وتلتقط قواعد البيانات في حالة متسقة، وقد جرت استعادتها وقياس زمن الاستعادة مؤخرًا. مع restic يعني ذلك مستودعًا مشفرًا على تخزين متوافق مع S3 محميًا بـ object lock أو بخادم في وضع append-only، وتفريغات لقواعد البيانات قبل كل تشغيل، ومؤقت systemd مع تنبيهات عند الفشل، وتمرين استعادة منتظمًا. يبني هذا الدليل هذا الإعداد لخادم Linux، والمكونات نفسها تصلح لجهاز المطور.

حدد RPO وRTO قبل اختيار الأدوات

يحدد recovery point objective (RPO) مقدار البيانات الذي يمكنك تحمل فقدانه مقيسًا بالزمن. ويحدد recovery time objective (RTO) المدة التي يمكن أن تتوقف فيها الخدمة أثناء الاستعادة. يستطيع مالك الخدمة الإجابة عن السؤالين بكلمات بسيطة: «يمكننا أن نفقد طلبات يوم واحد على الأكثر» و«يجب أن يعود المتجر للعمل خلال أربع ساعات».

أسوأ خسارة في نسخة احتياطية مجدولة تساوي تقريبًا الفاصل بين مرات التشغيل مضافًا إليه الوقت الذي يحتاجه التشغيل للوصول إلى التخزين الخارجي. مهمة ليلية تبدأ في 02:00 وتنهي الرفع في 02:40 تعطي في أسوأ الأحوال RPO يقارب 24 ساعة و40 دقيقة. إذا كان هذا كثيرًا بالنسبة إلى قاعدة بيانات، فانسخها احتياطيًا بوتيرة أعلى أو أضف أرشفة WAL.

يتحدد RTO أساسًا بزمن النقل وإعادة البناء. ثواني النقل تساوي حجم البيانات بالميغابت مقسومًا على سرعة الرابط بالميغابت في الثانية. استعادة 200 غيغابايت تعادل 1,600,000 ميغابت، لذا تستغرق بسرعة 100 ميغابت في الثانية نحو 16,000 ثانية، أي قرابة 4.4 ساعات، قبل استعادة قاعدة البيانات أصلًا. إذا تجاوز ذلك قيمة RTO، فلن تكفي حاوية بعيدة وحدها، وستحتاج إلى نسخة محلية أو نظام احتياطي جاهز.

قاعدة 3-2-1 وامتدادها 3-2-1-1-0

تعني قاعدة 3-2-1 وجود ثلاث نسخ من البيانات على نوعين مختلفين من التخزين، إحداها خارج الموقع. على خادم VPS يكون قرص الإنتاج هو النسخة الأولى، وتفريغ محلي أو مستودع على تخزين منفصل هو النسخة الثانية، وحاوية لدى مزود آخر أو في حساب آخر هي النسخة الثالثة. القسم الثاني على القرص نفسه يتعطل مع الأول، لذا ضع النسخة المحلية على جهاز آخر (يساعد دليل شراء أقراص NVMe SSD في اختياره) أو على مضيف آخر.

روّج مورّدو حلول النسخ الاحتياطي للامتداد 3-2-1-1-0. الرقم 1 الإضافي نسخة غير متصلة أو غير قابلة للتعديل، بحيث لا يستطيع مهاجم يملك صلاحيات root على الخادم وبيانات اعتماده السحابية حذفها. أما الصفر فيعني عدم وجود أخطاء في فحوص السلامة واختبارات الاستعادة. تتجاهل معظم الإعدادات هذا الرقم الأخير، مع أنه وحده يبيّن ما إذا كانت الأرقام الأربعة الأخرى حقيقية.

ما الذي يجب نسخه وما الذي يجب استبعاده

انسخ ما لا يمكنك إعادة إنشائه بسرعة من مصدر عام: /etc، وشيفرة التطبيق والملفات المرفوعة ضمن /srv أو /var/www، والمجلدات المنزلية مع مفاتيح SSH وGPG، وملفات crontab، ومواد TLS، وتفريغات قواعد البيانات، وقائمة الحزم المثبتة من apt-mark showmanual. على جهاز المطور تكمن القيمة في العمل غير المدفوع إلى المستودع البعيد، وstash، وملفات dotfiles، والمفاتيح، والمستندات.

استبعد البيانات الكبيرة أو القابلة لإعادة الإنتاج: ذاكرات التخزين المؤقت، وnode_modules، والبيئات الافتراضية، ومخرجات البناء، وصور الحاويات، وأوزان النماذج، وملف swap. واستبعد مجلدات قواعد البيانات العاملة مثل /var/lib/postgresql، لأن نسخة مأخوذة من خادم قيد التشغيل ليست نسخة احتياطية صالحة. في ملفات الاستبعاد الخاصة بـ restic يطابق النمط الذي يبدأ بـ / مسارًا مطلقًا، ويطابق الاسم المجرد مثل node_modules أي عمق:

/var/lib/postgresql
/var/lib/docker
/var/cache
/var/tmp
/swapfile
/home/*/.cache
node_modules
.venv
__pycache__
*.tmp

يتخطى --exclude-caches أيضًا المجلدات التي تحتوي على ملف CACHEDIR.TAG القياسي، ويمنع --one-file-system أداة restic من الانتقال إلى أنظمة ملفات أخرى مركّبة.

أساسيات restic: المستودع والتشفير وكلمة المرور

يخزّن restic لقطات مزالة التكرار في مستودع على قرص محلي أو عبر SFTP أو على خادم REST الخاص به أو في تخزين كائنات، ولا يرفع إلا الأجزاء الجديدة. لا يمكن تعطيل التشفير: وفق وثائق تصميم restic تُشفَّر جميع البيانات بـ AES-256 في وضع العداد وتُصادَق بـ Poly1305-AES، ويحمي المفتاحَ كلمةُ مرور مقوّاة بـ scrypt.

احتفظ بالإعدادات في ملف بيئة لا يقرؤه إلا root حتى تستخدم الأوامر التفاعلية والمهام المجدولة القيم نفسها. تسرد وثائق المستودعات صيغ العناوين، مثل s3:https://server:port/bucket للخدمات المتوافقة مع S3.

# /etc/restic/restic.env, mode 600
RESTIC_REPOSITORY=s3:https://s3.example.com/web1-backups
RESTIC_PASSWORD_FILE=/etc/restic/password
RESTIC_CACHE_DIR=/var/cache/restic
AWS_ACCESS_KEY_ID=replace-me
AWS_SECRET_ACCESS_KEY=replace-me
HEARTBEAT_URL=https://monitor.example.net/ping/web1-backup
ALERT_WEBHOOK_URL=https://chat.example.net/hooks/backups
install -d -m 700 /etc/restic
(umask 077; openssl rand -base64 32 > /etc/restic/password)
set -a; . /etc/restic/restic.env; set +a
restic init
restic key add    # كلمة مرور ثانية لنسخة استرداد غير متصلة
restic backup --one-file-system --exclude-caches \
  --exclude-file=/etc/restic/excludes.txt /etc /root /home /srv /var/www
restic snapshots

الوثائق صريحة: فقدان كلمة المرور يعني فقدان البيانات بلا رجعة. احفظ كلمة المرور وعنوان المستودع وبيانات اعتماد الحاوية في مدير كلمات مرور وفي نسخة مختومة غير متصلة، ولا تحفظها أبدًا على الخادم المحمي وحده. وإذا كنت تستخدم مدير أسرار، يمكن لـ RESTIC_PASSWORD_COMMAND أن يطبع كلمة المرور بدلًا من قراءة ملف.

الاحتفاظ عبر forget وprune والسلامة عبر check

يطبق restic forget سياسة احتفاظ على كل مجموعة من اللقطات (حسب المضيف والمسارات افتراضيًا)، ويحذف prune البيانات التي لم تعد أي لقطة تشير إليها. يجمع الخيار --prune الخطوتين:

restic forget --prune --keep-daily 14 --keep-weekly 8 --keep-monthly 12 --keep-yearly 3
restic check --read-data-subset=10%

تحتفظ هذه السياسة بأحدث لقطة لكل يوم من آخر 14 يومًا توجد فيها لقطات، ثم بلقطة واحدة لكل أسبوع لمدة 8 أسابيع، ولكل شهر لمدة 12 شهرًا، ولكل سنة لمدة 3 سنوات. عاين أي تغيير في السياسة بإضافة --dry-run. تشير وثائق forget إلى أن المستودع يُقفل أثناء prune، لذا جدوله بعيدًا عن النسخ الاحتياطي وأضف --retry-lock إلى أمر النسخ.

يتحقق restic check من البنية والبيانات الوصفية. يُنزّل --read-data كل حزمة pack، وهذا يكلف وقتًا وحركة بيانات صادرة، بينما يقرأ --read-data-subset جزءًا منها: تختار 10% مجموعة جزئية عشوائية في كل تشغيل، وتقرأ 3/12 جزءًا ثابتًا من اثني عشر جزءًا، كما يشرح دليل صيانة المستودع. نجاح الفحص يثبت الاتساق، لا قدرتك على إعادة الخدمة.

مستودع خارجي لا تستطيع برامج الفدية مسحه

يستطيع مهاجم يملك صلاحيات root قراءة /etc/restic/restic.env. إذا كانت بيانات الاعتماد هذه تسمح بحذف الكائنات، فيمكن حذف النسخة الخارجية أيضًا. اقصر بيانات الاعتماد على حاوية واحدة، وأبقِ بيانات اعتماد الإدارة خارج الخادم، وطبّق مبادئ أقل الصلاحيات من دليل تحصين خادم Linux VPS. ثم اختر إحدى وسيلتي الحماية.

خادم REST في وضع append-only

يوفر rest-server التابع لـ restic وضع --append-only الذي يقبل النسخ الجديدة لكنه يمنع حذف النسخ الموجودة أو تعديلها. توصي وثائق restic بتشغيل forget وprune لهذه المستودعات من جهاز منفصل ومحمي جيدًا، وباستخدام --keep-within بدلًا من قواعد مثل --keep-weekly، لأن عميلًا مخترقًا قد يضيف لقطات مزيفة بطوابع زمنية مختارة تحتل أماكن اللقطات الحقيقية.

Object lock على تخزين متوافق مع S3

يحفظ S3 Object Lock إصدارات الكائنات وفق نموذج write-once-read-many. يتطلب تفعيل الإصدارات، وفي وضع compliance لا يستطيع أحد، بما في ذلك المستخدم الجذر للحساب، حذف إصدار مقفل قبل تاريخ انتهاء الاحتفاظ. منذ الإصدار 0.13 يرسل restic مجاميع التحقق عند الرفع التي تتطلبها هذه الحاويات، لذا يعمل مع سياسة احتفاظ افتراضية للحاوية (أضف --endpoint-url للمزودين الآخرين):

aws s3api create-bucket --bucket web1-backups --object-lock-enabled-for-bucket
aws s3api put-object-lock-configuration --bucket web1-backups \
  --object-lock-configuration '{"ObjectLockEnabled":"Enabled","Rule":{"DefaultRetention":{"Mode":"COMPLIANCE","Days":30}}}'

عندما يحذف restic ملفًا في حاوية كهذه يضيف S3 علامة حذف ويبقى الإصدار المقفل. بعد الهجوم انسخ الحاوية كما كانت، مثلًا بالأمر rclone copy --s3-version-at "2026-09-20" offsite:web1-backups /srv/recovered-repo، ثم استعد من تلك النسخة. تظل البيانات التي حذفها prune تشغل مساحة حتى انتهاء الاحتفاظ، لذا اجعل قاعدة دورة الحياة تنهي الإصدارات غير الحالية في موعد لا يسبق تلك المدة. اختبر الدورة كاملة لدى مزودك، لأن التطبيقات المتوافقة مع S3 تختلف فيما بينها.

نسخ احتياطية متسقة لـ PostgreSQL

وثائق PostgreSQL حول النسخ الاحتياطي على مستوى نظام الملفات واضحة: يجب إيقاف الخادم للحصول على نسخة صالحة من مجلد بياناته، ولا يكفي منع الاتصالات، لأن أدوات مثل tar لا تلتقط لقطة ذرية، ولأن الخادم يحتفظ ببيانات في مخازن داخلية. أما اللقطة الذرية لوحدة تخزين تضم جميع ملفات البيانات وWAL فتعمل، لأن PostgreSQL يعيد تشغيل WAL كما بعد انهيار.

بالنسبة إلى معظم الخوادم تبقى التفريغات المنطقية أبسط خيار صحيح. ينتج pg_dump تصديرًا متسقًا أثناء استخدام قاعدة البيانات ولا يحجب المستخدمين الآخرين. تتيح صيغة directory التفريغ والاستعادة بالتوازي. لا تدخل الأدوار في التفريغ، لذا فرّغ الكائنات العامة بشكل منفصل. يعمل هذا السكربت قبل كل نسخة احتياطية ويحتفظ بآخر التفريغات على القرص المحلي:

#!/usr/bin/env bash
# /usr/local/sbin/pg-dump-for-backup
set -euo pipefail
out=/var/backups/postgresql
install -d -m 700 -o postgres -g postgres "$out"
runuser -u postgres -- pg_dumpall --globals-only --file="$out/globals.sql"
runuser -u postgres -- psql -Atc \
  "SELECT datname FROM pg_database WHERE datallowconn AND NOT datistemplate" |
while read -r db; do
  rm -rf "$out/$db.new"
  runuser -u postgres -- pg_dump --format=directory --jobs=2 --file="$out/$db.new" "$db"
  rm -rf "$out/$db"
  mv "$out/$db.new" "$out/$db"
done

استخدم pg_basebackup عندما تتجاوز الاستعادة المنطقية لعنقود كبير قيمة RTO أو عندما تحتاج إلى الاستعادة إلى نقطة زمنية محددة. فهو ينسخ العنقود بأكمله إلى مجلد فارغ عبر بروتوكول النسخ المتماثل، ويحتاج إلى دور يملك REPLICATION وإلى مدخل في pg_hba.conf، وينقل WAL اللازم لتشغيل النسخة. يتحقق pg_verifybackup منها مقابل ملف البيان:

sudo -u postgres pg_basebackup --pgdata=/var/backups/pg-base --wal-method=stream --checkpoint=fast
sudo -u postgres pg_verifybackup /var/backups/pg-base

يعيد pg_restore بناء الفهارس من تعريفاتها. ومع فهارس متجهات كبيرة، مثل فهارس HNSW في دليل البحث الهجين باستخدام pgvector، قد تستهلك إعادة البناء هذه معظم زمن الاستعادة.

الجدولة بخدمة ومؤقت systemd

تشغّل خدمة من نوع oneshot التفريغ أولًا ثم النسخ الاحتياطي. إذا فشل التفريغ لا يبدأ النسخ وتنتقل الوحدة إلى حالة الفشل. احفظ الوحدات في /etc/systemd/system/:

# restic-backup.service
[Unit]
Description=restic backup to the off-site repository
Wants=network-online.target
After=network-online.target postgresql.service
OnFailure=backup-alert@%n.service

[Service]
Type=oneshot
EnvironmentFile=/etc/restic/restic.env
CacheDirectory=restic
Nice=10
IOSchedulingClass=idle
ExecStartPre=/usr/local/sbin/pg-dump-for-backup
ExecStart=/usr/local/bin/restic backup --retry-lock 30m \
    --one-file-system --exclude-caches \
    --exclude-file=/etc/restic/excludes.txt --tag scheduled \
    /etc /root /home /srv /var/www /var/backups/postgresql
ExecStartPost=-/usr/bin/curl -fsS --max-time 10 --retry 3 ${HEARTBEAT_URL}
# restic-backup.timer
[Unit]
Description=Nightly restic backup

[Timer]
OnCalendar=*-*-* 02:15:00
RandomizedDelaySec=30m
Persistent=true

[Install]
WantedBy=timers.target

فعّل المؤقت بالأمر systemctl enable --now restic-backup.timer وتحقق منه عبر systemctl list-timers. وفق systemd.timer يعوّض Persistent=true التشغيل الذي فات أثناء إطفاء الجهاز، ويوزع RandomizedDelaySec= المضيفات التي تشترك في حاوية واحدة على فترة زمنية. لا تملك خدمات oneshot مهلة بدء افتراضية. وعلى حاسوب Linux محمول يعمل الزوج نفسه كوحدات مستخدم للمجلد المنزلي.

وتتولى خدمة أسبوعية باسم restic-maintenance.service وبالترويسة نفسها الاحتفاظ والفحوص:

ExecStart=/usr/local/bin/restic forget --prune --retry-lock 2h \
    --keep-daily 14 --keep-weekly 8 --keep-monthly 12 --keep-yearly 3
ExecStart=/usr/local/bin/restic check --read-data-subset=10%%

اكتب %% للحصول على علامة نسبة مئوية حرفية، لأن % تبدأ محددًا في ملفات الوحدات.

المراقبة والتنبيه عند الفشل

استخدم إشارتين مستقلتين، وإلا فسيُكتشف الفشل الصامت أثناء الحادثة نفسها. الإشارة الأولى هي OnFailure=. رموز الخروج في restic محددة: 1 يعني فشل الأمر، و3 يعني تعذر قراءة بعض الملفات المصدر، و10 غياب المستودع، و11 فشل القفل، و12 كلمة مرور خاطئة. يعد systemd أي رمز غير صفري فشلًا، وهذا صحيح بالنسبة إلى 3 أيضًا: اللقطة موجودة لكن فيها ثغرات.

# [email protected]
[Unit]
Description=Alert for failed %i

[Service]
Type=oneshot
EnvironmentFile=/etc/restic/restic.env
ExecStart=/usr/bin/curl -fsS --max-time 10 --retry 3 \
    --data-urlencode "text=%H: %i failed, see journalctl -u %i" \
    ${ALERT_WEBHOOK_URL}

قالب واحد يخدم جميع المهام، ويمرر إليه systemd متغيرات مثل MONITOR_EXIT_STATUS. اختبر السلسلة بالأمر systemctl start [email protected].

الإشارة الثانية هي dead man's switch: يستدعي ExecStartPost= عنوان heartbeat بعد النجاح فقط، ويطلق نظام المراقبة تنبيهًا إذا لم يصل أي استدعاء خلال 26 ساعة مثلًا. هكذا يُكتشف مؤقت معطّل أو مضيف مطفأ. وللحصول على رؤية من الخارج شغّل يوميًا restic snapshots --latest 1 --json --no-lock من جهاز آخر، وأطلق تنبيهًا إذا كانت أحدث لقطة أقدم من RPO.

نفّذ تمرين استعادة موقوتًا

يحوّل التمرين الافتراضات إلى أرقام. نفّذه بعد الإعداد الأولي وبعد التغييرات الكبيرة ومرة كل ربع سنة على الأقل، على آلة افتراضية نظيفة لا على مضيف الإنتاج، ومستخدمًا فقط ما سيتوفر للمهندس في حادثة حقيقية: مدخل مدير كلمات المرور ودليل التشغيل runbook.

  1. شغّل ساعة الإيقاف لحظة بدء الحادثة المفترضة.
  2. احصل على بيانات الاعتماد وثبّت restic واعرض قائمة اللقطات. عمر أحدث لقطة هو RPO الفعلي لديك.
  3. استعد الملفات والتفريغات ثم قاعدة البيانات، مع قياس زمن كل خطوة.
  4. شغّل نسخة staging من التطبيق وأكمل سيناريو حقيقيًا واحدًا.
  5. سجّل المدد والمشكلات ثم حدّث دليل التشغيل.
set -a; . ./restic.env; set +a
restic snapshots --latest 3
time restic restore latest --target /restore --verify
sudo -u postgres psql -f /restore/var/backups/postgresql/globals.sql
sudo -u postgres createdb app
time sudo -u postgres pg_restore --jobs=4 --dbname=app /restore/var/backups/postgresql/app
sudo -u postgres psql -d app -c "SELECT max(created_at) FROM orders;"

يعيد --verify قراءة الملفات المستعادة ويقارنها باللقطة. يبيّن الاستعلام الأخير مقدار البيانات التي فقدتها الاستعادة فعليًا. الأخطاء التي تفيد بأن بعض الأدوار موجودة مسبقًا متوقعة. تدرّب أيضًا على استعادة ملف واحد باستخدام --include /etc/nginx/nginx.conf، فهذا أكثر الطلبات الحقيقية شيوعًا.

قارن الزمن الإجمالي بقيمة RTO. المفاجآت المعتادة هي بيانات اعتماد مفقودة، وكلمة مرور لم تكن إلا على الخادم المعطوب، وإعادة بناء بطيئة للفهارس، وملفات منسية مثل مفاتيح TLS أو ملف .env خارج المسارات المنسوخة.

restic أو BorgBackup أو لقطات نظام الملفات

تجمع إعدادات كثيرة بين هذه الأساليب: لقطة ZFS أو Btrfs أو LVM للتراجع المحلي السريع، إضافة إلى نسخ احتياطية للملفات خارج الموقع. BorgBackup 1.4 هو الفرع المستقر الحالي.

الخاصية restic BorgBackup 1.4 لقطات نظام الملفات
وجهات التخزين محلي، SFTP، خادم REST، تخزين متوافق مع S3 وسحب أخرى محلي، أو SSH ويفضل وجود Borg على الخادم المجمّع نفسه؛ النسخة الخارجية تتطلب نسخًا متماثلًا مثل zfs send
التشفير مفعّل دائمًا يُختار عند إنشاء المستودع، اختياري يعتمد على التخزين
العميل المخترق rest-server --append-only، وobject lock على الحاوية borg serve --append-only محمية فقط عند نسخها إلى نظام يُدار بشكل منفصل
اتساق قواعد البيانات يحتاج إلى تفريغات يحتاج إلى تفريغات متسقة كما بعد الانهيار إذا جمعت لقطة ذرية واحدة البيانات وWAL
الاستعادة من ملف واحد إلى الشجرة كاملة، وتركيب عبر FUSE من ملف واحد إلى الشجرة كاملة، وتركيب عبر FUSE تراجع الوحدة كاملة أو النسخ من لقطة مركّبة
العملاء Linux وmacOS وWindows وBSD Linux وmacOS وBSD؛ Windows فقط عبر WSL أو Cygwin تجريبيًا يعتمد على نظام الملفات

اللقطة على المجمّع نفسه تحمي من نشر فاشل، لا من تعطل المجمّع أو اختراق المضيف، لذا فهي وحدها ليست نسخة بمعنى قاعدة 3-2-1.

قائمة التحقق

  • RPO وRTO مكتوبان ومتفق عليهما مع مالك الخدمة.
  • توجد ثلاث نسخ، إحداها خارج الموقع وأخرى غير قابلة للتعديل أو append-only.
  • كلمة المرور وعنوان المستودع وبيانات الاعتماد محفوظة في مدير كلمات مرور وبشكل غير متصل.
  • مجلدات قواعد البيانات العاملة مستبعدة، والتفريغات مضمّنة.
  • PostgreSQL مغطى بـ pg_dump مع الكائنات العامة أو بـ pg_basebackup جرى التحقق منه.
  • المؤقت يستخدم Persistent=true، ومعالج OnFailure= انطلق مرة واحدة على الأقل في اختبار.
  • نبضة heartbeat تنبّه عند غياب النجاح، وفحص خارجي ينبّه عند تقادم اللقطات.
  • يعمل forget --prune وcheck --read-data-subset أسبوعيًا.
  • بيانات اعتماد الخادم لا تستطيع حذف الإصدارات المقفلة.
  • جرى تمرين استعادة موقوت في هذا الربع، ويعكس دليل التشغيل نتائجه.

مقالات أخرى