هندسة المنصات وتجربة المطورين

تعدد المستأجرين والضمانات في المنصات

18 دقيقة الدرس 8 من 28

تعدد المستأجرين والضمانات في المنصات

عندما تخدم فرقة المنصة عشرات أو مئات من الفرق المنتجة على بنية تحتية مشتركة، فإن كل قرار تصميمي يتعلق بـعزل المستأجرين والحصص والإعدادات الافتراضية الآمنة له تداعيات على نطاق الكارثة. فالنيمسبيس المُهيَّأ بشكل خاطئ قد يحرم خدمة حيوية من المعالج؛ وغياب NetworkPolicy قد يسمح لـ Pod مخترق بسرقة الأسرار عبر حدود الفرق؛ ومن دون LimitRange، قد يُحجب حاوية مفلتة ذاكرة العقدة بأكملها. هذا الدرس يغطي كيفية بناء هذا الانضباط داخل منصتك.

نماذج عزل المستأجرين

يُخصَّص المستأجرون في المنصة عادةً لأحد حدود العزل الثلاثة، ولكل منها مقايضة مختلفة بين العزل والتكلفة:

  • Namespace لكل فريق — النموذج الأكثر شيوعاً في Kubernetes. تتشارك الفرق العنقود ذاته لكنها مفصولة بـ RBAC وNetworkPolicy وResourceQuota. كفؤ من حيث التكلفة؛ العزل منطقي وليس مادياً.
  • Node Pool لكل مستأجر — تُنشر أعباء عمل الفرق الحساسة على node pools مخصصة عبر nodeSelector وTaint/Toleration. يزول خطر الجار الصاخب، لكن التكلفة أعلى. شائع للمستأجرين الخاضعين لـ PCI/HIPAA.
  • Cluster لكل مستأجر — عزل كامل. يُستخدم للمجالات الخاضعة لمتطلبات الامتثال. أدوات مثل vCluster تجلب هذا النموذج داخل عنقود واحد بتكلفة قريبة من النيمسبيس.
النموذج الصحيح يُحدَّد بناءً على نموذج التهديد، لا الراحة. منصة تخدم فرقاً داخلية ذات متطلبات امتثال موحدة يمكنها العمل بأمان بعزل النيمسبيس. أما المنصة التي تستضيف أعباء عمل منظَّمة جنباً إلى جنب مع خدمات عامة، فيجب أن تفصلهما مادياً.

ResourceQuota وLimitRange في Kubernetes

كل نيمسبيس تُنشئها المنصة يجب أن يحصل على ResourceQuota (حدود صارمة على مستوى العنقود) وLimitRange (الإعدادات الافتراضية والحدود القصوى لكل حاوية). حذف LimitRange يعني أن Pod بدون requests/limits صريح يعمل بدون قيود على المعالج/الذاكرة — وهو أكثر أسباب حوادث الجار الصاخب شيوعاً.

# namespace-quota.yaml — يُولَّد تلقائياً من مُوفِّر المنصة لكل فريق apiVersion: v1 kind: ResourceQuota metadata: name: team-quota namespace: team-payments spec: hard: requests.cpu: "8" requests.memory: 16Gi limits.cpu: "16" limits.memory: 32Gi pods: "60" services: "20" persistentvolumeclaims: "10" count/deployments.apps: "30" count/jobs.batch: "20" --- apiVersion: v1 kind: LimitRange metadata: name: team-limitrange namespace: team-payments spec: limits: - type: Container default: # يُحقن عند غياب limits في الحاوية cpu: "500m" memory: 256Mi defaultRequest: # يُحقن عند غياب requests في الحاوية cpu: "100m" memory: 128Mi max: cpu: "4" memory: 4Gi min: cpu: "50m" memory: 64Mi

أضف تنبيهات استنفاد الحصة: قاعدة Prometheus على kube_resourcequota{type="used"} / kube_resourcequota{type="hard"} > 0.85 ترسل تحذيراً قبل امتلاء النيمسبيس وبدء الـ Pods في الانتظار. بدون هذا التنبيه، يكتشف المهندسون حد الحصة فقط حين يفشل نشرهم بصمت خلال حادثة.

عزل الشبكة بـ NetworkPolicy

Kubernetes يسمح افتراضياً بكل حركة المرور بين الـ Pods. في منصة متعددة المستأجرين يجب عكس ذلك: رفض كل شيء افتراضياً، ثم إضافة قواعد سماح صريحة. السياسة الأساسية أدناه مُدرجة في كل نيمسبيس جديدة.

# default-deny.yaml — تُطبَّق على كل نيمسبيس تُنشئها المنصة apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-all namespace: team-payments spec: podSelector: {} # يختار جميع الـ Pods في النيمسبيس policyTypes: - Ingress - Egress --- # allow-same-namespace.yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-intra-namespace namespace: team-payments spec: podSelector: {} policyTypes: - Ingress - Egress ingress: - from: - podSelector: {} # أي Pod في النيمسبيس ذاتها egress: - to: - podSelector: {} - to: # دقة أسماء DNS (kube-dns) - namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system ports: - port: 53 protocol: UDP - port: 53 protocol: TCP
توافق CNI: كائنات NetworkPolicy تُطبَّق فقط إذا كان مكوِّن الشبكة (CNI) يطبِّق المواصفة. Flannel لا يفعل ذلك. Calico وCilium وWeave Net يفعلون. على عنقود يتجاهل فيه الـ CNI الـ NetworkPolicy بصمت، يعتقد المستأجرون أنهم معزولون وهم ليسوا كذلك. تحقق من التطبيق أثناء تشغيل المنصة بـ pod اختبار يفحص الاتصال عبر النيمسبيسات ويؤكد أنه محجوب.

التحكم في القبول: السياسة كضمانات

ResourceQuota وNetworkPolicy تعمل بأثر رجعي — تقيد ما هو موجود. سياسات القبول استباقية: تحجب أو تعدِّل أعباء العمل وقت الإنشاء. المنصة الناضجة تستخدم Kyverno أو OPA/Gatekeeper لتطبيق القواعد التي كانت تتطلب مراجعة يدوية بشرية على نطاق واسع.

السياسات الأساسية التي يجب أن تشحنها كل منصة:

  • اشترط تشغيل الحاويات بمستخدم غير root — ارفض أي Pod لا يحمل securityContext.runAsNonRoot: true.
  • احظر الحاويات ذات الامتيازات — ارفض securityContext.privileged: true.
  • اشترط ديجست الصورة أو السجل المسموح به — ارفض وسم latest.
  • اشترط طلبات الموارد وحدودها — ارفض Pods التي ستتجاوز LimitRange.
  • اشترط تسميات الفريق — ارفض أي عبء عمل يفقد app.kubernetes.io/team (يُمكِّن تخصيص التكلفة وتوجيه المناوبة).
# kyverno-require-labels.yaml apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: require-team-labels spec: validationFailureAction: Enforce rules: - name: check-team-label match: any: - resources: kinds: [Deployment, StatefulSet, DaemonSet] validate: message: "يجب أن تحمل أعباء العمل تسميتَي app.kubernetes.io/team وapp.kubernetes.io/service." pattern: metadata: labels: app.kubernetes.io/team: "?*" app.kubernetes.io/service: "?*"

الإعدادات الافتراضية الآمنة: سياق الأمان الأساسي

بدلاً من الاعتماد على المطورين لكتابة سياق أمان صحيح، يجب أن تُعدِّل المنصة مواصفات الـ Pod وقت القبول لحقن الإعدادات الافتراضية الآمنة. كل أساس عمل يجب أن يشمل:

  • runAsNonRoot: true
  • readOnlyRootFilesystem: true
  • allowPrivilegeEscalation: false
  • seccompProfile.type: RuntimeDefault
  • capabilities.drop: [ALL] — أضف فقط ما هو مطلوب صراحةً
Multi-Tenant Platform Guardrails Stack Multi-Tenant Guardrails Stack Cluster Namespace: team-payments ResourceQuota + LimitRange NetworkPolicy (default-deny) RBAC (team role binding) Pod A Pod B NodeAffinity → pool-standard Namespace: team-data ResourceQuota + LimitRange NetworkPolicy (default-deny) RBAC (team role binding) Pod C Pod D NodeAffinity → pool-gpu Admission Control (Cluster-wide) Kyverno: require-team-labels Kyverno: disallow-privileged Kyverno: require-resource-limits Pod Security: restricted OPA/Gatekeeper: registry allowlist BLOCKED
طبقات الضمانات: الحصص لكل نيمسبيس، عزل الشبكة، وسياسات القبول على مستوى العنقود تُطبِّق حدود المستأجرين.

تخصيص التكاليف والمحاسبة

تعدد المستأجرين بدون رؤية للتكاليف يخلق مأساة الموارد المشتركة: الفرق تُضخِّم المطالبات لأنها لا ترى الفاتورة. يجب أن تُصدر المنصة بيانات التكلفة لكل نيمسبيس. OpenCost (CNCF) أو Kubecost يمكن نشرهما كخدمة منصة لإنتاج تقارير الإنفاق لكل فريق. تسمية app.kubernetes.io/team التي تفرضها سياسة Kyverno هي مفتاح الربط.

استراتيجية هامش الحصة: اضبط حدود ResourceQuota على 1.5 ضعف الاستخدام التاريخي للفريق عند P95، ليس عند الحد النظري الأقصى. مراجعة الحصص ربع سنوياً بالشراكة مع الفرق خلال دورات تخطيط السعة. أرسل تنبيهاً عند 85% استخداماً حتى تتمكن الفرق من طلب الزيادة قبل الوصول للحد أثناء ارتفاع حركة المرور.

vCluster للعزل الأقوى بدون تكلفة عنقود كاملة

عندما يحتاج فريق إلى عزل على مستوى العنقود — خادم API منفصل، عالم RBAC منفصل — لكن توفير EKS/GKE كامل لكل فريق مكلف جداً، يُوفِّر vcluster (من Loft Labs) virtualisation لـ control plane داخل نيمسبيس بالعنقود المضيف. يرى المستأجر خادم API حقيقي؛ العنقود المضيف يرى فقط Pods. هذا هو النمط الذي تستخدمه Datadog وعدة منصات SaaS كبيرة.

# تثبيت vcluster CLI ثم إنشاء عنقود افتراضي معزول للمستأجر المنظَّم vcluster create team-finserv \ --namespace vcluster-team-finserv \ --values - <<EOF syncer: extraArgs: - --enforce-node-selector=true - --node-selector=pool=finserv-dedicated isolation: enabled: true resourceQuota: enabled: true quota: requests.cpu: "8" requests.memory: 16Gi limitRange: enabled: true default: cpu: "500m" memory: 256Mi defaultRequest: cpu: "100m" memory: 128Mi networkPolicy: enabled: true EOF

حوكمة الضمانات: التدقيق والتجاوز ومخارج الطوارئ

السياسات الصارمة التي تحجب العمل المشروع تولِّد IT الظل: المهندسون يتحايلون على المنصة بدلاً من العمل معها. كل ضمان يجب أن يكون له مخرج طوارئ موثَّق ومُدقَّق. في Kyverno، يمكن للسياسات أن تعمل في وضع Audit (تُسجَّل الانتهاكات دون حجب) قبل الترقية إلى Enforce. كائن Kyverno PolicyException يوفر تجاوزاً مُدقَّقاً دون إزالة السياسة من العنقود بأكمله.

هدف ضمانات المنصة ليس تطبيق الامتثال بجعل عدم الامتثال مستحيلاً — بل جعل المسار الصحيح أسهل من المسار الخاطئ. عندما تكون الضمانات مقيِّدة جداً بدون آلية هروب، يكون نطاق تداعيات الحلول البديلة أسوأ من المخاطرة التي صُمِّم الضمان لمنعها. صمِّم لـ 95% أتمتة و5% تجاوز مُدقَّق.