چرا به لود بالانسر نیاز دارید و از کجا شروع کنید؟
وقتی ترافیک سایت شما از توان یک سرور عبور میکند یا نیاز به high availability دارید، اولین راهحلی که به ذهن میرسد افزودن سرورهای بیشتر است. اما سرورهای اضافه بدون یک لود بالانسر نه تنها مشکل را حل نمیکنند، بلکه مدیریت نشدن درخواستها بین آنها باعث میشود برخی سرورها بیش از حد بارگذاری شوند و برخی دیگر بیکار بمانند. لود بالانسر درخواستهای ورودی را بین چند سرور backend توزیع میکند، سلامت آنها را بررسی میکند و در صورت خرابی یکی، ترافیک را به سرورهای سالم هدایت میکند.
در این مقاله یک لود بالانسر عملیاتی با Nginx راهاندازی میکنیم. Nginx به دلیل مصرف منابع پایین و کارایی بالا، انتخاب رایجی برای این کار است. فرض میکنیم دو سرور backend با آدرسهای 10.0.0.11 و 10.0.0.12 دارید که یک اپلیکیشن وب روی پورت 8080 اجرا میکنند. لود بالانسر روی یک سرور جداگانه با آدرس 10.0.0.10 نصب میشود.
نصب و پیکربندی پایه لود بالانسر
ابتدا Nginx را روی سرور لود بالانسر نصب کنید. در توزیعهای مبتنی بر Debian/Ubuntu:
sudo apt update
sudo apt install nginx -y
سپس فایل کانفیگ اصلی را ویرایش کنید. بهتر است یک فایل جداگانه در مسیر /etc/nginx/sites-available/ بسازید و آن را به sites-enabled لینک کنید:
sudo nano /etc/nginx/sites-available/loadbalancer
محتوای اولیه برای توزیع round-robin ساده:
upstream backend_servers {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
این کانفیگ درخواستها را به ترتیب بین دو سرور توزیع میکند. هدرهای X-Forwarded-* را حتماً اضافه کنید تا اپلیکیشن شما آدرس واقعی کاربر را ببیند، نه آدرس لود بالانسر را. در غیر این صورت، لاگهای اپلیکیشن و ابزارهای تحلیل ترافیک شما اطلاعات نادرستی نشان میدهند.
فایل را ذخیره کنید و لینک symbolic بسازید:
sudo ln -s /etc/nginx/sites-available/loadbalancer /etc/nginx/sites-enabled/
sudo nginx -t
اگر خروجی syntax is ok بود، سرویس را ریاستارت کنید:
sudo systemctl restart nginx
انتخاب الگوریتم توزیع مناسب
الگوریتم پیشفرض round-robin برای اکثر کاربردها مناسب است، اما همیشه بهترین انتخاب نیست. اگر سرورهای شما توان پردازشی متفاوتی دارند، از weight استفاده کنید:
upstream backend_servers {
server 10.0.0.11:8080 weight=3;
server 10.0.0.12:8080 weight=1;
}
این تنظیم یعنی از هر ۴ درخواست، ۳ تا به سرور اول و ۱ تا به سرور دوم ارسال شود. اگر اپلیکیشن شما session-based است و نمیخواهید کاربر بین سرورها جابهجا شود، از ip_hash استفاده کنید:
upstream backend_servers {
ip_hash;
server 10.0.0.11:8080;
server 10.0.0.12:8080;
}
با ip_hash درخواستهای هر کاربر همیشه به یک سرور مشخص ارسال میشود. این روش سادهترین راه برای مدیریت session است، اما اگر کاربر از IP متغیر استفاده کند (مثل موبایل)، کارایی خود را از دست میدهد. در آن صورت باید session stickiness را در سطح اپلیکیشن پیادهسازی کنید، نه لود بالانسر.
پیکربندی health check برای تشخیص سرورهای خراب
مشکل کانفیگ بالا این است که اگر یکی از سرورهای backend از کار بیفتد، Nginx همچنان درخواستها را به آن ارسال میکند و کاربران خطای 502 دریافت میکنند. برای حل این مشکل باید health check فعال کنید. Nginx به صورت پیشفرض health check غیرفعال دارد که فقط در لحظه اتصال، سرور را بررسی میکند. اما برای بررسی دورهای و دقیقتر، باید از ماژول تجاری یا راهحلهای جایگزین استفاده کنید.
سادهترین راهحل رایگان، استفاده از max_fails و fail_timeout است:
upstream backend_servers {
server 10.0.0.11:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.12:8080 max_fails=3 fail_timeout=30s;
}
این تنظیم یعنی اگر در بازه ۳۰ ثانیه، ۳ بار اتصال به یک سرور ناموفق باشد، آن سرور به مدت ۳۰ ثانیه از چرخه حذف میشود. اما این روش فقط خطاهای اتصال را تشخیص میدهد، نه خطاهای اپلیکیشن. اگر اپلیکیشن شما پاسخ HTTP 500 بدهد، اتصال برقرار شده و Nginx آن را سالم میداند.
برای health check واقعی که پاسخ اپلیکیشن را بررسی کند، میتوانید از ماژول nginx-upstream-check-module استفاده کنید یا یک اسکریپت خارجی بنویسید. روش سادهتر، استفاده از یک endpoint مخصوص در اپلیکیشن است:
location /health {
proxy_pass http://backend_servers;
proxy_set_header Host $host;
}
location / {
proxy_pass http://backend_servers;
proxy_next_upstream error timeout http_500 http_502 http_503;
}
با proxy_next_upstream، اگر سرور اول خطای 500 یا 502 برگرداند، Nginx به طور خودکار درخواست را به سرور بعدی ارسال میکند. این روش ساده و مؤثر است، اما برای تشخیص دورهای سلامت سرورها کافی نیست.
پیادهسازی health check با اسکریپت خارجی
برای یک راهحل کاملتر، میتوانید یک اسکریپت bash بنویسید که هر ۱۰ ثانیه endpoint سلامت هر سرور را بررسی کند و در صورت خرابی، آن را از upstream حذف کند. اما این روش پیچیده است و نگهداری آن سخت است. پیشنهاد من استفاده از HAProxy برای health check واقعی است، اما اگر میخواهید با Nginx بمانید، ترکیب proxy_next_upstream و max_fails برای اکثر کاربردها کافی است.
اشتباه رایج: بسیاری از افراد فقط max_fails را تنظیم میکنند و فکر میکنند مشکل حل شده است. اما اگر اپلیکیشن شما پاسخ 200 با محتوای خطا برگرداند (مثل خطای PHP که در HTML نمایش داده میشود)، Nginx آن را سالم میداند. همیشه یک endpoint سلامت جداگانه در اپلیکیشن تعریف کنید که فقط وضعیت واقعی را برگرداند.
ختم SSL روی لود بالانسر
یکی از مهمترین وظایف لود بالانسر، ختم SSL است. به این معنی که ترافیک HTTPS از کاربر تا لود بالانسر رمزنگاری میشود، اما بین لود بالانسر و سرورهای backend به صورت HTTP منتقل میشود. این کار بار رمزنگاری را از سرورهای اپلیکیشن برمیدارد و مدیریت گواهی را ساده میکند.
ابتدا گواهی SSL را روی سرور لود بالانسر نصب کنید. اگر گواهی ندارید، با Let's Encrypt یک گواهی رایگان بگیرید:
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.com -d www.example.com
سپس کانفیگ لود بالانسر را برای SSL تنظیم کنید:
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://backend_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
نکته مهم: هدر X-Forwarded-Proto را حتماً تنظیم کنید. اپلیکیشن شما باید بداند که درخواست اصلی از طریق HTTPS آمده است، حتی اگر بین لود بالانسر و backend از HTTP استفاده میشود. اگر این هدر را تنظیم نکنید، اپلیکیشن ممکن است لینکهای HTTP تولید کند یا ریدایرکتهای ناامن ایجاد کند.
بهینهسازی SSL برای کارایی بهتر
برای کاهش زمان handshake و بهبود کارایی، میتوانید session cache را فعال کنید:
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
این تنظیم به Nginx اجازه میدهد session های SSL را بین اتصالهای مختلف به اشتراک بگذارد. مقدار 10m یعنی حدود ۱۰ مگابایت حافظه برای کش session ها که برای حدود ۴۰٬۰۰۰ session کافی است.
همچنین اگر همه سرورهای backend شما در یک شبکه خصوصی امن هستند، میتوانید از HTTP ساده بین لود بالانسر و backend استفاده کنید. اما اگر شبکه شما قابل اعتماد نیست، باید SSL را بین لود بالانسر و backend هم فعال کنید. در این صورت، به جای proxy_pass http:// از proxy_pass https:// استفاده کنید و گواهیها را روی هر سرور backend نصب کنید.
خطاهای رایج و رفع آنها
در ادامه چند خطای رایج که هنگام راهاندازی لود بالانسر با آن مواجه میشوید و راهحل آنها را میبینید:
خطای 502 Bad Gateway
این خطا یعنی Nginx نمیتواند به سرور backend متصل شود. ابتدا بررسی کنید که سرویس روی سرور backend در حال اجراست:
curl -I http://10.0.0.11:8080
اگر پاسخ دریافت نکردید، فایروال سرور backend را بررسی کنید. پورت 8080 باید از آدرس لود بالانسر قابل دسترسی باشد:
sudo ufw allow from 10.0.0.10 to any port 8080
خطای 504 Gateway Timeout
این خطا یعنی سرور backend پاسخ را در زمان تعیینشده برنگردانده است. زمان timeout پیشفرض Nginx 60 ثانیه است. اگر اپلیکیشن شما پردازش طولانی دارد، این مقدار را افزایش دهید:
location / {
proxy_pass http://backend_servers;
proxy_read_timeout 120s;
proxy_connect_timeout 10s;
}
مشکل session و لاگین
اگر کاربران شما بعد از لاگین به صفحه اصلی منتقل میشوند یا session آنها مدام قطع میشود، مشکل از توزیع درخواستها بین سرورهای مختلف است. از ip_hash استفاده کنید یا session stickiness را در اپلیکیشن پیادهسازی کنید. همچنین مطمئن شوید که هدر X-Forwarded-For به درستی تنظیم شده است، زیرا برخی اپلیکیشنها از این هدر برای شناسایی کاربر استفاده میکنند.
جمعبندی و گامهای بعدی
در این مقاله یک لود بالانسر عملیاتی با Nginx راهاندازی کردیم، سرورهای backend را اضافه کردیم، health check را پیکربندی کردیم و SSL را روی بالانسر ختم کردیم. این تنظیمات پایه برای اکثر کاربردهای تولیدی کافی است، اما اگر ترافیک شما بسیار بالا است یا نیاز به قابلیتهای پیشرفتهتری مثل rate limiting و caching دارید، میتوانید از HAProxy یا سرویسهای مدیریتشده لود بالانسر استفاده کنید.
اگر زیرساخت شما روی سرورهای ابری اجرا میشود، ServerNet امکان راهاندازی لود بالانسر اختصاصی یا استفاده از سرویسهای مدیریتشده را فراهم میکند که میتواند زمان راهاندازی را به شدت کاهش دهد. اما در هر صورت، درک مفاهیمی که در این مقاله پوشش داده شد، به شما کمک میکند تا تصمیم بهتری بگیرید و مشکلات زیرساخت خود را سریعتر عیبیابی کنید.
در نهایت، همیشه تنظیمات لود بالانسر را در محیط staging تست کنید و مانیتورینگ مناسبی برای سرورهای backend و خود لود بالانسر راهاندازی کنید. یک لود بالانسر بدون مانیتورینگ، فقط یک نقطه شکست جدید است.