2-4-1-镜像仓库

熊 0 阅读 容器

1. 上传至互联网仓库

1.1 命令说明

1.1.1 docker login

登录Docker镜像仓库 , 默认是Docker hub

  • 语法 : docker login [OPTIONS] [SERVER]

  • OPTIONS

    • -p : 密码

    • -u : 用户名

    • --password-stdin : 通过STDIN提供密码

示例

通过命令行方式输入账号密码

docker login
docker-register-login-1

通过参数输入用户名密码

docker login -u poloyy -p ***
docker-register-login-2

这种方式不安全 , 密码会出现在shell的历史记录或日志文件中

通过STDIN输入密码

先将密码存储在pwd.txt文件中

cat pwd.txt | docker login -u poloyy --password-stdin
docker-register-login-3

可以解决上一个例子的不安全问题

1.1.2 docker logout

退出Docker镜像仓库 , 默认是Docker hub

  • 语法 : docker logout [SERVER]

例子

docker logout
docker-register-logout-1

1.1.3 docker push

将本地的镜像上传到镜像仓库 , 要先登录到镜像仓库

上传本地镜像的前置操作

  1. 注册Docker hub账号

  2. Linux登录Docker hub账号

  3. 给镜像设置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/ , 实现镜像夸主机备份。如果需要使用该官方镜像库, 需要访问网页, 注册对应账户。注册完成之后进行如下步骤

  1. 在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
  2. 查看认证信息

    登录成功之后会在当前目录生成一个隐藏文件用于保持登录认证信息

    [root@centos7 ~]# cat /root/.docker/config.json 
    {
    	"auths": {
    		"https://index.docker.io/v1/": {
    			"auth": "eGlvbmd4aW9uZzEyMzQ6eGlvbmdaSFVBTmcxLg=="
    		}
    	}
  3. 给镜像做tag并开始上传

    [root@centos7 ~]# docker tag ubuntu:nginx docker.io/xiongxiong1234/ubuntu:nginx
    [root@centos7 ~]# docker push docker.io/xiongxiong1234/ubuntu:nginx
  4. 登录到docker镜像官网查看信息

    image-20240212180111094

1.2 上传到阿里云

将本地镜像上传至阿里云,实现镜像备份与统一分发的功能

https://cr.console.aliyun.com

注册账户、创建namespace、创建仓库、修改镜像tag及上传镜像

image-20240212235435382
  1. 在虚拟机中登录阿里云账号用户。密码为开通服务时设置的密码。

    [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
  2. 从Registry中拉取镜像

    $ docker pull registry.cn-hangzhou.aliyuncs.com/tomikes/ubuntu:[镜像版本号]

  3. 将镜像推送到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
    335ea763a2fa4508ebf3ec6f8b11f3b620a11bdcaa0ab43176b781427e0beee6
  • Step2.上传本地镜像到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: 1360
  • Step3.当我们在本地启动一个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"
          }
       ]
    }

总结思想:

  1. 同一镜像在不同Registry镜像仓库中, 存储的方式、位置和内容完全一样因为它们的Layer digest在仓库中唯一。

    • 通过Registry API获得的两个镜像仓库中相同镜像的manifest信息完全相同。

    • 两个镜像仓库中相同镜像的manifest信息的存储路径和内容完全相同。

    • 两个镜像仓库中相同镜像的blob信息的存储路径和内容完全相同。

  2. 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 registry

2.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:/auth

2.2.3 生产实例

2.2.3.1 有证书配置

描述:此处以实际生产环境为例进行Docker Registry私有仓库搭建;

  1. 服务器拉取Docker Registry镜像并创建Autho认证的.htpasswd文件;

    $ docker pull registry:2
    # Digest: sha256:8be26f81ffea54106bae012c6f349df70f4d5e7e2ec01b143c46e2c03b9e551d
    # Status: Downloaded newer image for registry:2
    # docker.io/library/registry:2
    
    $ mkdir -vp /opt/registry/ && cd $_
    htpasswd -Bbn weiyigeek 123456                  # -n 不更新文件输入到终端标准输出
    htpasswd -bB -c auth.htpasswd weiyigeek 123456  # -c 创建存储认证字符串 , -b 强制加密密码 , -B 在命令行中接收密码

    Tips:在Push或者Delete镜像是通过HTTP请求Registry的API完成的 , 每个请求都需要一个Token才能完成操作 , 而此Token需要使用auth文件(明文用户/密码编码)来进行鉴权;

  2. Docker Registry自签证书 描述:如果Registry仓库使用TLS认证时必须带有证书 , 当外部访问该Registry仓库时候提供安全通道 , 我们可以在认证机构购买签名证书或者自签证书也可以; 使用OpenSSL来生成CA ( 证书授权中心 , certificate authority) 、 中级CA ( intermediate CA) 和末端证书 (end certificate) 。包括OCSP、CRL 和CA颁发者Issuer信息、具体颁发和失效日期。

    # 方式1:交互式证书生成
    $openssl req -newkey rsa:4096 -nodes -sha256 -keyout ./certs/domain.key -x509 -days 365 -out ./certs/domain.crt
    
    # 方式2:配置文件方式生成
    cat >ca.conf <<EOF
    [ req ]
    default_bits  = 2048
    distinguished_name = req_distinguished_name
    prompt   = no
    encrypt_key  = no
    x509_extensions  = v3_ca
    [ req_distinguished_name ]
    CN = localhost
    [ CA_default ]
    copy_extensions = copy
    [ alternate_names ]
    DNS.2=localhost
    [ v3_ca ]
    subjectAltName=@alternate_names
    subjectKeyIdentifier=hash
    authorityKeyIdentifier=keyid:always,issuer:always
    basicConstraints = critical,CA:true
    keyUsage=keyCertSign,cRLSign,digitalSignature,keyEncipherment,nonRepudiation
    EOF
    openssl req -days 365 -x509 -config ca.conf -new -keyout certs/domain.key -out certs/domain.crt
  3. 挂载安全认证与自签证书并启动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

  4. 验证registry是否可登录 , 登录后账号密码将会base64存储在/root/.docker/config.json之中 , 为后续使用skopeo的时候做准备;

    $docker login localhost -u weiyigeek -p 123456
    # WARNING! Using --password via the CLI is insecure. Use --password-stdin.
    # WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
    Login Succeeded
    
    cat /root/.docker/config.json
    {
      "auths": {
        "https://index.docker.io/v1/": { "auth": "d2VpxOQ=="},
        "localhost": {"auth": "d2VpeWlnZWVrOjEyMzQ1Ng==" }
      },
      "HttpHeaders": {"User-Agent": "Docker-Client/19.03.9 (linux)"}
    }
    
    # base64 编码
    $echo "d2VpeWlnZWVrOjEyMzQ1Ng==" | base64 -d
    weiyigeek:123456
  5. 上传一个镜像到registry之中

    $docker tag alpine localhost/alpine:latest
    $docker images
    localhost/alpine    latest              a24bb4013296        2 months ago        5.57MB
    $docker push localhost/alpine:latest
    # The push refers to repository [localhost/alpine]
    # 50644c29ef5a: Pushed
    # latest: digest: sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 size: 528

  6. registry查看上传的镜像

    # (1) 查看registry中存储的镜像仓库名称 (注意--cacert参数如果证书未导入到系统中则必须加上)
    $curl --cacert /opt/registry/certs/domain.crt -u 'weiyigeek:123456' -X GET https://localhost/v2/_catalog
    {"repositories":["alpine"]}
  7. 利用skopeo转储镜像到registry之中和操作镜像:

    # (1) 首先信任CA证书 , 根据不同的发行版选择相应的路径和命令行即可 , 为后面skopeo命令使用做准备。
    # CentOS
    update-ca-trust force-enable
    cp certs/domain.crt /etc/pki/ca-trust/source/anchors/localhost.crt
    update-ca-trust
    # Ubuntu
    cp certs/domain.crt /usr/local/share/ca-certificates/localhost.crt
    $ update-ca-certificates
    # Debian
    cp certs/domain.crt /usr/share/ca-certificates/localhost.crt
    echo localhost.crt >> /etc/ca-certificates.conf
    update-ca-certificates
    
    # (2) COPY 镜像到 registry
    skopeo inspect docker://docker.io/alpine
    # 以 localhost/library/alpine:3.10 为例
    # localhost   就是该 registry 的域名或者 URL 
    # library     就是项目名称 project
    # alpine:3.12 就是镜像名和镜像的 tag
    skopeo copy docker://alpine:3.12 docker://localhost/library/alpine:3.12
    # Getting image source signatures
    # Copying blob df20fa9351a1 done
    # Copying config a24bb40132 done
    # Writing manifest to image destination
    # Storing signatures
    
    # (3) 删除registry仓库中的镜像(删除后并不彻底)
    skopeo delete docker://localhost/alpine
    
    # (4) 查看仓库里的镜像
    curl --cacert /opt/registry/certs/domain.crt -u 'weiyigeek:123456' -X GET https://localhost/v2/_catalog
    {"repositories":["library/alpine"]}
    skopeo inspect docker://localhost/library/alpine:3.12
    {
        "Name": "localhost/library/alpine",
        "Digest": "sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65",
        "RepoTags": ["3.12"],
        "Created": "2020-05-29T21:19:46.363518345Z",
        "DockerVersion": "18.09.7",
        "Labels": null,
        "Architecture": "amd64",
        "Os": "linux",
        "Layers": ["sha256:df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c"],
        "Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]
    }

注意事项:

  1. 证书颁发者可以用中间证书提供给您。在这种情况下 , 你必须用中间证书串连您的证书形成的证书捆绑。

    cat domain.crt intermediate-certificates.pem > certs/domain.crt
  2. Let’s Encrypt:https://letsencrypt.org/how-it-works/

2.2.3.2 无证书配置

  1. 准备用户名密码

    [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 在命令行中接收密码
  2. 创建对应容器

    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
  3. 测试登录

    image-20240214172728660

    解决方法:

    直接编辑对应的systemd服务文件, 在ExecStart后直接添加--insecure-registry

    image-20240214173219229
    [root@centos7 ~]# echo "weiyigeek:123456" | base64
    d2VpeWlnZWVrOjEyMzQ1Ngo=
    [root@centos7 ~]# cat /root/.docker/config.json 
    {
    	"auths": {
    		"192.168.194.130:5000": {
    			"auth": "d2VpeWlnZWVrOjEyMzQ1Ng=="
    		}
    	}
    }[root@centos7 ~]# 
    
  4. 修改镜像标签

    [root@centos7 ~]# docker tag alpine:latest 192.168.194.130:5000/weiyigeek/alpine:latest 
  5. 上传镜像

    [root@centos7 ~]# docker push 192.168.194.130:5000/alpine:latest
  6. 使用其他服务器登录镜像并且下载

    客户端服务器类型: CentOS8

    容器软件为:podman

    直接登录会提示以下内容

    image-20240214174302728

    /etc/containers/registries.conf 文件中添加以下内容

    [[registry]]
    insecure = true 
    prefix = "192.168.194.130:5000"
    location = "192.168.194.130:5000"
    image-20240214174456704

    修改文件之后重启服务并登录

    [root@centos8 ~]# systemctl restart podman
    [root@centos8 ~]# podman login 192.168.194.130:5000
    Username: weiyigeek
    Password: 
    Login Succeeded!
  7. 下载镜像

    [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
image-20240214141135751

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/
        a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65
      • tags目录下的文件夹名 , 例如3.10就是镜像的Tag ,在它的子目录下的current/link文件则记录了当前tag指向的 manifest文件的位置; 比如我们的alpine:latest镜像 , 每次push新的latest镜像时current/link都会更新成指向最新镜像的manifest文件。

        $ ls /var/lib/registry/docker/registry/v2/repositories/alpine(镜像名称)/_manifests/tags/3.12(镜像标记)/
        • current 、index # 目前都指向a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 即 镜像 data manifest

  • Blobs

    $ls /var/lib/registry/docker/registry/v2/blobs/sha256/
    a1/ a2/ df/ 
    
    $find /var/lib/registry/docker/registry/v2/ -name "data" -exec ls -sh {} \;
    • image layer : 文件是gzip格式的tar包 , 是镜像层真实内容的tar.gzip 格式存储形式。

      2.7M ./blobs/sha256/df/df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c/data
    • image config : 文件是json格式的 , 它是在构建时候生成的根据DockerFile和宿主机的一些信息; ( 记录了"rootfs"."diff_ids" )

      4.0K ./blobs/sha256/a2/a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e/data
    • data manifest : 文件json文件格式的 , 存放该镜像layer和image config文件的索引。(镜像拉取首先需拉取此文件)

      4.0K ./blobs/sha256/a1/a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65/data

docker-register-directory-structure-1

此时我们可以再往Registry中COPY一个镜像方便后面进行对比分析:

$skopeo copy docker://debian:buster-slim docker://localhost/library/debian:buster-slim
$curl --cacert /opt/registry/certs/domain.crt -u 'weiyigeek:123456' -X GET https://localhost/v2/_catalog
{"repositories":["library/alpine","library/debian"]}

# 对比分析 registry 中 alpine:3.12 和 debian:buster-slim 两个基础镜像 , 此时在registry 存储目录的结构如下:
$tree -h /var/lib/registry/docker/registry/v2/
/var/lib/registry/docker/registry/v2/
├── [  20]  blobs
│   └── [  66]  sha256
│       ├── [  78]  a1
│       │   └── [  18]  a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65
│       │       └── [ 528]  data
│       ├── [  78]  a2
│       │   └── [  18]  a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e
│       │       └── [1.5K]  data
│       ├── [  78]  bf
│       │   └── [  18]  bf59529304463f62efa7179fa1a32718a611528cc4ce9f30c0d1bbc6724ec3fb  # debian:buster-slim 的 Data Layer
│       │       └── [ 26M]  data
│       ├── [  78]  c7
│       │   └── [  18]  c7346dd7f20ef06fd3c58446fab0c3edf22e78131d374775f5f947849537b773  # debian:buster-slim 的 Data Config
│       │       └── [1.5K]  data
│       ├── [  78]  df
│       │   └── [  18]  df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c
│       │       └── [2.7M]  data
│       └── [  78]  e0
│           └── [  18]  e0a33348ac8cace6b4294885e6e0bb57ecdfe4b6e415f1a7f4c5da5fe3116e02  # debian:buster-slim 的 Data Manifest
│               └── [ 529]  data
└── [  21]  repositories
    └── [  34]  library
        ├── [  55]  alpine  # 此处不做过多说明
        │   ├── [  20]  _layers
        │   │   └── [ 150]  sha256
        │   │       ├── [  18]  a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e
        │   │       │   └── [  71]  link
        │   │       └── [  18]  df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c
        │   │           └── [  71]  link
        │   ├── [  35]  _manifests
        │   │   ├── [  20]  revisions
        │   │   │   └── [  78]  sha256
        │   │   │       └── [  18]  a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65
        │   │   │           └── [  71]  link
        │   │   └── [  18]  tags
        │   │       └── [  34]  3.12
        │   │           ├── [  18]  current
        │   │           │   └── [  71]  link
        │   │           └── [  20]  index
        │   │               └── [  78]  sha256
        │   │                   └── [  18]  a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65
        │   │                       └── [  71]  link
        │   └── [   6]  _uploads
        └── [  55]  debian
            ├── [  20]  _layers
            │   └── [ 150]  sha256
            │       ├── [  18]  bf59529304463f62efa7179fa1a32718a611528cc4ce9f30c0d1bbc6724ec3fb
            │       │   └── [  71]  link  # 摘要指向 blobs 中debian:buster-slim 的 Data Layer 
            │       └── [  18]  c7346dd7f20ef06fd3c58446fab0c3edf22e78131d374775f5f947849537b773
            │           └── [  71]  link  # 摘要指向 blobs 中debian:buster-slim 的 Data Config
            ├── [  35]  _manifests
            │   ├── [  20]  revisions
            │   │   └── [  78]  sha256
            │   │       └── [  18]  e0a33348ac8cace6b4294885e6e0bb57ecdfe4b6e415f1a7f4c5da5fe3116e02
            │   │           └── [  71]  link # 摘要指向 blobs 中 debian:buster-slim 的 Data Manifest
            │   └── [  25]  tags
            │       └── [  34]  buster-slim # 以下摘要都是指向 blobs 中 debian:buster-slim 的 Data Manifest
            │           ├── [  18]  current
            │           │   └── [  71]  link
            │           └── [  20]  index
            │               └── [  78]  sha256
            │                   └── [  18]  e0a33348ac8cace6b4294885e6e0bb57ecdfe4b6e415f1a7f4c5da5fe3116e02
            │                       └── [  71]  link
            └── [   6]  _uploads
48 directories, 16 files

然后我们再采用skopeo删除我们上传到Registry仓库中的镜像 , 再进行目录的对比:

$skopeo delete docker://localhost/library/alpine:3.12 --debug

# 列出变化的目录结构部分
repositories
    └── library
        ├── alpine
        │   ├── _layers
        │   │   └── sha256
        │   │       ├── a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e
        │   │       │   └── link
        │   │       └── df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c
        │   │           └── link
        │   ├── _manifests
        │   │   ├── revisions
        │   │   │   └── sha256
        │   │   │       └── a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65
        │   │   └── tags  # tags 下面的目录与文件被删除
        │   └── _uploads

总结上述:

  1. 上述可以看见当skopeo delete删除一个镜像时 , 只是对_manifests下的文件revisions/sha256/a15790640a6690...ka9f2b0d7cc90fd65/link与tags即其子目录文件进行删除;实际上两者删除的是同一个内容 , 即对记录了该镜像manifests文件digest摘要 的link 文件。

  2. 运行后从--debug参数中得到DEBU[0000] DELETE https://localhost/v2/library/alpine/manifests/sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65可以得出通过registry API来DELETE 一个镜像实质上是删除repositories元数据文件夹下的tag名文件夹和该tag 的revisions下的link文件。

我们也可以采用Registry API进行操作达到同样的效率

我们定义摘要字符串匹配以下语法:

digest      := algorithm ":" hex
algorithm   := /[A-Fa-f0-9_+.-]+/
hex         := /[A-Fa-f0-9]+/
# digest = sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b
  1. 获取到镜像Docker-Content-Digest :

    $curl -I -u 'weiyigeek:123456' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X GET https://localhost/v2/library/alpine/manifests/3.12
    
    HTTP/1.1 200 OK
    Content-Length: 528
    Content-Type: application/vnd.docker.distribution.manifest.v2+json
    Docker-Content-Digest: sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65
    Docker-Distribution-Api-Version: registry/2.0
    Etag: "sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65"
    X-Content-Type-Options: nosniff
    Date: Fri, 21 Aug 2020 02:30:06 GMT
  2. 删除registry仓库中的指定Docker-Content-Digest的镜像tags与links文件

    $curl -u 'weiyigeek:123456' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X DELETE https://localhost/v2/library/alpine/manifests/sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65

    可以看出删除仓库中的镜像 , 只是把在registry仓库中镜像的tags子目录与revisions/sha256/.../link文件进行删除 , 而blobs中的镜像data与 repositories 中镜像目录library/alpine/并未被删除 , 这样导致的后果是registry仓库的服务器将会占用一部分存储资源导致资源的浪费 , 那如何解决这个问题就是我们下面提到的Registry GC 机制;

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列表涵盖如下表 :

Method

Path

Entity

Description

GET

/v2/

Base

Check that the endpoint implements Docker Registry API V2.

GET

/v2/_catalog /v2/_catalog?n=<integer>

Catalog

检索注册中心中可用的存储库的排序json列表

GET

/v2/<name>/tags/list

Tags

获取存储库下由“name”标识的标记。

GET

/v2/manifests/

Manifest

获取由“name”和“reference”标识的清单 , 其中“reference”可以是标记或摘要。还可以向这个端点发出一个'HEAD'请求 , 在不接收所有数据的情况下获取资源信息。

PUT

/v2/manifests/

Manifest

把由“name”和“reference”标识的清单放在“reference”可以是标签或摘要的地方。

DELETE

/v2/manifests/

Manifest

删除由“name”和“reference”标识的清单。注意 , 一个清单只能被“摘要”删除。

GET

/v2/blobs/

Blob

从由“摘要”标识的注册表中检索blob。还可以向这个端点发出一个' HEAD'请求 , 在不接收所有数据的情况下获取资源信息。

DELETE

/v2/blobs/

Blob

删除由“name”和“digest”标识的blob

POST

/v2/blobs/uploads/

Initiate Blob Upload

如果成功 , 将提供一个上传位置来完成上传。可选地 , 如果“digest”参数存在 , 请求主体将用于在单个请求中完成上传。

GET

/v2/blobs/uploads/

Blob Upload

此端点的主要目的是解决可恢复上传的当前状态利用uuid。

PATCH

/v2/blobs/uploads/

Blob Upload

上传指定上传的数据块。

PUT

/v2/blobs/uploads/

Blob Upload

完成'uuid'指定的上传 , 可选附加主体作为最后块

DELETE

/v2/blobs/uploads/

Blob Upload

取消未完成的上传进程 , 释放相关资源。如果没有调用此操作 , 未完成的上传最终将超时。

结合registry仓库解释镜像PULL与PUSH过程:

  • PULL镜像 : 镜像由一个json清单和层叠文件组成 ,pull镜像的过程就是检索这两个组件的过程。

    1. 第一步就是获取清单 , 清单由下面几个字段组成:

      registry:5000/v2/redis/manifests/latest(获取redis:latest清单文件)
      # 字段	描述
      # name	镜像名称
      # tag	镜像当前版本的tag
      # fsLayers	层描述列表(包括摘要)
      # signature	一个JWS签名 , 用来验证清单内容
    2. 第二步当获取清单之后 , 客户端需要验证前面(signature) , 以确保名称和fsLayers层是有效的。确认后客户端可以使用digest去下载各个fs层。在V2api中层存储在blobs中已digest作为键值.

      1. 首先拉取镜像清单(pulling an Image Manifest)

        $ HEAD /v2/<image/manifests/<reference>#检查镜像清单是否存在
        $ GET /v2/<image>/manifests/<reference>#拉取镜像清单

        提示 : reference可是tag或者是digest

      2. 开始拉取每个层(pulling a Layer)

        $ GET /v2/<image>/blobs/<digest>

        提示 : digest是镜像每个fsLayer层的唯一标识 , 存在于清单的fsLayers里面。

  • PUSH镜像: 推送镜像和拉取镜像过程相反,先推各个层到registry仓库 , 然后上传清单.(Pushing a Layer(上传层)分为2步)

    1. 使用post请求在registry仓库启动上传服务,返回一个url这个url用来上传数据和检查状态。

      首先Existing Layers(检查层是否存在) , 若返回200 OK则表示存在 , 不用上传

      $ HEAD /v2/image/blobs/<digest>

      开始上传服务(Starting An Upload) , 如果post请求返回202 accepted , 一个url会在location字段返回.

      $ POST /v2/image/blobs/uploads/
      202 Accepted
      Location: /v2/\<image>/blobs/uploads/\<uuid>
      Range: bytes=0-<offset>
      Content-Length: 0
      Docker-Upload-UUID: <uuid> # 可以用来查看上传状态和实现断点续传
    2. 开始上传层(Uploging the Layer)

      > PUT /v2/<name>/blobs/uploads/<uuid>?digest=<digest> 
      Content-Length: <size of layer>>
      Content-Type: application/octet-stream

      上传进度(Upload Progress)

      $ GET /v2/<image>/blobs/uploads/<uuid>
      204 No Content
      Location: /v2/<name>/blobs/uploads/<uuid>
      Range: bytes=0-<offset>
      Docker-Upload-UUID: <uuid>

      重点-整块上传(Monolithic Upload)

      > PUT /v2/<name>/blobs/uploads/<uuid>?digest=<digest> 
      > Content-Length: <size of layer>
      > Content-Type: application/octet-stream
      
      <Layer Binary Data>

      重点-分块上传(Chunked Upload)

      > PATCH /v2/\<name>/blobs/uploads/\<uuid>
      > Content-Length: \<size of chunk>
      > Content-Range: \<start of range>-\<end of range>
      
      > Content-Type: application/octet-stream
      
      \<Layer Chunk Binary Data>
      
      # 如果服务器不接受这个块 , 则返回:
      #   416 Requested Range Not Satisfiable
      #   Location: /v2/<name>/blobs/uploads/<uuid>
      #   Range: 0-<last valid range>
      #   Content-Length: 0
      #   Docker-Upload-UUID: <uuid>
                   
      # 成功则返回 : 
      #   202 Accepted
      #   Location: /v2/<name>/blobs/uploads/<uuid>
      #   Range: bytes=0-<offset>
      #   Content-Length: 0
      #   Docker-Upload-UUID: <uuid>

      重点-交叉上传(Cross Repository Blob Mount)可以把客户端有访问权限的已有存储库中的层挂载到当前存储库中

      POST /v2/<name>/blobs/uploads/?mount=<digest>&from=<repository name>
      Content-Length: 0
      
      # 成功返回 : 
      #   201 Created
      #   Location: /v2/<name>/blobs/<digest>
      #   Content-Length: 0
      #   Docker-Content-Digest: <digest>
      
      # 失败返回:
      #   202 Accepted
      #   Location: /v2/<name>/blobs/uploads/<uuid>
      #   Range: bytes=0-<offset>
      #   Content-Length: 0
      #   Docker-Upload-UUID: <uuid>
    3. 上传完成(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的digest

    curl -I --cacert /opt/registry/certs/domain.crt -u 'weiyigeek:123456' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X GET https://localhost/v2/go-hello/manifests/scratch
    # HTTP/1.1 200 OK
    # Content-Length: 528
    # Content-Type: application/vnd.docker.distribution.manifest.v2+json
    # 注意Docker-Content-Digest中的内容: 在registry2.3或更高版本删除清单时,必须在HEAD或GET获取清单以获取要删除的正确digest携带以下头;
    # Docker-Content-Digest: sha256:8dabce532312b587329fe225ef501051c60f81ffdb2c801a5da6348b9cab132e
    # Docker-Distribution-Api-Version: registry/2.0
    # Etag: "sha256:8dabce532312b587329fe225ef501051c60f81ffdb2c801a5da6348b9cab132e"
    # X-Content-Type-Options: nosniff
    # Date: Fri, 21 Aug 2020 02:30:06 GMT
    
    # 【简约版本:直接提取Docker-Content-Digest头内容】
    curl -Is -u 'weiyigeek:123456' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X GET https://localhost/v2/library/alpine/manifests/3.12 | grep "Docker-Content-Digest:" | cut -f 2 -d " "
    sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65
    
    # 【如不指定Accept则默认为 application/vnd.docker.distribution.manifest.v1+prettyjws 】
    curl -I -u 'weiyigeek:123456' -X GET https://localhost/v2/library/alpine/manifests/3.12                    # HTTP/1.1 200 OK
    # Content-Length: 2783
    # Content-Type: application/vnd.docker.distribution.manifest.v1+prettyjws
    # Docker-Content-Digest: sha256:cae82a43ba96214acd380f3d4ed043445f56f80f0fc99f3f927d5e6eaee40791
    # Docker-Distribution-Api-Version: registry/2.0
    # Etag: "sha256:cae82a43ba96214acd380f3d4ed043445f56f80f0fc99f3f927d5e6eaee40791"
    # X-Content-Type-Options: nosniff
    # Date: Sat, 22 Aug 2020 03:01:16 GMT
  • 删除仓库中的镜像即删除(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请求来进行拉取一个层的信息

    1. 先查看镜像data相关的Digest码

      curl -s -u 'weiyigeek:123456' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -X GET https://localhost/v2/library/alpine/manifests/3.12 | jq '"Data Config - " + .config.digest','"Data Layer - " + .layers[0].digest'
      # "Data Config - sha256:a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e"
      # "Data Layer - sha256:df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c"
    2. 获取拉取镜像的data config与data layer文件

      #获取拉取镜像的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返回的最新值。

    1. 开始上载一个POST请求

      POST /v2/<name>/blobs/uploads/
      
      curl -I -u 'weiyigeek:123456' -X POST https://localhost/v2/test-images/blobs/uploads/
      # 如果POST请求是成功的它将返回 202响应 将与Location头上传的URL返回 : 
      # HTTP/1.1 202 Accepted
      # Content-Length: 0
      # Docker-Distribution-Api-Version: registry/2.0
      # Docker-Upload-Uuid: 9efa5ca7-d009-4532-88de-695c1f945e59  # 必须额
      # Location: https://localhost/v2/test-images/blobs/uploads/9efa5ca7-d009-4532-88de-695c1f945e59?_state=XqlcahxfSzts1a43SgEs_MQ9-GAczgadg-Ra3vayoh57Ik5hbWUiOiJ0ZXN0LWltYWdlcyIsIlVVSUQiOiI5ZWZhNWNhNy1kMDA5LTQ1MzItODhkZS02OTVjMWY5NDVlNTkiLCJPZmZzZXQiOjAsIlN0YXJ0ZWRBdCI6IjIwMjAtMDgtMjJUMDg6NDA6NDguMDkwNTk0NzQ4WiJ9  # 指定了位置标头
      # Range: 0-0
      # X-Content-Type-Options: nosniff
      # Date: Sat, 22 Aug 2020 08:40:48 GMT
    2. 通过HEAD请求到BLOB存储API进行检查镜像相关层是否存在(可用返回200 OK)

      HEAD /v2/<name>/blobs/<digest>
      
      curl -I -u 'weiyigeek:123456' -X HEAD https://localhost/v2/test-images/blobs/sha256:a14....jk5
    3. 上传进度查看此时需要第一步中的Docker-Upload-Uuid之进行请求

      # GET /v2/<name>/blobs/uploads/<uuid>
      curl -I -u 'weiyigeek:123456' -X GET https://localhost/v2/test-images/blobs/uploads/9efa5ca7-d009-4532-88de-695c1f945e59
    4. Monolithic Upload简单的单块上传 , 并可以通过想避免分块的复杂性的

      # PUT /v2/<name>/blobs/uploads/<uuid>?digest=<digest>
      # Content-Length: <size of layer>
      # Content-Type: application/octet-stream
      
      # <Layer Binary Data>
    5. Chunked Upload进行组块的上载 , 该客户机可以指定一个范围报头和仅包括层文件的一部分 :

      # PATCH /v2/<name>/blobs/uploads/<uuid>
      # Content-Length: <size of chunk>
      # Content-Range: <start of range>-<end of range>
      # Content-Type: application/octet-stream
      
      # <Layer Chunk Binary Data>
    6. 跨存储库Blob挂载,可以从客户机具有读访问权的另一个存储库挂载blob , 从而不需要将已知的blob上传到注册中心,要发出一个blob挂载而不是一个upload, POST请求应该以以下格式发出(成功将返回202 Accepted):

      POST /v2/<name>/blobs/uploads/?mount=<digest>&from=<repository name>
      Content-Length: 0
    7. Completed Upload当镜像上传完毕必须进行以下请求否则仓库不认为镜像个层全部上传 , 当接收到最后一个块和层已被验证时候将返回201 Create并且返回该镜像的Docker-Content-Digest值;

      # PUT /v2/<name>/blobs/uploads/<uuid>?digest=<digest>
      # Content-Length: <size of chunk>
      # Content-Range: <start of range>-<end of range>
      # Content-Type: application/octet-stream
      
      # <Last Layer Chunk Binary Data>
    8. 取消上传镜像到仓库 , 可通过发出DELETE请求到registry之中;

      DELETE /v2/<name>/blobs/uploads/<uuid>
    9. 删除层(Deleting a Layer)

       DELETE /v2/<image>/blobs/<digest>
       
       # 成功返回:
      # 202 Accepted
      # Content-Length: None
    10. 上传镜像清单(Pushing an Image Manifest),我们上传完镜像层之后 , 就开始上传镜像清单

      # PUT /v2/<name>/manifests/<reference>
      # Content-Type: <manifest media type>
      # {
      # "name": <name>,
      # "tag": <tag>,
      # "fsLayers": [
      #   {
      #     "blobSum": <digest>
      #   },
      #   ...
      # ]
      # ],
      # "history": <v1 images>,
      # "signature": <JWS>,
      # ...
      # }
      
      # 如果清单中有层("blobSum":<digest>)是未知的 , 则返回
      # {  "errors:" [{          "code": "BLOB_UNKNOWN",          "message": "blob unknown to registry",          "detail": {              "digest": <digest>
      #         }
      #     },
      #     ...
      #   ]
      # }
      # ```

删除镜像Manifests与镜像层信息:

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的主要分两个阶段:

  1. 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")
    	}
  2. 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
    })
docker-register-gc-1

我们可以利用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 files

2.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

EOF

2.5.3 章节总结

  1. 在GC之后registry存储目录我们可以看到 , 原本blobs目录下有6个data文件现在已经变成3个 , 而alpine:3.12镜像相关的Layer、Config、Manifest文件已经被GC清理掉了; 但是在repositories目录下 , 该镜像的_layers下的link文件依旧存在🤔。

  2. 总结以上 , 用下面这三张图片就能直观地理解这些过程啦

    • delete镜像之前的registry存储目录结构

      docker-register-gc-delete-1
  • delete镜像之后的registry存储目录结构

    docker-register-gc-delete-2
  • GC之后的registry存储目录结构

    docker-register-gc-delete-3
  1. GC之后一定要重启 , 因为registry容器缓存了镜像layer的信息当删除掉一个镜像 A 后边 GC 掉该镜像的layer之后 , 如果不重启registry容器 , 当重新 PUSH 镜像A的时候就会提示镜像 layer 已经存在 , 不会重新上传layer但实际上已经被 GC 掉了 , 最终会导致镜像A不完整无法pull到该镜像`。

  2. 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/harbor

  • vmware官方开源服务列表地址: https://vmware/github.io/harbor/cn

  • harbor官方网址: https://goharbor.io

  • Harbor是一个开源的可信云本机注册表项目 , 用于存储 , 签名和扫描内容。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这样一个数据配置管理中心来完成。

2387773-20211018135633451-704924999

  • nginx : harbor 的一个反向代理组件 , 代理 registry 、ui、token 等服务。这个代理会转发harbor web 和docker client的各种请求到后端服务上。

  • harbor-adminserver : harbor系统管理接口, 可以修改系统配置以及获取系统信息。

  • harbor-db : 存储项目的元数据、用户、规则、复制策略等信息。

  • harbor-jobservice : harbor 里面主要是为了镜像仓库之前同步使用的。

  • harbor-log : 收集其他harbor的日志信息

  • harbor-ui : 一个用户界面模块 , 用来管理registry

  • registry : 存储docker images的服务, 并且提供pull/push服务

  • redis : 存储缓存信息

  • webhook : 当registry中的image 状态发生变化的时候取记录更新日志、复制等操作

  • token service : 在docker client 进行pull/push 的时候负责token的发放

3.3 Harbor配置参数

3.3.1 harbor1.7配置参数

  • 参数介绍

    1. 配置参数位于文件harbor.cfg中。

    2. 在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 = true

      • email_server = smtp.mydomain.com

      • email_server_port = 25

      • email_identity =

      • email_username = sample_admin@mydomain.com

      • email_password = abc

      • email_from = admin sample_admin@mydomain.com

      • email_ssl = false

      • email_insecure = false

    • harbor_admin_password : 管理员的初始密码。此密码仅在Harbor首次启动时生效。之后 , 将忽略此设置 , 并且应在Portal中设置管理员密码。请注意 , 默认用户名/密码为admin / Harbor12345

    • auth_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 / aut

3.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/data

    password : harbor数据库root用户的密码 , 生产环境建议修改 ;

    max_idle_conns: 空闲连接池中的最大连接数。如果小于等于0 , 则不保留空闲连接。

    max_open_conns: 打开到数据库的最大连接数。如果小于等于0 , 则对打开的连接数没有限制。

    data_volume: 数据存储目录 , harbor中的每个组件的数据都存储在该指定路径下的子目录中。

  • 是否启用外部存储

    如果需要使用外部存储 , 则将该部分的注释去掉 , 生产环境建议使用外部存储。

    # storage_service:
    #   ca_bundle
    #   filesystem:
    #     maxthreads: 100    # 最大线程数
    #   redirect:  # 是否启用重定向
    #     disabled: false

    ca_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: 10
  • webhook配置

    默认启用 , 默认的值为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/harbor

    rotate_count : 日志文件在被删除之前旋转的次数。如果为0 , 旧版本将被删除而不是旋转。 rotate_size : 只有当日志文件的大小大于该值字节时 , 才会对其进行旋转。如果size后跟k , 则假定大小以KB为单位。如果使用M , 则大小以兆字节为单位 , 如果使用G , 则大小以千兆字节为单位。那么100、100k、100M和100G。 location : 主机上存储日志的目录。

  • 是否启用外部系统日志

    是否将日志放到另外的主机上 ( 默认关闭该功能 )

    # external_endpoint:
      #   protocol: tcp
      #   host: localhost
      #   port: 5140

    protocol : 用于将日志传输到外部端点的协议 , 选项为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环境

  1. 修改主机名

    [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
  2. 配置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
  3. 安装并启动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.
  4. 安装docker-compose(主要用于harbor安装和管理)

    [root@centos7 ~]# yum install -y epel-release
    [root@centos7 ~]# yum install -y docker-compose

3.4.1.2 安装harbor环境

  1. 下载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) 文件下载加速网站

    加速网站下载地址: https://mirror.ghproxy.com/https://github.com/goharbor/harbor/releases/download/v2.10.0/harbor-offline-installer-v2.10.0.tgz

  2. 解压安装包

    [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
  3. 切换到解压目录

    [root@centos7 ~]# cd /usr/local/src/harbor/
  4. 修改配置文件 , 该配置文件中包含主机名设置, 端口 , 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
    
    image-20240221215455222
  5. 运行prepare脚本

    harbor prepare 文件主要用于准备 Harbor 环境 , 具体来说 , 它的功能包括 :

    1. 准备环境 : harbor prepare 会检查你的系统环境 , 包括检查Docker和Docker Compose是否已安装 , 以及加载Harbor镜像等。

    2. 准备 Harbor 配置文件 : harbor prepare 会将 harbor.yml 配置文件的内容注入到各个组件的配置文件中。

    3. 应用配置 : 在你修改了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
  6. 运行安装脚本./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.----
  7. 编辑对应的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两行

    image-20240222223333249
  • 直接运行安装脚本

    [root@localhost harbor]# ./install.sh 

3.5 使用说明

3.5.1 基础使用

  1. 通过浏览器直接输入IP登录 , 首页默认的用户名密码为admin/Harbor12345 , 这个密码可以通过harbor.yml配置文件的harbor_admin_password: Harbor12345 修改初始密码。

    image-20240222161030934

    1. 创建新项目

      image-20240222202656626
      image-20240222202719655
    2. 创建新用户

      image-20240222202948459
      image-20240222202913211

3.5.2 远程复制

  1. 仓库管理中设置新建目标 , 点击测试连接之后直接点击确定

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

    image-20240222204014078
    image-20240222204153812

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

    由于配置了远程复制,所以可以在另外一个节点中看到复制过来的镜像

    image-20240222210054029
  • 下载镜像

    [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所依赖的数据可分为两大类 : 临时数据和持久化数据

  1. 临时数据 在Harbor安装期间通过配置文件生成的数据 , 主要是Harbor组件所依赖的配置文件和环境变量。

    这些数据通常在Harbor安装目录的common目录下( 如果Harbor是通过源代码安装的 , 则这些数据在源码目录的"make/common"目录下 )

    在Harbor各组件启动时会被挂载到对应的容器中。虽然临时数据对服务的顺利运行至关重要 , 但是安装程序每次都会读取Harbor 配置文件重新生成一份临时数据 , 所以我们仅需备份配置文件即可 , 不必将整个common目录全部备份

  2. 持久化数据 存放在数据目录配置项下( 即配置文件中data_volume项所配置的值 ) , 这些数据主要包括Harbor的数据库数据、Artifacts数据、Redis数据、Chart数据 , 以及Harbor各个组件所依赖的运行时数据

    data目录包含的文件夹和相应的作用如下

    • ca_download : 存放用户访问Harbor时所需的CA

    • cert : 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_bak

harbor.yml中data_volume值为 持久化数据目录

cp -rf /data /data/harbor_data_back

3.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.yml

3.5.5.2.完整升级操作

  • 停止harbor

    docker-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路径}
  • 使用新配置文件安装Harbor

    tar zxf ${新版本Harbor离线包}
    
    cd ${新版本Harbor解压的文件夹}
    
    cp ${新生成的harbor.yml} ${新版本Harbor目录}
    
    ./install.sh 

  • 升级后harbor-db无法启动 通过修改docker-compose.yml

    privileged: 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节点过程中提示如上内容

    这个问题主要报错的原因没有配置证书认证不安全的原因导致的。

    主要有三种处理方式,

    1. 直接配置https服务

    2. 在/etc/docker/daemon.json中添加如下内容

      [root@centos7 harbor]# cat /etc/docker/daemon.json 
      { 
          "insecure-registries" : [
            "192.168.194.130",
            "192.168.194.146"
           ]
      }
    3. 在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
    image-20240222224132643

    处理方式可以直接将docker-compose.yml的version: '2'修改为version: '2.1'

    image-20240222224329488
  • harbor2.10版本中, 系统管理-> 仓库管理 , 新建目标的过程中测试连接总是失败。

    image-20240222020148310

    查看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

    此时测试连接成功

    image-20240222015928694

    上面的方法只能临时解决问题 , 当容器重新启动后主机记录就会丢失。所以需要修改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登录

造成原因 :

  1. Harbor的password_version加密版本 , 重置密码并将sha256改成sha1 ;

  2. 没有https却在配置的extend_url保留了https , 导致登录时信息加密 ;

解决方案 :

  1. 对于原因1 , 可修改harbor.yml的extend_url、将协议改成http

  2. 对于原因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)

镜像搬运工 skopeo - 知乎 (zhihu.com)

11.Docker容器镜像体积缩小技巧 (qq.com)

Docker之四----搭建私有docker仓库 - 一叶知秋~~ - 博客园 (cnblogs.com)

【登陆harbor仓库报错——解决方法】Error response from daemon:Get... http: server gave HTTP response to HTTs client-CSDN博客

Harbor新建目标时测试连接失败 ( no such host) - fengkm - 博客园 (cnblogs.com)

Harbor快速实现企业镜像仓库管理 (qq.com)

Harbor安装与配置-CSDN博客

Harbor配置优化 , 配置文件深度解析!51CTO博客harbor安装与配置