一、背景

首先,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. OFS环境,删除Resource 时出现错误失败,应该如何继续

    From the Windows failover cluster manager,select the group listener, stop it, and delete it.  Do the ...

  2. navicat 创建查询失败 can not create file

    数据库连接很正常, 却无法创建查询, 不知道啥毛病 竟然是存储路径问题,点开连接属性,修改高级里面的保存路径,删掉“:3308”, OK了.冒号是个windows保留的盘符,应该就是这个原因

  3. Android AccessibilityService(辅助服务) 使用示例

    1.前言 网上关于Android辅助服务的使用方式已经非常丰富了,所以也不在乎再多我这一篇了:-D.有同学说这是重复造轮子,题主很同意,但反过来说,如果自己没有能力造出轮子,还对重复造轮子嗤之以鼻,那 ...

  4. 《深入浅出NodeJS》mindmap

    接触NodeJS有快两年了,但因为和我的工作内容关系不大,所以一直没有系统的学习.最近终于有空能系统地了解学习一下这门技术,于是买了一本朴灵老师的<深入浅出NodeJS>仔细研读.这本书内 ...

  5. Asp.Net_HttpModule的应用

    IHttpModule向实现类提供模块初始化和处置事件. IHttpModule包含兩個方法: public void Init(HttpApplication context);public voi ...

  6. 软件测试----H模型

    H模型将测试活动完全独立出来,形成一个完整的流程,同时将测试准备和测试执行清晰表现出来. 测试流程: --测试准备:所有测试活动的准备判断是否到测试就绪点. --测试就绪点:测试准入准则,即是否可以开 ...

  7. 《杜增强讲Unity之Tanks坦克大战》9-发射子弹时蓄力

    9 发射子弹时蓄力 实现效果如下   image 按下开火键(坦克1为空格键)重置力为最小力,一直按着的时候蓄力,抬起的时候发射.如果按着的时候蓄力到最大,则自动发射,此时在抬起则不会重复发射. 首先 ...

  8. shell实现压缩多个文件

    Linux环境下写一个脚本 从键盘让用户输入几个文件,脚本能够将此几个文件归档压缩成一个文件: 1.首先介绍一下case语句格式 case SWITCH in value1) statement .. ...

  9. TimelineJS JSON 数据格式 - 译文 [原创]

    TimelineJS 是用于绘制时间轴的 Javascript 开源脚本,目前是 TimelineJS3 版.参阅 https://github.com/NUKnightLab/TimelineJS3 ...

  10. POW的重力之美

    定律一:每一个UTXO都保持其状不变,直到有外力迫使它改变这种状态为止--艾萨克•牛顿,原理2.0 在过去的几年里,关于比特币的工作量证明(PoW)所造成的"巨大的能源浪费"已经被 ...