首先,Docker Hub是一個很好的用於管理公共鏡像的地方,咱們能夠在上面找到想要的鏡像(Docker Hub的下載量已經達到數億次);並且咱們也能夠把本身的鏡像推送上去。可是,有的時候,使用場景須要咱們有一個私有的鏡像倉庫用於管理本身的鏡像,這個時候咱們就經過Registry來實現此目的。本文詳細介紹了本地鏡像倉庫Docker Registry & Portus的搭建過程。node
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的鏡像。linux
另外,Registry 2.4版本以後支持了回收站機制,也就是能夠刪除鏡像了。在2.4版本以前是沒法支持刪除鏡像的,因此若是你要使用最好是大於Registry 2.4版本的。nginx
Docker build鏡像時會爲每一個layer生成一串layer id,這個layer id是一個客戶端隨機生成的字符串,和鏡像內容無關。咱們能夠經過一個簡單的例子來查看。提供一個簡單的dockerfile。docker
1
2
3
4
5
|
$ cat dockerfile
FROM nginx
MAINTAINER dkey
EXPOSE 80
ENTRYPOINT nginx -g "daemon off;"
|
加上–no-cache強制從新build。shell
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。json
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與內容無關的設計會帶來不少的問題。swift
首先, 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的一個主要緣由也就是爲了解決該問題。bash
新版的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
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私有倉庫安裝成功。可是仍是有些缺點:只要有了證書,仍是誰均可以往庫裏推鏡像。簡單的解決方案就是使用用戶認證。
下面都是以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私有倉庫到這裏就結束了,我的感受仍是有不少不足。有興趣能夠看看: