تو قسمت چهاردهم از مسیر بلاگ پستهای کوبرنتیز، میریم سراغ مفاهیم مالتی تننسی تا بررسی کنیم که اگر بخواهیم چند تیم یا مشتری رو روی کلاستر داشته باشیم با چه مواردی روبرو هستیم.
در دنیای مدیریت کانتینرها، یکی از رویکردهای مهم و رایج، به اشتراکگذاری کلاستر کوبرنتیز بین چندین تیم یا چندین مشتری است. این رویکرد که تحت عنوان چند-مستاجری (Multi-Tenancy) شناخته میشود، مزایایی همچون صرفهجویی در هزینهها، سادهسازی مدیریت و بهرهبرداری بهتر از منابع را به همراه دارد. با این حال، چند-مستاجری چالشهایی نیز به همراه دارد؛ از جمله مسائل امنیتی، تضمین عدالت در توزیع منابع و مدیریت مشکلات ناشی از Noisy Neighbors و … .
انواع مالتی تننسی در کوبرنتیز

- چندین تیم (Multiple Teams):
در این حالت، یک سازمان میتواند چندین تیم را بر روی یک کلاستر مشترک مستقر کند. هر تیم یک یا چند ورکلود دارد و ممکن است نیاز به برقراری ارتباط با یکدیگر و حتی کلاسترهای دیگر داشته باشند.
اعضای تیمها غالباً از طریق ابزارهایی مانند kubectl یا سیستمهای GitOps به منابع کوبرنتیز دسترسی دارند. اینجا اعتماد نسبی بین تیمها وجود دارد، اما همچنان باید از سیاستهای کوبرنتیز همچونRBAC ، Quotas و Network Policies برای اطمینان از امنیت، مدیریت منصفانه و جداسازی نسبی استفاده شود. - چندین مشتری (Multiple Customers):
نوع دیگر مالتی تننسی زمانی است که یک ارائهدهندهی سرویس SaaS چندین نمونه از سرویس خود را برای مشتریان مختلف بر روی یک کلاستر اجرا کند. در این حالت، مشتریان دسترسی مستقیمی به کلاستر ندارند و کوبرنتیز عملاً از دید آنها پنهان است. هدف اصلی اینجا معمولاً بهینهسازی هزینهها و تامین ایزولهسازی قوی بین ورکلودهای مشتریان مختلف است.

روشهای اعمال مالتی تننسی و ابزارهای مربوطه در کوبرنتیز
مالتی تننسی در کوبرنتیز را میتوان به روشهای مختلفی پیادهسازی کرد و هر روش نیز مصالحههایی در سطح امنیت، پیچیدگی، هزینه و میزان جداسازی ایجاد میکند. در ادامه به برخی از ابزارها و الگوهای رایج در این زمینه میپردازیم:
- کنترل دسترسی مبتنی بر نقش (RBAC):
RBAC روشی استاندارد برای کنترل سطوح دسترسی در کوبرنتیز است. با استفاده از Roles و RoleBindings (در سطح Namespace) میتوانید تعیین کنید که کدام کاربر یا سرویساکانت به چه منابعی دسترسی دارد. در محیط چند-تیمی، RBAC ابزاری کلیدی برای محدود کردن دسترسی تیمها به فضای نام (Namespace) مختص خود و جلوگیری از دخالت در منابع کلاستر سطح بالا (Cluster-Level) توسط کاربران غیرمجاز است. - سهمیهها (Resource Quotas):
کوبرنتیز این امکان را میدهد که استفاده از منابعی همچون CPU، حافظه، یا حتی تعداد آبجکتها (مثلاً تعداد Pod یا ConfigMap) را در هر Namespace محدود کنید. این قابلیت در محیطهای چند-تیمی که هر تیم به API کوبرنتیز دسترسی دارد، اطمینان میدهد که یک تیم نتواند با ایجاد تعداد زیادی منبع، منابع کلاستر را اشغال کند و برای دیگران مشکل ایجاد نماید. سهمیهها ابزاری مهم برای تضمین عدالت و جلوگیری از بروز مشکل «همسایه پرسروصدا» هستند. البته باید توجه داشت که سهمیهها (Quotas) همه چیز را کنترل نمیکنند؛ برای مثال، روی ترافیک شبکه اثر مستقیم ندارند. - جداسازی شبکه (Network Policies):
با استفاده از Network Policy، میتوان ارتباط بین پادها را کنترل و محدود کرد. در محیطهای مالتی تننسی حساس، میتوانید یک سیاست پیشفرض تعریف کنید که ارتباط بین پادها را مسدود نماید، و صرفاً ارتباط با سرویس DNS یا جریانهای ارتباطی مجاز شده را فعال کنید. بدین ترتیب امنیت و جداسازی به مراتب افزایش خواهد یافت. به صورت پیش فرض تمام پادها داخل کلاستر کوبرنتیز به یکدیگر از طریق شبکه دسترسی دارند. - جداسازی ذخیرهسازی (Storage Isolation):
ذخیرهسازی در کوبرنتیز با استفاده از PersistentVolume و PersistentVolumeClaim مدیریت میشود. با تعریف StorageClassهای متفاوت برای هر مستاجر، میتوانید به هر تیم یا مشتری فضای ذخیرهسازی ویژهای اختصاص دهید. اگر از یک StorageClass مشترک استفاده میکنید، توصیه میشود از سیاست Reclaim با مقدار Delete استفاده کنید تا پس از آزاد شدن منابع، دوباره در اختیار مستاجر دیگری قرار نگیرد و از خطرات امنیتی احتمالی جلوگیری شود. - جداسازی در سطح نود (Node Isolation):
با اختصاص دادن مجموعهای از نودها به یک مستاجر خاص، میتوان از مخلوط شدن پادهای مستاجران مختلف بر روی یک نود جلوگیری کرد. این کار ریسک حملات احتمالی و مشکلات Noisy Neighbors را کاهش میدهد. حتی اگر مهاجمی به یک نود نفوذ کند، تنها به پادها و حجمهای ذخیرهسازی همان مستاجر دسترسی خواهد داشت.
چند-مستاجری در کوبرنتیز روشی قدرتمند برای بهینهسازی هزینهها و بهرهبرداری مؤثر از منابع مشترک است. با این حال، نیازمند برنامهریزی دقیق، انتخاب مناسب ابزارهای نظارتی و امنیتی، و پیادهسازی سیاستهای کنترلی است. از RBAC و سهمیهها گرفته تا Network Policy و Node Isolation، هر کدام بخشی از پازل مالتی تننسی در کوبرنتیز را کامل میکنند. با استفادهی هوشمندانه از این ابزارها و الگوها، میتوان به یک کلاستر اشتراکی امن، پایدار و عادلانه دست یافت که نیازهای چندین تیم یا چندین مشتری را به خوبی برآورده کند.

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

بیشتر تیمها کلاستر خود را بر اساس محیطها (environment) پارتیشنبندی میکنند.
برای مثال، ده تیم ممکن است هر کدام سه محیط (مثلاً dev ،test و prod) داشته باشند.
اگر کلاستر را بر مبنای تیم و محیط تقسیمبندی کنید، در مجموع ۳۰ بخش (slice) متمایز خواهید داشت.
حالا اگر بخواهیم مقیاس را به ۵۰ تیم گسترش دهیم چه میشود؟ در این صورت، ۱۵۰ بخش به دست میآید (۵۰ تیم × ۳ محیط = 150).
اما پیامدهای این تصمیم چیست؟
تصور کنید میخواهید برای مدیریت ترافیک ورودی، یک Ingress Controller مستقر کنید.

دو انتخاب اصلی دارید:
- یک Ingress Controller واحد برای تمام مستأجران.
- یک Ingress Controller اختصاصی برای هر مستأجر.
یک Ingress Controller واحد در مقابل Ingress Controllerهای اختصاصی
فرض کنید میخواهید از nginx-ingress controller استفاده کنید و request پیشفرض آن ۱۰۰ میلیهسته (millicore) CPU و ۹۰ مگابایت حافظه است.
اگر تصمیم بگیرید برای هر مستأجر یک Ingress Controller اختصاصی مستقر کنید، برای ۵۰ مستأجر به صورت زیر خواهد بود:
- CPU: 50 × ۱۰۰ millicore = 5 vCPU
- Memory: 50 × 90MB = 4.5GB
نزدیکترین نمونهی EC2 که با این مشخصات همخوانی دارد یک c6i.2xlarge با قیمت حدود ۲۵۰ دلار در ماه است. اگر با اشتراک گذاشتن یک Ingress Controller بین ۵۰ مستأجر مشکلی ندارید، هزینهها تنها کسر کوچکی از این مقدار خواهد بود، چرا که فقط بابت ۱۰۰ میلیهسته و 90MB حافظه میپردازید.
اما آیا این واقعبینانه نیست زیرا ترافیک ورودی ۵۰ مستأجر احتمالاً بیش از یک Ingress Controller واحد نیاز دارد و به طور متوسط ممکن است از مقدار 100mi و 90MB بیشتر مصرف کند. علاوه بر این، در سناریویی که چیزی خراب شود یا نیاز به ارتقا داشته باشد، همهی مستأجران تحت تأثیر قرار میگیرند.
از آنجا که Kubernetes برای soft multi-tenancy طراحی شده، ارزش دارد که پیکربندیهای مختلف چندمستأجری را از نرم تا سخت بررسی کنیم.
هزینهها را برای سه پیکربندی با سطوح جداسازی افزایشی مقایسه کردند که در ادامه نتایج را بررسی میکنیم:
- Hierarchical Namespace controller for soft multi-tenancy.
- vCluster for isolating control planes.
- Karmada for managing a cluster per tenant (hard multi-tenancy).
بیایید با Hierarchical Namespace Controller شروع کنیم.
Hierarchical Namespace Controller (HNC):
یک مؤلفه است که در کلاستز نصب میکنید و به شما اجازه میدهد Namespaceها را تو در تو (nested) ایجاد کنید.

ایدهی هوشمندانه پشت آن این است که همه Namespaceهای فرزند، منابع را از والد به ارث میبرند و این تو در تو بودن میتواند بینهایت ادامه یابد.
به این ترتیب، اگر یک Role در Namespace والد ایجاد کنید، همان منبع در فرزندان هم در دسترس خواهد بود. در پشت صحنه، کنترلر تفاوت بین دو Namespace را محاسبه کرده و منابع را کپی میکند.
بیایید یک مثال ببینیم.
بعد از نصب کنترلر، میتوانید یک Namespace ریشه به شکل زیر ایجاد کنید:
$ kubectl create ns parent
حالا میتوانید یک نقش (Role) در Namespace والد با دستور زیر ایجاد کنید:
$ kubectl -n parent create role test1 --verb=* --resource=pod
حالا یک اسکریپت برای ایجاد ۵۰ Namespace فرزند بنویسید:
#!/bin/bash
for i in {1..50}
do
kubectl hns create "tenant-$i" -n parent
done
اگر لیست Roleها را در هر یک از Namespaceهای فرزند بررسی کنید، میبینید که Role به آنها منتقل شده است:
$ kubectl get roles -n tenant-1
NAME CREATED AT
test1 2024-02-29T20:16:05Z
اما Namespaceهای تو در تو چگونه پیادهسازی شدهاند؟
نیماسپیسهای فرزند در واقع همان Namespaceهای معمولی Kubernetes هستند. میتوانید با دستور زیر آنها را فهرست کنید:
$ kubectl get namespaces
NAME STATUS
default Active
hnc-system Active
kube-node-lease Active
kube-public Active
kube-system Active
parent Active
tenant-1 Active
tenant-10 Active
tenant-11 Active
tenant-12 Active
tenant-13 Active
#...
ارتباطات آنها در Hierarchical Namespace Controller ذخیره شده و این کنترلر مسئول انتشار (propagate) منابع از والد به فرزند است.
هزینه اجرای چنین اپراتوری پایین است: درخواست کنونی برای حافظه و CPU حدود 300MB حافظه و ۱۰۰ میلیهسته CPU است. اما این روش محدودیتهایی دارد. در Kubernetes، منابعی مانند Pod و Deployment در سطح Namespace اعمال میشوند. اما بعضی منابع در سطح کل کلاستر global هستند، مانند ClusterRole ، ClusterRoleBinding ، Namespaceها ، PersistentVolumeها، CustomResourceDefinitionها (CRD) و غیره. اگر مستأجران بتوانند Persistent Volume مدیریت کنند، همهی PVهای کلاستر را خواهند دید، نه فقط PVهای خودشان.
این منابع گلوبال در کنترل پلین ذخیره میشوند. پس اگر بخواهیم برای هر مستأجر یک کنترل پلین مجزا داشته باشیم چه؟
vCluster: the cost of isolated control planes
شما میتوانید به هر مستأجر یک کلاستر اختصاص دهید یا از یک روش سبکتر استفاده کنید: اجرای یک کنترل پلین به صورت یک پاد در کلاستر میزبان.
مستأجران مستقیماً به این کنترل پلین (که در قالب پاد است) وصل شده و منابع خود را در آن ایجاد میکنند.
با این کار، کنترل پلین فقط متعلق به آنهاست و در نتیجه منابع گلوبال و مشکل contention و رقابت بر سر منابع حذف میشود.
اما پاد کنترل پلین کجا اسکجول میشود اگر فقط یک کنترل پلین اجرا کنید؟
کلاستر ویرچوآل یا vCluster این رویکرد را در پیش گرفت و راهحل مبتکرانهای ارائه داد: یک کنترلر که منابع را از کنترل پلین مستأجر به کنترل پلین میزبان کپی میکند.
هنگامی که یک Deployment در کنترل پلین مستأجر ایجاد میکنید، مشخصات پادهای حاصل به کنترل پلین میزبان کپی شده و در آنجا زمانبندی میشوند.
مزایا و معایب:
- هر مستأجر یک کنترل پلین کامل دارد که انعطافپذیری یک کلاستر واقعی را فراهم میکند.
- این کنترل پلین تنها برای ذخیره منابع در یک دیتابیس استفاده میشود.
- کنترلر میتواند طوری پیکربندی شود که فقط منابع خاصی را کپی کند.
به عبارت دیگر، یک مکانیزم همگامسازی دقیق به شما اجازه میدهد گزینشی تعیین کنید کدام منابع از کلاستر مستأجر به خوشه میزبان منتقل شوند.
فرآیند تست کردن این روش چیزی شبیه موارد زیر است:
vcluster create test --set 'sync.persistentvolumes.enabled=true'
وقتی nested cluster آماده شد، یک PersistentVolume را در فایل pv.yaml ذخیره کنید:
apiVersion: v1
kind: PersistentVolume
metadata:
name: task-pv-volume
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"
سپس آن را با دستور زیر اعمال کنید:
$ kubectl apply -f pv.yaml
persistentvolume/task-pv-volume created
از کلاستر مستأجر خارج شوید و همه PVها را در خوشه میزبان ببینید:
$ vcluster disconnect
$ kubectl get pv
NAME CAPACITY STATUS CLAIM
pvc-6ced7d97-c0f4-4a82-a5f8-2337907fff0b 5Gi Bound vcluster-test/data-test-0
vcluster-task-pv-volume-x-vcluster-test-x-test 10Gi Available
دو PV داریم: یکی برای کنترل پلین و دیگری که تازه ساختیم.
اگر همین آزمایش را برای مستأجر دوم تکرار کنیم چه میشود؟
$ vcluster create test2 --set 'sync.persistentvolumes.enabled=true'
دوباره همان pv.yaml را اعمال میکنیم (نام تکراری اما بدون مشکل):
$ kubectl apply -f pv.yaml
persistentvolume/task-pv-volume created
از کلاستر خارج شده و PVها را در میزبان بررسی کنید:
$ vcluster disconnect
$ kubectl get pv
NAME CAPACITY STATUS CLAIM
pvc-131f1b41-7ed3-4175-a33d-080cdff41b44 5Gi Bound vcluster-test2/data-test2-0
pvc-6ced7d97-c0f4-4a82-a5f8-2337907fff0b 5Gi Bound vcluster-test/data-test-0
vcluster-task-pv-volume-x-vcluster-test-x-test 10Gi Available
vcluster-task-pv-volume-x-vcluster-test2-x-test2 10Gi Available
هر مستأجر فقط یک PV را میبیند، اما کلاستر میزبان همه را میبیند!
توجه کنید برای هر مستأجر ما یک پاد و یک PV داریم. حال برای اجرای یک خوشه برای ۵۰ مستأجر به چه منابعی نیاز داریم؟
یک خوشه با یک pool شامل نودی با 2GB RAM و ۱ vCPU را در نظر بگیرید که یک نود دارد و Cluster Autoscaler هم روش نصب شده. سپس اسکریپت زیر را اگر اجرا کنید:
#!/bin/bash
for i in {1..50}
do
vcluster create "tenant-$i" --connect=false --upgrade
done
کلاستر در نهایت روی ۱۷ نود پایدار بر اساس تستهای انجام شده استیبل میشود.
از آنجایی که هر نود حدود ۱۲ دلار در ماه هزینه دارد، مجموع حدود ۲۰۴ دلار در ماه شد. همچنین ۱ دلار در ماه برای حجم 10GB PV هزینه داریم. مجموع حدود ۲۵۴ دلار در ماه یا تقریباً ۵ دلار به ازای هر مستأجر در این حالت هزینه میبرد.
اگر به دلایل قانونی نیاز به جداسازی ورکلود در کلاستر مجزا داشته باشید، چه؟
یک گزینه دیگر داشتن یک خوشه اختصاصی برای هر مستأجر است.
Hard multi-tenancy: dedicated clusters with Karmada:
میتوانید از Karmada برای مدیریت کلاستر مستأجران و استقرار ورکلودهای مشترک روی تمام کلاسترها استفاده کنید. معماری Karmada مشابه vCluster است:
ابتدا، یک کنترل پلین مدیر (cluster manager) دارید که از چند کلاستر آگاه است.
معمولاً آن را در یک کلاستر ویژه که ورکلودی اجرا نمیکند، مستقر میکنند.
سپس Karmada از یک عامل (agent) استفاده میکند که دستورات را از کنترل پلین Karmada دریافت و به کلاستر محلی ارسال میکند.
در نهایت چنین ترکیبی دارید:
- کنترل پلین Karmada میتواند ورکلود را روی همه کلاسترها اسکجول کند.
- هر کلاستر میتواند همچنان به صورت مستقل ورکلود اجرا کند.
از میان تمام گزینهها، این گرانترین حالت برای نگهداری و بهرهبرداری است.
در آزمایش انجام شده ۵۱ کلاستر (۵۰ مستأجر و ۱ مدیریت) با یک نود (1vCPU، 2GB) ساخته شده.
همه کلاسترها منطقهای (Regional) و بدون HA بودند و پلتفرم Akamai هزینهای برای کنترل پلینها دریافت نکرد. تنها هزینه، هزینه یک نود کاری (Worker) برای هر کلاستر بود.
The total: 50$12=~$612/month or ~$12/tenant/month (the cost of the single node).
Comparing multi-tenancy costs:
سه گزینهی چندمستأجری دارای سطوح جداسازی و هزینههای بسیار متفاوتی هستند. باید حواسمون باشه که مالتی تننسی رو باید برای تمام اعضای کلاستر در نظر بگیریم یعنی مانیتورینگ، لاگینگ، پایپلاینها و غیره هم هستند.
هزینهها بسیار متفاوت است، اما توجه داشته باشید که همه گزینههای چندمستأجری مشابه هم نیستند.
با استفاده از کلاسترهای جداگانه عملاً انتخابی ندارید: باید حداقل یک Ingress Controller اختصاصی به ازای هر خوشه داشته باشید — بنابراین گزینههای کمتری برای اشتراک و صرفهجویی در هزینه دارید.
میزبانی چندین مستأجر در یک کلاستر Kubernetes مستلزم تعادل بین هزینهها و جداسازی است.
میتوانید چندمستأجری نرم را با سربار کم انتخاب کنید، اما ریسک تأثیرگذاری یک مشکل روی همهی مستأجران وجود دارد. یا میتوانید جداسازی کامل را انتخاب کنید اما باید نصب، مدیریت و ارتقای کلاسترها را برای چندین تیم را بر عهده بگیرید. گزینهی دیگر یک راه میانه است: یک خوشه مشترک با یک کنترل پلین اختصاصی برای هر مستأجر (مثل vCluster) که مقداری جداسازی همراه با هزینههای قابل مدیریتتر ارائه میدهد.
در بلاگ پستهای بعدی مسیرمون رو ادامه میدیم و بیشتر و بیشتر در مورد کوبرنتیز یاد میگیریم.
مراقب خودتون باشید.
پراب، ریکوئست و لیمیت (قسمت نهم) توی این قسمت موارد مربوط به محدود کردن منابع کانتینر توی کوبرنتیز رو بررسی کردیم و در مورد انواع probe ها توی کوبرنتیز توضیح دادیم.
پاد تو نود (قسمت دهم) توی این قسمت درمورد فرآیند انتقال پاد به نود مناسب مفاهیم پیشرفتهتری مثل affinity و anti-affinity و taint و toleration رو بررسی کردیم.
اولویت پاد و امنیت (قسمت یازدهم) توی این قسمت در مورد تعیین اولویت برای پادها و جنبههای مختلف امنیت در کوبرنتیز توضیح دادیم.
کنترل دسترسی به کوبر (قسمت دوازدهم) توی این قسمت در مورد مراحل دسترسی به api کوبرنتیز صحبت کردیم و بعدش مفاهیمی مثل سرویس اکانت رو توضیح دادیم.


