Einführung
Sie können Anwendungen nun bereitstellen und Fehler beheben, aber Clients benötigen weiterhin eine zuverlässige Möglichkeit, sie zu erreichen. Die IP-Adresse eines Pods ist nur vorübergehend gültig: Ein Deployment kann einen Pod jederzeit ersetzen, wobei der neue Pod eine andere IP-Adresse erhält.
Kubernetes löst dieses Problem mit einem Service. Ein Service wählt eine sich verändernde Gruppe von Pods aus und stellt Clients eine stabile Netzwerkidentität bereit. In diesem Lab verfolgen Sie den vollständigen Verbindungsweg – von Labels über EndpointSlices und Cluster-DNS bis hin zu zwei Service-Typen: ClusterIP für den Zugriff innerhalb des Clusters und NodePort für den Zugriff über einen Node.
Dieses Lab behandelt bewusst nur Services. Ingress erweitert die Lösung um HTTP-Routing und einen separaten Controller. Das lässt sich leichter verstehen, sobald die Auswahl und Erreichbarkeit von Services konkret nachvollziehbar sind.
Service-Backends bereitstellen
Start der Umgebung: Dieses Lab startet einen vollständigen Kubernetes-Cluster für Sie. Die Konfiguration der Steuerungsebene, des Knotens und der Netzwerkkomponenten dauert normalerweise 2–3 Minuten. Bitte warten Sie geduldig, bis die Umgebung vollständig geladen ist, bevor Sie beginnen.
In diesem Schritt erstellen Sie die replizierte Anwendung, die später von Services verfügbar gemacht wird. Ein Backend ist hier ein Pod, der Datenverkehr für einen Service empfangen kann.
Wechseln Sie in den Arbeitsbereich. cd ändert das aktuelle Verzeichnis der Shell und gibt bei erfolgreicher Ausführung normalerweise nichts aus:
cd /home/labex/project/service-lab
Erstellen Sie ein Deployment mit zwei Replikaten. Das Pod-Label app: course-nginx ist besonders wichtig, da Services es als Auswahlregel verwenden.
Die Shell-Syntax cat <<'EOF' > filename bezeichnet ein Here-Dokument. Alles bis zum abschließenden EOF wird in die Datei geschrieben, und > erstellt die Datei oder ersetzt ihren bisherigen Inhalt. Durch die Anführungszeichen beim ersten EOF wird verhindert, dass die Shell innerhalb der YAML-Datei unbeabsichtigt Variablen ersetzt oder andere Ausdrücke auswertet.
cat <<'EOF' > course-nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: course-nginx
spec:
replicas: 2
selector:
matchLabels:
app: course-nginx
template:
metadata:
labels:
app: course-nginx
spec:
containers:
- name: nginx
image: nginx:1.27-alpine
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
protocol: TCP
EOF
Wenden Sie die Konfiguration an und warten Sie auf beide Pods. -f gibt an, aus welcher Datei apply lesen soll. rollout status wartet auf den Deployment-Controller, und --timeout=60s begrenzt diese Wartezeit. Im letzten Befehl filtert -l nach einem Label, während -o wide zusätzliche Spalten mit der Pod-IP-Adresse und dem Node einblendet:
kubectl apply -f course-nginx-deployment.yaml
kubectl rollout status deployment/course-nginx --timeout=60s
kubectl get pods -l app=course-nginx -o wide
Der benannte Port http dokumentiert den TCP-Port 80 in jedem Container. Die beiden Zeilen sollten den Status Running anzeigen und unterschiedliche Pod-IP-Adressen enthalten. Diese IP-Adressen sind zwar gültig, aber keine dauerhaften Client-Adressen. In den nächsten Schritten richten Sie davor eine stabile Service-Identität ein.
Labels mit der Service-Auswahl verknüpfen
In diesem Schritt untersuchen Sie die Metadaten, die einen Service mit Pods verbinden. Ein Service wählt kein Deployment anhand seines Namens aus, sondern sucht unabhängig nach Pods, deren Labels mit seinem Selektor übereinstimmen.
Zeigen Sie die Pod-Labels an. -l app=course-nginx ist ein Label-Selektor, während --show-labels die vollständige Menge der Labels als letzte Spalte ergänzt:
kubectl get pods -l app=course-nginx --show-labels
Jeder Pod besitzt app=course-nginx sowie ein automatisch erzeugtes pod-template-hash. Ihr Service sollte ausschließlich das stabile Anwendungs-Label verwenden.
Vergleichen Sie Namen, Labels und IP-Adressen in einer kompakten Ansicht. -o custom-columns erstellt aus ausgewählten Objektfeldern eine Tabelle. Auf jede Überschrift vor : folgt der Feldpfad für die jeweilige Spalte. Der umgekehrte Schrägstrich setzt einen Befehl in der nächsten Anzeigezeile fort:
kubectl get pods -l app=course-nginx \
-o custom-columns='NAME:.metadata.name,LABEL:.metadata.labels.app,IP:.status.podIP,READY:.status.containerStatuses[0].ready'
Die automatisch erzeugten Namen und IP-Adressen unterscheiden die einzelnen Pods, während das gemeinsame Label ihre Rolle beschreibt. Diese Abstraktionsebene ermöglicht es einem Service, weiterhin zu funktionieren, wenn ein Pod ersetzt wird.
Bestätigen Sie, dass der Selektor des Deployments und das Label der Pod-Vorlage übereinstimmen. -o jsonpath='...' extrahiert nur die angeforderten Felder. Text außerhalb geschweifter Klammern wird als Beschriftung ausgegeben, Feldpfade innerhalb der Klammern liefern Werte, und {"\n"} fügt einen Zeilenumbruch ein:
kubectl get deployment course-nginx \
-o jsonpath='Selector: {.spec.selector.matchLabels.app}{"\n"}Pod label: {.spec.template.metadata.labels.app}{"\n"}'
Beide Werte sollten course-nginx lauten. Ein nicht übereinstimmender Selektor würde dazu führen, dass der Controller oder Service von den vorgesehenen Pods getrennt bleibt.
Einen ClusterIP-Service erstellen
In diesem Schritt erstellen Sie den standardmäßigen Service-Typ ClusterIP. Er stellt eine virtuelle IP-Adresse und einen DNS-Namen bereit, die von Workloads innerhalb des Clusters erreichbar sind.
Erstellen Sie das Manifest mit demselben Here-Dokument-Muster wie zuvor:
cat <<'EOF' > course-nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: course-nginx
spec:
type: ClusterIP
selector:
app: course-nginx
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
EOF
Lesen Sie die Port-Zuordnung sorgfältig:
port: 80ist der Port, den Clients am Service verwenden.targetPort: httpverweist auf den benannten Container-Port in jedem ausgewählten Pod.- Der Selektor wählt die Backend-Pods aus; er ist keine Netzwerkadresse.
Validieren, wenden Sie die Konfiguration an und untersuchen Sie den Service. --dry-run=client analysiert die Datei lokal, ohne etwas zu erstellen. Ohne diese Option wird die Konfiguration tatsächlich angewendet. get service liest anschließend das aktuelle Objekt aus dem Cluster:
kubectl apply --dry-run=client -f course-nginx-service.yaml
kubectl apply -f course-nginx-service.yaml
kubectl get service course-nginx
Der Wert unter CLUSTER-IP wird von Kubernetes vergeben. Er bleibt während der gesamten Lebensdauer dieses Services stabil, selbst wenn sich seine Backend-Pods ändern.
Den Service bis zu den EndpointSlices verfolgen
In diesem Schritt verfolgen Sie den Selektor des Services bis zu den tatsächlichen Backend-Adressen. Kubernetes speichert diese Adressen in EndpointSlice-Objekten.
Beschreiben Sie den Service. describe erweitert ein benanntes Objekt um Konfiguration, Status und zugehörige Endpoint-Informationen und ist daher nach der kürzeren get-Tabelle besonders hilfreich:
kubectl describe service course-nginx
Achten Sie auf Selector: app=course-nginx und auf eine Endpoints-Zeile mit zwei Pod-IP-Adressen am Port 80.
Listen Sie die vom Service ausgewählte EndpointSlice anhand des Service-Namenslabels auf. Dieser -l-Selektor verwendet das automatisch hinzugefügte Label kubernetes.io/service-name=course-nginx:
kubectl get endpointslices -l kubernetes.io/service-name=course-nginx
Untersuchen Sie die Adressen und ihre Bereitschaft. Dieser JSONPath verwendet range, um die eingeschlossene Vorlage für jeden Endpoint zu wiederholen. Ausgegeben werden die erste Adresse, der wörtliche Text ready=, der Bereitschaftswert und ein Zeilenumbruch:
kubectl get endpointslices -l kubernetes.io/service-name=course-nginx \
-o jsonpath='{range .items[*].endpoints[*]}{.addresses[0]}{" ready="}{.conditions.ready}{"\n"}{end}'
Sie sollten zwei Adressen mit ready=true sehen. Die Verbindungskette ist nun konkret:
Service selector -> matching Pod labels -> EndpointSlice addresses -> ready Pods
Wenn ein Service vorhanden ist, aber keine Endpoints besitzt, vergleichen Sie zunächst seinen Selektor mit den Labels und dem Bereitschaftsstatus der Pods.
Den Service über Cluster-DNS erreichen
In diesem Schritt agieren Sie als Client innerhalb des Clusters. Mit Kubernetes-DNS kann ein Pod im selben Namespace den Servicenamen course-nginx verwenden, anstatt sich seine virtuelle IP-Adresse merken zu müssen.
Starten Sie einen temporären BusyBox-Pod, fordern Sie die NGINX-Seite an und lassen Sie den Client nach dem Beenden automatisch entfernen. Lesen Sie die Optionen von oben nach unten:
--imagewählt das Container-Image aus, und--image-pull-policy=IfNotPresentverwendet eine bereits vorhandene Kopie aus dem Cache erneut.--restart=Nevererstellt einen eigenständigen Pod statt eines von einem Controller verwalteten Workloads.--rmentfernt den Pod nach dem Ende seines Befehls, während-idie Ein- und Ausgabe des Befehls verbunden hält.- Das Trennzeichen
--beendet die kubectl-Optionen. Alles danach ist der Befehl innerhalb des Containers. wget -qO-fordert die URL ohne zusätzliche Ausgaben an und schreibt den Antworttext direkt auf das Terminal.
kubectl run service-client \
--image=busybox:1.36 \
--image-pull-policy=IfNotPresent \
--restart=Never \
--rm -i \
-- wget -qO- http://course-nginx
Die Antwort enthält die NGINX-Willkommensseite. Der Datenverkehr lief über den Service und nicht direkt zu einer ausgewählten Pod-IP-Adresse.
Führen Sie eine leisere Erfolgsprüfung durch. >/dev/null verwirft den HTML-Inhalt, und && gibt die Meldung nur aus, wenn der Anfragebefehl erfolgreich war:
kubectl run service-client-check \
--image=busybox:1.36 \
--image-pull-policy=IfNotPresent \
--restart=Never \
--rm -i \
-- wget -qO- http://course-nginx >/dev/null && echo "ClusterIP Service responded"
Die Erfolgsmeldung bestätigt sowohl die DNS-Auflösung als auch die Erreichbarkeit über HTTP. Der Client-Pod ist temporär; der Service und seine beiden Backend-Pods bleiben bestehen.
Einen NodePort-Service hinzufügen
In diesem Schritt erstellen Sie für dieselben Pods einen zweiten Service vom Typ NodePort. Ein NodePort öffnet an jedem Node einen Port aus dem Standardbereich 30000–32767 und leitet den Datenverkehr an die Backends des Services weiter.
Kubernetes kann mehrere Objekte aus einer YAML-Datei mit mehreren Dokumenten einlesen. Jedes Objekt besitzt eigene Angaben für apiVersion, kind, metadata und spec. Eine Zeile mit --- trennt ein YAML-Dokument vom nächsten.
Erstellen Sie eine wiederverwendbare Datei mit dem vorhandenen ClusterIP-Service und dem neuen NodePort-Service. Das erneute Angeben der ClusterIP-Definition ist unproblematisch: Beim Anwenden desselben gewünschten Zustands bleibt sie unverändert.
cat <<'EOF' > course-nginx-services.yaml
apiVersion: v1
kind: Service
metadata:
name: course-nginx
spec:
type: ClusterIP
selector:
app: course-nginx
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: course-nginx-nodeport
spec:
type: NodePort
selector:
app: course-nginx
ports:
- name: http
port: 80
targetPort: http
nodePort: 30080
protocol: TCP
EOF
Validieren Sie beide YAML-Dokumente gemeinsam, bevor Sie den Cluster ändern. Die Ausgabe sollte sowohl service/course-nginx als auch service/course-nginx-nodeport nennen, gefolgt von (dry run):
kubectl apply --dry-run=client -f course-nginx-services.yaml
Wenden Sie die Konfiguration an und untersuchen Sie die Services. Der erste Befehl liest beide Dokumente aus einer Datei. Der vorhandene ClusterIP-Service sollte als unchanged erscheinen, während der NodePort-Service erstellt wird. Der zweite Befehl liest den neuen Service aus dem Cluster:
kubectl apply -f course-nginx-services.yaml
kubectl get service course-nginx-nodeport
Die Spalte PORT(S) zeigt 80:30080/TCP: Port 80 ist der Service-Port, und 30080 ist der nach außen gerichtete Node-Port. Beide Services wählen dieselben Pods aus und können daher dieselben Backend-Adressen besitzen.
Die beiden Zugriffsgrenzen vergleichen
In diesem Schritt testen Sie den NodePort und fassen zusammen, wann welcher Service-Typ geeignet ist.
Ermitteln Sie die IP-Adresse des Minikube-Nodes. $(...) ist eine Befehlsersetzung: Die Shell führt minikube ip aus und speichert die Ausgabe in der Variablen NODE_IP. -p labex-v135 wählt das vorbereitete Profil aus, und echo zeigt den gespeicherten Wert an:
NODE_IP=$(minikube ip -p labex-v135)
echo "$NODE_IP"
Fordern Sie die Anwendung über den nach außen gerichteten Node-Port an. Kubernetes benötigt möglicherweise einige Sekunden, um die neue Netzwerkregel auf Node-Ebene einzurichten, nachdem der Service akzeptiert wurde. Die Wiederholungsoptionen sorgen dafür, dass curl dieses kurze Zeitfenster der Konvergenz abwartet, anstatt beim ersten abgelehnten Verbindungsversuch sofort fehlzuschlagen:
curl -s --retry 5 --retry-connrefused --retry-delay 2 "http://${NODE_IP}:30080" | grep 'Welcome to nginx'
-s blendet die Fortschrittsanzeige aus, --retry 5 erlaubt bis zu fünf Wiederholungsversuche, --retry-connrefused behandelt eine anfänglich abgelehnte Verbindung als wiederholbaren Fehler, und --retry-delay 2 wartet zwischen den Versuchen jeweils zwei Sekunden. Durch die doppelten Anführungszeichen kann ${NODE_IP} innerhalb der URL ersetzt werden. Die Pipe | übergibt das zurückgegebene HTML an grep, das die passende Willkommenszeile als Nachweis ausgibt.
Der übereinstimmende HTML-Titel bestätigt, dass die Anfrage einen Backend-Pod erreicht hat. Vergleichen Sie die beiden von Ihnen aufgebauten Wege:
in-cluster Pod -> course-nginx:80 -> ready backend Pod
VM/node client -> NODE_IP:30080 -> course-nginx-nodeport:80 -> ready backend Pod
Untersuchen Sie beide Services gemeinsam:
kubectl get services course-nginx course-nginx-nodeport
Verwenden Sie ClusterIP für eine stabile Kommunikation innerhalb des Clusters. Dies ist der Standard und bildet häufig die Grundlage für weitere Mechanismen zur Bereitstellung. NodePort ergänzt einen Zugangspunkt auf Node-Ebene und eignet sich zum Lernen, für Entwicklungsumgebungen oder zur Anbindung externer Load-Balancer. Beide Varianten hängen von korrekten Selektoren und bereiten EndpointSlices ab.
Im nächsten Abschnitt wenden Sie diese Konzepte selbstständig an, indem Sie mehr als einen Web-Workload verfügbar machen.
Zusammenfassung
Sie haben vor ersetzbaren Pods eine stabile Netzwerkidentität eingerichtet. Sie haben Service-Selektoren mit Pod-Labels verknüpft, die ausgewählten Backends über EndpointSlices verfolgt, einen ClusterIP-Service über Cluster-DNS erreicht und einen NodePort-Zugangspunkt über den Node hinzugefügt.
Das wichtigste Denkmodell lautet: Ein Service ist weder die Anwendung noch enthält er Pods. Stattdessen repräsentiert er fortlaufend eine ausgewählte Gruppe bereiter Backends. Wenn die Verbindung fehlschlägt, prüfen Sie die Kette der Reihe nach: Service-Ports, Selektor, Pod-Labels, EndpointSlices, Bereitschaft der Backends und anschließend die Zugriffsgrenze des Clients.


