دليل تصميم Listener في Envoy Gateway: أنماط Multi-Port و Multi-Hostname و Multi-Ingress
كثير من الناس الذين يتعلمون Gateway API يتعاملون مع Listener وكأنه مجرد "آه، هذا فقط حقل port: 80."
لكن في النشر الحقيقي، لا يكون Listener مجرد دور ثانوي، بل أقرب إلى مركز التحكم في المرور لطبقة ingress كاملة.
الـ listener لا يفتح منفذًا فقط، بل يعرّف:
- أي بروتوكول يقبل
- أي hostname يقبل
- هل سيجري TLS أم لا
- ما أنواع الـ routes المسموح لها بالارتباط
وبمعنى آخر، الـ listener هو عقد ingress. اكتب عقودًا نظيفة، وستصبح الـ routes اللاحقة وملكية الفرق وتعقيد debugging أنظف تلقائيًا.
ابنِ النموذج الذهني الصحيح: Listener واحد = فتحة Ingress واحدة
خذ هذا الـ Gateway:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: eg
spec:
gatewayClassName: eg
listeners:
- name: http
protocol: HTTP
port: 80
- name: https
protocol: HTTPS
port: 443
hostname: app.example.com
tls:
mode: Terminate
certificateRefs:
- name: app-cert
- name: grpc
protocol: HTTPS
port: 8443
hostname: grpc.example.com
tls:
mode: Terminate
certificateRefs:
- name: grpc-certهذا ليس مجرد "Gateway واحد فيه ثلاث فتحات." بل فكّر فيه هكذا:
- listener
http: من أجل HTTP غير المشفر - listener
https: من أجلapp.example.com - listener
grpc: فتحة ingress مخصصة لخدمات gRPC
بمجرد أن تبدأ بالتفكير بهذه الطريقة، ستتضح كثير من قرارات التصميم فورًا. أنت لا تركّب YAML فحسب، بل تصمّم حدود ingress.
ثلاثة أنماط شائعة للتقسيم
النمط 1: التقسيم حسب البروتوكول
الأسلوب الأكثر بداهة:
- HTTP على listener واحد
- HTTPS على listener واحد
- gRPC على listener واحد
- و TCP/TLS passthrough منفصلان وحدهما
هذا يعطي وضوحًا في المسؤولية. حتى لو كان gRPC و web APIs العامة يعملان كلاهما فوق HTTP/2، فإن نماذج الخدمة لديهما مختلفة. والفصل بينهما يمنع تداخل الـ routes بعضها مع بعض.
النمط 2: التقسيم حسب Hostname
إذا كان لديك عدة domains، فهناك نمط شائع يقوم على listener واحد لكل hostname رئيسي:
listeners:
- name: app
hostname: app.example.com
protocol: HTTPS
port: 443
- name: admin
hostname: admin.example.com
protocol: HTTPS
port: 443وهذا مناسب جدًا لـ:
- فصل حوكمة العام عن الإداري
- hostnames المختلفة التي تحتاج شهادات مختلفة
- إبقاء حدود ارتباط الـ routes نظيفة
النمط 3: التقسيم حسب مسؤولية الفريق أو البيئة
في الإعدادات متعددة الفرق، يمكن للـ listeners أن تكون حدودًا للحوكمة:
public-apiمن أجل مرور المنتج الخارجيinternal-apiمن أجل الأنظمة الداخليةpartner-apiمن أجل تكاملات الشركاء
هنا تتجاوز قيمة الـ listener كونه إعدادًا تقنيًا فقط، ليصبح حدًا للصلاحيات والمسؤوليات. وهذا النمط يصنع فرقًا حقيقيًا في الفرق المتوسطة والكبيرة، لأنه يقلل كارثة "Ingress واحد يمكن للجميع أن يربطوا به أي شيء يريدونه."
sectionName و allowedRoutes: حقلان حاسمان
يرتبط الـ route بـ listener محدد عبر parentRefs.sectionName:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: app-route
spec:
parentRefs:
- name: eg
sectionName: https
hostnames:
- "app.example.com"
rules:
- backendRefs:
- name: app-service
port: 8080هذا الـ route سيبحث فقط عن listener اسمه https.
ومن دون تحديد sectionName، يطبّق النظام قواعد للعثور على listeners القابلة للارتباط، لكن في البيئات متعددة الـ listeners يكون التصريح الواضح أكثر أمانًا تقريبًا دائمًا.
أما الحقل الحاسم الآخر فهو allowedRoutes:
listeners:
- name: public
protocol: HTTPS
port: 443
allowedRoutes:
namespaces:
from: Sameوهذا يعني أن الـ routes الموجودة في نفس namespace فقط مع Gateway يمكنها الارتباط.
القيم الشائعة:
Same: نفس الـ namespace فقطAll: أي namespaceSelector: فقط namespaces التي تطابق label selector
إذا كنت تريد أن تكون مقصودًا في حدود المنصة، فهذا الحقل قوي جدًا. فهو يقرر "من يمكنه الارتباط بهذا ingress" على مستوى الـ listener نفسه.
نصيحة عملية: كيف تنظّم Listeners من دون ندم
هناك مبدأ مفيد جدًا:
كل listener يحمل مسؤولية واحدة محددة بوضوح.
على سبيل المثال:
- Listener واحد يخدم مجموعة hostname رئيسية واحدة
- Listener واحد يتعامل مع نوع بروتوكول رئيسي واحد
- Listener واحد مفتوح لنوع Route واحد أو لمجموعة ثابتة من namespaces
لا تحشر كل المرور في listener واحد شامل لكل شيء ثم تأمل أن تنظّم الـ routes نفسها في الأسفل. هذا النمط يبدو فعالًا في البداية، لكن بعد ثلاثة أشهر سيصبح أشبه بدرج الكابلات الغامض الموجود في كل بيت: كل شيء بداخله، ولا شيء يمكن العثور عليه.
اقتراحات محددة:
- يجب أن تكون أسماء الـ listeners دلالية:
https-publicوgrpc-internal - في البيئات متعددة الـ listeners، اربط دائمًا ذلك مع
sectionName - لا تخلط hostnames المهمة داخل listener واحد
- اجعل تقييد
allowedRoutesهو الافتراضي أولًا، ولا تبدأ بـfrom: All
الخلاصة في سطر واحد
تعامل مع Listener على أنه عقد ingress، لا مجرد إعداد منفذ.
فالحدود النظيفة للـ listeners تعني أن الـ routes سترتبط بشكل صحيح، والفرق ستمتلك مناطقها بشكل طبيعي، وسيصبح debugging أقل شبهًا بمطاردة قطة داخل متاهة.
الخطوة التالية
بعد حسم تصميم الـ listeners، يأتي السؤال الشائع التالي: عبر namespaces وفرق مختلفة، من يمكنه إرفاق routes، ومن يمكنه الإشارة إلى ماذا؟
المقالة التالية تغطي الأنماط عبر namespaces المختلفة ونموذج الملكية: 👉 تعدد الـ Namespace وملكية الفرق