Risoluzione dei problemi dei container Docker: problemi comuni di avvio e soluzioni
Diagnostica i container Docker che si arrestano, non riescono a legare le porte, mancano file, incontrano problemi di permessi o vengono terminati per memoria.
Risoluzione dei problemi dei container Docker: problemi comuni di avvio e soluzioni
Quando un container Docker non si avvia, la soluzione più rapida di solito arriva resistendo all'impulso di indovinare. Un container è solo un processo con un filesystem, un ambiente, impostazioni di rete e limiti che lo circondano. Se quel processo esce, Docker registra il motivo. Il tuo compito è raccogliere le prove nell'ordine giusto.
Di solito inizio con tre domande: Docker ha creato il container, il processo principale è iniziato e qualcosa al di fuori del processo lo ha ucciso o bloccato? Quelle domande separano un nome di immagine sbagliato da un comando rotto, un conflitto di porta da un arresto anomalo dell'applicazione e un problema di permessi da un limite di memoria.
Inizia con il comando noioso che ti dice la verità:
docker ps -a
Guarda STATUS, PORTS e NAMES. Created significa che Docker ha creato il container ma non lo ha effettivamente avviato. Exited (1) spesso significa che l'applicazione ha restituito un errore normale. Exited (127) comunemente indica un comando mancante. Exited (137) spesso significa che il processo è stato ucciso dall'esterno, frequentemente a causa di pressione sulla memoria. Questi codici sono indizi, non risposte finali, ma ti impediscono di eseguire il debug del livello sbagliato.
Quindi leggi i log:
docker logs --tail 100 <container>
docker logs -f <container>
Se il container muore immediatamente, docker logs è di solito più utile che rieseguire lo stesso comando docker run. I framework applicativi spesso stampano la variabile d'ambiente mancante esatta, il fallimento della migrazione, il file di configurazione non valido o l'errore di bind prima di uscire.
Per lo stato di basso livello, ispeziona il container:
docker inspect <container> --format '{{json .State}}'
docker inspect <container> --format 'exit={{.State.ExitCode}} oom={{.State.OOMKilled}} error={{.State.Error}}'
Vale la pena memorizzare quel secondo comando. Ti dice se Docker ha visto un kill OOM, quale codice di uscita è stato registrato e se il runtime stesso ha avuto un errore.
Se il container esce immediatamente
Un container rimane vivo solo finché il suo processo principale rimane vivo. Se il comando finisce, Docker ferma il container. Questo sorprende le persone quando eseguono script che avviano un demone in background e poi restituiscono il controllo.
Ad esempio, questo modello spesso esce:
CMD service nginx start
Il comando service può avviare nginx e poi finire. Docker vede il processo principale terminare e ferma il container. Il modello adatto ai container è eseguire il server in primo piano:
CMD ["nginx", "-g", "daemon off;"]
La stessa idea si applica a Node, Python, Java e processi worker. Il comando in CMD o ENTRYPOINT dovrebbe essere il processo a lunga esecuzione, non un lanciatore che mette in background il lavoro reale ed esce.
Se i log mostrano command not found, no such file or directory o exec format error, testa l'immagine in modo interattivo:
docker run --rm -it --entrypoint sh <image>
Alcune immagini non includono bash, specialmente le immagini Alpine e distroless. Usa sh per primo a meno che non sai che bash esiste. Una volta dentro, controlla il percorso del file, i permessi e l'interprete:
ls -l /app
which python || true
head -1 /app/start.sh
Uno script può esistere e fallire comunque con no such file or directory se il suo shebang punta a un interprete mancante, come #!/bin/bash in un'immagine che ha solo /bin/sh. Un'altra causa comune sono le terminazioni di riga di Windows. Se uno script shell è stato modificato su Windows, il \r invisibile può far cercare a Linux /bin/sh\r.
Se Docker dice che la porta è già allocata
I conflitti di porta si verificano sul lato host. In -p 8080:80, 8080 è la porta host e 80 è la porta del container. Se qualcosa sta già ascoltando sulla porta host 8080, Docker non può legarla.
Potresti vedere un errore come bind: address already in use o port is already allocated. Trova l'ascoltatore:
sudo lsof -i :8080
# oppure
sudo ss -ltnp 'sport = :8080'
Su macOS, lsof è di solito il più semplice. Su server Linux, ss è spesso disponibile per impostazione predefinita. Su Windows PowerShell, usa:
Get-NetTCPConnection -LocalPort 8080
Quindi scegli una porta host diversa o ferma il servizio che la possiede:
docker run -d -p 8081:80 nginx
Non cambiare la porta del container a meno che l'applicazione all'interno del container non ascolti effettivamente su quella nuova porta. Se nginx ascolta sulla 80 all'interno del container, -p 8081:80 è corretto. -p 8081:8081 fallirà dal browser se nulla all'interno del container sta ascoltando sulla 8081.
Se l'app si avvia ma non riesce a trovare la configurazione
Molti fallimenti di avvio sono variabili d'ambiente mancanti. L'immagine è a posto, il comando è a posto, ma l'app si aspetta DATABASE_URL, REDIS_URL, una chiave API o un file di configurazione.
Controlla cosa Docker ha passato:
docker inspect <container> --format '{{range .Config.Env}}{{println .}}{{end}}'
Per progetti Compose, ispeziona la configurazione risolta piuttosto che leggere solo docker-compose.yml:
docker compose config
Questo cattura errori di indentazione, sorprese del file .env e variabili che si sono espanse in stringhe vuote. Un esempio reale: DATABASE_URL=${DATABASE_URL} sembra innocuo, ma se la shell o il file .env non lo definisce, la tua applicazione potrebbe ricevere un valore vuoto e fallire durante l'avvio.
Fai attenzione con i segreti nei log e nella cronologia del terminale. Per un debug locale rapido, passare -e NAME=value va bene. Per sistemi condivisi, usa il meccanismo dei segreti della tua piattaforma o un file di ambiente con permessi controllati.
Se i bind mount o i volumi causano errori di permesso
Un container può fallire all'avvio perché non può leggere un file di configurazione, scrivere un file PID, creare una directory cache o inizializzare una directory del database. I log di solito dicono permission denied, read-only file system o operation not permitted.
Prima ispeziona il mount:
docker inspect <container> --format '{{json .Mounts}}'
Quindi controlla quale utente esegue il container:
docker inspect <container> --format 'user={{.Config.User}}'
Se user è vuoto, l'immagine potrebbe essere eseguita come root per impostazione predefinita, ma molte immagini di produzione impostano un utente non root. Una directory host di proprietà del tuo UID locale potrebbe non essere scrivibile dall'UID 1000, 1001 o da un utente specifico del servizio all'interno del container.
Una sequenza di debug pratica è:
ls -ld ./data
docker run --rm -it -v "$PWD/data:/data" --entrypoint sh <image>
id
ls -ld /data
touch /data/test
Evita di risolvere ogni problema di permessi con chmod 777. Può nascondere il problema immediato mentre ne crea uno peggiore. Preferisci abbinare la proprietà o usare volumi nominati per i dati dell'applicazione:
docker volume create app_data
docker run -d -v app_data:/var/lib/app <image>
I volumi nominati sono particolarmente utili su Docker Desktop, dove i bind mount attraversano un confine di virtualizzazione e possono comportarsi diversamente dai filesystem Linux nativi.
Se il container è stato ucciso per memoria
Il codice di uscita 137 è un forte indizio che il processo ha ricevuto SIGKILL. Nel lavoro Docker, questo spesso significa che il kernel o Docker Desktop lo ha ucciso perché la memoria è esaurita. Conferma con inspect:
docker inspect <container> --format 'exit={{.State.ExitCode}} oom={{.State.OOMKilled}}'
Se OOMKilled è true, hai due compiti: dare al processo abbastanza memoria per avviarsi e capire perché ne aveva bisogno così tanta. Aumentare il limite potrebbe essere la giusta correzione di produzione per un database o un servizio JVM. Per un piccolo servizio web, potrebbe rivelare un'impostazione predefinita sbagliata.
Le app Java sono un esempio classico. Il comportamento JVM più vecchio non si adattava sempre bene ai limiti del container, e anche le JVM moderne hanno ancora bisogno di impostazioni -Xmx sensate o basate su percentuali per un comportamento prevedibile. I servizi Node potrebbero aver bisogno di --max-old-space-size in ambienti con memoria limitata. I database potrebbero aver bisogno di impostazioni esplicite della cache.
Per un test una tantum:
docker run --memory=1g <image>
Se usi Docker Desktop, controlla anche la memoria assegnata alla VM Docker. Un limite del container non può aiutare se la VM stessa è affamata.
Se l'immagine non viene mai scaricata o la build non ha mai prodotto un'immagine
A volte non c'è un problema del container perché non c'è un'immagine utilizzabile. Se docker run fallisce prima di creare un container, verifica l'immagine separatamente:
docker image ls | grep my-app
docker pull my-registry/my-app:tag
Per registri privati, conferma l'autenticazione:
docker login <registry>
Per immagini locali, assicurati che il tag che esegui sia il tag che hai costruito:
docker build -t my-app:dev .
docker run --rm my-app:dev
Un errore locale comune è costruire my-app:dev ed eseguire my-app:latest, che potrebbe puntare a un'immagine più vecchia o a niente.
Se la rete è incolpata ma il servizio non sta ascoltando
Quando un browser non riesce a raggiungere un container, le persone spesso saltano alla rete Docker. Prima dimostra che l'applicazione sta ascoltando all'interno del container.
docker exec -it <container> sh
ss -ltnp || netstat -ltnp
Se l'app è legata a 127.0.0.1 all'interno del container, la pubblicazione della porta Docker non aiuterà. L'app deve ascoltare su 0.0.0.0 o sull'indirizzo dell'interfaccia del container. Questo è comune con i server di sviluppo. Ad esempio, molti framework predefiniti usano localhost e hanno bisogno di un flag come --host 0.0.0.0.
Quindi conferma la porta pubblicata:
docker port <container>
docker ps --format 'table {{.Names}} {{.Ports}}'
Vuoi vedere qualcosa come 0.0.0.0:8080->3000/tcp. Se non c'è una porta pubblicata, il servizio potrebbe funzionare da un altro container sulla stessa rete ma non dal browser del tuo host.
Una checklist di avvio affidabile
Usa questo ordine quando sei bloccato:
docker ps -aper vedere se il container esiste e come è uscito.docker logs --tail 100 <container>per leggere il reclamo dell'applicazione stessa.docker inspect <container>per controllare il codice di uscita, lo stato OOM, il comando, l'utente, i mount e le porte.docker run --rm -it --entrypoint sh <image>per testare l'immagine manualmente.- Rimuovi una variabile alla volta: prima esegui senza mount, poi senza reti personalizzate, poi solo con le variabili d'ambiente richieste.
Quell'ultimo passo è importante. Un lungo comando docker run con porte, volumi, file env, DNS personalizzato, limiti di memoria e un entrypoint personalizzato ti dà troppi sospetti. Riducilo fino a quando l'immagine si avvia, poi aggiungi le impostazioni fino a quando si rompe. L'impostazione che hai appena aggiunto è di solito dove vive il vero problema.