Files
clinicpro/.claude/prompt/coolify-restart-diagnosis-hardening.md

8.6 KiB

تشخیص «ری‌استارت» سرور روی Coolify + رفع نویز لاگ + بررسی خطای ACME

پروژه

clinicpro (فقط فایل‌های deploy — بدون تغییر در کد PHP/React)

زمینه

کاربر در لاگ‌های production (Coolify روی Ubuntu) می‌بیند که workerها مدام exit/spawn می‌شوند و نگران است سرور down می‌شود. تحلیل لاگ ارائه‌شده:

2026-07-08 07:40:54,435 INFO exited: worker-scheduler (exit status 0; expected)
2026-07-08 07:40:54,457 INFO spawned: 'worker-scheduler' with pid 1464
2026-07-08 07:40:54,460 INFO exited: worker-async (exit status 0; expected)
2026-07-08 07:40:54,468 INFO spawned: 'worker-async' with pid 1465
2026-07-08 07:40:55,674 INFO success: worker-async entered RUNNING state ...

این crash نیست. این رفتار طراحی‌شده است:

  • docker/supervisord.conf هر دو worker را با --time-limit=3600 اجرا می‌کند → هر worker دقیقاً هر ۱ ساعت با exit code 0 خارج می‌شود (به همین دلیل supervisor می‌نویسد expected).
  • supervisord با autorestart=true در کمتر از ۱ ثانیه دوباره spawn می‌کند.
  • در تمام مدت لاگ، GET /health هر ۱۵ ثانیه 200 برگردانده — یعنی nginx + php-fpm (وب‌سرور اصلی) هرگز down نبوده.
  • recycle ساعتیِ consumer الگوی استاندارد Symfony Messenger است (جلوگیری از نشت حافظه و connection کهنه به DB/Redis). مستندات Coolify هم healthcheck/restart policy را همین‌طور توصیه می‌کند.

اما یک مشکل واقعی در لاگ هست که می‌تواند در آینده باعث «down شدن» واقعی (خطای TLS) شود:

GET /.well-known/acme-challenge/hb1abFQX9d-... HTTP/1.1" 404
"Mozilla/5.0 (compatible; Let's Encrypt validation server; ...)"

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

فایل‌های مرتبط

فایل نقش
docker/supervisord.conf supervisor داخل کانتینر: nginx + php-fpm + دو worker با --time-limit=3600 -v
docker-compose.yml حالت Compose در Coolify: app + دو worker به‌صورت سرویس جدا، همان --time-limit=3600 -v
supervisor.conf نسخه Liara — همان الگو
docker/healthcheck.sh healthcheck کانتینر: GET /health → exit 0/1
Dockerfile HEALTHCHECK --interval=15s --retries=5 --start-period=60s
docs/ مقصد runbook جدید

وضعیت فعلی

docker/supervisord.conf:

[program:worker-async]
command=php bin/console messenger:consume async --time-limit=3600 --memory-limit=128M -v
autorestart=true

[program:worker-scheduler]
command=php bin/console messenger:consume scheduler_default --time-limit=3600 -v
autorestart=true

فلگ -v باعث می‌شود هر بار restart شدن worker، بنر چندخطی «[OK] Consuming messages…» و «The worker will automatically exit…» در stdout چاپ شود — همین بنرها لاگ را شلوغ و «ری‌استارت» را ترسناک‌تر از واقعیت نشان می‌دهند.

وظایف

۱. کاهش نویز لاگ workerها (بدون تغییر رفتار)

فلگ -v را از هر چهار محل حذف کن (دو program در docker/supervisord.conf، دو service در docker-compose.yml، دو program در supervisor.conf). --time-limit=3600 و --memory-limit=128M را دست نزن — recycle ساعتی عمداً وجود دارد و باید بماند.

command=php bin/console messenger:consume async --time-limit=3600 --memory-limit=128M

خطاهای واقعی worker همچنان از طریق monolog (stderr) دیده می‌شوند؛ -v فقط verbosity کنسول بود.

۲. Runbook تشخیص «restart واقعی» از «recycle عادی»

فایل docs/ops-restarts.md بساز (فارسی) با این محتوا:

  • توضیح اینکه exited (exit status 0; expected) + spawn فوری = recycle عادی و بی‌ضرر است؛ نشانه‌های مشکل واقعی: exit status غیرصفر، entered FATAL state، too many start retries، یا گپ در لاگ‌های /health.
  • دستورهای تشخیص روی سرور:
# آیا خودِ کانتینر ری‌استارت شده؟ (uptime کانتینر vs uptime پروسه)
docker ps --format 'table {{.Names}}\t{{.Status}}'   # Status باید Up X hours (healthy) باشد
docker inspect --format '{{.RestartCount}} {{.State.StartedAt}}' <container>
docker events --since 24h --filter event=restart --filter event=die
  • اگر RestartCount صفر است و StartedAt قدیمی → هیچ ری‌استارت واقعی‌ای در کار نیست؛ فقط recycle داخلی worker است.
  • بخش Coolify: فعال‌کردن Notifications (Telegram/Email) در Coolify برای event های container stop/unhealthy، تا down شدن واقعی بلافاصله اطلاع داده شود. ارجاع به مستندات: https://coolify.io/docs/knowledge-base/notifications و https://coolify.io/docs/knowledge-base/health-checks.

۳. بررسی و مستندسازی خطای ACME (مهم‌ترین ریسک down شدن واقعی)

در همان docs/ops-restarts.md بخش «تمدید گواهی TLS» اضافه کن:

  • علامت مشکل: GET /.well-known/acme-challenge/... → 404 در access log کانتینر app. چالش HTTP-01 باید توسط Traefik خود Coolify جواب داده شود و اصلاً نباید به app برسد؛ رسیدنش یعنی صدور/تمدید cert برای آن دامنه دارد شکست می‌خورد.
  • چک‌لیست رفع روی سرور Coolify:
    1. در Coolify → resource مربوط به app → تب Domains: مطمئن شو دامنه clinic-pro.irwww اگر لازم است) دقیقاً با schema https:// روی سرویس app ثبت شده باشد. دامنه‌ای که در Coolify ثبت نشده ولی DNS آن به سرور اشاره می‌کند، همین الگوی 404 را می‌سازد.
    2. وضعیت فعلی cert را چک کن: echo | openssl s_client -connect clinic-pro.ir:443 -servername clinic-pro.ir 2>/dev/null | openssl x509 -noout -dates
    3. لاگ Traefik/proxy در Coolify (Servers → Proxy → Logs) را برای خطای acme بررسی کن.
    • ارجاع: https://coolify.io/docs/knowledge-base/proxy/traefik/overview و بخش Domains در مستندات Coolify.

۴. سازگاری healthcheck

فقط بررسی (بدون تغییر مگر مغایرت پیدا شد): Dockerfile مقادیر interval=15s, retries=5, start-period=60s دارد و docker-compose.yml سرویس app همان مقادیر را — مطمئن شو یکسان بمانند. healthcheck های worker در compose (ps | grep messenger:consume) در لحظه exit ساعتیِ worker ممکن است یک بار fail شوند؛ چون retries: 3 و interval: 30s است، سه شکست پیاپی لازم است و restart کانتینر (که Coolify آن را event می‌کند) رخ نمی‌دهد مگر worker واقعاً بالا نیاید — این را در runbook هم ذکر کن.

نکات مهم

  • هیچ تغییری در رفتار runtime نده جز حذف -v. --time-limit و autorestart عمدی و صحیح‌اند.
  • این پرامپت فایل PHP/React را لمس نمی‌کند → migration و به‌روزرسانی docs/api/ لازم ندارد.
  • docs/ops-restarts.md فارسی نوشته شود (زبان محصول)، ولی دستورها و خروجی‌های shell انگلیسی بمانند.
  • سه فایل config (docker/supervisord.conf، docker-compose.yml، supervisor.conf) باید بعد از تغییر هم‌چنان آینه هم بمانند — کامنت‌های بالای هر فایل همین را می‌گویند.
  • بعد از merge، اعمال روی سرور نیاز به redeploy در Coolify دارد؛ در runbook یادآوری کن.