حاويات تطبيقات متعددة لموقع Discourse واحد

يمكنك استضافة عدة تثبيتات مستقلة لـ Discourse على خادم واحد (حاويات منفصلة / منافذ منفصلة / ملفات app.yml منفصلة) دون استخدام ميزة “multisite” في Discourse.

هذه الطريقة تتطلب جهدًا يدويًا أكثر من multisite، لكنها تحافظ على عزلة كل مثيل وتسهل نقل موقع فردي إلى خادمه الخاص لاحقًا.

إحدى الأنماط العملية هي:

• قاعدة بيانات Postgres خارجية (مثيل واحد)
• Redis خارجي (مثيل واحد)
• حاويات ويب متعددة لـ Discourse
• عقدة Sidekiq واحدة
• وكيل عكسي مع فحوصات الصحة

هذا يتجنب multisite تمامًا مع السماح بتوفير التكاليف في الإعدادات ذات الحركة المنخفضة.



دليل تشغيل متعدد الحاويات لـ Discourse
Postgres خارجي + Redis + HAProxy + app1 / app2


  1. حزم المضيف
الخطوة الأمر
تحديث النظام apt-get update
تثبيت الأدوات الأساسية apt-get install -y ca-certificates curl gnupg lsb-release
تثبيت HAProxy + certbot + socat apt-get install -y haproxy certbot socat

  1. شبكة Docker (مطلوبة)

مطلوب شبكة Docker محددة بواسطة المستخدم حتى تتمكن الحاويات من حل أسماء بعضها البعض.

الخطوة الأمر
إنشاء الشبكة docker network create discourse-net
التحقق docker network ls | grep discourse-net

هذا يسمح بـ:

• DISCOURSE_DB_HOST=pg
• DISCOURSE_REDIS_HOST=redis

ليعمل بشكل صحيح.


  1. الأسرار
الغرض الأمر
مستخدم خارق لـ Postgres export PG_SUPERPASS='REPLACE_ME_super_strong'
كلمة مرور قاعدة بيانات Discourse export DISCOURSE_DBPASS='REPLACE_ME_discordb_strong'
كلمة مرور Redis export REDIS_PASS='REPLACE_ME_redis_strong'
مفتاح السر الأساسي export SECRET_KEY_BASE="$(openssl rand -hex 64)"

  1. حاوية Postgres
الخطوة الأمر
إنشاء المجلد mkdir -p /var/discourse/external/postgres
تشغيل الحاوية docker run -d --name pg --restart=always --network=discourse-net -e POSTGRES_PASSWORD="$PG_SUPERPASS" -v /var/discourse/external/postgres:/var/lib/postgresql/data postgres:15
التحقق docker ps | grep pg

  1. إنشاء قاعدة البيانات
الخطوة الأمر
إنشاء الدور docker exec -it pg psql -U postgres -c "CREATE ROLE discourse LOGIN PASSWORD '$DISCOURSE_DBPASS';"
إنشاء قاعدة البيانات docker exec -it pg psql -U postgres -c "CREATE DATABASE discourse OWNER discourse ENCODING 'UTF8' TEMPLATE template0;"
البحث النصي docker exec -it pg psql -U postgres -d discourse -c "ALTER DATABASE discourse SET default_text_search_config = 'pg_catalog.english';"
اختبار تسجيل الدخول docker exec -it pg psql -U discourse -d discourse -c "select 1;"

  1. امتداد PGVECTOR

مطلوب لإصدارات Discourse الحديثة.

الخطوة الأمر
التثبيت docker exec -it pg bash -lc 'apt-get update && apt-get install -y postgresql-15-pgvector && rm -rf /var/lib/apt/lists/*'
إنشاء الامتداد docker exec -it pg psql -U postgres -d discourse -c "CREATE EXTENSION IF NOT EXISTS vector;"
التحقق docker exec -it pg psql -U postgres -d discourse -c "SELECT extname FROM pg_extension WHERE extname='vector';"

  1. حاوية Redis
الخطوة الأمر
إنشاء المجلد mkdir -p /var/discourse/external/redis

قالب إعداد Redis:

requirepass REPLACE_ME_REDIS
appendonly yes
save 900 1
save 300 10
save 60 10000
الخطوة الأمر
كتابة الإعداد tee /var/discourse/external/redis/redis.conf >/dev/null <<EOF
إدراج كلمة المرور sed -i "s/REPLACE_ME_REDIS/$REDIS_PASS/" /var/discourse/external/redis/redis.conf
تشغيل Redis docker run -d --name redis --restart=always --network=discourse-net -v /var/discourse/external/redis:/data -v /var/discourse/external/redis/redis.conf:/usr/local/etc/redis/redis.conf redis:7-alpine redis-server /usr/local/etc/redis/redis.conf
اختبار المصادقة docker exec -it redis redis-cli -a "$REDIS_PASS" ping

  1. تخطيط مجلدات Discourse
الخطوة الأمر
إنشاء المجلد الأساسي mkdir -p /var/discourse
الدخول cd /var/discourse
استنساخ المستودع git clone https://github.com/discourse/discourse_docker.git
مجلد الحاويات mkdir -p /var/discourse/containers
سجلات مشتركة mkdir -p /var/discourse/shared/web-only/log/var-log
ربط الحاويات ln -sfn /var/discourse/containers /var/discourse/discourse_docker/containers
ربط المشغل ln -sfn /var/discourse/discourse_docker/launcher /var/discourse/launcher

  1. حاويات التطبيق

app1.yml
• web + sidekiq
• المنفذ 8001

docker_args: "--network=discourse-net"
expose:
  - "8001:80"

app2.yml
• web فقط
• المنفذ 8002
• sidekiq معطل

docker_args: "--network=discourse-net"
expose:
  - "8002:80"

run:
  - exec: bash -lc 'mkdir -p /etc/service/sidekiq && touch /etc/service/sidekiq/down'

  1. التمهيد
الخطوة الأمر
الدخول cd /var/discourse/discourse_docker
تمهيد app1 ./launcher bootstrap app1
تشغيل app1 ./launcher start app1
تمهيد app2 ./launcher bootstrap app2
تشغيل app2 ./launcher start app2

  1. فحوصات الصحة
الخطوة الأمر
app1 curl -sSf http://127.0.0.1:8001/srv/status
app2 curl -sSf http://127.0.0.1:8002/srv/status
sidekiq app1 docker exec -it app1 pgrep -fa sidekiq
sidekiq app2 `docker exec -it app2 pgrep -fa sidekiq

  1. شهادة TLS
الخطوة الأمر
إيقاف الوكيل systemctl stop haproxy
إصدار الشهادة certbot certonly --standalone -d example.com --agree-tos -m you@example.com --non-interactive
تشغيل الوكيل systemctl start haproxy

  1. منطق HAProxy
frontend fe_discourse
    bind :80
    bind :443 ssl crt /etc/letsencrypt/live/example.com/haproxy.pem

    http-request set-header X-Forwarded-Proto https if { ssl_fc }
    http-request set-header X-Forwarded-Proto http if !{ ssl_fc }

    redirect scheme https code 301 if !{ ssl_fc }

    use_backend be_discourse if { nbsrv(be_discourse) gt 0 }
    default_backend be_maint
backend be_discourse
    balance roundrobin
    option httpchk GET /srv/status
    server app1 127.0.0.1:8001 check
    server app2 127.0.0.1:8002 check
backend be_maint
    http-request return status 503 content-type text/html string "<h1>Maintenance</h1>"

  1. إعادة البناء بدون توقف
الخطوة الأمر
تعطيل app1 echo "disable server be_discourse/app1" | socat stdio /run/haproxy/admin.sock
إعادة بناء app1 ./launcher rebuild app1
تمكين app1 echo "enable server be_discourse/app1" | socat stdio /run/haproxy/admin.sock
الخطوة الأمر
تعطيل app2 echo "disable server be_discourse/app2" | socat stdio /run/haproxy/admin.sock
إعادة بناء app2 ./launcher rebuild app2
تمكين app2 echo "enable server be_discourse/app2" | socat stdio /run/haproxy/admin.sock

النهاية

شبكة Docker مطلوبة
Postgres و Redis خارجيان
pgvector مثبت
Sidekiq معزول في app1
فحوصات صحة HAProxy مفعلة
خيار التراجع عن الأعطال نشط
إعادة البناء المتدحرجة مدعومة

نقل موقع واحد إلى خادمه الخاص لاحقًا

إحدى مزايا تشغيل تثبيتات مستقلة بالكامل لـ Discourse (بدلاً من multisite) هي أن النقل مباشر ومنخفض المخاطر.

كل مثيل لـ Discourse لديه بالفعل:

• حاوية خاصة به
• تحميلات خاصة به
• قاعدة بيانات خاصة به
• استخدام Redis خاص به
• ملف app.yml خاص به

لا حاجة لفك اقتران multisite.


خطوات النقل عالية المستوى

  1. توفير VPS جديد

ثبت Docker و Discourse بشكل طبيعي على الخادم الجديد.
لا تقم بإعداد multisite.


  1. إنشاء نسخة احتياطية كاملة

من الموقع المصدر:

الإدارة → النسخ الاحتياطي → إنشاء نسخة احتياطية

قم بتنزيل ملف النسخة الاحتياطية.

يشمل ذلك:

• قاعدة البيانات
• التحميلات
• المستخدمين
• الإعدادات
• السمات


  1. الاستعادة على الخادم الجديد

على الخادم الجديد:

• إكمال الإعداد الأولي
• تسجيل الدخول كمسؤول
• رفع النسخة الاحتياطية
• الاستعادة

يتعامل Discourse تلقائيًا مع توافق المخطط.


  1. تبديل DNS

قم بتحديث سجل A للنطاق ليوجه إلى عنوان IP للخادم الجديد.

بمجرد انتشار DNS، ينتقل المستخدمون بشكل شفاف.


  1. إيقاف الحاوية القديمة

على الخادم الأصلي:

• إيقاف الحاوية القديمة
• إزالتها عند التأكد

التثبيتات الأخرى لـ Discourse على نفس المضيف لا تتأثر.


لماذا هذا أبسط من multisite

في إعدادات multisite، غالبًا ما يتطلب النقل:

• فصل قواعد البيانات
• استخراج البيانات الخاصة بكل موقع
• تعديل ملف multisite.yml
• إعادة هيكلة Sidekiq
• إعادة تكوين التحميلات والبريد الإلكتروني

مع التثبيتات المستقلة، لا حاجة لأي من ذلك.

كل موقع مستقل بالفعل.


ملخص

هذا النهج يضحي ببعض التعقيد التشغيلي في البداية
مقابل فصل بسيط جدًا لاحقًا.

يعمل بشكل خاص جيدًا أثناء التجارب
أو بناء المجتمع في مراحله الأولى.


متى لا يكون هذا النهج خيارًا جيدًا على الأرجح

عادةً لا يكون هذا الإعداد فكرة جيدة إذا:

• تتوقع المواقع حركة مرور متوسطة أو عالية في وقت مبكر
• تعتمد بشكل كبير على الدعم الرسمي لـ Discourse
• غير مرتاح لتصحيح أخطاء Docker أو الشبكات أو الوكلاء العكسيين
• متطلبات وقت التشغيل صارمة أو حرجة للعمل
• المواقع مترابطة تشغيليًا بشكل وثيق
• تتوقع تجارب متكررة للإضافات عبر جميع المثيلات

في هذه الحالات، إما:

• إعداد multisite مدعوم
أو
• تثبيت واحد لـ Discourse لكل خادم

سيؤدي عادةً إلى مفاجآت تشغيلية أقل.


ملاحظة هامة

هذا النهج يزيد من مرونة البنية التحتية،
لكنه يزيد أيضًا من مسؤولية المسؤول.

يعمل بشكل أفضل عندما يكون الشخص المشغّل مريحًا في امتلاك البنية الكاملة
ومعالجة الأعطال العرضية كجزء من عملية التعلم.

إذا كانت الاستقرار والقابلية للدعم هما الأهداف الأساسية،
فإن الإعداد المدعوم هو دائمًا الخيار الأفضل تقريبًا.

ملاحظة إضافية واحدة ترتبط مباشرة بجزء HAProxy في الإعداد أعلاه.

هناك سلوك شائع مع HAProxy + Discourse حيث يؤدي إعادة بناء حاوية الويب (على سبيل المثال باستخدام ./launcher rebuild app1) إلى إرجاع استجابات 503 Service Unavailable لفترة وجيزة لأن HAProxy لا يزال يرسل حركة المرور إلى هذا الواجهة الخلفية أثناء إعادة تشغيلها. هذا ليس خطأ في Discourse بحد ذاته - يحدث لأن الواجهة الخلفية غير متاحة للحظة أثناء إعادة البناء.

الحل البديل الموصى به هو استخدام مقبس إدارة HAProxy لـ:
\t1. تعطيل الخادم في HAProxy قبل إعادة البناء، و
\t2. إعادة تمكينه بعد انتهاء إعادة البناء

هذا يمنع أخطاء 503 العابرة تلك.

هناك مناقشة موجودة في Meta توثق هذا السلوك وشرح الحل البديل:

إذا كان أي شخص هنا يستخدم HAProxy لإعادة البناء المتدحرج، فإن هذا الموضوع يوفر سياقًا مفيدًا لسبب تضمين أوامر مقبس الإدارة في دليل التشغيل.

أنا أفعل شيئًا مشابهًا، مع حاوية واحدة بأسلوب الويب فقط لكل موقع و Traefik (على الرغم من أنني قمت أيضًا بإعداد يستخدم nginx-proxy) كوكيل عكسي. لقد جربت HAproxy لفترة من الوقت (وهو ما تستخدمه CDCK، آخر مرة علمت بها)، ولكني وجدته مرهقًا.

أنا متأكد تمامًا من أنك بحاجة إلى Redis واحد لكل خادم Discourse.

أعتقد أن هناك اختلافًا بسيطًا في المصطلحات هنا.

عندما تقول “Redis واحد لكل خادم Discourse”، أتفق إذا كنا نعني بخادم موقع Discourse منطقي واحد.

في حالتي:

  • يتم استخدام HAProxy فقط للتبديل التلقائي / الواجهة الأمامية
  • لا يوجد تكوين متعدد المواقع (multisite)
  • يوجد موقع Discourse واحد فقط (اسم مضيف واحد، قاعدة بيانات Postgres واحدة)
  • هناك فقط حاويتا تطبيق (app containers) قادرتان على خدمة نفس الموقع

لذا، هذا أقرب إلى تخطيط متعدد العمال/الويب (multi-web / HA layout)، وليس تثبيتَي Discourse مستقلين.

في هذا الإعداد، يعد مشاركة Redis أمرًا متوقعًا ومطلوبًا - وإلا ستفقد:

  • الجلسات المشتركة
  • تسليم MessageBus
  • تحديد المعدل (rate limiting)
  • تنسيق المهام في الخلفية (background job coordination)

هذا هو نفس نمط تشغيل حاويات web_only متعددة أو توسيع نطاق عمال الويب أفقيًا:
حاويات تطبيق متعددة ← قاعدة بيانات Postgres واحدة + Redis واحد.

حيث يجب ألا تتم مشاركة Redis عندما يكون هناك موقعان منفصلان لـ Discourse (أسماء مضيفات/قواعد بيانات مختلفة). في هذه الحالة، يحتاج كل موقع إلى قاعدة بيانات Redis خاصة به (أو مثيل) لتجنب تضارب المفاتيح.

لذا، أعتقد أننا متفقون من الناحية المفاهيمية - الأمر يتعلق فقط بـ:

  • :white_check_mark: Redis واحد لكل موقع Discourse
  • :cross_mark: Redis واحد لكل حاوية تطبيق فردية

يسعدني التوضيح أكثر إذا كنت قد أسأت فهم أي شيء - أردت فقط شرح الطوبولوجيا بمزيد من الوضوح.

أوه. هذا هو عكس ما اعتقدت أننا نتحدث عنه. العنوان هو “خادم واحد لمجتمعي ديسكورس” أنت تتحدث عن “خادمين لمجتمع ديسكورس واحد”

أنت على حق - لقد خلطت بين طوبولوجيتين مختلفتين، وعنوان الموضوع هو الدليل.

هذا الموضوع يدور حول “خادم واحد لمجتمعين” (موقعان مستقلان).
تعليقي السابق حول “Redis خارجي (مثيل واحد)” كان يصف نمطًا مختلفًا: “حاويتا تطبيق لمجتمع واحد” (التوافر العالي / تعدد الويب لموقع واحد).

لذا، لإعادة التأكيد بوضوح:

أ) موقعا Discourse مستقلان على خادم واحد (ما يسأل عنه صاحب الموضوع الأصلي)

  • تعامل معهما على أنهما تثبيتان منفصلان
  • يجب أن يكون لديهما قواعد بيانات Postgres منفصلة ومثيلات Redis منفصلة (أو عزل كافٍ لـ MessageBus Pub/Sub، وهو المأزق الذي اقتبسته)

ب) موقع Discourse واحد مع حاويات ويب/تطبيق متعددة (ما كنت أصفه)

  • يجب أن تشترك في قاعدة بيانات Postgres نفسها و Redis نفسه لذلك الموقع
    (الجلسات، تحديد المعدل، MessageBus، إلخ.)

إذًا: :white_check_mark: تحذيرك بشأن Redis ينطبق على أ (مجتمعان / موقعان).
ملاحظتي حول “Redis مشترك” تنطبق فقط على ب (مجتمع واحد موسع عبر حاويات متعددة).

شكرًا لك على التصحيح - سأبقي الحالتين منفصلتين بشكل صريح في أي أدلة تشغيل/منشورات متابعة.