راهنمای استقرار و عملیات موتوشاب (Motoshub Ops Guide)

این سند برای تیم DevOps و فروش تهیه شده است: نیازمندی‌های سرور برای هر مشتری، خط‌لوله‌ی استقرار، پایش زنده‌ی مصرف منابع، استراتژی احراز هویت میان PHP و Django، و وضعیت ORM. همه‌ی اعداد از اندازه‌گیری واقعی روی استک در حال اجرا به‌دست آمده‌اند.

۱) پشته‌ی اجرایی (Runtime Stack)

سرویسایمیج/بیلدنقش
appphp:8.4-fpm-bullseye (build docker/Dockerfile)PHP-FPM
webnginx:alpinereverse proxy / FastCGI
dbmariadb:10.5پایگاه‌داده
cronهمان appاجرای ow_cron/run.php هر ۶۰ ثانیه (تعریف‌شده، فعلاً غیرفعال)

سرویس‌های خارجی که کد فرض می‌کند

سرویسوضعیت
RabbitMQ/AMQPدر کد هست ولی log-over-RabbitMQ غیرفعال شده؛ broker در compose نیست
WebSocket (Ratchet)سرویس socket در compose کامنت شده
Elasticsearch (ruflin/elastica)backend جستجو؛ container ندارد
درگاه پیامک (iistpnetsms)فعال؛ API خارجی صدا می‌زند
SMTPنیازمند relay خارجی
S3/CloudFilesOW_USE_CLOUDFILES=false (غیرفعال؛ فایل‌ها محلی)
Nexus/Debian mirror (nexus.danafakher.ir, debian-main.devneeds.ir)وابستگی زمان build؛ بدون دسترسی به این میرورها ایمیج build نمی‌شود
Redisندارد

۲) مصرف منابع واقعی (اندازه‌گیری‌شده)

docker stats --no-stream (استک بی‌بار)

کانتینرRAM
app (php-fpm)~۵۲ MiB بی‌بار → ~۶۹ زیر بار
db (MariaDB)~۱۶۶ MiB (با دیتابیس ۱۷MB و buffer pool پیش‌فرض ۱۲۸MB)
web (nginx)~۲۶ MiB

هر worker از php-fpm (اندازه‌گیری VmRSS زیر بار)

فرمول بدترین حالت RAM کانتینر app

RAM app  =  master  +  pm.max_children × RSS هر worker
31 + 5 × 256 MB  =  1.31 GB

چرا RAM سرور شما زیاد مصرف می‌شود (تشخیص دقیق)


۳) مشخصات سرور برای هر مشتری (بدبینانه)

فرض‌ها: بدون swap (سقف سخت RAM)، ~۲۰٪ headroom، هر worker ۷۰MB (بدبینانه برای جذب spike تصویر/اکسپورت)، OS+Docker ~۵۰۰MB.

پروفایلworker های PHPbuffer poolریاضی سقفRAM (بدبینانه)vCPUدیسک
بار تست فعلی۵۱۲۸MB۳۱۶ + ۱۶۰ + ۳۰ + ۵۰۰ ≈ ۱GB۲GB۱–۲۲۰GB
کوچک (<۵۰۰ کاربر)۱۰۲۵۶MB۷۳۰ + ۳۲۰ + ۴۰ + ۵۰۰ ≈ ۱.۶GB۲–۳GB۲۲۰–۳۰GB
متوسط (<۵۰۰۰ کاربر)۲۵–۳۰۱–۲GB۲.۱GB + ۲GB + ۵۰ + ۵۰۰ ≈ ۴.۷GB۸GB۴۶۰–۱۰۰GB

هشدارهای بدبینانه:

جمله‌ی نهایی: این اپ memory-bound است، نه CPU-bound. هرگز روی <۲GB اجرا نکنید. RAM را این‌طور اندازه بگیرید:

500MB (OS) + buffer_pool + (max_children × 70MB) + 20% headroom

۴) خط‌لوله‌ی استقرار برای مشتری (تمیز و مرحله‌به‌مرحله)

پیش‌نیازها روی سرور مشتری

مراحل

# ۱) کد
git clone <repo> motoshub && cd motoshub
git checkout motonext            # شاخه‌ی زنده

# ۲) تنظیمات محیط
cp .env.example .env
#   ویرایش .env: DB_*، SITE_URL، PROJECT_NAME، OW_PASSWORD_PEPPER (کلید امضای JWT)

# ۳) بالا آوردن استک
docker compose up -d --build     # app + db + web (+ cron در prod)

# ۴) نصب/آپدیت اکسوال و پلاگین‌ها
#   نصب تازه از طریق ماژول init یا صفحه‌ی نصب؛
#   سایت موجود: از پنل مدیریت، آپدیت‌های در انتظار پلاگین‌ها را اجرا کنید
#   (اکسوال با دیدن بامپ build در plugin.xml، update.php ها را خودکار اجرا می‌کند).

# ۵) پاک‌سازی کش
rm -rf ow_smarty/template_c/* ow_static/tmp/*

# ۶) راستی‌آزمایی
curl -i https://<domain>/                 # 200
curl -i https://<domain>/api/v1/docs/json # 200، خروجی OpenAPI

اصلاحات لازم پیش از production (بدهی فنی شناسایی‌شده)


۵) پایش زنده‌ی مصرف (چطور live چک کنم؟)

سطح کانتینر (روی هاست production)

docker stats                    # زنده CPU/MEM هر کانتینر
docker stats --no-stream        # snapshot یک‌باره برای اسکریپت

داخل کانتینر PHP

docker compose exec app top                      # اگر procps نصب باشد
# fallback بدون top/ps (این ایمیج ندارد):
docker compose exec app sh -c 'for p in $(ls /proc|grep -E "^[0-9]+$"); do \
  grep -qa "pool www" /proc/$p/cmdline 2>/dev/null && \
  echo "$p $(grep VmRSS /proc/$p/status|awk "{print \$2}")kB"; done'

صفحه‌ی وضعیت php-fpm (پس از رفع کپی www.conf)

curl -s "https://<domain>/status?full"   # worker های active/idle/total، صف، peak
curl -s https://<domain>/ping            # → pong

MariaDB

docker compose exec db mysql -uroot -p<pass> -e "SHOW FULL PROCESSLIST;"
docker compose exec db mysql -uroot -p<pass> -e "SHOW ENGINE INNODB STATUS\G"
docker compose exec db mysql -uroot -p<pass> -e \
  "SET GLOBAL slow_query_log=ON; SET GLOBAL long_query_time=1; \
   SET GLOBAL slow_query_log_file='/var/lib/mysql/slow.log';"   # فعال‌سازی بدون restart
# روند رشد دیتابیس:
docker compose exec db mysql -uroot -p<pass> -e \
  "SELECT table_schema, ROUND(SUM(data_length+index_length)/1024/1024,1) MB \
   FROM information_schema.tables GROUP BY table_schema;"

گزینه‌های always-on (با overhead خودشان)

ابزارoverheadتوضیح
ctop~۱۰–۲۰MBباینری Go تک‌فایل، نمای top مثل هر کانتینر. بهترین انتخاب کم‌زحمت
glances --docker~۵۰–۸۰MBداشبورد یک‌صفحه‌ای host+container
cAdvisor~۵۰–۱۰۰MBصادرکننده‌ی متریک کانتینر، خوراک Prometheus
node_exporter + Prometheus + Grafana~۵۰۰MB مجموعاستک کامل متریک/هشدار؛ برای ناوگان، نه یک هاست کوچک

۶) مدیریت احراز هویت میان PHP و Django (استراتژی — بدون تغییر کد)

وضعیت فعلی: API (لایه‌ی جدید) با JWT (الگوریتم HS256) کار می‌کند. توکن با hash('sha256', OW_PASSWORD_PEPPER) امضا می‌شود و claims دارد: { iat, exp, sub: userId }. ورود از POST /api/v1/auth/login توکن یک‌ساعته می‌دهد.

معماری هدف شما: api.shub.ir (Gateway) جلوی api1 (PHP/Oxwall) و api2 (Django).

گزینه‌ی توصیه‌شده (کم‌ریسک، بدون تغییر کد فعلی): کلید متقارن مشترک

مزیت: هیچ تغییری در کد PHP لازم نیست؛ فقط راز مشترک + پیاده‌سازی decode در Django.

محدودیت‌ها (صادقانه):

گزینه‌ی بلندمدت (تمیزتر): Identity Provider مرکزی (Keycloak)

همه (فرانت، Gateway، PHP، Django) توکن را از یک IdP می‌گیرند و با کلید عمومی verify می‌کنند. تک‌نقطه‌ی حقیقت برای هویت، پشتیبانی refresh/revoke استاندارد. برای مقیاس چندسرویسی درست است، ولی راه‌اندازی جدا می‌خواهد.


۷) وضعیت ORM (آیا Laravel Eloquent است؟)

پاسخ دقیق: بله و نه — بستگی به لایه دارد. دو لایه‌ی دسترسی به داده روی یک MariaDB مشترک (shub_db، پیشوند ow_):

نکته‌ی صادقانه: این Laravel نیست. در composer.json هیچ laravel/framework نیست؛ فقط اجزای مستقل Illuminate (illuminate/database v13.16.1 و چند بسته). Eloquent دستی و از طریق یک «Bridge» سفارشی راه‌اندازی شده (bridge/src/Kernel.php با Capsule::bootEloquent()). پس عبارت درست: «API جدید از کامپوننت ORM لاراول (Eloquent) استفاده می‌کند، اما اپ یک اپ لاراولی نیست؛ یک اپ Oxwall است که Eloquent به آن اضافه شده.»

ORM یعنی چه؟ ابزاری که ردیف‌های جدول را به شیء PHP تبدیل می‌کند تا به‌جای SQL با متدهای شیء کار کنید. Eloquent ORM لاراول با الگوی Active Record است: هر کلاس مدل = یک جدول، هر شیء = یک ردیف که خودش می‌داند چطور ذخیره/به‌روزرسانی/حذف شود.

ریسک مهم — نوشتن دوگانه: هر دو لایه به همان جدول‌ها می‌نویسند. نوشتن با Eloquent، رویدادها و پاک‌سازی کشِ لایه‌ی قدیمی را دور می‌زند:

جمع‌بندی برای مستندات: لایه‌ی API یک بازنویسی مشروع مبتنی بر Eloquent روی لایه‌ی DAO قدیمی است، هر دو روی یک MariaDB. Eloquent است، نه Laravel. خطر اصلی، مسیر نوشتن دوگانه است؛ هر جدولی که هر دو لایه می‌نویسند نیاز به دقت (یا یک نویسنده‌ی معتبر واحد) دارد.


تهیه‌شده پس از مطالعه‌ی کامل سورس و اندازه‌گیری زنده روی استک Docker.