مالتی تننسی در کوبر (قسمت چهاردهم)

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

خب یه مروری کنیم پست‌های قبلی رو:

توصیه می‌کنم که حتما این پست‌ها رو هم مطالعه کنید. بریم که ادامه بدیم.

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

انواع مالتی تننسی در کوبرنتیز

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

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

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

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

چند-مستاجری در کوبرنتیز روشی قدرتمند برای بهینه‌سازی هزینه‌ها و بهره‌برداری مؤثر از منابع مشترک است. با این حال، نیازمند برنامه‌ریزی دقیق، انتخاب مناسب ابزارهای نظارتی و امنیتی، و پیاده‌سازی سیاست‌های کنترلی است. از RBAC و سهمیه‌ها گرفته تا Network Policy و Node Isolation، هر کدام بخشی از پازل مالتی تننسی در کوبرنتیز را کامل می‌کنند. با استفاده‌ی هوشمندانه از این ابزارها و الگوها، می‌توان به یک کلاستر اشتراکی امن، پایدار و عادلانه دست یافت که نیازهای چندین تیم یا چندین مشتری را به خوبی برآورده کند.

Multi-tenancy
Multi-tenancy

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

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

team environments
team environments

بیشتر تیم‌ها کلاستر خود را بر اساس محیط‌ها (environment) پارتیشن‌بندی می‌کنند.

برای مثال، ده تیم ممکن است هر کدام سه محیط (مثلاً dev ،test و prod) داشته باشند.

اگر کلاستر را بر مبنای تیم و محیط تقسیم‌بندی کنید، در مجموع ۳۰ بخش (slice) متمایز خواهید داشت.

حالا اگر بخواهیم مقیاس را به ۵۰ تیم گسترش دهیم چه می‌شود؟ در این صورت، ۱۵۰ بخش به دست می‌آید (۵۰ تیم × ۳ محیط = 150).

اما پیامدهای این تصمیم چیست؟

تصور کنید می‌خواهید برای مدیریت ترافیک ورودی، یک Ingress Controller مستقر کنید.

Ingress Controller
Ingress Controller

دو انتخاب اصلی دارید:

  1. یک Ingress Controller واحد برای تمام مستأجران.
  2. یک 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) ایجاد کنید.

Hierarchical Namespace Controller
Hierarchical Namespace Controller

ایده‌ی هوشمندانه پشت آن این است که همه 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 &quottenant-$i&quot -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

شما می‌توانید به هر مستأجر یک کلاستر اختصاص دهید یا از یک روش سبک‌تر استفاده کنید: اجرای یک کنترل پلین به صورت یک پاد در کلاستر میزبان.

مستأجران مستقیماً به این کنترل پلین (که در قالب پاد است) وصل شده و منابع خود را در آن ایجاد می‌کنند.

vCluster

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

اما پاد کنترل پلین کجا اسکجول می‌شود اگر فقط یک کنترل پلین اجرا کنید؟

کلاستر ویرچوآل یا vCluster این رویکرد را در پیش گرفت و راه‌حل مبتکرانه‌ای ارائه داد: یک کنترلر که منابع را از کنترل پلین مستأجر به کنترل پلین میزبان کپی می‌کند.

هنگامی که یک Deployment در کنترل پلین مستأجر ایجاد می‌کنید، مشخصات پادهای حاصل به کنترل پلین میزبان کپی شده و در آنجا زمانبندی می‌شوند.

vCluster

مزایا و معایب:

  • هر مستأجر یک کنترل پلین کامل دارد که انعطاف‌پذیری یک کلاستر واقعی را فراهم می‌کند.
  • این کنترل پلین تنها برای ذخیره منابع در یک دیتابیس استفاده می‌شود.
  • کنترلر می‌تواند طوری پیکربندی شود که فقط منابع خاصی را کپی کند.

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

فرآیند تست کردن این روش چیزی شبیه موارد زیر است:

				
					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: &quot/mnt/data&quot
				
			

سپس آن را با دستور زیر اعمال کنید:

				
					$ 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 &quottenant-$i&quot --connect=false --upgrade
done
				
			

کلاستر در نهایت روی ۱۷ نود پایدار بر اساس تست‌های انجام شده استیبل می‌شود.

از آنجایی که هر نود حدود ۱۲ دلار در ماه هزینه دارد، مجموع حدود ۲۰۴ دلار در ماه شد. همچنین ۱ دلار در ماه برای حجم 10GB PV هزینه داریم. مجموع حدود ۲۵۴ دلار در ماه یا تقریباً ۵ دلار به ازای هر مستأجر در این حالت هزینه میبرد.

اگر به دلایل قانونی نیاز به جداسازی ورک‌لود در کلاستر مجزا داشته باشید، چه؟

یک گزینه دیگر داشتن یک خوشه اختصاصی برای هر مستأجر است.

Hard multi-tenancy: dedicated clusters with Karmada:

می‌توانید از Karmada برای مدیریت کلاستر مستأجران و استقرار ورک‌لودهای مشترک روی تمام کلاسترها استفاده کنید. معماری Karmada مشابه vCluster است:

ابتدا، یک کنترل پلین مدیر (cluster manager) دارید که از چند کلاستر آگاه است.

Karmada

معمولاً آن را در یک کلاستر ویژه که ورک‌لودی اجرا نمی‌کند، مستقر می‌کنند.

سپس 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) که مقداری جداسازی همراه با هزینه‌های قابل مدیریت‌تر ارائه می‌دهد.

در بلاگ پست‌های بعدی مسیرمون رو ادامه میدیم و بیشتر و بیشتر در مورد کوبرنتیز یاد می‌گیریم.

مراقب خودتون باشید. 

دیدگاه‌ خود را بنویسید

مقاله های داکرمی

Kubernetes

نصب کلاستر با kubespray (قسمت هجدهم)

توی این قسمت میریم سراغ اینکه بررسی کنیم چطوری می‌تونیم یه کلاستر کوبرنتیز رو با استفاده از kubespray ستاپ کنیم و بیاریم بالا. روشی که می‌تونیم باهاش کلاستر پروداکش رو ستاپ و نگهداری کنیم. خب یه مروری کنیم پست‌های قبلی

توضیحات بیشتر »
Kubernetes

نصب کلاستر با kubeadm (قسمت هفدهم)

توی این قسمت میریم به سراغ اینکه بررسی کنیم چطوری می‌تونیم یه کلاستر کوبرنتیز رو با استفاده از kubeadm ستاپ کنیم و بیاریم بالا. خب یه مروری کنیم پست‌های قبلی رو: دواپس چیه و چرا لازمه؟ اینجا در مورد دواپس و

توضیحات بیشتر »
Kubernetes

سی آر دی و اُپراتور (قسمت شانزدهم)

توی این قسمت میریم به سراغ اینکه بررسی کنیم چطوری می‌تونیم ریسورس‌های خودمون رو به صورت کاستوم شده توی کوبرنتیز تعریف کنیم و بعدش هم در مورد اُپراتورها توی کوبرنتیز توضیح می‌دیم. خب یه مروری کنیم پست‌های قبلی رو: دواپس

توضیحات بیشتر »
پیمایش به بالا