پروژهٔ پایانی؛ بازیابی یک رخداد
در این درس ۶ بخش
این پروژه پیشنیازهای دورهٔ مقدماتی و درسهای عیبیابی، خودکارسازی و پشتیبان همین دوره را کنار هم میگذارد. تحویل نهایی، یک runbook قابل اجراست: نشانه، شاهد، فرضیه، تغییر کوچک، بررسی نتیجه و برگشت. سناریو کاملاً آفلاین است و به Python 3، Bash و ابزارهای GNU نیاز دارد. فایلهای تمرین را با بلوک آمادهسازی همین درس بساز و سپس در همان ترمینال ادامه بده؛ پوشهٔ incident-lab بهصورت محلی و مستقل ساخته میشود. فایلها ساختگیاند؛ هیچ سرویس، محدودیت حافظه یا پیکربندی واقعی سیستم تغییر نمیکند. healthcheck یک مدل سادهٔ ظرفیت را ارزیابی میکند و نتیجهٔ آن آزمون زندهٔ سرویس، سنجش بار یا اثبات رفع نشت حافظه نیست.
آمادهسازی کامل فایلهای پروژه
بلوک زیر را یکجا در Bash اجرا کن. Python 3 فایلهای متنی ساختگی را در یک پوشهٔ تازه داخل خانهٔ کاربر میسازد؛ برنامهای نصب نمیشود و سرویس واقعی تغییر نمیکند. پس از پیام Fixture files ready، در همان ترمینال مرحلهٔ ثبت شواهد را ادامه بده. این فایلها سناریوی آموزشیاند و اطلاعات سرور تو را نشان نمیدهند.
FIXTURE_BASE=$(mktemp -d "$HOME/linux2-fixtures.XXXXXX") || exit 1
export FIXTURE_BASE
python3 - <<'PY_FIXTURES'
import os
from pathlib import Path
root = Path(os.environ["FIXTURE_BASE"])
files = {
"incident-lab/README.txt": "Linux 2 final project: offline incident recovery\nRequirements: Python 3, Bash, GNU coreutils. No network or elevated privileges.\nCopy this directory to a fresh mktemp directory under your own HOME as shown in lesson 16.\nInitial healthcheck must exit 78; the backup configuration must pass with exit 0.\nThe healthcheck computes base + workers * per-worker peak against a fictional limit.\nIt does not run a server, allocate workload memory, read cgroups, or validate production health.\nUse only the copied fixture. Preserve evidence before changing live/service.conf.\n",
"incident-lab/backup/SHA256SUMS": "9cab169b2f203d02835283e68d08617d72acb0a4db04e9e2a3d4f6c340629df7 service.conf\n",
"incident-lab/backup/service.conf": "[service]\nport=8080\nworkers=2\nmode=training\n",
"incident-lab/events.log": "2026-10-29T09:55:00Z training-web status=healthy workers=2 requests_failed=0 oom_kill_total=0\n2026-10-29T10:00:00Z deploy change=workers old=2 new=8 actor=training-operator\n2026-10-29T10:02:00Z training-web status=degraded requests_failed=12 oom_kill_total=2\n2026-10-29T10:03:00Z resource memory_limit_mib=256 host_free_mib=4096\n# Synthetic fixture. No actual kernel or service event is represented.\n",
"incident-lab/healthcheck.py": "#!/usr/bin/env python3\n\"\"\"Offline capacity model; does not inspect or change the host system.\"\"\"\nimport configparser\nimport pathlib\nimport sys\n\ndef read_config(path):\n parser = configparser.ConfigParser(interpolation=None)\n with pathlib.Path(path).open(encoding=\"utf-8\") as stream:\n parser.read_file(stream)\n return parser\n\ndef main():\n if len(sys.argv) != 3:\n print(\"usage: healthcheck.py CONFIG INVENTORY\", file=sys.stderr)\n return 64\n try:\n conf = read_config(sys.argv[1])\n host = read_config(sys.argv[2])\n workers = conf.getint(\"service\", \"workers\")\n port = conf.getint(\"service\", \"port\")\n budget = host.getint(\"host\", \"memory_limit_mib\")\n base = host.getint(\"host\", \"base_mib\")\n peak = host.getint(\"host\", \"worker_peak_mib\")\n if not 1 <= workers <= 16 or not 1024 <= port <= 65535:\n raise ValueError(\"workers or port outside training range\")\n if min(budget, base, peak) <= 0:\n raise ValueError(\"capacity inputs must be positive\")\n except (OSError, ValueError, configparser.Error) as exc:\n print(\"invalid fixture: \" + str(exc), file=sys.stderr)\n return 65\n required = base + workers * peak\n print(f\"SIMULATION required_mib={required} budget_mib={budget}\")\n if required > budget:\n print(\"capacity model: FAIL\", file=sys.stderr)\n return 78\n print(\"capacity model: PASS (not a live service health test)\")\n return 0\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n",
"incident-lab/inventory.txt": "[host]\ndistribution=Debian 12 (fictional host)\nservice=training-web\nmemory_limit_mib=256\nbase_mib=96\nworker_peak_mib=48\nrpo_minutes=5\nrto_minutes=15\n",
"incident-lab/live/service.conf": "[service]\nport=8080\nworkers=8\nmode=training\n",
"incident-lab/runbook.txt": "OFFLINE INCIDENT RECOVERY RUNBOOK\nAll fixture data is synthetic. Do not write to system configuration.\n\nScope and impact:\nRPO / RTO (scenario targets):\nEvidence paths and timestamps:\nTimeline:\nHypothesis:\nEvidence that would disprove the hypothesis:\nSmallest proposed change:\nCandidate validation command and expected exit status:\nPublish command:\nVerification and remaining limitations:\nRollback trigger:\nRollback command and expected state:\nRecovery retry:\nFollow-up guard / monitoring / restore drill:\n"
}
for name, content in files.items():
target = root / name
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(content, encoding="utf-8")
print("Fixture files ready:", len(files))
PY_FIXTURES
test "$?" -eq 0 || exit 1
cd "$FIXTURE_BASE" || exit 1
printf "FIXTURE_ROOT=%s\n" "$FIXTURE_BASE"
صورت رخداد و هدف بازیابی
موجودی، یک میزبان فرضی Debian 12 با خدمت آموزشی را توصیف میکند. ساعت ۱۰ شمار workerها از دو به هشت افزایش یافته و سپس لاگ ساختگی، خطای درخواست و افزایش oom_kill را ثبت کرده است. سقف خدمت ۲۵۶ MiB، مصرف پایهٔ مدل ۹۶ و سهم هر worker برابر ۴۸ MiB است. هدف پیشنهادی سناریو، بازگرداندن پیکربندی شناختهشده در پانزده دقیقه با حداکثر پنج دقیقه ازدسترفتن تغییر پیکربندی است. این اعداد توافق آزمایشاند و به سامانهٔ واقعی تعمیم داده نمیشوند. پیش از لمس فایل زنده، نسخهٔ پیش از تغییر و شواهد را حفظ میکنیم.
umask 077
test -f incident-lab/healthcheck.py || exit 1
LAB16=$(mktemp -d "$HOME/linux2-incident.XXXXXX") || exit 1
export LAB16
cp -R -- incident-lab/. "$LAB16/"
mkdir -- "$LAB16/evidence"
cp -- "$LAB16/live/service.conf" "$LAB16/evidence/service.conf.before"
cp -- "$LAB16/events.log" "$LAB16/evidence/events.before.log"
cat "$LAB16/inventory.txt"
cat "$LAB16/events.log"
diff -u -- "$LAB16/backup/service.conf" "$LAB16/live/service.conf"
python3 "$LAB16/healthcheck.py" "$LAB16/live/service.conf" "$LAB16/inventory.txt"diff برای دو فایل متفاوت با کد ۱ پایان مییابد؛ اینجا همان نتیجهٔ مورد انتظار است، پس آن را خطای ابزار تعبیر نکن. healthcheck نخست باید نیاز ۴۸۰ در برابر بودجهٔ ۲۵۶ و خروج ۷۸ نشان دهد. inventory مشخصات سناریو را میگوید، نه مشخصات دستگاه تو را؛ خواندن /etc/os-release دستگاه میزبان برای این رخداد شاهد مناسبی نیست. همچنین حافظهٔ آزاد کل میزبان با سقف یک cgroup یکسان نیست. در محیط واقعی باید محدودهٔ خدمت، محدودیت مؤثر، شمارندههای درست و زمان ثبت داده را پیدا کرد؛ این پروژه هیچیک را از سیستم تو استخراج نمیکند.
از همزمانی به فرضیهٔ قابل آزمون
فرضیهٔ نخست: افزایش workerها با سقف فعلی سازگار نیست. شاهدها شامل اختلاف پیکربندی، ترتیب زمانی رویدادها و محاسبهٔ ظرفیتاند. وجود oom_kill نشاندهندهٔ کشتهشدن فرایندها توسط سازوکار OOM است، اما بهتنهایی نشت حافظه یا مقصر بودن آخرین تغییر را ثابت نمیکند؛ شمارنده نیز تجمعی است و تغییر آن در بازه اهمیت دارد. فرضیهٔ رقیب، خرابی محتوای پیکربندی یا تغییر محدودیت است. در این fixture، موجودی ثابت و نسخهٔ پشتیبان قابل بررسی است. کاهش workerها به مقدار شناختهشده، تغییر کوچکتر و قابل برگشتتری از بالابردن بیبررسی سقف حافظه است.
اعتبارسنجی نامزد و انتشار
(cd "$LAB16/backup" && sha256sum -c SHA256SUMS) || exit 1
(
candidate=$(mktemp "$LAB16/live/.candidate.XXXXXX") || exit 1
trap 'rm -f -- "$candidate"' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
cp -- "$LAB16/backup/service.conf" "$candidate" || exit 1
python3 "$LAB16/healthcheck.py" "$candidate" "$LAB16/inventory.txt" || exit 1
mv -fT -- "$candidate" "$LAB16/live/service.conf" || exit 1
) || exit 1
python3 "$LAB16/healthcheck.py" "$LAB16/live/service.conf" "$LAB16/inventory.txt"
sha256sum -- "$LAB16/live/service.conf" > "$LAB16/evidence/after.sha256"نسخهٔ پشتیبان ابتدا با manifest بررسی میشود، سپس نامزد در همان پوشهٔ فایل زنده ساخته و پیش از انتشار ارزیابی میشود. با دو worker، نیاز مدل ۱۹۲ MiB و کد خروج صفر است. جابهجایی نام روی همان فایلسیستم، انتشار تکفایل را اتمی میکند. اگر نامزد رد شود، فایل زنده دستنخورده میماند؛ این ویژگی را در runbook بنویس. ثبت چکیدهٔ پس از تغییر برای مقایسه مفید است، اما اصالت یا سلامت برنامه را اثبات نمیکند. در خدمت واقعی، مرحلهٔ بعد آزمون عملکرد، بررسی خطای تازه و مشاهدهٔ مصرف در بازهٔ توافقشده است؛ fixture چنین دادهٔ تازهای تولید نمیکند.
برگشت نیز باید قابل اجرا باشد
شرط برگشت را پیشاپیش بنویس: اگر پس از انتشار، ناسازگاری تازهای ظاهر شد، نسخهٔ پیش از مداخله را بازگردان و رخداد را باز نگه دار. در این سناریو نسخهٔ قبل، همان پیکربندی هشت worker و ناسالم است؛ rollback فقط وضعیت پیشین را بازسازی میکند و مساوی بهبود نیست. فرمان زیر این واقعیت را عمداً آزمایش میکند. پس از مشاهدهٔ خروج ۷۸، بلوک بازیابی نامزد را دوباره اجرا کن تا تحویل نهایی در وضعیت سالم مدل باشد. شواهد اولیه را بازنویسی نکن و زمان آزمایش برگشت را جدا ثبت کن.
(
undo=$(mktemp "$LAB16/live/.rollback.XXXXXX") || exit 1
trap 'rm -f -- "$undo"' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
cp -- "$LAB16/evidence/service.conf.before" "$undo" || exit 1
mv -fT -- "$undo" "$LAB16/live/service.conf" || exit 1
) || exit 1
if python3 "$LAB16/healthcheck.py" "$LAB16/live/service.conf" "$LAB16/inventory.txt"; then
printf 'unexpected success\n'; exit 1
else
test "$?" -eq 78 || exit 1
fiتحویل، تمرین و پاسخ
runbook.txt بسته را کامل کن: دامنه و اثر رخداد، هدف بازیابی، خط زمانی با نام فایل شاهد، فرضیه و شاهد ردکننده، فرمان تغییر، خروج مورد انتظار، شرط برگشت و کار پیگیری. معیار قبولی این است که شخص دیگری از روی نوشته، شکست اولیه، رد نامزد بد، بازیابی، برگشت و بازیابی دوباره را بازتولید کند. تمرین: اگر مدل سبز شد اما خطاهای درخواست ادامه داشت، آیا رخداد بسته است؟ پاسخ: خیر؛ صحت مدل ظرفیت فقط یکی از شرطهاست و باید فرضیهٔ بعدی و آزمون عملکرد بررسی شود. برای پیشگیری، guard ظرفیت را به مرحلهٔ تولید Bash یا اعتبارسنجی مدیریت پیکربندی پیوند بده و بازیابی نسخهٔ پشتیبان را دورهای تمرین کن.
منابع رسمی انگلیسی:
Linux Kernel — Control Group v2, memory interface: https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
systemd — Resource control: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html
GNU Coreutils — mv: https://www.gnu.org/software/coreutils/manual/coreutils.html#mv-invocation
NIST SP 800-34 Rev. 1: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-34r1.pdf
تألیف و مثالهای اختصاصی: تحریریه درسآوند. منابع رسمی انگلیسی برای مطالعهٔ تکمیلی معرفی شدهاند.
متن درسهای عمومی را بدون ورود بخوانید.