如何在Docker中获取依赖子镜像的列表?

183

我正在尝试删除一张图片,但是出现了以下提示:

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

这是 Docker 版本:

# docker version
Client:
 Version:      1.10.3
 API version:  1.22
 Go version:   go1.5.3
 Git commit:   20f81dd
 Built:        Thu Mar 10 21:49:11 2016
 OS/Arch:      linux/amd64

Server:
 Version:      1.10.3
 API version:  1.22
 Go version:   go1.5.3
 Git commit:   20f81dd
 Built:        Thu Mar 10 21:49:11 2016
 OS/Arch:      linux/amd64

但是没有额外的信息:

# docker images --format="raw" | grep f50f9524513f -C3

repository: debian
tag: 8
image_id: f50f9524513f
created_at: 2016-03-01 18:51:14 +0000 UTC
virtual_size: 125.1 MB

repository: debian
tag: jessie
image_id: f50f9524513f
created_at: 2016-03-01 18:51:14 +0000 UTC
virtual_size: 125.1 MB

我该如何获取所声称拥有的依赖子图像

没有正在运行或停止的容器具有该镜像ID。


23
为什么 Docker 团队不能提供一种本地化的方法来实现这个?请问您的看法? - Alkanshel
1
我完全同意@Alkanshel的观点,但对我来说,这不仅仅是一个不便,也是一个安全问题。无论是docker-cli还是docker-hub都没有提供更直接的方法来查找镜像中包含了什么。为什么获取这些信息如此困难? - Axel Heider
13个回答

139

如果你没有大量的图片,那么总有一种蛮力方法:

for i in $(docker images -q)
do
    docker history $i | grep -q f50f9524513f && echo $i
done | sort -u

2
我认为这是“最佳”解决方案。您可以稍微扩展一下,并通过将echo $1更改为更丑陋(但仍然是暴力的)docker images | grep $i来使其更加清晰(在Docker版本中比使用--filter标志更具可移植性)。 - Jon V
你可以在 docker history 命令后添加 -q 标志以加快执行速度。 - Lord Elrond

73

简短回答: 这里有一个Python3脚本,可以列出依赖的Docker镜像。

详细回答: 您可以使用以下操作查看在所询问的映像之后创建的所有映像的映像ID和父ID:

docker inspect --format='{{.Id}} {{.Parent}}' \
    $(docker images --filter since=f50f9524513f --quiet)

您应该能够查找父ID以f50f9524513f开头的图像,然后查找这些的子图像等等。 但是 .Parent 并不是您想要的。,因此在大多数情况下,您需要在上面指定docker images --all才能使其工作,然后您将获得所有中间层的图像ID。

这是一个更有限的python3脚本,用于解析Docker输出并进行搜索以生成图像列表:

#!/usr/bin/python3
import sys

def desc(image_ids, links):
    if links:
        link, *tail = links
        if len(link) > 1:
            image_id, parent_id = link
            checkid = lambda i: parent_id.startswith(i)
            if any(map(checkid, image_ids)):
                return desc(image_ids | {image_id}, tail)
        return desc(image_ids, tail)
    return image_ids


def gen_links(lines):
    parseid = lambda s: s.replace('sha256:', '')
    for line in reversed(list(lines)):
        yield list(map(parseid, line.split()))


if __name__ == '__main__':
    image_ids = {sys.argv[1]}
    links = gen_links(sys.stdin.readlines())
    trunc = lambda s: s[:12]
    print('\n'.join(map(trunc, desc(image_ids, links))))

如果您将这个保存为desc.py,您可以按照以下方式调用它:

docker images \
    | fgrep -f <(docker inspect --format='{{.Id}} {{.Parent}}' \
        $(docker images --all --quiet) \
        | python3 desc.py f50f9524513f )

或者只需使用上面的代码片段,它可以完成相同的操作。


2
如果它们中没有一个以期望字符开头,那怎么办?这是否表示可能存在错误?就我而言,我正在使用Docker for Mac beta版本,所以这并不让我感到惊讶。 - neverfox
3
这并不是真正回答原问题。这展示了在所提问题的图像之后创建的内容,这可能与发布者试图删除的图像有关或无关。对于至少小样本大小的图像,Simon Brady的回答解决了这个问题。 - penguincoder
2
@penguincoder,这就是Python脚本的用途。 - Aryeh Leib Taurog
像 @neverfox 一样,这个答案对我也没用。不过下面 Simon Brady 的回答确实有效。 - Mark
{btsdaf} - Aryeh Leib Taurog
显示剩余2条评论

20

安装dockviz,并按照树形视图中的映像ID跟随分支:

dockviz images --tree -l

最好先执行 sudo apt-get update ; sudo apt-get install golang-go; export GOPATH=$HOME/.go - loretoparisi
4
可以通过在 macOS 上运行 brew install dockviz 安装 dockviz。 - rcoup
当使用Ubuntu时要小心。它通常使用过时的存储库来安装go。 - Qohelet
展示了一棵漂亮的树,但是所涉及的图像在该树中没有子节点,尽管这是Docker所说的。 - Nico

9

我创建了一个 gist,其中包含 shell 脚本,用于打印 Docker 镜像的后代树形结构,如果有人对 bash 解决方案感兴趣:

#!/bin/bash
parent_short_id=$1
parent_id=`docker inspect --format '{{.Id}}' $1`

get_kids() {
    local parent_id=$1
    docker inspect --format='ID {{.Id}} PAR {{.Parent}}' $(docker images -a -q) | grep "PAR ${parent_id}" | sed -E "s/ID ([^ ]*) PAR ([^ ]*)/\1/g"
}

print_kids() {
    local parent_id=$1
    local prefix=$2
    local tags=`docker inspect --format='{{.RepoTags}}' ${parent_id}`
    echo "${prefix}${parent_id} ${tags}"

    local children=`get_kids "${parent_id}"`

    for c in $children;
    do
        print_kids "$c" "$prefix  "
    done
}

print_kids "$parent_id" ""

虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。如果链接页面更改,仅链接的答案可能会失效。-【来自审查】 - Juan Cruz Soler
我刚刚在格式化代码方面遇到了麻烦,所以我放弃了,谢谢你做这件事情,Jan。 - Michal Linhard
嘿@MichalLinhard!我发现你的代码片段非常有用。我已经将它包含在https://github.com/PHPExpertsInc/DockerUpgrade/blob/master/docker-images-update中。如果您能授予我(或所有人!)这段代码的MIT许可证,我们将不胜感激! - Theodore R. Smith

5

根据 slushyMichael Hoffman 的答案,如果您没有大量的图片可以使用此 shell 函数:

docker_image_desc() {
  for image in $(docker images --quiet --filter "since=${1}"); do
    if [ $(docker history --quiet ${image} | grep ${1}) ]; then
      docker_image_desc "${image}"
    fi
  done
  echo "${1}"
}

之后使用以下方式调用它

docker_image_desc <image-id> | awk '!x[$0]++'

5
这里有一个简单的方法来获取依赖于父级图片的子级图片列表:
image_id=123456789012

docker images -a -q --filter since=$image_id |
xargs docker inspect --format='{{.Id}} {{.Parent}}' |
grep $image_id

这将生成一个子/父图像ID列表,例如(为简洁起见进行了截断):

sha256:abcdefghijkl sha256:123456789012
左边是子镜像ID,右边是我们要删除的父镜像ID,但是它说“镜像有依赖的子镜像”。这告诉我们abcdefghijkl是依赖于123456789012的子镜像。所以我们需要先docker rmi abcdefghijkl,然后再 docker rmi 123456789012
现在,可能会有一系列依赖的子镜像,因此您可能需要不断重复,找到最后一个子镜像。

2
而且这个链可能比你这个逐步方法的时间长得多:) - mirekphd

4
以下是基于Python API(pip install docker)的解决方案,它可以递归列出后代及其标签(如果有的话),根据关系深度(子代、孙代等)增加缩进:
import argparse
import docker

def find_img(img_idx, id):
    try:
        return img_idx[id]
    except KeyError:
        for k, v in img_idx.items():
            if k.rsplit(":", 1)[-1].startswith(id):
                return v
    raise RuntimeError("No image with ID: %s" % id)

def get_children(img_idx):
    rval = {}
    for img in img_idx.values():
        p_id = img.attrs["Parent"]
        rval.setdefault(p_id, set()).add(img.id)
    return rval

def print_descendants(img_idx, children_map, img_id, indent=0):
    children_ids = children_map.get(img_id, [])
    for id in children_ids:
        child = img_idx[id]
        print(" " * indent, id, child.tags)
        print_descendants(img_idx, children_map, id, indent=indent+2)

def main(args):
    client = docker.from_env()
    img_idx = {_.id: _ for _ in client.images.list(all=True)}
    img = find_img(img_idx, args.id)
    children_map = get_children(img_idx)
    print_descendants(img_idx, children_map, img.id)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("id", metavar="IMAGE_ID")
    main(parser.parse_args())

例子:

$ python find_dep_img.py 549afbf12931
 sha256:913d0981fdc7d2d673f2c8135b7afd32ba5037755e89b00432d3460422ba99b9 []
   sha256:0748dbc043b96ef9f88265c422e0267807f542e364b7a7fadf371ba5ee082b5d []
     sha256:6669414d2a0cc31b241a1fbb00c0ca00fa4dc4fa65dffb532bac90d3943d6a0a []
       sha256:a6441e7d9e92511608aad631f9abe8627033de41305c2edf7e03ee36f94f0817 ['foo/bar:latest']

我已将其作为一个gist在https://gist.github.com/simleo/10ad923f9d8a2fa410f7ec2d7e96ad57上提供。


3
这是我为了保留我的最终“镜像”(实际上是层次结构,这让我有些困惑,因为我刚开始接触 Docker 构建)所做的事情。
我一直收到“... 无法强制删除...”的错误信息。我意识到我无法删除那些不需要的镜像,因为它们并不是由 'docker commit' 创建的独立镜像。我的问题在于,在基础镜像和最终镜像之间有几个镜像(或层次结构),而仅仅尝试清理就遇到了关于子级和父级的错误/警告。
1. 我将最终镜像(或层次结构,如果您愿意这么称呼)导出到一个 tarball 中。 2. 然后我删除了所有我想要的镜像,包括我的最终镜像 - 我已经将其保存到了一个 tarball 中,所以虽然我不确定是否能够使用它,但我只是在尝试。 3. 然后我运行了 docker image load -i FinalImage.tar.gz。输出结果类似于:
7d9b54235881: Loading layer [==================================================>]  167.1MB/167.1MB
c044b7095786: Loading layer [==================================================>]  20.89MB/20.89MB
fe94dbd0255e: Loading layer [==================================================>]  42.05MB/42.05MB
19abaa1dc0d4: Loading layer [==================================================>]  37.96MB/37.96MB
4865d7b6fdb2: Loading layer [==================================================>]  169.6MB/169.6MB
a0c115c7b87c: Loading layer [==================================================>]    132MB/132MB

加载的镜像ID: sha256:82d4f8ef9ea1eab72d989455728762ed3c0fe35fd85acf9edc47b41dacfd6382

现在,当我使用“docker image ls”命令列出时,我只看到了原始的基础镜像和之前保存为tar包的最终镜像。

[root@docker1 ~]# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
httpd               import              82d4f8ef9ea1        3 days ago          747MB
centos              httpd               36540f359ca3        5 weeks ago         193MB

我的系统现在很“干净”。我只有我想要的图片,甚至没有遇到删除基础图像的问题。

[root@docker1 ~]# docker rmi 36540f359ca3
Untagged: centos:httpd
Untagged:     centos@sha256:c1010e2fe2b635822d99a096b1f4184becf5d1c98707cbccae00be663a9b9131
Deleted: sha256:36540f359ca3b021d4b6a37815e9177b6c2bb3817598979ea55aee7ecc5c2c1f

1
很好的回答,但是"load"只应该用于从"docker save"创建的镜像。对于"docker export"的恰当还原方法是"docker import"。 - Alkanshel

0

怎么样:

ID=$(docker inspect --format="{{.Id}}" "$1")
IMAGES=$(docker inspect --format="{{if eq \"$ID\" .Config.Image}}{{.Id}}{{end}}" $(docker images --filter since="$ID" -q))
echo $(printf "%s\n" "${IMAGES[@]}" | sort -u)

它将打印子图像的ID,带有sha256:前缀。
我还有以下内容,它会附加名称:

IMAGES=$(docker inspect --format="{{if eq \"$ID\" .Config.Image}}{{.Id}}{{.RepoTags}}{{end}}" $(docker images --filter since="$ID" -q))

  • ID= 获取图像的完整ID
  • IMAGES= 获取所有子图像,这些子图像将此图像列为Image
  • echo... 删除重复项并输出结果

我喜欢Go语言的条件格式,但也许它们作为此用例的过滤器会更好。 - ingyhere

0
这是一个直接使用awk给予依赖子图像ID的脚本。
#!/bin/sh
set -e
parent=$1
echo "Dependent Child Images 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作为第一个参数传递来使用:

$ ./get_children_of.sh sha256:15f16e6da280b21e8d3b17ac49022732a553550a15ad2d0d12d5cf22b36254cb
Dependent Child IDs of sha256:15f16e6da280b21e8d3b17ac49022732a553550a15ad2d0d12d5cf22b36254cb 
sha256:c18db0311efb263c0f52e520d107b02a373aac7a4821164e7a1b46b14a3a1419

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接