- 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.
152 lines
9.3 KiB
Markdown
152 lines
9.3 KiB
Markdown
# تشخیص عمیق 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** و سپس **پر شدن دیسک**:
|
||
|
||
1. php-fpm قبلاً بدون `pm.max_requests` بود → پروسهها هرگز recycle نمیشدند و نشت حافظه ساعتبهساعت جمع میشد؛ `memory_limit=1024M` هم به یک request اجازه میداد 1GB بخورد.
|
||
2. Redis (کش اپ + صف messenger، یک instance مشترک) پیشفرض `maxmemory=0` → رشد بینهایت.
|
||
3. لاگهای 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`):
|
||
|
||
```ini
|
||
[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`:
|
||
|
||
```ini
|
||
memory_limit = 256M
|
||
```
|
||
|
||
⚠️ کاربر ممکن است `memory_limit` را به 1024M برگردانده باشد — وضعیت فعلی فایل را بخوان و اگر برگشته، با ذکر دلیل (۸ پروسه × 1024M = 8GB سقف نظری روی VPS کوچک) دوباره 256M کن، مگر اینکه کاربر صراحتاً 1024M خواسته باشد — در آن صورت دست نزن و فقط در گزارش ذکر کن.
|
||
|
||
## وظایف
|
||
|
||
### ۱. وریفای فیکسهای repo (idempotent)
|
||
|
||
هر ۷ فایل جدول بالا را چک کن؛ هر موردی که غایب/revert شده را دوباره اعمال کن:
|
||
|
||
```bash
|
||
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، بدون وابستگی خاص) که کاربر روی **هاست سرور** (نه داخل کانتینر) اجرا میکند و همه شواهد لازم را یکجا چاپ میکند:
|
||
|
||
```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، چکلیست کوتاه:
|
||
|
||
1. Redeploy بعد از merge این تغییرات (configها فقط با image جدید اعمال میشوند).
|
||
2. env واقعی Coolify: به `MESSENGER_TRANSPORT_DSN` پارامتر `?stream_max_entries=20000` اضافه شود.
|
||
3. Redis resource: `maxmemory 256mb` + `maxmemory-policy volatile-lru` (نه `allkeys-lru` — پیامهای صف TTL ندارند و evict میشوند).
|
||
4. App resource → Memory Limit = 1G.
|
||
5. `/etc/docker/daemon.json`: `{"log-driver":"json-file","log-opts":{"max-size":"20m","max-file":"3"}}` + restart docker.
|
||
6. اگر `free -h` نشان داد swap صفر است: 2G swap بساز.
|
||
7. 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 اسکریپت.
|