Kubernetes Kubernetes

Services: Stabiles Networking für kurzlebige Pods

Gib Pods eine stabile Adresse, stelle sie innerhalb und außerhalb des Clusters bereit und beschränke, wer mit wem sprechen darf.

14 min Fortgeschritten

Jeder Pod bekommt seine eigene IP-Adresse, aber Pods kommen und gehen, und ihre Adressen mit ihnen. Ein Service bietet eine stabile Adresse vor einer wechselnden Menge von Pods. Diese Anleitung baut auf dem Deployment web der vorherigen Anleitung auf und behandelt Services, ihre Bereitstellung außerhalb des Clusters, Ingress und Network Policies.

Das Problem: Pod-IPs ändern sich

kubectl get pods -l app=web -o wide
kubectl delete pod POD_NAME
kubectl get pods -l app=web -o wide

Der Ersatz-Pod hat eine andere IP, und er kann auf einem anderen Knoten liegen. Alles, was die alte Adresse fest eingetragen hatte, ist nun kaputt, und bei Replikas, die kommen und gehen, ist es nicht möglich, Adressen von Hand zu verfolgen.

Einen ClusterIP-Service erstellen

apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

Der selector wählt die Pods aus, die den Service stützen, mit denselben Labels, die du von Deployments kennst. port ist der Port, auf dem der Service lauscht, und targetPort ist der Port der Pods, an den er weiterleitet, der zum Port des Containers passen sollte. Da kein type gesetzt ist, ist das ein ClusterIP-Service: nur von innerhalb des Clusters erreichbar, auf einer stabilen virtuellen IP.

kubectl apply -f service.yaml
kubectl get service web
kubectl get endpointslices -l kubernetes.io/service-name=web

Der EndpointSlice listet die Pod-IPs auf, die gerade hinter dem Service liegen. Skaliere das Deployment hoch oder runter, und er aktualisiert sich automatisch, während die eigene Adresse des Services sich nie ändert. (Ältere Cluster und Tutorials nutzen kubectl get endpoints web für dieselbe Information. Neuere Versionen markieren diese API zugunsten von EndpointSlices als veraltet.)

Innerhalb des Clusters bekommt der Service auch einen DNS-Namen: web aus demselben Namespace oder web.NAMESPACE.svc.cluster.local von überall. Teste es aus einem Wegwerf-Pod:

kubectl run tmp --rm -it --image=busybox:1.36 --restart=Never -- wget -qO- http://web

Du solltest die nginx-Willkommensseite sehen. Auf jedem Knoten pflegt kube-proxy die Regeln, die Verbindungen auf die bereiten Pods des Services verteilen. Im üblichen iptables-Modus ist das effektiv zufällig statt striktes Round-Robin, und Pods, die ihren Readiness-Check nicht bestehen, werden aus der Rotation genommen.

Einen Service außerhalb des Clusters bereitstellen

Es gibt zwei weitere Service-Typen, und jeder baut auf dem vorherigen auf:

  • NodePort öffnet zusätzlich einen Port (30000–32767) auf jedem Knoten. Alles, was die IP eines Knotens erreichen kann, erreicht den Service auf diesem Port.
  • LoadBalancer fragt zusätzlich die Umgebung nach einem externen Load Balancer, der auf diese Node-Ports zeigt. Bei einem Cloud-Anbieter geschieht das automatisch. Auf Bare Metal brauchst du etwas wie MetalLB.
apiVersion: v1
kind: Service
metadata:
  name: web-public
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080

Lässt du nodePort weg, wählt Kubernetes einen freien. Auf minikube mit dem Docker-Treiber ist die IP des Knotens von deinem Laptop oft nicht direkt erreichbar, lass minikube sie also für dich öffnen:

minikube service web-public --url

Für einen LoadBalancer-Service auf minikube führe minikube tunnel in einem anderen Terminal aus.

HTTP mit Ingress routen

Ein LoadBalancer pro Service wird teuer und behandelt nur rohen Traffic. Ein Ingress lässt dich HTTP(S)-Traffic nach Host und Pfad über einen einzigen Eintrittspunkt auf viele Services routen und dort TLS terminieren. Ein Ingress beschreibt nur die Regeln. Er braucht einen im Cluster laufenden Ingress-Controller, der darauf reagiert (auf minikube minikube addons enable ingress).

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
spec:
  ingressClassName: nginx
  rules:
    - host: web.example.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

Anfragen für web.example.local gehen nun an den Service web. Zertifikate für den Host lassen sich mit einem Werkzeug wie cert-manager automatisieren.

Traffic mit NetworkPolicy beschränken

Standardmäßig kann jeder Pod mit jedem anderen Pod sprechen. Eine NetworkPolicy wählt Pods aus und listet auf, welcher Traffic erlaubt ist. Sobald ein Pod von irgendeiner Policy für Ingress ausgewählt ist, wird alles, was nicht ausdrücklich erlaubt ist, verweigert. Beginne damit, allen eingehenden Traffic in einem Namespace zu verweigern:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
spec:
  podSelector: {}
  policyTypes:
    - Ingress

Der leere podSelector wählt jeden Pod im Namespace aus. Erlaube dann nur, was du brauchst, zum Beispiel Traffic zu den web-Pods von Pods mit dem Label role: client:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-client-to-web
spec:
  podSelector:
    matchLabels:
      app: web
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              role: client
      ports:
        - protocol: TCP
          port: 80

Network Policies sind namespace-bezogen, und sie werden nur durchgesetzt, wenn das Netzwerk-Plugin des Clusters sie unterstützt, zum Beispiel Calico oder Cilium. Manche minimalen lokalen Setups akzeptieren die Objekte, ignorieren sie aber stillschweigend, teste also, dass eine verweigerte Verbindung wirklich blockiert wird.

Beliebte Anleitungen