搭建私有Docker Registry,使用阿里云OSS作为存储引擎

1. 环境

阿里云ECS, 操作系统:CentOS Linux release 7.3.1611 (Core)。

如何在CentOS 7中安装Docker?

Docker版本如下:

Client:
    Version:         1.12.6    
    API version:     1.24    
    Package version: docker-1.12.6-32.git88a4867.el7.centos.x86_64     
    Go version:      go1.7.4    
    Git commit:      88a4867/1.12.6    
    Built:           Mon Jul  3 16:02:02 2017    
    OS/Arch:         linux/amd64    

Server:
    Version:         1.12.6   
    API version:     1.24   
    Package version: docker-1.12.6-32.git88a4867.el7.centos.x86_64   
    Go version:      go1.7.4  
    Git commit:      88a4867/1.12.6   
    Built:           Mon Jul  3 16:02:02 2017   
    OS/Arch:         linux/amd64  

docker-compose版本:

docker-compose version 1.20.0, build ca8d3c6
docker-py version: 3.1.3
CPython version: 3.6.4
OpenSSL version: OpenSSL 1.0.1t  3 May 2016

参考: 如何在linux安装docker-compose

2. 准备工作

服务器公网ip: 106.14.144.237
服务器内网ip: 172.19.115.186
容器仓库域名: hub.delznet.com

域名已经解析到公网ip,并且已经申请了CA证书。

如何申请免费证书请参考”如何申请阿里云的免费ssl证书

3. 安装

// 建立docker-compose.yml文件
$ mkdir -p /data/docker-registry
$ cd /data/docker-registry
$ vi docker-compose.yml

内容如下:

registry:
    restart: always
    image: "registry:2"
    ports:
        - 15000:5000
    volumes:
        - ./data:/var/lib/registry

启动容器:

$ docker-compose up -d
Creating dockerregistry_registry_1 ... done

说明启动容器成功,下面创建一个镜像发送个这个仓库。

//下载一个常用的镜像,如busybox 
$ docker pull busybox
$ docker images
REPOSITORY          TAG     IMAGE ID        CREATED         SIZE
docker.io/busybox   latest  2716f21dc1e3    44 hours ago    1.146 MB
docker.io/registry  2       d1fd7d86a825    12 weeks ago    33.26 MB
//为busybox重新打tag,域名是hub.delznet.com:15000
$ docker tag busybox hub.delznet.com:15000/delz/busybox
$ docker images
REPOSITORY                          TAG     IMAGE ID        CREATED         SIZE
docker.io/busybox                   latest  2716f21dc1e3    45 hours ago    1.146 MB
hub.delznet.com:15000/delz/busybox  latest  2716f21dc1e3    45 hours ago    1.146 MB
docker.io/registry                  2       d1fd7d86a825    12 weeks ago    33.26 MB

发送新建的镜像到仓库:

$ docker push hub.delznet.com:15000/delz/busybox

返回错误信息:

The push refers to a repository [hub.delznet.com:15000/delz/busybox]
Get https://hub.delznet.com:15000/v1/_ping: http: server gave HTTP response to HTTPS client

这个错误表示:docker客户端默认要求安全传输,必须支持https协议,而我们的hub.delznet.com:15000这个网址是http协议。

怎么解决?

(1)让客户端不要求支持https

//用docker info查看Insecure Registries部分,默认只支持127开头的ip地址
$ docker info
...
Insecure Registries:
    127.0.0.0/8
//修改/etc/docker/daemon.json,没有这个文件,创建之
$ vi /etc/docker/daemon.json
//将hub.delznet.com:15000添加到insecure-registries:
{
    "insecure-registries": [
        "hub.delznet.com:15000"
    ]
}
//重启docker
$ systemctl daemon-reload
$ systemctl restart docker.service
//再次上传镜像成功了
$ docker push hub.delznet.com:15000/delz/busybox
//查看镜像中是否已经有了上传的镜像
$ curl http://hub.delznet.com:15000/v2/_catalog
{"repositories":["delz/busybox"]}

这种方式的缺点要每个Docker客户端配置insecure-registries,如果是内部使用,可以让团队统一配置。

(2)支持https

在准备工作中,我们已经申请好了 hub.delznet.com的CA证书,下面将配置这个证书。

//在docker-compose.yml的同级目录创建certs目录,将申请到的阿里云免费CA证书放入这个目录
$ mkdir -p /data/docker-registry/certs
//上传证书到服务器,214572164630922是阿里云提供的证书
$ scp 214572164630922/214572164630922.* root@106.14.144.237:/data/docker-registry/certs
214572164630922.key 100% 1678   81.7KB/s    00:00    
214572164630922.pem 100% 3658   159.1KB/s   00:00

修改docker-compose.yml, 如下:

registry:
    restart: always
    image: "registry:2"
    ports:
        - 15000:5000
    volumes:
        - ./data:/var/lib/registry
        - ./certs:/certs
    environment:
        - REGISTRY_HTTP_TLS_CERTIFICATE=/certs/214572164630922.pem
        - REGISTRY_HTTP_TLS_KEY=/certs/214572164630922.key

将刚才启动的registry容器停止并删除,删除/data/docker-registry/data目录下所有文件。

修改/etc/docker/daemon.json, 将hub.delznet.com:15000从insecure-registries中删除。

重启启动容器:

$ docker-compose up -d
//测试一下是否支持了https,返回表明确认支持了
$ curl https://hub.delznet.com:15000/v2/_catalog
{"repositories":[]}
//而http返回是空白
$ curl http://hub.delznet.com:15000/v2/_catalog
//测试一下是否可以上传镜像
$ docker push hub.delznet.com:15000/delz/busybox
The push refers to a repository [hub.delznet.com:15000/delz/busybox]
3e596351c689: Pushed 
latest: digest:sha256:fdd0a4b8982284cb82780ad69173a8ea8dfc3f24ba71a91459e9ec49852e2b3e size: 527
$ curl https://hub.delznet.com:15000/v2/_catalog
{"repositories":["delz/busybox"]}

确实可以上传了,下载可以测试一下,也没有问题了。

4.鉴权

既然是私有的镜像仓库,那么不是允许所有人能上传和下载的,这就需要鉴权。

这里提供一种利用htpasswd生成用户名和密码的方式来鉴权。htpasswd是apache下的一个密码生成工具。虽然我们的服务器没有安装apache,但是registry容器里面有这个工具。我们可以利用ENTRYPOINT来生成。

//创建目录放置htpasswd生成的密码文件
$ mkdir -p  /data/docker-registry/auth
//生成一个用户名为demo,密码为123456的账号放入密码文件htpasswd
$ docker run --entrypoint htpasswd registry:2 -Bbn demo 123456 >> /data/docker-registry/auth/htpasswd
//可以生成了一个htpasswd的文件,包括demo用户的密码信息,docker ps -a查看容器,可以把临时容器删除(作用只是为了生成一个密码)。

修改docker-compose.yml文件,如下:

registry:
    restart: always
    image: "registry:2"
    ports:
        - 15000:5000
    volumes:
        - ./data:/var/lib/registry
        - ./certs:/certs
        - ./auth:/auth
    environment:
        - REGISTRY_HTTP_TLS_CERTIFICATE=/certs/214572164630922.pem
        - REGISTRY_HTTP_TLS_KEY=/certs/214572164630922.key
        - REGISTRY_AUTH=htpasswd
        - REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm
        - REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd

老操作,先停止registry容器,再删除registry容器,然后重启新的registry容器。

//测试一下是否需要权限
$ curl https://hub.delznet.com:15000/v2/_catalog
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"registry","Class":"","Name":"catalog","Action":"*"}]}]}

//测试一下push镜像,返回结果表示无法上传
$ docker push hub.delznet.com:15000/delz/nginx
The push refers to repository [hub.delznet.com:15000/delz/nginx]
f93c2b24cb18: Preparing 
343bb8320f2b: Preparing 
7066df57739c: Preparing 
d39d92664027: Preparing 
no basic auth credentials

// 用curl加上用户名和密码也返回正确
$ curl -u demo:123456 https://hub.delznet.com:15000/v2/_catalog
{"repositories":[]}

//确实需要权限,那么我们登录一下
$ docker login hub.delznet.com:15000 
Username: demo
Password: 
Login Succeeded

//下面测试一下上传镜像,结果显示上传成功
$ docker push hub.delznet.com:15000/delz/nginx
The push refers to repository [hub.delznet.com:15000/delz/nginx]
f93c2b24cb18: Pushed 
343bb8320f2b: Pushed 
7066df57739c: Pushed 
d39d92664027: Pushed 
latest: digest: sha256:a4ac36c250ec97bb6108e04e3cdd2848e338ee197737cebd39c960d7e3237775 size: 1153

5. 阿里云OSS作为存储引擎

在上面的配置中,我们使用/data/docker-registry/data目录存储Docker镜像仓库的文件,从安全、速度、带宽和运维的角度上考虑,选择一个第三方的文件存储服务,比放在某台服务器上要好。

我们的云服务器使用的是阿里云的,文件存储也就使用了阿里云的OSS。

怎么申请阿里云的OSS请上阿里云网站查看。

修改docker-compose.yml文件,如下(出于机密,隐藏了阿里云OSS的参数):

registry:
    restart: always
    image: "registry:2"
    ports:
        - 15000:5000
    volumes:
        - ./certs:/certs
        - ./auth:/auth
    environment:
        - REGISTRY_HTTP_TLS_CERTIFICATE=/certs/214572164630922.pem
        - REGISTRY_HTTP_TLS_KEY=/certs/214572164630922.key
        - REGISTRY_AUTH=htpasswd
        - REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm
        - REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd
        - REGISTRY_STORAGE=oss
        - REGISTRY_STORAGE_OSS_ACCESSKEYID=****
        - REGISTRY_STORAGE_OSS_ACCESSKEYSECRET=****        
        - REGISTRY_STORAGE_OSS_REGION=oss-cn-shanghai
        - REGISTRY_STORAGE_OSS_BUCKET=delz-docker

注意到:上面的文件中,删除了./data目录到/var/lib/registry的映射,因为文件已经放在阿里云OSS上了,我们可以删除data目录的内容

$ rm -rf /data/docker-registry/data

还是老操作,先停止registry容器,再删除registry容器,然后重启新的registry容器。

但是这个会出现一个问题,如下:

 $ curl  https://hub.delznet.com:15000/v2/_catalog
 {"errors":[{"code":"UNAVAILABLE","message":"service unavailable","detail":"health check failed: please see /debug/health"}]}

这个问题困扰我很久,最后在stackoverflow 找到解决方案,直接往OSS随便传入一个文件就好了,据说是一个bug,不允许OSS是空的。

解决直接上传镜像就好了:

$ docker push hub.delznet.com:15000/delz/busybox
The push refers to repository [hub.delznet.com:15000/delz/busybox]
3e596351c689: Pushed 
latest: digest: sha256:14c5b9511c7ed9c811c6d18895e834836b2b78970ba22286585727ff78c081a5 size: 527

直接登录容器,发现用OSS存储,/var/lib/registry目录是空的,如果本地目录映射,是不会空的。出现这个问题是否跟这个有关?

6. 用nginx代理转发

上面的网址hub.delznet.com:15000不好看,想取这样的网址hub.delznet.com,但是443端口肯定不能被容器单独占用,所以可以用nginx做代理转发。

修改docker-compose.yml, 删除ca证书部分,这个会放到nginx,如下:

registry:
    restart: always
    image: "registry:2"
    ports:
        - 15000:5000
    volumes:
        - ./auth:/auth
    environment:
        - REGISTRY_AUTH=htpasswd
        - REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm
        - REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd
        - REGISTRY_STORAGE=oss
        - REGISTRY_STORAGE_OSS_ACCESSKEYID=***
        - REGISTRY_STORAGE_OSS_ACCESSKEYSECRET=***    
        - REGISTRY_STORAGE_OSS_REGION=oss-cn-shanghai-internal
        - REGISTRY_STORAGE_OSS_BUCKET=delz-docker

nginx新增服务如下:

upstream docker-registry {
    server 127.0.0.1:15000;
}
server {
    listen 443;
    server_name hub.delznet.com;
    client_max_body_size 0;
    chunked_transfer_encoding on;
    ssl on;
    ssl_certificate   /data/docker-registry/certs/214572164630922.pem;
    ssl_certificate_key  /data/docker-registry/certs/214572164630922.key;
    ssl_session_timeout 10m;
    ssl_session_cache shared:SSL:10m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    location / {
        proxy_pass http://docker-registry;
        proxy_read_timeout 900;
    }
}

~

如果是nginx不是在一个机器上,可以用局域网ip,这个ip要写入”insecure-registries”。

重新安装registry容器,重启nginx。