如何在Ubuntu20.04上设置私有Docker注册表
作为 Write for DOnations 计划的一部分,作者选择了 Free and Open Source Fund 来接受捐赠。
介绍
Docker Registry 是一个管理存储和交付 Docker 容器镜像的应用程序。 注册表集中容器映像并减少开发人员的构建时间。 Docker 镜像通过虚拟化保证了相同的运行时环境,但是构建镜像可能需要大量的时间投资。 例如,开发人员可以从包含所有必要组件的注册表中下载压缩映像,而不是单独安装依赖项和包来使用 Docker。 此外,开发人员可以使用持续集成工具(例如 TravisCI)自动将镜像推送到注册表,从而在生产和开发过程中无缝更新镜像。
Docker 还有一个免费的公共注册表 Docker Hub,它可以托管您的自定义 Docker 镜像,但在某些情况下您不希望您的镜像公开可用。 映像通常包含运行应用程序所需的所有代码,因此在使用专有软件时最好使用私有注册表。
在本教程中,您将设置和保护您自己的私有 Docker Registry。 您将使用 Docker Compose 来定义运行 Docker 容器的配置,并使用 Nginx 将服务器流量从互联网转发到正在运行的 Docker 容器。 完成本教程后,您将能够将自定义 Docker 映像推送到您的私有注册表并从远程服务器安全地拉取该映像。
先决条件
- 按照Ubuntu 20.04初始服务器设置指南设置两台Ubuntu 20.04服务器,包括一个
sudo
非root用户和一个防火墙。 一台服务器将 托管 您的私有 Docker Registry,另一台将是您的 客户端 服务器。 - 按照 How To Install and Use Docker on Ubuntu 20.04 的 Step 1 和 2 在两台服务器上安装 Docker。
- 按照如何在Ubuntu 20.04上安装和使用Docker Compose的Step 1在主机服务器上安装Docker Compose。
- 按照 如何在 Ubuntu 20.04 上安装 Nginx 中的步骤在 主机 服务器上安装 Nginx。
- 遵循 如何在 Ubuntu 20.04 上使用 Let's Encrypt 保护 Nginx 教程,使用 Let's Encrypt 在您的服务器上保护 Nginx 以获取私有 Docker Registry。 确保在 Step 4 中将所有流量从 HTTP 重定向到 HTTPS。
- 解析为用于私有 Docker Registry 的服务器的域名。 您将把它设置为 Let's Encrypt 先决条件的一部分。 在本教程中,我们将其称为
your_domain
。
第 1 步 — 安装和配置 Docker 注册表
命令行上的 Docker 在启动和测试容器时很有用,但对于涉及并行运行的多个容器的大型部署,它被证明是笨拙的。
使用 Docker Compose,您可以编写一个 .yml
文件来设置每个容器的配置和容器相互通信所需的信息。 您可以使用 docker-compose
命令行工具向构成应用程序的所有组件发出命令,并将它们作为一个组进行控制。
Docker Registry 本身就是一个包含多个组件的应用程序,因此您将使用 Docker Compose 来管理它。 要启动注册表实例,您将设置一个 docker-compose.yml
文件来定义它以及注册表将存储其数据的磁盘位置。
您将把配置存储在主服务器上名为 docker-registry
的目录中。 通过运行创建它:
mkdir ~/docker-registry
导航到它:
cd ~/docker-registry
然后,创建一个名为 data
的子目录,您的注册表将在其中存储其图像:
mkdir data
通过运行以下命令创建并打开一个名为 docker-compose.yml
的文件:
nano docker-compose.yml
添加以下行,定义 Docker Registry 的基本实例:
~/docker-registry/docker-compose.yml
version: '3' services: registry: image: registry:2 ports: - "5000:5000" environment: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data volumes: - ./data:/data
首先,您将第一个服务命名为 registry
,并将其映像设置为 registry
,版本 2
。 然后,在 ports
下,将主机上的端口 5000
映射到容器的端口 5000
。 这允许您向服务器上的端口 5000
发送请求,并将请求转发到注册表。
在 environment
部分中,您将 REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY
变量设置为 /data
,指定应将其数据存储在哪个卷中。 然后,在 volumes
部分中,将主机文件系统上的 /data
目录映射到容器中的 /data
中,充当直通。 数据实际上将存储在主机的文件系统中。
保存并关闭文件。
您现在可以通过运行以下命令开始配置:
docker-compose up
将下载并启动注册表容器及其依赖项。
OutputCreating network "docker-registry_default" with the default driver Pulling registry (registry:2)... 2: Pulling from library/registry e95f33c60a64: Pull complete 4d7f2300f040: Pull complete 35a7b7da3905: Pull complete d656466e1fe8: Pull complete b6cb731e4f93: Pull complete Digest: sha256:da946ca03fca0aade04a73aa94b54ff0dc614216bdd1d47585f97b4c1bdaa0e2 Status: Downloaded newer image for registry:2 Creating docker-registry_registry_1 ... done Attaching to docker-registry_registry_1 registry_1 | time="2021-03-18T12:32:59.587157744Z" level=warning msg="No HTTP secret provided - generated random secret. This may cause problems with uploads if multiple registries are behind a load-balancer. To provide a shared secret, fill in http.secret in the configuration file or set the REGISTRY_HTTP_SECRET environment variable." go.version=go1.11.2 instance.id=119fe50b-2bb6-4a8d-902d-dfa2db63fc2f service=registry version=v2.7.1 registry_1 | time="2021-03-18T12:32:59.587912733Z" level=info msg="redis not configured" go.version=go1.11.2 instance.id=119fe50b-2bb6-4a8d-902d-dfa2db63fc2f service=registry version=v2.7.1 registry_1 | time="2021-03-18T12:32:59.598496488Z" level=info msg="using inmemory blob descriptor cache" go.version=go1.11.2 instance.id=119fe50b-2bb6-4a8d-902d-dfa2db63fc2f service=registry version=v2.7.1 registry_1 | time="2021-03-18T12:32:59.601503005Z" level=info msg="listening on [::]:5000" go.version=go1.11.2 instance.id=119fe50b-2bb6-4a8d-902d-dfa2db63fc2f service=registry version=v2.7.1 ...
您将在本教程后面部分解决 No HTTP secret provided
警告消息。 请注意,输出的最后一行显示它已成功开始侦听端口 5000
。
您可以按 CTRL+C
停止执行。
在这一步中,您创建了一个 Docker Compose 配置,该配置启动了一个 Docker Registry 侦听端口 5000
。 在接下来的步骤中,您将在您的域中公开它并设置身份验证。
第 2 步 — 设置 Nginx 端口转发
作为先决条件的一部分,您已在您的域中启用 HTTPS。 要在那里公开您的安全 Docker Registry,您只需配置 Nginx 以将流量从您的域转发到注册表容器。
您已经设置了包含服务器配置的 /etc/nginx/sites-available/your_domain
文件。 通过运行打开它进行编辑:
sudo nano /etc/nginx/sites-available/your_domain
找到现有的 location
块:
/etc/nginx/sites-available/your_domain
... location / { ... } ...
您需要将流量转发到端口 5000
,您的注册表将在该端口监听流量。 您还希望将标头附加到转发到注册表的请求中,它提供来自服务器的有关请求本身的附加信息。 将 location
块的现有内容替换为以下行:
/etc/nginx/sites-available/your_domain
... 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
上的注册表容器,请运行它:
docker-compose up
然后,在浏览器窗口中,导航到您的域并访问 v2
端点,如下所示:
https://your_domain/v2
您将看到一个空的 JSON 对象:
{}
在您的终端中,您将收到类似于以下内容的输出:
Outputregistry_1 | time="2018-11-07T17:57:42Z" level=info msg="response completed" go.version=go1.7.6 http.request.host=cornellappdev.com http.request.id=a8f5984e-15e3-4946-9c40-d71f8557652f http.request.method=GET http.request.remoteaddr=128.84.125.58 http.request.uri="/v2/" http.request.useragent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/604.4.7 (KHTML, like Gecko) Version/11.0.2 Safari/604.4.7" http.response.contenttype="application/json; charset=utf-8" http.response.duration=2.125995ms http.response.status=200 http.response.written=2 instance.id=3093e5ab-5715-42bc-808e-73f310848860 version=v2.6.2 registry_1 | 172.18.0.1 - - [07/Nov/2018:17:57:42 +0000] "GET /v2/ HTTP/1.0" 200 2 "" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/604.4.7 (KHTML, like Gecko) Version/11.0.2 Safari/604.4.7"
从最后一行可以看到,从浏览器向 /v2/
发出了 GET
请求,这是您向其发送请求的端点。 容器从端口转发接收到您发出的请求,并返回 {}
响应。 输出最后一行的代码 200
表示容器成功处理了请求。
按 CTRL+C
停止执行。
现在您已经设置了端口转发,您将继续提高注册表的安全性。
第 3 步 — 设置身份验证
Nginx 允许您为其管理的站点设置 HTTP 身份验证,您可以使用它来限制对 Docker Registry 的访问。 为此,您将使用 htpasswd
创建一个身份验证文件,并向其中添加将被接受的用户名和密码组合。
您可以通过安装 apache2-utils
软件包获得 htpasswd
实用程序。 通过运行:
sudo apt install apache2-utils -y
您将在 ~/docker-registry/auth
下存储带有凭据的身份验证文件。 通过运行创建它:
mkdir ~/docker-registry/auth
导航到它:
cd ~/docker-registry/auth
创建第一个用户,将 username
替换为您要使用的用户名。 -B
标志命令使用 bcrypt
算法,Docker 需要该算法:
htpasswd -Bc registry.password username
出现提示时输入密码,凭据组合将附加到 registry.password
。
注意:要添加更多用户,重新运行上一个不带-c
的命令,创建一个新文件:
htpasswd -B registry.password username
现在已创建凭据列表,您将编辑 docker-compose.yml
以命令 Docker 使用您创建的文件来验证用户身份。 通过运行打开它进行编辑:
nano ~/docker-registry/docker-compose.yml
添加突出显示的行:
~/docker-registry/docker-compose.yml
version: '3' services: registry: image: registry:2 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
在您的浏览器中,刷新您的域的页面。 系统会要求您输入用户名和密码。
提供有效的凭据组合后,您将看到一个空的 JSON 对象:
{}
这意味着您已成功通过身份验证并获得了对注册表的访问权限。 按 CTRL+C
退出。
您的注册表现在是安全的,只有在身份验证后才能访问。 您现在将其配置为作为后台进程运行,同时通过自动启动来恢复重启。
第 4 步 — 启动 Docker Registry 作为服务
您可以通过指示 Docker Compose 始终保持其运行来确保注册表容器在每次系统启动或崩溃后启动。 打开docker-compose.yml
进行编辑:
nano docker-compose.yml
在 registry
块下添加以下行:
码头工人-compose.yml
... registry: restart: always ...
将 restart
设置为始终可确保容器在重新启动后仍然有效。 完成后,保存并关闭文件。
您现在可以通过传入 -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
部分,并添加以下行:
/etc/nginx/nginx.conf
... http { client_max_body_size 16384m; ... } ...
client_max_body_size
参数现在设置为 16384m
,使最大上传大小等于 16GB。
完成后保存并关闭文件。
重新启动 Nginx 以应用配置更改:
sudo systemctl restart nginx
您现在可以将大图像上传到您的 Docker 注册表,而 Nginx 不会阻止传输或出错。
第 6 步 — 发布到您的私有 Docker 注册表
现在您的 Docker Registry 服务器已启动并正在运行,并且可以接受大文件,您可以尝试将图像推送到它。 由于您没有任何可用的映像,因此您将使用来自公共 Docker 注册表的 Docker Hub 中的 ubuntu
映像进行测试。
在您的第二个客户端服务器上,运行以下命令来下载 ubuntu
映像,运行它,然后访问它的 shell:
docker run -t -i ubuntu /bin/bash
-i
和 -t
标志为您提供对容器的交互式 shell 访问。
进入后,通过运行以下命令创建一个名为 SUCCESS
的文件:
touch /SUCCESS
通过创建此文件,您已经自定义了您的容器。 稍后您将使用它来检查您是否使用完全相同的容器。
运行以下命令退出容器外壳:
exit
现在,从您刚刚自定义的容器中创建一个新图像:
docker commit $(docker ps -lq) test-image
新镜像现在在本地可用,您将把它推送到新的容器注册表。 首先,您必须登录:
docker login https://your_domain
出现提示时,输入您在本教程第 3 步中定义的用户名和密码组合。
输出将是:
Output... Login Succeeded
登录后,重命名创建的图像:
docker tag test-image your_domain/test-image
最后,将新标记的图像推送到您的注册表:
docker push your_domain/test-image
您将收到类似于以下内容的输出:
OutputThe push refers to a repository [your_domain/test-image] 420fa2a9b12e: Pushed c20d459170d8: Pushed db978cae6a05: Pushed aeb3f02e9374: Pushed latest: digest: sha256:88e782b3a2844a8d9f0819dc33f825dde45846b1c5f9eb4870016f2944fe6717 size: 1150
您已验证您的注册表通过登录处理用户身份验证,并允许经过身份验证的用户将图像推送到注册表。 您现在将尝试从注册表中提取图像。
第 7 步 — 从您的私有 Docker 注册表中提取
现在您已将图像推送到您的私有注册表,您将尝试从中提取。
在主服务器上,使用您之前设置的用户名和密码登录:
docker login https://your_domain
尝试通过运行拉动 test-image
:
docker pull your_domain/test-image
Docker 应该下载图像。 使用以下命令运行容器:
docker run -it your_domain/test-image /bin/bash
通过运行列出存在的文件:
ls
您将看到您之前创建的 SUCCESS
文件,确认它与您创建的图像相同:
SUCCESS bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
运行以下命令退出容器外壳:
exit
现在您已经测试了推送和拉取图像,您已经完成了设置一个可用于存储自定义图像的安全注册表。
结论
在本教程中,您设置了自己的私有 Docker Registry,并向其发布了 Docker 镜像。 正如介绍中提到的,您还可以使用 TravisCI 或类似的 CI 工具,直接自动推送到私有注册表。 通过在您的工作流程中利用 Docker 容器,您可以确保包含代码的映像在任何机器上都会产生相同的行为,无论是在生产中还是在开发中。 有关编写 Docker 文件的更多信息,您可以访问 官方文档 最佳实践。