docker磁盘空间管理

感情迁移 提交于 2020-04-06 17:57:35

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	.

可以看出磁盘主要占用都在overlay2containers这两个文件夹中,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”镜像仅包含了应用和运行时依赖。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!