数据专栏

智能大数据搬运工,你想要的我们都有

科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

摘要: H5 Video是不提供截图的API的, 视频截图需要借助Canvas,通过Canvas提供的drawImage方法,把Video的当前画面渲染到画布上, 最终通过toDataURL方法可以导出图片的base64编码,基本就完成了图片截图的功能。
基本介绍
H5 Video是不提供截图的API的, 视频截图需要借助Canvas,通过Canvas提供的drawImage方法,把Video的当前画面渲染到画布上, 最终通过toDataURL方法可以导出图片的base64编码,基本就完成了图片截图的功能。
功能实现
Canvas接口介绍
drawImage接口 在画布上绘制图像、画布或视频,也能够绘制图像的某些部分,以及/或者增加或减少图像的尺寸,主要参数为: 参数 描述 img 规定要使用的图像、画布或视频。
sx 可选。开始剪切的 x 坐标位置。
sy 可选。开始剪切的 y 坐标位置。
swidth 可选。被剪切图像的宽度。
sheight 可选。被剪切图像的高度。
dx 在画布上放置图像的 x 坐标位置。
dy 在画布上放置图像的 y 坐标位置。
dwidth
dheight
可选。要使用的图像的宽度。(伸展或缩小图像)
可选。要使用的图像的高度。(伸展或缩小图像)
参考一下这个图应该就比较清楚的了:
看一个例子,比如一个视频是640X480,现在需要截取下半部分: function(){ var c=document.getElementById("myCanvas"); var ctx=c.getContext("2d"); var video=document.getElementByTagName("video"); ctx.drawImage(video,0,240,640,240,0,0,640,240); };
toDataURL接口
方法返回一个包含图片展示的 data URI 。可以使用 type 参数其类型,默认为 PNG 格式。图片的分辨率为96dpi 参数 描述
type encoderOptions
可选。图片格式,默认为 image/png 可选。在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略
比如设置导出图片的质量: var fullQuality = canvas.toDataURL("image/jpeg", 1.0); var mediumQuality = canvas.toDataURL("image/jpeg", 0.5); var lowQuality = canvas.toDataURL("image/jpeg", 0.1);
导出图片时需注意的: 如果画布的高度或宽度是0,那么会返回字符串“ data:,”。 如果传入的类型非“ image/png ”,但是返回的值以“ data:image/png ”开头,那么该传入的类型是不支持的。 Chrome支持“ image/webp ”类型。 图片地址或者视频地址需要支持跨越访问,并且在 img或video Dom元素上添加属性crossOrigin:"anonymous"
Aliplayer的截图功能
Alilayer里实现的截图除了截图一张图片,还可以添加水印,还可以截取镜像的视频,效果如下:
截图功能介绍
创建Canvas,设置大小为实际视频的大小,根据视频的镜像方向,对Canvas做相应的处理,代码如下: var canvas = document.createElement('canvas'), video = that._player.tag, canvas.width =video.videoWidth,//视频原有尺寸 canvas.height = video.videoHeight;//视频原有尺寸 var ctx = canvas.getContext('2d'); ctx.save(); //判断用户是否对视频做过镜像 var image = that._player.getImage(); if(image == "vertical")//垂直镜像 { ctx.translate(0,canvas.height); ctx.scale(1, -1); } else if(image == "horizon")//水平镜像 { ctx.translate(canvas.width, 0); ctx.scale(-1, 1); } //视频的当前画面渲染到画布上 ctx.drawImage(video, 0, 0, w, h); ctx.restore();
水印功能介绍
Aliplayer水印支持添加的位置、字体、画笔颜色和填充样式,在创建播放器的时候指定,代码如下: let drawText =>(ctx, options) { var watermark = options.snapshotWatermark; if(watermark && watermark.text) { //设置字体样式 ctx.font = watermark.font; //设置字体填充颜色 if(watermark.fillColor) { ctx.fillStyle = watermark.fillColor; ctx.fillText(watermark.text, watermark.left, watermark.top); } //设置画笔颜色 if(watermark.strokeColor) { ctx.strokeStyle = watermark.strokeColor; ctx.strokeText(watermark.text, watermark.left, watermark.top); } //画布上画出水印 ctx.stroke(); } }

作者: nahom
原文链接
本文为云栖社区原创内容,未经允许不得转载。
云计算
2019-01-09 15:47:00
摘要: 2018年12月20日,云栖社区3岁。阿里巴巴常说“晴天修屋顶”,所以我们特别制作了这个专辑——分享给开发者们20个阿里故事,50本书籍。
2015年12月20日,云栖社区上线。2018年12月20日,云栖社区3岁。
阿里巴巴常说“晴天修屋顶”。
在我们看来,寒冬中,最值得投资的是学习,是增厚的知识储备。
所以社区特别制作了这个专辑——分享给开发者们20个弥足珍贵的成长感悟,50本书单。
多年以后,再回首2018-19年,留给我们自己的,除了寒冷,还有不断上升的技术能力与拼搏后的成就感。
云栖社区钉钉技术讨论群,已有5000+开发者。
云栖社区2019年1月30+场活动预告【持续更新,欢迎加入】 学习,是一种氛围。
1月9日,技术拓宽技术本身价值,商业价值,更加拓宽的是人生价值的边界。这是我们送给开发者的第14个感悟。
孤尽,阿里巴巴技术专家,花名取意“孤帆远影碧空尽”的意思,《阿里巴巴Java开发手册》的主要编写者。2016年3月,孤尽带领约码项目组编写《阿里巴巴Java开发手册》,码出高效,码出质量,推动阿里系与业界一起进步,让代码变得更舒服,更清澈,更好维护。
2018年5月,孤尽在《向代码致敬,寻找你的第83行》活动中 晒出了他入职第一年写的代码 ,大家赶紧来找找是否有不合规约的地方。
欢迎开发者 关注他 ,并加入 钉群 与孤尽深度讨论。
《阿里巴巴Java开发手册》史上最全资料 点此查看 ! 阿里孤尽:技术拓展价值边界
杨冠宝(孤尽)
阿里巴巴代码平台负责人。
阿里巴巴代码规约的起草者和推动者。
在阿里历任研发、架构师、技术主管等不同的角色,承担过双十一、国际化、代码中心等大型项目,有着丰富的一线编程经验,在大数据、高并发、研发效能领域均有较深的造诣,乐于分享与总结,在国内外做过多次大型交流和培训。 技术拓宽技术本身价值,商业价值,更加拓宽的是人生价值的边界。
好的代码是艺术,好的代码思维是哲学。程序员在技术进阶的路上,敢于象艺术大师一样不断地雕琢自己的作品,思考代码背后的逻辑,不断地提炼和总结,成就传奇代码人生。
记忆、思考、表达、融会贯通是学习的四步曲,第一步是基础,更是一个体力活。
出版图书: 《阿里巴巴Java开发手册》
《码出高效》
推荐书单: 《Effective Java》
《设计模式之禅》
《深入JVM虚拟机》
作者: 云篆
原文链接
本文为云栖社区原创内容,未经允许不得转载。
云计算
2019-01-09 15:10:00
一言不合就发新版本。
年关将至,寒意习习,落叶萧萧下,阳光日日稀。RancherOS团队历时两个来月的开发,正式发布RancherOS v1.5.0版本。 在此期间同为Container Linux阵营的CoreOS已经从红帽再入IBM,潮流之变,业界态势,让我们无不更加努力去争得一席之地。 无论是商业用户的积累,还是业界变化带来的社区用户增长,都在催促我们不断革新,应该说1.5.0版本是用户的需求推着我们走出来的。
重大特性更新
本版本的新特征众多,无法一次性全部说明,以下只表述一些用户关注度比较高的特性。个别特性详细说明,我们会不断推出文章一一展开。
启动性能提升
一直以来RancherOS的initrd一直采用xz格式压缩,随着RancherOS的体积不断增大,xz压缩越来越影响系统启动速度。虽然xz格式能够带来比较小的initrd和ISO, 但是我们也需要兼顾启动速度。v1.5.0版本的initrd已经采用了gzip格式,文件体积有所增大,但是启动速度有了质的飞跃。 同时我们也优化了system-docker的镜像加载和cloud-init的启动,对启动速度进行了深度优化。
LUKS磁盘加密支持
支持LUKS,允许用户对跟磁盘分区进行加密,在一些特殊场景下增强了RancherOS的安全性。运行效果参考下图:
WiFi和4G支持
Intel正在micro PC领域不断发力,RancherOS被纳入其生态体系,我们支持了WiFi和4G网络,用户可以通过简单的cloud-config配置就可以开启, 带来了十分简洁的用户体验,这部分我们会在后续其他文章中详细介绍。
Hyper-V支持
很多社区用户一直希望能在Hyper-V使用RancherOS,先前我们一直提供给用户一些custom build的方式来实现它,现在我们正式支持了它,并会持续维护。 无论是docker-machine方式还是boot from ISO方式均可以支持。
下一个版本我们也会带来RancherOS的Azure Cloud支持。
多docker engine支持
这是一个很有趣的特性,目前RancherOS中默认拥有一个user docker。在v1.5.0中,用户可以用过ROS CLI来创建多个user docker engine, 并且每个docker拥有独立的ROOTFS和网络栈,并且可以在console很容易的切换使用任意一个docker。
当然我们并不推荐您在生产中使用,我们的某个商业客户把这个特性应用在其CI环境中,极大的提升了资源的利用率,减少了物理机器数量的开销。
改善VMware的支持
RancherOS的广大用户中Vmware是占有很大的用户群,之前我们的版本中只针对docker-machine方式做了一些改善,但是很多用户还希望使用boot from ISO方式和VMDK方式, 我们相关的镜像也做了支持,用户可以直接下载使用它: [VMDK]
https://releases.rancher.com/os/v1.5.0/vmware/rancheros.vmdk [Docker Machine]
https://releases.rancher.com/os/v1.5.0/rancheros-vmware.iso [Boot From ISO]
https://releases.rancher.com/os/v1.5.0/vmware/rancheros.iso
ARM的支持
由于Rancher和ARM已经开始了战略合作,我们会在一起做很多有趣的事。RancherOS的ARM支持也是其中的一部分,原先我们只是对RPi做了支持, 现在我们提供ARM版本的initrd和vmlinuz,用户可以用它们使用iPXE方式启动:
https://releases.rancher.com/os/v1.5.0/arm64/initrd
https://releases.rancher.com/os/v1.5.0/arm64/vmlinuz
我们依然只会对ARM64支持,且v1.5.0的ARM支持只是实验性质的,并不推荐应用在生产中。 我们会和ARM进行合作进行更广泛的测试,后续的版本将会是更稳定的。
更加友好的自定义
社区中越来越多的发烧友并不局限使用我们的正式发布版本,他们会根据自己的需求修改RancherOS,构建自己的RancherOS。 我们提供了一些友好的编译选项,用户可以自定义自己的RancherOS。
更改默认docker engine
RancherOS的每个版本都会有自己设定的默认docker engine,而在用户的场景下,可能需要一个内部认可的docker engine,且希望它是RancherOS默认的版本。 那么用户可以在构建时候指定docker engine版本,来构建自己的RancherOS,以docker 17.03.2为例:
USER_DOCKER_VERSION=17.03.2 make release
更改默认console
RancherOS支持很多console,比如ubuntu、alpine、centos等,由于我们的default console基于busybox,有些用户并不喜欢它,且不希望每次都去切换console。 那么用户可以使用这种方式构建一个默认console是自己喜欢的版本,以alpine console为例:
$ OS_CONSOLE=alpine make release
其 他
AWS相关镜像已经上传到各个region中,可以直接搜索查找并使用,包括AWS中国区。其他主要镜像列表参考:
https://github.com/rancher/os/blob/v1.5.x/README.md#release
更多新特性和Bug Fix请参考v1.5.0的Release Notes
文档说明:
https://rancher.com/docs/os/v1.x/en/
最后,RancherOS还是一个小众的开源项目,我们专注Docker在Linux上的精简体验,如果喜欢RancherOS,请在Github上给我们一个star,鼓励我们继续前行。
RancherOS Github:
https://github.com/rancher/os/releases/tag/v1.5.0
云计算
2019-01-09 10:45:00
摘要: 本文主要介绍如何使用消费组实时监控多个域中的多个日志库中的异常数据,并进行下一步告警动作。具备配置简单、逻辑灵活、支持跨域多Region、实时监测,无需配置索引等特点,并且性能可靠、自动负载均衡与保持高可用性。
解决问题
使用日志服务进行数据处理与传递的过程中,你是否遇到如下监测场景不能很好的解决: 特定数据上传到日志服务中需要检查数据内的异常情况,而没有现成监控工具? 需要检索数据里面的关键字,但数据没有建立索引,无法使用日志服务的告警功能? 数据监测要求实时性(<5秒,例如Web访问500错误),而特定功能都有一定延迟(1分钟以上)? 存在多个域的多个日志库(例如每个Region的错误文件对应的日志库),数据量不大,但监控逻辑类似,每个目标都要监控与配置,比较繁琐?
如果是的,您可以考虑使用日志服务Python消费组进行跨域实时数据监控,本文主要介绍如何使用消费组实时监控多个域中的多个日志库中的异常数据,并进行下一步告警动作。可以很好解决以上问题,并利用消费组的特点,达到自动平衡、负载均衡和高可用性。
基本概念
协同消费库(Consumer Library)是对日志服务中日志进行消费的高级模式,提供了消费组(ConsumerGroup)的概念对消费端进行抽象和管理,和直接使用SDK进行数据读取的区别在于,用户无需关心日志服务的实现细节,只需要专注于业务逻辑,另外,消费者之间的负载均衡、failover等用户也都无需关心。
消费组(Consumer Group) - 一个消费组由多个消费者构成,同一个消费组下面的消费者共同消费一个logstore中的数据,消费者之间不会重复消费数据。
消费者(Consumer) - 消费组的构成单元,实际承担消费任务,同一个消费组下面的消费者名称必须不同。
在日志服务中,一个logstore下面会有多个shard,协同消费库的功能就是将shard分配给一个消费组下面的消费者,分配方式遵循以下原则: 每个shard只会分配到一个消费者。 一个消费者可以同时拥有多个shard。
新的消费者加入一个消费组,这个消费组下面的shard从属关系会调整,以达到消费负载均衡的目的,但是上面的分配原则不会变,分配过程对用户透明。
协同消费库的另一个功能是保存checkpoint,方便程序故障恢复时能接着从断点继续消费,从而保证数据不会被重复消费。
使用消费组进行实时分发
这里我们描述用Python使用消费组进行编程,实时跨域监测多个域的多个日志库,全文或特定字段检查
注意:本篇文章的相关代码可能会更新,最新版本在这里可以找到: Github样例 .
安装
环境 建议程序运行在靠近源日志库同Region下的ECS上,并使用 局域网服务入口 ,这样好处是网络速度最快,其次是读取没有外网费用产生。 强烈推荐 PyPy3 来运行本程序,而不是使用标准CPython解释器。 日志服务的Python SDK可以如下安装: pypy3 -m pip install aliyun-log-python-sdk -U
更多SLS Python SDK的使用手册,可以参考 这里
程序配置
如下展示如何配置程序: 配置程序日志文件,以便后续测试或者诊断可能的问题(跳过,具体参考样例)。 基本的日志服务连接与消费组的配置选项。 目标Logstore的一些连接信息
请仔细阅读代码中相关注释并根据需要调整选项: #encoding: utf8 def get_option(): ########################## # 基本选项 ########################## # 从环境变量中加载SLS参数与选项,endpoint、project、logstore可以多个并配对 endpoints = os.environ.get('SLS_ENDPOINTS', '').split(";") # ;分隔 projects = os.environ.get('SLS_PROJECTS', '').split(";") # ;分隔 logstores = os.environ.get('SLS_LOGSTORES', '').split(";") # ;分隔,同一个Project下的用,分隔 accessKeyId = os.environ.get('SLS_AK_ID', '') accessKey = os.environ.get('SLS_AK_KEY', '') consumer_group = os.environ.get('SLS_CG', '') # 消费的起点。这个参数在第一次跑程序的时候有效,后续再次运行将从上一次消费的保存点继续。 # 可以使”begin“,”end“,或者特定的ISO时间格式。 cursor_start_time = "2018-12-26 0:0:0" # 一般不要修改消费者名,尤其是需要并发跑时 consumer_name = "{0}-{1}".format(consumer_group, current_process().pid) # 设定共享执行器 exeuctor = ThreadPoolExecutor(max_workers=2) # 构建多个消费组(每个logstore一个) options = [] for i in range(len(endpoints)): endpoint = endpoints[i].strip() project = projects[i].strip() if not endpoint or not project: logger.error("project: {0} or endpoint {1} is empty, skip".format(project, endpoint)) continue logstore_list = logstores[i].split(",") for logstore in logstore_list: logstore = logstore.strip() if not logstore: logger.error("logstore for project: {0} or endpoint {1} is empty, skip".format(project, endpoint)) continue option = LogHubConfig(endpoint, accessKeyId, accessKey, project, logstore, consumer_group, consumer_name, cursor_position=CursorPosition.SPECIAL_TIMER_CURSOR, cursor_start_time=cursor_start_time, shared_executor=exeuctor) options.append(option) # 设定检测目标字段与目标值,例如这里是检测status字段是否有500等错误 keywords = {'status': r'5\d{2}'} return exeuctor, options, keywords
注意,配置了多个endpoint、project、logstore,需要用分号分隔,并且一一对应;如果一个project下有多个logstore需要检测,可以将他们直接用逗号分隔。如下是一个检测3个Region下的4个Logstore的配置: export SLS_ENDPOINTS=cn-hangzhou.log.aliyuncs.com;cn-beijing.log.aliyuncs.com;cn-qingdao.log.aliyuncs.com export SLS_PROJECTS=project1;project2;project3 export SLS_LOGSTORES=logstore1;logstore2;logstore3_1,logstore3_2
数据监测
如下代码展示如何构建一个关键字检测器,针对数据中的目标字段进行检测,您也可以修改逻辑设定为符合需要的场景(例如多个字段的组合关系等)。 class KeywordMonitor(ConsumerProcessorBase): """ this consumer will keep monitor with k-v fields. like {"content": "error"} """ def __init__(self, keywords=None, logstore=None): super(KeywordMonitor, self).__init__() # remember to call base init self.keywords = keywords self.kw_check = {} for k, v in self.keywords.items(): self.kw_check[k] = re.compile(v) self.logstore = logstore def process(self, log_groups, check_point_tracker): logs = PullLogResponse.loggroups_to_flattern_list(log_groups) match_count = 0 sample_error_log = "" for log in logs: m = None for k, c in self.kw_check.items(): if k in log: m = c.search(log[k]) if m: logger.debug('Keyword detected for shard "{0}" with keyword: "{1}" in field "{2}", log: {3}' .format(self.shard_id, log[k], k, log)) if m: match_count += 1 sample_error_log = log if match_count: logger.info("Keyword detected for shard {0}, count: {1}, example: {2}".format(self.shard_id, match_count, sample_error_log)) # TODO: 这里添加通知下游的代码 else: logger.debug("No keyword detected for shard {0}".format(self.shard_id)) self.save_checkpoint(check_point_tracker)
控制逻辑
如下展示如何控制多个消费者,并管理退出命令: def main(): exeuctor, options, keywords = get_monitor_option() logger.info("*** start to consume data...") workers = [] for option in options: worker = ConsumerWorker(KeywordMonitor, option, args=(keywords,) ) workers.append(worker) worker.start() try: for i, worker in enumerate(workers): while worker.is_alive(): worker.join(timeout=60) logger.info("worker project: {0} logstore: {1} exit unexpected, try to shutdown it".format( options[i].project, options[i].logstore)) worker.shutdown() except KeyboardInterrupt: logger.info("*** try to exit **** ") for worker in workers: worker.shutdown() # wait for all workers to shutdown before shutting down executor for worker in workers: while worker.is_alive(): worker.join(timeout=60) exeuctor.shutdown() if __name__ == '__main__': main()
启动
假设程序命名为"monitor_keyword.py",可以如下启动: export SLS_ENDPOINTS=cn-hangzhou.log.aliyuncs.com;cn-beijing.log.aliyuncs.com;cn-qingdao.log.aliyuncs.com export SLS_PROJECTS=project1;project2;project3 export SLS_LOGSTORES=logstore1;logstore2;logstore3_1,logstore3_2 export SLS_AK_ID= export SLS_AK_KEY= export SLS_CG=<消费组名,可以简单命名为"dispatch_data"> pypy3 monitor_keyword.py
性能考虑
启动多个消费者
如果您的目标logstore存在多个shard,或者您的目标监测日志库较多,您可以进行一定划分并并启动多次程序: # export SLS_ENDPOINTS, SLS_PROJECTS, SLS_LOGSTORES nohup pypy3 dispatch_data.py & # export SLS_ENDPOINTS, SLS_PROJECTS, SLS_LOGSTORES nohup pypy3 dispatch_data.py & # export SLS_ENDPOINTS, SLS_PROJECTS, SLS_LOGSTORES nohup pypy3 dispatch_data.py & ...
注意:
所有消费者使用了同一个消费组的名字和不同的消费者名字(因为消费者名以进程ID为后缀)。
但数据量较大或者目标日志库较多时,单个消费者的速度可能无法满足需求,且因为Python的GIL的原因,只能用到一个CPU核。强烈建议您根据目标日志库的Shard数以及CPU的数量进行划分,启动多次以便重复利用CPU资源。
性能吞吐
基于测试,在没有带宽限制、接收端速率限制(如Splunk端)的情况下,以推进硬件用 pypy3 运行上述样例,单个消费者占用大约 10%的单核CPU 下可以消费达到 5 MB/s 原始日志的速率。因此,理论上可以达到 50 MB/s 原始日志 每个CPU核 ,也就是 每个CPU核每天可以消费4TB原始日志 。
注意: 这个数据依赖带宽、硬件参数等。
高可用性
消费组会将检测点(check-point)保存在服务器端,当一个消费者停止,另外一个消费者将自动接管并从断点继续消费。
可以在不同机器上启动消费者,这样当一台机器停止或者损坏的清下,其他机器上的消费者可以自动接管并从断点进行消费。
理论上,为了备用,也可以启动大于shard数量的消费者。
其他
限制与约束
每一个日志库(logstore)最多可以配置10个消费组,如果遇到错误 ConsumerGroupQuotaExceed 则表示遇到限制,建议在控制台端删除一些不用的消费组。
监测 在控制台查看消费组状态 通过云监控查看消费组延迟,并配置报警
Https
如果服务入口(endpoint)配置为 https:// 前缀,如 https://cn-beijing.log.aliyuncs.com ,程序与SLS的连接将自动使用HTTPS加密。
服务器证书 *.aliyuncs.com 是GlobalSign签发,默认大多数Linux/Windows的机器会自动信任此证书。如果某些特殊情况,机器不信任此证书,可以参考 这里 下载并安装此证书。
更多案例 日志服务Python消费组实战(一): 日志服务与SIEM(如Splunk)集成实战 日志服务Python消费组实战(二): 实时日志分发 日志服务Python消费组实战(三): 实时跨域监测多日志库数据 本文Github样例
作者: 成喆
原文链接
本文为云栖社区原创内容,未经允许不得转载。
云计算
2019-01-21 14:43:00
本文由云+社区发表 作者:颜国平
摘要:最近几年,电商行业飞速发展,各种创业公司犹如雨后春笋大量涌现,商家通过各种活动形式的补贴来获取用户、培养用户的消费习惯,即将到来的“ 购物狂欢节”尤其明显。但任何一件事情都具有两面性,高额的补贴、优惠同时了也催生了“羊毛党”。“羊毛党”的行为距离欺诈只有一步之遥,他们的存在严重破环了活动的目的,侵占了活动的资源,使得正常的用户享受不到活动的直接好处。这篇文章主要分享下腾讯自己是如何通过大数据、用户画像、建模来防止被刷、恶意撞库的。
黑产“羊毛党”现状介绍
“羊毛党”一般先利用自动机注册大量的目标网站账号,当目标网站搞促销、优惠等活动的时候,利用这些账号参与活动刷取较多的优惠,最后通过某宝等电商平台转卖获益。
一.“羊毛党”分工
据初步估计,全国“羊毛党”在20万人左右,他们往往有着明确的分工,已形成了几大团伙:
1.软件制作团伙:专门制作各种自动、半自动的黑产工具,比如注册自动机、刷单自动机等;他们主要靠出售各种黑产工具、提供升级服务等形式来获利。
2.短信代接平台:实现手机短信的自动收发。这其中,有一些短信平台是亦正亦邪,不但提供给正常的商家使用,一些黑产也会购买相关的服务。
3.账号出售团伙:他们主要是大量注册各种账号,通过转卖账号来获利;该团伙与刷单团伙往往属于同一团伙。
4.刷单团伙:到各种电商平台刷单,获取优惠,并且通过第三方的电商平台出售优惠,实现套xian。
也就是说,这些“羊毛党“在电商促销或优惠活动中,已逐步形成了相对完善的刷单及变现工作流程(见图1:电商刷单团伙工作流程):
图1:电商刷单团队的工作流程
二.“羊毛党”从业特点
从“羊毛党“的分工与工作流程看,他们具有以下从业特点:
1.专业化:有专业团队、人员、机器来做。
2.团伙化:已经形成一定规模的团伙,而且分工明确;从刷单软件制作、短信代收发平台、电商刷单到变卖套xian等环节,已经形成完整的刷单团伙。
3.地域化:刷单黑产团伙基本分布在沿海的一些经济发达城市,比如,北京、上海、广东等城市,这或许跟发达城市更加容易接触到新事物、新观念有关。
下图2显示了我们TOP5的黑产刷单团伙分别位于:北京、上海、广州、江苏、浙江这些沿海较发达的经济区域。
图2 TOP5黑产刷单团伙的地域分布
三.对抗刷单的思路
面对黑产刷单,我们有什么对抗思路呢?一般来讲,对抗刷单主要从注册、登陆、活动三个环节入手:
1.**注册环节:**识别虚假注册、减少“羊毛党”能够使用的账号量。在注册环节识别虚假注册的账号,并进行拦截和打击。
2.**登录场景:**提高虚假账号登录门槛,从而减少能够到达活动环节的虚假账号量。比如,登录环节通过验证码、短信验证码等手段来降低自动机的登录效率,从而达到减少虚假账号登录量、减轻活动现场安全压力的目的。
3.**活动环节:**这个是防刷单对抗的主战场,也是减少“羊毛党”获利的直接战场;这里的对抗措施,一般有两个方面: 1)通过验证码(短信、语音)降低黑产刷单的效率。
2)大幅度降低异常账号的优惠力度。
电商防刷架构
一. 腾讯防刷总体架构
面对疯狂的黑产刷单,按照对抗刷单的思路,基于腾讯的多维度大数据,腾讯专门设计并构建了电商防刷技术体系。总体防刷架构见下图3:
图3:腾讯防刷单技术架构
从上面的总体架构我们可以看到,整个系统分三层:接入层、实时计算层、存储&挖掘层。整个系统充分利用了腾讯海量业务生态中的多维度大数据,以及全方位的风控经验与能力。接下来我们详细介绍其中的一些核心模块。
二.防刷单技术架构核心模块与框架介绍
1.风险学习引擎
对于风险学习引擎而言,效率问题非常关键。该模块线上采用的都是C++实现的DBScan等针对大数据的快速聚类算法,性能卓越;而且主要的工作都是线下进行,所以线上系统也不存在学习的效率问题。
风险学习引擎采用了黑/白双分类器风险判定机制,可以很好地减少对正常用户的误伤。例如,某个IP是恶意的IP,那么该IP上可能会有一些正常的用户,比如大网关IP。再比如,黑产通过ADSL拨号上网,那么就会造成恶意与正常用户共用一个IP的情况。
其中,黑分类器风险判定是根据特征、机器学习算法、规则/经验模型,来判断本次请求异常的概率。而白分类器风险判定则是判断属于正常请求的概率。见下图4示意:
图4 风险引擎的宏观构成
2.分类器逻辑框架
如何实现风险评估的判定呢?我们以黑分类器为例,来详细剖析下分类器的逻辑框架。
系统总体是采用一种矩阵式的逻辑框架。
黑分类器最初设计是整体检测判定,即按需随意地建立一个个针对黑产的检测规则、模型。但这种设计出来的结果,发现不是这个逻辑漏过了,而是那个逻辑误伤量大,要对某一类的账号加强安全打击力度,改动起来也非常麻烦。
因此,我们最终设计出一套矩阵式的框架(见下图5),较好地解决上述问题。
图5 黑分类器的矩阵逻辑框架
矩阵的横向采用了Adaboost方法,该方法是一种迭代算法,其核心思想是针对同一个训练集训练不同的弱分类器,然后把这些分类器集合起来,构成一个最终的分类器。而我们这里每一个弱分类器都只能解决一种帐号类型的安全风险判断,集中起来才能解决所有账户的风险检测。
这个矩阵逻辑的横向方法,在工程实现上也带来三个好处:
1.便于实现轻重分离。比如某平台虚假账号集中在邮箱账号,策略就可以加大对邮箱账号的打击力度,影响范围也局限在邮箱帐号,而不是该平台所有的账号。
2.减少模型训练的难度。模型训练最大的难度在于样本的均衡性问题,拆分成子问题,就不需要考虑不同账号类型之间的数据配比、均衡性问题,大大降低了模型训练时正负样本比率的问题。
3.逻辑的健壮性。某一个分类器的训练出现了问题,受影响的范围不至于扩展到全局。
矩阵纵向则采用了Bagging方法,该方法是一种用来提高学习算法准确度的方法。它在同一个训练集合上构造预测函数系列,然后设法将他们组合成一个预测函数,从而来提高预测结果的准确性。
腾讯大数据处理平台-魔方
毫无疑问,对抗黑产刷单离不开大数据。大数据一直在安全对抗领域发挥着重要的作用,从我们的对抗经验来看,大数据不仅仅是数据规模很大,而且还包括两个方面:
1.数据广度:要有丰富的数据类型。比如,不仅仅要有社交领域的数据、还要有游戏、支付、自媒体等领域的数据,这样就提供了一个广阔的视野让我们来看待黑产的行为特点。
2.数据深度:黑产的对抗,我们一直强调纵深防御。不仅仅要有注册数据,还要有登录,以及账号的使用的数据,这样我们才能更好的识别恶意。
所以想要做风控和大数据的团队,一定要注意在自己的产品上多埋点,拿到足够多的数据,先沉淀下来。
腾讯安全团队研发了一个叫魔方的大数据处理和分析的平台,底层集成了MySQL、MongoDB,Spark、Hadoop等技术,在用户层面我们只需要写一些简单的SQL语句、完成一些配置就可以实现例行分析。
这里我们收集了社交、电商、支付、游戏等场景的数据,针对这些数据我们建立一些模型,发现哪些是恶意的数据,并且将数据沉淀下来。
沉淀下来的对安全有意义的数据,一方面就存储在魔方平台上,供线下审计做模型使用;另一方面会做成实时的服务,提供给线上的系统查询使用。
一.腾讯用户画像沉淀方法
用户画像,本质上就是给账号、设备等打标签。但我们这里主要从安全的角度出发来打标签,比如IP画像,我们会标注IP是不是代理IP,这些对我们做策略是有帮助的。
我们看看腾讯的IP画像,目前沉淀的逻辑如下图6:
图6 IP画像系统构成
一般的业务都有针对IP的频率、次数限制的策略,那么黑产为了对抗,必然会大量采用代理IP来绕过限制。既然代理IP的识别如此重要,那我们就以代理IP为例来谈下腾讯识别代理IP的过程。
识别一个IP是不是代理IP,技术不外乎就是如下四种:
1.反向探测技术:扫描IP是不是开通了80,8080等代理服务器经常开通的端口,显然一个普通的用户IP不太可能开通如上的端口。
2.HTTP头部的X_Forwarded_For:开通了HTTP代理的IP可以通过此法来识别是不是代理IP;如果带有XFF信息,该IP是代理IP无疑。
3.Keep-alive报文:如果带有Proxy-Connection的Keep-alive报文,该IP毫无疑问是代理IP。
4.查看IP上端口:如果一个IP有的端口大于10000,那么该IP大多也存在问题,普通的家庭IP开这么大的端口几乎是不可能的。
以上代理IP检测的方法几乎都是公开的,但是盲目去扫描全网的IP,被拦截不说,效率也是一个很大的问题。
因此,我们的除了利用网络爬虫爬取代理IP外,还利用如下办法来加快代理IP的收集:通过业务建模,收集恶意IP(黑产使用代理IP的可能性比较大)然后再通过协议扫描的方式来判断这些IP是不是代理IP。每天腾讯都能发现千万级别的恶意IP,其中大部分还是代理IP。
二.腾讯用户画像类别概览
腾讯用户画像的维度与类别很多,这里仅举部分用户画像数据来说明。比如用户画像其中有手机画像和QQ画像这两个重要类别。涉及画像的标签见下图7:
以QQ的画像为例,比如,一个QQ只登录IM、不登录其他腾讯的业务、不聊天、频繁的加好友、被好友删除、QQ空间要么没开通、要么开通了QQ空间但是评论多但回复少,这种号码我们一般会标注QQ养号(色情、营销),类似的我们也会给QQ打上其他标签。
标签的类别和明细,需要做风控的人自己去设定,比如:地理位置,按省份标记。性别,按男女标记。其他细致规则以此规律自己去设定。
三. 风险判定的基础逻辑
有了用户画像的基础数据,我们就可以进行风险判定了。腾讯风险判定的系统现已开放为腾讯云的能力,即天御系统。我们来看一下风险判定的基础逻辑,见下图8:
图8 腾讯云天御系统防御逻辑示意图
实时防御系统使用C/C++开发实现,所有的数据通过共享内存的方式进行存储,相比其他的系统,安全系统更有他自己特殊的情况,因此这里我们可以使用“有损”的思路来实现,大大降低了开发成本和难度。
但这里在安全策略方面,可能会面临一个挑战:多台机器,使用共享内存,如何保障数据一致性?其实,安全策略不需要做到强数据一致性。
从安全本身的角度看,风险本身就是一个概率值,不确定,所以有一点数据不一致,不影响全局。但是安全系统也有自己的特点,安全系统一般突发流量比较大,我们这里就需要设置各种应急开关,而且需要微信号、短信等方式方便快速切换,避免将影响扩散到后端系统。
电商企业接入天御系统,启用带防刷能力的业务架构
通过剖析腾讯对抗“羊毛党”刷单的防刷系统技术架构与原理,我们了解到了天御系统可以帮助咱们电商企业在促销、优惠活动时,有效打击黑产刷单团伙。特别是马上到来的双十一“购物狂欢节”,该系统将让广大电商企业更好地服务用户,真正为用户带来贴心的实惠与好处。
天御系统可适应的场景包括但不限于: 电商o2o刷单、刷券、刷红包 防止虚假账号注册 防止用户名、密码被撞库 防止恶意登录
那么如何接入并使用天御系统呢?其实,电商企业接入天御系统仅需要四步,见下图9:
其中:第一步离线数据分析与第二步搭建实时模型为前期的准备工作,第三步正式接入其实并不耗费多少时间,不过第四步上线后,还需要持续的优化,以进一步提高对抗的能力。
图9 企业接入天御系统步骤
业务接入天御系统后的架构图,见下图10:
图10 业务接入天御防刷后的架构图
从上图可以看到,接入天御防刷是旁路接入,不需要调整现有业务任何核心逻辑、关键流程,可以快速上线。另外,运行过程中,即使天御防刷有任何异常也不会影响业务主逻辑。
Q&A
Q:风险学习引擎是自研的,还是使用的开源库?
风险学习引擎包括两个部分,线上和线下两部分:
线上:自己利用c/c++来实现。
线下:涉及利用python开源库来做的,主要是一些通用算法的训练和调优。
Q:请问魔方平台中用到的MongDB是不是经过改造?因为MongDB一直不被看好,出现问题也比较多。
我们做了部分改造,主要是DB的引擎方面。
Q:请问黑分类器和白分类器有什么区别?
白分类器主要用来识别正常用户,黑分类器识别虚假用户。
Q:风险概率的权重指标是如何考虑的?
先通过正负样本进行训练,并且做参数显著性检查;然后,人工会抽查一些参数的权重,看看跟经验是否相符。
Q:安全跟风控职责如何区分呢?
相比安全,风控的外延更丰富,更注重宏观全局;针对一个公司来讲,风控是包括安全、法务、公关、媒体、客服等在内一整套应急处理预案。
Q:如果识别错了,误伤了正常用户会造成什么后果么?比如影响单次操作还是会一直失败。
如果识别错了正常用户不会被误伤,但是会导致体验多加了一个环节,如弹出验证码、或者人工客服核对等。 作者:颜国平,原腾讯云-天御系统研发负责人。一直负责腾讯自有验证码、业务安全、防刷、账号安全等研发工作。内部支持的产品(游戏、电商、腾讯投资的O2O企业)非常广泛。在业务安全领域项目经验丰富,并且具备深度学习、大数据架构搭建等实战经验。
此文已由腾讯云+社区在各渠道发布
获取更多新鲜技术干货,可以关注我们 腾讯云技术社区-云加社区官方号及知乎机构号
云计算
2019-01-21 11:54:00
在媒体行业的转型升级中,云计算的出现多维度促进了媒体融合,打破传统媒体行业与新媒体的界限和竞争格局,在媒体素材管理、移动端功能演进的过程中扮演着重要角色,颠覆了传统媒体新闻采编、发布及传播的方式。
七牛云平台可全方位为媒体行业赋能,比如:
· 海量数据的存储承载
· 视频结构化技术支持
· 直播与点播功能助力
· 人工智能的创新应用
传统媒体在移动互联网的冲击挑战下纷纷转型升级,开始打造移动新媒体平台,以此不断扩展业务范围。其中,中国网作为媒体代表,推出了以中国网客户端为核心的多语种移动产品矩阵和中国网直播等品牌栏目,自 2016 年起,就选择与七牛云达成深度合作,借力云端实现升级转型。
媒体平台的云上升级之路
中国网是国务院新闻办公室领导的重点新闻网站,是国家重大活动、各大部委新闻发布会、全国「两会」新闻中心指定的网络报道和直播媒体。自成立以来,逐步实现了利用近 10 个语种、 11 个文版,24 小时对外发布信息,访问用户覆盖全球 200 多个国家和地区。
截至 2018 年,中国网的移动用户体量突破千万,日活 800 万+,每日输出稿件达 15000 篇。日益增长的用户数量和业务的拓展,使得传统 IT 架构很难适应目前业务的快速发展。利用丰富、灵活、可扩展、安全可靠的服务资源来更好地满足业务快速增长的需求,是中国网转型升级迫在眉睫的选择。
计算、存储高可靠,业务响应无延迟
作为了解中国的门户,中国网需不间断对外发布多语言、多版本的新闻资讯,随着业务的发展,这对于服务器的压力愈发增大,庞大的资料和媒体素材也需要大量的存储空间。当重大事件发生后,海量用户的同步访问对于运维的挑战也逐步升级。如何在成本可控的范围内,提高供稿效率,提升海内外用户访问中国网供稿服务站的速度,是中国网业务发展所遇到的技术难题。
中国网选择使用七牛云主机与云数据库服务部署各项后端服务,保障其业务稳定、安全、可靠运行。七牛云主机利用虚拟化的方式,为中国网提供了可灵活扩展的计算资源,缩短业务部署上线以及扩展升级的周期,并大大降低运维成本。同时,中国网将移动端数据存储至七牛云,用户访问速度,媒体素材加载速度获得大幅提升。在新闻内容不断拓展的基础上,保障了数据安全性与业务的稳定不间断,大大增强了中国网的品牌竞争力及国际化的传播能力。
手机直播大事件,新闻现场不缺席
新闻传播不限于以图配文的传统稿件形式,在直播风口上,将重大新闻事件以视频形式实时直播,从而做到更高效快速地触达更多人群,让用户真正所见即所得。中国网在 2016 年,借力七牛云提供的直播云服务,成为中国最大的政务直播平台。
七牛云直播云服务基于强大的全球化流网络,打造了端到端直播解决方案,提供低延迟、稳定流畅、高可用的一站式直播云服务。直播 SDK 接入,使得驻外记者可以在任何时间、任何地点,快速、灵活地进行新媒体直播,大大提高新闻传播的时效性。同时,七牛云全网融合 CDN 节点实现了稳定的同步直播、同步互动,极大优化了海内外用户观看直播的体验。
媒体行业新实践,AI 蕴藏大能量
除了移动端接入、直播功能拓展之外,基于用户个性化需求而产生的推荐阅读正逐渐成为媒体平台独特的竞争优势,只有通过对存储数据进行大数据及 AI 分析,才能从海量信息中找到真正的满足用户个性化需求的内容,对此中国网也进行了大量的尝试与探索。
在 2018 年两会期间,中国网曾与七牛云进行合作,根据用户特征研究样本信息,推送个性内容。在云存储与大数据相结合的基础上,将 AI 技术创新运用于新闻之中,推出「刷脸看新闻」这款产品,用户通过七牛云 AI 人脸识别,扫描分析用户特征,为其推送具有匹配度的文章,扩宽了新闻阅读的方式,增强了用户体验感。此款「刷脸看新闻」产品,日后也有望在中国网客户端上线。
中国网坚持以新闻为前导,以国情为基础,通过精心整合即时新闻和独家的深度报道,以多语种、多媒体形式,向世界及时全面地介绍中国,其业务的有序运作背后离不开七牛云的技术支持。云计算正在为媒体平台提供技术赋能,七牛云愿与媒体行业携手,通过成熟、先进、可靠的云+智能技术,共同推进媒体融合升级转型,探索前沿技术在新媒体传播中的实践和应用。
云计算
2019-01-21 10:49:00

ZStack的 系统测试 系统在真实的硬件环境中运行测试用例;像集成测试一样,这个系统测试也是全自动的,而且覆盖的层面包括:功能性测试、压力测试、性能测试。

概述
虽然集成测试系统,如我们在ZStack—自动化测试系统1:集成测试中所介绍的,强大到可以暴露开发过程中大多数的缺陷,也是有着固有的弱点的。首先,由于测试用例使用模拟器,它们不能测试真实场景,比如在一个物理的KVM主机上创建一个VM。第二,集成测试用例主要关注一个简单的场景,在一个简单的人造的环境中;举个例子,还是创建VM的这个用例,它可能只部署一个最小的环境,包括一个主机和一个L3网络,仅仅用于满足创建一个VM的需求。这些弱点,然而也是深思熟虑过的,因为我们想要开发人员能够在他们开发新特性时快速和容易地写测试用例,这是一个我们必须采取的权衡。

系统测试,目标在于测试整个软件,在一个真实的、复杂的环境中,很自然地补充集成测试。ZStack的系统测试系统被设计用于以下两个目标: 复杂的场景:这些场景应该比真实世界的使用场景更复杂,以测试软件的极限。举个例子,挂载和卸载磁盘的测试用例应该持续地、重复地对虚拟机执行,以一种非常快,人类无法手动做到的方式。 易于编写和维护测试用例:就像集成测试系统,系统测试系统接管了大多数无聊重复的任务,让测试人员有效率地写测试用例。

这个系统测试系统是一个Python项目,命名为zstack-woodpecker,由以下三个部分组成: 测试框架:一个测试框架,管理所有的测试用例,以及提供必须的库和工具。 环境部署工具:一个工具,用于从XML配置文件部署一个环境;它非常类似于集成测试系统的部署器。 模块化测试用例:测试用例是高度模块化的,而且覆盖了:功能测试、性能测试和压力测试。

系统测试
zstack-woodpecker完全由我们自己创建;在决定重新造这个轮子之前,我们试过了流行的Python测试框架,像nose,然后最终选择了创造一个新的工具,用以最大化地满足我们的目标。

套件配置
类似所有的其他测试框架,一个zstack-woodpecker中的测试套件是以suite setup开始,以suite teardown结束,在其中有一些测试用例。这里的suite setup和suite teardown是两个特殊的测试用例,suite setup负责准备后续的测试用例所需的环境,suite teardown负责在所有测试用例结束之后清理这个环境。一个典型的测试套件配置文件看起来像:

敏锐的读者可能会注意到一些参数是在其他的测试框架中看不到的。
第一个是timeout;每一个测试用例可以定义自己的超时时间,如果在这段时间内不能完成,它将被在最终的结果里被标记成超时。
第二个是repeat,允许你在测试套件中指定这个用例应该被执行多少次。
第三个,也是杀手级的参数是parallel,允许测试人员设定这个套件的并行级别;这是一个使得zstack-woodpecker运行测试用例非常快的关键特性;在上面这个例子中,parallel被设置成8,这意味着将有至多8个用例在同时运行;这不只是加速运行测试用例,也创造了一个复杂的场景,模拟许多用户在共享同一个环境时执行不同的任务。然而,不是所有的用例都可以被同时执行;在我们的例子中,用例test_delete_l2.py将会删除被其他用例依赖的L2网络,所以在其他用例执行时,它不能被执行;这就是第四个参数noparallel发挥作用的地方;一旦它被设置成true,这个用例将会单独被执行,不会有其他用例可以同时运行。

命令行工具
zstest.py是一个命令行工具,用于帮助测试人员控制测试框架,执行任务,像启动测试套件,列出测试用例,等等。zstest.py提供了丰富的选项帮助测试人员简化他们的工作。这些选项中的一些,用于在我们的日常测试中,特别有用,列在了下面。
测试人员可以通过选项-l获取可用的测试用例,例如:./zstest.py -l
它将会展示如下的结果:

测试套件名,是测试用例的第一级文件夹的名称;例如,在上图中你看到了大量的用例以basic开头(例如:basic/test_reboot_vm.py),是的,basic就是这个测试套件的名字。测试人员可以通过选项-s启动一个套件,使用套件名的全称或者部分都行,只要它是独一无二的,例如:./zstest.py -s basic 或 ./zstest.py -s ba

测试人员也可以选择性地执行测试用例,通过使用它们的名字或者ID,已经选项-c;例如:./zstest.py -c 1,6 或 ./zstest.py -c suite_setup,test_add_volume.py
记住,你需要运行suite setup的用例:suite_setup.py作为第一个用例,除非你已经这么做了。
由于一个测试套件将会执行所有的测试用例,清理环境,发出一个结果报告,测试人员有时可能想要停止测试套件,并在一个用例失败时保持环境,这样他们就可以深入查看失败结果并调试;选项-n和-S就是为此准备的;-n指示测试框架不要清理环境,-S要求跳过没有被执行的用例;例如:./zstest.py -s virtualrouter -n -S
另外,选项-b可以拉取最新的源代码并构建一个全新的zstack.war,这在Nightly测试中特别有用,这种测试被假定为测试最新的代码:./zstest.py -s virtualrouter -b
一旦所有的测试用例完成,一个报告将会被生成并被打印到屏幕上:
测试框架将会保存所有的日志,并直接输出每一个失败日志的绝对路径,如果存在的话。为了在一般的日志中记录更多的细节,有一种特殊的日志action log,用于记录每一个API调用;因为这是一个完全纯粹关于API的日志,我们可以容易地找到一个失败的根本来源,而不用被测试框架的日志分散注意力。另外,它是一种重要的工具,可以自动地生成一个新的用例用于重现失败,这是一个我们所使用的魔法武器,用于在基于模型的测试(每个用例都随机地执行各种API)中调试失败。你可以在ZStack--自动化测试系统3:基于模型的测试中找到细节。Action log的片段如下:

环境部署工具
类似于集成测试,对每一个测试用例来说,准备环境是频繁且重复的任务;例如,用于测试创建虚拟机的用例需要去配置独立的资源,像zone,cluster,host等等。Zstack-woodpecker调用zstack-cli,这个ZStack的命令行工具去从一个XML配置文件部署测试环境。例如:zstack-cli -d zstack-env.xml
这里的XML配置文件的格式类似于集成测试所用的,一个片段看起来像这样:
部署工具通常在运行任何用例前被suite setup调用,测试人员可以在XML配置文件中通过以$符号开头来定义变量,然后在一个独立的配置文件中解析。通过这种方式,这个XML配置文件像模板一个工作,可以产生不同的环境。配置文件的例子如下:
注意:正如你可能会猜测的,这个工具可以被管理员用于从一个XML配置文件部署一个云环境;更进一步,管理员们做相反的事情,将一个云环境写入到一个XML配置文件,通过zstack-cli -D xml-file-name.
对于性能和压力测试,环境通常需要大量的资源,例如100个zone,1000个cluster。为了避免手动在配置文件中重复1000行,我们引入了一个属性duplication,用于帮助创建重复的资源。例如:
这段不翻译了。

模块化的测试用例
在系统测试中测试用例可以被高度模块化。每一个用例本质上执行以下三步: 创建要被测试的资源 验证结果 清理环境
Zstack-woodpecker 本身提供一个完整的库用于帮助测试人员调度这些活动。API也很好地被封装在一个,从zstack源代码自动生成的库中。测试人员不需要去写任何的原生的API调用。检查器,用于验证测试结果,也已为每一个资源创建;例如,VM检查器,云盘检查器。测试人员可以很容易地调用这些检查器去验证他们创建的资源,而不需写成顿成吨的代码。如果当前检查器不能满足某些场景,测试人员也能创建自己的检查器,并作为插件放入测试框架。
一段测试用例看起来像:
像集成测试一样,测试人员可以仅以十几行便写出一个测试用例。模块化不只是帮助简化测试用例的编写,也为基于模型的测试构建了一个坚实的基础,下篇文章我们会详细讨论。

总结
在这篇文章中,我们引入了我们的系统测试系统。通过执行比现实世界的用例更复杂的测试,系统测试可以给我们更多的自信,关于ZStack在真实的硬件环境中的表现。使得我们可以快速进化成一个成熟的产品。
云计算
2019-01-21 10:30:00
在上篇文章里,我们分享了CNCF为广大Kubernetes用户建议的9项Kubernetes安全最佳实践,分享了用户使用Kubernetes管理集群时的9个能进一步确保集群安全的基本操作。
上篇文章中的建议非常好,但不足之处在于它们都过于依赖GKE了。对于那些使用谷歌服务的用户来说,GKE固然是一个很好的解决方案。然而,还有更多的人则是在亚马逊、Azure、阿里云、华为云、DigitalOcean、甚至是他们自己的基础设施上或其他他们任何想在的地方上运行着Kubernetes集群,那么此时,GKE相关的解决方案对他们而言并没有太大帮助。
对于这些用户而言,Rancher作为一个开源的解决方案,是一个很棒的选择。
Rancher Labs对待安全问题十分严肃谨慎。Rancher Labs联合创始人及首席架构师Darren Shepherd,是2018年年底Kuberntes 被爆出的首个严重安全漏洞(CVE-2018-1002105)的发现者。安全性不应该是事后的想法,也不应该是部署了不安全的集群之后才记得要去做的事。就像你建造房子时,不应该把所有物品都搬进去之后,才开始安装门锁。
在本文中,我将回顾上篇文章中CNCF提出的每个要点,并向您分析Rancher和RKE能如何在默认设置中满足这些安全建议。
升级到最新版本
这是一个合理的建议,并且不仅适用于Kubernetes。因为未修补的程序常常是攻击者的切入点。当某个安全漏洞出现、poc代码公开可用时,Metasploit之类的工具套件很快就会在其标准套件中包含这些漏洞。此时,任何会从Internet复制和粘贴命令的人都可以控制您的系统。
使用Rancher Kubernetes Engine(RKE)时,无论是单独使用还是和Rancher一起使用,您都可以选择要安装的Kubernetes版本。Rancher Labs使用原生上游Kubernetes,这使公司能够快速响应安全警报,发布修复版本的软件。因为RKE是在Docker容器中运行Kubernetes组件的。运维团队可以对关键基础架构进行零停机升级。
您可以通过Rancher的GitHub主页、官网等各个渠道接收有关新版本发布的信息。我还强烈建议您在升级之前,先在staging环境中测试新版本。如果升级出错,Rancher也可以轻松回滚到以前的版本。
启用基于角色的访问控制(RBAC)
安装RKE后,RBAC会默认启动。如果您只使用RKE或任何其他独立的Kubernetes部署,则您需要负责配置帐户、角色和绑定以保护您的集群。
如果您正在使用Rancher,它不仅会安装安全集群,还会通过Rancher服务器,代理与这些集群的所有通信。Rancher可以插入许多后端身份验证程序,例如Active Directory、LDAP、SAML、Github等。当以这种方式连接时,Rancher使您能够将现有的企业身份验证扩展到Rancher的保护伞下的所有Kubernetes集群,无论这些集群在哪里运行。
Rancher在全局、集群和项目级别启用角色,使管理员可以在一个位置定义角色并将其应用于所有集群。这种RBAC-by-default和强大的身份验证和授权控制的组合意味着从使用Rancher或RKE部署集群的那一刻起,集群就是安全的。
使用命名空间建立安全边界
由于Kubernetes处理默认命名空间的特殊方式,我不建议您使用它。我建议您为每个应用程序创建一个命名空间,将它们定义为逻辑组。
Rancher定义了一个名为Project的附加抽象层。Project是命名空间的集合,可以在其上映射角色。用户可能有权访问某一Project,但他们无法看到任何他们无权访问的Project中运行的任何工作负载,也无法与其进行交互。这样一来,其实就是有效地创建了单集群多租户。
使用Projects,管理员可以更轻松地授予对单个集群中多个命名空间的访问权限。它最大限度地减少了重复配置以及人为错误。
将敏感工作负载彼此分开
这是一个很好的建议,因为它假定了一个问题,“如果工作负载受到损害会发生什么?”。提前采取行动可以减少破坏地范围使攻击者更难以升级权限,但也并不是完全不可能。所以这可能得花费您额外的时间处理。
Kubernetes允许您设置污点(taints)和容差(torlerations),从而控制可能部署Pod的位置。
Rancher还允许您通过Kubernetes标签控制工作负载的调度。除了污点和容差之外,在部署工作负载时,您可以为主机设置 必须、应该或可以 具有的标签,这些标签会控制Pod的部署位置。 如果您的环境是静态的,您还可以将工作负载安排到特定节点。
安全的云元数据访问
该建议指出,敏感的元数据“有时可能被盗或被滥用”,但未能概述“何时”或“如何”的条件。上篇文章中提到了Shopify的赏金细节的泄露, 2018年12月13日的北美KubeCon上提到了这一事件。虽然上篇文章指出GKE具有“元数据隐藏”的功能,但值得注意的是,在最开始泄露凭据的服务,正是Google Cloud元数据API。
此外,没有任何证据显示任何其他云提供商存在相同的漏洞。
此漏洞可能存在的唯一位置是托管的Kubernetes服务,例如GKE。如果您直接或通过Rancher将RKE部署到裸机或云计算实例上,您将最终得到一个无法通过云提供商的元数据API泄露凭据的集群。
如果您正在使用GKE,我建议您激活此功能以防止任何凭据通过元数据服务泄漏。我还认为云提供商不应该将凭证嵌入到可通过API访问的元数据中。即使这样做是为了方便,但这是一种不必要的风险,可能会产生难以想象的后果。
创建和定义集群网络策略
直接部署或由Rancher部署的RKE集群默认使用Canal,当然,您也可以选择Calico或Flannel。Canal和Calico都支持网络策略。当使用Canal作为网络提供商时,Rancher部署的集群也支持Project网络策略。激活后,工作负载可以与其项目中的其他工作负载通信,而系统项目(包括入口控制器等集群范围的组件)可以与所有项目进行通信。
早期版本的Rancher默认启用Project网络策略,但这给一些不了解额外安全性的用户造成了混乱。因此,为了给用户提供最佳体验,此功能现在默认情况下已关闭,但如果您想启用,也可以在启动后轻松激活。
运行集群范围的Pod安全策略
Pod安全策略(PSP)控制Pod必须具有某些功能和配置才能在集群中运行。例如,您可以阻止特权模式、主机网络或以root身份运行容器。通过Rancher或RKE安装集群时,您可以选择是否要默认启用受限制的PSP。如果选择启用它,则您的集群将立即对工作负载权限强制实施强制限制。
受限制的和不受限制的PSP在RKE和Rancher中是相同的,因此它们在安装时激活的内容是一样的。Rancher允许无限数量的额外PSP模板,所有这些都可以在全局范围内处理。管理员定义PSP,然后将它们应用于Rancher管理的每个集群。与前面讨论的RBAC配置类似,它将安全配置保存在一个位置,并大大简化了策略的配置和应用。
加强节点安全
这不是Kubernetes特定的建议,而是一个很好的普适策略。当要与您无法控制的流量进行交互时(例如,在Kubernetes中运行的应用程序的用户点击量),应该让其在攻击面较小的节点上运行。此外,禁用和卸载不需要的服务也是必要的。还有,应该通过SSH限制root访问权限并需要sudo密码加密。在SSH密钥上使用密码短语,或使用2FA、U2F密钥或Krypton等服务将密钥绑定到用户拥有的设备。 以上这些是安全系统的基本标准配置示例。
除了受支持的Docker版本之外,Rancher在主机上不需要其他。并且,RKE只需要SSH访问,它将在继续安装Kubernetes之前安装Kubernetes支持的最新版本的Docker。
如果您想进一步减少攻击面,可以了解一下RancherOS,这是一个轻量级Linux操作系统,可以将所有进程作为Docker容器运行。System Docker仅运行提供访问所需的最少数量的进程,并在用户空间中为实际工作负载运行Docker实例。
启用审核日志(Audit Logging)
Rancher服务器可在RKE集群内部运行,因此除了Kubernetes审核日志之外,激活对服务器本身的API调用的审核日志也很重要。此日志将显示用户对任何集群执行的所有操作,包括发生的事件、执行操作的人员、执行操作的时间以及执行操作的集群。从有问题的服务器发送这些日志也很重要。Rancher可以连接到Splunk、Elasticsearch、Fluentd、Kafka或任何系统日志端点,您可以从中生成可疑活动的仪表盘和警报。
有关为Rancher 服务器启用审核日志的信息,请参阅我们的文档。
( https://rancher.com/docs/rancher/v2.x/en/admin-settings/api-audit-log/ )
有关为RKE集群启用审核日志的信息,请参阅下一节。
安全保障行动正在进行中
真正保护Kubernetes集群需要9项以上的操作,Rancher有一份安全强化指南( https://rancher.com/docs/rancher/v2.x/en/faq/security/ )和一份自我评估指南( https://releases.rancher.com/documents/security/latest/Rancher_Benchmark_Assessment.pdf ),涵盖了CIS基准用于保护Kubernetes的100多种控制。
如果您十分在意安全性,那么Rancher、RKE以及RancherOS将会帮助您。
云计算
2019-01-29 09:53:00
本文介绍Sidecar模式的特点,及其应用的场景。熟悉 Native Cloud 或者 微服务 的童鞋应该知道,在云环境下,技术栈可以是多种多样的。那么如何能够将这些异构的服务组件串联起来,成为了服务治理的一个重大课题。而Sidecar模式为服务治理,提供了一种解决方案。
将应用程序的组件部署到单独的进程或容器中,以提供隔离和封装。此模式还可以使应用程序由异构组件和技术组成。
这种模式被称为Sidecar,因为它类似于连接到摩托车的边车。在该模式中,边车附加到父应用程序并为应用程序提供支持功能。 sidecar还与父应用程序共享相同的生命周期,与父项一起创建和退役。边车图案有时被称为搭接图案并且是分解图案。
问题背景
应用程序和服务通常需要相关的功能,例如监控、日志、集中化配置和网络服务等。这些外围任务可以作为单独的组件或服务来实现。
如果它们紧密集成到应用程序中,它们可以在与应用程序相同的进程中运行,从而有效地使用共享资源。但是,这也意味着它们没有很好地隔离,并且其中一个组件的中断可能会影响其他组件或整个应用程序。此外,它们通常需要使用与父应用程序相同的语言或者技术栈来实现。因此,组件和应用程序彼此之间具有密切的相互依赖性。
如果将应用程序分解为服务,则可以使用不同的语言和技术构建每个服务。虽然这提供了更大的灵活性,但这意味着每个组件都有自己的依赖关系,并且需要特定于语言的库来访问底层平台以及与父应用程序共享的任何资源。此外,将这些功能部署为单独的服务可能会增加应用程序的延迟。管理这些特定于语言的接口的代码和依赖关系也会增加相当大的复杂性,尤其是对于托管、部署和管理服务。
解决方案
上述问题的解决方案是,将一组紧密结合的任务与主应用程序共同放在一台主机(Host)中,但会将它们部署在各自的进程或容器中。这种方式也被称为“Sidecar(边车)模式”。
下图展示了任务与主应用程序的部署关系图。
Sidecar模式
边车服务不一定是应用程序的一部分,而是与之相关联。它适用于父应用程序的任何位置。Sidecar支持与主应用程序一起部署的进程或服务。这就像是如下图所示的边三轮摩托车那样,将边车安装在一辆摩托车上,就变成了边三轮摩托车。每辆边三轮摩托车都有自己的边车。类似同样的方式,边车服务共享其父应用程序的主机。对于应用程序的每个实例,边车的实例被部署并与其一起托管。
使用边车模式的优点包括: 在运行时环境和编程语言方面,边车独立于其主要应用程序,因此不需要为每种语言开发一个边车。 边车可以访问与主应用程序相同的资源。例如,边车可以监视边车和主应用程序使用的系统资源。 由于它靠近主应用程序,因此在它们之间进行通信时没有明显的延迟。 即使对于不提供可扩展性机制的应用程序,也可以使用边车通过将其作为自己的进程附加到与主应用程序相同的主机或子容器中来扩展功能。
Sidecar模式通常与容器一起使用,并称为边车容器。有关容器方面的内容,可以参阅 https://waylau.com/ahout-docker/ 。
Sidecar模式的实现
Spring Cloud Netflix Sidecar框架提供了Sidecar模式的现成解决方案。Spring Cloud Netflix Sidecar框架框架可以提供对其他非Spring Cloud技术栈的微服务的治理。比如,你可以使用Node或者Golang编写一个Web项目,这个服务同样可以以Sidecar模式,纳入到Spring Cloud管理中去。
下面是实现步骤。
1. 为Web项目添加健康检查接口
提供REST接口,返回JSON格式内容 {"status" : "up"} 。其中status用于描述微服务的状态,常见的取值有UP、DOWN、OUT_OF_SERVICE和UNKNOWN等。
2. 编写Sidecar微服务
创建项目,添加Eureka、Sidecar的依赖: org.springframework.cloud spring-cloud-netflix-sidecar org.springframework.cloud spring-cloud-starter-eureka
启动类上加上@EnableSidecar注解。这是一个组合注解,它整合了三个注解,分别是@EnableCircuiBreaker和@EnableDiscoveryClient。
在配置文件中加入端口号、服务名称、Eureka地址以及Web项目的端口以及健康检查地址,如: server.port=8887 spring.application.name=sidecar-mylife-service eureka.client.serviceUrl.defaultZone=http://localhost:8881/eureka/ eureka.client.instance.prefer-ip-address=true sidecar.port=8080 sidecar.health-uri=http://localhost:8080/health eureka.instance.hostname=localhost
启动项目,并访问8887接口,就可以访问到Web项目中的接口。
3. Sidecar的一些端点
以下是Sidecar的常用端点: /hosts/{serviceId} 指定微服务在Eureka上的实例列表 /ping 返回OK字符串 /{serviceId} 请求对应的微服务
4. 部署应用
将Sidecar与Web服进行部署。一般是部署在相同的主机里面。
有关Spring Cloud的更多内容,可以参阅 Spring Cloud 教程 。
参考引用 原文同步至 https://waylau.com/sidecar-pattern/ Spring Cloud 微服务架构开发实战: https://github.com/waylau/spring-cloud-microservices-development Spring Boot 企业级应用开发实战: https://github.com/waylau/spring-boot-enterprise-application-development https://docs.microsoft.com/en-us/azure/architecture/patterns/sidecar
云计算
2019-01-28 23:55:00
基本介绍
网络环境比较复杂、网速不稳定,Aliplayer提供了多分辨率播放的模式,用户可以手工切换分辨率和播放器选择最优分辨率,基本UI如下:
Source模式
source的方式指定多个清晰度的地址,这个模式在直播场景下使用的比较多,阿里云直播服务可以提供多码率的播放流地址,详细请参考: 视频直播转码 。Aliplayer提供了下面清晰度的对应关系:
Code Text OD 原画
FD 流畅
LD 标清
SD 高清
HD 超清
2K
4K
2K
4K
source如何支持多清晰度,参考下面的代码:
var player = new Aliplayer({ id: "player-con", isLive:true, source:'{"HD":"https://livecdn.com/appname/testhd.flv", "SD":"https://livecdn.com/appname/testsd.flv", "FD":"https://livecdn.com/appname/testfd.flv", "LD":"https://livecdn.com/appname/testld.flv" }', width: "100%", height: "500px", autoplay: true }, function (player) { console.log("播放器创建成功"); });
videoId模式
播放点播服务的视频时,采用videoId的模式, Aliplayer会获取用户在点播服务转码生成的多分辨率的视频地址,生成多清晰度选择列表,详细参考 点播转码 。
如果用户转码生成多种视频格式文件,Aliplayer将会按照mp4->m3u8->flv的顺序优先选择播放,如果有加密视频和普通视频一起,点播服务将只会返回加密视频的播放地址。Aliplayer提供了其他一些属性可以做精确的获取点播视频的播放地址:
属性名称 类型 说明 format String 指定播放地址格式可选值为mp4、m3u8、flv、mp3,默认为空
mediaType String 指定返回音频还是视频,可选值为video和audio,默认为video,audio主要是针对只包含音频的视频格式
qualitySort String 指定排序方式,desc表示按倒序排序(即:从大到小排序),asc表示按正序排序(即:从小到大排序)默认值:‘asc’
definition
defaultDefinition
String
String
显示视频清晰度,多个用逗号分隔,比如:’FD,LD’,此值是vid对应流清晰度的一个子集,取值范围:FD(流畅)LD(标清)SD(高清)HD(超清)OD(原画)2K(2K)4K(4K)
默认播放视频清晰度,取值范围:FD(流畅)LD(标清)SD(高清)HD(超清)OD(原画)2K(2K)4K(4K),默认是上次播放时选择的清晰度
播放器的使用代码为:
var player = new Aliplayer({ id: "player-con", width: "100%", height: "500px", autoplay: true, language: "en-us", vid : '1e067a2831b641db90d570b6480fbc40', playauth:'ddddfdfdf' format:'m3u8', mediaType:'video', qualitySort:'desc', definition:'FD,LD', defaultDefinition:'LD' }, function (player) { console.log("播放器创建成功"); });
HLS的多码率
HLS HTTP Live Streaming 是Apple提出的基于http的流媒体传输协议,支持不同带宽的多码率地址,基本格式如下:
#EXTM3U #EXT-X-VERSION:4 #EXT-X-STREAM-INF:BANDWIDTH=454521,AVERAGE-BANDWIDTH=432061,CODECS="avc1.42c01e,mp4a.40.5",RESOLUTION=340x192,FRAME-RATE=25.000 GEPH-ONTHERECS02E012C-_E17101101_master_stream_x3f6b2fee5c3d4f388ed8edee586bd6f8_340.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=691401,AVERAGE-BANDWIDTH=644868,CODECS="avc1.42c01e,mp4a.40.5",RESOLUTION=384x216,FRAME-RATE=25.000 GEPH-ONTHERECS02E012C-_E17101101_master_stream_x3f6b2fee5c3d4f388ed8edee586bd6f8_384.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=926476,AVERAGE-BANDWIDTH=850169,CODECS="avc1.42c01f,mp4a.40.5",RESOLUTION=512x288,FRAME-RATE=25.000 #EXT-X-STREAM-INF:BANDWIDTH=7011961,AVERAGE-BANDWIDTH=6374698,CODECS="avc1.640028,mp4a.40.5",RESOLUTION=1920x1080,FRAME-RATE=25.000 GEPH-ONTHERECS02E012C-_E17101101_master_stream_x3f6b2fee5c3d4f388ed8edee586bd6f8_1920.m3u8
EXT-X-STREAM-INF:BANDWIDTH里的指定带宽,下面为此带宽使用对应分辨率的视频地址,Aliplayer解析上面的master m3u8内容,展示清晰度列表,并且会根据网络情况选择合适的清晰度播放:
阿里云的媒体处理服务支持多分变率的打包, 详细参考: 如何进行HLS打包
作者: 隽阜
原文链接
本文为云栖社区原创内容,未经允许不得转载。
云计算
2019-01-28 17:21:00
2018年下半年,UCloud首尔数据中心因外部原因无法继续使用,需要在很短时间内将机房全部迁走。为了不影响用户现网业务,我们放弃了离线迁移方案,选择了非常有挑战的机房整体热迁移。经过5个月的多部门协作,终于完成了既定目标,在用户无感知下,将所有业务完整迁移到同样位于首尔的新机房内。
本文将详述这个大项目中最有难度的工作之一:公共组件与核心管理模块迁移的方案设计和实践历程。
计划
整个项目划分为四个大阶段(准备阶段、新机房建设、新旧迁移、旧机房裁撤下线)。正如一位同事的比喻,机房的热迁移,相当于把一辆高速行驶高铁上的用户迁移到另一辆高速行驶的高铁上,高铁是我们的机房,高铁上的用户是我们的业务。要让迁移可行需要两辆高铁相对静止,一个方法是把两辆高铁变成一辆,如此两者速度就一致了。UCloud机房热迁移采用类似方案,把两个机房在逻辑上变成一个机房。为此,上层的业务逻辑要在新老机房间无缝迁移,下面的管理系统也要统一成一套。
其中,我们SRE和应用运维团队主要负责以下几个工作:1)机房核心zookeeper服务的扩缩容服务;2)核心数据库中间层udatabase服务的部署和扩容;3)大部分管理服务的部署和迁移;4)核心数据库的部署和迁移。以上涉及到前期规划、方案设计、项目实施、稳定性保证、变更校验等所有方面。
挑战
我们刚接到机房整体热迁移需求时,着实有些头疼,首尔机房属于较早期部署的机房之一,相关的技术架构比较老旧。而核心数据库、核心配置服务(zookeeper)、核心数据库中间层(udatabase)等几个服务都是比较重要的基础组件,老旧架构可能会因为基础层面的变动发生复杂的大范围异常,从而影响到存量用户的日常使用。
幸好SRE团队在过去一年里,针对各种服务的资源数据进行了全面的梳理,我们开发了一套集群资源管理系统(Mafia-RMS) ,该系统通过动态服务发现、静态注册等多种手段,对存量和增量的服务资源进行了整理,每一个机房有哪些服务、集群,某个集群有哪些服务器,每一个实例的端口、状态、配置等信息,都记录到了我们的资源管理系统中,如下图所示:
图1: UCloud SRE资源管理系统-集群管理功能
通过SRE资源管理系统,可以清楚地知道首尔机房存量内部服务的集群信息、每个实例的状态。我们基于SRE资源系统还构建了基于Prometheus的SRE监控体系,通过上图右侧Monitor按钮就可以跳转到监控页面,获取整个业务的实时运行状况。
有了这些资源数据之后,剩下的就是考虑怎么进行这些服务的扩容和迁移工作。
ZooKeeper服务的扩缩容
首先是内部服务注册中心zookeeper的扩缩容。
UCloud内部大规模使用zookeeper作为内部服务注册和服务发现中心,大部分服务的互访都是通过使用zookeeper获取服务注册地址,UCloud内部使用较多的wiwo框架(C++) 和 uframework (Golang) 都是基于主动状态机定时将自己的Endpoint信息注册到zookeeper中,相同Endpoint前缀的服务属于同一个集群,因此对于某些服务的扩容,新节点使用相同的Endpoint前缀即可。wiwo和uframework两个框架的客户端具备了解析zookeeper配置的能力,可以通过对Endpoint的解析获取到真实的IP和端口信息。然后通过客户端负载均衡的方式,将请求发送到真实的业务服务实例上去,从而完成服务间的相互调用。如下图所示:
图2:UCloud 首尔机房部署调用及服务注册/发现路径图
首尔老机房的zookeeper集群是一个具有3个节点的普通集群(当时规模相对较小,3个节点足够)。 然而首尔新机房的规模要大很多,因此新机房zookeeper的集群规模也要扩充,经过我们的评估,将新机房的zookeeper集群扩充到5个节点,基本上可以满足所需。
其实,一个理想的迁移架构应该是如图3所示,整个新机房使用和老机房相同的技术架构(架构和版本统一),新架构完全独立部署,与老机房并没有数据交互工作,而用户的业务服务(如UHost/UDB/EIP/VPC等)通过某种方式平滑的实现控制和管理面的迁移,以及物理位置的迁移工作。
图3:理想状态下的老旧机房服务迁移示意图
但是理想状态在现实中无法达到,内部架构和代码逻辑的限制,导致业务实例无法平滑实现逻辑和控制层面的迁移,更何况物理层面的迁移。新部署的管理服务需要和老机房的管理服务进行通信,因此,我们调整了新机房服务的部署架构,并适配实际情况分别使用两种部署模式,如图4和图5所示:
图4: 同集群扩容模式的跨机房服务部署
图5: 新建集群灰度迁移模式的跨机房服务部署
无论是图4的同集群扩容模式,还是图5的新建集群灰度迁移模式,在zookeeper层面必须让新旧机房的zookeeper集群处于一体的状态,需要两个集群的数据一致、实时同步。因此在zookeeper的技术层面,必须将这两个集群变成一个集群,将原有的3节点的zookeeper集群,经过异地机房扩容的方式扩充到8个节点(1个leader,7个follower),只有这种模式下数据才能够保持一致性和实时性。
而对于新机房新部署的需要注册的服务来说,他们的配置文件中对于zookeeper地址的配置,却不是新建的8个ip的列表,而是只配置新机房5个IP的列表。这样新老机房的后端服务使用同一套zookeeper,但是配置的却是不同的IP,这样做的目的,是为了后续老机房下线裁撤时,所有新机房的服务不需要因为zookeeper集群的缩容而重启更新配置,只要将集群中老机房所在的3个节点下线,剩余5个节点的配置更新重新选主即可。
因此在zookeeper的机房扩容方案上,我们采用了先同集群扩容后拆分的模式。zookeeper的扩容是整个机房扩建的第一步,后续所有的服务都会依托于该操作新建的5个节点的zookeeper配置;而zookeeper集群的缩容是最后的操作,待所有的服务都扩容完成,所有业务实例迁移完成之后,将zookeeper集群进行缩容重新选主,这样即可完成整个机房的裁撤。
数据库中间层udatabase的迁移
接下来是数据库中间层udatabase的迁移工作。
图4和图5两种模式对于zookeeper的处理方式是相同的,不同点在于后端对于内部管理和控制面服务的扩容迁移方式。udatabase迁移使用图4模式,这种模式下相当于在原有的集群进行异地机房扩容,扩容的新实例使用和原有集群相同的Endpoint前缀,也就是说它们是属于同一个集群,当服务启动后,新扩容的实例的状态会与原有集群的实例相同,框架(wiwo或uframework)层会通过客户端方式从zookeeper中发现到该集群节点的变化(新增),同时使用某种负载均衡算法将请求流量路由到新的节点上。这样属于同一个集群,但却处于两个地址位置的实例都有部分流量,而进行缩容的方式就是直接将老机房同集群的服务下线即可,这样客户端就会将所有该集群的流量都转发到新机房扩容的节点上,从而完成平滑的服务扩容。udatabase通过这样的方式完成了集群的迁移过程。
新建集群灰度迁移模式
其实图4模式对于大部分服务来说都是可行的,但为什么还出现了图5所示的新建集群灰度迁移模式呢?因为某些场景下图4会有一定的不可控性。假如新建的实例(如图4中Service A Instance 2)存在软件稳定性和可靠性的问题,比如配置异常、软件版本异常、网络异常,可能导致路由到新节点的请求出现问题,会直接影响在线业务,影响的规模由扩容的节点占集群总节点的比例决定,像我们这种1:1的扩容方式,如果服务有问题可能50%的请求就直接异常了。udatabase使用图4方案,是因为其代码的稳定性比较高,功能和配置比较简单,主要依托于其高性能的转发能力。
而对于某些功能逻辑都比较复杂的业务来说(如ULB/CNAT),就使用了更稳妥的图5模式,由于业务层面支持跨集群迁移,因此可以新建一个全新的无业务流量的集群,该集群在zookeeper中的Endpoint路径前缀和原有的集群不相同,使用一个全新的路径,然后在业务层面,通过迁移平台或工具,将后端服务或实例按需迁移,整个过程可控,出现问题立刻回滚,是最安全的迁移方案。我们通用的灰度迁移平台SRE-Migrate如图6所示。
图6:UCloud内部通用业务迁移系统SRE-Migrate
机房部署平台SRE-Asteroid
UCloud产品线和产品名下服务数量繁多,无论是图4还是图5的方案,都需要大量的服务部署工作。SRE团队在2018年中推进的机房部署优化项目,意在解决UCloud新机房建设(国内及海外数据中心、专有云、私有云等)交付时间长和人力成本巨大的问题,2018年底该项目成功产品化落地,覆盖主机、网络等核心业务近百余服务的部署管理,解决了配置管理、部署规范、软件版本等一系列问题。首尔机房迁移也正是利用了这一成果,才能够在很短的时间内完成近百个新集群的部署或扩容工作,图7所示就是我们的新机房部署平台 SRE-Asteroid。
图7:UCloud内部机房部署平台SRE-Asteroid
核心数据库的部署和迁移
最后,是核心数据库层面的部署和迁移工作如何进行。UCloud内部服务所使用的数据库服务为MySQL, 内部MySQL集群采用物理机/虚拟机在管理网络内自行建设,以一个主库、一个高可用从库、两个只读从库和一个备份库的方式部署,使用MHA+VIP的方式解决主库的高可用问题,使用BGP/ECMP+VIP的方式解决从库的负载均衡和高可用问题,大体的架构如图8所示:
图8:UCloud内部MySQL服务架构图
首尔新老机房使用的内部MySQL数据库集群的架构跟上图类似,为了进行新老机房的集群切换,我们设计了如下的方案,如图9所示:
图9:首尔集群内部数据库集群迁移示意图
整体来说,为了保证核心数据库集群能够稳定完成迁移工作,我们抛弃了双主库、双写的切换方案,防止因为网络或其他因素导致新老集群的数据不一致、同步异常等问题。我们采用了最简单的解决方案,在业务低峰期停止console服务,直接修改数据库中间层配置切换的方案。
在部署阶段,我们在首尔新机房部署了相同高可用架构的MySQL集群,老机房的数据库逻辑备份导入,将新老机房的集群做成级联模式(图9中绿色虚线),新机房的主库作为老机房的从库,通过MySQL异步同步的方式(binlog)进行数据同步。我们使用pt-table-checksum工具,定期对两个集群的数据一致性进行校验,以保证新老机房的数据完全一致。与此同时使用内部开发的拓扑分析工具,将所有调用老集群数据库主从库的业务情况确认清楚(主要是哪些udatabase集群)。
部署完成后,数据一致性和实时性通过级联得到保障,udatabase仍然访问老机房的MySQL主库的VIP(图9蓝色虚线),此时并没有业务通过直连的方式写入新机房的主库(为保证数据的一致性,新机房的主库暂时设置成只读模式)。
在确定迁移时间和迁移方案之后,在某个业务低峰期的时间点,公告用户后,首尔机房整个console的操作停止一段时间(期间首尔机房的API请求可能会失败),在确定流量很低的前提下,通过修改数据库中间层(udatabase cluster)中数据库主从库VIP的配置,将业务从老机房MySQL集群切换到新机房MySQL集群,此时该业务所有的请求都会流入到新集群(图9红色虚线)。为了防止老集群仍然有业务写入或读取,我们将老集群主库设置为只读,然后继续通过tcpdump抓包分析老集群上可能存在的请求并手动处理,最终保证所有业务都使用新的MySQL集群。
由于需要对主机、网络、存储和监控等几个业务都进行集群切换,为保证不互相影响,使用逐个集群处理的方式,整体切换加检测的时间耗时近1个小时。
在整个机房切换的过程中,只有数据库集群是有状态的业务,因此重要性和危险性也比较高,该服务切换完成后,最重要的一个环节也宣告完成,剩下的业务层面(UHost/UDB/EIP等)的迁移工作由各个业务团队自行完成即可。
收尾
最终所有业务实例完成迁移后,理论上就可以完成本次机房迁移工作了,不过还是要对老机房仍然运行的实例进行流量监测,确认没有流量后进行下线,停止服务。最后对老机房的zookeeper集群(老机房的3个zookeeper节点)进行请求监测和连接监测,确认没有本机房以及新机房发来的请求(排除zookeeper集群自主同步的情况),在完成确认后,进行最后的zookeeper集群变更,将整个集群(8个节点)拆分成老机房(3个节点)和新机房(5个节点),老机房的集群直接停止服务,而新机房的新的zookeeper集群完成新的选主操作,确认同步正常和服务正常。
写在最后
经历了上文所述的一切操作后,整个首尔机房的迁移工作就完成了,整个项目历经5个月,其中大部分时间用于业务实例的迁移过程,主要是针对不同的用户要确定不同的迁移策略和迁移时间;内部管理服务的迁移和部署所花费的时间还是比较少的。UCloud内部针对本次迁移的每一个步骤都制定了详细的方案规划,包括服务依赖分析、操作步骤、验证方式、切换风险、回滚方案等,为了完成如此巨大的新机房热迁移工作,团队投入了充足的人力和时间。首尔新机房具有更好的建设规划、硬件配置和软件架构,能够为用户提供更好的服务,我们相信这一切都是很有价值的。
云计算
2019-01-28 16:56:00
https://blog.csdn.net/diantun00/article/details/81180694
https://illya-chekrygin.com/2017/08/26/configuring-certificates-for-jenkins-kubernetes-plugin-0-12/
云计算
2019-02-27 14:36:00
pod.yaml apiVersion: v1 kind: Pod metadata: name: two-containers labels: label1: ok label2: nok annotations: label3: good label4: nogood namespace: mynamespace spec: restartPolicy: Always containers: #--------------------swarm service-------------------# - name: swarm-container-name0 image: 192.168.175.143:5000/swarm:0.0 imagePullPolicy: Always ports: - name: exposeport containerPort: 9090 hostPort: 9090 env: - name: sex value: boy - name: addr value: guangzhou volumeMounts: - mountPath: /root/test readOnly: true name: mountfunction #--------------------swarm service--------------------# #--------------------jenkins service------------------# - name: jenkins-container-name1 image: 192.168.175.143:5000/android:0.2 imagePullPolicy: Always ports: - containerPort: 8083 hostPort: 8083 - containerPort: 22 hostPort: 2222 volumeMounts: - mountPath: /home/jenkins/.jenkins readOnly: false name: share-data #--------------------jenkins service------------------# #--------------------common setting-------------------# volumes: - name: share-data nfs: server: 192.168.175.143 path: "/home/ubuntu/jenkins/JENKINS_HOME" - name: mountfunction hostPath: path: "/home/ubuntu/STU" nodeSelector: slave: "146" dnsPolicy: "None" dnsConfig: nameservers: - 8.8.8.8 - 10.192.27.32 - 10.192.33.32 #--------------------common setting-------------------#
deployment_python_http.yml apiVersion: apps/v1 kind: Deployment metadata: name: service-test-1 spec: replicas: 3 selector: matchLabels: app: service_test_pod1 template: metadata: labels: app: service_test_pod1 spec: containers: - name: simple-http image: 192.168.175.149:5000/swarm:0.0 imagePullPolicy: IfNotPresent ports: - name: http containerPort: 9090 hostPort: 7777
nginx-deploy-v1.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 2 template: metadata: labels: app: nginx spec: containers: - name: nginx image: 192.168.175.143:5000/nginx:0.0 ports: - name: expose-port containerPort: 80
云计算
2019-02-27 13:41:03
毋庸置疑,K8s已经成为云容器编排系统的标准,但是,如果缺乏K8s环境相关的安全问题认识的话,会致使各种组件暴露在网络集群内外的攻击之下。本文将介绍通过强身份验证如何确保企业的K8s集群免受外部攻击。
这是关于Kubernetes安全性的三篇系列文章中的第一篇,在本系列文章中,我们将依次介绍如何确保企业的Kubernetes集群免受外部攻击、内部攻击,以及如何处理资源消耗或noisy neighbors问题。
毋庸置疑,Kubernetes已经成为云容器编排系统的标准,据Cloud Native Computing Foundation(CNCF)的定义,它提供了一个“自动化部署、扩展以及跨主机集群操作应用程序容器的平台”。但是,如果缺乏Kubernetes环境相关的安全问题认识的话,会致使各种组件暴露在网络集群内外的攻击之下。
锁定API服务器,Kubelets
Kubernetes环境中有多种可由外部访问的组件,包括应用程序编程接口(API)服务器、kubelet以及数据存储。如果没有正确锁定和保护它们的话,这些组件有可能会造成数据泄漏和系统损害。
Kubernetes为开发、运维和安全团队提供了一个API结构,可用于和应用程序和Kubernetes平台交互。Kubelet是一个在节点上运行并且读取容器清单的服务,它确保定义了的容器已经启动并运行。Kubernetes利用etcd分布式键值存储来保存和复制Kubernetes在整个集群中使用的数据。基本上,最经常受到攻击的Kubernetes系统是那些根本没有设置访问控制的系统。
Goins指出,Kubernetes易于部署,在默认情况下并没有内置太多确保安全性的东西。例如,直到2017年年中,容器编排系统才开始具有RBAC(基于角色的访问控制)功能。
Kubernetes 1.8版本的亮点之一是RBAC(基于角色的访问控制),这是一种用于管理Kubernetes资源周围权限的授权机制。RBAC允许配置灵活的授权策略,可以在不需要重启集群的情况下进行更新。
“在很多Kubernetes部署中,一旦出现compromise,用户可以使用root权限安装和运行他们想要的软件。”Goins说,“黑客和网络犯罪分子希望进入一个系统,升级他们的权限,接着转向其他系统,开始收集信用卡和个人身份识别数据等信息。”
2018年12月在Kubernetes爆出的首个安全漏洞——特权升级漏洞(CVE-2018-1002105),当时是由Rancher Labs联合创始人及首席架构师Darren Shepherd发现的。该漏洞演示了用户如何通过Kubernetes API服务器建立和后端服务器的连接。建立连接之后,攻击者可以通过网络连接直接向后端集群服务(如kubelets)发送任意的请求。该漏洞让任何用户在任何计算节点上都拥有完整的管理员权限。后来发布了专门修复受支持的Kubernetes版本的补丁,在1.10.11,1.11.5和1.12.3中都提供了这样的补丁。
企业该如何保护K8s集群免受外部攻击
Goins建议,Kubernetes用户需要做的第一件事是完全关闭外部API访问,或者将该功能封装在某种强身份验证中设置保护。为了减轻外部攻击的威胁,信息技术/安全管理员必须确保只有必要的Kubernetes服务能对外暴露。此外,他们必须设置身份验证并且给所有公开的服务配置正确的网络安全策略。
Handy Tecchnologies的Alexander Uricoli在一篇博客中写道:“除非你在kubelet上指定了一些标志(flags),否则它在默认操作模式下,是会接受未经身份验证的API请求的。”在这篇博客中Uricoli分析了黑客是如何入侵同时个人服务器上的Kubernetes集群的:
https://medium.com/handy-tech/analysis-of-a-kubernetes-hack-backdooring-through-kubelet-823be5c3d67c
Uricoli说:“看来有人找到了一种方法,可以在一个正在运行的容器上放置一些加密挖掘软件,然后执行这个过程。”Kubernetes API服务器虽然面向internet公开,但是受到证书身份验证的保护。
结果,同事的服务器公开了kubelet端口(tcp 10250和tcp 10255)。Uricoli指出,尽管问题已显而易见,这样的入侵仍然应该引起大家对Kubernetes的部署的一些问题的关注。如果你的用户可以通过网络节点来访问你的节点,那么kubelet API就是一个通往集群的API后门,它功能齐全且未经身份验证。如果用户已经花了很多心思在webhook、RBAC或其他方法启用身份验证和授权上,那么他们也同样应该将kubelet好好地锁定上。
互联网安全中心(CIS)建议用户为kubelet连接部署HTTPS。CIS在关于为Kubernetes 1.11建立安全配置的指南中写道,“从API服务器到kubelets的连接可能带有机密和密钥等敏感数据。因此,在API服务器和kubeletes之间的任何通信中使用在途加密(in-transit)是非常重要的。”
Kubernetes用户应该禁用对API服务器的匿名请求。启用时,没有被其他配置好的身份验证方法拒绝的请求将被视为匿名请求,然后API服务器会处理这些请求。根据CIS的说法,Kubernetes的用户应该依靠身份验证来授权访问,并切拒绝匿名请求,而组织应该根据需要实现可控制的访问。
Goins指出,加强内部集群用户防御的安全控制——RBAC、隔离以及权限限制——对于保护Kubernetes免受外部攻击同等重要。
他说:“如果有人使用任何内部用户的账户从外部访问集群,他们会立刻获得完全访问权限。”所以,这并不是说你需要内部控制来抵御外部的攻击。而是说如果你没有这些措施,当受到攻击的时候你就遭大殃了。
结 语
Rancher Kubernetes平台拥有着超过一亿次下载量,我们深知安全问题对于用户而言的重要性,更遑论那些通过Rancher平台在生产环境中运行Docker及Kubernetes的数千万用户。
2018年年底 Kubernetes被爆出的首个严重安全漏洞CVE-2018-1002105 ,就是由Rancher Labs联合创始人及首席架构师Darren Shepherd发现的。
2019年1月 Kubernetes被爆出仪表盘和外部IP代理安全漏洞时 ,Rancher Labs也是业界第一时间向用户响应,确保了所有Rancher 2.x和1.6.x的用户都完全不被漏洞影响。
未来Rancher将会向用户分享更多容器及Kubernetes安全技巧。在下一篇博客中,我们将分享三种保护Kubernetes不受内部攻击的方法:基于角色的访问、Kubernetes特性(如逻辑隔离,即命名空间)和Rancher资源(如Project)。记得保持关注~
云计算
2019-02-27 12:41:00
本文作者:y****n
人脸识别技术在 AI 时代不仅可以带来便利,同时也提高了效率。8月21日,百度大脑开放日在乌镇举办 “乘风新基建,加速产业智能化升级” 专场,桐乡市经济和信息化局副局长陈再飞、乌镇镇科协秘书长钱永琪、桐乡市工业互联网企业联合会会长于亚东等出席活动。

会上,百度大脑发布了新近开放和升级的 AI 能力, 重磅推出乘风人脸通行考勤平台,并在现场实操模拟通行考勤场景演示平台部署过程, 全面展现百度大脑人脸识别服务的端边云协同的解决方案。同时, 由百度主办,乌镇镇人民政府、桐乡市经济和信息化局与桐乡市工业互联网联合会协办的乌镇 X EasyDL 产业智能创新大赛也正式启动。

陈再飞在致辞中表示:“我们将与百度加大合作力度,把打造乌镇 AI 高地作为共同目标和任务,把开放日等一系列活动办好、办扎实,政企合办 EasyDL 产业智能创新大赛,推动产业创新与升级。”
“乘风平台”实现端云结合一站式管理

随着人工智能技术的不断升级,“刷脸”已成为新的行业趋势。目前,人脸识别系统已经在写字楼、智慧园区、景区人脸验票、考场实名核验、酒店等场所被广泛应用。也仍有不少企业使用指纹考勤,或采用传统的 IC 卡门禁系统,但这样较为传统的方式弊病也愈发明显: 指纹打卡识别失灵、上下班打卡高峰、进出携带不方便、遗忘丢卡现象还有很大的安全隐患。 而市场上现有的一些人脸识别的硬件、算法、软件仅能交付单点的功能性产品,还需要企业自行开发后端业务管理平台。但人脸识别管理系统业务逻辑复杂,需要与花样繁多的硬件、算法做适配与兼容,开发成本高、周期长、部署/运维难度大,对企业也是极大的挑战。

为了更好地解决这些难题,本次开放日百度发布了乘风人脸通行考勤平台,该平台依托百度大脑人脸识别能力,提供端到端的一站式通行考勤的解决方案,可以 满足企业对智慧通行、无感考勤等场景的管理需求,实现跨地域、跨环境的人脸设备在线管理,适配多模态设备接入,实现端云数据同步。 乘风平台支持公有云和私有化两种部署和使用方案, 灵活应对各类项目的需求。在前端设备上无论是业务已有的抓拍机器,识别 Pad,或其他第三方厂商设备、用户自研的硬件设备,百度度目硬件设备都可以兼容,灵活利旧。同时黑名单、访客、VIP、员工等人脸库的自定义创建、对应的考勤通行策略的自定义创建,差异化的策略管理皆可灵活编辑。实时的通行考勤记录与报表,对设备的状态跨地域管理等业务功能,能够全面支撑客户基于人脸识别能力更加高效、安全地进行通行、考勤管控,较好地覆盖了多地多分公司多店的考勤、通行管理场景。
直播现场,产品经理还进行了实操演示,模拟了考勤通行场景,并演示了整个平台部署过程。从点击开始使用,到下载配套软件、不同设备添加、人脸库管理,考勤通行策略的制定,到最终系统功能使用, 只需要10分钟,零代码开发即可完成, 同时展现出乘风人脸通行考勤平台灵活易用、兼容性强、功能完善的显著优势。

不断创新升级
百度大脑 AI 进行时

乘风平台之所以能够具备这样的优势,还要得益于百度大脑一直以来持续领先的 AI 技术能力,此次开放日上百度大脑产品经理向开发者们展示了百度大脑开放平台7-8月新增和升级的 AI 能力。
在人脸识别方面, 有4项能力升级优化,包括人脸离线识别 SDK 升级、H5 视频活体检测能力升级、H5 端人脸实名认证解决方案升级、黑眼圈/眼袋检测能力开放邀测; 在文字识别方面, 共有6项能力升级优化,2项能力公测上线,7项新能力开放邀测,涉及通用场景文字识别、卡证文字识别、财务票据文字识别、医疗票据文字识别、汽车场景文字识别以及其他场景文字识别。 在内容审核方面, 百度大脑不断优化图像、文本审核能力,持续丰富审核维度、提升识别精度; 在语音技术方面, 在线语音合成上线2个新款精品音库,并支持按次数方式购买,低至0.5元/千次; 在语言与知识方面, 智能招聘解决方案开放邀测、合同智能处理开放邀测、智能对话定制与服务平台 UNIT 3项能力全新升级。由于本月也是百度大脑语言与知识主题月,因此还有主题月系列活动等着大家参与。 除此之外,可以提供完善的学习和实训环境的百度 AI Studio 也在不断更新相关内容,助力 AI 算法工程师的成长;百度 AI 市场也更新了数据,AI 商品发布达到1300+,成交金额达到千万级。

可以说,从技术到硬件,从场景到应用, 百度大脑始终在坚持“软硬一体化”发展,更有效的降低各行业应用 AI 技术的门槛。 截至目前,百度大脑已是服务规模最大的 AI 开放平台,已开放 269项 技术能力,有 超210万 开发者使用。

政企联合举办大赛
用 EasyDL 加速产业 AI 转型

在百度和波士顿咨询公司的联合调研中, 约86%的市场需求需要定制开发业务场景下的 AI 模型。 正是基于这样的市场诉求,百度推出了 EasyDL 零门槛 AI 开发平台,用户根据可视化页面提示,通过采集与标注数据、训练模型、部署模型的核心操作流程,即可获得公有云 API、本地服务器部署、设备端 SDK、软硬一体方案等类型 AI 服务,方便企业将 AI 服务快速集成在自身业务系统中。

现场还通过一系列 EasyDL 实际落地场景的案例介绍,向大家展示了各行各业如何应用 EasyDL 实现产业创新和 AI 转型。例如:慧安信科通过使用百度 EasyDL 实现实时 监管非机动车辆乱停乱放现象; 安徽恒科结合 EasyDL 训练的机械车辆识别模型, 实时监控工地光缆下是否有机车出现, 保障光缆安全;搭把手通过使用百度 EasyDL 软硬一体开发套件 ,制作智能厨余垃圾回收箱 等。目前 EasyDL 产品服务已经广泛应用到工业、安防、互联网、零售、物流、智能硬件、教育 、医疗、电商等各个行业,成为诸多企业的首选。 为了吸引更多企业、开发者感受 AI 的魅力,此次开放日上,百度携手乌镇镇政府、桐乡市经济和信息化局、桐乡市工业互联网联合会正式启动乌镇 X EasyDL 产业智能创新大赛, 无论企业还是个人,只要有 AI 应用落地的场景与需求,都可以通过本届大赛,定制 AI 服务,解决行业实际问题。 目前大赛已在百度大脑 AI 开放平台 AI Studio 比赛板块开启报名通道。报名需要提交使用 EasyDL 训练 AI 模型信息、创意思路、产业价值等相关内容。 大赛于9月底将完成初审和复审,并公布决赛名单;10月22日将迎来线下决赛路演与颁奖。
原文链接地址: https://developer.baidu.com/topic/show/291191
云计算
2020-08-26 10:40:00
摘要: 弹性裸金属服务器服务于市场的技术概要分析
混合云和第三方虚拟化软件部署
伴随着公有云的高速发展,混合云打通客户线下专有云和线上公有云资源的需求日趋强烈。Open stack和VMware等IaaS stack在公有云部署,同时管理客户线上和线下IaaS资源。
可以看到,VMware cloud on AWS就属于此种混合云业务应用场景
而Open stack和VMware cloud等IaaS stack在公有云部署,最为关键就是要求公有云暴露CPU虚拟化能力,否则在普通虚拟机中部署嵌套虚拟化技术,性能完全无法接受。
具体到intel X86 CPU体系,则要求公有云平台把计算资源的完整虚拟化特性(intel VT-x和VT-d等硬件虚拟化技术)对外提供,使得VMware ESXi, KVM, XEN, Hyper-V等虚拟化平台和技术能够平滑上云
高隔离容器部署
容器技术具备轻量敏捷等技术优势,正在成为Devops主流技术。相对于公有云VM部署容器,使用弹性裸金属服务器部署容器具备零虚拟化开销等性能优势。
同时我们注意到clear container, RunV,以及Kata container等具备高隔离安全特性的新型容器技术,依赖CPU完整虚拟化特性(比如intel VT-x)。此种高隔离高安全容器技术只可能部署在弹性裸金属服务器
高质量计算服务
所谓高质量计算服务,是指零资源争抢、零虚拟化开销,和高隔离安全
1) 零资源争抢
虚拟化技术在提高数据中心资源利用率同时,也引入资源争抢等业务难题。考虑多个VM运行在一台物理服务器之上:CPU core、L1/L2/LLC cache、内存带宽等CPU和内存子系统资源通过虚拟化技术抽象和切分,同时提供给多个VM使用。上述CPU和内存带宽在VM间的资源争抢很难根本解决。
2) 零虚拟化开销
虚拟化技术必然带来性能开销,而弹性裸金属服务器不存在CPU和内存虚拟化开销
3) 高隔离安全
弹性裸金属服务器是真正意义上用户独占,诸如hypervisor逃逸、CPU微架构侧信道攻击等问题天然免疫
高速低时延RDMA网络支持
RDMA网络在超低时延和减轻CPU负载等方面优势明显,但是在网络虚拟化支持方面的短板明显;而公有云网络部署的关键是通过网络虚拟化达到网络资源的租户隔离。而弹性裸金属服务器在支持原生ROCE和IB RDMA网络方面,具有天然优势。
因此可以看到各家云厂商均以裸金属服务器支持RDMA网络,以此部署HPC和异构计算的高速低时延互联需求
RISC ISA CPU支持
传统来讲,intel x86体系结构对CPU虚拟化技术等软硬件支持最为完善,加之intel XEON x86处理器在服务器市场的垄断市场地位,主流公有云IaaS虚拟化技术均基于intel XEON x86。
但是必须看到对于特定细分市场,RSIC CPU仍然具备相当优势。比如Power ISA CPU在金融保险等市场的优势地位,以及ARMv8 ISA在新兴服务器市场崭露头角。如何支持Power和ARMv8等RISC服务器,是公有云厂商必须回答的问题
弹性裸金属服务器无需CPU和内存虚拟化技术的特别适配,能够使得Power和ARMv8等处理器快速公有云上线部署
GPU性能无损支持
诸如Nvidia 1080Ti等GPU,其对虚拟化技术支持有限,通过虚拟机方式输出GPU计算性能,会有严重性能下降;而弹性裸金属服务器可做到GPU性能无损输出
阿里云弹性裸金属产品详情页>>
原文链接
本文为云栖社区原创内容,未经允许不得转载
云计算
2018-05-18 20:57:00
摘要: 阿里云宣布全新一代FPGA云服务器F3正式上线,并且开通邀测!
近期,阿里云宣布全新一代FPGA云服务器F3正式上线,并且开通邀测。实现云上 FPGA 加速业务的快速研发、安全分发、一键部署和弹性伸缩能力。为人工智能产业、图片视频转码、基因计算提供强有力的加速服务。
在FPGA Shell架构上,F3不仅沿用了前代的技术,并且充分支持OpenCL,HLS以及RTL的开发流程,能够让多种应用程序开发的工程师,在不需要关注底层硬件细节的情况下,很好地完成异构计算的定制开发工作。
在硬件上,采用了创新的单卡双芯片设计,卡内双芯片互联带宽高达600Gbps,卡间互联通过硬核实现,支持100G Mac协议。单片FPGA逻辑量250万,外挂4个DDR4通道,提供64GB存储能力。
原文链接
云计算
2018-05-18 20:26:00
摘要: 520不认怂! 男生学了这招,再也不用担心找不到女朋友了!传说中成功率99%的表白语都在这儿。
马上就要到520了,如果还有男生觉得现在开始准备过节有点早的话,那我只想说:朋友,注孤生了解一下!520、521这天大家都可以放心大胆地告白,虐狗程度堪比情人节(单身狗瑟瑟发抖)。
云栖小编总结了以下表白语,给开发哥哥们做参考,男生学(戏)了(精)这(上)些(身),再也不用担心找不到女朋友了!
颜值不够?情话来走一走!纸短情长啊,道不尽太多涟漪。。。
白羊座:
金牛座:
双子座:
巨蟹座:
狮子座:
处女座:
天秤座:
天蝎座
射手座
摩羯座:
水瓶座:
双鱼座:

最后一个:祝福各位猿们都表白成功!(ps:小编已经成功了,哈哈哈哈)!
本文为云栖社区原创内容,未经允许不得转载。
原文链接
云计算
2018-05-18 19:57:00
什么是Kubernetes?
Kubernetes是一个便携式的、可扩展的开源平台,用于管理容器化的工作负载和服务,这有助于声明式配置和自动化。它有一个巨大的,快速增长的生态系统。Kubernetes的服务、支持和工具被广泛使用。
2014年,谷歌开源了Kubernetes项目。Kubernetes建立在 谷歌大规模运行生产工作负载的十年半的经验之上 ,并结合社区的最佳思想和实践。
为什么我需要Kubernetes,它能做什么?
Kubernetes有许多特性。它可以被认为是:*一个容器平台*一个微服务平台*一个移动的云平台,还有更多。
Kubernetes提供了一个以容器为中心的管理环境。它代表用户工作负载协调计算、网络和存储基础设施。这提供了许多简单的平台作为服务(PaaS),以基础设施的灵活性作为服务(IaaS),并支持跨基础设施提供商的可移植性。
Kubernetes是怎样一个平台的?
尽管Kubernetes提供了很多功能,但是仍然有一些新的场景会从新特性中受益。应用程序特定的工作流可以简化,以加速开发人员的速度。最初可以接受的特别编排通常需要在规模上实现健壮的自动化。这就是为什么Kubernetes也被设计为构建组件和工具的生态系统的平台,使其更易于部署、扩展和管理应用程序。
标签 授权用户组织他们的资源。 注释 使用户可以用定制的信息来装饰资源,以方便他们的工作流程,并为管理工具提供一种方便的检查点状态的方法。
此外, Kubernetes控制plane 是建立在开发人员和用户可用的相同 api 之上的。用户可以编写自己的控制器,比如 调度程序 ,使用 自己的api ,这些api可以被通用 命令行工具 作为目标.
这个 设计 使许多其他系统能够在Kubernetes之上构建。
Kubernetes不做的事
Kubernetes并不是一个传统的、包罗万象的PaaS(平台作为服务)系统。由于Kubernetes在容器级别而不是在硬件级别上运行,所以它提供了一些通常适用的特性,比如部署、扩展、负载平衡、日志记录和监视。然而,Kubernetes并不是单一的,这些默认的解决方案是可选的和可插入的。Kubernetes为构建开发者平台提供了构建模块,但保留了用户的选择和灵活性。
Kubernetes: 不限制所支持的应用程序的类型。Kubernetes的目标是支持极其多样化的工作负载,包括无状态、有状态和数据处理工作负载。如果一个应用程序可以在容器中运行,它应该在Kubernetes上运行得很好。 不部署源代码,也不构建应用程序。持续集成、交付和部署工作流是由组织文化和偏好以及技术需求决定的。 不提供应用程序级的服务,例如中间件(例如消息总线)、数据处理框架(例如Spark)、数据库(如mysql)、缓存和集群存储系统(例如Ceph)作为内置服务。这样的组件可以在Kubernetes上运行,也可以通过在Kubernetes上运行的应用程序访问,比如开放服务代理。 不指定日志、监视或警报解决方案。它提供了一些集成作为概念的证明,以及收集和导出度量的机制。 不提供或授权配置语言/系统(例如, jsonnet )。它提供了一个声明性的API,它可以被任意形式的声明性规范作为目标。 不提供或采用任何综合的机器配置、维护、管理或自修复系统。
此外,Kubernetes不只是一个编排系统。事实上,它消除了编排的需要。编排的技术定义是执行定义的工作流:首先是a,然后是B,然后是C。相反,Kubernetes由一组独立的、可组合的控制过程组成,这些过程不断地将当前状态驱动到所提供的期望状态。怎么从A到C都不重要。也不需要集中控制。用它得到一个更容易使用、更强大、更健壮、更有弹性和可扩展的系统。
为什么使用容器?
比对以下图片可找到你应该使用容器的理由
部署应用程序的旧方法是使用操作系统包管理器在主机上安装应用程序。这样做的缺点是将应用程序、配置、库和生命周期与主机操作系统纠缠在一起。
新的方法是基于操作系统级虚拟化(而不是硬件虚拟化)部署容器。这些容器彼此隔离,从主机上分离:它们有自己的文件系统,它们不能看到彼此的进程,而且它们的计算资源使用也可以被限制。它们比vm更容易构建,而且由于它们与底层基础结构和主机文件系统之间的解耦,它们在云和OS分布中是可移植的。
因为容器小而快速,所以一个应用程序可以在每个容器映像中打包。这种一对一的应用程序对图像的关系释放了容器的全部好处。使用容器,可以在构建或发布时创建不可变容器映像,而不是部署时间,因为每个应用程序不需要与应用程序堆栈的其余部分组成,也不需要与生产基础设施环境结合。在构建或发布时生成容器映像,可以使一个一致的环境从开发进入生产。类似地,容器比VMs更加透明,这有助于监视和管理。当容器程序的生命周期由基础结构管理而不是由容器内的程序管理器隐藏时,这一点尤其正确。最后,对于每个容器使用单个应用程序,管理容器就等于管理应用程序的部署。
容器的好处摘要: 敏捷的创建与部署: 与VM镜像使用相比,增加了容器镜像创建的方便性和效率。 持续开发、集成和部署: 提供可靠和频繁的容器镜像构建和部署,快速和简单的回滚(由于镜像的不变性)。 Dev和Ops分离的关注: 在构建或发布时间而不是部署时间创建应用程序容器镜像,从而将应用程序与基础结构解耦。 可观察性: 不仅仅是表面的信息和度量,还包括应用程序的健康和其他信号。 跨开发、测试和生产的环境一致性 :在笔记本电脑上运行和在云中运行一样。 可在 OS distribution 与云之间移植: 在Ubuntu、RHEL、CoreOS、on-prem、谷歌Kubernetes引擎和其他任何地方运行。 以应用程序为中心的管理: 提高抽象级别在虚拟硬件上运行OS以使用逻辑资源在OS上运行应用程序。 松散耦合,分布式,弹性,自由的 微服务: 应用程序被分割成更小的独立的块,并且可以动态地部署和管理,而不是在一个大型的单用途机器上运行一个庞大的整体堆栈。 资源隔离: 可预测的应用程序的性能。 资源利用: 高效率和密度。
Kubernetes是什么意思?K8s?
Kubernetes这个名字源于希腊语,意思是舵手或领航员,是总督和 控制论 的根源。K8s是用8来替换“ubernete” 这8个字母而得到的缩写。
云计算
2018-05-17 23:56:00
摘要: 阿里FPGA云服务器平台FaaS(FPGA as a Service)在云端提供统一硬件平台与中间件,可大大降低加速器的开发与部署成本。普惠开发者
FPGA (现场可编程门阵列)由于其硬件并行加速能力和可编程特性,在传统通信领域和IC设计领域大放异彩。一路走来,FPGA的技术并不是一个新兴的硬件器件,由于其开发门槛过高,硬件加速算法的发布和部署保护要求非常高,FPGA的使用一直是高冷的美人,没有走入平常百姓家。也就导致FPGA的计算潜力还没有得到深入的挖掘。
阿里FPGA云服务器平台FaaS(FPGA as a Service)在云端提供统一硬件平台与中间件,可大大降低加速器的开发与部署成本。用户可以直接使用部署加速器提供商提供的加速服务;也可以在无需了解底层硬件的情况下,快速开发和部署自己的定制加速器。
FaaS平台提供给所有的加速器开发者统一的FPGA硬件接口Shell,提前帮用户解决了FPGA开发难度最大的高速接口开发及调试,例如PCIe,Fiber接口, DDR控制器等等,大大简化了开发的时间;用户能够直接得到硬件平台和FPGA接口的最大性能,不会因为团队开发能力和经验的欠缺,造成硬件平台性能浪费。
在提供统一接口提供安全性和便捷性的前提下,阿里云FaaS也尽最大努力保证用户设计的灵活性和快捷性,Role的概念应运而生。Role在动态区域,不同于Shell,用户可以根据需要,随时更换Role部分;这种Shell + Role的组合方式,保证了Shell的最轻量化,极大的提升了开发的便捷性,大大缩短了开发所需时间。
与Shell和 Role对应,在服务器端,FaaS也提供相应的驱动和软件库,为用户提供统一及灵活的软件支持,比如DMA驱动,寄存器访问驱动等等。
传统的FPGA开发如果从硬件设计开始,需要经历原理设计、PCB设计、PCB生产、装配测试等 漫长的硬件周期; 在逻辑设计阶段,也需要从板卡启动调试、接口调试、驱动开发等最底层的工作开始;这些工作完成之后,开能开始正常的逻辑开发工作。
阿里FaaS平台大大简化了整个FPGA的设计流程。使用FaaS实例,无需硬件周期;逻辑设计阶段,也可直接跳过板卡启动调试以及接口调试,可以直接开始用户逻辑设计;而且,Role的提供,也可简化或者省略一部分用户逻辑的设计。
原文链接
本文为云栖社区原创内容,未经允许不得转载。
云计算
2018-05-17 21:35:00
本节我们讨论 Kubernetes 网络这个重要主题。
Kubernetes 作为编排引擎管理着分布在不同节点上的容器和 Pod。Pod、Service、外部组件之间需要一种可靠的方式找到彼此并进行通信,Kubernetes 网络则负责提供这个保障。本章包括如下内容: Kubernetes 网络模型 各种网络方案 Network Policy
Kubernetes 网络模型
Kubernetes 采用的是基于扁平地址空间的网络模型,集群中的每个 Pod 都有自己的 IP 地址,Pod 之间不需要配置 NAT 就能直接通信。另外,同一个 Pod 中的容器共享 Pod 的 IP,能够通过 localhost 通信。
这种网络模型对应用开发者和管理员相当友好,应用可以非常方便地从传统网络迁移到 Kubernetes。每个 Pod 可被看作是一个个独立的系统,而 Pod 中的容器则可被看做同一系统中的不同进程。
下面讨论在这个网络模型下集群中的各种实体如何通信。知识点前面都已经涉及,这里可当做复习和总结。
Pod 内容器之间的通信
当 Pod 被调度到某个节点,Pod 中的所有容器都在这个节点上运行,这些容器共享相同的本地文件系统、IPC 和网络命名空间。
不同 Pod 之间不存在端口冲突的问题,因为每个 Pod 都有自己的 IP 地址。当某个容器使用 localhost 时,意味着使用的是容器所属 Pod 的地址空间。
比如 Pod A 有两个容器 container-A1 和 container-A2,container-A1 在端口 1234 上监听,当 container-A2 连接到 localhost:1234,实际上就是在访问 container-A1。这不会与同一个节点上的 Pod B 冲突,即使 Pod B 中的容器 container-B1 也在监听 1234 端口。
Pod 之间的通信
Pod 的 IP 是集群可见的,即集群中的任何其他 Pod 和节点都可以通过 IP 直接与 Pod 通信,这种通信不需要借助任何的网络地址转换、隧道或代理技术。Pod 内部和外部使用的是同一个 IP,这也意味着标准的命名服务和发现机制,比如 DNS 可以直接使用。
Pod 与 Service 的通信
Pod 间可以直接通过 IP 地址通信,但前提是 Pod 得知道对方的 IP。在 Kubernetes 集群中, Pod 可能会频繁的销毁和创建,也就是说 Pod 的 IP 不是固定的。为了解决这个问题,Service 提供了访问 Pod 的抽象层。无论后端的 Pod 如何变化,Service 都作为稳定的前端对外提供服务。同时,Service 还提供了高可用和负载均衡功能,Service 负责将请求转发给正确的 Pod。
外部访问
无论是 Pod 的 IP 还是 Service 的 Cluster IP,它们只能在 Kubernetes 集群中可见,对集群之外的世界,这些 IP 都是私有的。
Kubernetes 提供了两种方式让外界能够与 Pod 通信: NodePort
Service 通过 Cluster 节点的静态端口对外提供服务。外部可以通过 : 访问 Service。 LoadBalancer
Service 利用 cloud provider 提供的 load balancer 对外提供服务,cloud provider 负责将 load balancer 的流量导向 Service。目前支持的 cloud provider 有 GCP、AWS、Azur 等。
以上就是 Kubernetes 网络模型的相关讨论。
下一节我们来看看 Kubernetes 支持的网络方案。
书籍:
1.《每天5分钟玩转Kubernetes》
https://item.jd.com/26225745440.html
2.《每天5分钟玩转Docker容器技术》
https://item.jd.com/16936307278.html
3.《每天5分钟玩转OpenStack》
https://item.jd.com/12086376.html
云计算
2018-05-17 20:34:00

OSD服务端消息的接收起始于OSD::init()中的messenger::add_dispatcher_head(Dispatcher *d)函数 |- 358 void add_dispatcher_head(Dispatcher *d) { || 359 bool first = dispatchers.empty(); || 360 dispatchers.push_front(d); || 361 if (d->ms_can_fast_dispatch_any()) || 362 fast_dispatchers.push_front(d); || 363 if (first) || 364 ready(); //如果dispatcher list空,启动SimpleMessenger::ready,不为空证明SimpleMessenger已经启动了 || 365 }
在SimpleMessenger::ready()中,启动DispatchQueue等待mqueue,如果绑定了端口就启动 accepter接收线程 76 void SimpleMessenger::ready() - 77 { | 78 ldout(cct,10) << "ready " << get_myaddr() << dendl; | 79 dispatch_queue.start(); //启动DispatchQueue,等待mqueue | 80 | 81 lock.Lock(); | 82 if (did_bind) | 83 accepter.start(); | 84 lock.Unlock(); | 85 }
Accepter是Thread的继承类,Accepter::start()最终调用Accepter::entry(),在entry中 accept并把接收到的sd加入到Pipe类中 void *Accepter::entry() { ... struct pollfd pfd; pfd.fd = listen_sd; pfd.events = POLLIN | POLLERR | POLLNVAL | POLLHUP; while (!done) { int r = poll(&pfd, 1, -1); if (pfd.revents & (POLLERR | POLLNVAL | POLLHUP)) break; // accept entity_addr_t addr; socklen_t slen = sizeof(addr.ss_addr()); int sd = ::accept(listen_sd, (sockaddr*)&addr.ss_addr(), &slen); if (sd >= 0) { errors = 0; ldout(msgr->cct,10) << "accepted incoming on sd " << sd << dendl; msgr->add_accept_pipe(sd); //注册一个pipe,启动读线程,从该sd中读取数据 } else { ldout(msgr->cct,0) << "accepter no incoming connection? sd = " << sd << " errno " << errno << " " << cpp_strerror(errno) << dendl; if (++errors > 4) break; } } ... return 0;
在SimpleMessenger::add_accept_pipe(int sd)中,申请一个Pipe类并把sd加入到Pipe中,开始Pipe::start_reader() 340 Pipe *SimpleMessenger::add_accept_pipe(int sd) - 341 { | 342 lock.Lock(); | 343 Pipe *p = new Pipe(this, Pipe::STATE_ACCEPTING, NULL); | 344 p->sd = sd; | 345 p->pipe_lock.Lock(); | 346 p->start_reader(); | 347 p->pipe_lock.Unlock(); | 348 pipes.insert(p); | 349 accepting_pipes.insert(p); | 350 lock.Unlock(); | 351 return p; | 352 }
Pipe类内部有一个Reader和Writer线程类,Pipe::start_reader()启动Pipe::Reader::entry(),最终启动Pipe::reader函数 134 void Pipe::start_reader() - 135 { | 136 assert(pipe_lock.is_locked()); | 137 assert(!reader_running); |- 138 if (reader_needs_join) { || 139 reader_thread.join(); || 140 reader_needs_join = false; || 141 } | 142 reader_running = true; | 143 reader_thread.create("ms_pipe_read", msgr->cct->_conf->ms_rwthread_stack_bytes); | 144 } |- 48 class Reader : public Thread { || 49 Pipe *pipe; || 50 public: || 51 explicit Reader(Pipe *p) : pipe(p) {} || 52 void *entry() { pipe->reader(); return 0; } || 53 } reader_thread;
在Pipe::reader函数中根据tag接收不同类型的消息,如果是CEPH_MSGR_TAG_MSG类型消息调用read_message接收消息,并把消息加入到mqueue中 void Pipe::reader() { pipe_lock.Lock(); if (state == STATE_ACCEPTING) { accept(); //第一次进入此函数处理 assert(pipe_lock.is_locked()); } // loop. while (state != STATE_CLOSED && state != STATE_CONNECTING) { assert(pipe_lock.is_locked()); ...... ...... else if (tag == CEPH_MSGR_TAG_MSG) { ldout(msgr->cct,20) << "reader got MSG" << dendl; Message *m = 0; int r = read_message(&m, auth_handler.get()); pipe_lock.Lock(); if (!m) { if (r < 0) fault(true); continue; } ...... ...... ...... // note last received message. in_seq = m->get_seq(); cond.Signal(); // wake up writer, to ack this ldout(msgr->cct,10) << "reader got message " << m->get_seq() << " " << m << " " << *m << dendl; in_q->fast_preprocess(m); //mds 、mon不会进入此函数,预处理 if (delay_thread) { utime_t release; if (rand() % 10000 < msgr->cct->_conf->ms_inject_delay_probability * 10000.0) { release = m->get_recv_stamp(); release += msgr->cct->_conf->ms_inject_delay_max * (double)(rand() % 10000) / 10000.0; lsubdout(msgr->cct, ms, 1) << "queue_received will delay until " << release << " on " << m << " " << *m << dendl; } delay_thread->queue(release, m); } else { if (in_q->can_fast_dispatch(m)) { reader_dispatching = true; pipe_lock.Unlock(); in_q->fast_dispatch(m); pipe_lock.Lock(); reader_dispatching = false; if (state == STATE_CLOSED || notify_on_dispatch_done) { // there might be somebody waiting notify_on_dispatch_done = false; cond.Signal(); } } else { //mds进入此else in_q->enqueue(m, m->get_priority(), conn_id); //把接收到的messenger加入到mqueue中 } } } ...... ...... } // reap? reader_running = false; reader_needs_join = true; unlock_maybe_reap(); ldout(msgr->cct,10) << "reader done" << dendl; }
在Pipe::DispatchQueue::enqueue函数中加入到mqueue中 void DispatchQueue::enqueue(Message *m, int priority, uint64_t id) { Mutex::Locker l(lock); ldout(cct,20) << "queue " << m << " prio " << priority << dendl; add_arrival(m); if (priority >= CEPH_MSG_PRIO_LOW) { mqueue.enqueue_strict( id, priority, QueueItem(m)); } else { mqueue.enqueue( id, priority, m->get_cost(), QueueItem(m)); } cond.Signal(); //唤醒dispatch_queue.start() 启动的dispatchThread,进入entry进行处理 }
云计算
2018-05-17 18:52:00
Dipatcher类是消息分发的接口,OSD、MON、等类都继承该类,并实现了Dipatcher的消息分发接口 1079 class OSD : public Dispatcher, - 1080 public md_config_obs_t { | 1081 /** OSD **/ 128 class Monitor : public Dispatcher, - 129 public md_config_obs_t { | 130 public: | 131 // me | 132 string name;
在OSD::init()函数中把不同类型的Dipatcher加入到SimpleMessenger实例中 | 2146 // i'm ready! | 2147 client_messenger->add_dispatcher_head(this); | 2148 cluster_messenger->add_dispatcher_head(this); | 2149 | 2150 hbclient_messenger->add_dispatcher_head(&heartbeat_dispatcher); | 2151 hb_front_server_messenger->add_dispatcher_head(&heartbeat_dispatcher); | 2152 hb_back_server_messenger->add_dispatcher_head(&heartbeat_dispatcher); | 2153 | 2154 objecter_messenger->add_dispatcher_head(service.objecter);
在Messenger::add_dispatcher_head(Dispatcher *d)中加入Messenger::list dispatchers中,并调用ready(),SimpleMessenger::ready()重写了基类的ready, |- 358 void add_dispatcher_head(Dispatcher *d) { || 359 bool first = dispatchers.empty(); || 360 dispatchers.push_front(d); || 361 if (d->ms_can_fast_dispatch_any()) || 362 fast_dispatchers.push_front(d); || 363 if (first) || 364 ready(); || 365 }
在ready函数中调用DispatchQueue::start, start()函数启动DispatchQueue::DispatchThread和DispatchQueue::LocalDeliveryThread线程类,最终调用DispatchQueue::entry()和DispatchQueue::run_local_delivery。 216 void DispatchQueue::start() - 217 { | 218 assert(!stop); | 219 assert(!dispatch_thread.is_started()); | 220 dispatch_thread.create("ms_dispatch"); //调用Thread::create->Thread::try_create->Thread::_entry_func->Thread::entry_wrapper->DispatchThread::entry | 221 local_delivery_thread.create("ms_local"); | 222 } |- 98 class DispatchThread : public Thread { || 99 DispatchQueue *dq; || 100 public: || 101 explicit DispatchThread(DispatchQueue *dq) : dq(dq) {} ||- 102 void *entry() { ||| 103 dq->entry(); ||| 104 return 0; ||| 105 } || 106 } dispatch_thread;
在DispatchQueue::entry()中调用根据不同的命令码调用不同的Messenger类中的处理函数 void DispatchQueue::entry() { . . switch (qitem.get_code()) { case D_BAD_REMOTE_RESET: msgr->ms_deliver_handle_remote_reset(qitem.get_connection()); break; case D_CONNECT: msgr->ms_deliver_handle_connect(qitem.get_connection()); break; case D_ACCEPT: msgr->ms_deliver_handle_accept(qitem.get_connection()); break; case D_BAD_RESET: msgr->ms_deliver_handle_reset(qitem.get_connection()); break; default: assert(0); } } else { Message *m = qitem.get_message(); if (stop) { ldout(cct,10) << " stop flag set, discarding " << m << " " << *m << dendl; m->put(); } else { uint64_t msize = pre_dispatch(m); msgr->ms_deliver_dispatch(m); post_dispatch(m, msize); } } . . }
在Messenger::ms_deliver_dispatch中最终调用不同的Dipatcher继承类的ms_dispatch进行处理 |- 579 void ms_deliver_dispatch(Message *m) { || 580 m->set_dispatch_stamp(ceph_clock_now(cct)); || 581 for (list::iterator p = dispatchers.begin(); || 582 p != dispatchers.end(); ||- 583 ++p) { ||| 584 if ((*p)->ms_dispatch(m)) //在Dispatcher继承类中进行处理 ||| 585 return; ||| 586 } || 587 lsubdout(cct, ms, 0) << "ms_deliver_dispatch: unhandled message " << m << " " << *m << " from " || 588 << m->get_source_inst() << dendl; || 589 assert(!cct->_conf->ms_die_on_unhandled_msg); || 590 m->put(); || 591 }
云计算
2018-05-17 17:39:00
CoreOS下的Docker配置是通过flannel unit来实现的
通过命令可以看出配置文件的默认位置 systemctl cat docker # /run/systemd/system/docker.service [Unit] Requires=torcx.target After=torcx.target Description=Docker Application Container Engine Documentation=http://docs.docker.com After=containerd.service docker.socket network-online.target Wants=network-online.target Requires=containerd.service docker.socket [Service] EnvironmentFile=/run/metadata/torcx Type=notify EnvironmentFile=-/run/flannel/flannel_docker_opts.env Environment=DOCKER_SELINUX=--selinux-enabled=true /run/flannel/flannel_docker_opts.env
这个文件默认是没有的,所以我们创建对应目录和文件即可。
补充:如果docker run的时候报如下错误: Error response from daemon: error creating overlay mount to /var/lib/docker/overlay2/007018df729636dd7c3d22ea683d13b6f5f0657b7c2c9e0014c671id argument.
则需要修改/run/systemd/system/docker.service文件中的这一项
Environment=DOCKER_SELINUX=--selinux-enabled=false
然后
systemctl daemon-reload
systemctl restart docker
然后编辑文件内容如下: vi flannel_docker_opts.env DOCKER_OPTS="--registry-mirror=https://xxxxx.mirror.aliyuncs.com"
保存后,启动docker服务即可 systemctl daemon-reload systemctl restart docker
最后验证一下 ps aux |grep docker root 831 1.3 0.8 272276 32908 ? Ssl 16:55 0:00 /run/torcx/bin/dockerd --host=fd:// --containerd=/var/run/docker/libcontainerd/docker- containerd.sock --selinux-enabled=true --registry-mirror=https://xxxxx.mirror.aliyuncs.com
这样就成功了,很简单吧。
补充:
大家有没有发现docker.service配置文件的路径在/run/下,我一直觉得很奇怪/run/应该都是运行时,果然发现系统或服务重启后,docker.service中的配置经常会还原了。一直想解决的办法,经过尝试,可以通过如下方法处理: # 复制/run/里面的配置文件到/etc/下 cp /run/systemd/system/docker.service /etc/systemd/system/docker.service # 重新加载服务 systemctl daemon-reload # 重启服务 systemctl restart docker.service # 验证 systemctl cat docker.service
可以看到文件已经变更过来了。
云计算
2018-05-17 17:05:00
有时候,我们无法使用像GKE或AKE这样的托管服务,甚至处于离线或与外网隔离的状态,而无法直接访问互联网。然而,即使在这种情况下,仍然是有方法使用Rancher管理集群的。
本文中,我们将向你介绍如何在离线或内网环境中运行Rancher 2.0。
私有镜像库
因为所有与Rancher相关的服务都在容器中运行,因此首先你需要的是在环境中存储容器。在本文的示例中,我们将使用Docker Registry(Docker镜像仓库)。如果你已经有了镜像仓库,可以跳过这些步骤。
注意:在Rancher 2.0中,只有没有身份认证的镜像仓库才能获取启动和运行Rancher 2.0所需的所有镜像。这并不会影响在工作负载中使用的可配置镜像仓库。
要运行Docker Registry,你需要运行一个registry:2镜像的实例。我们将公开默认端口(5000),挂载一个主机目录确保我们有足够的空间(至少需要8GB)并且获得适当的I/O性能。 docker run -d -p 5000:5000 --restart=always --name registry -v /opt/docker-registry:/var/lib/registry registry:2
让Rancher镜像运行起来
在镜像仓库设置完成后,就开始同步所需的镜像来运行Rancher 2.0。这一步骤我们将讨论两个场景: 场景1:你有一台可访问DockerHub的主机来提取和保存镜像,另有一台可以访问你的私有镜像仓库的单独的主机,以用于push镜像。 场景2:你有一台可以访问DockerHub以及私有镜像仓库的主机。
场景1:一台主机访问DockerHub,另一台访问私有镜像仓库
Rancher每一次的版本更新发布( https://github.com/rancher/rancher/releases/tag/v2.0.0),都会随之提供针对这一场景的插件。你需要如下脚本:
**rancher-save-images.sh:**该脚本将从DockerHub中拉取所有需要的镜像,并且将所有镜像保存成一个rancher-images.tar.gz压缩文件。可以将该文件传输到能够访问你私人镜像仓库的内部部署主机上。
**rancher-load-images.sh:**该脚本将从rancher-images.tar.gz中加载镜像,将它们push到你的私有镜像仓库。在脚本的第一个参数需要提供私有镜像仓库的主机名rancher-load-images.sh registry.yourdomain.com:5000
场景一的流程
场景2:一台可以访问DockerHub以及私有镜像仓库的主机
针对这一场景,Rancher每一次的版本更新时
( https://github.com/rancher/rancher/releases/tag/v2.0.0 ),都会提供一个名为rancher-images.txt的文件。该文件包含了运行Rancher 2.0所需要的全部镜像。可以将它绑定到任何现有的自动化服务中同步你可能拥有的镜像,也可以使用我在下面展示的脚本/Docker镜像。
场景二的流程
配置Rancher,使用私有镜像仓库
流程的最后一步是配置Rancher,将私有镜像仓库作为获取镜像的源。这可以通过在Setting视图中使用system-default-registry进行配置。
设置栏
若想要使用私有镜像仓库,在配置设置时不要使用https:// 或 http://作为前缀
这样做可以确保那些被用于向集群添加节点的rancher/rancher-agent容器,会使用该值作为前缀。其他需要使用的镜像也需要这样配置。
如果你想在启动rancher/rancher容器时进行配置,可以使用环境变量CATTLE_SYSTEM_DEFAULT_REGISTRY
例如: docker run -d -p 80:80 -p 443:443 -e CATTLE_SYSTEM_DEFAULT_REGISTRY=registry.yourdomain.com:5000 registry.yourdomain.com:5000/rancher/rancher:v2.0.0
创建一个集群
你可以通过运行rancher/rancher容器所在主机的IP来访问Rancher 2.0 UI。最开始启动大约需要1分钟,第一次访问时系统会提示你设置密码。
设置密码
接下来,你需要配置URL,节点将通过该地址和Rancher 2.0安装建立联系。在默认情况下,它会显示你访问UI的IP地址,不过当你想使用DNS名称或者负载均衡器的话,你可以在这里进行更改。
在Global视图中,点击Add Cluster
_ 添加集群_
本文会带领你创建一个没有高级选项的custom集群。想要在你的集群中配置高级选项,请参考相关的文档。
添加名叫testcluster的自定义集群
点击Next,创建testcluster集群。
在下一个界面,你会获得一个生成的命令,用于在你想要添加到集群的节点上启动。在该命令中使用的镜像应该会自动地用你配置好的私有镜像仓库作为前缀。
为集群添加节点
现在,对你想要添加的节点,你可以选择要为它们使用哪些角色,并且可以选择配置该节点的IP。如果没有指定,则会自动检测IP。有关Node Roles的含义,也请参考文档。
在项目中配置对镜像仓库的访问
如前所述,目前Rancher 2.0还不支持使用带有身份认证的私有镜像仓库来运行Rancher 2.0需要的镜像。不过在这种情况下,Rancher 2.0支持project中的工作负载。
想要使用身份认证配置镜像仓库,你可以在集群中打开你的项目(Default项目是自动为你创建的)。在Default项目中,可以导航到Resources->Registries来配置用于工作负载的镜像仓库。
在project默认设置里配置镜像仓库
点击Add Registry
添加镜像仓库
填写访问镜像仓库所需的信息:
总 结
希望这篇指南能够帮助大家进一步了解如何在离线或内网环境中设置Rancher 2.0。我们知道很多环境中还会有代理,我们后续还会发布代理设置的相关文章,敬请期待!
最后附上我在本文中用到的一些命令,希望它们能够为你所用或带给你更多灵感。
云计算
2018-05-17 09:38:00
Author: xidianwangtao@gmail.com
Equivalence Class概念及其意义
2015年,google发表的关于Borg的论文“ Large-scale cluster management at Google with Borg ”中对Equivalence Class的描述如下: Equivalence classes: Tasks in a Borg job usually have identical requirements and constraints, so rather than determining feasibility for every pending task on every machine, and scoring all the feasible machines, Borg only does feasibility and scoring for one task per equivalence class – a group of tasks with identical requirements.
Equivalence Class目前是用来在Kubernetes Scheduler加速Predicate,提升Scheduler的吞吐性能。Kubernetes scheduler及时维护着Equivalence Cache的数据,当某些情况发生时(比如delete node、bind pod等事件),需要立刻invalid相关的Equivalence Cache中的缓存数据。
一个Equivalence Class是用来定义一组具有相同Requirements和Constraints的Pods的相关信息的集合,在Scheduler进行Predicate阶段时可以只需对Equivalence Class中一个Pod进行Predicate,并把Predicate的结果放到Equivalence Cache中以供该Equivalence Class中其他Pods(成为Equivalent Pods)重用该结果。只有当Equivalence Cache中没有可以重用的Predicate Result才会进行正常的Predicate流程。
什么样的Pods会被归类到同一Equivalence Class呢?按照其定义,其实只要Pods有某些相同的field,比如resources requirement、label、affinity等,它们都被认为是Equivalent Pods,属于同一Equivalence Class。但是考虑到用户可能随时修改Pods的fields,会导致Scheduler需要及时更新该Pod所属的Equivalence Class变动,从而导致可能正在进行的Predicate需要感知这一变化并作出变更,这使得问题变得异常复杂。因此,目前Scheduler只把那些属于同一OwnerReference(包括RC,RS,Job, StatefulSet)的Pods归类到同一Equivalence Class,比如某个RS定义了N个副本,那么这N个副本Pods就对应一个Equivalence Class。Scheduler会为每个Equivalence Class中的Equivalent Pods计算出一个uint64 EquivalenceHash值。
注意,截止Kubernetes 1.10,即使有两个一样Pod Template的RS,也会对应两个Equivalence Class。
Equivalence Class工作原理
要想使用Equivalence Class需要启用 EnableEquivalenceClassCache Feature Gate,截止Kubernetes 1.10,该Feature还是Alpha阶段。
前期我的几遍关于scheduler的博客中对Predicate的分析中提到,所有注册成功的Predicate Policy都会在 scheduler.findNodesThatFit(pod, nodes, predicateFuncs ...) 过程中按照一定的并行数对每个node调用 scheduler.podFitsOnNode(pod, node, predicateFuncs ...) 进行注册的Predicate Policys检查。
podFitsOnNode的输入是一个pod,一个node和一系列注册成功的predicateFuncs,用来检查node是否满足该pod的预选条件。加入了Equivalence Class之后,预选阶段会发生了如下变化: 预选之前,先检查该pod是否有对应的Equivalence Class。 如果有对应的Equivalence Class,那么接下来检查Equivalence Cache中是否有可用的Predicate Result,否则触发完整的正常预选。 如果有可用的Predicate Result,那么直接使用该Cached Predicate Result完成预选,否则触发完整的正常预选。
Equivalence Cache会存储每个node的Predicates Results,是一个3层Map对象: 第一层key是node name,表示节点名称; 第二层key是predicateKey,表示预选策略,因此该node对应的algorithmCache Entries数量最多不超过Scheduler注册的Predicate Policies数量,这用来保证Cache大小,防止查找Equivalence Cache时性能太差。 第三层key是Equivalence Hash,前面已经提到过。
比如, algorithmCache[$nodeName].predicatesCache.Get($predicateKey)[$equivalenceHash] 表示 $equivalenceHash 对应的Pods在 $nodeName 节点上进行 $predicateKey 进行预选是否成功。
截止Kubernetes 1.10,predicateKey支持列表如下(20个): MatchInterPodAffinity CheckVolumeBinding CheckNodeCondition GeneralPredicates HostName PodFitsHostPorts MatchNodeSelector PodFitsResources NoDiskConflict PodToleratesNodeTaints CheckNodeUnschedulable PodToleratesNodeNoExecuteTaints CheckNodeLabelPresence CheckServiceAffinity MaxEBSVolumeCount MaxGCEPDVolumeCount MaxAzureDiskVolumeCount NoVolumeZoneConflict CheckNodeMemoryPressure CheckNodeDiskPressure
注意,即使该Pod找到对应的Equivalence Class,Equivalence Cache中也有可能没有可用的Predicate Result,或者对应的Predicate Result已经失效。这时就会触发正常的Predicate,并把Result写到Equivalence Cache中。
如何维护和更新Equivalence Cache呢?如果频繁的更新整个node对应的Equivalence Cache,这违背了Equivalence Cache设计的初衷,并不能提升Predicate的效率。
前面提到过Equivalence Cache的三层Map结构设计,第二层Key是predicateKey,因此Scheduler能做到只invalid单个Predicate Result,而不是盲目的invalid整个node的algorithmCache。
Scheduler会Watch相关API Objects Add/Update/Delete Event,并根据相关策略invalid对应的Equivalence Cache数据,具体的逻辑请看下面的源码分析部分。
Equivalence Class源码分析
Equivalence Cache数据结构
Equivalence Cache结构定义如下: // EquivalenceCache holds: // 1. a map of AlgorithmCache with node name as key // 2. function to get equivalence pod type EquivalenceCache struct { sync.RWMutex getEquivalencePod algorithm.GetEquivalencePodFunc algorithmCache map[string]AlgorithmCache } // The AlgorithmCache stores PredicateMap with predicate name as key type AlgorithmCache struct { // Only consider predicates for now predicatesCache *lru.Cache } Equivalence Cache真正的缓存数据是通过algorithmCache Map存储,其key为nodeName。 每个node上的Predicate Result Cache通过AlgorithmCache.predicateCache存储,predicateCache是LRU(Least Recently Used,最少最近使用算法)Cache,只能存储一定数量的Entries,Kubernetes中指定最大值为100(Kubernetes 1.10默认实现的Predicate Funcs一共有20个)。 LRU Cache是一个Cache置换算法,含义是“最近最少使用”,当Cache满(没有空闲的cache块)时,把满足“最近最少使用”的数据从Cache中置换出去,并且保证Cache中第一个数据是最近刚刚访问的。由“局部性原理”,这样的数据更有可能被接下来的程序访问,提升性能。 predicateCache也是k-v存储,key为predicateKey,value为PredicateMap。 predicateMap的key为uint64的Equivalence Hash,value为HostPredicate。 HostPredicate用来表示Pod使用Predicate Policy与某个node的匹配结果,结构如下: // HostPredicate is the cached predicate result type HostPredicate struct { Fit bool FailReasons []algorithm.PredicateFailureReason }
Equivalence Cache的核心操作 InvalidateCachedPredicateItem :用来从Equivalence Cache中删除某个node上某个predicate policy的所有EquivalenceHash(对应Equivalent Pods)的Predicate Result缓存数据。 func (ec *EquivalenceCache) InvalidateCachedPredicateItem(nodeName string, predicateKeys sets.String) { ... if algorithmCache, exist := ec.algorithmCache[nodeName]; exist { for predicateKey := range predicateKeys { algorithmCache.predicatesCache.Remove(predicateKey) } } ... } InvalidateCachedPredicateItemOfAllNodes :用来删除所有node上指定predicate policy集合对应的所有EquivalenceHash(对应Equivalent Pods)的Predicate Result缓存数据。 func (ec *EquivalenceCache) InvalidateCachedPredicateItemOfAllNodes(predicateKeys sets.String) { ... // algorithmCache uses nodeName as key, so we just iterate it and invalid given predicates for _, algorithmCache := range ec.algorithmCache { for predicateKey := range predicateKeys { // just use keys is enough algorithmCache.predicatesCache.Remove(predicateKey) } } ... } PredicateWithECache :检查Equivalence Cache中的Predicate Result缓存数据是否有可用的数据,如果命中缓存,则直接根据缓存中的Predicate Result作为该pod在该node上该Predicate policy的预选结果返回。如果没命中,则返回false和失败原因。 // PredicateWithECache returns: // 1. if fit // 2. reasons if not fit // 3. if this cache is invalid // based on cached predicate results func (ec *EquivalenceCache) PredicateWithECache( podName, nodeName, predicateKey string, equivalenceHash uint64, needLock bool, ) (bool, []algorithm.PredicateFailureReason, bool) { ... if algorithmCache, exist := ec.algorithmCache[nodeName]; exist { if cachePredicate, exist := algorithmCache.predicatesCache.Get(predicateKey); exist { predicateMap := cachePredicate.(PredicateMap) // TODO(resouer) Is it possible a race that cache failed to update immediately? if hostPredicate, ok := predicateMap[equivalenceHash]; ok { if hostPredicate.Fit { return true, []algorithm.PredicateFailureReason{}, false } return false, hostPredicate.FailReasons, false } // is invalid return false, []algorithm.PredicateFailureReason{}, true } } return false, []algorithm.PredicateFailureReason{}, true } UpdateCachedPredicateItem :当PredicateWithECache使用Predicate Result Cache数据命中失败时,scheduler会调用对应的Predicate Funcs触发真正的预选逻辑,完成之后,就通过UpdateCachedPredicateItem将刚预选的结果更新到Equivalence Cache缓存中。每个node的predicateCache的初始化也是在这里完成的。 // UpdateCachedPredicateItem updates pod predicate for equivalence class func (ec *EquivalenceCache) UpdateCachedPredicateItem( podName, nodeName, predicateKey string, fit bool, reasons []algorithm.PredicateFailureReason, equivalenceHash uint64, needLock bool, ) { ... if _, exist := ec.algorithmCache[nodeName]; !exist { ec.algorithmCache[nodeName] = newAlgorithmCache() } predicateItem := HostPredicate{ Fit: fit, FailReasons: reasons, } // if cached predicate map already exists, just update the predicate by key if v, ok := ec.algorithmCache[nodeName].predicatesCache.Get(predicateKey); ok { predicateMap := v.(PredicateMap) // maps in golang are references, no need to add them back predicateMap[equivalenceHash] = predicateItem } else { ec.algorithmCache[nodeName].predicatesCache.Add(predicateKey, PredicateMap{ equivalenceHash: predicateItem, }) } }
Equivalence Cache的初始化
Kubernetes在注册predicates、priorities、scheduler extenders时,同时也会进行Equivalence Cache的初始化,并将其传入scheduler config中。 // Creates a scheduler from a set of registered fit predicate keys and priority keys. func (c *configFactory) CreateFromKeys(predicateKeys, priorityKeys sets.String, extenders []algorithm.SchedulerExtender) (*scheduler.Config, error) { ... // Init equivalence class cache if c.enableEquivalenceClassCache && getEquivalencePodFuncFactory != nil { pluginArgs, err := c.getPluginArgs() if err != nil { return nil, err } c.equivalencePodCache = core.NewEquivalenceCache( getEquivalencePodFuncFactory(*pluginArgs), ) glog.Info("Created equivalence class cache") } ... } // NewEquivalenceCache creates a EquivalenceCache object. func NewEquivalenceCache(getEquivalencePodFunc algorithm.GetEquivalencePodFunc) *EquivalenceCache { return &EquivalenceCache{ getEquivalencePod: getEquivalencePodFunc, algorithmCache: make(map[string]AlgorithmCache), } }
NewEquivalenceCache负责Equivalence Cache的初始化工作,那么getEquivalencePod又是在哪完成注册的呢?defualt algorithm provider初始化时完成注册GetEquivalencePodFunc(只能使用defualt provider?通过configfile就不行吗?),注意这里 factory.PluginFactoryArgs 只传入了PVCInfo。 GetEquivalencePodFunc is a function that gets a EquivalencePod from a pod. pkg/scheduler/algorithmprovider/defaults/defaults.go:38 func init() { ... // Use equivalence class to speed up heavy predicates phase. factory.RegisterGetEquivalencePodFunction( func(args factory.PluginFactoryArgs) algorithm.GetEquivalencePodFunc { return predicates.NewEquivalencePodGenerator(args.PVCInfo) }, ) ... }
为什么只传入PVCInfo呢?或者为什么需要PVCInfo呢?要回答这个问题,我们先来看看EquivalencePod和getEquivalencePod的定义。 // EquivalencePod is a group of pod attributes which can be reused as equivalence to schedule other pods. type EquivalencePod struct { ControllerRef metav1.OwnerReference PVCSet sets.String }
EquivalencePod定义了具备哪些相同属性的Pods属于Equivalent Pods,Equivalence Hash就是根据Pod的EquivalencePod中指定的两个属性来计算的,这两个属性分别是: ControllerRef:对应Pod的meta.OwnerReference,对应Pod所属的Controller Object,可以是RS,RC,Job,StatefulSet类型之一。 PVCSet:是Pod所引用的所有PVCs IDs集合。
因此,只有两个Pod属于同一个Controller并且引用可同样的PVCs对象才被认为是EquivalentPod,对应同一个Equivalence Hash。
getEquivalencePod根据Pod Object中的OwnerReference和PVC信息获取它所属的EquivalencePod对象。 func (e *EquivalencePodGenerator) getEquivalencePod(pod *v1.Pod) interface{} { for _, ref := range pod.OwnerReferences { if ref.Controller != nil && *ref.Controller { pvcSet, err := e.getPVCSet(pod) if err == nil { // A pod can only belongs to one controller, so let's return. return &EquivalencePod{ ControllerRef: ref, PVCSet: pvcSet, } } return nil } } return nil }
何时生成Pod对应的Equivalence Hash
预选的入口是findNodesThatFit,也就是在findNodesThatFit中调用了getEquivalenceClassInfo计算Pod的EquivalenceHash,然后把该hash值传入podFitsOnNode中进行后续的Equivalence Class功能。 func findNodesThatFit( pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo, nodes []*v1.Node, predicateFuncs map[string]algorithm.FitPredicate, extenders []algorithm.SchedulerExtender, metadataProducer algorithm.PredicateMetadataProducer, ecache *EquivalenceCache, schedulingQueue SchedulingQueue, alwaysCheckAllPredicates bool, ) ([]*v1.Node, FailedPredicateMap, error) { ... var equivCacheInfo *equivalenceClassInfo if ecache != nil { // getEquivalenceClassInfo will return immediately if no equivalence pod found equivCacheInfo = ecache.getEquivalenceClassInfo(pod) } checkNode := func(i int) { nodeName := nodes[i].Name fits, failedPredicates, err := podFitsOnNode( pod, meta, nodeNameToInfo[nodeName], predicateFuncs, ecache, schedulingQueue, alwaysCheckAllPredicates, equivCacheInfo, ) ... } ... }
getEquivalenceClassInfo计算pod的EquivalenceHash的原理如下: // getEquivalenceClassInfo returns the equivalence class of given pod. func (ec *EquivalenceCache) getEquivalenceClassInfo(pod *v1.Pod) *equivalenceClassInfo { equivalencePod := ec.getEquivalencePod(pod) if equivalencePod != nil { hash := fnv.New32a() hashutil.DeepHashObject(hash, equivalencePod) return &equivalenceClassInfo{ hash: uint64(hash.Sum32()), } } return nil }
可见,EquivalenceHash就是对getEquivalencePod利用FNV算法进行哈希的。
Equivalent Pod的Predicate Result何时加到PredicateCache中
我们先看看podFitsOnNode的相关实现: func podFitsOnNode( pod *v1.Pod, meta algorithm.PredicateMetadata, info *schedulercache.NodeInfo, predicateFuncs map[string]algorithm.FitPredicate, ecache *EquivalenceCache, queue SchedulingQueue, alwaysCheckAllPredicates bool, equivCacheInfo *equivalenceClassInfo, ) (bool, []algorithm.PredicateFailureReason, error) { ... if predicate, exist := predicateFuncs[predicateKey]; exist { // Use an in-line function to guarantee invocation of ecache.Unlock() // when the in-line function returns. func() { var invalid bool if eCacheAvailable { // Lock ecache here to avoid a race condition against cache invalidation invoked // in event handlers. This race has existed despite locks in equivClassCacheimplementation. ecache.Lock() defer ecache.Unlock() // PredicateWithECache will return its cached predicate results. fit, reasons, invalid = ecache.PredicateWithECache( pod.GetName(), info.Node().GetName(), predicateKey, equivCacheInfo.hash, false) } if !eCacheAvailable || invalid { // we need to execute predicate functions since equivalence cache does not work fit, reasons, err = predicate(pod, metaToUse, nodeInfoToUse) if err != nil { return } if eCacheAvailable { // Store data to update equivClassCacheafter this loop. if res, exists := predicateResults[predicateKey]; exists { res.Fit = res.Fit && fit res.FailReasons = append(res.FailReasons, reasons...) predicateResults[predicateKey] = res } else { predicateResults[predicateKey] = HostPredicate{Fit: fit, FailReasons: reasons} } result := predicateResults[predicateKey] ecache.UpdateCachedPredicateItem( pod.GetName(), info.Node().GetName(), predicateKey, result.Fit, result.FailReasons, equivCacheInfo.hash, false) } } }() ... }
podFitsOnNode时会先通过PredicateWithECache检查是否Equivalence Cache中有该缓存命中: 如果有命中数据可用,则对应的Predicate Policy就算处理完成。 如果没有命中数据才会触发调用predicate,然后将predicate的结果通过UpdateCachedPredicateItem添加/更新到缓存中。
维护Equivalence Cache
我们回到Scheduler Config Factory,看看Scheduler中podInformer、nodeInformer、serviceInformer、pvcInformer等注册的EventHandler中对Equivalence Cache的操作。
Assume Pod
当完成pod的调度后,在Bind Node之前,会先进行Pod Assume,在Assume过程中,会对Equivalence Cache有操作。 // assume signals to the cache that a pod is already in the cache, so that binding can be asynchronous. // assume modifies `assumed`. func (sched *Scheduler) assume(assumed *v1.Pod, host string) error { ... // Optimistically assume that the binding will succeed, so we need to invalidate affected // predicates in equivalence cache. // If the binding fails, these invalidated item will not break anything. if sched.config.Ecache != nil { sched.config.Ecache.InvalidateCachedPredicateItemForPodAdd(assumed, host) } return nil }
Assume Pod时调用InvalidateCachedPredicateItemForPodAdd对Equivalence Cache进行操作。 func (ec *EquivalenceCache) InvalidateCachedPredicateItemForPodAdd(pod *v1.Pod, nodeName string) { // GeneralPredicates: will always be affected by adding a new pod invalidPredicates := sets.NewString("GeneralPredicates") // MaxPDVolumeCountPredicate: we check the volumes of pod to make decision. for _, vol := range pod.Spec.Volumes { if vol.PersistentVolumeClaim != nil { invalidPredicates.Insert("MaxEBSVolumeCount", "MaxGCEPDVolumeCount", "MaxAzureDiskVolumeCount") } else { if vol.AWSElasticBlockStore != nil { invalidPredicates.Insert("MaxEBSVolumeCount") } if vol.GCEPersistentDisk != nil { invalidPredicates.Insert("MaxGCEPDVolumeCount") } if vol.AzureDisk != nil { invalidPredicates.Insert("MaxAzureDiskVolumeCount") } } } ec.InvalidateCachedPredicateItem(nodeName, invalidPredicates) }
InvalidateCachedPredicateItemForPodAdd中可以看出,Assume Pod会删除该node上以下predicateKey对应的predicateCache: GeneralPredicates; 如果该pod中引用了PVCs,则会删除"MaxEBSVolumeCount", "MaxGCEPDVolumeCount", "MaxAzureDiskVolumeCount"这些PredicateCaches; 如果pod volume中使用了AWSElasticBlockStore,则会删除MaxEBSVolumeCount PredicateCache; 如果pod volume中使用了GCEPersistentDisk,则会删除MaxGCEPDVolumeCount PredicateCache; 如果pod volume中使用了AzureDisk,则会删除MaxAzureDiskVolumeCount PredicateCache;
Update Pod in Scheduled Pod Cache
在scheduler进行NewConfigFactory时,注册Update assignedNonTerminatedPod Event Handler为updatePodInCache。 func (c *configFactory) updatePodInCache(oldObj, newObj interface{}) { ... c.invalidateCachedPredicatesOnUpdatePod(newPod, oldPod) c.podQueue.AssignedPodUpdated(newPod) } func (c *configFactory) invalidateCachedPredicatesOnUpdatePod(newPod *v1.Pod, oldPod *v1.Pod) { if c.enableEquivalenceClassCache { // if the pod does not have bound node, updating equivalence cache is meaningless; // if pod's bound node has been changed, that case should be handled by pod add & delete. if len(newPod.Spec.NodeName) != 0 && newPod.Spec.NodeName == oldPod.Spec.NodeName { if !reflect.DeepEqual(oldPod.GetLabels(), newPod.GetLabels()) { // MatchInterPodAffinity need to be reconsidered for this node, // as well as all nodes in its same failure domain. c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes( matchInterPodAffinitySet) } // if requested container resource changed, invalidate GeneralPredicates of this node if !reflect.DeepEqual(predicates.GetResourceRequest(newPod), predicates.GetResourceRequest(oldPod)) { c.equivalencePodCache.InvalidateCachedPredicateItem( newPod.Spec.NodeName, generalPredicatesSets) } } } }
updatePodInCache调用invalidateCachedPredicatesOnUpdatePod对Equivalence Cache做了如下处理: 如果pod Labels做了更新,那么会删除所有nodes上Equivalence Cache中的MatchInterPodAffinity PredicateCache; 如果pod的resource request做了更新,那么会删除该node上Equivalence Cache中的GeneralPredicates PredicateCache;
Delete Pod in Scheduled Pod Cache
同样的,当发生删除assignedNonTerminatedPod时,对应会调用invalidateCachedPredicatesOnDeletePod更新Equivalence Cache。 func (c *configFactory) invalidateCachedPredicatesOnDeletePod(pod *v1.Pod) { if c.enableEquivalenceClassCache { // part of this case is the same as pod add. c.equivalencePodCache.InvalidateCachedPredicateItemForPodAdd(pod, pod.Spec.NodeName) // MatchInterPodAffinity need to be reconsidered for this node, // as well as all nodes in its same failure domain. // TODO(resouer) can we just do this for nodes in the same failure domain c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes( matchInterPodAffinitySet) // if this pod have these PV, cached result of disk conflict will become invalid. for _, volume := range pod.Spec.Volumes { if volume.GCEPersistentDisk != nil || volume.AWSElasticBlockStore != nil || volume.RBD != nil || volume.ISCSI != nil { c.equivalencePodCache.InvalidateCachedPredicateItem( pod.Spec.NodeName, noDiskConflictSet) } } } }
invalidateCachedPredicatesOnDeletePod更新Equivalence Cache的处理总结为: 删除该node上Equivalence Cache中的GeneralPredicates PredicateCache; 如果该pod中引用了PVCs,则会删除该node上Equivalence Cache中的"MaxEBSVolumeCount", "MaxGCEPDVolumeCount", "MaxAzureDiskVolumeCount"这些PredicateCaches; 如果pod volume中使用了AWSElasticBlockStore,则会删除该node上Equivalence Cache中的MaxEBSVolumeCount PredicateCache; 如果pod volume中使用了GCEPersistentDisk,则会删除该node上Equivalence Cache中的MaxGCEPDVolumeCount PredicateCache; 如果pod volume中使用了AzureDisk,则会删除该node上Equivalence Cache中的MaxAzureDiskVolumeCount PredicateCache; 删除所有nodes上Equivalence Cache中的MatchInterPodAffinity PredicateCache; 如果pod的resource request做了更新,那么会删除该node上Equivalence Cache中的GeneralPredicates PredicateCache; 如果pod volume中引用了GCEPersistentDisk、AWSElasticBlockStore、RBD、ISCSI之一,则删除该node上Equivalence Cache中的NoDiskConflict PredicateCache。
Update Node
当发生node update event时,对应会调用invalidateCachedPredicatesOnNodeUpdate更新Equivalence Cache。 func (c *configFactory) invalidateCachedPredicatesOnNodeUpdate(newNode *v1.Node, oldNode *v1.Node) { if c.enableEquivalenceClassCache { // Begin to update equivalence cache based on node update // TODO(resouer): think about lazily initialize this set invalidPredicates := sets.NewString() if !reflect.DeepEqual(oldNode.Status.Allocatable, newNode.Status.Allocatable) { invalidPredicates.Insert(predicates.GeneralPred) // "PodFitsResources" } if !reflect.DeepEqual(oldNode.GetLabels(), newNode.GetLabels()) { invalidPredicates.Insert(predicates.GeneralPred, predicates.CheckServiceAffinityPred) // "PodSelectorMatches" for k, v := range oldNode.GetLabels() { // any label can be topology key of pod, we have to invalidate in all cases if v != newNode.GetLabels()[k] { invalidPredicates.Insert(predicates.MatchInterPodAffinityPred) } // NoVolumeZoneConflict will only be affected by zone related label change if isZoneRegionLabel(k) { if v != newNode.GetLabels()[k] { invalidPredicates.Insert(predicates.NoVolumeZoneConflictPred) } } } } oldTaints, oldErr := helper.GetTaintsFromNodeAnnotations(oldNode.GetAnnotations()) if oldErr != nil { glog.Errorf("Failed to get taints from old node annotation for equivalence cache") } newTaints, newErr := helper.GetTaintsFromNodeAnnotations(newNode.GetAnnotations()) if newErr != nil { glog.Errorf("Failed to get taints from new node annotation for equivalence cache") } if !reflect.DeepEqual(oldTaints, newTaints) || !reflect.DeepEqual(oldNode.Spec.Taints, newNode.Spec.Taints) { invalidPredicates.Insert(predicates.PodToleratesNodeTaintsPred) } if !reflect.DeepEqual(oldNode.Status.Conditions, newNode.Status.Conditions) { oldConditions := make(map[v1.NodeConditionType]v1.ConditionStatus) newConditions := make(map[v1.NodeConditionType]v1.ConditionStatus) for _, cond := range oldNode.Status.Conditions { oldConditions[cond.Type] = cond.Status } for _, cond := range newNode.Status.Conditions { newConditions[cond.Type] = cond.Status } if oldConditions[v1.NodeMemoryPressure] != newConditions[v1.NodeMemoryPressure] { invalidPredicates.Insert(predicates.CheckNodeMemoryPressurePred) } if oldConditions[v1.NodeDiskPressure] != newConditions[v1.NodeDiskPressure] { invalidPredicates.Insert(predicates.CheckNodeDiskPressurePred) } if oldConditions[v1.NodeReady] != newConditions[v1.NodeReady] || oldConditions[v1.NodeOutOfDisk] != newConditions[v1.NodeOutOfDisk] || oldConditions[v1.NodeNetworkUnavailable] != newConditions[v1.NodeNetworkUnavailable] { invalidPredicates.Insert(predicates.CheckNodeConditionPred) } } if newNode.Spec.Unschedulable != oldNode.Spec.Unschedulable { invalidPredicates.Insert(predicates.CheckNodeConditionPred) } c.equivalencePodCache.InvalidateCachedPredicateItem(newNode.GetName(), invalidPredicates) } }
因此,node update时,会删除该node对应的Equivalence Cache中如下PredicateKey的PredicateCache: GeneralPredicates, 前提:node.Status.Allocatable或node labels发生变更. ServiceAffinity, 前提:node labels发生变更。 MatchInterPodAffinity, 前提:node labels发生变更。 NoVolumeZoneConflict, 前提:failure-domain.beta.kubernetes.io/zone或failure-domain.beta.kubernetes.io/region Annotation发生变更; PodToleratesNodeTaints, 前提: Node的Taints(对应scheduler.alpha.kubernetes.io/taints Annotation)发生变更. CheckNodeMemoryPressure, CheckNodeDiskPressure, CheckNodeCondition, 前提:如果对应的Node Condition发生变更。
Delete Node
当发生node delete event时,对应会调用InvalidateAllCachedPredicateItemOfNode更新Equivalence Cache。 // InvalidateAllCachedPredicateItemOfNode marks all cached items on given node as invalid func (ec *EquivalenceCache) InvalidateAllCachedPredicateItemOfNode(nodeName string) { ec.Lock() defer ec.Unlock() delete(ec.algorithmCache, nodeName) glog.V(5).Infof("Done invalidating all cached predicates on node: %s", nodeName) }
因此,node delete时,则会从Equivalence Cache中删除整个node对应的algorthmCache。
Add or Delete PV
当发生pv add或者delete event时,对应会调用invalidatePredicatesForPv更新Equivalence Cache。 func (c *configFactory) invalidatePredicatesForPv(pv *v1.PersistentVolume) { // You could have a PVC that points to a PV, but the PV object doesn't exist. // So when the PV object gets added, we can recount. invalidPredicates := sets.NewString() // PV types which impact MaxPDVolumeCountPredicate if pv.Spec.AWSElasticBlockStore != nil { invalidPredicates.Insert(predicates.MaxEBSVolumeCountPred) } if pv.Spec.GCEPersistentDisk != nil { invalidPredicates.Insert(predicates.MaxGCEPDVolumeCountPred) } if pv.Spec.AzureDisk != nil { invalidPredicates.Insert(predicates.MaxAzureDiskVolumeCountPred) } // If PV contains zone related label, it may impact cached NoVolumeZoneConflict for k := range pv.Labels { if isZoneRegionLabel(k) { invalidPredicates.Insert(predicates.NoVolumeZoneConflictPred) break } } if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { // Add/delete impacts the available PVs to choose from invalidPredicates.Insert(predicates.CheckVolumeBindingPred) } c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes(invalidPredicates) }
因此,当add或者delete PV时,会从Equivalence Cache中删除所有nodes的以下predicateKey对应的PredicateCache: MaxEBSVolumeCount, MaxGCEPDVolumeCount, MaxAzureDiskVolumeCount,前提:PV类型是这三者的范围内;
Update PV
当发生pv update event时,对应会调用invalidatePredicatesForPvUpdate更新Equivalence Cache。 func (c *configFactory) invalidatePredicatesForPvUpdate(oldPV, newPV *v1.PersistentVolume) { invalidPredicates := sets.NewString() for k, v := range newPV.Labels { // If PV update modifies the zone/region labels. if isZoneRegionLabel(k) && !reflect.DeepEqual(v, oldPV.Labels[k]) { invalidPredicates.Insert(predicates.NoVolumeZoneConflictPred) break } } c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes(invalidPredicates) }
因此,当update PV时,会从Equivalence Cache中删除所有nodes的以下predicateKey对应的PredicateCache: NoVolumeZoneConflict, 前提:PV的failure-domain.beta.kubernetes.io/zone或failure-domain.beta.kubernetes.io/region Annotation发生变更;
Add or Delete PVC func (c *configFactory) invalidatePredicatesForPvc(pvc *v1.PersistentVolumeClaim) { // We need to do this here because the ecache uses PVC uid as part of equivalence hash of pod // The bound volume type may change invalidPredicates := sets.NewString(maxPDVolumeCountPredicateKeys...) // The bound volume's label may change invalidPredicates.Insert(predicates.NoVolumeZoneConflictPred) if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { // Add/delete impacts the available PVs to choose from invalidPredicates.Insert(predicates.CheckVolumeBindingPred) } c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes(invalidPredicates) }
当发生pvc add或者delete event时,会从Equivalence Cache中删除所有nodes的以下predicateKey对应的PredicateCache: "MaxEBSVolumeCount", "MaxGCEPDVolumeCount", "MaxAzureDiskVolumeCount" PredicateCaches; NoVolumeZoneConflict PredicateCaches; CheckVolumeBinding,前提,VolumeScheduling这个Feature Gate是启用状态;
Update PVC func (c *configFactory) invalidatePredicatesForPvcUpdate(old, new *v1.PersistentVolumeClaim) { invalidPredicates := sets.NewString() if old.Spec.VolumeName != new.Spec.VolumeName { if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { // PVC volume binding has changed invalidPredicates.Insert(predicates.CheckVolumeBindingPred) } // The bound volume type may change invalidPredicates.Insert(maxPDVolumeCountPredicateKeys...) } c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes(invalidPredicates) }
当发生pvc update event时,会从Equivalence Cache中删除所有nodes的以下predicateKey对应的PredicateCache: CheckVolumeBinding,前提:VolumeScheduling这个Feature Gate是启用状态,并且PVC对应的PV发生变更; "MaxEBSVolumeCount", "MaxGCEPDVolumeCount", "MaxAzureDiskVolumeCount" PredicateCaches,前提:PVC对应的PV发生变更;
Add or Delete Service func (c *configFactory) onServiceAdd(obj interface{}) { if c.enableEquivalenceClassCache { c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes(serviceAffinitySet) } c.podQueue.MoveAllToActiveQueue() } func (c *configFactory) onServiceDelete(obj interface{}) { if c.enableEquivalenceClassCache { c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes(serviceAffinitySet) } c.podQueue.MoveAllToActiveQueue() }
当发生Service Add或Delete event时,会从Equivalence Cache中删除所有nodes的以下predicateKey对应的PredicateCache: CheckServiceAffinity;
Update Service func (c *configFactory) onServiceUpdate(oldObj interface{}, newObj interface{}) { if c.enableEquivalenceClassCache { // TODO(resouer) We may need to invalidate this for specified group of pods only oldService := oldObj.(*v1.Service) newService := newObj.(*v1.Service) if !reflect.DeepEqual(oldService.Spec.Selector, newService.Spec.Selector) { c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes(serviceAffinitySet) } } c.podQueue.MoveAllToActiveQueue() }
当发生Service Update event时,会从Equivalence Cache中删除所有nodes的以下predicateKey对应的PredicateCache: CheckServiceAffinity,前提:Service的Selector发生变更。
Equivalence Class的不足 Equivalence Class Feature最困难的就是如何最优的维护和更新Equivalence Cache,做到每次更新都是最小粒度的、准确无误的,目前这方面还需优化。 Equivalence Cache只缓存Predicate Result,并不支持Priority Result数据的缓存和维护(社区正在实现基于Map-Reduce方式优化),通常情况下,Priority Funcs的处理逻辑要比Predicate Funcs复杂,支持的意义就更大。 Equivalence Class目前只能根据Pod对应的OwnerReference和PVC信息进行Equivalence Hash,如果能摒弃OwnerReference的考虑,充分考虑Pod spec中那些核心的field,比如resource request, Labels,Affinity等,缓存命中的几率可能会大的多,Predicate的性能就能得到更显著的提升。
总结
Equivalence Class是用来给Kubernetes Scheduler加速Predicate,从而提升Scheduler的吞吐性能。当然,普通用户其实无需关注Equivalence Class Feature,因为目前的scheduler性能对大部分用户来说已经足够了,但对于有大规模AI训练场景的用户,可以多关注它。
云计算
2018-05-17 00:48:00
摘要: 表格存储的增量数据流功能能够使用户使用API获取Table Store表中增量数据,并可以进行增量数据流的实时增量分析、数据增量同步等。通过创建Table Store触发器,能够实现Table Store Stream和函数计算的自动对接,让计算函数中自定义的程序逻辑自动处理Table Store表中发生的数据修改,充分的利用了函数计算全托管、弹性伸缩的特点。
函数计算(Function Compute) 是一个事件驱动的服务,通过函数计算,用户无需管理服务器等运行情况,只需编写代码并上传。函数计算准备计算资源,并以弹性伸缩的方式运行用户代码,而用户只需根据实际代码运行所消耗的资源进行付费。
Table Store Stream是用于获取Table Store表中增量数据的一个数据通道,通过创建Table Store触发器,能够实现Table Store Stream和函数计算的自动对接,让计算函数中自定义的程序逻辑自动处理Table Store表中发生的数据修改。
表格存储高并发的写入性能以及低廉的存储成本非常适合物联网、日志、监控数据的存储,我们可以将数据写入到表格存储中,同时在函数计算中对新增的数据做简单的清洗、转换、聚合计算等操作,并将清洗之后的数据写回到表格存储的结果表中,并对原始明细数据及结果数据提供实时访问。
下面,我们使用函数计算对表格存储中的数据做简单的清洗,并写入到结果表中。
数据定义
我们假设写入的为日志数据,包括三个基础字段:
字段名称 类型 含义 id 整型 日志id
level message
整型 字符串
日志的等级,越大表明等级越高 日志的内容
我们需要将 level>1 的日志写入到另外一张数据表中,用作专门的查询。
实现过程:
创建实例及数据表
在 表格存储的控制台 创建表格存储实例(__本次以 华东2 distribute-test 为例__),并创建源表(__source_data__)及结果表(__result__),主键为均 __id (整型)__,由于表格存储是 schemafree 结构,无需预先定义其他属性列字段。
开启数据源表的Stream功能
触发器功能需要先开启数据表的 Stream功能 ,才能在函数计算中处理写入表格存储中的增量数据。
Stream记录过期时长 为通过 StreamAPI 能够读取到的增量数据的最长时间。
由于触发器只能绑定现有的函数,故先到函数计算的控制台上在同region创建服务及函数。
创建函数计算服务
在 函数计算的控制台 上创建服务及处理函数,我们继续使用华东2节点。
1.在华东2节点创建服务。
2.创建函数依次选择:空白函数——不创建触发器。 函数名称为:etl_test,选择 python2.7 环境,在线编辑代码 函数入口为:etl_test.handler 代码稍后编辑,点击下一步。
3.进行服务授权
由于函数计算需要将运行中的日志写入到日志服务中,同时,需要对表格存储的表进行读写,故需要对函数计算进行授权,为方便起见,我们先添加 AliyunOTSFullAccess 与 __AliyunLogFullAccess __权限,实际生产中,建议根据权限最小原则来添加权限。
4.点击授权完成,并创建函数。
5.修改函数代码。
创建好函数之后,点击对应的 函数 — 代码执行 ,编辑代码并保存,其中,INSTANCE_NAME(表格存储的实例名称)、REGION(使用的区域)需要根据情况进行修改:
使用示例代码如下: #!/usr/bin/env python # -*- coding: utf-8 -*- import cbor import json import tablestore as ots INSTANCE_NAME = 'distribute-test' REGION = 'cn-shanghai' ENDPOINT = 'http://%s.%s.ots-internal.aliyuncs.com'%(INSTANCE_NAME, REGION) RESULT_TABLENAME = 'result' def _utf8(input): return str(bytearray(input, "utf-8")) def get_attrbute_value(record, column): attrs = record[u'Columns'] for x in attrs: if x[u'ColumnName'] == column: return x['Value'] def get_pk_value(record, column): attrs = record[u'PrimaryKey'] for x in attrs: if x['ColumnName'] == column: return x['Value'] #由于已经授权了AliyunOTSFullAccess权限,此处获取的credentials具有访问表格存储的权限 def get_ots_client(context): creds = context.credentials client = ots.OTSClient(ENDPOINT, creds.accessKeyId, creds.accessKeySecret, INSTANCE_NAME, sts_token = creds.securityToken) return client def save_to_ots(client, record): id = int(get_pk_value(record, 'id')) level = int(get_attrbute_value(record, 'level')) msg = get_attrbute_value(record, 'message') pk = [(_utf8('id'), id),] attr = [(_utf8('level'), level), (_utf8('message'), _utf8(msg)),] row = ots.Row(pk, attr) client.put_row(RESULT_TABLENAME, row) def handler(event, context): records = cbor.loads(event) #records = json.loads(event) client = get_ots_client(context) for record in records['Records']: level = int(get_attrbute_value(record, 'level')) if level > 1: save_to_ots(client, record) else: print "Level <= 1, ignore."
对表格存储 Stream 数据的格式详情请参考 Stream 数据处理
绑定触发器
1.回到表格存储的实例管理页面,点击表 source_data 后的 使用触发器 按钮,进入触发器绑定界面,点击 使用已有函数计算 , 选择刚创建的服务及函数,勾选 表格存储发送事件通知的权限 , 进行确定。
2.绑定成功之后,能够看到如下的信息:
运行验证
1.向 source_data 表中写入数据。
2.在 result 表中查询清洗后的数据 点击 result 表的数据管理页面,会查询到刚写入到 source_data 中的数据。 当然,向 soure_data 写入level <=1的数据将不会同步到 result 表中
原文链接
本文为云栖社区原创内容,未经允许不得转载
云计算
2018-05-16 22:07:00
摘要: 智能媒体管理提供了 Cloud Native 架构的文档转换/预览服务,本文介绍快速搭建的示例,让您用 DIY 的体验方式实现文档预览功能。
一、导语
智能媒体管理 提供了 Cloud Native 架构的文档转换/预览服务,本文介绍快速搭建的示例,让您用 DIY 的体验方式实现文档预览功能。
二、环境准备
2.1 准备 RAM 子账号
为了实现安全的文档转换/预览功能,本文使用 RAM 子账号来管理资源并进行代码开发。
2.1.1 创建 test 子账号
登陆 访问控制 页面,点击“用户管理”,选择“新建用户”,创建子账号 test ,创建成功如下图所示:
2.1.2 子账号授权
在创建子账号成功后的界面,选择 test 子账号,点击“授权”,确保给该子账号授予如下策略: AliyunOSSFullAccess 。具有完整访问 OSS 的权限,让文档转换能够在 OSS 存储转换数据。本文为了快速搭建而选择此权限,如果需要更精细的权限控制,可以参考 OSS 子账号设置常见问题 。 AliyunSTSAssumeRoleAccess 。具有申请 STS Token的权限,预览时为客户端提供 STS Token,从而保证前端访问 OSS 的安全性。 AliyunIMMFullAccess 。具有执行 IMM 服务的权限,具有了该权限就可以调用文档转换接口。
2.1.3 生成子账号的AK(Access Key)
代码开发时,需要使用AK(Access Key)。在 2.1.1章节 创建子账号成功后的界面,选择 test 子账号,点击“管理”,进入 test 子账号后再点击“创建 AccessKey”,保存好 AccessKey ID 和 Secret,它们将用于代码开发。
注意:目前每个子账号最多使用2个 AK,可以通过删除旧的 AK 来解决。
如上图中,得到 AK ID 类似 LTAIxxxxxxxxxxx ,对应的 AK Secret 类似 W1yyyyyyyyyyyyyyyyyyyy 。
注意:这里提供的值只是示例,不能直接使用,具体的值请您在控制台获取并保存。
2.2 准备 OSS 服务
文档转换/预览是基于 OSS 实现,需要 OSS 的存储空间支持,本文通过 创建新桶 来实现,如下图所示。
在 华东2(上海区域)创建了 yourid-dev-imm 桶。
注意:该桶名只是示例,不能直接使用,需要替换为您的桶名。
2.3 准备 IMM 服务
转换/预览使用 IMM 服务,先 开通产品 产品,然后 创建项目 ,如下图所示,在 华东2(上海区域)创建了 imm 项目。
注意:一定需保证创建 IMM 项目 时指定的 Region 要和您创建 OSS 桶 指定的区域相同,才能够正常工作,目的是避免转换时带来跨 OSS Region 访问的流量费用。
2.4 准备开发环境
本文基于 “IntelliJ IDEA + Maven” 准备 Java 开发环境,参考 IMM 服务的 Java SDK 文档。
注意:在 pom.xml 文件中添加 aliyun-java-sdk-core 和 aliyun-java-sdk-imm 的版本依赖。 com.aliyun aliyun-java-sdk-core true 3.5.1 com.aliyun aliyun-java-sdk-imm true 1.2.1
三、开发部署
3.1 转换代码
环境准备好后,可以参考如下 Java 代码实现文档转换,技术细节请参考 转换原理 。 import com.aliyuncs.imm.main.IMMClient; import com.aliyuncs.imm.model.v20170906.*; import com.aliyuncs.exceptions.*; public class TestImmOffice { public static void main(String[] args) { String accessKeyId = "LTAIxxxxxxxxxxx"; //RAM 中 test 子账号的 AK ID String accessKeySecret = "W1yyyyyyyyyyyyyyyyyyyy"; //RAM 中 test 子账号的 AK Secret IMMClient client = new IMMClient("cn-shanghai", accessKeyId, accessKeySecret); ConvertOfficeFormatResponse resp = new ConvertOfficeFormatResponse(); ConvertOfficeFormatRequest req = new ConvertOfficeFormatRequest(); req.setProject("IMM"); //在 IMM 中创建的项目 req.setSrcUri("oss://yourid-dev-imm/test-data/office/paxos.pptx"); //OSS 源文件路径 req.setTgtUri("oss://yourid-dev-imm/test-data/office/paxos.pptx/output"); //OSS 转换文件路径 req.setTgtType("vector"); try { resp = client.getResponse(req); System.out.printf("requestId=%s, taskId=%s, tgtloc=%s", resp .getRequestId(), resp.getTaskId(),resp.getTgtLoc()); }catch (ClientException e){ System.out.println("error"); } } }
转换成功后,您将在 OSS 的 yourid-dev-imm 桶( 华东2---上海区域 ),对应 test-data/office/paxos.pptx/output/doc 目录下(注意:转换引擎增加了 doc 后缀),得到 meta.json , fp*.json , 以及 I 目录下的文件。
3.2 预览部署
转换成功后,可以基于部署的前端预览引擎实现预览功能,技术介绍请参考 预览原理 。如下图所示,在 yourid-dev-imm 桶( 华东2---上海区域 )中添加了 preview目录 ,它包含了预览引擎的文件。
3.3 前端访问
部署预览引擎后,前端就可以通过访问预览引擎所在 OSS 桶的域名,实现预览功能。基于上述章节介绍,可以通过 https://yourid-dev-imm.oss-cn-shanghai.aliyuncs.com/preview/index.html 路径访问渲染引擎。
注意:实际使用时请把 yourid-dev-imm 更换为您的桶。
3.3.1 OSS 公共读权限预览
为了快速体验预览引擎的效果,我们简化 OSS 的权限配置为公共读,如下图所示。
此时,可以通过如下地址格式完成文档预览。 https://yourid-dev-imm.oss-cn-shanghai.aliyuncs.com/preview/index.html ?url=https://yourid-dev-imm.oss-cn-shanghai.aliyuncs.com/test-data/office/paxos.pptx/output ®ion=oss-cn-shanghai &bucket=yourid-dev-imm
其中 ? 前面部分为访问渲染引擎的路径,而 url=xxx 为转换文档的目标地址( 注意:在 output 后无需加上 / ,渲染引擎会自动处理 ), region=oss-cn-shanghai 表示 OSS 桶所在区域, bucket=yourid-dev-imm 为桶名。
注意:实际使用时,需要把 yourid-dev-imm 更换为自己的桶。
3.3.2 OSS 私有权限预览(高安全)
设置桶为私有,但让渲染引擎 preview 目录下的文件为公共读,然后为转换文件申请STS Token policy = { "Version": "1", "Statement": [ { "Effect": "Allow", "Action": [ "oss:*" ], "Resource": [ "acs:oss:*:" + accountId + ":" + bucket + "/" + prefix + "/*" ] }, { "Effect": "Allow", "Action": [ "oss:ListObjects" ], "Resource": [ "acs:oss:*:" + accountId + ":" + bucket ], "Condition": { "StringLike": { "oss:Prefix": prefix + "/*" } } } ] }
此时把 accountId 设置为 * ,表示任意用户;bucket 设置为 yourid-dev-imm ,表示您自己创建的桶;prefix 设置为本文中的路径 test-data/office/paxos.pptx/output 。然后,调用 STS 的 AssumeRole 得到 AccessKeyId,AccessKeySecret,SecurityToken三元组,然后用如下方式组合,即可实现安全的预览访问。 https://yourid-dev-imm.oss-cn-shanghai.aliyuncs.com/preview/index.html ?url=https://yourid-dev-imm.oss-cn-shanghai.aliyuncs.com/test-data/office/paxos.pptx/output &accessKeyId=STS.AAAAAAAAAA &accessKeySecret=BBBBBBBBBBB &stsToken=CCCCCCCCCCCCC ®ion=oss-cn-shanghai &bucket=yourid-dev-imm &...
四、参考文档
参考如下的文章,帮助您掌握相关背景: 智能媒体管理产品文档转换/预览功能介绍(1)---Cloud Native架构 智能媒体管理产品文档转换/预览功能介绍(2)---转换原理 智能媒体管理产品文档转换/预览功能介绍(3)---前端预览
原文链接
本文为云栖社区原创内容,未经允许不得转载
云计算
2018-05-16 21:36:00
摘要: 本文将为您介绍,在采集多类日志数据的情况下,阿里云业务实时监控服务(ARMS)之前端监控如何优化日志上报
前端监控 (又叫UEM,User Experience Management, 用户体验管理) 一般帮助用户定位页面性能瓶颈、复现用户端的偶发问题。其监控的主要功能包括但不限于: 日志采集 日志上报 数据分析 平台展示 异常报警
使用前端监控平台时,用户最关心的问题往往首先是: 平台可以监控哪些数据? 会不会影响业务性能?
这就涉及到前端监控的监控指标和日志上报。带着这两个问题,本文就为您介绍一下,在采集多类日志数据的情况下, 阿里云业务实时监控服务(ARMS)之前端监控 (以下简称为阿里云前端监控)如何优化日志上报,让上报速度快、更快、无比快!
文章主要分为两个部分:第一部分"监控指标介绍"解释前端监控一般需要上报哪些数据;第二部分"日志上报优化秘笈"解释前端监控如何针对这些数据上报进行优化。
监控指标介绍
阿里云前端监控 专注于 Web 端真实用户体验数据监控,从访问速度、页面运行稳定性和服务调用成功率三个方面监控前端的健康度。另外,阿里云前端监控还提供针对轻量级交互行为的实时统计功能,可帮助您及时发现业务问题。
阿里云前端监控的指标如下:
1. 页面是否正常响应 页面性能日志 — 实时统计与页面相关的 12 个关键性能指标,帮助您迅速准确地定位性能瓶颈: DNS 解析耗时 TCP 连接耗时 SSL 安全连接耗时 网络请求耗时 DOM 解析耗时 资源加载耗时 首包时间 白屏时间 首次可交互时间 DOM Ready 时间 页面完全加载时间 …… 访问统计日志 — 统计 PV、UV 数据。短时间内断崖式的变化很容易反映业务问题。
2. 页面运行是否稳定 页面稳定性日志 — 阿里云前端监控以 JS 错误率衡量页面运行稳定性,会采集因页面加载和页面交互产生的 JS Error 报错文件、错误堆栈的详细信息,快速定位用户访问时发生的错误问题。
3. API 请求是否正常响应 API 调用日志 — 提供 API 调用结果、耗时及相关信息,快速发现并定位 API 问题。
4. 业务相关日志 自定义上报日志 — 某些业务逻辑的结果、速度、统计值等自定义内容可帮助您发现业务问题。
如果前端业务复杂、访问量级较大,那么相应地,前端监控上报的日志类型及日志量也会快速增长。前端监控的最基本原则是日志获取和日志上报不能影响业务性能,所以在这种情况下,日志上报性能尤为重要。
接下来,我们就介绍一下阿里云前端监控平台的日志上报优化秘笈。只要学会了这几种新姿势,即便日志量不断增长,您也能游刃有余!
日志上报优化秘笈
第一招:HTTP No Content
日志上报只关心日志有没有上报,并不关心上报请求的返回内容,甚至完全可以不需要返回内容。所以使用 HTTP HEAD 的方式上报,并且返回的响应体为空,可避免响应体传输造成的资源损耗。只需要设置一个 nginx 服务器来记录日志内容并返回 200 状态码即可。 fetch(`${url}?t=perf&page=lazada-home&load=1168`, {mode:'no-cors',method:'HEAD'})
第二招:HTTP 2.0
HTTP/2 头部压缩
每次 HTTP 请求都会传输一系列的请求头来描述请求的资源及其特性,然而实际上每次请求都有很多相同的值,如 Host: 、 user-agent: 、 Accept 等。这些数据会占用 300-800 byte 的传输量,如果携带大的 cookie,请求头甚至会占据 1 kb 的空间,而实际真正需要上报的日志数据仅有 10~50 byte。在 HTTP 1.x 中,每次日志上报请求头都携带了大量的重复数据导致性能浪费。
HTTP/2 头部压缩 采用 Huffman Code 压缩请求头,并用动态表更新每次请求不同的数据,从而将每次请求的头部压缩到很小。 HTTP/1.1 效果 HTTP/2.0 效果
压缩头部后,每条日志请求都大幅缩小,响应的速度也相应提升。
HTTP/2 多路复用
用户浏览器和日志服务器之间产生多次 HTTP 请求,而在 HTTP/1.1 Keep-Alive 下,日志上报会以串行的方式传输,并让后面的日志上报延时。通过 HTTP/2 的多路复用来合并上报,可为您节省网络连接的开销。
第三招:HTTP POST 合并
POST 请求因为 request body 可以有更大施展空间,相比一条日志一次 HTTP HEAD 请求的方式,在 HTTP POST 中一次包含多条日志的内容更加经济。
在 HTTP POST 的基础上,可以顺便解决用户关掉或者切换页面造成的漏报问题。
以前常见的解决方式是监听页面的 unload 或者 beforeunload 事件,并以通过同步的 XMLHttpRequest 请求或者构造一个特定 src 的 标签来延迟上报。 window.addEventListener("unload", uploadLog, false); function uploadLog() { var xhr = new XMLHttpRequest(); xhr.open("POST", "/r.png", false); // false表示同步 xhr.send(logData); }
这种上报方式的弊端是会影响下一个页面的性能。更优雅的方式是使用 navigator.sendBeacon() ,它能够异步发送日志数据。 window.addEventListener("unload", uploadLog, false); function uploadLog() { navigator.sendBeacon("/r.png", logData); } 合并前
合并后(navigator.sendBeacon)
理想情况下,合并 N 个日志上报耗费的总时间可缩减至原来的 1/N。
总结
阿里云前端监控 的日志上报整体优化流程如下:
经过这几步优化后,日志上报性能明显提升: 日志上报的传输量可大幅降低至原来的 1/10 左右 日志上报响应时间可提速 80% 左右
实际大促业务场景在线上的验证结果表明,业务性能不会受到影响。所以,即便您的业务访问量级较大或者性能要求较高,也无需担心接入后的性能问题,可放心接入使用。
附:阿里云业务实时监控服务(ARMS)前端监控系介绍 业务实时监控服务(ARMS) 应用监控 前端监控 自定义监控
原文链接
本文为云栖社区原创内容,未经允许不得转载

云计算
2018-05-16 21:05:00
摘要: 一、 MaxCompute是什么 你的OSS数据是否作堆积在一旁沉睡已久存储成本变为企业负担你是否想唤醒沉睡的数据驱动你的业务前行MaxCompute可以帮助你高效且低成本的解决这些问题通过对海量数据进行分析和计算来实现勾勒用户画像、提升营销转化、挖掘产品优化方向、预测业务发展等丰富的业务场景。
一、 MaxCompute 是什么?
你的OSS数据是否作堆积在一旁沉睡已久,存储成本变为企业负担?你是否想唤醒沉睡的数据,驱动你的业务前行?MaxCompute可以帮助你高效且低成本的解决这些问题,通过对海量数据进行分析和计算来实现勾勒用户画像、提升营销转化、挖掘产品优化方向、预测业务发展等丰富的业务场景。
MaxCompute是一项提供快速、完全托管的EB级数据仓库解决方案的大数据计算服务,可以高效并经济的分析处理海量数据。作为阿里云大数据旗舰产品,MaxCompute的EB级别性能处理达到了全球领先性,被Forrester评为全球云端数据仓库领导者。同时,MaxCompute也是阿里巴巴内部大数据旗舰平台,阿里巴巴近99%的数据存储以及95%的计算能力都在这个平台上产生。
最近MaxCompute重磅推出了一项重要特性: OSS外表查询功能 。该功能可以帮助您直接对OSS中的海量文件进行查询,而不必将数据加载到MaxCompute 表中,既节约了数据搬迁的时间和人力,也节省了多地存储的成本。除此之外,MaxCompute外表查询功能还拥有如下的优势:
1、MaxCompute是一个无服务器的分布式计算架构,无需用户再额外维护和管理服务器基础设施,能方便及时的为OSS用户提供临时按需的查询服务,从而大大帮助企业节省成本。目前该功能处于公测阶段,免费使用;
2、支持处理OSS上开源格式的结构化文件,包括:Avro、CSV、ORC、Parquet、RCFile、RegexSerDe、SequenceFile和TextFile,同时支持gzip压缩格式;
3、提供灵活的用户自定义代码的处理框架,用来支持处理OSS上非结构化文件,用户可以自行编写代码直接对OSS上的数据进行处理和计算。比如对OSS上的视频,图像,音频,基因,气象等数据进行特征提取和分析,可以支持丰富的第三方音视频处理库;
二、 客户案例
1、 华大基因
基因技术从实验室逐渐进入生活场景,数据体量爆发式增长,远超出传统计算能力所能支持的范围。基于这样的背景,华大选择了MaxCompute。在百万人基因组项目中,对人群结构的分析,oss存放了大量的fastq文件,传统计算方式需3-5天,且需要将数据同步到数据仓库,现在通过外表功能,MaxCompute可使整个分析在1小时内完成,极大加速了数据吞吐和交付生产效率。
2、天弘基金
天弘基金旗下的余额宝,是中国规模最大的货币基金。除理财功能外,余额宝还是移动互联网时代的现金管理工具。余额宝每天有大量的金融数据交换文件存放在oss上,需要进行超大文本文件的结构化分析,之前是把oss文件先下载到本地,然后再上传到MaxCompute,链路长且效率不高。现在oss上的大文件可以直接用外部表的方式加载到MaxCompute做分析,整个链路的效率得到了大幅提升。
三、 如何使用MaxCompute?
下面我们通过两个简单的示例,介绍如何通过MaxCompute外表功能实现对OSS数据的分析和处理。
场景一:物联网采集数据分析
Step1 :准备工作
1、开通OSS 、MaxCompute服务
您可以通过官网分别开通OSS、 MaxCompute服务 ,并创建OSS bucket、 MaxCompute Project 。
2、采集数据到OSS
您可以使用任何数据集来执行测试,以验证我们在这篇文章中概述的最佳实践。
本文准备一批 CSV 数据存在 OSS 上,endpoint 为 oss-cn-beijing-internal.aliyuncs.com ,bucket 为 oss-odps-test ,数据文件的存放路径为 /demo/vehicle.csv 。
3、授权MaxCompute访问OSS
MaxCompute需要直接访问OSS的数据,前提需要将OSS的数据相关权限赋给MaxCompute的访问账号,可以直接登录阿里云账号后, 点击此处完成一键授权 。
Step2 :通过MaxCompute创建外部表
创建外部表,语句如下: CREATE EXTERNAL TABLE IF NOT EXISTS ambulance_data_csv_external ( vehicleId int, recordId int, patientId int, calls int, locationLatitute double, locationLongtitue double, recordTime string, direction string ) STORED BY 'com.aliyun.odps.CsvStorageHandler' LOCATION 'oss://oss-cn-beijing-internal.aliyuncs.com/oss-odps-test/Demo/';

Step3 :通过MaxCompute查询外部表
外部表创建成功后,便可如同普通表一样使用这个外部表。假设/demo/vehicle.csv数据如下: 1. 1,1,51,1,46.81006,-92.08174,9/14/2014 0:00,S 2. 1,2,13,1,46.81006,-92.08174,9/14/2014 0:00,NE 3. 1,3,48,1,46.81006,-92.08174,9/14/2014 0:00,NE 4. 1,4,30,1,46.81006,-92.08174,9/14/2014 0:00,W 5. 1,5,47,1,46.81006,-92.08174,9/14/2014 0:00,S 6. 1,6,9,1,46.81006,-92.08174,9/14/2014 0:00,S 7. 1,7,53,1,46.81006,-92.08174,9/14/2014 0:00,N 8. 1,8,63,1,46.81006,-92.08174,9/14/2014 0:00,SW 9. 1,9,4,1,46.81006,-92.08174,9/14/2014 0:00,NE 10. 1,10,31,1,46.81006,-92.08174,9/14/2014 0:00,N
执行如下 SQL 语句:
1. select recordId, patientId, direction from ambulance_data_csv_external where patientId > 25;
输出结果如下: 1. +------------+------------+-----------+ 2. | recordId | patientId | direction | 3. +------------+------------+-----------+ 4. | 1 | 51 | S | 5. | 3 | 48 | NE | 6. | 4 | 30 | W | 7. | 5 | 47 | S | 8. | 7 | 53 | N | 9. | 8 | 63 | SW | 10. | 10 | 31 | N | 11. +------------+------------+-----------+
关于更多详细的OSS外表使用方法,请参考官方文档, 点这里 。
场景二:阿里云产品消费账单分析
Step1 :准备工作
完成案例1中准备工作1、3步骤。
Step2 :通过费用中心同步账单数据到OSS
打开费用中心->消费记录-> 存储到OSS ,输入oss bucket,此示例为oms-yl
, 服务开通后,每天会将增量的实例消费明细数据生成文件同步存储到您的OSS指定的bucket中。
Step3 :通过MaxCompute注册账单处理类
1、点击 这里下载, odps-udf-example-0.30.0-SNAPSHOT-jar-with-dependencies.jar
2、将自定义代码编译打包,并上传到 MaxCompute。
add jar odps-udf-example-0.30.0-SNAPSHOT-jar-with-dependencies.jar
Step4 :通过MaxCompute创建外部表
示例:创建5月4日的账单消费表 CREATE EXTERNAL TABLE IF NOT EXISTS oms_oss_0504 ( 月份 string, 资源拥有者 string, 消费时间 string, 消费类型 string, 账单编号 string, 商品 string, 计费方式 string, 服务开始时间 string, 服务结束时间 string, 服务时长 string, 财务核算单元 string, 资源id string, 资源昵称 string, TAG string, 地域 string, 可用区 string, 公网ip string, 内网ip string, 资源配置 string, 原价 string, 优惠金额 string, 应付金额 string, 计费项1 string, 使用量1 string, 资源包扣除1 string, 原价1 string , 应付金额1 string, 计费项2 string, 使用量2 string, 资源包扣除2 string, 原价2 string, 应付金额2 string, 计费项3 string, 使用量3 string, 资源包扣除3 string, 原价3 string, 应付金额3 string, 计费项4 string, 使用量4 string, 资源包扣除4 string, 原价4 string, 应付金额4 string, 计费项5 string, 使用量5 string, 资源包扣除5 string, 原价5 string, 应付金额5 string, 计费项6 string, 使用量6 string, 资源包扣除6 string, 原价6 string, 应付金额6 string, 计费项7 string, 使用量7 string, 资源包扣除7 string, 原价7 string, 应付金额7 string, 计费项8 string, 使用量8 string, 资源包扣除8 string, 原价8 string, 应付金额8 string, 计费项9 string, 使用量9 string, 资源包扣除9 string, 原价9 string, 应付金额9 string ) STORED BY 'com.aliyun.odps.udf.example.text.TextStorageHandler' --STORED BY 指定自定义 StorageHandler 的类名。 with SERDEPROPERTIES ( 'odps.text.option.complex.text.enabled'='true', 'odps.text.option.strict.mode'='false' --遇到列数不一致的情况不会抛异常,如果实际列数少于schema列数,将所有列按顺序匹配,剩下的不足的列补NULL ) LOCATION 'oss://oss-cn-beijing-internal.aliyuncs.com/oms-yl/2018-05-04/' USING 'text_oss.jar'; --同时需要指定账单中的文本处理类定义所在的 jar 包

Step5 :通过MaxCompute查询外部表
查询示例:查询MaxCompute按量存储消费账单
select 月份,使用量3,原价3,应付金额3 from oms_oss where 计费项3='Storage' and 商品=大数据计算服务MaxCompute(按量付费);

输出结果如下:
四、 总结
通过上述示例,将沉睡在OSS中的非结构化数据激活,通过MaxCompute把海量数据分析工作效率提升至分钟级,帮助客户更高效、更低成本的挖掘海量数据价值。
原文链接
本文为云栖社区原创内容,未经允许不得转载


云计算
2018-05-16 20:24:00
盘点云计算的优势,较低的托管成本、较低的基础架构复杂性、较高的可扩展性……这些都是实实在在的好处。不过对于企业来说,选择云计算最关键的驱动在于产品速度,换句话说,利用适当的云计算产品和技术,我们可以在最短时间内把理念变成用户需要的实际产品。
过去三年里,有大量的企业投入到了容器化架构的怀抱之中,从踩坑到克服未知的难题,再到得心应手,一部分公司逐渐尝到了“产品速度”带来的甜头。
但也有另一部分企业,容器运行测试、扩大测试范围、得到管理层批准、全面迁移上云,并且相信容器编排工具Kubernetes会处理好所有的事情,然后在一切似乎顺利进展的过程中,莫名其妙陷入了与指数级增长的复杂度抗争的漩涡之中——监控、存储、处理不同组件、定义通信、网络安全……进退两难。
问题到底出在哪呢?以下六点,抛砖引玉。
第一:基础设施
企业一般会在IaaS厂商那里买几台机器,希望通过将基础设施迁移到云端来达到马上省钱的目的,却发现成本非但没有降低,反而可能有些上升,于是开始反思是不是哪里出了问题。
出现这种情况其实是正常的,首先通过不断的调优,资源方面的节省才能显现出来;其次云计算的核心更多在于低风险和敏捷性,而不完全是“可见的成本“。
想象我们用过去普遍的方式为数据中心购买硬件,很可能会买到错误的大小或者错误的机器,此时我们应该拿这些机器怎么办呢?另一方面,把应用调整到一个不适合的环境中本身是一个耗时费力的过程,这方面的成本怎么算?
选择云计算的话,以上问题迎刃而解,原本购买基础设施的风险要小得多,配置环境的时间和人力成本同样大大降低,也就是说我们产品速度上的成本降低了。对于开发者,是花两年时间来做对业务有直接价值的工作,还是与错误的环境斗争到底,答案是很明显的。
所以第一个经验是,首先思考如何降低风险,而不是降低硬件成本。
第二:自动化
过去的经验告诉我们,私有云也可以实现非常高的自动化水平,一切取决于企业是否选择了正确的服务和工具,像自动化部署、CI/CD、自动化配置、伸缩等等。
在这一方面需要注意的问题主要集中在落地模式和建设规划两方面。落地模式上,企业需要按照自身情况来做出选择,是使用定制化产品,还是在开源框架的基础上自己进行开发;建设规划上,企业的自动化过程往往不是一蹴而就的,需要一个循序渐进的过程,前期的IT架构梳理极为关键。
第三:文化
在技术向云计算转变的同时,企业文化也应该随之而变。传统的模式下,企业规避风险即不惜一切代价将不确定性最小化,这导致很多企业IT拒绝进行改变,也就逐渐在竞争中落了下风,而这也是部分企业在云计算技术方面实施后,发现并没有解决问题,反而产生了新问题的原因所在。
正确的做法是应用一种“实验“的文化,通过分析、规划、评估和测试来尝试不同的方法,根据结果来进行调整,随时准备好迭代、准备好回滚、准备好再一次尝试。
这也就是为什么DevOps被公认为是一种文化,也是IT转型的重要途径。当然文化本身的转变也不是一朝一夕的事情,需要从早期开始落实,更需要持续不断的影响和强调。
第四:微服务
关于微服务架构的定义、优势等等不必多言,从spring cloud、dubbo到最新的service mesh,这些年大红大紫的微服务架构,真正能够拿出来作为可实践案例的不多。
除了对于技术栈的选择困难,很多企业的误区在于“一步到位”的错觉,导致企业本身将最初的门槛设立极高,而正常的过程是应当以最小代价发布第一个微服务,在跨过这个门槛之后,剩下的工作可以被复制而且速度会越来越快。
另一个教训,其实不止是微服务架构方面,就是尽量不要重复造轮子,参考或者选用成熟的解决方案永远会是最便捷的落地途径。
第五:容器化
Docker是容器化最突出的技术代表。利用Docker构建一个容器,包含一个微服务所需的所有依赖,而后部署在任何想要部署的地方。
而在最近,docker受到了一些批评和质疑,我们认为原因在于很多企业对docker的期望有些不切实际,而忽略了容器化同时需要考虑的调度编排、存储网络、服务注册发现以及宿主机管理等等紧密联系的技术问题。
另一方面,我们要知道Docker其实并不是一项新技术,它的创新更多是在思想层面,在它对于封装、隔离、发布的改进。
第六:编排
真正用于生产的应用往往会跨越多个容器,往往会部署在多台机器上,会涉及到如服务发现、负载均衡、高可用、存储、网络等等。目前容器编排事实上的标准是Kubernetes。
关于Kubernetes,难点在于这一容器编排工具本身包含很多高难度技术概念,学习门槛很高、学习曲线很陡峭,同时缺少管理流程。所以这里容易出现的是“用不起来“的问题,解决办法除了活学活用,还可以考虑使用第三方包装好的Kubernetes面板和解决方案。
好雨,让云落地,提供以应用为中心的云计算产品和服务。访问 https://www.goodrain.com 了解更多
云计算
2018-05-16 13:07:00
Rancher 2.0正式版已全面发布。Rancher 2.0是一个开源的Kubernetes管理平台,为企业用户提供Kubernetes-as-a-Service (Kubernetes即服务),并且能够实现多Kubernetes集群的统一纳管。这一创造性的统一纳管功能将解决生产环境中企业用户可能面临的基础设施不同的困境。Rancher 2.0是业界第一个能统一纳管来自Google(GKE)、Amazon(EKS)和Azure(AKS)等公有云上托管的Kubernetes服务的平台。
在Rancher 2.0中,我们重点关注的一个领域就是身份认证和授权。在Kubernetes强大的基础功能之外,Rancher 2.0格外专注于简易性和易用性,它是一个既强大又易于使用的系统。Rancher 2.0让管理员能够管理多集群环境,同时还能够帮助用户快速启动并运行环境。本文将从身份认证和授权的角度,介绍Rancher能够给组织、管理员和用户带来哪些好处。
在深入讨论Rancher能带来什么之前,我们将先在本文前半部分简要回顾一下Kubernetes身份认证与授权相关的概念。如果想深入了解这些概念的更多细节,可参考Kubernetes官方的文档:
https://kubernetes.io/docs/admin/authentication/
https://kubernetes.io/docs/admin/authorization/rbac/
身份认证
想要理解Kubernetes的身份认证以及Rancher如何对这一功能进行拓展加强,那么就必须要先理解下面这几个关键的概念:身份认证策略、用户和组,以及用户模拟。
身份认证策略(Authentication Strategies)
Kubernetes提供了多种身份认证策略,包括:客户端证书、OpenID Connect令牌、Webhook令牌认证、身份认证代理、服务账户令牌等等。每一种策略都有它的优缺点,但最终它们都要负责判断申请API调用的用户的身份,这样Kubernetes RBAC框架才可以决定是否要授权给申请调用者,让其执行其请求的操作。
尽管已经有大量可用的策略能解决大多数情况,但需要注意的是,配置它们需要精确控制Kubernetes控制平台的配置和部署。像Google这样的云服务提供商通常会将其锁定,防止用户按照自己的喜好配置它。而Rancher 2.0解决了这个问题,我们会在后面讨论。
用户和组(Users and Groups)
Kubernetes有两种类型的用户:服务账户和普通用户。其中服务账户完全由Kubernetes管理,而“普通”用户则完全不受Kubernetes的管理。事实上,Kubernetes没有用户或组的API资源。因此最终,普通用户和组在用户绑定中表现为晦涩的对象,用以做权限的检查。
用户模拟(User Impersonation)
Kubernetes中的用户模拟是一个用户(或服务账户)扮演另一个用户的能力。一个subject必须明确地具有“模拟”特权,才能够执行对其他用户的模拟。虽然这可能看起来是一个相当模糊和细微的功能,但它对于Rancher如何实现身份验证至关重要。
授 权
要理解Kubernetes中的授权以及Rancher如何构建它,还必须理解这些概念:roles(角色)、clusterRoles(集群角色)、roleBindings(角色绑定)和clusterRoleBindings(集群角色绑定)。从命名就能看出,这些概念之间非常相似,但适用于不同的范围。
roles是命名空间的一个作用域,这意味着它是在命名空间中创建的,并且只能由该命名空间内的roleBinding引用。roleBinding在用户、组或者服务账户(在Kubernetes中称为subject)和命名空间中的role之间创建关联。它有效地说明了用户 X在命名空间Z中具有Y角色,或者我们给一个具体的例子:Sarah能够在“dev”这个命名空间中进行部署的创建、更新和删除。
clusterRole的样子和作用方面与role非常相似。唯一的区别是它没有命名空间。clusterRole是在集群层面定义的。同样的,clusterRoleBinding是roleBinding的无命名空间版本。当你创建clusterRoleBinding时,意味着你为特定的subject赋予了可用于整个集群、每个命名空间的权限。
需要注意的是:roleBinding可以引用role或者clusterRole。无论它引用的role类型是什么,权限只适用于rolebinding所在的命名空间。
有了对Kubernetes基础概念的理解,我们接下来可以开始讨论Rancher是如何使用和增强它们,创建出强大且易于使用的身份认证和授权系统的。
Rancher的身份认证和授权
Rancher 2.0的主要目标之一,是帮助系统管理员运行多个异构的Kubernetes集群。这些集群可以是来自于云提供商或本地解决方案的任何组合,这就产生了许多有趣的身份认证和授权挑战。其中我们确定并解决的关键问题是: 如何在不同类型的集群中拥有统一的身份验证体验? 如何管理跨集群的用户和权限? 如何启用“自动服务”方式使用集群,同时保持适当的控制水平? 如何防止用户在低信任环境中获得对底层基础设施资源的过多访问?
每一种挑战我们接下来都会讨论。
统一认证
为了实现跨集群的统一身份认证体验,我们将Rancher服务器设计成所有身份验证的中心点。管理员只需在Rancher中配置一次身份认证工具,它就可以应用到任何地方。之后,在所有访问Kubernetes集群的请求面前,Rancher都相当于一个身份验证代理。
由于大多数云提供商不公开必要的hooks用来插入Kubernetes的各种认证策略,因此Rancher的身份验证代理位于外部,独立于集群而存在。它执行必要的身份认证工作,收集用户的身份和任何的组,然后通过用户模拟将请求转发到适当的集群,以充当该用户。正因为认证方法是标准的Kubernetes无记号令牌,因此Rancher的代理可以无缝地插入kubectl等现有的Kubernetes工具中。
用户管理
正如之前所说,Kubernetes没有一等用户的理念,而Rancher有。用户可以由管理员手动创建,也可以在GitHub等身份认证工具那里按需创建(Github在头一次打开时需要用户登录)。我们从Rancher 1.x中吸取了经验教训,在默认情况下,本地身份认证是开启且始终开启的。这样以来,Rancher在默认情况下是安全的,并且在身份认证工具出现故障时提供了访问Rancher的备份机制。
创建一个驻留在中央Rancher服务器上的一等用户资源可以带来很多好处。例如,管理员现在可以查看和操作任何特定用户对所有集群的访问权限。它还使Rancher能够管理特定于每个用户的资源,如系统首选项、API令牌和节点模板。最后,它使得管理用户权限变得更简单,我们将在下文讨论。
RBAC 授权
在深入讨论授权之前,我们必须先介绍和讨论一个关键的Rancher概念:项目。项目是可以应用于各种策略的命名空间的集合。这些策略(并非所有的策略都进入了我们的初始GA版本)包括RBAC、网络访问、pod安全性和配额管理。项目“拥有”命名空间,以及为项目所做的任何RBAC绑定都适用于项目中的所有命名空间。这个关键概念允许将集群有效地分割和组织成更小、更易于管理的块(chunks)。
Rancher有效地为用户提供了三层roles或权限:全局、集群和项目层级。全局定义了你可以在单个集群之外执行的操作。对于大多数人来说,这可以认为是将用户或组的子集标记为“管理员”,其余部分标记为“普通”用户。除了可以完全访问所有集群外,管理员还可以执行配置身份验证提供者和管理用户等操作。而普通用户只能访问他们拥有或已被邀请的集群或项目。
Rancher RBAC直接建立在Kubernetes RBAC之上(前面讨论的role和binding概念)。如果你了解Kubernetes的概念,Rancher RBAC就很容易理解。实际上,我们在Rancher服务器中创建roles和bindings模板,并将它们传播到适当的集群。因此,我们在Rancher API中有以下自定义资源:roleTemplates,clusterRoleTemplateBindings以及projectRoleTemplateBindings。管理员可以管理roleTemplates和集群,而项目所有者可以使用它们授予对其集群或项目不同程度的访问权限。
自助服务访问
Rancher默认支持自助服务访问模式,帮助组织授权用户从Kubernetes获得更多信息。普通用户可以创建自己的集群并成为其所有者。他们是该集群的管理员,可以将其他用户和组设成集群成员,授予他们访问权限。一旦用户成为了集群成员,它就可以在集群中创建项目并成为这些项目的所有者。作为项目所有者,可以邀请其他人称为项目成员或所有者。项目成员能够在他们所属的项目中创建命名空间并部署工作负载。你可以看到,这个自助服务系统是如何创建的,并让用户能够快速且轻松地启动和运行。
而这种方式下,也有常见的问题:“如果我不想让用户创建集群或项目,该怎么办?”
这一问题有几个答案。首先,如果他们不能访问基础设施资源(意味着他们无法创建虚拟机或者没有组织云提供商的密钥),那么他们无法创建功能集群。其次,我们的RBAC系统是可配置的,这样管理员可以在默认情况下明确地选择用户可以做什么。最后,用户可以直接被添加到项目中,而不需要创建明确的集群成员。这意味着他们将不能创建新的项目,而只能使用那些他们被明确添加进去的项目。通过这种方式,Rancher使组织能够授权它们的用户,同时给予管理员他们所需要的控制。
控制基础设施层级的访问
许多用例会要求用户限制他们可以部署的容器类型以及这些容器允许执行的内容。为了解决这个问题,Kubernetes搬出了podSecurityPolicies。这是一个非常重要的功能,但它的原始形式却很难正确使用。关于它是如何工作的,以及他能做什么,这些讨论操出了本文的范围,但我们可以这么总结它:podSecurityPolicies允许管理员限制可以部署在集群中的pod类型。用一个简单和容易理解的例子来说就是,它可以防止用户部署特权容器,这为许多用例解决了大的安全漏洞。
Rancher不仅支持podSecurityPolicies,而且增强了该功能,大大提高了可用性。使用Rancher,管理员可以在全局定义一组用于所有集群的podSecurityPolicy模板。然后,集群所有者可以将默认策略分配给集群,并在每个项目基础上管理例外情况。换句话说,集群所有者可以说:“除了少数特殊项目外,所有项目都有一个限制策略,阻止他们部署特权容器。”此功能可用于安全的多租户集群。
总 结
通过本文,希望你能看到我们在Rancher 2.0中对身份验证和授权的关注。所有这一切都建立在Kubernetes基本概念的基础之上。秉承Rancher一贯关注可用性及简单性的原则,Rancher 2.0对Kubernetes身份认证和授权进行了更多增强和扩展,以创建出更加强大的组合,帮助企业用户更简单快捷落地Kubernetes。
云计算
2018-05-16 10:26:00
安装 chart
当我们觉得准备就绪,就可以安装 chart,Helm 支持四种安装方法: 安装仓库中的 chart,例如: helm install stable/nginx 通过 tar 包安装,例如: helm install ./nginx-1.2.3.tgz 通过 chart 本地目录安装,例如: helm install ./nginx 通过 URL 安装,例如: helm install https://example.com/charts/nginx-1.2.3.tgz
这里我们使用本地目录安装:
当 chart 部署到 Kubernetes 集群,便可以对其进行更为全面的测试。
将 chart 添加到仓库
chart 通过测试后可以将其添加到仓库,团队其他成员就能够使用。任何 HTTP Server 都可以用作 chart 仓库,下面演示在 k8s-node1 192.168.56.106 上搭建仓库。 在 k8s-node1 上启动一个 httpd 容器。
通过 helm package 将 mychart 打包。
执行 helm repo index 生成仓库的 index 文件。

Helm 会扫描 myrepo 目录中的所有 tgz 包并生成 index.yaml 。 --url 指定的是新仓库的访问路径。新生成的 index.yaml 记录了当前仓库中所有 chart 的信息:

当前只有 mychart 这一个 chart。 将 mychart-0.1.0.tgz 和 index.yaml 上传到 k8s-node1 的 /var/www/charts 目录。
通过 helm repo add 将新仓库添加到 Helm。

仓库命名为 newrepo ,Helm 会从仓库下载 index.yaml。 现在已经可以 repo search 到 mychart 了。

除了 newrepo/mychart ,这里还有一个 local/mychart 。这是因为在执行第 2 步打包操作的同时, mychart 也被同步到了 local 的仓库。 已经可以直接从新仓库安装 mychart 了。
如果以后仓库添加了新的 chart,需要用 helm repo update 更新本地的 index。

这个操作相当于 Ubutun 的 apt-get update 。
小结
本章我们学习了 Kubernetes 包管理器 Helm。
Helm 让我们能够像 apt 管理 deb 包那样安装、部署、升级和删除容器化应用。
Helm 由客户端和 Tiller 服务器组成。客户端负责管理 chart,服务器负责管理 release。
chart 是 Helm 的应用打包格式,它由一组文件和目录构成。其中最重要的是模板,模板中定义了 Kubernetes 各类资源的配置信息,Helm 在部署时通过 values.yaml 实例化模板。
Helm 允许用户开发自己的 chart,并为用户提供了调试工具。用户可以搭建自己的 chart 仓库,在团队中共享 chart。
Helm 帮助用户在 Kubernetes 上高效地运行和管理微服务架构应用,Helm 非常重要。
下节我们开始学习 Kubernetes 网络。
书籍:
1.《每天5分钟玩转Kubernetes》
https://item.jd.com/26225745440.html
2.《每天5分钟玩转Docker容器技术》
https://item.jd.com/16936307278.html
3.《每天5分钟玩转OpenStack》
https://item.jd.com/12086376.html
云计算
2018-05-16 07:06:00
本文作者:AIOps智能运维
作者简介
凌薇 百度云智能运维业务研发负责人

负责百度云Noah自动化运维平台和智能运维解决方案的探索,在服务管理、资源管理、变更管理和故障管理的业务分析和设计方面经验丰富,致力于推进AIOps在百度业务、公有云以及私有云客户的运维场景落地。

为什么要写这篇文章
做了这么多年项目,参加过无数次团队内外的项目复盘,发现不少 项目延期 和客户 交付质量 的问题。这些问题给产品和技术负责人带来了不少应急“救火”的困扰。分析这些Case后,发现问题集中在以下几个方面: 需求界定不清晰、系统设计有缺陷、研发质量无保障、无效沟通耗时长,导致项目反复并且严重延期; 跨团队协作推动成本高,多团队交付进度出现Delay,项目整体目标不可控; 概要设计文档、API手册、产品使用手册和运维手册质量差,客户学习成本高;
我们团队通常会使用 项目复盘 (Case Study)的方法来应对这些情况。复盘主要为了解决以下两个问题:其一, 为项目延期和客户交付风险找到可行的解决方法 ;其二, 给团队成员一些指导,避免同一个问题重复出现 。当然,这些复盘工作一般在某个项目组内部开展,需要一种方式能够在多个项目组之间共享,这便是我写此文章的原因。
项目管理和研发质量控制是一个比较复杂的系统工程,本文不会系统的讲解一些理论和原则,而是以实际案例为索引分享一下项目管理中常见的问题以及必须要采取的方法。前车之覆,后车之鉴,希望能对新晋的项目管理同学有些帮助。
案例一:多角色人员协作的业务项目
场景描述
某监控产品需要对多个Region的多个云服务(例如虚机、数据库组件、缓存组件、消息队列组件)提供多个指标趋势图对比查看功能。产品研发需要PM设计产品形态和逻辑,UE设计产品视觉交互,若干FE研发前端页面,若干RD研发后端业务逻辑,QA测试业务功能,测试通过后FE和RD联合上线。项目研发从预期的1个半月拖延至实际3个月。研发中经历至少5轮测试,发现2个产品缺陷,5个技术方案缺陷,低级Bug预估20+,Bug收敛速度极慢。这是一个极其典型的项目管理和研发质量失控案例,参与项目的多数是新同学, 研发流程规范 上认知度严重欠缺。
为了便于大家对项目过程的理解,附注一下相关的产品设计、接口设计和低级Bug例子: 产品设计缺陷 :PM产品设计时遗漏在趋势图上标注不同Region的云服务名字,导致用户无法定位指标所归属的云服务实例。 接口设计缺陷 :产品要求一个趋势图最多支持30个云服务实例的指标对比,前端FE接口参数检验限定为20个,后端RD接口参数校验限定为10个;趋势图指标数据查询无论用户选择的时间段多长,指标查询的粒度都是60s,导致在时间跨度很大的情况下,返回数据点过多页面渲染性能差。 低级Bug :选择实例和监控项之后可以查看趋势图,但是取消监控项选择之后趋势图未消失;时间选择框对趋势图展示的指标数据的时间段控制作用失效。
关键问题 产品设计和接口设计缺陷应该是在什么阶段发现? 低级Bug为什么那么多?Bug收敛速度为什么那么慢? 项目出现延期风险时,项目负责人应该在什么阶段管控风险?
解决方案
这个项目的关键是没有严格遵循常规的软件研发流程规范。 PRD没有经过评审 ,PM简单画了几个原型图开始安排前端FE和业务RD开发,导致产品缺陷没有在PRD评审阶段发现; 前端FE和后端RD的接口设计 没有完备的文档 ,没有经过充分的沟通;RD提测代码时没有经过整体场景的联调和Demo Review,导致低级Bug在测试阶段才暴露出来; QA的 测试准入 没有严格执行,低级Bug多的情况下,QA未实施测试打回; 技术负责人没有在项目即将延期时进行 问题复盘 ,而是在延期两周后才跟进问题,错过了关键的 项目修复时间 ,增加了很多不必要的多人协作成本。
这个案例在很多 新项目新团队成员 中比较常见。对于多角色协作的项目需要执行严格的研发流程规范,需求相关的MRD,产品设计PRD,UE设计稿、总体设计和详细设计文档都需按照规范撰写并且经过 团队评审 ,只有前一个环节通过才能进入下一个环节。尽早交流和持续沟通使开发人员获得对产品和业务的信息,始终遵守“ 及早发现,及早解决 ”的准则,如此我们便能在软件研发过程中价值最低的阶段修正问题。RD在交付QA之前需要进行 系统联调 和 Demo Review ,确保研发和产品设计保持一致。研发质量需要符合 编码规范 ,并且有 单测指标 ,单测的行覆盖率和分支覆盖率不达标QA可以拒绝测试。QA需要严格执行测试准入,对于低级Bug多的同学不仅拒绝测试,并且团队内公示项目中每个同学的 代码质量 。
项目管理者需要执行严格的研发流程规范,需要合理的安排项目的进度。通常的经验是为 1/3计划、1/6编码、1/4构件测试以及1/4系统测试 ,所以一定不要在前期的设计评审阶段和后期的联调单测阶段节省时间,不然会使得项目风险频发,脱离控制。任何创造性活动都伴随着枯燥艰苦的劳动,编程也不例外。大家通常期望项目在接近结束时,软件项目能收敛的快一些,然而,情况却是越接近完成,收敛的越慢。一个风险问题的暴露,一个里程碑的进度偏离,最终会累积使得整体进度偏离很远。慢性进度偏离是士气杀手,及早的问题复盘,持续的信息同步有助于将脱轨的项目拉回到正常的轨道。
案例二:多团队联合解决方案实施
场景描述
部门成立一个近20个团队的 联合项目 ,实施核心业务线的SCAR(项目代号)。该项目的主要目标是结合多个平台系统提供的能力,组合成一个复杂解决方案,帮助业务提升能力。项目的周期是一年,多个平台研发团队和十几个业务团队需要完成该解决方案的研发和业务落地。项目实施中的初期发现平台研发符合预期,若干个业务团队没有承诺明确的落地时间,或者承诺的时间因为团队的其他项目影响落后于项目预期。联合团队采取了 紧急沟通 ,实施了一些 双重汇报机制和指标管控方法 ,保障了项目如期交付。这个项目成功落地业务线取得了非常好的效果,作为成功案例在多个团队做项目管理分享。
关键问题 如何确保多团队目标的一致性? 如何跟进项目及时发现进度风险?
解决方案
多团队协作的一个重要问题是每个团队都有各自的 重点项目指标 ,SCAR项目只是其中的一个,也可能不是其重点项目,各个团队投入的关注度和资源不一定充分。在这种情况下,需要成立横向联合的 虚拟团队 ,在多个团队的经理层面明确项目目标,使得该目标变成经理自身考核KPI中的一项,并且每个团队还需要抽取出一名成员作为接口人参与联合项目。虚拟团队实施双重汇报机制,团队成员除了向各自的经理汇报以外,还需要向横向联合团队的负责人汇报,团队成员的年底绩效考核时,横向联合团队的负责人也会给出一份评价。这种机制可以确保各个团队的项目经理对项目的 支持度 ,以及跨团队的成员在项目中有足够的投入和友好的协作。
因为涉及的团队比较多,多个业务团队的落地进展对横向团队负责人来说是一个黑箱。横向联合团队负责人需要设定相应的指标监督项目进度和识别项目风险。关键的指标包括以下三类: 先行指标(Leading Indicator) :项目启动之初建立该项指标,明确到项目结束时SCAR系统具备的能力以及在业务线实施的覆盖度,通过这些指标可以引导项目负责人关注黑箱中该注意的事情。 线性指标(Linearity Indicator) :为了达到目标设定的理想进度指标,可以理解为项目分季度分月的KPI指标,比如系统研发的进度,比如业务线实施覆盖度。以业务线实施覆盖度为例,可能14个业务线,第一个季度实施4个业务线,后面的两个季度每个季度实施5个业务线。设置线性目标是为了指导 项目分阶段 的进度,避免因为项目时间跨度过长项目风险整体不可控。 趋势指标(Trend Indicator) :以时间标准为基础,根据对过去的观察,从趋势中预测未来。例如,项目的初期系统易用性较差,业务落地的成本高,前期的业务实施覆盖度指标有可能落后于一开始设置的线性指标。经过一段时间的系统优化,业务落地的成本变低了,后期的业务实施覆盖度指标有可能快于一开始设置的线性指标。项目负责人需要周期性Review项目的趋势指标,及时 协调资源 ,调整项目的进度和 把控风险 。
通过虚拟团队的双重汇报机制,通过设定项目的先行指标和线性指标,周期性Review趋势指标,可以帮助项目负责人在多团队协作项目中能够比较好识别项目风险和调度资源解决问题。
案例三:ToB客户验收项目
场景描述
团队承接了某个客户的平台研发,需要交付时提供完备的系统概要设计文档、用户手册和标准运维流程手册给客户。项目交付前期团队内部抽查了部分文档,发现一些文档内容的 完备性、可读性和可操作性欠缺 ,急需规范和优化。为了保障对客户高质量的输出,团队陷入紧急的文档优化过程,很多RD和PM进入了加班“救火”状态。在ToB项目中,每一份文档都代表着对客户的承诺和服务意识,代表着一个团队的技术素养,需要认真对待。
关键问题 什么阶段该写文档?一个好的文档应该具备什么样的特征? 团队经理和项目负责人如何保障文档质量?
解决方案
概要设计文档需要在项目设计阶段产出并且评审通过,而不是在项目交付阶段进行补充;接口设计需要在研发之前完备并且经过评审;用户手册需要在实施客户培训前完备,具备良好的易读性,客户学习成本低;运维巡检和故障处理SOP手册需要在交付客户运维之前完备,并且经过演练执行。
一个团队应该有确定的 可执行文档规范 ,而不是每个项目每个团队成员想当然的自行发挥才华,交付质量参差不齐。对每个文档的维护也提供了项目的状态监督和预警机制,文档使各项计划和决策在整个团队范围内得到交流,也便于及时发现项目的问题。
概要设计文档 需要明确项目的背景、名字解释、功能概述、性能指标、依赖的软硬件环境、系统的总体架构、系统核心业务模型、系统各模块交互的数据关系、各模块的功能概述、各模块依赖第三方服务的接口说明。
HTTP Restful风格的 接口设计文档 需要明确面向资源的HTTP URL设计方法、HTTP Method使用说明、HTTP Status Code使用说明、接口鉴权方法,接口输入和返回的各种参数说明、接口返回系统错误码的明确含义等。
用户使用手册 需要场景化,有截图,需要明确给用户标识出错误使用的风险。 运维巡检和故障处理手册 需要步骤清晰可执行。
文档规范的执行效果需要有质量控制方法,不然文档规范就成了摆设。项目负责人常用的方法可以称之为“ 海关与监视器 ”,团队经理常用的质量控制方法是随机检验。 “ 海关 ”是指我们先设下 重重关卡 ,文档唯有通过检验之后才能进入下一步的研发流程,如果文档无法通过评审,便被打回重做或者是废弃。概要设计文档和接口设计文档应该使用此方法。 “ 监视器 ”是指我们可以将不满足质量检测的文档内容标识上 记号 ,这个文档将不会在流程中的各个关卡被截住,整个流程将会变得顺畅,在这种检测中,有问题的地方超过一定的量,则需要立即修订。对于用户手册和运维巡检手册中场景的覆盖度问题可以视情况采用“监视器”的方法进行多轮迭代。 随机检验 就是随机抽查。因为项目很多,不同项目负责人对文档把控的严格程度也会有偏差,所以对于团队经理来说,逐个文档检查的成本非常高,改变检验的频率也就理所当然。如果一个经理对他的下属事必躬亲,就可能造成干预,而且将会浪费更多的时间在不会出错的下属上。更糟糕的是,他的下属可能因此会形成 依赖性 ——反正什么事情老板最后都会检查。随机检验应用在管理上,既可以增加项目负责人的责任感,又可以节省经理时间。
不管使用上述哪种文档质量检查方法都是在培养团队的文档质量意识,因为交付给客户每一项质量差的文档都可能会导致客户的流失,也会影响团队口碑和产品品牌。
寄 语
以上是对几个典型项目场景下遇到问题的复盘思考,这些案例给我们的启示如下: 多角色人员协作的业务项目: 严格遵守软件研发流程&及早发现问题及早解决 ; 多团队联合解决方案实施: 建立双重汇报机制&设定并且盯好业务指标 ; ToB客户验收项目: 珍惜客户&重视团队文档交付质量 ;
希望这些分享可以给新晋的项目管理负责人一些参考,避免一些不必要的弯路。后续依然会持续关注团队的项目实施和分析,欢迎更多有兴趣的同学一起讨论和分享。
原文链接地址: https://developer.baidu.com/topic/show/290334
云计算
2019-09-12 17:32:00
本文作者:AIOps智能运维
作者简介
芃熙 百度云高级研发工程师

负责百度云Noah监控产品报警系统的设计和研发,在大规模分布式系统、监控、运维on-call方面具有广泛的实践经验。

干货概览
百度云的Noah监控系统( Argus )是保障百度内外服务高可用的基石。它具有诸如机器监控、实例监控、HTTP监控、域名监控、日志监控、自定义监控等多种监控手段,具备“海陆空” 全方位的监控能力 ,让服务异常无处遁形。如果你看过本公众号之前的系列文章,相信你会觉得我所言非虚。然而如此强大的监控系统所产生的“辣么多”报警如果不能及时精准地送达给运维人员,那么一切都还只是个传说。今天我们就聊聊报警如何送达的问题,注意,我们今天不谈报警,我们只谈报警的搬运工—— 百度云Noah通告平台 !
一个都不能少
报警不同于普通的通知,它反映的是线上服务即将或正在遭受损失,如果我们把核心报警搞丢了,造成线上故障得不到及时解决,这个责任是巨大的。由于报警系统天然就这样要求高可靠性,因此我们奉行“ at-least-once ”的投递原则,确保报警至少有一次能成功抵达用户,做到“ 该报的 报警一个都不能少 ”。而为了实现这个目标我们经历过不少坑: 机房网络连通性问题
我们发送报警要依赖四个底层发送网关( 电话网关、短信网关、IM网关、邮件网关 )来向用户发送消息,如下图所示。由于公司网络环境的原因,这些网关部署在某些特定机房,和上游的监控系统部署在不同机房中,这样机房间的网络拥塞或抖动将直接影响报警发送。解决这种问题,可以将底层发送网关主备部署到不同的机房,由上游系统重试解决。也可以考虑建设额外的网络路由通路,例如机房A到B不通时,绕经机房C “曲线救国”。具体选择何种策略,要依据不同的网络现状而定。
限流
资源永远是有限的。对我们来说底层网关的发送能力是瓶颈所在,尤其是电话网关,线路资源非常宝贵,有明确的 路数限制 (例如100路)。这样当某业务的报警量很大时,它的报警将用光有限的发送资源,将导致其他业务的重要报警发送延迟甚至失败。因此,需要对不同业务的报警量进行限流,避免单业务报警量过大影响其他业务。 报警追查
监控系统中关于如何报警有多种配置,例如报警最大次数、报警允许等待时长、报警屏蔽等,这些配置都会影响报警发送行为。经常有用户反馈报警 不符合预期 ,例如“该收的报警没有收到,不该收的报警却收到了”等这种咨询问题,其实往往都是由于报警配置导致的,并非系统功能不正常,因此我们面临很多报警追查的需求。为此,我们将报警从产生到最终发送整个生命周期中的处理历史都记录下来,让用户像查询快递物流信息一样去追查报警处理历史。我的工作是不是真的很像快递搬运工(偷笑.jpg)!
若报警只如初见
随着业务的发展,我们发现报警量越来越大。通告平台每天都面临百万级的报警量,而这些报警中却有大量相似、重复的 冗余报警 。导致核心报警淹没在大量冗余报警中,极易造成 报警遗漏 。如果遇到骨干网拥塞或数据中心故障,那报警量就需要再加几个数量级,这就是传说中的报警风暴,风暴期间运维人员的手机、邮箱迅速会“爆”掉,据说以前有人根据报警短信的响铃频率来判断故障是否恢复,不管这是真事还是笑谈,他的囧境可见一斑。
因此, 报警收敛 成为了监控报警领域面临的一个共同命题。目前,业界一般的策略是分析报警内容,按照相同关键字进行报警合并。这种策略往往效果很差,因为事实上很多关联报警的内容本身并不包含相同关键字。针对这一问题我们逐渐演化出两类策略: 分维度报警合并策略 ,即按照报警维度属性(机房、机器、实例、服务等)合并。 基于关联挖掘的合并策略 ,即采用离线数据挖掘或机器学习的方式,从历史报警中挖掘出具有关联关系的监控策略,然后将相关联监控策略下的报警进行合并。
经过以上的合并策略后,我们的报警量减少了 九成以上 ,有效地减少了冗余报警对运维人员的干扰。对报警 合并算法 感兴趣的朋友可以参考我们的之前的专题文章《我在百度对抗报警风暴》详细了解呦!
优雅的外表
经过合并后的多个报警,如何友好展示是另一个随之而来的问题。如果把原始报警内容堆叠到一起展示的话,用户将很难理解。毫无疑问,需要对这些原始报警的内容进行抽象概括以友好显示。我们将这一过程称之为“ 报警渲 染 ”。举个例子,在一个服务集群下配置监控策略Rule_A,该策略下在一个短期时间窗口内共有110条报警, 如图所示:
经过报警合并,最终渲染为一条报警,展示如下:
从最终的报警内容中,运维人员可以快速了解报警的严重程度、触发报警的监控策略、影响范围、报警时间等信息。我们针对不同的合并策略分别有不同的渲染模版,目的就是让运维人员能快速准确地获取到报警信息。另外,对于 电话报警 ,渲染逻辑要更加复杂些,因为报警是以语音的形式触达用户的,受TTS技术所限,很难把形如“pr-nginx_xxx_pv_rule”的策略名,通过电话语音播报给用户,即使播放出英文读音,也会让人莫名其妙,因此我们定义了若干简化的 语音模板 ,只播报核心概要内容,同时提醒用户关注短信、邮件等其他渠道获取报警详情。
7*24值班也能睡个安稳觉
刚刚我们聊了 报警风暴 问题,它往往是由机房级故障或者网络故障触发大量的冗余报警导致的。而实际上我们观察到还有一个问题同样能带来报警冗余——那就是报警接收人配置过多。很多业务线将多个运维成员都配置到报警接收人里,而实际上运维人员是轮流值班的,非值班人员完全没有必要接收这些报警,这也造成了资源浪费。因此,我们集成了 值班功能 ,支持设置值班周期、交接班时间、值班提醒,多种值班角色,每天动态地将报警发送给值班人,这减少了一大批冗余报警。
另外,为了确保核心报警能得到及时响应并有效解决,我们引入了“ 报警升级 ”,即一个报警如果没有在限定时间内得到处理的话,那么该报警将自动升级到更高一级的接收人那里。看个示例,如下图:
报警发送给值班人后,如果该值班人在2分钟内没有认领或者在10分钟之内没有处理完成,则自动把该报警发送下一级接收人,如图中的yunxiaolin, yunxiaobo,并直接发送电话报警;如果他们在10分钟之内没有认领或者20分钟之内没有处理完成该报警,则继续升级到下一级接收人,如图中的yunxiaoyu。值班人收到报警后要回复正确的指令来“认领”或“完成”该报警,我们借此来判断该报警是否继续升级。假如你认为报警足够重要的话,你可以设置 多级升级 ,甚至到“厂长”!(偷笑.jpg)
总结
今天我们一起聊了 百度云Noah 通告平台 遇到过的沟沟坎坎,有如何发报警的基础问题,有报警风暴时报警压缩、报警渲染的难点问题,也有我们在on-call轮值和报警升级场景下的思考。作为报警的“搬运工”,我们始终相信“简单”的事也可以做得不同!希望我们的努力能让运维兄弟们过得更轻松一点,幸福一点。
目前平台还在持续进化中,欢迎大家积极留言共同交流!
原文链接地址: https://developer.baidu.com/topic/show/290333
云计算
2019-09-12 17:31:00
本文作者:AIOps智能运维
作者简介
周伟 百度云高级研发工程师

负责百度云智能运维(Noah)告警通告系统的设计和研发,在大规模分布式系统、运维监控、精准报警等方面具有广泛的实践经验。

干货概览
本文提到的 异常检测(Anomaly Detection) 特指在运维领域中对 时序数据 的异常检测,目的是为了发现时序数据中状态的 变化 。
在我们看来,异常检测一般可分为两类:简单异常检测和复杂异常检测 简单异常检测 :一般指 恒定阈值检测 ,比如判断可用性指标是否小于99.99%,如果小于则检测结果为异常,否则检测结果为正常。简单恒定阈值检测一般适用于可用性、资源利用率等监控指标。 复杂异常检测 :对于收入、错误率、流量等业务监控指标,需要检测天/周/月/年等 环比变化 或者 突增突降 等状态变化,这类业务监控指标的 维度特征较多 (比如地域、运营商等), 特征变化也比较快 (例如电商定期搞活动,流量瞬间涨起来,干扰正常的异常检测)。恒定阈值检测往往无法检测到这类业务监控指标的状态变更,所以还需要复杂异常检测算法来检测业务监控指标的异常。为了发现监控指标的状态变化,复杂异常检测往往会采用 机器学习 、 深度学习 等比较复杂的技术。
和简单异常检测相比,复杂异常检测对落地流程和异常检测的运行平台提出了更高的要求。
落地复杂异常检测的阻塞点
为了支持简单异常检测,异常检测的运行平台只需要能够支持用户添加自定义报警判断规则即可。比如,对于上面提到的可用性指标,用户可以在系统中将报警判断规则配置为表达式“SLI < 99.99%”,然后等着接收报警就可以了。
然而对高级异常检测算法,算法从研发到上线,一般需要包括以下三步: 线下编写算法脚本 (一般是Matlab或Python脚本),并根据历史数据,离线训练算法所依赖的参数,通过用历史Case进行回溯验证效果。在这个过程中,算法工程师需要 反复调整算法参数 ,直到准确率和召回率都达到目标为止。 将算法脚本改写成线上代码 (一般是C++、JAVA语言等编译型语言)。这是因为线下编写的算法代码一般和线上运行代码使用不同语言开发,线下实验算法代码更侧重研发效率,需要快速迭代和测试,而线上运行代码更侧重运行效率,高效稳定运行。 最后 将改写后的算法代码生效到线上 ,并观察其运行稳定性和资源消耗情况,及时调整线上资源分配情况。
以上流程在实践的过程中存在若干阻塞点,导致复杂算法落地时间长,算法迭代效率低: 线下实验的过程中,历史数据、历史Case、不同实验所使用的算法、参数和效果全靠人工管理,常常出现 指标数据不全 ,历史Case不全,实验过程兜圈子的情况。 线下代码和线上代码 开发语言不一样 ,代码移植需要消耗比较多的时间,常常不能保证线上代码和线下代码的效果完全一致。 一些复杂算法对 资源的消耗很大 ,贸然上线会影响监控系统部分或整体的稳定性。
落地复杂异常检测的需求
上述阻塞点很容易导致算法迭代速度跟不上指标变化速度的情况,最终限制了检测效果的提升。所以,为了保证算法的顺利落地,我们需要提供一个新的异常检测支持方案,满足以下的需求: 需要一个方便算法 快速迭代 和 算法验证 的环境,用于在线下调试算法脚本,并对历史Case回溯,保证算法的 普适性 。 需要能 弥合 线下脚本和线上 代码 鸿沟 的机制,尽快将算法生效到线上,能快速验证算法代码的真实效果,否则线下实验脚本和线上生效代码迁移成本太高,费事费力费程序员。 需要评估线上代码的 资源消耗和稳定性 情况。因为复杂异常判断算法,其资源需求差异性特别大,比如有的会采用深度学习等非常耗CPU/GPU的算法,而有的仅仅是简单公式计算。另外,这类算法开发迭代特别快,算法代码很容易引入Bug,所以需要在不影响其他线上算法运行的同时,采用线上真实流量验证算法稳定性。 需要 分离 算法代码和算法参数,并支持 算法参数的独立更新 。因为算法经过快速迭代后,会逐渐稳定下来,但是算法的参数是对历史数据的特征表达,所以算法所依赖的参数需要周期性更新,保障算法达到最优效果。
落地方案
为了解决上述问题和需求,我们推出了 异常检测运行平台 。基于运行平台,算法工程师可以用脚本语言(当前支持Python脚本语言)线下编写异常检测算法,并在线下回溯历史Case,当策略调试完毕后,可以直接将Python算法脚本生效到到线上。同时,还支持算法参数的独立更新,大大加快算法的生效速度。
异常检测运行平台包括三个环境: 离线环境、在线环境、近线环境 ,下面详细介绍。
1 离线环境
离线环境会提供如图1所示的 策略开发框架 ,异常开发框架提供了Python运行环境和常用的Python库,基于开发框架,算法工程师采用 一致的抽象接口 编写算法代码,这样可以保证在线下开发的算法代码可以直接放到线上环境运行,从而弥合线下脚本和线上代码鸿沟。为了回溯Case,开发框架还提供了历史时序数据组件、异常判断评价组件(支持准确率、召回率、F1 Score等评价指标)。由于复杂异常检测算法不像恒定阈值算法可以根据数据直观看出判断结果,复杂异常检测算法往往需要运行并输出异常判断结果(能图形化展示更好),才能评估算法效果,所以开发框架提供了 图形组件 ,可以图形化展示异常检测结果。
图1 策略开发框架
图2展示了算法开发接口,其中包括四个标准接口: load_model函数 负责加载算法参数; get_data函数 负责加载指定时间段的历史时序数据; initialize函数 负责在算法正式运行前进行初始化,比如加载算法参数、申请内存等等; detect函数 负责对时序数据点进行异常检测,并返回异常检测结果(正常或异常)。
图2 用于高级异常检测的抽象接口
2 在线环境
算法工程师在线下环境基于开发框架开发完策略算法后,可以将算法发布到在线环境。在线环境提供了跟策略开发框架一致接口的 策略运行时环境 。策略运行时环境会接收上游发送的时序数据,并驱动Python算法脚本周期性运行,同时将产生的异常判断结果发送到下游,用于报警通告。
图3 在线环境架构
在线环境的架构图如图3所示,在线环境主要包括以下三个模块: 任务分发模块 :负责管理运维人员提交的高级异常检测算法配置,并将每个算法配置组装成任务,分配给任务运行模块。 数据调度模块 :数据调度模块 周期性 从任务管理模块同步任务的分配信息,根据任务分配信息,将每个任务所需的数据调度给相应的任务运行模块,同时将数据也Copy一份,写入到时序数据缓存中。 任务运行模块 :任务运行模块周期性从任务分发模块拉取分配到的任务,并为每个任务启动一个策略运行时环境。策略运行时环境支持跟开发框架相同的接口,所以可以直接驱动基于开发框架开发的算法代码的运行。策略运行环境刚启动的时候,会调用intialize函数,进行初始化操作,然后策略运行环境不断接收数据调度模块调度过来的数据,并驱动调用detect函数,detect函数会对接收到的数据进行判断,并返回判断结果(正常或异常),运行时环境收到判断结果后,将其发送到下游,用于报警发送。有时算法在刚启动或运行的时候,需要拉取近期的时序数据,这时通过get_data函数从时序数据缓存中获取即可。另外,任务运行时环境还会周期性检测算法依赖的参数是否有变更,如果算法参数被更新了,则会调用load_model函数重新加载配置。
3 近线环境
在线下环境编写算法代码,如果直接贸然上线到在线环境,往往会存在很多问题,所以策略算法在离线环境验证可用后,首先会放到近线环境运行起来,近线环境和线上环境的架构和功能其实没有本质差别,只是用途不同而已。近线环境的目的,一方面是为了用线上真实流量数据来 验证算法的资源消耗 ,以发现算法运行的真实资源需求,只是不真正发送告警而已;另一方面,是为了 验证算法的稳定性 ,比如是否有内存泄漏、是否有崩掉、是否有错误日志等等。如果发现有问题,算法工程师可以快速调整并重新部署到近线环境进行验证。
总 结
本文主要介绍了异常检测的相关背景、应用场景和需求分析,然后我们给出了百度云的高级异常检测算法快速落地方案——异常检测运行平台。目前运行在这套异常检测运行平台上的高级算法超过20个,每天处理近千万监控指标的异常判断。算法的线上迭代周期从周级别减少到天或小时级别,很好地支撑了业务方对不同高级异常检测的需求。另外,我们还将平台开放给业务运维人员使用,由业务运维人员研发的异常检测算法可以有效引入业务线人员的专家经验。
关于高级异常检测算法的落地,有任何想法和疑问,欢迎留言一起交流。
原文链接地址: https://developer.baidu.com/topic/show/290332
云计算
2019-09-12 17:30:00
本文作者:AIOps智能运维
作者简介
运小伟 百度高级研发工程师

负责百度监控平台报警子系统的设计和研发,在大规模分布式系统、运维监控、精准报警等方面具有广泛的实践经验。

干货概览

Argus(Noah 监控3.0)是百度内部最大的监控平台,提供了 机器监控、进程监控、日志监控、远程监控、自定义监控 等多种监控方式。它还支持集群级别的监控配置和管理,并支持复杂的 异常判断 ,提供多种途径的 报警手段 。
图1 Argus监控系统示意图
从系统架构层面,Argus主要包括 采集、汇聚计算、数据存储、报警通路 和 可视化 五个主要部分。报警通路除负责异常判断、报警发送外,还支持 报警回调 和联动 故障自愈机器人 等功能。报警通路目前承载了千万级实例异常判断和报警,每天会自动执行数百次故障自愈任务。本篇文章会重点分异常判断和报警发送两部分来介绍报警通路的功能。
异常判断

判断规则
异常判断是报警通路的核心部分,其支持的判断规则决定了监控报警能力的强大与否,Argus报警通路支持以下两类判断规则: 内置的判断规则 : 该部分支持 四则运算 、 逻辑运算 以及各种 内置函数 。例如:metric_a < 99.99% && metric_b < 99.99% 、 abs(metric_c) > 100 等 。 自定义的判断规则 : 运维人员可先用脚本编写异常判断规则,然后将脚本提交到报警通路,报警通路负责 执行脚本 并产生报警。该部分适用于比较复杂的异常判断场景,比如复杂的同环比报警、多维度数据分析等场景。
判断流程
图2 异常判断流程
报警通路在接收到上游发送过来的数据后,首先找到跟数据相关联的报警配置,然后根据配置的判断规则进行异常判断,如满足判断规则,则产生报警;为了防止频繁的抖动报警,报警通路还支持 防抖动过滤策略 ,例如,M个周期中有N个周期的数据被检测为异常才会产生一个报警。另外,报警通路还会将产生的报警事件存储到运维知识库,供业务平台或用户来查询和使用。
异常判断例子
图3 异常判断例子
图3是一个异常判断的例子,为判断某业务在某机房的可用性指标是否达标,报警通路会对该指标的每个数据点逐一进行异常判断,如果连续3次都小于99.99%,则产生异常警报;反过来,只有连续3次都大于或等于99.99%,该次故障才会被判定为结束。

异常的自动化处理方案
异常判断产生的异常除了用于报警发送,还有下面三种自动化处理方案: 脚本回调功能: 报警通路支持在异常实例或机器上执行某个脚本的能力。比如当检测到某个实例异常时,可以执行此实例上的脚本(如:重启实例),就可以在无需人工参与的情况下自动修复异常的实例。 HTTP回调功能: 为了支持集群级别的故障处理能力,报警通路还支持HTTP回调功能。报警通路会将报警POST给指定的URL,接收端即可处理集群下的所有报警,以此来决定是否触发某个复杂的故障处理动作。 联动故障自愈机器人: 产生的报警还可以联动故障自愈机器人,执行相关自愈动作。故障自愈机器人是一款面向感知、决策、执行的故障自愈机器人,相关介绍,详见之前的公众号文章《AIOps时代,你准备好了吗?》。
报警发送
图4 报警发送流程图
异常判断产生的警报,如果想到达运维工程师,还需要经过 报警过滤、报警合并、报警渲染 三个环节。报警过滤会将运维工程师不希望收到的冗余警报过滤掉。报警合并会将相关联的警报合并到一块发送。报警渲染就是将结构化的警报数据渲染成文本信息,供运维工程师查看。

报警过滤
用户希望在以下三个场景过滤掉报警,针对这三种需求,我们提供了相应的功能: 报警屏蔽过滤: 运维工程师不希望接收预期内(比如模块上线期间)的报警,希望可以临时过滤掉相关报警。 报警依赖过滤: 运维工程师希望只收到产生故障的根因报警(即精准报警),这样利于快速定位线上问题。比如某台机器出现故障(因)时,那么这台机器上所有实例的报警(果)应该过滤掉,只需给运维工程师发送机器故障的报警。 最大报警次数过滤: 如果一个服务持续异常,运维工程师不希望持续收到同一故障的报警,可以限制最大报警次数。
报警合并
即使有上述报警过滤措施,但在较大规模故障发生时,仍然还可能产生大量的报警,造成 报警风暴 。因此,我们需要对同源的报警进行合并发送,一条信息中一般会包含多个相关联的警报。报警合并的细节详见本公众号之前的文章《我在百度对抗报警风暴(一)》。
报警渲染
合并后的报警,会按照默认格式渲染成短信、邮件、语音等,发送给运维工程师。此外,运维工程师也可以通过配置 报警 渲染模板 ,来自定义报警样式。

总结

本篇文章主要介绍了百度监控平台报警通路子系统的核心功能,在报警规则、异常判断、警报自动化处理、报警过滤等方面做了详细介绍。关于报警通路的实现细节和系统架构,会在后续的文章中介绍,敬请期待。
原文链接地址: https://developer.baidu.com/topic/show/290331
云计算
2019-09-12 17:30:00
本文作者:AIOps智能运维
作者简介
运小博 百度高级研发工程师
从事有关运维数据分析相关的工作,负责异常检测系统和报警收敛等工作,重点关注时序数据分析、故障诊断等相关领域技术。
干货概览
自动异常检测旨在发现复杂业务指标(请求量、收入等)的异常波动,是智能监控系统中的重要环节。百度的业务种类繁多,各业务的监控需求迥异,参数配置成本繁重,给异常检测带来了巨大的挑战。本文整理了运小博在 2017CNUTCon全球运维技术大会 上分享的《百度大规模时序指标自动异常检测实战》和在 CCF TF“人工智能时代的互联网运维” 主题研讨会中分享的《百度智能运维实践之异常检测》的内容。主要介绍百度运维部IOP团队开发的自动异常检测系统及其核心技术能力,并重点讨论了大规模时序异常检测参数配置成本高的问题。演讲展示了三种常用异常检测算法及其适用场景,并基于此讨论了算法的自主选择策略,以及每种算法的参数自动配置方法。
背景介绍
异常检测需要监控的业务繁多,覆盖了搜索、广告、地图、糯米等百度大部分的产品业务。及时发现这些业务请求数、拒绝数、响应时间、流水和订单等数据的异常波动,是业务稳定性的重要保证。这些数据不但数量众多,而且不同业务的曲线也有截然不同的特征。从上图的三幅曲线图可以看出:
☞第一幅曲线图中有蓝、绿两根曲线,分别代表当前时刻数据和上周同一时刻的数据。蓝色曲线几乎完全覆盖了绿色曲线,说明数据有规整的周期特性。
☞第二幅曲线图中,紫色曲线是当前时刻数据,蓝色曲线是上一周的数据。可以看出:数据有一定的周期性,但又不如第一幅图那么规整。
☞第三幅曲线图中的数据大致平稳,在某些时段出现了异常上涨。
所以,我们的异常检测系统面临两个挑战:一是数据规模大---总共有百万量级的指标;二是曲线的特征差异明显,监控难度大。
通用场景的异常检测算法

对曲线特征进行梳理后,我们发现大多数曲线都可以分数到下面三个场景中:
场景一:数据无规律波动,但正常基本在一个较小的波动范围内,典型的场景就是拒绝数监控,通常我们会按照拒绝数的常态波动范围设定一个或多个恒定阈值,超过阈值即报警。
场景二:数据的长期波动幅度较大,但正常情况下短期的波动幅度较小,体现在图像上是一根比较光滑的曲线,不应该有突然性的上涨或者下跌。典型的场景包括糯米的订单、流水。这类场景监控的主要思想就是环比附近的数据,检查是否存在突然的大幅上涨或下跌。
场景三:数据有规律地周期性波动,比如广告收入或搜索流量等。检测这类数据的方法是与历史数据作同比,从而发现异常。
恒定阈值类算法
场景一的问题可以使用恒定阈值解决,超过设定阈值就报警。比如拒绝数监控,我们可以设定在一个单位时间内超过100个拒绝就报警。但是,实际使用中会出现单点毛刺的问题,也就是一个单点超过阈值的报警。当数据来回抖动时,就会产生大量无效报警。常见方法就是通过filter来解决,比如设置为连续5个时刻都超过阈值才报警,但这种方法太过僵硬,中间只要有一个点回到阈值范围内就不报。
我们采用的是更加柔性的累积法:一段时间窗口内数据的均值超过阈值触发才报警。这样不但能够滤除毛刺,还考虑了原始数据的累计效应。
突升突降类算法
场景二要解决的是突升突降的问题,我们求取数据最近两个窗口的均值变化比例(见上图公式),将原始数据转换到了变化比例空间(r空间),如右下的小图所示。在r空间上设置阈值就可以检测出数据的突升或突降。

同比类算法
场景三中的数据有显著的周期性,我们计算历史上相同时间窗口内数据的均值和标准差,然后计算当前点的z-score值,即当前点的值减去均值之后再除以标准差。逐点计算z值可以把原始数据转换到另外一个空间(z空间),在z空间设置阈值就可以发现这类异常了。比如左下的小图里蓝色曲线是当前的数据,红色和绿色的曲线是历史同时刻数据。如果要检测图中红色圆圈的部分是否异常,我们以历史数据(红色方块内的数据)为基准计算均值和标准差。右下的小图展示了蓝色曲线在z空间的形态,如果取值位于红色阈值线的下方,即可报警。
算法选择决策树&参数自动配置算法

不同曲线需要选取不同的算法,大量曲线的算法选择成本很高。例如,右上的小图是某产品在不同省份的流量数据,我们看到流量大的省份(如北京、广东)的曲线周期性很明显,更适合同比算法,流量小的省份比如西藏的曲线基本区域平稳,更适合配置恒定阈值。
另外,算法在不同时段的参数不同,工作日和休假日的参数、白天和晚上的参数都不同,参数配置成本非常高。
除此之外,曲线特征会随着业务系统的架构调整发生相应的变化,算法和参数需要定期维护。例如右下的小图是某个子系统的流量数据,箭头时刻这个子系统下线了,此事算法和参数都需要做出相应调整。
因此,我们希望帮助用户自动选择算法和配置参数。接下来我们将分别介绍算法选择决策树和参数自动配置算法。
算法选择决策树

曲线配置算法本质上在建立数据特点与算法本身的映射。周期性数据选择配置同比算法,非周期数据会通过波动范围来界定。当数据的全局波动(长期波动)远大于局部波动(短时波动)的时候,我们倾向于选择突升突降;当全局波动近似等于局部波动的时候,恒定阈值算法就会更合适。
接下来需要解决的问题就是:如何判断数据是否有周期性?如何界定数据的全局与局部波动范围?
我们提出了一种基于差分的数据周期特征判断方法。先将临近的两天数据做差分,如果是周期数据,差分后就可以消除掉原有数据的全局波动,然后结合方差的阈值判断就可以确定数据是否有周期性。实验发现,不同天的数据有一定的上下浮动,因此差分之前可以先对数据做归一化。
前面的方法能够分离出周期性数据,接下来要度量数据的全局波动和局部波动的相对大小。数据方差可以直接表达全局波动范围。对数据施加小尺度的小波变换可以得到局部波动,局部波动的方差反应了局部波动的大小。
结合周期性数据的判断方法和数据的全局、局部波动的表示,就可以得到图中的算法选择决策树了。
参数自动配置算法
算法选择以后,我们需要给每种算法自动配置参数。首先,介绍恒定阈值的自动参数配置。如左下小图中的一段数据,直观来说红色区域的数值因为很罕见所以一般会被认为是有异常。通过估算这些罕见数据出现的概率,即可确定曲线的阈值。把数据看作是一组独立同分布的随机变量的值,我们可以使用ECDF(经验累积概率分布曲线)来估计随机变量的概率分布(右下角的小图所示)。ECDF曲线的横轴是数据值,纵轴是概率,表达的是小于等于某数值的样本比例。用户给定经验故障概率(ECDF的纵轴),即可查找到数值的阈值(ECDF的横轴)。我们通过ECDF把配置阈值转换成了配置经验故障概率。尽管不同曲线的阈值不一样,但曲线的经验故障概率常常是一致的。
实际使用中,因为历史数据样本有限,ECDF与真实CDF有一定差距,直接使用容易有较多误报,我们使用了补偿系数解决这个问题。
刚才介绍了恒定阈值算法的自动配置参数过程,突升突降算法自动配置参数也是类似的,我们可以利用前文提到的空间转换公式把原始数据转换到r空间,然后在r空间上配置恒定阈值。除了r空间上的阈值之外,还有窗口大小w需要设置,不同曲线一般不会有太大区别,我们就不自动设置了。
同比算法也一样,使用z-score的方法把原始数据转换到z空间,就转换成了在z空间上自动配置恒定阈值参数的问题。同比天数k和窗口大小w一般也可以使用全局设置。
总结
本文从百度内部的实际异常检测场景出发,介绍了三种通用的异常检测方法,并介绍了算法自主选择策略,以及三种算法的参数自动配置策略,极大的降低了用户算法选择和参数配置的成本,有效地解决了百度内部大规模时序指标的自动异常检测的实际问题。
若您有其他疑问,或者想进一步了解百度在异常检测方面的实战经验,欢迎在 AIOps智能运维 后台留言或通过评论反馈!
若本次分享的内容对您有所帮助,不妨通过赞赏功能鼓励运小博! AIOps智能运维 将坚持分享运维领域的原创干货!
欲睹运小博真容,速速点击文末“ 阅读原文 ”回顾2017CNUTCon全球运维技术大会精彩瞬间!
原文链接地址: https://developer.baidu.com/topic/show/290330
云计算
2019-09-12 17:29:00
本文作者:AIOps智能运维
作者简介
运小博 百度高级研发工程师
从事有关运维数据分析相关的工作,负责异常检测系统和报警收敛等工作,重点关注时序数据分析、故障诊断等相关领域技术。

干货概览
在本系列上一篇文章《我在百度对抗报警风暴(一)》中,百度高级研发工程师运小博介绍了报警风暴的成因及简单的报警合并策略。本篇文章中,运小博将介绍关联策略的报警合并策略、基于报警数据挖掘的机房故障分析、报警关注度分析、值班与逐级通告机制和报警回调等技术。
报警合并策略
关联策略的报警合并
某个模块的出现问题的时候,往往会引发上游或者下游模块也一并报警。假设模块A调用了模块B,当模块B出现问题的时候,很显然模块A和模块B都会产生报警。为了解决这个问题,我们尝试从 历史 报警数据 中 挖掘 关 联的报警策略 列表,然后就可以使用《我在百度对抗报警风暴(一)》提到的 报警合并 机制 对跨模块的相关报警进行合并。
历史上每次B模块出现同样的问题的时候都会导致A模块有类似的报警,换言之,若历史上A模块的策略rule1和B模块的rule2经常同时报警,那么A模块的策略rule1和B模块的策略rule2就可能存在关联。因此我们可以挖掘历史报警数据中的关联关系,即 关联的报警策略列表 。
如上图横轴为时间轴,每一个位置都产生报警的时间,比如最开始的报警是A模块的rule1,然后是B模块的rule2等等。然后使用 挖掘窗口 (上图中的大括号为一个挖掘窗口)进行滑动,把位于同一个窗口内的报警归结为一个事务。
接下来我们就可以使用常见关联分析算法挖掘 频繁项集 (历史上经常在一起出现的报警策略)和 关联规则 (报警策略之间存在很强的关系)了。我们先要回答一个问题,怎么样定义报警策略的频繁出现?或者说,两个报警策略是否存在关联?
一个项集的 支持度计数 被定义为该项集出现的次数,这里没有用传统的支持度是因为历史报警数据产生的数据往往较多,而实际项集数据出现的比较稀疏,意味着支持度的分母巨大,分子却很小。
置信度 是针对一条关联规则X:rulem->Y:rulen而言定义的,代表了X:rulem导致Y:rulen发生的可能的概率。
支持度计数S_count(X:rulem)=以 X:rulem开头的transaction的数量
支持度计数S_count(X:rulem->Y:rulen)=以X:rulem开头,并且包含Y:rulen的transaction的数量
置信度c(X→Y)计算公式如下:
支持度和置信度超过一定数值即为所需的关联规则。按照这样的规则,在等待发送队列中当某个报警发送时在报警策略关联表中查找等待队列中是否包含关联策略,如果包含就合并成一条报警信息发送。

机房故障期间的报警合并
机房故障 (网络中断、机房断电等)会导致部署在该机房的模块异常,引发大规模的报警风暴。报警风暴不但会对报警的处理造成打扰,而且可能会增大报警系统压力,严重时可能导致后续报警 延迟送达 。因此,如果能快速准确地感知机房故障,并将相关报警进行合并,将会有利于运维人员 快速捕捉故障根因 ,还能 减少报警系统压力 。
一个机房往往部署有多个业务系统,而一个业务系统也会把自己的模块部署在多个机房中以提高可用性。当某个业务的运维工程师发现单个机房的多个模块出现故障,就会怀疑是机房故障,但很难直接确认。所以,他们一般都会找其它业务的运维工程师寻求确认。如果多个业务都在这个机房出现问题,就基本能够确定是机房的基础设施出了故障。
通常,我们会想使用每个机房的报警数量来表征这个机房的状态,但百度部分产品的体量庞大,当这些业务故障的时候,会导致报警量突升,从而影响对机房的判断,因此最终我们选择了异常策略比例 RuleRatio 和异常业务比例 ProductRatio 两个特征。
为了寻找如何用上面的两个特征确定是否有机房故障,我们抽取了历史一段时间各个机房的特征,并利用散点图来训练。如下图所示,横坐标是 RuleRatio ,纵坐标是 ProductRatio ,每个点代表某个时段的某个机房。图中红色为机房有故障的样本,蓝色为无故障的样本。
从上图看,线性分类器就能区分故障和非故障的样本。
对历史数据进行回溯,上图横轴是时间,纵轴是按上述指标计算的异常评分,每一条曲线代表了一个机房,几个异常点分别是某一次机房的故障。
报警关注度

报警关注度 是指报警发送后有实际处理的比例,但系统运维一段时间后都会发现部分报警的关注度并非100%,大多数情况下并非是值班工程师不尽责,而是部分报警策略随着系统的演化已经失效而又没有及时删除,因此需要我们有一种方法识别无效报警。
夜间关注度分析

我们观察了一条报警的处理过程。收到一条有效的短信报警后,值班工程师会登录运维系统(包括监控系统、预案系统等)对报警进行定位、处理,这些行为会体现在各种运维系统的访问日志中。通过收集这些日志,就可以对每条报警的处理情况进行分析:如果在收到报警后的一段时间内访问过运维系统,可以认为该报警得到了关注,反之就认为该报警没有得到关注。汇总一段时间后,就能够筛选出 关注度较低 的报警策略,即为 无效报警策略 。
报警值班与逐级通告机制
百度监控系统( Argus )报警通路帮助运维团队建立值班机制,支持配置值班表,并设定交接班时间、值班周期等,值班系统会按周期自动轮转,并以短信、邮件等形式在交接班前通知接班值班人。为了保证核心报警得到及时有效的处理, Argus 中还建立有 逐级通告机制 ,支持配置多个通报升级时间和通报方式(电话、短信等)。报警发出后,值班人必需及时认领报警,超时未认领会按照配置升级并通告。下图就是一个值班和逐级通告配置的例子。

报警自愈机制

很多报警都有明确的处理预案,报警发生后,值班工程师登录机器或者中控机执行预案(脚本)就可以完成这类故障的处理。比如,清理磁盘这类操作,可能就是一个简单的删除日志和tmp目录的脚本。如果这类报警可以完全自动处理,无需人工干预,就能够大量节省人工成本,同时减少报警量。因此, Argus 报警系统提供了 报警回调 机制,在报警发生时可以回调预案处理脚本。
上面介绍的这类自愈场景比较简单,这类预案往往对于程序没有任何干扰,可以“无脑”执行预案脚本即可。对于更复杂的场景,值班工程师往往需要根据服务的整体情况来调整预案。例如当某个实例异常需要重启的时候,需要综合判断其他实例的状态才能确定是否以及何时可以重启该实例,如果无脑重启可能会给服务造成损失。这种场景的自愈操作可能是有损的,需要对服务整体情况有一个判断才可以执行预案, Argus 报警系统为此提供了另外一种回调机制。在发生报警时,报警系统会把相关的报警策略、实例的状态都统一发送给一个 中 枢决策服务 ,由中枢决策服务统一做出判断。

总结
《我在百度对抗报警风暴》系列文章初步介绍了智能报警合并策略、机房故障挖掘、报警关注度分析、值班与逐级通告平台和报警回调等技术。
时至今日,还有更多的复杂策略在 Argus 上运行,在这些策略的帮助下,百度运维工程师收到的短信报警量减少了 80% 以上,尤其是网络、数据中心等大规模故障期间,有效地减少了报警风暴对运维人员的干扰,增强了报警的精准程度和实际价值。
若您有其他疑问或想进一步了解 Argus ,欢迎留言反馈!
原文链接地址: https://developer.baidu.com/topic/show/290329
云计算
2019-09-12 17:28:00
本文作者:AIOps智能运维
作者简介
运小博 百度高级研发工程师
从事有关运维数据分析相关的工作,负责异常检测系统和报警收敛等工作,重点关注时序数据分析、故障诊断等相关领域技术。
干货概览
百度监控系统 Argus 保障了百度内外产品服务的高可用,《我在百度对抗报警风暴》系列文章将会介绍百度高级研发工程师运小博在实践中如何运用 报警合并 、 机房故障分析 、 报警关注度分析 、 值班与逐级通告平台 和 报警回调 技术等对抗报警风暴。本文将主要介绍报警风暴形成的原因和报警合并策略中简单的报警合并策略。

Argus 名字含义:希腊语“Argus”的意思是“明亮的”、“明察秋毫的”,在古代希腊神话里面的巨人Argus长有一百只眼睛,因此可以观察到所有方向的事物与动静;后世以此来比喻机警、机灵的护卫,我们希望百度监控系统能够如巨人Argus全面洞察异常并报警,故命名为 Argus 。
报警风暴
百度监控系统( Argus )是保障百度内外产品服务高可用的利器。小到机器的磁盘是否打满、虚拟机实例上的进程或端口是否存活,大到产品的流量是否稳定、机房网络是否联通,尽在 Argus 的掌握之中。
两年前,百度每一个运维工程师都被报警风暴所困扰。白天,百度的运维工程师们平均11分钟就会接收一次短信报警,在夜间则是平均14分钟一次,而实际数据统计发现,有效短信报警占比不到15%。因此短信报警的冗余度是非常高的,已经造成了报警风暴。
经过分析,报警风暴的形成主要有这么几个原因:
报警重复度>58%
分析其原因,首先报警策略执行周期计算,因此会持续产生 重复报警 ,部分策略甚至会导致持续报警达1小时以上。更严重的情形是,一次故障可能引发多个相关策略报警。比如一台机器死机,首先机器层面报警,然后实例层面报警,接着服务上游也可能会报警。
报警关注度不足
我们把报警发送后有实际处理的比例作为 报警关注度 的度量指标,发现实际关注度并不高,而在夜间短信报警关注率则低至25%。但事实上夜间短信报警的级别一般都是比较高的。这就意味着很多报警策略的发送方式和实际的报警等级已经相违背了。这是因为报警的关注度随着业务发展发生变化,但是这些关注度的变化没有及时的在报警系统中修改,导致已经变得不那么重要紧急的报警,却还在以短信的形式给值班工程师发送报警。
报警接收人冗余
每个报警策略平均有3个接收人,部分报警甚至超过了7个。报警策略的接收人往往会填写了运维团队中的所有人,但实际值班人只有一个人,大家按周期轮转。因此,对于一个特定的报警,大部分同学是不需要即时关注的。
报警有效性不足
超过88%以上的报警都是单实例报警,40%以上只需要简单的处理即可恢复,比如磁盘打满或者内存泄露等。因此我们在 Argus 中增加了 自愈机制 ,自愈成功后,报警也就无需继续发送了。
针对上面的这些问题,我们在设计 Argus 时使用了智能报警合并策略、基于报警数据挖掘的机房故障分析、报警关注度分析、值班与逐级通告平台和报警回调技术等。 Argus 的功能逐渐完善,并把周级报警短信总量削减了 85% 。

报警合并策略
报警合并对很多做监控的同学来说并不陌生,大部分介绍报警收敛的文章都提到了这个过程,但大多数提及的报警合并都是将某个时间窗口内的报警简单的合并成为一条,此举对削减报警数量固然有效,但不利于值班工程师进行故障诊断。我们希望把若干描述同一故障的报警合并在一起,让值班工程师可以快速捕捉到故障本质,甚至故障根因,而并非一味的削减报警量。
在本文中,我们先介绍一个合并来源于 相同报警策略 或者相同模块的重复报警的策略,下一篇文章中将讨论如何合并 跨模块 、 跨策略 的报警和一个消除机房网络故障期间报警风暴的方法,这一方法也可以用来检测机房的网络故障。

简单的报警合并策略
最简单的报警合并方法可以基于报警策略的自然属性,包含策略名或者部署维度等。百度的生产系统使用了 虚拟化 技术混布各项服务,因此部署的逻辑维度包含了实例、模块、集群等层次,物理维度包含机器和机房层次。一个层次同时刻的报警,大多数都存在着一定联系,因而可以将这些报警合并。举一个实际的例子,A模块在bj机房部署有100个实例,统一为每个实例配置了端口存活报警策略rule1,某个时刻这个策略在0.A.bj(A模块在bj机房的0号实例)和1.A.bj(A模块在bj机房的1号实例)上都报警了。这两个报警属于同一个模块的同一个策略,因而可以合并在一起,便于值班工程师了解整体情况。最终发送的报警样式如下:

{A:instance:rule1}{总体异常实例比例:2%}{异常(2):0.A.bj,1.A.bj}{05-02 16:49:36 - 16:54:09} {http://dwz.cn/… }
简单介绍一下这条报警的意思:
◤A:instance:rule1代表的是报警策略rule1属于A模块,并且是实例级别的报警;
◤总体异常实例比例是使用异常实例数量除以实例总数量计算出来的;
◤0.A.bj和1.A.bj属于A服务下的两个异常实例,异常时间段是05-02 16:49:36 到16:54:09;
◤后面有个短链,用户可以打开短链查看更详细的报警内容。
当合并的内容过多时,我们将最主要的报警或者报警的总结汇总到短信内容里面,具体的每一条细节报警、报警起始结束时间、报警持续时间、报警配置内容等细节信息都会在短链的页面中展示。
了解了报警合并的策略,接下来介绍一下实际合并的机制。一个报警产生以后,我们先把这个报警插入一个发送等待队列而非立即发送。每一个报警策略都有一个在等待队列里的最长存留时间,换言之,是这条报警可以容忍的 最长延迟发送时间 。报警产生后先插入等待队列里面去,在队列里等进行延迟计时,当达到了能够容忍的延迟时间以后,我们在等待队列中找到可以和该报警一起合并发送的报警,根据实际的合并维度渲染成不同的报警短信内容,然后合并成一条报警短信发送。在下面的例子里,A,B,C代表了3个不同的模块,A模块上配置了两条报警策略分别为rule1,rule2,B模块上配置了rule3,C模块上配置了rule4,红色的报警A:rule1即将到达最长存留时间,按照合并策略,黄色的报警可以一起合并为一条发送,这样实际的报警信息中包含了三条报警。
总结
在今天的故事里,我们从多个角度分析了报警风暴的问题,并重点介绍了报警合并技术中简单的报警合并策略。之后的故事里我们会继续介绍关联规则的报警合并策略、基于报警数据挖掘的机房故障分析、报警关注度分析、值班与逐级通告平台和报警回调技术等,请持续关注AIOps智能运维!
若您有其他疑问或者想进一步了解百度监控系统 Argus ,欢迎留言或评论!
原文链接地址: https://developer.baidu.com/topic/show/290328
云计算
2019-09-12 17:28:00