reverse proxy در برابر forward proxy
یک forward proxy برای کلاینت کار میکند. مرورگر برای استفاده از آن پیکربندی میشود، و از طرف مرورگر اینترنت را میگیرد — فیلترینگ خروجی سازمانی، و بیشتر آن چیزی که افراد وقتی «proxy» جستوجو میکنند منظورشان است.
یک reverse proxy برای سرور کار میکند. کلاینتها هیچ ایدهای از وجودش ندارند؛ هاستنیم شما را resolve میکنند، آن پاسخ میدهد، و تصمیم میگیرد با درخواست چهکار کند. اپلیکیشن شما ممکن است روی ماشینی دیگر باشد، در یک container، یا روی دهتا از آنها پخش شده باشد.
این تمایز مهم است چون نتایج جستوجو آن را محو میکنند. اگر صفحهای دربارهٔ «proxy serverها» دارد از بازکردن دسترسی به وبسایتهای مسدود صحبت میکند، دربارهٔ آن چیزی که جلوی اپلیکیشن شماست نیست.
واقعاً چه چیزی بهدست میآورید
TLS termination. گواهیها بهجای روی هر سرور اپلیکیشن، در یک جا زندگی میکنند. تمدید یک کار واحد میشود.
مسیریابی. یک هاستنیم، چند backend: /api به سرویس Node، / به WordPress، /static به object storage. کلاینت یک سایت میبیند.
Cache. خودِ proxy به درخواستهای تکراری پاسخ میدهد. این بزرگترین پیروزی عملکرد منفرد در دسترس اکثر سایتهاست، و همان چیزی است که اغلب خاموش گذاشته میشود.
فشردهسازی. Brotli یا gzip یکبار در edge استک شما اعمال میشود، نه در هر اپلیکیشن.
Rate limiting و فیلترینگ. جایی برای اعمال حدها و مسدودکردن ترافیک پیش از رسیدن به کدی که اجرایش هزینهٔ مالی دارد.
پنهانکردن origin. اگر سرور اپلیکیشن شما فقط از proxy قابلدسترس باشد، حملهها باید از دری عبور کنند که شما کنترلش میکنید.
جایی برای تغییر رفتار. ریدایرکتها، بازنویسی هدر و صفحات نگهداری که به یک استقرار اپلیکیشن نیاز ندارند.
یک پیکربندی کارآمد nginx
حداقلی که واقعاً درست است — هدرهای زیر تزئین اختیاری نیستند، همان چیزی هستند که باعث میشوند اپلیکیشن شما کلاینت را ببیند نه proxy را:
``` server { listen 443 ssl; server_name example.com;
ssl_certificate /etc/ssl/example.com/fullchain.pem; ssl_certificate_key /etc/ssl/example.com/privkey.pem;
location / { proxy_pass http://127.0.0.1:3000;
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;
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } ```
Host — بدون آن backend میبیند 127.0.0.1، و هر آدرس مطلقی که میسازد اشتباه است.
X-Forwarded-For و X-Real-IP — بدون آنها هر درخواست در لاگ اپلیکیشن شما از proxy میآید، و هر منطق per-IPای که دارید بیسروصدا روی proxy اعمال میشود بهجای بازدیدکننده.
X-Forwarded-Proto — بدون آن، اپلیکیشنی که پشت TLS termination است فکر میکند روی HTTP ساده است و لینکهای http:// تولید میکند، که همان حلقهٔ ریدایرکت را تولید میکند که دستکم یکبار برای همه یک بعدازظهر هزینه میکند.
جفت Upgrade — بدون آن، WebSocketها کار نمیکنند، و شکست شبیه یک باگ اپلیکیشن به نظر میرسد نه یک باگ proxy.
cache در proxy، بهاختصار
nginx میتواند پاسخهای upstream را با proxy_cache کش کند. مکانیک آن بهاندازهٔ کافی ساده است: یک مسیر cache اعلام کنید، آن را در location فعال کنید، و تصمیم بگیرید چه چیزی برای چه مدت ذخیره شود.
``` proxy_cache_path /var/cache/nginx keys_zone=site:50m max_size=5g inactive=24h;
location / { proxy_cache site; proxy_cache_valid 200 10m; proxy_cache_key "$scheme$request_method$host$request_uri"; add_header X-Cache-Status $upstream_cache_status; proxy_pass http://127.0.0.1:3000; } ```
دو چیز تعیین میکند این کمک میکند یا آسیب میزند.
cache key. پیشفرض کل URI را شامل میشود، پس ?utm_source=twitter یک ورودی جدا از آدرس تمیز میسازد. یک کمپین میتواند cache شما را به هزاران نسخه از یک صفحه تکهتکه کند و نرخ hit را به صفر برساند. پارامترهایی را که پاسخ را تغییر نمیدهند حذف کنید.
Purging. nginx متنباز هیچ مکانیزم purge ندارد؛ یا منتظر TTL میمانید یا فایلها را دستی از دایرکتوری cache، روی هر ماشین، حذف میکنید. این معمولاً همان نقطهای است که «ما proxy خودمان را اجرا میکنیم» شروع به آسیبزدن میکند، چون منتشرکردن یک اصلاحیه و ده دقیقه منتظرماندن تا ظاهر شود، workflowای نیست که کسی از آن لذت ببرد.
اجرای مال خودتان واقعاً چقدر هزینه دارد
یک nginx جلوی یک اپلیکیشن ارزان و کاملاً منطقی است. هزینه بعداً، تکهتکه، میرسد.
یک ماشین منفرد است. یک reverse proxy که تنها راه ورود است، تنها چیزی هم هست که باید بالا بماند. redundant کردنش یعنی یک ماشین دوم، یک آدرس شناور یا DNS failover، و پیکربندیای که روی هر دو یکسان است.
گواهیها. تمدید خودکار یک مشکل حلشده است تا وقتی که hook تمدید روی گره دوم بیسروصدا شکست بخورد و شما از یک هشدار مرورگر متوجه شوید.
بیاعتبارسازی cache در میان گرهها. با دو proxy، دو cache دارید، و purgeکردن یعنی این کار را روی هر دو، بهطور قابلاعتماد، از pipeline استقرارتان انجام دهید.
در یک جاست. بازدیدکنندگان شما اینطور نیستند. یک proxy در یک دیتاسنتر نمیتواند به کاربران در کشوری دیگر نزدیک باشد؛ آن یک مشکل فیزیک است، نه پیکربندی.
تیونکردن مداوم است. اندازههای بافر، timeoutها، keepalive به upstreamها، کانکشنهای worker — هرکدام از اینها روی ترافیک فعلی شما خوب است و روی ده برابرش اشتباه.
کِی کار را واگذار کنیم
یک CDN یک reverse proxy است که کس دیگری در مکانهای زیادی آن را اجرا میکند. کارکردها یکساناند — TLS termination، cache، فشردهسازی، فیلترینگ، مسیریابی — و تفاوتها همان بخشهایی هستند که سخت بودند: جغرافیا، redundancy، و purge فوری در همهٔ nodeها.
خط منطقی این است. تا وقتی proxy خودتان دارد کار محلی انجام میدهد نگهش دارید: مسیریابی بین سرویسها درون یک ماشین یا یک cluster، اجرای قواعدی که به وضعیت داخلی وابستهاند. وقتی خودتان را در حال حلکردن مشکلات توزیع دیدید بخش روبهعموم را واگذار کنید — یک node دوم برای redundancy، purge cache در میان ماشینها، بازدیدکنندگانی در کشوری دیگر که منتظر یک رفتوبرگشت به سمت شما هستند.
اکثر تیمها با هر دو تمام میشوند، و همین چیدمان منطقی است: یک CDN روبهروی اینترنت، و یک nginx کوچک داخلی که همان مسیریابیای را انجام میدهد که در آن خوب است. چیزی که نمیخواهید این است که یک فصل را صرف بازسازیِ بد بخشهایی کنید که یک CDN از روز اول به شما میدهد.
پرسشهای پرتکرار
آیا یک reverse proxy همان load balancer است؟
همپوشانی دارند. یک load balancer درخواستها را بین چند backend توزیع میکند؛ یک reverse proxy همچنین TLS را terminate میکند، cache میکند، بازنویسی و فیلتر میکند. nginx هر دو کار را انجام میدهد، و برای همین این اصطلاحات بهجای هم استفاده میشوند. اگر تنها کار پخشکردن ترافیک بین سرورهای یکسان است، «load balancer» کلمهٔ دقیقتری است.
آیا یک reverse proxy سایت من را کند میکند؟
یک hop اضافه میکند، که وقتی proxy نزدیک origin است در حد چند میلیثانیهٔ تکرقمی اندازهگیری میشود، و وقتی cache روشن است بسیار بیشتر از آن را حذف میکند — یک پاسخ cacheشده اصلاً به اپلیکیشن شما سفر نمیکند. موردی که آسیب میزند یک proxy در منطقهای متفاوت از هم کاربران شما و هم origin شماست.
چرا اپلیکیشن من بهجای بازدیدکننده، IP خودِ proxy را میبیند؟
چون هدرهای forward شده غایباند، یا چون اپلیکیشن برای اعتمادکردن به آنها پیکربندی نشده. هر دو نیمه لازم است: proxy باید X-Forwarded-For را بفرستد، و به framework باید گفته شود کدام آدرسهای proxy را میتواند باور کند. اعتمادکردن به هدر از هر جایی به یک کلاینت اجازه میدهد IP خودش را جعل کند.
آیا میتوانم صفحات واردشده (logged-in) را cache کنم؟
نه بهصورت پیشفرض، و معمولاً هم نباید بخواهید — این همان راهی است که یک کاربر داشبورد کاربر دیگر را میبیند. الگوی معمول این است که وقتی یک session cookie حضور دارد از cache بگذرید و برای بازدیدکنندگان ناشناس بهشدت cache کنید، که بخش اصلی ترافیک اکثر سایتهاست.
چگونه یک cache پراکسی nginx را purge کنم؟
nginx متنباز هیچ دستور purge ندارد. گزینهها ایناند: حذف فایلهای مربوطه از دایرکتوری cache روی هر node، استفاده از ماژول purge شخصثالث، یا منتظرماندن برای TTL. purge برحسب تقاضا در میان ماشینها یکی از چیزهای ملموسی است که با انتقال لایهٔ cache به یک CDN بهدست میآورید.