Έργο projet11-kubernetes-services · Έγγραφο αναφοράς σε βάθος.
Αυτό το έγγραφο πηγαίνει πολύ πιο μακριά από τη διόρθωση: αναλύει όλους τους τύπους Services, τις εσωτερικές έννοιες (kube-proxy, Endpoints, EndpointSlices, DNS), τα σημαντικά πεδία YAML, τις πολιτικές κίνησης, το session affinity, το multi-port, τις κλασικές παγίδες και τις καλές πρακτικές.
Ένα Pod είναι εφήμερο: μπορεί να αναδημιουργηθεί ανά πάσα στιγμή, με νέα IP. Δεν μπορούμε λοιπόν να βασιστούμε στην IP ενός Pod για επικοινωνία.
Ένα Service είναι μια σταθερή αφαίρεση που:
Κεντρική ιδέα: το Service δεν «περιέχει» τα Pods. Τα ξαναβρίσκει συνεχώς χάρη στον επιλογέα ετικετών, και διατηρεί τη λίστα των διευθύνσεών τους στα Endpoints.
apiVersion: v1
kind: Service
metadata:
name: mon-service
labels:
app: demo
annotations: {} # μεταδεδομένα (συχνά χρησιμοποιούνται από τους LoadBalancer cloud)
spec:
type: ClusterIP # ClusterIP | NodePort | LoadBalancer | ExternalName
selector: # ποια Pods στοχεύει αυτό το Service (ανά ετικέτες)
app: demo
ports:
- name: http # όνομα της θύρας (χρήσιμο αν υπάρχουν πολλές θύρες)
protocol: TCP # TCP (προεπιλογή) | UDP | SCTP
port: 80 # θύρα του Service (αυτό που βλέπουν οι πελάτες)
targetPort: 5000 # θύρα του κοντέινερ (ή όνομα θύρας του κοντέινερ)
nodePort: 30080 # (NodePort/LoadBalancer) θύρα ανοιχτή στον κόμβο
clusterIP: 10.96.0.10 # (προαιρετικό) σταθερή IP· "None" = headless
sessionAffinity: None # None | ClientIP
externalTrafficPolicy: Cluster # Cluster | Local (NodePort/LoadBalancer)
internalTrafficPolicy: Cluster # Cluster | Local
ipFamilyPolicy: SingleStack # SingleStack | PreferDualStack | RequireDualStack
externalIPs: [] # εξωτερικές IP δρομολογημένες προς αυτό το Service (προχωρημένο)Κάθε πεδίο αναλύεται παρακάτω. Μπορούμε να δημιουργήσουμε ένα ελάχιστο Service σε 8 γραμμές· όλα τα άλλα πεδία έχουν λογικές προεπιλεγμένες τιμές.
Ο προεπιλεγμένος τύπος. Αποδίδει μια εσωτερική εικονική IP (στο εύρος Service CIDR, π.χ. 10.96.0.0/12), προσβάσιμη μόνο από το εσωτερικό του συμπλέγματος.
apiVersion: v1
kind: Service
metadata:
name: demo-clusterip
spec:
type: ClusterIP
selector:
app: demo-back
ports:
- port: 80
targetPort: 5000Χαρακτηριστικά:
EXTERNAL-IP).http://demo-clusterip (βλ. §13).Πότε να το χρησιμοποιήσετε: για ό,τι μένει μέσα στο σύμπλεγμα. Είναι ο πιο συνηθισμένος τύπος.
Κάνει όλα όσα κάνει το ClusterIP (λαμβάνει μια εσωτερική IP), συν: ανοίγει μια στατική θύρα σε κάθε κόμβο του συμπλέγματος (προεπιλεγμένο εύρος 30000–32767).
apiVersion: v1
kind: Service
metadata:
name: demo-nodeport
spec:
type: NodePort
selector:
app: demo-back
ports:
- port: 80 # θύρα του Service (εσωτερική)
targetPort: 5000 # θύρα του κοντέινερ
nodePort: 30082 # θύρα ανοιχτή σε ΚΑΘΕ κόμβοΠρόσβαση: http://<ip-de-n-importe-quel-noeud>:30082 (με Docker Desktop: http://localhost:30082).
Σημαντικά σημεία:
nodePort, το Kubernetes επιλέγει μία στο εύρος.Κάνει όλα όσα κάνει το NodePort, συν: ζητά από την υποδομή (το νέφος) να προμηθεύσει έναν εξωτερικό εξισορροπητή φορτίου με δημόσια IP.
apiVersion: v1
kind: Service
metadata:
name: demo-lb
spec:
type: LoadBalancer
selector:
app: demo-back
ports:
- port: 8090
targetPort: 5000Ανάλογα με το περιβάλλον:
| Περιβάλλον | Συμπεριφορά |
|---|---|
| AWS / GCP / Azure | Δημιουργεί έναν πραγματικό διαχειριζόμενο LB (ELB/NLB, GCP LB…) και γεμίζει το EXTERNAL-IP |
| Docker Desktop | EXTERNAL-IP = localhost → http://localhost:8090 |
| minikube | minikube tunnel παρέχει την εξωτερική IP |
| kind / bare-metal | Μένει <pending> χωρίς ελεγκτή όπως το MetalLB |
Πλήρης αλυσίδα: LoadBalancer → NodePort → ClusterIP → Endpoints → Pods.
Οι σχολιασμοί (ειδικοί ανά πάροχο) κατευθύνουν τον LB, π.χ. στο AWS:
metadata:
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
service.beta.kubernetes.io/aws-load-balancer-internal: "true"Ένας LoadBalancer ανά υπηρεσία = ακριβός στο νέφος. Στην παραγωγή, προτιμάμε συχνά ένα μόνο σημείο εισόδου (Ingress/Gateway) μπροστά από πολλές υπηρεσίες (βλ. §17).
Ιδιαίτερη περίπτωση: κανένας επιλογέας, κανένα Pod, καμία IP. Δημιουργεί απλώς ένα ψευδώνυμο DNS (εγγραφή CNAME) προς ένα εξωτερικό όνομα.
apiVersion: v1
kind: Service
metadata:
name: base-externe
spec:
type: ExternalName
externalName: db.exemple.com # τα Pods που καλούν "base-externe" ανακατευθύνονται εδώΧρήση: να δείχνει ένα σταθερό εσωτερικό όνομα (base-externe) προς μια υπηρεσία ή εκτός συμπλέγματος (διαχειριζόμενη βάση, API τρίτου). Αν η διεύθυνση αλλάξει, τροποποιούμε ένα μόνο σημείο.
Όριο: είναι καθαρό DNS, χωρίς κατανομή φορτίου ούτε έλεγχο θύρας. Δεν ταιριάζει αν η εξωτερική υπηρεσία περιμένει ένα συγκεκριμένο HTTP
Host.
Θέτοντας clusterIP: None, λαμβάνουμε ένα Service χωρίς εικονική IP. Το DNS επιστρέφει τότε απευθείας τις IP όλων των Pods (λίστα εγγραφών A), αντί για μία μοναδική IP.
apiVersion: v1
kind: Service
metadata:
name: demo-headless
spec:
clusterIP: None # <-- headless
selector:
app: demo-back
ports:
- port: 80
targetPort: 5000Σε τι χρησιμεύει:
pod-0.demo-headless, pod-1.demo-headless…), χρήσιμο για αναπαραγόμενες βάσεις (Cassandra, Kafka, κ.λπ.).| Κανονικό ClusterIP | Headless (clusterIP: None) | |
|---|---|---|
| Εικονική IP | Ναι (μία μόνο) | Όχι |
| Απάντηση DNS | 1 IP (του Service) | N IP (των Pods) |
| Κατανομή | Από το kube-proxy | Στην ευθύνη του πελάτη |
| Περίπτωση χρήσης | Web/API χωρίς κατάσταση | Αναπαραγόμενες βάσεις, StatefulSet |
Ένα Service μπορεί να μην έχει επιλογέα. Σε αυτή την περίπτωση, το Kubernetes δεν γεμίζει μόνο του τα Endpoints: εσείς τα ορίζετε χειροκίνητα. Πρακτικό για να εκθέσετε έναν εξωτερικό πόρο κάτω από μια σταθερή εσωτερική IP.
apiVersion: v1
kind: Service
metadata:
name: api-legacy
spec:
ports:
- port: 80
targetPort: 8080
---
apiVersion: v1
kind: Endpoints # (ή EndpointSlice, πιο σύγχρονο)
metadata:
name: api-legacy # ΙΔΙΟ όνομα με το Service
subsets:
- addresses:
- ip: 192.168.1.50 # εξωτερικός διακομιστής
ports:
- port: 8080Διαφορά με το ExternalName: εδώ δρομολογούμε ανά IP (με δυνατότητα load balancing σε πολλές IP), όχι με CNAME DNS.
Αυτή είναι η πηγή σύγχυσης. Τρεις διαφορετικές θύρες, τρεις ρόλοι:
| Πεδίο | Πού | Σημασία |
|---|---|---|
port | Στο Service | Η θύρα που χρησιμοποιούν οι πελάτες για να φτάσουν το Service |
targetPort | Στο κοντέινερ | Η θύρα όπου ακούει πραγματικά η εφαρμογή στο Pod |
nodePort | Στον κόμβο | (NodePort/LoadBalancer) η θύρα ανοιχτή στο μηχάνημα |
Παράδειγμα διαβασμένο φωναχτά: «οι πελάτες χτυπούν τη θύρα 80 του Service, που μεταβιβάζει στη θύρα 5000 του κοντέινερ· σε NodePort, εισερχόμαστε επίσης από τη θύρα 30082 του μηχανήματος».
Το
targetPortμπορεί να αναφέρεται σε ένα όνομα θύρας ορισμένο στο κοντέινερ (βλ. §10), ώστε να μην κωδικοποιούμε τον αριθμό στα σκληρά.
Ένα Service μπορεί να εκθέσει πολλές θύρες (π.χ. HTTP + μετρικές). Σε αυτή την περίπτωση, κάθε εγγραφή πρέπει να έχει name.
spec:
selector:
app: demo
ports:
- name: http
port: 80
targetPort: web # αναφορά σε ΟΝΟΜΑΣΜΕΝΗ θύρα του κοντέινερ
- name: metrics
port: 9090
targetPort: 9090Στην πλευρά του κοντέινερ, ονομάζουμε τις θύρες:
containers:
- name: app
ports:
- name: web # <-- επαναχρησιμοποιείται από targetPort: web
containerPort: 5000
- name: metrics
containerPort: 9090Πλεονέκτημα των ονομασμένων θυρών: αν αλλάξει η θύρα του κοντέινερ, δεν αλλάζουμε τίποτα στο Service.
Το Service είναι ένα αφηρημένο αντικείμενο: δεν είναι μια διεργασία που δέχεται κίνηση. Η μαγεία γίνεται από το kube-proxy, ένα στοιχείο παρόν σε κάθε κόμβο, που προγραμματίζει τους κανόνες δικτύου του πυρήνα ώστε να ανακατευθύνει «IP:port του Service» προς «IP:port ενός Pod».
Λειτουργίες του kube-proxy:
| Λειτουργία | Αρχή | Σημειώσεις |
|---|---|---|
| iptables (προεπιλογή) | Κανόνες iptables, τυχαία επιλογή ενός Pod | Απλό, στιβαρό, πολύ διαδεδομένο |
| IPVS | Πίνακας κατακερματισμού πυρήνα, πραγματικοί αλγόριθμοι LB (rr, lc, sh…) | Πιο αποδοτικό σε μεγάλα συμπλέγματα |
| nftables | Διάδοχος του iptables | Πιο πρόσφατο |
Πρακτικές συνέπειες:
Ο δεσμός Service ↔ Pods υλοποιείται από αντικείμενα:
IP:port των έτοιμων Pods.kubectl get endpoints demo-clusterip
kubectl get endpointslices -l kubernetes.io/service-name=demo-clusteripΠοιος ενημερώνει τη λίστα; Ο endpoint controller: μόλις ένα Pod γίνει Ready (readinessProbe OK) και αντιστοιχεί στον επιλογέα, η IP του εισέρχεται· αν πέσει, βγαίνει.
Ένα Pod μη Ready αφαιρείται από τα Endpoints → δεν λαμβάνει κίνηση. Γι' αυτό το readinessProbe είναι ουσιώδες: ελέγχει ποιος είναι «μέσα» στο Service. (Εξαίρεση:
publishNotReadyAddresses: trueδημοσιεύει και τα μη έτοιμα Pods — ειδική χρήση headless.)
Το Kubernetes εκτελεί το CoreDNS. Κάθε Service λαμβάνει ένα ντετερμινιστικό όνομα DNS:
<service> # ίδιος χώρος ονομάτων
<service>.<namespace> # άλλος χώρος ονομάτων
<service>.<namespace>.svc.cluster.local # πλήρες FQDNΠαράδειγμα από ένα Pod:
curl http://demo-clusterip # ίδιος χώρος ονομάτων
curl http://demo-clusterip.default # ρητό
curl http://demo-clusterip.default.svc.cluster.localΕγγραφές που παράγονται:
_http._tcp.demo-clusterip….Ιστορικά, το Kubernetes εισήγε επίσης μεταβλητές περιβάλλοντος (
DEMO_CLUSTERIP_SERVICE_HOST,..._PORT) στα Pods που δημιουργήθηκαν μετά το Service. Το DNS παραμένει η συνιστώμενη μέθοδος (λειτουργεί ανεξάρτητα από τη σειρά δημιουργίας).
| Τιμή | Αποτέλεσμα | Συμβιβασμός |
|---|---|---|
| Cluster (προεπιλογή) | Η κίνηση μπορεί να ανακατευθυνθεί σε άλλον κόμβο για να φτάσει ένα Pod | Καλή κατανομή, αλλά η IP πηγής του πελάτη καλύπτεται (SNAT) και ένα επιπλέον άλμα δικτύου |
| Local | Εξυπηρετεί μόνο τα Pods του κόμβου που δέχεται το πακέτο | Διατηρεί την IP πηγής του πελάτη, χωρίς άλμα· αλλά ανισορροπία αν τα Pods είναι κακά κατανεμημένα |
| Τιμή | Αποτέλεσμα |
|---|---|
| Cluster (προεπιλογή) | Δρομολογεί προς οποιοδήποτε Pod του Service |
| Local | Δρομολογεί μόνο προς τα Pods του ίδιου κόμβου (χρήσιμο για καθυστέρηση / τοπικότητα) |
Το
externalTrafficPolicy: Localείναι η κρίσιμη ρύθμιση όταν χρειάζεστε να γνωρίζετε την πραγματική IP του πελάτη (ημερολόγια, ασφάλεια, γεωεντοπισμός).
Από προεπιλογή, κάθε αίτημα μπορεί να πάει σε οποιοδήποτε Pod. Για να «κολλήσει» ένας πελάτης στο ίδιο Pod:
spec:
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800 # 3 ώρεςNone (προεπιλογή): κατανομή σε κάθε αίτημα.ClientIP: όλα τα αιτήματα της ίδιας IP πηγαίνουν στο ίδιο Pod (βασικά sticky sessions L4).Για λεπτότερες HTTP συνεδρίες (ανά cookie), χρησιμοποιούμε μάλλον ένα Ingress (L7).
protocol: TCP (προεπιλογή), UDP (DNS, παιχνίδια, ροή), SCTP (τηλεπικοινωνίες).appProtocol (ενδεικτικό) διευκρινίζει το πρωτόκολλο εφαρμογής (http, https, grpc) για τα εργαλεία/LB.ports:
- name: dns-udp
port: 53
protocol: UDP
targetPort: 53
- name: dns-tcp
port: 53
protocol: TCP
targetPort: 53Ένα Service εργάζεται σε L4 (IP/port). Δεν ξέρει να δρομολογεί ανά URL, όνομα κεντρικού υπολογιστή, ούτε να διαχειρίζεται TLS. Για αυτό:
| Αντικείμενο | Στρώμα | Ρόλος |
|---|---|---|
| Service (ClusterIP/NodePort/LB) | L3/L4 | Σταθερή διεύθυνση + απλό LB προς Pods |
| Ingress | L7 (HTTP/HTTPS) | Δρομολόγηση ανά host και διαδρομή, TLS, ένα σημείο εισόδου για πολλές υπηρεσίες |
| Gateway API | L7 (διάδοχος του Ingress) | Πιο εκφραστικό, διαχωρισμός ρόλων, πολλαπλά πρωτόκολλα |
Τυπικό μοντέλο παραγωγής: ένας μόνο LoadBalancer → Ingress → πολλά εσωτερικά ClusterIP. Εξοικονομούμε ακριβούς LB και συγκεντρώνουμε TLS/δρομολόγηση.
| Τύπος | Εσωτερική IP | Εξωτερική πρόσβαση | DNS | Επιλογέας | Περίπτωση χρήσης |
|---|---|---|---|---|---|
| ClusterIP | Ναι | Όχι | 1 A (ClusterIP) | Ναι | Εσωτερική επικοινωνία (το πιο συνηθισμένο) |
| NodePort | Ναι | Θύρα του κόμβου | 1 A | Ναι | Dev/τοπικό, δομικό στοιχείο ενός LB |
| LoadBalancer | Ναι | Δημόσια IP | 1 A | Ναι | Δημόσια υπηρεσία στο νέφος |
| ExternalName | Όχι | — (CNAME) | CNAME | Όχι | Ψευδώνυμο προς εξωτερική υπηρεσία |
Headless (clusterIP: None) | Όχι | Όχι | N A (Pods) | Ναι | StatefulSet, αναπαραγόμενες βάσεις |
| Χωρίς επιλογέα | Ναι | ανάλογα με τον τύπο | 1 A | Όχι | Χειροκίνητα Endpoints (εξωτερικός πόρος) |
| Σύμπτωμα | Συχνή αιτία | Λύση |
|---|---|---|
| Το Service δεν απαντά | Επιλογέας ≠ ετικέτες των Pods | Ευθυγραμμίστε spec.selector και τα labels του προτύπου Pod |
| Κενά Endpoints | Κανένα Pod Ready ή κανένα αντίστοιχο Pod | kubectl get endpoints <svc>· ελέγξτε readinessProbe και ετικέτες |
| Άρνηση σύνδεσης εσωτερικά | Λάθος targetPort | targetPort = πραγματική θύρα του κοντέινερ |
Το EXTERNAL-IP μένει <pending> | Χωρίς ελεγκτή LB (kind/bare-metal) | Docker Desktop OK· αλλιώς MetalLB / port-forward |
| Η IP πηγής του πελάτη καλύπτεται | externalTrafficPolicy: Cluster | Περάστε σε Local |
| NodePort απρόσιτο | Θύρα εκτός εύρους / κατειλημμένη | Χρησιμοποιήστε 30000–32767, αλλάξτε nodePort |
| Το DNS δεν επιλύει | Λάθος χώρος ονομάτων / CoreDNS KO | Δοκιμάστε το FQDN· kubectl -n kube-system get pods (coredns) |
Εντολές διάγνωσης:
kubectl get svc <nom> -o wide
kubectl describe svc <nom>
kubectl get endpoints <nom>
kubectl get endpointslices -l kubernetes.io/service-name=<nom>
kubectl run test --rm -it --image=busybox:1.36 -- sh # nslookup <svc>, wget -qO- http://<svc>targetPort ανά όνομα → αποσύνδεση).app, tier, version).externalTrafficPolicy: Local όταν μετράει η πραγματική IP πελάτη.Αφαιρέστε το type: NodePort (ή βάλτε ClusterIP), εφαρμόστε ξανά, και αποδείξτε ότι δεν είναι πλέον προσβάσιμο από τον οικοδεσπότη αλλά είναι με όνομα από ένα Pod (curl http://demo-nodeport... μετονομασμένο). Παρατηρήστε το kubectl get svc: χωρίς στήλη nodePort.
Αλλάξτε τον επιλογέα του Service σε app: inexistant, εφαρμόστε ξανά, και διαπιστώστε kubectl get endpoints κενό + υπηρεσία απρόσιτη. Επαναφέρετε app: demo-back: τα Endpoints επιστρέφουν.
Δημιουργήστε ένα Service με clusterIP: None, έπειτα από ένα Pod: nslookup demo-headless. Πρέπει να δείτε πολλές IP (μία ανά Pod) αντί για μία μόνο.
Προσθέστε μια θύρα metrics (9090) ονομασμένη στο κοντέινερ και στο Service. Επαληθεύστε με kubectl describe svc ότι εμφανίζονται και οι δύο θύρες, και ότι το targetPort αναφέρεται στο όνομα της θύρας.
Δημιουργήστε ένα Service ExternalName προς example.com. Από ένα Pod: nslookup mon-alias πρέπει να επιστρέψει ένα CNAME προς example.com.
Επιστροφή στη διόρθωση του έργου · Βασικές έννοιες: 01-CONCEPTS-SERVICES.md · Εντολές: 02-COMMANDES.md.
Μάθημα δημιουργημένο από τον Dr. Haythem REHOUMA — Ανάπτυξη και ανάπτυξη λύσεων δεδομένων