4

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

 3 years ago
source link: https://soulteary.com/2021/04/13/use-docker-to-build-a-simple-and-reliable-container-registry.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

2021年04月13日阅读Markdown格式9825字20分钟阅读

提到容器仓库,我们一般会想到 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 pulldocker 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"

这里的证书可以使用 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 协议的对象存储一起使用,让生产数据更安全。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK