
更多技术干货请关注【尔达 Erda】公众号,与众多开源爱好者共同成长~
「人与人之间的如何高效协同?」这是一个伴随人类诞生以来,从未停止过追求更高目标的课题。从原始社会的集体狩猎、语言的创造和改进、农耕文化的协作耕种、工业革命后的协作生产再到现代文明中公司的团队协同,从始至终,「协同」在创造和生产中起到至关重要的作用。在软件研发中也不例外,其生产过程中最核心的角色是「产研团队成员」。「成员之间的透明高效协同研发」也是当前 DevOps 理念中非常重要的一个部分,为此,海内外也有很多优秀的公司一直在深耕这块领域,一直通过产品沉淀理念,帮助研发团队不断迈向协同高效的新高度。(今天我们不讨论具体的产品,有兴趣同学可以联系我们细聊)回归到今天的话题,我们先共同分析下:项目高效协同目标是什么?能够高效协同的团队在协同方面具备哪些共性?只有通过分析了解和深入认识什么是协同,才能进一步讨论分享协同的实践。基于自身的一些实践总结分析,在这里罗列出了几点高效协同团队的共性:团队信息透明清晰,目标明确、迭代节奏清晰、团队成员清楚自己的工作以及工作对于目标的贡献;研发团队协同的文化规范明确,不同角色清晰知道协同的工作流程;满足个性化的协同分析统计数据,帮助团队和个人成长;有强有力协同功能的平台支撑,让团队成员能够高效规划、跟踪处理团队/个人相关的事项;有强有力协同功能的平台支撑,让跨团队的沟通协同变得更简单,让团队及跨团队的成员沟通协同更高效;……以上罗列出的内容不一定全面,只是想通过自己的思考,针对「研发协同」这一话题进行一次 Erda 团队的实践分享,希望能给大家带来一点帮助或启发,也非常希望有同学能够深度参与进来一起讨论,共同促进团队的高效协同。团队文化良好的协作氛围是团队迈向成功的基石,良好的「团队文化」能促成团队成员之间相互信任,并能坦诚、开放、平等地沟通等。关于什么是团队文化,这里引用了百度百科的解释,主要关键词就是培训、统一、规范、凝聚。“团队文化是社会文化与团队长期形成的传统文化观念的产物,包含价值观、最高目标、行为准则、管理制度、道德风尚等内容。它以全体员工为工作对象,通过宣传、教育、培训和文化娱乐、交心联谊等方式,以最大限度地统一员工意志,规范员工行为,凝聚员工力量,为团队总目标服务。”加入到 Erda 项目的同学,进入项目后第一眼就会看到团队崇尚的协作文化,核心是透明和异步协同。我曾经一度认为,人与人之间面对面同步沟通和协作是最高效的,但是随着与开源社区接触和对异步协同的理解不断加深,这个曾经正确的想法也在不断被挑战。接下来,我们来看一个最典型的会议场景:会议前,只通知了会议时间、地点和会议主题,详细内容不知。即使组织会议者非常用心准备了会议内容的材料,参会者也会抱着懒惰心理,没有仔细阅读,详细内容留在开会时再去了解;会议中,发言者慷慨激昂地讲述着自己的观点,其他人对于正在讲述的内容兴趣度不高,甚至开小差,轮到自己发表观点时,仅仅是「发表观点」,无法形成观点间的碰撞和有效的讨论。有关开会的段子更是层出不穷,“开会的人基本不干事,干事的人基本不开会”、“人多的会议不重要,重要的会议人不多”……相信每个职场人看到都能会心一笑,一群“状况外”的员工凑在一起,期望通过一到两小时了解事情的来龙去脉,附带提出深刻的建设性意见,这可能吗?有些公司或者团队为了提高会议质量,制定了一系列规范措施,比如:会议室门口放一个手机收纳盒/袋;公司层面有一定会议制度,比如会议室预约,会议主题明确、会议内容事前发送等规范制度。这些措施执行到最后,往往都会变得形同虚设,有的执行好,那还得专门配备监督人员,成本增加的同时,收益微乎其微。异步协同Erda 项目崇尚的异步协同的方式,核心主要围绕以下几点:高质量的发言:「高质量的发言」还可以理解为让别人能看明白、听明白,为此,事前要有充分的图文信息做支撑,让人清晰的知道你要干什么、要达到什么目的、要什么样的协助等;信息的接收与处理:异步协同不追求成员做到时刻在实时通讯 APP 保持“待机”状态,但是要求至少每天来协同平台看一次,哪些需要自己处理和回复。同时,在内容发出之前,需要对内容质量负责,确认自己写的内容能够让别人明白。如何养成文化科学上认为「21 天能养成一个习惯」,但如果每来一个新人就是一个新的循环,这对于一个团队来说成本有点高,与高效协同的愿景相悖。这样的话,能不能把这些协同的规范和理念沉淀到协同产品的「功能特性」上,培养用户使用产品功能时的「用户心智」,不知不觉中按照团队的文化规范进行下一步行为?Erda 始终致力于将这样的特性与自身产品相结合,把所有的内容都提炼为 flow,然后通过实践沉淀 flow 配置,以配置内容的方式去分享自己的最佳实践。比如,研发协同事项上针对需求、任务、缺陷都有相应的状态流转流程,对应的状态需要相应的角色进行处理,每个角色处理好对应的内容后才能流转到下个状态,同时,流转状态会触发相应的动作……这些都是通过一条 flow 或者 多条 flow 配置来完成。这个和研发的分支管理策略配置沉淀是一样的,详细可以查看上一篇《8 年产品经验,我总结了这些持续高效研发实践经验 · 研发篇》文章。产品迭代规划的方针在团队的「协同」文化下,产品规划也变得井然有序,Erda 产研团队推行和建议采用以下策略:迭代规划周期推荐 1-2 周作为一个迭代周期,不要超过 2 周 (Erda 团队是 2 周 1 迭代 的周期)。至于为什么要定义这样一个快节奏的迭代周期,主要是为了应对不断变化的行业的需求,软件需要加快迭代速度,越快交付用户使用,接收有价值的反馈和用户数据,可以让产品尽量避免走过多的弯路,这也符合敏捷研发小步快跑的理念。迭代规划方面建议采用「1 + 1 模型」推进,进行一个迭代,规划一个迭代。虽然在当前快速变化的时代背景下,但是最近两个迭代(也就是一个月内)的目标还是需要非常明确,并且在有效的协同平台(如 Erda)中进行管理维护。一个月之后的目标往往变化较快,所以建议统一在产品的 backlog 中进行管理,由产品经理线下维护更为久远的产品 Roadmap;发布频率每周有发布班车(小版本,不对外部署);产品版本发布产品的版本可以由单个迭代或者多个迭代来完成,版本需根据产品功能特性来决定,推荐一个版本由多个迭代来支撑,需要团队控制好版本频率(Erda 一般是 3 个迭代发布一个正式产品版本,具体可以视情况灵活操作)。迭代事项协同在团队文化和研发迭代规划方针下,接下来就要真正进入产品/项目的迭代研发阶段了,在这个阶段中整个团队围绕的目标是明确的,在这个目标之下 PD/PM 会规划制定产品/项目的 RoadMap,把相关的目标拆分成若干迭代的需求来完成,当然目标拆分落地始终需要遵循 MVP 理论(Minimum Viable Product –最简化可实行产品)。任何规划和计划都一定的策略,在 RoadMap 规划这个事上,Erda 产品主要由以下的策略来支撑有效落地:责任者:主要由 产品经理 主导完成;规划周期:规划半年目标,明确 1 个月内的产品需求;目标/需求来源:用户诉求、竞品、行业预判、技术演进、知识理念的演进发展;目标/需求的维度:行业竞争力领先优势保持加强不足RoadMap 规划完成后,接下来的过程可能大家就非常熟悉了——迭代研发的阶段。围绕目标,研发团队在敏捷迭代评审会议上明确迭代的需求范围,然后紧接着就是需求、任务拆分、研发、测试、缺陷解决、发布上线等内容,本次主要还是围绕协同相关的需求、任务和缺陷进行讨论分享,研发过程相关内容可以查看上一篇《8 年产品经验,我总结了这些持续高效研发实践经验 · 研发篇》文章。需求迭代开始后,所有的研发成员都是应该围绕「需求」进行落地,产品经理确保需求的 PRD(Product Requirement Document)是清晰的,能够让所有成员明白这个是要做什么?谁用?解决什么问题?(BRD - 商业需求文档和 MRD -市场需求文档这边就不展开讨论了)为此 Erda 产品对需求的管理主要采用以下策略进行:责任者:每个迭代的需求规划主要由 产品经理来主导完成,最终需要进行迭代评审会议后最终由整个团队一起确定迭代的需求范围;节奏:上一个迭代的最后一周内完成本迭代所有的需求的设计;需求管理:每个需求都要管理好团队、模块、类型、上线时间等标签内容;需求的内容:一段话描述解决的问题(包含用户角色、完成活动、实现目标和验收标准),核心是方便团队每一个人都能够理解全团队做的事情,即便不是自己负责的模块,需求状态的设计如下:标签管理:标签的正确使用可以有效提高团队在线协同效率,为此 Erda 产品从以下几个标签维度出发,便于后续事项查询、统计分析使用,迭代执行产品规划规;其他重要内容:研发需求的粒度大小非常重要,一定要控制在一个迭代周期内完成,最好是一周内就可以搞定一个需求;所以产品上的大需求一定要拆解成合适粒度的小研发需求;根据团队历史数据总结出团队的需求吞吐量,一个迭代做多少需求的工作量是合适的。任务 & 缺陷迭代开始后,基于需求,「能够合理有效拆分任务和合理成员安排」是整个迭代中非常关键的一个内容,由于缺陷整理和任务管理类似,所以就合并在一起来讲了,具体的 Erda 团队实践的内容如下:责任者:由研发 TL/架构师等主导完成;任务粒度:每个 todo 项(任务)足够小,推荐控制在 2 天内完成,如果是一个开发 todo,那么就是 2 天内会完成对应任务的代码提交/合并;任务覆盖内容:产品设计、前后端开发、测试、文档 (完成一个需求的全生命周期的任务);任务标签:和需求的标签管理一致;任务拆分理念:任务拆分尽量小,实现功能能够尽快验证、快速看到效果。敏捷管理的好辅助——晨会迭代过程中关键进度和风险同步中,「看板」和「晨会」绝对是比较重要的两把利器,对于看板相信大家都比较熟悉,在此就不赘述,对于 Erda 晨会还是想罗列几点供大家参考讨论:晨会以完全的产品团队为单位 (包含 pd,开发,测试等完整关键角色);晨会主题始终围绕 “发布班车,能否正常上车,哪些内容会 delay 到下一班车”展开;晨会一起关注燃尽图和甘特图的 deadline;时间控制在 10 - 15分钟,主题不能过度发散。结束语正如开始所说,高效协同是一个永恒的话题,大家对于高效协同的目标也一定会伴随科技和文明的发展而有更高的追求,效率问题也一定会有一些创新的思维和方法来解决,比如 github 把五湖四海的研发兄弟姐妹汇聚在一个开源项目上进行协作开发,slack 让大家的异步沟通协作变得更轻松,等等。这些产品和大家正在创造新协作产品,肯定会让软件研发行业的远程异地协作模式变得越来普及和简单!最后欢迎对此有兴趣的小伙伴和我们一起来探讨~更多技术干货请关注【尔达 Erda】公众号,与众多开源爱好者共同成长~
前言:在产研全链路流程上,协同最大的目标就是团队信息的透明化,即在清晰目标的指引下进行团队信息透明的日常研发工作,助力项目/产品成功发布。基于此,研发过程是否行之有效就成为我们关注的另一重点要素。通常「研发过程」是指:代码到制品再到部署上线的全链路,这个过程是持续集成的重中之重。本期主要围绕「研发过程」展开探讨,在具体分享实践之前,笔者想先和大家畅想讨论下,在软件研发全流程中,我们需要怎样的产品和特性来支撑我们持续去追求高效研发(本期主要围绕成本、效能,质量这块后续单独再做分析)的目标,个人思考了几点内容(部分内容会在正文的实践分享中详细说明),在此抛个砖让欢迎大家一起探讨。思考一 支持团队把实践和文化通过自定义配置方式沉淀。所有团队成员在使用产品功能的过程中,会潜移默化按照团队希望的实践路线进行。一方面,团队的研发是按既定路线进行的,研发效能是可预期的;另一方面,团队新人也能快速融入到团队开发模式中,快速融入团队文化。思考二 在软件研发全链路中,应该具备高度自动化的能力。研发全链路工作流中的各节点,通过高度自动化的能力,能实现以下目标:除代码开发和项目管理事项信息录入外,尽量减少登录平台的手动操作;自动精准消息的推送,借助端的能力,让用户本地异步订阅查看研发和项目管理信息。思考三 强大且丰富的端能力。让研发成员能够在本地沉浸式开发,目标要做到不感知 DevOps 平台存在的情况下,无时无刻不在使用平台能力。思考四 产品能力和形态应该能够高度自定义化。想通过一种产品形态去支撑覆盖所有的行业是不现实的,一个 DevOps 平台研发团队,不可能深入了解熟悉各行各业的研发模式,更不可能去定义实践去引领所有行业的研发提效。平台能做和需要做的是:把平台底层基础能力做强、做灵活,让不同业领导者,在高效持续交付的理论引导下,利用平台丰富的基础能力和高度自定义配置能力,去构建属于自己行业的研发实践模式,最终通过行业研发实践模式沉淀模版的可复制化,助力该行业整体研发效能的提升。思考五 平台需要深度结合低代码开发平台,让应用研发变得更简单。思考六 借助 AI 的能力,辅助提升用户代码开发、质量、安全、问题定位等应用研发过程效能。思考七 结合大数据、AI、函数计算等技术特性,让研发效能度量、运维监控等观测能力变得更强大。通过公有云或者企业级大数据、AI 和底层云计算能力的对接,让研发效能度量、监控数据统计的成本变得更低;通过函数计算以低成本的方式满足用户对研发效能和监控数据的灵活统计诉求;通过低代码开发平台 + DevOps 平台底层 Tracing、Logs、Metrics + 函数计算 + 数据可视化,让用户能够快速构建行业最佳实践的监控运维系统,通过模版可以复制到其他行业用户。言归正传,接下来我将主要从「研发全链路」出发,分享一下 Erda 团队具体的实践过程,希望能给大家带来新的思考。基于主干的开发模式在研发模式方面,Erda 团队采用的是基于主干开发模式(Trunk Based Development),相对于主干开发模式还有特性分支开发模式(Feature Branch Development),其典型的代表就是 gitflow 模式。两者都是软件届常用的软件分支模式,各自有着各自的优缺点,选择适合自己的才是最重要的。Erda 团队选择基于主干开发模式的初衷,是想以持续集成的方式尽快发布版本,以及希望通过主干开发模式促成团队成为精英绩效团队。Erda 团队基于主干开发的目标:每日有新代码合入主干,每日完成持续集成、持续测试随时随刻能够提供可发布的版本,特性可以按需紧急发布上线产品特性快速实现验证,减少试错成本为了让团队通过主干开发模式来实现上述的目标,有两个实践要点非常重要:项目协同的高要求:任务拆分(参考迭代事项协同中的任务颗粒度建议,尽量拆分到 1-2 天)需要足够小驱动 mr 变小,从而能从项目管理上支持产品特性快速实现验证和试错;研发质量的高要求:特性开发的快,不能是牺牲质量的快,所以在这个快的前提下,对于合入的特性代码要求就会变的更高,这里的关键就是开发需要确保代码质量,不要想着把质量交给测试团队来保障 (建设自动化流程对规则、标准、规范进行自动检查;单元测试;数据库规范等等)。实践要点分析后,接下来一起看看具体的实践步骤以及需要怎样的 DevOps 平台工具来支撑。基于主干分支的代码研发通过项目协同把需求进行任务拆分,研发同学认领任务后即进入开发工作,开发的首要工作就是创建一个特性分支(Feature/ 任务 ID)用于该特性的代码托管。OK,对于团队新成员来说可能开工第一步问题就来了 Feature 的源分支是谁?团队采用的是主干分支的开发模式,自然就是从主干分支(Master)来拉取了,对于熟悉团队开发模式的同学来说,根本就不是问题,新同学只要通过培训或者自己去 google 学习一下也就明白了。团队的实践和文化固然可以通过管理规范来进行约束来实现,不过古人也云“堵不如疏”,是否能够把这些实践规范约束在产品中定义?通过产品功能交互,引导用户按团队的预定的实践路线进行。Erda 团队就是这样实践的,效果还不错!代码开发到提测的整体流程基于主干分支开发的规范配置Erda 团队的研发规范是在产品的研发工作流中进行定义配置,内容涉及分支清单、分支策略和工作流配置,具体示意图如下:Erda 产品-研发工作流配置示意图具体以产品实际交互为主工作流具体配置内容:分支策略具体配置内容:具体过程研发同学在任务详情中,选择应用一键选择创建工作流。工作流创建后就会自动创建对应的开发分支(如feature/XXXX,其中 XXXX 代表特性 ID);相同应用相同环境不同特性进行系统临时合并部署自测。通过 Erda 平台提供开发环境下相同应用不同特性分支的系统临时合并的功能,让所有特性研发同学可以一起部署和冒烟测试,在不影响效率的情况下大幅减少环境资源的压力;通过研发自测后,工作流中可以一键发起 MR 合并请求,研发 TL 对合并请求相关分支 Code Review 后(因为任务工作流中事项和分支的关系绑定,Code Review 的时候可以清晰知道本次合并请求的具体任务内容),特性分支自动合入主干分支,主干分支通过定时/代码变更的触发器进行集成部署+测试,至此研发同学的特性任务状态也会自动完成。(上述特性正在开发中,目前以手动发起 MR 请求为主)。基于主干分支的每日集成部署通过 Erda 平台来支撑项目级每日持续集成部署。通过项目流水线,构建各应用最新制品(基于各应用主干最新代码)并集成为项目整体制品进行自动化的部署到统一的主干测试环境(Erda 叫集成环境)。部署的策略为每日 23 时,定时触发流水线运行构建、集成、部署 (其他项目也可以根据实际情况设定其他的部署策略)。基于主干分支的每日持续测试自动化测试用例管理根据需求的 deadline ,测试同学会基于研发同学的特性完成时间,去安排自动化测试用例(主要是是基于 API 接口的测试)和功能测试用例的作成。在研发同学特性分支合并到主干分支后,测试同学会在原有的测试计划上追加新特性的自动化测试用例,已有的自动化测试用例回归确保之前特性的可用性。自动化测试执行那么每日持续测试如何触发?即通过部署测试流水线中的 action,触发自动化测试计划的执行。自动化测试结果通知自动化测试结果通过钉钉同步给研发团队。手动功能测试自动化测试并不能百分之百覆盖所有的产品特性,而对于自动化无法覆盖的特性,就需要手动完成。功能性测试是需要实打实投入测试同学人力去一遍遍手动覆盖的,且不说必要性,人力成本可能就是敏捷团队所不能容忍的。对于这样的场景,Erda 团队在手动功能性测试方面主要采用以下策略:针对迭代新功能需要手动功能测试覆盖,主要是测试团队对当前迭代的需求内容进行针对性的测试;测试团队基于需求、任务的状态来判断每日新增的大概内容 (erda 测试团队甚至被要求关注开发提交的 MR/PR,对测试团队的要求是更高的)。基于主干分支的制品管理策略通常意义上的「软件制品」主要是源码文件的集合或者编译后的产物,主要包括二进制包和压缩包两种形式。如果基于 Erda 平台,制品的概念上稍微有点难以区别,我们主要定义为包含部署一个应用所需的全部内容,包括镜像、依赖的 Addon 以及各类配置信息,最终的目的就是通过制品能够在任何一套 Erda 上进行环境级别的部署。这里暂时不去分析和讨论制品的定义和具体的功能点(如对产品制品管理功能有兴趣的同学可以联系我们继续讨论),还是回归到主题,以分享 Erda 团队对于制品管理的实践为主。语义化版本管理首先在制品版本和类型的管理上,团队主要采用的是「语义化版本」管理规范,主要涉及以下几种版本类型:alpha:仅仅是研发自测通过,未经过测试的版本,不稳定的版本beta:阶段性测试通过,相对稳定的版本stable:经过系统性测试,稳定的发布版本版本和分支的关系其次,制品版本和分支上做了强实践的绑定(主要通过研发工作流配置完成)。不管制品如何定义,肯定是基于代码或者编译产物的基础,也就是说制品肯定是从代码来的。所以 Erda 团队在实践中是把分支和制品类型进行绑定关联的,并且把这个以最佳实践沉淀到 Erda 产品中,当然产品支持研发工作流的自定义配置来满足其他特性分支和制品版本类型的关联。Erda 团队的研发模式是基于主干研发的模式,所以分支和版本类型的关系如下:如果采用 Gitflow 可以参考如下:版本和分支的配置分支和制品版本的配置:在研发工作流中配置主要过程说明:研发同学在 feature 分支上开发完成后,并在开发环境上完成自测,代码合入到主干分支(Master);主干分支(Master)通过每日定时触发器执行流水线,会自动构建生成【版本号 + alpha +时间戳】的制品;迭代版本所有的特性代码都合入到主干分支(Master),并且通过自动化测试用例及测试同学手动功能阶段性测试后,测试同学会从主干分支(Master)上切出迭代版本的 Release 分支(分支名:Release/版本号-beta+递增数字),时间频率取决于团队的发布频率(Erda 团队的发布频率是每周,所以这边的时间频率就是每周);当迭代版本进行到一定周期后,产品一般会对外发布一个正式的版本(这个版本可以是上面一个迭代版本,也可以由多个迭代版本组成,这个取决于产品的版本发布计划,Erda 产品的一个大的 Rc 版本由 4 个迭代 beta 版本的特性组成),测试同学会从主干分支(Master)切出Release 分支(分支名:Release/版本号)进行全面测试;测试同学通过 Release/版本号分支流水线构建部署该分支的 beta 制品,在通过测试验证后,测试同学会手动把该 beta 版本制品切换为 stable 版本,并且在制品中维护好制品的 ChangeLog 和制品标签,最终发布到 Gallery 提供的交付团队下载使用。发布火车概念和规则发布火车,通常是指设定固定的发布窗口,具体可以按周或者按月固定一个时间,在这个时间进行定时发布,产品特性能够赶上这趟火车就发布,否则就需要等下趟火车。发布火车的规则看似非常简单,只要固定时间发布即可,其实不然,团队并不能通过一个发布火车的概念就能让发布变得高效,真正能驱动团队高效发布的还需从建立团队的发布文化开始,文化又依托于发布规则的沉淀:定时定点发车。火车不等特性,拒绝未经测试的新特性上车;只发布高质量的特性。只有测试通过的特性才允许上车,即使研发已经完成相关特性并且要求上线的时候,也要坚决拒绝;频繁发布火车,特性拆分的足够小。更快更小的发布能够让产品的质量更高、成本更低,频繁发布使每次发布的特性变小,出现问题的时候定位和解决也会变得更明确和高效,当然发布的频率需要根据团队的研发和管理整体质量决定,不是一味地追求快;世界没有完美的发布,需要有明确的关键性能指标来决策争议特性的发布。Erda 团队实践基于以上的规则,Erda 团队的实践如下:小结不管从项目协同、研发全流程以及到最后的发布火车,其本质目标还是希望能够以高效的方式去支撑应用软件持续快速、高质量的更新发布,为了这个目标,每家公司都有自己的实践和见解,没有对和错之说,只要符合企业当下利益最大化,能够布局企业的未来即可。本文只是以 Erda 一个团队视角进行了分享,欢迎大家可以一起参与分享讨论研发提效的实践,共同促进整个行业效能的提升,谢谢!更多技术干货请关注【尔达 Erda】公众号,与众多开源爱好者共同成长~
为什么要进行数据库版本控制?现代软件工程逐渐向持续集成、持续交付演进,软件一次性交付了事的场景逐渐无法满足复杂多变的业务需求,“如何高效地进行软件版本控制”成为我们面临的挑战。同时,软件也不是仅仅部署到某一套环境中,而是需要部署到开发、测试、生产以及更多的客户环境中,“如何一套代码适应不同的环境”也成为我们要思考的问题。一套软件的副本要部署在不同的环境(图源:Flyway)代码版本管理工具(Git、SVN 等)和托管平台(Github、Erda DevOps Platform 等)让我们能有效地进行代码版本管理。越来越丰富的 CI/CD 工具让我们能定义可重复的构建和持续集成流程,发布和部署变得简单清晰。“基础设施即代码”的思想,让我们可以用代码定义基础设施,从而抹平了各个环境的差异。可以说,在软件侧我们应对这些挑战已经得心应手。但是绝大多数项目都至少包含两个重要部分:业务软件,以及业务软件所使用的数据库——许多项目数据库侧的版本控制仍面临乱局:很多项目的数据库版本控制仍依赖于“人肉维护”,需要开发者手动执行 SQL;环境一多,几乎没人搞得清某个环境上数据库是什么状态了;database migrations 脚本没有统一管理,遗失错漏时有发生;不确定脚本的状态是否应用,也许在这个环境应用了但在另个环境却没有应用;脚本里有一行破坏性代码,执行了后将一个表字段删除了,数据无法恢复,只能“从删库到跑路”;……为了应对这样的乱局,我们需要数据库版本控制工具。数据库版本控制,即 Database Migration,它能帮你:管理数据库的定义和迁移历程在任意时刻和环境从头创建数据库至指定的版本以确定性的、安全的方式执行迁移清楚任意环境数据库处于什么状态从而让数据库与软件的版本管理同步起来,软件版本始终能对应正确的数据库版本,同时提高安全性、降低维护成本。Erda 如何实践数据库版本控制Erda 是基于多云架构的一站式企业数字化平台,为企业提供 DevOps、微服务治理、多云管理以及快数据管理等云厂商无绑定的 IT 服务。Erda 既可以私有化交付,也提供了 SaaS 化云平台 Erda Cloud,以及开源的社区版。当你正在阅读这篇文章时,有无数来自不同组织的应用程序正在 Erda Cloud 或 Erda 私有化平台的流水线上完成以构建和部署为核心的 CI/CD 流程,无数的代码,以这种持续而自动化的方式转化成服务实例。Erda 平台不但接管了这些组织的应用程序的集成、交付,Erda 项目自身的集成也是托管在 Erda DevOps 平台的。Erda 自身的持续集成和丰富的交付场景要求它能进行安全、高效、可持续的数据库版本控制,托管在 Erda 上的应用程序也要求 Erda 提供一套完整的数据库版本控制方案。Erda 项目使用 Erda MySQL Migrator 作为数据库版本控制工具,它被广泛应用于 CI/CD 流程和命令行工具中。基本原理第一次使用 Erda MySQL Migrator 进行数据库版本控制时会在数据库中新建一个名为 schema_migration_history 的表,如下如所示:schema_migration_history 表的基本结构(部分主要字段)Erda MySQL Migrator 每次执行 database migration 时,会对比文件系统中的 migrations 脚本和 schema_migration_history 表中的执行记录,标记出增量的部分。在一系列审查后,Erda MySQL Migrator 将增量的部分应用到目标 database 中。成功应用的脚本被记录在案。Erda MySQL Migrator 命令行工具erda-cli 工具的构建与安装erda-cli 是 erda 项目命令行工具,它集成了 Erda 平台安装、Erda 拓展管理以及开发脚手架。其中 erda-cli migrate 命令集成了数据库版本控制全部功能。从 erda 仓库 拉取代码到本地,切换到 master 分支,执行以下命令可以编译erda-cli :% make prepare-cli % make cli注意编译前应确保当前环境已安装 docker。编译成功后项目目录下生成了一个 bin/erda-cli 可执行文件。使用 erda-cli migrate 进行数据库版本迁移Erda MySQL Migrator 要求按 modules/scripts 两级目录组织数据库版本迁移脚本,以 erda 仓库为例:.erda/migrations ├── apim │ ├── 20210528-apim-base.sql │ ├── 20210709-01-add-api-example.py │ └── requirements.txt ... ... ├── cmdb │ ├── 20210528-cmdb-base.sql │ ├── 20210610-addIssueSubscriber.sql │ ├── 20210702-updateMbox.sql │ └── 20210708-add-manageconfig.sql └── config.yml └── 20200528-tmc-base.sqlerda 项目将数据库迁移脚本放在 .erda/migrations 目录下,目录下一层级是按模块名(微服务名)命名的脚本目录,其各自下辖本模块所有脚本。与脚本目录同级的,还有一个 config.yml 的文件,它是 Erda MySQL Migration 规约配置文件,它描述了 migrations 脚本所需遵循的规约。脚本目录下按文件名字符序排列着 migrations 脚本,目前支持 SQL 脚本和 Python 脚本。如果目录下存在 Python 脚本,则需要用 requirements.txt 来描述 Python 脚本的依赖。进入 migrations 脚本所在目录 .erda/migrations,执行 erda-cli migrate :% erda-cli migrate --mysql-host localhost \ --mysql-username root \ --mysql-password 123456789 \ --sandbox-port 3307 \ --database erda INFO[0000] Erda Migrator is working INFO[0000] DO ERDA MYSQL LINT.... INFO[0000] OK INFO[0000] DO FILE NAMING LINT.... INFO[0000] OK INFO[0000] DO ALTER PERMISSION LINT.... INFO[0000] OK INFO[0000] DO INSTALLED CHANGES LINT.... INFO[0000] OK INFO[0000] COPY CURRENT DATABASE STRUCTURE TO SANDBOX.... INFO[0014] OK INFO[0014] DO MIGRATION IN SANDBOX.... INFO[0014] OK INFO[0014] DO MIGRATION.... INFO[0014] module=apim ... ... INFO[0014] module=cmdb INFO[0014] OK INFO[0014] Erda MySQL Migrate Success !执行 erda-cli migrate 命令从执行日志可以看到,命令行执行一系列检查以及沙盒预演后,成功应用了本次 database migration。我们可以登录数据库查看到脚本的应用情况。mysql> SELECT service_name, filename FROM schema_migration_history; +---------------+-------------------------------------------+ | service_name | filename | +---------------+-------------------------------------------+ | apim | 20210528-apim-base.sql | | apim | 20210709-01-add-api-example.py | ... ... ... ... ... ... | cmdb | 20210528-cmdb-base.sql | | cmdb | 20210610-addIssueSubscriber.sql | | cmdb | 20210702-updateMbox.sql | | cmdb | 20210708-add-manageconfig.sql | +---------------+-------------------------------------------+登录 MySQl Server 查看脚本应用情况基于 Python 脚本的 data migration从上一节我们看到,脚本目录中混合着 SQL 脚本和 Python 脚本,migrator 对它们一致地执行。Erda MySQL Migrator 在设计之初就决定了单脚本化的 migration,即一个脚本表示一次 migration 过程。大部分 database migration 都可以很好地用 SQL 脚本表达,但仍有些包含复杂逻辑的 data migration 用 SQL 表达则会比较困难。对这类包含复杂业务逻辑的 data migration,Erda MySQL Migrator 支持开发者使用 Python 脚本。erda-cli 提供了一个命令行 erda-cli migrate mkpy 来帮助开发者创建一个基础的 Python 脚本。执行:% erda-cli migrate mkpy --module my_module --name myfeature.py --tables blog,author,info命令生成如下脚本:""" Generated by Erda Migrator. Please implement the function entry, and add it to the list entries. """ import django.db.models class Blog(django.db.models.Model): name = models.CharField(max_length=100) tagline = models.TextField() class Meta: db_table = "blog" class Author(django.db.models.Model): name = models.CharField(max_length=200) email = models.EmailField() class Meta: db_table = "author" class Info(django.db.models.Model): blog = models.ForeignKey(Blog, on_delete=models.CASCADE) headline = models.CharField(max_length=255) body_text = models.TextField() pub_date = models.DateField() mod_date = models.DateField() authors = models.ManyToManyField(Author) number_of_comments = models.IntegerField() number_of_pingbacks = models.IntegerField() rating = models.IntegerField() class Meta: db_table = "info" def entry(): """ please implement this and add it to the list entries """ pass entries: [callable] = [ entry, ]该脚本可以分为四个部分:import 导入必要的包。脚本中采用继承了 django.db.models.Model 的类来定义库表,因此需要导入 django.db.model 库。开发者可以根据实际情况导入自己所需的包,但由于单脚本提交的原则,脚本中不应当导入本地其他文件。模型定义。脚本中 class Blog、 class Author 和 class Entry 是命令行工具为开发者生成的模型类。开发者可以使用命令行参数 --tables 指定要生成哪些模型定义,以便在开发中引用它们。注意,生成这些模型定义类时并没有连接数据库,而是根据文件系统下过往的 migration 所表达的 Schema 生成。生成的模型定义只表示了表结构而不包含表关系,如“一对一”、“一对多”、“多对多”等。如果开发者要使用关联查询,应当编辑模型,自行完成模型关系的描述。Django ORM 的模型关系仅表示逻辑层面的关系,与数据库物理层的关系无关。entry 函数。命令行为开发者生成了一个名为 entry 的函数,但是没有任何函数体,开发者需要自行实现该函数体以进行 data migration。entries,一个以函数为元素的列表,是程序执行的入口。开发者要将实现 data migration 的业务函数放到这里,只有 entries 中列举的函数才会被执行。从以上脚本结构可以看到,我们选用的 Django ORM 来描述模型和进行 CRUD 操作。为什么采用 Django ORM 呢?因为 Django 是 Python 语言里最流行的 web 框架之一,Django ORM 也是 Python 中最流行的 ORM 之一,其设计完善、易用、便于二次开发,且有详尽的文档、丰富的学习材料以及活跃的社区。无论是 Go 开发者还是 Java 开发者,都能在掌握一定的 Python 基础后快速上手该 ORM。我们通过两个简单的例子来了解下如何利用 Django ORM 来进行 CRUD 操作。示例 1 创建一条新记录。# 示例 1 # 创建一条记录 def create_a_blog(): blog = Blog() blog.name = "this is my first blog" blog.tagline = "this is looong text" blog.save()Django ORM 创建一条记录十分简单,引用模型类的实例,填写字段的值,调用 save()方法即可。示例 2 删除所有标题中包含 "Lennon" 的 Blog 条目。Django 提供了一种强大而直观的方式来“追踪”查询中的关系,在幕后自动处理 SQL JOIN 关系。它允许你跨模型使用关联字段名,字段名由双下划线分割,直到拿到想要的字段。# 示例 2 # 删除所有标题中含有 'Lennon' 的 Blog 条目: def delete_blogs_with_headline_lennon(): Blog.objects.filter(info__headline__contains='Lennon').delete()最后,别忘了将这两个函数放到 entries 列表中,不然它们不会被执行。entries: [callable] = [ create_a_blog, delete_blogs_with_headline_lennon, ]可以看到,编写基于 Python 的 data migration 是十分方便的。erda-cli migrate mkpy 命令行为开发者生成了模型定义,引用模型类及其实例可以便捷地操作数据变更,开发只须关心编写函数中的业务逻辑。想要进一步了解 Django ORM 的使用请查看文档:Django - 执行查询在 CI/CD 时进行数据库版本控制每日凌晨,Erda 上的一条流水线静静启动,erda 仓库的主干分支代码都会被集成、构建、部署到集成测试环境。开发者一早打开电脑,登录集成测试环境的 Erda 平台验证昨日集成的新 feature 是否正确,发现昨天新合并的 migrations 也一并应用到了集成测试环境。这是怎么做到的呢 ?Erda 每日自动化集成流水线(部分步骤)原来这条流水线每日凌晨拉取 erda 仓库主干分支代码 -> 构建应用 -> 将构建产物制成部署制品 -> 在集成测试环境执行 Erda MySQL 数据迁移 -> 将制品部署到集成测试环境。流水线中的 Erda MySQL 数据迁移 节点是集成了 Erda MySQL Migrator 全部功能的 Action,是 Erda MySQL Migrator 在 Erda CI/CD 流水线中的应用。Erda MySQL Migrator 除了可以作为 Action 编排在流水线中,还可以脱离 Erda 平台作为命令行工具单独使用。Erda MySQL Migrator 其他特性规约检查成熟的团队一般都会制定代码开发规约。Erda MySQL Migrator 支持开发者团队通过配置规约文件,来约定 SQL 脚本规范,如启用和禁用特定的 SQL 语句、约束表名与字段名格式、约束字段类型等。比如要求 id 字段必须是 varchar(36) 或 char(36),可以添加如下配置:- name: ColumnTypeLinter meta: columnName: id types: - type: varchar flen: 36 - type: char flen: 36比如要求表名必须以 "erda_" 开头,可以添加如下配置:- name: TableNameLinter alias: "TableNameLinter: 以 erda_ 开头仅包含小写英文字母数字下划线" white: committedAt: - "<20220215" ## 此处表示对提交时间早于2022年2月5日的文件不作此条规约要求 meta: patterns: - "^erda_[a-z0-9_]{1,59}"关于如何编写规约配置文件的更新信息见链接:https://github.com/erda-project/erda-actions/tree/master/actions/erda-mysql-migration/1.0-57#%E8%A7%84%E7%BA%A6%E9%85%8D%E7%BD%AE使用命令行工具进行规约检查erda-cli migrate lint 命令可以检查指定目录下所有脚本的 SQL 语句是否符合规约。开发者在编写 migration 时用该命令来预先检查,避免提交不合规了不合规的脚本。例如开发者在 SQL 脚本中编写了如下语句:alter table dice_api_assets add column col_name varchar(255);执行规约检查:% erda-cli migrate lint 2021/07/19 17:39:43 Erda MySQL Lint the input file or directory: . apim/20210715-01-feature.sql: dice_api_assets: - 'missing necessary column definition option: COMMENT' - 'missing necessary column definition option: NOT NULL' apim/20210715-01-feature.sql [lints] apim/20210715-01-feature.sql:1: missing necessary column definition option: COMMENT: apim/20210715-01-feature.sql:1: missing necessary column definition option: NOT NULL:使用命令行工具进行本地规约检查可以看到命令行返回了检查报告,指出了某个文件中存在不合规的语句,并指出了具体的文件、行号、错误原因等信息。上面示例中指出了这条语句有两条不合规处:一是新增列时,应当有列注释,此处缺失;二是新增的列应当是 NOT NULL 的,此处没有指定。使用 CI 工具进行规约检查开发者自行使用命令行工具自检是合规检查的第一道关卡。在提交的代码合并到 erda 仓库主干分支前,PR 触发的 CI 流程会利用命令行工具检查 migrations 合规性则是第二道关卡。当提交包含不合规的 SQL 的 PR 时,CI 就会失败:Github CI:Erda MySQL Lint 失败提示使用 Erda MySQL Migration Lint Action 进行规约检查对于托管在 Erda DevOps 平台的项目,可以使用 Erda MySQL Migration Lint Action 进行规约检查。前文中的 Erda MySQL 数据迁移 Action 已经包含了规约检查功能,所以从功能上来说,Erda MySQL Migration Lint Action 可以看做 Erda MySQL 数据迁移 Action 的一部分。下图是使用 Erda MySQL Migration Lint Action 编排的流水线检查脚本合规性的示例。使用 Erda MySQL Migration Lint Action 编排流水线检查脚本合规性示例中该 Action 失败,打开 Action 日志可以查看具体失败原因。沙盒与 Dryrun引入沙盒是为了在将 migrations 应用到目标数据库前进行一次模拟预演,期望将问题的发现提前,防止将问题 migration 应用到了目标数据库中。对 Erda 这样的有丰富的交付场景的项目而言,在 migrate 前先进行一道预演是十分有意义的。这是 Erda MySQL Migrator 根据自身实际设计的,是 Flyway 等工具所不具备的。Erda MySQL Migrator 可以配置仅在沙盒中而不在真实的 MySQL Server 中执行执行 migration,从而达到 Dryrun 的目的。文件篡改检查与修订机制Erda MySQL Migrator 不允许篡改已应用过的文件。之所以这样设计是因为一旦修改了已应用过的脚本,那么代码与真实数据库状态就不一致了。如果要修改表结构,应当增量地提交新的 migrations。这是一种常见的做法,Flyway 等工具也会对已执行的文件进行检查。但实际生产中,“绝不修改过往文件”这种理想状态很难达到,Erda MySQL Migrator 提供了一种修订机制。当用户想修改一个文件名为“some-feature.sql”过往文件时,他应该修改该文件,并提交一个名为“patch-some-feature.sql”的包含了修改内容的文件到 .patch 目录中。日志收集Erda MySQL Migrator 在 debug 模式下,会打印所有执行执行过程和 SQL 的标准输出。除此之外,它还可以将纯 SQL 输出到指定目录的日志文件中。获取工具erda-cli 下载地址Machttp://erda-release.oss-cn-hangzhou.aliyuncs.com/cli/mac/erda-cliLinuxhttp://erda-release.oss-cn-hangzhou.aliyuncs.com/cli/linux/erda-cli注意:以上 erda-cli 仅用于 amd64 平台,其他平台请按文中介绍的安装方式自行构建。源码地址Erda MySQL MIgrator Action 源码地址https://github.com/erda-project/erda-actions/tree/master/actions/erda-mysql-migration/1.0-57
在分布式、微服务架构下,应用一个请求往往贯穿多个分布式服务,这给应用的故障排查、性能优化带来新的挑战。分布式链路追踪作为解决分布式应用可观测问题的重要技术,愈发成为分布式应用不可缺少的基础设施。本文将详细介绍分布式链路的核心概念、架构原理和相关开源标准协议,并分享我们在实现无侵入 Go 采集 Sdk 方面的一些实践。为什么需要分布式链路追踪系统微服务架构给运维、排障带来新挑战在分布式架构下,当用户从浏览器客户端发起一个请求时,后端处理逻辑往往贯穿多个分布式服务,这时会浮现很多问题,比如:请求整体耗时较长,具体慢在哪个服务?请求过程中出错了,具体是哪个服务报错?某个服务的请求量如何,接口成功率如何?回答这些问题变得不是那么简单,我们不仅仅需要知道某一个服务的接口处理统计数据,还需要了解两个服务之间的接口调用依赖关系,只有建立起整个请求在多个服务间的时空顺序,才能更好的帮助我们理解和定位问题,而这,正是分布式链路追踪系统可以解决的。分布式链路追踪系统如何帮助我们分布式链路追踪技术的核心思想:在用户一次分布式请求服务的调⽤过程中,将请求在所有子系统间的调用过程和时空关系追踪记录下来,还原成调用链路集中展示,信息包括各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等。如上图所示,通过分布式链路追踪构建出完整的请求链路后,可以很直观地看到请求耗时主要耗费在哪个服务环节,帮助我们更快速聚焦问题。同时,还可以对采集的链路数据做进一步的分析,从而可以建立整个系统各服务间的依赖关系、以及流量情况,帮助我们更好地排查系统的循环依赖、热点服务等问题。分布式链路追踪系统架构概览核心概念在分布式链路追踪系统中,最核心的概念,便是链路追踪的数据模型定义,主要包括 Trace 和 Span。其中,Trace 是一个逻辑概念,表示一次(分布式)请求经过的所有局部操作(Span)构成的一条完整的有向无环图,其中所有的 Span 的 TraceId 相同。Span 则是真实的数据实体模型,表示一次(分布式)请求过程的一个步骤或操作,代表系统中一个逻辑运行单元,Span 之间通过嵌套或者顺序排列建立因果关系。Span 数据在采集端生成,之后上报到服务端,做进一步的处理。其包含如下关键属性:Name:操作名称,如一个 RPC 方法的名称,一个函数名StartTime/EndTime:起始时间和结束时间,操作的生命周期ParentSpanId:父级 Span 的 IDAttributes:属性,一组 <K,V> 键值对构成的集合Event:操作期间发生的事件SpanContext:Span 上下文内容,通常用于在 Span 间传播,其核心字段包括 TraceId、SpanId一般架构分布式链路追踪系统的核心任务是:围绕 Span 的生成、传播、采集、处理、存储、可视化、分析,构建分布式链路追踪系统。其一般的架构如下如所示:我们看到,在应用端需要通过侵入或者非侵入的方式,注入 Tracing Sdk,以跟踪、生成、传播和上报请求调用链路数据;Collect agent 一般是在靠近应用侧的一个边缘计算层,主要用于提高 Tracing Sdk 的写性能,和减少 back-end 的计算压力;采集的链路跟踪数据上报到后端时,首先经过 Gateway 做一个鉴权,之后进入 kafka 这样的 MQ 进行消息的缓冲存储;在数据写入存储层之前,我们可能需要对消息队列中的数据做一些清洗和分析的操作,清洗是为了规范和适配不同的数据源上报的数据,分析通常是为了支持更高级的业务功能,比如流量统计、错误分析等,这部分通常采用flink这类的流处理框架来完成;存储层会是服务端设计选型的一个重点,要考虑数据量级和查询场景的特点来设计选型,通常的选择包括使用 Elasticsearch、Cassandra、或 Clickhouse 这类开源产品;流处理分析后的结果,一方面作为存储持久化下来,另一方面也会进入告警系统,以主动发现问题来通知用户,如错误率超过指定阈值发出告警通知这样的需求等。刚才讲的,是一个通用的架构,我们并没有涉及每个模块的细节,尤其是服务端,每个模块细讲起来都要很花些功夫,受篇幅所限,我们把注意力集中到靠近应用侧的 Tracing Sdk,重点看看在应用侧具体是如何实现链路数据的跟踪和采集的。协议标准和开源实现刚才我们提到 Tracing Sdk,其实这只是一个概念,具体到实现,选择可能会非常多,这其中的原因,主要是因为:不同的编程语言的应用,可能采用不同技术原理来实现对调用链的跟踪不同的链路追踪后端,可能采用不同的数据传输协议当前,流行的链路追踪后端,比如 Zipin、Jaeger、PinPoint、Skywalking、Erda,都有供应用集成的 sdk,导致我们在切换后端时应用侧可能也需要做较大的调整。社区也出现过不同的协议,试图解决采集侧的这种乱象,比如 OpenTracing、OpenCensus 协议,这两个协议也分别有一些大厂跟进支持,但最近几年,这两者已经走向了融合统一,产生了一个新的标准 OpenTelemetry,这两年发展迅猛,已经逐渐成为行业标准。OpenTelemetry 定义了数据采集的标准 api,并提供了一组针对多语言的开箱即用的 sdk 实现工具,这样,应用只需要与 OpenTelemetry 核心 api 包强耦合,不需要与特定的实现强耦合。应用侧调用链跟踪实现方案概览应用侧核心任务应用侧围绕 Span,有三个核心任务要完成:生成 Span:操作开始构建 Span 并填充 StartTime,操作完成时填充 EndTime 信息,期间可追加 Attributes、Event 等传播 Span:进程内通过 context.Context、进程间通过请求的 header 作为 SpanContext 的载体,传播的核心信息是 TraceId 和 ParentSpanId上报 Span:生成的 Span 通过 tracing exporter 发送给 collect agent / back-end server要实现 Span 的生成和传播,要求我们能够拦截应用的关键操作(函数)过程,并添加 Span 相关的逻辑。实现这个目的会有很多方法,不过,在罗列这些方法之前,我们先看看在 OpenTelemetry 提供的 go sdk 中是如何做的。基于 OTEL 库实现调用拦截OpenTelemetry 的 go sdk 实现调用链拦截的基本思路是:基于 AOP 的思想,采用装饰器模式,通过包装替换目标包(如 net/http)的核心接口或组件,实现在核心调用过程前后添加 Span 相关逻辑。当然,这样的做法是有一定的侵入性的,需要手动替换使用原接口实现的代码调用改为包装接口实现。我们以一个 http server 的例子来说明,在 go 语言中,具体是如何做的:假设有两个服务 serverA 和 serverB,其中 serverA 的接口收到请求后,内部会通过 httpclient 进一步发起到 serverB 的请求,那么 serverA 的核心代码可能如下图所示:以 serverA 节点为例,在 serverA 节点应该产生至少两个 Span:Span1,记录 httpServer 收到一个请求后内部整体处理过程的一个耗时情况Span2,记录 httpServer 处理请求过程中,发起的另一个到 serverB 的 http 请求的耗时情况并且 Span1 应该是 Span2 的 ParentSpan我们可以借助 OpenTelemetry 提供的 sdk 来实现 Span 的生成、传播和上报,上报的逻辑受篇幅所限我们不再详述,重点来看看如何生成这两个 Span,并使这两个 Span 之间建立关联,即 Span 的生成和传播 。HttpServer Handler 生成 Span 过程对于 httpserver 来讲,我们知道其核心就是 http.Handler 这个接口。因此,可以通过实现一个针对 http.Handler 接口的拦截器,来负责 Span 的生成和传播。package http type Handler interface { ServeHTTP(ResponseWriter, *Request) } http.ListenAndServe(":8090", http.DefaultServeMux)要使用 OpenTelemetry Sdk 提供的 http.Handler 装饰器,需要如下调整 http.ListenAndServe 方法:import ( "net/http" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) wrappedHttpHandler := otelhttp.NewHandler(http.DefaultServeMux, ...) http.ListenAndServe(":8090", wrappedHttpHandler)如图所示,wrppedHttpHandler 中将主要实现如下逻辑(精简考虑,此处部分为伪代码):① ctx := tracer.Extract(r.ctx, r.Header):从请求的 header 中提取 traceparent header 并解析,提取 TraceId和 SpanId,进而构建 SpanContext 对象,并最终存储在 ctx 中;② ctx, span := tracer.Start(ctx, genOperation(r)):生成跟踪当前请求处理过程的 Span(即前文所述的Span1),并记录开始时间,这时会从 ctx 中读取 SpanContext,将 SpanContext.TraceId 作为当前 Span 的TraceId,将 SpanContext.SpanId 作为当前 Span的ParentSpanId,然后将自己作为新的 SpanContext 写入返回的 ctx 中;③ r.WithContext(ctx):将新生成的 SpanContext 添加到请求 r 的 context 中,以便被拦截的 handler 内部在处理过程中,可以从 r.ctx 中拿到 Span1 的 SpanId 作为其 ParentSpanId 属性,从而建立 Span 之间的父子关系;④ span.End():当 innerHttpHandler.ServeHTTP(w,r) 执行完成后,就需要对 Span1 记录一下处理完成的时间,然后将它发送给 exporter 上报到服务端。HttpClient 请求生成 Span 过程我们再接着看 serverA 内部去请求 serverB 时的 httpclient 请求是如何生成 Span 的(即前文说的 Span2)。我们知道,httpclient 发送请求的关键操作是 http.RoundTriper 接口:package http type RoundTripper interface { RoundTrip(*Request) (*Response, error) }OpenTelemetry 提供了基于这个接口的一个拦截器实现,我们需要使用这个实现包装一下 httpclient 原来使用的 RoundTripper 实现,代码调整如下:import ( "net/http" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) wrappedTransport := otelhttp.NewTransport(http.DefaultTransport) client := http.Client{Transport: wrappedTransport}如图所示,wrappedTransport 将主要完成以下任务(精简考虑,此处部分为伪代码):① req, _ := http.NewRequestWithContext(r.ctx, “GET”,url, nil) :这里我们将上一步 http.Handler 的请求的 ctx,传递到 httpclient 要发出的 request 中,这样在之后我们就可以从 request.Context() 中提取出 Span1 的信息,来建立 Span 之间的关联;② ctx, span := tracer.Start(r.Context(), url):执行 client.Do() 之后,将首先进入 WrappedTransport.RoundTrip() 方法,这里生成新的 Span(Span2),开始记录 httpclient 请求的耗时情况,与前文一样,Start 方法内部会从 r.Context() 中提取出 Span1 的 SpanContext,并将其 SpanId 作为当前 Span(Span2)的 ParentSpanId,从而建立了 Span 之间的嵌套关系,同时返回的 ctx 中保存的 SpanContext 将是新生成的 Span(Span2)的信息;③ tracer.Inject(ctx, r.Header):这一步的目的是将当前 SpanContext 中的 TraceId 和 SpanId 等信息写入到 r.Header 中,以便能够随着 http 请求发送到 serverB,之后在 serverB 中与当前 Span 建立关联;④ span.End():等待 httpclient 请求发送到 serverB 并收到响应以后,标记当前 Span 跟踪结束,设置 EndTime 并提交给 exporter 以上报到服务端。基于 OTEL 库实现调用链跟踪总结我们比较详细的介绍了使用 OpenTelemetry 库,是如何实现链路的关键信息(TraceId、SpanId)是如何在进程间和进程内传播的,我们对这种跟踪实现方式做个小的总结:如上分析所展示的,使用这种方式的话,对代码还是有一定的侵入性,并且对代码有另一个要求,就是保持 context.Context 对象在各操作间的传递,比如,刚才我们在 serverA 中创建 httpclient 请求时,使用的是http.NewRequestWithContext(r.ctx, ...) 而非http.NewRequest(...)方法,另外开启 goroutine 的异步场景也需要注意 ctx 的传递。非侵入调用链跟踪实现思路我们刚才详细展示了基于常规的一种具有一定侵入性的实现,其侵入性主要表现在:我们需要显式的手动添加代码使用具有跟踪功能的组件包装原代码,这进一步会导致应用代码需要显式的引用具体版本的 OpenTelemetry instrumentation 包,这不利于可观测代码的独立维护和升级。那我们有没有可以实现非侵入跟踪调用链的方案可选?所谓无侵入,其实也只是集成的方式不同,集成的目标其实是差不多的,最终都是要通过某种方式,实现对关键调用函数的拦截,并加入特殊逻辑,无侵入重点在于代码无需修改或极少修改。上图列出了现在可能的一些无侵入集成的实现思路,与 .net、java 这类有 IL 语言的编程语言不同,go 直接编译为机器码,导致无侵入的方案实现起来相对比较麻烦,具体有如下几种思路:编译阶段注入:可以扩展编译器,修改编译过程中的ast,插入跟踪代码,需要适配不同编译器版本。启动阶段注入:修改编译后的机器码,插入跟踪代码,需要适配不同 CPU 架构。如 monkey, gohook。运行阶段注入:通过内核提供的 eBPF 能力,监听程序关键函数执行,插入跟踪代码,前景光明!如,tcpdump,bpftrace。Go 非侵入链路追踪实现原理Erda 项目的核心代码主要是基于 golang 编写的,我们基于前文所述的 OpenTelemetry sdk,采用基于修改机器码的的方式,实现了一种无侵入的链路追踪方式。前文提到,使用 OpenTelemetry sdk 需要代码做一些调整,我们看看这些调整如何以非侵入的方式自动的完成:我们以 httpclient 为例,做简要的解释。gohook 框架提供的 hook 接口的签名如下:// target 要hook的目标函数 // replacement 要替换为的函数 // trampoline 将源函数入口拷贝到的位置,可用于从replcement跳转回原target func Hook(target, replacement, trampoline interface{}) error对于 http.Client,我们可以选择 hook DefaultTransport.RoundTrip() 方法,当该方法执行时,我们通过 otelhttp.NewTransport() 包装起原 DefaultTransport 对象,但需要注意的是,我们不能将 DefaultTransport 直接作为 otelhttp.NewTransport() 的参数,因为其 RoundTrip() 方法已经被我们替换了,而其原来真正的方法被写到了 trampoline 中,所以这里我们需要一个中间层,来连接 DefaultTransport 与其原来的 RoundTrip 方法。具体代码如下://go:linkname RoundTrip net/http.(*Transport).RoundTrip //go:noinline // RoundTrip . func RoundTrip(t *http.Transport, req *http.Request) (*http.Response, error) //go:noinline func originalRoundTrip(t *http.Transport, req *http.Request) (*http.Response, error) { return RoundTrip(t, req) } type wrappedTransport struct { t *http.Transport } //go:noinline func (t *wrappedTransport) RoundTrip(req *http.Request) (*http.Response, error) { return originalRoundTrip(t.t, req) } //go:noinline func tracedRoundTrip(t *http.Transport, req *http.Request) (*http.Response, error) { req = contextWithSpan(req) return otelhttp.NewTransport(&wrappedTransport{t: t}).RoundTrip(req) } //go:noinline func contextWithSpan(req *http.Request) *http.Request { ctx := req.Context() if span := trace.SpanFromContext(ctx); !span.SpanContext().IsValid() { pctx := injectcontext.GetContext() if pctx != nil { if span := trace.SpanFromContext(pctx); span.SpanContext().IsValid() { ctx = trace.ContextWithSpan(ctx, span) req = req.WithContext(ctx) } } } return req } func init() { gohook.Hook(RoundTrip, tracedRoundTrip, originalRoundTrip) }我们使用 init() 函数实现了自动添加 hook,因此用户程序里只需要在 main 文件中 import 该包,即可实现无侵入的集成。值得一提的是 req = contextWithSpan(req) 函数,内部会依次尝试从 req.Context() 和 我们保存的 goroutineContext map 中检查是否包含 SpanContext,并将其赋值给 req,这样便可以解除了必须使用 http.NewRequestWithContext(...) 写法的要求。详细的代码可以查看 Erda 仓库:https://github.com/erda-project/erda-infra/tree/master/pkg/trace参考链接https://opentelemetry.io/registry/https://opentelemetry.io/docs/instrumentation/go/getting-started/https://www.ipeapea.cn/post/go-asm/https://github.com/brahma-adshonor/gohookhttps://www.jianshu.com/p/7b3638b47845https://paper.seebug.org/1749/
在 Erda 2.0 版本中,我们完成了应用研发过程主体框架的构建,在后续版本(包含本次发布的版本)会逐步去完善和丰富研发全流程中的“毛细血管”,真正构建一个研发全流程可视化、自动化的有机结合体。这将会是一个持续丰富和改进的过程,为此 Erda 产品也围绕此目标,本次迭代就已经在路上。Erda 2.1 版本主要围绕研发全流程自动化的主线路,重点发布了协同事项信息自动化同步、流水线自动发现归类、全新的协同事项详情页交互以及其他用户体验等,大大小小共计 23 项特性。针对研发全流程可视化、自动化的目标,此版本先试探性迈出了“一小步”,非常希望收到大家在使用的过程中对我们的期待和建议,也欢迎大家积极参与到 Erda 社区的建设中,和我们一起完善推动社会研发效率提升!下面我们一起来看看,本次新版本将会有哪些亮点和大家见面~亮点功能 1:协同事项信息的自动化处理和同步有人的地方就有江湖,江湖快意间可免不了沟通协同。对于应用研发,无论是集中线下办公、还是异地线上沟通,都需要不同研发角色进行高效的沟通协同,协同处理的好坏直接关系到团队的效率和产品的质量。同时,“如何进行高效协同”又是一个复杂的课题,我们不想一上来就进行大刀阔斧的创新和改变,还是想在原有大交互不变的情况下加入一些自动化/半自动化的能力,方便大家来使用 Erda 的项目协同,一点点改善加验证来稳步前进,最终呈现给大家一个支撑异步、远程办公的项目协同工具。(对于如何远程办公、异步沟通等方案,有兴趣的同学可以联系我们一起讨论,后续我们也会陆续反馈到 Erda 产品功能上来)产研同学和项目管理者每天要手动登陆研发 PaaS 平台很多遍,前者是为了修改自己相关事项的状态,后者则需要不断去 check 进度,时不时还有友好提醒下哪些同学没有及时更新。修改事项的主要状态信息后(比如:迭代、截止日期等)还要一个个手动去修改事项 TodoList 下的内容,很多时候大家会被这些细小的工作所困惑,为此 Erda 2.1 版本也针对上述的情况,进行了一些产品的改善:协同事项状态流转自动化,TodoList 中的任务关联 MR 后,状态会自动转为进行中(目前还用的是关联功能,下一步整个工作流视图中会有更多的呈现,在下一个版本中会上线),TodoList 中任何一个事项状态变成进行中后,TodoList 上一层事项(需求)的状态也会自动进行转换协同事项的迭代、标签信息半自动化同步,需求的迭代、标签内容变更后,系统会提示变更内容,并询问是否要同步到 TodoList 事项中,选择是后就会直接自动同步所有的 TodoList 事项协同事项的截止日期自动计算,首先迭代内的事项完成交付 Deadline 其实是明确的,不能超过迭代的截止日期,所以本次版本中对于迭代内的事项截止日期进行了强制的规范迭代内创建事项:If 迭代开始日 ≥ 本日 Then 新创建的截止日期 = 迭代开始日 Else 新创建的截止日期 = 本日 End If规则的目标:大家在迭代开始或者本日第一时间去规划自己的事项,合理安排事项的截止日期亮点功能 2: 全新的事项协同详情交互新的事项详情页交互采用了上下结构的布局,突出了事项基本状态信息,让所有用户可以第一时间关注到:状态、截止日期等详情信息。同时,对事项的活动日志进行了类划分,主要包含了日常交流互动的评论、修改事项基本信息的活动日志、状态和处理人流向的流转信息,方便用户直观查看。亮点功能 3: 制品部署中配置、addon 复用能力加强本次针对制品所依赖的 addon 信息进行透出直观化展示,用户可以根据制品依赖的 addon 信息,在部署环境中提前准备好相关的 addon 中间件服务。如果环境中相关的 addon 已经存在,部署该制品的时候会自动对 addon 进行复用,减少了上个版本中 addon 会重复部署的资源开销,也有效降低 addon 的维护成本。追加对存储 quota 的配置,本次也在 Dice.yml 中新增支持容器 emptyDir 和 临时存储可用容量的配置。亮点功能 4: 微服务观测链路加强和自定义数据大盘的跨环境迁移本次升级主要解决了从微服务拓扑图发现问题后,需手动选择产品菜单进行具体问题分析调查的操作断层,在新版本中,用户从微服务拓扑图中就可以快速打开问题服务的详情页。对于应用、系统的维护者或 SRE 同学而言,各自有着不同的关注指标,但是对于指标的可视化需求又因人而异。之前 Erda 通过自定义可视化大盘解决了上述问题,随之而来的则是如何复用已有可视化大盘到其他项目中这一大难题。针对此问题,Erda 2.1 版本暂时增加了大盘导入导出的功能,后续 Erda 会以更优雅的方式去解决和扩大覆盖面。全部特性一览新增内容【协同】协同事项状态流转自动化【协同】协同事项的操作体验和信息同步能力的增强【协同】迭代详情页内增加事项统计信息,支持查看统计数据的详情信息【协同】迭代内所有事项的截止日期不能为空,截止日期未填写的情况下系统会自动赋值【协同】全新的事项详情页交互上线【制品】项目制品能力加强,新增一个项目制品适配多种部署场景的特性【制品】项目制品详情信息拓展,支持查看该制品依赖的 addon 信息【制品】新增制品的标签管理【流水线】流水线支持项目规范实践的分类管理(目前平台内置的分类配置,后续会开放自定义配置的功能),代码仓库中发现流水线文件后,支持用户一键创建【流水线】项目级流水线配置能力的加强,新增流水线参数配置管理【部署中心】项目制品中支持 job 类型【部署中心】新增 addon 根据环境中是否有无进行自动新增或者复用的适配,降低 addon 的维护成本【微服务观测治理】自定义大盘通过导入导出的方式,支持跨环境的迁移【稳定性】Dice.yml 文件中支持容器 emptyDir 和 临时存储 可用容量的配置优化改善【协同】迭代详情页支持快速切换其他迭代,方便用户在迭代间快速切换【协同】协同筛选条件归类排列优化,筛选器的右侧统一放人和日期,左侧按常用字段进行排序放置【协同】移除里程碑内容【制品】增强项目制品创建页面中应用筛选维度和功能【流水线】流水线详情页面优化,点击进入后直接展示最新一次执行的记录【流水线】pipeline 周期调度默认补偿机制策略优化-根据终态停止【流水线】项目流水线页面性能优化【个人工作台】页面加载性能的提升【微服务观测治理】从拓扑图中的错误调用到链路、日志的整体链路贯穿打通Erda V2.1 Changelog:https://github.com/erda-project/erda/releases/tag/v2.1.0更多内容欢迎关注【尔达Erda】公众号
背景通常而言,集群的稳定性决定了一个平台的服务质量以及对外口碑,当一个平台管理了相当规模数量的 Kubernetes 集群之后,在稳定性这件事上也许会“稍显被动”。我们可能经常会遇到这样的场景:客户一个电话,火急火燎地说业务出现问题了,你们平台快帮忙查询一下是不是哪里出了问题呀?技术同学连忙放下手头工作,上去一通操作加安抚客户……看似专业且厉害,急用户之所急,细想之后实则无章无法,一地鸡毛。通常我们依赖监控系统来提前发现问题,但是监控数据作为一个正向链路,很难覆盖到所有场景,经常会有因为集群配置的不一致性或者一些更底层资源的异常,即使监控数据完全正常,但是整个系统依然会有一些功能不可用。对此,我们做了一套巡检系统,针对系统中一些薄弱点以及一致性做诊断,但是这套系统的扩展性不是很好,对集群跟巡检项的管理也相对粗暴了一点。最后我们决定做一个更加云原生的诊断工具,使用 operator 实现集群跟诊断项的管理,抽象出集群跟诊断项的资源概念,以此来解决大规模 Kubernetes 集群的诊断问题,通过在中心下发诊断项到其他集群,并统一收集其他集群的诊断结果,实现任何时刻都可以从中心获取到其他所有集群的运行状态,做到对大规模 Kubernetes 集群的有效管理以及诊断。Talk is cheap, show me the demo:DemoKubeprober项目介绍项目地址: https://github.com/erda-project/kubeprober官网地址: https://k.erda.cloudKubeprober 是一个针对大规模 Kubernetes 集群设计的诊断工具,用于在 Kubernetes 集群中执行诊断项以证明集群的各项功能是否正常,Kubeprober 有如下特点:支持大规模集群支持多集群管理,支持在管理端配置集群跟诊断项的关系以及统一查看所有集群的诊断结果;云原生核心逻辑采用 operator 来实现,提供完整的 Kubernetes API 兼容性;可扩展支持用户自定义巡检项。其核心架构如下:区别于监控系统,Kubeprober 从巡检的角度来验证集群的各项功能是否正常,监控作为正向链路,无法覆盖系统中的所有场景,即使系统中各个环境的监控数据都正常,也无法保证系统是 100% 可用的,因此我们就需要一个工具从反向来证明系统的可用性,根本上做到先于用户发现集群中不可用的点,比如:集群中的所有节点是否均可以被调度,有没有特殊的污点存在等;pod 是否可以正常的创建,销毁,验证从 Kubernetes,Kubelet 到 Docker 的整条链路;创建一个 service,并测试连通性,验证 kube-proxy 的链路是否正常;解析一个内部或者外部的域名,验证 CoreDNS 是否正常工作;访问一个 ingress 域名,验证集群中的 ingress 组件是否正常工作;创建并删除一个 namespace,验证相关的 webhook 是否正常工作;对 Etcd 执行 put/get/delete 等操作,用于验证 Etcd 是否正常运行;通过 mysql-client 的操作来验证 MySQL 是否正常运行;模拟用户对业务系统进行登录,操作,验证业务的主流程是否正常;检查各个环境的证书是否过期;云资源的到期检查;……组件介绍Kubeprober 整体采用 Operator 来实现核心逻辑,集群之间的管理使用 remotedialer 来维持被纳管集群跟管理集群之间的心跳链接,被纳管集群通过 RBAC 赋予 probe-agent 最小所需权限并且通过心跳链接实时上报被纳管集群元信息以及访问 apiserver 的 token,实现在管理集群可以对被管理集群的相关资源进行操作的功能。probe-master运行在管理集群上的 operator 维护着两个 CRD:一个是 Cluster,用于管理被纳管的集群;另一个是 Probe,用于管理内置的以及用户自己编写的诊断项。probe-master 通过 watch 这两个 CRD,将最新的诊断配置推送到被纳管的集群,同时 probe-master 提供接口用于查看被纳管集群的诊断结果。probe-agent运行在被纳管集群上的 operator,这个 operator 维护两个 CRD:一个是跟 probe-master 完全一致的 Probe,probe-agent 按照 probe 的定义去执行该集群的诊断项;另一个是 ProbeStatus,用于记录每个 Probe 的诊断结果,用户可以在被纳管的集群中通过 kubectl get probestatus 来查看本集群的诊断结果。什么是 ProbeKubeprobe 中运行的诊断计划我们称之为 Probe,一个 Probe 为一个诊断项的集合,我们建议将统一场景下的诊断项作为一个 Probe 来运行,probe-agent 组件会 watch probe 资源,执行 Probe 中定义的诊断项,并且将结果写在 ProbeStatus 的资源中。我们期望有一个输出可以清晰地看到当前集群的运行状态,因此我们建议所有的 Probe 都尽可能属于应用、中间件、Kubernetes 以及基础设置这四大场景,这样我们可以在展示状态的时候,清晰且自上而下地查看究竟是系统中哪个层面引起的问题。目前的 Probe 还比较少,我们还在继续完善,也希望跟大家一起共建。欢迎广大爱好者一起来共建:自定义 Probe对比其他诊断工具目前社区已经有 Kuberhealthy 以及 Kubeeye 来做 Kubernetes 集群诊断这件事情。Kuberheathy 提供一套比较清晰的框架可以让你轻松编写自己的诊断项,将诊断项 CRD 化,可以轻松地使用Kubernetes 的方式来对单个 Kubernetes 进行体检。Kubeeye 同样是针对单个集群,主要通过调用 Kubernetes 的 event api 以及 Node-Problem-Detector 来检测集群控制平面以及各种节点问题,同时也支持自定义诊断项。其实,Kubeprober 做的也是诊断 Kubernetes 集群这件事情,提供框架来编写自己的诊断项。除此之外,Kubeprober 主要解决了大规模 Kubernetes 集群的诊断问题,通过中心化的思路,将集群跟诊断项抽象成 CRD,可以实现在中心 Kubernetes 集群管理其他 Kubernetes 诊断项配置,诊断结果收集,未来也会解决大规模 Kubernetes 集群的运维问题。如何使用Kubeprober 主要解决大规模 Kubernetes 集群的诊断问题,通常我们选择其中一个集群作为 master 集群,部署probe-master,其他集群作为被纳管集群,部署 probe-agent,详细的使用说明可参考官方文档。可视化Kubeprober 在多集群中根据 probe 的策略执行诊断项,会产生大量的诊断事件。由此,对这些诊断项进行可视化的展示就显得尤为重要,此时如果有一个全局的 dashboard 对大规模集群的海量诊断项进行统一查看分析,将会更有利于我们掌握这些集群的运行状态。Kubeprober 支持将诊断项事件写入 influxdb,通过 grafana 配置图表来统一展示诊断结果,比如:我们将 ERROR 事件统一展示出来作为最高优先级进行关注。同时,我们也可以通过扩展 probe-agent 上报的集群信息,展示一张详尽的集群列表:结语随着数字化的逐渐发展,企业的 IT 架构也变得越来越复杂,如何在复杂环境中保证业务连续性及稳定性?相信这是每一个 IT 从业者都会面临的问题,如果大家对稳定性的话题或者是对 Kuberprober 项目感兴趣,欢迎联系我们一起深入探讨,同时也欢迎广大开源爱好者一起参与,共同打造一个大规模的 Kubernetes 集群的管理神器。Contributing to Kubeprober我们致力于决社区用户在实际生产环境中反馈的问题和需求,如果您有任何疑问或建议,欢迎关注【尔达Erda】公众号给我们留言,加入 Erda 用户群参与交流或在 Github 上与我们讨论!
目前,市面上的流水线/工作流产品层出不穷,有没有一款工作流引擎,能够同时满足:支持各种任务运行时,包括 K8s Job、K8s Flink、K8s Spark、DC/OS Job、Docker、InMemory 等?支持快速对接其他任务运行时?支持任务逻辑抽象,并且快速地开发自己的 Action?支持嵌套流水线,在流水线层面进行逻辑复用?支持灵活的上下文参数传递,有好用的 UI 以及简单明确的工作流定义?······那么,不妨试试 Erda Pipeline 吧~Erda Pipeline 是一款自研、用 Go 编写的工作流引擎。作为基础服务,它在 Erda 内部支撑了许多产品:CI/CD快数据平台自动化测试平台SRE 运维链路……Erda Pipeline 之所以选择自研,其中最重要的三点是:自研能更快地响应业务需求,进行定制化开发;时至今日,开源社区还没有一个实质上的流水线标准,各种产品百花齐放;K8s、DC/OS 等的 Job 实现都偏弱、上下文传递缺失,无法满足我们的需求,更不用说灵活好用的 Flow 了,例如:嵌套流水线。本文我们将主要从架构层面对 Pipeline 进行剖析,和大家一起来深入探索 Pipeline 的架构设计。主要内容包括以下几个部分:整体架构内部架构水平扩展分布式架构功能特性实现细节整体架构Pipeline 支持灵活的使用方式,目前支持 UI 可视化操作、OPENAPI 开放接口、CLI 命令行工具几种方式。协议层面,在 Erda-Infra 微服务框架的加持下,以 HTTP 和 gRPC 形式对外提供服务:在早期的时候,我们只提供了 HTTP 服务,由于 Erda 平台本身内部是微服务架构,服务间调用就需要手动编写 HTTP 客户端,不好自动生成,麻烦且容易出错。后来我们改为使用 Protobuf 作为 IDL(Interface Define Language),在 Erda-Infra 中自动生成 gRPC 的客户端调用代码和服务端框架代码,内部服务间的调用都改为使用 gRPC 调用。在中间件依赖层面,我们使用 ETCD 做分布式协调,用 MySQL 做数据持久化。ETCD 我们也有计划把它替换掉,使用 MySQL 来做分布式协调。最关键的任务运行时(Task Runtime)层面,我们支持任务可以运行在 K8s、DC/OS(分布式云 OS,在 2017-2019 年非常火)、用户本地 Docker 环境等。我们还有开放的任务扩展市场,在平台级别内置了非常多的流水线任务扩展,开箱即用。同时,用户也可以开发企业/项目/应用/个人级别的任务扩展,这部分功能在代码层面已经完全支持,产品层面正在开发中,在后续迭代中很快就可以和大家见面。内部架构使用 Erda-Infra 微服务框架开发,功能模块划分清晰:Server 层包括业务逻辑,对 Pipeline 来说业务就是创建、执行、重试、重试失败节点等。Modules 层提供不含业务逻辑的公共模块,其余两层均可调用,包括预校验机制、定时守护进程、YAML 解析器、配置管理、事件管理等。Erda Pipeline 一直在践行:“把复杂留给自己,把简单留给别人”。在 Pipeline 中,我们对一个任务执行的抽象是 ActionExecutor。Engine 层负责流水线的推进,包括:Queue Manager 队列管理器,支持队列内工作流的优先级动态调整、资源检查、依赖检查等Dispatcher 任务分发器,用于将满足出队条件的流水线分发给合适的 Worker 进行推进Reconciler 协调器,负责将一条完整的流水线解析为 DAG 结构后进行推进,直至终态。模块内部使用插件机制,对接各种任务运行时。AOP 扩展点机制(借鉴 Spring),把代码关键节点进行暴露,方便开发同学在不修改核心代码的前提下定制流水线行为。目前已经有的一些扩展点插件,譬如自动化测试报告嵌套生成、队列弹出前检查、接口测试自动登录保持等。这个能力后续我们还会开放给调用方,包括用户,去做一些有意思的事情。统一使用 Event,封装了 WebHook / WebSocket / Metrics。水平扩展随着 Pipeline 需要支撑的业务场景和数据量与日俱增,单实例的 Pipeline 稳定性和推进性能面临着挑战。Pipeline 的多实例方案如下:Leader & Worker 模式,两者在部署上不区分状态,仅为 Replicas 多实例:使用 ETCD 选举,每个实例都可以是 Leader。只有 Leader 开启 Queue Manager 和 Dispatcher,分发任务给 Worker。Leader 本身也可以作为 Worker,支持单节点部署模式,Leader 必须开启 Reconciler。Dispatch 使用有界负载的一致性哈希算法:使用一致性哈希来分配任务。但一致性哈希会超载,例如某些热点内容会持续打到一个节点上。有界负载算法(The bounded-load algorithm):只要服务器未过载,请求的分配策略与一致性哈希相同。过载服务器的溢出负载将在可用服务器之间分配。Pipeline 实例增减时,已经被分配的流水线不重新分配,尽可能减少切换成本,防止重复推进;新增的流水线使用一致性 Hash 算法进行分配。在之前,我们使用过分布式锁的方案来做多实例协调。和选举比起来,竞争会更大,在具体实现里,如果想要多实例同时推进流水线,那竞争的资源就是流水线 ID。每个 ID 会请求一个分布式锁,而且这个分布式锁是阻塞性的,保证每个流水线 ID 一定会被推进,天然做了多节点重试。分布式架构该分布式架构是典型的 AP 模型,数据层面遵循最终一致性。这套分布式架构的核心目标(典型场景)是在网络分区的情况下,保证边缘集群的定时任务正常执行。我们来对比一下原有部署架构(运行时以 K8s 为例):中心 Pipeline 直接负责流程推进,调用边缘 K8s 创建 Job。当网络分区时,原有部署架构下,定时任务无法正常执行。分布式架构:中心下发任务定义,由 Edge Pipeline 负责推进,直连 K8s,更加稳定。在网络分区恢复时,主动上报执行数据,实现数据最终一致性。在代码层面,我们使用同一份代码构建出同一个镜像,通过配置(不同的部署模式)使得各个实例各司其职。另外,在数据同步时,我们遇到了前端 JavaScript Number 类型 53位最大值问题,对 SnowFlake ID 进行了 64bit -> 53bit 的改造,在保证唯一性的前提下,尽可能地增加可用性(生命周期约为 10 年,同时支持 4096 个分布式节点,每 10ms 可生成 64 个 ID)。功能特性这里简单列举一些比较常见的功能特性:配置即代码扩展市场丰富可视化编辑支持嵌套流水线灵活的执行策略,支持 OnPush / OnMerge 等触发策略支持工作流优先队列多维度的重试机制定时流水线及定时补偿功能动态配置,支持“值”和“文件”两种类型,均支持加密存储,确保数据安全性上下文传递,后置任务可以引用前置任务的“值”和“文件”开放的 OpenAPI 接口,方便第三方系统快速接入一些实现细节如何实现上下文传递(值引用)在一条流水线中,节点间除了有依赖顺序之外,一定会有数据传递的需求。值引用:每个节点的特殊输出(按格式写入指定文件或者打印到标准输出)会被保存在 Pipeline 数据库中;后续节点通过 outputs 语法声明的表达式会在节点开始执行前被替换为真正的值。举例:如图所示:第一个节点 repo 拉取代码;第二个节点 build erda 则是构建 Erda 项目。在例子中,第二步构建时同时用到了 “值引用” 和 “文件引用” 两种引用类型,是进依次入代码仓库,指定 GIT_COMMIT 进行构建。如何实现上下文传递(文件引用)文件引用比值引用复杂,因为文件的数据量比值大得多,不能存储在数据库中,而是存储在卷中。这里又根据是否使用共享存储而分为两种情况,两者的区别在于申请的卷的类型和个数。对于流水线使用者而言,没有任何区别。使用共享存储不使用共享存储如何实现缓存加速在许多流水线场景中,同一条流水线的多次执行之间是有关联的。如果能够用到上一次的执行结果,则可以大幅缩短执行时间。典型场景是 CI/CD 构建,我们以 Java 应用 Maven 构建举例:不但同一条流水线不同的多次执行可以复用 ${HOME}/.m2 目录(缓存目录),甚至同一个应用下的多个分支之间都可以使用同一个缓存目录,就像本地构建一样~举例:仍然使用前面的例子,在第二步 build erda 里加上 cache 即可。Action Agent在 Pipeline 里,每个节点都会对应一个 Action 类型,并且在扩展市场中都可以找到。为了更加方便 Action 开发者进行开发,我们提供了很多便捷的机制。如:对敏感日志进行脱敏处理,保证数据安全无感知的错误分析和数据上报文件变动监听及实时上报……上述所有机制都是由 Action Agent 程序完成的,它是一个静态编译的 Go 程序,可以运行在任意 Action 镜像中。Agent 完整的执行链路如下:Action Executor 扩展机制Pipeline 之所以好用,是因为它提供了灵活一致的流程编排能力,并且可以很方便地对接其他单任务执行平台,这个平台本身不需要有流程编排的能力。在 Pipeline 中,我们对一个任务执行的抽象是 ActionExecutor。一个执行器只要实现单个任务的创建、启动、更新、状态查询、删除等基础方法,就可以注册成为一个 ActionExecutor:恰当的任务执行器抽象,使得 Batch/Streaming/InMemory Job 的配置方式和使用方式完全一致,流批一体,对使用者屏蔽底层细节,做到无感知切换。在同一条流水线中,可以混用各种 ActionExecutor。调度时,根据任务类型智能调度到对应的任务执行器上,包括 K8sJob、Metronome Job、Flink Job、Spark Job 等等。Go 接口定义AOP 扩展点机制AOP 扩展点机制是借鉴 Java 里 Spring 的概念应运而生的。我们把代码关键节点进行暴露,方便开发同学在不修改核心代码的前提下定制流水线行为。AOP 扩展点机制已经使用 Erda Infra 的模块化思想重构,整个扩展点的插件开发和编排更为灵活,如下图所示:AOP 在 Pipeline 内部的使用这个能力后续我们还会开放给用户,让用户可以在 pipeline.yaml 中使用编程语言声明和编排扩展点插件,更灵活地控制流水线行为。我们致力于决社区用户在实际生产环境中反馈的问题和需求,如果您有任何疑问或建议,欢迎关注【尔达Erda】公众号给我们留言,加入 Erda 用户群参与交流或在 Github 上与我们讨论!
某个周一上午,小涛像往常一样泡上一杯热咖啡 ☕️,准备打开项目协同开始新一天的工作,突然隔壁的小文喊道:“快看,用户支持群里炸锅了 …”用户 A:“Git 服务有点问题,代码提交失败了!”用户 B:“帮忙看一下,执行流水线报错……”用户 C:“我们的系统今天要上线,现在部署页面都打不开了,都要急坏了!”用户 D:……小涛只得先放下手中的咖啡,屏幕切换到堡垒机,登录到服务器上一套行云流水的操作,“哦,原来是上周末上线的代码漏了一个参数验证造成 panic 了”,小涛指着屏幕上一段容器的日志对小文说到。十分钟后,小文使用修复后的安装包更新了线上的系统,用户的问题也得到了解决。虽然故障修复了,但是小涛也陷入了沉思,“为什么我们没有在用户之前感知到系统的异常呢?现在排查问题还需要登录到堡垒机上看容器的日志,有没有更快捷的方式和更短的时间里排查到线上故障发生的原因?”这时,坐在对面的小 L 说道:“我们都在给用户讲帮助他们实现系统的可观测性,是时候 Erda 也需要被观测了。”小涛:“那要怎么做呢…?”且听我们娓娓道来~﹀通常情况下,我们会搭建独立的分布式追踪、监控和日志系统来协助开发团队解决微服务系统中的诊断和观测问题。但同时 Erda 本身也提供了功能齐全的服务观测能力,而且在社区也有一些追踪系统(比如 Apache SkyWalking 和 Jaeger)都提供了自身的可观测性,给我们提供了使用平台能力观测自身的另一种思路。最终,我们选择了在 Erda 平台上实现 Erda 自身的可观测,使用该方案的考虑如下:平台已经提供了服务观测能力,再引入外部平台造成重复建设,对平台使用的资源成本也有增加开发团队日常使用自己的平台来排查故障和性能问题,吃自己的狗粮对产品的提升也有一定的帮助对于可观测性系统的核心组件比如 Kafka 和 数据计算组件,我们通过 SRE 团队的巡检工具来旁路覆盖,并在出问题时触发报警消息Erda 微服务观测平台提供了 APM、用户体验监控、链路追踪、日志分析等不同视角的观测和诊断工具,本着物尽其用的原则,我们也把 Erda 产生的不同观测数据分别进行了处理,具体的实现细节且继续往下看。OpenTelemetry 数据接入在之前的文章里我们介绍了如何在 Erda 上接入 Jaeger Trace ,首先我们想到的也是使用 Jaeger Go SDK 作为链路追踪的实现,但 Jaeger 作为主要实现的 OpenTracing 已经停止维护,因此我们把目光放到了新一代的可观测性标准 OpenTelemetry 上面。OpenTelemetry 是 CNCF 的一个可观测性项目,由 OpenTracing 和 OpenCensus 合并而来,旨在提供可观测性领域的标准化方案,解决观测数据的数据模型、采集、处理、导出等的标准化问题,提供与三方 vendor 无关的服务。如下图所示,在 Erda 可观测性平台接入 OpenTelemetry 的 Trace 数据,我们需求在 gateway 组件实现 otlp 协议的 receiver,并且在数据消费端实现一个新的 span analysis组件把 otlp 的数据分析为 Erda APM 的可观测性数据模型。OpenTelemetry 数据接入和处理流程其中,gateway 组件使用 Golang 轻量级实现,核心的逻辑是解析 otlp 的 proto 数据,并且添加对租户数据的鉴权和限流。关键代码参考 receivers/opentelemetryspan_analysis 组件基于 Flink 实现,通过 DynamicGap 时间窗口,把 opentelemetry 的 span 数据聚合分析后产生如下的 Metrics:service_node 描述服务的节点和实例service_call_* 描述服务和接口的调用指标,包括 HTTP、RPC、DB 和 Cacheservice_call_*_error 描述服务的异常调用,包括 HTTP、RPC、DB 和 Cacheservice_relation 描述服务之间的调用关系同时 span_analysis 也会把 otlp 的 span 转换为 Erda 的 span 标准模型,将上面的 metrics 和转换后的 span 数据流转到 kafka ,再被 Erda 可观测性平台的现有数据消费组件消费和存储。关键代码参考 analyzer/tracing通过上面的方式,我们就完成了 Erda 对 OpenTelemetry Trace 数据的接入和处理。接下来,我们再来看一下 Erda 自身的服务是如何对接 OpenTelemetry。Golang 无侵入的调用拦截Erda 作为一款云原生 PaaS 平台,也理所当然的使用云原生领域最流行的 Golang 进行开发实现,但在 Erda 早期的时候,我们并没有在任何平台的逻辑中预置追踪的埋点。所以即使在 OpenTelemetry 提供了开箱即用的 Go SDK 的情况下,我们只在核心逻辑中进行手动的 Span 接入都是一个需要投入巨大成本的工作。在我之前的 Java 和 .NET Core 项目经验中,都会使用 AOP 的方式来实现性能和调用链路埋点这类非业务相关的逻辑。虽然 Golang 语言并没有提供类似 Java Agent 的机制允许我们在程序运行中修改代码逻辑,但我们仍从 monkey 项目中受到了启发,并在对 monkey 、pinpoint-apm/go-aop-agent 和 gohook 进行充分的对比和测试后,我们选择了使用 gohook 作为 Erda 的 AOP 实现思路,最终在 erda-infra 中提供了自动追踪埋点的实现。关于 monkey 的原理可以参考 monkey-patching-in-go以 http-server 的自动追踪为例,我们的核心实现如下://go:linkname serverHandler net/http.serverHandler type serverHandler struct { srv *http.Server } //go:linkname serveHTTP net/http.serverHandler.ServeHTTP //go:noinline func serveHTTP(s *serverHandler, rw http.ResponseWriter, req *http.Request) //go:noinline func originalServeHTTP(s *serverHandler, rw http.ResponseWriter, req *http.Request) {} var tracedServerHandler = otelhttp.NewHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { injectcontext.SetContext(r.Context()) defer injectcontext.ClearContext() s := getServerHandler(r.Context()) originalServeHTTP(s, rw, r) }), "", otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string { u := *r.URL u.RawQuery = "" u.ForceQuery = false return r.Method + " " + u.String() })) type _serverHandlerKey int8 const serverHandlerKey _serverHandlerKey = 0 func withServerHandler(ctx context.Context, s *serverHandler) context.Context { return context.WithValue(ctx, serverHandlerKey, s) } func getServerHandler(ctx context.Context) *serverHandler { return ctx.Value(serverHandlerKey).(*serverHandler) } //go:noinline func wrappedHTTPHandler(s *serverHandler, rw http.ResponseWriter, req *http.Request) { req = req.WithContext(withServerHandler(req.Context(), s)) tracedServerHandler.ServeHTTP(rw, req) } func init() { hook.Hook(serveHTTP, wrappedHTTPHandler, originalServeHTTP) }在解决了 Golang 的自动埋点后,我们还遇到的一个棘手问题是在异步的场景中,因为上下文的切换导致 TraceContext 无法传递到下一个 Goroutine 中。同样在参考了 Java 的 Future 和 C# 的 Task 两种异步编程模型后,我们也实现了自动传递 Trace 上下文的异步 API:future1 := parallel.Go(ctx, func(ctx context.Context) (interface{}, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://www.baidu.com/api_1", nil) if err != nil { return nil, err } resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() byts, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } return string(byts), nil }) future2 := parallel.Go(ctx, func(ctx context.Context) (interface{}, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://www.baidu.com/api_2", nil) if err != nil { return nil, err } resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() byts, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } return string(byts), nil }, parallel.WithTimeout(10*time.Second)) body1, err := future1.Get() if err != nil { return nil, err } body2, err := future2.Get() if err != nil { return nil, err } return &pb.HelloResponse{ Success: true, Data: body1.(string) + body2.(string), }, nil写在最后在使用 OpenTelemetry 把 Erda 平台调用产生的 Trace 数据接入到 Erda 自身的 APM 中后,我们首先能得到的收益是可以直观的得到 Erda 的运行时拓扑:Erda 运行时拓扑通过该拓扑,我们能够看到 Erda 自身在架构设计上存在的诸多问题,比如服务的循环依赖、和存在离群服务等。根据自身的观测数据,我们也可以在每个版本迭代中逐步去优化 Erda 的调用架构。对于我们隔壁的 SRE 团队,也可以根据 Erda APM 自动分析的调用异常产生的告警消息,能够第一时间知道平台的异常状态:最后,对于我们的开发团队,基于观测数据,能够很容易地洞察到平台的慢调用,以及根据 Trace 分析故障和性能瓶颈:小 L:“除了上面这些,我们还可以把平台的日志、页面访问速度等都使用类似的思路接入到 Erda 的可观测性平台。”小涛恍然大悟道:“我知道了,原来套娃观测还可以这么玩!以后就可以放心地喝着咖啡做自己的工作了。”我们致力于决社区用户在实际生产环境中反馈的问题和需求,如果您有任何疑问或建议,欢迎关注【尔达Erda】公众号给我们留言,加入 Erda 用户群参与交流或在 Github 上与我们讨论!
引言:伴随着基础设施技术升级,应用研发环境也从最初的传统 IT 架构、虚拟化 & 容器化架构演变到现在的云原生多云架构。“应用研发新模式”本身就是一个比较大的话题,我们也不敢说一个人或者一个团队就能把这个话题聊透彻。但随着应用研发基础架构环境的演进,应用研发模式一定是在不断地调整和创新。今天我们大胆把话题抛出来,聊聊自己的一些想法,和大家一起探讨、共创云原生时代应用研发模式后续的演进路线。DevOps 平台现状和痛点(应用研发架构演进路线)本文暂将应用研发架构的演进路线归纳为四个阶段(如上图),传统 IT 架构和虚拟架构下的应用研发,相信大家都比较熟悉,DevOps 的概念在这两个阶段几乎没有激起什么水花,所以这两个阶段我们就不展开阐述了。伴随容器技术的出现(特别是 Docker 和 Kubernetes,后续简称 K8s),让 DevOps 概念火了一把,也在实践中开始快速落地和普及。容器能够封装微服务整个运行时环境的特性,天然就适用于微服务构建、发布和运行,让原本缓慢前进的 DevOps 得到飞速发展,开源社区也涌现了很多优秀的开源产品(比如 Jenkins、GitLab 等),大家通过这些开源产品能够快速搭建自己应用的持续集成环境,因此市场上也如雨后春笋般冒出许多 DevOps 相关的产品。现阶段中的 DevOps 产品通过 Docker 和 K8s 确实帮助用户解决了资源管理、微服务环境构建和持续集成的复杂、效率低等问题,但是伴随公有云等 Infra 基础设施持续高速发展,人们对于应用研发的效率追求也会有更高的要求,对于 DevOps 产品也不会满足停留在当前阶段(微服务应用的持续构建部署),那么如何在 DevOps 现阶段的版本基础上进一步提高研发效率和质量呢?我们还是一起通过梳理当前研发过程中面临的痛点出发吧!痛点 1:多云资源如何统一管理,解绑云厂商?在公有云、私有云等多元化的云环境下,大家手头往往都有两套或者多套云资源,如何让这些割裂的云资源统一进行管理?如何基于一个平台让应用快速进行跨云迁移、发布?比如:开发在私有云,生产在公有云等这些问题伴随资源环境多元化问题会越来越突出。痛点 2:复杂微服务组合如何快速进行环境构建、持续集成?当前 DevOps 对于单个微服务的环境构建和持续集成问题已经基本解决。但对于企业级软件研发交付团队来说,错综复杂的微服务组合而成的项目如何进行统一的环境构建、部署和交付,目前仍解决得不太彻底,只能让各应用的研发成员都参与到构建、部署的整个阶段。以上复杂的过程容易引起问题不说,效率成本上也是个大问题。痛点 3:研发效能如何进一步提升?在当前主流的 DevOps 产品中,代码、构建、部署全流程自动化触发执行的特性基本都是得到了比较好的解决,但是随着研发管理的深度、精细度要求越来越高,需要研发同学维护的数据也随之不断增多,管理维护项目数据的项目管理工作量也在不断增大,效率和成本这组矛盾如何得到妥善处理?新一代 DevOps 不仅应有效解决上述痛点,还应具有测试安全的左移、研发效能度量等更多开放性能力,这个全新的时代需要有更全面、更创新的特性。新一代 DevOps 的特性多云管理多云管理并不是简单指通过 K8s 集群来实现资源的调度管理,如果仅仅是统一资源调度那本身是 K8s 集群的特性。通过应用部署环境配置去关联集群,确实可以实现环境之间的隔离、环境之间快速迁移的能力,就如上面提到的:开发测试在本地私有云环境,生产可以通过同一套代码能够快速发布到公有云;还有就是业务在一个集群,数据处理可以在另外一个集群,实现业务和数据分离,两者互不影响,这些都可以通过集群管理来实现。既然说了这么多都是和集群相关的,那么和多云管理有什么关系呢?首先,我们对 K8s 集群的节点管理,希望可以一个平台上统一便捷购买/释放主流公有云厂商的资源节点。其次,现在公有云上都有容器相关的服务提供,平台只需要调度管理这些容器服务即可,所有容器服务的对接管理(包含但不仅限于容器服务的购买、释放、扩缩容等)都需要在平台完成。再者,公有云上其他的云服务都可以通过平台进行购买直接使用,无需用户不断切换登陆到各公有云的控制台,最后进行云资源的统计分析、资源成本的运营分析,帮助企业在资源方面进行降本增效。这些都是多云管理的范畴之内。Erda 目前在 K8s 集群管理、公有云服务对接管理方面都是做得比较好的,大家可以体验使用,对这部分内容感兴趣的同学欢迎联系我们,一起沟通、碰撞想法。项目级持续集成目前,对于单应用的持续构建部署,DevOps 的业界产品基本都是达成共识的,对于单个微服务应用构建部署的自动化完善程度较好。但是对于企业级项目研发过程,我们一起来回想看看,比如:单应用内不同任务需要拉多分支来进行开发(基于主干开发的模式可能没有这个问题),受开发环境资源的限制,不同任务开发同学要不断进行线下沟通合并代码发布开发环境(或者测试环境)进行测试,过程中可能存在谁的代码分支有问题导致整个环境不可用的现象,oh my god!项目级的联调部署就更复杂了,首先需要配置项目环境,其中包含了项目级的参数配置以及大家公用的项目级中间件准备部署;其次是复杂的微服务编排信息维护,这些繁琐的项目级维护管理动作,往往会导致项目部署过程中出现各种阻塞,比如项目共同的中间件准备阻塞,上游服务的部署和健康度也会影响或阻塞下游服务部署和测试等,这些问题会让项目部署更加困难化。既然已经分析出问题的所在,那么我们一起来看下 Erda 的解决之道。Erda 坚持以应用为中心,在单应用的研发过程中,基于任务分支开发的模式下(这里说明一下,Erda 产品本身的研发团队是基于主干开发来实现每日集成的),研发同学只要保证自己分支质量的基础上,随时可以发布到开发环境进行测试和验证。那么细心的同学肯定会发现如果开发环境只有一套,相同应用下有其他研发任务分支的同学该怎么办?这里就先透露一下 Erda 接下来即将规划的功能:大家只管发自己的分支部署,分支之间的临时合并后部署的事情,就交给 Erda 平台来完成吧。项目级构建部署的问题,Erda 通过项目级流水线、制品、环境部署(其实是环境准备和部署两个环节)三个重要特性来解决的。首先,在应用通过代码持续构建部署到开发/测试环境,实现了代码到服务的全流程自动化。其次,项目中共用的中间件进行了统一管理。这里的中间件不仅覆盖了平台内置丰富的中间件服务,还提供了公有云上的存储类中间件,甚至还支持通过自定义中间件的方式来管理对接用户自己部署维护的中间件。这些中间件在 Dice.yml 中可以一键部署,实现真正项目级的中间件管理,彻底解决用户依赖中间件的烦恼。前面提到了代码到服务的全流程自动化,其实制品到服务也是这个全流程中的一个环节。应用研发同学在开发环境冒烟测试通过后,测试同学可以基于应用制品可以组合成项目制品,通过项目制品 + 环境配置,可以快速在测试环境上部署测试(基于主干开发的话,这些步骤都是可以全自动化完成),测试同学通过自动化测试和手动测试确保项目质量达标后,就可以一键发布项目制品,后期项目交付实施同学可以基于此制品到客户环境上进行快速交付或升级应用。研发流程的自动化上述的代码到服务、制品到服务的全流程当然是在研发全流程自动化中进行的。除此之外,在优雅解决研发效能度量数据采集的同时,还要让我们的研发同学尽量少做一些维护工作,比如:需求任务拆解后,任务的状态、备注是否能够自动流转,不需要刻意地登陆到平台去进行维护就能完成。研发同学在进行沉浸式本地开发的同时,就能完成相关工作。这个也是 Erda 当前重点在解决的事情,研发角色在统一平台进行高效协同的认知角度可能也会适当调整一下。不同研发角色的协同数据在统一平台,具体的协同可以由多元化的端能力来完成,让用户在不感知平台的情况下,无时无刻不在使用平台的能力,比如:通过 ChatOps,让信息及时触达到用户后,用户可以在实时通讯 APP 中快速处理,处理后的数据会返回平台进行统一计算分析,伴随用户在实时通讯 APP 的处理,相关 Issue 事项的备注、截止日期、状态等属性信息也在同步进行自动化流转处理。视频内容见文章内 Erda 产品全景图研发流程自动化还可以体现在 Issue、代码、测试、环境实例之前的全方位自动化,通过 Issue 和代码分支的绑定,研发同学本地提交/合并代码后就会自动触发相关 Issue 状态流转、代码自动化构建部署、自动化测试、项目制品合成、制品 Changelog 的自动生成等全链路的自动化。以上仅仅罗列了新一代 DevOps 的部分特性,对于其他特性,都应该围绕效率、成本和质量这个铁三角展开,去创造更多有意义、有价值的新特性。本文只发表了一些个人对于 DevOps 演进的部分观点,相信其他小伙伴肯定有更好的的想法,如果大家对于本话题感兴趣,欢迎联系我们一起深入探讨。我们将定期组织小伙伴围绕相关话题进行持续讨论推进,期待更多小伙伴的加入!我们致力于决社区用户在实际生产环境中反馈的问题和需求,如果您有任何疑问或建议,欢迎关注【尔达Erda】公众号给我们留言,加入 Erda 用户群参与交流或在 Github 上与我们讨论!Erda Github 地址Erda Cloud 官网
早些时候 Erda Show 针对微服务监控、日志等内容做了专场分享,很多同学听完后意犹未尽,想了解更多关于日志分析的内容。Erda 团队做日志分析也有一段时间了,所以这次打算和大家详细分享一下我们在做的一些事情,希望对大家有所帮助。日志分析平台其实是 Erda 微服务治理子平台下面的一个功能模块,那么今天我将从三个方面来展开分享:日志分析平台出现的必要性;日志分析平台架构设计;Erda 目前是怎么做的、做了哪些工作以及未来的发展方向。日志分析平台的必要性“微服务”这一概念大概在 2013 年出现,从这一概念初期到现在,大部分应用的业务场景皆是分布式、容器化的部署架构,或者至少是多服务架构,每个服务基本上是非单点的,并且会做单服务多实例的高可用部署。在这种场景下,我们需要重点解决关于日志的几个问题。需要解决的问题1. 接口报错了,如何在应用的多个容器中快速的找到详细的异常日志?第一个要解决的问题是关于异常日志的定位效率问题。比如,前端在请求某个页面,接口报错了,随后反馈给开发人员,常规的接口报错通常不会直接给用户暴露特别详细的异常信息,只会有一个状态或是简要的错误概述,这时需要开发者通过日志找到具体的异常信息(比如堆栈等)。一般来说,通过接口路径我们可以定位是哪个服务的报错,但进一步讲,我们如何确定是这个服务下的哪个实例的报错呢?如果说我们采用这种比较原始的方式,可能需要开发者分别查看每个实例(容器)的日志,这样会直接对开发效率产生影响。2. 如何方便的查看已经宕机的应用容器的日志?另外一个需要解决的问题是日志存储的持久性问题。比如说在 K8s 平台下,某个应用服务的某个 pod 挂掉了被重新调度到了其他节点,或者本地存储的容器日志由于时间的滚动而滚动没了,这时如果我们想去回头看一下之前的日志,在机器上已经不太容易找到了。3. 如何能及时的发现某个应用容器发生了异常?前两个问题属于被动型的需求,也就是说前端业务上已经暴露出一些问题,然后我们去回溯、查找一些日志的详细记录的需求。因为是用户反馈,这个流程链上的故障处理时间是相对较长的,那么应该如何缩短故障处理时间?很自然我们会想到主动告警,在还没有大面积前端接口被大量用户发现前,后端出现异常时,系统能够更及时的进行告警,并通知相关人员及时处理,减少故障时间。这时我们就非常需要一个系统可以持续监听所有容器的日志,协助开发者发现其中的异常并主动告警。如果说没有日志分析平台,其实以上三个问题并不是不能解决,但是会极大程度的影响效率。那么假如有了这样的日志分析平台,由它来提供集中式查询、持久化存储以及主动告警等功能,我们可以快速且高效的解决这三个问题。日志分析平台能提供什么说到日志分析平台的必要性,我们务必要了解一下它能为我们提供什么样的服务,下面我们就来详细看一下:1. 集中式、一站式查询在查询方面,日志分析平台应该是集中式、一站式的查询,不再需要登录不同机器或者容器去低效地手动查看日志,而只需要在一个统一的页面上输入一些查询语句,就能轻松查询所有容器的日志了。2. 持久化,历史可追溯在存储方面,可以给日志分析平台配备有预期的专门存储配额,以便能够更好地应对宕机、升级、调度等导致日志跨节点的情况,保持查询历史日志时的简单性。3. 智能化,主动发现问题智能化告警通常也是一个必要功能,这里的智能化有两层意思:你可以主动配置一些规则,比如说根据代码或已经发生的一些异常日志,可以知道特定异常是什么样子,随后配一个规则,系统就会持续对输入的日志做一些规则检测,如果发现匹配项,就会进一步通过你提前配置的告警渠道,通知到具体的人;自动发现“异常”,其实有点类似目前的机器学习、深度学习,也就是说,即使你没有配置任何规则,但是系统可以通过对日志流的监听和学习,去发现异常的日志,然后通知你去关注,这是更智能化的一些东西。日志分析平台架构设计我们已经知道,日志分析平台可以给我们带来便利及效率的提升,那么如果我们想实现这样一个平台,需要如何进行架构设计呢?想要做架构设计,首先要了解业务场景和需求,然后结合被处理数据的特点,才能够推断平台架构设计应该具有哪些能力。之后我们再根据这些能力去寻找、设计相匹配的方案,并在这些方案中挑选真正可落地的去执行。数据特点1. 时序数据我们知道日志属于时序数据,只新增、不删除。它有几个字段比较关键:timestamp,tags,fieldstimestamp:时间字段对时序型数据是用来进行比较和关键的字段;tags:tags 代表一组字段,通常对于时序数据来讲,作为标签类型的字段一般都是可以搜索的,也就是这些字段需要建立索引,如:服务名、容器名、容器 IP 等;fields:fields 也代表一组字段,这些字段相对于 tags 的不同在于,fields 字段是通常存储那些不需要搜索的内容,比如:假如对于具体的日志内容你不打算去搜索,就可以用 fields 类型字段存储。日志时序数据的特点提示我们,可以考虑使用时序数据库来存储日志,比如 cassandra。2. 时效性强对于日志数据来讲,我们一般只关心一段时间的数据,对于很早之前的数据,比如一个月、两个月之前,甚至半年之前的数据,我们基本上是不会去关心的。因为一般有故障的时候,我们可能才需要去看一下具体的日志信息,而出现故障时不大可能会拖到很久之后才去解决和复盘这个问题。3. 数据量大数据量大有两个含义:一是说数据的单条日志可能比较大,比如像 Java 应用的一个异常堆栈,尤其那种 Caused by 嵌套了好几层的,可能单条日志就会有几百行;另外一个是说,日志的条数多,随着业务和应用的增多,加上某些应用还可能会开启 DEBUG 级别的日志,整体的日志量也会比较大,而且可能出现短时的峰值。以上是日志数据的特点,然后我们对从我们日志分析平台这个角度来看看,我们对系统有什么需求。业务需求特点1. 查询速度快,秒级响应首先,我们希望它能够快速查询,输入查询关键字,就能够秒级响应查询结果。2. 时间段范围查询通常,查询会按照一个明确的时间范围操作,这有一个好处:后端存储的选择会更多一些。3. 高基数值点查询什么是高基数值呢?像用户 IP、Trace ID 这类数据,几乎每个用户请求的值都不一样,这就属于高基数。关于这类数据的查询也是一个强需求,比如前端 web 接口报错,而响应里加了 Trace ID 这样的字段,此时就可以通过 Trace ID 字段去查看整个过程中记录的异常日志或关键日志,这也是一个比较常见的需求。4. 标签查询标签查询一般可以认为是对服务名、容器 IP 这样的字段查询需求,这也属于强需求之一。5. 全文检索查询全文检索查询是否属于强需求之一,其实是个值得权衡的问题。如果客户端在采集端已经做了一些预处理,如:把整行的日志 content 在采集时拆分成了具体的时间级别、异常类型等单个关键字段,这样来讲,全文检索查询可能就不是一个强需求了,但同时,备选方案的范围可能会更大一些。这里需要提醒的是,没有全文检索支持,并不代表不能模糊检索。借助列存储的高压缩率和高 IO 效率,在内存中进行模糊过滤的效果也很赞!6. 聚合统计聚合统计中最简单的是 Count 统计,更复杂一点的有基于更多字段维度的复杂聚合图表支持,这些功能在一些产品中也有提供,但需根据个体具体需求来判断该项是不是强需求。7. 主动告警主动告警意味着系统不仅具备被动查询功能,同时也能够及时发现问题并告警,这样才能减少故障时间。介绍完业务需求方面的 7 个特点后,在设计架构时,就能够助力我们迅速 get 到需要考虑哪些方面了。架构要求1. 软硬成本当然,成本是一定要的,不管是做什么设计,肯定需要考虑成本,这其中包括软硬的成本。硬件成本指的是我们的机器数量:CPU、内存、磁盘等这样的资源。这里面存在一个问题,因为对于日志而言,我们讲数据量大,单条的体积可能也比较大,如果确定不需要全文检索,或只检索其中很少的几个关键字段,对于那些较长的字段,仅仅只是想随着搜索条件把它展示出来,这个时候我们可能就会考虑,对于不需要索引的这些数据,是不是可以通过一些更廉价的存储方式(比如像 OSS)存下来,这样可以节省整体的存储成本。另一方面需要考虑软件成本,拿刚才 OSS 存储的例子,如果我们想用很高效的存储方式来存储索引的数据,而那些不需要查询的字段用 OSS 存储,这时的架构方案可能会稍微复杂一些,开发复杂度和难度,以及人力投入也会相对高一些,软件的整体成本也会相应增加。2. 存储要有过期机制数据的实效性对存储机制也提出了要求,对于数据的过期机制,需要考虑,如何保证和限制执行数据过期删除时的性能消耗不会对整个系统的吞吐有过大影响。3. 异步处理,吞吐要大,不能被业务流量打垮在数据量大的场景下,要求日志系统在接受采极端数据的时候,需要考虑异步处理等手段,保证不能被业务流量打垮。如果说业务系统有问题了,日志系统也因此出现问题,导致不能使用日志系统来查询、排查业务问题,那这个平台存在的意义就会受到挑战。一般来说我们会用 MQ 这样的中间件来做异步的削峰填谷处理。4. 即席查询能力(内存、缓存、并行、高效过滤等机制)基于对查询速度的要求,能够秒级响应的存储方案会被优先选择。5. 存储结构对时间范围查询友好基于时间段的范围查询是最高频的场景之一,针对此类场景,我们可以考虑选择时序数据库,因其本身对时间序列查询做了成本上的优化,同时也是效率较高的方案。6. 二级索引能力高基数单点查询对索引能力提出了要求,这将限制我们对于时序数据库的选择。因为像 promethus 这样的时序数据库对高基数值的单点查询是存在问题的,这是由于它的存储的特点决定的。一般来说,如果我们想支持高基数值的单点查询,需要需要有一个二级属性能力的数据库。7. 全文检索能力如何支持模糊查询,也是我们要考量的一个因素。因为如果要做完整的全文检索能力支持,比如:分词、相关性算分排序等,我们的可选方案会被进一步限制。8. 存储结构聚合操作友好(如列存储)们知道对于聚合统计操作对话,列存储的聚合性能是比较高的,因为它有很高对压缩比,一次读盘可以读到很多有效的数据,整体对 IO 效率会很高。9. 告警模块最后一点就是架构里必须规划告警模块如何去做。以上内容针对数据特点、业务需求提出的一些架构要求进行介绍,可以看出,核心取舍在于存储。下面一张图,展示了整个架构的处理流程和关键组件,接下来的内容将对存储部分的选型进行展开介绍。存储方案选型上图中关于存储部分,有几个开源的可选存储中间件:像 Cassandra、Hbase、ElasticSearch、ClickHouse、Grafana Loki等。下图将会针对这些中间件的优缺点进行对比分析:上图的方案选型图表对各个方案的描述已经相对比较详细,在此就不再赘述,接下来我们看下在 Erda 中是如何做的。Erda 日志分析平台实践Erda 目前采用的其实是比较常见的实现方案:使用 Elasticsearch 作为底层存储。当然,其中也存在历史原因,Erda 的开源时间虽然并不是很长,但是其存在历史可以算比较久了。在当时看来,选择 Elasticsearch 也是比较合理的选择。当然,现状并不是终点,我们后面仍会持续探索在成本、效率方面更优的方案。如果说选择 ES 这样的一个方案的话,具体应该怎么做呢?刚才的方案选型图表中列出了使用 Elasticsearch 的大致核心思路,接下来我们具体深入看下如何去做。之前提到,使用 Elasticsearch 方案的特点就是功能全和开箱即用,上图中列出了在 Erda 中所利用的一些关键能力来实现目前 Erda 想要提供的部分关键功能,如下图所示。总的来说,Erda 目前采用的其实还是比较常规的方案,并在此基础上有一些小的优化,整个代码结构上也是做了一层抽象,并没有说以后就是绑死在 Elasticsearch 上面,后续我们还会考虑支持一些其他可替换方案。Erda 日志分析平台未来的方向对 Erda 日志分析平台而言,未来我们有几个努力的方向:1. 存储更高效、可扩展首先是存储方面,上文我们也提到,Erda 目前主要采用基于 Elasticsearch 的存储方案,但使用 Elasticsearch 有一个不可忽视的缺点,那就是它的整体资源占用成本相对较高,而像 Clickhouse、Grafana Loki 等,确实有各自的优势需要我们去借鉴。所以说 Erda 作为一个开源产品,后续可能会支持更多的底层存储,用户可以在这些方案之间根据自身的需求和成本来进行选择。另外,自研存储也将会是我们的一个投入方向,因为监控领域除了日志,还有指标、Trace 数据。这些数据能否采用一个统一的存储内核来降低系统的复杂度,同时可以对不同数据类型做专项优化来平衡成本和性能,这些都是我们考虑自研存储的出发点。2. 告警更便捷、更智能目前在 Erda 平台上,如果想从日志分析出发去创建告警规则,实际使用的链路还是有点长,所以后续我们会优化这条链路上的产品和功能体验。另外的一个方向就是智能化,基于日志的自动异常检测。在上文中有简单提到这点,就是说即使用户没有显式的去配置任何规则,系统也可以帮助用户去发现预期之外的一些异常。这里的异常,不一定非得是业务应用抛出的错误的堆栈,它是一个相对于“正常”的概念,即正常数据流中突然出现了一个非常不同寻常的数据,这可能会需要用到一些机器学习的模型来检测。以上就是本次想要和大家分享的有关日志分析架构的一些内容,后续我们也将不忘初心,持续优化产品功能和用户体验,也非常期待有更多对此感兴趣的开发者参与进来与我们共建 Erda,每一条建议我们都会认真聆听,期待更多的声音帮助我们变得更好!更多技术干货请关注【尔达 Erda】公众号,与众多开源爱好者共同成长~
云原生时代背景下,快节奏的软件驱动型市场不断驱使着越来越多的企业寻求加速创新,改变原有的应用构建方式,转而尝试新的设计、构建和使用应用的方式 -- 即我们常说的“云原生应用”。它可谓是充分利用了云原生技术,最大程度释放了云原生时代的技术红利。为了让更多开发者了解到云原生应用,更好地拥抱云原生时代,本次线上 Meetup 我们邀请到了微软最有价值专家、开源爱好者、.NET 后端开发工程师 - 李帅,Apache Dubbo Committer、阿里云 MVP - 张天,Apache SkyWalking PMC 成员、Erda 微服务平台负责人 - 刘浩杨,Erda DevOps 平台负责人 - 郑嘉涛为大家带来详细解读,邀你共聊《云原生应用开发的探索与实践》。活动亮点抢先看什么是云原生应用?云原生应用相比较于传统应用的优势?如何成功实现云原生应用开发?一众技术专家的实践心得精彩好礼相赠······更多精彩,尽在 1 月 25 日活动直播间等你来~还有可能获取精美好礼哦!活动时间2021 年 1 月 25 日 14:00 - 17:05观看地址 - B 站嘉宾 & 议题介绍《探索云原生可观测性框架 OpenTelemetry》李帅.NET 后端开发工程师, 开源爱好者, 微软最有价值专家OpenTelemetry 是一个开源的可观测性框架, 它是云原生时代可观测性的新标准,在本次分享中, 我会带大家全面认识 OpenTelemetry, 项目的来世今生, 它是如何工作的, 项目架构, 以及它如何帮助开发人员在分布式系统中实现满足其业务目标所需的可观测性。《Dubbo Ecosystem 中的高性能网关和多语言解决方案 —— Pixiu》张天Apache Dubbo PMC 成员、阿里云 MVP以异构多语言服务接入已有 Dubbo 集群场景为切入点,介绍 Dubbo 生态中网关项目 Pixiu。Pixiu 作为代理网关或 sidecar 赋能服务轻量级接入 Dubbo 集群的能力,作为强依赖特定框架的另一种方案。此外,Pixiu 还提供 HTTP、Dubbo2、Triple 和 GRPC 等多协议转换和流量治理能力,是 proxy service mesh 方向的一种探索。《聊聊微服务的全栈可观测性》刘浩杨Erda 微服务平台负责人,Apache SkyWalking PMC 成员,开源爱好者在云原生架构下,基于 DevOps、微服务、容器化等云原生的能力,可以帮助业务团队快速、持续、可靠和规模化地交付系统,同时也使得系统的复杂度成倍提升,由此带来了前所未有的运维挑战。在此背景下,对分布式系统的“可观测性”应运而生,可观察性提供了一套理念来监控、诊断云原生微服务系统。和监控相比,可观察性从单一的度量扩展为 Metrics、Tracing、Logging 三大支柱,Erda 中的 APM 系统也在逐渐演进为以可观察性分析诊断为核心的微服务观测平台,本次主题将会解读 Erda 在微服务观测系统上的探索和实践。《终极套娃:如何利用 Erda 构建部署 Erda?》郑嘉涛开源爱好者,Erda DevOps 平台负责人我一直相信“dogfooding”的软件会有更棒的使用体验、更强更旺盛的生命力,同时也更具有说服力。以 Erda 自身为例,本次我将分享如何在 Erda 上进行软件开发和协作,持续构建出可用的制品;如何进行自动化测试,以及如何最终交付产品。详细议程
导读:许多人在选择“程序员”这一职业的背后,或多或少都会有故事可讲。本文是我们与一名 Erda 的用户沟通时深度挖掘到的故事,征得本人同意后对其进行了整理,并设立了【开发者故事】这一栏目,旨在收纳广大同学的故事。逝者如斯夫,不舍昼夜。随着 2021 年顺利闭幕,2022 年已经在路上。眼看还有 2 周就要开启新年模式,除了可以开开心心在家“躺平”,即将面临的可能还有:亲戚一轮一轮关切的问询、熊孩子上蹿下跳的闹腾、父母耳提面命让你明年一定要带对象回家过年……又到了新一轮立 flag 的时候,是继续抄抄 2021 年的作业,还是“重构”一下新年小目标?今天我们邀请到 Erda 星里一名有趣的开发者,和大家聊聊自己新年立下了哪些 flag~flamingo 开发工程师 从业 4 年 坐标:苏州我很热爱写代码,同时这件事对我来说也颇具意义。在这个信息化时代,一行行代码不仅为人们提供了巨大的便利,某种程度上也算是组成现代人生活的一部分。我所在的公司还算是一个国内比较大的 OTA,承载国内一些人的日常出行。能够用自己的能力为别人提供支撑和帮助、被别人所需要,这就是我不竭的源动力。同时,我也是一名云原生爱好者,一直对“开源”很感兴趣。最近参加了一个黑客马拉松活动,虽然没得奖,不过在参与的过程中和各位大佬学到了很多宝贵经验,不管是在技术上还是产品上,对如何做出更让人爱不释手的基础设施也有了更多、更深入的思考。作为 Erda 的最早使用的外部开发者,看着 Erda 一路成长,也是感慨颇多。曾经有幸从零开始负责过一家初创公司的基础架构,即 K8s 这种基础的镜像调度平台,所以我很理解从零搭建起来需要花费很多功夫,自己也曾经在运维方面走过弯路。技术的成长是需要一步一步积累的。一开始接触 Erda,就是觉得 Erda 已经在这方面走在很前沿,依托于这个平台,我们就可以很快速的接触到目前最佳的路线是什么,比方说监控,不需要花费多少的心智负担,就可以搭建一个成熟、稳定的平台来。对于 2022 年,我也有很多期待。网上很多人戏称,说 2022 年是 2020,too(英文发音一样),因为疫情的原因,我也有 2 年都没回家了。每次和家里打电话,家人都问什么时候可以带对象回去过年。哈哈,其实如果可以,新年的第一个小目标就是给自己 new 一个“对象”出来。一个人久了就容易沉浸在自己的世界里,程序员一般独处的时间很多,我也很享受没有人打扰、可以专注忙碌的心流时刻。不过这样久了就很容易忘我,经常一抬头发现忘了吃饭、忘了喝水也忘了时间。所以希望新的一年,可以减少一个人独处的时间,多一个人和我一起分享生活之美。第二个小目标就是可以多一些时间在工作以外的事情上,能够做一些自己喜欢的事。在这里也想和大家分享一些自己的爱好。恐怖片是我生活中一剂不错的调味品,比如温子仁导演的恐怖片在我这里就是经典中的经典,回味中的回味。我很喜欢温子仁导演对于镜头跟光线的运用,简简单单的两个镜头就能塑造出紧张感,在出其不意时给你会心一击。玩音乐也是我非常喜欢的放松方式。我从很小就开始接触音乐了,一开始接触的是管弦乐,参加了学校的乐队。当时学的是小号跟萨克斯,直到后来父亲送了我一架钢琴,我对音乐的喜爱日益剧增,不过随着年龄的增长,等待我去完成的事情也越来越多,玩音乐的时间也就越来越少。不过这并不影响我对音乐的向往。我很喜欢的一句话是这样的:“读书或者旅行,身体和心灵总有一个在路上。”去年和朋友们一起去了新疆维吾尔自治区的赛里木湖,这里是新疆海拔最高、面积最大、风光秀丽的高山湖泊,又是大西洋暖湿气流最后眷顾的地方,因此有“大西洋最后一滴眼泪”的说法,那美景真的是摄人心魄,在这里和大家分享两张当时拍摄的美景。最后还有一个小目标,或者说是心愿,就是希望疫情能够快快过去,告别昨日的紧张,大家都能身体健康。在新年即将来到之际,希望大家 2022 年的小目标都可以实现,新的一年都能遇到对的人,同时还有一句话送给大家:朝暮与岁月共往,愿我们一共行至天亮!写在最后当你也有一些想对自己讲的话或者单纯想记录一下自己的故事,欢迎关注公众号【尔达Erda】并和我们分享,我们会将其整理收在【开发者故事】栏目,让更多人了解到你。如果你仅仅想要聆听别人的故事,也欢迎关注【尔达Erda】公众号~与众多开发者共同成长。
引言:众所周知,对于一个云原生 PaaS 平台而言,在页面上查看日志与指标是最为基础的功能。无论是日志、指标还是链路追踪,基本都分为采集、存储和展示 3 个模块。这里笔者将介绍云原生下的常见的指标 & 日志的采集方案,以及 Erda 作为一个云原生 PaaS 平台是如何实将其现的。指标采集方案介绍常见架构模式1. Daemonset采集端 agent 通过 Daemonset 的方式部署在每个节点上。该模式下,通常是由 agent 主动采集的方式来获取指标,常见的 agent 有 telegraf、metricbeat、cadvisor 等。应用场景:通常用来采集节点级别的指标,例如:节点资源指标、节点上的容器资源指标、节点的性能指标等。2. 推 & 拉当我们需要采集程序的内部指标时,通常采用 agent 主动拉取指标或客户端主动推送指标的方式。应用场景:对于 Web 服务、中间件等长时间运行的服务来说,我们一般采用定时拉取的方式采集;对于 CI/CD、大数据等短时任务,则一般是以客户端主动推送的方式采集,例如:推送任务的运行耗时、错误数等指标。那么,到底是采用推还是拉的方式呢?我认为这取决于实际应用场景。例如:短时任务,由于 agent 可能还没开始采集它就已经结束了,因此我们采用推的方式;而对于 Web 服务则不存在这个问题,采用拉的方式还能减少用户侧的负担。开源方案介绍Prometheus 作为 CNCF 的 2 号毕业选手,一出生就基本成为云原生尤其是 Kubernetes 的官配监控方案了。它实际是一套完整的解决方案,这里我们主要介绍它的采集功能。拉场景下,Prometheus server 中的 Retrieval 模块,负责定时抓取监控目标暴露的指标。推场景下,客户端推送指标到 Pushgateway,再由 Retrieval 模块定时抓取 Pushgateway。其与推&拉方案基本相同,不过由于其即为丰富的 exporter 体系,基本可以采集包括节点级别的各种指标。Erda 采用的架构方案在 Erda 中,当前的方案是通过二开了 telegraf, 利用其丰富的采集插件,合并了 Daemonset 和推拉方案。telegraf[DS]:作为Daemonset采集节点级别的指标;telegraf-app[DS]:不采集指标,仅用于转发上报 trace 数据;telegraf-platform: 采集服务级别的指标;collector:收集 telegraf 上报的指标,以及客户端程序主动推送的指标。日志采集方案介绍常见架构模式1. Daemonset容器内应用的日志若输出到 stdout 中,容器运行时会通过 logging-driver 模块输出到其他媒介上,通常是本机的磁盘上,例如 Docker 通常会通过 json-driver 输出日志到 /var/log/docker/containers//*.log 文件中。对于这种场景,我们一般采用 Daemonset 的方案,即在每个节点上部署一个采集器,通过读取机器上的日志文件来采集日志。2. SidecarDaemonset 方案也有一些局限性,例如,当应用日志是输出到日志文件时,或者想对日志配置一些处理规则(例如,多行规则、日志提取规则)时。此时可采用 Sidecar 的方案,通过 logging-agent 与应用容器通过共享日志目录、主动上报等方式来采集。3. 主动上报当然,还可以主动(一般通过供应商提供的 SDK)上报日志。常见应用场景有:当你想转发日志到外部系统时可以使用主动上报的模式,例如,转发阿里云的日志到 Erda;当你不想或者不具备部署任何 logging-agent 时,例如:当你只想试用下 Erda 的日志分析时。开源方案介绍业界中,比较有名的就是使用 ELK 来作为日志方案,当然也是整套解决方案。采集模块主要是 beats 作为采集端,logstash 作为日志收集总入口,elasticsearch 作为存储,kibana 作为展示层。Erda的架构方案在 Erda 中,我们使用了 fluent-bit 作为日志采集器:针对容器日志:我们采用 Daemonset 的方案进行采集;针对 ECI 等无法部署 Daemonset 的场景:我们采用 Sidecar 的方案采集;针对第三方的日志:我们在 collector 端支持用户的自定义主动上报。小结不难看出,无论是指标还是日志,其数据采集方案还是比较简单清晰的,我们可以根据实际场景随意搭配。然而随着集群规模的增长以及用户自定义需求的增加,往往会出现如下难点:数据洪流问题:如何保障监控数据的时效性以及降低数据传输与存储成本;配置管理问题:如何管理大量的自定义配置,例如自定义指标采集规则、日志多行规则、日志分析规则等等对于这些问题,我们也在不断探索实践中,并会在后续的文章中进行分享。参考链接 & 延伸阅读https://kubernetes.io/docs/concepts/cluster-administration/logging/https://www.elastic.co/cn/what-is/elk-stackhttps://prometheus.io/docs/introduction/overview/#architecture更多技术干货请关注【尔达 Erda】公众号,与众多开源爱好者共同成长~文中部分图片源自网络,侵删
关键字解析 拓扑图用来描述平台各服务之间的依赖关系,也可以理解为平台服务的整体结构。拓扑图上的每个节点表示服务组件或服务的依赖项,且节点上标注有服务的运行状态和请求信息,点击后可获取详细的观测图表。功能简介Erda 微服务治理平台能够自动发现服务的上下游依赖关系,并生成服务拓扑大图,便于用户查询服务的性能瓶颈、错误热点和异常的服务依赖。下面我们一起来看看全局拓扑图如何帮助用户面对复杂的微服务系统观测时化繁为简~功能入口https://www.erda.cloud/ (加入用户交流群可获取免费试用资源)登陆后点击如下模块:微服务治理平台 --> (具体项目)--> 全局拓扑功能界面展示节点类型说明API 网关如果系统基于微服务治理平台中的 API 网关转发流量,拓扑图中将显示 API 网关节点,如下图所示。APIGateway 图标应用服务平台可自动识别 HTTP 和 RPC 请求,并标注为服务节点,如下图所示。服务节点图标中间件平台可自动识别服务调用的中间件,并标注为中间件节点,当前支持 MySQL、Redis、RocketMQ、Elasticsearch 等,如下图所示。中间件节点图标外部请求调用平台可自动识别服务调用的外部 HTTP 请求,并标注为外部事务节点,如下图所示。外部事务节点图标功能演示拓扑概览 & 拓扑分析界面左侧可显示/隐藏拓扑概览 & 拓扑分析,点击具体的类别可过滤显示节点信息。平台可自动发现系统全局拓扑中的异常节点,拓扑分析提炼不健康服务、离群服务、循环依赖三种异常节点类型,用户可重点关注。显示/隐藏拓扑概览 & 拓扑分析节点错误调用示意服务节点直观表示错误调用情况,其红色部分代表错误调用的占比。节点错误调用情况及详情鼠标悬浮当鼠标悬浮于节点上时,将高亮与当前节点关联的所有节点,使得服务间调用情况更为清晰。鼠标悬浮节点节点下钻点击鼠标点击节点时可显示当前节点的各项信息指标,如调用次数、平均响应时间(ms)、错误调用次数、错误率等。鼠标单击节点拓扑刷新节点图标直观展示其当前 RPS,可设置持续自动刷新或手动刷新,如下图所示。自动刷新示意手动刷新示意大家是不是已经迫不及待想要体验一下了呢~Erda V1.5 版本上线在即,更多全新特性及功能已经迫不及待和大家见面了~尽请期待❤️ 更多技术干货请关注【尔达 Erda】公众号,与众多开源爱好者共同成长~
导读 许多人在选择“程序员”这一职业的背后,或多或少都会有故事可讲。本文是我们与一名 Erda 的用户沟通时深度挖掘到的故事,征得本人同意后对其进行了整理,并设立了【开发者故事】这一栏目,旨在收纳广大同学的故事。自从字节跳动宣布采取“1075”工作制后,大厂们在“反内卷”这件事上越来越“卷”了:不准私自加班、加班时长与领导绩效挂钩、HR 到下班时间开始撵人回家等规定,似乎预示着 2021“反内卷”元年开始了!早些年的“工作”和“生活”二选一,现在看来正在逐步退出历史舞台。作为合格的新生代互联网打工人,会更注重“工作”和“生活”的平衡,他们认为“不加班”不能与“不努力、不奋斗”划等号。不过,虽然“反内卷”这事儿风头正盛,但是对于打工人来说,真的可以做到“工作”、“生活”二者相平衡吗?今天我们邀请到 Erda 星里一名朝九晚六的快乐的技术人,和大家聊聊自己是如何平衡工作和生活~ 郑子铭 后端开发 从业 4 年 坐标:广州 最初成为开发者的原因其实很简单:一是为了挣钱,二是为了实现“用代码改变世界”的梦想。对于代码,我总抱有极大程度的热情。很享受写代码时不断的思考的过程,可以一步一步按照自己的想法实现功能。我的第一份工作是做医疗系统相关的客户端开发,当我看到自己做出来的软件在医院大屏幕显示时,真的成就感爆棚,感觉自己不仅完成了工作任务,还做了非常有意义的事情,可以帮助医院、帮助患者更加方便地候诊。就是这样一点一点积累的成就感,让我在开发者这条路上愈发的坚定。现在我的工作每天平均敲三四个小时代码,上班时间基本上都是在做业务开发,下班的时间更多的是学习新的技术,点亮自己的技能树,往全栈方向发展。其实最近大家都说我比较“卷”,因为每天除了学习新知识之外,我还会把自己学到的新知识分享到各种微信群里,和大家讨论自己学习的新知识。群友们就会感叹“太卷了,救命啊!”,但是我觉得这是一个特别好的学习方式。按照费曼学习法所说的, 我们只有把自己掌握的知识,掰开揉碎、重组并传授给一个对这个话题知之甚少的人,才能达到智力的增长,而且在互相传授的过程中可以感受到学习的乐趣 ,切身体会到这点我真的感到特别激动哈哈。在工作中,有一件事情对我影响比较深刻。记得当时下班很多同事都走了,只剩下我和我的领导。我在实现一个功能的时候遇到一些问题,我的领导不顾家人催促回家吃饭的电话,非常耐心的指导我把功能实现了, 让我体会到了一个程序员的责任心不止是对自己的代码负责,还有对自己团队的负责 。最近身边的同事、朋友时不时都会聊起“内卷”与“反内卷”的话题,其实对我自己来说,除了感觉刘海日渐稀疏,其他方面倒没什么影响,可能是因为很幸运,每份工作需要加班的时间都不多。如果是连续加班严重的程序员,我相信身心应该都会很疲惫,最近几年程序员连续加班导致身体不适的新闻也不少,大家也一定要多注意身体,及时调节身心压力。我自己在面对压力时,有“四大法宝”:写文章,吃美食,功夫茶,峡谷遨游。第一个“法宝”是写文章。我写的最多的是学习笔记类型的文章,将自己的学习成果记录下来,分享到博客、公众号等,当有人评论或者提问的时候,我会特别兴奋,因为这种不确定性的反馈特别有意思,一直坚持下来,也成为了我的一种学习解压方式。第二个“法宝”是美食。我对吃这件事比较讲究,不是单纯追求美食的价格,关键是食材的新鲜程度还有烹饪技巧。作为一个在海边长大的潮汕人,从小就对各种海鲜,鱼类的新鲜程度的感知比较敏锐,基本是第一口就可以判断出来。上大学之后来到美食之都广州,在品尝到更多的美食的同时,也提升了对美食的认知,很多工作和生活上的疲惫和压力,都被一顿美食瞬间瓦解。第三个“法宝”是功夫茶。放假在家的时候基本上从早上起床到晚上睡前都在喝茶,这是潮汕人天生的爱好,也是一个打交道、做生意的媒介,有点类似于酒桌文化。人生如茶,苦后回甘,很多不如意在小小的茶盏里就慢慢散去了。最后的“法宝”是峡谷遨游。每天饭后会打上一两把放松身心、锻炼智力,由于匹配机制的存在,有时候就会被算法安排的明明白白,所以玩多了自然也可以修炼耐心,对输赢也不用太在意。有人会问起我,如果有一天不想写代码了,想去做些什么。这个问题好像是在问我失业了怎么办(笑),这个问题就有点类似于新东方裁员了,老师们怎么办。现在老师们都去直播带货了,甚至有一些去国外教中文了,所以本质上写代码只是实现功能的一种方式, 更重要的是写代码过程中对思维的锻炼 。就比如我们掌握了一门后端语言之后,学习另一门后端语言是非常快速的。如果有一天不写代码了,出去创业了,那么肯定是依托于我写代码时候积累的人脉和经验,做一些相关的工作,才可能快速的做大做强,实现自己的两个小目标。最后其实非常感谢 Erda 可以给我一个与大家相互增进了解的机会,每次 Erda 有活动我都非常乐于参加,现在已经集齐了全套 Erda 周边了!T恤啦、水杯啦,都特别棒!衣服的质量特别好,穿起来也很舒服,水杯就更不得了了,设计感拉满,真的太喜欢了❤️。我觉得还可以设计一款帽子,帽子比T恤更加的有辨识度,外套也可以哈哈,我前东家发的加绒卫衣质量特别好,虽然离职了,但是大家冬天都会穿哈哈。看看要不 Erda 什么时候安排一下? 更多技术干货请关注【尔达 Erda】公众号,与众多开源爱好者共同成长~
前言资源是影响 Spark 应用执行效率的一个重要因素。Spark 应用中真正执行 task 的组件是 Executor,可以通过spark.executor.instances 指定 Spark 应用的 Executor 的数量。在运行过程中,无论 Executor上是否有 task 在执行,都会被一直占有直到此 Spark 应用结束。上篇我们从动态优化的角度讲述了 Spark 3.0 版本中的自适应查询特性,它主要是在一条 SQL 执行过程中不断优化执行逻辑,选择更好的执行策略,从而达到提升性能的目的。本篇我们将从整个 Spark 集群资源的角度讨论一个常见痛点:资源不足。在 Spark 集群中的一个常见场景是,随着业务的不断发展,需要运行的 Spark 应用数和数据量越来越大,靠资源堆砌的优化方式也越来越显得捉襟见肘。当一个长期运行的 Spark 应用,若分配给它多个 Executor,可是却没有任何 task 分配到这些 Executor 上,而此时有其他的 Spark 应用却资源紧张,这就造成了资源浪费和调度不合理。要是每个 Spark 应用的 Executor 数也能动态调整那就太好了。动态资源分配(Dynamic Resource Allocation)就是为了解决这种场景而产生。Spark 2.4 版本中 on Kubernetes 的动态资源并不完善,在 Spark 3.0 版本完善了 Spark on Kubernetes 的功能,其中就包括更灵敏的动态分配。我们 Erda 的 FDP 平台(Fast Data Platform)从 Spark 2.4 升级到 Spark 3.0,也尝试了动态资源分配的相关优化。本文将针对介绍 Spark 3.0 中 Spark on Kubernetes 的动态资源使用。原理一个 Spark 应用中如果有些 Stage 稍微数据倾斜,那就有大量的 Executor 是空闲状态,造成集群资源的极大浪费。通过动态资源分配策略,已经空闲的 Executor 如果超过了一定时间,就会被集群回收,并在之后的 Stage 需要时可再次请求 Executor。如下图所示,固定 Executor 个数情况,Job1 End 和 Job2 Start 之间,Executor 处于空闲状态,此时就造成集群资源的浪费。开启动态资源分配后,在 Job1 结束后,Executor1 空闲一段时间便被回收;在 Job2 需要资源时再申Executor2,实现集群资源的动态管理。动态分配的原理很容易理解:“按需使用”。当然,一些细节还是需要考虑到:何时新增/移除 ExecutorExecutor 数量的动态调整范围Executor 的增减频率Spark on Kubernetes 场景下,Executor 的 Pod 销毁后,它存储的中间计算数据如何访问这些注意点在下面的参数列表中都有相应的说明。参数一览spark.dynamicAllocation.enabled=true #总开关,是否开启动态资源配置,根据工作负载来衡量是否应该增加或减少executor,默认false spark.dynamicAllocation.shuffleTracking.enabled=true #spark3新增,之前没有官方支持的on k8s的Dynamic Resouce Allocation。启用shuffle文件跟踪,此配置不会回收保存了shuffle数据的executor spark.dynamicAllocation.shuffleTracking.timeout #启用shuffleTracking时控制保存shuffle数据的executor超时时间,默认使用GC垃圾回收控制释放。如果有时候GC不及时,配置此参数后,即使executor上存在shuffle数据,也会被回收。暂未配置 spark.dynamicAllocation.minExecutors=1 #动态分配最小executor个数,在启动时就申请好的,默认0 spark.dynamicAllocation.maxExecutors=10 #动态分配最大executor个数,默认infinity spark.dynamicAllocation.initialExecutors=2 #动态分配初始executor个数默认值=spark.dynamicAllocation.minExecutors spark.dynamicAllocation.executorIdleTimeout=60s #当某个executor空闲超过这个设定值,就会被kill,默认60s spark.dynamicAllocation.cachedExecutorIdleTimeout=240s #当某个缓存数据的executor空闲时间超过这个设定值,就会被kill,默认infinity spark.dynamicAllocation.schedulerBacklogTimeout=3s #任务队列非空,资源不够,申请executor的时间间隔,默认1s(第一次申请) spark.dynamicAllocation.sustainedSchedulerBacklogTimeout #同schedulerBacklogTimeout,是申请了新executor之后继续申请的间隔,默认=schedulerBacklogTimeout(第二次及之后) spark.specution=true #开启推测执行,对长尾task,会在其他executor上启动相同task,先运行结束的作为结果 实战演示无图无真相,下面我们将动态资源分配进行简单演示。1.配置参数动态资源分配相关参数配置如下图所示:如下图所示,Spark 应用启动时的 Executor 个数为 2。因为配置了spark.dynamicAllocation.initialExecutors=2运行一段时间后效果如下,executorNum 会递增,因为空闲的 Executor 被不断回收,新的 Executor 不断申请。2. 验证快慢 SQL 执行使用 SparkThrfitServer 会遇到的问题是一个数据量很大的 SQL 把所有的资源全占了,导致后面的 SQL 都等待,即使后面的 SQL 只需要几秒就能完成。我们开启动态分配策略,再来看 SQL 执行顺序。先提交慢 SQL:再提交快 SQL:如下图所示,开启动态资源分配后,因为 SparkThrfitServer 可以申请新的 Executor,后面的 SQL 无需等待便可执行。Job7(慢 SQL)还在运行中,后提交的 Job8(快 SQL)已完成。这在一定程度上缓解了资源分配不合理的情况。3. 详情查看我们在 SparkWebUI 上可以看到动态分配的整个流程。登陆 SparkWebUI 页面,Jobs -> Event Timeline,可以看到 Driver 对整个应用的 Executor 调度。如下图所示,显示了每个 Executor 的创建和回收。同时也能看到此 Executor 的具体创建和回收时间。在 Executors 标签页,我们可以看到所有历史 Executor 的当前状态。如下图所示,之前的 Executor 都已被回收,只有 Executor-31 状态为 Active。总结动态资源分配策略在空闲时释放 Executor,繁忙时申请 Executor,虽然逻辑比较简单,但是和任务调度密切相关。它可以防止小数据申请大资源,Executor 空转的情况。在集群资源紧张,有多个 Spark 应用的场景下,可以开启动态分配达到资源按需使用的效果。以上是我们在 Spark 相关优化的一点经验,希望能够对大家有所帮助。注:文中部分图片源自于网络,侵删。更多技术干货请关注【尔达 Erda】公众号,与众多开源爱好者共同成长~
关键字解析:火焰图(Flame Graph)由性能优化大师 Brendan Gregg 发明,和所有其他的 profiling 方法不同的是,火焰图以一个全局的视野来看待时间分布,列出所有可能导致性能瓶颈的调用栈。通过火焰图,可以非常方便的看到性能资源都消耗在了哪里,从而能够直观的看到程序的性能瓶颈,以进行程序的优化。为了使服务链路追踪可视化更高效,Erda 在微服务治理平台新版本中也引入了火焰图功能,下面我们一起来看看吧~功能入口https://www.erda.cloud/ (需要注册账号,如需演示可加入用户交流群获取免费试用资源)登陆后点击如下模块:微服务治理平台 - (具体项目) - 诊断分析 - 链路追踪 – 查看详情 – 火焰图进入功能界面:X 轴代表调用耗时时间长度Y 轴是函数块叠加而成,有点像程序调试堆栈,代表调用的深度火焰块 X 轴越长,说明 span 调用耗时越长,可联合其火焰图的纵轴判断是否存在子调用过多或者自身调用过慢的情况,更方便排查请求过程中的业务瓶颈或异常点。鼠标悬浮火焰的每一层都会标注完整操作名,鼠标悬浮时会显示:完整的服务名具体的操作名调用类型及调用的对象当前调用耗时总调用耗时(当前调用耗时 + 子调用耗时)点击放大在某一层点击,火焰图会水平放大,该层会占据所有宽度,显示详细信息。火焰图整体功能演示参看视频:火焰图整体演示参考链接 & 延伸阅读Brendan's site如何读懂火焰图更多技术干货请关注【尔达 Erda】公众号,与众多开源爱好者共同成长~
11 月 23 日,Erda 与 OSCHINA 社区联手发起了【高手问答第 271 期 -- 聊聊大规模 K8s 集群管理】,目前问答活动已持续一周,由 Erda SRE 团队负责人骆冰利为大家解答,以下是本次活动的部分问题整理合集,其他问题也将于近期整理后发布,敬请期待!Q1:K8s 上面部署不通的应用对于存储有不同的要求,有的要高吞吐,有的是要低响应。大规模 K8s 部署的时候是怎么协调这种存储差异的问题?还是说需要根据不同的场景,运维不同的存储服务?又或者说尽量存储使用解决方案?A1:存储相对于 CPU 和内存确实会更复杂一些,就是因为它会包含更多类型,不同的存储空间,不同的性能要求。所以存储还是得从应用需求出发,来满足不同的存储需求。Q2:请问下你们维护的最大 K8s 集群规模大小是多少?遇到了哪些性能、稳定性问题?做了哪些优化?A2:我们目前维护的单个集群规模不大,总量相对大些,维护了几百个集群。量上来了就会碰到形形色色的问题,比如:如何提升运维效率?如何比用户更早地发现问题?如何优化内存碎片问题?如何优化磁盘驱逐带来的隐患?。我们也做了很多事情:第一步进行标准化,比如统一操作系统、统一版本、标准化节点规格、系统数据盘分离等等。接着开始建设诊断系统,覆盖操作系统、容器、K8s、常规中间件、平台(应用)等,目前就是先于用户发现问题,能全方位进行巡检覆盖,可以将其理解为运维系统的眼睛,近期我们刚好也开源了这个系统:kubeprober。当前也会有对应的一些优化,比如: 补充 docker k8s 的 log rotate 参数,优化 gc、eviction 参数,防止磁盘被写满;对 Pod PID 进行限制、EmtyDir 存储、容器可写层大小等进行限制;保障 K8s 关键 Pod 的调度;关闭 swap,优化 /proc/sys/vm/min_free_kbytes 等参数,优化内存回收。问题有些大,涉及的工作也会特别多,我也只是列举了部分,每个点上都还可以做更多的事情。kubeprober 开源地址:https://github.com/erda-project/kubeproberQ3:老师目前容器化部署编排企业私有成本远没有云厂商实惠,这会不会形成垄断趋势?还有 Serverless 的发展是不是对容器技术的冲击呢?A3:会有些现状问题,国内不少企业都有自建 IDC,尤其是一些头部企业。不论考虑是进行利旧,还是数据安全性等,客户都会有不同的决策,所以一定会有共存的情况。Q4:K8s 对标两地三中心这样的部署架构老师有什么推荐么?是一套 K8s 用 namespace 区分好,还是各自搭建,优缺点老师能分享一下吗?A4:一套的好处,管理成本比较低,部署的业务可以直接基于地域标签进行打散部署。但会有较大的问题,比如两地三中心本身就跨地域的,网络质量的保障是个大问题。本身方案就需要能跨城市级的高可用,那单 K8s 集群的 ETCD 高可用怎么保障?如果真出现城市级自然灾害,那就会导致你的 etcd 集群异常。本身的容灾方案还没起作用,可能就会出现该 K8S 集群因为网络等因素导致的不稳定。容灾方案本身就会有较大的复杂性,跟你的环境,跟你的场景,都会有较大的关系。我可能没办法直接告诉你一套方案,但可以一起探讨下。Q5:您好,请问需要把所有的服务都拆分为微服务吗?并发量到多大才需要这样?A5:微服务是否拆分,可能还不是仅跟并发量有关,很多时候你拆分后,性能可能比你单体架构还要差。核心还是得看你要解决什么问题,比如研发效率太低了、团队规模太大了、业务复杂度太高了等等。并不只是一个简单的拆分动作,还得去考虑你开发运维方式的变化、组织结构的变化等。Q6:K8s 持久化存储有推荐方案吗?nfs 性能和稳定性都不行,ceph 蛮复杂的(还要区分 rbd、ceph),貌似也有人反应不稳定。local pv 的话 pod 要锁死节点了,K8s 优势大减呀~A6:是的,只是举个例子。local pv 也是一个场景,你需要有更强的性能时,就是一个不错的选择,虽然和节点绑定了,但是可以通过应用层的架构来提升高可用的能力,解决单点故障问题。只是举例子,所以关键是看场景去配对存储实现。Q7:数据库这类对存储敏感的软件,你们会部署到 K8s 上吗?有什么要注意的?A7:我们目前进行了区分,非生产环境采用了数据库上 K8s,可以有更高的成本和运维能力。生产环境还没有跑在 K8s 上,主要是考虑稳定性。很多中间件都一样,不仅仅是数据库,只考虑存储还不够,比如你需要注意扩缩容、监控、快照备份、故障恢复等等,还有一些特定中间件的运维需求。Q8:请问老师你们运维的 K8s 集群是运行在物理机上还是虚拟机上呢?现在不少公司都已经有虚拟化环境,虚拟机和容器共存有什么经验、建议吗?A8:我们现在运维的 K8s 集群大部分都是在虚拟机上。多一层虚拟机,会多一些开销,比如资源开销、VM 平台的管理开销,甚至还会有采购成本。多一层虚拟化,可以弥补下容器的隔离性及安全性,扩缩容的成本也比物理机要低,现在不少 VM 平台还提供了热迁移等功能,运维能力上还是会强一些。有没有虚拟机这层,对 K8s 的使用层面关系不是特别大。Q9:老师您好,关于 K8s 我们主要是使用一些管理平台去做管理如 Kubesphere、rancher 等等,针对 K8s 学习路线,想问一下怎么能更地去结合现状实践学习?A9:很好的一点是你已经有了实际的环境去使用以及研究 K8s 了,带着实际的场景以及问题去学习 K8s 往往是最有效的方式,但前提是你已经掌握了 K8s 的基本知识和原理,在这些知识背景下再碰到工作上的实际问题往往都能思考的更深,也对 K8s 掌握的更细致,尤其是 kubesphere 、rancher 管理下的 K8s,往往遇到问题要先甄别是 K8s 的问题还是管理平台的问题,这时基本的理论知识就显得尤为重要,共勉。Q10:如果存在要跨地域建 K8s、跨时区的场景下,如何保障 K8s 集群的稳定性,主机时间如何处理?A10:个人不建议跨地域、跨时区,构建同一个 K8s 集群。建议考虑多集群的方案。,主要是两类: Pod IP + Service IP。集群网络算是这两类的统称,看个人怎么理解了。Service 核心是用于服务发现及 Pod 流量负载。Q11:如何理解 pod 内网络、集群网络以及 service 网络呢?目前该如何选择网络插件 CNI?A11:如果没有太多的需求,可以选择 flannel,相对简单一些。当然还有很多其他的插件,比如 calico、weave 等,如果你想要有更强的性能,更丰的网络策略配置,可以考虑下它们。更多技术干货请关注【尔达 Erda】公众号,与众多开源爱好者共同成长~
前言Apache Spark 自 2010 年面世,到现在已经发展为大数据批计算的首选引擎。而在 2020 年 6 月份发布的Spark 3.0 版本也是 Spark 有史以来最大的 Release,其中将近一半的 issue 都属于 SparkSQL。这也迎合我们现在的主要场景(90% 是 SQL),同时也是优化痛点和主要功能点。我们 Erda 的 FDP 平台(Fast Data Platform)也从 Spark 2.4 升级到 Spark 3.0 并做了一系列的相关优化,本文将主要结合 Spark 3.0 版本进行探讨研究。为什么 Spark 3.0 能够“神功大成”,在速度和性能方面有质的突破?本文就为大家介绍 Spark 3.0 中 SQL Engine 的“天榜第一”——自适应查询框架 AQE(Adaptive Query Execution)。AQE,你是谁?简单来说,自适应查询就是在运行时不断优化执行逻辑。Spark 3.0 版本之前,Spark 执行 SQL 是先确定 shuffle 分区数或者选择 Join 策略后,再按规划执行,过程中不够灵活;现在,在执行完部分的查询后,Spark 利用收集到结果的统计信息再对查询规划重新进行优化。这个优化的过程不是一次性的,而是随着查询会不断进行优化, 让整个查询优化变得更加灵活和自适应。这一改动让我们告别之前无休止的被动优化。AQE,你会啥?了解了 AQE 是什么之后,我们再看看自适应查询 AQE 的“三板斧”:动态合并 Shuffle 分区动态调整 Join 策略动态优化数据倾斜动态合并 shuffle 分区如果你之前使用过 Spark,也许某些“调优宝典”会告诉你调整 shuffle 的 partitions 数量,默认是 200。但是在不同 shuffle 中,数据的大小和分布基本都是不同的,那么简单地用一个配置,让所有的 shuffle 来遵循,显然不是最优的。分区过小会导致每个 partition 处理的数据较大,可能需要将数据溢写到磁盘,从而减慢查询速度;分区过大又会带来 GC 压力和低效 I/O 等问题。因此,动态合并 shuffle 分区是非常必要的。AQE 可以在运行期间动态调整分区数来达到性能最优。 如下图所示,如果没有 AQE,shuffle 分区数为 5,对应执行的 Task 数为 5,但是其中有三个的数据量很少,任务分配不平衡,浪费了资源,降低了处理效率。而 AQE 会合并三个小分区,最终只执行三个 Task,这样就不会出现之前 Task 空转的资源浪费情况。动态调整 join 策略SparkJoin 策略大致可以分三种,分别是 Broadcast Hash Join、Shuffle Hash Join 和 SortMerge Join。其中 Broadcast 通常是性能最好的,Spark 会在执行前选择合适的 Join 策略。例如下面两个表的大小分别为 100 MB 和 30 MB,小表超过 10 MB (spark.sql.autoBroadcastJoinThreshold = 10 MB),所以在 Spark 2.4 中,执行前就选择了 SortMerge Join 的策略,但是这个方案并没有考虑 Table2 经过条件过滤之后的大小实际只有 8 MB。AQE 可以基于运行期间的统计信息,将 SortMerge Join 转换为 Broadcast Hash Join。在上图中,Table2 经过条件过滤后真正参与 Join 的数据只有 8 MB,因此 Broadcast Hash Join 策略更优,Spark 3.0 会及时选择适合的 Join 策略来提高查询性能。动态优化数据倾斜数据倾斜一直是我们数据处理中的常见问题。当将相同 key 的数据拉取到一个 Task 中处理时,如果某个 key 对应的数据量特别大的话,就会发生数据倾斜,如下图一样产生长尾任务导致整个 Stage 耗时增加甚至 OOM。之前的解决方法比如重写 query 或者增加 key 消除数据分布不均,都非常浪费时间且后期难以维护。AQE 可以检查分区数据是否倾斜,如果分区数据过大,就将其分隔成更小的分区,通过分而治之来提升总体性能。没有 AQE 倾斜优化时,当某个 shuffle 分区的数据量明显高于其他分区,会产生长尾 Task,因为整个 Stage 的结束时间是按它的最后一个 Task 完成时间计算,下一个 Stage 只能等待,这会明显降低查询性能。开启 AQE 后,会将 A0 分成三个子分区,并将对应的 B0 复制三份,优化后将有 6 个 Task 运行 Join,且每个 Task 耗时差不多,从而获得总体更好的性能。通过对倾斜数据的自适应重分区,解决了倾斜分区导致的整个任务的性能瓶颈,提高了查询处理效率。自适应查询 AQE 凭借着自己的“三板斧”,在 1TB TPC-DS 基准中,可以将 q77 的查询速度提高 8 倍,q5 的查询速度提高 2 倍,且对另外 26 个查询的速度提高 1.1 倍以上,这是普通优化无法想象的傲人战绩!真的吗?我不信口说无凭,自适应查询 AQE 的优越性到底是如何实现,我们“码”上看看。AQE 参数说明#AQE开关 spark.sql.adaptive.enabled=true #默认false,为true时开启自适应查询,在运行过程中基于统计信息重新优化查询计划 spark.sql.adaptive.forceApply=true #默认false,自适应查询在没有shuffle或子查询时将不适用,设置为true将始终使用 spark.sql.adaptive.advisoryPartitionSizeInBytes=64M #默认64MB,开启自适应执行后每个分区的大小。合并小分区和分割倾斜分区都会用到这个参数 #开启合并shuffle分区 spark.sql.adaptive.coalescePartitions.enabled=true #当spark.sql.adaptive.enabled也开启时,合并相邻的shuffle分区,避免产生过多小task spark.sql.adaptive.coalescePartitions.initialPartitionNum=200 #合并之前shuffle分区数的初始值,默认值是spark.sql.shuffle.partitions,可设置高一些 spark.sql.adaptive.coalescePartitions.minPartitionNum=20 #合并后的最小shuffle分区数。默认值是Spark集群的默认并行性 spark.sql.adaptive.maxNumPostShufflePartitions=500 #reduce分区最大值,默认500,可根据资源调整 #开启动态调整Join策略 spark.sql.adaptive.join.enabled=true #与spark.sql.adaptive.enabled都开启的话,开启AQE动态调整Join策略 #开启优化数据倾斜 spark.sql.adaptive.skewJoin.enabled=true #与spark.sql.adaptive.enabled都开启的话,开启AQE动态处理Join时数据倾斜 spark.sql.adaptive.skewedPartitionMaxSplits=5 #处理一个倾斜Partition的task个数上限,默认值为5; spark.sql.adaptive.skewedPartitionRowCountThreshold=1000000 #倾斜Partition的行数下限,即行数低于该值的Partition不会被当作倾斜,默认值一千万 spark.sql.adaptive.skewedPartitionSizeThreshold=64M #倾斜Partition的大小下限,即大小小于该值的Partition不会被当做倾斜,默认值64M spark.sql.adaptive.skewedPartitionFactor=5 #倾斜因子,默认为5。判断是否为倾斜的 Partition。如果一个分区(DataSize>64M*5) || (DataNum>(1000w*5)),则视为倾斜分区。 spark.shuffle.statistics.verbose=true #默认false,打开后MapStatus会采集每个partition条数信息,用于倾斜处理 AQE 功能演示Spark 3.0 默认未开启 AQE 特性,样例 sql 执行耗时 41 s。存在 Task 空转情况,shuffle 分区数始终为默认的 200。开启 AQE 相关配置项,再次执行样例 sql。样例 sql 执行耗时 18 s,快了一倍以上。并且每个 Stage 的分区数动态调整,而不是固定的 200。无 task 空转情况,在 DAG 图中也能观察到特性开启。总结Spark 3.0 在速度和性能方面得提升有目共睹,它的新特性远不止自适应查询一个,当然也不意味着所有的场景都能有明显的性能提升,还需要我们结合业务和数据进行探索和使用。注:文中部分图片源自于网络,侵删。
最近,一个帖子在互联网圈子火了起来,并在社会上引发广泛关注,“程序员 35 岁危机”这一话题再次频现热搜。一位名叫 Mary 的网友给总理留言提到:“我是一名计算机专业出身的软件开发人员,今年 45 岁,精通 java 的各种技术体系,包括微服务、大数据等技术,并能应用到实际工作中,帮助所在公司提升、改造所使用的技术框架,业余我还考取了 PMP 项目管理证书、系统架构师证书,成为所在公司的系统架构师、核心技术骨干。我对计算机理论的理解也随着实践的增多越来越深刻,我感觉我的职业生涯进入一生中最好的时刻。在我儿子读初二上学期时,我辞职回家陪伴儿子。半年后,当我再回来寻找工作机会的时候,却发现连个面试机会都很难得到,更别提发挥自己的专业特长了 。现在国家鼓励延迟退休,我觉得,40岁以上的有经验的专业技术人员此刻正是自身职业发展的黄金时期,他们找工作时不能被年龄限制了。”图片源自:中国政府网外界对于程序员群体也会调侃的赋予一个个标签:“高薪”、“格子衫”、“加班”、“技术宅”、“压力大”……但仍有越来越多的人投身于这一行业中,程序员的身影也不断出现在我们身边各个领域。本文的主人公将从自己如何成为一名程序员出发,来讲述自己与这一行业的故事,一起来看下吧~Richfiter 高级研发工程师 从业 10 年 坐标:成都 成为一名开发者可以说是小时候的梦想。上小学的时候,还记得那时操作系统是 Windows 95,第一次接触计算机的我产生了浓厚的兴趣,在微机课上就学习了 LOGO 语言,可以用一只小海龟,通过代码控制画出各式各样有趣的图形、动作等。对于那时候的我来说,觉得能控制计算机实现我的想法,这真的太神奇了!随着 Windows 98 的普及,市面上出现了很多有趣的游戏,比如《风云之天下会》、《仙剑奇侠传》、《星际争霸》、《红色警戒》……相信从那个年代过来的我们,都会背“show me the money”,都会用《金山游侠》,也都对精彩的电脑世界所着迷。后来,在订阅书刊报纸的年代,《电脑爱好者》、《电脑报》之类的杂志里有大量电脑操作的技巧、黑客攻防战的文章以及各类软件的使用评测。每篇文章,都令我读的津津有味,那时的我就定下了一个目标:以后上学和工作,都要去做计算机方面的事情。上大学后,我主修了计算机专业,随后顺利成为了一名开发者。和大多数程序员一样,我心中也怀揣着“科技改变世界”这一美好的理想。现在写代码这件事已经融为我生活中的一部分,我会用代码去尝试解决工作以及生活上的问题。每次成功解决问题,都会让我感受到一次小小的荣耀感。不过一个不能忽略的事实是,各行各业,都有对应的职业病,干咱们这行的,要是没有个颈椎病、腰椎问题、脱发、内分泌失调和过劳肥的,都不正常。工作前几年,每天都还能够按时上下班,每周平均写 40 个小时的代码。最近这几年,感觉大家都在拼加班、拼业务量、拼方案、拼各种 KPI......随后就演变成了部门制度、公司制度、面试潜规则……目前我每周平均要写 55-60 个小时的代码,说句心里话:“我好累啊,你们不累吗?”。很多人眼里的程序员是没有“生活”的,不是在上班就是在去加班的路上。虽然工作很辛苦,但是我仍然保留了自己小小的爱好:打乒乓球。小时候并没有系统训练过,不过通过看视频,还是学会了一些适合自己的打法,也拥有了属于自己的专业球拍,上个照片吧:关于最近很火的“程序员 35 岁危机”话题,我认为这是一个危险信号,同时也是一个机遇。危险信号体现在几个方面:体力、精力、压力、以及工作机会......机遇方面,我觉得就是如何转化从业经验和业务能力,可以做业务专家,也能做管理,甚至,自己当老板......目前,我就在积极主动地往业务专家和管理方向转型,期待我可以做的和编程一样长久。我眼中的开发者:爱折腾,善留坑,很敬业,非常开心可以从事这一行业,和大家一起用代码改变世界!最后以分享一件有趣的事情结束:湖南卫视的综艺节目《时光音乐会》满新颖的,不妨试试看?😊更多技术干货请关注【尔达 Erda】公众号,与众多开源爱好者共同成长~
云原生下的机遇和挑战时至今日,Kubernetes 已至成熟期,云原生时代则刚刚开始。虽说云原生不只是围绕着 Kubernetes 生态,但无可质疑,Kubernetes 已是云原生生态的基石。通过规范 API 和 CRD 标准,Kubernetes 已经建立起了一个云原生 PaaS 生态帝国,成为了 PaaS 领域的事实标准。这一层事实标准,对企业交付有着巨大的意义。在 Kubernetes 生态出现之前,类比于土木工程,连螺丝、螺帽这样的东西都缺少统一的标准,而甲方企业如果只关注上层业务功能,很容易把万丈高台架构于浮沙之上,导致业务的倾覆。不夸张的说,在企业交付领域,真是“天不生 Kubernetes,万古如长夜”。以 API 管理中的 API 路由功能为例,如果不使用 Kubernetes,企业可能会选择 F5/Nginx/HAProxy/Zuul 等各式网关软件,做对应的路由配置。有的软件提供了控制台 UI,有的可能是人肉脚本运维,缺乏标准,运维技能也无法沉淀,关键人员离职可能会带来灾难。Kubernetes 把 API 路由的能力抽象为了 Ingress 资源,定义了标准,屏蔽了底层软件细节,通过 CNCF CKA 认证的人员都会具备 API 路由运维的能力。在 API 管理的领域,除了 API 路由,还有 API 流量治理策略,API 开放鉴权,以及调用量观测审计等环节,Kubernetes 以及 Istio 等生态都给出了一些标准定义,虽然其中很多尚未成熟,但标准和生态的未来已经愈发清晰。API 全生命周期管理是什么API 全生命周期管理(Full Life Cycle API Management)是指对 API 从规划、设计到实施、测试、发布、运行、调用直至版本变更与退出的整个周期的管理。一般来说,API 全生命周期可以分为三个层面和六个阶段。三个方面是指:设计,实施,管理,如下图所示:Mulesoft 对 API 管理三个层面的图示六个阶段是指:规划与设计阶段、开发阶段、测试阶段、部署与运行时阶段、运维监控阶段、版本管理与弃用阶段。用以支持 API 全生命周期管理的工具应当具备以下能力:API 集市,用于 API 提供者发布文档展示应用程序的服务能力,API 的使用者查阅服务接口进而开发客户端。API 网关和访问管理工具,用于 runtime 管理、访问管理、安全管理、数据收集等。监控管理工具,用于监控 API 相关指标。接口测试工具,用于测试接口。API 设计工具,用于设计和编写 API 文档。近年来谷歌收购 Apigee、Red Hat 收购 3scale 等事件无一不在证明 API 生命周期管理越来越被业界所重视。从2020 Ganter API 全生命周期管理“魔力象限”可以看到Google、Mulesoft、Microsoft、IBM、Kong 等众多熟悉的身影出现在了领导者第一象限;Amazon Web Services、TIBCO Software、Broadcom 等也紧随其后。Magic Quadrant for Full Life Cycle API Management by gartner.comAPI 生命周期不同阶段解读API 管理的核心是需要服务 API 的整个生命周期并启用关联的生态系统。API-First 方法将 API 视为产品并对其进行管理,强调整个生命周期的重要性。通过精心设计、管理和维护的 API 可为开发人员提供良好体验,为组织带来价值。API 全生命周期管理设计的产物是 API 文档,实施的产物是 API 的服务实例,它们都是被管理对象。下面我们将针对 API 生命周期管理的不同阶段进行详细解读。规划与设计阶段规划与设计阶段要规划应用程序功能,设计 API,编写、评审以及发布 API 文档。当开始规划应用程序新的功能点时,就要着手构思应用程序要呈现怎样的 API。API 涉及哪些资源、哪些操作、什么样的权限、什么样的场景等等,都是这个阶段的思考重点。设计 API 时需要充分考虑,如接口易用性、实现难度、价值等。如果不在此阶段思虑充分,就会设计出不可靠的 API,以至于开发出“腐烂”的代码和不可靠的功能,为组织带来风险。设计阶段共有四个主要任务:设计:确定业务流程和需求,对资源合行为进行抽象。建模:API 资源建模,API 操作与方法建模,请求/响应有效负载/代码建模等。反馈:开发人员间互相反馈,完善设计稿。验证:根据开发人员的反馈适当修改 API 设计,继续验证。API 设计的目标是产生一份 API 协议,一般是一份具有可读性的 API 文档。这种先行设计 API 的方法被称为“API-First”。API-First 是 DevOps 实践中发展出来的,在项目开发中致力于开发出一致可重用的 API 方法论。顾名思义,API-First 就是 API 先行,在计划开发应用程序时,先设计应用程序接口,然后实现接口功能。与之相对的是 Code-First,即先实现应用程序功能,然后在此基础上根据外部需求抽象出接口。相较于 Code-First,API-First 更加敏捷。API-First 的思路使得功能易于解耦,更加适合微服务拆分;API-First 通过接口发布功能,小巧轻快,能提高迭代速率;通过文档协调开发者间协作,可以提升开发效率;通过版本化的 API 持续集成,符合 DevOps 的精神内核。开发阶段开发阶段要实现规划与设计的全部接口,实现应用程序全部新功能。开发阶段是产品功能从无到有的核心阶段,应用程序开发人员根据完善的 API 设计文档进行并行开发,以节约开发时间,提高开发效率。设计合理、表述清晰、风格统一、高一致性的 API 能令开发人员如沐春风,缩短学习时间,降低学习成本。利用 API 管理工具,可以根据 API 文档生成服务端和客户端代码,多语言甚至框架级别的代码生成能力,能节约开发人员的编码成本;还可以生成接口测试代码和脚本,使得开发人员不必专门编写接口测试代码或者只需花少量的时间修改即可完成接口测试编写工作。基于 API 文档的 mocking service 能很好地协调服务端和客户端开发人员的协作,当服务端 API 功能还未实现时,客户端开发人员可以利用 mocking service 调试开发,待服务端开发人员将阶段性成果部署到开发环境时,只需修改下客户端软件服务域名就可以联调。API 文档支持可编程 mocking,只需在文档中配置不同参数,就可以模拟不同场景下的接口响应,比如通过配置响应码模拟是否登录,通过配置 User-Agent 模拟不同来源的客户端等。测试阶段测试阶段要对已实现的接口进行充分测试,验证接口功能是否按预期实现,它要求接口可用、准确、稳定、可靠(也有人将开发和测试作为一个阶段,因为开发测试总是交织在一起的)。API 开发完成之后,要经过几轮 API 测试以确保其正常运行。如果测试顺利完成,则可以继续进行下一个生命周期阶段,但大多数情况下,API 会经历几轮测试和调整,然后再进行部署。API 全生命周期管理要求 API 测试自动化,因此不能仅仅依赖接口测试脚本、桌面接口测试工具来做接口测试,集成到持续交付和部署的 DevOps 流程中的自动化测试工具在这里至关重要。以往的许多 API 管理工具,将 API 生命周期各个阶段割裂开来。就开发阶段与测试阶段而言,接口测试往往面临许多痛点,比如:重复定义的问题:在 API 设计阶段,就已经设计过 API 接口,在测试阶段,又将接口要素重新编写一遍,从 URI 到各种参数,全要重新填写一遍。编排接口能力不足的问题,一些传统的接口测试工具虽然能测试单个接口,但却将接口孤立的看待,没有将接口有机编排起来,难以串联成一个个完整的场景。所以,必须将 API 生命周期的各个阶段有机地联系起来。用户在编写测试用例时,直接引用文档里的接口,就避免了重复定义的问题;在设计 API 时充分周全地建模,会让编排就变得十分自然。部署与运行时阶段运行时阶段要将实现了特定 API 的应用部署到相应的环境,使 API 作为服务实例正式向外提供服务。运行时阶段,可以从 API 角度对实例进行访问管理,授权客户端对实例进行访问,并限制它们的访问流量。还可以决定哪些接口可以被访问、哪些接口不可以被访问。每一个 API 的价值都值得单独考量,从商业运营角度看:流量:可以给初级用户开放少量流量,给重要用户开放大量流量。接口:给初级开放初级接口,给重要用户开放高级接口。运维监控阶段运维监控阶段要维护和监控实例的运行状态,对 API 的调用量、错误分布、响应时间、流量大小等维度进行监控。通过对接口的运维监控,可以调整实例的服务质量,如流量大小、访问限制等,还可以分析接口压力,调整服务资源。版本管理与弃用阶段版本管理是指添加新版 API、删除旧的 API、为版本标记语义化版本号等工作。弃用是指将某版本的 API 标记为弃用。由于服务的迭代更新,原来的 API 不再适应需求时,须需要进行版本管理或弃用。API 的订阅者收到版本变化的消息后,可以重新决定如何使用该系列接口。API 全生命周期管理最佳实践Erda 作为新一代企业级云原生 PaaS 平台,一直坚定地走在这条道路上,为企业提供符合标准并且值得信赖的 API 管理产品。Erda API 管理产品形态如图所示,是一个以 API 集市为中心的,包含 API 设计、API 访问管理等贯穿 API 全生命周期的产品矩阵。API 管理产品构成API 设计中心Erda Cloud API 设计中心基于可视化的编辑方式,通过直观而友好的交互界面,用户无需了解任何 REST API 规范标准,也无需具备任何关于 API 描述语言的知识,就可以轻松编写出一份具有专业水平的 API 文档。同时采用 OpenAPI 3.0 协议标准,任何时候都可以交付、迁出文档,一次设计,随处使用;在其他平台托管的 API 文档、代码中生成的 Swagger 文件等,也都能轻松迁移上来。Erda API 设计中心将 API 文档托管到代码仓库中,这一设计使得接口描述和接口实现代码关联在一起。开发人员进入代码仓库,选择对应的代码分支,维护接口文档,可以很好地保持文档和新开发功能的同步。这样的理念遵循了 GitOps 配置即代码的思想。文档托管到仓库中,还意味着可以基于分支进行文档协作。不同用户编写同一篇文档时,只要从源分支切出新的分支,在新的分支上编辑文档,然后再进行分支的合并。同一服务不同接口的负责人,随时可以设计自己负责的接口,又随时合并回去,不会相互影响和阻塞。API 集市API 集市使用了语义化版本机制来实现 API 文档的版本管理。版本号格式形如 major.minor.patch ,其中:major 为主版本号,主版本号的变化通常表示发生了重大变更或不向下兼容的变更。minor 是次版本号,次版本号的变化通常表示增加了新特性,仍向下兼容。patch 是修订号,修订号的变化通常表示对现有版本作较小的、局部的修正。除了语义化版本号外,还有一个称为“版本名称”的版本标记,它一般是有自解释性的单词或短语,表示当前文档版本的命名。版本名称与语义化版本号中的 major 是唯一对应的,版本名称可以视作是主版本号 major 的别名。这样版本化管理的好处是,将 API 文档的增长与应用程序的增长一视同仁,可以从 API 的角度审视应用程序的功能。版本号解释了服务更迭间的兼容性和依赖关系,不管是所有者还是使用者,都能根据版本号语义清晰地了解服务的变更情况。API 资源可以关联到 Erda Cloud 上具体的服务实例地址。通过这样的关联,API 提供方可以进一步实现 API 的访问管理,调用方也就可以在 API 集市中申请调用并测试接口。访问管理API 提供者在集市中将 API 资源与 Erda Cloud 上具体的服务实例地址关联之后,再为 API 资源创建访问管理,调用者就可以在 API 集市中申请调用该 API;提供者收到调用申请后进行审批,为客户端设置 SLA 配额;获批的客户端获得访问资质,就可以从外部访问接口了。此后,调用方还可以在访问管理中切换 API 版本,将请求转发到不同版本对应的服务实例上,从而在客户不感知的情况下进行 API 版本的升级或回滚。API 访问管理的功能都是基于 Erda 云原生网关产品的能力实现的,相比直接使用网关的配置能力,使用 API 访问管理简化了很多 —— 用户仅仅跟 API 打交道。基于 Erda Cloud 的 API 管理产品,企业可以实现 API 全生命周期管理的最佳实践。如下图所示,可以分别从 API 的提供者和调用方两个视角来看待API 管理这件事:API 所有者(左)和使用者(右)的视角看 API 管理从 API 提供者的视角来看:首先需要跟随服务功能变更,及时更新 API 设计中心的文档,因为文档也基于代码仓库管理,可以通过 Code Review 的方式确保 API 文档的及时同步。在开发联调阶段,API 提供者可以将 API 文档发布到集市,依赖此接口的其他模块功能就可以并行开发。如果有 API 对外开放的需求,API 提供者就为对应的 API 资源设置访问管理功能,在访问管理控制台可以实时观测外部的调用流量。从 API 调用方的视角来看:如果是测试工程师,应该基于开发人员提供的 API 文档,进行自动化接口测试用例的设计,而不是维护一份测试专用的接口文档。如果是外部集成方,通过 API 集市去发现所需的功能接口,申请调用成功后,应该在 API 集市进行简单的接口访问测试,确认功能符合预期;然后根据 API 文档进行集成模块的代码编写、部署;最后可以在 “我的访问” 中查看调用流量。软件在自己的生命周期里不断迭代变化,API 也是一样。无论 API 提供者还是调用方,都要重视 API 迭代的影响。提供方要严格遵循 API 集市的语义化版本机制,当出现 Breaking Change 时,应该为新的 Major 版本创建独立的访问管理入口,并将旧版本标记弃用,引导调用方使用新的版本;调用方应该及时关注订阅通知,了解所使用 API 文档的最新版本情况。Erda Cloud 的 DevOps 功能提供了云原生场景下 CI/CD 能力,应该把 API 管理也视作 CI/CD 的一部分。可以使用 Erda Cloud 的自动化测试平台,对接 API 集市,在 CI 流程中加入自动化接口测试;可以使用 Erda 的流水线扩展,在 CD 流程后自动发布 API 版本,并自动关联上服务的 Kubernetes Service 地址。Erda Cloud 基于云原生为企业系统架构提供了一站式 PaaS 服务,Erda Cloud 的 API 管理亦是在云原生的土壤上自然生长出的产品。API 全生命周期管理作为企业数字化的关键一环,企业如果采用云原生的架构,一定要选择与之契合的 API 管理产品,否则可能导致适配成本的增加和管理效率的低下。更多技术干货请关注【尔达 Erda】公众号,与众多开源爱好者共同成长~
2022年08月
2022年07月
2022年06月
2022年05月
2022年04月
2022年03月
2022年02月
2022年01月
2021年12月
2021年11月
2021年10月