الوحدة 4 · أساسيات Kubernetes
الـ Services: عناوين ثابتة لـ Pods متنقّلة
20 دقراءة 4 داكتشف الخطأ
الدرس في لمحة
أعطِ notes-api وقاعدة بياناته أسماء وعناوين ثابتة بالـ Services، واربط port وtargetPort وcontainerPort بشكل صحيح، واختر بين ClusterIP وNodePort وLoadBalancer.
- 1Service لـ notes-api
- 2ثلاثة منافذ، وثلاثة معانٍ
- 3إيجاد الـ Services بالاسم
- 4أنواع الـ Services
- 5الوصول إليه من حاسوبك
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
شغّل kubectl get pods -o wide مرتين، مع rollout بينهما، وستجد كل عنوان IP مختلفًا. الـ Pods تُستبدل باستمرار: إصدارات جديدة، وإعادة جدولة، وتوسّع. كل ما كان يتذكّر عنوان IP لـ Pod صار يشير إلى لا شيء. الـ Service هو الشيء الثابت الذي تشير إليه بدلًا من ذلك.
Service لـ notes-api
apiVersion: v1
kind: Service
metadata:
name: notes-api
labels:
app.kubernetes.io/name: notes-api
spec:
selector:
app.kubernetes.io/name: notes-api
ports:
- name: http
port: 80
targetPort: httpهذا ينشئ عنوان IP افتراضيًا واسم DNS، notes-api، يدومان ما دام الـ Service موجودًا. الاتصالات إليه تُوزَّع على الـ Pods التي يطابقها الـ selector الخاص به، وهو استعلام الـ labels نفسه الذي يستخدمه الـ Deployment. يتتبّع Kubernetes المجموعة الحالية من الـ Pods الجاهزة المطابقة في EndpointSlices ويحدّثها كلما جاءت Pods وذهبت. والعملاء لا يلاحظون شيئًا.
الـ ClusterIP ليس جهازًا، ولا تستمع عليه أي عملية. طبقة الشبكة في كل node (kube-proxy، أو بديل مبني على eBPF في كثير من الـ clusters) تبرمج قواعد تعيد كتابة الحزم المرسلة إلى عنوان IP الخاص بالـ Service ومنفذه لتذهب إلى أحد الـ Pods الجاهزة. لهذا لا تستطيع تنفيذ ping على عنوان IP الخاص بـ Service، ولا داعي للقلق حين لا يجيب: المنافذ المصرَّح بها هي وحدها الموجودة. ولهذا أيضًا لا يكلّف الـ Service شيئًا تقريبًا، فأنشئ واحدًا لكل حمل عمل يستقبل حركة.
ثلاثة منافذ، وثلاثة معانٍ
هنا تعيش معظم أخطاء الـ Services.
containerPortفي مواصفات الـ Pod هو المنفذ الذي تستمع عليه عمليتك: 3000. إنه معلوماتي، مثلEXPOSE، لكن تسميته (name: http) تتيح للكائنات الأخرى الإشارة إليه.targetPortفي الـ Service هو المكان الذي تُسلَّم إليه الحركة على الـ Pod. يجب أن يطابق المكان الذي يستمع عليه التطبيق فعلًا. وهنا يستخدم الاسمhttp، الذي يُحلّ إلى 3000.portفي الـ Service هو ما يتّصل به العملاء: 80.
إيجاد الـ Services بالاسم
يعطي DNS الخاص بالـ cluster كل Service اسمًا بالشكل <service>.<namespace>.svc.cluster.local. من Pod في الـ namespace نفسه يكفي الاسم القصير notes-api؛ ومن namespace آخر استخدم notes-api.notes أو الاسم الكامل.
يحتاج notes-api إلى قاعدة بيانات، فأعطه واحدة بالطريقة نفسها. للتدرّب في kind، شغّل Postgres كـ Deployment بنسخة واحدة مع Service من نوع ClusterIP:
apiVersion: apps/v1
kind: Deployment
metadata:
name: notes-db
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: notes-db
template:
metadata:
labels:
app.kubernetes.io/name: notes-db
spec:
containers:
- name: postgres
image: postgres:17
env:
- name: POSTGRES_USER
value: notes
- name: POSTGRES_PASSWORD
value: notes # moved to a Secret in the next lesson
- name: POSTGRES_DB
value: notes
ports:
- name: postgres
containerPort: 5432
---
apiVersion: v1
kind: Service
metadata:
name: notes-db
spec:
selector:
app.kubernetes.io/name: notes-db
ports:
- name: postgres
port: 5432
targetPort: postgresالمضيف في DATABASE_URL الخاص بـ notes-api صار الآن notes-db، تمامًا كما كان db في Compose. والفاصل --- يفصل بين كائنين في ملف واحد.
أنواع الـ Services
| النوع | يمكن الوصول إليه من | استخدمه لـ |
|---|---|---|
ClusterIP (الافتراضي) |
داخل الـ cluster | الحركة بين الخدمات، مثل notes-api ← notes-db |
NodePort |
عنوان IP لكل node على منفذ ضمن 30000–32767 | الاختبارات السريعة، أو كلبنة بناء لموازنات الأحمال الخارجية |
LoadBalancer |
عنوان موازن أحمال سحابي | عرض خدمة واحدة للعموم على cluster مُدار |
وهناك أيضًا الـ Service من نوع headless، مع clusterIP: None. لا يحصل على عنوان IP افتراضي؛ بل يُرجع اسم الـ DNS الخاص به عناوين IP للـ Pods المفردة. تستخدمه قواعد البيانات والأنظمة العنقودية الأخرى حين يحتاج كل عضو إلى أن يُخاطَب مباشرة. لن تحتاج إليه لـ notes-api.
يُبنى LoadBalancer فوق النوعين الآخرين: ينشئ مزوّد السحابة موازن أحمال يحوّل في العادة إلى منافذ الـ nodes، وهذه تحوّل إلى الـ Service (وبعض الـ controllers يرسل الحركة مباشرة إلى عناوين IP الخاصة بالـ Pods بدلًا من ذلك). واحد لكل خدمة عامة يصبح مكلفًا ومتكرّرًا حين تكون لديك خدمات HTTP كثيرة؛ والدرس 5.4 يغطّي Gateway API، الذي يضع مسارات كثيرة خلف نقطة دخول واحدة.
الوصول إليه من حاسوبك
kubectl apply -f k8s/
kubectl get svc
kubectl get endpointslices -l kubernetes.io/service-name=notes-api
kubectl port-forward svc/notes-api 8080:80
curl localhost:8080/readyzالأمر port-forward يحفر نفقًا من منفذ محلي إلى الـ Service عبر الـ API server. إنه للتشخيص، لا للحركة الحقيقية. إذا لم يسرد الـ EndpointSlice أي عناوين، فالـ Service لا يطابق أي Pods جاهزة: قارن الـ selector بمخرجات kubectl get pods --show-labels، ثم تحقّق من الجاهزية.
مشكلتان صارتا ظاهرتين الآن في الـ YAML: كلمة مرور مكتوبة نصًّا صريحًا، وإعدادات مخلوطة داخل الـ Deployment. الدرس التالي يُخرج الاثنتين.
أهم النقاط
- الـ Service يعطي مجموعة متغيّرة من الـ Pods عنوان IP افتراضيًا واسم DNS ثابتين، ويوزّع الاتصالات على الـ Pods الجاهزة التي يطابقها الـ selector الخاص به.
- الحقل
portهو ما يتّصل به العملاء على الـ Service؛ وtargetPortهو وجهة الحركة على الـ Pod، ويجب أن يطابق المنفذ الذي يستمع عليه الـ container. - داخل الـ cluster، تصل الـ Pods إلى الـ Service باسمه، مثل
notes-dbفي الـ namespace نفسه أوnotes-db.notes.svc.cluster.localمن أي مكان. - استخدم ClusterIP للحركة الداخلية، وLoadBalancer لعرض خدمة واحدة عبر موازن أحمال سحابي، وNodePort للاختبار أساسًا.
- الـ EndpointSlice الفارغ يعني أن الـ Service لا يطابق أي Pods جاهزة؛ تحقّق من الـ selector والجاهزية أولًا.