Docker registry是专门用于存放docker镜像的,docker官方提供了docker hub,是全球最大的docker镜像存储中心。但是在中国既没有服务器也没有CDN,所以导致pull镜像特别的慢,而且很不稳定。解决这个问题的方式一般有两种:

  1. 搭建自己私有的docker registry,存储镜像,并定期同步官方常用的镜像。
  2. 搭建docker mirror。

其实,选用哪一种或者both主要取决于自己的使用场景。如果你想要一个类似docker hub的私有registry,那肯定是第一种。如果你只是想解决从docker hub拉取镜像慢的问题,那就选择第二种,因为第一种的维护成本比较高。当然,不管是哪一种,我们都可以使用docker官方开源的registry镜像去实现。如果你想部署私有的registry,可以关注一下VMware开源的Harbor项目,其在开源registry的基础上增加了一些实际应用场景中需要的一些特性,比如项目权限、角色管理等。本文介绍如何使用部署registry mirror。

在这之前,需要强调一下目前如果registry部署为mirror方式,将只能pull镜像而不能push镜像

registry mirror原理

Docker Hub的镜像数据分为两部分:index数据和registry数据。前者保存了镜像的一些元数据信息,数据量很小;后者保存了镜像的实际数据,数据量比较大。平时我们使用docker pull命令拉取一个镜像时的过程是:先去index获取镜像的一些元数据,然后再去registry获取镜像数据。

所谓registry mirror就是搭建一个registry,然后将docker hub的registry数据缓存到自己本地的registry。整个过程是:当我们使用docker pull去拉镜像的时候,会先从我们本地的registry mirror去获取镜像数据,如果不存在,registry mirror会先从docker hub的registry拉取数据进行缓存,再传给我们。而且整个过程是流式的,registry mirror并不会等全部缓存完再给我们传,而且边缓存边给客户端传。

对于缓存,我们都知道一致性非常重要。registry mirror与docker官方保持一致的方法是:registry mirror只是缓存了docker hub的registry数据,并不缓存index数据。所以我们pull镜像的时候会先连docker hub的index获取镜像的元数据,如果我们registry mirror里面有该镜像的缓存,且数据与从index处获取到的元数据一致,则从registry mirror拉取;如果我们的registry mirror有该镜像的缓存,但数据与index处获取的元数据不一致,或者根本就没有该镜像的缓存,则先从docker hub的registry缓存或者更新数据。

registry mirror的工作原理我们就介绍完了,下面介绍如何利用docker开源的registry镜像部署自己的registry mirror。

registry mirror部署

NB:假设我们将缓存的数据存放到/data目录。

  1. 从官方拉取registry的镜像,目前最新的registry镜像是2.5版本(我使用的是2.5.0)。
  2. 获取registry的默认配置:

    docker run -it --rm --entrypoint cat registry:2.5.0  /etc/docker/registry/config.yml > config.yml

    文件的内容大概是下面这样:

    version: 0.1
    log:
      fields:
        service: registry
    storage:
      cache:
        blobdescriptor: inmemory
      filesystem:
        rootdirectory: /var/lib/registry
    http:
      addr: :5000
      headers:
        X-Content-Type-Options: [nosniff]
    health:
      storagedriver:
        enabled: true
        interval: 10s
        threshold: 3

    我们在最后面加上如下配置:

    proxy:
      remoteurl: https://registry-1.docker.io
      username: [username]
      password: [password]

    usernamepassword是可选的,如果配置了的话,那registry mirror除了可以缓存所有的公共镜像外,也可以访问这个用户所有的私有镜像。

  3. 启动registry容器:

    docker run  --restart=always -p 5000:5000 --name v2-mirror -v /data:/var/lib/registry -v  $PWD/config.yml:/etc/registry/config.yml registry:2.5.0 /etc/registry/config.yml

    当然我们也可以使用docker-compose启动:

    version: '2'
    services:
      registry:
        image: library/registry:2.5.0
        container_name: registry_mirror
        restart: always
        volumes:
          - /data:/var/lib/registry
          - ./config.yml:/etc/registry/config.yml
        ports:
          - 5000:5000
        command:
          ["serve", "/etc/registry/config.yml"]

    当我们看到如下日志输出的时候就说明已经启动成功了:

    time="2016-12-19T14:22:35Z" level=warning msg="No HTTP secret provided - generated random secret. This may cause problems with uploads if multiple registries are behind a load-balancer. To provide a shared secret, fill in http.secret in the configuration file or set the REGISTRY_HTTP_SECRET environment variable." go.version=go1.6.3 instance.id=da5468c4-1ee1-4df2-95cf-1336127c87bb version=v2.5.0
    time="2016-12-19T14:22:35Z" level=info msg="redis not configured" go.version=go1.6.3 instance.id=da5468c4-1ee1-4df2-95cf-1336127c87bb version=v2.5.0
    time="2016-12-19T14:22:35Z" level=info msg="Starting upload purge in 39m0s" go.version=go1.6.3 instance.id=da5468c4-1ee1-4df2-95cf-1336127c87bb version=v2.5.0
    time="2016-12-19T14:22:35Z" level=info msg="using inmemory blob descriptor cache" go.version=go1.6.3 instance.id=da5468c4-1ee1-4df2-95cf-1336127c87bb version=v2.5.0
    time="2016-12-19T14:22:35Z" level=info msg="Starting cached object TTL expiration scheduler..." go.version=go1.6.3 instance.id=da5468c4-1ee1-4df2-95cf-1336127c87bb version=v2.5.0
    time="2016-12-19T14:22:35Z" level=info msg="Registry configured as a proxy cache to https://registry-1.docker.io" go.version=go1.6.3 instance.id=da5468c4-1ee1-4df2-95cf-1336127c87bb version=v2.5.0
    time="2016-12-19T14:22:35Z" level=info msg="listening on [::]:5000" go.version=go1.6.3 instance.id=da5468c4-1ee1-4df2-95cf-1336127c87bb version=v2.5.0

至此,registrymirror就算部署完了。我们也可以用curl验证一下服务是否启动OK:

curl -I http://registrycache.example.com:5000/v2/
HTTP/1.1 200 OK
Content-Length: 2
Content-Type: application/json; charset=utf-8
Docker-Distribution-Api-Version: registry/2.0
Date: Thu, 17 Sep 2015 21:42:02 GMT

registry mirror使用

要使用registry mirror,我们需要配置一下自己的docker daemon。

对于Mac:在docker的客户端的Preferences——>Advanced——>Registry mirrors里面添加你的地址,然后重启。

对于Ubuntu 14.04:在/etc/default/docker文件中添加DOCKER_OPTS="$DOCKER_OPTS --registry-mirror=http://registrycache.example.com:5000”,然后重启docker(services docker restart)。

其他系统和发行版的配置方法请Google之。

然后我们pull一个本地不存在的镜像,这时去查看registry mirror服务器的data目录下面已经有了数据。执行如下命令也可以看到效果:

curl https://mycache.example.com:5000/v2/library/busybox/tags/list

需要说明的是缓存的镜像的有效期默认是一周(168hour),而且如果registry被配置成mirror模式,这个时间是不能通过maintenance部分来改变的:

  maintenance:
    uploadpurging:
      enabled: true
      age: 168h
      interval: 24h
      dryrun: false
    readonly:
      enabled: false

我研究了好久发现怎么改都不能生效,最后发现mirror模式下这个时间竟然在registry的代码里面写死了:

// todo(richardscothern): from cache control header or config
const repositoryTTL = time.Duration(24 * 7 * time.Hour)

囧o(╯□╰)o~不过看这TODO,应该是后面会改的~

当然如果你想你的registry mirror是https的话,在config.yml的http部分增加tls配置即可:

   http:
      addr: :5000
      headers:
        X-Content-Type-Options: [nosniff]
      tls:
        certificate: /etc/registry/domain.crt
        key: /etc/registry/domain.key

OK,至此registry mirror就介绍完了。