如何在Ubuntu上搭建私有Docker仓库

Docker作为在服务器上,非常不错的一个部署部署工具。那大家应该使用过Docker hub,一个存储镜像的公有的Docker仓库。但是,当你创建了一个镜像上传到Docker hub上之后,必须公开你所创建的镜像——这可能不是你所想要的。因为,我们的很多项目,可能需要私有化

这是一篇如何搭建Docker私有仓库,并对仓库进行加密的教程。在教程的最后,你将学会如何建立私有镜像上传到自己的Docker仓库,并且以一种加密的方式,把自己的镜像从私有仓库拉取回来。

这篇教程没有把容器相关的知识全部涵盖到,只是把创建私有Docker放在第一介绍的位置。如果说,你想去学习Docker(而非Docker仓库本身),你可以在这个地方学习:

  1. 如何安装和使用Docker:Getting Started tutorial.
  2. Docker官方教程

这篇教程在Ubuntu14.04环境上架设,也可以在基于Debian的发行版上架设。同样,本教程涵盖了Docker 2.0版本。

Docker相关概念

如果说之前你没有使用过Docker,那可能需要先来学习几个Docker相关的概念。如果说,你已经学习并使用过Docker,可以跳过这个章节,进入下一步。

为小白准备了一个Docker备忘录

Docker的核心是分离应用和运行于该操作系统上的应用依赖。为了达到这个目的,Docker使用了容器与镜像。Docker镜像是一个基础的模块文件系统。当创建了Docker镜像之后,一个实例的文件系统就会创建,并在宿主机的Docker容器中运行起来。但是,宿主机的文件系统与容器的文件系统默认是隔离的,容器是一个隔离的运行环境。

无论在容器内部作了任何的改变,都不会影响到Docker的原始镜像,只会影响到正在运行的镜像本身。如果说,需要把改变保留下来,使用commit命令把该镜像容器生成一个镜像(docker commit命令)。这意味着,你可以根据旧的容器来产生新的容器,并且不影响原先的容器的文件系统(或者说镜像)。如果说你之前学习过git,那么Docker与Git有很相近的工作流:Docker里面的新容器或者说新镜像,就像是在Git中新建
了一个分支一样,运行珍上新的镜像,就像是git中的切换分支git checkout

形象一点说,存放Docker镜像的Docker仓库中的就像Git仓库一样。

先决条件

继续这个教程,可能需要以下环境:

  • 有管理员账户的2个Ubuntu14.04环境: 一个Docker仓库,一个Docker客户机。
  • 安装Docker和Docker Compose。
  • 一个可以解析到Ubuntu机器的域名。

安装Apache2扩展包

为了让Docker仓库更加安全,最好使用Docker Compose。 通过Docker Compose可以轻松在Docker容器中构建Docker仓库,并且在另一个Docker镜像中构建Nginx容器,来负责Docker仓库认证环境。

所以,我们需要一个文件来存放访问我们私有仓库的用户名与密码。需要安装apached2-utils,它包含了htpasswd工具包,可以产生Nginx可以理解的hashes值,来保证Docker仓库的安全:

sudo apt-get -y install apache2-utils

安装与配置Docker仓库

Docker原生的命令行工具可以很好的管理与运行Docker容器,但是大多数容器并不是独立运行的容器。为了可以部署大部分应用,可能需要并行运行多个组件。例如:大多数应用需要的Web服务,可能需要像解析语言PHP、或者Ruby(with rails),数据库服务MySQL。

使用Docker Compose可以在.yml的配置文件中,配置所有的Docker容器,以及容器之间的通信。使用docker-compose命令行工具,可以轻松的管理docker容器,docker-compose就像是一个复合管理命令。

Docker仓库需要多个组件来运行,使用docker-compose来进行管理,唯一需要定义的是仓库存放数据的位置。开始吧,在宿主机上,建立一个放置YAML配置文件和数据的位置:

mkdir ~/docker-registry && cd $_
mkdir data 

使用nano或者vim来编辑docker-compose.yml文件:

nano docker-compose.yml

添加如下内容:

registry:
  container_name: "docker-registry"
  image: registry:2
  ports:
    - 5000:5000
  environment:
    REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
  volumes:
    - /root/docker-registry/data:/data

通过设置docker仓库的环境变量REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY来指定仓库容器建立之后,数据在容器中的存放位置。当容器启动之后,会到该变量的地方去建立数据。而volumes配置,设置了与宿主机的数据映射关系:在容器中的/data目录与宿主机的~/docker-registry/data建立了宿主关系。启动容器:

cd ~/docker-registry
docker-compose up -d

可以通过docker logs docker-registry查看docker-registry的运行日志信息。

上面的配置文件很好理解,有一个叫docker-registry的docker镜像registry:2,运行在5000端口,映射到宿主机的5000端口。

可以通过docker-compose stop docker-registrydocker=compose rm docker-registry来停止与删除容器。

运行Nginx容器

下面来着手解决容器的安全问题。第一步,需要另外新建一个Nginx容器,并且与我们的Dokcer仓库链接起来。新建一个Ngnix的配置文件:

mkdir ~/docker-registry/nginx

nano ~/docker-registry/nginx/registry.conf

加入以下内容:

upstream docker-registry {
  server registry:5000;
}

server {
  listen 443;
  server_name myregistrydomain.com;

  # SSL
  # ssl on;
  # ssl_certificate /etc/nginx/conf.d/domain.crt;
  # ssl_certificate_key /etc/nginx/conf.d/domain.key;

  # disable any limits to avoid HTTP 413 for large image uploads
  client_max_body_size 0;

  # required to avoid HTTP 411: see Issue #1486 (https://github.com/docker/docker/issues/1486)
  chunked_transfer_encoding on;

  location /v2/ {
    # 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;
    }

    # To add basic authentication to v2 use auth_basic setting plus add_header
    # auth_basic "registry.localhost";
    # auth_basic_user_file /etc/nginx/conf.d/registry.password;
    # add_header 'Docker-Distribution-Api-Version' 'registry/2.0' always;

    proxy_pass                          http://docker-registry;
    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;
  }
}

并且再次编辑目录~/docker-registry下的docker-compose.yml文件:

nano docker-compose.yml

加入以下内容:

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

nginx:
  container_name: "docker-nignx"
  image: "nginx:1.9"
  ports:
    - 5043:443
  links:
    - registry:registry
  volumes:
    - ./nginx/:/etc/nginx/conf.d:ro

volumes可以使用绝对位置,也可以使用相对位置,相对该配置文件的位置。

使用docker-compose up -d命令,可以在同一时间一次运行两个容器:docker仓库和Nginx。同样,使用log命令,查看docker与nginx的运行情况。

测试HTTP请求:

curl http://localhost:5000/v2/

如果看到:

{}

或者:

curl http://localhost:5043/v2/

看到:

{}

说明,已经成功构建了Docker仓库,并且Nginx运行良好。如果,没有curl命令,可以通过apt-get install curl来安装curl

使用docker-compose中的日志命令,在docker-compose.yml配置文件下使用如下命令:

docker-compose logs

可以看到如下信息:

registry_1 | time="2015-08-11T10:24:53.746529894Z" level=debug msg="authorizing request" environment=development http.request.host="localhost:5043" http.request.id=55c3e2a6-4f34-4b0b-bc57-11c814b4f4d3 http.request.method=GET http.request.remoteaddr=172.17.42.1 http.request.uri="/v2/" http.request.useragent="curl/7.35.0" instance.id=55634dfc-c9e0-4ec9-9872-6f4930c17759 service=registry version=v2.0.1
registry_1 | time="2015-08-11T10:24:53.747650205Z" level=info msg="response completed" environment=development http.request.host="localhost:5043" http.request.id=55c3e2a6-4f34-4b0b-bc57-11c814b4f4d3 http.request.method=GET http.request.remoteaddr=172.17.42.1 http.request.uri="/v2/" http.request.useragent="curl/7.35.0" http.response.contenttype="application/json; charset=utf-8" http.response.duration=8.143193ms http.response.status=200 http.response.written=2 instance.id=55634dfc-c9e0-4ec9-9872-6f4930c17759 service=registry version=v2.0.1
registry_1 | 172.17.0.21 - - [11/Aug/2015:10:24:53 +0000] "GET /v2/ HTTP/1.0" 200 2 "" "curl/7.35.0"
nginx_1    | 172.17.42.1 - - [11/Aug/2015:10:24:53 +0000] "GET /v2/ HTTP/1.1" 200 2 "-" "curl/7.35.0" "-"

设置认证服务

现在,Nginx作为代理控制着Docker仓库的HTTP请求,所以,可以通过Nginx来控制用户访问权限。使用之前安装的apache的htpasswd组件,创建第一个用户:(下面的USERNAME可以替换成其他的用户)

cd ~/docker-registry/nginx
htpasswd -c registry.password USERNAME

创建用户密码:

htpasswd registry.password USERNAME

使用-c可以重新创建配置文件,会覆盖之前的用户配置信息。

查看之前创建的Nginx的配置文件:

nano ~/docker-registry/nginx/registry.conf

把如下注释给取消:

# To add basic authentication to v2 use auth_basic setting plus add_header
auth_basic "registry.localhost";
auth_basic_user_file /etc/nginx/conf.d/registry.password;
add_header 'Docker-Distribution-Api-Version' 'registry/2.0' always;

使用docker-compose up --force-recreate -d重新创建容器,使上述的配置生效。再次使用curl命令:

curl http://localhost:5043/v2/

会看到如下内容:

<html>
<head><title>401 Authorization Required</title></head>
<body bgcolor="white">
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.9.7</center>
</body>
</html>

使用如下命令:

curl http://USERNAME:[email protected]:5043/v2/

如果看到:

{}

说明,设置认证服务已经生效。

设置SSL

首先,设置nginx配置文件,取消如下注释内容:

server {
  listen 443;
  server_name myregistrydomain.com;

  # SSL
  ssl on;
  ssl_certificate /etc/nginx/conf.d/domain.crt;
  ssl_certificate_key /etc/nginx/conf.d/domain.key;

这个地方要注意一下,之前在docker-compose.yml配置文件中,宿主机的~/docker-registry/nginx/与Nginx容器的/etc/nginx/conf.d/映射关系,把相应的认证密钥与证书放置在宿主机的~/docker-registry/nginx/下,即Nginx容器可以访问了。

两种获取SSL证书的方式:自签或者申请。

申请有免费的DV证书,也有前面博客介绍的使用Let’s Encrypt全站启用HTTPS协议的方法申请Let’s Encrypt的免费证书。

下面介绍一下自签证书的方法:

cd ~/docker-registry/nginx

使用openssl来自签证书:

openssl genrsa -out devdockerCA.key 2048

openssl req -x509 -new -nodes -key devdockerCA.key -days 10000 -out devdockerCA.crt

openssl genrsa -out domain.key 2048

重要:在Common Name处输入docker服务器的域名或者IP。

openssl req -new -key domain.key -out dev-docker-registry.com.csr

如果域名www.wayearn.com,那么可以按照如下设置:

Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:www.wayearn.com
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

不需要设置challenge password。

openssl x509 -req -in dev-docker-registry.com.csr -CA devdockerCA.crt -CAkey devdockerCA.key -CAcreateserial -out domain.crt -days 10000

因为证书没有任何认证机构进行认证,所以需要给所有的客户机这是一个合法的证书。首先,在宿主机上设置,这样,可以在Docker容器中使用Docker仓库中的证书。

sudo mkdir /usr/local/share/ca-certificates/docker-dev-cert
sudo cp devdockerCA.crt /usr/local/share/ca-certificates/docker-dev-cert
sudo update-ca-certificates

重启Docker服务:

sudo service docker restart

测试SSL证书

cd ~/docker-registry
docker-compose up --force-recreate -d

使用curl来测试HTTPS连接:

curl https://USERNAME:[email protected][YOUR-DOMAIN]:5043/v2/

如果使用是自签的证书,可能看到如下错误:

curl: (60) SSL certificate problem: self signed certificate

使用-k选项,不认证peer:

curl -k https://USERNAME:[email protected][YOUR-DOMAIN]:5043/v2/

如果域名是:www.wayearn.com

curl https://liwei:[email protected]:5043/v2/

收到{},说明HTTPS工作正常。

设置SSL为443端口

打开docker-compose.yml的配置:

nano ~/docker-registry/docker-compose.yml

修改端口的映射关系:

nginx:
  image: "nginx:1.9"
  ports:
    - 443:443
  links:
    - registry:registry
  volumes:
    - ./nginx/:/etc/nginx/conf.d:ro
registry:
  image: registry:2
  ports:
    - 5000:5000
  environment:
    REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
  volumes:
    - ./data:/data

使用--force-recreate来重新生成容器。

现在可以使用:

docker login https://<YOURDOMAIN>

或者:

curl https://<YOURUSERNAME>:<YOURPASSWORD>@YOUR-DOMAIN/v2/

在客户机上进行测试。

如何使用

与docker.io的使用是一样的,只不过在commit镜像的时候,需要加上仓库地址,如:

docker login https://<YOURDOMAIN>

输入密码,然后:

docker commit <容器ID> <YOURDOMAIN>/<容器名>

docker push <YOURDOMAIN>/<容器名>

使用镜像:

docker pull <YOURDOMAIN>/<容器名>
一路前行,感谢有你~~