[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。
[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。
1 2 3 4 5 6 7 8 9 10 11 12 例如 - sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b let C = 'a small string' let B = sha256(C)let D = 'sha256:' + EncodeHex(B)let ID(C) = Dimport hashlib C = 'a small string' B = hashlib.sha256(C) D = 'sha256:' + B.hexdigest()
0x01 基础安装 测试环境:1 2 3 4 5 6 CentOS Linux release 7.8.2003 (Core) Server Version: 19.03.9 Storage Driver: overlay2
1) 基础命令 基础实例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 docker run -d -p 5000:5000 --restart=always --name registry registry:2 docker pull ubuntu docker run --name ubuntu-test -d ubuntu docker commit -a "Weiyigeek" -m "镜像描述" ubuntu-test ubuntu-custom:1.0 docker image tag ubuntu-custom:1.0 127.0.0.1:5000/ubuntu-custom:1.0 docker push localhost:5000/ubuntu-custom:1.0 docker pull localhost:5000/ubuntu-custom:1.0 docker image remove ubuntu-custom:1.0 docker image remove localhost:5000/ubuntu-custom:1.0 docker container stop registry && docker container rm -v registry
2) 基础配置 Registry 镜像环境变量:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/var/lib/registry REGISTRY_STORAGE_DELETE_ENABLED=true REGISTRY_HTTP_ADDR=0.0.0.0:5000 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 2 3 4 5 6 7 8 9 10 11 12 13 $ 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 docker run -d -p 5000:5000 --restart=always --name registry \ -v `pwd `/config.yml:/etc/docker/registry/config.yml \ registry:2 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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文件;1 2 3 4 5 6 7 8 $ docker pull registry:2 $ mkdir -vp /opt/registry/ && cd $_ htpasswd -Bbn weiyigeek 123456 htpasswd -bB -c auth.htpasswd weiyigeek 123456
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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 $openssl req -newkey rsa:4096 -nodes -sha256 -keyout ./certs/domain.key -x509 -days 365 -out ./certs/domain.crtcat >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容器1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 psCONTAINER 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的时候做准备;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $docker login localhost -u weiyigeek -p 123456Login 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)" } } $echo "d2VpeWlnZWVrOjEyMzQ1Ng==" | base64 -dweiyigeek:123456[
3.5) 上传一个镜像到registry之中1 2 3 4 5 6 7 $docker tag alpine localhost/alpine:latest$docker imageslocalhost/alpine latest a24bb4013296 2 months ago 5.57MB $docker push localhost/alpine:latest
3.6)registry 查看上传的镜像1 2 3 $curl --cacert /opt/registry/certs/domain.crt -u 'weiyigeek:123456' -X GET https://localhost/v2/_catalog{"repositories" :["alpine" ]}
3.7) 利用skopeo转储镜像到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 update-ca-trust force-enable cp certs/domain.crt /etc/pki/ca-trust/source /anchors/localhost.crt update-ca-trust cp certs/domain.crt /usr/local /share/ca-certificates/localhost.crt $ update-ca-certificates cp certs/domain.crt /usr/share/ca-certificates/localhost.crt echo localhost.crt >> /etc/ca-certificates.confupdate-ca-certificates skopeo inspect docker://docker.io/alpine skopeo copy docker://alpine:3.12 docker://localhost/library/alpine:3.12 skopeo delete docker://localhost/alpine 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" ] }
注意事项:
0x02 Registry 目录结构 描述: 此时以上传的library/alpine:3.12镜像为例查看registry目录中文件变化;
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 $ls /var/lib/registry/docker/registry/v2/blobs repositories tree -h /var/lib/registry/docker/registry/v2/ /var/lib/registry/docker/registry/v2/ ├── [ 20] blobs │ └── [ 36] sha256 │ ├── [ 78] a1 │ │ └── [ 18] a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 │ │ └── [ 528] data │ ├── [ 78] a2 │ │ └── [ 18] a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e │ │ └── [1.5K] data │ └── [ 78] df │ └── [ 18] df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c │ └── [2.7M] data └── [ 21] repositories └── [ 20] 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 26 directories, 8 files
Q: 那 registry 存储目录到底长什么样? 🤔
答: 结合下面这张图可以看见,registry 存储目录下只有两种文件名的文件即data与link文件
(1) link 文件: 是普通的文本文件存放在 repositories 目录下,其内容是指向 data 文件的 sha256 digest值, 从字面意义上就很好理解它; (2) data 文件: 存放在 blobs 目录下文件且分为三个文件(即镜像的layer/config/manifest
等文件)
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 $ls /var/lib/registry/docker/registry/v2/repositories/alpine/_layers _manifests _uploads $ls /var/lib/registry/docker/registry/v2/repositories/alpine(镜像名称)/_manifests/revisions/sha256/ a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 $ ls /var/lib/registry/docker/registry/v2/repositories/alpine(镜像名称)/_manifests/tags/3.12(镜像标记)/ current index $ls /var/lib/registry/docker/registry/v2/blobs/sha256/a1/ a2/ df/ $find /var/lib/registry/docker/registry/v2/ -name "data" -exec ls -sh {} \;2.7M ./blobs/sha256/df/df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c/data 4.0K ./blobs/sha256/a2/a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e/data 4.0K ./blobs/sha256/a1/a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65/data
weiyigeek.top-registry存储结构
此时我们可以再往Registry中COPY一个镜像方便后面进行对比分析: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 $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" ]} $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 │ │ └── [ 26M] data │ ├── [ 78] c7 │ │ └── [ 18] c7346dd7f20ef06fd3c58446fab0c3edf22e78131d374775f5f947849537b773 │ │ └── [1.5K] data │ ├── [ 78] df │ │ └── [ 18] df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c │ │ └── [2.7M] data │ └── [ 78] e0 │ └── [ 18] e0a33348ac8cace6b4294885e6e0bb57ecdfe4b6e415f1a7f4c5da5fe3116e02 │ └── [ 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 │ └── [ 18] c7346dd7f20ef06fd3c58446fab0c3edf22e78131d374775f5f947849537b773 │ └── [ 71] link ├── [ 35] _manifests │ ├── [ 20] revisions │ │ └── [ 78] sha256 │ │ └── [ 18] e0a33348ac8cace6b4294885e6e0bb57ecdfe4b6e415f1a7f4c5da5fe3116e02 │ │ └── [ 71] link │ └── [ 25] tags │ └── [ 34] buster-slim │ ├── [ 18] current │ │ └── [ 71] link │ └── [ 20] index │ └── [ 78] sha256 │ └── [ 18] e0a33348ac8cace6b4294885e6e0bb57ecdfe4b6e415f1a7f4c5da5fe3116e02 │ └── [ 71] link └── [ 6] _uploads 48 directories, 16 files
然后我们再采用skopeo删除我们上传到Registry仓库中的镜像,再进行目录的对比:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $skopeo delete docker://localhost/library/alpine:3.12 --debugrepositories └── library ├── alpine │ ├── _layers │ │ └── sha256 │ │ ├── a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e │ │ │ └── link │ │ └── df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c │ │ └── link │ ├── _manifests │ │ ├── revisions │ │ │ └── sha256 │ │ │ └── a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 │ │ └── 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 文件。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 我们定义摘要字符串匹配以下语法: digest := algorithm ":" hex algorithm := /[A-Fa-f0-9_+.-]+/ hex := /[A-Fa-f0-9]+/ $curl -I -u 'weiyigeek:123456' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X GET https://localhost/v2/library/alpine/manifests/3.12$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镜像的过程就是检索这两个组件的过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - 第一步就是获取清单,清单由下面几个字段组成: registry:5000/v2/redis/manifests/latest(获取redis:latest清单文件) - 第二步当获取清单之后,客户端需要验证前面(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步)
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 $ HEAD /v2/image/blobs/<digest> $ POST /v2/image/blobs/uploads/ > PUT /v2/<name>/blobs/uploads/<uuid>?digest=<digest> Content-Length: <size of layer>> Content-Type: application/octet-stream $ GET /v2/<image>/blobs/uploads/<uuid> > PUT /v2/<name>/blobs/uploads/<uuid>?digest=<digest> > Content-Length: <size of layer> > Content-Type: application/octet-stream <Layer Binary Data> > 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> POST /v2/<name>/blobs/uploads/?mount=<digest>&from=<repository name> Content-Length: 0 > 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>
实际示例 Tips: 后续不再加--cacert /opt/registry/certs/domain.crt
参数,默认大家都已经把证书导入带系统本地;
2.获取某个镜像的标签列表 (注意加或者未加Project的区别)
1 2 3 4 5 6 curl -u 'weiyigeek:123456' -X GET https://localhost/v2/alpine/tags/list curl -u 'weiyigeek:123456' -X GET https://localhost/v2/library/alpine/tags/list curl -u 'weiyigeek:123456' -X GET https://localhost/v2/library/alpine/tags/list?n=<integer >
3.拉取Registry 仓库镜像中Manifests(清单)文件
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 curl -I -u 'weiyigeek:123456' -X HEAD https://localhost/v2/library/alpine/manifests/3.12 $ curl -u 'weiyigeek:123456' -X GET https://localhost/v2/library/alpine/manifests/3.12
4.获取仓库镜像的manifests内容 (go-hello:scratch)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 curl -v -u 'weiyigeek:123456' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X GET https://localhost/v2/go-hello/manifests/scratch
5.获取(镜像:版本)标识的data manifests的 digest1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 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 -I -u 'weiyigeek:123456' -X GET https://localhost/v2/library/alpine/manifests/3.12 HTTP/1.1 200 OK
6.删除仓库中的镜像即删除(repositories下面的 _manifests 中的Tags 与 revisions 下的link)1 2 3 4 5 $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
注意:默认情况下,registry不允许删除镜像操作,需要在启动registry时指定环境变量REGISTRY_STORAGE_DELETE_ENABLED=true
,或者修改其配置文件即可。reference必须是digest,否则删除将失败。在registry2.3或更高版本删除清单时,必须在HEAD或GET获取清单以获取要删除的正确digest携带以下头: Accept: application/vnd.docker.distribution.manifest.v2+json
简单粗暴清空 Registry 仓库:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $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/*
0x04 Registry GC 描述:在上一章节中我们阐述了为什么要引入Registry GC机制,再说其目的用处前我们先对其进行一个简单的介绍;
Q: 什么是Registry GC ?
答 :GC英文全称Garbage collection
就是垃圾回收的意思,从 docker 官方文档关于GC偷来的 example 来解释一下吧。 官网文档:https://docs.docker.com/registry/garbage-collection/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - A - | | a - b - c | | ---- B ---- - A - | | a - b - c - A - | | a - b
此处可以借鉴registry GC 的源码文件 garbagecollect.go 可以看到 GC 的主要分两个阶段:
(1) marking 阶段:根据上文我们提到的 link 文件,通过扫描所有镜像 tags 目录下的 link 文件就可以得到这些镜像的 manifest,在 manifest 中保存在该镜像所有的 layer 和 config 文件的 digest 值,把这些值标记为不能清除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 markSet := make (map [digest.Digest]struct {}) manifestArr := make ([]ManifestDel, 0 ) err := repositoryEnumerator.Enumerate(ctx, func (repoName string ) error { emit(repoName) var err error named, err := reference.WithName(repoName) if err != nil { return fmt.Errorf("failed to parse repo name %s: %v" , repoName, err) } repository, err := registry.Repository(ctx, named) if err != nil { return fmt.Errorf("failed to construct repository: %v" , err) } manifestService, err := repository.Manifests(ctx) if err != nil { return fmt.Errorf("failed to construct manifest service: %v" , err) } manifestEnumerator, ok := manifestService.(distribution.ManifestEnumerator) if !ok { return fmt.Errorf("unable to convert ManifestService into ManifestEnumerator" ) }
(2) sweep 阶段:删除操作当marking完成之后没有标记blobs(layer 和 config)就会被清理掉;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 vacuum := NewVacuum(ctx, storageDriver) if !opts.DryRun { for _, obj := range manifestArr { err = vacuum.RemoveManifest(obj.Name, obj.Digest, obj.Tags) if err != nil { return fmt.Errorf("failed to delete manifest %s: %v" , obj.Digest, err) } } } blobService := registry.Blobs() deleteSet := make (map [digest.Digest]struct {}) err = blobService.Enumerate(ctx, func (dgst digest.Digest) error { if _, ok := markSet[dgst]; !ok { deleteSet[dgst] = struct {}{} } return nil })
weiyigeek.top-marking and sweep
Q:那 GC 都干了啥?
答: 我们可以利用registry容器中的registry garbage-collect
命令进行GC回收操作;
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 $ docker exec -it registry sh -c "/bin/registry garbage-collect -m --delete-untagged=true /etc/docker/registry/config.yml" / version: 0.1 log : fields: service: registry storage: cache: blobdescriptor: inmemory filesystem: rootdirectory: /var/lib/registry http: addr: :5000 headers: X-Content-Type-Options: [nosniff] health: storagedriver: enabled: true interval: 10s threshold: 3 library/alpine library/debian library/debian: marking manifest sha256:e0a33348ac8cace6b4294885e6e0bb57ecdfe4b6e415f1a7f4c5da5fe3116e02 library/debian: marking blob sha256:c7346dd7f20ef06fd3c58446fab0c3edf22e78131d374775f5f947849537b773 library/debian: marking blob sha256:bf59529304463f62efa7179fa1a32718a611528cc4ce9f30c0d1bbc6724ec3fb 3 blobs marked, 3 blobs and 0 manifests eligible for deletion blob eligible for deletion: sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/a1/a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 go.version=go1.11.2 instance.id=fe470a94-4f51-4764-93e5-0f1d5d6172bf service=registry blob eligible for deletion: sha256:a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/a2/a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e go.version=go1.11.2 instance.id=fe470a94-4f51-4764-93e5-0f1d5d6172bf service=registry blob eligible for deletion: sha256:df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/df/df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c go.version=go1.11.2 instance.id=fe470a94-4f51-4764-93e5-0f1d5d6172bf service=registry
此时经过GC之后的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 $ tree /var/lib/registry/docker/registry/v2 /var/lib/registry/docker/registry/v2 ├── blobs │ └── sha256 │ ├── a1 │ ├── a2 │ ├── bf │ │ └── bf59529304463f62efa7179fa1a32718a611528cc4ce9f30c0d1bbc6724ec3fb │ │ └── data │ ├── c7 │ │ └── c7346dd7f20ef06fd3c58446fab0c3edf22e78131d374775f5f947849537b773 │ │ └── data │ ├── df │ └── e0 │ └── e0a33348ac8cace6b4294885e6e0bb57ecdfe4b6e415f1a7f4c5da5fe3116e02 │ └── data └── repositories └── library ├── alpine │ ├── _layers │ │ └── sha256 │ │ ├── a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e │ │ │ └── link │ │ └── df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c │ │ └── link │ ├── _manifests │ │ ├── revisions │ │ │ └── sha256 │ │ │ └── a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 │ │ └── tags │ └── _uploads └── debian ├── _layers │ └── sha256 │ ├── bf59529304463f62efa7179fa1a32718a611528cc4ce9f30c0d1bbc6724ec3fb │ │ └── link │ └── c7346dd7f20ef06fd3c58446fab0c3edf22e78131d374775f5f947849537b773 │ └── link ├── _manifests │ ├── revisions │ │ └── sha256 │ │ └── e0a33348ac8cace6b4294885e6e0bb57ecdfe4b6e415f1a7f4c5da5fe3116e02 │ │ └── link │ └── tags │ └── buster-slim │ ├── current │ │ └── link │ └── index │ └── sha256 │ └── e0a33348ac8cace6b4294885e6e0bb57ecdfe4b6e415f1a7f4c5da5fe3116e02 │ └── link └── _uploads 40 directories, 10 files
Shell 脚本 示例1:查看Docker官方镜像仓库中镜像的所有标签
方式1:1 2 3 4 5 6 #!/bin/sh image_name=$1 repo_url=https://registry.hub.docker.com/v1/repositories curl -s ${repo_url} /${image_name} /tags | jq | grep name | awk '{print $2}' | sed -e 's/"//g'
方式2:一条命令搞定1 skopeo inspect docker://docker.io/alpine | jq ".RepoTags"
示例2.registry信息查看脚本与RegistryGC回收脚本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 #!/bin/bash set -xPARM=$1 IMAGE_NAME=${2} ACTION=${PARM:="NONE"} REGISTRY_URL="https://localhost/v2" REGISTRY_NAME="registry" REGISTRY_HOME="/var/lib/registry/docker/registry/v2" MANIFESTS_DIGEST="" AUTH="Authorization: Basic d2VpeWlnZWVrOjEyMzQ1Ng==" function Usage (){ echo -e "\e[32mUsage: $0 {view} \e[0m" echo -e "\e[32m $0 {tags} <image-name> \e[0m" echo -e "\e[32m $0 {gc} <registry-container-name|container-id> \e[0m" echo -e "\e[32m $0 {delete} <image-name> <reference> \e[0m" echo -e "\e[32m #查看仓库中的镜像信息并从仓库中删除指定镜像,然后进行垃圾回收 \e[0m" exit ; } function ViewRegistry (){ curl -s -H "${AUTH} " "${REGISTRY_URL} /_catalog" | jq ".repositories" } function ViewTags (){ local FLAG=0 local IMAGE_NAME=$1 curl -s -H "${AUTH} " "${REGISTRY_URL} /_catalog" | jq ".repositories" > registry.repo sed -i "s#\[##g;s#]##g;s# ##g;s#\"##g;s#,##g;/^\s*$/d" registry.repo for i in $(cat registry.repo) do if [[ "$i " == "${IMAGE_NAME} " ]];then FLAG=1 break fi done if [[ $FLAG -eq 1 ]];then curl -s -H "${AUTH} " "${REGISTRY_URL} /${IMAGE_NAME} /tags/list" | jq ".tags" else echo -e "\e[31m[ERROR]: Registry 不存在 ${IMAGE_NAME} 该镜像\e[0m" exit fi } function GcRegistry (){ docker exec -it $1 sh -c "/bin/registry garbage-collect -m --delete-untagged=true /etc/docker/registry/config.yml" if [[ $? -ne 0 ]];then echo -e "\e[31m[ERROR]:GC Failed! \e[0m" exit fi for i in $(find ${REGISTRY_HOME} /blobs/sha256/ | grep -v "data" );do if [[ $(ls -A $i |wc -c) -eq 0 ]];then echo -e "[info]delete empty directory : ${i} " rm -rf ${i} fi done echo -e "[+ Registry restart ....]" docker restart $1 } function Del () { local IMAGE_NAME=$1 local TAGS=$2 if [[ "$TAGS " != "" ]];then curl -s -H "${AUTH} " -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' "${REGISTRY_URL} /${IMAGE_NAME} /manifests/${TAGS} " > images.mainfests err_flag=$(grep -c '"errors"' images.mainfests) if [[ $err_flag -ne 0 ]];then echo -e "\e[31m[ERROR]:$(cat images.mainfests) \e[0m" exit fi MANIFESTS_DIGEST=$(curl -s -H "${AUTH} " -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' "${REGISTRY_URL} /${IMAGE_NAME} /manifests/${TAGS} " | grep "Docker-Content-Digest:" | cut -f 2 -d " " ) grep "digest" images.mainfests | sed 's# ##g;s#"##g;s#digest:##g' > images.digest echo ${MANIFESTS_DIGEST} >> images.digest curl -v -H "${AUTH} " -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X DELETE "${REGISTRY_URL} /${IMAGE_NAME} /manifests/${MANIFESTS_DIGEST} " for digest in $(cat images.digest);do curl -v -H "${AUTH} " -X DELETE "${REGISTRY_URL} /${IMAGE_NAME} /blobs/${digest} " done fi GcRegistry ${REGISTRY_NAME} $flag_tags =$(curl -s -H "${AUTH} " "${REGISTRY_URL} /${IMAGE_NAME} /tags/list" | jq ".tags" ) if [[ -z $flag_tags ]];then rm -rf "${REGISTRY_HOME} /repositories/${IMAGE_NAME} " fi for i in $(find ${REGISTRY_HOME} /repositories/${IMAGE_NAME} /_layers/sha256/ | grep -v "link" );do if [[ $(ls -A $i |wc -c) -eq 0 ]];then echo -e "[info]delete empty directory : ${i} " rm -rf ${i} fi done for i in $(find ${REGISTRY_HOME} /repositories/${IMAGE_NAME} /_manifests/revisions/sha256/ | grep -v "link" );do if [[ $(ls -A $i |wc -c) -eq 0 ]];then echo -e "[info]delete empty directory : ${i} " rm -rf ${i} fi done } if [[ "$ACTION " = "NONE" ]];then Usage elif [[ "$ACTION " = "view" ]];then ViewRegistry elif [[ "$ACTION " = "tags" ]];then ViewTags $2 elif [[ "$ACTION " = "delete" ]];then Del $2 $3 elif [[ "$ACTION " = "gc" ]];then GcRegistry $2 else Usage fi EOF
章节总结:
(1) 在GC之后registry存储目录我们可以看到,原本blobs目录下有6个data文件现在已经变成3个,而alpine:3.12镜像相关的Layer、Config、Manifest
文件已经被GC清理掉了; 但是在 repositories 目录下,该镜像的 _layers 下的 link 文件依旧存在🤔。
(2) 总结以上,用下面这三张图片就能直观地理解这些过程啦
2.1 delete 镜像之前的 registry 存储目录结构
weiyigeek.top-1
2.2 delete 镜像之后的 registry 存储目录结构
weiyigeek.top-2
2.3 GC 之后的 registry 存储目录结构
weiyigeek.top-
(3) GC 之后一定要重启,因为 registry 容器缓存了镜像 layer 的信息当删除掉一个镜像 A 后边 GC 掉该镜像的 layer 之后,如果不重启 registry 容器,当重新 PUSH 镜像 A 的时候就会提示镜像 layer 已经存在,不会重新上传 layer 但实际上已经被 GC 掉了,最终会导致镜像 A 不完整无法 pull 到该镜像
。
(4) GC 不是事务性操作,所以在进行 GC 的时候最好暂停 PUSH 镜像,以免把正在上传的镜像 layer 给 GC 掉。
0x05 配置文件解析 config.yaml 文件一览 Registry 仓库的 config.yaml 文件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 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 version: 0.1 log: accesslog: disabled: true level: debug formatter: text fields: service: registry environment: staging hooks: - type: mail disabled: true levels: - panic options: smtp: addr: mail.example.com:25 username: mailuser password: password insecure: true from: sender@example.com to: - errors@example.com loglevel: debug storage: filesystem: rootdirectory: /var/lib/registry maxthreads: 100 azure: accountname: accountname accountkey: base64encodedaccountkey container: containername gcs: bucket: bucketname keyfile: /path/to/keyfile credentials: type: service_account project_id: project_id_string private_key_id: private_key_id_string private_key: private_key_string client_email: client@example.com client_id: client_id_string auth_uri: http://example.com/auth_uri token_uri: http://example.com/token_uri auth_provider_x509_cert_url: http://example.com/provider_cert_url client_x509_cert_url: http://example.com/client_cert_url rootdirectory: /gcs/object/name/prefix chunksize: 5242880 s3: accesskey: awsaccesskey secretkey: awssecretkey region: us-west-1 regionendpoint: http://myobjects.local bucket: bucketname encrypt: true keyid: mykeyid secure: true v4auth: true chunksize: 5242880 multipartcopychunksize: 33554432 multipartcopymaxconcurrency: 100 multipartcopythresholdsize: 33554432 rootdirectory: /s3/object/name/prefix swift: username: username password: password authurl: https://storage.myprovider.com/auth/v1.0 or https://storage.myprovider.com/v2.0 or https://storage.myprovider.com/v3/auth tenant: tenantname tenantid: tenantid domain: domain name for Openstack Identity v3 API domainid: domain id for Openstack Identity v3 API insecureskipverify: true region: fr container: containername rootdirectory: /swift/object/name/prefix oss: accesskeyid: accesskeyid accesskeysecret: accesskeysecret region: OSS region name endpoint: optional endpoints internal: optional internal endpoint bucket: OSS bucket encrypt: optional data encryption setting secure: optional ssl setting chunksize: optional size valye rootdirectory: optional root directory inmemory: delete: enabled: false redirect: disable: false cache: blobdescriptor: redis maintenance: uploadpurging: enabled: true age: 168 h interval: 24 h dryrun: false readonly: enabled: false auth: silly: realm: silly-realm service: silly-service token: autoredirect: true realm: token-realm service: token-service issuer: registry-token-issuer rootcertbundle: /root/certs/bundle htpasswd: realm: basic-realm path: /path/to/htpasswd middleware: registry: - name: ARegistryMiddleware options: foo: bar repository: - name: ARepositoryMiddleware options: foo: bar storage: - name: cloudfront options: baseurl: https://my.cloudfronted.domain.com/ privatekey: /path/to/pem keypairid: cloudfrontkeypairid duration: 3000 s ipfilteredby: awsregion awsregion: us-east-1, use-east-2 updatefrenquency: 12 h iprangesurl: https://ip-ranges.amazonaws.com/ip-ranges.json storage: - name: redirect options: baseurl: https://example.com/ reporting: bugsnag: apikey: bugsnagapikey releasestage: bugsnagreleasestage endpoint: bugsnagendpoint newrelic: licensekey: newreliclicensekey name: newrelicname verbose: true http: addr: localhost:5000 prefix: /my/nested/registry/ host: https://myregistryaddress.org:5000 secret: asecretforlocaldevelopment relativeurls: false draintimeout: 60 s tls: certificate: /path/to/x509/public key: /path/to/x509/private clientcas: - /path/to/ca.pem - /path/to/another/ca.pem letsencrypt: cachefile: /path/to/cache-file email: emailused@letsencrypt.com hosts: [myregistryaddress.org] debug: addr: localhost:5001 prometheus: enabled: true path: /metrics headers: X-Content-Type-Options: [nosniff] http2: disabled: false notifications: events: includereferences: true endpoints: - name: alistener disabled: false url: https://my.listener.com/event headers: <http.Header> timeout: 1 s threshold: 10 backoff: 1 s ignoredmediatypes: - application/octet-stream ignore: mediatypes: - application/octet-stream actions: - pull redis: addr: localhost:6379 password: asecret db: 0 dialtimeout: 10 ms readtimeout: 10 ms writetimeout: 10 ms pool: maxidle: 16 maxactive: 64 idletimeout: 300 s health: storagedriver: enabled: true interval: 10 s threshold: 3 file: - file: /path/to/checked/file interval: 10 s http: - uri: http://server.to.check/must/return/200 headers: Authorization: [Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==] statuscode: 200 timeout: 3 s interval: 10 s threshold: 3 tcp: - addr: redis-server.domain.com:6379 timeout: 3 s interval: 10 s threshold: 3 proxy: remoteurl: https://registry-1.docker.io username: [username] password: [password] compatibility: schema1: signingkeyfile: /etc/registry/key.json enabled: true validation: manifests: urls: allow: - ^https?://([^/]+\.)*example\.com/ deny: - ^https?://www\.example\.com/