有时候,使用docker images
指令我们可以发现大量的无REPOSITORY、TAG的docker镜像,这些镜像究竟是什么?
它们没有REPOSITORY、TAG名称,没有办法引用,那么它们还有什么用?
[root@cdh-100 data]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
app_worker cuda a7ce8fa8e585 4 weeks ago 7.39GB
<none> <none> 2a2ad8902f37 4 weeks ago 7.39GB
<none> <none> 53d165ed9913 5 weeks ago 7.39GB
<none> <none> a79c59ed7311 6 weeks ago 7.34GB
一、 什么是悬空镜像?
我们所看到的这些没有REPOSITORY
和TAG
的Docker镜像,其实是悬空镜像(dangling images)。当新的镜像被构建出来,并且和旧镜像具有相同的标签时,旧镜像就会变成悬空状态。简单来说,它们属于没有被标记的旧版本镜像。
一般而言,这类镜像不会对系统造成危害,不过它们会占用大量的磁盘空间。所以,删除它们是可行的,而且这也是清理Docker环境的常见操作。
批量删除悬空镜像的方法
要删除这些悬空镜像,可以借助Docker提供的特定过滤功能来实现批量删除:
docker rmi $(docker images -f "dangling=true" -q)
上述命令的具体作用如下:
docker images -f "dangling=true" -q
:用于筛选出所有悬空镜像,并只返回它们的ID。docker rmi
:负责删除镜像,这里会删除前面命令所列出的全部悬空镜像。
执行此命令时需要留意,它会删除所有的悬空镜像。所以在执行之前,要确保我们确实不需要这些镜像了。
更安全的操作方式
如果想先查看哪些镜像会被删除,然后再确认是否删除,可以分两步进行操作:
# 查看所有悬空镜像
docker images -f "dangling=true"
# 确认无误后再删除
docker rmi $(docker images -f "dangling=true" -q)
要是希望在删除镜像之前查看它们占用的空间大小,可以使用以下命令:
docker system df
该命令会显示Docker各部分占用的磁盘空间,包括悬空镜像。
自动清理设置
为了避免悬空镜像不断积累,在构建新镜像时,可以添加--no-cache
选项。另外,还可以设置Docker定期自动清理:
docker system prune -a --volumes
这个命令会删除所有未使用的镜像、容器、网络和卷。不过要谨慎使用,因为它可能会删除我们需要的内容。可以通过添加--filter
选项来更精确地控制删除范围。
清理完成后,再次运行docker images
命令,会发现悬空镜像已经被成功删除了。
二、 Docker删除镜像前,如何找到所有关联的已停止容器?
在删除Docker镜像之前,找到所有关联的已停止容器是很重要的,因为这些容器可能依赖于想要删除的镜像。以下是几种方法可以帮助我们识别这些关联的容器:
方法1:通过镜像ID查找关联容器
如果知道要删除的镜像ID(例如 a7ce8fa8e585
),可以使用以下命令查找所有使用该镜像的容器(包括已停止的容器):
docker ps -a --filter "ancestor=a7ce8fa8e585" --format "{{.ID}} {{.Image}} {{.Status}} {{.Names}}"
- 参数说明:
docker ps -a
:显示所有容器(包括已停止的)。--filter "ancestor=镜像ID"
:筛选出基于指定镜像创建的容器。--format
:自定义输出格式,显示容器ID、镜像、状态和名称。
方法2:查找所有已停止的容器并检查镜像
如果想先列出所有已停止的容器,再手动检查它们使用的镜像,可以使用:
docker ps -a --filter "status=exited" --format "{{.ID}} {{.Image}} {{.Status}} {{.Names}}"
- 然后,从输出中找到与要删除的镜像ID或名称匹配的容器。
方法3:批量停止/删除关联容器
如果确定要删除所有关联的已停止容器,可以结合上述命令批量操作:
# 查找并停止所有使用指定镜像的容器(如果仍在运行)
docker stop $(docker ps -q --filter "ancestor=a7ce8fa8e585")
# 删除所有使用指定镜像的已停止容器
docker rm $(docker ps -aq --filter "ancestor=a7ce8fa8e585" --filter "status=exited")
- 注意:执行前请确认这些容器不再需要,避免数据丢失。
方法4:使用脚本自动化检查
如果需要频繁清理镜像,可以编写一个简单的脚本,先检查镜像关联的容器,再决定是否删除:
#!/bin/bash
# 检查镜像是否有关联容器
check_image_containers() {
local IMAGE=$1
local CONTAINERS=$(docker ps -aq --filter "ancestor=$IMAGE")
if [ -n "$CONTAINERS" ]; then
echo "警告:镜像 $IMAGE 有关联的容器:"
docker ps -a --filter "ancestor=$IMAGE" --format "{{.ID}} {{.Image}} {{.Status}} {{.Names}}"
read -p "是否删除这些容器?(y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
docker rm -f $CONTAINERS
echo "已删除关联容器。"
else
echo "取消操作。"
exit 1
fi
else
echo "镜像 $IMAGE 没有关联容器,可以安全删除。"
fi
}
# 使用示例:检查镜像并删除
IMAGE_ID="a7ce8fa8e585"
check_image_containers $IMAGE_ID && docker rmi $IMAGE_ID
总结步骤
- 查找关联容器:使用
docker ps -a --filter "ancestor=镜像ID"
。 - 停止并删除容器(如果需要):
docker stop
和docker rm
。 - 删除镜像:确认无关联容器后,执行
docker rmi 镜像ID
。
通过这些方法,可以安全地清理不再需要的镜像和容器,避免因依赖关系导致的删除失败。
三、对应的shell 脚本 和Python脚本
下面是两种实现方式的脚本,它们都能够在删除大量Docker镜像之前,找出所有与之关联的已停止容器,并且会对容器和镜像进行相应的处理。
Shell脚本实现
#!/bin/bash
# 检查是否有root权限
if [ "$(id -u)" -ne 0 ]; then
echo "请使用root权限运行此脚本"
exit 1
fi
# 获取所有镜像ID
image_ids=$(docker images -q)
# 确认是否继续
echo "即将处理以下镜像:"
docker images
read -p "确定要继续吗?(y/n): " confirm
if [ "$confirm" != "y" ]; then
echo "操作已取消"
exit 0
fi
# 处理每个镜像
for image_id in $image_ids; do
echo -e "\n处理镜像: $image_id"
# 查找关联的已停止容器
stopped_containers=$(docker ps -aq --filter "ancestor=$image_id" --filter "status=exited")
if [ -n "$stopped_containers" ]; then
echo "找到关联的已停止容器:"
docker ps -a --filter "ancestor=$image_id" --filter "status=exited"
# 确认是否删除容器
read -p "是否删除这些容器?(y/n): " delete_containers
if [ "$delete_containers" == "y" ]; then
docker rm $stopped_containers
echo "已删除关联的已停止容器"
else
echo "跳过删除容器"
fi
else
echo "没有找到关联的已停止容器"
fi
# 确认是否删除镜像
read -p "是否删除此镜像?(y/n): " delete_image
if [ "$delete_image" == "y" ]; then
# 尝试删除镜像(可能会失败,因为有运行中的容器)
if docker rmi $image_id 2>/dev/null; then
echo "已删除镜像: $image_id"
else
echo "无法删除镜像: $image_id (可能有运行中的容器依赖它)"
fi
else
echo "跳过删除镜像"
fi
done
echo -e "\n镜像处理完成"
Python脚本实现
import subprocess
import sys
def run_command(command):
"""执行shell命令并返回输出"""
try:
result = subprocess.run(command, shell=True, capture_output=True, text=True, check=True)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
print(f"命令执行失败: {e.stderr.strip()}")
return None
def get_all_images():
"""获取所有Docker镜像ID"""
output = run_command("docker images -q")
if not output:
print("没有找到Docker镜像")
return []
return output.split()
def get_stopped_containers(image_id):
"""获取与镜像关联的已停止容器"""
output = run_command(f'docker ps -aq --filter "ancestor={image_id}" --filter "status=exited"')
if not output:
return []
return output.split()
def main():
# 检查root权限
if sys.platform != 'win32':
import os
if os.geteuid() != 0:
print("请使用root权限运行此脚本")
return
# 获取所有镜像
images = get_all_images()
if not images:
return
# 确认是否继续
print("即将处理以下镜像:")
subprocess.run("docker images", shell=True)
confirm = input("确定要继续吗?(y/n): ").strip().lower()
if confirm != 'y':
print("操作已取消")
return
# 处理每个镜像
for image_id in images:
print(f"\n处理镜像: {image_id}")
# 获取关联的已停止容器
stopped_containers = get_stopped_containers(image_id)
if stopped_containers:
print("找到关联的已停止容器:")
subprocess.run(f'docker ps -a --filter "ancestor={image_id}" --filter "status=exited"', shell=True)
# 确认是否删除容器
delete_containers = input("是否删除这些容器?(y/n): ").strip().lower()
if delete_containers == 'y':
container_ids = ' '.join(stopped_containers)
if run_command(f"docker rm {container_ids}") is not None:
print("已删除关联的已停止容器")
else:
print("删除容器失败")
else:
print("跳过删除容器")
else:
print("没有找到关联的已停止容器")
# 确认是否删除镜像
delete_image = input("是否删除此镜像?(y/n): ").strip().lower()
if delete_image == 'y':
# 尝试删除镜像
if run_command(f"docker rmi {image_id}") is not None:
print(f"已删除镜像: {image_id}")
else:
print(f"无法删除镜像: {image_id} (可能有运行中的容器依赖它)")
else:
print("跳过删除镜像")
print("\n镜像处理完成")
if __name__ == "__main__":
main()
使用说明
这两个脚本都提供了以下功能:
- 先获取系统中所有的Docker镜像。
- 针对每个镜像,查找与之关联的已停止容器。
- 展示找到的容器信息,并询问是否要删除这些容器。
- 询问是否要删除当前处理的镜像。
- 执行相应的删除操作,并显示操作结果。
注意事项
- 运行脚本需要有root权限或者适当的Docker权限。
- 脚本在删除镜像之前,会先处理已停止的容器,但不会处理正在运行的容器。如果有运行中的容器依赖某个镜像,那么删除该镜像将会失败。
- 脚本会显示详细的操作提示,在进行删除操作前会多次确认,避免误删。
- 对于生产环境,建议先备份重要数据,再运行脚本。
四、删除悬空镜像后磁盘占用为什么变化不大?
在使用docker rmi $(docker images -f "dangling=true" -q)
命令删除悬空镜像后磁盘空间未减少,可能由以下原因导致,以下是详细分析及解决方案:
一、原因分析
1. 镜像层被其他镜像共享
- Docker采用分层存储机制,镜像由多个只读层(layer)组成。当删除一个悬空镜像时,若其底层被其他镜像共享,则这些层不会被立即删除,因此磁盘空间不会减少。
- 示例:镜像A和镜像B共享底层
layer1
,若删除镜像A,layer1
仍被镜像B使用,不会释放空间。
2. 未清理其他Docker资源
- 悬空镜像只是Docker占用空间的一部分,其他资源如:
- 已停止的容器:即使容器停止,其文件系统仍可能占用空间。
- 未使用的卷(Volumes):容器挂载的卷可能存储大量数据。
- 日志文件:容器运行时产生的日志未被清理。
- 网络和缓存:未使用的网络配置或构建缓存。
3. 悬空镜像未完全删除
- 可能存在以下情况:
- 命令执行时遗漏了部分悬空镜像(如镜像正在被其他进程引用)。
- 新的悬空镜像在删除后迅速重建(如自动构建任务)。
4. 未使用Docker系统清理命令
docker rmi
仅删除镜像本身,不会清理:- 未被引用的镜像层(需通过
docker system prune
处理)。 - 其他未使用的资源(如卷、网络)。
- 未被引用的镜像层(需通过
5. 文件系统缓存或显示延迟
- 磁盘空间显示可能因文件系统缓存未刷新而未更新,需通过系统命令强制刷新。
二、解决方案
1. 检查悬空镜像是否完全删除
# 再次查看悬空镜像,确认是否有残留
docker images -f dangling=true
若仍有输出,尝试使用-f
参数强制删除:
docker rmi -f $(docker images -f dangling=true -q)
2. 清理所有未使用的Docker资源
# 清理悬空镜像、已停止容器、未使用网络
docker system prune -a
# 可选:同时清理未使用的卷(谨慎操作,可能删除有用数据)
docker system prune -a --volumes
3. 手动清理共享镜像层
# 查看所有镜像层的使用情况(需安装dive工具)
dive list-layers
# 或通过存储驱动查看层引用关系(以Overlay2为例)
ls -la /var/lib/docker/overlay2/layers/
若发现大量共享层,可通过删除依赖这些层的镜像释放空间(需谨慎操作)。
4. 清理容器日志
# 查看容器日志大小
du -sh /var/lib/docker/containers/*/*.log
# 清空日志(不删除文件,保留权限)
find /var/lib/docker/containers/ -name "*.log" -exec truncate -s 0 {} \;
5. 清理未使用的卷
# 查看所有卷及其大小
docker volume ls
docker system df
# 清理未使用的卷
docker volume prune
6. 刷新磁盘空间显示
# 重启Docker服务
systemctl restart docker
# 或使用系统命令强制刷新磁盘统计
sync && echo 3 > /proc/sys/vm/drop_caches
三、预防措施
定期自动化清理
创建定时任务,每周执行系统清理:# crontab -e 0 0 * * 0 docker system prune -a --volumes --force
镜像构建时正确打标签
避免生成悬空镜像:docker build -t myimage:tag .
使用Docker Compose管理资源
通过compose down --remove-orphans
自动清理孤儿资源。
四、关键命令总结
操作目的 | 命令 |
---|---|
清理所有未使用资源 | docker system prune -a --volumes |
仅清理悬空镜像 | docker image prune -a |
清理未使用的卷 | docker volume prune |
查看磁盘使用统计 | docker system df |
强制删除所有镜像(危险) | docker rmi $(docker images -q) -f |
通过以上步骤,可全面释放Docker占用的磁盘空间。若问题仍存在,建议检查宿主机其他服务(如容器运行时、Kubernetes)是否占用了额外存储。