1. 概述

在这篇教程中,我们将学习如何获取Docker中给定父级图像的所有子级图像列表。首先,我们将看到何时需要获取此类信息。然后,我们将根据所使用的Docker构建器检索它。

2. 动机

当一个Docker镜像基于另一个镜像构建时,前者称为子级,后者称为父级。特别地,当我们遇到Docker阻止删除父级的情况时,获取给定父级的所有子级图像往往很有帮助:

$ docker rmi 645c04d243f1  
Failed to remove image (a3550c143d4e): Error response from daemon: conflict: unable to delete a3550c143d4e (cannot be forced) - image has dependent child images

在这里,Docker提示我们删除父级a3550c143d4e的子级图像。使用–force标志绕过错误并不能解决问题。因此,找到这些子级图像变得至关重要。接下来的几节将解决这个问题。

获取给定父级镜像的依赖子级图像取决于所使用的Docker构建器。从Docker Desktop和Docker Engine 23.0起,BuildKit是Linux镜像的新默认构建器。之前的版本使用的是旧构建器。我们将区分这两种构建器。

3. 使用Docker旧版构建器

旧版构建器是低于23.0版本的Docker的默认构建器。无论如何,我们可以通过设置*DOCKER_BUILDKIT=0.*来选择它。

让我们在上一节中重现错误。

3.1. 案例研究

首先,我们将定义一个父级镜像:

$ DOCKER_BUILDKIT=0 docker build -t parent -<<EOF
FROM scratch
LABEL kind=parent
EOF

重要的是要注意,旧版构建器将在未来的发布中被移除:

DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
            BuildKit is currently disabled; enable it by removing the DOCKER_BUILDKIT=0
            environment-variable.

我们需要记住这个警告,并准备好迁移到BuildKit。

接下来,让我们基于父级创建一个子级镜像:

$ DOCKER_BUILDKIT=0 docker build -t child -<<EOF
FROM parent:latest
LABEL kind=child
EOF

现在我们已经准备好了所有内容,让我们检索父级ID:

$ docker images ls --filter reference=parent
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
parent       latest    a3550c143d4e   4 minutes ago   0B

尝试删除父级时,我们会遇到最初的错误:

$ docker rmi 645c04d243f1  
Failed to remove image (a3550c143d4e): Error response from daemon: conflict: unable to delete a3550c143d4e (cannot be forced) - image has dependent child images

这是由于旧版构建器的工作方式。它缓存用于构建其他镜像的图像,并阻止它们的删除。这些信息保存在子级图像的Parent元数据中:

$ docker inspect --format '{{json .Parent}}' child
"sha256:a3550c143d4e25331167344afeb842b21780ef7d3f20dabe748390d89c16cf8b"

旧版构建器跟踪了父级。我们将使用此信息根据父级镜像ID获取依赖子级图像。

3.2. 获取依赖子级图像

让我们创建一个get_children_of.sh脚本来获取依赖子级图像:

#!/bin/sh
set -e
parent=$1
echo "Dependent Child IDs of $parent "
docker inspect --format='{{.Id}} {{.Parent}}' $(docker images --all --quiet --filter since=$parent) \
awk -v parent=$parent '{ if($2 == parent) print $1 }'

让我们分解一下发生了什么。首先,脚本以参数形式接受父级镜像ID。接下来,我们列出在父级之后构建的所有图像:

docker images --all --quiet --filter since=$parent

–all标志很重要,用于列出即使是隐藏的图像。–quiet标志仅显示镜像ID。接下来,我们检查这些图像的元数据:

docker inspect --format='{{.Id}} {{.Parent}}' 

命令docker images的结果用作docker inspect的参数。我们将输出格式缩小到仅获取子级图像ID和父级ID。接着,我们使用*awk*打印依赖子级图像:

awk -v parent=$parent '{ if($2 == parent) print $1 }'

这里,条件检查至关重要,仅打印相关父级ID的子级图像。

最后,脚本获取我们所需的父级图像a3550c143d4e的依赖子级图像:

$ ./get_children_of.sh sha256:a3550c143d4e25331167344afeb842b21780ef7d3f20dabe748390d89c16cf8b
Dependent Child IDs of sha256:a3550c143d4e25331167344afeb842b21780ef7d3f20dabe748390d89c16cf8b 
sha256:ddfecb5979b759ff56c1a730e5fc957e963cfd5bbe2108c72244b65182857bab

传递长形式ID到脚本是至关重要的。我们可以通过传递–no-trunc标志描述父级镜像来获取它。

有了这些子级图像ID,我们可以删除它们以解决我们的初始错误。

4. 使用BuildKit

BuildKit自23.0版本起成为Linux镜像的默认Docker构建器。它带来了许多从旧版构建器改进的功能。

特别是,它不缓存父级图像。这意味着当我们使用BuildKit时,我们将不会遇到之前遇到的错误。

让我们激活Buildkit在我们之前的案例研究中,使用DOCKER_BUILDKIT=1。另一种实现方法是通过更新Docker守护程序配置文件*/etc/docker/daemon.json*:

{
    "features":{
        "buildkit":true
    }
}

现在让我们重新创建父级和子级镜像。首先,我们将定义父级镜像:

$ DOCKER_BUILDKIT=1 docker build -t parent -<<EOF
FROM scratch
LABEL kind=parent
EOF

接下来,让我们基于父级创建子级镜像:

$ DOCKER_BUILDKIT=0 docker build -t child -<<EOF
FROM parent:latest
LABEL kind=child
EOF

我们将立即注意到,我们无法从子级镜像中获取任何关于父级图像的信息:

$ docker inspect --format '{{json .Parent}}' child
 ""

BuildKit不会设置Parent字段。删除父级图像不会导致任何错误

$ docker rmi sha256:a3550c143d4e25331167344afeb842b21780ef7d3f20dabe748390d89c16cf8b
Untagged: parent:latest
Deleted: sha256:a3550c143d4e25331167344afeb842b21780ef7d3f20dabe748390d89c16cf8b

无论使用哪种构建器,我们都可以始终从系统中删除废弃和未使用的图像

$ docker image prune --all --force

使用–force标志在不需要确认的情况下非常有用。

5. 总结

在这篇文章中,我们看到了如何在Docker中获取依赖子级图像的列表。首先,我们讨论了它的主要用途。然后,我们了解了根据使用的Docker构建器来实现结果的方法。