# Docker build导致的磁盘空间不足问题

最近新搭建了一套CI/CD,大体流程上是通过git提交代码到gitea,通过gitea仓库的web钩子来触发Jenkins运行代码打包(golang docker)和发布(kuboard k8s)的脚本。但是在昨天运行的过程中,一直构建失败,报错代码如下:

#12 37.01 # github.com/gin-gonic/gin
#12 37.01 compile: writing output: write $WORK/b504/_pkg_.a: no space left on device
------
Dockerfile:29
--------------------
  28 |     # 编译项目可执行文件,并压缩生成的二进制文件
  29 | >>> RUN go build -ldflags="-s -w" -tags=jsoniter -o api/bin/main api/main.go \
  30 | >>>     && upx -q -9 /builder/api/bin/main
  31 |     
--------------------
ERROR: failed to solve: process "/bin/sh -c go build -ldflags=\"-s -w\" -tags=jsoniter -o api/bin/main api/main.go     && upx -q -9 /builder/api/bin/main" did not complete successfully: exit code: 1
Build step 'Execute shell' marked build as failure
Finished: FAILURE

根据报错信息中的no space left on device我们知道原因是磁盘空间不足。紧接着,我查看了一下磁盘空间的占用情况。占用情况如下: docker-build-df 通过磁盘占用情况来看是docker挂载的磁盘占用达到105G,99%。问题定位到docker,通过执行docker system df命令来查看docker的磁盘占用情况。

# docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          22        10        2.843GB   777.4MB (27%)
Containers      10        9         6.281MB   -1B (0%)
Local Volumes   36        9         336B      252B (75%)
Build Cache     30        0         103GB   103GB

根据docker系统磁盘占用分析可以得出磁盘空间不足主要是103G的docker build生成的缓存信息。通过执行docker builder prune来清理掉缓存信息就好了。

# docker builder prune
WARNING! This will remove all dangling build cache. Are you sure you want to continue? [y/N] y
ID                                              RECLAIMABLE     SIZE            LAST ACCESSED
tkxtvbls98amsvcl8858nlccd*                      true            0B              About an hour ago
sb228tc0ociq2df7a168z4c9r*                      true    0B              About an hour ago
kxo8crv5fbq03wemt0d9khwpb*                      true    1.264MB         About an hour ago
6yy4h3fs87j76gn4colz0q8f8*                      true    1.332MB         About an hour ago
rwl1td4rawihkkkwrlbqmriyb*                      true    1.509kB         About an hour ago
dk3h9iv0evdgynqoxx1516o9c*                      true    1.509kB         About an hour ago
o0mxn2z37ontg9fc7f9iitfav                       true    1.332MB         About an hour ago
......
8ao7d7fpigrgshdsld2u0i34t                       true    5.881MB         About an hour ago
93bx3h1meta3rdckru15hkuif                       true    95B             About an hour ago
zax420luhzvbm9yam2v5sqo0d                       true    0B              About an hour ago
usqke9r7bycbf0tk66whg1uu1                       true    0B              2 hours ago
y37oz85zvd0eoj6sh05vprjt0                       true    0B              2 hours ago
Total:  103GB

通过上面的问题分析,我们知道了docker build的过程中会产生缓存,同时缓存磁盘占用并不会自己清理。今天我们将带着下面两个问题来学习一下docker build缓存相关的内容。

  • dockerbuild的过程中为什么要产生缓存?
  • 该如何合理的清理掉docker缓存?

# dockerbuild的过程中为什么要产生缓存?

在 Docker 构建过程中,产生缓存是为了优化镜像构建的效率和速度。Docker 构建缓存的机制允许开发人员重复使用先前构建的中间层,从而避免了不必要的重新执行相同步骤,节省时间和网络资源。

# Docker 构建过程概述

Docker 构建过程是将 Dockerfile 中的指令转化为一个个构建层(Build Layer)。每一个指令都会生成一个构建层,而所有构建层组合在一起,形成一个完整的镜像。构建过程中的每一步都是无状态的,意味着在同一主机上,对相同的 Dockerfile 执行相同的指令应该得到相同的结果。

# 构建缓存的原理

Docker 构建缓存机制的核心思想是在构建过程中缓存中间层,而不是每次都重新执行所有步骤。当 Docker 执行一个构建指令时,它会计算该指令的哈希值,并将其作为构建缓存的标识符。如果 Docker 在后续的构建过程中遇到相同的指令内容(哈希值相同),它将会使用缓存的中间层,而不是重新执行该指令。

这种缓存机制可以显著减少构建过程中的重复操作。在构建过程的后续阶段,只有发生了变化的指令以后的构建层会重新执行,而之前的层会从缓存中获取。

# 为什么需要构建缓存?

构建缓存的优势体现在以下几个方面:

# 提高构建速度

由于 Docker 构建过程是将每个指令转化为构建层,并将其缓存,因此在后续构建过程中,如果没有发生变化,Docker 只需重新构建发生变化的部分,而之前构建过的部分可以直接使用缓存,从而大幅度提高构建速度。特别是在大型或复杂的镜像构建中,这种优化对于节省构建时间非常重要。

# 节省网络带宽

构建缓存避免了相同指令的重复下载,节省了网络带宽。对于远程 Docker 守护进程或分布式构建环境,这对于减少镜像传输时间非常有益。

# 减少磁盘占用

Docker 缓存利用已构建的中间层,避免了重复构建相同层的开销。这导致在本地磁盘上存储的镜像和中间层数量较少,减少了磁盘占用。

# 构建缓存的使用注意事项

虽然构建缓存在大多数情况下是有益的,但也有一些情况需要注意:

# 缓存失效

构建缓存是根据指令内容的哈希值来判断是否可以使用缓存。如果在 Dockerfile 中的某一步骤后续发生了变化,会导致该步骤以及之后的构建层缓存失效,需要重新构建。因此,在修改 Dockerfile 时,要谨慎处理那些影响缓存的指令。

# 使用 .dockerignore 文件

在构建镜像时,Docker 使用构建上下文中的文件作为缓存判断的一部分。因此,确保构建上下文中只包含必要的文件,并使用 .dockerignore 文件排除不必要的文件,以避免不必要的缓存失效。

# 该如何合理的清理掉docker缓存?

# 查询 Docker 缓存使用情况

首先,我们需要了解 Docker 缓存的使用情况。运行以下命令可以查看 Docker 系统的磁盘使用情况,包括缓存的大小:

# docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          22        10        2.843GB   777.4MB (27%)
Containers      10        9         6.281MB   -1B (0%)
Local Volumes   36        9         336B      252B (75%)
Build Cache     12        0         0B        0B

通过此命令,您将获得 Docker 系统的磁盘使用摘要,包括镜像、容器、数据卷和构建缓存的大小。

# 清理不再使用的镜像和容器

在优化 Docker 缓存之前,先确保您已清理不再使用的镜像和容器,以释放一些磁盘空间。运行以下命令来列出所有本地的 Docker 镜像和已停止的容器,并删除不再需要的镜像和容器:

# 列出所有镜像
docker images -a

# 列出所有容器(包括已停止的)
docker ps -a

# 删除不再使用的镜像
docker image rm <IMAGE_ID>

# 删除不再使用的容器
docker rm <CONTAINER_ID>

# 使用 .dockerignore 文件优化构建缓存

.dockerignore 文件用于排除不必要的文件和目录,以减少构建上下文中的数据量,从而优化构建缓存的使用。请确保 .dockerignore 文件包含不需要的构建排除项,以减少构建上下文中不必要的文件和目录。

# 使用 --no-cache 标志禁用缓存

如果您希望完全禁用构建缓存并强制 Docker 重新执行所有构建步骤,可以使用 --no-cache 标志。但请注意,这样可能会导致构建速度显著降低,特别是在构建大型镜像时。

docker build --no-cache -t <IMAGE_NAME> <DOCKERFILE_PATH>

# 定期清理构建缓存

如果您发现构建缓存占用了过多的磁盘空间,可以定期进行清理。使用以下命令来清理未使用的构建缓存:

docker builder prune