df lleno y du no cuadra: encuentra el espacio
Si df indica disco lleno pero du no muestra dónde, identifica archivos borrados aún abiertos por procesos y libera espacio sin reiniciar. Incluye otras causas.
Por qué df indica que el disco está lleno y du muestra lo contrario
df informa de que el disco está lleno, mientras que du no encuentra el espacio ocupado porque un proceso todavía mantiene abierto un archivo que se ha eliminado. Al eliminar un archivo, se borra su nombre del directorio. Los bloques de datos sólo se liberan cuando se cierra el último descriptor de archivo abierto que apunta a ese inodo. du recorre los nombres, por lo que no cuenta nada. df consulta al sistema de archivos cuántos bloques están asignados, por lo que sigue contando el archivo que ya no tiene nombre.
Esta guía reproduce el problema en un VPS de Ubuntu estándar con herramientas que ya están instaladas, identifica el proceso que mantiene abierto el archivo mediante /proc y libera el espacio sin reiniciar. También se describen otras causas del mismo síntoma: una tabla de inodos sin entradas libres, archivos ocultos bajo un punto de montaje y bloques reservados para root.
Ejecute cada comando y revise su propia salida. Los valores dependen de su disco. Compare el estado anterior y el posterior en su propia máquina, en lugar de compararlos con una cifra impresa en una guía.
Qué cuenta df y qué cuenta du
df (disk free) consulta cada sistema de archivos montado para obtener sus propios datos: cuántos bloques existen, cuántos están asignados y cuántos están libres. Nunca abre un directorio. La respuesta incluye todos los bloques asignados, incluidos los bloques pertenecientes a un archivo al que ya no apunta ninguna entrada de directorio.
du (disk usage) hace lo contrario. Empieza en la ruta que se le indique, lee los directorios, obtiene información de cada entrada encontrada y suma los bloques. Un archivo sin nombre no es visible para este comando. Lo mismo ocurre con cualquier directorio que no pueda leer. Por eso un usuario normal obtiene un total menor que root. Ejecute du con sudo antes de extraer conclusiones de la comparación.
Dos opciones son importantes cada vez que compare ambos comandos.
-xmantieneduen un solo sistema de archivos. Sin esta opción,du /entra en todos los sistemas de archivos montados debajo de/y produce un total quedf /nunca estaba midiendo.-smuestra una línea de resumen por argumento en lugar de una línea por directorio.
Con esto puede ejecutar ambos comandos uno al lado del otro en el sistema de archivos que le interese.
df -h /
sudo du -xhs / 2>/dev/nulldf responde de inmediato. du tarda minutos en un sistema de archivos grande porque obtiene información de cada archivo durante el recorrido. Cuando los dos totales difieren mucho y du se ejecutó como root con -x, el espacio que falta está asignado a algo que no tiene nombre.
Reproduzca la discrepancia de forma intencionada
Hágalo en una VPS de prueba. Todo lo siguiente usa bash y coreutils, por lo que no se instala nada.
Registre el estado inicial del sistema de archivos que contiene /var/tmp.
cd /var/tmp
df -h .
df --output=used -B1 .El segundo comando muestra los bytes utilizados sin redondear, por lo que la comprobación final es exacta.
Ahora cree un archivo. Su tamaño se obtiene del espacio libre que informa la propia máquina, por lo que la demostración se adapta a cualquier disco.
free=$(df --output=avail -B1 . | tail -n 1)
fallocate -l $((free / 10)) ghost.bin
ls -l ghost.bin
df -h .$(...) es una sustitución de comandos: el shell ejecuta el comando que contiene y la salida se convierte en el valor de free. Si esta sintaxis es nueva para usted, la sustitución de comandos en bash la explica correctamente. fallocate reserva bloques reales sin escribir en ellos, por lo que termina al instante. En un sistema de archivos que no la admite, el comando falla, y head -c $((free / 10)) /dev/zero > ghost.bin realiza la misma tarea escribiendo los bytes.
Compare este df -h . con el que registró. La columna de espacio utilizado ha aumentado y la columna de espacio disponible se ha reducido.
Ahora mantenga el archivo abierto desde otro proceso y elimínelo.
sleep infinity < ghost.bin &
holder=$!
rm ghost.bin
ls -l ghost.bin
df -h .
sudo du -xhs . 2>/dev/nullLa redirección es todo el mecanismo. sleep infinity < ghost.bin & inicia un proceso en segundo plano cuya entrada estándar es ese archivo, por lo que el shell abre el archivo y entrega el descriptor a sleep, que lo mantiene abierto. $! contiene el ID de proceso de ese trabajo en segundo plano. rm elimina el nombre mientras el descriptor sigue abierto.
Lea la salida. ls no puede encontrar el archivo porque el nombre ya no existe. du vuelve a estar cerca del valor inicial porque recorre los nombres. df no ha cambiado porque los bloques siguen asignados. Ahora el sistema de archivos y el árbol de directorios no coinciden, y la diferencia entre ambos corresponde al archivo que acaba de eliminar.
Buscar el proceso que mantiene abierto el archivo eliminado
Cada descriptor de archivo abierto aparece en /proc/<pid>/fd/ como un enlace simbólico al archivo al que hace referencia. Cuando se elimina el enlace al archivo, el kernel marca como eliminado el destino de ese enlace. Por tanto, para encontrar el proceso que lo mantiene abierto hay que buscar un enlace cuyo destino incluya esa marca.
sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/null-lname busca en el destino de un enlace simbólico, no en su nombre, %p muestra la ruta del descriptor y %l muestra aquello a lo que apunta. El ID del proceso es el segundo elemento de la ruta que muestra. Ejecútelo con sudo, porque, de lo contrario, sólo podrá leer /proc/<pid>/fd en sus propios procesos. La redirección de stderr descarta los mensajes de los procesos que terminan mientras find recorre el sistema.
Un servidor con carga mantiene varios archivos eliminados abiertos en cada momento, y la mayoría son pequeños e inofensivos. Ordénelos por tamaño para que sólo los más relevantes aparezcan al principio.
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 sigue el enlace hasta el propio inode, por lo que %s muestra el tamaño del archivo que ya no tiene nombre. Ordenar por ese número coloca primero el archivo más grande.
A continuación, identifique el proceso asociado al descriptor principal. La ruta que aparece al principio de la lista contiene los dos números que necesita. Guárdelos primero en variables y sustituya PID y N por los valores que haya mostrado su propia lista.
pid=PID
n=N
ps -o pid,user,etime,args -p "$pid"
sudo stat -L "/proc/$pid/fd/$n"ps indica el nombre del programa y cuánto tiempo lleva ejecutándose. stat -L muestra el tamaño y el número de bloques asignados del inode eliminado. Juntos responden a la pregunta importante: qué servicio mantiene vivo este archivo.
Si la máquina ya tiene lsof, sudo lsof +L1 enumera los archivos abiertos cuyo número de enlaces ha bajado a cero y muestra los tamaños en una sola tabla. No está presente en una imagen mínima de Ubuntu, y la instalación de un paquete en un sistema de archivos sin espacio libre también puede fallar. Por eso, el recorrido con /proc es la opción que siempre funciona.
Liberar espacio sin reiniciar
Reiniciar sí lo soluciona, pero es la primera medida equivocada: deja el servicio fuera de línea y destruye las evidencias. Existen cuatro opciones menos agresivas, en este orden.
Primero, copie los datos a otra ubicación si todavía los necesita. Leer la ruta del descriptor lee el inode activo.
sudo cp /proc/<pid>/fd/<n> /root/recovered.logEste es el único caso en el que recuperar un archivo eliminado resulta sencillo. Por eso recuperar archivos eliminados con rm -rf comienza preguntando si un proceso todavía mantiene abierto el archivo. Cuando se cierra el último descriptor, esa vía desaparece.
Segundo, vacíe el archivo a través del descriptor. La ruta /proc apunta al mismo inode, por lo que truncarlo libera los bloques mientras el proceso sigue ejecutándose.
sudo truncate -s 0 "/proc/$pid/fd/$n"
df -h /Esto funciona correctamente cuando el proceso que escribe abrió el archivo en modo append, porque cada escritura se realiza al final actual del archivo. Si no lo hizo, el proceso conserva su antigua posición de escritura. Por tanto, la siguiente escritura se realiza mucho más adelante en el archivo y lo recrea con un hueco al principio. Un hueco no se asigna, por lo que los bloques siguen libres y df conserva el espacio que acaba de devolver. Lo único que vuelve a aumentar es el tamaño: ejecute sudo stat -L "/proc/$pid/fd/$n" de nuevo después de que el proceso escriba y mostrará el tamaño anterior junto a un recuento de bloques que ya no coincide. Reinicie el proceso si también quiere que el tamaño vuelva a empezar desde cero.
Tercero, pida al servicio que vuelva a abrir sus registros. Un daemon cuyo archivo de registro se eliminó mientras lo tenía abierto es la versión real más habitual de este problema. Muchos daemons vuelven a abrir sus archivos de registro al recibir una señal: nginx usa SIGUSR1 y rsyslog usa SIGHUP. Consulte la documentación del daemon correspondiente en lugar de adivinar, porque enviar la señal incorrecta al daemon equivocado puede detenerlo.
sudo systemctl kill -s USR1 nginxEsto envía la señal al proceso que systemd registra como principal de la unidad. Por tanto, una unidad que declara el Type= incorrecto para la forma en que realmente se inicia su daemon puede enviar la señal a un proceso que nunca tuvo abierto el archivo eliminado, y el espacio permanece ocupado.
Cuarto, reinicie la unidad. sudo systemctl restart <unit> cierra todos los descriptores que mantenía abiertos el proceso anterior, por lo que los bloques vuelven a estar disponibles con certeza. En la demostración anterior, el proceso que mantiene abierto el archivo es un sleep que inició usted mismo, así que basta con finalizarlo.
kill $holder
df -h .
df --output=used -B1 .
sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/nullCompare los bytes usados con el valor que registró antes de crear el archivo. Vuelven a coincidir y find ya no muestra el descriptor. Verificarlo con el mismo comando que detectó el problema es una práctica que conviene conservar.
Observar cómo cambia ese valor resulta más sencillo que ejecutar df manualmente una y otra vez. watch repite un comando en un intervalo fijo y vuelve a mostrar la salida en el mismo lugar, por lo que watch df -h / muestra cómo cambia la columna de espacio usado a medida que se libera el espacio.
Cuando los totales coinciden y el disco sigue lleno
Si df y un du -x de root coinciden entre sí, no hay ningún archivo eliminado implicado. Las causas restantes son de otro tipo y cada una requiere su propia comprobación.
Agotamiento de inodos, no de bloques
Un inodo contiene los metadatos de un archivo. ext4 crea un número fijo de inodos al crear el sistema de archivos, por lo que este puede quedarse sin inodos aunque todavía tenga bloques libres. La creación de archivos nuevos falla aunque df -h muestre espacio disponible.
df -h /
df -i /El primer comando cuenta bloques y el segundo cuenta inodos. Compare la columna de uso de cada uno. Un uso bajo de bloques y un uso de inodos en el límite indican que el problema es un número muy grande de archivos muy pequeños.
df rechaza -i y --output en la misma invocación, por lo que, si necesita leer los recuentos sin formato o pasarlos a otro comando, seleccione los campos de inodos por nombre y deje -i desactivado.
df --output=itotal,iused,iavail,ipcent /Esas columnas contienen los mismos datos de contabilidad que muestra df -i, en un formato que puede analizar.
Busque los archivos contando entradas en lugar de bytes.
sudo du --inodes -x -d 1 / 2>/dev/null | sort -rn | headRepita el mismo comando un nivel más abajo sobre el directorio que aparece primero, hasta llegar al árbol que está creando los archivos. Si su du no admite --inodes, sudo find /var -xdev -type f | wc -l cuenta un subárbol de forma más lenta.
La solución consiste en eliminar o mover esos archivos. No puede añadir inodos a un sistema de archivos ext4 existente, porque el recuento se fija al crear el sistema de archivos en mkfs. Por tanto, aumentarlo requiere volver a crear el sistema de archivos y restaurar una copia de seguridad. XFS asigna inodos según los necesita, por lo que no alcanza un límite fijo de la misma forma. Una máquina que ejecuta contenedores alcanza ambos límites antes que la mayoría, porque las capas de imagen contienen muchos archivos pequeños. En esa máquina, liberar espacio de Docker en el disco de un VPS es la solución específica y recupera mucho más espacio que una limpieza general del sistema de archivos.
Espacio oculto bajo un punto de montaje
Un directorio puede contener archivos antes de montar algo en él. Monte un sistema de archivos sobre ese directorio y los archivos subyacentes permanecerán exactamente donde estaban: seguirán ocupando espacio, seguirán contabilizándose en df y ya no serán accesibles por nombre. du no puede verlos porque el montaje los cubre.
Muéstrelo con tmpfs, que no necesita espacio de disco adicional. Esta parte requiere una máquina donde tenga permiso para montar sistemas de archivos, por lo que funciona en un KVM VPS.
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/coveredEl ls central muestra un directorio vacío. La copia no desapareció: sigue en el sistema de archivos raíz y volverá a aparecer en cuanto desmonte el sistema de archivos. Ahora imagine un servicio que escribió registros en esa ruta durante un mes antes de que alguien montara un volumen sobre ella.
Para encontrar los archivos reales en un servidor en ejecución, monte de nuevo el sistema de archivos raíz en otra ubicación. Un montaje bind muestra un sistema de archivos sin los sistemas de archivos montados dentro de él.
sudo mkdir -p /mnt/rootcheck
sudo mount --bind / /mnt/rootcheck
sudo du -xhs /mnt/rootcheck/* 2>/dev/null | sort -h
sudo umount /mnt/rootcheckTodo lo que aparezca en ese listado pero no bajo la ruta normal está oculto bajo un punto de montaje. Desmonte el montaje bind cuando termine; de lo contrario, un du posterior sin -x contabilizará los mismos archivos dos veces.
Bloques reservados para root
ext4 reserva una parte de sus bloques para el usuario root. Así, un disco lleno no impide que root inicie sesión y repare el sistema. Un proceso que se ejecuta como un usuario normal llega primero a ese límite, mientras df todavía muestra algo de espacio disponible. Consulte este ajuste en su propio sistema de archivos en lugar de asumir el valor predeterminado.
dev=$(df --output=source / | tail -n 1)
sudo tune2fs -l "$dev" | grep -i 'block count'Este comando muestra el recuento total de bloques y el recuento de bloques reservados en las mismas unidades. Por tanto, la proporción entre ambos valores es directa. df muestra en la columna disponible el espacio que todavía puede usar un usuario normal. Por eso, la suma de usado y disponible es menor que el tamaño total. La diferencia es la reserva.
Cambie este valor con sudo tune2fs -m <percent> "$dev". El cambio se aplica de inmediato y no requiere volver a montar el sistema de archivos. Reducir la reserva en un sistema de archivos independiente para datos puede ser razonable. En el sistema de archivos raíz, deje suficiente espacio para que root pueda seguir escribiendo. Un sistema de archivos raíz sin ningún espacio libre es mucho más difícil de reparar. Esta reserva también evita que se quede sin acceso: una clave añadida a authorized_keys en un sistema de archivos sin espacio puede escribirse de forma incompleta o no escribirse. En ese caso, el siguiente inicio de sesión responde Permiso denegado (publickey) por una causa que no está relacionada con la clave. tune2fs funciona con ext2, ext3 y ext4. XFS no tiene un ajuste equivalente.
Dónde puede inducir a error du por sí solo
Cuatro comportamientos de du producen totales que parecen incorrectos.
- Enlaces físicos:
ducuenta un inode una sola vez aunque varios nombres apunten a él, por lo que un árbol lleno de enlaces físicos muestra menos espacio que la suma de sus archivos. - Archivos dispersos:
dumuestra los bloques realmente asignados, mientras quels -lmuestra el tamaño aparente. Añada--apparent-sizepara ver el otro valor. - Permisos: si se ejecuta como un usuario normal,
duomite lo que no puede leer y muestra un uso inferior al real. Los errores que imprime son los que algunas personas redirigen a/dev/nully dejan de revisar. - Límites del sistema de archivos: sin
-x,du /cuenta todos los sistemas de archivos montados debajo de/, por lo que su total puede superar el valor que muestradf /.
df también tiene un comportamiento que conviene conocer. Muestra cada sistema de archivos por separado, así que ejecútelo sobre la ruta exacta a la que intenta escribir el proceso que falla. Un /boot independiente se llena según su propio ritmo a medida que se acumulan paquetes del kernel, y eliminar kernels antiguos en Ubuntu es una tarea distinta de liberar espacio en /.
Orden de trabajo para un incidente real
- Ejecute
df -h <path>ydf -i <path>en el sistema de archivos al que intentaba escribir el proceso fallido, no en/por inercia. - Ejecute
sudo du -xh -d 1 <mountpoint> 2>/dev/null | sort -hy, después, descienda por el directorio más grande. - Si
duno puede explicar el espacio quedfmuestra como usado, busque en/procarchivos eliminados que sigan abiertos. - Si ambos coinciden, monte el sistema de archivos mediante un bind mount en otra ruta y busque archivos ubicados bajo un punto de montaje.
- Si el límite corresponde al uso de inodos, cuente archivos en lugar de bytes.
Cada paso incluye un comando cuya salida se puede leer. Esa es la diferencia entre solucionar el problema y hacer suposiciones.
FAQ
¿Por qué df muestra el disco lleno cuando du encuentra mucho menos?
La causa habitual es un archivo que se eliminó mientras un proceso aún lo tenía abierto. Al eliminar el archivo, se elimina su entrada de directorio, por lo que du ya no tiene un nombre que recorrer y deja de contabilizarlo. El inode y sus bloques siguen asignados hasta que se cierra el último descriptor, y df contabiliza los bloques asignados. Busque en /proc/<pid>/fd enlaces simbólicos cuyo destino esté marcado como eliminado y obtendrá tanto el archivo como el proceso que lo mantiene abierto. Antes de confiar en la comparación, confirme que ejecutó du como root y con -x, porque un usuario normal omite silenciosamente los directorios que no puede leer.
¿Cómo encuentro un archivo eliminado que sigue abierto sin lsof?
Use el registro propio del kernel sobre los descriptores abiertos. sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/null muestra todos los descriptores que apuntan a un archivo sin nombre, y el ID del proceso aparece dentro de la ruta que imprime. sudo stat -Lc %s sobre una de esas rutas de descriptor informa de su tamaño, por lo que puede ordenarlas y elegir la que corresponda. Esto no requiere ningún paquete, algo importante porque instalar uno en un sistema de archivos sin espacio libre puede fallar.
¿Puedo liberar el espacio sin detener el proceso?
A veces. sudo truncate -s 0 /proc/<pid>/fd/<n> accede al mismo inode mediante el descriptor y libera sus bloques mientras el proceso sigue ejecutándose. Es la opción más limpia cuando el proceso abrió el archivo en modo append, porque sus escrituras siempre se realizan al final actual. Si no lo hizo, el desplazamiento de escritura permanece donde estaba y la siguiente escritura vuelve a crear el archivo con un hueco al principio. El tamaño informado vuelve a aumentar, mientras los bloques situados bajo el hueco siguen libres. Reiniciar la unidad o enviarle la señal que indique su documentación para volver a abrir los registros es la solución que no deja un archivo disperso.
df muestra espacio libre, pero las escrituras siguen fallando. ¿Cuál puede ser la causa?
Compruebe los inodes con df -i en la misma ruta, porque un sistema de archivos con bloques libres pero sin inodes disponibles rechaza los archivos nuevos. Compruebe si la escritura se ejecuta como un usuario que no es root en un sistema de archivos ext4 donde sólo quedan los bloques reservados. sudo tune2fs -l en el dispositivo mostrará esta situación. Compruebe que está leyendo el sistema de archivos al que realmente apunta la escritura, porque un /boot o /var independiente se llena por separado de /.
¿Por qué du informa de un total mayor que df?
du sin -x entra en todos los sistemas de archivos montados bajo la ruta indicada, por lo que suma varios sistemas de archivos mientras df describe uno solo. Los bind mounts agravan el problema, porque los mismos archivos se contabilizan una vez por cada ruta en la que aparecen. Añada -x para mantener du en un único sistema de archivos y proporcione a df la misma ruta, de modo que ambos comandos describan lo mismo.