基于OSS搭建跨区域部署的分布式Docker镜像仓库
Docker镜像是Docker的核心价值之一,Docker镜像仓库(Registry)是用于Docker镜像的管理和分发的基础设施。现在已经有了Docker Hub等多家公有镜像管理服务供应商,阿里云容器Hub服务也是您在云端的一个非常好的选择。但是有些情况,为了更加灵活的部署控制和一些管控要求,您也许会考虑在云端的部署一个私有镜像仓库。
为了满足异地容灾和就近访问等需求,需要在不同的区域(region)部署分布式Docker应用;在不同的region中,为了提升部署速度并节省网络成本,需要在每个region部署私有Docker Registry。然而,这就会给分布式部署中的镜像管理带来很多挑战。本文将结合阿里云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之间实现内容同步?我们有以下几种可能的方案
-
当客户端推送镜像时,分别向不同region的Docker Registry推送。
- 好处:没有任何外部依赖
- 不足:手工同步导致客户端逻辑复杂;当扩展region之后需要手工同步已有镜像
-
利用Docker Registry的通知机制,实现Web Hook;当一个Docker Registry收到镜像推送完成事件之后,由Web Hook的实现向其他region的Docker Registry推送刚完成的镜像
- 好处:没有任何外部依赖,自动化同步
- 不足:基于事件的同步逻辑实现复杂,需要考虑网络中断,region变更等多种边界情况
-
利用存储复制的机制,实现不同region的镜像复制
- 好处:自动化同步,实现简单
- 不足:需要底层存储驱动支持
由于Docker Registry本身是无状态的应用,非常适合采用数据复制的方案实现分布式部署。去年一系列公司提出了分布式Docker Registry的解决方案,大多基于其商业化的数据复制技术,比如NetApp在DockerCon的演讲。
然而利用阿里云OSS的新特性,Bucket跨区域复制和回源设置,我们也可以轻松实现一个大规模分布式的镜像仓库。在下面示例中:我们会把北京registry作为主registry,允许用户推送、拉取镜像;创建上海registry作为从registry,允许用户拉取镜像。其高层架构图如下:
利用OSS跨区域复制轻松实现分布式镜像仓库
OSS的Bucket Cross-Region Replication是跨不同区域数据中心的自动化、异步复制技术。它会将对源Bucket中的对象的改动(新建、覆盖、删除等)同步到目标Bucket。详细信息请参见帮助文档
首先,我们创建一个上海region的bucket,作为目标bucket
然后选择北京region的bucket作为源bucket; 并配置跨区域复制规则到目标bucket
同步规则配置完成之后,等待几分钟同步完成的效果如下
在上海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上配置回源规则如下
这里我们使用镜像方式:将回源地址指向北京bucket的公网访问地址
当然我们还需要对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应用的交付流程。