摘要
这篇文章内容包括搭建docker私有仓库的一些配置项和遇到的问题及解决方案。
1.配置项
1.1. 数据持久化
1.2. TLS 支持
1.3. 登录授权验证
1.4. docker compose
2. 测试
3. NGINX做代理
3.1. 我的方式和遇到的问题
3.2. NGINX 作为一个容器
4. 其它方案
5. 相关链接
Docker官方提供了 registry镜像, 可以方便的搭建私有仓库,详细文档参考这里。
配置项
数据持久化
可以通过采用数据卷挂载或者直接挂载宿主机目录的方式来进行。挂载到容器内默认位置: /var/lib/registry
。
比如可以像如下方式启动, 这里将容器数据存储在了 /mnt/registry
.
$ docker run -d \
-p 5000:5000 \
--restart=always \
--name registry \
-v /mnt/registry:/var/lib/registry \
registry:2
当然,镜像还提供了其它支持的存储方式,比如OSS等。
官方文档参见这里。
TLS 支持
为了使得私有仓库安全地对外开放,需要配置 TLS 支持。
测试的时候,如果不配置的话TLS,可以在docker客户端中的 “insecure registry” 里添加私有仓库地址,不然默认的都以安全的tsl方式来访问私有仓库,具体更改方式可以参考这里。
我的CA证书是从阿里云获取的(因为域名是在上面注册的,可以提供免费的证书,虽然如果做得很隐蔽)。
registry镜像可以通过 REGISTRY_HTTP_TLS_CERTIFICATE
和 REGISTRY_HTTP_TLS_KEY
环境参数配置TLS支持。
例如下面这样, domain.crt
和 domain.key
是获得的证书,另外配置容器监听ssl默认的 443
端口。
$ docker run -d \
--restart=always \
--name registry \
-v `pwd`/certs:/certs \
-e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
-p 443:443 \
registry:2
官方文档参见这里。
登录授权验证
可以通过 htpasswd
来配置简单的authentication (注意:验证需要 TLS 支持)。
首先在 auth
目录下通过reistry里的 htpasswd
工具创建 验证文件 auth/htpasswd
。
$ mkdir auth
$ docker run \
--entrypoint htpasswd \
registry:2 -Bbn testuser testpassword > auth/htpasswd
启动的时候通过 REGISTRY_AUTH
, REGISTRY_AUTH_HTPASSWD_REALM
, REGISTRY_AUTH_HTPASSWD_PATH
来配置:
$ docker run -d \
-p 5000:5000 \
--restart=always \
--name registry \
-v `pwd`/auth:/auth \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
-v `pwd`/certs:/certs \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
registry:2
这样就启动了一个监听5000端口的、支持TLS和简单登录验证的docker 私有仓库。
官方文档参见这里。
docker compose
“docker compose” 是一个方便定义和运行多个容器的工具, 安装参见这里, 或者通过pip安装: pip install docker-compose
以上配置项通过 docker compose
的方式组织起来如下:
文件命名成 docker-compose.yaml
registry:
restart: always
image: registry:2
ports:
- 5000:5000
environment:
REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt
REGISTRY_HTTP_TLS_KEY: /certs/domain.key
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
volumes:
- /path/data:/var/lib/registry
- /path/certs:/certs
- /path/auth:/auth
在 docker-compose.yaml
所在目录运行:
docker-compose up
测试
私有仓库搭建好了如何测试?
# 先拉取官方镜像
$ docker pull ubuntu:16.04
# 打上标签
$ docker tag ubuntu:16.04 myregistrydomain.com/my-ubuntu
# 推到私有仓库
$ docker push myregistrydomain.com/my-ubuntu
# 从私有仓库获取
$ docker pull myregistrydomain.com/my-ubuntu
Nginx做代理
我的方式和遇到的问题
实际配置中,我采用了 nginx
作为代理,来访问 registry服务。我将TLS支持和登录验证都加到了nginx一层。
nginx 配置文件:
upstream docker-registry {
server localhost:5000; # !转发到registry 监听的5000 端口!
}
## Set a variable to help us decide if we need to add the
## 'Docker-Distribution-Api-Version' header.
## The registry always sets this header.
## In the case of nginx performing auth, the header is unset
## since nginx is auth-ing before proxying.
map $upstream_http_docker_distribution_api_version $docker_distribution_api_version {
'' 'registry/2.0';
}
server {
listen 443 ssl;
server_name domain.com; # !这里配置域名!
# SSL
ssl_certificate /path/to/domain.pem; # !这里配置CA 证书信息!
ssl_certificate_key /path/to/domain.key; # !这里配置CA 证书信息!
# Recommendations from https://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 413 for large image uploads
client_max_body_size 0;
# required to avoid HTTP 411: see Issue #1486 (https://github.com/moby/moby/issues/1486)
chunked_transfer_encoding on;
location /v2/ {
# Do not allow connections from docker 1.5 and earlier
# docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents
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.
auth_basic "Registry realm";
auth_basic_user_file /path/to/auth/htpasswd; # !这里配置auth文件位置!
## If $docker_distribution_api_version is empty, the header is not added.
## See the map directive above where this variable is defined.
add_header 'Docker-Distribution-Api-Version' $docker_distribution_api_version 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;
}
}
其中 /path/to/auth/htpasswd
文件是通过 registry 中的或者本地的 htpasswd
工具生成的
$ docker run \
--entrypoint htpasswd \
registry:2 -Bbn testuser testpassword > auth/htpasswd
registry的 docker-compose
文件:
version: '2'
services:
my_registry:
restart: always
image: registry:2
ports:
- 127.0.0.1:5000:5000
volumes:
- ./data:/var/lib/registry
启动后,当我从本地视图login到私有仓库时,发生错误:
➜ ~ docker login domain.com
Username: testuser
Password:
Error response from daemon: login attempt to https://hub.docker.equiz.cn/v2/ failed with status: 500 Internal Server Error
查看日志发现nginx 错误日志里有个编码相关的错误:
# nginx error log
*4 crypt_r() failed (22: Invalid argument)
经过一番研究,发现之前加密时,是采用 Bcrypt
加密方式,看下 htpasswd
的使用说明:
root@data1:~# htpasswd
Usage:
htpasswd [-cimBdpsDv] [-C cost] passwordfile username
htpasswd -b[cmBdpsDv] [-C cost] passwordfile username password
htpasswd -n[imBdps] [-C cost] username
htpasswd -nb[mBdps] [-C cost] username password
-c Create a new file.
-n Don't update file; display results on stdout.
-b Use the password from the command line rather than prompting for it.
-i Read password from stdin without verification (for script usage).
-m Force MD5 encryption of the password (default).
-B Force bcrypt encryption of the password (very secure).
-C Set the computing time used for the bcrypt algorithm
(higher is more secure but slower, default: 5, valid: 4 to 31).
-d Force CRYPT encryption of the password (8 chars max, insecure).
-s Force SHA encryption of the password (insecure).
-p Do not encrypt the password (plaintext, insecure).
-D Delete the specified user.
-v Verify password for the specified user.
On other systems than Windows and NetWare the '-p' flag will probably not work.
The SHA algorithm does not use a salt and is less secure than the MD5 algorithm.
可以看到 -B
会使用 bcrypt
的方式来加密,nginx默认不支持。至于如何让nginx支持bcrypt我暂时还未找到方案,留待以后研究了(TODO)
简单的解决方式是换成默认的MD5加密(因为安全等级问题又不推荐不用bcrypt方式的),
docker run --rm --entrypoint htpasswd registry:2 -bn testuser testpassword > auth/htpasswd # 这里少了 -B 选项
关于 bcrypt
加密方式,这里 有一篇不错的文章介绍。不过好像对于这个加密方式,网上有一些争论,我就不详究了。
不依赖 “apche tools” 的 nginx 加密方式参考 这里, 比如MD5加密:
printf "testuser:$(openssl passwd -1 testpassword)\n" >> .htpasswd # this example uses MD5 encryption
Nginx 作为一个容器
docker 文档也有如何采用nginx容器和registry配合使用的说明,参考这里。
“docker-compose.yaml” 如下:
nginx:
# Note : Only nginx:alpine supports bcrypt.
# If you don't need to use bcrypt, you can use a different tag.
# Ref. https://github.com/nginxinc/docker-nginx/issues/29
image: "nginx:alpine" # !这里一定要采用alpine镜像,因为它里的nginx支持 bcrypt 加密!
ports:
- 5043:443
links:
- registry:registry
volumes:
- ./auth:/etc/nginx/conf.d
- ./auth/nginx.conf:/etc/nginx/nginx.conf:ro
registry:
image: registry:2
ports:
- 127.0.0.1:5000:5000
volumes:
- ./data:/var/lib/registry
这里nginx容器监听的是5043, 所以使用的私有仓库的地址变成了 registrydomain.com:5043
,
我不喜欢后面加端口,但本机又存在其他需要nginx监听的443端口的服务(不同doamin下),所以不能让nginx容器直接监听443端口,故没有采用这种方式。
另外在寻找解决方案的时候发现一个镜像 nginx-proxy
, 可以方便地监听新添加的容器,留待以后探索。
其它方案
或许你想用letsencrypts免费证书,不妨看看这个工具:acme.sh
或许你不满足的简易方案,你可能还需要web界面来方便查看和管理你的镜像仓库,那么你可以查看下企业级的容器仓库方案: vmware/harbor
相关链接
- docker ce ubuntu 安装 文档: https://docs.docker.com/insta…
- docker compose 安装文档: https://docs.docker.com/compo…
- docker registr 镜像: https://hub.docker.com/_/regi…
- deploy 部署步骤文档: https://docs.docker.com/regis…
- 详细的registry的可配置项参见: https://docs.docker.com/regis…
- nginx容器bcrypt问题: https://github.com/docker/dis…
- nginx容器bcrypt问题2: https://github.com/nginxinc/d…