搭建Docker私有仓库
这篇文章内容包括搭建docker私有仓库的一些配置项和遇到的问题及解决方案。
Table of Contents
Docker官方提供了 registry镜像, 可以方便的搭建私有仓库,详细文档参考这里。
1 配置项
1.1 数据持久化
可以通过采用数据卷挂载或者直接挂载宿主机目录的方式来进行。挂载到容器内默认位置: /var/lib/registry
。
比如可以像如下方式启动, 这里将容器数据存储在了 /mnt/registry
.
$ docker run -d \ -p 5000:5000 \ --restart=always \ --name registry \ -v /mnt/registry:/var/lib/registry \ registry:2
当然,镜像还提供了其它支持的存储方式,比如OSS等。
官方文档参见这里。
1.2 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
官方文档参见这里。
1.3 登录授权验证
可以通过 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 私有仓库。
官方文档参见这里。
1.4 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
2 测试
私有仓库搭建好了如何测试?
# 先拉取官方镜像 $ 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
3 Nginx做代理
3.1 我的方式和遇到的问题
实际配置中,我采用了 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
3.2 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
, 可以方便地监听新添加的容器,留待以后探索。
4 其它方案
或许你想用letsencrypts免费证书,不妨看看这个工具:acme.sh
或许你不满足的简易方案,你可能还需要web界面来方便查看和管理你的镜像仓库,那么你可以查看下企业级的容器仓库方案: vmware/harbor
5 相关链接
- docker ce ubuntu 安装 文档: https://docs.docker.com/install/linux/docker-ce/ubuntu/
- docker compose 安装文档: https://docs.docker.com/compose/install/
- docker registr 镜像: https://hub.docker.com/_/registry/
- deploy 部署步骤文档: https://docs.docker.com/registry/deploying/
- 详细的registry的可配置项参见: https://docs.docker.com/registry/configuration/
- nginx容器bcrypt问题: https://github.com/docker/distribution/issues/655
- nginx容器bcrypt问题2: https://github.com/nginxinc/docker-nginx/issues/29