بسیاری از مردم فکر می کنند که کافی است برنامه را به Kubernetes منتقل کنید (چه با استفاده از Helm یا به صورت دستی) - و خوشحالی وجود خواهد داشت. اما همه چیز به این سادگی نیست.
تیم Mail.ru Cloud Solutions مقاله ای توسط مهندس DevOps جولیان گیندی ترجمه شده است. او می گوید که شرکتش در طول فرآیند مهاجرت با چه مشکلاتی روبرو شده است تا شما روی همان چنگک پا نگذارید.
مرحله اول: درخواستها و محدودیتهای پاد را تنظیم کنید
بیایید با ایجاد یک محیط تمیز که در آن غلاف های ما اجرا می شوند شروع کنیم. Kubernetes در زمانبندی و failover عالی است. اما معلوم شد که برنامهریز گاهی اوقات نمیتواند یک غلاف را قرار دهد، اگر تخمین زدن به تعداد منابع مورد نیاز برای موفقیت کار دشوار باشد. اینجاست که درخواست منابع و محدودیت ها ظاهر می شود. بحث های زیادی در مورد بهترین رویکرد برای تعیین درخواست ها و محدودیت ها وجود دارد. گاهی اوقات به نظر می رسد که این واقعاً بیشتر یک هنر است تا یک علم. رویکرد ما اینجاست.
درخواست های پاد مقدار اصلی است که توسط زمانبندی برای قرار دادن بهینه غلاف استفاده می شود.
از اسناد Kubernetes: مرحله فیلتر مجموعه ای از گره ها را تعریف می کند که در آنها می توان یک Pod را برنامه ریزی کرد. به عنوان مثال، فیلتر PodFitsResources بررسی می کند که آیا یک گره منابع کافی برای برآورده کردن درخواست های منابع خاص از یک pod را دارد یا خیر.
ما از درخواست های برنامه به گونه ای استفاده می کنیم که بتوانیم تعداد منابع را تخمین بزنیم در واقع برنامه برای عملکرد صحیح به آن نیاز دارد. به این ترتیب زمانبندی می تواند گره ها را به صورت واقع بینانه قرار دهد. در ابتدا، ما می خواستیم برای اطمینان از منابع کافی برای هر پاد، درخواست ها را بیش از حد زمان بندی کنیم، اما متوجه شدیم که زمان زمان بندی به میزان قابل توجهی افزایش یافته است و برخی از پادها به طور کامل برنامه ریزی نشده اند، گویی هیچ درخواست منبعی برای آنها وجود ندارد.
در این مورد، زمانبند اغلب پادها را «فشار» میکند و نمیتواند آنها را دوباره زمانبندی کند، زیرا صفحه کنترل نمیدانست برنامه به چه مقدار منابع نیاز دارد، که جزء کلیدی الگوریتم زمانبندی است.
محدودیت های غلاف حد واضح تری برای غلاف است. این نشان دهنده حداکثر مقدار منابعی است که خوشه به ظرف اختصاص می دهد.
باز هم از اسناد رسمی: اگر یک کانتینر دارای محدودیت حافظه 4 گیگا بایت باشد، Kubelet (و زمان اجرای کانتینر) آن را اعمال می کند. زمان اجرا مانع از استفاده بیش از حد منبع مشخص شده در کانتینر می شود. به عنوان مثال، هنگامی که یک پردازش در یک ظرف سعی می کند بیش از مقدار مجاز حافظه استفاده کند، هسته سیستم با خطای "Out of memory" (OOM) فرآیند را خاتمه می دهد.
یک کانتینر همیشه می تواند از منابع بیشتری نسبت به درخواست منبع استفاده کند، اما هرگز نمی تواند بیشتر از حد مجاز استفاده کند. تنظیم صحیح این مقدار دشوار است، اما بسیار مهم است.
در حالت ایده آل، ما می خواهیم منابع مورد نیاز یک غلاف در طول چرخه حیات یک فرآیند بدون تداخل با سایر فرآیندهای سیستم تغییر کند - این هدف از تعیین محدودیت است.
متأسفانه، من نمی توانم دستورالعمل خاصی در مورد مقادیر تعیین کنم ارائه دهم، اما ما خودمان به قوانین زیر پایبند هستیم:
با استفاده از ابزار تست بار، سطح پایه ترافیک را شبیه سازی می کنیم و استفاده از منابع پاد (حافظه و پردازنده) را مشاهده می کنیم.
درخواستهای پاد را روی مقدار دلخواه کم (با محدودیت منابع حدوداً 5 برابر مقدار درخواستها) تنظیم کنید و رعایت کنید. هنگامی که درخواست ها در سطح بسیار پایین هستند، فرآیند نمی تواند شروع شود، که اغلب باعث خطاهای مرموز زمان اجرا Go می شود.
توجه داشته باشم که محدودیتهای منابع بالاتر زمانبندی را دشوارتر میکند زیرا پاد به یک گره هدف با منابع کافی در دسترس نیاز دارد.
موقعیتی را تصور کنید که یک وب سرور سبک وزن با محدودیت منابع بسیار بالا، مانند 4 گیگابایت حافظه دارید. این فرآیند احتمالاً باید به صورت افقی کوچک شود و هر پاد جدید باید در گره ای با حداقل 4 گیگابایت حافظه در دسترس برنامه ریزی شود. اگر چنین گره ای وجود نداشته باشد، خوشه باید یک گره جدید برای پردازش این غلاف معرفی کند که ممکن است مدتی طول بکشد. برای اطمینان از مقیاسبندی سریع و روان، دستیابی به حداقل تفاوت بین درخواستهای منابع و محدودیتها مهم است.
مرحله دوم: تستهای سرزندگی و آمادگی را تنظیم کنید
این موضوع ظریف دیگری است که اغلب در جامعه Kubernetes مورد بحث قرار می گیرد. درک خوب تستهای Liveness و Readiness بسیار مهم است، زیرا این تستها مکانیزمی را برای عملکرد پایدار نرمافزار و به حداقل رساندن زمان خرابی فراهم میکنند. با این حال، اگر به درستی پیکربندی نشده باشند، می توانند بر عملکرد برنامه شما تأثیر جدی بگذارند. در زیر خلاصه ای از هر دو نمونه آورده شده است.
سرزندگی نشان می دهد که آیا ظرف در حال اجرا است. اگر شکست بخورد، kubelet ظرف را می کشد و خط مشی راه اندازی مجدد برای آن فعال می شود. اگر کانتینر مجهز به کاوشگر Liveness نباشد، وضعیت پیشفرض موفقیت آمیز خواهد بود - همانطور که در اینجا گفته شد اسناد Kubernetes.
کاوشگرهای Liveness باید ارزان باشند، یعنی منابع زیادی مصرف نکنند، زیرا به طور مکرر اجرا می شوند و باید به Kubernetes اطلاع دهند که برنامه در حال اجرا است.
اگر گزینه اجرای هر ثانیه را تنظیم کنید، در هر ثانیه 1 درخواست اضافه می شود، بنابراین توجه داشته باشید که برای پردازش این ترافیک به منابع اضافی نیاز است.
در شرکت ما، تستهای Liveness اجزای اصلی یک برنامه کاربردی را آزمایش میکنند، حتی اگر دادهها (به عنوان مثال، از یک پایگاه داده راه دور یا حافظه پنهان) به طور کامل در دسترس نباشد.
ما یک نقطه پایانی "سلامت" را در برنامهها تنظیم کردهایم که به سادگی یک کد پاسخ 200 را برمیگرداند. این نشانگر این است که فرآیند در حال اجرا است و قادر به رسیدگی به درخواستها (اما هنوز ترافیک نیست).
نمونه آمادگی نشان می دهد که آیا کانتینر برای ارائه درخواست ها آماده است یا خیر. اگر کاوشگر آمادگی از کار بیفتد، کنترلکننده نقطه پایانی آدرس IP غلاف را از نقاط پایانی همه سرویسهای منطبق با پاد حذف میکند. این نیز در اسناد Kubernetes بیان شده است.
کاوشگرهای آمادگی منابع بیشتری را مصرف می کنند، زیرا باید به گونه ای به پشتیبان ضربه بزنند تا نشان دهند که برنامه برای پذیرش درخواست ها آماده است.
بحث های زیادی در جامعه در مورد دسترسی مستقیم به پایگاه داده وجود دارد. با در نظر گرفتن سربار (بررسیها مکرر هستند، اما میتوان آنها را کنترل کرد)، تصمیم گرفتیم که برای برخی از برنامهها، آمادگی برای سرویسدهی به ترافیک تنها پس از بررسی اینکه آیا رکوردها از پایگاه داده برگردانده شدهاند، محاسبه میشود. آزمایشهای آمادگی به خوبی طراحی شده سطوح بالاتری از در دسترس بودن را تضمین میکنند و زمان توقف را در طول استقرار حذف میکنند.
اگر تصمیم دارید برای آزمایش آمادگی برنامه خود از پایگاه داده پرس و جو کنید، مطمئن شوید که تا حد امکان ارزان است. بیایید این پرس و جو را مطرح کنیم:
SELECT small_item FROM table LIMIT 1
در اینجا مثالی از نحوه پیکربندی این دو مقدار در Kubernetes آورده شده است:
initialDelaySeconds - از پرتاب کانتینر و شروع پرتاب کاوشگر چند ثانیه می گذرد.
periodSeconds - فاصله انتظار بین اجراهای نمونه.
timeoutSeconds - تعداد ثانیه هایی که پس از آن غلاف به عنوان اضطراری در نظر گرفته می شود. تایم اوت عادی
failureThreshold تعداد خطاهای تست قبل از ارسال سیگنال راه اندازی مجدد به پاد است.
successThreshold تعداد آزمایشهای موفقیتآمیز قبل از انتقال غلاف به حالت آماده (پس از خرابی هنگام راهاندازی یا بازیابی غلاف) است.
مرحله سوم: تنظیم سیاست های شبکه پیش فرض Pod
Kubernetes دارای یک توپوگرافی شبکه "مسطح" است، به طور پیش فرض همه پادها مستقیماً با یکدیگر ارتباط برقرار می کنند. در برخی موارد این مطلوب نیست.
یک مشکل امنیتی بالقوه این است که یک مهاجم می تواند از یک برنامه آسیب پذیر واحد برای ارسال ترافیک به تمام پادهای شبکه استفاده کند. مانند بسیاری از حوزه های امنیتی، اصل کمترین امتیاز در اینجا نیز اعمال می شود. در حالت ایدهآل، سیاستهای شبکه باید به صراحت بیان کنند که کدام اتصالات بین پادها مجاز هستند و کدامها مجاز نیستند.
به عنوان مثال، زیر یک سیاست ساده است که تمام ترافیک ورودی را برای یک فضای نام خاص رد می کند:
(https://miro.medium.com/max/875/1*-eiVw43azgzYzyN1th7cZg.gif)
جزئیات بیشتر اینجا.
مرحله چهارم: رفتار سفارشی با قلاب ها و کانتینرهای اولیه
یکی از اهداف اصلی ما ارائه استقرار در Kubernetes بدون خرابی برای توسعه دهندگان بود. این مشکل است زیرا گزینه های زیادی برای خاموش کردن برنامه ها و آزاد کردن منابع استفاده شده آنها وجود دارد.
مشکلات خاصی به وجود آمد Nginx. ما متوجه شدیم که هنگام استقرار این Pods به ترتیب، اتصالات فعال قبل از تکمیل موفقیت آمیز قطع می شوند.
پس از تحقیقات گسترده در اینترنت، مشخص شد که Kubernetes منتظر نمی ماند تا اتصالات Nginx قبل از خاموش کردن پاد، خود را خسته کنند. با کمک قلاب pre-stop، عملکرد زیر را اجرا کردیم و به طور کامل از خرابی خلاص شدیم:
#!/bin/bash
sleep 3
PID=$(cat /run/nginx.pid)
nginx -s quit
while [ -d /proc/$PID ]; do
echo "Waiting while shutting down nginx..."
sleep 10
done
پارادایم بسیار مفید دیگر استفاده از کانتینرهای init برای مدیریت راه اندازی برنامه های خاص است. این به ویژه در صورتی مفید است که یک فرآیند انتقال پایگاه داده با منابع فشرده داشته باشید که باید قبل از شروع برنامه اجرا شود. همچنین می توانید بدون تعیین چنین محدودیتی برای برنامه اصلی، محدودیت منابع بالاتری را برای این فرآیند تعیین کنید.
یکی دیگر از طرح های رایج دسترسی به اسرار در کانتینر init است که این اعتبارنامه ها را در اختیار ماژول اصلی قرار می دهد که از دسترسی غیرمجاز به اسرار از ماژول اصلی برنامه جلوگیری می کند.
طبق معمول، نقل قولی از مستندات: کانتینرهای init به طور ایمن کد کاربر یا برنامه های کاربردی را اجرا می کنند که در غیر این صورت امنیت تصویر کانتینر برنامه را به خطر می اندازد. با جدا نگه داشتن ابزارهای غیر ضروری، سطح حمله تصویر کانتینر برنامه را محدود می کنید.
مرحله پنجم: پیکربندی هسته
در نهایت، اجازه دهید در مورد یک تکنیک پیشرفته تر صحبت کنیم.
Kubernetes یک پلتفرم بسیار منعطف است که به شما امکان میدهد تا حجم کاری را هر طور که میخواهید اجرا کنید. ما تعدادی برنامه بسیار کارآمد داریم که منابع بسیار زیادی دارند. پس از انجام آزمایشهای بارگذاری گسترده، متوجه شدیم که یکی از برنامهها در زمانی که تنظیمات پیشفرض Kubernetes اعمال میشدند، به سختی با بار ترافیک مورد انتظار همراهی میکرد.
با این حال، Kubernetes به شما اجازه می دهد تا یک کانتینر ممتاز را اجرا کنید که فقط پارامترهای هسته را برای یک pod خاص تغییر می دهد. در اینجا چیزی است که ما برای تغییر حداکثر تعداد اتصالات باز استفاده کردیم:
این یک تکنیک پیشرفته تر است که اغلب مورد نیاز نیست. اما اگر برنامه شما برای مقابله با یک بار سنگین مشکل دارد، می توانید برخی از این تنظیمات را تغییر دهید. اطلاعات بیشتر در مورد این فرآیند و تنظیم مقادیر مختلف - مثل همیشه در اسناد رسمی.
در نتیجه
در حالی که Kubernetes ممکن است به عنوان یک راه حل خارج از جعبه به نظر برسد، چند مرحله کلیدی وجود دارد که باید برای اجرای روان برنامه ها انجام شود.
در طول مهاجرت به Kubernetes، پیروی از "چرخه تست بار" مهم است: برنامه را اجرا کنید، آن را تحت بار آزمایش کنید، معیارها و رفتار مقیاس بندی را مشاهده کنید، پیکربندی را بر اساس این داده ها تنظیم کنید، سپس این چرخه را دوباره تکرار کنید.
در مورد ترافیک مورد انتظار واقع بین باشید و سعی کنید از آن فراتر بروید تا ببینید کدام مؤلفه اول خراب می شود. با این رویکرد تکراری، تنها تعدادی از توصیه های ذکر شده ممکن است برای دستیابی به موفقیت کافی باشد. یا ممکن است نیاز به سفارشی سازی عمیق تری داشته باشد.
همیشه این سوالات را از خود بپرسید:
برنامه ها چقدر منابع مصرف می کنند و این مقدار چگونه تغییر خواهد کرد؟
الزامات واقعی مقیاس بندی چیست؟ برنامه به طور متوسط چقدر ترافیک را اداره می کند؟ اوج ترافیک چطور؟
هر چند وقت یکبار این سرویس نیاز به کوچک شدن دارد؟ برای دریافت ترافیک، پادهای جدید با چه سرعتی باید راه اندازی شوند؟
غلاف ها با چه زیبایی خاموش می شوند؟ اصلا لازمه؟ آیا امکان استقرار بدون خرابی وجود دارد؟
چگونه می توان خطرات امنیتی را به حداقل رساند و آسیب ناشی از هر غلاف در معرض خطر را محدود کرد؟ آیا هیچ سرویسی مجوز یا دسترسی دارد که نیازی به آن نداشته باشد؟
Kubernetes یک پلت فرم باورنکردنی ارائه می دهد که به شما امکان می دهد از بهترین شیوه ها برای استقرار هزاران سرویس در یک خوشه استفاده کنید. با این حال، همه برنامه ها متفاوت هستند. گاهی اوقات پیاده سازی نیاز به کمی کار بیشتر دارد.
خوشبختانه Kubernetes تنظیمات لازم برای دستیابی به تمام اهداف فنی را فراهم می کند. با استفاده از ترکیبی از درخواستها و محدودیتهای منبع، کاوشگرهای Liveness و Readiness، کانتینرهای اولیه، سیاستهای شبکه و تنظیم هسته سفارشی، میتوانید به عملکرد بالا همراه با تحمل خطا و مقیاسپذیری سریع دست پیدا کنید.