如何从私有Docker注册表中删除图像?


162

我运行了一个私有Docker注册表,我想latest从存储库中删除除以外的所有图像。我不想删除整个存储库,仅删除其中的一些图像。该API文档不提一个办法做到这一点,但肯定这是可能的?


1
接受的答案不再正确(尽管绝对很好)。您可以使用DELETE / v2 / <name> / manifests / <reference>删除图像:docs.docker.com/registry/spec/api/#deleting-an-image
Michael Zelensky

Answers:


116

当前,您不能将注册表API用于该任务。它仅允许您删除存储库或特定标签。

通常,删除存储库意味着删除与此仓库关联的所有标签。

删除标签意味着删除图像和标签之间的关联。

以上所有都不删除单个图像。它们留在您的磁盘上。


解决方法

对于此解决方法,您需要将docker映像存储在本地。

解决方案的一种解决方法是删除除最新标签以外的所有标签,从而潜在地删除对关联图像的引用。然后您可以运行此脚本以删除所有图像,这些图像未被任何标签或任何已使用图像的祖先所引用。

术语(图像和标签)

考虑一个这样的图像图,其中大写字母(A,,B...)表示短图像ID,并且<-意味着一个图像基于另一个图像:

 A <- B <- C <- D

现在,我们向图片添加标签:

 A <- B <- C <- D
           |    |
           |    <version2>
           <version1>

在此,标签<version1>引用图像C,标签<version2>引用图像D

完善您的问题

在您的问题中,您说过您想删除

所有图片,但 latest

。现在,该术语不太正确。您已经混合了图像和标签。查看图表,我想您会同意该标记<version2>代表最新版本。实际上,根据此问题,您可以具有代表最新版本的标签:

 A <- B <- C <- D
           |    |
           |    <version2>
           |    <latest>
           <version1>

由于<latest>标签引用了图像,D所以我问您:您是否真的要删除除图像以外的所有图像D?可能不是!

如果删除标签会怎样?

如果<version1>使用Docker REST API 删除标签,则会得到以下信息:

 A <- B <- C <- D
                |
                <version2>
                <latest>

切记: Docker永远不会删除映像!即使这样做,在这种情况下也无法删除图像,因为该图像CD被标记图像的祖先的一部分。

即使您使用此脚本,也不会删除任何图像。

何时可以删除图像

在您可以控制何时有人可以拉入或推入注册表的条件下(例如,通过禁用REST接口)。如果没有其他图像基于该图像且没有标签引用该图像,则可以从图像图中删除该图像。

请注意,下面的图,该图像D没有根据C,但对B。因此,D不依赖C。如果您<version1>在此图中删除标签,则该图像C将不会被任何图像使用,并且此脚本可以将其删除。

 A <- B <--------- D
      \            |
       \           <version2>
        \          <latest>
         \ <- C
              |
              <version1>

清理后,您的图像图如下所示:

 A <- B <- D
           |
           <version2>
           <latest>

这是你想要的吗?


没错,删除标签实际上并没有删除关联的图像。那么,有什么办法可以删除通过ssh访问该框的图像呢?
狮子座

1
我已经用适合我的解决方法来编辑答案。希望对您有所帮助。
Konrad Kleine 2014年

就是这样-我知道映像是逐步建立的,但是我需要一种方法来修剪那些死枝。谢谢!
狮子座

@KonradKleine如何使图像图形从您从注册表中获得的图像列表之外?
拉斐尔·巴罗斯

9
垃圾收集最终到达了Registry v2.4.0 docs.docker.com/registry/garbage-collection
Markus Lindberg,

68

我的注册表遇到了同样的问题,然后从博客页面尝试了下面列出的解决方案。有用。

步骤1:列出目录

您可以通过以下网址列出目录:

http://YourPrivateRegistyIP:5000/v2/_catalog

回复将采用以下格式:

{
  "repositories": [
    <name>,
    ...
  ]
}

步骤2:列出相关目录的标签

您可以通过调用以下网址列出目录标签:

http://YourPrivateRegistyIP:5000/v2/<name>/tags/list

回复将采用以下格式:

{
"name": <name>,
"tags": [
    <tag>,
    ...
]

}

步骤3:列出相关标签的清单值

您可以在Docker注册表容器中运行以下命令:

curl -v --silent -H "Accept: application/vnd.docker.distribution.manifest.v2+json" -X GET http://localhost:5000/v2/<name>/manifests/<tag> 2>&1 | grep Docker-Content-Digest | awk '{print ($3)}'

回复将采用以下格式:

sha256:6de813fb93debd551ea6781e90b02f1f93efab9d882a6cd06bbd96a07188b073

使用清单值运行以下命令:

curl -v --silent -H "Accept: application/vnd.docker.distribution.manifest.v2+json" -X DELETE http://127.0.0.1:5000/v2/<name>/manifests/sha256:6de813fb93debd551ea6781e90b02f1f93efab9d882a6cd06bbd96a07188b073

步骤4:删除标记的清单

在docker registy容器中运行以下命令:

bin/registry garbage-collect  /etc/docker/registry/config.yml  

这是我的config.yml

root@c695814325f4:/etc# cat /etc/docker/registry/config.yml
version: 0.1
log:
  fields:
  service: registry
storage:
    cache:
        blobdescriptor: inmemory
    filesystem:
        rootdirectory: /var/lib/registry
    delete:
        enabled: true
http:
    addr: :5000
    headers:
        X-Content-Type-Options: [nosniff]
health:
  storagedriver:
    enabled: true
    interval: 10s
    threshold: 3

命令成功完成,包括有关“ Deleteing blob: /docker/...”的消息,但未更改所使用的磁盘空间。使用bin / registry github.com/docker/distribution v2.4.1
JamesThomasMoon1979 '17

3
要删除映像,应使用REGISTRY_STORAGE_DELETE_ENABLED = true参数运行注册表容器。
阿迪尔

3
我在/etc/docker/registry/config.yml文件中将“删除:已启用”值设置为true。对于此配置,无需设置REGISTRY_STORAGE_DELETE_ENABLED变量@AdilIlhan
Yavuz Sert

重新:步骤3,“ Docker-Content-Digest”必须在标题中吗?(在curl输出中看不到它)。在删除已标记的清单之前,我是否应该能够看到标记清单的结果,才能知道我已成功标记该清单?无论我发送什么,它似乎都会给我202答复。
SteveW '18年

Docker-Content-Digest部分应为小写字母(在docker engine v18.09.2上测试),即curl -v --silent -H "Accept: application/vnd.docker.distribution.manifest.v2+json" -X GET http://localhost:5000/v2/<name>/manifests/<tag> 2>&1 | grep docker-content-digest | awk '{print ($3)}'
Muhammad Abdurrahman

63

当前v2注册表现在支持通过删除DELETE /v2/<name>/manifests/<reference>

参见:https : //github.com/docker/distribution/blob/master/docs/spec/api.md#deleting-an-image

工作用法:https//github.com/byrnedo/docker-reg-tool

编辑:以上清单<reference>可以从请求中检索到

GET /v2/<name>/manifests/<tag>

并检查 Docker-Content-Digest响应中的标题。

编辑2:您可能必须使用以下环境集来运行注册表:

REGISTRY_STORAGE_DELETE_ENABLED="true"

Edit3:您可能必须运行垃圾回收以释放此磁盘空间:https ://docs.docker.com/registry/garbage-collection/


3
这里的其他一些答案显示了如何提取清单参考哈希。这是将标签转换成要传递给的内容所必需的DELETE
JamesThomasMoon1979 '17

出现不受支持的错误。我已经给出了适当的摘要值作为参考。
Winster

1
@ JamesThomasMoon1979更新了有关获取参考哈希的说明。
byrnedo

11
在V2上也出现错误{"errors":[{"code":"UNSUPPORTED","message":"The operation is unsupported."}]}
Gadelkareem,

1
检查编辑@Gadelkareem
byrnedo

17

问题1

您提到这是您的私有Docker注册表,因此您可能需要检查Registry API而不是Hub注册表API doc,这是您提供的链接。

问题2

docker Registry API是一种客户端/服务器协议,是否要删除后端中的映像取决于服务器的实现。(我猜)

DELETE /v1/repositories/(namespace)/(repository)/tags/(tag*)

详细说明

下面我将从您的描述中演示它的工作原理,以帮助您理解问题。

您运行您运行的私有Docker注册表,我使用默认的注册表并监听5000端口

docker run -d -p 5000:5000 registry

然后,我标记本地图像并将其推入。

$ docker tag ubuntu localhost:5000/ubuntu
$ docker push localhost:5000/ubuntu
The push refers to a repository [localhost:5000/ubuntu] (len: 1)
Sending image list
Pushing repository localhost:5000/ubuntu (1 tags)
511136ea3c5a: Image successfully pushed
d7ac5e4f1812: Image successfully pushed
2f4b4d6a4a06: Image successfully pushed
83ff768040a0: Image successfully pushed
6c37f792ddac: Image successfully pushed
e54ca5efa2e9: Image successfully pushed
Pushing tag for rev [e54ca5efa2e9] on {http://localhost:5000/v1/repositories/ubuntu/tags/latest}

之后,您可以使用注册表API来检查它是否存在于您的私有Docker注册表中

$ curl -X GET localhost:5000/v1/repositories/ubuntu/tags
{"latest": "e54ca5efa2e962582a223ca9810f7f1b62ea9b5c3975d14a5da79d3bf6020f37"}

现在,我可以使用该API删除标签了!

$ curl -X DELETE localhost:5000/v1/repositories/ubuntu/tags/latest
true

再次检查,该标签在您的私人注册表服务器中不存在

$ curl -X GET localhost:5000/v1/repositories/ubuntu/tags/latest
{"error": "Tag not found"}

2
请查看下面的答案,以解释为何此命令无济于事。
Konrad Kleine 2014年

2
您删除标签,但不删除图像数据。后者由删除标签后引用的脚本完成。我还使用注册表API删除标签。
康拉德·克莱恩

3
从协议的角度来看,它取决于每个注册表API服务器的实现,但是它不存在。
拉里·蔡

没错,这取决于实现方式,但是我正在/正在寻找一种从规范registry图像中删除图像数据的方法。即使不理想,也可以通过SSH登录并运行脚本。顺便提一句,我仍然认为这是一个不完整的API,您可以删除标签,但是如果执行GET操作,/images您仍然会看到所有剩余的图片数据。
2014年

感谢您提到注册表API,我们正在寻找一种在私有注册表中删除映像的方法。即使只是“取消标记”而实际上没有释放磁盘空间,这种方法对我来说也足够好。
霍华德·李

16

这确实很难看,但是它可以工作,文本在注册表2.5.1上进行了测试。即使在更新配置以启用删除之后,我也无法使删除工作顺利进行。该ID确实很难检索,必须登录才能获取,可能有些误解。无论如何,以下工作原理:

  1. 登录到容器

    docker exec -it registry sh
    
  2. 定义与您的容器和容器版本匹配的变量:

    export NAME="google/cadvisor"
    export VERSION="v0.24.1"
    
  3. 移至注册表目录:

    cd /var/lib/registry/docker/registry/v2
    
  4. 删除与您的哈希相关的文件:

    find . | grep `ls ./repositories/$NAME/_manifests/tags/$VERSION/index/sha256`| xargs rm -rf $1
    
  5. 删除清单:

    rm -rf ./repositories/$NAME/_manifests/tags/$VERSION
    
  6. 登出

    exit
    
  7. 运行GC:

    docker exec -it registry  bin/registry garbage-collect  /etc/docker/registry/config.yml
    
  8. 如果一切操作正确,则会显示一些有关已删除Blob的信息。


我遵循了上述相同的步骤,但是当我检查Docker注册表{container}磁盘使用情况时,它显示与执行此步骤之前相同的内容...知道为什么吗?
Savio Mathew

不起作用。易于检查。当您执行以下步骤并再次推送存储库时,它会显示“ 064794e955a6:图层已存在”
Oduvan

8

有一些客户端(使用Python,Ruby等)可以做到这一点。以我的口味,在注册表服务器上安装运行时(例如Python)是不可持续的,只是为了保存注册表!


所以deckschrubber我的解决方案是:

go get github.com/fraunhoferfokus/deckschrubber
$GOPATH/bin/deckschrubber

超过给定年龄的图像将被自动删除。年龄可以使用指定的-year-month-day,或它们的组合:

$GOPATH/bin/deckschrubber -month 2 -day 13 -registry http://registry:5000

更新这是关于deckschrubber 的简短介绍


1
deckschrubber非常好-非常易于安装(单个二进制文件),并允许您按名称(带有正则表达式匹配项)和使用期限删除图像。
RichVel

ERRO[0000] Could not delete image! repo=.... tag=latest:/
scythargon

@scythargon无法说出您的说明是否正确。
抖动

警告:在我的存储库上什么也没做。
理查德·沃伯顿

不幸的是,它不会删除没有标签的图像。因此,如果您一直将图像推送到单个标签上,它将仅检查被标记的图像,而不检查其他图像。
克里斯汀(Krystian)

1

简短地

1)您必须为docker repo的RepoDigests输入以下命令;

## docker inspect <registry-host>:<registry-port>/<image-name>:<tag>
> docker inspect 174.24.100.50:8448/example-image:latest


[
    {
        "Id": "sha256:16c5af74ed970b1671fe095e063e255e0160900a0e12e1f8a93d75afe2fb860c",
        "RepoTags": [
            "174.24.100.50:8448/example-image:latest",
            "example-image:latest"
        ],
        "RepoDigests": [
            "174.24.100.50:8448/example-image@sha256:5580b2110c65a1f2567eeacae18a3aec0a31d88d2504aa257a2fecf4f47695e6"
        ],
...
...

$ {digest} = sha256:5580b2110c65a1f2567eeacae18a3aec0a31d88d2504aa257a2fecf4f47695e6

2)使用注册表REST API

  ##curl -u username:password -vk -X DELETE registry-host>:<registry-port>/v2/<image-name>/manifests/${digest}


  >curl -u example-user:example-password -vk -X DELETE http://174.24.100.50:8448/v2/example-image/manifests/sha256:5580b2110c65a1f2567eeacae18a3aec0a31d88d2504aa257a2fecf4f47695e6

成功调用后,您应该获得202 Accepted。

3-)运行垃圾收集器

docker exec registry bin/registry garbage-collect --dry-run /etc/docker/registry/config.yml

注册表 —注册表容器名称。

有关更多详细说明,请在此处输入链接描述



0

Bash脚本下方删除注册表中除最新标签以外的所有标签。

for D in /registry-data/docker/registry/v2/repositories/*; do
if [ -d "${D}" ]; then
    if [ -z "$(ls -A ${D}/_manifests/tags/)" ]; then
        echo ''
    else
        for R in $(ls -t ${D}/_manifests/tags/ | tail -n +2); do
            digest=$(curl -k -I -s -H -X GET http://xx.xx.xx.xx:5000/v2/$(basename  ${D})/manifests/${R} -H 'accept: application/vnd.docker.distribution.manifest.v2+json'  | grep Docker-Content-Digest | awk '{print $2}' )
            url="http://xx.xx.xx.xx:5000/v2/$(basename  ${D})/manifests/$digest"
            url=${url%$'\r'}
            curl -X DELETE -k -I -s   $url -H 'accept: application/vnd.docker.distribution.manifest.v2+json' 
        done
    fi
fi
done

运行之后

docker exec $(docker ps | grep registry | awk '{print $1}') /bin/registry garbage-collect /etc/docker/registry/config.yml

您能否详细说明如何使用此精美脚本?
哈利勒·加尔包伊


0

这是一个基于Yavuz Sert答案的脚本。它将删除不是最新版本的所有标签,并且它们的标签大于950。

#!/usr/bin/env bash


CheckTag(){
    Name=$1
    Tag=$2

    Skip=0
    if [[ "${Tag}" == "latest" ]]; then
        Skip=1
    fi
    if [[ "${Tag}" -ge "950" ]]; then
        Skip=1
    fi
    if [[ "${Skip}" == "1" ]]; then
        echo "skip ${Name} ${Tag}"
    else
        echo "delete ${Name} ${Tag}"
        Sha=$(curl -v -s -H "Accept: application/vnd.docker.distribution.manifest.v2+json" -X GET http://127.0.0.1:5000/v2/${Name}/manifests/${Tag} 2>&1 | grep Docker-Content-Digest | awk '{print ($3)}')
        Sha="${Sha/$'\r'/}"
        curl -H "Accept: application/vnd.docker.distribution.manifest.v2+json" -X DELETE "http://127.0.0.1:5000/v2/${Name}/manifests/${Sha}"
    fi
}

ScanRepository(){
    Name=$1
    echo "Repository ${Name}"
    curl -s http://127.0.0.1:5000/v2/${Name}/tags/list | jq '.tags[]' |
    while IFS=$"\n" read -r line; do
        line="${line%\"}"
        line="${line#\"}"
        CheckTag $Name $line
    done
}


JqPath=$(which jq)
if [[ "x${JqPath}" == "x" ]]; then
  echo "Couldn't find jq executable."
  exit 2
fi

curl -s http://127.0.0.1:5000/v2/_catalog | jq '.repositories[]' |
while IFS=$"\n" read -r line; do
    line="${line%\"}"
    line="${line#\"}"
    ScanRepository $line
done
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.