天天看点

带你找回那些被 Docker 吃掉的磁盘空间

作者:dbaplus社群

如果你是 Docker/Kubernetes 的重度使用者,应该多多少少会遇到一个问题“no space left on device”。

带你找回那些被 Docker 吃掉的磁盘空间

当然,如果你的硬盘空间很大,也不介意把大量空间用来存放不必要的 Docker 资源,那你可以忽略这篇文章了XD~

在我们讨论如何有效避免Docker占用大量磁盘空间之前,让我们先来谈谈Docker会占用哪些资源(导致空间占用):

  • Image:Container 的镜像文件通常是通过 Dockerfile 结合 docker build 命令生成的
  • Container:是具有独立环境的Process(在Linux操作系统中使用namespace实现环境的隔离),通常使用docker create或docker run命令来启动
  • Volume:使用者可以通过Docker volume将Container挂载Host环境的数据(文件或整个文件夹)
  • Network:可以使多个Docker Container(s)的网络环境相互通信,Docker network的运作方式很大程度上取决于开发者使用何种Network driver,常见的driver有bridge、host、overlay、macvlan、none、external plugin

在了解了 Docker 的常用资源之后,让我们来看看这些资源分别有哪些状态:

  • used:指正在被某个container(s)使用的资源
  • unused:完全没有被container使用
  • dangling:指那些失效的image,永远不会被使用到

要判断资源是 used 或是 unused 其实非常简单,Docker 的判断标准是该资源目前是否被至少一个 Container 使用,如果不是,该资源就是 unused resource。

而 dangling 是比较特别的状态,它只存在于 Docker 镜像中,原因是 Docker 镜像具有版本概念。一般来说,在构建 Docker 镜像时,我们会使用 -t 标志为镜像打上标签(映像文件名加上版本号)。如果我们不指定版本号,Docker 将默认为最新版本。

然而,当我们重复编译同一个 tag name 的 docker image,那么第一次生成的 docker image 在第二次编译结束时就会进入 dangling 状态(用 docker image ls 会观察到一个 tag name 为 <none> 的 image),而这些 dangling image(s) 永远不会被使用到,如果不定期清除便会白白地占据磁盘空间。

在讨论完Docker资源和相关状态之后,现在让我们来看看如何清理这些unused/dangling resources吧!

Docker 提供了一系列的 docker prune 指令,可以帮助我们清理不同类型的资源:

# Remove all unused images, not just dangling ones
$ docker image prune -a
# Remove dangling images
$ docker image prune


$ docker network prune


$ docker volume prune


$ docker container prune
# Remove all unused containers, networks, images (both dangling and unreferenced), and optionally, volumes.
$ docker system prune           

其实,Docker的清理功能已经解决了一些场景的问题。比如,对于正在运行生产服务的虚拟机或CI/CD Runner,我们只需要使用这些命令,并配合系统的定时任务,就可以轻松应对了。

然而,就开发者个人环境而言,很多时候我们其实不希望删除那些unused image(s),让我们考虑以下情景:

小明用 docker-compose 一次部署了20个容器,这些容器使用了20多个Docker镜像。如果小明使用docker compose rm + docker image prune来清空资源,他会发现等到下次要启用服务时,这些镜像可能已经被Docker移除了。

为了避免这个问题(偷懒),我们通常在本地测试部署环境的时候,都会选择忽略清空资源的步骤,久而久之这些 dangling image 以及 unused volume 就会把本机的磁盘空间占满……

所以,更好的做法实际上是使用 Makefile 或者 shell 脚本来处理 Docker 镜像的编译工作,同时在编译完成后移除悬空镜像或者指定标签名称的镜像:

docker image prune
# or
docker image ls | grep "<YOUR_TAG_NAME>" | awk '{print $3}' | xargs docker image rm           

在上面的代码中,第二个命令可以一次删除所有符合 grep 中的条件的image(s),这也是我经常使用的方法(毕竟使用 docker image prune 很容易误删无辜的镜像……)

到目前为止,我们已经知道了如何处理不同使用情境下的unused image和dangling image。然而,上述解决方案对于初学者来说并不太友好(也不太方便)。为了节省时间,我们可以将这些步骤编写成一个shell脚本,并放在系统的/bin目录下,这样就可以直接通过终端执行该脚本:

#!/bin/bash


force=false


while getopts ":f" opt; do
  case ${opt} in
    f )
      force=true
      ;;
    ? )
      echo "Invalid option: -$OPTARG" 1>&2
      exit 1
      ;;
    : )
      echo "Option -$OPTARG requires an argument." 1>&2
      exit 1
      ;;
  esac
done


shift $((OPTIND -1))


if [ $# -ne 2 ]; then
    echo "Usage: docker-clean [-f] <resource> <keyword>"
    exit 1
fi


resource=$1
keyword=$2


if [ "$force" = true ]; then
  force_args="-f"
else
  read -p "Are you sure you want to delete all $resource with $keyword? [y/N] " confirmation
  if [ "$confirmation" != "y" ] && [ "$confirmation" != "Y" ]; then
    echo "Operation cancelled."
    exit 0
  fi
fi


case $resource in
    "image")
        docker images | grep "$keyword" | awk '{print $3}' | xargs docker rmi $force_args
        ;;
    "container")
        docker ps -a | grep "$keyword" | awk '{print $1}' | xargs docker rm $force_args
        ;;
    *)
        echo "Invalid resource type. Must be either 'image' or 'container'."
        exit 1
        ;;
esac           

顺便说一句:

整个 shell 脚本都是使用 chatGPT 生成的,对于一些简单的应用案例,chatGPT 真的是非常棒的工具(但也要记得检查 chatGPT 提供的数据的可靠性,避免发生毁掉重要数据导致连夜订票跑路……)

这个脚本可以移除特定的标签名称的图像和容器。使用教程请参考下面的示例:

# 安裝
$ git clone https://github.com/ianchen0119/docker-clean.git
$ mv docker-clean/docker-clean /bin/docker-clean
$ chmod 777 /bin/docker-clean




# 使用
$ docker-clean 
Usage: docker-clean [-f] <resource> <keyword>           

总结

Docker这类容器化方案对开发者来说是一个非常友善的工具,但使用不当的话其实很容易影响到执行环境的机器。本篇文章简单地介绍一些可参考的处理方式,如果你也有不错的管理方式,也欢迎留言共同交流!

最后补充一下个人不喜欢批量处理unused & dangling image(s)的原因:

  • 在重新编译时,docker image会根据commit hash使用相应的缓存层(cache layer),以避免重复工作(re-work)。如果每次编译都固定移除未使用的镜像,也会同时清空这些缓存,从而大幅提高镜像编译的速度。
  • 在某些微服务测试场景下,如果一个图像需要额外的3分钟来重现之前的步骤,那么启动整个服务可能需要额外占用30分钟以上的时间……

要在使用docker的同时最大化磁盘空间利用率,除了定期清理不需要的docker静态资源,我们还可以从其他方面入手:

  • 使用精简化的 Docker 镜像作为基础;
  • 尽量重用基础镜像:如果应用程序没有特殊的依赖关系,应尽量选择重复使用的基础镜像来进行编译或作为应用程序的执行环境;
  • 避免过多的 docker commit:这一点与上述类似,有助于保持镜像的简洁。但并非强制性要求,因为有时运维人员可能会频繁更新某些特定的镜像,如果 Dockerfile 写得过于简洁,可能会导致难以阅读和难以利用缓存的情况发生;
  • 在安装完成后,使用rm或apt-get remove等命令移除安装包,以清除编译image期间产生的垃圾;
  • 使用distroless镜像:现在圈内也有许多人在呼吁尽量不要使用alpine这类的镜像作为基础,而是建议开发者尽可能地选择distroless镜像,具体原因就是出于容器安全的考虑。

作者丨睡醒想钱钱

来源丨juejin.cn/post/7259032711019855930

dbaplus社群欢迎广大技术人员投稿,投稿邮箱:[email protected]