Project 12 — Kubernetes Services · Level intermediate → advanced · Estimated duration: 3 to 5 h
All the application code is provided in the annexes of this document. Your work: write, determine, and complete the Services that are missing — that is, make a system communicate that, as it stands, is completely silent.
A team deployed a small online-commerce platform on Kubernetes. The images are built, the Deployments and the StatefulSet are running, all Pods are Running.
And yet, nothing works.
The portal displays a dashboard that is entirely red: it cannot reach any component. The database is unreachable. The cache is invisible. No page is reachable from the browser.
The reason is simple: the person who was supposed to write the Services left without delivering them.
Fundamental reminder that this project will make you live: running Pods do not make an application. Without Services, they are isolated islands, with no stable address or name, unable to find one another.
Your mission: restore all communications, only by writing the right Services.
Essential concepts before you start
This section is a self-contained mini-manual: it contains all the vocabulary needed for the missions. Read it once, come back when a word escapes you.
1. The problem a Service solves
A Pod is mortal: Kubernetes can delete it, move it, recreate it — and its new IP will be different. You therefore never connect to a Pod by its IP.
A Service is a stable object — name, IP, port — that follows the Pods wherever they go. The mechanism is simple:
What links a Service to “its” Pods is the label selector:
yaml
spec: selector: app: api-produits # every Pod carrying THIS label is reached
The list of matching Pods forms the Endpoints object — that is your X-ray of the Service.
powershell
kubectl get endpoints api-produits# api-produits 10.244.0.3:8000,10.244.0.4:8000,10.244.0.5:8000
If Endpoints is empty, the selector matches no Pod: that is almost always a typo in a label.
2. The five Service types (the only ones you need)
A ClusterIP without a virtual IP: returns the list of Pod IPs, plus a DNS name per Pod
No
bd-interne
ExternalName
DNS alias to an external name. No Pod, no selector.
No
paiement-externe
Important point: a Service that “does not work” is almost never a type problem. It is almost always selector or port.
3. Internal DNS: the rules that look like magic
In the cluster, CoreDNS automatically builds names according to fixed rules:
You call
CoreDNS resolves to
api-produits
the api-produits Service of the same namespace
api-produits.default
the api-produits Service of the default namespace
api-produits.default.svc.cluster.local
long, fully qualified form
Capital corollary: the Service name is the name the application calls. A Service named notification does not answer http://notifications. That trap is at the heart of mission 6.
4. StatefulSet and headless Service: the pair
A Deployment treats its replicas as interchangeable twins (web-abc123-x7k9, web-abc123-p2m1…). Perfect for stateless web.
A StatefulSet instead produces numbered, stable Pods: bd-0, bd-1, bd-2. Each Pod keeps its identity across restarts — indispensable for a database where you must designate precisely the primary.
But a StatefulSet is not enough alone: it must be associated with a headless Service whose name it indicates in its serviceName field.
yaml
kind: StatefulSetspec: serviceName: bd-interne # <-- points to a headless Service of the same name replicas: 3
That headless Service then gives one DNS name per Pod:
bd-0.bd-interne -> IP of Pod bd-0 ONLYbd-1.bd-interne -> IP of Pod bd-1 ONLYbd-interne -> IPs of the three Pods (list)
Without the word None in spec.clusterIP, none of these names exist.
yaml
spec: clusterIP: None # turns the Service into "headless"
Remember this: to reach bd-0.bd-interne, you need two simultaneous conditions — a StatefulSet whose serviceName: bd-interne, and a headless Service named bd-interne. If either is missing, the individual name does not exist.
5. Named ports and multi-port Services
When a Service exposes several ports, each entry necessarily becomes named:
Usefulness: your code keeps the same internal name (paiement-externe) whether it is a service in the cluster, an external SaaS, or an API move. You change the Service, not the code.
7. The three questions that unlock 90 % of outages
Every time a Service does not work, ask these three questions in this order:
That is exactly the method of mission 6.
What you will be able to do at the end
Distinguish the five Service types and know when to use each.
Use kubectl get svc, kubectl describe svc, kubectl get endpoints as three complementary tools.
Pair a StatefulSet correctly with a headless Service.
Write a clean multi-port Service, with targetPort referenced by name.
Diagnose the three most frequent outages in companies (wrong label, wrong port, wrong name).
The architecture to bring online
Seven components are already running. None is reachable.
Component
Container port(s)
Pod labels
Controller
portail
5000
app: portail
Deployment (1 replica)
api-produits
8000
app: api-produits
Deployment (3 replicas)
api-commandes
8000
app: api-commandes
Deployment (2 replicas)
cache
6379
app: cache
Deployment (1 replica)
notifications
7000
app: notifications
Deployment (2 replicas)
metriques
8080 (named web) and 9090 (named prom)
app: metriques
Deployment (2 replicas)
bd
5432
app: bd
StatefulSet (3 replicas)
File layout
Create exactly this tree, by copying the content of the annexes. Each annex indicates the exact path of the file to create.
projet12-mission-services/├── 00-ENONCE.md <- this document│├── apps/ <- THE CODE (ANNEX A) — DO NOT MODIFY│ ├── micro/│ │ ├── app.py│ │ ├── requirements.txt│ │ └── Dockerfile│ ├── metriques/│ │ ├── app.py│ │ ├── requirements.txt│ │ └── Dockerfile│ └── portail/│ ├── app.py│ ├── requirements.txt│ └── Dockerfile│├── k8s/│ ├── 01-deployments.yaml <- PROVIDED (ANNEX B) — DO NOT MODIFY│ ├── 02-statefulset-bd.yaml <- PROVIDED (ANNEX B) — DO NOT MODIFY│ ││ └── services/ <- YOUR TURN│ ├── 01-api-produits.yaml <- skeleton to complete (ANNEX C)│ ├── 02-portail.yaml <- skeleton to complete (ANNEX C)│ ├── 03-bd-interne.yaml <- skeleton to complete (ANNEX C)│ ├── 04-metriques.yaml <- skeleton to complete (ANNEX C)│ ├── 05-paiement-externe.yaml <- skeleton to complete (ANNEX C)│ ││ └── 06-casses/ <- PROVIDED but DEFECTIVE (ANNEX D)│ ├── casse-1.yaml│ ├── casse-2.yaml│ └── casse-3.yaml│├── outils/│ └── valider.ps1 <- PROVIDED (ANNEX E)│└── RAPPORT.md <- TO WRITE by you
Only three images are needed: micro:1.0 serves five different components (behavior changes via environment variables), metriques:1.0 exposes two ports, and portail:1.0 displays the dashboard.
The dashboard: your progress indicator
The portal continuously queries each component and displays a tile per link, refreshed every 3 seconds:
Tile
Meaning
Where to look for the error
RED
The DNS name does not exist
The Service was not created, or its name is wrong
ORANGE
The name is resolved, but nobody answers
The Service exists, but its selector or its port is wrong
GREEN
Communication established
Your Service is correct
Final goal: the 8 tiles in green, and the counter showing 8 / 8.
This red/orange distinction is not decorative: it tells you which side to search. Red = the Service does not exist (nothing to debug, you must write it). Orange = the Service exists but does not find its Pods or hits the wrong port.
Absolute ban on modifying the apps/ folder, as well as 01-deployments.yaml and 02-statefulset-bd.yaml.
(The whole difficulty is adapting to what already exists: that is exactly the situation of a real job.)
You create and modify only files located in k8s/services/.
No hard-coded IP address. Everything must rest on DNS names and label selectors.
You must determine the type yourself of each Service: nothing tells you whether it is a ClusterIP, a NodePort, a LoadBalancer, a headless service, or an ExternalName. That is the heart of the evaluation.
The Service names are imposed: the application code calls them as-is. A wrong name gives a red tile.
You work on the Kubernetes built into Docker Desktop (Settings → Kubernetes → Enable Kubernetes).
Preparation
Prerequisites — to check once only
Docker Desktop is started (green icon in the system tray).
Kubernetes is enabled in Docker Desktop: Settings → Kubernetes → Enable Kubernetes → Apply & Restart. Without that box checked, no kubectl command will work.
Docker Desktop has at least 4 GB of RAM: Settings → Resources → Memory ≥ 4 GB. This project starts 14 Pods; on 2 GB the machine chokes and Pods stay Pending.
You have Internet (for mission 5 and to download the busybox image).
Startup sequence
powershell
# 0) Switch to the right cluster (required if minikube or kind was already used)kubectl config use-context docker-desktopkubectl get nodes # must show docker-desktop Ready# 1) Build the three imagesdocker build -t micro:1.0 ./apps/microdocker build -t metriques:1.0 ./apps/metriquesdocker build -t portail:1.0 ./apps/portail# 2) Deploy the provided base (Pods, and NO Service)kubectl apply -f k8s/01-deployments.yamlkubectl apply -f k8s/02-statefulset-bd.yaml# 3) Wait until ALL Pods are Ready (about 30 s)kubectl wait --for=condition=ready pod --all --timeout=180s# 4) Observe the starting situationkubectl get pods # 14 Pods, all Runningkubectl get svc # only "kubernetes": none of your Services
At this stage: all Pods are running and nothing communicates. That is the normal starting point.
Two technical warnings to know from now — this is not a mistake on your part:
Warning Endpoints is deprecated in v1.33+ — Kubernetes systematically shows this message on every kubectl get endpoints. The command still works perfectly, ignore the warning. The equivalent new API is kubectl get endpointslices, but all commands in this lab deliberately use endpoints, more readable to learn.
The dashboard can take 10 to 15 seconds to display the first time: the portal tests 8 network links on each display, each with a 1.5 s timeout. When nothing works yet, it waits each timeout before showing red or orange. Once the Services are correct, the response time drops back to a few hundred milliseconds.
Question to ask yourself immediately: how will you even see the dashboard, since no entry door exists yet?
Lifeline:kubectl port-forward works without any Service, directly on a Pod.
powershell
kubectl port-forward deploy/portail 5000:5000
Then open http://localhost:5000. You will see the dashboard all red, with the “External access” tile in orange (expected: you did not go through port 30500).
The missions
Mission 1 — Make the portal talk to the products API (15 points)
The portal calls http://api-produits on port 80. The API Pods listen on port 8000 and carry the label app: api-produits.
File to complete:k8s/services/01-api-produits.yaml
To determine: the Service type, the selector, as well as port and targetPort.
Validation:
powershell
kubectl apply -f k8s/services/01-api-produits.yamlkubectl get svc api-produitskubectl get endpoints api-produits # must list 3 IP addresses
The API Produits tile turns green.
Mission 2 — Open the entry door (15 points)
The dashboard must be reachable from your browser at the exact address http://localhost:30500. The portal Pods listen on port 5000.
File to complete:k8s/services/02-portail.yaml
To determine: which Service type exposes an application outside the cluster on a fixed machine port? What is the allowed port range for that field?
Validation:
powershell
kubectl get svc portail # PORT(S) must show 80:30500/TCPstart http://localhost:30500
The External access tile turns green.
Question to answer in the report: another Service type would also have made the portal reachable from the browser on Docker Desktop. Which one? And what difference would that make in cloud production?
Mission 3 — Give an identity to each database (20 points)
The bd StatefulSet provides 3 replicas. The portal must reach precisely the first one (the primary), at the address:
bd-0.bd-interne
The database Pods carry the label app: bd and listen on port 5432.
File to complete:k8s/services/03-bd-interne.yaml
To determine: which Service type gives an individual DNS name to each Pod, instead of a single virtual IP? Which field must you write, and with which particular value?
Validation:
powershell
# 1) From a utility Pod, reach bd-0 directly:kubectl run test --rm -i --restart=Never --image=busybox:1.36 -- wget -qO- http://bd-0.bd-interne:5432/ping# must answer: {"pod":"bd-0","port":5432,"service":"base-de-donnees"}# 2) Check the DNS entries with the fully qualified name# (busybox nslookup does NOT apply search domains, you must give the FQDN):kubectl run test --rm -i --restart=Never --image=busybox:1.36 -- nslookup bd-0.bd-interne.default.svc.cluster.local# must return ONE address only (that of Pod bd-0)kubectl run test --rm -i --restart=Never --image=busybox:1.36 -- nslookup bd-interne.default.svc.cluster.local# must return THREE addresses (one per StatefulSet Pod)
Trap not to miss
examine the serviceName field of the provided StatefulSet. The name of your Service must match it exactly, otherwise the individual Pod names will never be created.
Technical trap (busybox)
nslookup short-name does not work from a busybox Pod because its resolver does not use the search domains of /etc/resolv.conf. From a real application Pod (such as portail), however, bd-0.bd-interneresolves perfectly. So use wget to test the real application chain, and the FQDN to remove any DNS ambiguity.
Mission 4 — Expose two ports on the same Service (15 points)
The metriques component listens on two ports:
Usage
Container port
Port name declared in the Deployment
Web interface
8080
web
Metrics
9090
prom
The portal calls http://metriques (port 80) andhttp://metriques:9090/metrics.
File to complete:k8s/services/04-metriques.yaml
To determine: how do you declare several ports on a Service? Which constraint then becomes mandatory for each entry? And how do you point targetPort to a container port by its name rather than by its number, so the Service stays valid even if the number changes?
Validation:
powershell
kubectl describe svc metriques # BOTH ports must appear
Mission 5 — Give an internal name to an external service (10 points)
The portal must reach a payment service hosted outside the cluster, but the code calls an internal name: paiement-externe. That name must resolve to example.com.
File to complete:k8s/services/05-paiement-externe.yaml
To determine: which Service type creates a simple DNS alias to an external name, with no selector and no Pod?
Validation:
powershell
kubectl run test --rm -i --restart=Never --image=busybox:1.36 -- nslookup paiement-externe.default.svc.cluster.local# must display:# paiement-externe.default.svc.cluster.local canonical name = example.com# Name: example.com# Address: <public IP>
This mission requires the cluster to resolve public names. If you have no Internet access, replace the target with api-produits.default.svc.cluster.local and mention it in your report.
Mission 6 — The investigation: repair three defective Services (20 points)
The folder k8s/services/06-casses/ contains three Services already written… that do not work. Each has a single error, and they are the three most frequent mistakes in companies.
powershell
kubectl apply -f k8s/services/06-casses/
File
Observed symptom
casse-1.yaml
The Service exists, but kubectl get endpoints returns <none>
casse-2.yaml
The Endpoints are filled, but every connection is refused
casse-3.yaml
The Service looks perfect, but the portal never reaches it
For each of the three cases, your report must contain:
the diagnostic command that put you on the trail;
the exact cause of the outage;
the fix applied;
the proof that the link works (green tile + command output).
Recommended method: proceed like an investigator. kubectl describe svc, kubectl get endpoints, kubectl get pods --show-labels, then compare line by line the Service and the Pods. The difference between “empty Endpoints” and “connection refused” already tells you which side to search.
Mission 7 — Bonus: the stretch (5 points)
Choose one only:
a) Make the same client always served by the same Pod of the products API. (Hint: a Service field allows “stickiness” based on the client IP.)
b) Create a Service without a selector pointing to a fixed external IP address, by writing yourself its Endpoints.
c) Write a LoadBalancer Service for the portal, then explain what EXTERNAL-IP becomes on Docker Desktop, and what it would become on AWS.
Automatic validation
A script gives you your score at any time:
powershell
.\outils\valider.ps1
If PowerShell refuses to run the script with a message such as script execution is disabled on this system, bypass the restriction for this command only:
[OK] Mission 1 - api-produits ............. 15/15[OK] Mission 2 - portail .................. 15/15[FAIL] Mission 3 - bd-interne ............... 0/20 -> clusterIP must be None[OK] Mission 4 - metriques ................ 15/15[FAIL] Mission 5 - paiement-externe ......... 0/10 -> Service not found[FAIL] Mission 6 - repairs .................. 7/20 -> cache: no Pod answersSCORE : 52 / 100
The script gives no solution: it only indicates what fails and where to look.
Deliverables
The complete k8s/services/ folder: your 5 written Services and the 3 repaired Services.
A RAPPORT.md containing:
for each Service: the chosen type and a two-sentence justification (“why this one and not another”);
the full investigation of mission 6 (command → cause → fix → proof);
a screenshot of the dashboard showing 8 / 8;
a screenshot of kubectl get svc showing all your Services and their types;
your answers to the reflection questions.
The final output of .\outils\valider.ps1.
Reflection questions
Why could the application absolutely not work without Services, even though all Pods were Running?
What is the concrete difference between a red tile and an orange tile? What does each tell you about where the error is?
Why must the database Service be headless, while an ordinary Service is enough for the products API?
What exactly does the Endpoints list contain, and who updates it? What happens when a Pod becomes NotReady?
You delete a products API Pod; Kubernetes recreates one with a different IP address. Why does the portal keep working with no modification at all?
In production, would you expose ten applications with ten LoadBalancer Services? Justify, and propose an alternative.
Grading
Item
Points
Mission 1 — Internal Service and DNS discovery
15
Mission 2 — External exposure on port 30500
15
Mission 3 — Headless Service and stable identities
20
Mission 4 — Multi-port and named ports
15
Mission 5 — Alias to an external service
10
Mission 6 — Diagnosis and repair (3 outages)
20
Report quality and justification of choices
5
Bonus — Mission 7
+5
Total
100 (+5)
Penalties: −10 per modification of a forbidden file (apps/, 01-deployments.yaml, 02-statefulset-bd.yaml); −5 per hard-coded IP address.
Success criteria
Criterion
Expected
Dashboard
8 / 8 green tiles
Service types
Each one adapted to its use and justified
Individual DNS
bd-0.bd-interne resolved to a single Pod
Multi-port
Both ports visible, targetPort referenced by name
Investigation
The 3 outages identified, explained, and fixed
Resilience
After deleting a Pod, the portal keeps working
No hard-coded IP
Only DNS names and label selectors
Toolbox
No solution here — only leads.
powershell
kubectl get svc # types, IP, portskubectl describe svc <nom> # details + Endpointskubectl get endpoints <nom> # WHO is behind the Service?kubectl get pods --show-labels # the real Pod labelskubectl get pods -l app=<valeur> # test a selectorkubectl port-forward deploy/portail 5000:5000 # reach a Pod WITHOUT a Servicekubectl 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 # what the portal cannot reachkubectl delete svc <nom> # start over on a Service
Trap to know with kubectl run test: if you chain several commands quickly, the previous Pod is not always deleted in time and you will get:
Error from server (AlreadyExists): pods "test" already exists
Two solutions: change the name (test1, test2…) on each command, or clean up first:
powershell
kubectl delete pod test --ignore-not-found; kubectl run test --rm -i --restart=Never ...
The three questions that unlock 90 % of situations:
Does the Service exist, with the right name? (if not → red tile: there is nothing to debug, you must write it)
Are the Endpoints filled? (empty → the selector matches no Pod label)
Does targetPort match the port actually listened on by the container? (if not → connection refused)
ANNEX A — The applications
Do not modify any of these files. Copy them as-is to the indicated paths.
A.1 — The generic micro-service
This single application serves five components (api-produits, api-commandes, cache, notifications, bd). Its name and port are set by environment variables.
File: apps/micro/app.py
python
"""Generic demonstration micro-service.The same image serves several components: the name and listen port areprovided by environment variables (APP_NAME, PORT).Each response contains the Pod name, which makes the load balancingperformed by a Service visible."""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)
# ---------------------------------------------------------------------------# THE DATABASE (3 replicas) — PROVIDED, DO NOT MODIFY## WARNING: the serviceName field below imposes the NAME of the Service that# you will have to write so that bd-0, bd-1 and bd-2 each get a DNS name.# ---------------------------------------------------------------------------apiVersion: apps/v1kind: StatefulSetmetadata: name: bdspec: serviceName: bd-interne # <-- read this line carefully 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
ANNEX C — The Service skeletons to complete
Copy these five files, then replace each TODO with the right value.
Lines starting with # ? are questions to decide: you decide whether to add, change, or delete the line concerned.
File: k8s/services/01-api-produits.yaml
yaml
# MISSION 1 — Make the products API reachable from the portal.## The portal calls: http://api-produits (so port 80)# The Pods listen on: 8000# The Pods carry the label: app: api-produits## ? Which Service type for INTERNAL cluster communication?apiVersion: v1kind: Servicemetadata: name: api-produits # IMPOSED name: do not changespec: type: TODO selector: TODO: TODO ports: - port: TODO # the port clients call targetPort: TODO # the port actually listened on by the container
File: k8s/services/02-portail.yaml
yaml
# MISSION 2 — Make the dashboard reachable from the browser,# at the exact address: http://localhost:30500## The Pods listen on: 5000# The Pods carry the label: app: portail## ? Which Service type opens a port on the MACHINE?# ? What is the allowed range for this port?# ? Which extra field must you add to impose port 30500?apiVersion: v1kind: Servicemetadata: name: portail # IMPOSED name: do not changespec: type: TODO selector: TODO: TODO ports: - port: TODO targetPort: TODO # ? a line is missing here
File: k8s/services/03-bd-interne.yaml
yaml
# MISSION 3 — Give an INDIVIDUAL DNS name to each database replica,# so you can reach precisely: bd-0.bd-interne## The Pods listen on: 5432# The Pods carry the label: app: bd## ? Which Service type does NOT have a single virtual IP?# ? Which field, with which very particular value, produces this effect?# ? The name below must match which StatefulSet field?apiVersion: v1kind: Servicemetadata: name: bd-interne # IMPOSED name: do not changespec: # ? an essential line is missing here selector: TODO: TODO ports: - port: TODO targetPort: TODO
File: k8s/services/04-metriques.yaml
yaml
# MISSION 4 — Expose TWO ports on one and the same Service.## The portal calls: http://metriques (port 80)# and: http://metriques:9090/metrics## The Pods listen on: 8080 (port named "web") and 9090 (port named "prom")# The Pods carry the label: app: metriques## ? Which constraint becomes MANDATORY as soon as a Service exposes several ports?# ? How do you point targetPort to a container port BY ITS NAME?apiVersion: v1kind: Servicemetadata: name: metriques # IMPOSED name: do not changespec: type: TODO selector: TODO: TODO ports: - TODO: TODO # ? a mandatory field is missing on each entry port: TODO targetPort: TODO - TODO: TODO port: TODO targetPort: TODO
File: k8s/services/05-paiement-externe.yaml
yaml
# MISSION 5 — Point an INTERNAL name to an EXTERNAL service.## The portal uses the name: paiement-externe# That name must resolve to: example.com## ? Which Service type creates a simple DNS alias (CNAME)?# ? Does this type have a selector? ports? Pods?apiVersion: v1kind: Servicemetadata: name: paiement-externe # IMPOSED name: do not changespec: type: TODO TODO: TODO # ? the field that indicates the external target
ANNEX D — The three defective Services
Copy these three files as-is, apply them, then diagnose and fix.
Each contains exactly one error. Do not rewrite the file from scratch: find the fault.
File: k8s/services/06-casses/casse-1.yaml
yaml
# OUTAGE 1# Symptom: the Service exists, but "kubectl get endpoints api-commandes"# returns <none>. The portal shows an ORANGE tile.apiVersion: v1kind: Servicemetadata: name: api-commandesspec: type: ClusterIP selector: app: api-commande ports: - port: 80 targetPort: 8000
File: k8s/services/06-casses/casse-2.yaml
yaml
# OUTAGE 2# Symptom: "kubectl get endpoints cache" does show an IP address,# but every connection fails. The portal shows an ORANGE tile.apiVersion: v1kind: Servicemetadata: name: cachespec: type: ClusterIP selector: app: cache ports: - port: 80 targetPort: 6380
File: k8s/services/06-casses/casse-3.yaml
yaml
# OUTAGE 3# Symptom: this Service looks perfect (correct type, correct selector,# Endpoints filled, coherent ports)... and yet the portal# shows a RED tile and NEVER reaches it.apiVersion: v1kind: Servicemetadata: name: notificationspec: type: ClusterIP selector: app: notifications ports: - port: 80 targetPort: 7000
ANNEX E — The validation script
File: outils/valider.ps1
powershell
# ---------------------------------------------------------------------------# Validation script — gives a score, NEVER the solution.# Usage: .\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 { "[FAIL] " } $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: restore the communications ===" -ForegroundColor CyanWrite-Host ""# --- Mission 1 : api-produits ---------------------------------------------$p = 0; $note = ""if (-not (Existe "api-produits")) { $note = "Service api-produits not found" }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 = "Empty Endpoints: the selector matches no Pod" } elseif ("$tp" -ne "8000") { $note = "targetPort does not match the listened port" } 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 not found" }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 = "the port exposed on the machine must be 30500 (current: '$np')" } elseif ($type -notin @("NodePort", "LoadBalancer")) { $note = "type unsuitable for external access" } 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 not found (check the StatefulSet serviceName)" }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 = "this Service must NOT have a virtual IP" } elseif (-not $eps) { $note = "Empty Endpoints: check the selector" } 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 not found" }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 = "a port is missing: two are expected (80 and 9090)" } elseif (-not $noms) { $note = "each port must have a name when there are several" } elseif ($cible -match '^\s*\d+(\s+\d+)*\s*$') { $note = "targetPort must reference the ports BY THEIR NAME" } 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 not found" }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 = "this is not the expected type for a DNS alias" } elseif (-not $cible) { $note = "the external target is not set" } else { $p = 10 }}Afficher "Mission 5 - paiement-externe" $p 10 $note; $total += $p# --- Mission 6 : the three repairs ------------------------------------------$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 not found"; 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) : empty Endpoints" } elseif ("$tp" -ne $cas.port) { $notes += "$($cas.nom) : no Pod answers on this port" } else { $p += 7 }}if ($p -gt 20) { $p = 20 }Afficher "Mission 6 - repairs" $p 20 ($notes -join " | "); $total += $pWrite-Host ""$couleur = if ($total -ge 90) { "Green" } elseif ($total -ge 50) { "Yellow" } else { "Red" }Write-Host ("AUTOMATIC SCORE : {0} / 95" -f $total) -ForegroundColor $couleurWrite-Host " (+5 for report quality, +5 bonus: evaluated manually)" -ForegroundColor DarkGrayWrite-Host ""Write-Host "Reminder: the dashboard must show 8 / 8 at http://localhost:30500" -ForegroundColor DarkGrayWrite-Host ""
Course created by Dr. Haythem REHOUMA — Development and Deployment of Data Solutions