برنامه‌ریزی تعمیرات داخلی با Cloudflare Workers

برنامه‌ریزی تعمیرات داخلی با Cloudflare Workers

نگهداری و به‌روزرسانی زیرساخت‌های ابری، به خصوص برای شرکت‌هایی مانند Cloudflare که شبکه‌ای گسترده در سطح جهانی دارند، نیازمند برنامه‌ریزی دقیق و هماهنگی است. تصور کنید بیش از ۳۳۰ مرکز داده در سراسر جهان داشته باشید؛ مدیریت دستی زمان‌بندی تعمیرات حتی بخش کوچکی از این مراکز نیز به امری غیرقابل‌اجرا تبدیل می‌شود.

در گذشته، با رشد Cloudflare، هماهنگی بین متخصصان زیرساخت و شبکه تنها از طریق نظارت انسانی امکان‌پذیر بود. دیگر نمی‌شد به سادگی امیدوار ماند که یک به‌روزرسانی نرم‌افزاری در یک منطقه خاص باعث اختلال در سرویس‌های حیاتی در مناطق دیگر نشود.

به همین دلیل، ما نیاز به یک سیستم متمرکز و خودکار داشتیم؛ نوعی «مغز» هوشمند که قادر باشد وضعیت کامل شبکه را به‌طور همزمان مشاهده کند. با بهره‌گیری از Cloudflare Workers برای ساخت این زمان‌بند (Scheduler)، توانستیم محدودیت‌های ایمنی را برنامه‌نویسی کنیم تا اطمینان حاصل شود، صرف‌نظر از سرعت پیشرفت ما، از قابلیت اطمینان سرویس‌هایی که مشتریانمان به آن وابسته هستند، محافظت می‌شود. در ادامه توضیح خواهید داد که چگونه این سیستم را طراحی و پیاده‌سازی کردیم و چه نتایجی تاکنون کسب کرده‌ایم.

بیایید یک مثال ساده را بررسی کنیم: تصور کنید یک روتر لبه (Edge Router) بخشی از مجموعه‌ای کوچک از دروازه‌های افزونگی است که در مجموع، اینترنت عمومی را به چندین مرکز داده Cloudflare در یک شهر به هم متصل می‌کنند. برای اطمینان از اینکه مراکز داده پشت این روترها به‌طور ناگهانی غیرفعال نمی‌شوند، باید همیشه حداقل یک روتر لبه فعال باشد.

یک چالش دیگر از محصول Zero Trust ما، Dedicated CDN Egress IPs (که به طور خلاصه آن را «Aegis» می‌نامیم) ناشی می‌شود. این قابلیت به مشتریان اجازه می‌دهد تا ترافیک کاربران خود را از مراکز داده خاصی هدایت کنند تا تأخیر کمتری داشته باشند. اگر همه مراکز داده انتخاب‌شده توسط یک مشتری به‌طور همزمان غیرفعال شوند، آن‌ها با تأخیر بیشتر و احتمالاً خطاهای 5xx مواجه می‌شوند.

زمان‌بند تعمیرات ما دقیقاً برای حل این نوع مشکلات طراحی شده است. این سیستم تضمین می‌کند که همیشه حداقل یک روتر لبه در یک منطقه معین فعال باشد. هنگام برنامه‌ریزی تعمیرات، امکان بررسی وجود دارد که آیا ترکیب چندین رویداد زمان‌بندی‌شده باعث غیرفعال شدن همه مراکز داده مورد استفاده مشتریان Aegis به طور همزمان می‌شود.

پیش از این سیستم، بروز چنین وقایع ناخواسته‌ای می‌توانست منجر به قطعی سرویس برای مشتریان شود. اکنون، زمان‌بند ما در صورت وجود هرگونه تداخل احتمالی، اپراتورهای داخلی را مطلع می‌کند و امکان پیشنهاد زمان جدیدی را برای جلوگیری از هم‌پوشانی با سایر رویدادهای تعمیرات مراکز داده فراهم می‌سازد.

ما این سناریوهای عملیاتی (مانند در دسترس بودن روتر لبه و قوانین مشتری) را به‌عنوان محدودیت‌های تعمیرات تعریف می‌کنیم. هر محدودیت از مجموعه پیشنهادات تعمیر، شامل مواردی مانند یک روتر شبکه یا لیستی از سرورها است. سپس تمامی رویدادهای تعمیراتی را در تقویم پیدا می‌کنیم که با بازه زمانی پیشنهادی همپوشانی دارند.

در مرحله بعد، ما API‌های محصولی را جمع‌آوری می‌کنیم (مانند لیست IP pool های مشتریان Aegis). Aegis مجموعه‌ای از محدوده‌های IP را برمی‌گرداند که نشان می‌دهد یک مشتری درخواست کرده است ترافیک خود را از مراکز داده خاصی خارج کند. به عنوان مثال، در سناریوی زیر، مراکز داده 21 و 45 با توجه به مشتری 9876 با هم مرتبط هستند؛ زیرا برای سرویس‌دهی به این مشتری نیاز است حداقل یکی از این دو مرکز داده فعال باشد. اگر سعی کنیم هر دو مرکز داده 21 و 45 را به‌طور همزمان غیرفعال کنیم، سیستم هماهنگی ما هشدار می‌دهد که این کار می‌تواند پیامدهای ناخواسته‌ای برای بار کاری آن مشتری ایجاد کند.

در ابتدا، ما یک راه حل ساده را اتخاذ کردیم: لود کردن تمامی داده‌ها در یک Worker. این شامل تمام روابط سرورها، پیکربندی‌های محصول و معیارهای سلامت محصول و زیرساخت بود تا محدودیت‌ها محاسبه شوند. حتی در مرحله اثبات مفهوم (Proof of Concept)، با خطاهای «خلاء حافظه» مواجه شدیم.

متوجه شدیم که باید هوشیارانه از محدودیت‌های پلتفرم Workers استفاده کنیم. این امر مستلزم بارگذاری تنها داده‌هایی بود که برای پردازش منطق کسب‌وکار محدودیت ضروری بودند. به عنوان مثال، هنگامی‌که درخواست تعمیر روتر در فرانکفورت آلمان دریافت می‌شود، احتمالاً اهمیتی ندارد چه اتفاقی در استرالیا رخ می‌دهد؛ زیرا هیچ هم‌پوشانی منطقه‌ای وجود ندارد. بنابراین ما باید تنها داده‌های مربوط به مراکز داده مجاور در آلمان را بارگذاری کنیم.

با بررسی محدودیت‌ها، الگوهایی ظاهر شد که نشان داد هر محدودیت اساساً شامل دو مفهوم است: اشیاء (Objects) و ارتباطات (Associations). از دیدگاه نظریه گراف، این موارد به عنوان رأس‌ها (Vertices) و لبه‌ها (Edges) شناخته می‌شوند. یک شیء می‌تواند یک روتر شبکه باشد، و یک ارتباط می‌تواند لیستی از Pool های Aegis در مرکز داده‌ای باشد که برای آنلاین بودن آن روتر نیاز است.

ما از مقاله‌ی TAO پژوهشی فیس‌بوک الهام گرفتیم تا رابط گراف را روی داده‌های محصول و زیرساخت خود ایجاد کنیم. این API شامل موارد زیر است:

  • DATACENTER_INSIDE_AEGIS_POOL: اطلاعات مربوط به pool های Aegis که یک مرکز داده در آن قرار دارد.
  • AEGIS_POOL_CONTAINS_DATACENTER: اطلاعات مراکز داده‌ای که یک pool از Aegis برای سرویس‌دهی به ترافیک نیاز دارد.

این ارتباطات، فهرست‌های وارون شده‌ی یکدیگر هستند. الگوی دسترسی دقیقاً همانند قبل است، اما پیاده‌سازی گراف اکنون کنترل بسیار بیشتری بر میزان داده‌هایی که پرس و جو می‌شود، دارد.

این رابط قدرتمند است زیرا پیاده‌سازی گراف می‌تواند عملکرد را در پشت صحنه بهبود بخشد بدون اینکه پیچیدگی منطق کسب‌وکار را افزایش دهد. این به ما اجازه می‌دهد از مقیاس‌پذیری Workers و CDN Cloudflare برای دریافت داده‌ها از سیستم‌های داخلی خود بسیار سریع استفاده کنیم.

با تغییر به پیاده‌سازی گراف جدید، درخواست‌های API هدفمندتری ارسال شدیم. حجم پاسخ‌ها یکباره 100 برابر کاهش یافت – از بارگذاری چند درخواست بزرگ به تعداد زیادی درخواست کوچک تبدیل شد.

در حالی که این مشکل بارگیری بیش از حد داده‌ها را حل می‌کند، اکنون با یک مشکل زیردرخواست (Subrequest) مواجه هستیم؛ زیرا به‌جای چند درخواست HTTP بزرگ، تعداد زیادی درخواست کوچک ارسال می‌کنیم. به‌طور مداوم محدودیت‌های زیردرخواست را نقض می‌کردیم.

برای رفع این مشکل، ما یک لایه واسط هوشمند بین پیاده‌سازی گراف و API fetch ساختیم. اگر با زبان Go آشنا باشید، احتمالاً بسته‌ی singleflight را دیده‌اید. ما از این ایده الهام گرفتیم تا قطعه اول میان‌افزار خط لوله، درخواست‌های HTTP در جریان را حذف کند؛ بنابراین همه آن‌ها منتظر یک Promise برای داده‌ها می‌مانند و از تولید درخواست‌های تکراری در همان Worker جلوگیری می‌کنند.

سپس، ما از کش Least Recently Used (LRU) سبک استفاده می‌کنیم تا درخواست‌هایی که قبلاً دیده‌ایم را به‌طور داخلی کش کنیم. پس از آن، ما از تابع matchfunction Cloudflare’s caches.default برای کش کردن تمام درخواست‌های GET در منطقه‌ای که Worker در حال اجرا است استفاده می‌کنیم.

از آنجا که ما منابع داده‌ی مختلف با ویژگی‌های عملکردی متفاوت داریم، TTL (Time To Live) را به دقت انتخاب می‌کنیم. به عنوان مثال، داده‌های بی‌درنگ فقط برای 1 دقیقه کش می‌شوند. داده‌های زیرساختی نسبتاً ثابت می‌توانند تا 1-24 ساعت کش شوند بسته به نوع داده. داده های مدیریت توان ممکن است به صورت دستی تغییر کنند و به‌ندرت در دسترس باشند؛ بنابراین می توانیم آن را برای مدت طولانی‌تری در لبه کش کنیم.

علاوه بر این، ما لایه‌های استاندارد exponential backoff, retries و jitter را نیز داریم. این کار به کاهش دفعات ناموفق fetch کمک می‌کند، جایی که یک منبع downstream ممکن است به‌طور موقت در دسترس نباشد. با عقب نشینی جزئی، شانس موفقیت‌آمیز واکشی درخواست بعدی افزایش می‌یابد.

📌 توجه: این مطلب از منابع بین‌المللی ترجمه و بازنویسی شده است.