چرا این موضوع مهم است؟
Reverse proxy بین client و upstream قرار میگیرد و میتواند TLS termination، routing، buffering و header transformation انجام دهد. همین نقش آن را به مرز اعتماد تبدیل میکند؛ application باید بداند کدام header را از چه proxyای باور کند.
در یک سیستم واقعی، قبل از انتخاب ابزار باید مدل ذهنی درستی از لایهها داشته باشید. هر بار که یک مشکل را فقط با یک command یا تنظیم حفظی حل میکنیم، دانش به نسخه و محیط خاص گره میخورد. روش بهتر این است که ورودی، state، policy، مسیر داده و خروجی را مشخص کنیم. بعد ابزار را برای مشاهدهٔ یکی از این لایهها انتخاب کنیم. این رویکرد باعث میشود وقتی نسخه یا توزیع عوض شد، هنوز بدانیم دنبال چه evidenceای هستیم.
مدل معماری
در موضوع طراحی Reverse Proxy production با NGINX، componentها را روی یک مسیر ساده بکشید و مرز مسئولیت هرکدام را مشخص کنید. state پایدار، state runtime، dependency بیرونی و نقطهٔ اعمال policy را جدا کنید. وقتی diagram ساده دارید، خطا از یک «سیستم بزرگ و مبهم» به چند مرحلهٔ قابل آزمون تبدیل میشود.
روش عملی کار
Host، X-Forwarded-For/Proto و timeoutها را آگاهانه تنظیم کنید. health check و upstream failure را از status code application جدا ببینید. log request ID در هر دو لایه correlation را آسان میکند.
برای Production، تغییر باید قابل اندازهگیری و قابل برگشت باشد. baseline قبل از تغییر شامل نسخه، config مؤثر، log سالم، metric اصلی و یک تست end-to-end است. rollout را با canary شروع کنید؛ بعد از هر مرحله هم تست مثبت و هم تست منفی داشته باشید. اگر success criterion از قبل نوشته نشده باشد، بعد از اجرا بهسادگی هر نتیجهای را میتوان «موفق» تفسیر کرد. rollback هم باید تمرین شود، نه اینکه فقط یک دستور روی کاغذ باشد.
از دید امنیت
اعتماد مستقیم به X-Forwarded-For ورودی از اینترنت میتواند IP spoofing منطقی ایجاد کند. timeout خیلی بلند resource را نگه میدارد و خیلی کوتاه workload سالم را قطع میکند.
از دید امنیت، کمکردن سطح دسترسی مهم است اما availability و قابلیت عیبیابی هم جزو امنیت عملیاتیاند. secretها، tokenها، capabilityها، network path و logهای حساس را جدا بررسی کنید. هر exception باید owner، دلیل و تاریخ بازبینی داشته باشد. کنترلهایی که هیچکس نمیتواند اثرشان را مشاهده یا علتشان را توضیح دهد، در زمان incident به مانع تبدیل میشوند.
آزمایشگاه پیشنهادی
دو upstream بسازید، NGINX را جلوی آنها قرار دهید، headerها و timeout را تست کنید و یک upstream را عمداً خاموش کنید. رفتار error و logها را ثبت کنید.
برای هر مرحله timestamp و خروجی را ذخیره کنید. یک failure کنترلشده ایجاد کنید تا مطمئن شوید monitoring واقعاً failure را میبیند. سپس سیستم را به baseline برگردانید و زمان recovery را ثبت کنید.
عیبیابی بر پایهٔ Evidence
Troubleshooting را با تغییر config شروع نکنید. اول محل دقیق failure را پیدا کنید: آیا ورودی وارد سیستم میشود؟ آیا state مورد انتظار ساخته شده؟ آیا policy اجازه میدهد؟ آیا خروجی از component خارج میشود؟ log، packet capture، metric و state API باید به یک timeline مشترک وصل شوند. نبودن event نیز evidence است؛ اگر request قبل از یک component دیده میشود و بعد از آن نه، دامنهٔ مسئله کوچک شده است.
Performance و ظرفیت
عملکرد را فقط با average نسنجید. p95/p99 latency، saturation، queue depth، retry و resource pressure تصویر بهتری میدهند. تغییری که در lab کوچک سریع است ممکن است زیر concurrency یا dataset واقعی bottleneck متفاوتی نشان دهد. capacity limitها را قبل از rollout بزرگ شناسایی کنید و alert را به thresholdهای قابل اقدام وصل کنید.
Production readiness
قبل از rollout نهایی، owner، change window، blast radius، success metric و rollback trigger باید مشخص باشند. اگر dependency خارجی دارید، رفتار هنگام timeout یا unavailable شدن آن را تست کنید. اگر config به چند host میرود، rollout مرحلهای و inventory دقیق لازم است. یک تغییر خوب باید observable، explainable و reversible باشد.
Runbook و انتقال دانش
در مستندسازی، فقط «چه دستورهایی اجرا شد» را ننویسید. سؤال، فرضیه، evidence مورد انتظار، نتیجه و دلیل تصمیم را ثبت کنید. اگر همکار دیگری بتواند با همان ورودی آزمایش را تکرار کند و به نتیجهٔ مشابه برسد، runbook ارزش عملیاتی دارد. اگر فقط نویسنده میداند چرا مرحلهای لازم است، دانش هنوز منتقل نشده است.
قبل از Production چه چیزی را اعتبارسنجی کنیم؟
نتیجهٔ Lab را مستقیم به Production تعمیم ندهید. یک change plan کوتاه بسازید که در آن دامنهٔ تغییر، owner، زمان اجرا، dependencyها، success metric و rollback trigger مشخص باشد. ابتدا روی canary یا درصد کوچکی از workload اجرا کنید و حداقل یک چرخهٔ واقعی restart، credential rotation یا deploy دوم را ببینید؛ بسیاری از خطاها در اولین اجرای سالم ظاهر نمیشوند. رفتار هنگام unavailable شدن dependency خارجی، پر شدن disk، timeout شبکه و ورودی نامعتبر را نیز بررسی کنید. اگر feature امنیتی است، test منفی باید نشان دهد مسیر غیرمجاز واقعاً بسته است؛ اگر feature performance است، benchmark باید با load و dataset نزدیک به واقعیت اجرا شود. در پایان، تصمیم rollout را با evidence ثبت کنید، نه با جملهٔ مبهم «بهنظر خوب است».
نگهداری بلندمدت و بازبینی دورهای
معماری بعد از deploy ثابت نمیماند. نسخهٔ سیستمعامل، dependency، policy سازمان، certificate، endpoint و حجم workload تغییر میکنند. برای همین باید بازبینی دورهای تعریف شود: نسخههای پشتیبانینشده را پیدا کنید، exceptionهای قدیمی را ببندید، alertهای بیارزش را حذف کنید و recovery را دوباره تمرین کنید. اگر configuration در Git نگهداری میشود، drift میان state واقعی و repository را اندازه بگیرید. اگر ابزار SaaS یا سرویس خارجی است، تغییر API، pricing، limit و مدل permission را دنبال کنید. مستندات باید تاریخ review داشته باشند تا تیم بداند کدام فرضها هنوز معتبرند. نگهداری خوب یعنی سیستم بعد از شش ماه هم قابل توضیح و قابل بازیابی باشد، نه اینکه فقط روز اول deploy موفق بوده باشد.
جمعبندی
هدف این مقاله دربارهٔ طراحی Reverse Proxy production با NGINX ارائهٔ یک دستور جادویی نیست؛ هدف ساختن مدل ذهنی است. وقتی معماری را بفهمید، baseline داشته باشید و تغییر را با evidence کوچک کنید، ابزارها قابل تعویض میشوند اما روش حل مسئله باقی میماند. منبع اصلی این مقاله از NGINX انتخاب شده تا جزئیات فنی قابل پیگیری باشند.
چکلیست سریع
- مسئله را در یک جمله تعریف کن.
- component و مرز اعتماد را مشخص کن.
- baseline را قبل از تغییر ذخیره کن.
- یک test مثبت و یک test منفی بساز.
- تغییر را با canary شروع کن.
- evidence را timestampدار نگه دار.
- rollback را واقعاً تمرین کن.
- نتیجه و exceptionها را در runbook ثبت کن.
مسیر درخواست را با یک مثال واقعی بررسی کنیم
فرض کنید کاربران نشانی عمومی فروشگاه را باز میکنند، اما برنامه روی یک پورت داخلی پاسخ میدهد. NGINX اتصال کاربر را میپذیرد و اتصال جداگانهای به برنامه میسازد. این دو اتصال لزوماً نسخه HTTP، رمزنگاری یا عمر یکسان ندارند. اگر کاربر از HTTPS استفاده کرده ولی برنامه درخواست را با HTTP دیده باشد، ساختن لینک بازگشت بر اساس اتصال داخلی ممکن است کاربر را به نشانی ناامن یا حلقه تغییر مسیر بفرستد. راهحل، توافق روشن میان پراکسی و برنامه درباره طرح اصلی درخواست و اعتماد فقط به پراکسی شناختهشده است.
برای آزمایش، یک endpoint تشخیصی موقت در محیط غیرعمومی بسازید که host و scheme تشخیصدادهشده را نشان دهد. همان درخواست را مستقیم و سپس از مسیر پراکسی ارسال کنید و تفاوت را ببینید. اطلاعات محرمانه یا همه headerها را در پاسخ عمومی منتشر نکنید. اگر چند پراکسی دارید، باید مشخص باشد هر کدام کدام بخش زنجیره آدرسها را اضافه یا بازنویسی میکند. برداشتن اولین مقدار یک header ورودی بدون بررسی منبع، آدرس واقعی کاربر را اثبات نمیکند.
اسلش پایانی و معنای مسیر
یکی از خطاهای رایج در تنظیم location، تصور یکسان بودن همه شکلهای proxy_pass است. وجود بخش URI در مقصد میتواند نحوه جایگزینی بخش منطبقشده مسیر را تغییر دهد. برای نمونه مسیر عمومی دارای پیشوند خدمات ممکن است در برنامه بدون آن پیشوند تعریف شده باشد. پیش از تغییر، مجموعهای از مسیرهای ریشه، زیرمسیر، query string و مسیر دارای نویسه کدگذاریشده تهیه کنید. پاسخ هر کدام باید با قرارداد برنامه تطبیق داده شود. درست کار کردن صفحه نخست به تنهایی صحت مسیر فایلهای استاتیک، callback ورود و API را ثابت نمیکند.
زمانسنجی و buffering را از هم جدا کنید
Timeout اتصال مربوط به برقراری ارتباط با upstream است، در حالی که خواندن پاسخ مرحله دیگری است. خطای اتصال سریع با درخواست کندی که برنامه در حال پردازش آن است علت مشترک ندارد. در گزارش دسترسی، زمان کل درخواست و زمان upstream را کنار وضعیت پاسخ ذخیره کنید. اگر بخش عمده تأخیر پیش از رسیدن به برنامه باشد، بزرگ کردن timeout برنامه درمان مناسبی نیست. اگر برنامه دیر پاسخ میدهد، باید صف، پایگاه داده و وابستگی آن را نیز بررسی کرد.
Buffering اجازه میدهد پراکسی پاسخ upstream را دریافت و با سرعت متفاوت به کاربر منتقل کند. این کار میتواند برنامه را از مشتری کند جدا کند، ولی برای جریان زنده رویدادها باید رفتار ارسال تدریجی بررسی شود. غیرفعال کردن buffering برای همه مسیرها بدون نیاز، الگوی مصرف اتصال و حافظه را تغییر میدهد. یک مسیر بارگیری فایل و یک مسیر رویداد زنده را جدا آزمایش کنید؛ پاسخ کامل فایل و رسیدن سریع نخستین رویداد دو معیار متفاوت هستند. همچنین محدودیت اندازه بدنه درخواست را با کاربرد واقعی بارگذاری فایل هماهنگ کنید.
تشخیص خطا بدون پنهان کردن علت
وقتی خطای دروازه میبینید، ابتدا بررسی کنید آیا برنامه درخواست را ثبت کرده است. نبودن درخواست در برنامه میتواند به نامگشایی، پورت، سیاست شبکه یا برقراری TLS داخلی مربوط باشد. وجود درخواست با پردازش طولانی توجه را به برنامه و timeout میبرد. تغییر پاسخ خطا به صفحه زیبا نباید وضعیت واقعی HTTP را به موفقیت تبدیل کند؛ ابزارهای پایش و مشتریان برنامه به این وضعیت وابستهاند. پس از اصلاح، پیکربندی را از نظر نحو بررسی و یک درخواست واقعی از بیرون شبکه ارسال کنید تا هم مسیر عمومی و هم backend سنجیده شوند.