Skip to content

Erstes Deployment

Diese Seite geht einmal komplett durch: Container-Image rein, funktionierende HTTPS-Adresse raus. Sie setzt voraus, dass kubectl steht und dein Standard-Namespace gesetzt ist, siehe Zugang und kubectl.

Die Konventionen für Routen, Listener-Namen und Zertifikate stehen unter Gateway API. Hier werden sie angewendet, nicht noch einmal erklärt.

Das Manifest

Drei Objekte, eine Datei. Speicher sie als app.yaml und ersetz zwei Dinge: deinen Namespace im Hostnamen und dein eigenes Image im Container.

Dein Namespace besitzt bereits jeden Namen unterhalb von <dein-namespace>.itsh.dev, myapp.<dein-namespace>.itsh.dev funktioniert also sofort und ohne DNS-Einrichtung. Free-Tier-Namespaces nutzen stattdessen <dein-namespace>.itsh-apps.dev. Eine eigene Domain auszuliefern ist ein separater Schritt, siehe Gateway API.

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: registry.example.com/myapp:1.0.0
          ports:
            - name: http
              containerPort: 8080
          securityContext:
            allowPrivilegeEscalation: false
            runAsNonRoot: true
            seccompProfile:
              type: RuntimeDefault
            capabilities:
              drop: ["ALL"]
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 256Mi
          readinessProbe:
            httpGet:
              path: /readyz
              port: http
            initialDelaySeconds: 3
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /healthz
              port: http
            initialDelaySeconds: 10
            periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  selector:
    app: myapp
  ports:
    - name: http
      port: 80
      targetPort: http
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: myapp
  annotations:
    cert-manager.io/cluster-issuer: "auto"
spec:
  parentRefs:
    - name: default
      namespace: nginx-gateway
      sectionName: https-myapp-dein-namespace-itsh-dev
  hostnames:
    - myapp.<dein-namespace>.itsh.dev
  rules:
    - backendRefs:
        - name: myapp
          port: 80

Vier Punkte daran sind keine Kür:

  • Setz deine Requests bewusst. Sie bestimmen, wie viel Kapazität für deinen Pod reserviert wird, und sie sind die Grundlage der Abrechnung, siehe Abrechnung und Kontingente. Sie wegzulassen schlägt nicht fehl: Dein Namespace setzt dann einen kleinen Standard ein, aktuell 50m CPU und 64Mi Speicher, mit Limits von 200m und 256Mi. Das passt selten zu deiner Anwendung, abgerechnet wird es trotzdem, und ein späterer HPA hat keine brauchbare Bezugsgröße. Setz die Werte, statt sie zu erben.
  • Der securityContext hält deinen ersten Apply ruhig. Ohne ihn gibt Kubernetes vier Warnungen zu Privilege Escalation, Capabilities, Root und Seccomp aus. Das sind Warnungen, keine Ablehnungen, der Pod startet also trotzdem. Die Einstellungen sind aber sinnvoll und der Lärm ist es nicht. Ein Vorbehalt: runAsNonRoot: true braucht ein Image, das tatsächlich als Nicht-Root läuft. Wenn deins das nicht tut, lass diese eine Zeile weg und behalte die anderen drei, die weiterhin gelten und drei der vier Warnungen beseitigen.
  • Probes trennen "läuft" von "kann Anfragen beantworten". Ohne Readiness-Probe schickt der Service Traffic an Pods, die noch starten. Pass die Pfade an das an, was deine Anwendung tatsächlich ausliefert.
  • Der sectionName muss zum Hostnamen passen. Nimm den Hostnamen und ersetz jeden Punkt durch einen Bindestrich, myapp.tenant-7.itsh.dev ergibt also https-myapp-tenant-7-itsh-dev. Passt das nicht zusammen, hängt deine Route am falschen Listener oder an gar keinem.

Deine Workloads laufen in einer Sandbox-Runtime, und Manifeste, die gegen die Regeln der Plattform verstoßen, werden abgelehnt, bevor sie überhaupt im Cluster landen. Welche Regeln das sind, steht unter Berechtigungen und Richtlinien.

Anwenden

bash
kubectl apply -f app.yaml
bash
kubectl get pods
kubectl rollout status deployment/myapp

rollout status kehrt erst zurück, wenn alle Replicas bereit sind. Bleibt der Befehl stehen, ist das schon die halbe Diagnose: Es wird gerade kein Pod bereit. Brich mit Ctrl+C ab und schau dir unten die Fehlersuche an.

DNS

Nichts zu tun. Die Subdomain deines Namespace zeigt bereits auf das Gateway, der Hostname aus dem Manifest oben ist also erreichbar, sobald die Route existiert.

Ein eigener Schritt wird das erst, wenn du eine eigene Domain mitbringst, siehe Gateway API.

Wenn etwas nicht läuft

kubectl describe pod <name> ist bei allen dreien der erste Griff, die Events am Ende der Ausgabe nennen fast immer den Grund.

  • ImagePullBackOff: Das Image konnte nicht geladen werden. Bei einer privaten Registry fehlen die Zugangsdaten. Leg ein Secret vom Typ kubernetes.io/dockerconfigjson an und trag es unter imagePullSecrets im Pod-Template ein. Ist die Registry öffentlich, prüf Schreibweise und Tag.
  • CrashLoopBackOff: Der Container startet und beendet sich sofort wieder. kubectl logs deployment/myapp zeigt, warum. Ist der Pod schon neu gestartet, brauchst du die Logs des vorherigen Versuchs: kubectl logs deployment/myapp --previous.
  • Pending: Der Pod bekommt keinen Platz. In aller Regel ist das Kontingent deines Namespace erschöpft. kubectl get resourcequota zeigt, wie viel davon noch frei ist, und Abrechnung und Kontingente erklärt, was in dieses Kontingent einzahlt.

Wie es weitergeht