Files
Siro/docker/README.md
T

111 lines
12 KiB
Markdown

# سيرو في حاويات — دليل التشغيل والفحص
> الهدف: نسخة سيرو كاملة على سيرفر واحد داخل compose واحدة، والتطبيق لا يتغير فيه
> شيء سوى الـ base URLs (تُبنى مرة واحدة بـ `.env` الجديد — envied وقت الترجمة).
> القرارات والأسباب في `Tripz/docs/30-siro-port-plan.md` §5.
## 0) قبل أي شيء — على السيرفر
```bash
df -h / # ⚠️ الصندوق المشترك مزمنياً ~90% ممتلئ — الصور والقواعد تحتاج ~4-6GB
docker image prune -f # نظّف أولاً، وإن لم تكفِ المساحة فالفحص على صندوق آخر
```
## 1) الإعداد (مرة واحدة)
```bash
cd /path/to/Siro/docker
cp .env.example .env && nano .env # املأ الأسرار من قيم اللايف — خاصة JWT_SECRET وكلمات MySQL
# ⚠️ طابق أسماء القواعد في mysql/init/00-databases.sql مع ما يتوقعه الكود
docker compose build
# تبعيات composer للخدمات التي لا تحمل vendor/ داخلها:
docker compose run --rm socket_driver composer install
docker compose run --rm socket_passenger composer install
docker compose run --rm --workdir /var/www/backend php composer install
```
## 2) التشغيل والاستيراد
```bash
docker compose up -d
# استيراد السكيمات (أول مرة فقط):
docker compose exec -T mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" siro_primary < ../backend/schema_primary.sql
docker compose exec -T mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" siro_ride < ../backend/schema_ride.sql
docker compose exec -T mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" siro_tracking < ../backend/schema_tracking.sql
docker compose exec -T mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" siro_wallet < ../payment_server/WalletDB.sql
docker compose exec -T mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" siro_location < ../loction_server/locationDB.sql
```
## 3) التحقق
```bash
docker compose ps # الست خدمات up
curl -s http://localhost:8080/backend/index.html # nginx→ملفات
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8080/backend/connect.php # يرد (401/400 طبيعي بلا JWT)
curl -s http://localhost:8080/fpm-status # حالة حوض fpm
docker compose logs socket_driver | tail # Workerman أقلع على 2020/2021
```
اتصال التطبيق للتجربة: `http://IP:8080/backend` + سوكيت `http://IP:2020` — فقط بدّل
الـ base URLs في `.env` تبع التطبيق وأعد البناء. `APP_DOMAIN` فارغ = حارس
REGION_MISMATCH متعطل أثناء التجربة.
## 4) اختبار الضغط (الهدف: 10-20 ألف رحلة/ساعة)
```bash
cd ../stress_test && npm install
# دخان أولاً (الأداة القديمة، دفعة صغيرة):
node load_test.js --trips=10 -b http://localhost:8080/backend -s http://localhost:2020
# ثم المعدل المستمر:
node rate_test.js --rate 10000 --duration 10 -b http://localhost:8080/backend -s http://localhost:2020
node rate_test.js --rate 20000 --duration 10 -b http://localhost:8080/backend -s http://localhost:2020
node rate_test.js --rate 30000 --duration 5 ... # حد الانهيار — أين تبدأ p95 بالقفز؟
```
**أثناء التشغيل راقب من طرفية ثانية:**
```bash
docker stats # cpu/ذاكرة كل حاوية
docker compose exec mysql sh -c 'tail -f /var/lib/mysql/slow.log' # استعلامات >200ms
watch -n2 'curl -s http://localhost:8080/fpm-status' # طابور fpm (listen queue)
```
### قواعد قراءة النتيجة بصدق
1. **سيناريو الاختبار أخف من الرحلة الحقيقية بـ 3-5×** (4 نداءات API + 6 نبضات موقع؛
الحقيقية فيها تسعير وبحث وقرب ودردشة). لتبنّي رقم "10k رحلة حقيقية بالذروة"
يجب أن يعبر الاختبار **30k** بنجاح >99% و p95<500ms.
2. الصندوق مشغول ~40% بغيرنا — **افحص خارج الذروة وبجولات قصيرة (5-10 د)**،
وأعد الجولة مرتين للتأكد من ثبات الرقم.
3. مولّد الحمل على نفس الصندوق يسرق ~5% CPU عند هذه المعدلات — مقبول؛ الأدق
تشغيله من جهاز آخر نحو `http://IP:8080`.
4. المُلتزم به للعملاء = الرقم الذي عبر الاختبار، لا أكثر.
## 5) ملاحظات أداء محسومة (لماذا الحاويات لا تبطئنا)
- الحاوية على لينكس = عمليات عادية بنفس النواة (namespaces/cgroups) — **لا محاكاة ولا آلة افتراضية**. حمل CPU/ذاكرة ≈ صفر.
- المكان الوحيد الذي يُخسر فيه أداء فعلاً: كتابة قاعدة البيانات فوق overlayfs — **محلول** بـ volume مسمّى (`mysql-data`).
- أداء PHP الحقيقي يصنعه opcache + مقاس حوض fpm + فهارس MySQL — كلها مضبوطة في `php/` وليست متعلقة بالدوكر أصلاً.
- التقسيم إلى 6 حاويات لا يكلف أداءً (نفس العمليات) ويعطي: إعادة تشغيل خدمة وحدها، حدود ذاكرة لكل خدمة، لوغات منفصلة، واستنساخ عميل = `clone + .env + up`.
## 6) Mawasalati (transit) — حاوية php-fpm معزولة
- الخدمة `php_transit` منفصلة تماماً عن `php` الرئيسي: صورة مختلفة (`php/Dockerfile.fpm.transit`، بدون composer/vendor)، حوض fpm أصغر (`php/www-pool.transit.conf`، 16 عامل)، وحدّ ذاكرة 512m خاص بها.
- الملفات المرَكَّبة داخلها فقط: `backend/core` (read-only — JWT/Database/RateLimiter المشتركة) و`backend/functions.php` (read-only) و`backend/transit` (rw). لا صلة بـ`backend` الرئيسي كاملاً ولا `payment_server/v2` ولا `loction_server`.
- التوجيه في nginx: أي طلب يطابق `^/backend/transit/.*\.php$` يذهب إلى `php_transit:9000` بدل `php:9000` — هذا location يجب أن يبقى **قبل** الـ `location ~ \.php$` العام في `nginx/default.conf`.
- المشترك الوحيد فعلياً مع النظام الأساسي: مصادقة JWT للسائق/الراكب (`backend/core`)، ونفس `mysql`/`redis` (قاعدة `transit` منفصلة أصلاً عبر `Database::get('transit')`).
- انهيار أو ازدحام `php` الرئيسي لا يوقف Mawasalati، والعكس صحيح.
- **cron jobs** (`cron_sync_members.php`, `cron_cleanup.php`, `cron_approaching_alerts.php`): تبقى تُستدعى من crontab المضيف كما هي حالياً (لا تغيير) — الملفات ما تزال متاحة من حاوية `php` الرئيسية أيضاً لأن `../backend` كاملة ما تزال مركّبة فيها.
## 7) طلبات الطعام (food) — نفس نمط العزل
- الخدمتان `php_food` و`socket_food` منفصلتان تماماً عن `php` الرئيسي وعن `php_transit`، بنفس منطق §6: صورة `php/Dockerfile.fpm.transit` (fpm خفيفة بلا composer، مُعاد استخدامها لأن الطعام أيضاً لا يحتاج vendor)، حوض `php/food-pool.conf` (24 عاملاً)، حد ذاكرة 768m لـ`php_food` و512m لـ`socket_food`.
- الملفات المرَكَّبة داخل `php_food` فقط: `backend/core` (ro)، `backend/functions.php` (ro)، `backend/food` (rw). لا صلة بـ`backend` الرئيسي ولا `payment_server/v2` ولا `loction_server`.
- التوجيه في nginx: `^/backend/food/.*\.php$` يذهب إلى `php_food:9000`، ويجب أن يبقى **قبل** الـ location العام.
- `socket_food` — WS بورت 4040 (يتطلب TLS من nginx المضيف مثل بقية السوكيتات، انظر §… أعلاه) + HTTP داخلي 4041 يستقبل نداءات من `php_food` بمفتاح `X-Internal-Key` (نفس `INTERNAL_SOCKET_KEY`) لدفع تحديثات حالة الطلب لحظياً — نفس نمط `broadcast_bus_location` في مواصلاتي، وليس Redis pub/sub.
- قاعدة `siro_food` منفصلة تماماً (`Database::get('food')`، ممنوع `Database::get('main')` داخل `backend/food/`)، والمحفظة/الدفع/JWT/FCM مشتركة مع النظام الرئيسي — نفس مبدأ transit تماماً، موثّق بالتفصيل في [docs/10_food_orders/FOOD_ORDERS_PLAN_AR.md](../docs/10_food_orders/FOOD_ORDERS_PLAN_AR.md).
- `FOOD_ENABLED=false` في `.env` يُرجع 503 من كل بوابات `backend/food/*` فوراً بلا نشر جديد — مفتاح التراجع الأول.
- **الحالة الحالية: المراحل صفر–الرابعة مبنية على فرع `feature/food-delivery-module`** (غير مُلتزم بها على main، وغير مُختبرة على بيئة حقيقية):
- **صفر — التأسيس**: حاويتان، nginx، `Database::get('food')`، `schema_food.sql`، بوابات الزبون/المطعم/السائق/الإدارة.
- **الأولى — الكتالوج**: تصفح/بحث/تفاصيل مطعم، اعتماد إداري للمطاعم، دخول لوحة المطعم، تفعيل/إيقاف صنف.
- **الثانية — الطلب**: `cart/quote.php` (تسعير من الخادم فقط، موقّع بـ HMAC صالح 10 دقائق)، `order/create.php` (idempotent عبر `client_order_uuid`، آلة الحالة `food_transition_status()`)، `order/status.php|cancel.php|rate.php|history.php`، `merchant_ops/accept.php|reject.php|preparing.php|ready.php`.
- **الثالثة — التوصيل**: `courier/toggle_availability.php|offer_respond.php|picked_up.php|delivered.php|active.php`، قفل ذرّي `SET NX EX 20` يمنع سباق القبول، `cron_order_timeouts.php` لإعادة العرض بعد المهلة وإلغاء الطلبات المعلّقة.
- **الرابعة — المال**: خصم/استرجاع فوري من المحفظة عبر نفس عقد `initiate_prime.php` S2S، `admin/payouts.php` لتوليد تقرير تسوية المطاعم.
**⚠️ ثلاث نقاط تحتاج تأكيداً حقيقياً قبل أي تشغيل بمال فعلي — موثّقة كتعليقات صريحة في الكود نفسه (`food/functions.php`)، وليست تفاصيل تنفيذ ثانوية**:
1. **لا يوجد حجز حقيقي (hold/capture)**: سيرفر المحفظة الخارجي لا يعرض API حجز مسبق — التنفيذ الحالي هو خصم فوري عند إنشاء الطلب + استرجاع كامل عند الرفض/الإلغاء. إن أُضيف hold حقيقي لاحقاً، `foodWalletMove()` هو أول مكان يُعدَّل.
2. **عامل تحويل العملة غير مؤكَّد**: `FOOD_CURRENCY_DIVISOR` (افتراضي 1000، أي fils) يحوّل "أصغر وحدة نقدية" في `siro_food` إلى المبلغ العشري الذي يتوقعه سيرفر المحفظة — يجب تأكيده مع فريق المحفظة.
3. **لا تحويل آلي لأرباح السائقين**: العقد S2S المؤكد فقط لتحويلات سائق↔سائق (`driverWallet/transfer.php`)، لا لإيداع أرباح من المنصة مباشرة إلى محفظة سائق. أرباح التوصيل (`delivery_fee`) وديون التحصيل النقدي تُسجَّل محاسبياً فقط في `food_order_payments` (نوع `courier_payout` / `cash_settlement`) بحالة `pending` — **لا صرف فعلي بعد**.
4. **أسطول التوصيل معزول عمداً عن `loction_server`**: لا تعديل على `driver_socket.php` الحي. السائق يُفعّل "وضع التوصيل" فيُضاف إلى SET مستقلة `food:couriers:opted_in` نتقاطع معها مع `geo:drivers:available` (قراءة فقط).
**لم يُبنَ بعد**: المرحلة الخامسة (التقسية والإطلاق — مراجعة أمنية مخصصة، اختبار ضغط، إطلاق تدريجي)، وربط الصرف الفعلي بمجرد تأكيد النقاط الثلاث أعلاه.