وقتی اولین کلاستر کوبرنتیز (Kubernetes) را در محیط عملیاتی یک سرویس پرداخت مستقر کردم، تصور می‌کردم ابزاری که در محیط توسعه روان کار می‌کند، در پروداکشن هم بدون دردسر پیش می‌رود. سه هفته بعد با ترکیبی از خطاهای OOMKilled (Out Of Memory Killed) در پادهای حساس، تأخیر پنهان در CoreDNS و صورت‌حساب ماهانه چند برابر برآورد مواجه شدم؛ تجربه‌ای که برایم به یک اصل تبدیل شد: کوبرنتیزی که در Minikube یا Docker Desktop بی‌نقص کار می‌کند، در محیط تولید با ترافیک واقعی و الزامات سازمانی، موجود کاملاً متفاوتی است.

چرا کوبرنتیز در محیط توسعه با تولید تفاوت بنیادین دارد؟

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

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

دوم، داده‌های حالت‌مند (Stateful Data). پایگاه داده، صف پیام و ذخیره‌ساز اشیاء، همه در محیط عملیاتی نقش حیاتی دارند. برخلاف میکروسرویس‌های بی‌حالت (Stateless)، این نوع بار کاری نیاز به هویت پایدار، دیسک پایدار و ترتیب راه‌اندازی دقیق دارد. اگر با ذهنیت محیط توسعه به آن‌ها نگاه کنیم، فاجعه در انتظار ماست.

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

محیط توسعه به شما می‌گوید می‌تواند کار کند؛ محیط تولید می‌پرسد آیا زیر فشار واقعی هم دوام می‌آورد؟

تمایز چهارم که کمتر به آن پرداخته می‌شود، چند‌مستأجری (Multi-tenancy) است. در محیط توسعه همه اعضای تیم تقریباً همان سطح دسترسی را دارند؛ در تولید، تیم فروش نباید به Namespace تیم مالی دسترسی داشته باشد. طراحی RBAC (Role-Based Access Control) به‌گونه‌ای که هم امن باشد و هم تیم‌ها را فلج نکند، یک هنر است که با تجربه به دست می‌آید.

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

مدیریت منابع و معمای Over و Under-Provisioning

منابع در کوبرنتیز با سه عدد کلیدی کنترل می‌شوند: درخواست (Request)، محدودیت (Limit) و سهمیه (Quota). تفاوت میان این سه، همان جایی است که تیم‌های مبتدی به دام می‌افتند. Request به Scheduler می‌گوید حداقل چه مقدار CPU یا RAM برای پاد رزرو کن؛ Limit به kubelet می‌گوید حداکثر چه مقدار اجازه مصرف داری. اگر این دو عدد را با دقت انتخاب نکنید، یکی از دو سناریوی زیر رخ می‌دهد: پاد شما به دلیل OOMKilled کشته می‌شود، یا نود با ظرفیت خالی اما پادهای Pending مواجه می‌شود.

در پروژه‌ای روی یک سرویس تحلیل داده، پادهای Python که به‌طور میانگین ۳۰۰ مگابایت مصرف داشتند، با Limit برابر ۵۱۲ مگابایت تنظیم شده بودند. وقتی حجم داده ورودی ناگهان دو برابر شد، پردازش‌های موقت به دیوار ۵۱۲ مگابایت می‌خوردند و kubelet آن‌ها را می‌کشت. راه‌حل کوتاه‌مدت افزایش Limit بود؛ راه‌حل بلندمدت این بود که خود اپلیکیشن را طوری بازنویسی کنیم که داده را در جریان پردازش کند نه در حافظه. این‌جاست که تصمیم‌های فنی کوبرنتیز با تصمیم‌های معماری نرم‌افزار در هم می‌آمیزند.

طبقه‌بندی کیفیت سرویس (QoS Class) که کوبرنتیز بر پایه نسبت Request به Limit تعیین می‌کند، تأثیر مستقیمی روی رفتار eviction دارد. سه طبقه وجود دارد:

QoS Classشرطرفتار eviction
GuaranteedRequest برابر Limit برای همه کانتینرهاآخرین گزینه برای eviction
BurstableRequest کوچک‌تر از Limitاولویت متوسط
BestEffortهیچ Request یا Limit تعریف نشدهاولین قربانی فشار نود

برای بارهای حساس مانند دروازه پرداخت یا سرویس احراز هویت، همیشه Guaranteed انتخاب کنید. برای کارهای دسته‌ای و موقت، Burstable کافی است. BestEffort را تنها برای ابزارهای جانبی مانند log shipper یا metrics agent در نظر بگیرید که از دست رفتن‌شان فاجعه نیست.

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

مفهوم دومی که در تولید حیاتی است، Vertical Pod Autoscaler (VPA) است. برخلاف Horizontal Pod Autoscaler (HPA) که تعداد Replicaها را زیاد و کم می‌کند، VPA پیشنهاد می‌دهد Request و Limit را بر اساس مصرف تاریخی چه مقدار تنظیم کنید. در حالت Auto، VPA حتی می‌تواند پاد را ری‌استارت کند تا تنظیمات جدید اعمال شود؛ که در سرویس‌های حساس ممکن است ناخواسته باشد. رویکرد محافظه‌کارانه‌تر، استفاده از حالت Off است: VPA فقط پیشنهاد می‌دهد و شما دستی اعمال می‌کنید.

یک اشتباه رایج دیگر، تعریف ResourceQuota در سطح Namespace و فراموش‌کردن LimitRange است. ResourceQuota کل مصرف یک Namespace را محدود می‌کند، اما LimitRange به پادهای بدون تعریف صریح، مقادیر پیش‌فرض می‌دهد. بدون LimitRange، یک توسعه‌دهنده می‌تواند با فراموش‌کردن Limit در manifest، کلاستر را در معرض BestEffort قرار دهد. این موضوع در مدیریت درست منابع سرور که در راهنمای مدیریت سرور به آن پرداخته‌ام، اهمیت دوچندان پیدا می‌کند.

شبکه در کلاستر تولیدی: از CNI تا Service Mesh

شبکه کوبرنتیز، پیچیده‌ترین لایه این پلتفرم است. کوبرنتیز به‌تنهایی شبکه‌ای برای پادها فراهم نمی‌کند؛ این کار را افزونه CNI (Container Network Interface) انجام می‌دهد. سه انتخاب محبوب در محیط تولید وجود دارد: Calico، Cilium و Flannel. تفاوت‌های آن‌ها را می‌توان در جدول زیر خلاصه کرد:

CNIمدل پیاده‌سازینقاط قوت اصلی
FlannelOverlay با VXLANسادگی نصب و نگهداری
CalicoBGP یا IP-in-IPNetworkPolicy غنی و کارایی بالا
CiliumeBPF-basedمشاهده‌پذیری در سطح هسته، کارایی عالی

در کلاسترهای کوچک با ترافیک کم، Flannel کاملاً کافی است. اما وقتی به سیاست‌های شبکه‌ای دقیق، مشاهده‌پذیری در سطح بسته و کاهش تأخیر نیاز دارید، Calico یا Cilium انتخاب‌های حرفه‌ای‌تری هستند. Cilium به‌ویژه با استفاده از eBPF تأخیر کمتری تولید می‌کند و در محیط‌هایی که تعداد سرویس‌ها از چند صد فراتر می‌رود، تفاوت محسوسی دارد.

مشکل دومی که کمتر دیده می‌شود، تأخیر CoreDNS است. DNS در کوبرنتیز تقریباً در هر درخواست سرویس به سرویس نقش دارد و اگر تعداد رکوردها زیاد شود، پاسخ‌دهی کند می‌شود. ترفندهایی مانند NodeLocal DNSCache و کاهش ndots در resolv.conf تأثیر چشمگیری دارند. در یک کلاستر با بیش از ۳۰۰۰ سرویس، ما شاهد کاهش تأخیر DNS از ۸۰ میلی‌ثانیه به ۹ میلی‌ثانیه پس از فعال‌سازی NodeLocal DNSCache بودیم.

Service Mesh لایه‌ای است که بالاتر از شبکه پایه می‌نشیند و قابلیت‌هایی مثل mTLS، retry، circuit breaker و tracing توزیع‌شده را فراهم می‌کند. Istio و Linkerd دو نام اصلی در این حوزه‌اند. تجربه‌ام می‌گوید Service Mesh را از روز اول وارد نکنید؛ تا وقتی تعداد سرویس‌ها از ۲۰ عبور نکرده و به mTLS یا canary deployment نیازی نداشته باشید، سربار پیچیدگی‌اش بیشتر از سودش است. اما وقتی وارد شود، تفکیک مسئولیت‌های شبکه از کد اپلیکیشن را ممکن می‌کند. مفاهیم پایه این معماری را در مقاله مقایسه Docker و Kubernetes بیشتر باز کرده‌ام.

موضوع آخری که در شبکه تولیدی اهمیت دارد، هزینه LoadBalancer است. در سرویس‌های ابری، هر Service از نوع LoadBalancer معمولاً یک load balancer ابری با هزینه ماهانه ثابت ایجاد می‌کند. اگر به‌جای یک Ingress Controller مرکزی، هر سرویس را با LoadBalancer منتشر کنید، بعد از چند ماه با صورتحسابی غیرمنتظره مواجه می‌شوید. راه‌حل استاندارد، استفاده از Ingress با host یا path-based routing است.

داده‌های حالت‌مند و معمای ذخیره‌سازی پایدار

پایگاه داده در کوبرنتیز، بحثی است که تیم‌ها را به دو اردوگاه تقسیم می‌کند: کسانی که پایگاه داده را از کلاستر خارج می‌کنند و کسانی که آن را درون کلاستر می‌برند. حقیقت این است که هر دو رویکرد در جای خود درست‌اند. برای پایگاه‌داده‌های تولیدی سنگین، استفاده از سرویس ابری مدیریت‌شده (RDS، Cloud SQL) یا کلاستر خارجی، معمولاً قابل اطمینان‌تر است. اما برای پایگاه‌داده‌های سبک، Redis، صف پیام یا Elasticsearch، اجرای درون کلاستر با StatefulSet کاملاً منطقی است.

StatefulSet سه تضمین می‌دهد که Deployment نمی‌تواند: هویت شبکه‌ای پایدار (مانند pod-0.service.namespace)، ذخیره‌سازی پایدار با PersistentVolumeClaim اختصاصی، و ترتیب شروع و خاتمه (Ordinality). این ترتیب در پایگاه‌داده‌های کلاستری مانند PostgreSQL با Patroni یا MySQL با Galera حیاتی است؛ چون گره Primary باید قبل از Replicaها راه بیفتد.

StorageClass و CSI (Container Storage Interface) نقش مهمی در تجربه ذخیره‌سازی دارند. StorageClass تعریف می‌کند که PVCها با چه سیاستی ساخته شوند: SSD یا HDD، replicated یا non-replicated، با قابلیت snapshot یا بدون آن. اگر روی AWS هستید، gp3 معمولاً انتخاب متعادل بین قیمت و کارایی است؛ io2 برای بارهای حساس با IOPS بالا. اما نکته‌ای که اکثر تیم‌ها نادیده می‌گیرند، ReclaimPolicy است. با پیش‌فرض Delete، حذف PVC منجر به نابودی داده می‌شود؛ در محیط تولید همیشه Retain انتخاب کنید تا حتی در صورت اشتباه، داده قابل بازیابی باشد.

پشتیبان‌گیری از داده‌های حالت‌مند در کوبرنتیز، موضوعی است که معمولاً تا زمان فاجعه به تعویق می‌افتد. ابزارهایی مانند Velero به شما اجازه می‌دهند snapshot از PersistentVolumeها و manifestهای کوبرنتیز بگیرید و در صورت نیاز، به کلاستر دیگری بازگردانید. اما صادقانه بگویم: برای پایگاه‌داده‌های مهم، پشتیبان‌گیری در سطح اپلیکیشن (مانند pg_dump یا mysqldump از طریق CronJob) به همراه snapshot حجم، ترکیبی است که در سناریوهای واقعی قابل اطمینان‌تر عمل می‌کند. تجربه‌های مشابه در دنیای کانتینر را در مقاله یادگیری Docker با مثال‌های واقعی به تفصیل نوشته‌ام.

در کوبرنتیز تولیدی، تفاوت میان یک PVC سالم و یک فاجعه داده‌ای، اغلب فقط یک خط ReclaimPolicy است.

امنیت در عمق: از RBAC تا مدیریت Secrets

امنیت کوبرنتیز چند لایه دارد و هر لایه دشمن مخصوص خودش را می‌شناسد. لایه اول، دسترسی به API Server است. با فعال بودن RBAC، هر درخواست به API باید توسط یک ServiceAccount یا User احراز هویت شود و سپس مجوز آن بررسی شود. اگرچه RBAC در همه توزیع‌های مدرن پیش‌فرض فعال است، اما در بسیاری از کلاسترها پیکربندی آن بسیار سخاوتمندانه است: ClusterRole با مجوز * روی * که به یک ServiceAccount غیرضروری چسبیده، یک در پشتی باز است.

لایه دوم، جداسازی شبکه است. با NetworkPolicy می‌توان تعریف کرد که کدام پادها به کدام سرویس‌ها دسترسی داشته باشند. بدون NetworkPolicy، همه پادها می‌توانند همه پادهای دیگر را ببینند؛ که در محیط Multi-tenancy غیرقابل قبول است. حداقل سیاست‌ها: اجازه دادن به ترافیک خروجی فقط به DNS و سرویس‌های لازم، و بستن تمام ورودی‌های غیرضروری.

لایه سوم، Pod Security Standards (PSS) است که جانشین PodSecurityPolicy شده است. سه سطح وجود دارد: Privileged، Baseline و Restricted. سطح Restricted جلوی اجرای کانتینر با کاربر root، دسترسی به host namespace یا mount‌های خطرناک را می‌گیرد. این را در سطح Namespace با label فعال کنید تا توسعه‌دهنده‌ها نتوانند پادهای ناامن بسازند.

لایه چهارم، مدیریت Secrets است. اجرای پیش‌فرض Secrets در کوبرنتیز، ذخیره Base64 در etcd است که رمزنگاری نیست. باید در سطح API Server، Encryption at Rest را فعال کنید و در سطح اپلیکیشن، Secretها را از سرویس‌های خارجی مانند HashiCorp Vault، AWS Secrets Manager یا Sealed Secrets از Bitnami تزریق کنید. تجربه‌های امنیتی لایه‌های سرور را در راهنمای افزایش امنیت سرور به‌تفصیل آورده‌ام و توصیه می‌کنم آن را در کنار این بخش بخوانید.

لایه پنجم، اسکن تصاویر کانتینر است. Trivy، Grype و Clair ابزارهایی هستند که آسیب‌پذیری‌های شناخته‌شده را در لایه‌های ایمیج پیدا می‌کنند. این اسکن باید در مرحله CI انجام شود نه در کلاستر، چرا که اجرای آن در محیط عملیاتی بار اضافه تولید می‌کند. همچنین اجرای ایمیج با تگ latest را به‌کل ممنوع کنید؛ تگ باید immutable باشد (مانند sha256 یا نسخه دقیق) تا rollback واقعی ممکن باشد. رویکردهای امنیتی در DevOps را در فرهنگ و فرآیند DevOps باز کرده‌ام.

پایش، لاگ و مشاهده‌پذیری در مقیاس

مشاهده‌پذیری در کوبرنتیز تنها جمع‌آوری متریک نیست؛ یک زنجیره کامل شامل متریک، لاگ و trace است. کلاستر بدون مشاهده‌پذیری، یک جعبه سیاه است که تنها وقتی سرش را بالا می‌آورد که سیستم قبلاً شکسته است.

در لایه متریک، Prometheus استاندارد تقریباً پذیرفته‌شده است. اما جمع‌آوری متریک از هر پاد، با افزایش مقیاس، خودش منبع فشار روی API Server می‌شود. راه‌حل استاندارد، استفاده از Prometheus Operator و ServiceMonitorهای هدف‌دار است. برای کلاسترهای بسیار بزرگ، Thanos یا VictoriaMetrics راهکارهایی برای ذخیره‌سازی بلندمدت و federation هستند.

در لایه لاگ، مدیریت متمرکز ضروری است. پادها مداوم متولد و کشته می‌شوند و لاگ‌شان به‌سرعت نابود می‌شود. جمع‌آوری لاگ با Fluent Bit و ارسال به Loki یا Elasticsearch، مسیر استاندارد است. نکته‌ای که در پروژه‌ها فراموش می‌شود، فیلتر سطح لاگ (log level) است: لاگ DEBUG در محیط تولید، هم فضای ذخیره‌سازی را می‌بلعد و هم هزینه انتقال را بالا می‌برد.

در لایه trace، استاندارد OpenTelemetry به‌سرعت جای خود را باز کرده است. تفاوت trace با متریک این است که trace داستان یک درخواست را در سراسر میکروسرویس‌ها روایت می‌کند. اگر یک درخواست از گیت‌وی به سرویس احراز هویت، سپس به سرویس سفارش و در نهایت به پایگاه داده می‌رود، trace نشان می‌دهد کدام گام کندتر است. اما اجرای trace بدون نمونه‌گیری هوشمند (sampling) خودش بار اضافه تولید می‌کند.

مفهومی که کمتر به آن توجه می‌شود، خستگی هشدار (Alert Fatigue) است. اگر همه چیز به هشدار تبدیل شود، تیم فنی در نهایت هشدارها را نادیده می‌گیرد. قاعده‌ای که در تیم‌ها اعمال می‌کنم: هر هشدار باید به یک runbook مشخص وصل باشد و هر کسی که هشدار را می‌گیرد باید بداند دقیقاً چه اقدامی انجام دهد. اگر نمی‌دانید با هشدار چه کنید، آن هشدار را حذف کنید تا بعداً با راه‌حل مناسب برگردانید. ابزارهای پایش سرور در مقایسه ابزارهای مانیتورینگ سرور آمده است.

یکپارچگی با CI/CD و رویکرد GitOps

استقرار در کوبرنتیز تنها با kubectl apply انجام نمی‌شود. کلاستر تولیدی نیاز به یک خط لوله منظم دارد که از کد منبع تا پاد در حال اجرا، همه چیز قابل ردیابی و قابل بازگشت باشد. دو رویکرد اصلی وجود دارد: push-based و pull-based که به‌عنوان GitOps شناخته می‌شود.

در رویکرد push-based، خط لوله CI (مانند GitHub Actions یا GitLab CI) بعد از build ایمیج، با استفاده از kubectl یا helm، manifestها را به کلاستر اعمال می‌کند. ساده است اما نقص دارد: خط لوله نیاز به دسترسی نوشتن به کلاستر دارد و در صورت نفوذ به CI، مهاجم می‌تواند مستقیماً در کلاستر تغییر ایجاد کند.

در رویکرد GitOps، ابزارهایی مانند Argo CD یا Flux در کلاستر مستقر می‌شوند و خودشان مخزن Git را رصد می‌کنند. هر تغییری که در Git اعمال شود، به‌طور خودکار در کلاستر هم اعمال می‌شود. مزیت اصلی این است که Git منبع حقیقت واحد (Single Source of Truth) است و هر تغییر قابل audit است. همچنین در صورت خرابی کلاستر، بازگردانی از Git بسیار ساده‌تر است. جزئیات بیشتر این الگو را در تحول CI/CD در تحویل نرم‌افزار نوشته‌ام.

انتخاب میان Helm و Kustomize نیز موضوعی است که تیم‌ها را دو دسته می‌کند. Helm با template و values کار می‌کند و برای بسته‌بندی نرم‌افزار برای استفاده‌کنندگان خارجی مناسب‌تر است. Kustomize با overlay و base کار می‌کند و برای محیط‌های multi-tenant که یک base مشترک با تفاوت‌های محیطی نیاز دارند، طبیعی‌تر است. در پروژه‌های شخصی، ترکیب هر دو هم رایج است: Helm برای بسته‌های شخص ثالث، Kustomize برای سرویس‌های درون‌سازمانی.

یکپارچگی با کلاستر تنها محدود به استقرار نیست. Rollback، Canary و Blue-Green هم بخشی از خط لوله هستند. Argo Rollouts و Flagger ابزارهایی هستند که این الگوها را در بالای کوبرنتیز ممکن می‌کنند. بدون آن‌ها، هر استقرار یک ریسک قمار است. حتی در پروژه‌های کوچک، راه‌اندازی یک CI/CD ساده که در راه‌اندازی CI/CD برای پروژه‌های کوچک توضیح داده‌ام، از استقرار دستی قابل اطمینان‌تر است.

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

مدیریت هزینه در کلاسترهای پرهزینه

هزینه کوبرنتیز در ابر، موضوعی است که تیم‌ها معمولاً بعد از دیدن اولین صورتحساب جدی می‌گیرند. سه منبع اصلی هزینه عبارتند از: نودها (کامپیوت)، ذخیره‌سازی (PVها) و ترافیک خروجی (Data Transfer). تمرکز روی بهینه‌سازی کامپیوت بدون توجه به دو مورد دیگر، نصف راه است.

اولین قدم، انتخاب درست نوع نمونه (Instance Type) است. نمونه‌های General Purpose مانند m5 در AWS برای اکثر بارها متعادل‌اند؛ اما برای بارهای CPU-bound مانند پردازش ویدئو، نمونه‌های Compute Optimized (c5) و برای بارهای حافظه‌محور مانند Elasticsearch، نمونه‌های Memory Optimized (r5) منطقی‌تر هستند. انتخاب اشتباه، معادل پرداخت هزینه CPU گران برای بار حافظه‌محور است.

دومین ابزار، استفاده از Spot Instance یا Preemptible VM است. نمونه‌های Spot تا ۷۰ درصد ارزان‌ترند اما می‌توانند هر لحظه بازیابی شوند. برای بارهای تحمل‌کننده قطعی مانند batch job، CI runner یا بارهای stateless با تعداد Replica بالا، Spot ترکیبی عالی از قیمت و عملکرد است. ترکیب Spot با On-Demand با نسبت‌های مناسب، به‌عنوان Spot Fleet یا با Karpenter، در پروژه‌ها صرفه‌جویی ۴۰ تا ۶۰ درصدی در هزینه کامپیوت به همراه داشته است.

سومین ابزار، Cluster Autoscaler یا Karpenter برای مقیاس‌دهی خودکار نود است. اگر شب‌ها ترافیک کم است، چرا نودها بیدار بمانند؟ تفاوت Cluster Autoscaler و Karpenter در این است که اولی روی Node Groupهای از پیش تعریف‌شده کار می‌کند و دومی نودهای دقیقاً مطابق نیاز را می‌سازد. Karpenter در AWS بسیار قدرتمندتر است اما vendor-specific است.

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

به‌روزرسانی کلاستر بدون قطعی سرویس

ارتقای کوبرنتیز یکی از دلهره‌آورترین کارها برای تیم‌های عملیاتی است، اما با انضباط می‌توان آن را بی‌دردسر انجام داد. سه نوع ارتقا وجود دارد: patch، minor و major. ارتقای patch معمولاً بدون تغییر رفتاری است و می‌توان آن را در هر زمان انجام داد. ارتقای minor (مثلاً از 1.26 به 1.27) شامل deprecation APIهاست و نیاز به بررسی manifestها دارد. ارتقای major (وقتی از 1.27 به 1.28 می‌رویم که گاهی major خوانده می‌شود) می‌تواند APIهایی را حذف کند و باید با احتیاط انجام شود.

قبل از هر ارتقا، ابزاری مانند kubent یا pluto را اجرا کنید تا مشخص شود کدام manifestها از APIهای deprecated استفاده می‌کنند. این ابزارها با بررسی کلاستر، فهرست دقیقی از منابعی که در نسخه بعدی پشتیبانی نمی‌شوند می‌دهند. این کار را می‌توان به‌عنوان بخشی از خط لوله CI نیز خودکار کرد.

مرحله دوم، ارتقای Control Plane است. در کلاسترهای مدیریت‌شده ابری، این کار با یک کلیک انجام می‌شود اما همچنان نیاز به دقت دارد. در کلاسترهای خودگردان، باید یک نود در هر زمان ارتقا داده شود تا etcd کورم (Quorum) از دست نرود.

مرحله سوم، ارتقای Worker Nodeهاست. استراتژی استاندارد این است که یک Node Pool جدید با نسخه جدید بسازید، نودهای قدیمی را cordon و drain کنید تا پادها به نودهای جدید منتقل شوند و سپس نودهای قدیمی را خاتمه دهید. برای این که این جابجایی بدون قطعی سرویس انجام شود، PodDisruptionBudget (PDB) ضروری است.

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

مقیاس‌پذیری هوشمند در ترافیک انفجاری

مقیاس‌پذیری کوبرنتیز چند لایه دارد و انتخاب اشتباه در هر لایه، مانع از کارایی لایه‌های دیگر می‌شود. سه لایه اصلی عبارتند از: مقیاس‌دهی پاد (HPA)، مقیاس‌دهی نود (Cluster Autoscaler) و مقیاس‌دهی رویداد‌محور (KEDA).

HPA بر اساس متریک‌هایی مانند CPU، حافظه یا متریک‌های سفارشی، تعداد Replicaهای یک Deployment یا StatefulSet را زیاد یا کم می‌کند. اما HPA بدون resource request درست، کار نمی‌کند؛ چرا که درصد مصرف نسبت به request محاسبه می‌شود. نکته‌ای که اکثر تیم‌ها از آن غافلند این است که HPA با پادهای کشته شده به دلیل فشار نود هماهنگ نیست؛ اگر Cluster Autoscaler نتواند به‌سرعت نود جدید بسازد، پادهای جدید در وضعیت Pending باقی می‌مانند.

Cluster Autoscaler نیز شرایط خودش را دارد. وقتی نودهای جدید ساخته می‌شود، به زمان نیاز است تا آماده شوند و به کلاستر بپیوندند. اگر در این فاصله ترافیک بالا برود، کاربران تأخیر را تجربه می‌کنند. ترفند در تولید، حفظ یک نود بافر یا استفاده از Overprovisioning است: چند پاد با اولویت پایین که فضای نود را رزرو می‌کنند و با آمدن بار جدید، جای خود را به پادهای واقعی می‌دهند.

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

در نهایت، بدون تست بار (Load Testing)، هیچ تنظیم مقیاس‌دهی معتبری وجود ندارد. k6، Locust یا Gatling ابزارهایی هستند که می‌توانند ترافیک واقعی را شبیه‌سازی کنند. تست بار باید بخشی از خط لوله پیش از هر انتشار بزرگ باشد، نه یک فعالیت یک‌باره. بدون آن، تمام تنظیمات HPA و Cluster Autoscaler حدس و گمان هستند. جزئیات بیشتر درباره ظرفیت‌سنجی سرور در شناخت سرور و نحوه کارکرد آمده است.

اشتباهات رایجی که تیم‌های فنی را زمین می‌زند

پس از سال‌ها کار با کلاسترهای تولیدی، الگوهای تکراری‌ای از شکست را دیده‌ام. این‌ها اشتباهاتی نیستند که از ناآگاهی بیاید؛ از عادت‌هایی می‌آیند که در محیط توسعه کار کرده‌اند و در تولید به دام می‌افتند.

اشتباه اول، نبود resource request و limit روی همه پادها. تیم‌ها معمولاً فقط روی سرویس‌های مهم آن را تنظیم می‌کنند و بقیه را BestEffort رها می‌کنند. وقتی فشار نود بالا می‌رود، پادهای BestEffort اول کشته می‌شوند؛ اگر یکی از آن‌ها تصادفاً یک سرویس حیاتی باشد، فاجعه است. قاعده: هر پاد باید حداقل request داشته باشد.

اشتباه دوم، نبود PodDisruptionBudget. بدون PDB، یک drain یا ارتقای خودکار کلاستر می‌تواند همه Replicaهای یک سرویس را به‌طور همزمان خاتمه دهد. نتیجه‌اش چند دقیقه قطعی سرویس است که به‌سادگی با یک PDB ساده قابل جلوگیری بود.

اشتباه سوم، استفاده از ذخیره‌سازی پیش‌فرض بدون بررسی ReclaimPolicy. در محیط‌های ابری مدیریت‌شده، StorageClass پیش‌فرض معمولاً Delete است. اگر یک PVC با اشتباه حذف شود، داده هم می‌رود. در محیط تولید، StorageClass پیش‌فرض شما باید Retain باشد.

اشتباه چهارم، نداشتن رویه chaos engineering. تیم‌ها معمولاً فرض می‌کنند سیستم در شرایط عادی کار می‌کند و همین کافی است. اما سیستم‌های توزیع‌شده در شرایط غیرعادی تست می‌شوند. ابزارهایی مانند LitmusChaos یا Chaos Mesh به شما اجازه می‌دهند خرابی‌های شبیه‌سازی‌شده تولید کنید: کشتن یک پاد تصادفی، تأخیر در شبکه، پر کردن دیسک. آن‌چه در محیط chaos نمی‌شکند، در محیط تولید هم معمولاً سالم می‌ماند. تجربه‌های مشابه در انتخاب پلتفرم ابری مناسب در Microsoft Azure در کسب‌وکار نیز قابل مشاهده است.

اشتباه پنجم، نداشتن runbook برای خطاهای شناخته‌شده. وقتی کلاستر ساعت سه بامداد خطا می‌دهد، کسی که در شیفت است باید بداند دقیقاً چه باید بکند. Runbook باید شامل چه متریک‌هایی را نگاه کند، چه دستوراتی را اجرا کند و در چه شرایطی escalate کند. تیم‌هایی که این را جدی می‌گیرند، MTTR (Mean Time To Recovery) خود را به‌طور محسوس کاهش می‌دهند.

پاسخ به پرسش‌های پرتکرار درباره کوبرنتیز تولیدی

پرسش اول: آیا باید از همان ابتدا کوبرنتیز را برای پروژه‌های کوچک انتخاب کنم؟ پاسخ کوتاه، نه. اگر پروژه شما در حد یک اپلیکیشن چندسرویسه با ترافیک متوسط است، Docker Compose یا یک سرور VPS ساده ممکن است کافی و ارزان‌تر باشد. کوبرنتیز زمانی توجیه دارد که تعداد سرویس‌ها از ده عبور کرده، نیاز به مقیاس خودکار دارید یا تیم شما به تجربه عملیاتی کافی رسیده است.

پرسش دوم: آیا پایگاه داده را درون کلاستر بگذارم یا بیرون؟ پاسخ به اندازه داده و ترافیک بستگی دارد. برای پایگاه‌داده‌های تولیدی با داده‌های مهم، سرویس ابری مدیریت‌شده انتخاب امن‌تری است. برای Redis، صف پیام یا پایگاه‌داده‌های سبک، اجرای درون کلاستر با StatefulSet و PVC مناسب، منطقی است.

پرسش سوم: آیا باید از Service Mesh استفاده کنم؟ در تیم‌هایی که تعداد سرویس‌ها زیر ۲۰ است و نیاز به mTLS یا circuit breaker ندارند، استفاده از Service Mesh بیشتر از این که کمک کند، پیچیدگی اضافه می‌کند. اما وقتی تعداد سرویس‌ها بالا رفت و نیاز به مشاهده‌پذیری دقیق یا سیاست‌های شبکه پیشرفته پیدا شد، Service Mesh ابزار درستی است.

پرسش چهارم: چگونه از manifestها در برابر فراموشی محافظت کنم؟ استفاده از GitOps با Argo CD یا Flux بهترین رویکرد است. Git منبع حقیقت واحد می‌شود و هر تغییری رد قابل audit دارد. همچنین اجرای ابزارهایی مانند kubeval یا kubeconform در CI، از اعمال manifestهای نامعتبر جلوگیری می‌کند.

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

پرسش ششم: چگونه از هزینه‌های غیرمنتظره در کوبرنتیز جلوگیری کنم؟ سه اصل: اول، budget alert در سطح ابری فعال کنید تا قبل از عبور از حد مطلوب هشدار بگیرید. دوم، از Karpenter یا Cluster Autoscaler برای حذف نودهای بلااستفاده استفاده کنید. سوم، هزینه را در سطح Namespace اندازه‌گیری کنید تا هر تیم مسئول هزینه‌های خودش باشد.

نگاه نهایی به بلوغ کوبرنتیز در محیط تولید

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

پنج درس که در پایان باید برجسته کنم: اول، طراحی منابع را جدی بگیرید؛ Request و Limit یک جزئیات فنی نیستند، یک تصمیم معماری هستند. دوم، امنیت را از روز اول بسازید، نه به‌عنوان لایه اضافه‌شده بعدی. سوم، مشاهده‌پذیری را قبل از حادثه فعال کنید، نه بعد از آن. چهارم، از ابزار GitOps برای قابل پیش‌بینی کردن استقرار استفاده کنید. پنجم، هزینه را به‌عنوان یک متریک مهندسی ببینید، نه هزینه‌ای که حسابدار رسیدگی می‌کند.

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

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