رفتن به محتوا
LoopX

داکر در production

توی این درس یاد می‌گیری چه چیزهایی یک کانتینر «روی لپ‌تاپ» را به یک سرویس «روی سرور» تبدیل می‌کند. restart policy را درست انتخاب می‌کنی و می‌بینی بعد از ریبوت کدام کانتینر برمی‌گردد (--restart unless-stopped)، سرویس را با docker compose pull && docker compose up -d بدون از دست رفتن داده به‌روزرسانی می‌کنی، با blue-green و nginx بدون قطعی جابه‌جا می‌شوی، از volume و دیتابیس بکاپ می‌گیری و برمی‌گردانی، و یک مانیتورینگ ساده می‌نویسی. تمرین اصلی: یک سرویس را بدون از دست رفتن داده به‌روزرسانی می‌کنی.

مسئله: «کار می‌کرد» کافی نیست

Section titled “مسئله: «کار می‌کرد» کافی نیست”

روی سرور چیزهایی پیش می‌آید که روی لپ‌تاپ نمی‌آید: سرور ریبوت می‌شود، اپ کرش می‌کند، باید نسخه‌ی جدید بدهی بدون اینکه مشتری‌ها بفهمند، دیسک خراب می‌شود، و کسی باید بفهمد سرویس پایین است قبل از اینکه مشتری زنگ بزند. هر کدام از اینها یک عادت ساده می‌خواهد.

تشبیه: فروشگاه ۲۴ ساعته

Section titled “تشبیه: فروشگاه ۲۴ ساعته”

یک فروشگاه ۲۴ ساعته چند چیز دارد: اگر برق رفت خودکار چراغ‌ها روشن می‌شوند (restart)، قفسه‌ها را بدون بستن مغازه عوض می‌کنند (به‌روزرسانی بی‌دردسر)، حساب‌ها را هر شب یدک می‌گیرند (بکاپ)، و یک دوربین/زنگ دارد که اگر چیزی شد خبر بدهد (مانیتورینگ).

چهار پایه‌ی اجرای پایدار: بازگشت خودکار، به‌روزرسانی بی‌دردسر، بکاپ و مانیتورینگ. هر کدام ساده است و با هم یک سرویس قابل‌اعتماد می‌سازند.

مثال ۱: restart policy و ریبوت سرور

Section titled “مثال ۱: restart policy و ریبوت سرور”

تفاوت policy ها را وقتی می‌بینی که daemon دوباره شروع می‌شود (مثل ریبوت سرور). روی یک daemon آزمایشی سه کانتینر می‌سازیم و بعد آن را ریستارت می‌کنیم:

Terminal window
D() { docker exec lx-prod-dind "$@"; }
docker run -d --privileged --name lx-prod-dind -e DOCKER_TLS_CERTDIR= docker:dind >/dev/null
until docker exec lx-prod-dind docker info >/dev/null 2>&1; do sleep 1; done
D docker pull alpine >/dev/null 2>&1
D docker run -d --name no-policy alpine sleep 1000 >/dev/null
D docker run -d --name unless-stopped-up --restart unless-stopped alpine sleep 1000 >/dev/null
D docker run -d --name unless-stopped-down --restart unless-stopped alpine sleep 1000 >/dev/null
D docker run -d --name always-down --restart always alpine sleep 1000 >/dev/null
D docker stop unless-stopped-down always-down >/dev/null
echo "--- قبل از «ریبوت»:"
D docker ps -a --format '{{.Names}}: {{.Status}}' | sed -E 's/(Up|Exited \([0-9]+\)) .*/\1/' | sort
docker restart lx-prod-dind >/dev/null
until docker exec lx-prod-dind docker info >/dev/null 2>&1; do sleep 1; done; sleep 4
echo "--- بعد از «ریبوت» (restart daemon):"
D docker ps -a --format '{{.Names}}: {{.Status}}' | sed -E 's/(Up|Exited \([0-9]+\)) .*/\1/' | sort
خروجی
--- قبل از «ریبوت»:
always-down: Exited (137)
no-policy: Up
unless-stopped-down: Exited (137)
unless-stopped-up: Up
--- بعد از «ریبوت» (restart daemon):
always-down: Up
no-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 برای دیتابیسی که نباید بعد از توقف دستی خودبه‌خود برگردد ولی بعد از ریبوت و کرش بالا بیاید مناسب است؟

مثال ۲: به‌روزرسانی بدون از دست رفتن داده

Section titled “مثال ۲: به‌روزرسانی بدون از دست رفتن داده”

نسخه‌ی ۱.۰ و ۲.۰ یک اپ کوچک (فقط نسخه را برمی‌گرداند)، و یک Redis با volume:

up/app/server.py
import os
from 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()
up/app/Dockerfile
FROM python:3.12-alpine
ARG VERSION
ENV VERSION=$VERSION
COPY server.py .
CMD ["python", "server.py"]
up/compose.yaml
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:

نسخه‌ی ۱.۰ را اجرا و داده‌ای ثبت می‌کنیم:

Terminal window
cd up
docker build -q -t lx-prod-app:1.0 --build-arg VERSION=1.0 app >/dev/null
docker build -q -t lx-prod-app:2.0 --build-arg VERSION=2.0 app >/dev/null
docker compose up -d >/dev/null 2>&1
sleep 2
echo "نسخه‌ی در حال اجرا: $(curl -s localhost:8360)"
docker compose exec -T db redis-cli set customer:1 "علی" >/dev/null
docker compose exec -T db redis-cli save >/dev/null
echo "داده‌ی ثبت‌شده: $(docker compose exec -T db redis-cli get customer:1)"
خروجی
نسخه‌ی در حال اجرا: version 1.0
داده‌ی ثبت‌شده: علی

حالا به‌روزرسانی: اول image های آماده را می‌گیریم (pull) و بعد با up -d فقط سرویس‌های تغییرکرده دوباره ساخته می‌شوند. در همین حین، یک حلقه‌ی پس‌زمینه هر ۰.۱ ثانیه سرویس را صدا می‌زند تا قطعی را بسنجیم:

Terminal window
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 1
docker compose pull web db 2>&1 | grep -E 'Pulled|up to date' | sed -E 's/^ +//' | sort -u
TAG=2.0 docker compose up -d 2>&1 | grep -E 'Recreate|Running|Started' | sed -E 's/^ +//' | awk '!s[$0]++' | sort
sleep 2
kill $LOOP 2>/dev/null; wait $LOOP 2>/dev/null
echo "نسخه‌ی جدید: $(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 Pulled
Image redis:alpine Pulled
Container lxup-app-1 Recreate
Container lxup-app-1 Recreated
Container lxup-app-1 Started
Container lxup-db-1 Running
Container 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 «نرم» است: درخواست‌های در حال انجام را تمام می‌کند و کارگر جدید با تنظیم جدید شروع می‌شود.

bg/conf/default.conf
server {
listen 80;
resolver 127.0.0.11 valid=2s;
include /etc/nginx/active/backend.inc;
location / {
proxy_pass $backend;
}
}
bg/active/backend.inc
set $backend http://blue:8000;
Terminal window
cd bg
docker network create lxbg >/dev/null
docker run -d --name lx-pr-blue --network lxbg --network-alias blue lx-prod-app:1.0 >/dev/null
docker run -d --name lx-pr-green --network lxbg --network-alias green lx-prod-app:2.0 >/dev/null
docker 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/null
sleep 2
echo "قبل از جابه‌جایی: $(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 1
echo 'set $backend http://green:8000;' > active/backend.inc
docker exec lx-pr-nginx nginx -t 2>&1 | tail -1
docker exec lx-pr-nginx nginx -s reload 2>&1 | grep -v notice
sleep 2
docker rm -f lx-pr-blue >/dev/null
sleep 2
kill $LOOP 2>/dev/null; wait $LOOP 2>/dev/null
echo "بعد از جابه‌جایی: $(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.0
nginx: configuration file /etc/nginx/nginx.conf test is successful
بعد از جابه‌جایی: version 2.0
درخواست‌ها: 65 — ناموفق: 0

ترافیک بدون هیچ درخواست ناموفقی از نسخه‌ی ۱ به نسخه‌ی ۲ رفت و بعد blue حذف شد. بازگرداندن (rollback) هم همین‌قدر ساده است: blue را نگه می‌داری و backend.inc را برمی‌گردانی. (این با nginx که آدرس upstream را در حین کار resolve می‌کند ممکن شد، همان resolver و متغیر که در پروژه‌ی Compose دیدی.)

در blue-green دو نسخه‌ی کامل کنار هم هستند؛ سوییچ فقط تغییر مقصد در proxy است، پس هم بدون قطعی است و هم برگشت فوری دارد.

داده‌ی مهم در volume است؛ بکاپ یعنی یک کپی از آن. ساده‌ترین روش: یک کانتینر کمکی که volume و یک پوشه‌ی میزبان را mount می‌کند و tar می‌سازد:

Terminal window
mkdir -p backup
docker volume create lx-pr-data >/dev/null
docker 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/null
docker run --rm -v lx-pr-data2:/data -v "$PWD/backup":/backup:ro alpine tar xzf /backup/data.tgz -C /data
docker 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”
Terminal window
docker run -d --name lx-pr-pg1 -e POSTGRES_PASSWORD=lxdemo postgres:16-alpine >/dev/null
for i in $(seq 30); do docker exec lx-pr-pg1 pg_isready -U postgres >/dev/null 2>&1 && break; sleep 1; done; sleep 2
docker 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.sql
echo "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/null
for i in $(seq 30); do docker exec lx-pr-pg2 pg_isready -U postgres >/dev/null 2>&1 && break; sleep 1; done; sleep 2
docker exec -i lx-pr-pg2 psql -U postgres -q < backup/db.sql >/dev/null 2>&1
docker 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/null
خروجی
dump: 97 خط، شامل: 1 جدول clients
3 ردیف بازیابی شد: علی، سارا، رضا

pg_dump یک فایل SQL سازگار می‌سازد (حتی وقتی دیتابیس در حال کار است) و می‌توان آن را روی یک دیتابیس تازه بازیابی کرد. هر دیتابیس ابزار خودش را دارد (mysqldump، redis-cli --rdb،…). بکاپی که بازیابی‌اش را آزمایش نکرده‌ای، بکاپ نیست. بکاپ را زمان‌بندی کن (cron)، بیرون از همان سرور ذخیره کن و دوره‌ای بازیابی را تست کن.

مثال ۶: مانیتورینگ ساده

Section titled “مثال ۶: مانیتورینگ ساده”

به مانیتورینگ پیچیده نیاز نداری تا شروع کنی. چهار چیز: healthcheck (درس قبل)، docker ps/stats، یک اسکریپت بررسی که از cron اجرا می‌شود، و فضای دیسک. یک اسکریپت بررسی که برای هر کانتینری که سالم نیست هشدار می‌دهد:

mon/check.sh
#!/bin/sh
# هر کانتینری که در حال اجرا نیست یا unhealthy است را گزارش می‌دهد
bad=0
for 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
fi
done
[ $bad -eq 0 ] && echo "OK: همه‌ی سرویس‌های نشان‌دارشده سالم‌اند"
exit $bad
Terminal window
docker run -d --name lx-pr-ok --label monitor=yes alpine sleep 300 >/dev/null
docker run -d --name lx-pr-sick --label monitor=yes --health-cmd 'false' --health-interval 1s --health-retries 1 alpine sleep 300 >/dev/null
sleep 4
sh mon/check.sh; echo "کد خروج اسکریپت: $?"
docker rm -f lx-pr-sick >/dev/null
sh mon/check.sh; echo "کد خروج اسکریپت: $?"
docker rm -f lx-pr-ok >/dev/null
خروجی
ALERT: lx-pr-sick state=running health=unhealthy
کد خروج اسکریپت: 1
OK: همه‌ی سرویس‌های نشان‌دارشده سالم‌اند
کد خروج اسکریپت: 0

با label=monitor=yes فقط کانتینرهای مهم را بررسی می‌کنی. اسکریپت وقتی مشکل ببیند کد خروج ۱ می‌دهد؛ از cron می‌توانی خروجی را ایمیل/پیام کنی یا به سرویس پایش بفرستی. ابزارهای کامل‌تر: Prometheus + cAdvisor، Uptime Kuma، Grafana، و جمع‌آوری لاگ مرکزی؛ این‌ها خارج از این درس‌اند.

  • 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) سازگاری را تضمین می‌کند.
کار دستور
بازگشت بعد از کرش و ریبوت --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 تشخیص و بازگشت خودکار
بکاپ آزمایش‌شده فقط بکاپ بازیابی‌شده معتبر است

-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 “۴) بکاپ فقط روی همان سرور”

دیسک که بسوزد، بکاپ هم می‌رود. راه‌حل: بکاپ را به جای دیگر (سرور دیگر، ذخیره‌ساز ابری) کپی کن.

Terminal window
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 را بخوان.

دیدن جواب
Terminal window
docker run -d --name lx-e1 --restart unless-stopped alpine sleep 300 >/dev/null
docker inspect lx-e1 --format 'policy={{.HostConfig.RestartPolicy.Name}} restarts={{.RestartCount}}'
docker rm -f lx-e1 >/dev/null
خروجی
policy=unless-stopped restarts=0
✎ تمرینمتوسط

تمرین اصلی: یک سرویس را بدون از دست رفتن داده به‌روزرسانی کن. با up/compose.yaml نسخه‌ی ۱.۰ را اجرا کن، یک کلید در Redis بنویس، با TAG=2.0 docker compose up -d به‌روز کن و ثابت کن (۱) نسخه عوض شد (۲) کلید ماند. بعد با TAG=1.0 rollback کن.

دیدن جواب
Terminal window
cd up
docker compose up -d >/dev/null 2>&1; sleep 2
docker compose exec -T db redis-cli set plan "gold" >/dev/null; docker compose exec -T db redis-cli save >/dev/null
echo "۱.۰: $(curl -s localhost:8360)"
TAG=2.0 docker compose up -d >/dev/null 2>&1; sleep 2
echo "۲.۰: $(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 2
echo "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 | داده: gold
rollback: version 1.0 | داده: gold
✎ تمرینسخت

یک اسکریپت بکاپ و بازیابی کامل بنویس: backup VOL FILE و restore VOL FILE. با یک volume تازه که فایل دارد تست کن: بکاپ بگیر، volume را پاک کن، با restore برگردان و محتوا را مقایسه کن.

دیدن جواب
Terminal window
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/null
docker 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.tgz
docker volume rm lx-e3 >/dev/null
restore lx-e3 e3.tgz
after=$(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
خروجی
بازیابی دقیق بود (هش محتوا یکی است)
؟ آزمونک
  1. کدام policy بعد از ریبوت سرور کانتینری را که دستی متوقف کرده بودی بالا نمی‌آورد ولی بعد از کرش بالا می‌آورد؟

  2. چرا docker compose down -v در به‌روزرسانی خطرناک است؟

  3. docker compose pull && up -d چه سرویس‌هایی را دوباره می‌سازد؟

  4. چطور به‌روزرسانی بدون قطعی انجام می‌شود؟

  5. چرا برای دیتابیس به‌جای کپی فایل volume از pg_dump استفاده می‌کنیم؟

  6. بکاپ زمانی معتبر است که…

  • 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 -drollback به نسخه‌ی قبل
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مصرف دیسک داکر