Fork me on GitHub
Fork me on GitHub

Registry私有仓库搭建及认证

Registry相关概念

前面的文章讲过Docker的组成部分,我们一般在使用Docker的过程中更为常用的是pull image、run image、build image和push image。主要是围绕image展开的。
image和Registry的关系可以想象成自己机器上的源码和远端SVN或者Git服务的关系。Registry是一个几种存放image并对外提供上传下载以及一系列API的服务。可以很容易和本地源代码以及远端Git服务的关系相对应。
Docker hub是Docker公司提供的一些存储镜像的空间,这部分空间是有限的。我们一般会自主建设Docker私有仓库Registry。

Registry V1和V2

Docker Registry 2.0版本在安全性和性能上做了诸多优化,并重新设计了镜像的存储的格式。Docker目前1.6之后支持V2。

安装Docker

见前面发布的文章《CentOS安装Docker CE》。

搭建本地registry v2

环境:172.16.7.151 CentOS 7.0

[root@node1 ~]# docker run -d -p 5000:5000 --name wisedu_registry registry:2

本地push镜像到仓库:

[root@node1 ~]# docker pull ubuntu:16.04
[root@node1 ~]# docker tag ubuntu:16.04 localhost:5000/my-ubuntu
[root@node1 ~]# docker push localhost:5000/my-ubuntu

删除本地的ubuntu:16.04和localhost:5000/my-ubuntu镜像

[root@node1 ~]# docker image remove ubuntu:16.04
[root@node1 ~]# docker image remove localhost:5000/my-ubuntu

从本地registry中拉取 localhost:5000/my-ubuntu 镜像:

[root@node1 ~]# docker pull localhost:5000/my-ubuntu

但是这种registry只是本地能使用,我们找另外一台主机172.16.7.152往该registry中push镜像:

[root@node2 ~]# docker pull ubuntu:16.04
[root@node2 docker]# docker tag ubuntu:16.04 172.16.7.151:5000/ubuntu:v1
[root@node2 docker]# docker push 172.16.7.151:5000/ubuntu:v1
The push refers to a repository [172.16.7.151:5000/ubuntu]
Get https://172.16.7.151:5000/v2/: http: server gave HTTP response to HTTPS client
[root@node2 ~]# echo $?
1

这是因为从docker1.13.2版本开始,使用registry时,必须使用TLS保证其安全。

停止并删除本地registry:

[root@node1 ~]# docker stop wisedu_registry         
wisedu_registry
[root@node1 ~]# docker rm -v wisedu_registry
wisedu_registry

搭建外部可访问的Registry

官方文档:https://docs.docker.com/registry/deploying/#run-an-externally-accessible-registry
Running a registry only accessible on localhost has limited usefulness. In order to make your registry accessible to external hosts, you must first secure it using TLS.

使用TLS认证registry容器时,必须有证书。一般情况下,是要去认证机构购买签名证书。这里使用openssl生成自签名的证书。
环境信息:172.16.206.32 CentOS 7.0 主机名:spark32

1.生成自签名证书

[root@spark32 ~]# mkdir -p /opt/docker/registry/certs
[root@spark32 ~]# openssl req -newkey rsa:4096 -nodes -sha256 -keyout /opt/docker/registry/certs/domain.key -x509 -days 365 -out /opt/docker/registry/certs/domain.crt
Generating a 4096 bit RSA private key
...
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:JiangSu
Locality Name (eg, city) [Default City]:NanJing
Organization Name (eg, company) [Default Company Ltd]:wisedu
Organizational Unit Name (eg, section) []:edu
Common Name (eg, your name or your server's hostname) []:registry.docker.com
Email Address []:01115004@wisedu.com

2.创建带有TLS认证的registry容器

[root@spark32 ~]# docker run -d --name registry2 -p 5000:5000 -v /opt/docker/registry/certs:/certs -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key registry:2 

3.在每一个docker客户端宿主机上配置/etc/hosts,以使客户端宿主机可以解析域名”registry.docker.com”。并创建与这个registry服务器域名一致的目录(因为我这里的域名是假的)

# vim /etc/hosts
172.16.206.32 registry.docker.com
[root@node1 ~]# cd /etc/docker/certs.d/
[root@node1 certs.d]# mkdir registry.docker.com:5000

4.将证书 domain.crt 复制到每一个docker客户端宿主机/etc/docker/certs.d/registry.docker.com:5000/ca.crt,不需要重启docker

[root@spark32 ~]# scp -p /opt/docker/registry/certs/domain.crt root@172.16.7.151:/etc/docker/certs.d/registry.docker.com\:5000/ca.crt

5.push镜像到registry
另找一台客户机,node1。

[root@node1 certs.d]# docker tag ubuntu:16.04 registry.docker.com:5000/my-ubuntu:v1
[root@node1 certs.d]# docker push registry.docker.com:5000/my-ubuntu:v1
The push refers to a repository [registry.docker.com:5000/my-ubuntu]
a09947e71dc0: Pushed 
9c42c2077cde: Pushed 
625c7a2a783b: Pushed 
25e0901a71b8: Pushed 
8aa4fcad5eeb: Pushed 
v1: digest: sha256:634a341aa83f32b48949ef428db8fefcd897dbacfdac26f044b60c14d1b5e972 size: 1357

6.列出私有仓库中的所有镜像

[root@node1 certs.d]# curl -X GET https://registry.docker.com:5000/v2/_catalog -k
{"repositories":["my-ubuntu"]}

7.查看存储在registry:2宿主机上的镜像
在registry:2创建的私有仓库中,上传的镜像保存在容器的/var/lib/registry目录下。创建registry:2的容器时,会自动创建一个数据卷(Data Volumes),数据卷对应的宿主机下的目录一般为:/var/lib/docker/volumes/XXX/_data。

[root@spark32 ~]# ls /var/lib/docker/volumes/91a0091963fa6d107dc988a60b61790bba843a115573e331db967921d5e83372/_data/docker/registry/v2/repositories/my-ubuntu/
_layers  _manifests  _uploads

可以在创建registry:2的容器时,通过-v参数,修改这种数据卷关系:

–v /opt/docker/registry/data:/var/lib/registry

除了可以将数据保存在当前主机的文件系统上,registry也支持其他基于云的存储系统,比如S3,Microsoft Azure, Ceph Rados, OpenStack Swift and Aliyun OSS等。可以在配置文件中进行配置:https://github.com/docker/distribution/blob/master/docs/configuration.md#storage

【补充】:
一般情况下,证书只支持域名访问,要使其支持IP地址访问,需要修改配置文件openssl.cnf。
在Redhat7系统中,文件所在位置是/etc/pki/tls/openssl.cnf。在其中的[ v3_ca]部分,添加subjectAltName选项:

[ v3_ca ]  
subjectAltName = IP:192.168.1.104 

生成证书:

...  
Country Name (2 letter code) [XX]:  
State or Province Name (full name) []:  
Locality Name (eg, city) [Default City]:  
Organization Name (eg, company) [Default Company Ltd]:  
Organizational Unit Name (eg, section) []:  
Common Name (eg, your name or your server's hostname) []:172.16.206.32:5000  
Email Address []:  

添加认证

Native basic auth

The simplest way to achieve access restriction is through basic authentication (this is very similar to other web servers’ basic authentication mechanism). This example uses native basic authentication using htpasswd to store the secrets.

1.创建用户密码文件,testuser,testpassword

[root@spark32 ~]# mkdir /opt/docker/registry/auth
[root@spark32 ~]# docker run --entrypoint htpasswd registry:2 -Bbn testuser testpassword > /opt/docker/registry/auth/htpasswd

2.运行registry容器

[root@spark32 ~]# docker run -d --name registry_native_auth -p 5000:5000 -v /opt/docker/registry/auth:/auth -e "REGISTRY_AUTH=htpasswd" -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd -v /opt/docker/registry/certs:/certs -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key registry:2

3.现在尝试拉取镜像

[root@node1 ~]# docker pull registry.docker.com:5000/my-ubuntu:v1
Error response from daemon: Get https://registry.docker.com:5000/v2/my-ubuntu/manifests/v1: no basic auth credentials

4.登录registry,push镜像

[root@node1 ~]# docker login registry.docker.com:5000
Username: testuser
Password: 
Login Succeeded
[root@node1 ~]# docker tag ubuntu:16.04 registry.docker.com:5000/my-ubuntu:v1
[root@node1 ~]# docker push registry.docker.com:5000/my-ubuntu:v1 

更高级的认证

更好的方式是在registry前使用代理,利用代理提供https的ssl的认证和basic authentication。https://docs.docker.com/registry/recipes/

配置Nginx作为认证代理

https://docs.docker.com/registry/recipes/nginx/

1.创建需要的目录

[root@spark32 ~]# mkdir -p /opt/nginx_proxy_registry/{auth,data}

2.创建Nginx主配置文件

[root@spark32 ~]# vim /opt/nginx_proxy_registry/auth/nginx.conf
events {
    worker_connections  1024;
}

http {

  upstream docker-registry {
    server 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 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 443 ssl;
    server_name registry.docker.com;

    # SSL
    ssl_certificate /etc/nginx/conf.d/domain.crt;
    ssl_certificate_key /etc/nginx/conf.d/domain.key;

    # 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 /etc/nginx/conf.d/nginx.htpasswd;

      ## If $docker_distribution_api_version is empty, the header will not be 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;
    }
  }
}

3.创建密码认证文件

[root@spark32 ~]# docker run --rm --entrypoint htpasswd registry:2 -bn testuser testpassword > /opt/nginx_proxy_registry/auth/nginx.htpasswd

4.拷贝之前生成的证书和key到auth目录下

[root@spark32 ~]# cp /opt/docker/registry/certs/domain.crt /opt/nginx_proxy_registry/auth/
[root@spark32 ~]# cp /opt/docker/registry/certs/domain.key /opt/nginx_proxy_registry/auth/

5.创建compose文件

[root@spark32 ~]# cd /opt/nginx_proxy_registry/
[root@spark32 nginx_proxy_registry]# vim docker-compose.yml
nginx:
  image: "nginx:1.9"
  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

6.启动

[root@spark32 nginx_proxy_registry]# docker-compose up -d

7.验证启动的服务

[root@spark32 nginx_proxy_registry]# docker-compose ps
[root@spark32 ~]# docker ps 

找一台docker客户端机器登录:
创建需要的目录:

[root@node1 ~]# mkdir /etc/docker/certs.d/registry.docker.com:5043

把 domain.crt 传到上一步生成的目录里:

[root@spark32 ~]# scp -p /opt/nginx_proxy_registry/auth/domain.crt root@172.16.7.151:/etc/docker/certs.d/registry.docker.com:5043/ca.crt

登录:

[root@node1 ~]# docker login -u=testuser -p=testpassword registry.docker.com:5043
Login Succeeded
[root@node1 ~]# docker tag ubuntu:16.04 registry.docker.com:5043/ubuntu-test:v1
[root@node1 ~]# docker push registry.docker.com:5043/ubuntu-test:v1

8.停止服务

[root@spark32 ~]# cd /opt/nginx_proxy_registry/
[root@spark32 nginx_proxy_registry]# docker-compose stop

9.查看日志

[root@spark32 nginx_proxy_registry]# docker-compose logs

补充Docker compose

Docker compose是什么

Docker Compose是一个用来定义和运行复杂应用的Docker工具。使用Compose,你可以在一个文件中定义一个多容器应用,然后使用一条命令来启动你的应用,完成一切准备工作。
一个使用Docker容器的应用,通常由多个容器组成。使用Docker Compose,不再需要使用shell脚本来启动容器。
Docker Compose将所管理的容器分为三层,工程(project)、服务(service)以及容器(contaienr)。一个工程当中可包含多个服务,每个服务中定义了容器运行的镜像,参数,依赖。一个服务当中可包括多个容器实例,Docker Compose并没有解决负载均衡的问题,因此需要借助其他工具实现服务发现及负载均衡。

安装docker compose

将变量 $dockerComposeVersion 换成指定的版本

[root@spark32 ~]# curl -L https://github.com/docker/compose/releases/download/$dockerComposeVersion/docker-compose-`uname -m` -o /usr/local/bin/docker-compose

比如下载安装1.15.0版本:

[root@spark32 ~]# curl -L https://github.com/docker/compose/releases/download/1.15.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

可能会下载失败,多试几次。实在不行就需要翻墙去下载。

赋予执行权限:

[root@spark32 ~]# chmod +x /usr/local/bin/docker-compose

查看docker compose版本:

[root@spark32 ~]# docker-compose --version
docker-compose version 1.15.0, build e12f3b9

卸载docker compose

如果docker compose是通过curl安装的:

rm /usr/local/bin/docker-compose

如果docker compose是通过pip安装的:

pip uninstall docker-compose

registry web ui

搭建完了docker registry,我们可以使用 docker 命令行工具对我们搭建的 registry 做各种操作了,如 push / pull。但是不够方便,比如不能直观的查看 registry 中的资源情况,如果有一个 ui 工具,能够看到仓库中有哪些镜像、每个镜像的版本是多少。
registry web ui主要有3个,一个是 docker-registry-frontend,一个是 hyper/docker-registry-web,还有一个是Portus
关于registry ui的搭建会在后面的文章中介绍。