一、背景

首先,Docker Hub是一个很好的用于管理公共镜像的地方,我们可以在上面找到想要的镜像(Docker Hub的下载量已经达到数亿次);而且我们也可以把自己的镜像推送上去。但是,有的时候,使用场景需要我们有一个私有的镜像仓库用于管理自己的镜像,这个时候我们就通过Registry来实现此目的。本文详细介绍了本地镜像仓库Docker Registry & Portus的搭建过程。

Registry作为Docker的核心组件之一负责镜像内容的存储与分发,客户端的docker pull以及push命令都将直接与registry进行交互。最初版本的registry 由Python实现。由于设计初期在安全性,性能以及API的设计上有着诸多的缺陷,该版本在0.9之后停止了开发,新的项目distribution(新的docker register被称为Distribution,你可以在这里找到文档 。)来重新设计并开发下一代registry。新的项目由go语言开发,所有的API,底层存储方式,系统架构都进行了全面的重新设计已解决上一代registry中存在的问题。2016年4月份rgistry 2.0正式发布,docker 1.6版本开始支持registry 2.0,而八月份随着docker 1.8 发布,docker hub正式启用2.1版本registry全面替代之前版本 registry。新版registry对镜像存储格式进行了重新设计并和旧版不兼容,docker 1.5和之前的版本无法读取2.0的镜像。

另外,Registry 2.4版本之后支持了回收站机制,也就是可以删除镜像了。在2.4版本之前是无法支持删除镜像的,所以如果你要使用最好是大于Registry 2.4版本的。

二、Registry V2的变化

Docker build镜像时会为每个layer生成一串layer id,这个layer id是一个客户端随机生成的字符串,和镜像内容无关。我们可以通过一个简单的例子来查看。提供一个简单的dockerfile。

 
1
2
3
4
5
$ cat dockerfile
FROM nginx
MAINTAINER dkey
EXPOSE 80
ENTRYPOINT nginx -g "daemon off;"

加上–no-cache强制重新build。

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ docker build --no-cache=true -t nginx:dockerfile .
Sending build context to Docker daemon 230.9 MB
Step 1 : FROM nginx
---> 19146d5729dc
Step 2 : MAINTAINER dkey
---> Running in e3f81ad4b150
---> 7164bae33eb7
Removing intermediate container e3f81ad4b150
Step 3 : EXPOSE 80
---> Running in 7e73d2d24587
---> 3610eda3790c
Removing intermediate container 7e73d2d24587
Step 4 : ENTRYPOINT nginx -g "daemon off;"
---> Running in 1733ff25370e
---> 60792ac79d11
Removing intermediate container 1733ff25370e
Successfully built 60792ac79d11

接下来再次build。

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ docker build -t nginx:dockerfile .
Sending build context to Docker daemon 230.9 MB
Step 1 : FROM nginx
---> 19146d5729dc
Step 2 : MAINTAINER dkey
---> Using cache
---> 7164bae33eb7
Step 3 : EXPOSE 80
---> Using cache
---> 3610eda3790c
Step 4 : ENTRYPOINT nginx -g "daemon off;"
---> Using cache
---> 60792ac79d11
Successfully built 60792ac79d11

可以看到使用cache层的layer id一致,其余layer的id都发生了变化。这种随机layer id以及layer id与内容无关的设计会带来很多的问题。

首先, registry v1通过id来判断镜像是否存在,客户需不需要重新push,而由于镜像内容和id无关,再重新build后layer在内容不变的情况下很可能id发生变化,造成无法利用registry中已有layer反复push相同内容。服务器端也会有重复存储造成空间浪费。

其次,尽管id由32字节组成但是依然存在id碰撞的可能,在存在相同id的情况下,后一个layer由于id和仓库中已有layer相同无法被push到registry中,导致数据的丢失。用户也可以通过这个方法来探测某个id是否存在。

最后,同样是由于这个原因如果程序恶意伪造大量layer push到registry中占位会导致新的layer无法被push到registry中。Docker官方重新设计新版registry的一个主要原因也就是为了解决该问题。

新版的registry吸取了旧版的教训,在服务器端会对镜像内容进行哈希,通过内容的哈希值来判断layer在registry中是否存在,是否需要重新传输。这个新版本中的哈希值被称为digest是一个和镜像内容相关的字符串,相同的内容会生成相同的digest。由于digest和内容相关,因此只要重新build的内容相同理论上讲无需重新push,但是由于安全性的考量在特定情况下layer依然要重新传输。由于layer是按digest进行存储,相对v1按照随机id存储可以大幅减小磁盘空间占用。registry服务端会对冲突digest进一步进行处理,同时由于digest是由registry服务端生成,用户无法伪造digest也很大程度上保证了registry内容的安全性。

1)安全性改进

除了对image内容进行唯一性哈希外,新版registry还在鉴权方式以及layer权限上上进行了大幅度调整。鉴权方式:

旧版本的服务鉴权模型如下图所示:

该模型每次client端和registry的交互都要多次和index打交道,新版本的鉴权模型去除了上图中的第四第五步,如下图所示:

新版本的鉴权模型需要registry和authorization service在部署时分别配置好彼此的信息,并将对方信息作为生成token的字符串,已减少后续的交互操作。新模型客户端只需要和authorization service进行一次交互获得对应token即可和registry进行交互,减少了复杂的流程。同时registry和authorization service一一对应的方式也降低了被攻击的可能。

2)权限控制

旧版的registry中对layer没有任何权限控制,所有的权限相关内容都由index完成。在新版registry中加入了对layer的权限控制,每个layer都有一个manifest来标识该layer由哪些repository共享,将权限做到repository级别。

3)Pull性能改进

旧版registry中镜像的每个layer都包含一个ancestry的json文件包含了父亲layer的信息,因此当我们pull镜像时需要串行下载,下载完一个layer后才知道下一个layer的id是多少再去下载。如下图所示:

新版registry在image的manifest中包含了所有layer的信息,客户端可以并行下载所有的layer如下图所示:

4)其他改进

– 全新的API。

– push和pull支持断点。

– 后端存储的插件。

– notification机制。

– 支持删除镜像,有了回收站机制。

三、安装配置Registry

直接下载registry

 
1
$ docker pull registry:2.4.1

v2.4.1的registry是把image文件放到了/var/lib/registry下。

最简单方式启动,启动一个registry是很容易的,如下:

 
1
2
3
4
5
# 下载Registry镜像;
$ docker pull registry:2.4.1
 
# 启动Registry;
$ docker run -d -p 5000:5000 --restart=always --name registry --privileged=true -v /data/:/var/lib/registry registry:2.4.1

--name :指定容器名称。

--privileged=true :CentOS7中的安全模块selinux把权限禁掉了,参数给容器加特权,不加上传镜像会报权限错误。

这里指定了一个/var/lib/registry的卷,是为了把真实的镜像数据储存在主机上,而别在容器挂掉之后丢失数据。就算这样,也还是不保险。要是主机挂了呢?Docker官方建议可以放到ceph 、 swift这样的存储里,或是亚马逊S3 、微软Azure 、谷歌GCS 、阿里云OSS之类的云商那里。Docker registry提供了配置文件,可以从容器里复制出来查看:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ docker cp registry:/etc/docker/registry/config.yml /data/config.yml
$ cat /data/config.yml
version: 0.1
log:
  fields:
    service: registry
storage:
    cache:
        blobdescriptor: inmemory
    filesystem:
        rootdirectory: /var/lib/registry
http:
    addr: :5000
    headers:
        X-Content-Type-Options: [nosniff]
health:
  storagedriver:
    enabled: true
    interval: 10s
    threshold: 3

配置文件里有一个storage ,按照这里写的配置,然后执行以下命令重新挂载这个文件来启动registry就可以了,有条件的话可以去试一试:

 
1
2
$ docker rm -fv registry
$ docker run -d -p 5000:5000 --restart=always --name registry -v /data/:/var/lib/registry -v /data/config.yml:/etc/docker/registry/config.yml registry:2.4.1

Docker Registry配置完了,然后可以在本机通过docker push 127.0.0.1:5000/xxx的方式推送镜像到registry中(推送镜像必须使用docker images可查看)。

 
1
2
$ docker tag wordpress 127.0.0.1:5000/wordpress
$ docker push 127.0.0.1:5000/wordpress

但是只能在本地使用127.0.0.1进行推送,不能在其他主机push镜像,包括本机通过IP地址也不可以推送镜像。当在其他主机或者在本机通过IP推送镜像时,docker默认会认为地址是HTTPS加密的,而实际上我们启动registry时并没有加密,所以会报错。如下:

 
1
2
3
4
$ docker tag wordpress 10.99.73.10:5000/wordpress
$ docker push 10.99.73.10:5000/wordpress
The push refers to a repository [172.17.0.1:5000/wordpress]
Get https://172.17.0.1:5000/v1/_ping: http: server gave HTTP response to HTTPS client

解决方案:

第一种:在需要推送镜像的服务器上修改dockerd启动参数【官方资料】,然后重启docker。再推送镜像时就会认为这个地址是HTTP,不会报错了,但在每一台主机添加这个配置是很麻烦和危险的。另外我参照官方的做法和网上的做法根本没有办法解决。后来就在网上翻了很久找到了一个解决办法,在docker host端的/etc/docker目录下添加一个daemon.json文件,内容如下:

 
1
2
$ cat /etc/docker/daemon.json
{ "insecure-registries":["10.99.73.10:5000"] }

然后重启docker,就OK了。

 
1
$ systemctl restart docker

如果有多个地址,可以这么写。

 
1
{ "insecure-registries":["10.99.73.10:5000","10.106.201.12:5000"] }

再次PUSH镜像就成功了。

 
1
2
3
4
5
6
7
$ docker tag nginx 10.99.73.10:5000/nginx
$ docker push 10.99.73.10:5000/nginx
The push refers to a repository [10.99.73.10:5000/nginx]
bc1394447d64: Pushed
6591c6f92a7b: Pushed
f96222d75c55: Mounted from wordpress
latest: digest: sha256:dedbce721065b2bcfae35d2b0690857bb6c3b4b7dd48bfe7fc7b53693731beff size: 948

第二种:自建证书,让register以TLS的方式启动,【官方资料】。

1. 创建你自己的CA证书

 
1
2
3
4
5
6
7
8
$ openssl req -newkey rsa:4096 -nodes -sha256 -keyout /data/cert/ca.key -x509 -days 365 -out /data/cert/ca.crt
Country Name (2 letter code) [XX]:cn
State or Province Name (full name) []:sh
Locality Name (eg, city) [Default City]:sh
Organization Name (eg, company) [Default Company Ltd]:ca
Organizational Unit Name (eg, section) []:ca
Common Name (eg, your name or your server's hostname) []:10.99.73.10
Email Address []:admin@ca.com

2. 生成证书签名请求

如果使用像dockerhub.ywnds.com这样的FQDN连接register主机,则必须使用dockerhub.ywnds.com作为CN(通用名称)。否则,如果你使用IP地址连接你的register主机,CN可以是任何类似你的名字等等:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
$ openssl req -newkey rsa:4096 -nodes -sha256 -keyout /data/cert/ywnds.com.key -out /data/cert/ywnds.com.csr
Country Name (2 letter code) [XX]:cn
State or Province Name (full name) []:sh
Locality Name (eg, city) [Default City]:sh
Organization Name (eg, company) [Default Company Ltd]:ywnds
Organizational Unit Name (eg, section) []:tech
Common Name (eg, your name or your server's hostname) []:10.99.73.10
Email Address []:admin@ywnds.com
 
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

3. 生成register主机的证书

如果你使用的是像dockerhub.ywnds.com这样的FQDN来连接您的register主机,请运行以下命令以生成register主机的证书:

 
1
$ openssl x509 -req -days 365 -in /data/cert/ywnds.com.csr -CA /data/cert/ca.crt -CAkey /data/cert/ca.key -CAcreateserial -out /data/cert/ywnds.com.crt

如果你使用的是IP,比如你的register主机10.99.73.10,你可以运行下面的命令生成证书:

 
1
2
$ echo subjectAltName = IP:10.99.73.10 > extfile.cnf
$ openssl x509 -req -days 365 -in /data/cert/ywnds.com.csr -CA /data/cert/ca.crt -CAkey /data/cert/ca.key -CAcreateserial -extfile extfile.cnf -out /data/cert/ywnds.com.crt

启动register:

 
1
2
3
4
5
6
7
8
9
10
$ docker rm -fv registry
$ docker run -d \
    -p 5000:5000 \
    --restart=always \
    --name registry \
    -v /data/cert/:/cert \
    -v /data/docker/registry:/var/lib/registry \
    -e REGISTRY_HTTP_TLS_CERTIFICATE=/cert/ywnds.com.crt \
    -e REGISTRY_HTTP_TLS_KEY=/cert/ywnds.com.key \
    registry:2.4.1

启动后访问会报错:certificate signed by unknown authority,因为这是个自签名的证书(没有经过CA签证的)。docker在验证TLS时会自动读取这个目录下的证书。然后重启docker即可。

解决方案是将刚生成的docker.crt复制到客户端/etc/docker/certs.d/${registry}:${port}/ca.crt(${registry}是域名或你的register主机IP),如果该目录不存在,请创建它。客户端操作如下,需要把此证书复制到客户端即可(更名为ca.crt),操作如下:

 
1
2
$ mkdir -p /etc/docker/certs.d/10.99.73.10:5000
$ scp /data/certs/docker.crt 10.99.73.9:/etc/docker/certs.d/10.99.73.10:5000/ca.crt

此时再push就ok了,如下:

 
1
2
3
4
$ docker push 10.99.73.10:5000/wordpress
The push refers to a repository [10.99.73.10:5000/wordpress]
2ff5b2ab6416: Layer already exists
..............

如果报cannot validate certificate for 10.99.73.10 because it doesn’t contain any IP SANs错误,检查一下ca.crt证书是否正确,以及生成register主机的证书的时候使用的是域名还是IP,其方式是否正确。

问题解决。至此, docker registry私有仓库安装成功。但是还是有些缺点:只要有了证书,还是谁都可以往库里推镜像。简单的解决方案就是使用用户认证。

四、操作Registry镜像

下面都是以http方式访问,如果你加了证书就需要使用https进行访问了。

1)列出当前所有镜像

 
1
2
$ curl http://10.99.73.10:5000/v2/_catalog
{"repositories":["busybox_1","nginx","wordpress"]}

2)列出当前指定镜像

 
1
$ curl http://10.99.73.10:5000/v2/_catalog?n=100

3)搜索镜像

 
1
2
$ curl http://10.99.73.10:5000/v2/wordpress/tags/list
{"name":"wordpress","tags":["latest"]}

4)确认Registry是否正常工作

 
1
2
$ curl http://10.99.73.10:5000/v2/
{}

返回{}就表示正常工作。

5)删除镜像

Docker仓库在2.1版本中支持了删除镜像的API,但这个删除操作只会删除镜像元数据,不会删除层数据。在2.4版本中对这一问题进行了解决,增加了一个垃圾回收命令,删除未被引用的层数据。但有一些条件限制,具体操作步骤如下:

启动仓库容器

 
1
2
3
4
5
6
7
$ docker run -d \
    -p 5000:5000 \
    --restart=always \
    --name registry \
    -v /data/:/var/lib/registry \
    -v /data/config.yml:/etc/docker/registry/config.yml \
    registry:2.4.1

这里需要说明一点,在启动仓库时,需在配置文件中的storage配置中增加delete=true配置项,允许删除镜像,本次试验采用如下配置文件:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
version: 0.1
log:
  fields:
    service: registry
storage:
    delete:
        enabled: true
    cache:
        blobdescriptor: inmemory
    filesystem:
        rootdirectory: /var/lib/registry
http:
    addr: :5000
    headers:
        X-Content-Type-Options: [nosniff]
health:
  storagedriver:
    enabled: true
    interval: 10s
    threshold: 3

查看数据进行仓库容器中,通过du命令查看大小,可以看到当前仓库数据大小为339M。

 
1
2
$ du -sh /data/docker/registry/v2/
339M /data/docker/registry/v2/

删除镜像对应的API如下:

 
1
DELETE /v2/<name>/manifests/<reference>

name:镜像名称。

reference:镜像对应sha256值。

首先查看要删除镜像的sha256

 
1
2
$ ls /data/docker/registry/v2/repositories/wordpress/_manifests/revisions/sha256/
4eefa1b7fdce1b6e6953ca18b6f49a68c541e9e07808e255c3b8cc094ff085da

进行删除操作

 
1
2
3
4
5
6
7
$ curl -I -X DELETE http://10.99.73.10:5000/v2/wordpress/manifests/sha256:4eefa1b7fdce1b6e6953ca18b6f49a68c541e9e07808e255c3b8cc094ff085da
HTTP/1.1 202 Accepted
Docker-Distribution-Api-Version: registry/2.0
X-Content-Type-Options: nosniff
Date: Thu, 15 Dec 2016 06:27:19 GMT
Content-Length: 0
Content-Type: text/plain; charset=utf-8

执行垃圾回收

命令:registry garbage-collect config.yml

 
1
2
$ docker exec -ti registry bash
root@ef45a8a624c1:/# registry garbage-collect /etc/docker/registry/config.yml

再看数据大小

 
1
2
$ du -sh /data/docker/registry/v2/
88K /data/docker/registry/v2/

可以看到镜像数据已被删除,从339M变成了88K。

PS:尝试过直接在目录中把镜像删除,然后重启docker daemon,此镜像也会删除。

下载镜像

 
1
$ docker pull 10.99.73.10:5000/wordpress

PS:注意后面还可以跟上tags,默认就是latest。

五、用户认证

首先在registry生成用户名hello和密码world:

 
1
2
$ mkdir /data/auth
$ sh -c "docker run --entrypoint htpasswd registry:2.4.1 -Bbn hello world > /data/auth/htpasswd"

还得指定认证方式和认证文件等参数,重新启动registry容器:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ docker rm -f registry
$ docker run -d \
    -p 5000:5000 \
    --name registry \
    --restart=always \
    -v /data/docker/:/var/lib/registry \
    -v /data/auth:/auth \
    -v /data/cert:/cert \
    -e REGISTRY_HTTP_TLS_CERTIFICATE=/cert/ywnds.com.crt \
    -e REGISTRY_HTTP_TLS_KEY=/cert/ywnds.com.key \
    -e REGISTRY_AUTH=htpasswd \
    -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
    -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
    registry:2.4.1

再次push就会失败啦。

 
1
2
3
4
$ docker push 10.99.73.10:5000/wordpress
The push refers to a repository [10.99.73.10:5000/wordpress]
........
no basic auth credentials

但是我们可以用用户名hello和密码world登录,然后在进行push:

 
1
2
$ docker login -u hello -p world 10.99.73.10:5000
Login Succeeded

登录成功后,再次push就会成功了。如果想退出登录,使用logout即可。

 
1
2
$ docker logout 10.99.73.10:5000
Remove login credentials for 10.99.73.10:5000

Docker私有仓库到这里就结束了,个人感觉还是有很多不足。有兴趣可以看看:

喜欢 (16)or分享 (1)

Docker:搭建私有仓库(Registry 2.4)的更多相关文章

  1. Docker 搭建私有仓库

    Docker 搭建私有仓库 环境: docker 版本 :18.09.1 主机地址:192.168.1.79 1.运行并创建私有仓库 docker run -d \ -v /opt/registry: ...

  2. [Docker]docker搭建私有仓库(ssl、身份认证)

    docker搭建私有仓库(ssl.身份认证) 环境:CentOS 7.Docker 1.13.1 CentOS 7相关: https://www.cnblogs.com/ttkl/p/11041124 ...

  3. 菜鸟系列docker——搭建私有仓库harbor(6)

    docker 搭建私有仓库harbor 1. 准备条件 安装docker sudo yum update sudo yum install -y yum-utils device-mapper-per ...

  4. Docker搭建私有仓库

    1,下载仓库镜像. docker pull  registry    //主要用于搭建私有仓库的. 2,将宿主机端口映射到容器中去,容器的5000端口是不能更改的. docker run -d -p ...

  5. Docker:私有仓库registry [十一]

    一.运行docker私有仓库 安装registry docker run -d -p 5000:5000 --restart=always --name registry -v /opt/myregi ...

  6. docker 搭建私有仓库 harbor

    前提 已安装好 docker 和  docker-compose 环境:CentOS Linux release 7.5 docker 版本:18.09.05 1.安装harbor wget -P / ...

  7. docker 搭建私有云仓库

    docker搭建私有仓库   registry私有仓库 下载docker-distribution软件包 yum install epel-release yum install docker-dis ...

  8. Ubuntu Docker Registry 搭建私有仓库

    服务器版本 Ubuntu 16.04 LTS. 安装命令: $ docker run -d -v /opt/registry:/var/lib/registry -p 5000:5000 --rest ...

  9. docker学习(8) 在mac机上搭建私有仓库

    docker的私有仓库类似maven的私服,一般用于公司内部搭建一个类似docker hub的环境,这样上传.下载镜像速度较快,本文将演示如何在mac上利用docker-machine搭建无需SSL证 ...

  10. Docker入门之四搭建私有仓库

    前面学习了下镜像和容器,今天来学习下仓库,来搭建本地私有仓库.当然可以使用远程的共有的仓库,但在企业中有的还是放在本地,所以需要搭建私有仓库. 一.搭建仓库 可以在容器中run一个仓库镜像. dock ...

随机推荐

  1. python 回溯法 子集树模板 系列 —— 6、排课问题

    问题 某乡村小学有六个年级,每个年级有一个班,共六个班. 周一到周五,每天上6节课,共计30节课. 开设的课程 一年级:语(9)数(9)书(2)体(2)美(2)音(2)德(2)班(1)安(1) 二年级 ...

  2. sqlyog mysql 外键引用列找不到想要的字段的原因

    这是因为引用列必须为一个主键才行

  3. SSIS 你真的了解事务吗?

    事务用于处理数据的一致性,事务的定义是,处于同一个事务中的操作是一个工作单元,要么全部执行成功,要么全部执行失败.把事务的概念应用到在实际的SSIS Package场景中,如何在Package中实现事 ...

  4. effective c++ 笔记 (23-25)

    //---------------------------15/04/08---------------------------- //#23   宁以non_member.non_friend替换m ...

  5. node基础:文件系统-文件读取

    node的文件读取主要分为同步读取.异步读取,常用API有fs.readFile.fs.readFileSync.还有诸如更底层的fs.read,以及数据流(stream),后面再总结下咯~ 直接上简 ...

  6. 数据中心网络(1)-VXLAN

    想写个DC系列的文章,站在传统路由交换网络基础上谈谈数据中心网络,一方面是给自己的学习做下总结,另一方面也想分享一些东西. 谈到数据中心网络,能想到的东西无非就VXLAN.SDN.NFV.EVPN这些 ...

  7. 智能合约bug以及修改方案

    截取两篇文章:第一遍文章说的是智能合约能不能修改的问题: ETC转到ETH地址以及转币进ETH智能合约账户能不能转出来? 第0章 引言 如果ETC充值到了ETH地址上,能找回来吗?答案是不一定. ET ...

  8. 重温jsp③

    Jsp详细   九大内置对象 Out jsp的输出流,用来向客户端响应 page 当前jsp对象!他的引用类型是object,即真身中有如下代码:object page=this: Session h ...

  9. Spark 实践——用 Scala 和 Spark 进行数据分析

    本文基于<Spark 高级数据分析>第2章 用Scala和Spark进行数据分析. 完整代码见 https://github.com/libaoquan95/aasPractice/tre ...

  10. 黄金分割点(第五周 c语言版)

    在上一周,学习其他课程的同时,用C语言编写了黄金分割点小游戏.因为要做界面需要mfc,当时学的时候还做了个简单的计算器.目前c++的知识忘的差不多了,所以就先用C语言来实现算法.打算接下来的一周复习c ...