Ξέρεις τον θερμοστάτη του σαλονιού σου: ρυθμίζεις 21 °C και δεν το ξανασκέφτεσαι. Μετράει τη θερμοκρασία, τη συγκρίνει με την επιθυμητή τιμή σου, θερμαίνει ή σταματά, και αν κάποιος ανοίξει το παράθυρο το αναπληρώνει χωρίς να κουνηθείς. Το Kubernetes κάνει ακριβώς αυτό με τα containers σου. Δηλώνεις «θέλω τρία αντίγραφα του API μου, πάντα προσβάσιμα στη θύρα 8080»· συγκρίνει διαρκώς αυτό που θέλεις με αυτό που τρέχει, και διορθώνει: ένα container πεθαίνει στις τρεις το πρωί, ξαναεκκινεί ένα· μια μηχανή πέφτει, μεταφέρει τα containers αλλού· αλλάζεις την έκδοση της εικόνας, αντικαθιστά τα αντίγραφα ένα προς ένα χωρίς να διακόψει την υπηρεσία. Με docker run, εσύ είσαι ο θερμοστάτης: εσύ παρακολουθείς και εσύ επανεκκινείς. Σε αυτό το μάθημα, θα δεις τη διαφορά με τα ίδια σου τα μάτια: θα σκοτώσεις ένα container Docker και θα διαπιστώσεις ότι μένει νεκρό, έπειτα θα κοιτάξεις τα «κομμάτια» του Kubernetes που τρέχουν ήδη στο μηχάνημά σου, έτοιμα να κάνουν αυτή τη δουλειά στη θέση σου.
Τρεις καταστάσεις που όλοι καταλήγουν να ζήσουν με το Docker μόνο. Ένα: το API σου τρέχει σε ένα container σε έναν server· ένα βράδυ καταρρέει (διαρροή μνήμης, εξωτερική εξάρτηση σε βλάβη) και κανείς δεν το βλέπει πριν το πρωί. Το --restart=always επανεκκινεί τη διεργασία, αλλά δεν σου λέει τίποτα, δεν επαληθεύει ότι η εφαρμογή απαντά πραγματικά, και δεν κάνει τίποτα αν σβήσει ολόκληρος ο server. Δύο: έχεις δέκα containers του ίδιου API πίσω από ένα reverse proxy και πρέπει να παραδώσεις την έκδοση 2 χωρίς οι χρήστες να δουν λάθος· με το χέρι, αυτό σημαίνει να σταματήσεις, να επανεκκινήσεις και να αφαιρέσεις από το proxy κάθε container στη σωστή σειρά, δέκα φορές, χωρίς λάθος. Τρία: η κίνησή σου δεν χωράει πια σε ένα μηχάνημα· πρέπει να προσθέσεις δύο, να αποφασίσεις ποιο container πάει πού, και τα containers να βρίσκουν το ένα το άλλο ενώ οι διευθύνσεις IP τους αλλάζουν σε κάθε επανεκκίνηση.
Αυτά τα τρία προβλήματα είναι η δουλειά ενός ορχηστρωτή. Το Docker προσφέρει δύο μερικές απαντήσεις: το Compose περιγράφει πολλά containers σε ένα αρχείο αλλά μένει σε ένα μόνο μηχάνημα και δεν παρακολουθεί τίποτα· το Swarm προσθέτει τα πολλαπλά μηχανήματα και τα replicas αλλά το οικοσύστημά του έμεινε μικρό. Το Kubernetes (K8s: «K», οκτώ γράμματα, «s»), γεννημένο στη Google το 2014 και σήμερα διαχειριζόμενο από το CNCF, έγινε το πρότυπο: αυτό θα ξαναβρείς στην AWS (EKS), στη Google (GKE), στην Azure (AKS), στην OVH, στη Scaleway, και στους servers των περισσότερων επιχειρήσεων.
| Κριτήριο | Docker Compose | Docker Swarm | Kubernetes |
|---|---|---|---|
| Εμβέλεια | ένα μηχάνημα | πολλά μηχανήματα | πολλά μηχανήματα (χιλιάδες) |
| Αυτόματη επανεκκίνηση | μόνο restart: | ναι | ναι, με ανιχνευτές υγείας (readiness, liveness) |
| Ενημέρωση χωρίς διακοπή | όχι | ναι, βασική | ναι, προοδευτική, με επιστροφή πίσω με μία εντολή |
| Ενσωματωμένη κατανομή φορτίου | όχι | ναι | ναι (Services), πλέον Ingress, εσωτερικό DNS |
| Αυτόματη κλιμάκωση | όχι | όχι | ναι (HPA, σε CPU, μνήμη ή επιχειρησιακές μετρικές) |
| Οικοσύστημα (Helm, operators, cloud) | — | μικρό | απέραντο |
| Καμπύλη μάθησης | ήπια | μέτρια | απότομη, εξ ου και αυτό το μάθημα |
Ένα cluster Kubernetes αποτελείται από δύο μέρη. Το control plane αποφασίζει· οι κόμβοι εκτελούν. Στο Docker Desktop, τα δύο ζουν στην ίδια εικονική μηχανή, αλλά οι ρόλοι μένουν οι ίδιοι όπως στην παραγωγή.
| Στοιχείο | Πού | Ρόλος σε μία φράση |
|---|---|---|
kube-apiserver | control plane | Λαμβάνει όλες τις εντολές (kubectl, controllers, kubelet) σε HTTPS στη θύρα 6443· τίποτα δεν συμβαίνει χωρίς να περάσει από αυτόν. |
etcd | control plane | Βάση κλειδιού-τιμής που αποθηκεύει την επιθυμητή κατάσταση και την παρατηρούμενη κατάσταση ολόκληρου του cluster· να το χάσεις χωρίς αντίγραφο ασφαλείας σημαίνει να χάσεις το cluster. |
kube-scheduler | control plane | Επιλέγει σε ποιον κόμβο θα τοποθετηθεί κάθε νέο Pod (ελεύθεροι πόροι, περιορισμοί). Δεν εκκινεί τίποτα ο ίδιος. |
kube-controller-manager | control plane | Συγκεντρώνει τους βρόχους συμφιλίωσης: «λείπει ένα replica; δημιουργώ ένα», «ένας κόμβος δεν απαντά πια; σημειώνω τα Pods του για επανατοποθέτηση». |
kubelet | κάθε κόμβος | Ο πράκτορας που διαβάζει στο API τα Pods που του έχουν ανατεθεί, ζητά από το runtime να εκκινήσει τα containers, και αναφέρει την κατάστασή τους. |
kube-proxy | κάθε κόμβος | Προγραμματίζει τους κανόνες δικτύου ώστε μια διεύθυνση Service να καταλήγει στο σωστό Pod. |
| runtime | κάθε κόμβος | Το λογισμικό που εκτελεί πραγματικά τα containers: containerd ή CRI-O γενικά, docker://29.3.1 στο Docker Desktop. |
Η καρδιά του μοντέλου είναι η δήλωση επιθυμητής κατάστασης. Δεν διατάζεις «εκκίνησε ένα container»· γράφεις (ή ζητάς να παραχθεί) ένα αντικείμενο που λέει «θέλω ένα Deployment api, εικόνα traefik/whoami:v1.10, 3 replicas». Το API το καταγράφει στο etcd. Οι controllers συγκρίνουν έπειτα, σε βρόχο και για πάντα, αυτή την επιθυμητή κατάσταση με την παρατηρούμενη κατάσταση που αναφέρουν τα kubelets· κάθε απόκλιση πυροδοτεί μια ενέργεια. Γι' αυτό ένα διαγραμμένο Pod ξαναγεννιέται: κανείς δεν το «επανεκκίνησε», ο controller απλώς διαπίστωσε 2 αντί για 3 και διόρθωσε. Θα δεις τον βρόχο σε δράση στο μάθημα 04.
Γιατί να μάθεις στο Docker Desktop παρά στο minikube, στο kind ή σε ένα cluster cloud; Γιατί το έχεις πιθανότατα ήδη, γιατί ένα κλικ ενεργοποιεί ένα πλήρες cluster, γιατί οι εικόνες που χτίζεις με docker build είναι άμεσα ορατές από το Kubernetes (ίδιο αποθετήριο εικόνων, κανένα registry να εγκαταστήσεις), και γιατί τα Services τύπου LoadBalancer λαμβάνουν τη διεύθυνση localhost: ανοίγεις τον browser σου και απαντά, όπως στην παραγωγή πίσω από έναν πραγματικό εξισορροπητή φορτίου. Τα όριά του είναι εξίσου ξεκάθαρα: ένας μόνο κόμβος (καμία «μηχανή που πέφτει» να προσομοιώσεις), καμία NetworkPolicy που να εφαρμόζεται από προεπιλογή (το ενσωματωμένο CNI τις αγνοεί), κανένα δικτυακό storage ούτε δημόσια IP. Όλα όσα θα γράψεις εδώ θα εφαρμοστούν ως έχουν σε ένα πραγματικό cluster· μόνο ο τρόπος πρόσβασης σε αυτό θα αλλάξει.
| Επιλογή | Εγκατάσταση | Κόμβοι | LoadBalancer στο localhost | Τοπικές εικόνες χωρίς registry | Για ποιον |
|---|---|---|---|---|---|
| Docker Desktop (αυτό το μάθημα) | ένα κλικ στην εφαρμογή | 1 (kubeadm) ή πολλοί (kind, 4.38+) | ναι | ναι | μάθηση και ανάπτυξη στον σταθμό εργασίας σου |
| minikube | εκτελέσιμο + driver (Docker, VM) | 1, πολλαπλοί δυνατοί | μέσω minikube tunnel | μέσω minikube image load | μάθηση, add-ons έτοιμα προς χρήση |
| kind | εκτελέσιμο, κόμβοι = containers Docker | πολλοί | όχι (port-forward) | μέσω kind load docker-image | αυτοματοποιημένα tests, CI |
| Cloud (EKS, GKE, AKS…) | λογαριασμός, χρέωση, δίκτυο | όσοι θέλεις | πραγματική δημόσια IP | όχι, χρειάζεται registry | παραγωγή |
Όλες οι εντολές πληκτρολογούνται στο PowerShell (Windows) ή σε ένα τερματικό (macOS, Linux)· είναι ταυτόσημες. Τίποτα από όσα ακολουθούν δεν τροποποιεί το cluster σου: παρατηρείς.
Εκκίνησε ένα container με Docker, όπως ήδη ξέρεις να κάνεις. Το traefik/whoami είναι μια μίνι εφαρμογή web που εμφανίζει το όνομα της μηχανής που απαντά: είναι η εικόνα-οδηγός των τριών πρώτων ενοτήτων.
docker run -d --name essai-whoami -p 8089:80 traefik/whoami:v1.10
docker ps --filter name=essai-whoami --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"Πραγματική έξοδος:
a088124875835ed375ddb25a4e4246faf62416a9772751ec7eedecc48bb758f8
NAMES IMAGE STATUS PORTS
essai-whoami traefik/whoami:v1.10 Up Less than a second 0.0.0.0:8089->80/tcp, [::]:8089->80/tcpΤι πρέπει να δεις: μία γραμμή, Up, η θύρα 8089 του μηχανήματός σου ανακατευθυνόμενη στην 80 του container.
Ρώτησέ το. Το Hostname που επιστρέφεται είναι το αναγνωριστικό του container: κράτα αυτή τη λεπτομέρεια, θα γίνει το όνομα ενός Pod στο μάθημα 04.
curl -s http://localhost:8089Σε Windows, πληκτρολόγησε curl.exe (το curl χωρίς επέκταση είναι ψευδώνυμο PowerShell του Invoke-WebRequest). Πραγματική έξοδος (δύο πρώτες γραμμές):
Hostname: a08812487583
IP: 127.0.0.1Κοίτα καλά τι θα συμβεί: σκότωσε το container. Είναι η βλάβη των τριών το πρωί, σε γρήγορη κίνηση.
docker rm -f essai-whoami
docker ps --filter name=essai-whoami --format "table {{.Names}}\t{{.Status}}"Πραγματική έξοδος:
essai-whoami
NAMES STATUSΤι πρέπει να δεις: ο πίνακας είναι άδειος. Κανείς δεν θα επανεκκινήσει αυτό το container· το curl http://localhost:8089 αποτυγχάνει πλέον (curl: (52) Empty reply from server, (56) Recv failure ή Connection refused ανάλογα με τη στιγμή που το Docker Desktop απελευθερώνει τη θύρα). Το Docker έκανε αυτό που του είπαν, τίποτα περισσότερο, τίποτα λιγότερο. Κράτα αυτή την εικόνα στο μυαλό σου: στο μάθημα 04, θα κάνεις το ίδιο σε ένα Pod και θα έχει επιστρέψει πριν προλάβεις να ξαναδιαβάσεις τη γραμμή.
Επαλήθευσε ότι το Kubernetes είναι εκεί. Αν δεν έχεις ακόμη ενεργοποιήσει το Kubernetes στο Docker Desktop, πήγαινε στο μάθημα 02 και γύρνα πίσω· αλλιώς, κοίτα το cluster σου.
kubectl get nodesΠραγματική έξοδος:
NAME STATUS ROLES AGE VERSION
docker-desktop Ready control-plane 18d v1.34.1Τι πρέπει να δεις: ένας μόνο κόμβος, Ready, ρόλος control-plane. Στο Docker Desktop, η μηχανή που αποφασίζει και αυτή που εκτελεί είναι η ίδια. Το AGE είναι η ηλικία του cluster σου, το VERSION αυτή του Kubernetes (1.34 εδώ).
Άγγιξε το control plane με το δάχτυλο. Τα τέσσερα στοιχεία «που αποφασίζουν» τρέχουν τα ίδια ως Pods, στο δεσμευμένο namespace kube-system, με ετικέτα tier=control-plane.
kubectl get pods -n kube-system -l tier=control-planeΠραγματική έξοδος:
NAME READY STATUS RESTARTS AGE
etcd-docker-desktop 1/1 Running 0 18d
kube-apiserver-docker-desktop 1/1 Running 0 18d
kube-controller-manager-docker-desktop 1/1 Running 0 18d
kube-scheduler-docker-desktop 1/1 Running 0 18dΤι πρέπει να δεις: etcd, ο API server, ο controller-manager και ο scheduler, καθένας 1/1 Running, 0 επανεκκινήσεις. Το όνομά τους τελειώνει με το όνομα του κόμβου που τα φιλοξενεί. Σύγκρινε με τον πίνακα του «Πώς λειτουργεί»: έχεις τώρα ένα πρόσωπο για κάθε ρόλο.
Δες το υπόλοιπο του kube-system. Χωρίς το φίλτρο, ανακαλύπτεις τα στοιχεία του κόμβου και τις προσθήκες που είναι ειδικές στο Docker Desktop.
kubectl get pods -n kube-systemΠραγματική έξοδος στο μηχάνημα του μαθήματος (η γραμμή metrics-server προέρχεται από ένα στοιχείο που θα εγκαταστήσουμε στην ενότητα 6: δεν το έχεις ακόμη, είναι φυσιολογικό):
NAME READY STATUS RESTARTS AGE
coredns-66bc5c9577-6gwhl 1/1 Running 0 18d
coredns-66bc5c9577-9p89d 1/1 Running 0 18d
etcd-docker-desktop 1/1 Running 0 18d
kube-apiserver-docker-desktop 1/1 Running 0 18d
kube-controller-manager-docker-desktop 1/1 Running 0 18d
kube-proxy-2ddzv 1/1 Running 0 18d
kube-scheduler-docker-desktop 1/1 Running 0 18d
metrics-server-7c5fdf4664-6882s 1/1 Running 0 26m
storage-provisioner 1/1 Running 0 18d
vpnkit-controller 1/1 Running 0 18dΤι πρέπει να δεις: kube-proxy (οι κανόνες δικτύου του κόμβου), coredns ×2 (το εσωτερικό DNS που θα επιτρέψει στο frontend να καλεί το api με το όνομά του), και δύο κομμάτια ειδικά για το Docker Desktop: storage-provisioner (δημιουργεί τόμους hostpath κατ' απαίτηση, ενότητα 5) και vpnkit-controller (αυτός δίνει το localhost στα Services LoadBalancer, μάθημα 04). Το kubelet, από την άλλη, δεν εμφανίζεται: δεν είναι Pod αλλά υπηρεσία συστήματος του κόμβου.
Προαιρετικό: δες ότι το Kubernetes είναι «μόνο» containers. Αν η επιλογή «Show system containers» είναι τσεκαρισμένη στο Docker Desktop (μάθημα 02), το Docker σου δείχνει τα containers που φέρουν το control plane. Είναι το ίδιο Docker με αυτό του βήματος 1.
docker ps --filter "name=k8s_kube-apiserver_" --filter "name=k8s_etcd_" --filter "name=k8s_kube-scheduler_" --filter "name=k8s_kube-controller-manager_" --format "table {{.Names}}\t{{.Status}}"Πραγματική έξοδος:
NAMES STATUS
k8s_etcd_etcd-docker-desktop_kube-system_0b753cb7812d40a401f3a8f63b18f779_0 Up 44 minutes
k8s_kube-apiserver_kube-apiserver-docker-desktop_kube-system_647244f1c75810d936baf4253b7903ef_0 Up 44 minutes
k8s_kube-scheduler_kube-scheduler-docker-desktop_kube-system_b44739859c757a4712b786569a89a1f3_0 Up 44 minutes
k8s_kube-controller-manager_kube-controller-manager-docker-desktop_kube-system_10b0d524eef4b9a12d5827ba17a36f4f_0 Up 44 minutesΤι πρέπει να δεις: το όνομα Docker κατασκευάζεται ως k8s_<container>_<pod>_<namespace>_<uid>_<n>. Αν η επιλογή δεν είναι τσεκαρισμένη, ο πίνακας είναι άδειος: τίποτα σοβαρό, είναι μια ρύθμιση εμφάνισης. Το AGE των 18 ημερών από την πλευρά του Kubernetes και το Up 44 minutes από την πλευρά του Docker δεν αντιφάσκουν: το cluster υπάρχει εδώ και 18 ημέρες, το Docker Desktop επανεκκινήθηκε πριν από 44 λεπτά και ξαναδημιούργησε τα containers του χωρίς να χάσει την κατάσταση, που είναι αποθηκευμένη στο etcd.
Πού μιλάμε; Μια τελευταία εντολή για να εντοπίσεις την πόρτα εισόδου από την οποία θα περάσουν όλες οι άλλες.
kubectl cluster-infoΠραγματική έξοδος:
Kubernetes control plane is running at https://kubernetes.docker.internal:6443
CoreDNS is running at https://kubernetes.docker.internal:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.Τι πρέπει να δεις: ο API server ακούει σε HTTPS στη θύρα 6443 του kubernetes.docker.internal, ένα όνομα που το Docker Desktop κάνει να δείχνει στο μηχάνημά σου. Όταν το Docker Desktop είναι σταματημένο, αυτή η θύρα είναι που «αρνείται τη σύνδεση» (μάθημα 02, «Αν κολλήσει»). Τίποτα να καθαρίσεις: το container essai-whoami διαγράφηκε στο βήμα 3 και δεν δημιούργησες τίποτα στο Kubernetes.
docker: Error response from daemon: failed to set up container networking: … Bind for 0.0.0.0:8089 failed: port is already allocated → ένα άλλο container δημοσιεύει ήδη τη θύρα 8089 (μήνυμα που προκλήθηκε εκκινώντας δύο φορές το βήμα 1· αν είναι ένα πρόγραμμα εκτός Docker που την καταλαμβάνει, το μήνυμα λέει ports are not available). Άλλαξε τη θύρα από την πλευρά του μηχανήματος (-p 8090:80) ή εντόπισε τον κάτοχο: Get-NetTCPConnection -LocalPort 8089 (PowerShell) / lsof -i :8089 (macOS, Linux).kubectl : Le terme 'kubectl' n'est pas reconnu (ή kubectl: command not found) → το Kubernetes δεν είναι ενεργοποιημένο, ή το τερματικό ανοίχτηκε πριν την ενεργοποίηση και δεν ξαναφόρτωσε το PATH. Μάθημα 02 για να το ενεργοποιήσεις· έπειτα κλείσε και ξανάνοιξε το τερματικό.Unable to connect to the server: dial tcp 127.0.0.1:6443: connectex: No connection could be made because the target machine actively refused it. → κανείς δεν ακούει στη 6443: το Docker Desktop είναι σταματημένο ή το Kubernetes απενεργοποιημένο. Εκκίνησε το Docker Desktop, περίμενε το «Kubernetes is up and running» στην προβολή Kubernetes, ξαναδοκίμασε.kubectl get pods -n kube-system -l tier=control-plane επιστρέφει No resources found → είσαι πιθανότατα σε άλλο cluster (context minikube, kind-…) όπου το control plane δεν έχει τις ίδιες ετικέτες ή δεν είναι ορατό. Το kubectl config current-context πρέπει να απαντά docker-desktop· το μάθημα 03 εξηγεί πώς να το διορθώσεις.docker ps --filter "name=k8s_…" δεν δείχνει τίποτα ενώ το Kubernetes τρέχει → το «Show system containers (advanced)» δεν είναι τσεκαρισμένο στις ρυθμίσεις Kubernetes του Docker Desktop. Δεν είναι βλάβη· το βήμα 7 είναι προαιρετικό.kube-apiserver (πόρτα εισόδου, θύρα 6443), etcd (μνήμη του cluster), kube-scheduler (επιλέγει τον κόμβο), kube-controller-manager (βρόχοι συμφιλίωσης).kubelet (ο πράκτορας), kube-proxy (δίκτυο των Services), runtime (docker://29.3.1 στο Docker Desktop)· το kubectl get pods -n kube-system -l tier=control-plane δείχνει τα τέσσερα πρώτα σε λειτουργία.docker-desktop v1.34.1, τοπικές εικόνες κοινές με το Kubernetes, LoadBalancer στο localhost· όρια: ένας μόνο κόμβος, καμία εφαρμοζόμενη NetworkPolicy, κανένα πραγματικό cloud.docker-desktop.Ο μοναδικός κόμβος του Docker Desktop κρύβει μια σημαντική διαφορά από ένα πραγματικό cluster: στην παραγωγή, το control plane ζει σε αφιερωμένα μηχανήματα (συχνά τρία, ώστε το etcd να διατηρεί απαρτία) και δεν φιλοξενεί κανένα Pod εφαρμογής, χάρη σε ένα «taint» node-role.kubernetes.io/control-plane:NoSchedule. Εδώ, το kubectl describe node docker-desktop δείχνει Taints: <none>: όλα τρέχουν στο ίδιο μέρος. Η σελίδα Kubernetes Components της επίσημης τεκμηρίωσης αναλύει κάθε στοιχείο που είδες σε αυτό το μάθημα, με τις πιθανές παραλλαγές (cloud-controller-manager, άλλα runtimes).