143 lines
8.6 KiB
Markdown
143 lines
8.6 KiB
Markdown
# استقرار Production روی Coolify
|
|
|
|
این پروژه با سه سرویس اجرا میشود:
|
|
|
|
- `nginx`: فقط سرویسدهنده فایلهای `/static/` روی پورت 80
|
|
- `web`: اجرای Django با Gunicorn روی پورت داخلی 8000
|
|
- `notification-worker`: اجرای دائمی `python manage.py process_notifications` برای SMS و Bale
|
|
|
|
PostgreSQL جزو این Compose نیست و از دیتابیس مدیریتشده موجود استفاده میشود. Supervisor لازم نیست؛ هر process یک container مستقل دارد و `restart: unless-stopped` بازیابی آن را انجام میدهد.
|
|
|
|
## 1. Volumeهای Coolify
|
|
|
|
پیش از اولین deploy دو Docker volume با همین نامها ایجاد کنید:
|
|
|
|
```text
|
|
customer_ticket_static
|
|
customer_ticket_media
|
|
```
|
|
|
|
در صورت انتخاب نام دیگر، متغیرهای `STATIC_VOLUME_NAME` و `MEDIA_VOLUME_NAME` را مطابق آن تنظیم کنید. media حاوی آپلودهای ماندگار است. static در هر startup دوباره با `collectstatic` تکمیل میشود اما برای اشتراک بین web و Nginx volume لازم دارد.
|
|
|
|
## 2. دامنهها و Traefik خود Coolify
|
|
|
|
دامنه اصلی HTTPS را در Coolify مستقیماً به سرویس `web` و پورت `8000` متصل کنید. Traefik خود Coolify TLS، reverse proxy و هدر `X-Forwarded-Proto` را مدیریت میکند؛ Nginx جلوی Django قرار نمیگیرد.
|
|
|
|
برای همان دامنه، routeهای `/static/`، `/media/branding/` و `/protected-media/` را به سرویس `nginx` و پورت `80` بدهید و مطمئن شوید routeهای path-based نسبت به route عمومی web اولویت بالاتری دارند. برای `notification-worker` هیچ domain یا host port عمومی نسازید. اگر نسخه Coolify شما تعریف چند route روی یک دامنه را از UI پشتیبانی نمیکند، routing rule متناظر Traefik را برای این سه path به nginx اضافه کنید؛ معماری containerها همچنان بدون proxy_pass بین Nginx و Django است.
|
|
|
|
فایلهای آپلودی تیکت عمومی نیستند: Django ابتدا مجوز را بررسی میکند و سپس کاربر را با یک URL امضاشده پنجدقیقهای به `/protected-media/` روی Nginx میفرستد. branding عمومی زیر `/media/branding/` مستقیماً از Nginx سرو میشود.
|
|
|
|
## 3. متغیرهای ضروری
|
|
|
|
مقادیر زیر را در Environment Variables برنامه Coolify بسازید:
|
|
|
|
```dotenv
|
|
DJANGO_SECRET_KEY=<random-secret-at-least-50-characters>
|
|
DJANGO_ALLOWED_HOSTS=support.example.com
|
|
CSRF_TRUSTED_ORIGINS=https://support.example.com
|
|
PUBLIC_BASE_URL=https://support.example.com
|
|
WEBOTP_HOST=support.example.com
|
|
TIME_ZONE=Asia/Tehran
|
|
|
|
DATABASE_URL=postgresql://postgres:<URL_ENCODED_PASSWORD>@<INTERNAL_HOST>:5432/customerTicket
|
|
DB_CONN_MAX_AGE=60
|
|
DB_CONNECT_TIMEOUT=10
|
|
DB_SSLMODE=prefer
|
|
DATABASE_WAIT_TIMEOUT=60
|
|
|
|
STATIC_VOLUME_NAME=customer_ticket_static
|
|
MEDIA_VOLUME_NAME=customer_ticket_media
|
|
MEDIA_SIGNING_SECRET=<separate-random-secret-at-least-32-characters>
|
|
```
|
|
|
|
نام دیتابیس انتهای URL باید نام واقعی دیتابیس باشد. اگر دیتابیس ساختهشده `customerTicket` است، URL باید به `/customerTicket` ختم شود؛ URLی که به `/postgres` ختم میشود به دیتابیس پیشفرض `postgres` متصل خواهد شد. اگر password شامل `@`، `:`، `/`، `#` یا `%` است آن را URL-encode کنید.
|
|
|
|
میتوانید بهجای `DATABASE_URL` آن را خالی بگذارید و `POSTGRES_DB`، `POSTGRES_USER`، `POSTGRES_PASSWORD`، `POSTGRES_HOST` و `POSTGRES_PORT` را جداگانه تعریف کنید. یکی از این دو روش را انتخاب کنید.
|
|
|
|
## 4. تنظیم SMS
|
|
|
|
فقط provider مورد استفاده را انتخاب و secretهای همان provider را پر کنید.
|
|
|
|
### 0098SMS
|
|
|
|
```dotenv
|
|
SMS_PROVIDER_CLASS=apps.integrations.sms.sms0098.ZeroZeroNinetyEightSMSProvider
|
|
SMS0098_USERNAME=<username>
|
|
SMS0098_PASSWORD=<password>
|
|
SMS0098_FROM=<sender-number>
|
|
SMS0098_API_URL=http://www.0098sms.com/sendsmslink.aspx
|
|
```
|
|
|
|
### Behin SMS
|
|
|
|
```dotenv
|
|
SMS_PROVIDER_CLASS=apps.integrations.sms.behin.BehinSMSProvider
|
|
BEHIN_SMS_USERNAME=<username>
|
|
BEHIN_SMS_PASSWORD=<password>
|
|
BEHIN_SMS_SPECIAL_NUMBER=<sender-number>
|
|
BEHIN_SMS_WSDL_URL=http://panel.behinsms.com/SMSWS/SOAP.asmx?wsdl
|
|
```
|
|
|
|
### HTTP JSON provider
|
|
|
|
```dotenv
|
|
SMS_PROVIDER_CLASS=apps.integrations.sms.http.HTTPSMSProvider
|
|
SMS_API_URL=https://provider.example/api/messages
|
|
SMS_API_TOKEN=<token>
|
|
SMS_SENDER=<sender>
|
|
```
|
|
|
|
## 5. تنظیم Bale و worker
|
|
|
|
```dotenv
|
|
BALE_API_BASE=https://tapi.bale.ai
|
|
BALE_BOT_TOKEN=<bot-token>
|
|
BALE_GROUP_CHAT_ID=<group-chat-id>
|
|
INTEGRATION_HTTP_TIMEOUT_SECONDS=10
|
|
MAX_NOTIFICATION_ATTEMPTS=5
|
|
```
|
|
|
|
در production نبودن `BALE_BOT_TOKEN` خطای قابلمشاهده ایجاد میکند و job را بیصدا SENT علامت نمیزند. worker هر پنج ثانیه outbox دیتابیس را بررسی میکند، با قفل `skip_locked` از پردازش همزمان جلوگیری میکند و خطاها را با backoff تا سقف `MAX_NOTIFICATION_ATTEMPTS` دوباره امتحان میکند.
|
|
|
|
سیاست تأخیر پیامک پاسخ کارشناس و اعلان پاسخ مشتری در Bale از مدل `NotificationPolicy` در پنل مدیریت دیتابیس کنترل میشود؛ متغیر قدیمی `STAFF_REPLY_SMS_THROTTLE_SECONDS` مرجع اصلی این رفتار نیست.
|
|
|
|
## 6. تنظیمات امنیتی
|
|
|
|
```dotenv
|
|
SECURE_SSL_REDIRECT=true
|
|
SECURE_HSTS_SECONDS=31536000
|
|
SECURE_HSTS_INCLUDE_SUBDOMAINS=true
|
|
SECURE_HSTS_PRELOAD=false
|
|
```
|
|
|
|
`SECURE_HSTS_PRELOAD` را فقط وقتی true کنید که تمام subdomainها همیشه HTTPS هستند. `DJANGO_ALLOWED_HOSTS` فقط hostname و `CSRF_TRUSTED_ORIGINS` origin کامل همراه `https://` است. برای چند مقدار از کاما استفاده کنید.
|
|
|
|
## 7. ترتیب اولین deploy
|
|
|
|
1. volumeها و database خالی را ایجاد کنید.
|
|
2. Environment Variables را در Coolify وارد کنید.
|
|
3. Compose را deploy، دامنه اصلی را به `web:8000` و pathهای static/media گفتهشده را به `nginx:80` متصل کنید.
|
|
4. log سرویس `web` را برای موفقیت migrate، bootstrap، collectstatic و Gunicorn بررسی کنید.
|
|
5. log سرویس `notification-worker` را بررسی کنید؛ container باید Running بماند.
|
|
6. یکبار superuser بسازید:
|
|
|
|
```bash
|
|
docker compose exec web python manage.py createsuperuser
|
|
```
|
|
|
|
7. در پنل مدیریت، SMS templateها و `NotificationPolicy`ها را بررسی کنید و با گیرنده آزمایشی مجاز یک SMS و یک پیام Bale واقعی بفرستید.
|
|
|
|
## 8. کنترل سلامت و عیبیابی
|
|
|
|
- `GET /healthz/` هم web server و هم اتصال دیتابیس را بررسی میکند.
|
|
- `docker compose ps` باید `web` و `nginx` را healthy و worker را Running نشان دهد.
|
|
- `docker compose logs --tail=200 web notification-worker nginx` خطاهای startup و provider را نمایش میدهد.
|
|
- وضعیت outbox را میتوان در Django admin یا shell از مدل `NotificationJob` بررسی کرد؛ FAILED و `last_error` دلیل خطا را نگه میدارند.
|
|
- برای پردازش کنترلشده یک job مشخص: `python manage.py process_notifications --once --job-id <ID>`.
|
|
|
|
ارسال اعلان ماهیت at-least-once دارد: اگر provider پیام را بپذیرد ولی process پیش از ثبت نتیجه در PostgreSQL قطع شود، retry ممکن است پیام تکراری بسازد. `dedupe_key` جلوی ساخت job تکراری در برنامه را میگیرد، اما تضمین exactly-once به پشتیبانی idempotency از طرف provider نیاز دارد.
|
|
|
|
## 9. Mirrorهای ایرانی
|
|
|
|
Dockerfile بهطور پیشفرض PyPI لیارا را از آدرس `https://package-mirror.liara.ir/repository/pypi/simple` و mirror Debian لیارا را استفاده میکند. اگر mirror Debian در محیط شما متفاوت است، build argument با نام `DEBIAN_MIRROR_HOST` را تغییر دهید. برای image مربوط به Nginx نیز میتوانید `NGINX_IMAGE` را به مسیر registry mirror مورد اعتماد خود تغییر دهید.
|