基于OSS搭建跨区域部署的分布式Docker镜像仓库

基于OSS搭建跨区域部署的分布式Docker镜像仓库

Docker镜像是Docker的核心价值之一,Docker镜像仓库(Registry)是用于Docker镜像的管理和分发的基础设施。现在已经有了Docker Hub等多家公有镜像管理服务供应商,阿里云容器Hub服务也是您在云端的一个非常好的选择。但是有些情况,为了更加灵活的部署控制和一些管控要求,您也许会考虑在云端的部署一个私有镜像仓库。

为了满足异地容灾和就近访问等需求,需要在不同的区域(region)部署分布式Docker应用;在不同的region中,为了提升部署速度并节省网络成本,需要在每个region部署私有Docker Registry。然而,这就会给分布式部署中的镜像管理带来很多挑战。本文将结合阿里云OSS的新特性来介绍跨域部署的Docker镜像仓库的解决方案。

基于OSS搭建跨区域部署的分布式Docker镜像仓库

阿里云搭建Docker Registry入门

Docker Registry是存储和网络密集型应用。Docker镜像存储需要使用大量的存储资源,采用本地存储无法满足用户对容量和可用性的需求;同时镜像的上传,下载需要高速的网络带宽。Docker Registry自从2.1版本开始,就已经加入了对阿里云开放存储服务(OSS)的正式支持。这为Docker Registry提供一个支持海量存储,高性能访问,高可用,安全,低成本,无需运维的存储后端。而且利用ECS构建私有仓库可以使用OSS内部数据流量,降低公网网络流量成本。在集团内部和阿里云的镜像仓库服务,我们都采用了OSS的对象存储服务,来支持对内和对外的Docker镜像管理和分发。

在阿里云上ECS上搭建一个Docker Registry是非常方便的,最方便的方法是利用Docker镜像的方式来进行安装。

首先我们要在ECS上有一个Docker运行环境,你可以手工安装Docker Engine到已有的ECS实例上;或者,利用Docker Machine的ECS驱动方便的创建一个新的Docker就绪的ECS实例。请参见入门指南

启动一个使用OSS的存储的Docker Registry也是非常简单的,我推荐的方法是利用Docker Compose的方式配置和启动:

一个包含最简单的配置的docker-compose.yml如下

registry:
  restart: always
  image: "registry:2.3"
  ports:
    - 5000:5000
  environment:
    - REGISTRY_STORAGE=oss
    - REGISTRY_STORAGE_OSS_ACCESSKEYID=******
    - REGISTRY_STORAGE_OSS_ACCESSKEYSECRET=******
    - REGISTRY_STORAGE_OSS_REGION=oss-cn-beijing
    - REGISTRY_STORAGE_OSS_BUCKET=aliyungo-beijing
    - REGISTRY_STORAGE_OSS_INTERNAL=true
    - REGISTRY_STORAGE_OSS_SECURE=false

这里,我们将利用Docker Registry 2.3官方镜像创建一个私有镜像仓库,通过环境变量的方式配置了OSS的Access Key ID, Access Key Secret, Region, Bucket等信息。关于OSS配置的详细信息请参见官方文档

然后执行 docker-compose up -d 命令, 私有镜像就启动成功了。

我们可以在ECS实例上执行下面命令来验证相关工作

docker pull node:5.7
docker tag node:5.7 127.0.0.1:5000/node:5.7
docker push 127.0.0.1:5000/node:5.7

执行结果如下,已经成功将镜像推送成功

[root@iZ25cz6gdslZ ~]# docker pull node:5.7
5.7: Pulling from library/node
fdd5d7827f33: Already exists 
a3ed95caeb02: Pull complete 
0f35d0fe50cc: Already exists 
627b6479c8f7: Already exists 
67c44324f4e3: Already exists 
b52090f6ca00: Pull complete 
1df855d9e713: Pull complete 
Digest: sha256:cde7a4fca0a3dd2330977078c3773164b172a0ec582bafced6db955dca02a5c4
Status: Downloaded newer image for node:5.7
[root@iZ25cz6gdslZ ~]# docker tag node:5.7 127.0.0.1:5000/node:5.7
[root@iZ25cz6gdslZ ~]# docker push 127.0.0.1:5000/node:5.7
The push refers to a repository [127.0.0.1:5000/node]
5f70bf18a086: Mounted from ubuntu 
1967867932fe: Pushed 
6b4fab929601: Pushed 
550f16cd8ed1: Mounted from python 
44267ec3aa94: Mounted from java 
bd750002938c: Mounted from java 
917c0fc99b35: Mounted from java 
5.7: digest: sha256:64413fb300f4dd491397dbf16edbc8499c521640ac52e68bdfee8241a8d0dafe size: 2389

注意:一定要配置Docker Hub加速器呀,要不会有欲哭无泪的赶脚。

在生产环节中使用,您还需要添加Nginx等组件来增加TLS访问,认证授权,负载均衡等能力。详见官方参考),本文不再详述。

分布式跨区域Docker Registry方案讨论

回到正题,为了支持多区域部署Docker应用的需求,我们需要在每个region部署Docker Registry。然而如何在分布式的Docker Registry之间实现内容同步?我们有以下几种可能的方案

  1. 当客户端推送镜像时,分别向不同region的Docker Registry推送。

    • 好处:没有任何外部依赖
    • 不足:手工同步导致客户端逻辑复杂;当扩展region之后需要手工同步已有镜像
  2. 利用Docker Registry的通知机制,实现Web Hook;当一个Docker Registry收到镜像推送完成事件之后,由Web Hook的实现向其他region的Docker Registry推送刚完成的镜像

    • 好处:没有任何外部依赖,自动化同步
    • 不足:基于事件的同步逻辑实现复杂,需要考虑网络中断,region变更等多种边界情况
  3. 利用存储复制的机制,实现不同region的镜像复制

    • 好处:自动化同步,实现简单
    • 不足:需要底层存储驱动支持

由于Docker Registry本身是无状态的应用,非常适合采用数据复制的方案实现分布式部署。去年一系列公司提出了分布式Docker Registry的解决方案,大多基于其商业化的数据复制技术,比如NetApp在DockerCon的演讲。

然而利用阿里云OSS的新特性,Bucket跨区域复制和回源设置,我们也可以轻松实现一个大规模分布式的镜像仓库。在下面示例中:我们会把北京registry作为主registry,允许用户推送、拉取镜像;创建上海registry作为从registry,允许用户拉取镜像。其高层架构图如下:

基于OSS搭建跨区域部署的分布式Docker镜像仓库

利用OSS跨区域复制轻松实现分布式镜像仓库

OSS的Bucket Cross-Region Replication是跨不同区域数据中心的自动化、异步复制技术。它会将对源Bucket中的对象的改动(新建、覆盖、删除等)同步到目标Bucket。详细信息请参见帮助文档

首先,我们创建一个上海region的bucket,作为目标bucket

基于OSS搭建跨区域部署的分布式Docker镜像仓库

然后选择北京region的bucket作为源bucket; 并配置跨区域复制规则到目标bucket
基于OSS搭建跨区域部署的分布式Docker镜像仓库

基于OSS搭建跨区域部署的分布式Docker镜像仓库

同步规则配置完成之后,等待几分钟同步完成的效果如下
基于OSS搭建跨区域部署的分布式Docker镜像仓库

在上海region的ECS实例上,我们利用上文的方法也创建一个Docker Registry,"docker-compose.yml"的配置如下,并配置上海region的OSS Bucket作为镜像存储

registry:
  restart: always
  image: registry:2.3
  ports:
    - 127.0.0.1:5000:5000
  environment:
    - REGISTRY_STORAGE=oss
    - REGISTRY_STORAGE_OSS_ACCESSKEYID=******
    - REGISTRY_STORAGE_OSS_ACCESSKEYSECRET=******
    - REGISTRY_STORAGE_OSS_REGION=oss-cn-shanghai
    - REGISTRY_STORAGE_OSS_BUCKET=aliyungo-shanghai
    - REGISTRY_STORAGE_OSS_INTERNAL=true
    - REGISTRY_STORAGE_OSS_SECURE=false

然后执行docker-compose up -d命令启动Docker Registry

我们可以通过下面的命令来验证镜像复制是否成功

# docker pull 127.0.0.1:5000/node:5.7
5.7: Pulling from node
fdd5d7827f33: Already exists 
a3ed95caeb02: Pull complete 
0f35d0fe50cc: Already exists 
627b6479c8f7: Already exists 
67c44324f4e3: Already exists 
b52090f6ca00: Pull complete 
1df855d9e713: Pull complete 
Digest: sha256:64413fb300f4dd491397dbf16edbc8499c521640ac52e68bdfee8241a8d0dafe
Status: Downloaded newer image for 127.0.0.1:5000/node:5.7

大家可以看到,无需任何编程,我们已经简单地实现了分布式数据的Docker Registry的自动化数据同步。用户可以在北京registry上push新的镜像,在上海registry上pull镜像,没有任何的手工维护工作。

需要注意的是:OSS的bucket复制是单向的,所以我们要在部署方案中确定registry的主从关系。只有在主registry的镜像是可以读写的,而在从registry中,镜像应是只读的。本文中主registy在北京region,而从registry在上海region。

目前为止,这个方案还不够完美。由于Bucket复制是异步的,采用的是最终一致性。对象从源复制到目的bucket需要一定的时间,这取决于复制对象的大小。我们知道Docker镜像是采用分层存储机制,每个layer包含相应的blob对象,元数据和digest信息。其中layer的blob对象可能非常大,数据复制完成时间会比较长。

所以,当我们在上海registry拉取镜像时,如果blob对象没有复制完成,则我们会遇到如下"unknown blob"错误

# docker pull 127.0.0.1:5000/redis:2
2: Pulling from redis
fdd5d7827f33: Already exists 
a3ed95caeb02: Download complete 
3868e1e933d6: Pulling fs layer 
1d007c18c656: Pulling fs layer 
ad75a8697e9c: Waiting 
8de500daf5d7: Waiting 
788fee3bdabf: Waiting 
8f359895dbf8: Waiting 
unknown blob

目前实现对于时效性不敏感的场景,这个方案已经足够。当然,也需要应用层做一些调整,在镜像未复制完成之前,需要反复尝试等待复制完成。

如果用户希望能够立刻从上海region的registry访问刚在北京region推送的镜像,有没有更好的解决方案?

利用OSS回源设置实现镜像按需热迁移

对于上面的问题一个自然的想法就是:如果在上海registry请求尚未同步完成的Docker layer数据时,就从北京region的源bucket中读取对象,并返回。

为了减少开发的复杂度,我们尝试使用OSS的回源设置(详见帮助文档)来自动化这个过程。当上海region的bucket中所需blob对象不存在时,让OSS可以从北京region的bucket中自动进行回源读取;在将layer内容返回给Docker请求的同时,自动将内容复制到上海region的目标bucket。

我们在上海region的bucket上配置回源规则如下
基于OSS搭建跨区域部署的分布式Docker镜像仓库

这里我们使用镜像方式:将回源地址指向北京bucket的公网访问地址
基于OSS搭建跨区域部署的分布式Docker镜像仓库

当然我们还需要对registry中的OSS driver的Stat和URLFor实现做一些调整,当Registry检查到文件的元数据不存在时,强制通过回源方式返回北京registry中的内容。对Docker Registry的修改可以从Github访问

您也可以直接利用编译过的"registry.aliyuncs.com/denverdino/registry:2.3"镜像来替换官方镜像。只需修改上海region部署所用的"docker-compose.yml"的image值修改即可。

registry:
  restart: always
  image: registry.aliyuncs.com/denverdino/registry:2.3
  ports:
    - 127.0.0.1:5000:5000
  environment:
    - REGISTRY_STORAGE=oss
    - REGISTRY_STORAGE_OSS_ACCESSKEYID=******
    - REGISTRY_STORAGE_OSS_ACCESSKEYSECRET=******
    - REGISTRY_STORAGE_OSS_REGION=oss-cn-shanghai
    - REGISTRY_STORAGE_OSS_BUCKET=aliyungo-shanghai
    - REGISTRY_STORAGE_OSS_INTERNAL=true
    - REGISTRY_STORAGE_OSS_SECURE=false

再次尝试,我们已经近乎同时地从上海region的registry访问刚刚在北京region推送的Docker镜像了。在这里,我们将异步的对象复制,和同步的按需复制结合在一起,可以满足绝大多数场景对分布式镜像仓库的要求。

大功告成!

总结

跨区域部署的Docker Registry可以在不同数据中心的用户高效、简单的利用Docker镜像进行软件交付、部署和运维;可以支持异地容灾、分布式DevOps等场景。

在本文介绍的这个分布式、跨域部署的Docker Registry的方案中,无需额外的手工运维就可保证不同地域Docker镜像仓库的数据一致性。其技术要点如下:

  • 使用OSS作为Docker镜像存储实现,不同region的registry配置到本地region的bucket上
  • 利用OSS提供的跨区域bucket复制能力,在主从registry中实现自动数据复制
  • 在从registry中,如果镜像layer对象存在于本地的OSS bucket中,则直接利用bucket中对象返回;
    如果镜像layer对象还未同步成功,则回源到主registry的bucket中

感谢阿里云OSS团队一直以来的大力协助和支持。

欢迎大家使用阿里云容器服务阿里云Docker镜像仓库服务。它能帮助你大大加速云端Docker应用的交付流程。

上一篇:Windows 10 Creators Update:自动激活磁盘清理工具


下一篇:Comparable与Comparator,java中的排序与比较