在 Ubuntu 上搭建私有 Docker 镜像仓库的指南

引言

Docker 仓库是一个管理 Docker 容器镜像存储和分发的应用。仓库集中管理容器镜像,并为开发者减少构建时间。Docker 镜像通过虚拟化保证相同的运行环境,但构建一个镜像可能需要大量的时间投入。例如,开发者不必单独安装依赖和包来使用 Docker,而是可以从仓库下载一个包含所有必要组件的压缩镜像。此外,开发者可以使用 TravisCI 等持续集成工具自动化推送镜像到仓库,以在生产和开发期间无缝更新镜像。

在本教程中,你将设置并保护你自己的私有 Docker 仓库。你将使用 Docker Compose 来定义配置,运行你的 Docker 容器,并使用 Nginx 将服务器流量从互联网转发到运行中的 Docker 容器。完成本教程后,你将能够将自定义 Docker 镜像推送到你的私有仓库,并从远程服务器安全地拉取镜像。

前提条件

完成本教程,你需要以下条件:

  • 两台按照 Ubuntu 初始服务器设置指南设置的 Ubuntu 服务器,包括一个 sudo 非根用户和防火墙。一台服务器将托管你的私有 Docker 仓库,另一台将是你的客户端服务器。
  • 两台服务器上都安装了 Docker,你可以通过遵循如何在 Ubuntu 上安装和使用 Docker 的步骤 1 和 2 来设置。

托管服务器上,你需要设置:

  • 托管服务器上安装 Docker Compose,你可以通过遵循如何在 Ubuntu 上安装和使用 Docker Compose 的步骤 1 来设置。

  • 在你的托管服务器上安装 Nginx,你可以通过遵循如何在 Ubuntu 上安装 Nginx 的步骤来设置。

  • 为你的私有 Docker 仓库使用 Let’s Encrypt 保护 Nginx,你可以通过遵循如何在 Ubuntu 上使用 Let’s Encrypt 保护 Nginx 的教程来设置。确保在步骤 4 中将所有流量从 HTTP 重定向到 HTTPS。

    • 一个注册的域名,解析到你用来托管私有 Docker 仓库的服务器。你将作为 Let’s Encrypt 先决条件的一部分来设置它。在本教程中,我们将称之为 your_domain

    建议使用至少有以下配置的服务器:

    4 核心的 CPU,4GB 的内存

    选择服务器提供商
    为了本教程的演示,我将以一个具体的云服务提供商为例,展示如何进行操作。选择哪个提供商根据个人偏好和需求来决定。

    以下步骤仅供参考,请根据实际需求选择配置。

    购买云服务器
    本示例中,我们选择了香港作为服务器区域。

    点击 云产品云服务器立即购买

    选择操作系统
    在创建服务器实例时,选择 Ubuntu 作为操作系统。

    连接到服务器
    使用 X-shell 或偏好的 SSH 客户端,通过远程用户名和密码连接到服务器。成功连接后,将看到特定的欢迎信息,表明已成功登录。

    https://syxoss.oss-cn-hangzhou.aliyuncs.com/Typora202411211021991.png

第 1 步 — 安装和配置 Docker 仓库

在命令行上运行 Docker 对于初学者和测试容器很有用,但对于涉及多个并行运行容器的更大部署,可能会变得不便。

使用 Docker Compose,你可以通过编写一个 .yml 文件来设置每个容器的配置和信息,以便容器之间能够相互通信。你可以使用 docker compose 工具向你的应用程序的所有组件发出命令,并作为一个组来控制它们。

Docker 仓库本身是一个具有多个组件的应用,因此你将使用 Docker Compose 来管理它。要启动仓库的一个实例,你将设置一个 docker-compose.yml 文件来定义它以及你的仓库将存储其数据的磁盘位置。

你将在托管服务器上的一个名为 docker-registry 的目录中存储配置。通过运行以下命令创建它:

mkdir -p ~/docker-registry
cd ~/docker-registry

然后,创建一个名为 data 的子目录,你的仓库将在这里存储其镜像:

mkdir -p ~/docker-registry/data

通过运行以下命令创建并打开一个名为 docker-compose.yml 的文件:

touch ~/docker-registry/docker-compose.yml
nano ~/docker-registry/docker-compose.yml

添加以下内容,定义一个基本的 Docker 仓库实例:

version: '3'

services:
  registry:
    image: registry:latest
    ports:
      - "5000:5000"
    environment:
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
    volumes:
      - ./data:/data

首先,你将第一个服务命名为 registry,并设置其镜像为 registry,使用最新版本。然后,在 ports 下,你将主机的端口 5000 映射到容器的端口 5000,这将允许你向服务器的端口 5000 发送请求,并将请求转发到仓库。

environment 部分,你将 REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY 变量设置为 /data,指定它应该存储数据的卷。然后,在 volumes 部分,你将主机文件系统上的 /data 目录映射到容器中的 /data,这充当一个通道。数据实际上将存储在主机的文件系统上。

保存并关闭文件。

你现在可以通过运行以下命令启动配置:

docker-compose up -d

仓库容器及其依赖项将被下载并启动。

你可以按 CTRL+C 停止其执行。

在这一步中,你创建了一个 Docker Compose 配置,启动了一个在端口 5000 上监听的 Docker 仓库。在接下来的步骤中,你将在你的域名下暴露它并设置认证。

第 2 步 — 设置 Nginx 端口转发

作为先决条件的一部分,你已在你的域名上启用了 HTTPS。要在那里暴露你安全的 Docker 仓库,你需要配置 Nginx 将流量从你的域名转发到仓库容器。

你已经设置了包含服务器配置的 /etc/nginx/sites-available/your_domain 文件。通过运行以下命令打开它进行编辑:

sudo nano /etc/nginx/sites-available/your_domain

找到现有的 location 块:

...
        location / {
  ...
        }
...

你需要将流量转发到端口 5000,你的仓库将在那里监听流量。你还想在转发到仓库的请求中追加头信息,这提供了服务器关于请求本身的额外信息。用以下行替换 location 块的现有内容:

...
location / {
    # Do not allow connections from docker 1.5 and earlier
    # docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents
    if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) {
      return 404;
    }

    proxy_pass                          http://localhost:5000;
    proxy_set_header  Host              $http_host;   # required for docker client's sake
    proxy_set_header  X-Real-IP         $remote_addr; # pass on real client's IP
    proxy_set_header  X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header  X-Forwarded-Proto $scheme;
    proxy_read_timeout                  900;
}
...

if 块检查请求的用户代理,并验证 Docker 客户端的版本是否高于 1.5,并且它不是一个尝试访问的 Go 应用程序。有关这方面的更多解释,你可以在 Docker 的仓库 Nginx 指南中找到 nginx 头配置。

保存并关闭文件。通过重启 Nginx 应用更改:


sudo systemctl restart nginx

如果你收到错误消息,请仔细检查你添加的配置。

为了确认 Nginx 正确地将流量转发到你的仓库容器的端口 `5000`,运行以下命令:

```bash
curl -I https://your_domain/v2

然后,在浏览器窗口中,导航到你的域名并访问 v2 端点,如下所示:

https://your_domain/v2

浏览器将加载一个空的 JSON 对象:

{}

容器由于端口转发接收了请求,并返回了 {} 的响应。代码 200 表示容器成功处理了请求。

按终端中的 CTRL+C 停止其执行。

现在你已经设置了端口转发,你将增强仓库的安全性。

第 3 步 — 设置认证

Nginx 允许你为其管理的网站设置 HTTP 认证,你可以用它来限制对你的 Docker 仓库的访问。为了实现这一点,你将创建一个带有 htpasswd 的认证文件,并向其中添加用户名和密码组合,这些组合将被接受。这个过程将启用对你的仓库的认证。

你可以通过安装 apache2-utils 包来获取 htpasswd 工具。运行以下命令来安装它:

sudo apt-get update
sudo apt-get install apache2-utils

你将在 ~/docker-registry/auth 下存储带有凭据的认证文件。通过运行以下命令创建它:

mkdir -p ~/docker-registry/auth
cd ~/docker-registry/auth

创建第一个用户,将 username 替换为你想要使用的用户名。-B 标志命令使用 bcrypt 算法,这是 Docker 要求的:

htpasswd -Bbn username password > registry.password

输入密码时会提示。凭据组合将被追加到 registry.password

注意: 要添加更多用户,请在没有 -c 的情况下重新运行上一个命令:

  • c 创建一个新文件,所以去掉它将更新现有文件。

现在既然已经制作了凭据列表,你将编辑 docker-compose.yml 以指示 Docker 使用你创建的文件进行用户认证。打开它进行编辑:

nano ~/docker-registry/docker-compose.yml

添加以下突出显示的行:

version: '3'

services:
  registry:
    image: registry:latest
    ports:
      - "5000:5000"
    environment:
      REGISTRY_AUTH: htpasswd
      REGISTRY_AUTH_HTPASSWD_REALM: Registry
      REGISTRY_AUTH_HTPASSWD_PATH: /auth/registry.password
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
    volumes:
      - ./auth:/auth
      - ./data:/data

你添加了环境变量,指定使用 HTTP 认证并提供 htpasswd 创建的文件的路径。对于 REGISTRY_AUTH,你指定了 htpasswd 作为其值,这是你使用的认证方案,并设置了 REGISTRY_AUTH_HTPASSWD_PATH 到认证文件的路径。REGISTRY_AUTH_HTPASSWD_REALM 表示 htpasswd 领域的名字。

你还挂载了 ./auth 目录,使文件在仓库容器内可用。保存并关闭文件。

现在你可以验证你的认证是否正确工作。首先,导航到主目录:

cd ~/docker-registry

然后,通过执行以下命令运行仓库:

docker-compose up -d

在你的浏览器中,刷新你的域名页面。系统将提示你输入用户名和密码。

在提供有效的凭据组合后,你将访问带有空 JSON 对象的页面:

{}

你已经成功认证并获得了对仓库的访问权限。按终端中的 CTRL+C 退出。

你的仓库现在是安全的,只有在认证后才能访问。你接下来将配置它作为后台进程运行,同时在重启后自动启动。

第 4 步 — 将 Docker 仓库作为服务启动

你可以确保仓库容器在系统每次启动时都会启动,或者在崩溃后,通过指示 Docker Compose 始终保持其运行。

打开 docker-compose.yml 进行编辑:

nano ~/docker-registry/docker-compose.yml

registry 块中添加以下行:

...
  registry:
    restart: always
...

restart 设置为 always 确保容器将在重启后存活。完成后,保存并关闭文件。

你现在可以通过传递 -d 将你的仓库作为后台进程启动:

docker-compose up -d

有了你的仓库在后台运行,你可以自由地关闭这个 SSH 会话,终端,仓库不会受到影响。

由于 Docker 镜像可能非常大,你接下来将增加 Nginx 接受上传的最大文件大小。

第 5 步 — 增加 Nginx 的文件上传大小

在你可以将镜像推送到仓库之前,你需要确保你的仓库能够处理大文件上传。Nginx 中文件上传的默认大小限制是 1m,这对于 Docker 镜像来说远远不够。为了提高它,你将修改位于 /etc/nginx/nginx.conf 的主要 Nginx 配置文件。

打开它进行编辑:

sudo nano /etc/nginx/nginx.conf

将以下突出显示的行添加到 http 部分:

...
http {
    client_max_body_size 16384m;
    ...
}
...

client_max_body_size 参数现在设置为 16384m,使得最大上传大小等于 16GB。

保存并关闭文件。

重启 Nginx 以应用配置更改:

sudo systemctl restart nginx

在这一步中,你更新了 Nginx 允许的文件大小。你现在可以上传大镜像到你的 Docker 仓库,而不会有 Nginx 阻止传输或出错。

第 6 步 — 发布到你的私有 Docker 仓库

现在你的 Docker 仓库服务器正在运行并接受大文件大小,你可以尝试将镜像推送到它。由于你没有现成的镜像可用,你将使用 Docker Hub 上的 ubuntu 镜像,这是一个公共 Docker 仓库,来测试这个。

在客户端服务器的新终端会话中,运行以下命令下载 ubuntu 镜像,运行它,并获取其 shell 访问:

docker run -it ubuntu /bin/bash
  • it 标志给你提供了容器的交互式 shell 访问。

一旦进入,通过运行以下命令创建一个名为 SUCCESS 的文件:

touch SUCCESS

通过创建这个文件,你已经自定义了你的容器。你稍后将使用它来检查你使用的是否完全相同的容器。

通过运行以下命令退出容器 shell:

exit

现在从你刚刚自定义的容器创建一个新的镜像:

docker commit [CONTAINER_ID] your_domain/test-image

[CONTAINER_ID] 替换为你的容器 ID。

这个新镜像现在在本地可用,你将把它推送到你的容器仓库。首先,你必须登录:

docker login your_domain

当提示时,输入你在第 3 步中定义的用户名和密码凭据。

输出将是:

Login Succeeded

登录成功后,重命名创建的镜像:

docker tag your_domain/test-image your_domain/test-image:latest

最后,将新标记的镜像推送到你的仓库:

docker push your_domain/test-image:latest

你将收到类似的输出:

The push refers to a repository [your_domain/test-image]
1cf9c9034825: Pushed
f4a670ac65b6: Pushed
latest: digest: sha256:95112d0af51e5470d74ead77932954baca3053e04d201ac4639bdf46d5cd515b size: 736

你已经验证了你的仓库通过登录处理用户认证,并允许认证用户将镜像推送到仓库。你接下来将尝试从你的仓库拉取镜像。

第 7 步 — 从你的私有 Docker 仓库拉取

现在你已经将镜像推送到你的私有仓库,你将尝试从它那里拉取。

在主服务器上,使用你之前设置的用户名和密码登录:

docker login your_domain

尝试通过运行以下命令拉取 test-image

docker pull your_domain/test-image:latest

Docker 将下载镜像。使用以下命令运行容器:

docker run -it your_domain/test-image:latest /bin/bash

它将加载容器的 shell。

通过运行以下命令列出存在的文件:

ls

文件列表将包括你之前创建的 SUCCESS 文件,证实这个容器使用的是你创建的相同镜像:

退出容器 shell:

exit

你已经测试了推送和拉取镜像,并完成了设置一个安全的仓库,你可以用它来存储自定义镜像。

结论

在本教程中,你设置了你自己的私有 Docker 仓库并发布了一个 Docker 镜像到它。如引言中提到的,你还可以使用 TravisCI 或类似的 CI 工具自动化推送到私有仓库。