使用容器搭建简单可靠的容器仓库

使用容器搭建简单可靠的容器仓库

提到容器仓库,我们一般会想到 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 nginx:alpine 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 pulldocker push 和仓库进行交互了。

docker push docker.soulteary.cn: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"

这里的证书可以使用 pemcrt等合法的证书格式。如果使用这个模式,则可以将上一小节中对于 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;
    }
}

上面的配置中,我们主要做了几件事情:

  1. 使用 Nginx 同时监听 80 和 443 端口,提供 HTTP 和 HTTPS 服务,而不是简单的使用端口转发,让同时支持两种客户端请求成为可能。
  2. 将 Nginx 接收到的请求转发到 distribution 服务中,并在请求转发过程中,添加并携带应用所需要的 Header。
  3. 对于 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


我们有一个小小的折腾群,里面聚集了一些喜欢折腾的小伙伴。

在不发广告的情况下,我们在里面会一起聊聊软硬件、HomeLab、编程上的一些问题,也会在群里不定期的分享一些技术沙龙的资料。

喜欢折腾的小伙伴欢迎扫码添加好友。(请注明来源和目的,备注实名,否则不会通过审核)


本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 署名 4.0 国际 (CC BY 4.0)

本文作者: 苏洋

创建时间: 2021年04月13日 统计字数: 9818字 阅读时间: 20分钟阅读 本文链接: soulteary.com/2021/04/1

编辑于 2021-04-13 21:14

文章被以下专栏收录