一、背景
某业务环境使用 Docker 作为业务支撑,某次业务人员反馈,业务网站无法访问,怀疑是云主机磁盘容量持续增长,磁盘空间耗尽导致容器实例异常,业务通信异常。 本文通过探求docker环境下资源增长情况,看Docker排障常用命令。
环境:Docker version 20.10.6, build 370c289,
官方命令手册:单击参看
二、过程记录
1)查看宿主机的文件系统使用情况:
du -sh --max-depth=0 目录名 //–max-depth=0等同于直接进入目录下执行du -sh;-s表示显示目录总大小,否则,列出该目录下所有子目录文件大小
du参数回顾:
-a:全部文件与目录大小都列出来。如果不加任何选项和参数只列出目录(包含子目录)大小。
-b:列出的值以bytes为单位输出,默认是以Kbytes
-c:最后加总;递归目录里所有文件,列出大小,最后总和
-k:以KB为单位输出
-m:以MB为单位输出
-s:只列出总和
-h:系统自动调节单位,例如文件太小可能就几K,那么就以K为单位显示,如果大到几G,则就以G为单位显示。常用du –sh filename
经排查:/var/lib/docker/占了15G多,总共磁盘空间20G。
2)docker system df //查询镜像(Images)、容器(Containers)和本地卷(Local Volumes)占用情况
2)docker system df -v //-v参数查看空间占用细节,以确定具体是哪个镜像、容器或本地卷占用了过高空间
**说明:**镜像的不同状态表示:
已使用镜像(used image): 指所有已被容器(包括已停止的)关联的镜像。即 docker ps -a 看到的所有容器使用的镜像。
未引用镜像(unreferenced image):没有被分配或使用在容器中的镜像,但它有 Tag 信息。
悬空镜像(dangling image):未配置任何 Tag (也就无法被引用)的镜像,所以悬空。这通常是由于镜像 build 的时候没有指定 -t 参数配置 Tag 导致的;一般来源有以下2种:
1> 构建镜像过程中因为脚本错误导致很多镜像构建终止,产生很多none标签的版本
2>手动构建镜像的时候没有进行提交,遗留来的垃圾镜像;这些镜像占据较大的存储空间,需要删除
Docker 采用保守的方法来清理未使用的对象(通常称为“垃圾回收”),例如镜像、容器、卷和网络:除非明确要求 Docker 这样做,否则通常不会删除这些对象。对于每种类型的对象,Docker 都提供了一条 prune 命令。也可以使用 docker system prune一次清理多种类型的对象。
docker images --no-trunc //全长列出image ID
docker image prune //清理none镜像(虚悬镜像),默认,docker image prune 命令只会清理 虚无镜像(没被标记且没被其它任何镜像引用的镜像)
docker image prune -a //加 -f 或 --force 可强制删除而不安全提示
docker images --filter “dangling=true” //显示未标记的image(悬空)
docker rmi $(docker images -f “dangling=true” -q) //批量删除悬空镜像, 删除所有悬空镜像,但不会删除未使用镜像
docker container prune //容器清理;停止容器后不会自动删除这个容器,除非在启动容器的时候指定了 –rm 标志。默认情况下,所有停止状态的容器会被删除。
docker container prune -a --filter “until=24h” //只会删除 24 小时之前创建的停止状态的容器
docker volume prune //卷可以被一个或多个容器使用,并占用 Docker 主机上的空间。卷永远不会被自动删除,因为这么做会破坏数据
docker volume rm $(docker volume ls -qf dangling=true) //删除所有未被任何容器关联引用的卷
docker network prune //清理没有被容器未使用的网络
docker network prune --filter “until=24h” //网络配置通常占用的空间非常低,可略过
docker volume prune //Volumes可被一个或多个容器使用会消耗host端的空间,也不会自动清理
说明: --filter参数采用“key=value”形式指定规则,多个规则就空格隔开写多个filter,如:–filter “foo=bar” --filter “bif=baz”;单击查看更多关于filter的使用。目前支持的过滤器有2个:
until () //只删除在给定时间戳之前创建的对象
label (label=, label==, label!=, or label!==) //仅删除带有(或不带有,如果使用 label!=… )指定标签的对象。
3)docker system prune //执行自动空间清理,实际它是修剪镜像、容器和网络的快捷方式
注:在 Docker 17.06.0 及以前版本中,可直接修剪卷。在 Docker 17.06.1 及更高版本中必须为 docker system prune 命令明确指定 --volumes 标志才会修剪卷。
docker system prune --volumes //
4)docker exec -it 容器_name sh
docker inspect
5)emq情况
EMQ X Broker v4.1.3 is started successfully!
6)docker服务重启后容器启动失败
docker ps -aq | xargs -I {} docker start {}
cat /proc/mounts | grep “mapper/docker” | awk ‘{print $2}’ 查看docker占用的挂载点,查看,然后手动umount卸载占用的挂载点,再次启动。
7)删除容器
docker rm -v $(docker ps -aq -f status=exited) //删除所有已退出的容器
docker rm -v $(docker ps -aq -f status=dead) //删除所有状态为 dead 的容器
8)在用空间资源分析
如果没有可清除的对象时,就需要对当前在用的空间进行资源分析,看有没可清理释放的空间
A、镜像空间分析
Docker 镜像由一系列层构建而成。每一层代表镜像的 Dockerfile 中的一条指令。除了最后一层之外,每一层都是只读的。如以下 Dockerfile:
# syntax=docker/dockerfile:1
FROM ubuntu:18.04 ##最底层
LABEL org.opencontainers.image.authors="org@example.com"
COPY . /app ##倒数第2层
RUN make /app ##倒数第3层
RUN rm -r $HOME/.cache ####倒数第4层
CMD python /app/app.py
上述这个 Dockerfile 包含四条命令。修改文件系统的命令会创建一个层。 FROM 语句首先从 ubuntu:18.04 镜像创建一个层。 LABEL 命令仅修改image的元数据,不会生成新图层。 COPY 命令向 Docker 客户端的当前目录添加一些文件。第一个 RUN 命令使用 make 命令构建用户的应用程序,并将结果写入新层。第二个 RUN 命令删除缓存目录,并将结果写入新层。最后,CMD 指令指定在容器内运行什么命令,它只修改image的元数据,不会产生图像层。
每一层只是在它之前的层上增加的一组差异后形成的新层。请注意,只要添加和删除文件都会产生一个新层。在上面的例子中,$HOME/.cache 目录被删除了,但在上一层仍然可用,并且加起来就是image的总大小。更多参看Dockefile最佳实践。
如果某个镜像占用了过高空间,则可通过如下方式做进一步空间分析:
1>通过 docker system df 获取占用过高空间的镜像信息。
2>基于相应镜像创建测试容器。
3>exec 进入容器后,结合 du 等 shell 指令做进一步空间分析,定位出占用最高空间的目录或文件。
4>结合业务情况做进一步处理,重新 build 镜像。
B、容器空间分析
一个容器的占用的总空间,包含其最顶层的读写层(writable layer)和底部的只读镜像层(base image layer,read-only),更多参考。如下所示:
容器和图像之间的主要区别在于顶部可写层。添加新数据或修改现有数据的所有写入容器都存储在此可写层中。当容器被删除时,可写层也被删除。底层图像保持不变。因为每个容器都有自己的可写容器层,所有的变化都存储在这个容器层中,所以多个容器可以共享对同一个底层镜像的访问,同时又拥有自己的数据状态。下图显示了共享同一个 Ubuntu 15.04 镜像的多个容器。
如果某个运行中的容器占用了过高空间,则可以通过如下方式做进一步空间分析:
1>通过 docker system df 获取占用过高空间的容器信息。
2>通过前述 -s参数确认到底是底层镜像,还是运行过程中产生的数据占用了过高空间。
3>exec 进入容器,结合 du 等 shell 指令做进一步空间分析,定位出占用最高空间的目录或文件。
4>结合业务情况做进一步处理。
可以通过 docker ps 的 -s参数来分别显示二者的空间占用情况,进而判断相应容器的空间占用主要是来自原始镜像,还是运行中产生。
上图中,vpm-idex原始镜像占用了 382MB 空间,实际运行过程中只占用了 545KB 空间:
9)Docker 磁盘空间限制与使用建议
A:磁盘空间限制1: 使用 Device Mapper 存储驱动限制容器磁盘空间
如果使用 Device Mapper 作为底层存储驱动,则可以通过 Docker daemon 的如下参数来全局限制单个容器占用空间的大小:
–storage-opt dm.basesize=20G //表示限制单个容器最多占用 20G 空间,将应用于任何新建容器。
关于 Device Mapper 存储驱动的说明,更多参看官网。
B:磁盘空间限制2: 使用 btrfs 存储驱动限制容器磁盘空间
btrfs 驱动主要使用 btrfs 所提供的 subvolume 功能来实现。一个容器会对应一个 subvolume。针对容器对应的 subvolume 启用并配置 quota 即可限制其磁盘空间。示例配置:
btrfs qgroup limit -e 50G /var/lib/docker/btrfs/subvolumes/<CONTAINER_ID> //更多参看官网
C:外挂 LVM 卷
如果使用的是其它不支持对单个容器的磁盘容量进行限制的存储驱动,则可以考虑如下通用方案:
通过 LVM 方式创建一个指定容量的卷,然后挂载到宿主操作系统上特定目录。最后通过 --volume 参数来让容器来挂载使用相应目录。
注意:该方案的前提条件是,容器中所有落盘操作要全部落到上述 “–volume” 参数指定的卷中,否则容器还会占用默认 aufs 所在盘的空间,进而造成统计不准。更多参看官网
Docker的数据是通过联合文件系统的方式存储到磁盘上,当需要在机器上运行的容器或者镜像的数量不断增加时,有可能磁盘的大小不再满足需求,这个时候就需要给Docker的数据目录通过增加数据盘的方式进行扩容。具体参看:如何给容器服务的Docker增加数据盘。
通用的存储使用建议如下:
1> 容器内的业务日志务必配置轮询覆写,或者使用日志驱动将日志输出到外部存储。避免日志文件持续增长,占用过高磁盘空间。
2> 结合外部监控对宿主机的磁盘空间使用情况进行监控和告警。