docker registry v2 私有仓库搭建

摘要

由于最近在做一个docker迁移项目,原环境是一套买来的,现在不用了,所以乘着这个机会就整个环境重新搭建了,网上找了一堆乱七八糟的文档,看着都不太一样,最终整合了一下,经过自己的测试,终于调通了。

img

安装docker

1
2
3
4
5
6
7
apt update && apt upgrade
apt install -y curl
curl https://get.docker.com | sh
apt-get install -y sysv-rc-conf
sysv-rc-conf
将docker设置开机自启

重建docker0网桥

1
2
3
4
5
6
7
8
9
apt-get install bridge-utils
/etc/init.d/docker stop
ip link set dev docker0 down
brctl delbr docker0
brctl addbr docker0
ip addr add 172.30.1.1/24 dev docker0
ip link set dev docker0 up
/etc/init.d/docker start

下载docker registry 镜像

1
docker pull registry

registry 测试

启动测试

1
2
3
4
5
6
7
8
docker run -d -p 5000:5000 \
--restart=always --name registry \
-v /data/registry:/var/lib/registry registry
docker ps 可以看到registry容器
netstat -ntlp 可以看到监听5000端口
curl http://127.0.0.1:5000/v2/_catalog
看到返回 {} 表示registry运行正常

推送验证

1
2
3
4
docker tag alpine 172.16.7.3:5000/myalpine
docker push 172.16.7.3:5000/myalpine
可以正常推送说明搭建成功

带验证的registry

创建本地目录

1
mkdir -p /data/registry/{registry,conf,auth}

生成用户和密码

1
2
cd /data/registry/auth
docker run --entrypoint htpasswd registry -Bbn httx httx_password > htpasswd;

创建配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cd /data/registry/conf
vim 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

启动命令

1
2
3
4
5
6
7
8
docker run -d -p 5000:5000 \
--restart=always --name registry \
-v /data/registry/auth:/auth \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
-v /data/registry/conf:/etc/docker/registry/ \
-v /data/registry/registry:/var/lib/registry registry

验证测试

1
2
3
docker login http://172.16.7.3:5000
docker push 172.16.7.3:5000/myalpine
curl http://httx:httx_password@127.0.0.1:5000/v2/_catalog

给registry添加nginx+https代理验证

编译安装nginx1.9.15、创建ngxin启动文件、创建nginx配置文件请参照另一篇文章

nginx1.9.15 编译安装

制作ca证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cd /usr/local/nginx/conf/ssl
mkdir -p demoCA/newcerts
touch demoCA/index.txt
echo 01 > demoCA/serial
# 上面这个不错有可能会报错
Using configuration from /usr/lib/ssl/openssl.cnf
Enter pass phrase for ca.key:
I am unable to access the ./demoCA/newcerts directory
./demoCA/newcerts: No such file or directory
生成根证书
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 7305 -key ca.key -out ca.crt
# 需要注意的是CN一定要天填写你的域名,后面的配置需要一致
Common Name (e.g. server FQDN or YOUR name) []:opreg.chinawayltd.com
Email Address []:xxx@163.com

生成服务端证书并用ca证书认证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
openssl genrsa -out opreg.key 2048
openssl req -new -key opreg.key -out opreg.chinawayltd.com.csr
# 需要注意的是CN一定要天填写你的域名, 跟前面的配置需要一致
Common Name (e.g. server FQDN or YOUR name) []:opreg.chinawayltd.com
Email Address []:xxx@163.com
# challenge password 一定要为空
openssl ca -policy policy_anything -days 1460 -cert ca.crt -keyfile ca.key -in opreg.chinawayltd.com.csr -out opreg.chinawayltd.com.crt
mkdir -p /etc/docker/certs.d/opreg.chinawayltd.com/
cp ca.crt /etc/docker/certs.d/opreg.chinawayltd.com/

生成验证文件

1
htpasswd -c conf/.htpasswd httx

创建虚拟主机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
vim conf/vhosts/opreg.conf
upstream docker-registry {
server 127.0.0.1:5000;
}
server {
listen 443 default_server;
server_name opreg.chinawayltd.com;
ssl on;
ssl_certificate /usr/local/nginx/conf/ssl/opreg.chinawayltd.com.crt;
ssl_certificate_key /usr/local/nginx/conf/ssl/opreg.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:demo-pay:4m;
# set HSTS-Header because we only allow https traffic
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
proxy_set_header Host $http_host; # required for Docker client sake
proxy_set_header X-Real-IP $remote_addr; # pass on real client IP
client_max_body_size 0; # disable any limits to avoid HTTP 413 for large image uploads
# required to avoid HTTP 411: see Issue #1486 (https://github.com/dotcloud/docker/issues/1486)
chunked_transfer_encoding on;
location / {
# let Nginx know about our auth file
auth_basic "Restricted";
auth_basic_user_file /usr/local/nginx/conf/.htpasswd;
proxy_pass http://docker-registry;
}
location /_ping {
auth_basic off;
proxy_pass http://docker-registry;
}
location /v1/_ping {
auth_basic off;
proxy_pass http://docker-registry;
}
location /v2/ {
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 realm";
auth_basic_user_file /usr/local/nginx/conf/.htpasswd;
# tengine 2.1.2
add_header 'Docker-Distribution-Api-Version' 'registry/2.0';
# nginx 1.9.15
#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;
}
}

client 配置

将自签ca证书发送到客户端

1
2
mkdir -p /etc/docker/certs.d/opreg.chinawayltd.com/
scp 172.16.7.1:/etc/docker/certs.d/opreg.chinawayltd.com/ca.crt /etc/docker/certs.d/opreg.chinawayltd.com/

docker api

以下关于docker registry api 的内容取自(http://blog.csdn.net/ztsinghua/article/details/51496658)

docker registry v2版本的http api 一直没有找到合适的,自己通过阅读官方文档整理了一下。
概要

method path Entity Description
GET /v2/ Base Check that the endpoint implements Docker Registry API V2.
GET /v2//tags/list Tags Fetch the tags under the repository identified by name.
GET /v2//manifests/ Manifest Fetch the manifest identified by nameand referencewhere referencecan be a tag or digest. A HEADrequest can also be issued to this endpoint to obtain resource information without receiving all data.
PUT /v2//manifests/ Manifest Put the manifest identified by nameand referencewhere referencecan be a tag or digest.
DELETE /v2//manifests/ Manifest Delete the manifest identified by nameand reference. Note that a manifest can only be deleted by digest.
GET /v2//blobs/ Blob Retrieve the blob from the registry identified bydigest. A HEADrequest can also be issued to this endpoint to obtain resource information without receiving all data.
DELETE /v2//blobs/ Blob Delete the blob identified by nameand digest
POST /v2//blobs/uploads/ Initiate Blob Upload Initiate a resumable blob upload. If successful, an upload location will be provided to complete the upload. Optionally, if thedigest parameter is present, the request body will be used to complete the upload in a single request.
GET /v2//blobs/uploads/ Blob Upload Retrieve status of upload identified byuuid. The primary purpose of this endpoint is to resolve the current status of a resumable upload.
PATCH /v2//blobs/uploads/ Blob Upload Upload a chunk of data for the specified upload.
PUT /v2//blobs/uploads/ Blob Upload Complete the upload specified by uuid, optionally appending the body as the final chunk.
DELETE /v2//blobs/uploads/ Blob Upload Cancel outstanding upload processes, releasing associated resources. If this is not called, the unfinished uploads will eventually timeout.
GET /v2/_catalog Catalog Retrieve a sorted, json list of repositories available in the registry.

专有名词解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
repository name(存储库名称)
存储库指在库中存储的镜像
语法
经典存储库名称由两级路径构成,每级路径小于30个字符,v2的api不强制要求这样的格式
每级路径名至少有一个小写字母或者数字,使用句号,破折号和下划线分隔。 更严格来说,它必须符合正则表达式:[a-z0-9]+(?:[._-][a-z0-9]+)*
多级路径用/分隔
存储库名称总长度(包括/)不能超过256个字符
digest(概要)
概要是镜像各个层的唯一标识。虽然算法允许使用任意算法,但是为了兼容性应该使用sha256。
例:sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b
语法
digest := algorithm ":" hex
algorithm := /[A-Fa-f0-9_+.-]+/
hex := /[A-Fa-f0-9]+/
生成摘要的伪代码
let C = 'a small string'
let B = sha256(C)
let D = 'sha256:' + EncodeHex(B)
let ID(C) = D
PULL镜像过程
镜像由一个json清单和 层叠文件组成,pull镜像的过程就是检索这两个组件的过程。
拉取镜像的第一步就是获取清单,清单由下面几个字段组成:
字段 描述
name 镜像名称
tag 镜像当前版本的tag
fsLayers 层描述列表(包含摘要)
signature 一个JWS签名,用来验证清单内容
当获取清单之后,客户端需要验证签名(signature),以确保名称和层是有效的。确认后,
客户端可以使用digest去下载各个层,在v2API中,层存储在blobs中以digest作为键值。
Pulling an Image Manifest(拉取镜像清单)
API
>>> HEAD /v2/<name>/manifests/<reference> #检查镜像清单是否存在
>>> GET /v2/<name>/manifests/<reference> #拉取镜像清单
Note
reference可以是tag或者是digest
Pulling a Layer(拉取层)
API
>>> GET /v2/<name>/blobs/<digest>
PUSHING镜像过程
推镜像和拉取镜像过程相反,先推各个层到registry仓库,然后上传清单。
Pushing a Layer(上传层)
上传层分为两步,第一步使用post请求在registry仓库启动上传服务
,返回一个url,这个url可以用来上传数据和检查状态。
Existing Layers(检查层是否存在)
API
>>> HEAD /v2/<name>/blobs/<digest>
RETURN
若返回200 OK则表示存在,不用上传
Starting An Upload(启动上传服务)
API
>>> POST /v2/<name>/blobs/uploads/
RETURN
如果post请求返回202 accepted,一个url会在location字段返回.
202 Accepted
Location: /v2/<name>/blobs/uploads/<uuid>
Range: bytes=0-<offset>
Content-Length: 0
Docker-Upload-UUID: <uuid> # 可以用来查看上传状态和实现断点续传
Uploading the Layer(上传层)
Upload Progress(上传进度)
API
>>> GET /v2/<name>/blobs/uploads/<uuid>
>>> Host: <registry host>
RETURN
204 No Content
Location: /v2/<name>/blobs/uploads/<uuid>
Range: bytes=0-<offset>
Docker-Upload-UUID: <uuid>
Monolithic Upload(整块上传)
API
>>> PUT /v2/<name>/blobs/uploads/<uuid>?digest=<digest>
>>> Content-Length: <size of layer>
>>> Content-Type: application/octet-stream
<Layer Binary Data>
Chunked Upload(分块上传)
API
>>> PATCH /v2/<name>/blobs/uploads/<uuid>
>>> Content-Length: <size of chunk>
>>> Content-Range: <start of range>-<end of range>
>>> Content-Type: application/octet-stream
<Layer Chunk Binary Data>
RETURN
如果服务器不接受这个块,则返回:
416 Requested Range Not Satisfiable
Location: /v2/<name>/blobs/uploads/<uuid>
Range: 0-<last valid range>
Content-Length: 0
Docker-Upload-UUID: <uuid>
成功返回:
202 Accepted
Location: /v2/<name>/blobs/uploads/<uuid>
Range: bytes=0-<offset>
Content-Length: 0
Docker-Upload-UUID: <uuid>
Completed Upload(上传完成)
分块上传在最后一块上传完毕后,需要提交一个上传完成的请求
API
>>> PUT /v2/<name>/blob/uploads/<uuid>?digest=<digest>
>>> Content-Length: <size of chunk>
>>> Content-Range: <start of range>-<end of range>
>>> Content-Type: application/octet-stream
<Last Layer Chunk Binary Data>
RETURN
201 Created
Location: /v2/<name>/blobs/<digest>
Content-Length: 0
Docker-Content-Digest: <digest>
Canceling an Upload(取消上传)
这个请求执行后uuid将失效,当上传超时或者没有完成,客户端都应该发送这个请求。
API
>>> DELETE /v2/<name>/blobs/uploads/<uuid>
Cross Repository Blob Mount(交叉挂载层)
此api可把客户端有访问权限的已有存储库中的层挂载到当前储存库中。
API
>>> POST /v2/<name>/blobs/uploads/?mount=<digest>&from=<repository name>
Content-Length: 0
RETURN
成功返回:
201 Created
Location: /v2/<name>/blobs/<digest>
Content-Length: 0
Docker-Content-Digest: <digest>
失败返回:
202 Accepted
Location: /v2/<name>/blobs/uploads/<uuid>
Range: bytes=0-<offset>
Content-Length: 0
Docker-Upload-UUID: <uuid>
Deleting a Layer(删除层)
API
>>> DELETE /v2/<name>/blobs/<digest>
RETURN
成功返回:
202 Accepted
Content-Length: None
失败返回404错误。
Pushing an Image Manifest(上传镜像清单)
当镜像层上传完毕后,可以上传清单。
API
>>> PUT /v2/<name>/manifests/<reference>
Content-Type: <manifest media type>
{
"name": <name>,
"tag": <tag>,
"fsLayers": [
{
"blobSum": <digest>
},
...
]
],
"history": <v1 images>,
"signature": <JWS>,
...
}
RETURN
如果清单中有层是未知的,则返回:
{
"errors:" [{
"code": "BLOB_UNKNOWN",
"message": "blob unknown to registry",
"detail": {
"digest": <digest>
}
},
...
]
}
检索功能
Listing Repositories(列出存储库)
API
>>> GET /v2/_catalog
RETURN
200 OK
Content-Type: application/json
{
"repositories": [
<name>,
...
]
}
Pagination(部分列出存储库)
API
>>> GET /v2/_catalog?n=<integer>
Note
integer表示要列出库的个数
RETURN
200 OK
Content-Type: application/json
Link: <<url>?n=<n from the request>&last=<last repository in response>>; rel="next"
{
"repositories": [
<name>,
...
]
}
Listing Image Tags(列出镜像tag)
API
>>> GET /v2/<name>/tags/list
RETURN
200 OK
Content-Type: application/json
{
"name": <name>,
"tags": [
<tag>,
...
]
}
Pagination(部分列出镜像tag)
API
>>> GET /v2/<name>/tags/list?n=<integer>
RETURN
200 OK
Content-Type: application/json
Link: <<url>?n=<n from the request>&last=<last tag value from previous response>>; rel="next"
{
"name": <name>,
"tags": [
<tag>,
...
]
}
Deleting an Image(删除镜像)
API
>>> DELETE /v2/<name>/manifests/<reference>
RETURN
成功
202 Accepted
Content-Length: None
失败返回404错误