写点什么

新浪微博混合云架构实践挑战之镜像分发实战

2016 年 6 月 06 日

编者按:《微博混合云架构》专栏是 InfoQ 向新浪微博技术团队的系列约稿,本专栏包含 8 篇内容,详细阐述以 DCP 设计理念为指导思想的混合云架构实践。本文是该系列的第四篇,主要讲解在新浪微博混合云在镜像分发方面的一些实践经验。

《微博混合云架构》专栏主要包括以下 8 篇内容:

  1. 混合云架构挑战与概述
  2. DCP 的不可变基础设施
  3. DCP 的弹性调度揭秘
  4. DCP 的镜像分发实战
  5. DCP 的容器编排设计与实践
  6. DCP 的服务发现
  7. DCP 的容量决策评估
  8. DCP 的监控体系

相信大家通过前面几篇的介绍,对微博 DCP 系统的架构已经有了一些了解,今天我们来聊一聊微博混合云在镜像分发方面的一些实践经验。

由于微博的业务特点,经常会面对突发的几倍于往常峰值的流量,比如重大新闻、娱乐圈的热点事件等。要想能相对平稳的应对这种峰值,我们必须具有快速且大批量甚至翻倍的扩容能力。要具备这种能力,除了要对整个系统的架构设计进行改造之外,对于我们的分发系统(无论是 Docker 还是非 Docker,扩容本质都是分发)也是一个很大的挑战。

整体架构

前几篇讲到,私有云(内网)和公有云(阿里云)是为了满足不同的场景,所以他们对于镜像仓库服务的需求也会有所不同:

  • 内网镜像仓库的压力主要来自日常发布以及弹性调度,带宽压力相对平稳。
  • 内网的环境要比阿里云复杂的多,各个业务间的操作系统版本,软件版本,配置等都存在很大差异。
  • 阿里云镜像仓库的压力是由当次需要扩容的机器数量决定的,无法预先估计出来。
  • 阿里云的环境是可定制的、纯净的、统一的。

基于以上几点,我们在内网和阿里云也采用了不同的部署架构,如下图:

为了消除差异,在内网和阿里云,使用了相同的域名,分别指向内网的 LVS 和阿里云的 SLB;其余的细节我们稍后再讲。

目前来看这个架构解决了我们面临的两个主要问题:

一:阿里云的分发能力问题

关于阿里云中的镜像分发,主要有两个痛点:

  • 镜像体积大:微博平台的业务以 Java 为主,众所周知,Java 的运行时环境是比较重的,我们一般的业务镜像体积都在 700M 以上。
  • 全量 Pull:每次扩容都是新的机器,所以每台机器都要拉取完整的镜像,而不是增量 Pull。

其实这两个痛点可以归为一点,就是扩容带来的带宽压力非常大。以扩容 50 台为例,估算一下总带宽消耗:50 * 700M = 35GB = 280Gb;理论上,1 台千兆网卡的机器可以在 280/60 ≈ 5 分钟内分发完,但实际上会出现大量 Pull 失败的情况。

为此,我们利用镜像仓库内置的 proxy 机制,构建了一套可以弹性扩容的多级缓存架构(上图中右侧部分)

  1. 我们将镜像仓库服务也作为一个特殊的服务池来管理,其操作系统镜像是定制的;
  2. 部署一台镜像仓库作为常备服务,同时也作为一级缓存,提高镜像仓库服务本身的扩容速度;
  3. 当需要在阿里云上进行大批量业务扩容时,会先按照一定比例扩容镜像仓库服务(这个比例是和镜像大小、机器硬件配置、所处网络环境等相关的,在我们的场景中是 1:20),作为二级缓存。

对镜像仓库的操作系统镜像,我们也做了一些优化,如下:

  • 支持指定一个镜像列表进行预热:可以通过环境变量 (docker run-e参数)将当次扩容需要的业务镜像列表传入镜像仓库容器内;这样,镜像仓库就可以支撑多种业务同时扩容;
  • 内置了 JDK,Tomcat 等常用的基础镜像,减少预热镜像的耗时;
  • 预热完毕后,自动调用阿里云 SLB 接口,添加后端节点,之后就可以对外提供服务了。

阿里云镜像仓库临时扩容区的生命周期如下图:

注意,预热这一步是很关键的,如果不预热,所有请求都会逐级回穿(而不是等待),结果可想而知。

备注1:20 是反复测试后得出的比例——即在只有 1 台镜像仓库的情况下,最大能供 20 台机器同时拉取 700M 的镜像,时间在 2 分钟以内;数量再多的话,就会出现拉取失败的情况。

目前,这套架构保证了在阿里云中 10 分钟扩容 1000 个节点的能力,并且同时降低了成本和专线的带宽压力。

二:内网环境不统一问题

由于 Docker 版本的更新非常快,且向后兼容性不够好。随着使用 Docker 的时间越来越长,我们的生产环境上运行的 Docker 版本也越来越多,从 1.2 到 1.8 都有。在 Docker 1.6 发布时,原有的镜像仓库项目(docker-registry)被标记为 Deprecated,迁移到了新的 distribution 项目,它使用 Go 实现,API 也和原来有很大的不同。Docker 1.6 以下的版本只支持旧版的 API,1.6 及以上的版本默认使用新的 API 和镜像仓库交互,并支持 fallback 到旧版 API。

为此,我们同时保留了 docker-registry 和 distribution 两个服务,并使用 docker-compose 编排了一组能够和我们已有的所有版本 Docker 正常交互的服务,大致的结构如下:

如图中所示,在 Nginx 层通过配置将来自不同版本 Docker 的请求转发给相应的后端;为了避免镜像同步的问题,拒绝来自 Docker 1.6 以下的所有 push 请求,同时利用内置的 notification 机制实现镜像的自动同步(单向)。

对于存储层,我们对比了 Glusterfs、Swift、Ceph 之后,最后选用了 Ceph。原因有几个:配置简单,社区更活跃,支持块存储。在保证了高吞吐量的同时,也解决了单点问题,一旦 distribution 服务器出现问题,只要 Ceph 还在,就可以快速重建出来。

这里值得一提的是,distribution 本身就是一个 Ceph 客户端,可以直接和 Ceph 交互,配置也很简单;而 docker-registry 则需要通过 ceph-gateway 来访问 Ceph,配置要复杂一些。

备注最新版本的 distribution 中已经把 Ceph 相关的配置和 rados 驱动代码都移除了,只能通过部署一套 Swift gateway 的方式来间接访问 Ceph。

Docker 及其工具本身的问题

当然,除了上面两个和微博环境相关的问题之外,还有一些 Docker 本身及其周边工具的问题。

上面提到,Docker 的版本更新非常快,而且经常有颠覆性的更新(比如 1.6 和 1.10),这也反映出整个 Docker 社区是比较激进的,所以很多使用上的细节的东西需要我们在实践中一点点去积累经验,同时关注 Docker 社区的动态,才能更加得心应手的使用这项技术。

这里列举几个我们碰到的相对比较重大的问题(和镜像仓库相关的):

proxy 机制的缓存失效问题

先来看一下镜像仓库 proxy 机制(官方一般叫 pull through cache)的原理:

一个 distribution 可以被配置为另一个 distribution(官方或私有都可以)的 proxy(只读的,即只能 pull,不能 push),在 config.yml 中添加如下配置即可:

复制代码
proxy:
remoteurl: http://10.10.10.10

配置好之后,当一个 Docker pull 请求过来时,会检查本地是否已经存在被请求的镜像;如果没有,则会穿透到配置的后端,并同时缓存在本地,这样后面的请求就不会再穿透了。

缓存默认的有效期为一周(可通过修改代码的方式调整,需要重新编译);超过有效期之后,会由一个 scheduler 协程来删除被缓存镜像的所有 layer 的文件及元数据。当一个镜像缓存过期之后,这个镜像的 pull 都会失败,并提示 image not found。

问题的原因在于 distribution 检测缓存有效的逻辑有问题,具体这里就不详述了,感兴趣的同学可以参考这个 issue 或者阅读源代码。

官方就这个问题做过一次修复,但并不彻底。在官方彻底修复之前,我们的解决办法是,将缓存有效期设置的长一点(一个月),同时定期(一个月)清除一次缓存目录,再重启 distribution 容器。

使用 HTTPS 协议时,X-Forwarded-Proto 头的设置问题

有时候,出于安全或使用规范等原因,我们会自己搭建一套支持 HTTPS 协议访问的镜像仓库服务;业界普遍的做法是把证书配置在 Nginx 层,并且转换为 HTTP 协议,再传给后端,我们也是这么做的。

这里有一点需要注意:原理上,无论是 Docker pull 还是 push,其实都是一系列的 HTTP 请求。而对于 Docker push,distribution 会根据”X-Forwarded-Proto”这个 Header 值来判断下一次返回给 client 的 Location 是 HTTP 协议还是 HTTPS 协议。所以要保证在请求到达 distribution 端时,X-Forwarded-Proto 头的值是正确的,是客户端最开始发起请求的协议。否则在开启镜像仓库的权限控制后,会有 push 失败的情况。

当整个 HTTP 调用栈中存在多层 Nginx 或类似的反向代理程序时,尤其要注意这个问题。比如,在我们的两层 Nginx 中,分别是这样配置的:

LVS 层:

复制代码
location / {
...
proxy_set_header X-Forwarded-Proto $scheme;
...
}

docker-compose 层:

复制代码
location / {
...
# proxy_set_header X-Forwarded-Proto $scheme;
...
}

使用了阿里云 SLB 之后,docker pull 会等待一段时间才开始拉取镜像

在将阿里云的 registry 域名指向 SLB 之后,发现一个奇怪的现象,每次从域名去 docker pull 都会等待一段时间才开始拉取镜像,而直接按 IP 拉取则没有问题,如下:

复制代码
docker pull registry.api.weibo.com/busybox:latest // 等待大约 20 秒才开始下载镜像
docker pull 10.75.0.52/busybox:latest // 正常

经过一番调查,发现 Docker daemon 在收到 pull 命令后,会先检测指定的地址是否是一个合法的服务提供者;检测时,会按照 https+v2、https+v1、http+v2、http+v1 的顺序逐个请求;而阿里云的 SLB 对于未监听的端口,默认行为是不回包,所以客户端只能等待超时。

我们的解决办法是给 SLB 添加了 TCP 的 443 端口,这样客户端很快就知道这个 distribution 并不提供 HTTPS 服务,从而立即 fallback 到 HTTP 协议。

此问题在 Docker 1.6.2 版本中存在,至于后面的版本有没有做修复,有兴趣的同学可以自行试验。

总结

镜像仓库服务作为微博混合云基础设施中的一部分,其分发能力和稳定性至关重要。我们采取的架构方案是综合考虑了微博的业务特点、成本、历史遗留问题等诸多因素而设计的,并不一定适合于大家。同时,为了解决需要先扩容镜像仓库的问题,缩短总扩容耗时,我们也在开发一套基于 BitTorrent 协议的镜像分发方案,希望能和业界同行多交流学习。


感谢魏星对本文的策划和审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016 年 6 月 06 日 17:381625

评论

发布
暂无评论
发现更多内容

深入浅出虚拟内存

程序喵大人

c c++ C#

关于职能型团队管理一些总结

黄大路

项目管理 管理

一个 UED 团队的自我修养

oldj

团队管理 UED

Elasticsearch原理讲透了!

for

lucene elasticsearch 倒排索引 分布式搜索引擎 数据的分片和备份

Web百度离线地图开发

玏佾

WebGIS 离线地图 Web离线地图

C++中glog源码剖析以及如何设计一个高效 log模块

程序喵大人

c c++ C#

原创 | 使用JUnit、AssertJ和Mockito编写单元测试和实践TDD (三)单元测试在整个测试体系中的位置

编程道与术

软件测试 TDD 单元测试 集成测试 验收测试

MacOS高效使用指南-我的体系化方案以及软件清单

lmymirror

高效工作 效率工具 知识管理 Mac 操作系统

拜托,别再问我Zookeeper如何实现分布式锁了!

不才陈某

zookeeper 分布式 后端 分布式锁

程序员• 后浪

古时的风筝

程序员 后浪

无所不能 就像妈妈一样

Neco.W

思考 情绪

作为自由职业者,我的近况

一尘观世界

程序员 自由职业 复盘

时间足够爱你

rmrf

学习 思考 持之以恒

直播电商行业一些看法

黄大路

互联网 商业 商业模式 商业价值 行业资讯

高仿瑞幸小程序 04 小程序的全局数据

曾伟@喵先森

小程序 微信小程序 前端

以不变应万变——复杂系统回归测试新思路

刘华Kenneth

DevOps 敏捷 测试 单体系统 复杂

Netty 源码解析(八): 回到 Channel 的 register 操作

猿灯塔

每日算法之leetcode 50 Power

12583

递归 LeetCode 分治

腊鸡与猴儿

黄大路

人生 小说

使用人工智能技术改进面试机器人

陆道峰

人工智能 学习 聊天机器人

游戏夜读 | 联网才能玩的单机

game1night

架构师快问快答2

IT民工大叔

想看懂stl代码,先搞定type_traits是关键

程序喵大人

c c++ C#

Java 环境配置与编辑器使用

旭霁

Java IDEA

C++ sqlite3使用指南

程序喵大人

c c++ C#

Java并发编程--ReentrantLock

Java收录阁

并发编程

数据湖引擎是什么鬼

数据社

大数据 数据仓库 数据湖 数据架构

读 Go Scheduler 有感:给产品经理的建议

Ya

程序员 产品经理 操作系统 OS Scheduler

来了来了,2020 首场 Meetup ,可!

Apache Flink

大数据 flink 流计算 实时计算 大数据处理

DDD 实践手册(5. Factory 与 Repository)

Joshua

企业架构 设计模式 领域驱动设计 DDD 架构模式

自助设备系列——增长点

孙苏勇

产品 行业资讯 智能设备

新浪微博混合云架构实践挑战之镜像分发实战-InfoQ