2-4-1-镜像仓库
1. 上传至互联网仓库
1.1 命令说明
1.1.1 docker login
登录Docker镜像仓库 , 默认是Docker hub
语法 :
docker login [OPTIONS] [SERVER]OPTIONS-p: 密码-u: 用户名--password-stdin: 通过STDIN提供密码
示例
通过命令行方式输入账号密码
docker login
通过参数输入用户名密码
docker login -u poloyy -p ***
这种方式不安全 , 密码会出现在shell的历史记录或日志文件中
通过STDIN输入密码
先将密码存储在pwd.txt文件中
cat pwd.txt | docker login -u poloyy --password-stdin
可以解决上一个例子的不安全问题
1.1.2 docker logout
退出Docker镜像仓库 , 默认是Docker hub
语法 :
docker logout [SERVER]
例子
docker logout
1.1.3 docker push
将本地的镜像上传到镜像仓库 , 要先登录到镜像仓库
上传本地镜像的前置操作
注册
Docker hub账号Linux登录Docker hub账号给镜像设置
TAG
语法 :
docker push [OPTIONS] NAME[:TAG]OPTIONS:-a: 将镜像的所有标签上传至镜像仓库--disable-content-trust: 忽略镜像的校验,默认开启
完整镜像名称具有以下格式: [HOST[:PORT_NUMBER]/]PATH。其中PATH , [NAMESPACE/]REPOSITORY:第一个可选组件通常是用户或组织的名称空间。第二个必需的组件是存储库名称。当命名空间不存在时 , Docker使用library作为默认命名空间。
1.1 上传至官方仓库
官方镜像库地址: https://hub.docker.com/ , 实现镜像夸主机备份。如果需要使用该官方镜像库, 需要访问网页, 注册对应账户。注册完成之后进行如下步骤
在
docker服务器中登录[root@centos7 ~]# docker login docker.io Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one. Username: xiongxiong1234 Password: WARNING! Your password will be stored unencrypted in /root/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store Login Succeeded查看认证信息
登录成功之后会在当前目录生成一个隐藏文件用于保持登录认证信息
[root@centos7 ~]# cat /root/.docker/config.json { "auths": { "https://index.docker.io/v1/": { "auth": "eGlvbmd4aW9uZzEyMzQ6eGlvbmdaSFVBTmcxLg==" } }给镜像做
tag并开始上传[root@centos7 ~]# docker tag ubuntu:nginx docker.io/xiongxiong1234/ubuntu:nginx [root@centos7 ~]# docker push docker.io/xiongxiong1234/ubuntu:nginx登录到docker镜像官网查看信息

1.2 上传到阿里云
将本地镜像上传至阿里云,实现镜像备份与统一分发的功能
注册账户、创建namespace、创建仓库、修改镜像tag及上传镜像

在虚拟机中登录阿里云账号用户。密码为开通服务时设置的密码。
[root@centos7 ~]# docker login --username=1034384708@qq.com registry.cn-hangzhou.aliyuncs.com Password: WARNING! Your password will be stored unencrypted in /root/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store Login Succeeded从
Registry中拉取镜像$ docker pull registry.cn-hangzhou.aliyuncs.com/tomikes/ubuntu:[镜像版本号]将镜像推送到
Registry[root@centos7 ~]# docker tag xiongxiong1234/ubuntu:nginx registry.cn-hangzhou.aliyuncs.com/tomikes/ubuntu:nginx [root@centos7 ~]# docker push registry.cn-hangzhou.aliyuncs.com/tomikes/ubuntu:nginx The push refers to repository [registry.cn-hangzhou.aliyuncs.com/tomikes/ubuntu] 0bed728e5533: Pushed dd0717a9ec3f: Pushed f0248875aae7: Pushed 5f70bf18a086: Pushed 1bad3e017ec1: Pushed 3be8f81f3e8d: Pushed f69f4879120f: Pushed dfab729b0041: Pushed 96b9ec98c6fb: Pushed 1a102d1cac2b: Pushed nginx: digest: sha256:dcacf91df717688dfd3cb20c1609665c89b7d42e1a88030a25cbc978279cc2e8 size: 2415
2. 单机Docker register
2.1 基础简介
Docker Registry作为Docker 的核心组件之一负责镜像内容的存储与分发,客户端,的docker pull以及push命令都将直接与registry进行交互,最初版本的registry由python实现, 由于设计初期在安全性, 性能以及API的设计有着诸多缺陷, 该版本在0.9之后停止了开发, 由新的项目distribution(新的docker register被称为Distribution)来重新设计并开发下一代registry , 新的项目由go语法开发 , 所有的API , 底层存储方式, 系统架构都镜像了全面的重新设计。已解决上一代registry中存在的问题, 2016年4月份registry2.0正式发布 , docker 1.6版本开始支持registry 2.0 , 而八月份随着docker 1.8发布 , docker hub正式启用2.1版本registry全面替代之前版本registry ,新版本的registry对镜像存储格式进行了重新设计并和旧版本不兼容, docker 1.5和之前的版本无法读取2.0的镜像。另外, Registry 2.4版本之后支持回收站机制, 也就是可以删除镜像了 , 在2.4版本之前是无法支持删除镜像的,所以使用最好大于Registry2.4
描述 : 镜像仓库主要是用来存储和分发镜像的, 并对外提供一套HTTP API V2。 镜像仓库中的所有镜像都是以数据块 (Blob) 的方式存储在文件系统中。 支持多种文件系统, 主要包括filesystem, S3, Swift, OSS等。
下面详细介绍一下, 镜像的所有数据, 是如何存储在镜像仓库的文件系统中的?
为了分析镜像如何存放在Registry上, 我们可以在本地docker环境中启动registry容器来实验即可, 所以在这种场景下不太合适用Harbor这种重量级的Registry镜像仓库实践 ;
Step1.
Registry镜像容器启动# 拉取并运行 registry 容器,注意此处为了方便演示没对Registry设置安全认证设置在实际环境中必须进行设置 $docker run -d --name registry -p 9188:5000 -v /var/lib/registry:/var/lib/registry registry $docker run -d --name registry -p 5000:5000 -v /var/lib/registry:/var/lib/registry registry 335ea763a2fa4508ebf3ec6f8b11f3b620a11bdcaa0ab43176b781427e0beee6Step2.上传本地镜像到
Registry仓库# 1) 列出本地镜像 $docker images REPOSITORY TAG IMAGE ID CREATED SIZE go-hello scratch cb05b87d0012 5 days ago 2.07MB go-hello stage 5934753f8f4f 5 days ago 73.9MB # 2) 将镜像标签重新命名为registry地址+镜像名称+版本 $docker tag go-hello:scratch 127.0.0.1:5000/go-hello:scratch $docker push 127.0.0.1:5000/go-hello:scratch # The push refers to repository [127.0.0.1:5000/go-hello] # a3586d882361: Pushed # scratch: digest: sha256:8dabce532312b587329fe225ef501051c60f81ffdb2c801a5da6348b9cab132e size: 528 $docker tag go-hello:stage 127.0.0.1:5000/go-hello:stage $docker push 127.0.0.1:5000/go-hello:stage # The push refers to repository [127.0.0.1:5000/go-hello] # f5c12d3201a7: Pushed # 095624243293: Pushed # a37e74863e72: Pushed # 8eeb4a14bcb4: Pushed # ce3011290956: Pushed # stage: digest: sha256:b96f0ada640f7d628a4f22766f5432cbb1a4dfa208b03dc6444f8c699da9533c size: 1360Step3.当我们在本地启动一个
registry容器之后, 容器内默认的存储位置为/var/lib/registry$ tree -d /var/lib/registry/docker/registry registry └── v2 ├── blobs │ └── sha256 │ ├── 0e │ │ └── 0e83886eae4fa0f68c7212e4667e030f8c95aa3669f68257a0fa45bf233492b6 │ ├── 32 │ │ └── 323d0d660b6a7da8df08a01dbc7250f38cfa2161db00c7c27c0b20be07a8236a │ ├── 3f │ │ └── 3ff22d22a8554f746f90a78b501da38d56a46f2ddba0dfec8b260aebaa61b3ba │ ├── 53 │ │ └── 5395625ce01dee311e2f7c879b0b148ac7525de7aad5080a518d7f7e5a99d368 │ │ └── layer │ ├── 59 │ │ └── 5934753f8f4f23efdff6e5108997385f17c3b0b25426fe54eb373fb2f1112d87 │ ├── 8d │ │ └── 8dabce532312b587329fe225ef501051c60f81ffdb2c801a5da6348b9cab132e │ ├── b7 │ │ └── b7f616834fd07522cbfd33f0dfa848903599320b5c7191b59fe9aa7562f956a1 │ ├── b9 │ │ └── b96f0ada640f7d628a4f22766f5432cbb1a4dfa208b03dc6444f8c699da9533c │ ├── cb │ │ └── cb05b87d001253772ae9a212200de5eb8304ab9691c61589332a2f57e7059209 │ └── e7 │ └── e7cb79d19722c46b9c0829811d7a5a0ae82c8771ab7f2f68e7d3a3ed6bd5d5d0 └── repositories └── go-hello ├── _layers │ └── sha256 │ ├── 0e83886eae4fa0f68c7212e4667e030f8c95aa3669f68257a0fa45bf233492b6 │ ├── 323d0d660b6a7da8df08a01dbc7250f38cfa2161db00c7c27c0b20be07a8236a │ ├── 3ff22d22a8554f746f90a78b501da38d56a46f2ddba0dfec8b260aebaa61b3ba │ ├── 5395625ce01dee311e2f7c879b0b148ac7525de7aad5080a518d7f7e5a99d368 │ ├── 5934753f8f4f23efdff6e5108997385f17c3b0b25426fe54eb373fb2f1112d87 │ ├── b7f616834fd07522cbfd33f0dfa848903599320b5c7191b59fe9aa7562f956a1 │ ├── cb05b87d001253772ae9a212200de5eb8304ab9691c61589332a2f57e7059209 │ └── e7cb79d19722c46b9c0829811d7a5a0ae82c8771ab7f2f68e7d3a3ed6bd5d5d0 ├── _manifests │ ├── revisions │ │ └── sha256 │ │ ├── 8dabce532312b587329fe225ef501051c60f81ffdb2c801a5da6348b9cab132e │ │ └── b96f0ada640f7d628a4f22766f5432cbb1a4dfa208b03dc6444f8c699da9533c │ └── tags │ ├── scratch │ │ ├── current │ │ └── index │ │ └── sha256 │ │ └── 8dabce532312b587329fe225ef501051c60f81ffdb2c801a5da6348b9cab132e │ └── stage │ ├── current │ └── index │ └── sha256 │ └── b96f0ada640f7d628a4f22766f5432cbb1a4dfa208b03dc6444f8c699da9533c └── _uploads目录说明
blobs目录: 存储镜像的Layer数据以及Manifest和Image Config, 这些文件都是以data为名的文件存放在于该层Layer sha256相对应的目录下 ;文件最大的
data文件实际是一个压缩文件, 在Docker拉取镜像时候会下载该data文件, 等待完成后docker-untar进程将会把data文件解压到/var/lib/docker/overlay2/${digest}/diff目录下文件最小的
data文件实际是Manifest, 它记录了一个镜像所包含的layer信息, 当我们pull镜像的时候会使用到这个文件 ;文件介于最大和最小 的
data文件实际是Image Config存储镜像相关信息(架构、容器配置、创建实际) 即docker inspect image_id命令所看到的一些信息 :
repositories目录: 存储镜像在仓库相关的信息, 它们为了使用以内容寻址的sha256散列存储方便索引文件;_uploads: 它是临时文件夹主要用来存放push镜像过程中的文件数据, 并且当镜像layer上传完成之后会清空该文件夹; 其中的data文件上传完毕后会移动到blobs目录下, 并根据该文件的sha256值来进行散列存储到相应的目录下。_manifests: 该文件夹是镜像上传完成之后由Registry来生成的, 在该目录下的文件都是一个名为link的文本文件其值指向blobs目录下与之对应的目录;在该目录中包含镜像的tags和revisions信息, 每一个镜像的每一个Tag对应着于tag名称相同的目录;_revisions目录里存放了该repository历史上上传版本的所有sha256编码信息。tags/current目录下的link文件保存了该tag目前的manifest文件的sha256编码对应在blobs中的sha256目录下的data文件tags/index目录则列出了该tag历史上传的所有版本的sha256编码信息
Step4.当我们
pull镜像的时候如果不指定镜像的tag名默认就是latest,registry会从HTTP请求中解析到这个tag名, 然后根据tag名目录下的link文件找到该镜像的manifest的位置返回给客户端, 客户端接着去请求这个manifest文件, 客户端根据这个manifest文件来pull相应的镜像layer。# 找到Manifest位置 $cat /var/lib/registry/docker/registry/v2/repositories/go-hello/_manifests/tags/scratch/current/link sha256:8dabce532312b587329fe225ef501051c60f81ffdb2c801a5da6348b9cab132e # 进入查看Manifest内容 $ls -lah /var/lib/registry/docker/registry/v2/blobs/sha256/8d/8dabce532312b587329fe225ef501051c60f81ffdb2c801a5da6348b9cab132e/ 总用量 4.0K drwxr-xr-x. 2 root root 18 8月 2 09:58 . drwxr-xr-x. 3 root root 78 8月 2 09:58 .. -rw-r--r--. 1 root root 528 8月 2 09:58 data $cat /var/lib/registry/docker/registry/v2/blobs/sha256/8d/8dabce532312b587329fe225ef501051c60f81ffdb2c801a5da6348b9cab132e/data { "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json", # 镜像拉取时镜像Image Config文件地址 "config": { "mediaType": "application/vnd.docker.container.image.v1+json", "size": 1472, "digest": "sha256:cb05b87d001253772ae9a212200de5eb8304ab9691c61589332a2f57e7059209" }, # 镜像拉取时下载并利用docker-untar线程解压 "layers": [ { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "size": 1106793, "digest": "sha256:5395625ce01dee311e2f7c879b0b148ac7525de7aad5080a518d7f7e5a99d368" } ] }
总结思想:
同一镜像在不同
Registry镜像仓库中, 存储的方式、位置和内容完全一样因为它们的Layer digest在仓库中唯一。通过
Registry API获得的两个镜像仓库中相同镜像的manifest信息完全相同。两个镜像仓库中相同镜像的
manifest信息的存储路径和内容完全相同。两个镜像仓库中相同镜像的
blob信息的存储路径和内容完全相同。
registry存储目录里并不会存储与该registry相关的信息, 比如我们push镜像时给镜像加上127.0.0.1:5000前缀; 所以我们在迁移一个大的Registry镜像仓库时候最快捷的方法就是打包(tar -cvf -不需要加z参数浪费CPU时间得不偿失)该registry存储然后将其tar包rsync到其它机器即可;
2.2 基础使用
2.2.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 registry2.2.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
# 例如:开发配置的confi.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:/auth2.2.3 生产实例
2.2.3.1 有证书配置
描述:此处以实际生产环境为例进行Docker Registry私有仓库搭建;
服务器拉取
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文件(明文用户/密码编码)来进行鉴权;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挂载安全认证与自签证书并启动registry容器
docker run -d -p 0.0.0.0:443: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验证
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上传一个镜像到
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: 528registry查看上传的镜像# (1) 查看registry中存储的镜像仓库名称 (注意--cacert参数如果证书未导入到系统中则必须加上) $curl --cacert /opt/registry/certs/domain.crt -u 'weiyigeek:123456' -X GET https://localhost/v2/_catalog {"repositories":["alpine"]}利用
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"] }
注意事项:
证书颁发者可以用中间证书提供给您。在这种情况下 , 你必须用中间证书串连您的证书形成的证书捆绑。
cat domain.crt intermediate-certificates.pem > certs/domain.crtLet’s Encrypt:https://letsencrypt.org/how-it-works/
2.2.3.2 无证书配置
准备用户名密码
[root@centos7 ~]# yum install httpd-tools [root@centos7 ~]#mkdir -vp /opt/registry/ && cd $_ [root@centos7 registry]#htpasswd -Bbn weiyigeek 123456 # -n 不更新文件输入到终端标准输出 [root@centos7 registry]#htpasswd -bB -c auth.htpasswd weiyigeek 123456 # -c 创建存储认证字符串 , -b 强制加密密码 , -B 在命令行中接收密码创建对应容器
docker run -it -d -p 5000:5000 --restart=always --name registry -v /opt/registry/auth.htpasswd:/etc/docker/registry/auth.htpasswd -e REGISTRY_AUTH="{htpasswd: {realm: localhost, path: /etc/docker/registry/auth.htpasswd}}" -v /var/lib/registry-net:/var/lib/registry registry [root@centos7 ~]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 0df271abf18a registry "/entrypoint.sh /etc…" 2 hours ago Up 2 hours 0.0.0.0:5000->5000/tcp, :::5000->5000/tcp registry测试登录

解决方法:
直接编辑对应的
systemd服务文件, 在ExecStart后直接添加--insecure-registry
[root@centos7 ~]# echo "weiyigeek:123456" | base64 d2VpeWlnZWVrOjEyMzQ1Ngo= [root@centos7 ~]# cat /root/.docker/config.json { "auths": { "192.168.194.130:5000": { "auth": "d2VpeWlnZWVrOjEyMzQ1Ng==" } } }[root@centos7 ~]#修改镜像标签
[root@centos7 ~]# docker tag alpine:latest 192.168.194.130:5000/weiyigeek/alpine:latest上传镜像
[root@centos7 ~]# docker push 192.168.194.130:5000/alpine:latest使用其他服务器登录镜像并且下载
客户端服务器类型:
CentOS8容器软件为:
podman直接登录会提示以下内容

/etc/containers/registries.conf文件中添加以下内容[[registry]] insecure = true prefix = "192.168.194.130:5000" location = "192.168.194.130:5000"
修改文件之后重启服务并登录
[root@centos8 ~]# systemctl restart podman [root@centos8 ~]# podman login 192.168.194.130:5000 Username: weiyigeek Password: Login Succeeded!下载镜像
[root@centos8 ~]#podman pull 192.168.194.130:5000/weiyigeek/alpine:latest Trying to pull 192.168.194.130:5000/weiyigeek/alpine:latest... Getting image source signatures Copying blob 4abcf2066143 done Copying config 05455a0888 done Writing manifest to image destination Storing signatures 05455a08881ea9cf0e752bc48e61bbd71a34c029bb13df01e40e3e70e0d007bd [root@centos8 ~]# podman images REPOSITORY TAG IMAGE ID CREATED SIZE 192.168.194.130:5000/weiyigeek/alpine latest 05455a08881e 2 weeks ago 7.67 MB
2.3 register 目录结构
描述 : 此时以上传的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
registry存储目录说明:
结合下面这张图可以看见 , registry存储目录下只有两种文件名的文件 , 即data与link文件
link文件 : 是普通的文本文件存放在repositories目录下 , 其内容是指向data文件的sha256 digest值 , 从字面意义上就很好理解它 ;data文件: 存放在blobs目录下文件且分为三个文件(即镜像的layer/config/manifest等文件)
具体目录说明
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/ a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65tags目录下的文件夹名 , 例如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
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/dataimage config: 文件是json格式的 , 它是在构建时候生成的根据DockerFile和宿主机的一些信息; ( 记录了"rootfs"."diff_ids" )4.0K ./blobs/sha256/a2/a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e/datadata manifest: 文件json文件格式的 , 存放该镜像layer和image config文件的索引。(镜像拉取首先需拉取此文件)4.0K ./blobs/sha256/a1/a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65/data

此时我们可以再往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总结上述:
上述可以看见当
skopeo delete删除一个镜像时 , 只是对_manifests下的文件revisions/sha256/a15790640a6690...ka9f2b0d7cc90fd65/link与tags即其子目录文件进行删除;实际上两者删除的是同一个内容 , 即对记录了该镜像manifests文件digest摘要 的link文件。运行后从
--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获取到镜像
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删除
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机制;
2.4 Registry API
描述:可以通过registry API操作管理镜像或者获取镜像manifest相关信息; 官方参考地址: https://docs.docker.com/registry/spec/api/
2.4.1 API 一览
描述:通过API遇到的错误代码如下表所示: https://docs.docker.com/registry/spec/api/#errors-2
API方法和URI列表涵盖如下表 :
结合registry仓库解释镜像PULL与PUSH过程:
PULL镜像 : 镜像由一个json清单和层叠文件组成 ,pull镜像的过程就是检索这两个组件的过程。第一步就是获取清单 , 清单由下面几个字段组成:
registry:5000/v2/redis/manifests/latest(获取redis:latest清单文件) # 字段 描述 # name 镜像名称 # tag 镜像当前版本的tag # fsLayers 层描述列表(包括摘要) # signature 一个JWS签名 , 用来验证清单内容第二步当获取清单之后 , 客户端需要验证前面(
signature) , 以确保名称和fsLayers层是有效的。确认后客户端可以使用digest去下载各个fs层。在V2api中层存储在blobs中已digest作为键值.首先拉取镜像清单(
pulling an Image Manifest)$ HEAD /v2/<image/manifests/<reference>#检查镜像清单是否存在 $ GET /v2/<image>/manifests/<reference>#拉取镜像清单提示 :
reference可是tag或者是digest开始拉取每个层(
pulling a Layer)$ GET /v2/<image>/blobs/<digest>提示 :
digest是镜像每个fsLayer层的唯一标识 , 存在于清单的fsLayers里面。
PUSH镜像: 推送镜像和拉取镜像过程相反,先推各个层到registry仓库 , 然后上传清单.(Pushing a Layer(上传层)分为2步)使用
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> # 可以用来查看上传状态和实现断点续传开始上传层(
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>上传完成(
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>
2.4.2 实际示例
Tips: 后续不再加--cacert /opt/registry/certs/domain.crt参数 , 默认大家都已经把证书导入带系统本地;
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查看
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"获取某个镜像的标签列表 (注意加或者未加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>拉取
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" # } # ] # }获取仓库镜像的
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" # } # ] # }获取(镜像:版本)标识的
data manifests的digestcurl -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删除仓库中的镜像即删除(
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拉取镜像 , 由于层被存储在注册表中的
blobs中所以是需要通过一个标准的HTTP请求来进行拉取一个层的信息先查看镜像
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"获取拉取镜像的
data config与data layer文件#获取拉取镜像的data config curl -u 'weiyigeek:123456' -X GET https://localhost/v2/library/alpine/blobs/sha256:a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e | jq #获取拉取镜像的data layer文件 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
镜像通过
Registry API上传到仓库中所有层上传使用两个步骤来管理上传过程。
第一步开始在注册表中的服务上传 , 返回一个
URL来进行第二步。第二步使用上载
URL传递的实际数据。上传都开始返回 , 可用于将数据推和检查上传状态URL的POST请求。Location头将用于每个请求后进行通信的上载位置。虽然它不会在本技术规格改变 , 客户应使用API返回的最新值。开始上载一个
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通过
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上传进度查看此时需要第一步中的
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-695c1f945e59Monolithic 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>跨存储库
Blob挂载,可以从客户机具有读访问权的另一个存储库挂载blob, 从而不需要将已知的blob上传到注册中心,要发出一个blob挂载而不是一个upload,POST请求应该以以下格式发出(成功将返回202 Accepted):POST /v2/<name>/blobs/uploads/?mount=<digest>&from=<repository name> Content-Length: 0Completed 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>取消上传镜像到仓库 , 可通过发出
DELETE请求到registry之中;DELETE /v2/<name>/blobs/uploads/<uuid>删除层(
Deleting a Layer)DELETE /v2/<image>/blobs/<digest> # 成功返回: # 202 Accepted # Content-Length: None上传镜像清单(
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与镜像层信息:
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/*2.5 Registry GC
2.5.1 GC基础
描述:在上一章节中我们阐述了为什么要引入Registry GC机制 , 再说其目的用处前我们先对其进行一个简单的介绍;
GC英文全称Garbage collection就是垃圾回收的意思 , 从docker官方文档关于GC偷来的example来解释一下吧。 官网文档:https://docs.docker.com/registry/garbage-collection/
#假如镜像 A 和镜像 B 它们分别引用了layer a , b和 a , c。
- A -
| |
a - b - c
| |
---- B ----
# 通过 registry API 删除镜像 B 之后 , layer c 并没有删掉 , 只是删掉了对它的引用所以 c 是多余的 ( 就是我们上诉提到那种清情况) 。
- A -
| |
a - b - c
#此时通过GC机制之后 Layer C 被删除掉了即没有被引用的层将被彻底删除;
- A -
| |
a - b 此处可以借鉴registry GC的源码文件garbagecollect.go可以看到GC的主要分两个阶段:
marking阶段 : 根据上文我们提到的link文件 , 通过扫描所有镜像tags目录下的link文件就可以得到这些镜像的manifest, 在manifest中保存在该镜像所有的layer和config文件的digest值 , 把这些值标记为不能清除。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") }sweep阶段 : 删除操作当marking完成之后没有标记blobs(layer和config)就会被清理掉;// sweep 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 { // check if digest is in markSet. If not, delete it! if _, ok := markSet[dgst]; !ok { deleteSet[dgst] = struct{}{} } return nil })

我们可以利用registry容器中的registry garbage-collect命令进行GC回收操作;
$ docker exec -it registry sh -c "/bin/registry garbage-collect -m --delete-untagged=true /etc/docker/registry/config.yml"
/ # cat /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
# marking 阶段
library/alpine # Registry中的镜像未标记将会被删除
library/debian
library/debian: marking manifest sha256:e0a33348ac8cace6b4294885e6e0bb57ecdfe4b6e415f1a7f4c5da5fe3116e02
library/debian: marking blob sha256:c7346dd7f20ef06fd3c58446fab0c3edf22e78131d374775f5f947849537b773
library/debian: marking blob sha256:bf59529304463f62efa7179fa1a32718a611528cc4ce9f30c0d1bbc6724ec3fb
# sweep 阶段
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 存储目录长什么样子?
$ 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 files2.5.2. Shell脚本
示例1:查看Docker官方镜像仓库中镜像的所有标签
方式1:
#!/bin/sh # set -xe # 其实实现方法就是通过镜像仓库的 restful API , 来查询 , 然后把返回的 json 结果简单处理一下 , 然后打印出来。 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:一条命令搞定
skopeo inspect docker://docker.io/alpine | jq ".RepoTags"
示例2 : registry信息查看脚本与RegistryGC回收脚本
#!/bin/bash
# Description:查看Registry仓库中的镜像信息并从仓库中删除指定镜像 , 然后进行垃圾回收
# Author:WeiyiGeek
# createTime:2020年8月23日 16:55:57
set -x
# [+ Defined]
PARM=$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
# 删除 blobs/sha256中的空目录
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
# 获取要删除镜像的digest摘要
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
# 删除 镜像 _Manifests目录中的Tags相关目录
curl -v -H "${AUTH}" -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X DELETE "${REGISTRY_URL}/${IMAGE_NAME}/manifests/${MANIFESTS_DIGEST}"
# 删除 镜像 _Layer 目录下的link
for digest in $(cat images.digest);do
curl -v -H "${AUTH}" -X DELETE "${REGISTRY_URL}/${IMAGE_NAME}/blobs/${digest}"
done
fi
# GC 回收(注意参数为容器镜像名称)
GcRegistry ${REGISTRY_NAME}
# 判断 镜像 是否存在其它 tags 不存在时候直接删除其目录
$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
# 删除 _layers 目录下的digest文件空的目录
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
# 删除 manifests 目录下的digest文件空的目录
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
}
# [Main]
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
EOF2.5.3 章节总结
在
GC之后registry存储目录我们可以看到 , 原本blobs目录下有6个data文件现在已经变成3个 , 而alpine:3.12镜像相关的Layer、Config、Manifest文件已经被GC清理掉了; 但是在repositories目录下 , 该镜像的_layers下的link文件依旧存在🤔。总结以上 , 用下面这三张图片就能直观地理解这些过程啦
delete镜像之前的registry存储目录结构
delete镜像之后的registry存储目录结构
GC之后的registry存储目录结构
GC之后一定要重启 , 因为registry容器缓存了镜像layer的信息当删除掉一个镜像A后边GC掉该镜像的layer之后 , 如果不重启registry容器 , 当重新PUSH镜像A的时候就会提示镜像 layer 已经存在 , 不会重新上传layer但实际上已经被 GC 掉了 , 最终会导致镜像A不完整无法pull到该镜像`。GC不是事务性操作 , 所以在进行GC的时候最好暂停PUSH镜像 , 以免把正在上传的镜像layer给GC掉。
2.6 配置文件解析
config.yaml 文件一览
# 配置版本
version: 0.1
# 日志配置日志系统的行为
log:
# 访问日志系统的ACCESSLOG配置行为
accesslog:
disabled: true
# 日志输出的格式默认info.Permitted values are error, warn, info, debug
level: debug
# 日志输出的格式默认 text , json, and logstash
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
# 日志等级(error, warn, info, debug):deprecated: use "log"
loglevel: debug
# 存储后端配置必须
storage:
# 使用本地磁盘存储注册表文件
filesystem:
rootdirectory: /var/lib/registry
maxthreads: 100
azure:
accountname: accountname
accountkey: base64encodedaccountkey
container: containername
# Google Cloud Storage
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
# Amazon Simple Storage Service (S3)
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
# Openstack Swift object storage.
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
# Aliyun OSS for object storage
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: # This driver takes no parameters
# 使用delete结构允许通过摘要删除映像blob和清单
delete:
enabled: false
redirect:
disable: false
# 使用高速缓存结构 , 以便能够在存储后端访问的数据缓存。目前唯一可用的高速缓存提供对层的元数据 , 它使用blobdescriptor字段如果配置的快速访问。
cache:
# 如果设置为Redis的 , 一个Redis的缓存池元数据层。如果设置为inmemory , 一个inmemory地图缓存层的元数据。
blobdescriptor: redis
# 维护维修
maintenance:
# 上传清除
uploadpurging:
enabled: true
age: 168h
interval: 24h
dryrun: false
# 如果在维护只读部分 , 使设置为true , 客户端将不会被允许写入注册表。
readonly:
enabled: false
# 认证相关(只能配置一个身份验证提供者。)
auth:
silly:
# 使用范围
realm: silly-realm
service: silly-service
# 令牌的认证您可以从registry中分离认证系统
token:
autoredirect: true
realm: token-realm
service: token-service
issuer: registry-token-issuer
rootcertbundle: /root/certs/bundle
# 支持htpasswd的认证允许您使用的是Apache的htpasswd文件来配置基本身份验证
htpasswd:
realm: basic-realm
# 唯一支持的密码格式是bcrypt。
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: 3000s
ipfilteredby: awsregion
awsregion: us-east-1, use-east-2
updatefrenquency: 12h
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服务器主机上的注册表中的配置
http:
addr: localhost:5000
prefix: /my/nested/registry/
host: https://myregistryaddress.org:5000
secret: asecretforlocaldevelopment
relativeurls: false
draintimeout: 60s
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
# 它来指定报头的HTTP服务器应该在响应 ;
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: 1s
threshold: 10
backoff: 1s
ignoredmediatypes:
- application/octet-stream
ignore:
mediatypes:
- application/octet-stream
actions:
- pull
# redis 相关配置
redis:
addr: localhost:6379
password: asecret
db: 0
dialtimeout: 10ms
readtimeout: 10ms
writetimeout: 10ms
pool:
maxidle: 16
maxactive: 64
idletimeout: 300s
# 健康检查
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
# 文件结构包括要定期检查的路径的列表为一个文件的\存在。如果文件存在于指定的路径 , 健康检查将失败。您可以使用这一机制通过创建一个文件 , 使注册表进行旋转。
file:
- file: /path/to/checked/file
interval: 10s
http:
- uri: http://server.to.check/must/return/200
headers:
Authorization: [Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==]
statuscode: 200
timeout: 3s
interval: 10s
threshold: 3
tcp:
- addr: redis-server.domain.com:6379
timeout: 3s
interval: 10s
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/ Error in deleting repository in a private registry V2 #1573; 问题原因:在启动registry时候没有启用REGISTRY_STORAGE_DELETE_ENABLED=true环境变量; ISSUE: https://github.com/docker/distribution/issues/1573 解决办法: 启动docker registry 加入环境变量;
-e "REGISTRY_STORAGE_DELETE_ENABLED=true"3. 分布式harbor
Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器 , 由vmware开源 , 其通过添加一些企业必需的功能特性 , 例如安全、标识和管理等 , 扩展了开源Docker Distribution 。作为一个企业级私有Registry服务器 , Harbor提供了更好的性能和安全。提升用户使用Registry构建和运行环境传输镜像的效率。 Harbor支持安装在多个Registry节点的镜像资源复制 , 镜像全部保存在私有Registry 中 , 确保数据和知识产权在公司内部网络中管控 , 另外 , Harbor也提供了高级的安全特性 , 诸如用户管理 , 访问控制和活动审计等。
Harbor gitlab项目地址 : https://github.com/goharbor/harborvmware官方开源服务列表地址: https://vmware/github.io/harbor/cnharbor官方网址: https://goharbor.ioHarbor是一个开源的可信云本机注册表项目 , 用于存储 , 签名和扫描内容。Harbor通过添加用户通常需要的功能 ( 如安全性 , 身份和管理 ) 来扩展开源Docker Distribution。使注册表更接近构建和运行环境可以提高镜像传输效率。Harbor支持在注册表之间复制映像 , 还提供高级安全功能 , 如用户管理 , 访问控制和活动审计。Harbour由Cloud Native Computing Foundation( CNCF) 托管。如果您是一个希望帮助塑造云原生技术发展的组织 , 请考虑加入CNCF。
3.1 harbor功能官方介绍
基于角色的访问控制 : 用户与Docker镜像仓库通过“项目”进行组织管理 , 一个用户可以对多个镜像仓库在同一命名空间(project)里有不同的权限。
镜像复制 : 镜像可以在多个
Registry实例中复制(同步) , 尤其适合于负载均衡 , 高可用 , 混合云和多云的场景。图形化用户界面 : 用户可以通过浏览器来浏览 , 检索当前Docker 镜像仓库 , 管理项目和命名空间。
AD/LDAP支 :Harbor可以集成企业内部已有的AD/LDAP, 用于鉴权认证管理。审计管理 : 所有针对镜像仓库的操作都可以被记录追溯 , 用于审计管理。
国际化 : 已拥有英文、中文、德文、日文和俄文的本地化版本。更多的语言将会添加进来。
RESTful API: 提供给管理员对于Harbor更多的操控 , 使得与其它管理软件集成变得更容易。部署简单 : 提供在线和离线两种安装工具 , 也可以安装到
vSphere平台(OVA方式 ) 虚拟设备。
Harbor特征
云本机注册表 :
Harbour支持容器映像和Helm图表 , 可用作云本机环境 ( 如容器运行时和业务流程平台 ) 的注册表。基于角色的访问控制 : 用户和存储库通过“项目”进行组织 , 用户可以对项目下的镜像拥有不同的权限。
基于策略的映像复制 : 可以基于具有多个过滤器 ( 存储库 , 标记和标签 ) 的策略在多个注册表实例之间复制 ( 同步 ) 映像。如果遇到任何错误 ,
Harbor将自动重试进行复制。非常适合负载平衡 , 高可用性 , 多数据中心 , 混合和多云场景。漏洞扫描 :
Harbor定期扫描镜像并警告用户漏洞。LDAP / AD支持:
Harbor与现有企业LDAP/AD集成以进行用户身份验证和管理 , 并支持将LDAP组导入Harbor并为其分配适当的项目角色。镜像删除和垃圾收集 : 可以删除镜像 , 并可以回收它们的空间。
公证 : 可以确保镜像的真实性。
图形用户门户 : 用户可以轻松浏览 , 搜索存储库和管理项目。
审计 : 跟踪存储库的所有操作。
RESTful API : 适用于大多数管理操作的
RESTful API, 易于与外部系统集成。易于部署 : 提供在线和离线安装程序。
3.2 harbor功能模块
Harbor在架构上主要有Proxy、Registry、Core services、Database ( Harbor-db) 、Log collector ( Harbor-log) 、Job services 六个组件。
Harbor的每个组件都是以Docker容器的形式构建的 , 因此 , 使用Docker Compose来对它进行部署。
总共分为7个容器运行 , 通过在docker- compose.yml所在目录中执行docker-compose ps命令来查看 , 名称分别为 :nginx、 harbor-jobservice、 harbor-ui、 harbor-db、 harbor-adminserver、registry、 harbor-log。
其中harbor-adminserver主要是作为一个后端的配置数据管理 , 并没有太多的其他功能。 harbor-ui所要操作的所有数据都通过harbor-adminserver这样一个数据配置管理中心来完成。

nginx:harbor的一个反向代理组件 , 代理registry、ui、token等服务。这个代理会转发harbor web和docker client的各种请求到后端服务上。harbor-adminserver:harbor系统管理接口, 可以修改系统配置以及获取系统信息。harbor-db: 存储项目的元数据、用户、规则、复制策略等信息。harbor-jobservice:harbor里面主要是为了镜像仓库之前同步使用的。harbor-log: 收集其他harbor的日志信息harbor-ui: 一个用户界面模块 , 用来管理registryregistry: 存储docker images的服务, 并且提供pull/push服务redis: 存储缓存信息webhook: 当registry中的image状态发生变化的时候取记录更新日志、复制等操作token service: 在docker client进行pull/push的时候负责token的发放
3.3 Harbor配置参数
3.3.1 harbor1.7配置参数
参数介绍
配置参数位于文件harbor.cfg中。
在
harbor.cfg中有两类参数 , 必需参数和可选参数。必需
required参数 : 需要在配置文件中设置这些参数。如果用户更新它们harbor.cfg并运行install.sh脚本以重新安装Harbor, 它们将生效。可选参数 : 这些参数对于更新是可选的 , 即用户可以将它们保留为默认值 , 并在启动
Harbour后在Web Portal上更新它们。如果它们已经启用harbor.cfg, 它们只会在首次启动Harbour时生效。harbor.cfg将忽略对这些参数的后续更新。
注意 : 如果您选择通过
Portal设置这些参数 , 请务必在Harbour启动后立即执行此操作。特别是 , 您必须在Harbour中注册或创建任何新用户之前设置所需的auth_mode。当系统中有用户时 ( 除默认管理员用户外) , 无法更改auth_mode。请注意 , 至少需要更改
hostname属性。
必需参数
hostname: 目标主机的主机名 , 用于访问Portal和注册表服务。它应该是目标计算机的IP地址或完全限定的域名 (FQDN) , 例如 ,192.168.1.10或reg.yourdomain.com。不要使用localhost或127.0.0.1作为主机名 - 外部客户端需要访问注册表服务!ui_url_protocol: (http或https。默认为http) 用于访问Portal和令牌/通知服务的协议。如果启用了公证 , 则此参数必须为https。默认情况下 , 这是http。要设置https协议 , 请参阅使用HTTPS访问配置Harbor。db_password: 用于db_auth的PostgreSQL数据库的root密码。更改此密码以用于任何生产用途!max_job_workers: (默认值为10) 作业服务中的最大复制工作者数。对于每个映像复制作业 , 工作程序将存储库的所有标记同步到远程目标。增加此数量可以在系统中实现更多并发复制作业。但是 , 由于每个工作者都消耗一定量的网络/ CPU / IO资源 , 请根据主机的硬件资源仔细选择该属性的值。customize_crt: (on/off. Default is on) , 如果此属性on, 在准备脚本创建注册表的令牌生成/验证私钥和根证书。当外部源提供密钥和根证书时 , 将此属性设置为off。有关详细信息 , 请参阅自定义密钥和港口令牌服务证书。ssl_cert:SSL证书的路径 , 仅在协议设置为https时应用。ssl_cert_key:SSL密钥的路径 , 仅在协议设置为https时应用。secretkey_path: 用于加密或解密复制策略中远程注册表密码的密钥路径。log_rotate_count: 日志文件在被删除之前会被轮换log_rotate_count次。如果count为0, 则删除旧版本而不是旋转。log_rotate_size: 仅当日志文件大于log_rotate_size字节时才会轮换日志文件。如果大小后跟k, 则假定大小以千字节为单位。如果使用M, 则大小以兆字节为单位 , 如果使用G, 则大小为千兆字节。http_proxy: 为Clair配置http代理 , 例如http://my.proxy.com:3128。https_proxy: 为Clair配置https代理 , 例如http://my.proxy.com:3128。no_proxy: 为Clair配置无代理 , 例如127.0.0.1,localhost,core,registry。
可选参数
电子邮件设置 :
Harbor需要这些参数才能向用户发送“密码重置”电子邮件 , 并且仅在需要该功能时才需要。另外 , 请注意 , 在默认情况下SSL连接时没有启用-如果你的SMTP服务器需要SSL, 但不支持STARTTLS, 那么你应该通过设置启用SSL email_ssl = TRUE。如果电子邮件服务器使用自签名证书或不受信任证书 , 则设置email_insecure = trueemail_server = smtp.mydomain.comemail_server_port = 25email_identity =email_username = sample_admin@mydomain.comemail_password = abcemail_from = admin sample_admin@mydomain.comemail_ssl = falseemail_insecure = false
harbor_admin_password: 管理员的初始密码。此密码仅在Harbor首次启动时生效。之后 , 将忽略此设置 , 并且应在Portal中设置管理员密码。请注意 , 默认用户名/密码为admin / Harbor12345auth_mode: 使用的身份验证类型。默认情况下 , 它是db_auth , 即凭据存储在数据库中。对于LDAP身份验证 , 请将其设置为ldap_auth。重要信息 : 从现有Harbor实例升级时 , 必须确保在启动新版本的Harbor之前auth_mode相同harbor.cfg。否则 , 用户可能无法在升级后登录。ldap_url:LDAP端点URL( 例如ldaps://ldap.mydomain.com) 。 仅在auth_mode设置为ldap_auth时使用。ldap_searchdn: 具有搜索LDAP/AD服务器权限的用户的DN( 例如uid=admin,ou=people,dc=mydomain,dc=com) 。ldap_search_pwd:ldap_searchdn指定的用户密码。ldap_basedn: 查找用户的基本DN, 例如ou=people,dc=mydomain,dc=com。 仅在auth_mode设置为ldap_auth时使用。ldap_filter: 用于查找用户的搜索过滤器 , 例如(objectClass=person)。ldap_uid: 用于在LDAP搜索期间匹配用户的属性 , 它可以是uid,cn,email或其他属性。ldap_scope: 搜索用户的范围 ,0-LDAP_SCOPE_BASE,1-LDAP_SCOPE_ONELEVEL,2-LDAP_SCOPE_SUBTREE。默认值为2。ldap_timeout: 连接LDAP服务器时超时 ( 以秒为单位) 。默认值为5。ldap_verify_cert: 验证来自LDAP服务器的证书。默认为true。ldap_group_basedn: 在LDAP/AD中查找组的基本dn, 例如ou=group,dc=mydomain,dc=com。ldap_group_filter: 搜索LDAP / AD组的过滤器 , 例如objectclass=group。ldap_group_gid: 用于命名LDAP / AD组的属性 , 它可以是cn,name。ldap_group_scope: 搜索ldap组的范围。0-LDAP_SCOPE_BASE,1-LDAP_SCOPE_ONELEVEL,2-LDAP_SCOPE_SUBTREE。默认值为2。self_registration:( 打开或关闭。默认打开) 启用/禁用用户注册他/她自己的能力。禁用时 , 新用户只能由管理员用户创建 , 只有管理员用户可以在Harbor中创建新用户。 注意 : 当auth_mode设置为ldap_auth时 , 始终禁用自注册功能 , 并忽略此标志。token_expiration: 令牌服务创建的令牌的到期时间 ( 以分钟为单位) , 默认为30分钟。project_creation_restriction: 用于控制用户有权创建项目的标志。默认情况下 , 每个人都可以创建一个项目 , 设置为“adminonly” , 这样只有管理员才能创建项目。
配置存储后端 ( 可选)
默认情况下 ,
Harbor将图像存储在本地文件系统中。在生产环境中 , 您可以考虑使用其他存储后端而不是本地文件系统 , 如S3,OpenStack Swift,Ceph等。这些参数是注册表的配置。registry_storage_provider_config: 存储提供程序配置的逗号分隔“key : value”对 , 例如“key1 : value,key2 : value2”。默认为空字符串。
registry_storage_provider_name: 注册表的存储提供程序名称 , 可以是filesystem,s3,gcs,azure等。默认为filesystem。registry_custom_ca_bundle: 自定义根ca证书的路径 , 它将注入到注册表和图表存储库容器的信任库中。当用户使用自签名证书托管内部存储时 , 通常需要这样做。
例如 , 如果使用Openstack Swift作为存储后端 , 则参数可能如下所示 :
registry_storage_provider_name = swift``registry_storage_provider_config = “ username : admin , password : ADMIN_PASS , authurl : http : ``//` `keystone_addr : 35357 / v3 / aut3.3.2 harbor2.10配置参数
在harbor安装包解压后 , 在目录下的harbor.yml.tmpl文件是配置文件模板 , 将其复制为harbor.yml ( harbor配置文件 )
服务访问地址
hostname: 192.168.2.250访问管理
UI和注册表服务的IP地址或主机名。不能使用localhost或127.0.0.1, 因为Harbor需要由外部客户端访问。值填写Harbor服务的主机IP或主机名 ( 域名访问方式 ) 。服务访问方式 ( 端口 )
http: port: 80 https: port: 443 # https安全访问端口 , 默认443 certificate: /etc/docker/CA/harbor.pem # 访问harbor的证书 private_key: /etc/docker/CA/harbor-key.pem # 访问harbor的私钥http的端口 , 默认为80。如果启用https, 此端口将重定向到https端口。模板文件模式禁用https安全访问方式 ( 适合初学者或测试环境的配置 ) 。生产环境需要将https部分取消注释 , 以增加Harbor的安全性。启用
https安全端口 , 还需要手动配置证书。官方是以openssl工具创建证书。是否启用
Harbor组件之间的tls通信默认为禁用状态。
# internal_tls: # 是否启用所有Harbor组件之间的tls通信 # enabled: true # 将enabled设置为true表示内部tls已启用 # dir: /etc/harbor/tls/internal #证书和密钥文件的存放目录是否启用外部代理
如果启用 , 那么主机名将不再使用 , 默认禁用 , 值为外部代理的地址。
# external_url: https://reg.mydomain.com:8433配置管理员密码
管理员
admin的初始密码 , 默认为Harbor12345, 只在第一次安装harbor时起作用 , 安装时不需要修改 , 登录Harbor UI管理界面后 , 为保证密码的安全性 , 强烈建议修改。harbor_admin_password: Harbor12345数据库配置
harbor数据库配置部分 ,harbor使用postgresql作为数据库。database: password: root123 max_idle_conns: 100 max_open_conns: 900 data_volume: /app/harbor/datapassword:harbor数据库root用户的密码 , 生产环境建议修改 ;max_idle_conns: 空闲连接池中的最大连接数。如果小于等于0, 则不保留空闲连接。max_open_conns: 打开到数据库的最大连接数。如果小于等于0, 则对打开的连接数没有限制。data_volume: 数据存储目录 ,harbor中的每个组件的数据都存储在该指定路径下的子目录中。是否启用外部存储
如果需要使用外部存储 , 则将该部分的注释去掉 , 生产环境建议使用外部存储。
# storage_service: # ca_bundle # filesystem: # maxthreads: 100 # 最大线程数 # redirect: # 是否启用重定向 # disabled: falseca_bundle: 自定义根ca证书的路径 , 该证书将被注入信任库。filesystem: 存储后端 , 默认为文件系统 , 选项包括文件系统、azure、gcs、s3、swift、NFS和oss存储。扫描器配置
是否启用
Trivy扫描器来扫描镜像的漏洞 , 默认未启用。trivy: ignore_unfixed: true # 是否显示已修复的漏洞 skip_update: false # 是否启用从github上下载的Trivy数据 insecure: false # 是否跳过证书验证 # github_token: xxx # github的token值job服务的最大数量jobservice: max_job_workers: 10webhook配置默认启用 , 默认的值为
10.notification: webhook_job_max_retry: 10 # webhook job的最大重试次数Chart配置harbor不仅可以存储镜像 , 还可以存Chart, 当helm服务器使用。chart: absolute_url: disabled # 是否启用日志配置
配置
harbor的日志信息log: level: info # 日志级别 local: # 配置本地存储中的日志 rotate_count: 50 rotate_size: 200M location: /var/log/harborrotate_count: 日志文件在被删除之前旋转的次数。如果为0, 旧版本将被删除而不是旋转。rotate_size: 只有当日志文件的大小大于该值字节时 , 才会对其进行旋转。如果size后跟k, 则假定大小以KB为单位。如果使用M, 则大小以兆字节为单位 , 如果使用G, 则大小以千兆字节为单位。那么100、100k、100M和100G。location: 主机上存储日志的目录。是否启用外部系统日志
是否将日志放到另外的主机上 ( 默认关闭该功能 )
# external_endpoint: # protocol: tcp # host: localhost # port: 5140protocol: 用于将日志传输到外部端点的协议 , 选项为tcp或udp。host: 外部主机的地址(主机IP)。port: 外部主机的端口号。Harbor版本号 因为配置文件是使用模板文件创建的 , 需要将版本号修改为安装包一样的版本 , 否则按照会失败。_version: 2.3.1是否启用外部数据库
如果要使用外部数据库 , 请取消注释该部分来启用。注意与前面的外部数据区分。
目前
Harbor仅支持PostgraSQL数据库 , 需要手动在外部的PostgreSQL上创建harbor、notary_signer、notary_servers三个数据库 ,Harbor启动时会自动在对应数据库下生成表。配置如下:# external_database: # harbor: # host: harbor_db_host # 数据库主机地址 # port: harbor_db_port # 数据库端口 # db_name: harbor_db_name # 数据库名称 # username: harbor_db_username # 连接该数据库的用户名 # password: harbor_db_password # 连接数据库的密码 # ssl_mode: disable # 启用SSL模式 # max_idle_conns: 2 # 最大空闲连接数 # max_open_conns: 0 # notary_signer: # host: notary_signer_db_host # port: notary_signer_db_port # db_name: notary_signer_db_name # username: notary_signer_db_username # password: notary_signer_db_password # ssl_mode: disable # notary_server: # host: notary_server_db_host # port: notary_server_db_port # db_name: notary_server_db_name # username: notary_server_db_username # password: notary_server_db_password # ssl_mode: disable是否启用外部
redis缓存服务器# external_redis: # 配置外部 Redis 实例。 # host: redis:6379 # redis的地址:端口 # password: # 连接外部redis服务的密码 # #sentinel_master_set: # registry_db_index: 1 # jobservice_db_index: 2 # chartmuseum_db_index: 3 # trivy_db_index: 5 # Trivy扫描器的数据库索引 # idle_timeout_seconds: 30 ``` `host:` `redis` : `redis的地址:端口` ; `sentinel_master_set` : 必须设置为支持`redis+sentinel`,仅在使用`Sentinel `模式时设置 ; `registry_db_index:` 注册表的数据库索引 ; `jobservice_db_index:` `job`服务的数据库索引 ;UAA身份验证 是否通过自签名证书托管的uaa实例证书的信任 ,2.4.0开始不支持该配置。# uaa: # ca_file: /path/to/ca全局代理 为组件配置
http代理 , 组件不需要通过http代理相互连接。如果要禁用代理 , 请从“components”中删除组件 , 如果要使用代理进行复制 , 必须启用代理。如果要禁用代理 , 请将域添加到“no_proxy”字段后面 ,proxy: http_proxy: https_proxy: no_proxy: components: - core - jobservice - trivy启用指标采集组件
是否启用指标采集组件来收集
Harbor相关的指标 , 默认未启用 , 该部分配置主要用来监控Harbor的metric: enabled: true port: 9090 path: /metrics
3.4 安装
3.4.1 安装 harbor 2.10
3.4.1.1 安装docker环境
修改主机名
[root@centos7 ~]# hostnamectl set-hostname harbor [root@centos7 ~]# cat /etc/hosts 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 [root@centos7 ~]# echo "192.168.194.130 harbor" >> /etc/hosts [root@centos7 ~]# echo "192.168.194.146 harbor2" >> /etc/hosts [root@centos7 ~]# cat /etc/hosts 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 192.168.194.130 harbor 192.168.194.146 harbor2配置
yum源[root@centos7 ~]# sed -e "s|^mirrorlist=|#mirrorlist=|g" \ -e "s|^#baseurl=http://mirror.centos.org/centos/\$releasever|baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos-vault/7.9.2009|g" \ -e "s|^#baseurl=http://mirror.centos.org/\$contentdir/\$releasever|baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos-vault/7.9.2009|g" \ -i.bak \ /etc/yum.repos.d/CentOS-*.repo [root@centos7 ~]# yum clean all Loaded plugins: fastestmirror Repodata is over 2 weeks old. Install yum-cron? Or run: yum makecache fast Cleaning repos: base extras updates Cleaning up list of fastest mirrors [root@centos7 ~]# yum makecache [root@centos7 ~]# yum install -y yum-utils [root@centos7 ~]# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo Loaded plugins: fastestmirror adding repo from: https://download.docker.com/linux/centos/docker-ce.repo grabbing file https://download.docker.com/linux/centos/docker-ce.repo to /etc/yum.repos.d/docker-ce.repo repo saved to /etc/yum.repos.d/docker-ce.repo [root@centos7 ~]# sed -i 's+https://download.docker.com+https://mirrors.tuna.tsinghua.edu.cn/docker-ce+' /etc/yum.repos.d/docker-ce.repo安装并启动
docker[root@centos7 ~]# yum install docker-ce -y [root@centos7 ~]# systemctl enable --now docker Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service. [root@centos7 ~]# systemctl status docker ● docker.service - Docker Application Container Engine Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled) Active: active (running) since Thu 2024-02-22 04:10:36 CST; 11s ago Docs: https://docs.docker.com Main PID: 1705 (dockerd) Tasks: 8 Memory: 31.0M CGroup: /system.slice/docker.service └─1705 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock Feb 22 04:10:36 centos7 systemd[1]: Starting Docker Application Container Engine... Feb 22 04:10:36 centos7 dockerd[1705]: time="2024-02-22T04:10:36.182758537+08:00" level=info msg="Starting up" Feb 22 04:10:36 centos7 dockerd[1705]: time="2024-02-22T04:10:36.285107434+08:00" level=info msg="Loading containers: start." Feb 22 04:10:36 centos7 dockerd[1705]: time="2024-02-22T04:10:36.497751694+08:00" level=info msg="Loading containers: done." Feb 22 04:10:36 centos7 dockerd[1705]: time="2024-02-22T04:10:36.509903704+08:00" level=info msg="Docker daemon" commit=f417435 containerd-snapshotter=false storage-driver=overlay2 version=25.0.3 Feb 22 04:10:36 centos7 dockerd[1705]: time="2024-02-22T04:10:36.510017164+08:00" level=info msg="Daemon has completed initialization" Feb 22 04:10:36 centos7 dockerd[1705]: time="2024-02-22T04:10:36.550885370+08:00" level=info msg="API listen on /run/docker.sock" Feb 22 04:10:36 centos7 systemd[1]: Started Docker Application Container Engine.安装
docker-compose(主要用于harbor安装和管理)[root@centos7 ~]# yum install -y epel-release [root@centos7 ~]# yum install -y docker-compose
3.4.1.2 安装harbor环境
下载
harbor离线安装包。官方网站下载地址: Tags · goharbor/harbor · GitHub
harbor2.10下载地址 : https://github.com/goharbor/harbor/releases/download/v2.10.0/harbor-offline-installer-v2.10.0.tgz
由于下载的git网站在外网, 推荐使用GitHub Proxy 代理加速 (ghproxy.com) 文件下载加速网站
解压安装包
[root@centos7 ~]# tar -xvf harbor-offline-installer-v2.10.0_3.tgz -C /usr/local/src/ harbor/harbor.v2.10.0.tar.gz harbor/prepare harbor/LICENSE harbor/install.sh harbor/common.sh harbor/harbor.yml.tmpl切换到解压目录
[root@centos7 ~]# cd /usr/local/src/harbor/修改配置文件 , 该配置文件中包含主机名设置, 端口 , https目录等各种服务的配置信息。此次主要修改主机名, 以及注释掉https相关内容
[root@centos7 harbor]# cp harbor.yml.tmpl harbor.yml [root@centos7 harbor]# vi harbor.yml [root@centos7 harbor]# cat harbor.yml # Configuration file of Harbor # The IP address or hostname to access admin UI and registry service. # DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients. hostname: harbor # http related config http: # port for http, default is 80. If https enabled, this port will redirect to https port port: 80 # https related config #https: # https port for harbor, default is 443 # port: 443 # The path of cert and key files for nginx # certificate: /your/certificate/path # private_key: /your/private/key/path
运行
prepare脚本harbor prepare文件主要用于准备 Harbor 环境 , 具体来说 , 它的功能包括 :准备环境 :
harbor prepare会检查你的系统环境 , 包括检查Docker和Docker Compose是否已安装 , 以及加载Harbor镜像等。准备 Harbor 配置文件 :
harbor prepare会将harbor.yml配置文件的内容注入到各个组件的配置文件中。应用配置 : 在你修改了
Harbor的配置文件后 , 需要运行harbor prepare来应用新的配置。
总的来说 ,
harbor prepare的主要功能就是为Harbor的运行做准备工作 , 包括环境检查、配置文件的准备和应用等。[root@centos7 harbor]# ./prepare prepare base dir is set to /usr/local/src/harbor Unable to find image 'goharbor/prepare:v2.10.0' locally v2.10.0: Pulling from goharbor/prepare 577f92c8b679: Pull complete a0d0953b0d98: Pull complete ee7af4211aa2: Pull complete 4da360042325: Pull complete 43e5fe5edbe4: Pull complete cd55cc46f080: Pull complete baeb9599408e: Pull complete a0d5489f51f3: Pull complete 997317a00013: Pull complete bb6dae348e49: Pull complete Digest: sha256:d5f6c4a87e97323214aef195507737ba9c9b3651aece517015b83012667f6e95 Status: Downloaded newer image for goharbor/prepare:v2.10.0 WARNING:root:WARNING: HTTP protocol is insecure. Harbor will deprecate http protocol in the future. Please make sure to upgrade to https Generated configuration file: /config/portal/nginx.conf Generated configuration file: /config/log/logrotate.conf Generated configuration file: /config/log/rsyslog_docker.conf Generated configuration file: /config/nginx/nginx.conf Generated configuration file: /config/core/env Generated configuration file: /config/core/app.conf Generated configuration file: /config/registry/config.yml Generated configuration file: /config/registryctl/env Generated configuration file: /config/registryctl/config.yml Generated configuration file: /config/db/env Generated configuration file: /config/jobservice/env Generated configuration file: /config/jobservice/config.yml Generated and saved secret to file: /data/secret/keys/secretkey Successfully called func: create_root_cert Generated configuration file: /compose_location/docker-compose.yml Clean up the input dir运行安装脚本
./install.sh[root@centos7 harbor]# ./install.sh [Step 0]: checking if docker is installed ... Note: docker version: 25.0.3 [Step 1]: checking docker-compose is installed ... Note: Docker Compose version v2.24.5 [Step 2]: loading Harbor images ... 7570a78aea36: Loading layer [==================================================>] 9.08MB/9.08MB 04774e0e84f2: Loading layer [==================================================>] 4.096kB/4.096kB 8a42710a9f5a: Loading layer [==================================================>] 3.072kB/3.072kB f1990e77f8e5: Loading layer [==================================================>] 197MB/197MB 172f5af926be: Loading layer [==================================================>] 17.6MB/17.6MB 2bef5dd17a10: Loading layer [==================================================>] 215.4MB/215.4MB Loaded image: goharbor/trivy-adapter-photon:v2.10.0 Loaded image: goharbor/prepare:v2.10.0 a36cb8a4e510: Loading layer [==================================================>] 126.1MB/126.1MB fd72ef63aacc: Loading layer [==================================================>] 3.584kB/3.584kB 162d9960a2b9: Loading layer [==================================================>] 3.072kB/3.072kB b71508b0d586: Loading layer [==================================================>] 2.56kB/2.56kB 18c685e189cb: Loading layer [==================================================>] 3.072kB/3.072kB 84690af2e82b: Loading layer [==================================================>] 3.584kB/3.584kB 8d7eb73e8207: Loading layer [==================================================>] 20.48kB/20.48kB Loaded image: goharbor/harbor-log:v2.10.0 001da4979db8: Loading layer [==================================================>] 8.562MB/8.562MB 275281f671bf: Loading layer [==================================================>] 4.096kB/4.096kB f6856e2d539f: Loading layer [==================================================>] 17.4MB/17.4MB 557317f3c1c5: Loading layer [==================================================>] 3.072kB/3.072kB 942b8c3060c6: Loading layer [==================================================>] 32.81MB/32.81MB b2bc30e737e7: Loading layer [==================================================>] 51MB/51MB Loaded image: goharbor/harbor-registryctl:v2.10.0 d5e5478da184: Loading layer [==================================================>] 116.8MB/116.8MB Loaded image: goharbor/nginx-photon:v2.10.0 74a7f6a8de8c: Loading layer [==================================================>] 11.58MB/11.58MB 46523ccaf371: Loading layer [==================================================>] 27.6MB/27.6MB 3cea6b428022: Loading layer [==================================================>] 4.608kB/4.608kB 9815d4ae0f06: Loading layer [==================================================>] 28.39MB/28.39MB Loaded image: goharbor/harbor-exporter:v2.10.0 7d7e15ae8ca1: Loading layer [==================================================>] 15.93MB/15.93MB e13b0ff80947: Loading layer [==================================================>] 111.8MB/111.8MB da74a4230588: Loading layer [==================================================>] 3.072kB/3.072kB 89240f6f343b: Loading layer [==================================================>] 59.9kB/59.9kB 7b00214da46a: Loading layer [==================================================>] 61.95kB/61.95kB Loaded image: goharbor/redis-photon:v2.10.0 345fdbd05997: Loading layer [==================================================>] 8.562MB/8.562MB 5c4443929555: Loading layer [==================================================>] 4.096kB/4.096kB 605c9788ef17: Loading layer [==================================================>] 3.072kB/3.072kB b2ab56de0e45: Loading layer [==================================================>] 17.4MB/17.4MB 950eb2734789: Loading layer [==================================================>] 18.19MB/18.19MB Loaded image: goharbor/registry-photon:v2.10.0 7693164e30e0: Loading layer [==================================================>] 116.8MB/116.8MB 6b50b5d516aa: Loading layer [==================================================>] 6.531MB/6.531MB 5aea217650ad: Loading layer [==================================================>] 246.8kB/246.8kB 0b3de92ff70b: Loading layer [==================================================>] 1.477MB/1.477MB Loaded image: goharbor/harbor-portal:v2.10.0 b79ac58f353c: Loading layer [==================================================>] 11.58MB/11.58MB 18c4c015e339: Loading layer [==================================================>] 3.584kB/3.584kB c6eef6a39935: Loading layer [==================================================>] 2.56kB/2.56kB 03db56130352: Loading layer [==================================================>] 58.57MB/58.57MB 78c9748f2d29: Loading layer [==================================================>] 5.632kB/5.632kB e1732f90232a: Loading layer [==================================================>] 123.4kB/123.4kB 6b733e4833c8: Loading layer [==================================================>] 80.38kB/80.38kB fe6828cc147a: Loading layer [==================================================>] 59.56MB/59.56MB 8e1349c44768: Loading layer [==================================================>] 2.56kB/2.56kB Loaded image: goharbor/harbor-core:v2.10.0 02b0385778eb: Loading layer [==================================================>] 15.93MB/15.93MB ac2be26232ee: Loading layer [==================================================>] 175MB/175MB 1e3e9dba5eaa: Loading layer [==================================================>] 25.47MB/25.47MB c7fa85d79c1c: Loading layer [==================================================>] 18.14MB/18.14MB f9ef6596c7ce: Loading layer [==================================================>] 5.12kB/5.12kB e35036cc139f: Loading layer [==================================================>] 6.144kB/6.144kB 0cb7ded8041a: Loading layer [==================================================>] 3.072kB/3.072kB 5d178abacde0: Loading layer [==================================================>] 2.048kB/2.048kB 67fe09fd709b: Loading layer [==================================================>] 2.56kB/2.56kB d0b4aac335c4: Loading layer [==================================================>] 7.68kB/7.68kB Loaded image: goharbor/harbor-db:v2.10.0 fced8697b2c7: Loading layer [==================================================>] 11.58MB/11.58MB af968b902c26: Loading layer [==================================================>] 3.584kB/3.584kB 7caaebde63bc: Loading layer [==================================================>] 2.56kB/2.56kB d6e3c7a6ef36: Loading layer [==================================================>] 44.96MB/44.96MB bc8a8ef654ea: Loading layer [==================================================>] 45.75MB/45.75MB Loaded image: goharbor/harbor-jobservice:v2.10.0 [Step 3]: preparing environment ... [Step 4]: preparing harbor configs ... prepare base dir is set to /usr/local/src/harbor WARNING:root:WARNING: HTTP protocol is insecure. Harbor will deprecate http protocol in the future. Please make sure to upgrade to https Clearing the configuration file: /config/portal/nginx.conf Clearing the configuration file: /config/log/logrotate.conf Clearing the configuration file: /config/log/rsyslog_docker.conf Clearing the configuration file: /config/nginx/nginx.conf Clearing the configuration file: /config/core/env Clearing the configuration file: /config/core/app.conf Clearing the configuration file: /config/registry/passwd Clearing the configuration file: /config/registry/config.yml Clearing the configuration file: /config/registryctl/env Clearing the configuration file: /config/registryctl/config.yml Clearing the configuration file: /config/db/env Clearing the configuration file: /config/jobservice/env Clearing the configuration file: /config/jobservice/config.yml Generated configuration file: /config/portal/nginx.conf Generated configuration file: /config/log/logrotate.conf Generated configuration file: /config/log/rsyslog_docker.conf Generated configuration file: /config/nginx/nginx.conf Generated configuration file: /config/core/env Generated configuration file: /config/core/app.conf Generated configuration file: /config/registry/config.yml Generated configuration file: /config/registryctl/env Generated configuration file: /config/registryctl/config.yml Generated configuration file: /config/db/env Generated configuration file: /config/jobservice/env Generated configuration file: /config/jobservice/config.yml loaded secret from file: /data/secret/keys/secretkey Generated configuration file: /compose_location/docker-compose.yml Clean up the input dir Note: stopping existing Harbor instance ... [Step 5]: starting Harbor ... [+] Running 9/10 ⠴ Network harbor_harbor Created 2.6s ✔ Container harbor-log Started 1.1s ✔ Container registry Started 1.1s ✔ Container harbor-db Started 1.3s ✔ Container redis Started 1.4s ✔ Container harbor-portal Started 1.5s ✔ Container registryctl Started 1.6s ✔ Container harbor-core Started 1.7s ✔ Container nginx Started 2.1s ✔ Container harbor-jobservice Started 2.2s ✔ ----Harbor has been installed and started successfully.----编辑对应的
systemd脚本 , 并设置为自启动[root@harbor ~]# systemctl cat harbor # /usr/lib/systemd/system/harbor.service [Unit] Description=Harbor After=docker.service systemd-networkd.service systemd-resolved.service Requires=docker.service Documentation=http://github.com/vmware/harbor [Service] Type=simple Restart=on-failure RestartSec=5 # docker-compose.yml文件为harbor启动文件 , 使用绝对路径 ExecStart=/usr/bin/docker-compose -f /usr/local/src/harbor/docker-compose.yml up ExecStop=/usr/bin/docker-compose -f /usr/local/src/harbor/docker-compose.yml down [Install] WantedBy=multi-user.target [root@centos7 harbor]# systemctl enable harbor Created symlink from /etc/systemd/system/multi-user.target.wants/harbor.service to /usr/lib/systemd/system/harbor.service.
3.4.2 安装harbor1.7
安装docker的步骤与上一章节一样 , 这里主要讲关于harbor安装流程
解压安装包
[root@localhost ~]# tar -xf harbor-offline-installer-v1.7.5.tgz -C /usr/local/src/切换到解压目录
[root@localhost ~]# cd /usr/local/src/harbor/ [root@localhost harbor]#修改
harbor.cfg文件, 修改hostname主机名,同时注释掉ssl_cert、ssl_cert_key的https两行
直接运行安装脚本
[root@localhost harbor]# ./install.sh
3.5 使用说明
3.5.1 基础使用
通过浏览器直接输入IP登录 , 首页默认的用户名密码为
admin/Harbor12345, 这个密码可以通过harbor.yml配置文件的harbor_admin_password: Harbor12345修改初始密码。
创建新项目


创建新用户


3.5.2 远程复制
仓库管理中设置新建目标 , 点击测试连接之后直接点击确定


复制管理中点击新建规则 , 之后点击确认。当镜像上传之后, 会直接传递到另外一个harbor节点中


3.5.3 镜像处理
上传镜像
[root@harbor ~]# docker login 192.168.194.130 Authenticating with existing credentials... WARNING! Your password will be stored unencrypted in /root/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store Login Succeeded [root@harbor ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu latest 3db8720ecbf5 9 days ago 77.9MB goharbor/harbor-exporter v2.10.0 1d3fdd3d102d 2 months ago 106MB goharbor/redis-photon v2.10.0 5083850c5206 2 months ago 165MB goharbor/trivy-adapter-photon v2.10.0 5873942a56be 2 months ago 478MB goharbor/harbor-registryctl v2.10.0 7a3b7d7d972c 2 months ago 149MB goharbor/registry-photon v2.10.0 9baecb934ded 2 months ago 83.4MB goharbor/nginx-photon v2.10.0 d1010aed334c 2 months ago 154MB goharbor/harbor-log v2.10.0 518f000ddf00 2 months ago 164MB goharbor/harbor-jobservice v2.10.0 4960b98063d3 2 months ago 140MB goharbor/harbor-core v2.10.0 00c9a2f5729c 2 months ago 168MB goharbor/harbor-portal v2.10.0 f3d83a4e7733 2 months ago 163MB goharbor/harbor-db v2.10.0 8215768668f6 2 months ago 269MB goharbor/prepare v2.10.0 2ac2fd8f7595 2 months ago 208MB krystism/harborclient latest 9ba187c8f4fa 6 years ago 730MB [root@harbor ~]# docker tag ubuntu:latest 192.168.194.130/harbor/ubuntu:latest [root@harbor ~]# docker push 192.168.194.130/harbor/ubuntu:latest The push refers to repository [192.168.194.130/harbor/ubuntu] d101c9453715: Pushed latest: digest: sha256:e2f5f8a6affed3c8779238f84534035acbe2f53dcc404d7cbbe0ecdf4ac23999 size: 529由于配置了远程复制,所以可以在另外一个节点中看到复制过来的镜像

下载镜像
[root@harbor2 harbor]# docker login 192.168.194.130 Username: admin Password: WARNING! Your password will be stored unencrypted in /root/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store Login Succeeded [root@harbor2 harbor]# docker pull 192.168.194.130/harbor/ubuntu:latest latest: Pulling from harbor/ubuntu d66d6a6a3687: Pull complete Digest: sha256:e2f5f8a6affed3c8779238f84534035acbe2f53dcc404d7cbbe0ecdf4ac23999 Status: Downloaded newer image for 192.168.194.130/harbor/ubuntu:latest 192.168.194.130/harbor/ubuntu:latest [root@harbor2 harbor]#
3.5.4 数据备份
3.5.4.1. Harbor中的数据
Harbor所依赖的数据可分为两大类 : 临时数据和持久化数据
临时数据 在
Harbor安装期间通过配置文件生成的数据 , 主要是Harbor组件所依赖的配置文件和环境变量。这些数据通常在
Harbor安装目录的common目录下( 如果Harbor是通过源代码安装的 , 则这些数据在源码目录的"make/common"目录下 )在
Harbor各组件启动时会被挂载到对应的容器中。虽然临时数据对服务的顺利运行至关重要 , 但是安装程序每次都会读取Harbor配置文件重新生成一份临时数据 , 所以我们仅需备份配置文件即可 , 不必将整个common目录全部备份持久化数据 存放在数据目录配置项下( 即配置文件中
data_volume项所配置的值 ) , 这些数据主要包括Harbor的数据库数据、Artifacts数据、Redis数据、Chart数据 , 以及Harbor各个组件所依赖的运行时数据data目录包含的文件夹和相应的作用如下ca_download: 存放用户访问Harbor时所需的CAcert:Harbor启动HTTPS服务时所需的证书和密钥。chart_storage: 存放Helm v2版本的Chart数据。database: 存放数据库的目录 ,Harbor、Clair和Notary数据库的数据都在此目录下。job_logs: 存放JobService的日志信息。redis: 存放Redis数据。registry: 存放OCI Artifacts数据( 对于大部分用户来说是镜像数据 ) 。secret: 存放Harbor内部组件通信所需的加密信息。
注意 :
Harbor在启动时需要挂载这些目录下的文件 , 在Harbor各组件的容器中除了log容器( 依赖logrotate, 必须以root权限运行 )其他容器都是以非
root用户身份运行的 , 这些文件的用户组信息及权限信息与其在容器中的信息不匹配 , 在容器中就会发生读取权限相关的错误所以在主机上需要将这些目录和文件设置成容器的指定用户和用户组 , 这些文件的用户和权限信息主要有如下两类 :
数据库和Redis : 以
999:999的用户组运行容器 , 即harbor-db组件和harbor-redis组件 ; Harbor的其他容器 : 以10000:10000的用户组运行容器
3.5.4.2 Harbor备份
备份harbor安装目录
cp -rf /usr/local/harbor /usr/local/harbor_bakharbor.yml中data_volume值为 持久化数据目录
cp -rf /data /data/harbor_data_back3.5.5 版本更新
软件的版本升级一般分为两个阶段 : 现有Harbor实例的数据迁移和Harbor软件升级。
3.5.5.1. 数据迁移
在升级Harbor版本之前 , 我们首先需要对现有的数据进行迁移 , 以适配新的版本。需要迁移的数据有两类 : 数据库模式 ( schema ) 和配置文件数据
数据库模式 , 也就是数据库中表的结构 , 每次新版本发布时新的功能及对老功能、代码的重构都会导致数据库模型的变更 , 因此几乎每次升级都需要升级数据库模式
配置文件数据 , 是指 Harbor 组件的配置文件 , 在部分新功能或者新的组件出现时 , 都需要在配置文件中新增其参数 ; 在老功能、组件重构或者废弃时 , 也会对配置文件进行更新 如果升级时不做数据迁移 , 则会导致数据与新版本不兼容而引发问题。而数据迁移是一个高风险操作 , 操作中出现问题会造成数据丢失等严重后果 , 所以一定要先进行数据备份 , 主要就是数据库的数据导出备份。
数据库迁移 其实每次启动Harbor实例时 , 它的数据库模式都是自动升级的
其原理为 : Harbor在每次启动时都会调用第三方库 "golang-migrate" , 它会检测当前数据库模式的版本 , 如果实例的版本比当前数据库的版本更高 , 则会自动升级。
配置升级 因为数据库升级是自动完成 , 用户其实只需操作配置文件的迁移即可。
需要用户手动执行升级配置文件的命令行工具包。此工具包与Harbor一同发布 , 被包含在"goharbor/prepare"镜像中
Harbor的升级遵循一个约定 : 小版本的升级不会涉及配置文件和数据库的修改 , 大版本升级不能大于2个版本号 , 如2.3可以直接升级到2.5
用户可以在Harbor的离线安装包导入 , 也可以在Docker Hub上获取 :
docker pull goharbor/prepare:version运行以下命令进行配置文件迁移 :
docker run -v /:/hostfs goharbor/prepare:v2.5.0 migrate --input /usr/local/harbor/harbor-2.4.2.yml --output /usr/local/harbor/harbor-2.5.0.yml --target 2.5.0
# 其中 , “-v /:/hostfs” 是将主机的根目录 “/” 挂载到容器中的“/hostfs”目录。
# 因为命令是运行在容器中的 , 而文件是在宿主机上的 , 为了能在容器中访问到指定的文件 , 需要这样挂载 , 之后 prepare 会对 “/hostfs” 这个文件做特殊处理 , 使得在容器里也能访问主机上的指定文件
migrate 命令有如下3个参数。
--input ( 缩写形式为“-i” ) : 是输入文件的绝对路径 , 也就是需要升级的原配置文件。
--output ( 缩写形式为“-o” ) : 是输出文件的绝对路径 , 也是升级后的配置文件 , 是可选参数 , 如果取默认值 , 则升级后的文件会被写回输入文件中。
--target ( 缩写形式为“-t” ) : 是目标版本 , 也就是打算升级到的版本 , 也是可选参数 , 如果取默认值 , 则版本为此工具发布时所支持的最新版
# 所以上命令是将 Harbor 2.4.2 的配置文件 “/usr/local/harbor/harbor-2.4.2.yml” 升级到 Harbor 2.5.0 版本 , 并且保存到 “/usr/local/harbor/harbor-2.5.0.yml” 目录下。若使用缩写和默认的参数 , 则命令可以简化如下 :
docker run -v /:/hostfs goharbor/prepare:v2.5.0 migrate -i /usr/local/harbor/harbor-2.4.2.yml3.5.5.2.完整升级操作
停止
harbordocker-compose down -v备份旧版本 harbor 安装路径
mv harbor /path/harbor_install_backup备份数据
cp -rp /data/database /habor_data_backup数据迁移
docker run -v /:/hostfs goharbor/prepare:v2.5.0 migrate -i ${原harbor.yml路径}使用新配置文件安装
Harbortar zxf ${新版本Harbor离线包} cd ${新版本Harbor解压的文件夹} cp ${新生成的harbor.yml} ${新版本Harbor目录} ./install.sh升级后
harbor-db无法启动 通过修改docker-compose.ymlprivileged: true #在 postgresql 下 添加
3.6 问题整理
3.6.1 安装测试过程中问题
Error response from daemon: Get "https://192.168.194.130/v2/": dial tcp 192.168.194.130:443: connect: connection refused客户端登录已经安装好的harbor节点过程中提示如上内容
这个问题主要报错的原因没有配置证书认证不安全的原因导致的。
主要有三种处理方式,
直接配置https服务
在
/etc/docker/daemon.json中添加如下内容[root@centos7 harbor]# cat /etc/docker/daemon.json { "insecure-registries" : [ "192.168.194.130", "192.168.194.146" ] }在docker.service服务中的启动脚本中添加如下内容
[root@centos7 harbor]# cat /usr/lib/systemd/system/docker.service ... ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --insecure-registry 192.168.194.130 --insecure-registry 192.168.194.146 ...
harbor1.7安装过程中提示如下报错ERROR: client version 1.22 is too old. Minimum supported API version is 1.24, please upgrade your client to a newer version
处理方式可以直接将
docker-compose.yml的version: '2'修改为version: '2.1'
harbor2.10版本中, 系统管理-> 仓库管理 , 新建目标的过程中测试连接总是失败。
查看
harbor-core的日志 , 日志显示无法解析主机, 提示no such host:2024-02-22T01:51:47Z [ERROR] [/pkg/reg/adapter/native/adapter.go:126]: failed to ping registry http://192.168.194.146: Get "http://harbor2/service/token?service=harbor-registry": dial tcp: lookup harbor2 on 127.0.0.11:53: no such host 2024-02-22T01:53:06Z [ERROR] [/pkg/reg/adapter/native/adapter.go:126]: failed to ping registry http://192.168.194.146: Get "http://harbor2/service/token?service=harbor-registry": dial tcp: lookup harbor2 on 127.0.0.11:53: no such host原因就是
harbor-core容器无法解析主机名。[root@harbor ~]# docker exec -u root -it harbor-core ping harbor2 ping: harbor2: Name or service not known在主机
harbor的harbor-core容器中添加主机记录 :[root@harbor ~]# docker exec -u root -it harbor-core bash root [ /harbor ]# echo "192.168.194.146 harbor2" >> /etc/hosts root [ /harbor ]# exit同样 , 在主机
harbor2的harbor-core容器中也添加主机记录 :[root@harbor2 ~]# docker exec -u root -it harbor-core bash root [ /harbor ]# echo "192.168.194.130 harbor" >> /etc/hosts root [ /harbor ]# exit此时测试连接成功

上面的方法只能临时解决问题 , 当容器重新启动后主机记录就会丢失。所以需要修改
docker-compose持久化保存。修改
harbor的docker-compose.yml。extra_hosts的作用就是在容器的/etc/hosts中添加记录。 、# 在harbor的工作目录下 # 停止容器 $ docker-compose down # 修改docker-compose.yml文件,在core和jobservice的最后添加extra_hosts。格式和/etc/hosts是相反的。 # core对应harbor-core容器 , jobservice对应harbor-jobservice容器 # harbor-jobservice负责镜像复制工作 , 如果不修改后续的复制会失败。 $ vim docker-compose.yml core: image: goharbor/harbor-core:v2.5.5 container_name: harbor-core ... ... extra_hosts: - "harbor:192.168.194.130" - "harbor2:192.168.194.146" jobservice: image: goharbor/harbor-jobservice:v2.5.5 container_name: harbor-jobservice ... ... extra_hosts: - "harbor:192.168.194.130" - "harbor2:192.168.194.146" # 启动容器 $ docker-compose up -d #确认有给你刚才添加的主机记录 [root@harbor ~]# docker exec -u root -it harbor-core cat /etc/hosts 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters 192.168.194.130 harbor 192.168.194.146 harbor2 172.18.0.8 8fcb3f9acc8e在
harbor2上也修改docker-compose.yml:$ vim docker-compose.yml core: image: goharbor/harbor-core:v2.5.5 container_name: harbor-core ... ... extra_hosts: - "harbor:192.168.194.130" - "harbor2:192.168.194.146" jobservice: image: goharbor/harbor-jobservice:v2.5.5 container_name: harbor-jobservice ... ... extra_hosts: - "harbor:192.168.194.130" - "harbor2:192.168.194.146"
3.6.2 无法通过admin/Harbor1234登录
造成原因 :
Harbor的password_version加密版本 , 重置密码并将sha256改成sha1;没有
https却在配置的extend_url保留了https, 导致登录时信息加密 ;
解决方案 :
对于原因1 , 可修改
harbor.yml的extend_url、将协议改成http对于原因2 , 可以根据以下步骤进行密码重置。具体步骤 :
通过docker ps找到harbor-db
docker ps通过docker ID进入命令行
docker exec -it 1116e210e8b5 /bin/bash命令行登入数据库
// 键入命令登录
psql -h postgresql -d postgres -U postgres
// 输入密码
root123连接数据库
\c registry查询harbor_user信息
SELECT * FROM harbor_user;通过sql对web UI账户进行密码重置
update harbor_user set password='a71a7d0df981a61cbb53a97ed8d78f3e', salt='ah3fdh5b7yxepalg9z45bu8zb36sszmr', password_version='sha1' where username='admin';退出数据库、并重启harbor-core
// 退出 harbor-db
exit
// 重启 harbor-core
docker restart harbor-core重新尝试使用admin/Harbor12345去登录。
参考链接
9.Docker容器镜像仓库存储原理(前世今身)与搬运技巧 (qq.com)
10.Docker容器镜像Registry仓库安全构建与GC回收 (qq.com)
最佳镜像搬运工 Skopeo 指南 ( 1) -阿里云开发者社区 (aliyun.com)
最佳镜像搬运工 Skopeo 指南 ( 2) -阿里云开发者社区 (aliyun.com)
Docker私有仓库Registry服务器配置SSL证书以支持HTTPS协议 (qq.com)
Docker私有仓库Registry服务器开启认证配置 (qq.com)
Docker之四----搭建私有docker仓库 - 一叶知秋~~ - 博客园 (cnblogs.com)
Harbor新建目标时测试连接失败 ( no such host) - fengkm - 博客园 (cnblogs.com)
Harbor配置优化 , 配置文件深度解析!51CTO博客harbor安装与配置