提到容器仓库,我们一般会想到 Nexus、Harbor ,那么有没有更轻量可靠的方案呢。尤其是在频繁构建的 CI 流水线中、或是分布式的环境中需要高频拉取镜像的场景中。
《使用容器搭建 APT Cacher NG 缓存代理服务》一文提到了缓存,虽然可以使用文末中的 Nginx 的补充方式来提供容器镜像导出文件的缓存托管,但是这种方式相比较使用镜像仓库而言,不能够直接使用 Docker Client 与之交互,需要借助导出和导入命令,使用起来颇有不便。
本篇文章继续聊聊,如何使用容器搭建轻量可靠的镜像仓库:distribution。
写在前面
提起 distribution ,你可能会觉得陌生,但是如果下面几个大型的仓库都是由它为基础,你或许会对它信任会更多一些:
- Docker Hub
- GitHub Container Registry
- GitLab Container Registry
- DigitalOcean Container Registry
- The CNCF Harbor Project
- VMware Harbor Registry
没错,distribution 作为基础依赖存在于上面这些大名鼎鼎的社区(商业)仓库中。
如果你不需要更细粒度的镜像管理,只是需要对“数据”进行托管,或者说,只需要基础的镜像存储和推送服务,那么直接使用 distribution 或许是当下最轻量可靠的不二选择,如果你对于存储稳定性有顾虑,它原生支持公有云对象存储,可以保证数据存储的稳定和安全。
当然,如果你的需求除了 Docker 仓库之外,你还有 Maven/Java、npm、NuGet、Helm、Docker、P2、OBR、APT、GO、R 等软件包仓库的需求,distribution 是无法胜任的,可以选择开源仓库的扛把子 Nexus。感兴趣的话,可以参考早些时候关于 Nexus 搭建和使用的内容。
为了行文方便,我们假设这个提供服务的容器仓库的域名为 docker.soulteary.cn,这个域名可以被正确解析到我们启动服务的那台机器上(比如本地、或者某台云主机)。
参与演示的镜像,为了省事,我们选择搭建仓库使用的 distribution 镜像 registry:2
,为了方便使用,提前使用 docker pull registry:2
下载至本地,并添加一个 docker.soulteary.cn/registry:2
的标签,确保我们后续能推送这个镜像到私有仓库。
docker pull registry:2
2: Pulling from library/registry
9b794450f7b6: Pull complete
6ba25693af03: Pull complete
9eb68e7589ff: Pull complete
6cf77150f665: Pull complete
339e0c26c7cc: Pull complete
Digest: sha256:5bb9b919833aa955dfe1d1121cc038330b025ec6506ce47066c9192927e3dc3d
Status: Downloaded newer image for registry:2
docker.io/library/registry:2
docker tag registry:2 docker.soulteary.cn/registry:2
系统环境准备
系统环境和上篇《使用容器搭建 APT Cacher NG 缓存代理服务》一样,我们只需要安装容器引擎和基础的编排工具即可。
如果你有参考上一篇文章进行缓存服务搭建,可以配置缓存服务来进行安装加速,如果没有,那么可以执行下面的命令,完成初始环境安装:
apt update && apt upgrade -y && apt install -y apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu/gpg | apt-key add -
add-apt-repository "deb http://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
apt install -y docker-ce
curl -L https://github.com/docker/compose/releases/download/1.29.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
下面我将介绍几种不同的搭建方式,你可以根据你的需求来进行选择或者组合使用。
配置无须身份验证的容器仓库
如果你只是需要在 CI 中使用,不考虑公开提供服务,将下面的配置保存为 docker-compose.yml
,执行 docker-compose up -d
即可得到一个不需要身份认证即可使用的容器仓库(注册表服务),相关数据会被存储在 registry
目录中。
如果你需要更高的数据可靠性,可以翻阅文档中关于 storage
配置的章节,将存储配置到你认为可靠的公有云服务中,它目前支持 GCS、Azure、S3、Swift、OSS等多个服务商。如果在意私密性,也可以考虑使用 MinIO 搭建私密的 S3 对象存储服务。
version: "3.0"
services:
registry:
restart: always
image: registry:2
ports:
- 80:80
environment:
REGISTRY_HTTP_ADDR: 0.0.0.0:80
REGISTRY_LOG_LEVEL: warn
REGISTRY_LOG_ACCESSLOG_DISABLED: "true"
volumes:
- ./registry:/var/lib/registry
logging:
driver: "json-file"
options:
max-size: "1m"
Docker Client 默认访问注册表会使用 HTTPS 方式,如果我们想使用 HTTP 方式访问仓库,还需要在 Docker 的 daemon.json 配置文件中添加一项配置,告诉 Docker Client 在下载这个域名的镜像的时候不使用 HTTPS :
{
"insecure-registries" : [ "docker.soulteary.cn"],
...
}
如果你的 deamon.json 中没有特别的设置,只需要添加这一个配置项,可以使用下面的命令快速进行设置:
echo "{\"insecure-registries\" : [ \"docker.soulteary.cn\" ]}" >/etc/docker/daemon.json
service docker restart
如果你没有正确配置,并重启 Docker 服务的话,尝试拉取或者推送镜像的时候,会遇到类似下面的错误:
docker pull docker.soulteary.cn/registry:2
Error response from daemon: Get https://docker.soulteary.cn/v2/: dial tcp 192.168.93.38:443: connect: connection refused
当配置添加完毕之后,使用 service docker restart
重启服务,就可以正常的使用 docker pull
和 docker push
和仓库进行交互了。
docker push docker.soulteary.cn/registry:2
The push refers to repository [docker.soulteary.cn/registry]
b2335c628697: Layer already exists
3cb95fe83bcd: Layer already exists
d2ecc62f3d1a: Layer already exists
8e95b38dd51d: Layer already exists
2b2bcc6e6724: Layer already exists
2: digest: sha256:160c621b9bd98c4becce1c3b14e4866524dbe898d3af2e48d81fa1821b82c615 size: 1363
配置标准的 HTTPS 容器仓库
如果你不想在各种系统和 CI 中配置“insecure-registries”,并且能够得到服务的证书(购买、免费申请、自签名),那么可以使用下的配置,将服务运行于 443 端口,并提供 HTTPS 服务。
需要注意的是,如果你使用自签名证书,则发起调用的的系统需要配置信任自签证书。
version: "3.0"
services:
registry:
restart: always
image: registry:2
ports:
- 443:443
environment:
REGISTRY_HTTP_ADDR: 0.0.0.0:443
REGISTRY_LOG_LEVEL: warn
REGISTRY_LOG_ACCESSLOG_DISABLED: "true"
REGISTRY_HTTP_TLS_CERTIFICATE: /ssl/soulteary.pem
REGISTRY_HTTP_TLS_KEY: /ssl/soulteary.key
volumes:
- ./registry:/var/lib/registry
- ./ssl:/ssl
logging:
driver: "json-file"
options:
max-size: "1m"
这里的证书可以使用 pem
、crt
等合法的证书格式。如果使用这个模式,则可以将上一小节中对于 daemon.json 中的特殊配置去掉。
使用 Nginx 配置同时支持两种协议的仓库
参考官方配置文档,我们知道 distribution 排除掉调试接口外,仅支持使用单个端口提供服务,所以如果我们想要同时支持 HTTP 和 HTTPS 就需要借助其他的软件,比如 Nginx、Traefik。
以 Nginx 为例,我们先进行容器编排配置的编写,将 Nginx 和 distribution 编排至同一网络:
version: "3.0"
services:
nginx:
image: nginx:1.19.8-alpine
restart: always
ports:
- 80:80
- 443:443
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
- ./ssl/:/etc/ssl/:ro
- ./default.conf:/etc/nginx/conf.d/default.conf
networks:
- dockerhub
healthcheck:
test: ["CMD-SHELL", "wget -q --spider --proxy off localhost/get-health || exit 1"]
interval: 10s
timeout: 1s
retries: 3
logging:
driver: "json-file"
options:
max-size: "1m"
registry:
restart: always
image: registry:2
networks:
- dockerhub
environment:
REGISTRY_HTTP_ADDR: 0.0.0.0:80
REGISTRY_LOG_LEVEL: warn
REGISTRY_LOG_ACCESSLOG_DISABLED: "true"
volumes:
- ./registry:/var/lib/registry
logging:
driver: "json-file"
options:
max-size: "1m"
networks:
dockerhub:
在上面的容器网络中,对外提供服务的职责由 Nginx 承担,而 distribution 仅需要监听容器网络内部的请求即可。
接着继续完成 Nginx 配置文件 default.conf 的编写:
server {
listen 80;
client_max_body_size 0;
client_body_buffer_size 16k;
access_log off;
location / {
add_header 'Docker-Distribution-Api-Version' 'registry/2.0';
proxy_pass http://registry:80;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto "http";
}
location = /get-health {
access_log off;
default_type text/html;
return 200 'alive';
}
}
server {
listen 443 ssl;
ssl_certificate /etc/ssl/black.com.pem;
ssl_certificate_key /etc/ssl/black.com.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
client_max_body_size 0;
client_body_buffer_size 16k;
access_log off;
location / {
proxy_pass http://127.0.0.1;
}
}
上面的配置中,我们主要做了几件事情:
- 使用 Nginx 同时监听 80 和 443 端口,提供 HTTP 和 HTTPS 服务,而不是简单的使用端口转发,让同时支持两种客户端请求成为可能。
- 将 Nginx 接收到的请求转发到 distribution 服务中,并在请求转发过程中,添加并携带应用所需要的 Header。
- 对于 HTTPS 请求,在 Nginx 内部转发给 HTTP 请求,减少重复的代码配置,提升整体可维护性。
重启服务,你会发现现在仓库同时支持 HTTP 和 HTTPS 两种访问模式了。
配置需要身份验证的容器仓库
如果我们不想要复杂的身份角色认证,但是还是期望有一些基础的身份验证,避免容器镜像被覆盖,或者被未授权下载,可以使用 Auth Realm
为仓库添加一层简单的,能够被 Docker Client 支持的身份认证。
我们以前文中“配置无须身份验证的容器仓库”的配置为例,只需要添加几行REGISTRY_AUTH
相关的环境变量即可开启基础的身份认证功能。
version: "3.0"
services:
registry:
restart: always
image: registry:2
ports:
- 80:80
environment:
REGISTRY_HTTP_ADDR: 0.0.0.0:80
REGISTRY_LOG_LEVEL: warn
REGISTRY_LOG_ACCESSLOG_DISABLED: "true"
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_PATH: /htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
volumes:
- ./htpasswd:/htpasswd
- ./registry:/var/lib/registry
logging:
driver: "json-file"
options:
max-size: "1m"
细心的同学会看到,这里的认证使用了一个名为 htpasswd
的文件,如何生成这个文件呢?其实很简单,为了保证各平台兼容,我们可以使用 docker 镜像来一键生成。例如,生成一个用户名和密码都是 soulteary 的“认证信息”:
docker run --rm -it httpd:alpine htpasswd -Bbn soulteary soulteary
Unable to find image 'httpd:alpine' locally
alpine: Pulling from library/httpd
ca3cd42a7c95: Pull complete
cd86b11706ae: Pull complete
2dd1cdb18fe6: Pull complete
7e270a528f77: Pull complete
f781adcbe79a: Pull complete
Digest: sha256:50ebd27377c8ac9f6ad44ae57b747b40ccac67bed59d448bf39514a9814b644d
Status: Downloaded newer image for httpd:alpine
soulteary:$2y$05$9HIf7tpwZZu7nD4TXGP4dONmlrTouHpC8ozMVyO6Vs5fmx1UJLlrS
执行完毕上面的命令,docker 将会自动下载工具镜像,并计算出我们指定的用户名和密码的认证文件内容,上面日志中最后一行即是我们所需要的“认证信息”。
我们将 soulteary: $2y$05$9HIf7tpwZZu7nD4TXGP4dONmlrTouHpC8ozMVyO6Vs5fmx1UJLlrS
这个内容保存至 htpasswd
文件后,重启服务,进行简单的功能验证。
先尝试再下载一次镜像,会看到因为没有登陆,缺少凭证而无法下载镜像内容。
docker pull docker.soulteary.cn/registry:2
Error response from daemon: Head http://docker.soulteary.cn/v2/abc/registry/manifests/2: no basic auth credentials
接着,使用刚刚指定的用户名和密码进行仓库登陆:
docker login --username soulteary --password soulteary docker.soulteary.cn
然后,尝试再次推送或者拉取镜像,会看到一切顺利:
docker push docker.soulteary.cn/registry:2
The push refers to repository [docker.soulteary.cn/registry]
b2335c628697: Layer already exists
3cb95fe83bcd: Layer already exists
d2ecc62f3d1a: Layer already exists
8e95b38dd51d: Layer already exists
2b2bcc6e6724: Layer already exists
2: digest: sha256:160c621b9bd98c4becce1c3b14e4866524dbe898d3af2e48d81fa1821b82c615 size: 1363
docker pull docker.soulteary.cn/registry:2
2: Pulling from abc/registry
Digest: sha256:160c621b9bd98c4becce1c3b14e4866524dbe898d3af2e48d81fa1821b82c615
Status: Image is up to date for docker.soulteary.cn/registry:2
docker.soulteary.cn/registry:2
切换使用 Nginx 提供仓库认证
虽然使用前文“使用 Nginx 配置同时支持两种协议的仓库”小节中的方式,也可以让容器仓库同时支持在 HTTP 和 HTTPS 模式下都能够支持认证功能。
但是考虑核心应用职责应该单一,以及后续服务扩展灵活,比如将认证服务外接之类的场景,更推荐的是使用 Nginx 来处理认证鉴权,让 distribution 只单纯负责容器镜像相关数据的 “CRUD”。
compose 配置同“使用 Nginx 配置同时支持两种协议的仓库”,这里不再赘述,我们仅需要调整 Nginx 配置文件即可:
server {
listen 80;
client_max_body_size 0;
client_body_buffer_size 16k;
access_log off;
location / {
auth_basic "Registry realm";
auth_basic_user_file /etc/nginx/conf.d/nginx.htpasswd;
add_header 'Docker-Distribution-Api-Version' 'registry/2.0';
proxy_pass http://docker-registry.black.com:80;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto "http";
}
location = /get-health {
access_log off;
default_type text/html;
return 200 'alive';
}
}
server {
listen 443 ssl;
ssl_certificate /etc/ssl/black.com.pem;
ssl_certificate_key /etc/ssl/black.com.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
client_max_body_size 0;
client_body_buffer_size 16k;
access_log off;
location / {
proxy_pass http://127.0.0.1;
}
}
在配置中添加 “auth_basic
” 配置后,重启服务,就可以完成认证功能的职责切换啦。
最后
关于容器镜像仓库先聊到这里。
如果你在生产使用,再次提醒,建议搭配支持 S3 协议的对象存储一起使用,让生产数据更安全。
–EOF