如何在Ubuntu 22.04上设置私有Docker注册表
简介
Docker注册表是一款管理Docker容器镜像存储和发布的应用程序。注册表将容器镜像集中存储,并缩短了开发者的构建时间。Docker镜像通过虚拟化提供相同的运行环境,但构建一个镜像可能需要很长时间。例如,开发者可以从注册表下载一个压缩的镜像,其中包含了所有必需的组件,而无需单独安装依赖和软件包以使用Docker。此外,开发者可以利用持续集成工具(例如TravisCI)自动推送镜像到注册表,在生产和开发期间无缝更新镜像。
Docker Hub是一个免费的公共注册表,可以托管你的自定义Docker镜像,但有些情况下你可能不希望你的镜像对外公开。镜像通常包含运行应用程序所需的所有代码,因此在使用专有软件时使用私有注册表可能更可取。
在本教程中,您将设置和保护自己的私有Docker Registry。您将使用Docker Compose来定义运行Docker容器的配置,并使用Nginx来将来自互联网的服务器流量转发到正在运行的Docker容器。完成本教程后,您将能够将自定义的Docker镜像推送到您的私有注册表,并从远程服务器安全地拉取该镜像。
先决条件
要完成这个教程,你需要准备以下物品:
- Two Ubuntu 22.04 servers set up by following the Ubuntu 22.04 Initial Server Setup Guide, including a sudo non-root user and a firewall. One server will host your private Docker Registry and the other will be your client server.
- Docker installed on both servers, which you can set up by following Steps 1 and 2 of How To Install and Use Docker on Ubuntu 22.04.
在主机服务器上,您将需要进行设置:
- Docker Compose installed on the host server, which you can set up by following Step 1 of How To Install and Use Docker Compose on Ubuntu 22.04.
- Nginx installed on your host server, which you can set up by following the steps in How To Install Nginx on Ubuntu 22.04.
- Nginx secured with Let’s Encrypt on your host server for the private Docker Registry, which you can set up by following the How To Secure Nginx with Let’s Encrypt on Ubuntu 22.04 tutorial. Make sure to redirect all traffic from HTTP to HTTPS in Step 4.A registered domain name that resolves to the server you’re using to host the private Docker Registry. You will set this up as part of the Let’s Encrypt prerequisite. In this tutorial, we’ll refer to it as your_domain.
步骤1 — 安装和配置Docker仓库
在开始和测试容器时,在命令行上运行Docker非常有用,但是在涉及并行运行多个容器的大规模部署中,这种方式可能变得难以控制。
使用Docker Compose,您只需编写一个.yml文件来设置每个容器的配置和容器之间需要进行通信的信息。您可以使用docker compose工具向构成应用程序的所有组件发出命令,并将它们作为一个组进行控制。
Docker Registry本身是一个具有多个组件的应用程序,因此您将使用Docker Compose来管理它。为了启动Registry的一个实例,您需要设置一个docker-compose.yml文件来定义它以及您的Registry将存储数据的磁盘位置。
您将在主机服务器上的一个名为docker-registry的目录中存储配置。通过运行以下命令创建该目录:
- mkdir ~/docker-registry
前往那里:
- cd ~/docker-registry
然后,创建一个名为”data”的子目录,您的注册表将在其中存储其图像。
- mkdir data
通过运行以下命令来创建并打开一个名为docker-compose.yml的文件:
- nano docker-compose.yml
添加下列代码行,定义一个基本的Docker Registry实例:
version: '3'
services:
registry:
image: registry:latest
ports:
- "5000:5000"
environment:
REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
volumes:
- ./data:/data
首先,您命名第一个服务注册表,并将其镜像设置为registry,使用最新版本。然后,在端口下,您将主机的端口5000映射到容器的端口5000,这将允许您向服务器的端口5000发送请求,并将请求转发到注册表。
在环境部分,您将REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY变量设置为/data,指定它应该存储数据的卷。然后,在卷部分,您将主机文件系统上的/data目录映射到容器中的/data目录,充当透传的作用。数据实际上将存储在主机的文件系统上。
保存并关闭文件。
现在你可以通过运行来开始配置:
- docker compose up
注册表容器及其依赖项将被下载并启动。
[+] Running 2/2 ⠿ Network docker-registry_default Created 0.1s ⠿ Container docker-registry-registry-1 Created 0.1s Attaching to docker-registry-registry-1 docker-registry-registry-1 | time=”2022-11-19T14:31:20.40444638Z” 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.16.15 instance.id=4fb8d420-eaf8-4a69-b740-bdc94fa52d91 service=registry version=”v2.8.1+unknown” docker-registry-registry-1 | time=”2022-11-19T14:31:20.404960549Z” level=info msg=”redis not configured” go.version=go1.16.15 instance.id=4fb8d420-eaf8-4a69-b740-bdc94fa52d91 service=registry version=”v2.8.1+unknown” docker-registry-registry-1 | time=”2022-11-19T14:31:20.412312462Z” level=info msg=”using inmemory blob descriptor cache” go.version=go1.16.15 instance.id=4fb8d420-eaf8-4a69-b740-bdc94fa52d91 service=registry version=”v2.8.1+unknown” docker-registry-registry-1 | time=”2022-11-19T14:31:20.412803878Z” level=info msg=”Starting upload purge in 52m0s” go.version=go1.16.15 instance.id=4fb8d420-eaf8-4a69-b740-bdc94fa52d91 service=registry version=”v2.8.1+unknown” docker-registry-registry-1 | time=”2022-11-19T14:31:20.41296431Z” level=info msg=”listening on [::]:5000″ go.version=go1.16.15 instance.id=4fb8d420-eaf8-4a69-b740-bdc94fa52d91 service=registry version=”v2.8.1+unknown” …
您将在本教程的后面解决“未提供HTTP密钥”警告信息的问题。
输出的最后一行表示成功启动,并监听在5000端口上。
您可以按下CTRL+C来停止它的执行。
在此步骤中,您已创建了一个Docker Compose配置,该配置在5000端口上启动了一个Docker注册表。在接下来的步骤中,您将在您的域名上公开它,并设置身份验证。
第二步 – 设置Nginx端口转发
作为先决条件的一部分,您在您的域名上启用了HTTPS。为了将您的安全Docker注册表暴露在那里,您需要配置Nginx将流量从您的域名转发到注册表容器。
您已经设置了/etc/nginx/sites-available/your_domain文件,其中包含您的服务器配置。运行以下命令打开文件进行编辑:
- sudo nano /etc/nginx/sites-available/your_domain
查找现有的位置区块。
...
location / {
...
}
...
您需要将流量转发到5000端口,您的注册表会监听流量。您还希望在转发到注册表的请求中添加头部,该头部从服务器提供有关请求本身的附加信息。请用以下行替换位置块的现有内容:
...
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对象。
{}
在你的终端中,你将会收到类似如下的输出结果。
docker-registry-registry-1 | time=”2022-11-19T14:32:50.082396361Z” level=info msg=”response completed” go.version=go1.16.15 http.request.host=your_domain http.request.id=779fe265-1a7c-4a15-8ae4-eeb5fc35de98 http.request.method=GET http.request.remoteaddr=87.116.166.89 http.request.uri=”/v2″ http.request.useragent=”Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36″ http.response.contenttype=”text/html; charset=utf-8″ http.response.duration=”162.546µs” http.response.status=301 http.response.written=39 docker-registry-registry-1 | 172.19.0.1 – – [19/Nov/2022:14:32:50 +0000] “GET /v2 HTTP/1.0” 301 39 “” “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36” docker-registry-registry-1 | 172.19.0.1 – – [19/Nov/2022:14:32:50 +0000] “GET /v2/ HTTP/1.0” 200 2 “” “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36″ docker-registry-registry-1 | time=”2022-11-19T14:32:50.132472674Z” level=info msg=”response completed” go.version=go1.16.15 http.request.host=your_domain http.request.id=0ffb17f0-c2a0-49d6-94f3-af046cfb96e5 http.request.method=GET http.request.remoteaddr=87.116.166.89 http.request.uri=”/v2/” http.request.useragent=”Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36″ http.response.contenttype=”application/json; charset=utf-8″ http.response.duration=2.429608ms http.response.status=200 http.response.written=2 docker-registry-registry-1 | 172.19.0.1 – – [19/Nov/2022:14:32:50 +0000] “GET /favicon.ico HTTP/1.0” 404 19 “your_domain/v2/” “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36”
从最后一行你可以知道是向/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
创建第一个用户,将用户名替换为您想要使用的用户名。-B标志指定了使用bcrypt算法,这是Docker所要求的。
- htpasswd -Bc registry.password username
在提示时输入密码。凭证的组合将追加到registry.password中。
Note
htpasswd -B registry.password 用户名
去掉 -c 参数将会更新现有文件,而不是创建新文件。
现在已经创建了凭证列表,您需要编辑docker-compose.yml文件,以便Docker使用您创建的文件来验证用户。打开它进行编辑。
- nano ~/docker-registry/docker-compose.yml
添加以下突出显示的行:
~/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
在您的浏览器中,刷新您的域名页面。系统将要求您输入用户名和密码。
在提供有效的凭证组合之后,您将可以访问带有空的JSON对象的页面。
{}
您已经成功进行身份验证,并获得了注册表的访问权限。在您的终端中按下CTRL+C退出。
你的注册表现已经安全,并且只能在认证后访问。接下来,你将配置它以后台进程运行,并通过自动启动来保持稳定,即使重新启动也不影响其功能。
第四步-将Docker注册表作为服务启动
通过指示Docker Compose始终保持运行状态,您可以确保注册表容器在系统启动时或崩溃后自动启动。
打开docker-compose.yml文件进行编辑。
- nano docker-compose.yml
在注册表块中添加以下行。
...
registry:
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;
...
}
...
客户端的最大body大小参数现已设置为16384m,使得最大上传文件大小变为16GB。
保存并关闭文件。
重启Nginx以应用配置更改。
- sudo systemctl restart nginx
在这一步中,您更新了Nginx允许的文件大小。现在您可以在Docker Registry中上传大型图片,而不会被Nginx阻止传输或出现错误。
第六步——将其发布到您的私有Docker注册表
现在你的Docker注册服务器正在运行,并且接受大文件大小,你可以尝试将一个镜像推送到上面。由于你没有现成的镜像可用,你可以使用Docker Hub上的ubuntu镜像(一个公共的Docker注册服务器)来进行测试。
在一个新的客户端服务器的终端会话中,运行以下命令来下载Ubuntu镜像,运行它,并获得对其shell的访问权限:
- docker run -t -i ubuntu /bin/bash
使用-i和-t标志可让您以交互方式访问容器的shell。
一旦进入系统,通过执行命令创建一个名为“成功”的文件。
- touch /SUCCESS
创建这个文件后,您已经定制了您的容器。以后您将使用它来检查您是否使用完全相同的容器。
运行以下命令退出容器 shell:
- exit
现在从您刚才自定义的容器中创建一个新的图像。
- docker commit $(docker ps -lq) test-image
现在可以在本地使用新的镜像,并将其推送到你的容器注册表中。首先,你需要登录:
- docker login https://your_domain
当提示时,请输入您在第3步中定义的用户名和密码凭据。
输出结果将为:
… Login Succeeded
一旦登录,将已创建的图像重命名。
- docker tag test-image your_domain/test-image
最后,将新标记的图像推送到您的注册表中。
- docker push your_domain/test-image
你将会收到类似以下的输出结果:
Using default tag: latest The push refers to a repository [your_domain/test-image] 1cf9c9034825: Pushed f4a670ac65b6: Pushed latest: digest: sha256:95112d0af51e5470d74ead77932954baca3053e04d201ac4639bdf46d5cd515b size: 736
您已经通过登录验证了您的注册表处理用户身份验证的功能,并且它允许已认证的用户向注册表推送图像。现在您将尝试从您的注册表中获取这个图像。
第7步 – 从您的私有Docker Registry中拉取
现在你已经将一个镜像推送到你的私有仓库,你将尝试从中拉取。
在主服务器上,使用您之前设置的用户名和密码登录:
- docker login https://your_domain
尝试运行命令以获取测试图像: 拉取测试图像。
- 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文件的更多信息,您可以参考官方文档中的最佳实践。