如何在Ubuntu 20.04上建立一个私有的Docker注册中心

452 阅读14分钟

作为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文件的更多信息,你可以访问关于最佳做法的官方文档