بلاگ/راه‌اندازی RabbitMQ روی سرور مجازی و اختصاصی: نصب، امن‌سازی و کلاستر با صف‌های Quorum برای HA
به‌روز شده

راه‌اندازی RabbitMQ روی سرور مجازی و اختصاصی: نصب، امن‌سازی و کلاستر با صف‌های Quorum برای HA

1405/06/230 بازدید
راه‌اندازی RabbitMQ روی سرور مجازی و اختصاصی: نصب، امن‌سازی و کلاستر با صف‌های Quorum برای HA

راه‌اندازی RabbitMQ روی سرور مجازی و اختصاصی: نصب، امن‌سازی و کلاستر با صف‌های Quorum برای HA

نمای معماری RabbitMQ روی سرور مجازی و اختصاصی RabbitMQ یک Message Broker است که پیام را از تولیدکننده می‌گیرد، براساس قواعد Exchange مسیریابی می‌کند و تا زمان تحویل به مصرف‌کننده در صف نگه می‌دارد. این جداسازی به سرویس‌ها اجازه می‌دهد با سرعت‌های متفاوت کار کنند و وابستگی مستقیم میان آن‌ها کمتر شود. راه‌اندازی RabbitMQ روی سرور مجازی برای محیط توسعه، سامانه‌های کوچک و بارهای متوسط انتخاب مناسبی است. در بارهای سنگین‌تر یا زمانی که دیسک، شبکه و تأخیر قابل پیش‌بینی اهمیت بیشتری دارند، سرور اختصاصی فضای مطمئن‌تری فراهم می‌کند. البته نوع سرور به‌تنهایی دسترس‌پذیری بالا ایجاد نمی‌کند؛ برای HA باید چند نود مستقل، صف مناسب و تنظیمات درست سمت کلاینت داشته باشید. در این راهنما RabbitMQ را روی Ubuntu نصب می‌کنیم، دسترسی مدیریتی و شبکه را محدود می‌کنیم، مفهوم Queue و Exchange را مرور می‌کنیم و سپس یک کلاستر سه‌نودی با صف‌های Quorum می‌سازیم.

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

برای نصب تک‌نودی، یک VPS با ۲ هسته پردازنده، ۴ گیگابایت RAM و دیسک SSD نقطه شروع معقولی است. مقدار واقعی منابع به نرخ پیام، اندازه پیام، تعداد اتصال‌ها، مدت نگهداری و Persistent بودن پیام‌ها بستگی دارد. در محیط تولید بهتر است فضای دیسک و حافظه را با حاشیه کافی در نظر بگیرید؛ RabbitMQ هنگام کمبود حافظه یا دیسک، Publisherها را مسدود می‌کند تا از خرابی داده جلوگیری شود. برای کلاستر HA به سه نود نیاز دارید. استفاده از دو نود توصیه نمی‌شود، زیرا صف Quorum برای ادامه کار به اکثریت اعضا احتیاج دارد. سه نود امکان از دست رفتن یک عضو را فراهم می‌کنند. پیش‌نیازهای این آموزش: - Ubuntu 22.04 یا 24.04 روی هر نود - دسترسی کاربر دارای sudo - نام میزبان ثابت و قابل Resolve مانند rmq1، rmq2 و rmq3 - ارتباط شبکه خصوصی میان نودها - ساعت هماهنگ‌شده با NTP - دیسک SSD با فضای آزاد کافی - بکاپ تنظیمات و Definitions پیش از هر تغییر مهم برای یک نصب تک‌نودی، فقط پورت AMQP را در اختیار برنامه‌ها قرار دهید. در کلاستر نیز پورت‌های داخلی RabbitMQ باید تنها روی شبکه خصوصی باز باشند.

نصب RabbitMQ روی Ubuntu

RabbitMQ به Erlang وابسته است. نسخه Erlang و RabbitMQ باید با یکدیگر سازگار باشند؛ بنابراین در محیط تولید، بسته‌ها را از مخازن رسمی RabbitMQ نصب و نسخه‌ها را Pin کنید. نصب ساده از مخزن Ubuntu برای آزمایش مناسب است:

sudo apt update
sudo apt install -y rabbitmq-server
sudo systemctl enable --now rabbitmq-server
sudo systemctl status rabbitmq-server

برای اطمینان از سلامت نود، وضعیت سرویس را بررسی کنید:

sudo rabbitmq-diagnostics ping
sudo rabbitmq-diagnostics status

سپس افزونه رابط مدیریتی را فعال کنید:

sudo rabbitmq-plugins enable rabbitmq_management

این افزونه رابط وب را به‌طور پیش‌فرض روی پورت 15672 ارائه می‌کند. حساب guest فقط از localhost قابل استفاده است و نباید محدودیت آن را برای دسترسی اینترنتی حذف کنید. یک کاربر مدیریتی جدا بسازید:

sudo rabbitmqctl add_user rmqadmin 'A-Long-Random-Password'
sudo rabbitmqctl set_user_tags rmqadmin administrator
sudo rabbitmqctl set_permissions -p / rmqadmin ".*" ".*" ".*"

پس از آزمایش ورود، حساب پیش‌فرض را حذف کنید:

sudo rabbitmqctl delete_user guest

قرار دادن رمز در History شل مناسب نیست. در محیط عملیاتی آن را از Secret Manager یا ورودی تعاملی دریافت کنید و دسترسی فایل‌های تنظیمات را محدود نگه دارید.

امن‌سازی RabbitMQ

RabbitMQ نباید با پورت‌های مدیریتی باز روی اینترنت اجرا شود. برنامه‌ها بهتر است از شبکه خصوصی به نودها متصل شوند و مدیران از VPN، Bastion Host یا تونل SSH استفاده کنند.

محدود کردن پورت‌ها با فایروال

پورت‌های مهم RabbitMQ عبارت‌اند از:

پورتکاربردمحدوده دسترسی پیشنهادی
5672اتصال AMQP بدون TLSفقط شبکه برنامه‌ها
5671اتصال AMQP با TLSشبکه برنامه‌ها
15672پنل مدیریت و HTTP APIفقط شبکه مدیریت
25672ارتباط بین نودها و ابزار CLIفقط شبکه خصوصی کلاستر
4369Erlang Port Mapperفقط شبکه خصوصی کلاستر

برای نمونه، با UFW می‌توان دسترسی را به Subnet خصوصی محدود کرد:

sudo ufw default deny incoming
sudo ufw allow OpenSSH
sudo ufw allow from 10.20.0.0/24 to any port 5672 proto tcp
sudo ufw allow from 10.20.0.0/24 to any port 15672 proto tcp
sudo ufw allow from 10.20.0.0/24 to any port 25672 proto tcp
sudo ufw allow from 10.20.0.0/24 to any port 4369 proto tcp
sudo ufw enable

Subnet نمونه را با شبکه واقعی خود جایگزین کنید. اگر رابط مدیریت از طریق Reverse Proxy ارائه می‌شود، پورت 15672 را فقط برای IP همان Proxy باز بگذارید. برای فایروالی مبتنی بر nftables به‌جای UFW، راه‌اندازی فایروال nftables روی سرور لینوکس قوانین معادل را نشان می‌دهد.

ساخت Virtual Host و کاربر برنامه

هر برنامه یا محیط را در یک Virtual Host جدا قرار دهید. این کار مجوزها، Policyها و نام صف‌ها را از یکدیگر جدا می‌کند:

sudo rabbitmqctl add_vhost production
sudo rabbitmqctl add_user app_producer 'Another-Random-Password'
sudo rabbitmqctl set_permissions -p production app_producer \
  "^orders-exchange$" "^orders-exchange$" "^orders\\..*$"

به هر حساب فقط مجوز موردنیازش را بدهید. کاربر برنامه نباید تگ administrator داشته باشد. برای Producer و Consumer نیز می‌توان حساب‌های جدا ساخت تا افشای یک Credential کل سامانه را درگیر نکند.

فعال‌سازی TLS

برای ترافیک میان برنامه و RabbitMQ از TLS استفاده کنید. نمونه‌ای از تنظیمات /etc/rabbitmq/rabbitmq.conf:

listeners.tcp = none
listeners.ssl.default = 5671
ssl_options.cacertfile = /etc/rabbitmq/tls/ca_certificate.pem
ssl_options.certfile = /etc/rabbitmq/tls/server_certificate.pem
ssl_options.keyfile = /etc/rabbitmq/tls/server_key.pem
ssl_options.verify = verify_peer
ssl_options.fail_if_no_peer_cert = false
management.tcp.ip = 10.20.0.11
management.tcp.port = 15672

اگر فقط احراز هویت با نام کاربری و رمز عبور می‌خواهید، fail_if_no_peer_cert باید false باشد. برای Mutual TLS آن را true کنید و برای کلاینت‌ها گواهی معتبر بسازید. فایل کلید خصوصی باید فقط برای کاربر سرویس RabbitMQ خواندنی باشد. بعد از ویرایش، صحت تنظیمات و سرویس را بررسی کنید:

sudo systemctl restart rabbitmq-server
sudo rabbitmq-diagnostics listeners
sudo journalctl -u rabbitmq-server --since "10 minutes ago"

صف، Exchange و الگوی تحویل پیام

Producer معمولاً پیام را مستقیم به Queue نمی‌فرستد؛ پیام به Exchange تحویل داده می‌شود و Binding مشخص می‌کند به کدام صف برسد. چهار نوع رایج Exchange عبارت‌اند از: - direct: مسیریابی براساس تطابق دقیق Routing Key - topic: تطابق الگویی مانند orders.* - fanout: ارسال یک نسخه به همه صف‌های متصل - headers: تصمیم‌گیری براساس Headerهای پیام برای پیام‌های مهم، صف را Durable و پیام را Persistent تعریف کنید. این دو تنظیم خطر از دست رفتن پیام در Restart را کم می‌کنند، اما تأیید قطعی دریافت به Publisher Confirm نیاز دارد. در سمت Consumer نیز پس از پایان موفق پردازش، Ack بفرستید؛ Ack زودهنگام ممکن است با Crash شدن Worker باعث از دست رفتن کار شود. پیام ناموفق را بی‌نهایت Requeue نکنید. یک Dead Letter Exchange، تعداد Retry محدود و تأخیر میان تلاش‌ها تعریف کنید. پیام تکراری هم ممکن است رخ دهد، پس Consumer باید تا حد امکان Idempotent باشد.

ساخت کلاستر سه‌نودی RabbitMQ

در نمونه زیر IP نودها چنین است:

10.20.0.11 rmq1
10.20.0.12 rmq2
10.20.0.13 rmq3

این نام‌ها را در DNS خصوصی یا /etc/hosts هر سه نود ثبت کنید (همین الگوی چند نود مستقل در راه‌اندازی کلاستر Elasticsearch با Ansible روی چند سرور مجازی و اختصاصی هم به کار رفته است). دستور زیر باید روی هر نود نام درست را برگرداند:

hostname -f
getent hosts rmq1 rmq2 rmq3

Erlang Cookie برای احراز هویت ارتباط داخلی نودها استفاده می‌شود و باید روی همه اعضا یکسان باشد. ابتدا RabbitMQ را روی نود اول متوقف کنید و مقدار /var/lib/rabbitmq/.erlang.cookie را از یک مسیر امن به دو نود دیگر منتقل کنید. سپس مالکیت و سطح دسترسی را اصلاح کنید:

sudo chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie
sudo chmod 400 /var/lib/rabbitmq/.erlang.cookie
sudo systemctl restart rabbitmq-server

Cookie را در پیام‌رسان، مخزن Git یا Log قرار ندهید. روی rmq2 و rmq3 این دستورات را اجرا کنید:

sudo rabbitmqctl stop_app
sudo rabbitmqctl reset
sudo rabbitmqctl join_cluster rabbit@rmq1
sudo rabbitmqctl start_app

اکنون وضعیت کلاستر را از هر نود ببینید:

sudo rabbitmqctl cluster_status
sudo rabbitmq-diagnostics check_running

اگر نام نودها Resolve نشود، Cookie یکسان نباشد یا پورت‌های داخلی بسته باشند، Join شکست می‌خورد. قبل از Reset کردن نودی که داده دارد، از Definitions و داده‌های موردنیاز بکاپ بگیرید.

راه‌اندازی صف‌های Quorum برای HA

کلاستر سه‌نودی RabbitMQ با صف‌های Quorum و توزیع Replica بین نودها صف Quorum داده را میان چند عضو Replicate می‌کند و تصمیم‌ها را با الگوریتم اجماع Raft می‌گیرد. در یک کلاستر سه‌نودی، صف تا زمانی در دسترس می‌ماند که حداقل دو Replica بتوانند با یکدیگر ارتباط داشته باشند. صف Quorum را می‌توان هنگام Declare کردن با آرگومان x-queue-type=quorum ساخت. روش دیگر، تعریف Policy برای صف‌هایی با الگوی مشخص است:

sudo rabbitmqctl set_policy -p production quorum-queues \
  "^qq\\." '{"queue-type":"quorum"}' \
  --apply-to queues --priority 10

پس از آن، صف‌هایی مانند qq.orders با نوع Quorum ساخته می‌شوند. نوع یک صف موجود را نمی‌توان درجا از Classic به Quorum تغییر داد. برای مهاجرت، صف جدید بسازید، Binding و مسیر انتشار را تغییر دهید و پس از تخلیه صف قدیمی آن را حذف کنید. مقایسه کلی صف‌ها:

ویژگیClassic QueueQuorum Queue
کاربرد معمولصف ساده یا داده قابل‌بازیابیپیام مهم و HA
Replication داخلیبه‌صورت پیش‌فرض نداردمبتنی بر Raft
تحمل خرابی در سه Replicaوابسته به طراحی بیرونیخرابی یک نود
مصرف دیسک و شبکهکمتربیشتر
Priority Queueپشتیبانی می‌شودبرای این نیاز مناسب نیست
روش پیشنهادی تحویلAck و Publisher ConfirmAck و Publisher Confirm

برای صف Quorum، سه یا پنج عضو معمولاً کافی است. افزایش تعداد Replica هزینه نوشتن و ترافیک شبکه را بالا می‌برد. کلاستر را بین دیتاسنترهایی با تأخیر زیاد پخش نکنید؛ RabbitMQ Cluster برای شبکه محلی پایدار طراحی شده است. برای ارتباط میان سایت‌ها، Federation یا Shovel انتخاب مناسب‌تری است.

تنظیم کلاینت برای Failover واقعی

وجود سه نود کافی نیست. اگر برنامه فقط آدرس rmq1 را بشناسد، با خاموش شدن همان نود اتصالش قطع می‌شود. فهرست چند Endpoint را در کلاینت تعریف کنید یا یک Load Balancer مبتنی بر TCP جلوی نودها قرار دهید. کلاینت باید این رفتارها را داشته باشد: - اتصال مجدد با Backoff و Jitter - تعریف مجدد Exchange، Queue و Binding پس از اتصال - فعال بودن Publisher Confirm - Ack دستی پس از پردازش موفق - Prefetch متناسب با زمان و ظرفیت Worker - Timeout مشخص برای اتصال و عملیات - ثبت شناسه پیام برای کنترل پردازش تکراری در زمان اختلال شبکه، نودی که در اقلیت قرار گرفته نمی‌تواند نوشتن روی صف Quorum را ادامه دهد. این توقف عمدی است و از ایجاد دو نسخه ناسازگار از صف جلوگیری می‌کند.

مانیتورینگ، ظرفیت و پشتیبان‌گیری

افزونه Prometheus داخلی را فعال کنید و Metricها را فقط در شبکه مانیتورینگ در دسترس قرار دهید؛ نحوه راه‌اندازی Prometheus و Grafana روی سرور در راه‌اندازی مانیتورینگ سرور با Prometheus و Grafana روی سرور مجازی و اختصاصی شرح داده شده است:

sudo rabbitmq-plugins enable rabbitmq_prometheus

موارد مهم برای Alert عبارت‌اند از رشد مداوم تعداد پیام‌های Ready، زیاد شدن پیام‌های Unacked، قطع شدن Consumerها، مصرف حافظه، فضای آزاد دیسک، File Descriptorها و تغییر وضعیت اعضای کلاستر. از Definitions شامل Exchangeها، صف‌ها، Bindingها، Userها و Policyها خروجی بگیرید:

sudo rabbitmqctl export_definitions /var/backups/rabbitmq-definitions.json

این فایل ساختار RabbitMQ را نگه می‌دارد، نه محتوای پیام‌های داخل صف را. برای پیام‌های حیاتی، طراحی Replay، نگهداری رویداد در منبع اصلی یا روش بکاپ سازگار با نسخه RabbitMQ لازم است. بازیابی را نیز دوره‌ای آزمایش کنید؛ بکاپی که Restore نشده، قابل اتکا نیست.

انتخاب VPS یا سرور اختصاصی برای RabbitMQ

VPS برای شروع سریع، محیط Stage و بارهای قابل پیش‌بینی مناسب است. سرور اختصاصی زمانی ارزش دارد که نرخ بالای پیام، I/O مداوم یا حساسیت به نوسان منابع دارید. برای HA، نودها را روی میزبان‌های فیزیکی یا Failure Domainهای جدا قرار دهید؛ سه ماشین مجازی روی یک میزبان، در برابر خرابی همان میزبان مقاوم نیستند. اگر می‌خواهید ابتدا یک نود بسازید یا کلاستر را روی چند ماشین مستقل توسعه دهید، صفحه سرور مجازی ابری آریاسرویس مشخصات گزینه‌های در دسترس را نشان می‌دهد. برای بارهای دیسک‌محور و پایدارتر نیز سرور اختصاصی قابل بررسی است.

جمع‌بندی

راه‌اندازی RabbitMQ روی سرور مجازی با نصب بسته تمام نمی‌شود. محدود کردن شبکه، حذف حساب پیش‌فرض، جداسازی Virtual Hostها، استفاده از TLS، مدیریت درست Ack و فعال کردن Publisher Confirm بخش‌های اصلی یک استقرار قابل‌اعتماد هستند. برای دسترس‌پذیری بالا، سه نود مستقل بسازید، ارتباط داخلی را روی شبکه خصوصی نگه دارید و پیام‌های مهم را در صف‌های Quorum قرار دهید. در نهایت Failover را با خاموش کردن یک نود آزمایش کنید و مطمئن شوید برنامه با Endpoint دیگر متصل می‌شود، انتشار پیام ادامه پیدا می‌کند و Consumer پس از بازیابی، پیام تکراری را به‌درستی مدیریت می‌کند.