پاسارگارد
آموزش‌ها

راه‌اندازی Multi-Worker

حالت Multi-Worker چند پردازش PasarGuard را همزمان اجرا می‌کند تا ترافیک API و کارهای پس‌زمینه با هم مقیاس‌پذیر شوند، به‌جای اینکه برای یک event loop واحد رقابت کنند.

از نسخه v5.3.0 پاسارگارد به بعد، روش توصیه‌شده برای اجرای Multi-Worker استفاده از یک سرویس واحد با all-in-one و UVICORN_WORKERS بزرگ‌تر از ۱ است که از طریق NATS هماهنگ می‌شود. راه‌اندازی قدیمی مبتنی بر سرویس‌های جدا (ROLE=backend / node / scheduler) منسوخ شده و در نسخه v7.0.0 حذف خواهد شد — اگر هنوز از آن استفاده می‌کنید، بخش «راه‌اندازی قدیمی مبتنی بر Role جدا (پیش از v5.3.0)» را در پایین همین صفحه ببینید.

در مثال‌های زیر، فایل‌های docker-compose.yml و .env در مسیر /opt/pasarguard قرار دارند و داده‌ها در /var/lib/pasarguard ذخیره می‌شوند.

نحوه عملکرد

  • ROLE=all-in-one (مقدار پیش‌فرض) به‌طور همزمان API HTTP، داشبورد، worker نود و کارهای زمان‌بندی‌شده را در یک پردازش اجرا می‌کند.
  • با تنظیم UVICORN_WORKERS می‌توانید چند پردازش سیستم‌عاملی از همان برنامه all-in-one را زیر نظر مدیریت‌کننده پردازش Uvicorn اجرا کنید، به‌جای اجرای سرویس‌های جدا.
  • هر زمان که بیش از یک پردازش نیاز به اشتراک وضعیت داشته باشد — چه UVICORN_WORKERS بزرگ‌تر از ۱ باشد و چه Roleهای جدای منسوخ‌شده — NATS انتخاب رهبر (برای کارهای زمان‌بندی‌شده) و همگام‌سازی نود/کاربر بین پردازش‌ها را هماهنگ می‌کند. در این حالت، PasarGuard بدون NATS اجرا نمی‌شود.

چه زمانی استفاده کنیم

  • تعداد نود/کاربر زیاد دارید و به توان عملیاتی بهتر از یک پردازش تنها نیاز دارید.
  • می‌خواهید با افزایش ساده UVICORN_WORKERS، ظرفیت API/worker را مقیاس‌پذیر کنید.
  • می‌خواهید کارهای زمان‌بندی‌شده و همگام‌سازی نود در چند پردازش به‌درستی کار کنند.

پیش‌نیازها

  • هر زمان که UVICORN_WORKERS بزرگ‌تر از ۱ باشد، NATS الزامی است.
  • همه پردازش‌های worker باید مسیر /var/lib/pasarguard را مشترک داشته باشند (قالب‌ها، گواهی‌ها، فایل‌های زمان اجرا).
  • تنظیمات pool پایگاه داده به‌ازای هر worker است، نه کلی — برای نمونه ۴ worker با pool size برابر ۱۰ می‌توانند تا ۴۰ اتصال پایه باز کنند.

حالت Multi-Worker هر زمان که UVICORN_WORKERS بزرگ‌تر از ۱ باشد، نیازمند NATS_ENABLED=True است. اگر NATS غیرفعال باشد، PasarGuard اجرا نمی‌شود.

مرحله ۱: آماده‌سازی .env

این حداقل تنظیمات است. بخش پایگاه داده را از تب انتخابی خود اضافه کنید.

# نقش پردازش — مقدار پیش‌فرض را نگه دارید (all-in-one شامل API + worker نود + زمان‌بند است)
# ROLE = "all-in-one"

# تعداد پردازش‌های worker در Uvicorn
# این مقدار را برابر تعداد هسته‌های CPU سرور خود قرار دهید تا بهترین کارایی حاصل شود
UVICORN_WORKERS = 4

# NATS — زمانی که UVICORN_WORKERS بزرگ‌تر از ۱ باشد الزامی است
NATS_ENABLED = True
NATS_URL = "nats://127.0.0.1:4222"

# Web server
# UVICORN_PORT = 8000

# اتصال پایگاه داده (بر اساس بخش پایگاه داده انتخابی تنظیم شود)
# SQLALCHEMY_DATABASE_URL = "..."

# Pooling (به‌ازای هر پردازش worker — به بخش پیش‌نیازها در بالا مراجعه کنید)
# SQLALCHEMY_POOL_SIZE = 10
# SQLALCHEMY_MAX_OVERFLOW = 30

مرحله ۲: انتخاب پایگاه داده

docker-compose.yml

services:
    nats:
        image: nats:2.10-alpine
        restart: unless-stopped
        command: ["-js"]
        ports:
            - "4222:4222"
        volumes:
            - nats-data:/data
        healthcheck:
            test: ["CMD", "nc", "-z", "localhost", "4222"]
            interval: 2s
            timeout: 2s
            retries: 10
            start_period: 30s

    pasarguard:
        container_name: pasarguard
        image: pasarguard/panel:latest
        restart: unless-stopped
        env_file: .env
        network_mode: host
        depends_on:
            nats:
                condition: service_healthy
            pgbouncer:
                condition: service_healthy
        volumes:
            - /var/lib/pasarguard:/var/lib/pasarguard
        healthcheck:
            test: ["CMD", "/code/healthcheck.sh"]
            interval: 5s
            timeout: 5s
            retries: 10
            start_period: 60s

    timescaledb:
        image: timescale/timescaledb:latest-pg17
        restart: always
        command: >
            postgres -c max_connections=${PG_MAX_CONNECTIONS:-400}
                     -c shared_buffers=${PG_SHARED_BUFFERS:-512MB}
                     -c work_mem=${PG_WORK_MEM:-32MB}
        environment:
            POSTGRES_DB: ${DB_NAME}
            POSTGRES_USER: ${DB_USER}
            POSTGRES_PASSWORD: ${DB_PASSWORD}
        ports:
            - "127.0.0.1:5432:5432"
        volumes:
            - /var/lib/postgresql/pasarguard:/var/lib/postgresql/data
        healthcheck:
            test: ["CMD-SHELL", "pg_isready -q -d ${DB_NAME} -U ${DB_USER}"]
            interval: 10s
            timeout: 5s
            retries: 5
        networks:
            - pg_backend
    pgbouncer:
        image: edoburu/pgbouncer:latest
        restart: always
        environment:
            DB_HOST: timescaledb
            DB_PORT: 5432
            DB_USER: ${DB_USER}
            DB_PASSWORD: ${DB_PASSWORD}
            DB_NAME: ${DB_NAME}
            LISTEN_PORT: 6432
            POOL_MODE: transaction
            MAX_CLIENT_CONN: ${PG_MAX_CLIENT_CONN:-700}
            DEFAULT_POOL_SIZE: ${PG_DEFAULT_POOL_SIZE:-50}
            RESERVE_POOL_SIZE: ${PG_RESERVE_POOL_SIZE:-50}
            AUTH_TYPE: scram-sha-256
            LOG_CONNECTIONS: 0
            LOG_DISCONNECTIONS: 0
            LOG_POOLER_ERRORS: 0
            VERBOSE: 0
        ports:
            - "127.0.0.1:6432:6432"
        depends_on:
            timescaledb:
                condition: service_healthy
        healthcheck:
            test: ["CMD-SHELL", "pgrep -f pgbouncer > /dev/null"]
            interval: 10s
            timeout: 5s
            retries: 5
            start_period: 10s
        networks:
            - pg_backend

    pgadmin:
        image: dpage/pgadmin4:latest
        container_name: pgadmin
        restart: unless-stopped
        network_mode: host
        environment:
            PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL}
            PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD}
            PGADMIN_LISTEN_ADDRESS: 127.0.0.1
            PGADMIN_LISTEN_PORT: 8010
        volumes:
            - pgadmin:/var/lib/pgadmin
        depends_on:
            timescaledb:
                condition: service_healthy

volumes:
    pgadmin:
    nats-data:
networks:
    pg_backend:
        driver: bridge

docker-compose.yml

services:
    nats:
        image: nats:2.10-alpine
        restart: unless-stopped
        command: ["-js"]
        ports:
            - "4222:4222"
        volumes:
            - nats-data:/data
        healthcheck:
            test: ["CMD", "nc", "-z", "localhost", "4222"]
            interval: 2s
            timeout: 2s
            retries: 10
            start_period: 30s

    pasarguard:
        container_name: pasarguard
        image: pasarguard/panel:latest
        restart: unless-stopped
        env_file: .env
        network_mode: host
        depends_on:
            nats:
                condition: service_healthy
            pgbouncer:
                condition: service_healthy
        volumes:
            - /var/lib/pasarguard:/var/lib/pasarguard
        healthcheck:
            test: ["CMD", "/code/healthcheck.sh"]
            interval: 5s
            timeout: 5s
            retries: 10
            start_period: 60s

    postgresql:
        image: postgres:latest
        restart: always
        command: >
            postgres -c max_connections=${PG_MAX_CONNECTIONS:-400}
                     -c shared_buffers=${PG_SHARED_BUFFERS:-512MB}
                     -c work_mem=${PG_WORK_MEM:-32MB}
        environment:
            POSTGRES_DB: ${DB_NAME}
            POSTGRES_USER: ${DB_USER}
            POSTGRES_PASSWORD: ${DB_PASSWORD}
        ports:
            - "127.0.0.1:5432:5432"
        volumes:
            - /var/lib/postgresql/pasarguard:/var/lib/postgresql
        healthcheck:
            test: ["CMD-SHELL", "pg_isready -q -d ${DB_NAME} -U ${DB_USER}"]
            interval: 10s
            timeout: 5s
            retries: 5
        networks:
            - pg_backend
    pgbouncer:
        image: edoburu/pgbouncer:latest
        restart: always
        environment:
            DB_HOST: postgresql
            DB_PORT: 5432
            DB_USER: ${DB_USER}
            DB_PASSWORD: ${DB_PASSWORD}
            DB_NAME: ${DB_NAME}
            LISTEN_PORT: 6432
            POOL_MODE: transaction
            MAX_CLIENT_CONN: ${PG_MAX_CLIENT_CONN:-700}
            DEFAULT_POOL_SIZE: ${PG_DEFAULT_POOL_SIZE:-50}
            RESERVE_POOL_SIZE: ${PG_RESERVE_POOL_SIZE:-50}
            AUTH_TYPE: scram-sha-256
            LOG_CONNECTIONS: 0
            LOG_DISCONNECTIONS: 0
            LOG_POOLER_ERRORS: 0
            VERBOSE: 0
        ports:
            - "127.0.0.1:6432:6432"
        depends_on:
            postgresql:
                condition: service_healthy
        healthcheck:
            test: ["CMD-SHELL", "pgrep -f pgbouncer > /dev/null"]
            interval: 10s
            timeout: 5s
            retries: 5
            start_period: 10s
        networks:
            - pg_backend

    pgadmin:
        image: dpage/pgadmin4:latest
        container_name: pgadmin
        restart: unless-stopped
        network_mode: host
        environment:
            PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL}
            PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD}
            PGADMIN_LISTEN_ADDRESS: 127.0.0.1
            PGADMIN_LISTEN_PORT: 8010
        volumes:
            - pgadmin:/var/lib/pgadmin
        depends_on:
            postgresql:
                condition: service_healthy

volumes:
    pgadmin:
    nats-data:
networks:
    pg_backend:
        driver: bridge

docker-compose.yml

services:
    nats:
        image: nats:2.10-alpine
        restart: unless-stopped
        command: ["-js"]
        ports:
            - "4222:4222"
        volumes:
            - nats-data:/data
        healthcheck:
            test: ["CMD", "nc", "-z", "localhost", "4222"]
            interval: 2s
            timeout: 2s
            retries: 10
            start_period: 30s

    pasarguard:
        container_name: pasarguard
        image: pasarguard/panel:latest
        restart: always
        env_file: .env
        network_mode: host
        depends_on:
            nats:
                condition: service_healthy
            mysql:
                condition: service_healthy
        volumes:
            - /var/lib/pasarguard:/var/lib/pasarguard
        healthcheck:
            test: ["CMD", "/code/healthcheck.sh"]
            interval: 5s
            timeout: 5s
            retries: 10
            start_period: 60s

    mysql:
        image: mysql:lts
        network_mode: host
        restart: always
        environment:
            MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
            MYSQL_ROOT_HOST: "%"
            MYSQL_DATABASE: ${DB_NAME}
            MYSQL_USER: ${DB_USER}
            MYSQL_PASSWORD: ${DB_PASSWORD}
        command:
            - --mysqlx=OFF
            - --bind-address=127.0.0.1
            - --character_set_server=utf8mb4
            - --collation_server=utf8mb4_unicode_ci
            - --skip-log-bin
            - --host-cache-size=0
            - --innodb-open-files=1024
            - --innodb-buffer-pool-size=256M
            - --general_log=0
            - --slow_query_log=1
            - --slow_query_log_file=/var/lib/mysql/slow.log
            - --long_query_time=2
        volumes:
            - /var/lib/mysql/pasarguard:/var/lib/mysql
        healthcheck:
            test:
                [
                    "CMD",
                    "mysqladmin",
                    "ping",
                    "-h",
                    "127.0.0.1",
                    "-u",
                    "${DB_USER}",
                    "--password=${DB_PASSWORD}",
                ]
            start_period: 5s
            interval: 5s
            timeout: 5s
            retries: 55

    phpmyadmin:
        container_name: phpmyadmin
        image: phpmyadmin:latest
        restart: always
        env_file: .env
        network_mode: host
        environment:
            PMA_HOST: 127.0.0.1
            APACHE_PORT: 8010
            UPLOAD_LIMIT: 1024M
        depends_on:
            mysql:
                condition: service_healthy

volumes:
    nats-data:

docker-compose.yml

services:
    nats:
        image: nats:2.10-alpine
        restart: unless-stopped
        command: ["-js"]
        ports:
            - "4222:4222"
        volumes:
            - nats-data:/data
        healthcheck:
            test: ["CMD", "nc", "-z", "localhost", "4222"]
            interval: 2s
            timeout: 2s
            retries: 10
            start_period: 30s

    pasarguard:
        container_name: pasarguard
        image: pasarguard/panel:latest
        restart: unless-stopped
        env_file: .env
        network_mode: host
        depends_on:
            nats:
                condition: service_healthy
            mariadb:
                condition: service_healthy
        volumes:
            - /var/lib/pasarguard:/var/lib/pasarguard
        healthcheck:
            test: ["CMD", "/code/healthcheck.sh"]
            interval: 5s
            timeout: 5s
            retries: 10
            start_period: 60s

    mariadb:
        image: mariadb:lts
        restart: always
        environment:
            MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
            MYSQL_ROOT_HOST: "%"
            MYSQL_DATABASE: ${DB_NAME}
            MYSQL_USER: ${DB_USER}
            MYSQL_PASSWORD: ${DB_PASSWORD}
        command:
            - --bind-address=0.0.0.0
            - --character_set_server=utf8mb4
            - --collation_server=utf8mb4_unicode_ci
            - --host-cache-size=0
            - --innodb-open-files=1024
            - --innodb-buffer-pool-size=256M
            - --binlog_expire_logs_seconds=1209600
            - --innodb-log-file-size=64M
            - --innodb-doublewrite=0
            - --general_log=0
            - --slow_query_log=1
            - --slow_query_log_file=/var/lib/mysql/slow.log
            - --long_query_time=2
            - --innodb_snapshot_isolation=0
        ports:
            - "127.0.0.1:3306:3306"
        volumes:
            - /var/lib/mysql/pasarguard:/var/lib/mysql
        healthcheck:
            test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
            start_period: 10s
            start_interval: 3s
            interval: 10s
            timeout: 5s
            retries: 3

volumes:
    nats-data:

مرحله ۳: اجرای سرویس‌ها

cd /opt/pasarguard
sudo docker compose up -d

برای بررسی وضعیت:

sudo docker compose ps

مشاهده لاگ‌های سرویس backend:

sudo docker compose logs -f pasarguard

راه‌اندازی قدیمی مبتنی بر Role جدا (پیش از v5.3.0)

استفاده از ROLE=backend / node / scheduler به‌صورت سرویس‌های جدا از نسخه v5.3.0 منسوخ شده و در PasarGuard v7.0.0 حذف خواهد شد. برای استقرارهای جدید از راه‌اندازی all-in-one در بالا استفاده کنید و اگر هنوز از Roleهای جدا استفاده می‌کنید، برای مهاجرت برنامه‌ریزی کنید.

پیش از نسخه v5.3.0، Multi-Worker به معنای تقسیم PasarGuard به سه سرویس جدا بود که هرکدام با یک ROLE متفاوت از همان image اجرا می‌شدند:

نقشکاربرد
backendAPI HTTP + داشبورد + اجرای مهاجرت‌ها
nodeعملیات مربوط به نودها، لاگ‌ها و وظایف وابسته به نود
schedulerکارهای زمان‌بندی‌شده، نوتیفیکیشن‌ها و پردازش صف‌ها

در این حالت NATS همیشه الزامی بود (صرف‌نظر از UVICORN_WORKERS) و ROLE باید برای هر سرویس جداگانه تنظیم می‌شد.