搭建Docker私有仓库

Nisen

摘要

这篇文章内容包括搭建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_CERTIFICATEREGISTRY_HTTP_TLS_KEY 环境参数配置TLS支持。
例如下面这样, domain.crtdomain.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

相关链接

阅读 3.1k
163 声望
43 粉丝
0 条评论
你知道吗?

163 声望
43 粉丝
宣传栏