作为 Write for DOnations 计划的一部分,作者选择了 Apache Software Foundation 来接受捐赠。
介绍
Docker Registry 是一个管理存储和交付 Docker 容器镜像的应用程序。 注册表集中容器映像并减少开发人员的构建时间。 Docker 镜像通过虚拟化保证了相同的运行时环境,但是构建镜像可能需要大量的时间投资。 例如,开发人员可以从包含所有必要组件的注册表中下载压缩映像,而不是单独安装依赖项和包来使用 Docker。 此外,开发人员可以使用持续集成工具(例如 TravisCI)自动将镜像推送到注册表,从而在生产和开发过程中无缝更新镜像。
Docker 还有一个免费的公共注册表 Docker Hub,它可以托管您的自定义 Docker 镜像,但在某些情况下您不希望您的镜像公开可用。 映像通常包含运行应用程序所需的所有代码,因此在使用专有软件时最好使用私有注册表。
在本教程中,您将设置和保护您自己的私有 Docker Registry。 您将使用 Docker Compose 定义配置以运行 Docker 应用程序,并使用 Nginx 将服务器流量从 HTTPS 转发到正在运行的 Docker 容器。 完成本教程后,您将能够将自定义 Docker 映像推送到您的私有注册表并从远程服务器安全地拉取该映像。
先决条件
在开始本指南之前,您需要以下内容:
- 按照Ubuntu 18.04初始服务器设置指南设置两台Ubuntu 18.04服务器,包括一个sudo非root用户和一个防火墙。 一台服务器将托管您的私有 Docker Registry,另一台将是您的 客户端 服务器。
- 按照 如何在 Ubuntu 18.04 上安装 Docker-Compose 教程在两台服务器上安装 Docker 和 Docker-Compose。 您只需完成本教程的第一步即可安装 Docker Compose。 本教程解释了如何安装 Docker 作为其先决条件的一部分。
- 按照 如何在 Ubuntu 18.04 上安装 Nginx 将 Nginx 安装在您的私有 Docker Registry 服务器上。
- 遵循 如何使用 Let's Encrypt 保护 Nginx 在您的服务器上使用 Let's Encrypt 保护私有 Docker Registry。 确保在步骤 4 中将所有流量从 HTTP 重定向到 HTTPS。
- 解析为用于私有 Docker Registry 的服务器的域名。 您将把它设置为 Let's Encrypt 先决条件的一部分。
第 1 步 — 安装和配置 Docker 注册表
Docker 命令行工具对于启动和管理一个或两个 Docker 容器很有用,但是,对于在 Docker 容器中运行的大多数应用程序的完整部署,需要其他组件并行运行。 例如,许多 Web 应用程序由一个 Web 服务器(如 Nginx)、一个解释性脚本语言(如 PHP)和一个数据库服务器(如 MySQL)组成,该服务器提供应用程序的代码。
使用 Docker Compose,您可以编写一个 .yml
文件来设置每个容器的配置以及容器相互通信所需的信息。 然后,您可以使用 docker-compose
命令行工具向构成应用程序的所有组件发出命令。
Docker Registry 本身就是一个包含多个组件的应用程序,因此您将使用 Docker Compose 来管理您的配置。 要启动注册表实例,您将设置一个 docker-compose.yml
文件来定义注册表将存储其数据的位置。
在您为托管私有 Docker Registry 而创建的服务器上,您可以创建一个 docker-registry
目录,移入其中,然后使用以下命令创建一个 data
子文件夹:
mkdir ~/docker-registry && cd $_ mkdir data
使用文本编辑器创建 docker-compose.yml
配置文件:
nano docker-compose.yml
将以下内容添加到文件中,该文件描述了 Docker Registry 的基本配置:
码头工人-compose.yml
version: '3' services: registry: image: registry:2 ports: - "5000:5000" environment: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data volumes: - ./data:/data
environment
部分在 Docker Registry 容器中使用路径 /data
设置环境变量。 Docker Registry 应用程序在启动时会检查此环境变量,并因此开始将其数据保存到 /data
文件夹。
但是,由于您已包含 volumes: - ./data:/data
行,Docker 将开始将该容器中的 /data
目录映射到您的注册表服务器上的 /data
。 最终结果是 Docker Registry 的数据全部存储在注册表服务器上的 ~/docker-registry/data
中。
ports
部分,配置为 5000:5000
,告诉 Docker 将服务器上的端口 5000
映射到正在运行的容器中的端口 5000
。 这允许您向服务器上的端口 5000
发送请求,并将请求转发到注册表应用程序。
您现在可以启动 Docker Compose 来检查设置:
docker-compose up
您将在输出中看到下载栏,显示 Docker 从 Docker 自己的注册表下载 Docker Registry 映像。 在一两分钟内,您将看到类似于以下内容的输出(版本可能会有所不同):
Output of docker-compose upStarting docker-registry_registry_1 ... done Attaching to docker-registry_registry_1 registry_1 | time="2018-11-06T18:43:09Z" 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.7.6 instance.id=c63483ee-7ad5-4205-9e28-3e809c843d42 version=v2.6.2 registry_1 | time="2018-11-06T18:43:09Z" level=info msg="redis not configured" go.version=go1.7.6 instance.id=c63483ee-7ad5-4205-9e28-3e809c843d42 version=v2.6.2 registry_1 | time="2018-11-06T18:43:09Z" level=info msg="Starting upload purge in 20m0s" go.version=go1.7.6 instance.id=c63483ee-7ad5-4205-9e28-3e809c843d42 version=v2.6.2 registry_1 | time="2018-11-06T18:43:09Z" level=info msg="using inmemory blob descriptor cache" go.version=go1.7.6 instance.id=c63483ee-7ad5-4205-9e28-3e809c843d42 version=v2.6.2 registry_1 | time="2018-11-06T18:43:09Z" level=info msg="listening on [::]:5000" go.version=go1.7.6 instance.id=c63483ee-7ad5-4205-9e28-3e809c843d42 version=v2.6.2
您将在本教程后面部分解决 No HTTP secret provided
警告消息。 输出显示容器正在启动。 输出的最后一行显示它已成功开始侦听端口 5000
。
默认情况下,Docker Compose 会一直等待你的输入,所以点击 CTRL+C
来关闭你的 Docker Registry 容器。
你已经设置了一个完整的 Docker Registry 监听端口 5000
。 此时注册表不会启动,除非您手动启动它。 此外,Docker Registry 没有内置任何身份验证机制,因此目前不安全且完全向公众开放。 在以下步骤中,您将解决这些安全问题。
第 2 步 — 设置 Nginx 端口转发
您已经使用 Nginx 在 Docker Registry 服务器上设置了 HTTPS,这意味着您现在可以设置从 Nginx 到端口 5000
的端口转发。 完成此步骤后,您可以直接在 example.com 上访问您的注册表。
作为 How to Secure Nginx With Let's Encrypt 先决条件的一部分,您已经设置了包含服务器配置的 /etc/nginx/sites-available/example.com
文件。
使用文本编辑器打开此文件:
sudo nano /etc/nginx/sites-available/example.com
找到现有的 location
行。 它看起来像这样:
/etc/nginx/sites-available/example.com
... location / { ... } ...
您需要将流量转发到端口 5000
,您的注册表将在该端口运行。 您还希望将标头附加到对注册表的请求中,从而为每个请求和响应提供来自服务器的附加信息。 删除 location
部分的内容,并将以下内容添加到该部分:
/etc/nginx/sites-available/example.com
... 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; } ...
$http_user_agent
部分验证客户端的 Docker 版本在 1.5
以上,并确保 UserAgent
不是 Go
应用程序。 由于您使用的是注册表的 2.0
版本,因此不支持较旧的客户端。 更多信息,您可以在 Docker 的 Registry Nginx 指南 中找到 nginx
头配置。
保存并退出文件。 通过重新启动 Nginx 应用更改:
sudo service nginx restart
您可以通过运行注册表来确认 Nginx 正在将流量转发到端口 5000
:
cd ~/docker-registry docker-compose up
在浏览器窗口中,打开以下网址:
https://example.com/v2
您将看到一个空的 JSON 对象,或者:
{}
在您的终端中,您将看到类似于以下内容的输出:
Output of docker-compose upregistry_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
表示容器成功处理了请求。
现在您已经设置了端口转发,您可以继续提高注册表的安全性。
第 3 步 — 设置身份验证
正确使用 Nginx 代理请求后,您现在可以使用 HTTP 身份验证保护您的注册表,以管理谁可以访问您的 Docker 注册表。 为此,您将使用 htpasswd
创建一个身份验证文件并向其中添加用户。 HTTP 身份验证可以通过 HTTPS 连接快速设置和保护,这也是注册表将使用的。
您可以通过运行以下命令来安装 htpasswd
包:
sudo apt install apache2-utils
现在,您将创建存储身份验证凭据的目录,并切换到该目录。 $_
扩展为上一个命令的最后一个参数,在本例中为 ~/docker-registry/auth
:
mkdir ~/docker-registry/auth && cd $_
接下来,您将创建第一个用户,如下所示,将 username
替换为您要使用的用户名。 -B
标志指定 bcrypt
加密,比默认加密更安全。 出现提示时输入密码:
htpasswd -Bc registry.password username
注意:要添加更多用户,请重新运行上一个不带-c选项的命令,(c
用于创建):
htpasswd registry.password username
接下来,您将编辑 docker-compose.yml
文件以告诉 Docker 使用您创建的文件来验证用户身份。
cd ~/docker-registry nano docker-compose.yml
您可以为您创建的 auth/
目录添加环境变量和卷,方法是编辑 docker-compose.yml
文件,告诉 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
对于 REGISTRY_AUTH
,您已指定 htpasswd
,这是您正在使用的身份验证方案,并将 REGISTRY_AUTH_HTPASSWD_PATH
设置为身份验证文件的路径。 最后,REGISTRY_AUTH_HTPASSWD_REALM
表示 htpasswd
领域的名称。
您现在可以通过运行注册表并检查它是否提示用户输入用户名和密码来验证您的身份验证是否正常工作。
docker-compose up
在浏览器窗口中,打开 https://example.com/v2
。
输入username
和对应的密码后,会再次看到{}
。 您已确认基本身份验证设置:注册表仅在您输入正确的用户名和密码后才返回结果。 您现在已经保护了您的注册表,并且可以继续使用注册表。
第 4 步 — 启动 Docker Registry 作为服务
您希望确保您的注册表在系统启动时启动。 如果有任何不可预见的系统崩溃,您要确保在服务器重新启动时注册表重新启动。 打开docker-compose.yml
:
nano docker-compose.yml
在registry:
下添加如下一行内容:
码头工人-compose.yml
... registry: restart: always ...
您可以将注册表作为后台进程启动,这将允许您退出 ssh
会话并保持该进程:
docker-compose up -d
随着您的注册表在后台运行,您现在可以准备 Nginx 进行文件上传。
第 5 步 — 增加 Nginx 的文件上传大小
在将图像推送到注册表之前,您需要确保您的注册表能够处理大文件上传。 尽管 Docker 将大图像上传拆分为单独的层,但有时它们可能会超过 1GB
。 默认情况下,Nginx 对文件上传有 1MB
的限制,因此您需要编辑 nginx
的配置文件,并将最大文件上传大小设置为 2GB
。
sudo nano /etc/nginx/nginx.conf
找到 http
部分,并添加以下行:
/etc/nginx/nginx.conf
... http { client_max_body_size 2000M; ... } ...
最后,重启 Nginx 以应用配置更改:
sudo service nginx restart
您现在可以将大图像上传到您的 Docker Registry,而不会出现 Nginx 错误。
第 6 步 — 发布到您的私有 Docker 注册表
您现在已准备好将映像发布到您的私有 Docker Registry,但首先您必须创建一个映像。 在本教程中,您将基于来自 Docker Hub 的 ubuntu
映像创建一个简单的映像。 Docker Hub 是一个公共托管的注册表,具有许多可用于快速 Dockerize 应用程序的预配置映像。 使用 ubuntu
映像,您将测试推送和拉取注册表。
从您的 client 服务器,创建一个小的空图像以推送到您的新注册表,-i
和 -t
标志为您提供对容器的交互式 shell 访问:
docker run -t -i ubuntu /bin/bash
下载完成后,您将进入 Docker 提示符,请注意 root@
后面的容器 ID 会有所不同。 通过创建一个名为 SUCCESS
的文件来快速更改文件系统。 在下一步中,您将能够使用此文件来确定发布过程是否成功:
touch /SUCCESS
退出 Docker 容器:
exit
以下命令基于已运行的映像以及您所做的任何更改创建一个名为 test-image
的新映像。 在我们的例子中,添加的 /SUCCESS
文件包含在新图像中。
提交更改:
docker commit $(docker ps -lq) test-image
此时,图像仅存在于本地。 现在您可以将其推送到您创建的新注册表中。 登录到您的 Docker 注册表:
docker login https://example.com
输入之前的username
和对应的密码。 接下来,您将使用私有注册表的位置标记图像,以便推送到它:
docker tag test-image example.com/test-image
将新标记的图像推送到注册表:
docker push example.com/test-image
您的输出将类似于以下内容:
OutputThe push refers to a repository [example.com/test-image] e3fbbfb44187: Pushed 5f70bf18a086: Pushed a3b5c80a4eba: Pushed 7f18b442972b: Pushed 3ce512daaf78: Pushed 7aae4540b42d: Pushed ...
您已验证您的注册表处理用户身份验证,并允许经过身份验证的用户将图像推送到注册表。 接下来,您将确认您也可以从注册表中提取图像。
第 7 步 — 从您的私有 Docker 注册表中提取
返回您的注册表服务器,以便您可以测试从 客户端 服务器拉取图像。 也可以从第三台服务器对此进行测试。
使用您之前设置的用户名和密码登录:
docker login https://example.com
您现在已准备好提取图像。 使用您在上一步中标记的域名和图像名称:
docker pull example.com/test-image
Docker 将下载映像并将您返回到提示符。 如果您在注册表服务器上运行映像,您将看到您之前创建的 SUCCESS
文件在那里:
docker run -it example.com/test-image /bin/bash
列出 bash shell 中的文件:
ls
您将看到为此图像创建的 SUCCESS
文件:
SUCCESS bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
您已完成设置用户可以向其推送和拉取自定义图像的安全注册表。
结论
在本教程中,您设置了自己的私有 Docker Registry,并发布了 Docker 映像。 正如介绍中提到的,您还可以使用 TravisCI 或类似的 CI 工具,直接自动推送到私有注册表。 通过在您的工作流程中利用 Docker 和注册表,您可以确保包含代码的映像在任何机器上都会产生相同的行为,无论是在生产中还是在开发中。 有关编写 Docker 文件的更多信息,您可以阅读此 Docker 教程 解释该过程。