• English icon
  • German icon
Erklärvideo

Kubernetes-Logs auf AKS: kubectl logs vs. Log Analytics

· Reihe: Container auf Azure

azureakskuberneteslogginglog-analyticskubectl

Das Video ist auf Englisch.

Meine neue Version ist auf Azure Kubernetes Service (AKS) abgestürzt: sechs Abstürze in unter vier Minuten. Ich habe sie mit einem Befehl zurückgerollt, und die App hat die ganze Zeit weiter geantwortet. Dann habe ich kubectl gefragt, warum sie abgestürzt war. Not found. Die Logs waren mit dem Pod verschwunden.

Dieser Beitrag zeigt, wo die Logs eines Pods wirklich liegen, warum die Events den Absturz nach einer Stunde vergessen und wie Container Insights eine Kopie aufbewahrt, die Sie später noch abfragen können. Alles stammt aus der Übung des Microsoft-Learn-Moduls, ausgeführt in meinem eigenen Cluster.

Das Wichtigste

  • kubectl logs liest Dateien auf dem Node, nur von Containern, die noch existieren. Über den API-Server bittet es das Kubelet auf dem Node des Pods, die Log-Datei des Containers zu lesen. Ein ersetzter, gelöschter oder verdrängter Pod nimmt seine Dateien mit.
  • --previous reicht nur innerhalb desselben Pods zurück. Es liest den Container vor dem aktuellen. Nach einem Rollback lag der Absturz in einem anderen Pod, also gibt es nichts zu lesen.
  • Events halten auf AKS eine Stunde. kubectl get events zeigte die BackOff-Warnung direkt nach dem Rollback und eine Stunde später nichts mehr. Die Live-Ansichten im Portal vergessen ebenso.
  • Container Insights hält eine Kopie außerhalb des Clusters. Ein Agent auf jedem Node schickt die Ausgabe der Container in einen Log-Analytics-Arbeitsbereich (ContainerLogV2) und die Warning-Events nach KubeEvents. Mehr als eine Stunde später fand eine KQL-Abfrage noch fünf der sechs Abstürze.

Woher kubectl logs liest

In der Übung läuft eine kleine Python-API im Namespace aks-troubleshoot. Ein Deployment hält einen Pod davon am Laufen, und die App startet nicht ohne ihren API-Schlüssel. Die kaputte Version der Übung ist dasselbe Deployment mit auskommentierter Variable (der Wert ist der Demo-Schlüssel der Übung):

# ERROR: API_KEY environment variable removed, causing application crash
# env:
# - name: API_KEY
# value: "demo-api-key-12345"

Ich habe sie angewendet, und ein kleines Skript von mir hat jede Änderung der Pods mit der Zahl der Neustarts ausgegeben (gekürzt):

> kubectl apply -f k8s/crashloop-deployment.yaml
deployment.apps/api-deployment configured
> python pods.py --secs 150
0.4s api-deployment-64598b4ccd-mjkwf ContainerCreating 0/1 restarts 0
0.4s api-deployment-695945c854-nx6qj Running 1/1 restarts 0
4.9s api-deployment-64598b4ccd-mjkwf Running 0/1 restarts 1 last exit 1
20.7s api-deployment-64598b4ccd-mjkwf Running 0/1 restarts 2 last exit 1
32.1s api-deployment-64598b4ccd-mjkwf CrashLoopBackOff 0/1 restarts 2 last exit 1
93.4s api-deployment-64598b4ccd-mjkwf Running 0/1 restarts 4 last exit 1
120.7s api-deployment-64598b4ccd-mjkwf CrashLoopBackOff 0/1 restarts 4 last exit 1

Der alte Pod …nx6qj lief mit null Neustarts weiter: Ein Deployment behält den alten Pod, bis der neue bereit ist. Ein Client-Pod, der den Service jede Sekunde aufrief, protokollierte keinen einzigen Fehler. Der Log-Befehl der Übung selbst, über das Label, fand den Grund:

> kubectl logs -l app=api
2026-11-02 14:01:45,119 - ERROR - FATAL: API_KEY environment variable is not set. Exiting.
2026-11-02 14:02:39,658 - INFO - GET /healthz - Health check requested
…

Wo diese Zeile liegt. Der Container schreibt nach Standard Out, und der Node speichert es in einer Datei. kubectl logs fragt den API-Server des Clusters, der fragt das Kubelet auf diesem Node, den Agenten, der die Pods betreibt. Das Kubelet liest die Datei und streamt sie zurück. Ein Hilfs-Pod, der die Festplatte des Nodes sieht (ein Node-Debug-Pod), listete die Log-Ordner der beiden Pods auf:

> python nodelogs.py
$ kubectl debug node/aks-nodepool1-21626672-vmss000000 --attach --image=mcr.microsoft.com/cbl-mariner/busybox:2.0 -- ls /var/log/pods (filtered)
/var/log/pods on the node:
aks-troubleshoot_api-deployment-64598b4ccd-mjkwf_<uid> 5.log
aks-troubleshoot_api-deployment-695945c854-nx6qj_<uid> 0.log

Ein Ordner pro Pod, eine Datei pro Container-Start, ab null nummeriert. Der abgestürzte Pod war bei 5.log, seinem sechsten Start; die Dateien der früheren Starts waren schon weg. Dann habe ich zurückgerollt und das Log des abgestürzten Pods mit seinem Namen abgefragt:

> kubectl rollout undo deployment api-deployment
deployment.apps/api-deployment rolled back
> kubectl logs api-deployment-64598b4ccd-mjkwf
error: error from server (NotFound): pods "api-deployment-64598b4ccd-mjkwf" not found in namespace "aks-troubleshoot"

Der Rollback ging zur funktionierenden Version zurück und löschte den abgestürzten Pod. Hat der Node seine Dateien behalten? Den Ordner, ja. Die Dateien, nein:

> python nodelogs.py
/var/log/pods on the node:
aks-troubleshoot_api-deployment-64598b4ccd-mjkwf_<uid>
aks-troubleshoot_api-deployment-695945c854-nx6qj_<uid> 0.log

Mehr als eine Stunde später war auch der Ordner des abgestürzten Pods weg. Wenn ein Pod geht, gehen seine Log-Dateien mit. Selbst solange er lebt, behält das Kubelet nur das Log des aktuellen Containers und das davor. kubectl logs kann nur lesen, was noch da ist.

Events: nach einer Stunde weg

Events sind die eigenen Notizen von Kubernetes darüber, was mit einem Objekt passiert ist: scheduled, pulled, started, back off. Direkt nach dem Rollback war die Warnung, die eine Crash-Schleife hinterlässt, noch da, für einen Pod, den es nicht mehr gab:

> kubectl get events --field-selector reason=BackOff
LAST SEEN TYPE REASON OBJECT MESSAGE
19s Warning BackOff pod/api-deployment-64598b4ccd-mjkwf Back-off restarting failed container api in pod api-deployment-64598b4ccd-mjkwf_aks-troubleshoot(<uid>)

Eine Stunde später, derselbe Befehl:

> kubectl get events --field-selector reason=BackOff
No resources found in aks-troubleshoot namespace.

Auf AKS sind Events eine Stunde nach ihrem Entstehen verfügbar. Eine Historie dahinter gibt es nicht. Das Azure-Portal hilft hier auch nicht: Live Logs und Live Events lesen dieselbe Kubernetes-API wie kubectl und sehen deshalb nur, was noch existiert.

Container Insights: eine Kopie außerhalb des Clusters

Logs, die den Pod überleben sollen, müssen den Cluster verlassen, solange der Pod noch lebt; die App ändert sich dafür nicht. Auf AKS ist das Container Insights, ein Teil von Azure Monitor. Ich habe es vor dem Absturz eingeschaltet, mit meinem eigenen Log-Analytics-Arbeitsbereich (ohne --workspace-resource-id legt die CLI einen Standard-Arbeitsbereich außerhalb Ihrer Ressourcengruppe an):

$ws = az monitor log-analytics workspace show -g rg-demo -n law-demo --query id -o tsv
az aks enable-addons -a monitoring -g rg-demo -n aks-demo --workspace-resource-id $ws

Das Add-on brauchte 142 Sekunden. Es betreibt auf jedem Node einen Agenten als DaemonSet, eine Kopie eines Pods pro Node:

> kubectl get daemonset ama-logs -n kube-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
ama-logs 1 1 1 1 1 <none> 9m6s

Der Agent liest dieselben Log-Dateien wie kubectl und schickt jede Zeile in den Arbeitsbereich, in die Tabelle ContainerLogV2. Warning-Events landen in einer zweiten Tabelle, KubeEvents; Normal-Events bleiben standardmäßig draußen. Mehr als eine Stunde nach dem Absturz fragte diese Abfrage (KQL, die Abfragesprache von Log Analytics) nach jeder FATAL-Zeile im Namespace:

ContainerLogV2
| where PodNamespace == "aks-troubleshoot"
| where LogMessage has "FATAL"
| project TimeGenerated, PodName, LogMessage
| order by TimeGenerated asc
14:00:15 api-deployment-64598b4ccd-mjkwf 2026-11-02 14:00:15,612 - ERROR - FATAL: API_KEY environment variable is not set. Exiting.
14:00:31 api-deployment-64598b4ccd-mjkwf 2026-11-02 14:00:31,011 - ERROR - FATAL: API_KEY environment variable is not set. Exiting.
14:00:58 api-deployment-64598b4ccd-mjkwf 2026-11-02 14:00:58,043 - ERROR - FATAL: API_KEY environment variable is not set. Exiting.
14:01:45 api-deployment-64598b4ccd-mjkwf 2026-11-02 14:01:45,119 - ERROR - FATAL: API_KEY environment variable is not set. Exiting.
14:03:12 api-deployment-64598b4ccd-mjkwf 2026-11-02 14:03:12,051 - ERROR - FATAL: API_KEY environment variable is not set. Exiting.
5 rows

Fünf Zeilen, alle von dem Pod, den kubectl nicht mehr finden konnte. Fünf der sechs Abstürze: Der allererste, Sekunden nach dem Start des Pods, kam nie an. Die BackOff-Warnungen, die kubectl get events vergessen hatte, standen noch in KubeEvents, mit ihren Zählern:

KubeEvents
| where Namespace == "aks-troubleshoot" and Reason == "BackOff"
| project TimeGenerated, Name, Reason, Message, Count
| order by TimeGenerated asc
14:00:16 api-deployment-64598b4ccd-mjkwf Back-off restarting failed container api in pod api-deployment-64598b4ccd-mjkwf_aks-troubleshoot(<uid>) x1
… (dasselbe Event mit den Zählern x5, x7, x8, x9)
5 rows

Die Kopie kommt nicht sofort. Direkt nach dem Rollback hatte der Arbeitsbereich erst einen der Abstürze; der Rest kam später. Die Frage nach der neuesten Zeile aus dem Namespace zeigte den Abstand (118 Sekunden beim ersten Versuch, 99 bei diesem):

newest line in the workspace: 15:05:46 UTC, now: 15:07:25 UTC, 99 s behind

Eine Kopie kann hinterherhängen oder eine Zeile verlieren. Sie kostet auch Geld: Der Arbeitsbereich rechnet nach aufgenommenen Gigabyte ab, und die ersten 5 GB pro Abrechnungskonto und Monat sind kostenlos. Analytics-Tabellen behalten die Daten standardmäßig 30 Tage. Der Cluster vergisst; der Arbeitsbereich behält, was der Agent kopiert hat.

Drei Wege, die Beweise zu verlieren

Drei Gewohnheiten kosten Sie den Absturz:

Was Sie tunWas passiertIn meinem Lauf
kubectl logs deployment/<name>es nimmt einen Pod, nicht jeden„Found 2 pods, using“ den gesunden: 517 Zeilen, keine einzige FATAL
kubectl logs <pod> --previouses liest den Container vor dem aktuellen, nur im selben Podnach dem Rollback: BadRequest, der verbliebene Pod hatte nie einen vorherigen Container
zuerst zurückrollender Rollout löscht den abgestürzten Pod samt Log-DateienNotFound
> kubectl logs deployment/api-deployment
Found 2 pods, using pod/api-deployment-695945c854-nx6qj
2026-11-02 13:58:45,055 - INFO - Application started successfully
…
> kubectl logs deployment/api-deployment --previous
Error from server (BadRequest): previous terminated container "api" in pod "api-deployment-695945c854-nx6qj" not found

Sichern Sie zuerst das Crash-Log. Rollen Sie zurück, aber schreiben Sie vorher in eine Datei, was der abgestürzte Pod gesagt hat: kubectl logs <pod> > crash.log, mit seinem Namen, solange er noch existiert. Oder schalten Sie Container Insights ein, bevor Sie es brauchen.

Der Log-Befehl, der 720 Zeilen versteckte

Als alles wieder gesund war, habe ich mit PowerShell Zeilen gezählt: das ganze Log des verbliebenen Pods, den Befehl der Übung und denselben mit --tail=-1:

> (kubectl logs deployment/api-deployment).Count
730
> (kubectl logs -l app=api).Count
10
> (kubectl logs -l app=api --tail=-1).Count
732

Mit einem Label-Selektor zeigt kubectl logs nur die letzten 10 Zeilen jedes Pods. Ohne Selektor zeigt es alles; --tail=-1 bedeutet jede Zeile (zwei weitere waren inzwischen dazugekommen). Jeder Health Check und jeder Aufruf schreibt eine Zeile, das Log wächst also schnell. Im ersten Kapitel tauchte die FATAL-Zeile nur auf, weil der abgestürzte Container genau diese eine Zeile ausgegeben hatte. Eine FATAL-Zeile elf Zeilen vor dem Ende wäre nie auf meinem Bildschirm gelandet.

Was beim Ausführen passiert ist

Getestet am 2. November 2026 in meinem eigenen Azure-Abonnement (AKS 1.36 in westeurope, Azure CLI 2.88.0); danach wurde alles gelöscht. Der Cluster hatte einen Node vom Typ Standard_D2s_v5 (Free-Tarif, Azure CNI, die Registry angebunden, wie in der Übung) und einen Arbeitsbereich in derselben Region. Das Anlegen dauerte 294 Sekunden, das Monitoring-Add-on 142. Direkt nach dem Add-on antwortete kubectl rollout status daemonset ama-logs noch mit NotFound; das DaemonSet erschien Sekunden später. kubectl rollout undo warnte, dass die Annotation last-applied-configuration aus kubectl apply nicht aktualisiert wird. Die Antworten NotFound und BadRequest oben endeten mit Exit-Code 1. Jede KQL-Abfrage dauerte 3,5 bis 4,2 Sekunden, die Node-Listen etwa 2,3; die kubectl-Befehle oben antworteten jeweils in unter einer Sekunde.

Was das Video weglässt

  • Die Ansichten im Portal (Workloads, Live Logs, Live Events, Diagnose and solve problems), die dieselbe API lesen wie kubectl.
  • Metriken: kubectl top, Prometheus und Grafana.
  • Auch Normal-Events sammeln (collect_all_kube_events in der ConfigMap des Agenten) und die übrigen Einstellungen zur Datensammlung.
  • Strukturierte Logs und Korrelations-IDs, mit denen sich die Kopie leichter durchsuchen lässt.
  • Die beiden anderen Fehler des Moduls (eine Readiness-Probe auf dem falschen Pfad, ein Service-Selektor, der nichts trifft): eigene Beiträge.

Weiterführende Links

Video-Transkript (Englisch)

My new version crashed on Azure Kubernetes Service. Six crashes in under four minutes. I rolled it back with one command, and the app kept answering the whole time. Then I asked kubectl why it had crashed. Not found. The logs had gone with the pod.

In this video, you'll see where a pod's logs live, and where to find them an hour later. At the end: the Microsoft Learn log command that hid seven hundred lines.

Where does kubectl logs actually read from? In the Microsoft Learn module's exercise, a small Python API runs on Azure Kubernetes Service. A deployment keeps one pod of it running, and the app refuses to start without its API key. So I applied the exercise's broken version, the one without the key. kubectl apply sends a manifest, the app's description, to the cluster. Configured. A new pod started next to the old one. A small script of mine printed every change of the pods, with the restart count. The new pod crashed after five seconds, then again, and again. The old pod kept running, with zero restarts. The deployment keeps the old pod until the new one is ready, so the app kept answering. Then I ran the exercise's own log command: the logs of every pod with the label app=api, the tag the deployment puts on its pods. There it is. FATAL: API_KEY environment variable is not set. But where does that line live? The container writes to standard out, and the node stores it in a file. kubectl asks the cluster's API server, its front door, which asks the kubelet on that node, the agent that runs the pods. The kubelet reads the file and streams it back. With a helper pod that can see the node's disk, I listed the log folders of the two pods. One folder per pod. Inside, one file per container start, numbered from zero. The crashed pod was at number five, its sixth start, and the older files were already gone. Then I rolled back, with kubectl rollout undo, the one command from the start. Rolled back. The deployment went back to the working version, and deleted the crashed pod. First, I asked kubectl for the crashed pod's log by name. Not found. The pod and its log were gone. The rollback deleted the pod. Did the node keep its log files? The folder, yes. The files, no. I listed it again with the same helper pod. When a pod goes, its log files go with it. Even while it lives, the kubelet keeps only the newest file and one before it. kubectl logs can only read what's still there.

Don't the events at least remember the crash? Events are Kubernetes' own notes about what happened to an object: scheduled, pulled, started, back off. Right after the rollback, I asked for the events with the reason BackOff, the warning a crash loop leaves behind. One warning: back-off restarting failed container api, in the pod that no longer existed. So I waited an hour, and asked again with the same command. What did kubectl get events show? Nothing. No resources found. On Azure Kubernetes Service, events are only kept for one hour after they happen. There's no history behind them. The Azure portal doesn't help here either. Its Live Logs and Live Events read the same Kubernetes API as kubectl, so they only see what still exists. This series runs one Azure service at a time, for real. Subscribe to follow the series.

So where do you keep logs that outlive the pod? You copy them out of the cluster while the pod is still alive, and the app doesn't have to change. On Azure Kubernetes Service, that's Container insights, part of Azure Monitor. I had turned it on before the crash, with two lines. The first looks up my own Log Analytics workspace, a database for logs in Azure. The second turns monitoring on for the existing cluster. It took two minutes and twenty seconds. It runs an agent on every node, as a DaemonSet: one copy of a pod on each node. I listed it: ama-logs, the Azure Monitor agent. One node, one agent, ready. Add a node, and the DaemonSet adds an agent there too. The agent reads the same log files as kubectl, and sends every line to the workspace, into a table called ContainerLogV2. Warning events go to a second table, KubeEvents. Normal events, the routine ones, are left out by default. Then I asked the workspace for every fatal line in the exercise's namespace, the part of the cluster its objects live in. In KQL, the query language of Log Analytics. Start with the table. Keep the rows from the exercise's namespace. Keep the lines that contain fatal. And show three columns: the time, the pod name and the message. My small script sent exactly this query, more than an hour after the crash. Five lines, all from the pod that kubectl couldn't find any more. Five of the six crashes: the very first one, seconds after the pod started, never arrived. And the events? I asked the KubeEvents table for the BackOff warnings, after kubectl had forgotten them. Still there, with their counts. One thing to expect: the copy isn't instant. I asked the workspace for its newest line from the namespace. Ninety-nine seconds behind. Right after the rollback, it had only one of the crashes; the rest arrived minutes later. It's a copy, and a copy can lag, or miss a line. The copy costs money too: the workspace bills by the gigabyte it takes in, and the first five gigabytes each month are free. The cluster forgets. The workspace keeps what the agent copied: thirty days by default.

Which mistakes lose the evidence, even before the rollback? One: kubectl logs on the deployment. It doesn't read every pod. It picks one. While the new pod was still crashing, I asked for the logs of the deployment. Found two pods, using the healthy one. Five hundred seventeen lines, and not one of them fatal. Two: --previous. It reads the container before the current one, but only inside the same pod. After the rollback, I asked the pod that was left for its previous container. Bad request. It never had one. The crash had lived in a different pod. Three: rolling back before saving the crash log. Roll back, but first save what the crashed pod said: kubectl logs with its name, while it still exists, into a file. Or turn on Container insights before you need it.

To sum it up: kubectl logs reads files on the node, only for containers that still exist. Events are kept for one hour. Container insights copies both to a Log Analytics workspace, where they outlive the pod.

And the exercise's own log command? While everything was healthy again, I counted lines. First, the whole log of the pod that was left. PowerShell counted its lines. Seven hundred thirty lines: every health check and every call writes one. Then the exercise's command, with the label selector, counted the same way. Ten. Out of seven hundred thirty. With a label selector, kubectl shows only the last ten lines of each pod. So I ran the exercise's command once more, with --tail=-1, which means every line. Seven hundred thirty-two. Two more had arrived in the meantime. In chapter one it found the fatal line only because the crashed container had printed just that one line. A fatal line eleven lines from the end would never have been on my screen.

In the queue scaling video, the only place the real error showed up was Log Analytics. That video is on screen now. If this was useful, subscribe for more practical .NET and Azure.

Brauchen Sie das in Ihrer eigenen Azure-Umgebung?

Ich entwickle, migriere und betreibe .NET-Plattformen auf Azure, mit Azure DevOps und Terraform. Wenn Ihr Team das sauber aufsetzen möchte, sehen Sie sich mein Angebot an.

Zu den Leistungen