对于中小型企业,如希望搭建 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-realm 或 Registry_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
启动命令参数:
增加了证书目录映射
-v /usr/local/etc/docker/registry/certs:/certs;增加了
-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