Files
clinicpro/docs/ops-restarts.md
T

6.9 KiB

Runbook — تشخیص «ری‌استارت» سرور: recycle عادی یا خرابی واقعی؟

این سند برای وقتی است که در لاگ‌های production (Coolify) خطوطی مثل exited: worker-... دیده می‌شود و به‌نظر می‌رسد «سرور مدام ری‌استارت می‌شود».

خلاصه یک‌خطی

خروج ساعتیِ workerها با exit status 0; expected طراحی‌شده و بی‌ضرر است. خرابی واقعی یعنی exit غیرصفر، FATAL، یا قطع شدن پاسخ /health.


۱. چرا workerها هر ساعت restart می‌شوند؟

هر دو consumer پیام (async و scheduler) با این فلگ‌ها اجرا می‌شوند:

messenger:consume async --time-limit=3600 --memory-limit=128M
messenger:consume scheduler_default --time-limit=3600
  • --time-limit=3600 → worker بعد از ۱ ساعت خودش با exit code 0 خارج می‌شود.
  • supervisord (یا restart: unless-stopped در compose) بلافاصله دوباره آن را بالا می‌آورد (زیر ۱ ثانیه).
  • هدف: جلوگیری از نشت حافظه PHP در پروسه‌های طولانی و تازه‌کردن اتصال‌های کهنه به MariaDB/Redis. الگوی استاندارد و توصیه‌شده Symfony Messenger است.

به همین دلیل این لاگ عادی است و هیچ اقدامی لازم ندارد:

INFO exited: worker-scheduler (exit status 0; expected)
INFO spawned: 'worker-scheduler' with pid 1464
INFO success: worker-scheduler entered RUNNING state, process has stayed up for > than 1 seconds

کلیدواژه expected یعنی supervisor از قبل می‌دانست این exit برنامه‌ریزی‌شده است.

۲. نشانه‌های خرابی واقعی

اگر هر یک از این‌ها را دیدید، مشکل واقعی است:

نشانه در لاگ معنی
exited: ... (exit status 1) یا هر عدد غیرصفر worker با خطا مرده
entered FATAL state, too many start retries too quickly worker بالا نمی‌آید (مثلاً DB/Redis در دسترس نیست)
گپ چند دقیقه‌ای در لاگ‌های GET /health ... 200 وب‌سرور واقعاً پاسخ نمی‌داده
GET /health با status غیر از 200 اپ boot نمی‌شود

۳. دستورهای تشخیص روی سرور

سؤال اصلی: آیا خود کانتینر ری‌استارت شده یا فقط پروسه worker داخل آن recycle شده؟

# وضعیت و uptime کانتینرها — Status باید «Up X hours (healthy)» باشد
docker ps --format 'table {{.Names}}\t{{.Status}}'

# تعداد ری‌استارت واقعی و زمان آخرین start کانتینر
docker inspect --format '{{.RestartCount}} {{.State.StartedAt}}' <container-name>

# رویدادهای restart/die در ۲۴ ساعت گذشته
docker events --since 24h --filter event=restart --filter event=die

تفسیر:

  • RestartCount = 0 و StartedAt قدیمی (مثلاً چند روز پیش) → هیچ ری‌استارت واقعی‌ای رخ نداده؛ فقط recycle داخلی worker است.
  • RestartCount بالا یا StartedAt تازه بدون deploy → کانتینر واقعاً crash/restart می‌شود؛ لاگ‌های قبل از مرگ را ببینید: docker logs --tail 200 <container>.

۴. اطلاع‌رسانی خودکار در Coolify

برای اینکه down شدن واقعی بلافاصله خبر داده شود (به‌جای پایش دستی لاگ):

  1. در Coolify → Notifications، یک کانال (Telegram / Email / Discord) فعال کنید.
  2. رویدادهای «container stopped / unhealthy» و «deployment failed» را روشن کنید.

مستندات:

۵. تمدید گواهی TLS (ACME) — مهم‌ترین ریسک down شدن واقعی

علامت مشکل

در access log کانتینر app:

GET /.well-known/acme-challenge/xxxx HTTP/1.1" 404 ... "Let's Encrypt validation server"

چالش HTTP-01 لتس‌انکریپت باید توسط Traefik (پراکسی Coolify) پاسخ داده شود و اصلاً نباید به کانتینر app برسد. رسیدن آن به app و گرفتن 404 یعنی صدور/تمدید گواهی برای آن دامنه دارد شکست می‌خورد. اگر رها شود، بعد از انقضای گواهی فعلی، سایت با خطای TLS از دسترس خارج می‌شود — و این همان «سرور down شد» است.

چک‌لیست رفع

  1. در Coolify → resource اپ → تب Domains: دامنه clinic-pro.irwww.clinic-pro.ir اگر DNS دارد) باید دقیقاً با https:// روی سرویس app ثبت باشد. دامنه‌ای که DNS آن به سرور اشاره می‌کند ولی در Coolify ثبت نیست، دقیقاً همین الگوی 404 را می‌سازد.

  2. تاریخ انقضای گواهی فعلی را چک کنید:

    echo | openssl s_client -connect clinic-pro.ir:443 -servername clinic-pro.ir 2>/dev/null \
      | openssl x509 -noout -dates
    
  3. لاگ پراکسی را برای خطاهای acme ببینید: Coolify → Servers → Proxy → Logs (جستجوی acme).

مستندات: https://coolify.io/docs/knowledge-base/proxy/traefik/overview

۶. Healthcheckها — رفتار انتظاری

  • کانتینر app: docker/healthcheck.sh مسیر واقعی /health را از داخل می‌زند. پارامترها در Dockerfile و docker-compose.yml یکسان‌اند: interval=15s, timeout=5s, retries=5, start_period=60s. یعنی برای unhealthy شدن باید ۵ بار پیاپی (~۷۵ ثانیه) fail شود.
  • کانتینرهای worker (حالت compose): healthcheck فقط وجود پروسه messenger:consume را با ps | grep چک می‌کند (interval=30s, retries=3). در لحظه exit ساعتیِ worker ممکن است یک چک fail شود — بی‌اهمیت است؛ برای unhealthy شدن ۳ شکست پیاپی (~۹۰ ثانیه) لازم است و worker در همان چند ثانیه اول برمی‌گردد. unhealthy شدن worker فقط وقتی رخ می‌دهد که consumer واقعاً بالا نیاید (مثلاً Redis در دسترس نباشد).

۷. اعمال تغییرات config

فایل‌های docker/supervisord.conf، docker-compose.yml و supervisor.conf باید آینه هم بمانند (workerهای یکسان، فلگ‌های یکسان). هر تغییری در آن‌ها فقط بعد از Redeploy در Coolify روی سرور اثر می‌کند.