作为Write for DOnations计划的一部分,作者选择了自由和开源基金来接受捐赠。
简介
Docker Registry是一个管理存储和交付Docker容器镜像的应用程序。注册处集中了容器镜像,减少了开发者的构建时间。Docker镜像通过虚拟化保证了相同的运行环境,但构建一个镜像可能需要投入大量的时间。例如,开发人员可以从注册中心下载一个包含所有必要组件的压缩镜像,而不是单独安装依赖性和软件包来使用Docker。此外,开发人员可以使用持续集成工具(如TravisCI)自动将镜像推送到注册中心,以便在生产和开发期间无缝更新镜像。
Docker也有一个免费的公共注册中心,即Docker Hub,它可以托管你的定制Docker镜像,但在有些情况下,你不希望你的镜像是公开可用的。镜像通常包含运行一个应用程序所需的所有代码,因此在使用专有软件时,最好使用私有注册表。
在本教程中,你将建立并保护自己的私有Docker注册表。你将使用Docker Compose定义配置来运行你的Docker容器,并使用Nginx将服务器流量从互联网转发到运行中的Docker容器。完成本教程后,你将能够把自定义的Docker镜像推送到你的私人注册中心,并从远程服务器上安全地提取镜像。
前提条件
- 两台按照Ubuntu 20.04初始服务器设置指南设置的Ubuntu 20.04服务器,包括一个
sudo非root用户和一个防火墙。一台服务器将托管你的私有Docker注册中心,另一台将是你的客户服务器。 - 按照《如何在Ubuntu 20.04上安装和使用Docker》的步骤1和2,在两台服务器上安装Docker。
- 按照《如何在Ubuntu 20.04上安装和使用Docker Compose》的第1步,在主服务器上安装Docker Compose。
- 按照《如何在Ubuntu 20.04上安装Nginx》的步骤,在主机上安装Nginx。
- 按照 "如何在Ubuntu 20.04上使用Let's Encrypt保护Nginx "的教程,在服务器上为私有Docker注册表保护Nginx。确保在第4步将所有流量从HTTP重定向到HTTPS。
- 一个能解析到你用于私有Docker注册表的服务器的域名。你将作为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注册表的基本实例。
~/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注册中心,你只需要配置Nginx来转发从你的域名到注册中心容器的流量。
你已经设置了 /etc/nginx/sites-available/your_domain文件,其中包含你的服务器配置。通过运行.NET来打开它进行编辑。
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注册中心的访问。为了实现这一点,你将用htpasswd ,并在其中添加将被接受的用户名和密码组合,创建一个认证文件。
你可以通过安装apache2-utils 包来获得htpasswd 工具。通过运行这样做。
sudo apt install apache2-utils -y
你将把带有证书的认证文件存储在~/docker-registry/auth 。通过运行:创建它。
mkdir ~/docker-registry/auth
浏览到它。
cd ~/docker-registry/auth
创建第一个用户,将 username换成你想使用的用户名。-B 标志命令使用Docker要求的bcrypt 算法。
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注册表作为一个服务启动
你可以通过指示Docker Compose始终保持注册表的运行,确保注册表容器在每次系统启动时或系统崩溃后都能启动。打开docker-compose.yml ,进行编辑。
nano docker-compose.yml
在registry 块下添加以下一行。
docker-compose.yml
...
registry:
restart: always
...
将restart 设置为始终,可以确保容器在重启后仍然存在。完成后,保存并关闭该文件。
现在你可以通过输入-d 来启动你的注册表作为一个后台进程。
docker-compose up -d
当你的注册表在后台运行时,你可以自由地关闭SSH会话,而注册表不会受到影响。
由于Docker镜像的大小可能非常大,现在你将增加Nginx接受的最大上传文件大小。
第5步 - 增加Nginx的文件上传大小
在推送镜像到注册表之前,需要确保注册表能够处理大型文件的上传。
Nginx默认的文件上传大小限制是1m ,这对Docker镜像来说是远远不够的。要提高这个限制,你需要修改Nginx的主配置文件,位于/etc/nginx/nginx.conf 。通过运行以下命令打开它进行编辑。
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注册表服务器已经启动并运行,并且接受大文件尺寸,你可以尝试推送一个镜像到它。由于你没有任何现成的镜像,你将使用Docker Hub(一个公共的Docker注册中心)的ubuntu 镜像来测试。
在你的第二台客户服务器上,运行下面的命令来下载ubuntu 镜像,运行它,并获得对其外壳的访问。
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注册表,并向其发布了一个Docker镜像。正如介绍中提到的,你也可以使用TravisCI或类似的CI工具来直接自动推送到私有注册表。通过在工作流程中利用Docker容器,你可以确保包含代码的镜像在任何机器上都会产生相同的行为,无论是在生产还是在开发中。关于编写Docker文件的更多信息,你可以访问关于最佳做法的官方文档。