docker 仓库,即所谓registry,实现了镜像的管理、分发,同时还包括用户的认证。dockerregistry仓库是一个无状态的、高可靠的服务器应用程序,用来存储docker镜像。docker.io为docker官方的仓库,默认所有的pull均是从官方仓库拉取镜像。
公司内部平台应用如果使用docker镜像,则必须搭建私有仓库,使用docker的官方镜像Registry的最新版本2(目前是2.5),可以很方便的搭建私有仓库。
命令如下:
docker pull registry:2
这条命令会从docker的官方仓库中下载registry镜像,版本号是2
下载完成后可以使用docker images查看。
myregistrydomain:port/foo/bar:version
以上是一个完整的镜像名称,myregistrydomain为镜像仓库所在的域名地址,port为镜像仓库开发的端口。foo为用户名,可以省略。bar为镜像名称,必填项。version为版本号,可以省略,默认为latest。
命令如下:
docker run --rm -p 5000:5000 registry:2
运行成功后可以看到本机已经在5000端口监听了,但此时还是无法使用pull和push。
由于目前没有公有机构颁发的ssl证书,而且使用明文http有不安全性,故计划使用自签名的ssl证书。命令如下:
mkdir-p certs && openssl req \
-newkey rsa:4096 -nodes -sha256 -keyoutcerts/domain.key \
-x509 -days 365 -out certs/domain.crt
注意必须填写CN字段为你的域名地址,目前使用yourdomain.com这个域名作为dockerregistry的域名。
所有docker客户端也需要作相应配置,使用证书文件之一domain.crt,复制到以下目录:
/etc/docker/certs.d/ yourdomain.com:5000/ca.crt
即需要将文件名从domain.crt修改为ca.crt,然后客户端的docker引擎也需要重启,如此可以直接使用docker pull yourdomain.com:5000/image-name来下载镜像了。
如果需要push镜像,则需要首先使用docker login,命令如下:
docker login yourdomain.com:5000 -u=user-p=pass
然后再使用docker push上传自己的镜像文件。
搭建一个docker私有仓库,存储所有“测试、生产”的平台应用。
使用ssl安全连接,ssl证书为自签名证书,使用者只有获取此证书才能使用。
所有有证书的人员,可以直接从仓库中下载镜像。
只有经过用户名、密码认证后的人员,才能往仓库中上传镜像。
本次尝试使用registry/nginx两个容器实现,但后续发现无法实现原有目标。由于v2版本的registry在登录、push、pull均使用GET /v2/这个相同的url,而且首次连接时一定使用,用来确认registry是否需要认证,即如果返回为401则表示registry需要认证,否则表示不认证。由于有这一层限制导致对GET /v2/这个url无法做处理,如果让其不认证,则客户端就会认为不需要认证,会以不认证的方式继续后续操作,比如push,如此会在push时无法成功;如果让其认证,则会导致匿名用户无法pull,因为匿名用户pull时第一个url也是GET /v2/,由于需要认证且又是匿名,会导致无法pull。
虽然整个章节都是失败的尝试,但也记录下来,一方面加深了对nginx作http代理的了解,另一方面对于registry的1.0版本,使用此方法是有效的。
使用registry:2和nginx两个镜像完成私有仓库的搭建,架构如下:
当用户使用pull下载镜像时,不过“认证”模块,故不需要用户登录;如果需要push上传镜像,则必须经过“认证”模块,故push镜像必须标准用户名、密码。详细研究registry后发现暂时找不到如何配置,故转而使用nginx配置规则实现此目标。
使用docker-compose统一配置,如下详细描述。
nginx:
restart: always
image: yourdomain.com:5000/nginx
ports:
- "5000:5000"
links:
- registry
volumes:
- ./auth:/etc/nginx/conf.d/
-./auth/nginx.conf:/etc/nginx/nginx.conf:ro
- ./log:/var/log/nginx
nginx容器会将数据转发到registry容器,故使用links指令。
对外开放5000端口。
有三个数据卷:
auth,其中存储ssl证书和push需要的用户名密码文件;在上一章节生成的证书也放置到此目录下,供nginx使用。
nginx.conf,nginx真正的配置文件
log,将nginx的日志输出到宿主机,方便查看日志。
l 用户名密码文件
使用htpasswd命令生成用户名密码文件,供nginx认证使用。
在镜像registry:2中包含了htpasswd程序,使用如下命令:
docker run --rm --entrypoint htpasswd registry:2 -bn user pass >auth/nginx.htpasswd
注意以上的user pass,正是在push镜像时需要认证的用户名、密码。
l nginx.conf
events {
worker_connections 1024;
}
http {
upstream docker-registry {
server registry:5000;
}
## Set a variable to help us decide if weneed to add the
## 'Docker-Distribution-Api-Version'header.
## The registry always sets this header.
## In the case of nginx performing auth,the header will be unset
## since nginx is auth-ing before proxying.
map$upstream_http_docker_distribution_api_version $docker_distribution_api_version{
'' 'registry/2.0';
}
server {
listen 5000;
server_name yourdomain.com;
ssl on;
ssl_certificate /etc/nginx/conf.d/domain.crt;
ssl_certificate_key /etc/nginx/conf.d/domain.key;
# Recommendations fromhttps://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
ssl_protocols TLSv1.1 TLSv1.2;
ssl_ciphers'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
# disable any limits to avoid HTTP 413for large image uploads
client_max_body_size 0;
# required to avoid HTTP 411: see Issue#1486 (https://github.com/docker/docker/issues/1486)
chunked_transfer_encoding on;
## If $docker_distribution_api_versionis empty, the header will not be added.
## See the map directive above wherethis variable is defined.
add_header 'Docker-Distribution-Api-Version'$docker_distribution_api_version always;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 900;
location ~ ^/v2/$ {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/conf.d/nginx.htpasswd;
proxy_pass https://docker-registry;
}
location / {
limit_except GET HEAD OPTIONS {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/conf.d/nginx.htpasswd;
}
proxy_pass https://docker-registry;
}
}
}
如下配置为nginx.conf的内容,其中大部分内容都取自于docker文档。
l 原理
(1) docker交互API
首先要详细分析docker客户端与v2版本的registry交互的流程,交互过程为HTTPS,可以通过nginx的access.log日志来分析。
a) 查询api
对应的URL是“GET /v2/_catalog HTTP/1.0”
b) pull镜像
对应的URL是"GET/v2/hello-world/manifests/latest HTTP/1.0"
c) push镜像
对应的URL是"POST/v2/hello-world/blobs/uploads/ HTTP/1.0"
d) dockerlogin
对应的URL是"GET /v2/ HTTP/1.0"
根据分析以上数据交互过程,确保pull时无需认证,push时必须认证,则需要对所有的非GET请求进行认证,同时对docker login也需要认证。
(2) location配置
使用两个location规则实现“不认证pull、认证才能push”的目的,location ~ ^/v2/$,此规则表示URL仅是’/v2/’,不包含后续的内容,恰好满足docker login的要求,即确保docker login时进入认证流程;location /此规则表示其他所有的URL都使用此配置,因为nginx中的location是最长匹配原则。其中limit_except GET HEADOPTIONS表示除了这三种HTTP方法:GET/HEAD/OPTIOINS,其他的方法都需要认证,因此对于push镜像就必须经过认证。认证所需要的文件就是htpasswd生成的用户名、密码文件。
只有push权限的客户端才有查询能力,即确保使用者的安全性,只告知可以下载哪些镜像,使用第三方的python脚本完成这个功能,文件名为registry.py,命令如下。
registry.py -r https://yourdomain.com:5000
如下显示内容是一个样例:
Image: django
tag: latest
Image: mysql
tag: latest
Image: nginx
tag: latest
Image: redis
tag: latest
镜像删除工作比较复杂,目前docker没有提供方便的接口完成registry中镜像的删除工作,不过可以使用上一章节中的registry.py脚本配合完成,步骤如下:
(1) 删除tags
registry.py -r https://yourdomain.com:5000 -d -i imagename -n numberoftags
imagename,要清理的镜像名称
numberoftags,保留版本的个数,默认是10个。
(2) 停止仓库registry
使用docker-compose down停止服务
(3) 垃圾回收
使用如下命令:
docker-compose run registry bin/registry garbage-collect /etc/docker/registry/config.yml
(4) 启动仓库registry
docker-compose up
引入docker_auth这个新的认证镜像,其中包含了一个静态认证的服务器,同时此认证服务器还有多种功能,支持google、github、ldap、mongo等多种动态认证方式,在搭建私有仓库时极其有用,比如要控制不同镜像的不同人员访问权限,php人员只能下载php镜像,java人员只可以下载、上传java相关镜像,有非常良好的用户认证机制。但由于此镜像隐藏得非常深,找了很久才发现,而且中文世界中对其描述很少,以下所有配置均可参见网站:
https://github.com/cesanta/docker_auth
也可以下载对应的github源码深度学习,其中有详细的配置,还有认证服务器的go语言源代码,非常有价值。
docker认证完全遵循oauth2.0标准,标准流程如下:
1. docker client 尝试到registry中进行push/pull操作;
2. registry会返回401未认证信息给client(未认证的前提下),同时返回的信息中还包含了到哪里去认证的信息;
3. client发送认证请求到认证服务器(authorization service);
4. 认证服务器(authorization service)返回token;
5. client携带这附有token的请求,尝试请求registry;
6. registry接受了认证的token并且使得client继续操作;
使用两个官方镜像袜,registry:2镜像实现仓库功能,cesanta/docker_auth实现认证功能。docker_auth镜像使用go语言实现认证功能,包括静态用户、google、github、mongo、外部认证等方式,总共代码行数才2600行。
覆盖默认的config.yml文件,如下:
version: 0.1
log:
fields:
service: registry
storage:
delete:
enabled: true
cache:
blobdescriptor: inmemory
filesystem:
rootdirectory: '/var/lib/registry'
auth:
token:
realm: 'https://dockerreg.maxnetsys.com:5001/auth'
service: 'Docker registry'
issuer: 'maxnet auth server'
rootcertbundle: '/certs/domain.crt'
http:
addr::5000
tls:
certificate: '/certs/domain.crt'
key:'/certs/domain.key'
headers:
X-Content-Type-Options: [nosniff]
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
在部分配置是默认配置,关注auth块,其中使用了token认证,填充四个参数,realm表示为docker_auth镜像启动时的路径,docker_auth监听5001端口;issuer值必须与docker_auth中的配置完全一致;rootcertbundle就是公钥;service随便填。
覆盖默认配置。
server:
addr:"0.0.0.0:5001"
certificate: "/ssl/domain.crt"
key:"/ssl/domain.key"
token:
issuer: "maxnet auth server"
expiration: 900
users:
"admin":
password:"$2y$05$Hr7we48EODPWnzRRmBJEfucuBc.RM9FJZHFe6aZk3gPKU67U3.9P2"
"": {}
acl:
-match: {account: ""}
actions:["pull"]
comment: "Anonymous users can pull everything."
-match: {account: "admin"}
actions: ["*"]
comment: "Allow everything for dockerreg users."
server块中指定监听端口,还需要写明ssl的公钥和私钥;token中的issuer与registry保持一致;users块为用户配置,其中有两个用户,一个是admin,另一个是匿名用户;acl配置用户的权限,匿名用户只能pull,admin用户有所有权限。
用户的密码请使用htpasswd生成,命令格式如下:
htpasswd –nb -B username password
注意忽略输出的内容中的用户名和冒号,即只复制冒号后面的内容到password。
docker_auth的配置详细可以参考github中的reference.yml,其中有所有项的详细说明。在实际生产环境中如果期望更细粒度的配置权限,可以扩展users和acl,非常方便。
使用docker-compose部署。
version: "2"
services:
registry:
restart: always
image: registry:2
ports:
-"5000:5000"
volumes:
-./certs:/certs:ro
-./registry_config.yml:/etc/docker/registry/config.yml
-./data:/var/lib/registry
dockerauth:
restart: always
image: cesanta/docker_auth
ports:
-"5001:5001"
command: --v=2 --alsologtostderr /config/auth_config.yml
volumes:
-./auth_config.yml:/config/auth_config.yml:ro
-./certs:/ssl:ro
- ./log:/logs
registry和docker_auth分别监听5000、5001端口,均使用https。registry需要将镜像存储的volumes,docker_auth启动命令将日志输出到终端,方便运维人员查看日志。
web化的仓库管理客户端,目前个人尚未找到,如果能找到将极大的降低运维人员的使用复杂度。尝试了hyper/docker-registry-web这个镜像,发现其对oauth2.0的认证方式不支持,故放弃。