· Kubernetes  · 7 Min. Lesezeit

Wenn der einzige Zugang die Rancher-Shell ist

Kein kubeconfig, kein kubectl von außen, nur die Kubectl-Shell im Browser. Wie man darin sauber arbeitet: Rechte feststellen, Manifeste ohne Datei-Upload anlegen, vor dem Schreiben prüfen und Ergebnisse einsammeln, ohne interaktiv im Cluster herumzustochern.

Kein kubeconfig, kein kubectl von außen, nur die Kubectl-Shell im Browser. Wie man darin sauber arbeitet: Rechte feststellen, Manifeste ohne Datei-Upload anlegen, vor dem Schreiben prüfen und Ergebnisse einsammeln, ohne interaktiv im Cluster herumzustochern.

Es gibt Umgebungen, die keinen direkten Cluster-Zugang herausgeben. Kein kubeconfig zum Herunterladen, kein kubectl vom eigenen Rechner, kein Ping auf die Nodes. Der einzige Weg führt über die Kubectl-Shell in der Rancher-Oberfläche.

Das ist beim ersten Mal frustrierend, und es ist trotzdem richtig so. Ein kubeconfig ist ein dauerhafter Generalschlüssel. Es liegt auf einem Laptop, wandert in ein Backup, landet versehentlich in einem Repo. Gibt es dieses Artefakt nicht, läuft jeder Zugriff über eine authentifizierte und protokollierte Oberfläche, und der Entzug erfolgt an einer Stelle.

Der Preis ist eine Shell im Browser: keine Dateien zum Hochladen, keine langlebige Sitzung, kein bequemes Blättern. Dieser Beitrag zeigt die Arbeitsweise, die sich darin bewährt, mit fertigen Blöcken zum Kopieren und einem Prüfschritt vor jedem Schreibzugriff.

Grundregel: ein Befehl je Block

In einer Web-Shell tippt man nicht zwanzig Befehle nacheinander. Man fügt vorbereitete Blöcke ein. Deshalb steht in jedem Block unten genau ein Befehl, und lesende Befehle kommen strikt vor schreibenden. Wer mehrere Befehle in einen Block packt, verliert bei einem Verbindungsabbruch die Übersicht, welcher davon durchgelaufen ist.

Schritt 1: Herausfinden, was Sie überhaupt dürfen

Bevor Sie irgendetwas planen, klären Sie Ihre Rechte. In restriktiven Umgebungen bekommt man selten eine Aufstellung dessen, was die eigene Rolle abdeckt.

kubectl auth can-i --list --namespace=mein-namespace

Das gibt alle erlaubten Aktionen im Namespace aus. Für eine gezielte Einzelfrage genügt die knappe Form, die nur einen Exit-Code liefert und sich damit in Bedingungen verwenden lässt:

kubectl auth can-i create jobs -n mein-namespace -q

Die vollständigen Optionen, darunter --as zum Prüfen fremder Identitäten, stehen in der kubectl-Referenz zu auth can-i.

Prüfung: Wenn --list nur selfsubjectaccessreviews und ähnliche Meta-Rechte zeigt, haben Sie im gewählten Namespace praktisch nichts. Dann stimmt der Namespace nicht oder die Rolle ist enger als angenommen.

Schritt 2: Manifeste anlegen, ohne eine Datei hochladen zu können

Das ist die erste echte Hürde. Es gibt kein scp in die Web-Shell. Die Lösung ist ein Here-Document, das das Manifest direkt an kubectl weiterreicht:

cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: beispiel
  namespace: mein-namespace
data:
  schluessel: wert
EOF

Die einfachen Anführungszeichen um EOF sind wichtig. Ohne sie ersetzt die Shell alles, was wie eine Variable aussieht, schon beim Einfügen. In einem Manifest mit $-Zeichen zerstört das den Inhalt lautlos.

Schritt 3: Vor dem Schreiben prüfen

Kein apply aus dem Gedächtnis. Zwei Prüfungen kosten Sekunden und fangen die meisten Fehler ab.

Erst die Validierung durch den Server, ohne etwas zu ändern:

cat <<'EOF' | kubectl apply --dry-run=server -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: beispiel
  namespace: mein-namespace
data:
  schluessel: wert
EOF

Dann der Vergleich mit dem, was tatsächlich im Cluster liegt:

cat <<'EOF' | kubectl diff -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: beispiel
  namespace: mein-namespace
data:
  schluessel: wert
EOF

kubectl diff vergleicht die Live-Fassung mit dem, was ein apply bewirken würde, und gibt die Unterschiede als YAML aus. Beachten Sie das Exit-Code-Verhalten, das in der Referenz zu kubectl diff beschrieben ist: 0 bedeutet keine Unterschiede, 1 bedeutet Unterschiede gefunden, alles darüber ist ein echter Fehler. Dazu mehr unter Stolperstein 4.

Schritt 4: Ergebnisse einsammeln, ohne interaktiv zu arbeiten

Sie brauchen die Ausgabe eines Befehls aus dem Cluster, kommen aber nicht sinnvoll in einen Pod. Der saubere Weg ist ein Job, der sein Ergebnis in die Termination-Message schreibt. Diese Datei liegt laut Kubernetes-Doku zur Fehlerursache von Pods standardmäßig unter /dev/termination-log.

cat <<'EOF' | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
  name: diagnose
  namespace: mein-namespace
spec:
  backoffLimit: 0
  ttlSecondsAfterFinished: 300
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: diagnose
          image: busybox:1.36
          command: ["/bin/sh", "-c"]
          args:
            - |
              {
                echo "== DNS =="
                nslookup mein-service.mein-namespace.svc.cluster.local
                echo "== Erreichbarkeit =="
                wget -q -O- --timeout=5 http://mein-service:8080/healthz
              } > /dev/termination-log 2>&1
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: FallbackToLogsOnError
EOF

ttlSecondsAfterFinished sorgt dafür, dass der Job sich selbst aufräumt. backoffLimit: 0 verhindert, dass ein fehlgeschlagener Versuch mehrfach wiederholt wird und Sie mehrere Ergebnisse gleichzeitig sortieren müssen.

Das Ergebnis abholen:

kubectl get pod -n mein-namespace -l job-name=diagnose \
  -o jsonpath='{.items[0].status.containerStatuses[0].state.terminated.message}'

Alternativ, wenn der Job noch existiert, die Logs:

kubectl logs -n mein-namespace job/diagnose

Schritt 5: In einen laufenden Pod hineinsehen

Wenn Sie einen bestehenden Pod untersuchen müssen, der selbst keine Shell mitbringt, hängt kubectl debug einen kurzlebigen Container daneben. Die Beispielformen stehen in der Referenz zu kubectl debug:

kubectl debug mein-pod -n mein-namespace -it --image=busybox:1.36 --target=app

--target verbindet den Debug-Container mit dem Prozessraum des genannten Containers. Wollen Sie den laufenden Pod gar nicht anfassen, arbeiten Sie stattdessen auf einer Kopie:

kubectl debug mein-pod -n mein-namespace -it --image=busybox:1.36 --copy-to=mein-pod-debug

Die Kopie ist in einer Web-Shell oft der bessere Weg, weil ein Verbindungsabbruch dann nicht den produktiven Pod betrifft.

Die Stolpersteine

1. Der abgebrochene Browser-Tab lässt Pods zurück

Symptom: Im Namespace liegen Debug-Pods herum, die niemand mehr braucht, teils seit Tagen.

Ursache: Interaktive Befehle mit --rm räumen erst auf, wenn der Client sich ordentlich verabschiedet. Eine Web-Shell, die durch einen geschlossenen Tab, eine Zeitüberschreitung oder einen Netzwechsel wegbricht, tut das nicht. Der Pod bleibt.

Prüfung:

kubectl get pods -n mein-namespace --sort-by=.metadata.creationTimestamp

Lösung: In einer Web-Shell nicht auf --rm verlassen. Nutzen Sie stattdessen Objekte, die sich selbst aufräumen, also ttlSecondsAfterFinished bei Jobs, und benennen Sie Debug-Pods erkennbar, damit sie sich gezielt einsammeln lassen.

2. Die Ausgabe ist abgeschnitten

Symptom: Die Termination-Message endet mitten im Satz.

Ursache: Das ist eine harte Grenze, keine Anzeigefrage. Die Kubernetes-Doku nennt sie ausdrücklich: Das Kubelet schneidet Nachrichten ab 4096 Bytes ab, und über alle Container eines Pods hinweg liegt die Grenze bei 12 KiB, gleichmäßig aufgeteilt. Bei zwölf Containern bleiben also je 1024 Bytes.

Prüfung: Länge der Ausgabe messen, bevor Sie sich auf sie verlassen.

Lösung: Nur Zusammenfassungen in die Termination-Message schreiben, keine vollständigen Logs. Für lange Ausgaben kubectl logs verwenden. Und wenn Sie terminationMessagePolicy: FallbackToLogsOnError setzen, kennen Sie dessen eigene Grenze: der zurückfallende Log-Auszug ist auf 2048 Bytes oder 80 Zeilen begrenzt, je nachdem was kleiner ist.

3. Der Debug-Pod wird von der Namespace-Richtlinie abgelehnt

Symptom: kubectl debug oder ein Testpod wird gar nicht erst angelegt, die Fehlermeldung nennt eine Verletzung der Pod-Security-Richtlinie.

Ursache: In Namespaces mit strenger Pod-Security-Einstufung sind viele Debug-Images und Optionen nicht zulässig, insbesondere alles, was erhöhte Rechte oder geteilte Namespaces verlangt.

Prüfung: Erst mit --dry-run=server versuchen, dann sehen Sie die Ablehnung ohne Nebenwirkung.

Lösung: Ein Image ohne Sonderrechte wählen und auf --target verzichten, wenn der geteilte Prozessraum abgelehnt wird. Was der strenge Modus konkret verbietet, ist im Beitrag Die Falle mit PodSecurity restricted beschrieben.

4. kubectl diff bricht Ihr Skript ab

Symptom: Ein Block, der mit set -e beginnt, endet nach dem diff, obwohl alles in Ordnung ist.

Ursache: kubectl diff liefert Exit-Code 1, wenn es Unterschiede gefunden hat. Das ist der Normalfall vor einem Deployment und kein Fehler, aber set -e wertet es als solchen.

Prüfung:

cat <<'EOF' | kubectl diff -f - ; echo "Exit-Code: $?"
apiVersion: v1
kind: ConfigMap
metadata:
  name: beispiel
  namespace: mein-namespace
data:
  schluessel: wert
EOF

Lösung: Den Aufruf gegen den Abbruch absichern, etwa mit || true, und den Exit-Code bewusst auswerten, statt ihn als Erfolgssignal zu behandeln.

Grenzen

Diese Arbeitsweise bremst, und ein Teil dieser Reibung ist gewollt. Sie zwingt dazu, jeden Schreibzugriff vorher zu formulieren und zu prüfen, statt im Cluster zu experimentieren.

Es gibt allerdings Aufgaben, für die eine Browser-Shell das falsche Werkzeug bleibt. Portweiterleitungen über längere Zeit, das Kopieren größerer Dateien aus einem Pod, oder jede Arbeit, die eine stabile Sitzung über Stunden braucht. Wenn solche Aufgaben regelmäßig anfallen, ist die richtige Antwort nicht, sich durch die Web-Shell zu quälen, sondern kurzlebige Zugangsdaten einzuführen. Wie das aussehen kann, steht im Beitrag Kurzlebige Cluster-Logins mit Pinniped.

Der Unterschied ist wichtig: Der Verzicht auf ein dauerhaftes kubeconfig ist ein Sicherheitsgewinn. Der Verzicht auf jeden programmatischen Zugang ist eine Einschränkung, die man bewusst treffen sollte und nicht aus Versehen.

Sie wollen, dass Zugriffe auf Ihre Systeme nachvollziehbar und eng geführt sind, statt an Schlüsseln zu hängen, die überall herumliegen? Ich richte Zugriffswege ein, die kontrolliert und protokolliert sind, und arbeite selbst diszipliniert darin. Mehr unter IT-Beratung, das Erstgespräch ist unverbindlich.

Alex Jabi, Jabi IT

Alex Jabi

Ich betreue IT, Informationssicherheit und Datenschutz für KMU und Praxen an der Bergstraße und im Odenwald, persönlich, dokumentiert und ohne Cloud-Zwang.

Mehr über mich
Zurück zum Blog

Passende Artikel

Alle Artikel »
Die eigene Registry mit Harbor: scannen, cachen, absichern

Die eigene Registry mit Harbor: scannen, cachen, absichern

Harbor speichert Container-Images nicht nur, es scannt sie mit Trivy und spiegelt fremde Registries als Proxy-Cache. Der Scanner ist dabei nicht im Standard-Setup enthalten, und die Sperre für verwundbare Images greift beim Pull statt beim Push. Aufbau mit Prüfbefehlen und die dokumentierten Fallen.

Harbor: die Stolpersteine im Überblick

Harbor: die Stolpersteine im Überblick

Die vollständige Fallsammlung zum Betrieb von Harbor: fehlender Scanner, Scan-on-push je Projekt, der Schwellwert, der negligible durchlässt, geerbte Upstream-Rechte im Proxy-Cache, ablaufende Robot-Accounts, TLS-Ketten, Reverse-Proxy-Header und die Selbstsperre über CONFIG_OVERWRITE_JSON. Jeder Fall mit Symptom, Ursache, Prüfung und Lösung.