docker虽然是“轻量级”的虚拟化解决方案,但其磁盘空间管理仍然是一个非常重要同时值得关注的问题,毕竟节约一份空间就是为企业节约一项成本,作为一个秉承勤俭持家的好员工,必须对这块的成本进行开刀。
docker 容器磁盘空间管理
docker 主要包括镜像、容器和数据卷三部分,对docker的磁盘空间管理也主要从着三块入手,在做docker磁盘空间分析之前我们需要简单了解下容器的“镜像层”的概念,一般容器的磁盘管理有一大半是镜像层相关:
什么是镜像层?
说到镜像的层,就要说说Docker镜像的存储组织方式 docker 镜像是采用分层的方式构建的,每个镜像都由一系列的 "镜像层" 组成。"镜像层"用来存储一组镜像相关的元数据信息,主要包括镜像的架构(如 amd64)、镜像默认配置信息、构建镜像的容器配置信息、包含所有镜像层信息的 rootfs。当需要修改容器镜像内的某个文件时,docker 利用 rootfs 中的 diff_id 计算出内容寻址的索引(chainID) 来获取 layer 相关信息,进而获取每一个镜像层的文件内容,容器对镜像的修改只对处于最上方的读写层进行变动,不覆写下层已有文件系统的内容,已有文件在只读层中的原始版本仍然存在,但会被读写层中的新版本所隐藏。在多个容器之间共享镜像,每个容器在启动的时候并不需要单独复制一份镜像文件,而是将所有镜像层以只读的方式挂载到一个挂载点,再在上面覆盖一个可读写的容器层。联合挂载技术可以在一个挂载点同时挂载多个文件系统,将挂载点的原目录与被挂载内容进行整合,使得最终可见的文件系统将会包含整合之后的各层的文件和目录。联合挂载包括可读写部分(read-write layer 以及 volumes)、init-layer、只读层(read-only layer) 这 3 部分结构。
layer(镜像层) 是 docker 用来管理镜像层的一个中间概念,镜像是由镜像层组成的,而单个镜像层可能被多个镜像共享,所以 docker 将 layer 与 image 的概念分离。docker 镜像管理中的 layer 主要存放了镜像层的 diff_id、size、cache-id 和 parent 等内容。
dockers磁盘使用空间分析
查看docker目录空间
从docker目录看磁盘使用情况:
$ cd /var/lib/docker
$ du -h --max-depth=1
1.1G ./containers
0 ./plugins
213G ./overlay2
28M ./image
798M ./volumes
0 ./trust
116K ./network
0 ./swarm
16K ./builder
56K ./buildkit
0 ./tmp
0 ./runtimes
215G .
可以看出磁盘主要占用都在overlay2
和containers
这两个文件夹中,containers是容器运行时所产生的文件读写变更,overlay2是容器镜像的层的概念。
进到overlay2
继续排查:
$ du -h --max-depth=1
...
196G ./a8f42e1ae9982a4b373d310a9ea1ee08b2d0c571c3757c07f909735c1632f0d7
...
发现其中一个目录就占用了近200多G,妥妥的大头,看看具体是哪个容器:
import os
overlay = "a8f42e1ae9982a4b373d310a9ea1ee08b2d0c571c3757c07f909735c1632f0d7"
names = [line.split(" ")[-1] for line in os.popen("docker ps -a").read().split("\n") if line ]
print([name for name in names[1:] if overlay in os.popen("docker inspect %s"%name).read()])
# 返回结果: ['livego']
找到最大的元凶容器了,livego是之前做得一个直播应用,因为当时只是作为试验容器,没有选择外挂的形式,因此容器空间很大,现在已经没什么用了,可以直接删掉:
docker rm -f livego
磁盘使用空间资源释放
除了直接查看docker目录,还可以通过docker system
命令查看各类资源使用状况:
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 34 21 9.211GB 4.939GB (53%)
Containers 50 28 13.24MB 12.01MB (90%)
Local Volumes 11 5 836.2MB 754.5MB (90%)
Build Cache 0 0 0B 0B
最后一列表示可回收的比例,可以看到有部分镜像、容器和卷可以回收了,一般是这些资源没有被使用,我们可以逐个去查看:
$ docker system df -v
Images space usage:
REPOSITORY TAG IMAGE ID CREATED SIZE SHARED SIZE UNIQUE SIZE CONTAINERS
iotorbhub_iotorbbec latest 9e80c675d506 13 days ago 1.023GB 802.9MB 220MB 1
iotorbhub_iot-web latest 3e6100128e21 13 days ago 1.501GB 908.1MB 592.5MB 1
nginx latest 6678c7c2e56c 2 weeks ago 126.8MB 69.21MB 57.56MB 1
golang 1.13 3a7408f53f79 3 weeks ago 802.9MB 802.9MB
...
Containers space usage:
CONTAINER ID IMAGE COMMAND LOCAL VOLUMES SIZE CREATED STATUS NAMES
2cb435f35f36 rancher/pause:3.1 "/pause" 0 0B 38 seconds ago Created k8s_POD_default-http-backend-67cf578fc4-rhpkv_ingress-nginx_8d427371-c7c4-44fa-9b5e-745b4fea3dbc_13474
a58df48bb9cc jenkins "/bin/tini -- /usr/l…" 1 0B 47 hours ago Created loving_mclean
be40bd464972 redis "docker-entrypoint.s…" 1 0B 11 days ago Up 11 days suspicious_spence
a365e2d14c35 nginx "nginx -g 'daemon of…" 1 2B 13 days ago Up 13 days iotorbhub_nginx_1
...
Local Volumes space usage:
VOLUME NAME LINKS SIZE
33f5c1809964242a82a70a8e8fddaadc413b593be5c4310a5022a52c29917fd7 0 174.1MB
70451b6e13e2584f5174cca6d0b30e6e143015d8cbb16160eedfbb1d7b88684a 0 25.27MB
9544fd226986dd7d1921b63927072fbf8634847f524326c713505ab74874cde4 1 120B
9fef3da76c45dfcba85359ddf1684ac6c9347ba99ab61be05796f5151cd43306 0 41.43MB
...
也可以通过docker system prune
的命令一键清理:
$ docker system prune --help
Usage: docker system prune [OPTIONS]
Remove unused data
Options:
-a, --all Remove all unused images not just dangling ones
--filter filter Provide filter values (e.g. 'label=<key>=<value>')
-f, --force Do not prompt for confirmation
--volumes Prune volumes
再看看:
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 15 15 3.712GB 71.03MB (1%)
Containers 28 28 1.234MB 0B (0%)
Local Volumes 11 3 836.2MB 779.8MB (93%)
Build Cache 0 0 0B 0B
发现镜像和容器空间都被释放了。
docker 镜像精简
除了对已有运行系统进行容器磁盘空间管理外,我们还可以在镜像的源头进行磁盘空间的管理工作:
选择小体积基础镜像
docker 镜像精简最简单的方法就是用alpine
作为底层基础镜像,像 alphine 镜像 只有 5MB,是非常小的。或者可以用带 -slim 标签的镜像,一般这类镜像是通过瘦身的,镜像的体积会比较小。
减少RUN、ADD、COPY会改变容器Layer的命令
每个镜像都由一系列的 "镜像层" 组成,每次对文件的改动命令(RUN、ADD、COPY)都会被提交到一个版本,所以应该尽量减少这些命令的使用,比如多个RUN可以用&&
合并。
对于镜像层是否可以优化,我们可以通过docker history
命令查看镜像各层的构建:
$ docker history shikanon.com/hyperkube:v1.16.6-rancher1
IMAGE CREATED CREATED BY SIZE COMMENT
4c20a23643b7 2 months ago /bin/sh -c #(nop) LABEL org.label-schema.vc… 0B
<missing> 2 months ago /bin/sh -c #(nop) LABEL org.label-schema.vc… 0B
<missing> 2 months ago /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B
<missing> 2 months ago /bin/sh -c #(nop) LABEL org.label-schema.bu… 0B
<missing> 2 months ago /bin/sh -c echo "deb http://deb.debian.org/d… 17.4MB
<missing> 2 months ago /bin/sh -c sed -i -e 's!\bmain\b!main contri… 608MB
<missing> 2 months ago /bin/sh -c #(nop) COPY file:0fce35cc22ae9eae… 152MB
<missing> 13 months ago /bin/sh -c #(nop) COPY dir:2245a63ce7eafa5c8… 45.9MB
<missing> 13 months ago /bin/sh -c echo b3a101b3-deba-49a1-afe2-d42d… 303MB
<missing> 13 months ago /bin/sh -c DEBIAN_FRONTEND=noninteractive dp… 9.55kB
<missing> 13 months ago /bin/sh -c echo "dash dash/sh boolean false"… 282B
<missing> 13 months ago /bin/sh -c echo b3a101b3-deba-49a1-afe2-d42d… 1.49MB
<missing> 13 months ago /bin/sh -c ln -s /hyperkube /apiserver && l… 140B
<missing> 13 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 13 months ago /bin/sh -c #(nop) ADD file:813b8c3d7b7496f29… 42.3MB
利用FROM AS 和 COPY 实现编译阶段和代码阶段分离
Docker 17.05版本以后提供了COPY --from
的语法,他提供了从镜像层中直接拷贝文件:
# 将 编译阶段 命名为 builder
FROM golang:1.10.3 as builder
# 编译的各类指令
ADD . /app
RUN go build /app/main.go
...
# 运行阶段
# 多个 FROM 会以最后一条 FROM 为准,之前的 FROM 会被抛弃
FROM scratch
# 从编译阶段的中拷贝编译结果到当前镜像中
COPY --from=builder /build/server /
COPY --from 不但可以从前置阶段中拷贝,还可以直接从一个已经存在的镜像中拷贝,例如:
COPY --from=quay.io/coreos/etcd:v3.3.9 /usr/local/bin/etcd /usr/local/bin/
这样最后运行的只有二进制而没有编译阶段的代码文件,容器体积会缩小很多。
借助distroless
借助镜像工具 distroless 减少镜像种不必要的依赖,distroless 是 google 开放的优化版容器镜像, “distroless”镜像仅包含了应用和运行时依赖。
来源:oschina
链接:https://my.oschina.net/Kanonpy/blog/3221664