تعدد الـ Namespace وملكية الفرق في Envoy Gateway: ‏allowedRoutes و ReferenceGrant وحوكمة العمل بين الفرق

5 min read

إحدى أكبر قيم Gateway API ليست مجرد أن الـ YAML صار أجمل، بل أنه أخيرًا يجيب بوضوح عن سؤال قديم:

من يملك طبقة ingress؟ من يستطيع تغيير الـ routes؟ وهل يمكن للـ namespaces أن تشير إلى بعضها؟

كثير من آلام Ingress جاءت من كون كل شيء متشابكًا معًا. كانت فرق المنصة تخشى تغييرات التعريض غير المصرح بها. وكانت فرق التطبيقات تشعر أنها تحتاج إلى ticket فقط لتغيير path واحد. وفي النهاية كان الجميع يعذب بعضه بعضًا داخل YAML.

ومقاربة Gateway API تقول بوضوح:

  • يمكن حوكمة ingress الخاص بالمنصة بشكل مستقل
  • يمكن تفويض الـ routes إلى فرق التطبيقات
  • السلوك عبر namespaces المختلفة يتطلب تصريحًا صريحًا

وهذه المقالة تشرح نموذج الملكية هذا من الأعلى إلى الأسفل.

ابدأ من هنا: Attachment و Reference شيئان مختلفان

هذا هو الفرق الذي يختلط على الناس أكثر من غيره.

ارتباط Route مع Gateway / Listener

هذا الأمر تُحكمه:

  • قيمة parentRefs في الـ route
  • وقيمة allowedRoutes في الـ listener

وبمعنى آخر، ما إذا كان route ما يستطيع الارتباط بـ listener ليس أمرًا يحدده ReferenceGrant، بل يحدده ما إذا كان الـ listener مستعدًا لقبوله أم لا.

الإشارات إلى الموارد عبر Namespaces مختلفة

هنا يأتي دور ReferenceGrant غالبًا. مثلًا:

  • يريد Gateway الإشارة إلى Secret في namespace آخر
  • يريد HTTPRoute الإشارة إلى Service في namespace آخر

هذا ليس سؤال ارتباط، بل سؤال "هل منحك الـ namespace الهدف تفويضًا رسميًا لاستخدام موارده؟"

كثير من الناس يضغطون هذين المفهومين في شيء واحد ثم يبدأون بالتشكيك في سلامتهم العقلية. لكن القواعد ليست غامضة إلى هذه الدرجة عندما تفصل الأدوار.

allowedRoutes: قرّر أولًا من يحق له الارتباط

أكثر شكل مقيّد من الـ listeners يبدو هكذا:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: shared-gateway
  namespace: infra
spec:
  gatewayClassName: eg
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      allowedRoutes:
        namespaces:
          from: Same

وهذا يعني أن الـ routes الموجودة داخل namespace infra فقط يمكنها الارتباط بهذا الـ listener.

وللسماح لـ namespaces خاصة بفرق محددة بالارتباط، استخدم Selector:

allowedRoutes:
  namespaces:
    from: Selector
    selector:
      matchLabels:
        shared-gateway-access: "true"

هذا النمط مناسب جدًا لفرق المنصة. فأنت لست مضطرًا لإغلاق كل شيء بالكامل، لكنك أيضًا لا تحتاج إلى فتحه عبر from: All وترك الجميع يدخلون من دون قيود. بل يمكنك جعل الـ namespaces "تستحق" حق الارتباط عبر حمل label معين.

💡 allowedRoutes يُضبط لكل listener على حدة، وليس لكل Gateway. ويمكن أن تملك listeners مختلفة سياسات وصول مختلفة.

ReferenceGrant: الـ Namespace الهدف هو من يقرر

إذا كان route أو gateway يريد الإشارة إلى موارد في namespace آخر، فعلى الـ namespace الهدف أن يمنحه الإذن.

على سبيل المثال، يريد HTTPRoute خاص بفريق تطبيق أن يرسل المرور إلى backend موجود في namespace مشترك:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app-route
  namespace: team-a
spec:
  parentRefs:
    - name: shared-gateway
      namespace: infra
      sectionName: https
  rules:
    - backendRefs:
        - name: shared-api
          namespace: shared-services
          port: 8080

هنا يحتاج namespace shared-services إلى ReferenceGrant:

apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-team-a-route
  namespace: shared-services
spec:
  from:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      namespace: team-a
  to:
    - group: ""
      kind: Service

وباللغة البسيطة:

  • يمكن لموارد HTTPRoute الموجودة في team-a
  • أن تشير إلى موارد Service الموجودة في shared-services

لاحظ أن التفويض يُنشأ من الـ namespace الهدف، وليس من المصدر. فالمصدر لا يستطيع فقط أن يعلن رغبته في الوصول، بل صاحب المورد هو من يقرر. وهذا التصميم في الحقيقة مبدئي جدًا، لأن من يملك المورد يجب أن يحدد من يستطيع الوصول إليه.

النموذج الشائع لتقسيم المسؤوليات بين فريق المنصة وفرق التطبيقات

أكثر نموذج شائع للملكية يبدو هكذا:

فريق المنصة يملك

  • GatewayClass
  • Gateway
  • Listener
  • إدارة شهادات TLS
  • سياسات allowedRoutes

فرق التطبيقات تملك

  • HTTPRoute / GRPCRoute داخل namespaces الخاصة بها
  • خدمات الـ backend
  • تعديلات قواعد التوجيه

فوائد هذا التقسيم واضحة:

  • فريق المنصة يحمي حدود التعريض والأمان في ingress
  • فرق التطبيقات لا تزال قادرة على التحرك بسرعة في قواعد مرورها الخاصة
  • لا أحد يحتاج إلى لمس الـ Gateway المشترك فقط من أجل تغيير path

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

نصائح عملية: كيف تتجنب كوارث Multi-Tenant

عادات مفيدة فعلًا:

  • اجعل Shared Gateways تبدأ افتراضيًا بـ allowedRoutes.namespaces.from: Selector
  • يجب أن تكون قواعد تسمية الـ namespaces والـ labels مستقرة، فلا تغيّر team-a إلى allow-gw الشهر القادم
  • كل الإشارات عبر namespaces إلى backends أو Secrets تمر عبر ReferenceGrant
  • يجب أن تدل أسماء الـ routes على الفريق أو الخدمة، مثل team-a-api-route
  • في الـ listeners المشتركة، استخدم دائمًا sectionName لمنع الـ routes من الارتباط بالمدخل الخطأ

هناك مبدأ يستحق الاحتفاظ به:

إذا كان بإمكانك فرض حد على طبقة ingress، فلا تعتمد فقط على التوثيق وعلى أمل أن ينظم الناس أنفسهم.

في الهندسة، العملية أوضح وأقوى من الاتفاق الشفهي. قد يبدو هذا كلام شخص أكبر سنًا مما ينبغي، لكنه مفيد فعلًا.

الخلاصة في سطر واحد

في Gateway API، يتحكم allowedRoutes في "من يمكنه الدخول"، بينما يتحكم ReferenceGrant في "ما الذي يمكنه الوصول إليه بعد الدخول." وحين تفرّق بين هذين الأمرين بوضوح، تصبح سيناريوهات تعدد الـ namespaces وتعدد الفرق أكثر قابلية للإدارة بكثير.

الخطوة التالية

بعد ترتيب الملكية والصلاحيات، تبقى القطعة الأخيرة: عندما لا تأخذ الإعدادات مفعولها، أي status وأي condition وأي command يجب أن تنظر إليه أولًا؟

المقالة التالية تغطي debugging وقراءة الحالات: 👉 دليل الحالة واستكشاف الأخطاء