Docker容器Registry私有镜像仓库安全配置与GC回收实践
作者:快盘下载 人气:[TOC]
0x00 前言简述
描述:本来我想直接写Harbor的docker镜像仓库搭建配置与使用,但是觉得还是应该从基础的Docker的Registry镜像讲起从安全构建到GC回收同时加深学习映像;
官网介绍:Registry是一个无状态的,高度可扩展的服务器端应用程序,存储,让你分发图像。它是开源的根据许可Apache许可证。
此时就不在详细讲解Registry的介绍了,有兴趣的朋友可以参见官网文档或者前面我笔记中的介绍; 以下是一些知识点复习:
(1) Registry是一个几种存放image并对外提供上传下载以及一系列API的服务,Registry与镜像的关系可以想象成自己机器上的源码和远端SVN或者Git服务的关系,可以很容易和本地源代码以及远端Git服务的关系相对应。(2) Registry开源常用于构建私有镜像仓库;Q:为什么不直接采用Docker官网Hub作为存储镜像的地方?
答:我认为主要是以下几个方面的影响 1.存储空间有限 2.上传/拉取速度有限 3.企业内部敏感开发项目(如果是您肯定不会上传到别人的服务器中) 4.免费开源
反之使用Registey好处:
镜像存储位置由您掌握全面管理控制自己的镜像镜像存储和分配紧密集成到您的内部开发流程Registry 版本说明:
Docker Registry 1.0版本(hub/docker.io等公共的镜像仓库还支持,安全性以及兼容性不如V2.0)Docker Registry 2.0版本在安全性和性能上做了诸多优化,并重新设计了镜像的存储的格式;(Docker目前1.6之后支持V2)名词解释:
1.repository name(存储库名词) 存储库指在库中存储的镜像。/project/redis:latest
经典存储库名称由2级路径构成,每级路径小于30个字符,V2的api不强制要求这样的格式。每级路径名至少有一个小写字母或者数字,使用句号,破折号和下划线分割。更严格来说,它必须符合正则表达式:[a-z0-9]+[._-][a-z0-9]+)多级路径用/分隔存储库名称总长度(包括/)不能超过256个字符2.digest(摘要) 摘要是镜像每个层的唯一标示。虽然算法允许使用任意算法,但是为了兼容性应该使用sha256。
例如 - sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b # 用一个简单的例子,在伪代码来演示摘要计算 let C = 'a small string' let B = sha256(C) let D = 'sha256:' + EncodeHex(B) let ID(C) = D # python伪代码 import hashlib C = 'a small string' B = hashlib.sha256(C) D = 'sha256:' + B.hexdigest()
0x01 基础安装
测试环境:
# OS CentOS Linux release 7.8.2003 (Core) # Linux Server Version: 19.03.9 Storage Driver: overlay2
1) 基础命令
基础实例:
# 1.Start your registry docker run -d -p 5000:5000 --restart=always --name registry registry:2 # 2.拉取镜像并修改 docker pull ubuntu docker run --name ubuntu-test -d ubuntu docker commit -a "Weiyigeek" -m "镜像描述" ubuntu-test ubuntu-custom:1.0 # 3.Tag the image so that it points to your registry docker image tag ubuntu-custom:1.0 127.0.0.1:5000/ubuntu-custom:1.0 # 4.从镜像仓储上传与下载镜像 docker push localhost:5000/ubuntu-custom:1.0 docker pull localhost:5000/ubuntu-custom:1.0 # 5.删除本地/远程的ubuntu-custom:1.0 docker image remove ubuntu-custom:1.0 docker image remove localhost:5000/ubuntu-custom:1.0 # 6.registry仓库删除与清除卷; docker container stop registry && docker container rm -v registry
2) 基础配置
Registry 镜像环境变量:
# Environment variable # 自定义Registry仓库镜像存放的物理地址 REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/var/lib/registry # 启用DELETE操作否则不能执行 -X DELETE REGISTRY_STORAGE_DELETE_ENABLED=true # 绑定Registry地址与端口 REGISTRY_HTTP_ADDR=0.0.0.0:5000 # 证书Certificate设置 REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt REGISTRY_HTTP_TLS_KEY=/certs/domain.key # 本地基础认证 REGISTRY_AUTH=htpasswd REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd
指定环境变量运行Registry仓库:
# (1) docker run 时指定环境变量 $ docker run -d --restart=always --name registry -v "$(pwd)"/auth:/auth -v /opt/docker/registry:/var/lib/registry -v "$(pwd)"/certs:/certs -e REGISTRY_HTTP_ADDR=0.0.0.0:443 -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key -e REGISTRY_STORAGE_DELETE_ENABLED=true -p 443:443 registry:2
# (2) Yaml 配置 docker run -d -p 5000:5000 --restart=always --name registry -v `pwd`/config.yml:/etc/docker/registry/config.yml registry:2 # 例如:开发配置的config.yaml version: 0.1 log: level: debug storage: filesystem: rootdirectory: /var/lib/registry http: addr: localhost:5000 host: https://registry.weiyigeek.top:5000 secret: asecretforlocaldevelopment debug: addr: localhost:5001 auth: htpasswd: realm: basic-realm path: /path/to/htpasswd
参考地址:https://github.com/docker/distribution/blob/master/cmd/registry/config-example.yml
# (3) docker-compose 构建镜像仓库 # docker-compose.yml registry: restart: always image: registry:2 ports: - 5000:5000 environment: REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt REGISTRY_HTTP_TLS_KEY: /certs/domain.key REGISTRY_AUTH: htpasswd REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm REGISTRY_STORAGE_DELETE_ENABLED=true volumes: - /opt/docker/registry:/var/lib/registry - /opt/docker/certs:/certs - /opt/docker/auth:/auth
3) 生产实例
描述:此处以实际生产环境为例进行Docker Registry私有仓库搭建;
3.1) 服务器拉取Docker Registry镜像并创建Autho认证的.htpasswd文件;
$ docker pull registry:2 # Digest: sha256:8be26f81ffea54106bae012c6f349df70f4d5e7e2ec01b143c46e2c03b9e551d # Status: Downloaded newer image for registry:2 # docker.io/library/registry:2 $ mkdir -vp /opt/registry/ && cd $_ htpasswd -Bbn weiyigeek 123456 # -n 不更新文件输入到终端标准输出 htpasswd -bB -c auth.htpasswd weiyigeek 123456 # -c 创建存储认证字符串,-b 强制加密密码,-B 在命令行中接收密码
Tips:在Push或者Delete镜像是通过HTTP请求Registry的API完成的,每个请求都需要一个Token才能完成操作,而此Token需要使用auth文件(明文用户/密码编码)来进行鉴权;
3.2) Docker Registry 自签证书 描述:如果Registry仓库使用TLS认证时必须带有证书,当外部访问该Registry仓库时候提供安全通道,我们可以在认证机构购买签名证书或者自签证书也可以; 使用 OpenSSL 来生成 CA (证书授权中心,certificate authority)、 中级 CA(intermediate CA) 和末端证书(end certificate)。包括 OCSP、CRL 和 CA颁发者Issuer信息、具体颁发和失效日期。
# 方式1:交互式证书生成 $openssl req -newkey rsa:4096 -nodes -sha256 -keyout ./certs/domain.key -x509 -days 365 -out ./certs/domain.crt # 方式2:配置文件方式生成 cat >ca.conf <<EOF [ req ] default_bits = 2048 distinguished_name = req_distinguished_name prompt = no encrypt_key = no x509_extensions = v3_ca [ req_distinguished_name ] CN = localhost [ CA_default ] copy_extensions = copy [ alternate_names ] DNS.2=localhost [ v3_ca ] subjectAltName=@alternate_names subjectKeyIdentifier=hash authorityKeyIdentifier=keyid:always,issuer:always basicConstraints = critical,CA:true keyUsage=keyCertSign,cRLSign,digitalSignature,keyEncipherment,nonRepudiation EOF openssl req -days 365 -x509 -config ca.conf -new -keyout certs/domain.key -out certs/domain.crt
3.3) 挂载安全认证与自签证书并启动registry容器
docker run -d -p 0.0.0.0:8443:5000 --name registry-net -v /var/lib/registry-net:/var/lib/registry -v /opt/registry/certs:/certs -v /opt/registry/auth.htpasswd:/etc/docker/registry/auth.htpasswd -e REGISTRY_AUTH="{htpasswd: {realm: localhost, path: /etc/docker/registry/auth.htpasswd}}" -e REGISTRY_HTTP_ADDR=0.0.0.0:5000 -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key -e REGISTRY_STORAGE_DELETE_ENABLED=true registry # 查看容器(只能本地访问) $docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 86bc983e8183 registry "/entrypoint.sh /etc…" About a minute ago Up About a minute 127.0.0.1:443->5000/tcp registry
3.4) 验证registry是否可登录,登录后账号密码将会base64存储在 /root/.docker/config.json 之中,为后续使用skopeo的时候做准备;
$docker login localhost -u weiyigeek -p 123456 # WARNING! Using --password via the CLI is insecure. Use --password-stdin. # WARNING! Your password will be stored unencrypted in /root/.docker/config.json. Login Succeeded cat /root/.docker/config.json { "auths": { "https://index.docker.io/v1/": { "auth": "d2VpxOQ=="}, "localhost": {"auth": "d2VpeWlnZWVrOjEyMzQ1Ng==" } }, "HttpHeaders": {"User-Agent": "Docker-Client/19.03.9 (linux)"} } # base64 编码 $echo "d2VpeWlnZWVrOjEyMzQ1Ng==" | base64 -d weiyigeek:123456[
3.5) 上传一个镜像到registry之中
$docker tag alpine localhost/alpine:latest $docker images localhost/alpine latest a24bb4013296 2 months ago 5.57MB $docker push localhost/alpine:latest # The push refers to repository [localhost/alpine] # 50644c29ef5a: Pushed # latest: digest: sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 size: 528
3.6)registry 查看上传的镜像
# (1) 查看registry中存储的镜像仓库名称 (注意--cacert参数如果证书未导入到系统中则必须加上) $curl --cacert /opt/registry/certs/domain.crt -u 'weiyigeek:123456' -X GET https://localhost/v2/_catalog {"repositories":["alpine"]}
3.7) 利用skopeo转储镜像到registry之中和操作镜像:
# (1) 首先信任CA证书,根据不同的发行版选择相应的路径和命令行即可,为后面skopeo命令使用做准备。 # CentOS update-ca-trust force-enable cp certs/domain.crt /etc/pki/ca-trust/source/anchors/localhost.crt update-ca-trust # Ubuntu cp certs/domain.crt /usr/local/share/ca-certificates/localhost.crt $ update-ca-certificates # Debian cp certs/domain.crt /usr/share/ca-certificates/localhost.crt echo localhost.crt >> /etc/ca-certificates.conf update-ca-certificates # (2) COPY 镜像到 registry skopeo inspect docker://docker.io/alpine # 以 localhost/library/alpine:3.10 为例 # localhost 就是该 registry 的域名或者 URL # library 就是项目名称 project # alpine:3.12 就是镜像名和镜像的 tag skopeo copy docker://alpine:3.12 docker://localhost/library/alpine:3.12 # Getting image source signatures # Copying blob df20fa9351a1 done # Copying config a24bb40132 done # Writing manifest to image destination # Storing signatures # (3) 删除registry仓库中的镜像(删除后并不彻底) skopeo delete docker://localhost/alpine # (4) 查看仓库里的镜像 curl --cacert /opt/registry/certs/domain.crt -u 'weiyigeek:123456' -X GET https://localhost/v2/_catalog {"repositories":["library/alpine"]} skopeo inspect docker://localhost/library/alpine:3.12 { "Name": "localhost/library/alpine", "Digest": "sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65", "RepoTags": ["3.12"], "Created": "2020-05-29T21:19:46.363518345Z", "DockerVersion": "18.09.7", "Labels": null, "Architecture": "amd64", "Os": "linux", "Layers": ["sha256:df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c"], "Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"] }
1.证书颁发者可以用中间证书提供给您。在这种情况下,你必须用中间证书串连您的证书形成的证书捆绑。
cat domain.crt intermediate-certificates.pem > certs/domain.crt
2.Let’s Encrypt:https://letsencrypt.org/how-it-works/
0x02 Registry 目录结构
描述: 此时以上传的library/alpine:3.12镜像为例查看registry目录中文件变化;
registry 仓库结构目录如下:
# registry 持久化位置 $ls /var/lib/registry/docker/registry/v2/ blobs repositories # registry 树形结构 tree -h /var/lib/registry/docker/registry/v2/ /var/lib/registry/docker/registry/v2/ ├── [ 20] blobs │ └── [ 36] sha256 │ ├── [ 78] a1 │ │ └── [ 18] a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 # 镜像data manifest (存储了data config与data layer 的 Digest摘要信息) │ │ └── [ 528] data │ ├── [ 78] a2 │ │ └── [ 18] a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e # 镜像data config │ │ └── [1.5K] data # "mediaType": "application/vnd.docker.container.image.v1+json", │ └── [ 78] df │ └── [ 18] df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c # 镜像data layer (通常体积最大) │ └── [2.7M] data # "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", └── [ 21] repositories └── [ 20] library └── [ 55] alpine ├── [ 20] _layers │ └── [ 150] sha256 │ ├── [ 18] a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e # 指向 > blobs/sha256/a24bb401......6b63d83e │ │ └── [ 71] link │ └── [ 18] df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c # 指向 > blobs/sha256/df20fa9.......9a752eb4c │ └── [ 71] link ├── [ 35] _manifests │ ├── [ 20] reVisions # 修订记录 │ │ └── [ 78] sha256 │ │ └── [ 18] a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 # 修订指向 manifest > blobs/sha256/a15790640......c90fd65 │ │ └── [ 71] link │ └── [ 18] tags │ └── [ 34] 3.12 │ ├── [ 18] current │ │ └── [ 71] link # 指向 blobs 中 data manifest digest a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 │ └── [ 20] index │ └── [ 78] sha256 │ └── [ 18] a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 │ └── [ 71] link # 同上 └── [ 6] _uploads # 镜像上传过程中的临时目录 26 directories, 8 files
Q: 那 registry 存储目录到底长什么样? ????
答: 结合下面这张图可以看见,registry 存储目录下只有两种文件名的文件即data与link文件 (1) link 文件: 是普通的文本文件存放在 repositories 目录下,其内容是指向 data 文件的 sha256 digest值, 从字面意义上就很好理解它; (2) data 文件: 存放在 blobs 目录下文件且分为三个文件(即镜像的layer/config/manifest等文件)
# (1) Repositories $ls /var/lib/registry/docker/registry/v2/repositories/alpine/ _layers _manifests _uploads # - _layers/sha256 目录下的文件夹名是镜像的Layer和Config的Digest,通该目录下的link文件找到对应 blobs 目录下的 data 文件,实际上当我们 pull 一个镜像的 layer 时,是通过 link 文件找到 layer 在 registry 中实际的存储位置的。 # - _manifests 文件夹下的 tags 和 revisions 目录下的 link 文件则指向该镜像的 manifest 文件,保存在所有历史镜像tag的manifest文件的link。当删除一个镜像时,只会删除该镜像最新的 tag 的 link 文件。 # - revisions 目录记录了镜像修订版本 $ls /var/lib/registry/docker/registry/v2/repositories/alpine(镜像名称)/_manifests/revisions/sha256/ a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 # - tags 目录下的文件夹名例如3.10就是镜像的Tag,在它的子目录下的 current/link 文件则记录了当前 tag 指向的 manifest 文件的位置; # 比如我们的 alpine:latest 镜像,每次 push 新的 latest 镜像时current/link 都会更新成指向最新镜像的 manifest 文件。 $ ls /var/lib/registry/docker/registry/v2/repositories/alpine(镜像名称)/_manifests/tags/3.12(镜像标记)/ current index # 目前都指向 a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 即 镜像 data manifest # (2) Blobs $ls /var/lib/registry/docker/registry/v2/blobs/sha256/ a1/ a2/ df/ $find /var/lib/registry/docker/registry/v2/ -name "data" -exec ls -sh {} ; # image layer 文件是 gzip 格式的 tar 包,是镜像层真实内容的 tar.gzip 格式存储形式。 2.7M ./blobs/sha256/df/df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c/data # image config 文件是 json 格式的,它是在构建时候生成的根据DockerFile和宿主机的一些信息; (记录了"rootfs"."diff_ids") 4.0K ./blobs/sha256/a2/a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e/data # image manifest 文件json 文件格式的,存放该镜像 layer 和 image config 文件的索引。(镜像拉取首先需拉取此文件) 4.0K ./blobs/sha256/a1/a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65/data
WeiyiGeek.registry存储结构
此时我们可以再往Registry中COPY一个镜像方便后面进行对比分析:
$skopeo copy docker://debian:buster-slim docker://localhost/library/debian:buster-slim $curl --cacert /opt/registry/certs/domain.crt -u 'weiyigeek:123456' -X GET https://localhost/v2/_catalog {"repositories":["library/alpine","library/debian"]} # 对比分析 registry 中 alpine:3.12 和 debian:buster-slim 两个基础镜像,此时在registry 存储目录的结构如下: $tree -h /var/lib/registry/docker/registry/v2/ /var/lib/registry/docker/registry/v2/ ├── [ 20] blobs │ └── [ 66] sha256 │ ├── [ 78] a1 │ │ └── [ 18] a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 │ │ └── [ 528] data │ ├── [ 78] a2 │ │ └── [ 18] a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e │ │ └── [1.5K] data │ ├── [ 78] bf │ │ └── [ 18] bf59529304463f62efa7179fa1a32718a611528cc4ce9f30c0d1bbc6724ec3fb # debian:buster-slim 的 Data Layer │ │ └── [ 26M] data │ ├── [ 78] c7 │ │ └── [ 18] c7346dd7f20ef06fd3c58446fab0c3edf22e78131d374775f5f947849537b773 # debian:buster-slim 的 Data Config │ │ └── [1.5K] data │ ├── [ 78] df │ │ └── [ 18] df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c │ │ └── [2.7M] data │ └── [ 78] e0 │ └── [ 18] e0a33348ac8cace6b4294885e6e0bb57ecdfe4b6e415f1a7f4c5da5fe3116e02 # debian:buster-slim 的 Data Manifest │ └── [ 529] data └── [ 21] repositories └── [ 34] library ├── [ 55] alpine # 此处不做过多说明 │ ├── [ 20] _layers │ │ └── [ 150] sha256 │ │ ├── [ 18] a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e │ │ │ └── [ 71] link │ │ └── [ 18] df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c │ │ └── [ 71] link │ ├── [ 35] _manifests │ │ ├── [ 20] revisions │ │ │ └── [ 78] sha256 │ │ │ └── [ 18] a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 │ │ │ └── [ 71] link │ │ └── [ 18] tags │ │ └── [ 34] 3.12 │ │ ├── [ 18] current │ │ │ └── [ 71] link │ │ └── [ 20] index │ │ └── [ 78] sha256 │ │ └── [ 18] a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 │ │ └── [ 71] link │ └── [ 6] _uploads └── [ 55] debian ├── [ 20] _layers │ └── [ 150] sha256 │ ├── [ 18] bf59529304463f62efa7179fa1a32718a611528cc4ce9f30c0d1bbc6724ec3fb │ │ └── [ 71] link # 摘要指向 blobs 中debian:buster-slim 的 Data Layer │ └── [ 18] c7346dd7f20ef06fd3c58446fab0c3edf22e78131d374775f5f947849537b773 │ └── [ 71] link # 摘要指向 blobs 中debian:buster-slim 的 Data Config ├── [ 35] _manifests │ ├── [ 20] revisions │ │ └── [ 78] sha256 │ │ └── [ 18] e0a33348ac8cace6b4294885e6e0bb57ecdfe4b6e415f1a7f4c5da5fe3116e02 │ │ └── [ 71] link # 摘要指向 blobs 中 debian:buster-slim 的 Data Manifest │ └── [ 25] tags │ └── [ 34] buster-slim # 以下摘要都是指向 blobs 中 debian:buster-slim 的 Data Manifest │ ├── [ 18] current │ │ └── [ 71] link │ └── [ 20] index │ └── [ 78] sha256 │ └── [ 18] e0a33348ac8cace6b4294885e6e0bb57ecdfe4b6e415f1a7f4c5da5fe3116e02 │ └── [ 71] link └── [ 6] _uploads 48 directories, 16 files
然后我们再采用skopeo删除我们上传到Registry仓库中的镜像,再进行目录的对比:
$skopeo delete docker://localhost/library/alpine:3.12 --debug # 列出变化的目录结构部分 repositories └── library ├── alpine │ ├── _layers │ │ └── sha256 │ │ ├── a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e │ │ │ └── link │ │ └── df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c │ │ └── link │ ├── _manifests │ │ ├── revisions │ │ │ └── sha256 │ │ │ └── a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 │ │ └── tags # tags 下面的目录与文件被删除 │ └── _uploads
总结上述:
1.上述可以看见当skopeo delete删除一个镜像时,只是对_manifests下的文件revisions/sha256/a15790640a6690...ka9f2b0d7cc90fd65/link与tags即其子目录文件进行删除;实际上两者删除的是同一个内容,即对记录了该镜像 manifests 文件 digest摘要 的 link 文件。
2.运行后从–debug参数中得到DEBU[0000] DELETE https://localhost/v2/library/alpine/manifests/sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65可以得出通过 registry API 来 DELETE 一个镜像实质上是删除 repositories 元数据文件夹下的 tag 名文件夹和该 tag 的 revisions 下的 link 文件。
# 我们也可以采用Registry API 进行操作达到同样的效率 我们定义摘要字符串匹配以下语法: digest := algorithm ":" hex algorithm := /[A-Fa-f0-9_+.-]+/ hex := /[A-Fa-f0-9]+/ # digest = sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b # (1) 获取到镜像 Docker-Content-Digest: $curl -I -u 'weiyigeek:123456' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X GET https://localhost/v2/library/alpine/manifests/3.12 # HTTP/1.1 200 OK # Content-Length: 528 # Content-Type: application/vnd.docker.distribution.manifest.v2+json # Docker-Content-Digest: sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 # Docker-Distribution-Api-Version: registry/2.0 # Etag: "sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65" # X-Content-Type-Options: nosniff # Date: Fri, 21 Aug 2020 02:30:06 GMT # (2) 删除registry仓库中的指定Docker-Content-Digest的镜像tags 与 links 文件 $curl -u 'weiyigeek:123456' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X DELETE https://localhost/v2/library/alpine/manifests/sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65
可以看出删除仓库中的镜像,只是把在registry仓库中镜像的tags子目录与revisions/sha256/…/link文件进行删除,而blobs中的镜像 data 与 repositories 中镜像目录library/alpine/并未被删除,这样导致的后果是registry仓库的服务器将会占用一部分存储资源导致资源的浪费,那如何解决这个问题就是我们下面提到的Registry GC 机制;
0x03 Registry API
描述:可以通过registry API操作管理镜像或者获取镜像manifest相关信息; 官方参考地址: https://docs.docker.com/registry/spec/api/
API 一览
描述:通过API遇到的错误代码如下表所示: https://docs.docker.com/registry/spec/api/#errors-2
API方法和URI列表涵盖如下表:
Method | Path | Entity | Description |
---|---|---|---|
GET | /v2/ | Base | Check that the endpoint implements Docker Registry API V2. |
GET | /v2/_catalog /v2/_catalog?n=<integer> | Catalog | 检索注册中心中可用的存储库的排序json列表 |
GET | /v2/<name>/tags/list | Tags | 获取存储库下由“name”标识的标记。 |
GET | /v2/manifests/ | Manifest | 获取由“name”和“reference”标识的清单,其中“reference”可以是标记或摘要。还可以向这个端点发出一个’ HEAD ‘请求,在不接收所有数据的情况下获取资源信息。 |
PUT | /v2/manifests/ | Manifest | 把由“name”和“reference”标识的清单放在“reference”可以是标签或摘要的地方。 |
DELETE | /v2/manifests/ | Manifest | 删除由“name”和“reference”标识的清单。注意,一个清单只能被“摘要”删除。 |
GET | /v2/blobs/ | Blob | 从由“摘要”标识的注册表中检索blob。还可以向这个端点发出一个’ HEAD ‘请求,在不接收所有数据的情况下获取资源信息。 |
DELETE | /v2/blobs/ | Blob | 删除由“name”和“digest”标识的blob |
POST | /v2/blobs/uploads/ | Initiate Blob Upload | 如果成功,将提供一个上传位置来完成上传。可选地,如果“digest”参数存在,请求主体将用于在单个请求中完成上传。 |
GET | /v2/blobs/uploads/ | Blob Upload | 此端点的主要目的是解决可恢复上传的当前状态利用uuid。 |
PATCH | /v2/blobs/uploads/ | Blob Upload | 上传指定上传的数据块。 |
PUT | /v2/blobs/uploads/ | Blob Upload | 完成’ uuid ‘指定的上传,可选附加主体作为最后块 |
DELETE | /v2/blobs/uploads/ | Blob Upload | 取消未完成的上传进程,释放相关资源。如果没有调用此操作,未完成的上传最终将超时。 |
(Important)结合registry仓库解释镜像PULL与PUSH过程:
(1) PULL 镜像: 镜像由一个json清单和层叠文件组成,pull镜像的过程就是检索这两个组件的过程。
- 第一步就是获取清单,清单由下面几个字段组成: registry:5000/v2/redis/manifests/latest(获取redis:latest清单文件) # 字段 描述 # name 镜像名称 # tag 镜像当前版本的tag # fsLayers 层描述列表(包括摘要) # signature 一个JWS签名,用来验证清单内容 - 第二步当获取清单之后,客户端需要验证前面(signature),以确保名称和fsLayers层是有效的。确认后客户端可以使用digest去下载各个fs层。在V2api中层存储在blobs中已digest作为键值. 1.首先拉取镜像清单(pulling an Image Manifest) $ HEAD /v2/<image/manifests/<reference>#检查镜像清单是否存在 $ GET /v2/<image>/manifests/<reference>#拉取镜像清单 提示:reference可是是tag或者是digest 2.开始拉取每个层(pulling a Layer) $ GET /v2/<image>/blobs/<digest> 提示:digest是镜像每个fsLayer层的唯一标识,存在于清单的fsLayers里面。
(2) PUSH 镜像: 推送镜像和拉取镜像过程相反,先推各个层到registry仓库,然后上传清单.(Pushing a Layer(上传层)分为2步)
# 2.1) 使用post请求在registry仓库启动上传服务,返回一个url这个url用来上传数据和检查状态。 # 首先Existing Layers(检查层是否存在),若返回200 OK 则表示存在,不用上传 $ HEAD /v2/image/blobs/<digest> # 开始上传服务(Starting An Upload),如果post请求返回202 accepted,一个url会在location字段返回. $ POST /v2/image/blobs/uploads/ # 202 Accepted # Location: /v2/<image>/blobs/uploads/<uuid> # Range: bytes=0-<offset> # Content-Length: 0 # Docker-Upload-UUID: <uuid> # 可以用来查看上传状态和实现断点续传 # 2.2) 开始上传层(Uploging the Layer) > PUT /v2/<name>/blobs/uploads/<uuid>?digest=<digest> Content-Length: <size of layer>> Content-Type: application/octet-stream # 上传进度(Upload Progress) $ GET /v2/<image>/blobs/uploads/<uuid> # 204 No Content # Location: /v2/<name>/blobs/uploads/<uuid> # Range: bytes=0-<offset> # Docker-Upload-UUID: <uuid> # 重点-整块上传(Monolithic Upload) > PUT /v2/<name>/blobs/uploads/<uuid>?digest=<digest> > Content-Length: <size of layer> > Content-Type: application/octet-stream <Layer Binary Data> # 重点-分块上传(Chunked Upload) > PATCH /v2/<name>/blobs/uploads/<uuid> > Content-Length: <size of chunk> > Content-Range: <start of range>-<end of range> > Content-Type: application/octet-stream <Layer Chunk Binary Data> # 如果服务器不接受这个块,则返回: # 416 Requested Range Not Satisfiable # Location: /v2/<name>/blobs/uploads/<uuid> # Range: 0-<last valid range> # Content-Length: 0 # Docker-Upload-UUID: <uuid> # 成功则返回: # 202 Accepted # Location: /v2/<name>/blobs/uploads/<uuid> # Range: bytes=0-<offset> # Content-Length: 0 # Docker-Upload-UUID: <uuid> # 重点-交叉上传(Cross Repository Blob Mount)可以把客户端有访问权限的已有存储库中的层挂载到当前存储库中 POST /v2/<name>/blobs/uploads/?mount=<digest>&from=<repository name> Content-Length: 0 # 成功返回: # 201 Created # Location: /v2/<name>/blobs/<digest> # Content-Length: 0 # Docker-Content-Digest: <digest> # 失败返回: # 202 Accepted # Location: /v2/<name>/blobs/uploads/<uuid> # Range: bytes=0-<offset> # Content-Length: 0 # Docker-Upload-UUID: <uuid> # 3.3) 上传完成(Completed Upload),但是注意分块上传在最后一块上传完毕后,需要提交一个上传完成的请求 > PUT /v2/<name>/blob/uploads/<uuid>?digest=<digest> > Content-Length: <size of chunk> > Content-Range: <start of range>-<end of range> > Content-Type: application/octet-stream <Last Layer Chunk Binary Data> # 成功返回: # 201 Created # Location: /v2/<name>/blobs/<digest> # Content-Length: 0 # Docker-Content-Digest: <digest>
实际示例
Tips: 后续不再加--cacert /opt/registry/certs/domain.crt参数,默认大家都已经把证书导入带系统本地;
0.Registry V2协议及其认证请求验证
# Registry 仓库协议 $curl -I -u 'weiyigeek:123456' -X GET https://localhost/v2/ HTTP/1.1 200 OK # 采用一个RFC7235兼容的授权头进行认证 $curl -I -H 'Authorization: Basic d2VpeWlnZWVrOjEyMzQ1Ng==' -X GET https://localhost/v2/ HTTP/1.1 200 OK
1.查看Registry仓库中有那些镜像(不精确-当通过delete删除镜像时候此处并未删除需要手动到repositories文件夹中删除)
# Registry 仓库中所有镜像 curl --cacert /opt/registry/certs/domain.crt -u 'weiyigeek:123456' -X GET https://localhost/v2/_catalog {"repositories":["library/alpine","library/debian"]} # 返回仓库中指定条目的镜像(通过-v 参数可看出last的不同) curl --cacert /opt/registry/certs/domain.crt -u 'weiyigeek:123456' -X GET "https://localhost/v2/_catalog?n=1&last=a"
2.获取某个镜像的标签列表 (注意加或者未加Project的区别)
curl -u 'weiyigeek:123456' -X GET https://localhost/v2/alpine/tags/list # {"name":"alpine","tags":["latest"]} curl -u 'weiyigeek:123456' -X GET https://localhost/v2/library/alpine/tags/list # 'library/alpine' # {"name":"library/alpine","tags":["3.12"]} # 列出镜像部分tags(Pagination) curl -u 'weiyigeek:123456' -X GET https://localhost/v2/library/alpine/tags/list?n=<integer>
3.拉取Registry 仓库镜像中Manifests(清单)文件
# 判断指定镜像与tags的Manifests(清单)是否存在 curl -I -u 'weiyigeek:123456' -X HEAD https://localhost/v2/library/alpine/manifests/3.12 # HTTP/1.1 200 OK # Content-Length: 2783 # Content-Type: application/vnd.docker.distribution.manifest.v1+prettyjws # 仓库中`Manifests`清单 $ curl -u 'weiyigeek:123456' -X GET https://localhost/v2/library/alpine/manifests/3.12 # { # "schemaVersion": 1, # "name": "library/alpine", # "tag": "3.12", # "architecture": "amd64", # "fsLayers": [{ # "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"},{ # "blobSum": "sha256:df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c" # } # ], # "history": [ # { # "v1Compatibility": "{"architecture":"amd64","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh"],"ArgsEscaped":true,"Image":"sha256:64771e4514cb653a0fe68e1ceed5bd16640ebf3bd859dc3333efe87dc4709a5d","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"ce1874fa1fc1eb128516899352f185645f492c443b5a80d9a3fae8b09d1b6b16","container_config":{"Hostname":"ce1874fa1fc1","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD ["/bin/sh"]"],"ArgsEscaped":true,"Image":"sha256:64771e4514cb653a0fe68e1ceed5bd16640ebf3bd859dc3333efe87dc4709a5d","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"created":"2020-05-29T21:19:46.363518345Z","docker_version":"18.09.7","id":"4a28aef4f8a9e13b1df98eaf8e651db2857161cea27134dad697ad0c7a7de12d","os":"linux","parent":"a5213fa3ad8fa7a42f88213945845ef49dcf11328d51576b8f076142ce75bdf8","throwaway":true}" # }, # { # "v1Compatibility": "{"id":"a5213fa3ad8fa7a42f88213945845ef49dcf11328d51576b8f076142ce75bdf8","created":"2020-05-29T21:19:46.192045972Z","container_config":{"Cmd":["/bin/sh -c #(nop) ADD file:c92c248239f8c7b9b3c067650954815f391b7bcb09023f984972c082ace2a8d0 in / "]}}" # } # ], # "signatures": [ # { # "header": { # "jwk": { # "crv": "P-256", # "kid": "P6TV:UOU3:V564:FNEL:DQG2:WQX5:6Z5P:NQF6:XZOR:JTMI:Q2QI:AQZ3", # "kty": "EC", # "x": "n70C5idlCOFB4ubdg5K6MCvRBIH6d5YzhTRumV1i6D8", # "y": "OmZn6AyifVg3kZ67ICPViHTHBXvMui8fPwqXzbTnWw0" # }, # "alg": "ES256" # }, # "signature": "S4Tvfqx0nA7hULgyKdKKdoYpgMsTqxlbQ6JDeQv1HXZie1zMCsafNZfLI59kivzHb7IV8hwEnvxehL0cKuoZ4w", # "protected": "eyJmb3JtYXRMZW5ndGgiOjIxMzYsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAyMC0wOC0yMlQwNzoyNDozM1oifQ" # } # ] # }
4.获取仓库镜像的manifests内容 (go-hello:scratch)
curl -v -u 'weiyigeek:123456' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X GET https://localhost/v2/go-hello/manifests/scratch # { # "schemaVersion": 2, # "mediaType": "application/vnd.docker.distribution.manifest.v2+json", # "config": { # "mediaType": "application/vnd.docker.container.image.v1+json", # "size": 1472, # "digest": "sha256:cb05b87d001253772ae9a212200de5eb8304ab9691c61589332a2f57e7059209" # }, # "layers": [ # { # "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", # "size": 1106793, # "digest": "sha256:5395625ce01dee311e2f7c879b0b148ac7525de7aad5080a518d7f7e5a99d368" # } # ] # }
5.获取(镜像:版本)标识的data manifests的 digest
curl -I --cacert /opt/registry/certs/domain.crt -u 'weiyigeek:123456' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X GET https://localhost/v2/go-hello/manifests/scratch # HTTP/1.1 200 OK # Content-Length: 528 # Content-Type: application/vnd.docker.distribution.manifest.v2+json # 注意Docker-Content-Digest中的内容: 在registry2.3或更高版本删除清单时,必须在HEAD或GET获取清单以获取要删除的正确digest携带以下头; # Docker-Content-Digest: sha256:8dabce532312b587329fe225ef501051c60f81ffdb2c801a5da6348b9cab132e # Docker-Distribution-Api-Version: registry/2.0 # Etag: "sha256:8dabce532312b587329fe225ef501051c60f81ffdb2c801a5da6348b9cab132e" # X-Content-Type-Options: nosniff # Date: Fri, 21 Aug 2020 02:30:06 GMT # 【简约版本:直接提取Docker-Content-Digest头内容】 curl -Is -u 'weiyigeek:123456' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X GET https://localhost/v2/library/alpine/manifests/3.12 | grep "Docker-Content-Digest:" | cut -f 2 -d " " sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 # 【如不指定Accept则默认为 application/vnd.docker.distribution.manifest.v1+prettyjws 】 curl -I -u 'weiyigeek:123456' -X GET https://localhost/v2/library/alpine/manifests/3.12 HTTP/1.1 200 OK # Content-Length: 2783 # Content-Type: application/vnd.docker.distribution.manifest.v1+prettyjws # Docker-Content-Digest: sha256:cae82a43ba96214acd380f3d4ed043445f56f80f0fc99f3f927d5e6eaee40791 # Docker-Distribution-Api-Version: registry/2.0 # Etag: "sha256:cae82a43ba96214acd380f3d4ed043445f56f80f0fc99f3f927d5e6eaee40791" # X-Content-Type-Options: nosniff # Date: Sat, 22 Aug 2020 03:01:16 GMT
6.删除仓库中的镜像即删除(repositories下面的 _manifests 中的Tags 与 revisions 下的link)
# 加入 -v 参数 查看请求返回流程 $curl -v --cacert /opt/registry/certs/domain.crt -u 'weiyigeek:123456' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X DELETE https://localhost/v2/go-hello/manifests/sha256:8dabce532312b587329fe225ef501051c60f81ffdb2c801a5da6348b9cab132e # 202 Accepted # Content-Length: None # 失败 返回404错误
注意:默认情况下,registry不允许删除镜像操作,需要在启动registry时指定环境变量REGISTRY_STORAGE_DELETE_ENABLED=true,或者修改其配置文件即可。reference必须是digest,否则删除将失败。在registry2.3或更高版本删除清单时,必须在HEAD或GET获取清单以获取要删除的正确digest携带以下头: Accept: application/vnd.docker.distribution.manifest.v2+json
7.拉取镜像,由于层被存储在注册表中的blobs中所以是需要通过一个标准的HTTP请求来进行拉取一个层的信息
# (1) 先查看镜像 data 相关的 Digest 码 curl -s -u 'weiyigeek:123456' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X GET https://localhost/v2/library/alpine/manifests/3.12 | jq '"Data Config - " + .config.digest','"Data Layer - " + .layers[0].digest' # "Data Config - sha256:a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e" # "Data Layer - sha256:df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c" # (2) 获取拉取镜像的data config 与 data layer 文件 curl -u 'weiyigeek:123456' -X GET https://localhost/v2/library/alpine/blobs/sha256:a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e | jq curl -u 'weiyigeek:123456' -X GET https://localhost/v2/library/alpine/blobs/sha256:df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c -o /tmp/alpine-3.12.tar.gz [root@k8s tmp]$ ls -lh alpine-3.12.tar.gz -rw-r--r--. 1 root root 2.7M 8月 22 16:27 alpine-3.12.tar.gz [root@k8s tmp]$ tar -zxvf alpine-3.12.tar.gz # 实际上该压缩文件中存放的是rootfs文件系统 # bin/ # bin/arch # bin/ash # bin/base64 # bin/bbconfig
8.镜像通过Registry API上传到仓库中
# 所有层上传使用两个步骤来管理上传过程。 * 第一步开始在注册表中的服务上传,返回一个URL来进行第二步。 * 第二步使用上载URL传递的实际数据。上传都开始返回,可用于将数据推和检查上传状态URL的POST请求。 # Location头将用于每个请求后进行通信的上载位置。虽然它不会在本技术规格改变,客户应使用API返回的最新值。 # (1) 开始上载一个POST请求 # POST /v2/<name>/blobs/uploads/ curl -I -u 'weiyigeek:123456' -X POST https://localhost/v2/test-images/blobs/uploads/ # 如果POST请求是成功的它将返回 202响应 将与Location头上传的URL返回: # HTTP/1.1 202 Accepted # Content-Length: 0 # Docker-Distribution-Api-Version: registry/2.0 # Docker-Upload-Uuid: 9efa5ca7-d009-4532-88de-695c1f945e59 # 必须额 # Location: https://localhost/v2/test-images/blobs/uploads/9efa5ca7-d009-4532-88de-695c1f945e59?_state=XqlcahxfSzts1a43SgEs_MQ9-GAczgadg-Ra3vayoh57Ik5hbWUiOiJ0ZXN0LWltYWdlcyIsIlVVSUQiOiI5ZWZhNWNhNy1kMDA5LTQ1MzItODhkZS02OTVjMWY5NDVlNTkiLCJPZmZzZXQiOjAsIlN0YXJ0ZWRBdCI6IjIwMjAtMDgtMjJUMDg6NDA6NDguMDkwNTk0NzQ4WiJ9 # 指定了位置标头 # Range: 0-0 # X-Content-Type-Options: nosniff # Date: Sat, 22 Aug 2020 08:40:48 GMT # (2) 通过HEAD请求到BLOB存储API进行检查镜像相关层是否存在(可用返回200 OK) # HEAD /v2/<name>/blobs/<digest> curl -I -u 'weiyigeek:123456' -X HEAD https://localhost/v2/test-images/blobs/sha256:a14....jk5 # (3) 上传进度查看此时需要第一步中的Docker-Upload-Uuid之进行请求 # GET /v2/<name>/blobs/uploads/<uuid> curl -I -u 'weiyigeek:123456' -X GET https://localhost/v2/test-images/blobs/uploads/9efa5ca7-d009-4532-88de-695c1f945e59 # (4) Monolithic Upload 简单的单块上传,并可以通过想避免分块的复杂性的 # PUT /v2/<name>/blobs/uploads/<uuid>?digest=<digest> # Content-Length: <size of layer> # Content-Type: application/octet-stream # <Layer Binary Data> # (5) Chunked Upload 进行组块的上载,该客户机可以指定一个范围报头和仅包括层文件的一部分: # PATCH /v2/<name>/blobs/uploads/<uuid> # Content-Length: <size of chunk> # Content-Range: <start of range>-<end of range> # Content-Type: application/octet-stream # <Layer Chunk Binary Data> # (6) 跨存储库Blob挂载,可以从客户机具有读访问权的另一个存储库挂载blob,从而不需要将已知的blob上传到注册中心,要发出一个blob挂载而不是一个upload, POST请求应该以以下格式发出(成功将返回202 Accepted): POST /v2/<name>/blobs/uploads/?mount=<digest>&from=<repository name> Content-Length: 0 # (7) Completed Upload 当镜像上传完毕必须进行以下请求否则仓库不认为镜像个层全部上传,当接收到最后一个块和层已被验证时候将返回201 Create 并且返回该镜像的Docker-Content-Digest值; # PUT /v2/<name>/blobs/uploads/<uuid>?digest=<digest> # Content-Length: <size of chunk> # Content-Range: <start of range>-<end of range> # Content-Type: application/octet-stream # <Last Layer Chunk Binary Data> # (8) 取消上传镜像到仓库,可通过发出DELETE请求到registry之中; # DELETE /v2/<name>/blobs/uploads/<uuid> # (9) 删除层(Deleting a Layer) # DELETE /v2/<image>/blobs/<digest> # 成功返回: # 202 Accepted # Content-Length: None # (10) 上传镜像清单(Pushing an Image Manifest),我们上传完镜像层之后,就开始上传镜像清单 # PUT /v2/<name>/manifests/<reference> # Content-Type: <manifest media type> # { # "name": <name>, # "tag": <tag>, # "fsLayers": [ # { # "blobSum": <digest> # }, # ... # ] # ], # "history": <v1 images>, # "signature": <JWS>, # ... # } # 如果清单中有层("blobSum":<digest>)是未知的,则返回 # { "errors:" [{ "code": "BLOB_UNKNOWN", "message": "blob unknown to registry", "detail": { "digest": <digest> # } # }, # ... # ] # } #
删除镜像Manifests与镜像层信息: ```bash curl -Is -u 'weiyigeek:123456' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X GET https://localhost/v2/library/alpine/manifests/3.12 | grep "Docker-Content-Digest:" | cut -f 2 -d " " sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 curl -s -u 'weiyigeek:123456' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X GET https://localhost/v2/library/alpine/manifests/3.12 | grep "digest" "digest": "sha256:a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e" "digest": "sha256:df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c" # 删除 镜像 _Manifests Tags curl -v --cacert /opt/registry/certs/domain.crt -u 'weiyigeek:123456' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X DELETE https://localhost/v2/library/alpine/manifests/sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 # 删除 镜像 _Layer curl -v -u 'weiyigeek:123456' -X DELETE https://localhost/v2/library/alpine/blobs/sha256:a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e curl -v -u 'weiyigeek:123456' -X DELETE https://localhost/v2/library/alpine/blobs/sha256:df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c curl -v -u 'weiyigeek:123456' -X DELETE "https://localhost/v2/library/alpine/blobs/sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" curl -v -u 'weiyigeek:123456' -X DELETE https://localhost/v2/library/alpine/blobs/sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 # 清空后效果 repositories | library │ ├── alpine │ │ ├── _layers │ │ │ └── sha256 │ │ │ ├── a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e │ │ │ ├── a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4 │ │ │ └── df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c │ │ ├── _manifests │ │ │ ├── revisions │ │ │ │ └── sha256 │ │ │ │ └── a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 │ │ │ └── tags │ │ └── _uploads
简单粗暴清空 Registry 仓库:
$tree /var/lib/registry/docker/registry/v2/blobs/sha256/ /var/lib/registry/docker/registry/v2/blobs/sha256/ ├── 0e ├── 32 ├── 3f ├── 53 ├── 59 ├── 8d ├── a1 ├── a2 ├── b7 ├── b9 ├── cb ├── df └── e7 rm -rf /var/lib/registry/docker/registry/v2/blobs/sha256/* rm -rf /var/lib/registry/docker/registry/v2/repositories/*
加载全部内容