توی این درس یاد میگیری چه چیزهایی یک کانتینر «روی لپتاپ» را به یک سرویس «روی سرور» تبدیل میکند. restart policy را درست انتخاب میکنی و میبینی بعد از ریبوت کدام کانتینر برمیگردد (--restart unless-stopped)، سرویس را با docker compose pull && docker compose up -d بدون از دست رفتن داده بهروزرسانی میکنی، با blue-green و nginx بدون قطعی جابهجا میشوی، از volume و دیتابیس بکاپ میگیری و برمیگردانی، و یک مانیتورینگ ساده مینویسی. تمرین اصلی: یک سرویس را بدون از دست رفتن داده بهروزرسانی میکنی.
مسئله: «کار میکرد» کافی نیست
Section titled “مسئله: «کار میکرد» کافی نیست”روی سرور چیزهایی پیش میآید که روی لپتاپ نمیآید: سرور ریبوت میشود، اپ کرش میکند، باید نسخهی جدید بدهی بدون اینکه مشتریها بفهمند، دیسک خراب میشود، و کسی باید بفهمد سرویس پایین است قبل از اینکه مشتری زنگ بزند. هر کدام از اینها یک عادت ساده میخواهد.
تشبیه: فروشگاه ۲۴ ساعته
Section titled “تشبیه: فروشگاه ۲۴ ساعته”یک فروشگاه ۲۴ ساعته چند چیز دارد: اگر برق رفت خودکار چراغها روشن میشوند (restart)، قفسهها را بدون بستن مغازه عوض میکنند (بهروزرسانی بیدردسر)، حسابها را هر شب یدک میگیرند (بکاپ)، و یک دوربین/زنگ دارد که اگر چیزی شد خبر بدهد (مانیتورینگ).
مثالهای عملی
Section titled “مثالهای عملی”مثال ۱: restart policy و ریبوت سرور
Section titled “مثال ۱: restart policy و ریبوت سرور”تفاوت policy ها را وقتی میبینی که daemon دوباره شروع میشود (مثل ریبوت سرور). روی یک daemon آزمایشی سه کانتینر میسازیم و بعد آن را ریستارت میکنیم:
D() { docker exec lx-prod-dind "$@"; }docker run -d --privileged --name lx-prod-dind -e DOCKER_TLS_CERTDIR= docker:dind >/dev/nulluntil docker exec lx-prod-dind docker info >/dev/null 2>&1; do sleep 1; doneD docker pull alpine >/dev/null 2>&1D docker run -d --name no-policy alpine sleep 1000 >/dev/nullD docker run -d --name unless-stopped-up --restart unless-stopped alpine sleep 1000 >/dev/nullD docker run -d --name unless-stopped-down --restart unless-stopped alpine sleep 1000 >/dev/nullD docker run -d --name always-down --restart always alpine sleep 1000 >/dev/nullD docker stop unless-stopped-down always-down >/dev/nullecho "--- قبل از «ریبوت»:"D docker ps -a --format '{{.Names}}: {{.Status}}' | sed -E 's/(Up|Exited \([0-9]+\)) .*/\1/' | sortdocker restart lx-prod-dind >/dev/nulluntil docker exec lx-prod-dind docker info >/dev/null 2>&1; do sleep 1; done; sleep 4echo "--- بعد از «ریبوت» (restart daemon):"D docker ps -a --format '{{.Names}}: {{.Status}}' | sed -E 's/(Up|Exited \([0-9]+\)) .*/\1/' | sort--- قبل از «ریبوت»:always-down: Exited (137)no-policy: Upunless-stopped-down: Exited (137)unless-stopped-up: Up--- بعد از «ریبوت» (restart daemon):always-down: Upno-policy: Exited (255)unless-stopped-down: Exited (137)unless-stopped-up: Upبعد از ریبوت: بدون policy برنگشت، unless-stopped که از قبل بالا بود برگشت، unless-stopped که دستی متوقف شده بود نماند (همان چیزی که خواستیم، «مگر دستی متوقف شود»)، و always که دستی متوقف شده بود هم برگشت (always بعد از ریستارت daemon همیشه برمیگرداند). برای سرویسهای ماندگار معمولاً unless-stopped بهترین انتخاب است.
| policy | بعد از کرش | بعد از ریبوت daemon | بعد از docker stop دستی |
|---|---|---|---|
no (پیشفرض) |
نه | نه | نه |
on-failure[:N] |
فقط با خروج غیرصفر (حداکثر N بار) | (در این درس آزمایش نشده؛ برای سرویس ماندگار استفاده نکن) | نه |
always |
بله | بله | بعد از ریستارت daemon بله |
unless-stopped |
بله | بله، اگر دستی متوقف نشده | نه (حتی بعد از ریبوت) |
برای اینکه ریبوت سرور واقعاً سرویس را برگرداند، خود سرویس docker هم باید با بوت شروع شود (روی لینوکس: sudo systemctl enable docker). Docker Desktop معمولاً خودش را با ورود کاربر راه میاندازد.
کدام policy برای دیتابیسی که نباید بعد از توقف دستی خودبهخود برگردد ولی بعد از ریبوت و کرش بالا بیاید مناسب است؟
unless-stopped: همیشه برگردان، مگر خودت دستی متوقفش کرده باشی.
مثال ۲: بهروزرسانی بدون از دست رفتن داده
Section titled “مثال ۲: بهروزرسانی بدون از دست رفتن داده”نسخهی ۱.۰ و ۲.۰ یک اپ کوچک (فقط نسخه را برمیگرداند)، و یک Redis با volume:
import osfrom http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.end_headers() self.wfile.write(f"version {os.environ['VERSION']}\n".encode())
def log_message(self, *args): pass
HTTPServer(("0.0.0.0", 8000), Handler).serve_forever()FROM python:3.12-alpineARG VERSIONENV VERSION=$VERSIONCOPY server.py .CMD ["python", "server.py"]name: lxup
services: db: image: redis:alpine volumes: - data:/data restart: unless-stopped web: image: nginx:alpine restart: unless-stopped app: image: lx-prod-app:${TAG:-1.0} ports: - "8360:8000" depends_on: - db restart: unless-stopped
volumes: data:نسخهی ۱.۰ را اجرا و دادهای ثبت میکنیم:
cd updocker build -q -t lx-prod-app:1.0 --build-arg VERSION=1.0 app >/dev/nulldocker build -q -t lx-prod-app:2.0 --build-arg VERSION=2.0 app >/dev/nulldocker compose up -d >/dev/null 2>&1sleep 2echo "نسخهی در حال اجرا: $(curl -s localhost:8360)"docker compose exec -T db redis-cli set customer:1 "علی" >/dev/nulldocker compose exec -T db redis-cli save >/dev/nullecho "دادهی ثبتشده: $(docker compose exec -T db redis-cli get customer:1)"نسخهی در حال اجرا: version 1.0دادهی ثبتشده: علیحالا بهروزرسانی: اول image های آماده را میگیریم (pull) و بعد با up -d فقط سرویسهای تغییرکرده دوباره ساخته میشوند. در همین حین، یک حلقهی پسزمینه هر ۰.۱ ثانیه سرویس را صدا میزند تا قطعی را بسنجیم:
cd up( while true; do curl -s -o /dev/null -m 1 -w '%{http_code}\n' localhost:8360; sleep 0.1; done > codes.txt ) &LOOP=$!sleep 1docker compose pull web db 2>&1 | grep -E 'Pulled|up to date' | sed -E 's/^ +//' | sort -uTAG=2.0 docker compose up -d 2>&1 | grep -E 'Recreate|Running|Started' | sed -E 's/^ +//' | awk '!s[$0]++' | sortsleep 2kill $LOOP 2>/dev/null; wait $LOOP 2>/dev/nullecho "نسخهی جدید: $(curl -s localhost:8360)"echo "داده بعد از بهروزرسانی: $(docker compose exec -T db redis-cli get customer:1)"echo "درخواستها: $(wc -l < codes.txt | tr -d ' ') — ناموفق: $(grep -vc '^200$' codes.txt)"Image nginx:alpine PulledImage redis:alpine PulledContainer lxup-app-1 RecreateContainer lxup-app-1 RecreatedContainer lxup-app-1 StartedContainer lxup-db-1 RunningContainer lxup-web-1 Runningنسخهی جدید: version 2.0داده بعد از بهروزرسانی: علیدرخواستها: 73 — ناموفق: 5فقط app دوباره ساخته شد و db و web دست نخوردند (Running)، داده ماند چون volume و سرویس db تغییر نکرد، و نسخهی جدید جواب میدهد. اما یک ویژگی را نباید گم کرد: وقتی app دوباره ساخته میشود، چند درخواست ناموفق میشود (عدد «ناموفق» بالا؛ در اجرای تو ممکن است فرق کند). یک نمونهی تنها ناچار لحظهای قطع میشود. برای صفر شدن قطعی باید دو نمونه داشته باشی (مثال بعد).
دستور استاندارد این الگو در production:
docker compose pull # گرفتن image های جدیدdocker compose up -d # فقط سرویسهای تغییرکرده دوباره ساخته میشوندdocker image prune -f # پاک کردن image های بیاستفاده (اختیاری)هرگز برای بهروزرسانی docker compose down -v نزن: -v volume ها را پاک میکند.
مثال ۳: blue-green، بدون قطعی با nginx
Section titled “مثال ۳: blue-green، بدون قطعی با nginx”ایده: نسخهی جدید (green) را کنار نسخهی قدیمی (blue) بالا بیاور، وقتی سالم بود ترافیک را با یک reload به آن بده، و بعد blue را خاموش کن. nginx reload «نرم» است: درخواستهای در حال انجام را تمام میکند و کارگر جدید با تنظیم جدید شروع میشود.
server { listen 80; resolver 127.0.0.11 valid=2s; include /etc/nginx/active/backend.inc; location / { proxy_pass $backend; }}set $backend http://blue:8000;cd bgdocker network create lxbg >/dev/nulldocker run -d --name lx-pr-blue --network lxbg --network-alias blue lx-prod-app:1.0 >/dev/nulldocker run -d --name lx-pr-green --network lxbg --network-alias green lx-prod-app:2.0 >/dev/nulldocker run -d --name lx-pr-nginx --network lxbg -p 8361:80 \ -v "$PWD/conf:/etc/nginx/conf.d:ro" -v "$PWD/active:/etc/nginx/active:ro" nginx:alpine >/dev/nullsleep 2echo "قبل از جابهجایی: $(curl -s localhost:8361)"( while true; do curl -s -o /dev/null -m 2 -w '%{http_code}\n' localhost:8361; sleep 0.05; done > codes.txt ) &LOOP=$!sleep 1echo 'set $backend http://green:8000;' > active/backend.incdocker exec lx-pr-nginx nginx -t 2>&1 | tail -1docker exec lx-pr-nginx nginx -s reload 2>&1 | grep -v noticesleep 2docker rm -f lx-pr-blue >/dev/nullsleep 2kill $LOOP 2>/dev/null; wait $LOOP 2>/dev/nullecho "بعد از جابهجایی: $(curl -s localhost:8361)"echo "درخواستها: $(wc -l < codes.txt | tr -d ' ') — ناموفق: $(grep -vc '^200$' codes.txt)"docker rm -f lx-pr-green lx-pr-nginx >/dev/null; docker network rm lxbg >/dev/nullقبل از جابهجایی: version 1.0nginx: configuration file /etc/nginx/nginx.conf test is successfulبعد از جابهجایی: version 2.0درخواستها: 65 — ناموفق: 0ترافیک بدون هیچ درخواست ناموفقی از نسخهی ۱ به نسخهی ۲ رفت و بعد blue حذف شد. بازگرداندن (rollback) هم همینقدر ساده است: blue را نگه میداری و backend.inc را برمیگردانی. (این با nginx که آدرس upstream را در حین کار resolve میکند ممکن شد، همان resolver و متغیر که در پروژهی Compose دیدی.)
مثال ۴: بکاپ volume
Section titled “مثال ۴: بکاپ volume”دادهی مهم در volume است؛ بکاپ یعنی یک کپی از آن. سادهترین روش: یک کانتینر کمکی که volume و یک پوشهی میزبان را mount میکند و tar میسازد:
mkdir -p backupdocker volume create lx-pr-data >/dev/nulldocker run --rm -v lx-pr-data:/data alpine sh -c 'echo "سفارش ۱۰۰۱" > /data/orders.txt; echo "سفارش ۱۰۰۲" >> /data/orders.txt'# بکاپdocker run --rm -v lx-pr-data:/data:ro -v "$PWD/backup":/backup alpine tar czf /backup/data.tgz -C /data .ls -l backup/data.tgz | awk '{print "فایل بکاپ:", $9, $5, "بایت"}'# فاجعه!docker volume rm lx-pr-data >/dev/null# بازیابی در یک volume تازهdocker volume create lx-pr-data2 >/dev/nulldocker run --rm -v lx-pr-data2:/data -v "$PWD/backup":/backup:ro alpine tar xzf /backup/data.tgz -C /datadocker run --rm -v lx-pr-data2:/data:ro alpine cat /data/orders.txtفایل بکاپ: backup/data.tgz 158 بایتسفارش ۱۰۰۱سفارش ۱۰۰۲volume اصلی پاک شد، ولی از فایل tar.gz به volume تازه برگشت. نکتهی مهم: بکاپ فایل از دیتابیسِ در حال کار ممکن است نیمهکاره (ناسازگار) باشد؛ برای دیتابیس یا کانتینر را موقتاً متوقف کن یا از ابزار خود دیتابیس dump بگیر (مثال بعد).
مثال ۵: بکاپ منطقی دیتابیس با pg_dump
Section titled “مثال ۵: بکاپ منطقی دیتابیس با pg_dump”docker run -d --name lx-pr-pg1 -e POSTGRES_PASSWORD=lxdemo postgres:16-alpine >/dev/nullfor i in $(seq 30); do docker exec lx-pr-pg1 pg_isready -U postgres >/dev/null 2>&1 && break; sleep 1; done; sleep 2docker exec lx-pr-pg1 psql -U postgres -qc "create table clients(id serial primary key, name text); insert into clients(name) values ('علی'),('سارا'),('رضا');"docker exec lx-pr-pg1 pg_dump -U postgres postgres > backup/db.sqlecho "dump: $(wc -l < backup/db.sql | tr -d ' ') خط، شامل: $(grep -c 'COPY public.clients' backup/db.sql) جدول clients"docker rm -f lx-pr-pg1 >/dev/null# بازیابی روی یک دیتابیس کاملاً تازهdocker run -d --name lx-pr-pg2 -e POSTGRES_PASSWORD=lxdemo postgres:16-alpine >/dev/nullfor i in $(seq 30); do docker exec lx-pr-pg2 pg_isready -U postgres >/dev/null 2>&1 && break; sleep 1; done; sleep 2docker exec -i lx-pr-pg2 psql -U postgres -q < backup/db.sql >/dev/null 2>&1docker exec lx-pr-pg2 psql -U postgres -tA -c "select count(*) || ' ردیف بازیابی شد: ' || string_agg(name, '، ' order by id) from clients"docker rm -f lx-pr-pg2 >/dev/nulldump: 97 خط، شامل: 1 جدول clients3 ردیف بازیابی شد: علی، سارا، رضاpg_dump یک فایل SQL سازگار میسازد (حتی وقتی دیتابیس در حال کار است) و میتوان آن را روی یک دیتابیس تازه بازیابی کرد. هر دیتابیس ابزار خودش را دارد (mysqldump، redis-cli --rdb،…). بکاپی که بازیابیاش را آزمایش نکردهای، بکاپ نیست. بکاپ را زمانبندی کن (cron)، بیرون از همان سرور ذخیره کن و دورهای بازیابی را تست کن.
مثال ۶: مانیتورینگ ساده
Section titled “مثال ۶: مانیتورینگ ساده”به مانیتورینگ پیچیده نیاز نداری تا شروع کنی. چهار چیز: healthcheck (درس قبل)، docker ps/stats، یک اسکریپت بررسی که از cron اجرا میشود، و فضای دیسک. یک اسکریپت بررسی که برای هر کانتینری که سالم نیست هشدار میدهد:
#!/bin/sh# هر کانتینری که در حال اجرا نیست یا unhealthy است را گزارش میدهدbad=0for c in $(docker ps -a --filter "label=monitor=yes" --format '{{.Names}}'); do state=$(docker inspect --format '{{.State.Status}}' "$c") health=$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$c") if [ "$state" != "running" ] || [ "$health" = "unhealthy" ]; then echo "ALERT: $c state=$state health=$health"; bad=1 fidone[ $bad -eq 0 ] && echo "OK: همهی سرویسهای نشاندارشده سالماند"exit $baddocker run -d --name lx-pr-ok --label monitor=yes alpine sleep 300 >/dev/nulldocker run -d --name lx-pr-sick --label monitor=yes --health-cmd 'false' --health-interval 1s --health-retries 1 alpine sleep 300 >/dev/nullsleep 4sh mon/check.sh; echo "کد خروج اسکریپت: $?"docker rm -f lx-pr-sick >/dev/nullsh mon/check.sh; echo "کد خروج اسکریپت: $?"docker rm -f lx-pr-ok >/dev/nullALERT: lx-pr-sick state=running health=unhealthyکد خروج اسکریپت: 1OK: همهی سرویسهای نشاندارشده سالماندکد خروج اسکریپت: 0با label=monitor=yes فقط کانتینرهای مهم را بررسی میکنی. اسکریپت وقتی مشکل ببیند کد خروج ۱ میدهد؛ از cron میتوانی خروجی را ایمیل/پیام کنی یا به سرویس پایش بفرستی. ابزارهای کاملتر: Prometheus + cAdvisor، Uptime Kuma، Grafana، و جمعآوری لاگ مرکزی؛ اینها خارج از این درساند.
پشت پرده
Section titled “پشت پرده”- restart policy را خود daemon اجرا میکند (نه systemd و نه Compose): وقتی daemon بالا میآید، لیست کانتینرها را میخواند و آنهایی که policy شان اجازه میدهد را شروع میکند. بنابراین سرویس
dockerباید با بوت بالا بیاید.live-restoreدرdaemon.jsonاجازه میدهد کانتینرها در زمان ریستارت خود daemon (مثلاً برای بهروزرسانی داکر) زنده بمانند. docker compose up -dحالت مطلوب را با حالت فعلی مقایسه میکند و فقط سرویسهای تغییرکرده را دوباره میسازد (هش پیکربندی روی هر کانتینر). همین است کهdbدستنخورده ماند.- یک نمونهی تنها همیشه هنگام جایگزینی لحظهای قطع میشود؛ بیقطعی یعنی دو نمونه + یک proxy/load balancer که ترافیک را جابهجا میکند (blue-green یا rolling). ابزارهای هماهنگی (Swarm، Kubernetes) همین را خودکار میکنند.
- بکاپ فایل از volume یک عکس لحظهای از بایتها است؛ اگر برنامه وسط نوشتن باشد ممکن است نیمهکاره باشد. dump منطقی (
pg_dump) سازگاری را تضمین میکند.
جدولهای مرجع
Section titled “جدولهای مرجع”| کار | دستور |
|---|---|
| بازگشت بعد از کرش و ریبوت | --restart unless-stopped / restart: unless-stopped |
| سرویس docker با بوت | sudo systemctl enable docker |
| بهروزرسانی | docker compose pull && docker compose up -d |
| ببین چه تغییر میکند | docker compose config، up -d (فقط تغییرها) |
| rollback | tag قبلی (TAG=1.0 docker compose up -d) |
| بکاپ volume | docker run --rm -v VOL:/data:ro -v $PWD:/b alpine tar czf /b/x.tgz -C /data . |
| بازیابی volume | tar xzf در volume تازه |
| بکاپ Postgres | docker exec DB pg_dump -U user db > db.sql |
| وضعیت | docker ps، docker stats، docker compose ps |
| فضا | docker system df |
| عادت | چرا |
|---|---|
tag دقیق (نه latest) |
قابل ردیابی و برگشتپذیر |
.env برای تنظیمات و رازها |
خارج از Git و image |
| سقف حافظه/CPU و لاگ | سرور را از یک کانتینر محافظت کن |
healthcheck و restart |
تشخیص و بازگشت خودکار |
| بکاپ آزمایششده | فقط بکاپ بازیابیشده معتبر است |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) down -v در بهروزرسانی
Section titled “۱) down -v در بهروزرسانی”-v volume های نامدار را پاک میکند و داده میرود. راهحل: docker compose up -d (بدون down) یا down بدون -v.
۲) استفاده از latest و ندانستن نسخه
Section titled “۲) استفاده از latest و ندانستن نسخه”یک pull میتواند نسخهی کاملاً متفاوتی بیاورد و بازگشت سخت شود. راهحل: tag دقیق نسخه (lx-prod-app:2.0) و تغییر آن در .env.
۳) فراموش کردن فعال بودن سرویس docker
Section titled “۳) فراموش کردن فعال بودن سرویس docker”systemctl is-enabled docker # باید enabled باشدsudo systemctl enable --now dockerاگر سرویس docker با بوت بالا نیاید، حتی restart: always هم کاری نمیکند. (این دستورها روی این مک قابل اجرا نیست؛ نمونهاند.)
۴) بکاپ فقط روی همان سرور
Section titled “۴) بکاپ فقط روی همان سرور”دیسک که بسوزد، بکاپ هم میرود. راهحل: بکاپ را به جای دیگر (سرور دیگر، ذخیرهساز ابری) کپی کن.
۵) بکاپ آزمایشنشده
Section titled “۵) بکاپ آزمایشنشده”docker run --rm -v "$PWD/backup":/b:ro alpine sh -c 'tar tzf /b/data.tgz | head -3; echo "---"; tar tzf /b/nonexistent.tgz 2>&1 | head -1'././orders.txt---tar: can't open '/b/nonexistent.tgz': No such file or directoryاولی فایل سالم را فهرست میکند؛ دومی نشان میدهد فایل بکاپی که وجود ندارد یا خراب است چه خطایی میدهد، و باید قبل از روز فاجعه بفهمی. راهحل: بازیابی را دورهای تمرین کن.
یک کانتینر alpine sleep 300 با --restart unless-stopped بساز و با docker inspect سیاست و تعداد restart را بخوان.
دیدن جواب
docker run -d --name lx-e1 --restart unless-stopped alpine sleep 300 >/dev/nulldocker inspect lx-e1 --format 'policy={{.HostConfig.RestartPolicy.Name}} restarts={{.RestartCount}}'docker rm -f lx-e1 >/dev/nullpolicy=unless-stopped restarts=0تمرین اصلی: یک سرویس را بدون از دست رفتن داده بهروزرسانی کن. با up/compose.yaml نسخهی ۱.۰ را اجرا کن، یک کلید در Redis بنویس، با TAG=2.0 docker compose up -d بهروز کن و ثابت کن (۱) نسخه عوض شد (۲) کلید ماند. بعد با TAG=1.0 rollback کن.
دیدن جواب
cd updocker compose up -d >/dev/null 2>&1; sleep 2docker compose exec -T db redis-cli set plan "gold" >/dev/null; docker compose exec -T db redis-cli save >/dev/nullecho "۱.۰: $(curl -s localhost:8360)"TAG=2.0 docker compose up -d >/dev/null 2>&1; sleep 2echo "۲.۰: $(curl -s localhost:8360) | داده: $(docker compose exec -T db redis-cli get plan)"TAG=1.0 docker compose up -d >/dev/null 2>&1; sleep 2echo "rollback: $(curl -s localhost:8360) | داده: $(docker compose exec -T db redis-cli get plan)"docker compose down -v >/dev/null 2>&1۱.۰: version 1.0۲.۰: version 2.0 | داده: goldrollback: version 1.0 | داده: goldیک اسکریپت بکاپ و بازیابی کامل بنویس: backup VOL FILE و restore VOL FILE. با یک volume تازه که فایل دارد تست کن: بکاپ بگیر، volume را پاک کن، با restore برگردان و محتوا را مقایسه کن.
دیدن جواب
backup() { docker run --rm -v "$1":/data:ro -v "$PWD":/b alpine tar czf /b/"$2" -C /data .; }restore() { docker volume create "$1" >/dev/null; docker run --rm -v "$1":/data -v "$PWD":/b:ro alpine tar xzf /b/"$2" -C /data; }docker volume create lx-e3 >/dev/nulldocker run --rm -v lx-e3:/data alpine sh -c 'echo alpha > /data/a.txt; echo beta > /data/b.txt'before=$(docker run --rm -v lx-e3:/data:ro alpine sh -c 'cat /data/*.txt | md5sum')backup lx-e3 e3.tgzdocker volume rm lx-e3 >/dev/nullrestore lx-e3 e3.tgzafter=$(docker run --rm -v lx-e3:/data:ro alpine sh -c 'cat /data/*.txt | md5sum')[ "$before" = "$after" ] && echo "بازیابی دقیق بود (هش محتوا یکی است)"docker volume rm lx-e3 >/dev/nullبازیابی دقیق بود (هش محتوا یکی است)آزمونک
Section titled “آزمونک”کدام policy بعد از ریبوت سرور کانتینری را که دستی متوقف کرده بودی بالا نمیآورد ولی بعد از کرش بالا میآورد؟
always بعد از ریستارت daemon همهی کانتینرها را برمیگرداند.
چرا docker compose down -v در بهروزرسانی خطرناک است؟
فقط pull و up -d.
docker compose pull && up -d چه سرویسهایی را دوباره میسازد؟
هش پیکربندی هر کانتینر مقایسه میشود.
چطور بهروزرسانی بدون قطعی انجام میشود؟
یک نمونهی تنها هنگام جایگزینی قطع میشود.
چرا برای دیتابیس بهجای کپی فایل volume از pg_dump استفاده میکنیم؟
dump منطقی لحظهای سازگار است.
بکاپ زمانی معتبر است که…
بکاپ آزمایشنشده یعنی امید، نه بکاپ.
جمعبندی
Section titled “جمعبندی”restart: unless-stoppedبرای سرویسهای ماندگار؛ سرویسdockerرا هم با بوت فعال کن.- بهروزرسانی: tag دقیق +
docker compose pull && docker compose up -d؛ هرگزdown -v. rollback = tag قبلی. - یک نمونه هنگام جایگزینی لحظهای قطع میشود؛ برای بیقطعی blue-green/rolling با proxy.
- بکاپ: tar از volume (با کانتینر کمکی) برای فایلها،
pg_dumpو مشابه برای دیتابیس؛ بیرون از سرور ذخیره و دورهای بازیابی را تست کن. - مانیتورینگ ساده: healthcheck، label برای کانتینرهای مهم، اسکریپت بررسی با cron،
docker statsوdocker system df. - سقف حافظه/CPU و لاگ (دو درس قبل) هم جزو productionاند.
| دستور | کاری که میکند |
|---|---|
docker run --restart unless-stopped IMG | بازگشت بعد از کرش و ریبوت |
sudo systemctl enable docker | سرویس docker با بوت |
docker compose pull && docker compose up -d | بهروزرسانی بدون از دست رفتن داده |
TAG=1.0 docker compose up -d | rollback به نسخهی قبل |
docker run --rm -v V:/data:ro -v $PWD:/b alpine tar czf /b/v.tgz -C /data . | بکاپ volume |
docker run --rm -v V2:/data -v $PWD:/b:ro alpine tar xzf /b/v.tgz -C /data | بازیابی در volume تازه |
docker exec DB pg_dump -U user db > db.sql | بکاپ منطقی Postgres |
docker exec nginx nginx -s reload | جابهجایی نرم ترافیک |
docker system df | مصرف دیسک داکر |