الخدمات والاكتشاف
الخدمات والاكتشاف
الـ Pods مؤقتة بطبيعتها. يُزيل التحديث المتدحرج (rolling update) الـ Pods القديمة ويُنشئ جديدة بعناوين IP مختلفة تماماً. تُستبدَل حاوية في حلقة تعطّل (crash-loop) خلال ثوانٍ. إن كان كود العميل يُضمّن عنوان IP لـ Pod مباشرةً، فإنه يتعطل فور أن تُعيد Kubernetes جدولة ذلك الـ Pod. تحلّ الخدمات (Services) هذه المشكلة بتوفير عنوان IP افتراضي مستقر (ClusterIP) واسم DNS يوجّه دائماً إلى نقاط النهاية السليمة — بصرف النظر عن عدد الـ Pods أو مكان تشغيلها. يستعرض هذا الدرس كل نوع من أنواع الخدمات، وكيف يُبرمج kube-proxy طبقة البيانات، وكيف يجعل kube-dns الأسماء قابلة للحل داخل الكلاستر.
كائن Endpoint — حلقة الوصل المفقودة
قبل استكشاف أنواع الخدمات، افهم السباكة الداخلية. في كل مرة تُنشئ فيها خدمة، يراقب متحكم Endpoints (جزء من kube-controller-manager) الـ Pods التي تتطابق تسمياتها مع selector الخدمة ويكتب عناوين IP الخاصة بها في كائن Endpoints بالاسم ذاته للخدمة. عند توقف Pod عن الاستعداد أو موتها، يُزال عنوان IP تلقائياً من قائمة Endpoints.
ClusterIP — عنوان IP افتراضي داخلي
ClusterIP هو النوع الافتراضي للخدمة. يُخصّص Kubernetes عنوان IP افتراضياً من النطاق --service-cluster-ip-range (عادةً 10.96.0.0/12). هذا العنوان الافتراضي غير قابل للتوجيه خارج الكلاستر؛ لا وجود له إلا في قواعد iptables (أو جداول IPVS) التي يُبرمجها kube-proxy على كل عقدة. يمكن لأي Pod في الكلاستر الوصول إلى الخدمة عبر ClusterIP أو اسمها في DNS.
يراقب kube-proxy الخدمات ونقاط النهاية ويكتب قواعد iptables من نوع DNAT: الحركة المرورية إلى 10.96.x.x:80 تُوزَّع عشوائياً (DNAT) إلى أحد عناوين IP الـ Pod الحية المُدرجة في Endpoints. في وضع IPVS (المفضّل عند النطاق الواسع) يحدث توازن الحمل ذاته داخل وحدة IPVS في نواة النظام مع خوارزميات جدولة أغنى (round-robin، least-connection، إلخ).
mode: ipvs في KubeProxyConfiguration) عند وجود أكثر من ~1000 خدمة. قواعد iptables تتسع بمعدل O(n) — كل قاعدة جديدة تُضاف إلى سلسلة متنامية. يستخدم IPVS جدول تجزئة ويتوسّع إلى عشرات الآلاف من الخدمات بزمن استجابة ثابت.
kube-dns — حل الأسماء داخل الكلاستر
يعمل CoreDNS (خلف kube-dns) كـ Deployment في مساحة الأسماء kube-system ومكشوف عبر خدمة ClusterIP خاصة به على العنوان الموجود في /etc/resolv.conf على كل Pod (عادةً 10.96.0.10). يُنتج مكوّن kubernetes في CoreDNS سجلات DNS من Kubernetes API:
- خدمة باسم
nginx-svcفي مساحة الأسماءproductionتُحلَّل إلى ClusterIP الخاص بها عبر:nginx-svc.production.svc.cluster.local - من داخل مساحة الأسماء ذاتها، تعمل الأسماء المختصرة:
nginx-svcأوnginx-svc.production. - تحصل عناوين IP للـ Pod الفردية على سجلات من الشكل
10-244-1-23.production.pod.cluster.local— نادراً ما تُستخدم مباشرةً.
NodePort — الكشف على كل العقد
يمتدّ NodePort فوق ClusterIP بفتح منفذ ثابت (النطاق الافتراضي 30000–32767) على كل عقدة في الكلاستر. الحركة المرورية الخارجية الوافدة إلى <any-node-ip>:<nodePort> يُعيد kube-proxy توجيهها إلى الخدمة ثم توزّع على Pod خلفي.
type: LoadBalancer أو متحكم Ingress.
LoadBalancer — الوصول الخارجي السحابي
LoadBalancer مجموعة عليا من NodePort. إضافةً إلى فتح NodePort على كل عقدة، يُرسل إشارةً إلى cloud-controller-manager لدى مزوّد السحابة لتوفير موازن حمل خارجي (AWS NLB، أو GCP Network LB، أو Azure LB) وتوجيهه إلى منافذ العقد. يُكتب عنوان IP أو اسم المضيف الخاص بموازن الحمل المُوفَّر في service.status.loadBalancer.ingress.
LoadBalancer تُنشئ موازن حمل سحابياً واحداً — وهذا مكلف عند النطاق الواسع (لكل NLB تكلفة). عند نطاق Google أو Amazon، تكشف الفرق عشرات الخدمات الدقيقة عبر متحكم Ingress واحد مدعوم بموازن حمل واحد، وتوجّه بقواعد على أساس hostname/path بدلاً من إنشاء موازنات حمل لكل خدمة.
الخدمات Headless — DNS Round-Robin بدون VIP
اضبط clusterIP: None لإنشاء خدمة headless. لا يُخصَّص VIP. بدلاً من ذلك يُعيد CoreDNS عناوين IP للـ Pod الفردية مباشرةً في رد سجل DNS من نوع A. يتلقى العملاء سجلات A متعددة ويجب أن يختاروا بأنفسهم. هذا هو النمط المستخدم مع StatefulSets (قواعد بيانات، Kafka، Zookeeper) حيث لكل Pod هوية مستقرة ويحتاج العملاء للوصول إلى نسخة محددة:
ExternalName — اسم بديل DNS للخدمات الخارجية
يُنشئ ExternalName اسماً بديلاً CNAME داخل الكلاستر لاسم DNS خارجي. لا يملك selector ولا نقاط نهاية — يُعيد CoreDNS ببساطة CNAME. يتيح لك ذلك الإشارة إلى مثيل RDS أو API قديم أو نقطة نهاية SaaS مُدارة بنفس أسلوب تسمية DNS في Kubernetes كأي خدمة داخلية، مما يُسهّل التبديل بين خلفية داخل الكلاستر وأخرى خارجية دون تغيير إعدادات التطبيق:
أوضاع الفشل الإنتاجية التي يجب معرفتها
- نقاط نهاية قديمة بعد انهيار سريع. يُحدّث kube-proxy قواعد iptables بعد أن يُزيل متحكم Endpoints الـ Pod الميت — هناك نافزة قصيرة (عادةً أقل من ثانية) قد تُرسَل فيها الحركة المرورية إلى Pod منتهية. خفّف ذلك بـ hook من نوع
preStopينتظر 2–5 ثوانٍ وعتبة فشل ضيّقة لـreadinessProbe. - مهلة تخزين DNS مؤقتاً. يُخزّن JVM وبعض عملاء Go استجابات DNS لفترة أطول بكثير من مهلة 5 ثوانٍ التي يُعيدها CoreDNS. بعد تغيير نقطة نهاية الخدمة، قد يوجّه العملاء القدامى إلى عناوين IP قديمة لدقائق. اضبط الـ JVM بـ
-Dsun.net.inetaddr.ttl=5وتحقق من إعدادات مهلة DNS لدى العميل. - تأخر مزامنة iptables في kube-proxy. في كلاستر كبير جداً (10 آلاف+ خدمة) مع وضع iptables قد تستغرق المزامنة ثوانٍ. وضع IPVS يُلغي هذا. راقب
sync_proxy_rules_duration_secondsفي مقاييس kube-proxy. - استنفاد نطاق ClusterIP. يُعطي النطاق الافتراضي
/12حوالي مليون عنوان لكن بعض الكلاسترات تُخصّص بإفراط. افحص بـkubectl cluster-info dump | grep service-cluster-ip-range.
targetPort مُسمّى (مثلاً targetPort: http يُشير إلى منفذ مُسمّى في مواصفات الـ Pod) على منفذ رقمي. عند تغيير منفذ الحاوية، يكفي تحديث اسم منفذ مواصفات الـ Pod وينتشر التغيير تلقائياً — دون الحاجة لتحديث كل بيان خدمة يُشير إليه.