df cheio e du normal: como encontrar o espaço
Veja por que `df` acusa disco cheio enquanto `du` não mostra o espaço: localize arquivos excluídos ainda abertos e verifique inodes, mounts e blocos reservados.
Por que df informa disco cheio e du não encontra o espaço
df informa que o disco está cheio, enquanto du não encontra o espaço porque um processo ainda mantém aberto um ficheiro que foi eliminado. Eliminar um ficheiro remove o seu nome do diretório. Os blocos de dados só são libertados quando o último descritor de ficheiro aberto que aponta para esse inode é fechado. du percorre os nomes, por isso não contabiliza nada. df pergunta ao sistema de ficheiros quantos blocos estão alocados, por isso continua a contabilizar o ficheiro que já não tem nome.
Este guia reproduz o problema numa VPS Ubuntu simples, usando ferramentas que já estão instaladas. Encontra o processo que mantém o ficheiro aberto através de /proc e liberta o espaço sem reiniciar o sistema. Também são apresentadas as outras causas do mesmo sintoma: uma tabela de inodes sem entradas livres, ficheiros ocultos sob um ponto de montagem e blocos reservados para root.
Execute cada comando e analise o seu próprio resultado. Os valores dependem do seu disco. Compare os valores antes e depois na sua própria máquina, em vez de os comparar com um valor apresentado num guia.
O que df conta e o que du conta
df (disk free) consulta cada sistema de ficheiros montado para obter os seus próprios dados: quantos blocos existem, quantos estão alocados e quantos estão livres. Nunca abre um diretório. O resultado inclui todos os blocos alocados, incluindo os blocos pertencentes a um ficheiro para o qual nenhuma entrada de diretório aponta.
du (disk usage) faz o contrário. Começa no caminho indicado, lê os diretórios, consulta cada entrada encontrada e soma os blocos. Um ficheiro sem nome fica invisível para o comando. O mesmo acontece com qualquer diretório que não tenha permissão para ler, razão pela qual um utilizador normal obtém um total inferior ao de root. Execute du com sudo antes de tirar conclusões da comparação.
Há duas opções importantes sempre que compara os dois comandos.
-xmantémdunum único sistema de ficheiros. Sem essa opção,du /percorre todos os sistemas de ficheiros montados abaixo de/e produz um total quedf /nunca estava a medir.-sapresenta uma linha de resumo por argumento, em vez de uma linha por diretório.
Assim, estes são os comandos a executar lado a lado no sistema de ficheiros relevante.
df -h /
sudo du -xhs / 2>/dev/nulldf responde imediatamente. du pode demorar minutos num sistema de ficheiros grande, porque consulta cada ficheiro durante o percurso. Quando os dois totais são muito diferentes e du foi executado como root com -x, o espaço em falta está alocado a algo que não tem nome.
Reproduza a inconsistência de propósito
Faça isto numa VPS de teste. Tudo abaixo usa bash e coreutils, por isso nada é instalado.
Registe o estado inicial do sistema de ficheiros que contém /var/tmp.
cd /var/tmp
df -h .
df --output=used -B1 .O segundo comando mostra os bytes utilizados sem arredondamento, o que torna a verificação final exata.
Agora crie um ficheiro. O tamanho é obtido a partir do espaço livre indicado pela própria máquina, por isso a demonstração adapta-se ao disco que tiver.
free=$(df --output=avail -B1 . | tail -n 1)
fallocate -l $((free / 10)) ghost.bin
ls -l ghost.bin
df -h .$(...) é substituição de comandos: a shell executa o comando dentro dela e o resultado passa a ser o valor de free. Se esta sintaxe for nova para si, substituição de comandos no bash explica-a corretamente. fallocate reserva blocos reais sem os escrever, por isso termina imediatamente. Num sistema de ficheiros que não suporte esta operação, o comando falha, e head -c $((free / 10)) /dev/zero > ghost.bin faz o mesmo escrevendo os bytes.
Compare este df -h . com o que registou. A coluna de espaço utilizado aumentou e a coluna de espaço disponível diminuiu.
Agora mantenha o ficheiro aberto por outro processo e elimine-o.
sleep infinity < ghost.bin &
holder=$!
rm ghost.bin
ls -l ghost.bin
df -h .
sudo du -xhs . 2>/dev/nullO redirecionamento é o mecanismo essencial. sleep infinity < ghost.bin & inicia um processo em segundo plano cuja entrada padrão é esse ficheiro, por isso a shell abre o ficheiro e entrega o descritor a sleep, que o mantém aberto. $! guarda o ID do processo desse trabalho em segundo plano. rm remove então o nome enquanto o descritor continua aberto.
Leia o resultado. ls não encontra o ficheiro, porque o nome desapareceu. du volta para perto do valor inicial, porque percorre os nomes. df não mudou, porque os blocos continuam alocados. O sistema de ficheiros e a árvore de diretórios estão agora em desacordo, e a diferença entre ambos é o ficheiro que acabou de eliminar.
Encontrar o processo que mantém aberto o ficheiro eliminado
Cada descritor de ficheiro aberto aparece em /proc/<pid>/fd/ como uma ligação simbólica para o ficheiro a que se refere. Quando o ficheiro é desvinculado, o kernel marca o destino dessa ligação como eliminado. Portanto, para encontrar o processo que o mantém aberto, procure uma ligação cujo destino contenha essa marca.
sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/null-lname compara o destino de uma ligação simbólica, e não o respetivo nome, %p mostra o caminho do descritor e %l mostra para onde aponta. O ID do processo é o segundo elemento do caminho apresentado. Execute-o com sudo, porque, caso contrário, só poderá ler /proc/<pid>/fd dos seus próprios processos. O redirecionamento de stderr elimina as mensagens dos processos que terminam enquanto find percorre a árvore.
Um servidor ocupado mantém vários ficheiros eliminados abertos a qualquer momento, e a maioria é pequena e inofensiva. Ordene-os por tamanho para que apenas os mais relevantes apareçam no topo.
sudo bash -c 'for fd in /proc/[0-9]*/fd/*; do
target=$(readlink "$fd" 2>/dev/null) || continue
case "$target" in
*"(deleted)") echo "$(stat -Lc %s "$fd" 2>/dev/null) $fd $target" ;;
esac
done' | sort -rn | headstat -L segue a ligação até ao próprio inode, pelo que %s apresenta o tamanho do ficheiro que já não tem nome. Ordenar por esse número coloca primeiro o ficheiro maior.
Em seguida, identifique o processo associado ao descritor selecionado. O caminho no topo da lista contém os dois números necessários. Coloque-os primeiro em variáveis, substituindo PID e N pelos valores apresentados na sua própria listagem.
pid=PID
n=N
ps -o pid,user,etime,args -p "$pid"
sudo stat -L "/proc/$pid/fd/$n"ps identifica o programa e mostra há quanto tempo está em execução. stat -L apresenta o tamanho e o número de blocos alocados do inode eliminado. Em conjunto, respondem à pergunta importante: que serviço está a manter este ficheiro aberto.
Se a máquina já tiver lsof, sudo lsof +L1 lista os ficheiros abertos cujo número de ligações caiu para zero e mostra os tamanhos numa única tabela. Essa ferramenta não está presente numa imagem mínima do Ubuntu, e instalar um pacote num sistema de ficheiros sem espaço livre pode falhar, pelo que a pesquisa com /proc é a versão que funciona sempre.
Liberar o espaço sem reiniciar
Um reboot resolve o problema, mas é a primeira medida errada: interrompe o serviço e destrói as evidências. Existem quatro opções menos intrusivas, pela ordem em que deve experimentá-las.
Primeiro, copie os dados para outro local se ainda precisar deles. Ler o caminho do descritor lê o inode ativo.
sudo cp /proc/<pid>/fd/<n> /root/recovered.logEste é o único caso em que é fácil recuperar um ficheiro eliminado. Por isso, recuperar ficheiros eliminados com rm -rf começa por perguntar se algum processo ainda mantém o ficheiro aberto. Quando o último descritor é fechado, essa possibilidade desaparece.
Segundo, esvazie o ficheiro através do descritor. O caminho /proc aponta para o mesmo inode. Truncá-lo liberta os blocos enquanto o processo continua em execução.
sudo truncate -s 0 "/proc/$pid/fd/$n"
df -h /Isto funciona corretamente quando o escritor abriu o ficheiro no modo append, porque cada escrita é feita no fim atual do ficheiro. Caso contrário, o processo mantém a posição de escrita antiga. A escrita seguinte é feita muito para além do início do ficheiro e recria-o com um hole no início. Um hole não é alocado, por isso os blocos continuam livres e df mantém o espaço que acabou de devolver. O que volta a aparecer é apenas o tamanho: execute sudo stat -L "/proc/$pid/fd/$n" novamente depois de o processo escrever. O comando mostra o tamanho antigo junto de uma contagem de blocos que já não corresponde a esse tamanho. Reinicie o processo quando também quiser que o tamanho comece em zero.
Terceiro, peça ao serviço para reabrir os logs. Um daemon cujo ficheiro de log foi eliminado enquanto estava aberto é a forma real mais comum deste problema. Muitos daemons reabrem os seus ficheiros de log depois de receberem um sinal: nginx usa SIGUSR1 e rsyslog usa SIGHUP. Consulte a documentação do daemon que está a administrar em vez de adivinhar. O sinal errado enviado ao daemon errado pode interrompê-lo.
sudo systemctl kill -s USR1 nginxQuarto, reinicie a unidade. sudo systemctl restart <unit> fecha todos os descritores que o processo antigo mantinha abertos, por isso os blocos são certamente libertados. Na demonstração anterior, o processo que mantém o descritor aberto é um sleep que iniciou manualmente. Por isso, terminá-lo é suficiente.
kill $holder
df -h .
df --output=used -B1 .
sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/nullCompare os bytes utilizados com o valor que registou antes de criar o ficheiro. Os valores voltam a coincidir e find já não mostra o seu descritor. Verificar com o mesmo comando que encontrou o problema é um hábito que vale a pena manter.
É mais fácil acompanhar essa alteração do que executar df manualmente várias vezes. watch repete um comando num intervalo fixo e reimprime o resultado no mesmo local. Assim, watch df -h / mostra a alteração da coluna de espaço utilizado à medida que o espaço é libertado.
Quando os totais coincidem e o disco continua cheio
Se df e um du -x de root coincidirem, não há nenhum ficheiro eliminado envolvido. As causas restantes são de outro tipo, e cada uma tem a sua própria verificação.
Sem inodes, não sem blocos
Um inode contém os metadados de um ficheiro. O ext4 cria um número fixo de inodes quando o sistema de ficheiros é criado, por isso um sistema de ficheiros pode ficar sem inodes enquanto ainda tem blocos livres. A criação de novos ficheiros falha, embora df -h ainda mostre espaço disponível.
df -h /
df -i /O primeiro comando conta blocos e o segundo conta inodes. Compare a coluna de utilização de cada um. Uma utilização baixa de blocos e uma utilização de inodes no limite indicam a existência de um número muito elevado de ficheiros muito pequenos.
df rejeita -i e --output na mesma invocação. Por isso, quando quiser obter as contagens brutas para as ler ou transmitir a outro comando, selecione os campos de inodes pelo nome e não inclua -i.
df --output=itotal,iused,iavail,ipcent /Essas colunas contêm os mesmos dados de contabilização que df -i apresenta, num formato que pode analisar.
Encontre os ficheiros contando entradas em vez de bytes.
sudo du --inodes -x -d 1 / 2>/dev/null | sort -rn | headRepita o mesmo comando um nível abaixo, no diretório que ficou em primeiro lugar, até chegar à árvore que está a criar os ficheiros. Se o seu du não suportar --inodes, então sudo find /var -xdev -type f | wc -l conta uma subárvore de forma mais lenta.
A solução é eliminar ou mover esses ficheiros. Não pode adicionar inodes a um sistema de ficheiros ext4 existente, porque a quantidade é fixada no momento mkfs. Para a aumentar, teria de recriar o sistema de ficheiros e restaurá-lo a partir de uma cópia de segurança. O XFS aloca inodes conforme necessário, por isso não fica limitado da mesma forma por um teto fixo. Uma máquina que executa contentores atinge ambos os limites mais cedo do que a maioria, porque as camadas das imagens contêm muitos ficheiros pequenos. Nessa máquina, libertar espaço em disco do Docker num VPS é a solução específica e recupera muito mais espaço do que uma limpeza geral do sistema de ficheiros.
Espaço oculto sob um ponto de montagem
Um diretório pode conter ficheiros antes de qualquer montagem ser feita sobre ele. Monte um sistema de ficheiros nesse diretório e os ficheiros que estão por baixo permanecem exatamente onde estavam: continuam alocados, continuam contabilizados por df e deixam de estar acessíveis pelo nome. du não os consegue ver porque a montagem os cobre.
Demonstre isso com tmpfs, que não precisa de espaço livre em disco. Esta parte requer uma máquina onde tenha permissão para montar sistemas de ficheiros, por isso funciona numa VPS KVM.
sudo mkdir -p /srv/covered
sudo cp /etc/services /srv/covered/
ls /srv/covered
sudo mount -t tmpfs tmpfs /srv/covered
ls /srv/covered
sudo umount /srv/covered
ls /srv/coveredO ls do meio mostra um diretório vazio. A cópia não foi para lado nenhum: continua no sistema de ficheiros raiz e volta a aparecer assim que desmontar. Agora imagine um serviço que tenha escrito nesse caminho durante um mês antes de alguém montar um volume sobre ele.
Para encontrar os ficheiros reais num servidor em execução, monte o sistema de ficheiros raiz uma segunda vez noutra localização. Uma montagem bind mostra um sistema de ficheiros sem os sistemas de ficheiros montados dentro dele.
sudo mkdir -p /mnt/rootcheck
sudo mount --bind / /mnt/rootcheck
sudo du -xhs /mnt/rootcheck/* 2>/dev/null | sort -h
sudo umount /mnt/rootcheckTudo o que aparecer nessa listagem, mas não no caminho normal, está oculto sob um ponto de montagem. Desmonte a montagem bind quando terminar, caso contrário um du posterior sem -x contabiliza os mesmos ficheiros duas vezes.
Blocos reservados para root
O ext4 reserva uma parte dos seus blocos para a conta root, para que um disco cheio não impeça o root de iniciar sessão e reparar a máquina. Um processo executado por um utilizador comum atinge primeiro esse limite, enquanto df ainda mostra algum espaço disponível. Consulte a configuração no seu próprio sistema de ficheiros em vez de assumir o valor predefinido.
dev=$(df --output=source / | tail -n 1)
sudo tune2fs -l "$dev" | grep -i 'block count'Esse comando apresenta o número total de blocos e o número de blocos reservados nas mesmas unidades, pelo que a proporção entre eles é direta. df apresenta a coluna de espaço disponível como o espaço que um utilizador normal ainda pode utilizar. Por isso, a soma do espaço usado com o espaço disponível é menor do que o tamanho total. A diferença corresponde à reserva.
Altere-a com sudo tune2fs -m <percent> "$dev". A alteração aplica-se imediatamente e não requer remontagem. Reduzir a reserva num sistema de ficheiros separado para dados é razoável. No sistema de ficheiros raiz, mantenha uma reserva suficiente para que o root ainda possa escrever, porque um sistema de ficheiros raiz sem espaço livre é muito mais difícil de reparar. tune2fs funciona com ext2, ext3 e ext4. O XFS não tem uma configuração equivalente.
Onde du induz em erro por si só
Quatro comportamentos de du produzem totais que parecem errados.
- Hard links:
duconta um inode uma única vez, mesmo quando vários nomes apontam para ele. Por isso, uma árvore cheia de hard links apresenta um valor inferior à soma dos seus ficheiros. - Ficheiros esparsos:
duapresenta os blocos efetivamente alocados, enquantols -lapresenta o tamanho aparente. Adicione--apparent-sizepara ver o outro valor. - Permissões: executado por um utilizador comum,
duignora o que não consegue ler e apresenta um valor inferior ao real. Os erros que apresenta são os que as pessoas redirecionam para/dev/nulle deixam de consultar. - Limites do sistema de ficheiros: sem
-x,du /conta todos os sistemas de ficheiros montados abaixo de/. Por isso, o total pode exceder o valor apresentado pordf /.
df também tem um comportamento que vale a pena conhecer. Apresenta cada sistema de ficheiros separadamente. Por isso, execute-o no caminho exato para o qual a escrita com falha é direcionada. Um /boot separado enche-se segundo o seu próprio calendário à medida que os pacotes do kernel se acumulam. remover kernels antigos no Ubuntu é uma tarefa diferente de libertar espaço em /.
Uma ordem de trabalho para um incidente real
- Execute
df -h <path>edf -i <path>no sistema de ficheiros para o qual a escrita falhada se destinava, e não em/por reflexo. - Execute
sudo du -xh -d 1 <mountpoint> 2>/dev/null | sort -he, em seguida, desça para o diretório maior. - Se
dunão conseguir explicar o espaço quedfindica como utilizado, procure em/procficheiros eliminados que ainda estejam abertos. - Se os dois resultados coincidirem, monte o sistema de ficheiros noutra localização com bind mount e procure ficheiros sob um ponto de montagem.
- Se o uso de inodes estiver no limite, conte ficheiros em vez de bytes.
Cada passo envolve um comando cujo resultado pode ser lido. É isso que distingue a resolução deste problema de uma tentativa baseada em suposições.
FAQ
Por que df mostra o disco cheio quando du encontra muito menos?
A causa habitual é um ficheiro que foi eliminado enquanto um processo ainda o mantinha aberto. Remover o ficheiro elimina a sua entrada no diretório, por isso du já não tem um nome para percorrer e deixa de o contabilizar. O inode e os respetivos blocos continuam alocados até o último descritor ser fechado, e df contabiliza os blocos alocados. Procure em /proc/<pid>/fd ligações simbólicas cujo destino esteja marcado como eliminado para identificar o ficheiro e o processo que o mantém aberto. Antes de confiar na comparação, confirme que executou du como root e com -x, porque um utilizador comum ignora silenciosamente os diretórios que não consegue ler.
Como encontro um ficheiro eliminado que ainda está aberto sem usar lsof?
Use o próprio registo do kernel dos descritores abertos. sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/null lista todos os descritores que apontam para um ficheiro sem nome, e o ID do processo aparece no caminho apresentado. sudo stat -Lc %s num desses caminhos de descritor mostra o respetivo tamanho, para que possa ordená-los e escolher o ficheiro relevante. Isto não requer qualquer pacote, o que é importante porque a instalação de um pacote num sistema de ficheiros sem espaço livre pode falhar.
Posso libertar o espaço sem terminar o processo?
Por vezes. sudo truncate -s 0 /proc/<pid>/fd/<n> acede ao mesmo inode através do descritor e liberta os respetivos blocos enquanto o processo continua em execução. Esta é a opção mais limpa quando o processo abriu o ficheiro em modo de append, porque as escritas são sempre feitas no fim atual. Caso contrário, o deslocamento de escrita permanece na posição anterior e a escrita seguinte recria o ficheiro com um buraco no início. O tamanho apresentado volta a aumentar, mas os blocos sob o buraco continuam livres. Reiniciar a unidade, ou enviar-lhe o sinal indicado na documentação para reabrir os logs, é a solução que não deixa um ficheiro esparso.
df mostra espaço livre, mas as escritas continuam a falhar. Que outras causas podem existir?
Verifique os inodes com df -i no mesmo caminho, porque um sistema de ficheiros com blocos livres mas sem inodes livres rejeita ficheiros novos. Verifique se a escrita é executada por um utilizador que não seja root numa filesystem ext4 onde apenas restem os blocos reservados. sudo tune2fs -l no dispositivo mostra essa situação. Confirme que está a consultar o sistema de ficheiros efetivamente usado pela escrita, porque um /boot ou /var separado fica cheio de forma independente de /.
Por que du apresenta um total maior do que df?
du sem -x atravessa todas as filesystems montadas sob o caminho indicado. Assim, soma várias filesystems, enquanto df descreve apenas uma. Os bind mounts agravam a situação, porque os mesmos ficheiros são contabilizados uma vez em cada caminho onde aparecem. Adicione -x para manter du numa única filesystem e forneça a df o mesmo caminho, para que ambos os comandos descrevam a mesma coisa.