时间:2017年3月22日10:00
地点:广新信息大厦唯品会
人物:小米&唯品会的技术宅们
唯品会算是南方技术流派的代表,小米算是北方技术流派的代表,彼此间心心相惜,相约唯品会总部,进行了一场华山论剑,欲知详情,请看下文。
第二个议题主讲人:喻波
议题:falcon系统设计
我们为什么要做falcon?
1、主要是公司原来的监控遇到了一些问题,业务一直在
发展
,导致维护成本非常大,机器的负载、存储的负载,还有数据库有很大的瓶颈,刚才伏晔也说了,数据库没法扩展。
2、第二个就是规则配置也非常难,虽然我们也可以做一些自动化的脚本,但是每加一台规则、加一台机器、或者业务上线都需要一些专门的人,我们不希望机器越来越多,维护的成本越来越大,同时数据的清理也很麻烦,随着时间增加,每个点都会落到数据库里,然后这个数据库越来越大,我们会写一些定期清理的脚本,但是这样会增大服务器的负载,
经历了这些事情之后,如果去设计一套监控系统,希望它具有什么样的特性呢?
首先我们希望它是一个可以水平扩展的,也就是当我的的公司规模有1000台机器的,一台监控服务器就可以了,那如果有10万台服务器,我们能否做一些水平扩展,但是它的样子并没有发生改变,我们希望它各个组建都可以水平扩展,而不是规模增加了,变成了另外一种样子。
数据是可维护的,不管用多长时间,数据规模有多大,希望这个数据不要让人去维护,如果我们写脚本去维护的话,相当于内部的机器存收一样,不会对我们的系统产生影响,至少这个性能上应该不会有卡顿,数据的维护希望它是在每时每刻发生的,而不希望有突发性的卡吨。然后我们希望说采集的数据是无状态的,也就是说希望这个数据在push的时候,对每个模块来说,不管是第一次出现,还是丢失了,还是一直在持续的空间状态的上传,对于模块来说应该是无感知的,我们希望它的这个数据所有模块对于数据的状态应该是忽略的,它能够自由定义的,我们还希望模块之间是弱吻合的,比如说数据是怎么产生的,我是去采样这个模块,如果你关心的话,实际上整个的架构是没办法更高级和抽象的,我们希望模块和模块之间是接口对应的,而不是业务上有任何关系的。
下面给大家介绍一下我们是如何实现的。这是我们的一个结构拓扑图,所有的模块都在里面了,但是我不打算一个一个讲,这个在官方文档上都会有详细描述,相信文档上比我现在要讲的清楚一些,我们是怎么开发的,它们之间是怎么工作的?
先来看一下数据是如何采集上来的,最关键的是这样一个数据样本,就是采样,当我们决定了数据采样信息之后,我们采样的时候,一般来说采样所有信息的东西就是value,它的值是多少,大概8个字符,其他是为了描述这个字符,但是这有很多个字段,比如说这样的一个KEY,每个样本都需要把这个KEY每一个字段全部加上去,对于metric来说,它带有一个完整的描述信息,为什么这么设定呢?我们希望所有的数据采样以后,push到数据当中它是要无状态,也就是这个值本身必须是可以自己来描述自己,这些描述的信息有些是固定的,有一些是可扩展的,可扩展的部分是在这个地方,它实际上是留给用户的一个可弹性自定义的一些东西,这个是用逗号来分割,这个是留给业务或者是留给其他的使用者去自定义的,还有就是要规定必须要有,首先监控的话,它是一个持续的跟时间相关的,所以说这个时间肯定是必须要有,下面这个是描述这个数据产生的机器,是哪台机器上的这个数据,然后除了这个机器以外,就是你的产量是什么,比如说你产量的这个地方是一分钟之内它的负载的值,如果在这台机器上,一般来说,一个系统是一个比较稳定的系统,如果我第一次是一个长链接,第一次在进行通讯的时候,相当于原数据的描述打到服务器上去,后面传送的每一个值,只要把这个值传过去就可以了,后面相应的信息在第一次宣告的时候就OK了,但是我们可能考虑网络它是不太稳定,或者是有数据的丢失,重新建联的时候怎么办?我们希望每一次数据在传送的时候,都会有一个完整的信息,这样我们在构建后面的模块流程的时候,就不用关心这个数据是第一传上来还是已经传上来过但是信息丢失了,我们要再去把这个信息怎么重新gat上,因为都会对这个有一个完整的数据描述,这是数据的采集。
然后数据传输,它包括从数据采集以后,打入到falcon里面,它们共同的特征就是把数据拿到以后,按照刚才的那种格式,当然我们也可以是有其他格式,但是信息的描述都必须要有,把它打到一个falcon的模块里,它会做两件事情,第一件事情就是根据这个数据的描述,就是它原始的一个KEY,把它变成一个全局唯一的核心值,后面会通过这个值,使用这个模块,这是关于位置存储的,我们要做到水平扩展,必须要加一个数据要按照某种规格分散到机器里面去,如果每一个数据的样板都对应KEY,通过某种算哪,把这个后端的机器选择其中的一台,把数据打进去,就实现了这个位置存储了一一映射的关系,也就是每一个确定的KEY,它都有一个确定的后端服务器的一个位置,不管我以后数据put进去,或者是想把这个数据从后端存储里面把它取出来。因为我们在存储数据的时候或者取数据的时候,必须明确的出这个值,所以实际上这个值对一般的用户是没有什么意义的,而且你是没有办法反推的,也就是说你只有拿到这些信息以后,通过计算才能得到这个KEY,所以如果你想要去做list操作,它的工作是把这些没有编码或者是把这些值和这个KEY做一个映射的关系,会让你去进行模糊查询。
这是小米的存储,当数据做完之后,回到KEY,然后得到了后端的某一个具体的KEY,它需要打到第二台的后端服务器上,数据是怎么存储的呢?
我们在调研的时候参考过很多存储类型的一些,最后选择了历史比较悠久的rrdtool,它和tsdb不同的地方,就是它在于实时归档,它并不是一个存储历史数据,哪怕比如说一分钟采一个样,把它存储下,然后再设置一分钟一个点把它存储下来,但实际上它并不能保证100%能存下去,它哪怕是一一对应的关系,它还是会做一个归档,比如我举一个例子,当我开机的时候我对我CPU的计数器做一次改良,比如说它是0,然后1秒钟以后我再去采样,它可能CPU可能是稳步增长的,比如说网卡的,过了一秒再去采样,它变成了100K,那也就是说这里面实际上有两个信息,第一个信息就是当我在第二次采样的时候,它的计数器确实是100K,还有一个就是在过去1秒钟平均的网络是100K除以60秒,是1秒,但是你如果把这个信息把它放到rrd库里,它会把第一个信息给,也就是说它并不会你关心你第二次采样采到的实际的值是多少,它关心的是在过去的一个时间分配里它的一个平均的增速是多少,就是说增加的速度,针对于刚才网卡的计数器,它在过去的1、2秒内的增速是多少。对于rrd它能够说明的问题就是你的采样的时间段里面,它的一个趋势,rrd它并不能够存储当你这个时间分片结束点的那个真实点,它只是存储的在那个时间分配里比如公司默认的是60秒,过去1分钟之内它的状态,就是它的网速是多少。当然如果希望存储一个真实的值的话,就是要这个时间点采样的这个值,原封不动的采下来,我们也支持tsdb,这个是由美团的东西来提供的,之所以可以很方便的支持tsdb因为刚才采样的样本实际上就是用tsdb作为参考发展出来的。
然后查询的话,用我们的监控系统可以看到匹配条件就可以看到这些图,这张图给大家举一个例子,在浏览器打入这样的一个东西,就会出现这样一个页面,这个页面里面会有很多图片,每张图片有很多线段,它的一个负载或者是查询的模式是怎么样的,首先我们是通过IE浏览器发动一个指令,到达dashboard,会由我们的浏览器有这样的一个值,这个值实际上是自己存储带来的,它会映射成20个图片,每个图片又针对这个请求,每个图片有8跟线,每个线有280个点,我们可以控制你不管在多大的时间跨度内,比方说要查1年或者是2年,或者是查过去1个小时,或者是查过去1个礼拜,它会比较智能的去选择它的采样率,给出一个点,这个点的大概分布是50个到700个之间,在这个例子里面是280K,一共加起来,我们要对服务器的模板要进行160次查询,一共有4万多个点到这个页面上来,它的耗时大概是0.7-1.2s。
报警这个地方刚才伏晔讲了,这边就不再详细讲这个东西,但是我们这个地方最大的一个特性就是所有的东西都必须是一个被动的,唯一主动的模块是发生在数据的采集阶段,就是数据是从A点主动采集的,比方说想1分钟采一次也好,采集完之后所有的数据都是从A点发生送到portal,然后再发送到后端,每一个发送到后端数据都会step去匹配,有没有相对应的报警规则,也就是说我们在设置报警的时候,实际上是不用去关心数据本身的,我们再去传送数据的时候也不用去关心有没有规则,当你的数据传送到服务器落地的时候,都会去检查有没有,就相当于是进行一次模糊匹配,有没有和这个数据描述相关的报警规则,如果有的话,就把这个值带到这个规则里进行预算。既然所有的东西都是被动的,那如果我想主动做一些事情怎么办?比如遇到的例子有两个,就是当没有数据,就是每分钟一个点传上来,突然这台机器挂了,没有数据了,既然你的报警是由数据来驱动的,没有数据的时候怎么办?还有一个就是当我有很多数据需要去聚合的时候怎么办,因为我们这个数据必须是一个,没办法去做多个,就是你再做一个模块,你再做一个查询的模块,当有一台机器没有数据了,它没办法去触发报警了,我们可以再去做一个模块,比方说我们希望每分钟去检查一次,就希望每分钟去通过这个找到你想去监控的那个点的值,然后使用placement去带到这个值的最后一个点把它取出来,这个值存不存在,就说明这个点在上一个时间点里没有数据上来,这个时候就可以由这个模块再相当于put一个新的值上面去,但是这个值明显是不符合规范的,这样就实际上间接的解决了这样一个问题,你如果要联合查询的话,实际上也是一样,就是说你如果要把五个相关的品取出来,对它进行逻辑判断。你可以把这5个值通过所有数据库得到它的核心的值,然后把它带到这里面把它取出来,每个周期取一次做一次判断,如果这5个值你可以简单的把它写成0和1,0就是正常,1就是异常。
这是我们后面会需要去做的一些事情,就是我们希望所有模块能够自动发现,这个功能已经实现了,我们会在4月份上线,我们会做一个中心化配置的管理模块,也会通过一个管理模块自动的去发送所有模块的配置,去做一个中心化配置管理。
第三个议题主讲人:孙寅
议题:小米弹性调度平台
大家好!
我是孙寅,现在在小米负责运维基础设施、基础平台的构建,
今天来介绍一下小米的弹性调度平台
Ocean。
Core
stack
主要是
mesos
、
marathon
、
docker
,也不排除未来网络会用
calico
构建
下面依赖的基础设施很多,解决不同层面的问题。
对于大多数无状态的业务开发
app
而言,我们有一套完整的从
build
到
deploy
的封装,以及经过产品抽象的
CI/CD
流程。
然后是很多有状态服务、以及负载均衡,我们把它们实现成
PaaS
组件形态,这是为了让整个私有云生态更完整和闭环。
整个体系希望达到的目标,我们希望业务能够持续交付,不用人为干预;无论是测试环境还是生产环境,都能根据服务依赖链,一键部署好一个产品线完整的后端服务;能够对
App
进行自动的容量测试,根据测试得到的指标自动缩扩容;能够自动
Failover
大多数的非业务逻辑故障。当然这些目标目前很多还是理想,理想总还是要有的,万一实现了呢。
对于构建这套平台体系,遇到的第一个问题就是,
Docker
如何在工业环境里落地。
由于我们起步比较早,大概在
15
年
8
、
9
月开始构建这套平台,那时
docker
、
mesos
的网络都还很稚嫩,业内好像也还没抽象出现在那么多种容器网络模式,我们自己琢磨出这么一种方式,用
dhcp
,让每个容器得到一个
IP
,然后桥接容器和物理网卡的
interface
,走交换机的
trunk
与其他容器以及物理主机相连。当时这个方案在业内是非常先进的,很久之后才知道这种方案被归类为
macvlan。
文件系统,选取方案的目标是让容器磁盘空间大小可控,同时保证磁盘
IO
性能不下降。
Aufs
、
btrfs
等等存储方式,由于都是在宿主机的文件系统之上,再做一层文件系统,所以性能上都会有比较明显的下降。
所以排除掉这些存储方式,我们就只能选
devicemapper
了,但
devicemapper
又有个问题,就是一个
docker
daemon
里只能设置一个固定大小的空间,这无法满足我们动态申请空间的需求,所以我们采用了这种方案,
root
分区用
devicemapper
,
home
分区随用随从物理设备里分一块
lvm
逻辑卷,再做上文件系统来使用。这样就完美了,大小可控,性能无损
弹性调度环境下,我们希望能够比较精准地隔离这样五种资源,
CPU
、内存、磁盘
IO
、磁盘空间和网络带宽。
Mesos+Docker
早期版本原生支持了
CPU
和内存,我们自己用折中方案扩展了磁盘空间和网络带宽,但磁盘
IO
还没有做。
飘红的这两个
tips
提一下,
一个是我们改
docker
的代码,嵌入
tc
限速来模拟网络带宽隔离,当时我们也调研了
ovs
限制带宽的方案,但不如
tc
稳定,而且也比
tc
重得多,最终我们选择直接在
docker
内集成
tc。
还一个刚才提过的。
我们希望每个发布到
Ocean
平台上的
App
,都是经过我们规范化的,所以我们通过
build
这一环节来规范,用户不写
Dockerfile
,而是写经过我们封装过的一个
docker.yml
配置文件,比如我们会规范业务程序的目录为
/home/work/bin/
;比如端口是必填的,用来由我们的
docker-init
检查端口存活后再起一个端口做
marathon
的
healthcheck
;有的同学可能已经发现了
require
这个配置,它是用来构建编译环境和运行环境的,加号分割的第一个域,会生成
Dockerfile
的
FROM
语句,其他域,每个域都会对应一个提前准备好的,可用的
Dockerfile
内容,在转换成
Dockerfile
时,会累加到
FROM
语句之后,这样就使我们的编译环境和运行环境也是经过我们标准化的,不会出现各式各样的环境,同时也让工程师们使用平台门槛更低。
在
Docker
容器内部我们封装了一个工具,叫
Docker-init
,所有容器启动的
CMD
都是它,它既做类似
supervisor
的这种进程管理工作,又封装了很多业务规范,以及和基础设施组件对接的逻辑,它就像是一个粘合剂。
功能比较多比较碎哈,大家直接看一下,我不细展开了。
了哥想让我讲下我们多集群管理怎么做的,其实挺简单的,规划每个
IDC
一个
mesos
集群,每个
APP
有一个属性表明它是哪个集群的,
Potal
根据这个属性来寻址,把这个
APP
的
API
请求发给对应
集群的
Marathon。
这样的架构下,公网故障和
IDC
间专线故障,都不会对各
mesos
集群产生影响。除非是
A
机房整机房掉电,才有必要把
Potal
和
Potal
DB
切换到
B
机房,然后再
scale
app
,切换业务流量。
再有一个很大的工程变化,是服务发现,想要让弹性伸缩规模化,就必须让所有相关的业务程序都具备服务发现能力。
对于我们自己开发的程序,我们需要有全面覆盖的
RPC
框架和名字服务
还有一些特例,就是这些第三方开源组件,它们也一样必须具备这项能力,比如我们可能需要改一个
nginx
module
来让
nginx
动态发现后端服务,再比如我们需要让业务服务动态发现
Mysql
、
redis
等等有状态服务。
接下来我们看下弹性伸缩之后,工程体系里发生了一些什么变化。
动态安全
静态环境下,很多服务通过简单的
IP
白名单来授权其他服务是否可以访问自己
动态环境下,这种方案无法
work
,所以就有了动态安全的问题,关于这个问题我们有两种方案,一种是针对
Mysql
的。
还一种是更抽象通用的方案,功能也更强,可以通过
scope
对函数集粒度进行授权,还可以动态更换
token
以提升安全性,但它的使用成本比较大,需要两边服务都植入
SDK。
配合功能全面的监控系统,做到自动伸缩比较容
关键字有这么几个:集群监控、报警回调。
自动伸缩看起来很美,但对业务的容量测试和工程能力要求很高,定时伸缩是自动伸缩的有效补充,我们可以凌晨缩容,早上扩容。
我们实现的方式也很简单,创建
chronos
任务来调用
Marathon
API
。
Chronos
也是
Mesos
的上层框架,用来支持定时任务、依赖链任务的调配,我们用它来做定时触发伸缩,能够很大程度上简化分布式容错问题。
服务化组件是为了进一步自动化封装,把负载均衡和有状态服务,抽象成创建即可用、能够自运维、自容灾的
PaaS
服务组件。
先说四层
ELB
多数互联网公司,都会用
LVS
来做公网入口的
NAT
和负载均衡,但在弹性环境下,
web
类服务也是弹性伸缩的,
IP
也是随时变的,所以需要解决
LVS
动态更新问题。早期我们是在
docker-init
中提供一个配置,用来与
LVS
管理系统对接,自动变更
LVS
配置,随着演进,我们把它实现成了和
AWS
ELB
类似的服务。可以创建一个
ELB
,绑定一个
web
服务,这个服务的
IP
无论如何动态变化,四层
ELB
都会动态更新相应的
LVS
RS
。
这里有三个关键技术点
一是我们会自动配置内网域名,并且让这个域名按运营商自动划分线路,比如
BGP
、联通单线、移动单线,这个是为了让使用者默认就用了最合理的域名配置,可以很有效的节省
BGP
带宽资源;
第二个是
docker-init
和旁路模块都会去动态更新
LVS
配置,更新动作是可重入的,这个是为了降低旁路模块故障的影响,即使它故障了,也不会对线上现有业务产生影响,只是无法创建新的
ELB
;
ALB
,它和
AWS
的
ALB
功能相近,可以配置多个
location
路由,指向多组后端。
它的底层架构和四层
ELB
完全相同,但在它的基础上多封装一层
Nginx
,这层
Nginx
需要能够动态更新
upstream
、可以根据
qps
来自动伸缩,刚才讲过这两个功能
Ocean
目前已经
有了
,只是需要在
ALB
这个服务里进行再封装。
RDS
是有状态服务中比较典型的一个,它所管辖的状态很重,而且还有角色,是个想要做到自运维比较困难的组件。
先看下
UI
,创建一个数据库,只需要很简单的几个信息,起个名字,选择集群、容器模板,提供实例数。
创建一组
RDS
集群时,由
manager
到
zookeeper
中创建一组对应的元信息,然后调用
marathon API
创建一个
APP group
,其中有两个
App
,一个是
DB
,一个是
DB
proxy
,
DB
proxy
也是我们自己开发的,今年会开源出来;
DB
的各个实例,通过到
zookeeper
抢锁的方式确定角色、自动配置、然后开始运行。
启动时
DB
这个
app
的每个
instance
,都没有角色,启动时由
docker-init
到
zookeeper
中抢主,未抢到主的把自己设定为
slave
,并配置好从
master
同步。当任意一个实例故障时,
HC
会先尝试重启挽救,以尽量保持数据持久化,挽救几次的确不行后,
HealthCheck
会指挥整个容器退出,
通过
gossip
协议其他
HC
会感知到有一个实例离线了,如果这个实例是
slave
,那么其他实例不用管;如果这个实例是
master
,
HC
会指挥本容器内的
FailOver
去重新抢主、自动配置主从等等过程。对于被
marathon
重新启动的实例而言,由于它出生时一定已经有
master
了,所以它只能是
slave
,在启动后发现集群里已经是有数据的状态了,它会去从
backuper
中先恢复数据,完成数据恢复后,重新对接同步,追平数据,然后才把自己重新注册进
dbproxy。
大家会发现,我们的实现,外部管理的
manager
很轻,但容器内部的组件很重,这是为什么呢?因为我们希望集群运行起来之后,就尽量不再依赖外部的中心控制模块,集群本身可以做到自运维,这样在网络出现故障时,整套系统才更可靠。
ElastiCache
目前是两种:
Redis
和
Memcached。
Redis
已经上线内测了,
Memcached
还在做。
Memcached
没有角色,状态也很轻,没什么好讲的;
redis
我们用的是
redis
cluster
,它是有角色、有槽位概念的,架构和上面提到的
RDS
的比较类似,但比
RDS
简单一些,我这里就不重复展开了。
CI/CD
项目目前还在开发中,我们打算分两步同时走。
一步是定义一种有一定规范,且比较灵活的
pipeline
模型,来作为基础设施来支持公司大多数业务的
CI/CD
需求。
另一步是把
Ocean
构建、发布的
API
,开放出来,供有更深层次
CI/CD
需求的业务自行定制其更复杂的
CI/CD
流程。
今年和目前在做的事情基本聊完了,再看下我们的未来规划:
-
补充完善有状态组件,像
kafka
、
hbase
都已经在我们的计划里
-
集成标准化
CI/CD
、强化故障自愈和自动容灾能力
-
再有是网络设计的再优化,考虑是不是要实现工业
SDN
到目前为止,小米
DCOS
的整个体系完成度已经非常高了,不排除我们会开源一部分核心组件,或是商业化这个产品的可能性,敬请期待,谢谢大家。
交流活动PPT下载:http://pan.baidu.com/s/1jIdY9Ci
商务合作,请加微信yunweibang555