- Created JSON representation for `collect-diagnostics.sh` including nodes and edges for its structure. - Added JSON for `ops-restarts.md` detailing various sections and their relationships. - Introduced JSON for `server-down-oom-diagnosis.md` capturing its content and connections.
9.3 KiB
تشخیص عمیق down شدن سرور بعد از ~۱۰ سیکل + وریفای و تکمیل فیکسهای پایداری
زمینه
روی production (Coolify، تککانتینر با supervisord: nginx + php-fpm + دو messenger worker) workerها هر ساعت با --time-limit=3600 و exit code 0 عمداً recycle میشوند — این عادی است. مشکل واقعی: بعد از حدود ۱۰ سیکل (~۱۰ ساعت) کل سرویس down میشود. در لاگهای ارائهشده هیچ crash دیده نمیشود (exit status 0; expected، /health همیشه 200، اسکنهای بات GET /*.php توسط nginx مستقیم 404 میشوند و به Symfony نمیرسند) — یعنی قاتل بیرون از لاگ اپ است.
فرضیه اصلی (به ترتیب احتمال): فشار تدریجی حافظه تا OOM و سپس پر شدن دیسک:
- php-fpm قبلاً بدون
pm.max_requestsبود → پروسهها هرگز recycle نمیشدند و نشت حافظه ساعتبهساعت جمع میشد؛memory_limit=1024Mهم به یک request اجازه میداد 1GB بخورد. - Redis (کش اپ + صف messenger، یک instance مشترک) پیشفرض
maxmemory=0→ رشد بینهایت. - لاگهای docker (json-file) بدون
max-size→ پر شدن تدریجی دیسک.
بخشی از فیکسها قبلاً در repo اعمال شده؛ این پرامپت آنها را وریفای میکند، یک اسکریپت جمعآوری شواهد برای اجرای روی سرور میسازد، و چکلیست تنظیمات سمت Coolify را نهایی میکند.
مشکل / هدف
- تشخیص قطعی و مبتنی بر شواهد (نه حدس): OOM؟ دیسک؟ یا چیز دیگر؟
- اطمینان از اینکه همه فیکسهای پایداری در repo حاضرند (اگر revert شدهاند، دوباره اعمال شوند).
- تحویل یک اسکریپت آماده که کاربر روی سرور اجرا کند و خروجیاش تشخیص را قطعی کند.
فایلهای مرتبط
| فایل | نقش |
|---|---|
docker/php/php.ini |
باید memory_limit = 256M باشد (فیکس قبلی؛ ممکن است revert شده باشد — وریفای کن) |
docker/php/zz-pool.conf |
باید بلوک pm = dynamic … pm.max_requests = 500 را داشته باشد |
docker/supervisord.conf |
هر دو worker بدون -v، هر دو با --memory-limit=128M |
docker-compose.yml |
آینه supervisord (حالت compose) |
supervisor.conf |
آینه (نسخه Liara) |
.env.coolify.example |
MESSENGER_TRANSPORT_DSN نمونه باید ?stream_max_entries=20000 داشته باشد |
docs/ops-restarts.md |
runbook — بخشهای ۷ (OOM) و ۸ باید موجود باشند |
docs/collect-diagnostics.sh |
جدید — اسکریپت جمعآوری شواهد روی سرور |
وضعیت فعلی
وضعیت انتظاری بعد از فیکسهای قبلی (docker/php/zz-pool.conf):
[www]
clear_env = no
pm = dynamic
pm.max_children = 8
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 500
و docker/php/php.ini:
memory_limit = 256M
⚠️ کاربر ممکن است memory_limit را به 1024M برگردانده باشد — وضعیت فعلی فایل را بخوان و اگر برگشته، با ذکر دلیل (۸ پروسه × 1024M = 8GB سقف نظری روی VPS کوچک) دوباره 256M کن، مگر اینکه کاربر صراحتاً 1024M خواسته باشد — در آن صورت دست نزن و فقط در گزارش ذکر کن.
وظایف
۱. وریفای فیکسهای repo (idempotent)
هر ۷ فایل جدول بالا را چک کن؛ هر موردی که غایب/revert شده را دوباره اعمال کن:
grep -n "memory_limit" docker/php/php.ini # انتظار: 256M
grep -n "pm.max_requests" docker/php/zz-pool.conf # انتظار: 500
grep -n "consume" docker/supervisord.conf docker-compose.yml supervisor.conf
# انتظار: هیچ ` -v` ای نباشد؛ هر دو worker دارای --time-limit=3600؛ هر دو --memory-limit=128M
grep -n "stream_max_entries" .env.coolify.example # انتظار: 20000
grep -n "OOM\|maxmemory" docs/ops-restarts.md # بخش ۷ موجود
سه فایل supervisor/compose باید آینه هم بمانند.
۲. ساخت docs/collect-diagnostics.sh — اسکریپت شواهد
اسکریپت shell (POSIX sh، بدون وابستگی خاص) که کاربر روی هاست سرور (نه داخل کانتینر) اجرا میکند و همه شواهد لازم را یکجا چاپ میکند:
#!/bin/sh
# ClinicPro production diagnostics — run ON THE HOST as root (or with docker access).
# Usage: sh collect-diagnostics.sh [container-name-or-id]
set -u
C="${1:-$(docker ps --format '{{.Names}}' | grep -i -m1 clinic || true)}"
echo "=== 1. OOM evidence (kernel, last 48h) ==="
journalctl -k --since "48 hours ago" 2>/dev/null | grep -i -E "oom|killed process" | tail -20 \
|| dmesg | grep -i -E "oom|killed process" | tail -20
echo "=== 2. Memory / swap now ==="
free -h
echo "=== 3. Disk ==="
df -h | head -10
du -sh /var/lib/docker/containers/*/*-json.log 2>/dev/null | sort -h | tail -5
echo "=== 4. Container state ==="
docker ps -a --format 'table {{.Names}}\t{{.Status}}'
[ -n "$C" ] && docker inspect --format \
'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}} RestartCount={{.RestartCount}} StartedAt={{.State.StartedAt}} FinishedAt={{.State.FinishedAt}}' "$C"
echo "=== 5. Live memory per container ==="
docker stats --no-stream
echo "=== 6. php-fpm inside app container ==="
[ -n "$C" ] && docker exec "$C" sh -c 'php -i 2>/dev/null | grep -E "^memory_limit"; ps -o rss,args 2>/dev/null | grep -E "php-fpm|messenger" | grep -v grep'
echo "=== 7. Redis memory (set host/port/pass if needed) ==="
R="$(docker ps --format '{{.Names}}' | grep -i -m1 redis || true)"
[ -n "$R" ] && docker exec "$R" redis-cli info memory 2>/dev/null | grep -E "used_memory_human|maxmemory_human|maxmemory_policy"
echo "=== 8. Docker log driver config ==="
docker info --format '{{.LoggingDriver}}'
cat /etc/docker/daemon.json 2>/dev/null || echo "(no daemon.json)"
echo "=== done — paste this whole output back for diagnosis ==="
اجراییاش کن (chmod +x). در انتهای docs/ops-restarts.md یک خط ارجاع به این اسکریپت اضافه کن.
۳. جدول تفسیر شواهد در runbook
به docs/ops-restarts.md (بخش ۷) جدول تصمیم اضافه کن:
| شاهد در خروجی اسکریپت | تشخیص | اقدام |
|---|---|---|
بخش ۱: Out of memory: Killed process ... php-fpm |
نشت fpm | فیکسهای pm/memory_limit + redeploy کافی است |
بخش ۱: Killed process ... mariadbd یا redis |
فشار کل RAM | Redis maxmemory + Coolify memory limit + swap |
بخش ۴: OOMKilled=true |
سقف حافظه کانتینر | سقف را بالاتر ببر یا مصرف را کم کن |
| بخش ۳: دیسک ≥ 90% یا json.log چند GB | دیسک پر | log rotation daemon.json + پاکسازی |
بخش ۷: maxmemory:0 و used_memory رشد کرده |
Redis بیسقف | maxmemory 256mb + maxmemory-policy volatile-lru |
| هیچکدام | فرضیه رد شد | خروجی کامل + لاگ ۵۰ خط آخر قبل از down برای تحلیل بعدی |
۴. چکلیست تنظیمات سمت Coolify (فقط مستندسازی — قابل اجرا از repo نیست)
در همان بخش runbook، چکلیست کوتاه:
- Redeploy بعد از merge این تغییرات (configها فقط با image جدید اعمال میشوند).
- env واقعی Coolify: به
MESSENGER_TRANSPORT_DSNپارامتر?stream_max_entries=20000اضافه شود. - Redis resource:
maxmemory 256mb+maxmemory-policy volatile-lru(نهallkeys-lru— پیامهای صف TTL ندارند و evict میشوند). - App resource → Memory Limit = 1G.
/etc/docker/daemon.json:{"log-driver":"json-file","log-opts":{"max-size":"20m","max-file":"3"}}+ restart docker.- اگر
free -hنشان داد swap صفر است: 2G swap بساز. - Coolify Notifications برای container stop/unhealthy فعال شود.
نکات مهم
- هیچ تغییری در رفتار workerها نده — recycle ساعتی عمدی است؛ فقط
--memory-limit=128Mباید روی هر دو باشد. - این پرامپت PHP/React لمس نمیکند → migration و
docs/api/لازم ندارد؛ فقط configهای deploy +docs/. - اسکریپت باید بدون bash-ism باشد (
shخالص) چون روی هاست Ubuntu اجرا میشود نه داخل کانتینر alpine. - بعد از هر تغییر config، سه فایل supervisord/compose/liara آینه بمانند (کامنت بالای فایلها همین را میگوید).
- تست repo-side:
docker compose -f docker-compose.yml config -q(فقط warning env، بدون خطا) وsh -n docs/collect-diagnostics.shبرای syntax اسکریپت.