对于中小型企业,如希望搭建 Docker 私有仓库,又或是小型镜像加速站,可采用官方提供的 Registry 镜像。

1. 准备工作

这里准备两台 Linux 服务器,且均已提前安装 Docker Engine,具体角色分工如下:

名称地址角色备注
仓库服务器192.168.50.75服务端部署私有仓库服务
演示服务器192.168.50.92客户端演示使用私有仓库

另,此次安装的版本:Registry 3.0.0

2. 仓库安装

本节操作均在 仓库服务器 执行。

# 创建数据卷挂载目录
sudo mkdir /var/lib/registry

# 拉取镜像
docker pull registry

# 查看镜像
docker images

# 运行容器
docker run -p 5000:5000 \
--restart=always \
--name registry \
-v /var/lib/registry:/var/lib/registry \
-d registry

测试 Registry 是否正常启动

# 发送查看所有镜像的请求
curl http://192.168.50.75:5000/v2/_catalog

# 如返回列表,表示部署成功
{"repositories":[]}

3. 服务测试

本节操作均在 演示服务器 进行。

3.1. 白名单

docker 客户端默认使用 HTTPS 访问镜像仓库。

当镜像仓库仅支持 HTTP 时,需将该仓库加入非安全连接白名单,否则会连接失败。

# 推送镜像到私有仓库
docker push 192.168.50.75:5000/hello-world:latest

# 返回结果:要求使用 https,推送失败
The push refers to repository [192.168.50.75:5000/hello-world]
Get "https://192.168.50.75:5000/v2/": http: server gave HTTP response to HTTPS client

编辑文件

sudo vim /etc/docker/daemon.json

添加配置

{
    "insecure-registries": [
        "192.168.50.75:5000"
    ]
}

另:如果配置为 0.0.0.0/0,则表示信任任意仓库地址(不建议)。

重启服务

sudo systemctl daemon-reload
sudo systemctl restart docker

3.2. 推送镜像

推送到私有仓库之前,需先在本地附加标签,加上私有仓库地址作为前缀,以区分公共仓库和私有仓库。

# 1. 添加标签
docker tag hello-world:latest 192.168.50.75:5000/hello-world:latest

# 2. 推送镜像到私有仓库
docker push 192.168.50.75:5000/hello-world:latest

# 3. 查看结果
curl http://192.168.50.75:5000/v2/_catalog

# 返回如下结果,可以看到已推送成功
{"repositories":["hello-world"]}

3.3. 拉取镜像

拉取镜像,也需加上私有仓库地址作为前缀,否则会从公共仓库进行拉取。

# 删除本地镜像
docker rmi 192.168.50.75:5000/hello-world
docker rmi hello-world

# 从私有仓库拉取镜像
docker pull 192.168.50.75:5000/hello-world

# 运行镜像
docker run 192.168.50.75:5000/hello-world

4. 安全认证

如果将 Registry 用于企业环境,通常需启用 HTTPS身份验证

4.1. 身份验证

身份验证需使用 htpasswd 来加密用户密码,且密码格式仅支持bcrypt

4.1.1. 软件安装

# htpasswd 包含在 apache2-utils 软件包 中
sudo apt update && sudo apt install apache2-utils

4.1.2. 密码文件

# 宿主机创建文件夹,用以存储 htpasswd 用户密码文件
sudo mkdir /usr/local/etc/docker/registry/auth
# 进入文件目录
cd /usr/local/etc/docker/registry/auth

# 初次使用,创建密码文件
htpasswd -Bbc passwords admin 12345678

# 再次使用时,需去掉参数 c,添加用户到已有密码文件
htpasswd -Bb passwords docker 12345678

# 查看生成的密码
cat passwords
# 显示如下结果
admin:$2y$05$nZp3XXbDpM0p7WXl6Y3EzuabFS3N6aGdUMxykFjPvEhYcuhJnXRgK
docker:$2y$05$SIR039qdQ1/bT9ABVcKNdeXXW7v.cs8wNkVuNvMRvKD7al59wx70O

htpasswd 参数说明:

参数说明
-c创建新文件(若文件已存在将覆盖旧文件)
-n不更新文件,输出到控制台
-b直接从命令行读取密码(避免交互提示)
-i从标准输入读取密码而不进行验证(用于脚本)
-m强制使用 MD5 加密(默认)
-B强制使用 bcrypt 加密(非常安全)
-C配置 bcrypt 算法的计算时间,时间越长越安全,但越慢。(默认:5;有效范围:4 ~ 7)
-d使用 CRYPT 加密(不安全,密码最长支持 8 个字符)
-s使用 SHA1 加密(不安全)
-p不加密密码(明文,非常不安全)
-D删除指定用户
-v验证指定用户的密码

4.1.3. 修改配置

/usr/local/etc/docker/registry/config.yml 添加 auth 配置:

auth:
  htpasswd:
    realm: basic-realm		# 身份认证信息作用领域
    path: /auth/passwords	# 容器内的密码文件地址

4.1.4. 重启容器

重启 Registry 容器以配置 htpasswd

# 停止容器
docker stop registry
# 删除容器
docker rm registry
# 启动容器
docker run \
-p 5000:5000 \
--restart=always \
--name registry \
-v /var/lib/registry:/var/lib/registry \
-v /usr/local/etc/docker/registry/auth:/auth \
-v /usr/local/etc/docker/registry/config.yml:/etc/distribution/config.yml \
-d registry:r3.0.0

4.1.5. 安全测试

注:安全测试均在客户端运行。

接口请求

# 不带身份认证发送请求
curl http://192.168.50.75:5000/v2/_catalog

# 提示需要认证
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"registry","Class":"","Name":"catalog","Action":"*"}]}]}

# 带身份认证发送请求
curl -u admin:12345678 http://192.168.50.75:5000/v2/_catalog

# 正常返回结果
{"repositories":["hello-world"]}

拉取镜像

# 拉取镜像
docker pull 192.168.50.75:5000/hello-world:latest

# 提示:无身份验证信息
no basic auth credentials

# 登录
docker login 192.168.50.75:5000

# 再次拉取镜像,成功
docker pull 192.168.50.75:5000/hello-world:latest

# 退出登录
docker logout 192.168.50.75:5000

推送镜像

# 添加标记
docker tag hello-world:latest 192.168.50.75:5000/my-hello-world:v1

# 推送镜像
docker push 192.168.50.75:5000/my-hello-world:v1

# 提示:无身份验证信息
no basic auth credentials

# 登录
docker login 192.168.50.75:5000

# 再次推送镜像,成功
docker push 192.168.50.75:5000/my-hello-world:v1

4.2. HTTPS

启用基本身份验证后,客户端发送请求时会将用户名和密码附加在请求头中发送,建议再配置 HTTPS

4.2.1. 准备证书

如仅是为了测试,可采用 openssl 工具生成自签名证书,生产环境请使用正式证书。

# 生成根证书:私钥和证书
openssl req -newkey rsa:4096 -nodes -sha256 \
  -keyout ca.key -x509 -days 3650 -out ca.crt \
  -subj "/C=CN/ST=GD/L=Shenzhen/O=Igeeksky Inc./CN=Igeeksky Root CA"

# 生成服务器私钥和csr文件
openssl req -newkey rsa:4096 -nodes -sha256 \
-keyout igeeksky.key -out igeeksky.csr \
-subj "/C=CN/ST=GD/L=Shenzhen/O=Igeeksky Inc./CN=Igeeksky.com"

# 使用根证书签发服务器证书
openssl x509 -req -days 3650 -in igeeksky.com.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out igeeksky.crt -extfile <(printf "subjectAltName=DNS:www.igeeksky.com")

4.2.2. 修改配置

修改配置文件,在 http 项下增加 tls 配置。

version: 0.1
log:
  level: info
  fields:
    service: registry
    environment: development
storage:
  delete:
    enabled: true
  cache:
    blobdescriptor: inmemory
  filesystem:
    rootdirectory: /var/lib/registry
  tag:
    concurrencylimit: 5
http:
  addr: :5000
  debug:
    addr: :5001
    prometheus:
      enabled: true
      path: /metrics
  tls:                                  # TLS 配置
    certificate: /certs/igeeksky.crt    # 指定证书文件
    key: /certs/igeeksky.key            # 指定私钥文件
    minimumtls: tls1.2                  # 指定 TLS 版本
    ciphersuites:                       # 指定加密算法集
      - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
      - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
health:
  storagedriver:
    enabled: true
    interval: 10s
    threshold: 3
auth:
  htpasswd:
    realm: Registry_Realm 	# 身份认证信息作用领域
    path: /auth/passwords   # 容器内的密码文件地址

注:

代理模式下,需将 auth 相关选项全部删除,否则报 “error authorizing context” 异常,且无法拉取镜像。

非代理模式,开启 auth 验证后,也会报 “error authorizing context” 异常,但不影响镜像的推送拉取。

另,auth.htpasswd.realm 无论配置成 basic-realmRegistry_Realm 都会报错。

4.2.3. 重启服务

# 停止容器
docker stop registry
# 删除容器
docker rm registry
# 启动容器
docker run \
-p 5000:5000 \
--restart=always \
--name registry \
-v /var/lib/registry:/var/lib/registry \
-v /usr/local/etc/docker/registry/auth:/auth \
-v /usr/local/etc/docker/registry/certs:/certs \
-v /usr/local/etc/docker/registry/config.yml:/etc/distribution/config.yml \
-e OTEL_TRACES_EXPORTER=none \
-d registry:latest

启动命令参数:

  1. 增加了证书目录映射 -v /usr/local/etc/docker/registry/certs:/certs

  2. 增加了 -e OTEL_TRACES_EXPORTER=none 参数,见 disable-traces-export

4.2.4. 安全测试

测试操作在客户端机器运行。

根证书

docker 客户端需要颁发机构的根证书来验证自签名的服务器证书,因此需将根证书放到客户端的指定目录。

# 创建目录
sudo mkdir -p /etc/docker/certs.d/www.igeeksky.com:5000

# 把根证书复制到此目录
sudo cp ca.crt /etc/docker/certs.d/www.igeeksky.com:5000/ca.crt

或者,将根证书直接添加到系统信任库,那么所有工具(包括curl)都将信任该 ca

sudo cp ca.crt /usr/local/share/ca-certificates/	# 复制证书
sudo update-ca-certificates                      	# 更新信任库

修改 hosts

最后,因为是内网测试,且又是自签名证书,所以还需在客户端机器添加域名解析。

# 编辑 hosts 文件
sudo vim /etc/hosts

# 将域名 www.igeeksky.com 解析到 `192.168.50.75`
192.168.50.75 www.igeeksky.com

测试 HTTPS

# 接口测试,如未将ca添加到系统信任库,需附加 --cacert ./ca.crt 以指定根证书
curl -u admin:12345678 https://www.igeeksky.com:5000/v2/_catalog --cacert ./ca.crt

# 返回结果,成功
{"repositories":["hello-world","my-hello-world"]}


# 拉取镜像
# 登录
docker login www.igeeksky.com:5000

# 拉取镜像,成功
docker pull www.igeeksky.com:5000/my-hello-world:v1


# 推送镜像
# 添加标签
docker tag www.igeeksky.com:5000/my-hello-world:v1 www.igeeksky.com:5000/my-hello-world:v2

# 推送镜像,成功
docker push www.igeeksky.com:5000/my-hello-world:v2

5. 代理模式

官方说明:https://distribution.github.io/distribution/recipes/mirror/

默认配置中,当私有仓库服务端无客户端请求的镜像时,那么会直接报错。

# 从私有仓库下载 nginx 镜像
docker pull www.igeeksky.com:5000/nginx:latest
# 报错:无法找到 nginx 镜像
Error response from daemon: manifest for www.igeeksky.com:5000/nginx:latest not found: manifest unknown: manifest unknown

Registry 支持配置为代理模式,其作用类似于云厂商提供的 mirror

当客户端请求某个镜像时,若该私有仓库无此镜像,将自动从指定的公共仓库拉取镜像并缓存,并返回给客户端。

5.1. 代理配置

增加代理配置

version: 0.1
log:
  level: debug
  fields:
    service: registry
    environment: development
storage:
    delete:
      enabled: true
    cache:
        blobdescriptor: inmemory
    filesystem:
        rootdirectory: /var/lib/registry
    tag:
      concurrencylimit: 5
http:
  addr: :5000
  debug:
    addr: :5001
    prometheus:
      enabled: true
      path: /metrics
  tls:                                  # TLS 配置
    certificate: /certs/igeeksky.crt    # 指定证书文件
    key: /certs/igeeksky.key            # 指定私钥文件
    minimumtls: tls1.2                  # 指定 TLS 版本
    ciphersuites:                       # 指定加密算法集
      - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
      - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
health:
  storagedriver:
    enabled: true
    interval: 10s
    threshold: 3
#auth:
#  htpasswd:
#    realm: Registry_Realm 	# 身份认证信息作用领域
#    path: /auth/passwords   # 容器内的密码文件地址
proxy:    # 增加代理配置
  remoteurl: https://registry-1.docker.io # 指定上游公共仓库
  ttl: 168h	# 镜像缓存存活时间,默认7天,过期将自动删除(设为 0 则不过期)

代理模式下,开启身份验证会触发 bug,报 “error authorizing context” 异常,且无法正常拉取镜像,因此这里需将 auth 选项全部注释掉。

重启服务

# 停止容器
docker stop registry
# 删除容器
docker rm registry
# 启动容器
docker run \
-p 5000:5000 \
--restart=always \
--name registry \
-v /var/lib/registry:/var/lib/registry \
-v /usr/local/etc/docker/registry/certs:/certs \
-v /usr/local/etc/docker/registry/config.yml:/etc/distribution/config.yml \
-e OTEL_TRACES_EXPORTER=none \
-d registry:latest

5.2. 测试代理

本小节测试操作在演示服务器运行。

准备

编辑 /etc/docker/daemon.json 文件

# 镜像源配置为 https://www.igeeksky.com:5000

{
  "registry-mirrors": [
    "https://www.igeeksky.com:5000"
  ]
}

# 编辑完成后重启服务
sudo systemctl daemon-reload
sudo systemctl restart docker

测试

# :使用 curl 发送查看所有镜像的请求
curl https://www.igeeksky.com:5000/v2/_catalog

# 返回结果列表
{"repositories":["hello-world", "my-hello-world"]}

# 第一次拉取 nginx 镜像,比较慢
# 私有仓库需先从远程服务器下载镜像,然后再返回给客户端
# 这里可以不用附加私有仓库地址前缀
docker pull nginx

# 演示服务器:使用 curl 发送查看所有镜像的请求
curl https://www.igeeksky.com:5000/v2/_catalog

# 返回结果列表:已有 nginx
{"repositories":["hello-world","library/nginx","my-hello-world"]}

# 删除本地 nginx 镜像
docker rmi nginx

# 第二次拉取 nginx 镜像,就会非常快了
docker pull nginx

5.3. 特别注意

⚠ 代理模式下,只能拉取镜像,不能推送镜像。

⚠ 代理模式下,镜像默认缓存 7 天,之后会自动删除。

⚠ 代理模式下,无法开启身份验证,开启后无法下载。其中一个解决方案是使用反向代理,将 HTTPS基本身份验证 均前移到反向代理层,具体实现见 Authenticate proxy with nginx

6. 小结

Registry 支持两种模式:

一是仓库模式,可以正常上传下载,当服务端不存在客户端请求的镜像时,返回空。

二是代理模式,只能下载不能上传,当服务端不存在客户端请求的镜像时,自动从指定公共仓库拉取镜像并返回给客户端,其作用是作为镜像缓存加速下载。

最后,如将 Registry 发布到公网,建议不用默认的 5000 端口。

附录(官方资料)

Github:https://github.com/distribution

镜像主页:https://hub.docker.com/_/registry

相关文档:https://distribution.github.io/distribution/