如何在Ubuntu 18.04上设置私有Docker注册表

Docker注册表是一个管理存储和交付Docker容器映像的应用程序。本教程介绍如何设置和保护私有Docker注册表以及如何从注册表中推送和提取图像。

作者选择Apache Software Foundation作为Write for DOnations计划的一部分进行捐赠。

介绍

Docker Registry是一个管理存储和交付Docker容器映像的应用程序。 注册表集中容器映像并减少开发人员的构建时间。 Docker镜像通过虚拟化保证了相同的运行时环境,但构建映像可能需要大量的时间投入。 例如,开发人员可以从包含所有必要组件的注册表中下载压缩映像,而不是单独安装依赖项和软件包以使用Docker。 此外,开发人员可以使用持续集成工具(如TravisCI)自动将图像推送到注册表,以便在生产和开发过程中无缝更新图像。

Docker还有一个免费的公共注册表Docker Hub ,可以托管您的自定义Docker镜像,但有些情况下您不希望您的图像公开。 映像通常包含运行应用程序所需的所有代码,因此在使用专有软件时,最好使用私有注册表。

在本教程中,您将设置并保护您自己的私有Docker Registry。 您将使用Docker Compose定义运行Docker应用程序的配置,并使用Nginx将服务器流量从HTTPS转发到正在运行的Docker容器。 完成本教程后,您将能够将自定义Docker镜像推送到私有注册表,并从远程服务器安全地提取映像。

先决条件

在开始本指南之前,您需要以下内容:

第1步 - 安装和配置Docker Registry

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的基本配置:

Docker窗,compose.yml
version: '3'

services:
  registry:
    image: registry:2
    ports:
    - "5000:5000"
    environment:
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
    volumes:
      - ./data:/data

environment部分使用路径/data在Docker Registry容器中设置环境变量。 Docker Registry应用程序在启动时检查此环境变量,因此开始将其数据保存到/data文件夹。

但是,正如您已包含volumes: - ./data:/data行,Docker将开始将该容器中的/data目录映射到注册表服务器上的/data 最终结果是Docker Registry的数据全部存储在注册表服务器上的~/docker-registry/data

配置为5000:5000ports部分告诉Docker将服务器上的端口5000映射到正在运行的容器中的端口5000 这允许您向服务器上的端口5000发送请求,并将请求转发到注册表应用程序。

您现在可以启动Docker Compose来检查设置:

docker-compose up

您将在输出中看到下载栏,显示Docker从Docker自己的注册表中下载Docker Registry映像。 在一两分钟内,您将看到类似于以下内容的输出(版本可能会有所不同):

Starting 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容器。

您已经在端口5000上设置了一个完整的Docker Registry。 此时,除非您手动启动注册表,否则注册表将无法启动。 此外,Docker Registry没有任何内置的身份验证机制,因此它目前不安全且完全对公众开放。 在以下步骤中,您将解决这些安全问题。

第2步 - 设置Nginx端口转发

您已经在Docker Registry服务器上使用Nginx设置了HTTPS,这意味着您现在可以设置从Nginx到端口5000端口转发。 完成此步骤后,您可以直接访问example.com上的注册表。

作为如何使用Let的加密前提条件保护Nginx的一部分,您已经设置了包含服务器配置的/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

在浏览器窗口中,打开以下URL:

https://example.com/v2

您将看到一个空的JSON对象,或者:

{}

在您的终端中,您将看到类似于以下内容的输出:

registry_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 Registry。 为此,您将使用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

您可以通过编辑docker-compose.yml文件来告知Docker如何对用户进行身份验证,从而为您创建的auth/目录添加环境变量和卷。 将以下突出显示的内容添加到文件中:

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 as a Service

您希望确保在系统启动时注册表将启动。 如果有任何无法预料的系统崩溃,您需要确保在服务器重新启动时重新启动注册表。 打开docker-compose.yml

nano docker-compose.yml

registry:下添加以下内容:

Docker窗,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映像,您将测试推送到您的注册表。

客户端服务器创建一个小的空图像以推送到新的注册表, -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

您的输出将类似于以下内容:

The 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 login 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教程来解释该过程。