ينتج StatefulSet على العكس Pods مرقّمة ومستقرة: bd-0، bd-1، bd-2. يحافظ كل Pod على هويته عبر إعادة التشغيل — ضروري لقاعدة بيانات يجب فيها تعيين الأساسية بدقة.
لكن StatefulSet لا يكفي وحده: يتطلّب أن يكون مرتبطاً بخدمة headless يشير إلى اسمها في حقله serviceName.
yaml
kind: StatefulSetspec: serviceName: bd-interne # <-- يشير إلى خدمة headless بالاسم نفسه replicas: 3
تعطي هذه الخدمة headless عندئذ اسماً DNS لكل Pod:
bd-0.bd-interne -> عنوان IP للـ Pod bd-0 فقطbd-1.bd-interne -> عنوان IP للـ Pod bd-1 فقطbd-interne -> عناوين الـ Pods الثلاثة (قائمة)
بلا الكلمة None في spec.clusterIP، لا يوجد أي من هذه الأسماء.
yaml
spec: clusterIP: None # يحوّل الخدمة إلى "headless"
احفظ هذا: للوصول إلى bd-0.bd-interne يلزم شرطان متزامنان — StatefulSet بحقل serviceName: bd-interne، وخدمة headless باسم bd-interne. إن غاب أحدهما، لا يوجد الاسم الفردي.
5. منافذ مسمّاة وخدمات متعددة المنافذ
عندما تعرض خدمة عدة منافذ، يصير كل مدخل مسمّى إلزامياً:
ثلاث صور فقط لازمة: تخدم micro:1.0 خمسة مكوّنات مختلفة (يتغيّر السلوك بـ متغيرات بيئة)، وتعرض metriques:1.0 منفذين، وتعرض portail:1.0 اللوحة.
لوحة التحكم: مؤشر تقدّمك
تستجوب البوابة باستمرار كل مكوّن وتعرض مربّعاً لكل اتصال، يُحدَّث كل 3 ثوانٍ:
المربّع
المعنى
أين تبحث عن الخطأ
أحمر
اسم DNS غير موجود
الخدمة لم تُنشأ، أو اسمها خاطئ
برتقالي
حُلّ الاسم، لكن لا أحد يجيب
الخدمة موجودة، لكن مُنتقيها أو منفذها خاطئ
أخضر
اتصال قائم
خدمتك صحيحة
الهدف النهائي: المربّعات الثمانية خضراء، والعدّاد يعرض 8 / 8.
هذا التمييز أحمر/برتقالي ليس تجميلياً: يخبرك من أي جهة تبحث. أحمر = الخدمة غير موجودة (لا شيء للتصحيح، يلزم كتابتها). برتقالي = الخدمة موجودة لكنها لا تجد Podsها أو تضرب المنفذ الخطأ.
حظر مطلق لتعديل المجلد apps/، وكذلك 01-deployments.yaml و 02-statefulset-bd.yaml.
(كل الصعوبة في التكيّف مع القائم: هذا تماماً وضع منصب عمل حقيقي.)
تنشئ وتعدّل فقط ملفات موجودة في k8s/services/.
لا عنوان IP ثابتاً. يجب أن يرتكز كل شيء على أسماء DNS ومُنتقيات الوسوم.
يجب أن تحدّد بنفسك نوع كل خدمة: لا شيء يخبرك إن كان ClusterIP أو NodePort أو LoadBalancer أو خدمة headless أو ExternalName. هذا قلب التقييم.
أسماء الخدمات مفروضة: تستدعيها الشفرة التطبيقية كما هي. اسم خاطئ يعطي مربّعاً أحمر.
تعمل على Kubernetes المدمج في Docker Desktop (Settings → Kubernetes → Enable Kubernetes).
التحضير
المتطلبات السابقة — تُتحقَّق مرة واحدة
Docker Desktop شغّال (أيقونة خضراء في شريط النظام).
Kubernetes مفعَّل في Docker Desktop: Settings → Kubernetes → Enable Kubernetes → Apply & Restart. بلا هذا المربّع، لن يعمل أي أمر kubectl.
لدى Docker Desktop 4 غيغابايت RAM على الأقل: Settings → Resources → Memory ≥ 4 GB. يشغّل هذا المشروع 14 Pod؛ على 2 غيغابايت تختنق الآلة وتبقى Pods في Pending.
لديك إنترنت (للمهمة 5 ولتنزيل الصورة busybox).
تسلسل التشغيل
powershell
# 0) الوضع على العنقود الصحيح (ضروري إن سبق استخدام minikube أو kind)kubectl config use-context docker-desktopkubectl get nodes # يجب أن يعرض docker-desktop Ready# 1) بناء الصور الثلاثdocker build -t micro:1.0 ./apps/microdocker build -t metriques:1.0 ./apps/metriquesdocker build -t portail:1.0 ./apps/portail# 2) نشر القاعدة المقدَّمة (Pods، ولا أي خدمة)kubectl apply -f k8s/01-deployments.yamlkubectl apply -f k8s/02-statefulset-bd.yaml# 3) انتظار أن تكون كل الـ Pods Ready (حوالي 30 ث)kubectl wait --for=condition=ready pod --all --timeout=180s# 4) ملاحظة وضع الانطلاقkubectl get pods # 14 Pod، كلها Runningkubectl get svc # "kubernetes" فقط: لا خدمة من خدماتك
في هذه المرحلة: كل الـ Pods تعمل ولا شيء يتواصل. هذه نقطة الانطلاق الطبيعية.
تحذيران تقنيان يُعرفان من الآن — ليسا خطأ منك:
تحذير Endpoints is deprecated in v1.33+ — يعرض Kubernetes هذه الرسالة منهجياً عند كل kubectl get endpoints. الأمر يعمل دائماً بشكل كامل، تجاهل التحذير. الواجهة الجديدة المعادلة هي kubectl get endpointslices، لكن كل أوامر هذا التدريب تستخدم عمداً endpoints، أوضح للتعلّم.
قد تستغرق اللوحة 10 إلى 15 ثانية للظهور أول مرة: تختبر البوابة 8 اتصالات شبكة في كل عرض، لكل منها مهلة انتظار 1.5 ث. عندما لا يعمل شيء بعد، تنتظر كل مهلة قبل عرض أحمر أو برتقالي. بعد صحة الخدمات، ينخفض زمن الاستجابة إلى بضع مئات من الميلي ثانية.
سؤال تطرحه فوراً: كيف سترى اللوحة أصلاً، بينما لا باب دخول موجود بعد؟
عوّامة نجاة: يعمل kubectl port-forwardبلا أي خدمة، مباشرة على Pod.
powershell
kubectl port-forward deploy/portail 5000:5000
ثم افتح http://localhost:5000. سترى اللوحة حمراء بالكامل، بمربّع «وصول خارجي» برتقالي (طبيعي: لم تمر عبر المنفذ 30500).
المهام
المهمة 1 — جعل البوابة تتحدث إلى واجهة المنتجات (15 نقطة)
تستدعي البوابة http://api-produits على المنفذ 80. تستمع Pods الواجهة على المنفذ 8000 وتحمل الوسم app: api-produits.
المطلوب تحديده:نوع الخدمة، والمُنتقي، وكذلك port و targetPort.
التحقق:
powershell
kubectl apply -f k8s/services/01-api-produits.yamlkubectl get svc api-produitskubectl get endpoints api-produits # يجب أن يسرد 3 عناوين IP
يصير مربّع واجهة المنتجات أخضر.
المهمة 2 — فتح باب الدخول (15 نقطة)
يجب أن تكون اللوحة متاحة من متصفحك بالعنوان الدقيق http://localhost:30500. تستمع Pods البوابة على المنفذ 5000.
الملف المطلوب إكماله:k8s/services/02-portail.yaml
المطلوب تحديده: أي نوع خدمة يعرض تطبيقاً خارج العنقود على منفذ ثابت للآلة؟ ما نطاق المنافذ المسموح لهذا الحقل؟
التحقق:
powershell
kubectl get svc portail # يجب أن يعرض PORT(S) القيمة 80:30500/TCPstart http://localhost:30500
يصير مربّع الوصول الخارجي أخضر.
سؤال يُعالَج في التقرير: نوع خدمة آخر كان سيجعل البوابة متاحة من المتصفح أيضاً على Docker Desktop. أي نوع؟ وما الفرق الذي سيحدثه ذلك في الإنتاج في السحابة؟
المهمة 3 — إعطاء هوية لكل قاعدة بيانات (20 نقطة)
يوفّر StatefulSet bd3 نسخ. يجب أن تصل البوابة بدقة إلى الأولى (الأساسية)، بالعنوان:
bd-0.bd-interne
تحمل Pods القاعدة الوسم app: bd وتستمع على المنفذ 5432.
المطلوب تحديده: أي نوع خدمة يعطي اسماً DNS فردياً لكل Pod، بدل عنوان IP افتراضي واحد؟ أي حقل يلزم كتابته، وبأي قيمة خاصة؟
التحقق:
powershell
# 1) من Pod أدوات، الوصول مباشرة إلى bd-0:kubectl run test --rm -i --restart=Never --image=busybox:1.36 -- wget -qO- http://bd-0.bd-interne:5432/ping# يجب أن يجيب: {"pod":"bd-0","port":5432,"service":"base-de-donnees"}# 2) التحقق من مدخلات DNS بالاسم المؤهَّل بالكامل# (لا يطبّق busybox nslookup نطاقات البحث، يلزم إعطاء FQDN):kubectl run test --rm -i --restart=Never --image=busybox:1.36 -- nslookup bd-0.bd-interne.default.svc.cluster.local# يجب أن يعيد عنواناً واحداً (عنوان الـ Pod bd-0)kubectl run test --rm -i --restart=Never --image=busybox:1.36 -- nslookup bd-interne.default.svc.cluster.local# يجب أن يعيد ثلاثة عناوين (واحد لكل Pod في StatefulSet)
فخ لا يُفوَّت
افحص الحقل serviceName لـ StatefulSet المقدَّم. يجب أن يطابق اسم خدمتك ذلك تماماً، وإلا لن تُنشأ الأسماء الفردية للـ Pods أبداً.
فخ تقني (busybox)
لا يعمل nslookup nom-court من Pod busybox لأن محلّله لا يستخدم search domains في /etc/resolv.conf. من Pod تطبيقي حقيقي (مثل portail)، على العكس، يحل bd-0.bd-interneبشكل كامل. استخدم إذن wget لاختبار السلسلة التطبيقية الحقيقية، و FQDN لرفع أي لبس DNS.
المطلوب تحديده: كيف تصرّح بـ عدة منافذ على خدمة؟ أي قيد يصير عندئذ إلزامياً لكل مدخل؟ وكيف تجعل targetPort يشير إلى منفذ الحاوية باسمه بدل رقمه، لكي تبقى الخدمة صالحة حتى إن تغيّر الرقم؟
التحقق:
powershell
kubectl describe svc metriques # يجب أن يظهر المنفذان
المهمة 5 — إعطاء اسم داخلي لخدمة خارجية (10 نقاط)
يجب أن تصل البوابة إلى خدمة دفع مستضافة خارج العنقود، لكن الشفرة تستدعي اسماً داخلياً: paiement-externe. يجب أن يحيل هذا الاسم نحو example.com.
المطلوب تحديده: أي نوع خدمة ينشئ مجرد اسم مستعار DNS نحو اسم خارجي، بلا مُنتقي وبلا أي Pod؟
التحقق:
powershell
kubectl run test --rm -i --restart=Never --image=busybox:1.36 -- nslookup paiement-externe.default.svc.cluster.local# يجب أن يعرض:# paiement-externe.default.svc.cluster.local canonical name = example.com# Name: example.com# Address: <IP publique>
تتطلّب هذه المهمة أن يستطيع العنقود حل الأسماء العامة. إن لم يكن لديك أي وصول إنترنت، استبدل الهدف بـ api-produits.default.svc.cluster.local وأشر إلى ذلك في تقريرك.
المهمة 6 — التحقيق: إصلاح ثلاث خدمات معيبة (20 نقطة)
يحتوي المجلد k8s/services/06-casses/ على ثلاث خدمات مكتوبة مسبقاً… لا تعمل. لكل منها خطأ واحد، وهذه الأخطاء الثلاثة الأكثر شيوعاً في المؤسسات.
powershell
kubectl apply -f k8s/services/06-casses/
الملف
العَرَض الملحوظ
casse-1.yaml
الخدمة موجودة، لكن يعيد kubectl get endpoints القيمة <none>
casse-2.yaml
Endpoints ممتلئة فعلاً، لكن كل اتصال مرفوض
casse-3.yaml
تبدو الخدمة مثالية، لكن البوابة لا تصل إليها أبداً
لكل من الحالات الثلاث، يجب أن يحتوي تقريرك:
أمر التشخيص الذي وضعك على الأثر؛
السبب الدقيق للعطل؛
التصحيح المطبَّق؛
الدليل أن الاتصال يعمل (مربّع أخضر + مخرجات أمر).
طريقة موصى بها: تصرّف كمحقق. kubectl describe svc، kubectl get endpoints، kubectl get pods --show-labels، ثم قارن سطراً سطراً الخدمة والـ Pods. الفرق بين «Endpoints فارغة» و «اتصال مرفوض» يخبرك سلفاً من أي جهة تبحث.
المهمة 7 — مكافأة: القفزة الكبيرة (5 نقاط)
حسب اختيارك، واحد فقط يكفي:
أ) جعل العميل نفسه يُخدَم دائماً من الـ Pod نفسه لواجهة المنتجات. (تلميح: حقل في الخدمة يتيح «التصاقاً» قائماً على عنوان IP للعميل.)
ب) إنشاء خدمة بلا مُنتقي تشير إلى عنوان IP خارجي ثابت، بكتابة أنت Endpointsها.
ج) كتابة خدمة من النوع LoadBalancer للبوابة، ثم شرح ما يصير إليه EXTERNAL-IP على Docker Desktop، وما كان سيصير إليه على AWS.
التحقق التلقائي
يعطيك مخطوط درجتك في أي لحظة:
powershell
.\outils\valider.ps1
إن رفض PowerShell تنفيذ المخطوط برسالة من نوع l'exécution de scripts est désactivée sur ce système، تجاوز القيد لهذا الأمر وحده:
لا يعطي المخطوط أي حل: يشير فقط إلى ما يفشل وأين تنظر.
المخرجات
المجلد k8s/services/ كاملاً: خدماتك الخمس المكتوبة والخدمات الثلاث المصلحة.
ملف RAPPORT.md يحتوي:
لـ كل خدمة: النوع المختار وتبرير بجملتين («لماذا هذا وليس آخر»)؛
التحقيق الكامل للمهمة 6 (أمر → سبب → تصحيح → دليل)؛
لقطة للوحة تعرض 8 / 8؛
لقطة من kubectl get svc تُظهر كل خدماتك وأنواعها؛
إجاباتك على أسئلة التفكير.
المخرجات النهائية لـ .\outils\valider.ps1.
أسئلة للتفكير
لماذا لم يكن ممكناً مطلقاً أن يعمل التطبيق بلا خدمات، بينما كانت كل الـ Pods بالحالة Running؟
ما الفرق الملموس بين مربّع أحمر ومربّع برتقالي؟ ماذا يعلّمك كل منهما عن مكان الخطأ؟
لماذا يجب أن تكون خدمة قاعدة البيانات headless، بينما تكفي خدمة عادية لواجهة المنتجات؟
ماذا تحتوي قائمة Endpoints بالضبط، ومن يحدّثها؟ ماذا يحدث عندما يصير Pod بالحالة NotReady؟
تحذف Podاً من واجهة المنتجات؛ يعيد Kubernetes إنشاء واحد بـ عنوان IP مختلف. لماذا تستمر البوابة في العمل بلا أي تعديل؟
في الإنتاج، هل تعرض عشرة تطبيقات بعشر خدمات من النوع LoadBalancer؟ برّر، واقترح بديلاً.
السلم
العنصر
النقاط
المهمة 1 — خدمة داخلية واكتشاف DNS
15
المهمة 2 — عرض خارجي على المنفذ 30500
15
المهمة 3 — خدمة headless وهويات مستقرة
20
المهمة 4 — تعدد المنافذ ومنافذ مسمّاة
15
المهمة 5 — اسم مستعار نحو خدمة خارجية
10
المهمة 6 — تشخيص وإصلاح (3 أعطال)
20
جودة التقرير وتبرير الاختيارات
5
مكافأة — المهمة 7
+5
المجموع
100 (+5)
عقوبات: −10 لكل تعديل لملف محظور (apps/، 01-deployments.yaml، 02-statefulset-bd.yaml)؛ −5 لكل عنوان IP مرمَّز ثابتاً.
معايير النجاح
المعيار
المتوقع
اللوحة
8 / 8 مربّعات خضراء
أنواع الخدمات
كل منها ملائم لاستخدامه ومبرَّر
DNS فردي
يُحل bd-0.bd-interne نحو Pod واحد
تعدد المنافذ
المنفذان ظاهران، targetPort مشار إليه بالاسم
التحقيق
الأعطال الثلاثة مُحدَّدة ومشروحة ومصحَّحة
المرونة
بعد حذف Pod، تستمر البوابة في العمل
لا IP ثابتاً
أسماء DNS ومُنتقيات وسوم فقط
صندوق الأدوات
لا حلول هنا — مسارات فقط.
powershell
kubectl get svc # أنواع، IP، منافذkubectl describe svc <nom> # تفاصيل + Endpointskubectl get endpoints <nom> # من يوجد خلف الخدمة؟kubectl get pods --show-labels # الوسوم الفعلية للـ Podskubectl get pods -l app=<valeur> # اختبار مُنتقٍkubectl port-forward deploy/portail 5000:5000 # الوصول إلى Pod بلا خدمةkubectl run test --rm -i --restart=Never --image=busybox:1.36 -- wget -qO- http://<nom>/pingkubectl run test --rm -i --restart=Never --image=busybox:1.36 -- nslookup <nom>.default.svc.cluster.localkubectl logs -l app=portail --tail=30 # ما تفشل البوابة في الوصول إليهkubectl delete svc <nom> # البدء من الصفر على خدمة
فخ يُعرَف مع kubectl run test: إن سلسلة عدة أوامر بسرعة، قد لا يُحذف الـ Pod السابق في الوقت فيحصل:
Error from server (AlreadyExists): pods "test" already exists
حلان: غيّر الاسم (test1، test2…) في كل أمر، أو نظّف قبل:
powershell
kubectl delete pod test --ignore-not-found; kubectl run test --rm -i --restart=Never ...
الأسئلة الثلاثة التي تفك 90% من الحالات:
هل الخدمة موجودة، بالاسم الصحيح؟ (وإلا ← مربّع أحمر: لا شيء للتصحيح، يلزم كتابتها)
هل Endpoints ممتلئة؟ (فارغة ← المُنتقي لا يطابق أي وسم Pod)
هل يطابق targetPort المنفذ الذي تستمع عليه الحاوية فعلاً؟ (وإلا ← اتصال مرفوض)
الملحق A — التطبيقات
لا تعدّل أيًا من هذه الملفات. انسخها كما هي إلى المسارات المشار إليها.
A.1 — الخدمة المصغّرة العامة
هذا التطبيق الوحيد يخدم خمسة مكوّنات (api-produits وapi-commandes وcache وnotifications وbd). يُحدَّد اسمه ومنفذه عبر متغيرات بيئة.
الملف : apps/micro/app.py
python
"""Micro-service generique de demonstration.La meme image sert a plusieurs composants : le nom et le port d'ecoute sontfournis par des variables d'environnement (APP_NAME, PORT).Chaque reponse contient le nom du Pod, ce qui rend visible la repartitionde charge realisee par un Service."""import osimport socketfrom flask import Flask, jsonifyapp = Flask(__name__)NOM = os.environ.get("APP_NAME", "micro")PORT = int(os.environ.get("PORT", "8000"))@app.route("/")@app.route("/ping")def ping(): return jsonify(service=NOM, pod=socket.gethostname(), port=PORT)@app.route("/health")def health(): return "OK", 200if __name__ == "__main__": app.run(host="0.0.0.0", port=PORT)
# ---------------------------------------------------------------------------# LA BASE DE DONNEES (3 repliques) — FOURNI, NE PAS MODIFIER## ATTENTION : le champ serviceName ci-dessous impose le NOM du Service que# vous devrez ecrire pour que bd-0, bd-1 et bd-2 obtiennent chacun un nom DNS.# ---------------------------------------------------------------------------apiVersion: apps/v1kind: StatefulSetmetadata: name: bdspec: serviceName: bd-interne # <-- lisez bien cette ligne replicas: 3 selector: matchLabels: app: bd template: metadata: labels: app: bd spec: containers: - name: micro image: micro:1.0 imagePullPolicy: IfNotPresent env: - { name: APP_NAME, value: "base-de-donnees" } - { name: PORT, value: "5432" } ports: - containerPort: 5432 readinessProbe: httpGet: { path: /health, port: 5432 } initialDelaySeconds: 3 periodSeconds: 5
الملحق C — هياكل الخدمات المطلوب إكمالها
انسخ هذه الملفات الخمسة، ثم استبدل كل TODO بالقيمة الصحيحة.
الأسطر المسبوقة بـ # ? هي أسئلة يجب حسمها: عليك أن تقرّر إن كان يلزم إضافة السطر أو تعديله أو حذفه.
الملف : k8s/services/01-api-produits.yaml
yaml
# MISSION 1 — Rendre l'API produits joignable depuis le portail.## Le portail appelle : http://api-produits (donc le port 80)# Les Pods ecoutent sur : 8000# Les Pods portent le label : app: api-produits## ? Quel type de Service pour une communication INTERNE au cluster ?apiVersion: v1kind: Servicemetadata: name: api-produits # nom IMPOSE : ne pas changerspec: type: TODO selector: TODO: TODO ports: - port: TODO # le port par lequel les clients appellent targetPort: TODO # le port reellement ecoute par le conteneur
الملف : k8s/services/02-portail.yaml
yaml
# MISSION 2 — Rendre le tableau de bord accessible depuis le navigateur,# a l'adresse exacte : http://localhost:30500## Les Pods ecoutent sur : 5000# Les Pods portent le label : app: portail## ? Quel type de Service ouvre un port sur la MACHINE ?# ? Quelle est la plage autorisee pour ce port ?# ? Quel champ supplementaire faut-il ajouter pour imposer le port 30500 ?apiVersion: v1kind: Servicemetadata: name: portail # nom IMPOSE : ne pas changerspec: type: TODO selector: TODO: TODO ports: - port: TODO targetPort: TODO # ? une ligne manque ici
الملف : k8s/services/03-bd-interne.yaml
yaml
# MISSION 3 — Donner un nom DNS INDIVIDUEL a chaque replique de la base,# afin de pouvoir joindre precisement : bd-0.bd-interne## Les Pods ecoutent sur : 5432# Les Pods portent le label : app: bd## ? Quel type de Service ne possede PAS d'IP virtuelle unique ?# ? Quel champ, avec quelle valeur tres particuliere, produit cet effet ?# ? Le nom ci-dessous doit correspondre a quel champ du StatefulSet ?apiVersion: v1kind: Servicemetadata: name: bd-interne # nom IMPOSE : ne pas changerspec: # ? une ligne essentielle manque ici selector: TODO: TODO ports: - port: TODO targetPort: TODO
الملف : k8s/services/04-metriques.yaml
yaml
# MISSION 4 — Exposer DEUX ports sur un seul et meme Service.## Le portail appelle : http://metriques (port 80)# et : http://metriques:9090/metrics## Les Pods ecoutent sur : 8080 (port nomme "web") et 9090 (port nomme "prom")# Les Pods portent le label : app: metriques## ? Quelle contrainte devient OBLIGATOIRE des qu'un Service expose plusieurs ports ?# ? Comment faire pointer targetPort vers un port du conteneur PAR SON NOM ?apiVersion: v1kind: Servicemetadata: name: metriques # nom IMPOSE : ne pas changerspec: type: TODO selector: TODO: TODO ports: - TODO: TODO # ? un champ obligatoire manque sur chaque entree port: TODO targetPort: TODO - TODO: TODO port: TODO targetPort: TODO
الملف : k8s/services/05-paiement-externe.yaml
yaml
# MISSION 5 — Faire pointer un nom INTERNE vers un service EXTERNE.## Le portail utilise le nom : paiement-externe# Ce nom doit renvoyer vers : example.com## ? Quel type de Service cree un simple alias DNS (CNAME) ?# ? Ce type possede-t-il un selecteur ? des ports ? des Pods ?apiVersion: v1kind: Servicemetadata: name: paiement-externe # nom IMPOSE : ne pas changerspec: type: TODO TODO: TODO # ? le champ qui indique la cible externe
الملحق D — الخدمات الثلاث المعطّلة
انسخ هذه الملفات الثلاثة كما هي، طبّقها، ثم شخّص وصحّح.
كل ملف يحتوي على خطأ واحد بالضبط. لا تعِد كتابة الملف من الصفر: اعثر على العيب.
الملف : k8s/services/06-casses/casse-1.yaml
yaml
# PANNE 1# Symptome : le Service existe, mais "kubectl get endpoints api-commandes"# renvoie <none>. Le portail affiche une tuile ORANGE.apiVersion: v1kind: Servicemetadata: name: api-commandesspec: type: ClusterIP selector: app: api-commande ports: - port: 80 targetPort: 8000
الملف : k8s/services/06-casses/casse-2.yaml
yaml
# PANNE 2# Symptome : "kubectl get endpoints cache" affiche bien une adresse IP,# mais toute connexion echoue. Le portail affiche une tuile ORANGE.apiVersion: v1kind: Servicemetadata: name: cachespec: type: ClusterIP selector: app: cache ports: - port: 80 targetPort: 6380
الملف : k8s/services/06-casses/casse-3.yaml
yaml
# PANNE 3# Symptome : ce Service semble parfait (type correct, selecteur correct,# Endpoints remplis, ports coherents)... et pourtant le portail# affiche une tuile ROUGE et ne le joint JAMAIS.apiVersion: v1kind: Servicemetadata: name: notificationspec: type: ClusterIP selector: app: notifications ports: - port: 80 targetPort: 7000
الملحق E — سكربت التحقق
الملف : outils/valider.ps1
powershell
# ---------------------------------------------------------------------------# Script de validation — donne un score, JAMAIS la solution.# Utilisation : .\outils\valider.ps1# ---------------------------------------------------------------------------$total = 0function Existe($nom) { kubectl get svc $nom -o name 2>$null | Out-Null return $LASTEXITCODE -eq 0}function Afficher($libelle, $points, $max, $note) { $etat = if ($points -eq $max) { "[OK] " } else { "[ECHEC] " } $ligne = "{0} {1} {2}/{3}" -f $etat, $libelle.PadRight(34, '.'), $points, $max if ($note) { $ligne += " -> $note" } Write-Host $ligne}Write-Host ""Write-Host "=== VALIDATION — Mission : retablir les communications ===" -ForegroundColor CyanWrite-Host ""# --- Mission 1 : api-produits ---------------------------------------------$p = 0; $note = ""if (-not (Existe "api-produits")) { $note = "Service api-produits introuvable" }else { $eps = (kubectl get endpoints api-produits -o jsonpath="{.subsets[*].addresses[*].ip}" 2>$null) $tp = (kubectl get svc api-produits -o jsonpath="{.spec.ports[0].targetPort}" 2>$null) if (-not $eps) { $note = "Endpoints vides : le selecteur ne correspond a aucun Pod" } elseif ("$tp" -ne "8000") { $note = "targetPort ne correspond pas au port ecoute" } else { $p = 15 }}Afficher "Mission 1 - api-produits" $p 15 $note; $total += $p# --- Mission 2 : portail ---------------------------------------------------$p = 0; $note = ""if (-not (Existe "portail")) { $note = "Service portail introuvable" }else { $type = (kubectl get svc portail -o jsonpath="{.spec.type}" 2>$null) $np = (kubectl get svc portail -o jsonpath="{.spec.ports[0].nodePort}" 2>$null) if ("$np" -ne "30500") { $note = "le port expose sur la machine doit etre 30500 (actuel : '$np')" } elseif ($type -notin @("NodePort", "LoadBalancer")) { $note = "type inadapte a un acces externe" } else { $p = 15 }}Afficher "Mission 2 - portail" $p 15 $note; $total += $p# --- Mission 3 : bd-interne (headless) -------------------------------------$p = 0; $note = ""if (-not (Existe "bd-interne")) { $note = "Service bd-interne introuvable (verifiez serviceName du StatefulSet)" }else { $cip = (kubectl get svc bd-interne -o jsonpath="{.spec.clusterIP}" 2>$null) $eps = (kubectl get endpoints bd-interne -o jsonpath="{.subsets[*].addresses[*].ip}" 2>$null) if ("$cip" -ne "None") { $note = "ce Service ne doit PAS avoir d'IP virtuelle" } elseif (-not $eps) { $note = "Endpoints vides : verifiez le selecteur" } else { $p = 20 }}Afficher "Mission 3 - bd-interne" $p 20 $note; $total += $p# --- Mission 4 : metriques (multi-port) ------------------------------------$p = 0; $note = ""if (-not (Existe "metriques")) { $note = "Service metriques introuvable" }else { $ports = (kubectl get svc metriques -o jsonpath="{.spec.ports[*].port}" 2>$null) $noms = (kubectl get svc metriques -o jsonpath="{.spec.ports[*].name}" 2>$null) $cible = (kubectl get svc metriques -o jsonpath="{.spec.ports[*].targetPort}" 2>$null) $liste = ($ports -split '\s+') | Where-Object { $_ } if ($liste.Count -lt 2) { $note = "il manque un port : deux sont attendus (80 et 9090)" } elseif (-not $noms) { $note = "chaque port doit porter un nom lorsqu'il y en a plusieurs" } elseif ($cible -match '^\s*\d+(\s+\d+)*\s*$') { $note = "targetPort doit referencer les ports PAR LEUR NOM" } else { $p = 15 }}Afficher "Mission 4 - metriques" $p 15 $note; $total += $p# --- Mission 5 : paiement-externe -------------------------------------------$p = 0; $note = ""if (-not (Existe "paiement-externe")) { $note = "Service paiement-externe introuvable" }else { $type = (kubectl get svc paiement-externe -o jsonpath="{.spec.type}" 2>$null) $cible = (kubectl get svc paiement-externe -o jsonpath="{.spec.externalName}" 2>$null) if ("$type" -ne "ExternalName") { $note = "ce n'est pas le type attendu pour un alias DNS" } elseif (-not $cible) { $note = "la cible externe n'est pas renseignee" } else { $p = 10 }}Afficher "Mission 5 - paiement-externe" $p 10 $note; $total += $p# --- Mission 6 : les trois reparations --------------------------------------$p = 0; $notes = @()foreach ($cas in @( @{ nom = "api-commandes"; port = "8000" }, @{ nom = "cache"; port = "6379" }, @{ nom = "notifications"; port = "7000" })) { if (-not (Existe $cas.nom)) { $notes += "$($cas.nom) : Service introuvable"; continue } $eps = (kubectl get endpoints $cas.nom -o jsonpath="{.subsets[*].addresses[*].ip}" 2>$null) $tp = (kubectl get svc $cas.nom -o jsonpath="{.spec.ports[0].targetPort}" 2>$null) if (-not $eps) { $notes += "$($cas.nom) : Endpoints vides" } elseif ("$tp" -ne $cas.port) { $notes += "$($cas.nom) : aucun Pod ne repond sur ce port" } else { $p += 7 }}if ($p -gt 20) { $p = 20 }Afficher "Mission 6 - reparations" $p 20 ($notes -join " | "); $total += $pWrite-Host ""$couleur = if ($total -ge 90) { "Green" } elseif ($total -ge 50) { "Yellow" } else { "Red" }Write-Host ("SCORE AUTOMATIQUE : {0} / 95" -f $total) -ForegroundColor $couleurWrite-Host " (+5 pour la qualite du rapport, +5 de bonus : evalues manuellement)" -ForegroundColor DarkGrayWrite-Host ""Write-Host "Rappel : le tableau de bord doit afficher 8 / 8 sur http://localhost:30500" -ForegroundColor DarkGrayWrite-Host ""
دورة من إعداد د. هيثم رحومة — تطوير ونشر حلول البيانات