1 |
相信各位同学在使用的过程中,会发现随着Docker镜像的增多,占用磁盘空间也约来越多。这时我们需要清理私有镜像仓库中不需要的镜像。但在实际操作时,才会发现这本以为很简单的任务中却暗藏玄机,遇到了不少的麻烦。在这里我们分享一下清理镜像仓库时遇到的坑点。想要直接看实践操作解决方案的同学可以直接往下翻。 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
清理镜像仓库时走过的坑 1.官方提供的接口并不能真正的删除镜像 这着实是最大的坑点。很多同学查资料发现,官方已经提供了删除镜像仓库的API,所以可能相当然的以为直接使用就好,殊不知掉入了官方埋下的最大的坑点,也是本文要着手解决的核心问题:官方提供的删除镜像仓库中镜像的接口,仅仅是把manifest删除了,真正的镜像文件还存在!官方并没有提供删除镜像层的接口!这也就是说,当我们调用删除镜像的接口之后,仅仅是查看镜像的列表时看不到原镜像了,然而原有镜像仍然在磁盘中,占用着宝贵的文件存储空间。 2.直接调用官方的删除镜像API,会返回405的错误码 意味着方法不被允许。实际上,官方可能是处于安全性的考虑,在默认的情况下禁止了直接删除镜像的功能。若要开启删除镜像功能,需要修改镜像仓库的配置文件。具体操作为修改文件: /etc/docker/registry/config.yml(登入registry容器修改) 在storage下添加delete的许可之后,重启镜像仓库服务。 3.使用官方提供的garbage-collect工具,会有无用的文件残留 官方为registry提供了garbage-collect(gc)工具清理镜像的物理存储,将没有引用的layer删除。 gc的清理过程分为两部分: mark:扫描所有的manifest,列出引用的layer sweep:扫描所有的layer,不在mark里的layer将被清理删除。 gc可以在dry-run的模式下运行(添加参数-d),只输出gc信息,不进行实际操作。我们可以通过这种方式来确认哪些镜像会被清除。 使用gc工具清理镜像的一个问题就是文件清理得不够干净,无法清理已经没有tag的镜像目录,并且还残存少部分文件,从十KB到几十KB不等。久而久之,垃圾文件和目录的数量会越来越多。 4.garbage-collect不是事务操作,清理镜像时可能会产生误操作 gc不是事务操作,当gc过程中刚好有push操作时,则可能会误删数据。一个可行的解决办法是手动更改镜像仓库的配置,暂时禁止镜像的push操作。 在镜像仓库的配置文件中可以配置read-only模式。当启用read-only之后,再push镜像时会得到405的错误。gc完成后取消read-only模式,再push镜像即可。 5.使用garbage-collect工具后,必须重启镜像仓库才能正常使用 如果不重启镜像仓库,则再次push该镜像时可能会得到layer already exists错误,其可能的原因是镜像被删除后,仓库的缓存中还存有已经删除的镜像信息,所以再次push会报层存在的错误。 |
实操一( 使用官方API + GC ):
使用官方提供的方法可以较为简便的清理镜像仓库。整个清理过程可能需要几百毫秒到几秒的时间。此操作有一定的危险性,因此清理镜像不宜过于频繁。官方在git上也有类似描述。点击查看
1.准备,修改仓库配置文件,一般在容器内的
1 |
/etc/docker/registry/config.yml |
在storage下添加delete的许可之后,重启镜像仓库。
1 2 |
[root@localhost ~]# docker exec -it registry sh / # vi /etc/docker/registry/config.yml |
保存后退出容器,退出容器后重启registry
1 2 3 |
docker restart registry 用docker方式启动的镜像仓库也可以添加环境变量: REGISTRY_STORAGE_DELETE_ENABLED=true |
2、获取待删镜像的digest
获取镜像digest的API为如下几个:
注意:仓库地址一定要是https, 并且参数要带 -k 1.13以前的docker不用如此
1 2 3 4 5 6 7 8 9 10 11 12 |
#查询镜像 curl <仓库地址>/v2/_catalog -k #查询镜像tag(版本) curl <仓库地址>/v2/<镜像名>/tags/list -k #删除镜像API curl -I -X DELETE "<仓库地址>/v2/<镜像名>/manifests/<镜像digest_hash>" -k #获取镜像digest_hash curl <仓库地址>/v2/<镜像名>/manifests/<tag> -k \ --header "Accept: application/vnd.docker.distribution.manifest.v2+json" |
注:因为我的私有仓库设置有认证,所以做以上操作要加账号和密码
本地测试
1 2 |
#获取所有镜像名 curl -s 192.168.6.220:5000/v2/_catalog -ucar:123456 -k |
获取镜像digest_hash
1 2 3 4 5 |
name是仓库名,reference是标签,此时需要注意,调用时需要加上header内容: Accept: application/vnd.docker.distribution.manifest.v2+json.其中Docker-Content-Digest的值就是镜像的digest curl --header "Accept: application/vnd.docker.distribution.manifest.v2+json" -Is 192.168.6.220:5000/v2/fms-c-agent/manifests/1.0.1 -u car:car123 -k |
1 |
curl --header "Accept: application/vnd.docker.distribution.manifest.v2+json" -Is 192.168.6.220:5000/v2/fms-c-agent/manifests/1.0.1 -u car:car123 -k |awk '/Digest/ {print $NF}' |
3.调用官方的HTTP API V2删除镜像
删除镜像的API为:
1 |
curl -I -X DELETE "<仓库地址>/v2/<镜像名>/manifests/<镜像digest_hash>" -k |
1 |
curl -I -X DELETE "192.168.6.220:5000/v2/fms-c-agent/manifests/sha256:efa4a9661c87671f56e610f3cf3a464f07f7cfddae44d0df522d7fe6425e1ea6" -u user:pwd -k |
4.调用GC清理镜像文件
1 2 |
使用gc工具的方式为: docker exec registry /bin/registry garbage-collect /etc/docker/registry/config.yml |
执行后
注意: gc清理需要时间,如果在gc过程中刚好有push操作,可能会产生未知的问题,建议设置read-only模式之后再进行gc,然后再改回来。
5、重启docker registry
注意,如果不重启会导致push相同镜像时产生layer already exists错误。
1 |
docker restart registry |
下面是我个人用的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
#!/bin/bash #cnetos7,docker-ce v17.12.0,registry v2.6.2 #Docker registry 私有仓库镜像查询、删除、上传、下载 #Author Elven <elven89@qq.com> #Blog http://www.cnblogs.com/elvi/p/8384675.html #root [[ $UID -ne 0 ]] && { echo "Run in root user !";exit; } #need jq ,get json data [[ -f /usr/bin/jq ]] || { echo 'install jq';yum install -y jq &>/dev/null; } #参数 variable #registry容器名称,默认registry #RN=${RN:-registr} #访问网址,默认localhost:5000 HUB=${HUB:-localhost:5000} HUB=192.168.6.220:5000 #检测 check function Check_hub() { [[ `curl -s $HUB/v2/_catalog -u car:car123 -k` == "Failed connect" ]] && { echo -e "\033[31m$HUB 访问失败\033[0m";exit; } } #查询images function Select_img() { IMG=$(curl -s $HUB/v2/_catalog -u car:car123 -k |jq .repositories |awk -F'"' '{for(i=1;i<=NF;i+=2)$i=""}{print $0}') [[ $IMG = "" ]] && { echo -e "\033[31m$HUB 没有docker镜像\033[0m";exit; } #echo "$HUB Docker镜像:" for n in $IMG; do TAG=$(curl -s http://$HUB/v2/$n/tags/list -u car:car123 -k |jq .tags |awk -F'"' '{for(i=1;i<=NF;i+=2)$i=""}{print $0}') for t in $TAG; do echo "$n:$t"; done done } #删除images function Delete_img() { for n in $IMGS; do IMG=${n%%:*} TAG=${n##*:} i=1 [[ "$IMG" == "$TAG" ]] && { TAG=latest; n="$n:latest"; } Digest=`curl --header "Accept: application/vnd.docker.distribution.manifest.v2+json" -Is ${HUB}/v2/${IMG}/manifests/${TAG} -u car:car123 -k |awk '/Digest/ {print $NF}'` [[ -z "$Digest" ]] && { echo -e "\033[31m$IMG:$TAG 镜像不存在\033[0m";} || { URL="${HUB}/v2/${IMG}/manifests/${Digest}" Rs=$(curl -Is -X DELETE ${URL%?} -u car:car123 -k | awk '/HTTP/ {print $2}') [[ $Rs -eq 202 ]] && { let i++;echo "$n 删除成功"; } || { echo -e "\033[31m$n 删除失败\033[0m"; } } done #registry垃圾回收 RN=registry [[ "$i" -gt 1 ]] && { echo "Clean...";docker exec ${RN} /bin/registry garbage-collect /etc/docker/registry/config.yml &>/dev/null;docker restart ${RN} &>/dev/null; } } #删除镜像所在目录(清除所有 -dd .* ) function Delete_img_a() { [[ -f /usr/bin/docker ]] || echo 'No docker !' [[ -z $(docker ps |awk '/'1+$RN'/ {print $NF}') ]] && { echo "$RN容器不存在!";exit; } for n in $IMGS; do CONTAINERID=$(docker ps |awk '{print $1"\t"$NF}' | grep "${n}" | cut -f 1) IMAGE=$(docker ps |awk '{print $2"\t"$NF}' | grep "$n" | cut -f 1 | awk -F '[:]' '{print $1":"$2}') TAGS=$(docker ps |awk '{print $2"\t"$NF}' | grep "$n" | cut -f 1 | awk -F '[:]' '{print $NF}') if [ ! -z $CONTAINERID ];then docker rm -f $CONTAINERID &> /dev/null if [ -z $(docker ps |awk '/'1+$RN'/ {print $1"\t"$NF}' | grep "$n" | cut -f 1) ];then IMAGEID=$(docker images | awk '/'$RN'/ {print $1"\t"$2"\t"$3}' | grep "${IMAGE}" | grep "${TAGS}" | cut -f 3) docker rmi $IMAGEID &> /dev/null fi if [ -z $(docker images | awk '/'1+$RN'/ {print $1"\t"$2"\t"$3}' | grep "${IMAGE}" | grep "${TAGS}" | cut -f 3) ];then start_script=$(cat /opt/.local/start | grep "${IMAGE}:${TAGS}") [[ -z $start_script ]] && { echo "未找到启动脚本!";exit; } echo ${start_script} fi else echo "未找到:容器${n}" fi done } function clear_all(){ IMG=$(curl -s $HUB/v2/_catalog -u car:car123 -k |jq .repositories |awk -F'"' '{for(i=1;i<=NF;i+=2)$i=""}{print $0}') [[ $IMG = "" ]] && { echo -e "\033[31m$HUB 没有docker镜像\033[0m";exit; } for n in $IMG; do TAG=$(curl -s http://$HUB/v2/$n/tags/list -u car:car123 -k |jq .tags |awk -F'"' '{for(i=1;i<=NF;i+=2)$i=""}{print $0}') for t in $TAG; do echo "$n:$t"; digest=$(curl -s http://$HUB/v2/$n/manifests/$t -u car:car123 -k --header "Accept: application/vnd.docker.distribution.manifest.v2+json" |awk '/digest/ {print $NF}'| sed '1,3d' | cut -d '"' -f 2) for d in ${digest}; do status=$(curl -I -X DELETE -o /dev/null -s -w %{http_code} "http://${HUB}/v2/${n}/manifests/${d}" -u car:car123 -k) if [ $status -eq 202 ];then echo "${n}的层镜像${d}删除成功" else echo "${n}的层镜像${d}删除失败!" fi done done done } case "$1" in "-h") echo echo "#默认查询images" echo "sh $0 -h #帮助 -d #删除 -dd #清理空间" echo "-pull img1 img2 #下载 -push #上传" echo echo "#示例:删除 nginx:1.1 nginx:1.2 (镜像名:版本)" echo "sh $0 -d nginx:1.1 nginx:1.2 " echo "sh $0 -dd nginx #删除nginx所有版本" echo echo "#定义仓库url地址192.168.66.220:5000(默认 localhost:5000)" echo "env HUB=192.168.66.220:5000 /bin/sh $0 -d nginx:1.1 " echo ;; "-d") Check_hub IMGS=${*/-dd/} IMGS=${IMGS/-d/} Delete_img ;; "-dd") Check_hub IMGS=${*/-dd/} IMGS=${IMGS/-d/} Delete_img_a ;; "-all") clear_all ;; "-pull") IMGS=${*/-pull/} Pull ;; "-push") IMGS=${*/-push/} Push ;; *) Check_hub Select_img ;; esac |
保存脚本赋予权限,直接执行查出所有image镜像名称
1 2 |
然后将查出的名称,作为参数传入(支持多个) ./test -d 镜像1 镜像2 镜像3 ... |
- 本文固定链接: https://www.yoyoask.com/?p=2843
- 转载请注明: shooter 于 SHOOTER 发表