عمليات Kubernetes المتقدمة

أنماط تعدد المستأجرين في Kubernetes

18 دقيقة الدرس 5 من 30

أنماط تعدد المستأجرين في Kubernetes

تعدد المستأجرين هو ممارسة تشغيل أعباء عمل لفِرق أو منتجات أو عملاء متعددين على بنية تحتية Kubernetes مشتركة. عند تنفيذه بشكل صحيح، يُقلّص التكاليف والأعباء التشغيلية بشكل ملحوظ. أما عند تنفيذه بشكل خاطئ، فيُفضي إلى مشكلات "نطاق الانفجار" حيث يُعطّل الجار المزعج اتفاقية مستوى الخدمة، أو يتجاوز pod مُهيّأ بشكل خاطئ حدوده المفروضة. يدرس هذا الدرس مستويات العزل الثلاثة المستخدمة في بيئات الإنتاج، والعناصر الأساسية لمساحات الأسماء التي تُطبّقها، والدروس الصعبة التي تعلّمتها فِرق منصات كبرى من تشغيل كلاسترات مشتركة على نطاق واسع.

طيف العزل: مساحات الأسماء مقابل الكلاسترات

يمتد تعدد المستأجرين في Kubernetes على طيف بين طرفين:

  • تعدد المستأجرين الخفيف (مساحة أسماء لكل مستأجر): يشترك عدة مستأجرين في كلاستر واحد، مفصولين بمساحات الأسماء وRBAC وNetworkPolicies وحصص الموارد. يُشارَك النواة وبيئة تشغيل الحاويات ومستوى التحكم. هذا هو النموذج الافتراضي لدى معظم فِرق المنصات في كبريات شركات التقنية للمستأجرين الداخليين (فِرق المنتجات) الموثوقين والذين يجب عزلهم عن بعضهم لأغراض تخصيص التكاليف ونطاق الانفجار وسياسة الأمان.
  • تعدد المستأجرين الصارم (كلاستر لكل مستأجر): يحصل كل مستأجر على كلاستر مخصص — أو على أقل تقدير مجموعة عقد مخصصة مع محددات عقد صارمة. يُستخدم عندما يكون المستأجرون عملاء خارجيين، أو تختلف نطاقات الامتثال (مثل PCI مقابل غير-PCI)، أو يكون نطاق الانفجار الناجم عن ثغرة نواة غير مقبول. التكلفة هي التشغيل: N كلاستر يُضاعف عبء الترقية ودوران الشهادات والمراقبة بمقدار N.
  • الكلاسترات الافتراضية (vcluster): حل وسط — خادم Kubernetes API كامل الوظائف يعمل داخل مساحة أسماء من كلاستر مضيف، مع etcd خاص به ومستوى تحكم خاص، لكنه يشترك في مجموعة عقد المضيف. تعمل أحمال العمل كـ pods اعتيادية على عقد المضيف. يرى المستأجرون كلاستر حقيقياً؛ فِرقة المنصة تُدير مجموعة عقد واحدة.
الإجابة الصحيحة تعتمد على نموذج الثقة. الفِرق الداخلية التي تشترك في كلاستر موثوق بها لعدم استغلال ثغرات النواة. العملاء الخارجيون — حتى مع RBAC وNetworkPolicies — يجب افتراض أنهم خصوم. بالنسبة لمنصات SaaS التي تخدم مستأجرين خارجيين، يميل الإجماع الحالي في الصناعة نحو الكلاسترات الافتراضية (vcluster أو Kamaji) أو إدارة كلاستر مخصص لكل عميل عبر أداة مثل Cluster API.
Kubernetes Multi-Tenancy Isolation Spectrum Multi-Tenancy Isolation Spectrum Soft (Namespaces) One shared cluster Namespace: team-alpha RBAC · Quota · NetworkPolicy Namespace: team-beta RBAC · Quota · NetworkPolicy Namespace: team-gamma RBAC · Quota · NetworkPolicy Shared Nodes + Kernel Virtual Cluster vcluster / Kamaji Virtual API Server + etcd (in host namespace) Tenant sees real K8s API Synced Pods → Host Pods Shared nodes, isolated API Shared Nodes + Kernel Hard (Cluster-per-Tenant) Dedicated cluster each Cluster A (Customer 1) Own control plane + nodes Cluster B (Customer 2) Own control plane + nodes Isolated Kernels Low Cost / Low Isolation Medium Cost / Medium Isolation High Cost / Full Isolation
ثلاثة مستويات عزل: تعدد المستأجرين الخفيف (مساحات الأسماء)، الكلاسترات الافتراضية، وتعدد المستأجرين الصارم (كلاسترات مخصصة).

مساحات الأسماء: أساس تعدد المستأجرين الخفيف

مساحة الأسماء في Kubernetes هي قسم منطقي من مخزن كائنات خادم API. معظم الموارد ذات النطاق المحدد بمساحة الأسماء (Pods وServices وConfigMaps وSecrets وDeployments وServiceAccounts) مُقيّدة بمساحة أسماء؛ وقليل منها ذو نطاق عنقودي (Nodes وPersistentVolumes وClusterRoles وStorageClasses). لا توفر مساحات الأسماء بمفردها أي عزل للشبكة أو أي عزل للموارد — فهي آلية وضع علامات ونطاق RBAC فحسب. تُضاف عناصر التطبيق فوقها.

# إنشاء مساحة أسماء لفريق مع إضافة بيانات وصفية للملكية kubectl create namespace team-payments kubectl label namespace team-payments \ team=payments \ env=production \ cost-center=fin-001 kubectl annotate namespace team-payments \ contact=payments-oncall@company.com \ allowed-registries=registry.company.com # التحقق kubectl get namespace team-payments --show-labels

ResourceQuotas: حدود صارمة على استهلاك مساحة الأسماء

يُطبّق كائن ResourceQuota حدوداً إجمالية على مساحة أسماء. بمجرد تعيين الحصة، يجب أن يحتوي كل Pod جديد على requests وlimits صريحة — وإلا رفض webhook القبول أي Pod يُهمل تعريفها. هذا مقصود: يمنع الفِرق من استنزاف الكلاستر عن طريق نشر أحمال عمل بلا حصص.

# resourcequota-payments.yaml apiVersion: v1 kind: ResourceQuota metadata: name: payments-quota namespace: team-payments spec: hard: # الحوسبة requests.cpu: "40" limits.cpu: "80" requests.memory: 80Gi limits.memory: 160Gi # التخزين requests.storage: 500Gi persistentvolumeclaims: "20" # أعداد الكائنات (حماية من المتحكمات المنفلتة) count/pods: "200" count/services: "30" count/configmaps: "100" count/secrets: "50" count/deployments.apps: "40" count/jobs.batch: "20"
اضبط حد CPU على ضعف الطلب تقريباً لمعظم خدمات الويب. يمنح ذلك هامشاً للارتفاعات دون السماح لمساحة أسماء واحدة باستهلاك الكلاستر بالكامل. لوظائف الدُفعات التي تحتاج إنتاجية مضمونة (تدريب نماذج ML، ETL)، اضبط requests == limits واستخدم فئة أولوية منفصلة كي لا تُزيح الخدمات الحساسة للاستجابة.

LimitRanges: الافتراضيات لكل Pod وكل حاوية

تُطبّق الحصص الإجماليات الكلية. تُطبّق كائنات LimitRange حدوداً لكل مورد — الحد الأدنى والأقصى والقيم الافتراضية التي تُطبَّق تلقائياً حين لا تحدد حاوية قيودها الخاصة. بدون LimitRange افتراضي، فِريق ينسى تعيين requests/limits سيفشل عند قبول الحصة مع أخطاء محيّرة. مع الافتراضي، تُحقن حدود معقولة تلقائياً.

# limitrange-payments.yaml apiVersion: v1 kind: LimitRange metadata: name: payments-limits namespace: team-payments spec: limits: - type: Container default: # تُحقن حين تُهمل الحاوية limits cpu: "500m" memory: 512Mi defaultRequest: # تُحقن حين تُهمل الحاوية requests cpu: "100m" memory: 128Mi max: # سقف صارم لكل حاوية cpu: "4" memory: 8Gi min: # يمنع التلاعب بالموارد الصفرية cpu: "50m" memory: 64Mi - type: PersistentVolumeClaim max: storage: 50Gi # لا يتجاوز أي PVC هذا الحجم min: storage: 1Gi

عزل الشبكة باستخدام NetworkPolicies

افتراضياً، يمكن لجميع pods في الكلاستر الوصول إلى كل pods أخرى بغض النظر عن مساحة الأسماء. لتعدد المستأجرين، يجب تقييد هذا السلوك. النمط القياسي هو سياسة رفض افتراضي لكل مساحة أسماء، ثم قواعد سماح صريحة للحركة المصرّح بها. بدون ذلك، pod مخترق في مساحة أسماء يمكنه الوصول إلى قواعد البيانات في كل مساحة أسماء أخرى.

# الخطوة 1: رفض كل الدخول والخروج افتراضياً في مساحة الأسماء apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-all namespace: team-payments spec: podSelector: {} # يطابق كل pods في مساحة الأسماء policyTypes: - Ingress - Egress --- # الخطوة 2: السماح بالدخول من مساحة أسماء ingress controller فقط apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-ingress-controller namespace: team-payments spec: podSelector: {} policyTypes: - Ingress ingress: - from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: ingress-nginx --- # الخطوة 3: السماح بالخروج إلى DNS (ضروري لأي تحليل اسم) apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-dns-egress namespace: team-payments spec: podSelector: {} policyTypes: - Egress egress: - ports: - port: 53 protocol: UDP - port: 53 protocol: TCP
لا تعمل NetworkPolicies إلا بقدر ما يدعمها إضافة CNI. تُطبَّق بواسطة CNI (Cilium أو Calico أو Weave)، لا بواسطة النواة. إذا كنت تستخدم CNI لا يُطبّق NetworkPolicy (مثل flannel الافتراضي)، يتم تجاهل سياساتك بصمت — تُطبَّق لكن لا أثر لها. تحقق دائماً أن إضافة CNI تُطبّق NetworkPolicy واختبر بمسبار اتصال بعد تطبيق سياسة الرفض.

نطاق RBAC: ServiceAccount واحد لكل فريق

يجب أن تحتوي كل مساحة أسماء للمستأجر على ServiceAccount خاصة بها، وRole يمنح الحد الأدنى من الصلاحيات التي يحتاجها الفريق، وRoleBinding يربط مجموعة الفريق (من موفر هوية مثل Okta أو Google Workspace أو Azure AD) بهذا Role. لا تربط ClusterRole الخاص بـ cluster-admin بفريق — فلا ينبغي أن يتمكنوا من قراءة Secrets في مساحات الأسماء الأخرى أو تعديل Nodes.

أنماط الفشل في الإنتاج

  • غياب LimitRange وغياب طلب الحصة: Pod بلا resource requests يُجدوَل على أي عقدة. في عقدة تشغّل 40 Pod من هذا النوع، Pod واحد يقفز إلى 2 CPU يُجوّع الـ39 الباقين. احرص دائماً على وجود LimitRange افتراضي وResourceQuota في كل مساحة أسماء للمستأجر.
  • حصص مُسرفة: تطلب الفِرق في أغلب الأحيان 10x حاجتها "احتياطاً". يبدو الكلاستر حينها ممتلئاً رغم أن متوسط الاستخدام 20%. فرض عملية مراجعة الحصص: ابدأ بمحافظة، وزد بناءً على الدليل، واستخدم kubectl describe quota -n <ns> أسبوعياً لإعادة التحجيم الصحيح.
  • الحركة الجانبية عبر رموز ServiceAccount المشتركة: Pods في مساحة الأسماء A القادرة على استدعاء Kubernetes API وذات ClusterRole مرنة يمكنها سرد Secrets في مساحة الأسماء B. افحص كل ClusterRoleBinding شهرياً — فهي المسار الأكثر شيوعاً لتصعيد الصلاحيات في الكلاسترات المشتركة.
  • إساءة استخدام NodePort: مستأجر يستطيع إنشاء Service من نوع NodePort يمكنه كشف منفذ على كل عقدة في الكلاستر، متجاوزاً NetworkPolicies. استخدم سياسة OPA/Gatekeeper لحظر خدمات NodePort وLoadBalancer في مساحات أسماء المستأجرين ما لم تُوافق عليها صراحة.

هيكلة مساحات الأسماء على نطاق واسع

على نطاق كبرى شركات التقنية (مئات مساحات الأسماء)، تصبح قائمة kubectl get namespaces المسطحة غير قابلة للإدارة. النمط الناضج هو تسلسل هرمي لمساحات الأسماء مُطبَّق عبر أداة منصة:

  • Hierarchical Namespace Controller (HNC): طوّرته Google، وهو الآن مشروع Kubernetes SIG. يتيح تعريف شجرة من مساحات الأسماء (org > division > team > environment) ونشر الكائنات (RoleBindings وNetworkPolicies وLimitRanges) تلقائياً أسفل الشجرة. تتدفق التغييرات على مستوى القسم إلى كل مساحات أسماء الفِرق الفرعية.
  • التسميات كمصدر الحقيقة: تحمل كل مساحة أسماء تسميات موحدة (team وenv وcost-center وtier). تختار سياسات Gatekeeper وقواعد Kyverno مساحات الأسماء بالتسمية، مما يتيح لفريق المنصة تطبيق سياسات الأمان على جميع مساحات أسماء env=production بسياسة ClusterPolicy واحدة.
عامل توفير مساحات الأسماء كأكواد. احفظ كل بيان مساحة أسماء (بما يشمل Quota وLimitRange وNetworkPolicies الافتراضية وRBAC) في مستودع Git. استخدم chart Helm أو قاعدة Kustomize يملأها الفريق بقيمه الخاصة. خط أنابيب آلي (مجموعة تطبيقات Argo CD أو خط Tekton) ينشئ مساحة الأسماء عند الدمج. لا تلمس الفِرق kubectl مباشرة لإنشاء مساحات الأسماء — بل يفتحون طلب سحب.