Risoluzione dei problemi: Diagnosi rapida degli errori comuni dei container Docker
Padroneggia l'arte della rapida risoluzione dei problemi dei container Docker con questa guida essenziale. Impara il processo strutturato per diagnosticare i fallimenti di avvio utilizzando i comandi Docker principali. Spieghiamo come sfruttare `docker ps -a` per identificare i crash, estrarre informazioni critiche con `docker logs` ed eseguire analisi avanzate della configurazione con `docker inspect`. Questo articolo fornisce esempi pratici e soluzioni mirate per problemi frequenti, inclusi errori con codice di uscita 127, conflitti di porta ed eventi OOMKilled, assicurandoti di poter identificare rapidamente la causa principale e ripristinare il servizio.
Risoluzione dei problemi: Diagnosi rapida degli errori comuni dei container Docker
Quando un container Docker esce immediatamente, non iniziare ricostruendo l'immagine o cambiando flag a caso. Inizia scoprendo cosa sa Docker: lo stato del container, il codice di uscita, i log e il comando esatto che Docker ha tentato di eseguire. Questi quattro elementi di solito restringono rapidamente il problema.
Un container è solo un processo con isolamento attorno. Se il processo principale esce, il container esce. Può essere un crash, un eseguibile mancante, un job batch completato, una dipendenza di health check fallita o il kernel che uccide il processo perché ha usato troppa memoria. I comandi seguenti ti aiutano a distinguere questi casi.
Trova prima il container fermo
docker ps mostra solo i container in esecuzione. I container che non sono riusciti ad avviarsi sono solitamente nascosti a meno che non chiedi tutti i container:
docker ps -a
Guarda STATUS, COMMAND e NAMES:
CONTAINER ID IMAGE COMMAND STATUS NAMES
2d3f4b5c6e7a my-app:latest "/usr/bin/start" Exited (127) 2 minutes ago web-service
91aa34c0db22 worker:latest "python worker.py" Exited (0) 10 minutes ago nightly-worker
Exited (0) spesso significa che il processo è stato completato con successo. Questo è normale per job one-shot. Per un servizio web, può significare che il comando è stato eseguito e terminato invece di rimanere in primo piano.
I codici di uscita diversi da zero indicano un fallimento, ma trattali come indizi piuttosto che come risposte definitive. Il codice di uscita 127 comunemente significa comando non trovato. 126 comunemente significa trovato ma non eseguibile. 137 spesso significa che il processo ha ricevuto SIGKILL; nei container questo è frequentemente, ma non sempre, correlato alla pressione della memoria. Conferma sempre con log e output di inspect.
Leggi i log prima di cambiare qualsiasi cosa
Docker cattura stdout e stderr dal processo principale del container per il driver di logging predefinito. Usa:
docker logs web-service
Opzioni utili:
docker logs --tail 100 web-service
docker logs --since 15m web-service
docker logs -t web-service
docker logs -f web-service
Se i log dicono config file not found, controlla i mount e l'ambiente. Se mostrano un stack trace dell'applicazione, esegui il debug dell'applicazione. Se sono vuoti, il processo potrebbe essere fallito prima di produrre output, o l'entrypoint dell'immagine potrebbe essere sbagliato.
Per un crash loop, evita docker logs -f come tuo unico strumento. Può far sembrare il fallimento attivo senza darti lo stato. Abbina i log con docker inspect.
Ispeziona lo stato del container
docker inspect restituisce un grande documento JSON. Raramente ti serve tutto. Inizia con campi formattati:
docker inspect -f 'status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} error={{.State.Error}}' web-service
Poi ispeziona il comando e la configurazione dell'immagine:
docker inspect -f 'entrypoint={{json .Config.Entrypoint}} cmd={{json .Config.Cmd}} user={{.Config.User}}' web-service
Controlla i mount quando l'errore coinvolge file:
docker inspect -f '{{json .Mounts}}' web-service
Se il container è stato ucciso per memoria, .State.OOMKilled è il campo importante. Se è true, aumentare la memoria può aiutare, ma la domanda migliore successiva è perché la memoria è cresciuta. Un limite più grande può nascondere una perdita abbastanza a lungo da fallire più tardi.
Riproduci con una shell interattiva quando possibile
Se l'immagine contiene una shell, sovrascrivi l'entrypoint e ispeziona il filesystem:
docker run --rm -it --entrypoint /bin/sh my-app:latest
Alcune immagini hanno Bash:
docker run --rm -it --entrypoint /bin/bash my-app:latest
All'interno, controlla i file e i percorsi dei comandi:
ls -l /usr/bin/start
id
env
Le immagini minime potrebbero non includere una shell. In tal caso, usa gli strumenti disponibili dell'immagine, ricostruisci una variante di debug temporanea o ispeziona il Dockerfile e l'output della build. Non aggiungere permanentemente pacchetti di debug a un'immagine di produzione solo perché la risoluzione dei problemi è stata scomoda una volta.
Comando non trovato: exit 127
L'uscita 127 di solito significa che Docker non ha potuto trovare l'eseguibile nominato da ENTRYPOINT o CMD, o uno script di avvio ha tentato di eseguire un comando mancante.
Cause comuni:
- L'eseguibile non è mai stato copiato nell'immagine.
- Il percorso è corretto sull'host ma non all'interno dell'immagine.
- Lo script usa
/bin/bash, ma l'immagine ha solo/bin/sh. - Il comando dipende da
PATH, ePATHdifferisce da quello che ti aspetti.
Controlla il comando dell'immagine:
docker inspect -f '{{json .Config.Entrypoint}} {{json .Config.Cmd}}' web-service
Se l'entrypoint è uno script, controlla il suo shebang e le terminazioni di riga. Uno script con terminazioni CRLF di Windows può fallire con messaggi confusi di "non trovato" perché il percorso dell'interprete contiene effettivamente un carriage return.
Permesso negato: exit 126 o errori di file
L'uscita 126 spesso significa che Docker ha trovato il comando ma non ha potuto eseguirlo. Per gli script, il file potrebbe non avere il bit eseguibile:
COPY start.sh /usr/local/bin/start.sh
RUN chmod 0755 /usr/local/bin/start.sh
ENTRYPOINT ["/usr/local/bin/start.sh"]
Per i file montati da volume, ricorda che si applicano i permessi dell'host. Se un container viene eseguito come UID 1000 e la directory dell'host è di proprietà di root senza permesso di scrittura, il container non può scrivere lì solo perché è "dentro Docker".
Controlla l'utente runtime:
docker inspect -f 'user={{.Config.User}}' web-service
Se è vuoto, molte immagini vengono eseguite come root per impostazione predefinita, ma non tutte. Le immagini ufficiali e hardened per la sicurezza usano spesso un utente non root.
Porta già allocata
Un errore di bind di solito appare quando pubblichi una porta host che è già in uso:
docker run -p 8080:80 nginx
Docker potrebbe segnalare qualcosa come bind: address already in use. Trova il conflitto:
docker ps --format 'table {{.Names}}\t{{.Ports}}'
lsof -iTCP:8080 -sTCP:LISTEN
Quindi ferma il processo in conflitto o scegli un'altra porta host:
docker run -p 8081:80 nginx
La porta del container può rimanere la stessa. La porta host è la parte prima dei due punti.
File mancanti e mount errati
Se i log dicono che un file di configurazione è mancante, confronta ciò che l'applicazione si aspetta con ciò che Docker ha montato:
docker inspect -f '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}' web-service
Un errore comune è montare una directory host su un percorso che già aveva file nell'immagine. Il mount nasconde i contenuti dell'immagine in quella destinazione. Se l'immagine contiene /app/config/default.yml e monti una directory host vuota su /app/config, il file predefinito scompare dalla vista del container.
Controlla anche i percorsi relativi. -v ./config:/app/config dipende dalla directory in cui hai eseguito docker run, non dalla directory in cui si trova il Dockerfile.
I fallimenti degli health check non sono sempre crash del container
Un container può essere in esecuzione ma non sano:
docker ps
Potresti vedere Up 2 minutes (unhealthy). Ispeziona l'output dell'health check:
docker inspect -f '{{json .State.Health}}' web-service
Gli health check spesso falliscono perché l'app è in ascolto su una porta diversa, si lega solo a 127.0.0.1, impiega più tempo ad avviarsi di quanto l'health check permetta, o ha bisogno di un database che non è ancora pronto. Non confondere un container non sano con uno uscito; il percorso diagnostico è diverso.
Una sequenza rapida di risoluzione dei problemi
Usa questo ordine quando hai bisogno di una risposta rapidamente:
docker ps -aper trovare il container e il codice di uscita.docker logs --tail 100 <nome>per leggere l'errore dell'applicazione.docker inspect -f ...per controllare stato, comando, utente e mount.- Esegui una shell temporanea nell'immagine se il comando o il filesystem sono sospetti.
- Controlla i conflitti host per porte e permessi delle directory montate.
- Ricostruisci solo dopo aver saputo se il problema è il contenuto dell'immagine, i flag runtime o la configurazione dell'applicazione.
Questa sequenza mantiene l'indagine concreta. Docker di solito ha abbastanza prove; il trucco è leggerle prima di cambiare la scena.
Controlla la politica di riavvio prima di fidarti di ciò che vedi
Una politica di riavvio può far sembrare un container in continuo fallimento o in continuo recupero. Controllala:
docker inspect -f 'restart={{json .HostConfig.RestartPolicy}}' web-service
Se la politica è always o unless-stopped, Docker potrebbe riavviare il container dopo ogni crash. docker ps potrebbe mostrarlo in esecuzione per pochi secondi, poi riavviarsi di nuovo. In tal caso, usa i log con timestamp e ispeziona il conteggio dei riavvii:
docker inspect -f 'restarts={{.RestartCount}} started={{.State.StartedAt}} finished={{.State.FinishedAt}}' web-service
Un conteggio di riavvii elevato di solito significa che il processo principale esce rapidamente. La soluzione è raramente "cambiare la politica di riavvio". La politica sta solo rivelando il fallimento sottostante.
Distinguere i problemi di build-time da quelli di run-time
Se un file manca all'interno del container, chiediti quando dovrebbe essere apparso. I file copiati nel Dockerfile sono problemi di build-time. I file montati con -v o volumi Compose sono problemi di run-time.
Controlli di build-time:
docker image inspect my-app:latest
docker run --rm --entrypoint /bin/sh my-app:latest -c 'ls -la /app'
Controlli di run-time:
docker inspect -f '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}' web-service
Questa divisione fa risparmiare tempo. Ricostruire l'immagine non risolverà un mount host errato. Cambiare un flag di volume non risolverà un Dockerfile che non ha mai copiato il binario.
Le variabili d'ambiente e i segreti possono fallire silenziosamente
Molte applicazioni escono perché una variabile d'ambiente richiesta è mancante, ma l'errore Docker dice solo che il processo è uscito con codice 1. Ispeziona attentamente l'ambiente configurato:
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' web-service
Fai attenzione a dove esegui quel comando; può stampare segreti. Nei log condivisi, stampa solo i nomi delle variabili:
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' web-service | sed 's/=.*//'
Se usi --env-file, controlla le terminazioni CRLF, gli spazi non quotati e i file mancanti. I file env di Docker non sono script shell completi. Mantienili semplici: righe KEY=value, commenti dove supportati dalla tua versione di Docker e nessuna assunzione che l'espansione della shell avvenga all'interno del file.
Una revisione realistica prima di distribuire
Prima di considerare uno script o una configurazione di container come finita, leggila una volta come se fossi la prossima persona che deve eseguirne il debug alle 2 del mattino. Questo cambia ciò che noti. Un prompt che aveva senso mentre scrivevi lo script potrebbe essere ambiguo quando appare in un log CI. Un nome di servizio Docker che sembrava ovvio potrebbe non corrispondere al nome della variabile nell'applicazione. Un valore predefinito di Bash potrebbe essere sicuro per lo sviluppo e pericoloso per la produzione.
Mi piace fare un breve dry run con valori deliberatamente scomodi. Usa un percorso con spazi. Usa un valore opzionale vuoto. Prova un nome di file che inizia con un trattino. Esegui lo script da una directory di lavoro diversa. Avvia il container senza una variabile d'ambiente prevista. Questi test non sono fantasiosi, ma catturano le assunzioni che di solito si rompono per prime.
Controlla anche il messaggio di fallimento. Se l'unico output è failed, il consiglio dell'articolo non è stato implementato. Un fallimento utile dice quale valore è stato usato, quale controllo è fallito e cosa l'operatore può cambiare. Questo non significa scaricare ogni variabile d'ambiente o stampare segreti. Significa essere specifici dove la specificità aiuta: il percorso di configurazione, il nome del comando mancante, il nome della rete, l'hostname del servizio o la porta a cui il processo ha tentato di legarsi.
L'abitudine finale è mantenere gli esempi vicini al modo in cui il sistema viene effettivamente eseguito. Se la produzione usa Compose, testa con Compose. Se uno script viene avviato da systemd, testalo con systemd o con un ambiente altrettanto minimo. Se un comando dovrebbe essere sicuro per copia e incolla, includi le virgolette, i separatori -- e la convalida nell'esempio stesso. I lettori copiano schemi funzionanti più spesso di quanto copino avvisi.
Quella revisione non è burocrazia. È come la piccola automazione rimane noiosa. Noioso è ciò che vuoi da prompt shell, caricatori di configurazione, espansione di variabili, diagnostica dei container e networking Docker. Meno sorprendente è il comportamento, più facile è per il prossimo operatore fidarsene.
Per i fallimenti Docker, salva il comando esatto che ha creato il container quando puoi. docker inspect può mostrare la configurazione corrente, ma una nota di incidente che include il comando docker run originale, il servizio Compose, il tag dell'immagine e il nome del file env è molto più facile da riprodurre in seguito. Evita di usare latest durante il debug serio. Un tag mobile può trasformare un fallimento in due indagini diverse perché l'immagine cambia mentre stai ancora leggendo i log.
Se stai diagnosticando un problema simile alla produzione localmente, tira la stessa immagine digest o tag, usa lo stesso layout di volume e passa la stessa forma di ambiente non segreto. La riproduzione batte la speculazione.