容器注册表是一个存储目录,您可以从中推送和拉取容器镜像。

有许多公共和私有注册表可供开发者使用,例如Docker HubAmazon ECRGoogle Cloud Artifact Registry。但是有时,您可能不想依赖外部供应商,而是想自己托管镜像。这使您能够更多地控制注册表的配置方式以及容器镜像的托管位置。

本文是一个实践教程,将教您如何自行托管容器注册表。

目录

如果你已经熟悉Docker和NGINX等工具,并对容器有一个大致的了解,那么你将能更好地理解这篇文章。

容器镜像是什么?

在讨论容器注册表之前,让我们先了解一下容器镜像。简而言之,容器镜像是一个包含运行容器所需的所有文件、库和配置的包。它们由组成,每个层代表一组文件系统更改,包括添加、删除或修改文件。

创建容器镜像最常见的方式是使用一个Dockerfile

# 构建一个镜像
docker build -t pliutau/hello-world:v0 .

# 检查本地镜像
docker images
# 仓库名        标签       镜像ID         创建时间          大小
# hello-world   latest    9facd12bbcdd   22秒前           11MB

这将创建一个容器镜像,并存储在您的本地机器上。但是,如果您想与他人共享此镜像或在不同机器上使用它怎么办?容器注册表就在这时发挥作用。

容器注册表是什么?

容器注册表是一个存储目录,您可以从中推送和拉取容器镜像。镜像按仓库分组,仓库是一组具有相同名称的相关镜像的集合。例如,在 Docker Hub 注册表上,nginx 是包含不同版本的 NGINX 镜像的仓库名称。

一些注册表是公开的,这意味着托管在它们上面的镜像可以被互联网上的任何人访问。像 Docker Hub 这样的公开注册表是托管开源项目的不错选择。

另一方面,私有注册表为企业的容器镜像存储提供了安全和隐私的解决方案,无论是在云中还是本地。这些私有注册表通常带有高级安全功能和技术支持。

有越来越多的私有注册表可供选择,例如 Amazon ECRGCP Artifact RegistryGitHub Container Registry,而 Docker Hub 也提供私有仓库功能。

作为开发人员,在使用`docker push`和`docker pull`命令时,你会与容器注册表进行交互。

docker push docker.io/pliutau/hello-world:v0

# 如果是Docker Hub,我们还可以省略注册表部分
docker push pliutau/hello-world:v0

让我们来了解一下容器镜像URL的结构:

docker pull docker.io/pliutau/hello-world:v0@sha256:dc11b2...
                |            |            |          |
                ↓            ↓            ↓          ↓
             registry    repository      tag       digest

为什么您可能想要自托管容器注册表

有时,您可能不想依赖AWS或GCP等提供商,而是想自己托管图像。这使得您的内部基础设施更加封闭,并减少对外部供应商的依赖。在某些高度受监管的行业,这甚至是一个要求。

自托管注册表运行在您自己的服务器上,这使您能够更多地控制注册表的配置方式以及容器镜像的托管位置。与此同时,它也带来了维护和保护注册表的代价。

如何自托管容器注册表

有几种开源的容器注册表解决方案可供选择。最受欢迎的一个是由Docker官方支持的,名为registry,它实现了用于存储和分发容器镜像和工件的配置。这意味着您可以在容器内部运行自己的注册表。

要在服务器上运行注册表,以下是主要步骤:

  • 在服务器上安装Docker和Docker Compose。

  • 配置并运行registry容器。

  • 运行NGINX处理TLS并转发请求到注册表容器。

  • 设置SSL证书并配置域名。

第一步:在服务器上安装Docker和Docker Compose。

您可以使用任何支持Docker的服务器。例如,您可以使用运行Ubuntu的DigitalOcean Droplet。为了这个演示,我使用了Google Cloud Compute来创建一个运行Ubuntu的虚拟机。

neofetch

# 操作系统:Ubuntu 20.04.6 LTS x86_64
# CPU:Intel Xeon (2) @ 2.200GHz
# 内存:3908MiB

一旦我们进入虚拟机内部,我们应该安装Docker和Docker Compose。Docker Compose是可选的,但它使得管理多容器应用程序变得更加容易。

# 安装docker引擎和docker-compose
sudo snap install docker

# 验证安装
docker --version
docker-compose --version

第二步:配置并运行注册表容器

接下来,我们需要配置我们的注册表容器。以下compose.yaml文件将创建一个带有用于存储图像的卷和用于存储密码文件的卷的注册表容器。

services:
  registry:
    image: registry:latest
    environment:
      REGISTRY_AUTH: htpasswd
      REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
      REGISTRY_AUTH_HTPASSWD_PATH: /auth/registry.password
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
    volumes:
      # 挂载密码文件
      - ./registry/registry.password:/auth/registry.password
      # 挂载数据目录
      - ./registry/data:/data
    ports:
      - 5000

REGISTRY_AUTH_HTPASSWD_PATH中定义的密码文件用于用户从注册表中推送或拉取图像时进行身份验证。我们应该使用htpasswd命令创建一个密码文件。我们还应该创建一个用于存储图像的文件夹。

mkdir -p ./registry/data

# 安装htpasswd
sudo apt install apache2-utils

# 创建密码文件。用户名:busy,密码:bee
htpasswd -Bbn busy bee > ./registry/registry.password

现在我们可以启动注册表容器。如果你看到这条消息,那么一切都在正常工作:

docker-compose up

# 成功的运行应该输出类似的内容:
# registry | level=info msg="listening on [::]:5000"

第3步:运行NGINX以处理TLS

如前所述,我们可以使用NGINX来处理TLS并将请求转发到注册表容器。

Docker注册表需要一个有效的受信任的SSL证书才能正常工作。你可以使用类似Let’s Encrypt的东西来获取证书,或者手动获取。确保你有指向你服务器的域名(在我的情况下是registry.pliutau.com)。为了这个演示,我已经使用certbot获取了证书,并将其放在./nginx/certs目录中。

由于我们正在容器中运行Docker Registry,我们也可以通过在compose.yaml文件中添加以下服务来在容器中运行NGINX:

services:
  registry:
    # ...
  nginx:
    image: nginx:latest
    depends_on:
      - registry
    volumes:
      # 挂载nginx配置
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      # 挂载从Let's Encrypt获得的证书
      - ./nginx/certs:/etc/nginx/certs
    ports:
      - "443:443"

我们的nginx.conf文件可能看起来像这样:

worker_processes auto;

events {
    worker_connections 1024;
}

http {
    upstream registry {
        server registry:5000;
    }

    server {
        server_name registry.pliutau.com;
        listen 443 ssl;

        ssl_certificate /etc/nginx/certs/fullchain.pem;
        ssl_certificate_key /etc/nginx/certs/privkey.pem;

        location / {
            # 对大图像的重要设置
            client_max_body_size                1000m;

            proxy_pass                          http://registry;
            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 $scheme;
            proxy_read_timeout                  900;
        }
    }
}

准备好了!

完成这些步骤后,我们可以运行我们的registry和NGINX容器。

docker-compose up

现在,在客户端方面,您可以从您的registry推拉图像。但是,我们首先需要登录到registry。

docker login registry.pliutau.com

# 用户名: busy
# 密码: bee
# 登录成功

现在,让我们构建并推送我们的镜像到我们自托管的registry:

docker build -t registry.pliutau.com/pliutau/hello-world:v0 .

docker push registry.pliutau.com/pliutau/hello-world:v0
# v0: digest: sha256:a56ea4... size: 738

您可以在服务器的data文件夹中检查上传的镜像:

ls -la ./registry/data/docker/registry/v2/repositories/

其他选项

按照上述示例,您也可以在Kubernetes上运行registry。或者,您可以使用像Harbor这样的托管registry服务,它是一个开源registry,提供高级安全特性,并兼容Docker和Kubernetes。

此外,如果您想要为您的自托管仓库提供一个用户界面,您可以使用如 joxit/docker-registry-ui 的项目,并将其运行在单独的容器中。

总结

自托管容器仓库可以让您完全控制您的仓库以及其部署方式。同时,它也需要您维护和保障仓库的安全,这是需要付出的代价。

无论您运行自托管仓库的原因是什么,您现在都知道如何去做了。从这您可以比较不同的选项,并选择最适合您需求的一个。

您可以在这个演示的完整源代码在 GitHub 上找到。此外,您也可以在 我们的YouTube频道 上观看它的视频。