Cómo solucionar problemas de espacio en Docker

En este artículo encontrarás como localizar y eliminar los contenedores, sus imágenes, logs, y volumenes que no están en uso.

Docker no tiene implementado ningún recolector de basura -garbage collector-, que en la práctica significa que es fácil alcanzar el tamaño máximo de la partición o el disco que estamos usando. Toma mayor relevancia en servidores físicos y sistemas que siguen las metodologías de continuos deployment donde conviven y se despliegan de forma continua distintas versiones de los servicios.

A la hora de contabilizar el espacio utilizado por un contenedor, hay que tener en cuenta que es la suma de distintos elementos que lo componen:

  • La imagen y sus capas o layers que sirven de base al contenedor.
  • El espacio ocupado por el contenedor propiamente dicho y sus ficheros de configuración.
  • El espacio ocupado por el volumen o volumenes que pudiera tener montados.
  • Los logs compuestos por stdout y stderr.
  • El espacio swap si estuviera configurado y volcado en disco.

La imagen, el contenedor, volumenes, logs y swap componen el espacio que ocupa un contenedor

En el punto final detallo como mover el punto de montaje del storage local de docker. Nos será de utilidad si tenemos problemas de espacio en la partición, /var/lib/docker, o bien si queremos cambiar el driver de storage predeterminado, por ejemplo a brfs o zfs.

Cómo ver el espacio consumido por un container

En primer lugar comprobamos la ocupación y el estado del contenedor.

Utilizamos el comando docker ps:

# ---------------------
# ver el espacio utilizado por los contenedores
# ---------------------

$ docker ps -a --format "{{.Names}} {{.Size}}"

dev_worker_1 97.3kB (virtual 580MB)
rabbitmq 206B (virtual 124MB)

# O bien:
$ docker ps -a -s 
  • La opción -a nos permite ver los contenedores en cualquier estado (running, stoped,…). Además hemos formateado la salida para obtener sólo el nombre y la ocupación del contenedor.
  • La opción -s nos ofrece rápidamente el dato del espacio consumido por este contenedor.

En el resultado del ejemplo, la primera cifra del campo Size corresponde al tamaño de la capa escribible, writable layer del contenedor. Es la capa con estado1 cuando arrancamos un contenedor.

El virtual size corresponde al tamaño del conjunto de capas que forman la imagen. Estas capas pueden ser comunes y pueden ser utilizadas por distintos contenedores.

Cuando tenemos muchos contenedores, nos podemos guiar por el valor de «virtual size» para identicar aquellos contenedores que tienen en común una imagen, o base de imágenes.

O bien podemos filtrar por una imagen en común de este modo:

# ---------------------
# ver espacio utilizado, filtrando los resultados
# ---------------------

$ docker ps -a --format "{{ json . }}" --filter ancestor=ubuntu:latest | jq -r '.Names +": "+ .Size' 
eager_mestorf: 0B (virtual 120MB)

El comando jq nos permite utilizar datos JSON en la línea de comandos

En este caso, buscamos todos los contenedores con una imagen base de ubuntu:latest y he dado formato json a la salida, para filtrar luego a través del comando jq en lugar de la opción –format del propio comando.

Problemas de espacio por logs

El consumo de espacio es un problema común a la hora de utilizar contenedores que generan muchos logs. Por ejemplo, el tamaño de un contenedor nginx sin rotado de logs crecerá indefinidamente.

El Dockerfile que encontramos en la mayoría de imagenes que usan nginx es este:

RUN ln -sf /dev/stdout /var/log/nginx/access.log
RUN ln -sf /dev/stderr /var/log/nginx/error.log

La idea que hay tras esta configuración es permitir la captura fácil del stdout desde una aplicación externa para la gestión de logs.

Además, podemos visualizar los logs sin montar un volumen o entrar al contenedor, simplemente:

# ---------------------
# ver los logs de un contenedor
# ---------------------

$ docker logs -f nginx

El problema es que este fichero puede alcanzar un tamaño considerable si no se realiza una rotación de logs. Para localizar estos ficheros de log, comprobar su tamaño y truncarlo si fuera necesario, podemos utilizar docker inspect sobre el container a estudiar:

# ---------------------
# localizar el path de logs de un contenedor
# ---------------------

$ docker inspect --format '{{ .LogPath }}' rabbitmq
/var/lib/docker/containers/d4dff49c0f9c0a0d3c912b01314fa26591ae7eedce24bc7fa0a4ee07c5a55e7f/d4dff49c0f9c0a0d3c912b01314fa26591ae7eedce24bc7fa0a4ee07c5a55e7f-json.log

Deshecha los logs o establece una política de rotado para que evitar que crezcan indefinidamente

Podemos establecer una política de rotado de logs, fácilmente a través de docker, modificando la configuración del driver de logs para docker:

# ---------------------
# Establecer un rotado de logs
# ---------------------

$ cat << EOF >> /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m"
  }
}
EOF

$ systemctl restart docker

Si no queremos gestionar los logs de nuestra aplicación o microservicio a través de docker podemos redirigir el stdout y el stderr de nuestra aplicación a null.

Como en este ejemplo:

$ docker run -d miapp:v1 bash -c "node index.js > /dev/null 2>&1"

de este modo, no nos preocuparemos por este punto.

Eliminar contenedores

Para eliminar contenedores utilizamos el comando docker rm. Hay que tener en cuenta que, para poder eliminar el contenedor, es necesario que este parado o pasarle el parametro force, -f que ejecutará un kill al container.

# ---------------------
# Parar y eliminar un contenedor por nombre o id
# ---------------------

$ docker kill rabbitmq 

$ docker rm rabbitmq # Nombre del contenedor
$ docker rm d4dff49c0f9c # Id del contenedor

docker rm no tiene opción de filtrado integrada. Para filtrar contenedores, podemos utilizar la salida de docker ps, como en este ejemplo:

# ---------------------
# Eliminar contenedores parados
# ---------------------

$ docker rm $(docker ps -q -v -f status=exited)

En este caso, hemos añadido la opción -v que nos borrará también los volumenes.

Si queremos eliminar todos los contenedores parados podemos utilizar docker container prune, que si tiene dos opciones de filtrado muy útiles:

  • until=<timestamps> Unix timestamps, o funciones y strings del paquetes time de golang (5m, 00h30m).
  • Por etiqueta si el contenedor fue etiquetado label=<key>, label=<key>=<value>, label!=<key>, or label!=<key>=<value>

Unos ejemplos de como usar este comando para borrar contenedores:

# ---------------------
# Borrar contenedore con filtro de tiempo
# ---------------------

# Arrancar el contenedor
$ docker run -l etiqueta -d ubuntu:latest

# Eliminar los contenedores que se han parado hace menos de 5 minutos
$ docker container prune -f --filter until=5m

Deleted Containers:
87ba685a9663cc7af656355407a20a704563ad1f693eb2695f18da2c86956721

Total reclaimed space: 373.4MB

Y para borrar por etiqueta:

# ---------------------
# Borrar contenedores por label
# ---------------------

# Arrancar el contenedor
$ docker run -l etiqueta -d ubuntu:latest

# Eliminar el contenedor con el label etiqueta
$ docker container prune -f --filter label=etiqueta

Deleted Containers:
e5f51a494bdef11c987e5986f2880537a7d3b7b75e841a202cdbf64e3852f806

Total reclaimed space: 0B

Por último, es posible arrancar contenedores con la opción remove, --rm que automaticamente borra el contenedor y sus volumenes al pararse o hacer un stop.

Eliminar imágenes de Docker

Utilizaremos de forma conjunta los comandos docker images y docker rmi para visualizar los detalles y eliminar las imágenes respectivamente. El comando docker images nos proporciona el espacio que consumen en disco incluidas las capas o layer intermedias.

# listar imagenes
$ docker images  -a
REPOSITORY                                  TAG                 IMAGE ID            CREATED             SIZE
<none>                                      <none>              bfe16bbe2c58        2 weeks ago         1.02GB

# ---------------------
# borrar imagen por ID
# ---------------------

$ docker rmi -f bfe16bbe2c58

Si estamos desplegando en un servidor físico, en despliegues blue-green o en entornos de desarrollo se nos irán acumulando varias imágenes que no están en uso, sin tag, sin un contenedor asociado.

Se pueden eliminar fácilmente:

# ---------------------
# eliminar imagenes sin tag
# ---------------------

$ docker rmi -f $(docker images | grep "^<none>" | awk '{print $3}')

# o bien, a partir de la versión 1.9
$ docker rmi $(docker images -f dangling=true -q)

Los comandos son equivalentes. El filtrado a través del paŕametro dangling permite localizar todas las imagenes que no están referenciadas por ningún container.

Al igual que con los contenedores podemos utilizar la opción prune para borrar las imagenes que no estén en uso o filtrando por tiempo o tags.

# ---------------------
# borrar las imagenes que no están en uso
# ---------------------

$ docker image prune -a

En este caso con la opción image prune -a para borrar todas las imágenes que no estén en uso.

Eliminar volumenes

Los volumenes contienen los datos de nuestas aplicaciones. Es necesario prestarles atención por dos motivos: para realizar copias de seguridad de su contenido, y en segundo lugar para controlar el espacio que consumen.

Para poder visualizar los volumenes montados podemos utilizar volume ls

$ docker volume ls
DRIVER              VOLUME NAME
local               123456789

$ docker inspect 123456789 | jq .
[
    {
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/123456789/_data",
        "Name": "123456789",
        "Options": {},
        "Scope": "local"
    }
]

Para el caso de los volumenes montados en un directorio especifico, fuera de /var/lib/docker no podemos utilizar el comando docker volume, utilizaremos docker inspect del siguiente modo:

$ for i in $(docker ps -q); \
do echo $(docker inspect --format '{{ range .Mounts }}{{ .Source }}{{ end }}' $i); done

/var/lib/docker/volumes/516d755eeb4fed5223f60f8a8f68b04399e6f8299188f80738b98a583b437757/_data

De este modo, podemos ver todos las rutas físicas asociadas a los volumentes de todos los contenedores que están arrancados, para así comprobar por ejemplo, su espacio.

Finalmente para borrar un volumen:

$ docker volume rm nombre_del_volumen

Borrar los volumenes que no estén en uso:

# ---------------------
# Borrar volumenes que no están en uso
# ---------------------

$ docker volume prune -f

O bien, borrar los volumenes que no estén asociados a ningún container:

# ---------------------
# Borrar volumenes sin container
# ---------------------

$ docker volume rm $(docker volume ls -f dangling=true -q)

Cambiar el punto de montaje del storage local

Por último una receta para cambiar el punto de montaje de docker.

Como administrador y por falta de espacio nos podemos encontrar en la necesidad de cambiar el punto de montaje a otro disco u otra partición más grande.

Comprobamos el punto de montaje, paramos el servicio y copiamos el contenido a la nueva ubicación. En este caso en estilo System V para un servidor CentOS.

$ docker info -f '{{ .DockerRootDir }}'
/var/lib/docker

$ service docker stop
$ mkdir /data/docker 
$ rsync -rtlv /var/lib/docker/ /data/docker/

Editamos el fichero de configuración para docker en /etc/sysconfig/docker y añadimos el parámetro: other_args=" –data-root /data/docker"

$ vi /etc/sysconfig/docker
# La opcion -g, graph, esta deprecada a partir de la 17.05
other_args=" -g /data/docker"

# en favor de --data-root
# other_args=" --data-root /data/docker"

$ service docker start

En el caso de utilizar un servidor con systemd, podemos cambiar la configuración de la unidad en el comando del ExecStart:

[Service]
ExecStart=/usr/bin/dockerd --data-root="/data/docker" 

Por último tras reiniciar la unidad comprobamos de nuevo el punto de montaje

# ---------------------
# comprobar el punto de montaje de docker
# ---------------------

$ docker info -f '{{ .DockerRootDir }}'
/data/docker

César Maeso


Creative Commons License

Esta obra está bajo una licencia de Creative
Commons Reconocimiento-NoComercial-CompartirIgual
4.0 Internacional
.