فصل ۴: ساخت کلاستر Kubernetes

۴.۱ توپولوژی کلاستر

معماری کلاستر Kubernetes این پروژه با رویکرد **High Availability** طراحی شده تا در برابر خرابی‌های محتمل، از حیث **تحمل خطا** و پایداری عملیاتی دچار اختلال نشود. در سطح کلان، کلاستر از دو بخش اصلی تشکیل شده است: صفحه کنترل (Control Plane) و نودهای کاری (Worker Nodes).

صفحه کنترل شامل سه نود Master است که به‌صورت توزیع‌شده مدیریت وضعیت کلاستر را انجام می‌دهند. این چینش سه‌نودی، الزامات Quorum در etcd را پوشش می‌دهد و بنابراین با از دسترس خارج شدن یک نود Master، تداوم سرویس‌دهی کلاستر حفظ می‌شود. هر نود Master اجزای کلیدی نظیر kube-apiserver، kube-controller-manager، kube-scheduler و etcd را میزبانی می‌کند. برای توزیع یکنواخت درخواست‌های API نیز یک Load Balancer در لایه ورودی در نظر گرفته شده است تا ترافیک بین نودهای Master متعادل شود.

نودهای Worker محل استقرار بارهای کاری (Workloads) هستند و تعداد آنها متناسب با نیاز محاسباتی پروژه قابل افزایش است. روی هر Worker مؤلفه‌هایی مانند kubelet (برای مدیریت Pod)، kube-proxy (برای وظایف شبکه) و Container Runtime (برای اجرای کانتینرها) فعال است. از منظر نقش عملیاتی، Workerها به دو گروه تقسیم شده‌اند: نودهای عمومی برای سرویس‌های پلتفرم و نودهای مجهز به GPU که اجرای Notebookهای یادگیری ماشین را بر عهده دارند.

شبکه کلاستر از CNI پشتیبانی می‌کند و ارتباط‌های Pod-to-Pod، Pod-to-Service و دسترسی خارجی را ممکن می‌سازد. برای جداسازی منطقی منابع و کنترل بهتر امنیت، Namespaceهای مختلف تعریف شده‌اند. در نهایت، این توپولوژی ضمن پاسخ‌گویی به نیازهای فنی فعلی، امکان توسعه افقی (Horizontal Scaling) را برای رشد آتی پلتفرم نیز حفظ می‌کند.

۴.۲ آماده‌سازی سیستم‌عامل: Kernel Params، Runtime، Sysctl

پیش از استقرار Kubernetes، سیستم‌عامل Ubuntu 20.04 LTS نیازمند پیکربندی‌های مشخصی است تا هم از منظر کارایی و هم از حیث ملاحظات امنیتی، بستر اجرای کلاستر پایدار باشد. این آماده‌سازی در سه محور انجام می‌شود: تنظیمات Kernel، نصب و پیکربندی Container Runtime و اعمال پارامترهای Sysctl.

در گام نخست، ماژول‌های Kernel مورد نیاز فعال می‌شوند. برای شبکه کانتینری، بارگذاری ماژول‌های `overlay` و `br_netfilter` ضروری است؛ این ماژول‌ها با تعریف در مسیر `/etc/modules-load.d/` به‌صورت پایدار پس از هر بوت در دسترس خواهند بود. از سوی دیگر، Swap باید غیرفعال شود؛ زیرا Kubernetes برای مدیریت منابع حافظه به کنترل مستقیم RAM وابسته است و فعال بودن Swap می‌تواند رفتار زمان‌بندی و تخصیص منابع را مختل کند.

پیکربندی Sysctl به‌منظور امکان‌پذیر کردن پردازش صحیح ترافیک بین Podها انجام می‌شود. به‌طور مشخص، پارامترهای `net.bridge.bridge-nf-call-iptables` و `net.bridge.bridge-nf-call-ip6tables` روی مقدار ۱ قرار می‌گیرند تا Iptables قادر به پردازش ترافیک bridged باشد. همچنین `net.ipv4.ip_forward` برای مسیریابی بسته‌ها میان کانتینرها فعال می‌شود. این تنظیمات در `/etc/sysctl.d/99-kubernetes.conf` ثبت می‌شوند تا پس از راه‌اندازی مجدد نیز حفظ شوند.

در انتخاب Container Runtime، معیار اصلی هم‌راستایی با CRI و سبک‌بودن سرویس اجرایی بوده است؛ از این رو، در این پروژه از containerd استفاده شده است. پس از نصب، فایل پیکربندی `/etc/containerd/config.toml` به‌گونه‌ای تنظیم می‌شود که systemd به‌عنوان Cgroup driver به‌کار گرفته شود. این همگرایی با systemd، مدیریت منابع را منسجم‌تر کرده و از ناسازگاری‌های ناشی از تفاوت Cgroup drivers جلوگیری می‌کند.

هم‌زمان، برخی تنظیمات امنیتی پایه نیز اعمال می‌شود: پیکربندی Firewall، غیرفعال‌سازی SELinux یا قرار دادن آن در وضعیت Permissive، و همگام‌سازی ساعت سیستم از طریق NTP به‌منظور یکسان‌سازی timestampها در سطح کلاستر.

۴.۳ مراحل نصب Kubernetes

استقرار Kubernetes با Kubespray انجام شده است؛ ابزاری مبتنی بر Ansible که فرآیند استقرار را اتوماتیک کرده و برای محیط‌های Production-Ready مناسب است. استفاده از Kubespray در مقایسه با نصب دستی مبتنی بر kubeadm، امکان تکرارپذیری، کنترل بهتر پیکربندی‌ها و کاهش خطای انسانی را فراهم می‌کند.

فرآیند با آماده‌سازی Inventory در Ansible آغاز می‌شود؛ در این فایل، نودهای Master و Worker معرفی شده و متغیرهای پیکربندی کلاستر تعریف می‌گردند. پارامترهای اصلی شامل نسخه Kubernetes، انتخاب CNI Plugin (در این پروژه Calico)، محدوده‌های Pod و Service CIDR و تنظیمات etcd است. انتخاب Calico با توجه به پشتیبانی از NetworkPolicy و عملکرد مناسب در مقیاس‌های بالاتر انجام شده است.

Kubespray وابستگی‌های ضروری را نصب می‌کند (از جمله kubeadm، kubelet، kubectl و Container Runtime) و سپس از طریق Playbookهای Ansible، ابتدا کلاستر etcd را به‌عنوان مخزن توزیع‌شده وضعیت کلاستر راه‌اندازی می‌کند. پس از آن، اجزای Control Plane روی نودهای Master مستقر می‌شوند و API Server به‌عنوان نقطه ورودی مرکزی فعال می‌گردد.

در مرحله بعد، نودهای Worker به کلاستر ملحق می‌شوند. این بخش شامل ایجاد Tokenهای امنیتی، پیکربندی kubelet روی هر Worker و ثبت نودها در API Server است. مدیریت CA و گواهی‌های TLS نیز توسط Kubespray انجام می‌شود تا ارتباط میان مؤلفه‌ها با کانال امن برقرار بماند.

پس از تکمیل نصب، اعتبارسنجی کلاستر با دستورهایی مانند `kubectl get nodes` و `kubectl get pods --all-namespaces` انجام می‌شود. انتظار می‌رود همه نودها در وضعیت Ready و Podهای سیستمی مرتبط با CoreDNS، kube-proxy و CNI در حالت Running باشند. در نهایت، دسترسی به API Server از خارج کلاستر و ارتباط میان Podها نیز بررسی می‌شود تا آمادگی کلاستر برای استقرار Workloadهای تولیدی تأیید گردد.

۴.۴ استقرار CNI و خط‌مشی‌های پایه NetworkPolicy

در کلاسترهای کانتینری، شبکه نقش زیرساختی تعیین‌کننده دارد؛ زیرا هر Pod باید IP مستقل داشته باشد و بتواند با سرویس‌ها و سایر Podها ارتباط برقرار کند. این وظیفه در Kubernetes از طریق CNI پیاده‌سازی می‌شود. در این پروژه، پس از ارزیابی گزینه‌هایی مانند Calico، Cilium و Flannel، با توجه به الزامات امنیتی و عملکردی، Calico انتخاب شد.

Calico در ابتدای راه‌اندازی کلاستر و پس از آماده‌شدن نودهای Master مستقر شد. این افزونه با فراهم‌کردن یک Overlay Network مبتنی بر BGP، ارتباط میان Podهایی را که روی نودهای مختلف قرار دارند برقرار می‌کند. مزیت مهم Calico، قابلیت‌های پیشرفته در NetworkPolicy است که امکان کنترل دقیق دسترسی شبکه در سطح Pod را فراهم می‌سازد.

پس از استقرار CNI، خط‌مشی‌های پایه امنیت شبکه به‌صورت Namespace-based اعمال شدند. سیاست پیش‌فرض در هر Namespace، مسدود بودن ترافیک ورودی است و تنها ارتباطاتی که به‌طور صریح تعریف شوند مجاز خواهند بود (Default Deny Policy). این رویکرد هم‌راستا با Zero-Trust، سطح حمله را در لایه شبکه کاهش می‌دهد.

به‌عنوان نمونه، برای Namespace مربوط به JupyterHub، NetworkPolicy به‌گونه‌ای تعریف شد که فقط Ingress Controller مجاز به دسترسی به Podهای JupyterHub باشد؛ در مقابل، Podهای Notebook کاربران امکان دسترسی کنترل‌شده به سرویس‌های مشترک مانند DNS و برخی سرویس‌های داخلی را دارند. نگهداری این سیاست‌ها به‌شکل فایل‌های YAML در مخزن GitOps انجام می‌شود و اعمال آنها نیز توسط ArgoCD صورت می‌گیرد؛ در نتیجه، نسخه‌بندی، بازبینی و ردیابی تغییرات سیاست‌های امنیتی در یک چرخه متمرکز قابل انجام است.

۴.۵ راه‌اندازی Ingress Controller و TLS Strategy

ارائه سرویس‌های مستقر روی Kubernetes به کاربران بیرونی، به لایه‌ای برای مدیریت ترافیک HTTP/HTTPS نیاز دارد؛ این وظیفه بر عهده Ingress Controller است. در معماری این پلتفرم، NGINX Ingress Controller به‌عنوان نقطه ورود اصلی انتخاب شده است؛ انتخابی که بر مبنای پایداری، کارایی و پشتیبانی مناسب از قابلیت‌هایی مانند SSL Termination و Path-based Routing انجام شد.

NGINX Ingress Controller در Namespace اختصاصی `ingress-nginx` مستقر شده است. برای تأمین دسترس‌پذیری بالا، تعداد Replicaها متناسب با ظرفیت نودهای Worker تنظیم شد و به کمک DaemonSet روی نودهای دارای Label مشخص اجرا گردید. مسیر ورود ترافیک نیز به این صورت است که درخواست‌ها ابتدا به HAProxy در لایه بیرونی کلاستر می‌رسند و سپس به NodePort سرویس Ingress Controller توزیع می‌شوند.

در لایه امنیت انتقال، مدیریت گواهی‌های TLS اهمیت ویژه دارد. در این پروژه، cert-manager برای صدور و تمدید خودکار گواهی‌ها به‌کار گرفته شده است. cert-manager با تکیه بر ACME و ارائه‌دهنده Let's Encrypt، گواهی‌های معتبر را صادر کرده و تمدید آنها را نیز بدون مداخله دستی انجام می‌دهد.

برای هر سرویس قابل دسترس از بیرون، یک منبع Ingress تعریف می‌شود که قوانین مسیریابی و تنظیمات TLS را در بر می‌گیرد. برای مثال، سرویس JupyterHub از طریق دامنه اختصاصی و با گواهی معتبر در دسترس است. cert-manager پیش از انقضای گواهی، فرآیند تمدید را آغاز کرده و گواهی جدید را در Secret مرتبط ذخیره می‌کند. نتیجه این رویکرد، کاهش ریسک خطای عملیاتی و تثبیت سطح امنیت ارتباطات است.

۴.۶ اتصال Storage به Kubernetes با استفاده از CSI

در محیط‌های Cloud-Native، بارهای کاری Stateful نیازمند ذخیره‌سازی پایدار (Persistent Storage) هستند و مدیریت این نیاز، بدون یک استاندارد یکپارچه دشوار می‌شود. CSI این استاندارد را فراهم می‌کند تا سامانه‌های ذخیره‌سازی گوناگون بتوانند به‌صورت سازگار به Kubernetes متصل شوند. با توجه به نیاز پروژه به کارایی مناسب در سناریوهای AI/ML، TopoLVM به‌عنوان CSI Driver انتخاب شد.

TopoLVM یک CSI مبتنی بر LVM (Logical Volume Manager) است که امکان استفاده مستقیم از دیسک‌های محلی نودها را فراهم می‌کند. نسبت به راهکارهای شبکه‌ای مانند NFS، استفاده از ذخیره‌سازی محلی معمولاً تأخیر (Latency) کمتری در I/O ایجاد می‌کند؛ موضوعی که برای پردازش داده و آموزش مدل‌ها اهمیت دارد. TopoLVM همچنین از Topology-Aware Scheduling پشتیبانی می‌کند؛ به این معنا که Podها بر اساس ظرفیت ذخیره‌سازی موجود، روی نود مناسب زمان‌بندی می‌شوند.

استقرار TopoLVM شامل نصب کنترلر مرکزی و اجرای DaemonSet روی نودهای Worker است. در هر نود، ابتدا یک Volume Group اختصاصی از دیسک‌های NVMe ایجاد شد و سپس TopoLVM Node Plugin بر مبنای آن پیکربندی گردید. پس از راه‌اندازی، چند StorageClass با سطوح عملکرد متفاوت تعریف شد: `topolvm-ssd` برای بارهای کاری با IOPS بالا و `topolvm-standard` برای استفاده عمومی.

Dynamic Provisioning نیز در این طراحی فعال است. کافی است برای هر سرویس، یک PersistentVolumeClaim (PVC) با StorageClass مناسب تعریف شود؛ TopoLVM به‌صورت خودکار Logical Volume را روی نود مقصد ایجاد کرده و آن را به Pod متصل می‌کند. این شیوه، مدیریت فضای ذخیره‌سازی را ساده‌تر کرده و رشد تدریجی کلاستر را بدون افزایش پیچیدگی عملیاتی پشتیبانی می‌کند.

۴.۷ فعال‌سازی GPU NVIDIA

برای پلتفرم یادگیری ماشین، دسترسی پایدار و نزدیک به توان واقعی سخت‌افزار GPU یک الزام عملیاتی است. در این پروژه، PCI Passthrough در Proxmox VE به‌کار گرفته شد تا کارت‌های گرافیک فیزیکی به‌صورت مستقیم به ماشین‌های مجازی تخصیص داده شوند. این روش، در مقایسه با مجازی‌سازی‌های متداول GPU، افت کارایی را به حداقل می‌رساند.

اجرای PCI Passthrough مستلزم فعال‌سازی IOMMU در BIOS سرورهای فیزیکی و اعمال پیکربندی ماژول‌های VFIO در هسته لینوکس است. پس از اختصاص GPUهای NVIDIA به نودهای Worker منتخب، یکپارچه‌سازی GPU با Kubernetes انجام شد. برای این منظور، NVIDIA GPU Operator به‌عنوان راهکار استاندارد و یکپارچه انتخاب گردید.

GPU Operator چرخه حیات مؤلفه‌های لازم برای بهره‌برداری از GPU در کانتینرها را مدیریت می‌کند؛ از جمله استقرار درایورهای NVIDIA، کتابخانه‌های CUDA و cuDNN، NVIDIA Container Toolkit و Device Plugin مورد نیاز برای شناسایی و زمان‌بندی منابع GPU. علاوه بر این، DCGM Exporter برای پایش شاخص‌های عملکردی GPU نصب می‌شود.

استقرار GPU Operator از طریق Helm Chart انجام شد و تنظیمات متناسب با معماری کلاستر اعمال گردید. پس از تکمیل این مرحله، کلاستر قادر به شناسایی نوع و تعداد GPUهای هر نود شد و در نتیجه Podهایی که GPU را در resource requests مشخص می‌کنند، به‌صورت خودکار روی نودهای دارای GPU زمان‌بندی می‌شوند. برای اعتبارسنجی نهایی، Podهای آزمایشی مبتنی بر CUDA اجرا شد و صحت یکپارچه‌سازی تأیید گردید.