Skip to main content

· 16 分钟阅读

随着Choerodon猪齿鱼的不断迭代更新,它已经被越来越多的用户开始在项目管理和开发中使用,成为了开发团队的一部分。

这个过程中,有很多用户向团队提出一些关于敏捷管理上的问题,或者想了解猪齿鱼敏捷团队是怎么来进行项目管理的。

今天就来聊一聊这方面,以下内容请使用敏捷管理的项目经理或产品经理务必仔细阅读。

本文将以敏捷管理这个子产品团队为例,由敏捷管理的产品负责人亲自讲述,希望能给大家提供一些参考和帮助,从而改善团队协作,提升团队交付价值和开发效率。

团队组成

猪齿鱼旨在帮助团队进行敏捷化的应用交付和自动化的运营管理,由敏捷、测试、CI/CD、开发流水线、知识管理等多个子产品组成,除了各个子产品团队团队还有架构组这样的基础服务团队。每个团队根据开发工作量由6-10人构成。

敏捷管理团队从组建以来一直保持8-10人的规模,目前有6名开发人员(2前端,4后端)、1名产品负责人(PO)、1名UI设计师,所有人都全职投入在这个项目中,基本上不会有跨团队的情况发生。

开发节奏

整个猪齿鱼产品的所有团队都保持在同一个开发节奏上,2周一个迭代,2个迭代加上1周的持续改进,这样5周一个版本的速率稳定向前更新。

有人会问:为什么是2周而不是1周一个迭代呢?经过团队长期的验证,2周的时间可以开发一些较为复杂的需求,并且还能完成测试验收。

当然,没有准确的标准说1周好还是2周好,这个可以通过团队的磨合,不断进行调整。

执行

团队组建好了,开发节奏也达成了一致。那么每个迭代都有什么工作要做?什么先做什么后做?谁来做?这些问题,敏捷管理子产品团队是这样来进行的:

新迭代开始前

之前的文章中,提到在SCRUM开发过程中涉及了很多会议,在一个迭代真正开始开发前,有一个重要的会议——迭代计划会,来讨论说明下个迭代的开发内容。

敏捷管理团队是每个迭代的第一天上午,召集团队全员,找一个相对安静的地方,大家坐在一起花费2-3个小时的时间进行计划会议。

会议上大家会打开下个迭代整理的待办事项,结合之前确认的UI界面,由PO给开发团队描述这些用户故事。过程中根据PO的功能描述,团队成员提出自己的疑问,在相互的反馈沟通中达成共识。最后给每个用户故事指派一个主要负责人,并对这个故事进行估算,将前后端的工作量进行统计,得出一个故事点。这个故事就结束了,进入下一个故事的讨论。

图为小组计划会现场

有可能你们会问,计划好的用户故事一定要在这个迭代完成吗?如果做不完怎么办呢?你们会有这样的情况吗?

当然有,敏捷小组是这样处理的:

大家把所有故事讨论完后,评估发现需要100个故事点(半天=1个点),超过了以往的迭代速率(80个故事点)。此时先把所有的问题按优先级排列,由PO把当前优先级最低的1个故事或几个故事(故事点加起来大约在10个左右)从未开启的迭代列表中移出。现在团队计划的故事中还有10个故事点是大于迭代速率的,这时PO可能会和开发团队商量:是否能尝试在这个迭代中完成90个故事点,如果达成一致,这时候迭代速率就从80个变为90个了。

当然,还有一种保守的做法:继续减少排列靠后的故事。不过,大家得遵循一个原则,减少掉的这个故事得是较为独立的,且与该迭代中其他的故事没有依赖关系,或者关系不大。

故事评估完后,将在每个故事的经办人处指定主要开发责任人。会后,由负责人带领参与这个故事的开发人员一同进行故事的拆分,并将拆分的任务以子任务问题类型创建在对应的故事下。

随后开启这个冲刺,正式进入该迭代的开发阶段。

▷ 系统工具:敏捷管理待办事项模块、sketch(UI原型设计) ▷ 物理工具:大显示屏(讨论时投放)

迭代中

团队在开发阶段中时,每个人都按之前领取的任务的优先级进行开发工作。期间,对于功能不确定的地方需要及时与PO沟通,甚至直接与提出需求的用户沟通。

SCRUM流程中还有一个大家都很熟悉的会议——每日站会。在开发阶段,每天早上敏捷管理团队都会举行10-15分钟的站会,会上只抛出遇到的问题,不对复杂的问题给出结论,会后再由开发人员与PO一同讨论作出决定。

▷ 系统工具:猪齿鱼敏捷管理活跃冲刺模块

图为每日站会

那么迭代中,开发人员进行代码的编写,其他的成员干什么呢?

▌工作1:整理下个迭代的内容

PO将下个迭代需要进行的需求进行拆分,以用户故事的方式进行描述,创建在backlog中。这时,PO与UI会就这些故事开始讨论,PO描述故事所要达成的功能,UI根据功能描述尽快出具高保真原型图。(在产品初期的迭代,PO也出具简单的原型图,用户确认后,再由UI出具高保真原型图。随着迭代的持续演进,逐渐弱化了PO出图这一环节,更多的关注在沟通、确认和产品体验上。)

在这个过程中,有一些不确定的问题及时与该功能模块较为熟悉的开发人员进行简单沟通,如可执行性、是否存在前置条件等等,这些问题不会花费开发人员太多的时间,他们的重点还是在当前迭代的任务中。确定了基本的功能设计,UI就可以进行出图工作。

UI出图

UI设计完成后,首先与PO进行沟通调整,再邀请猪齿鱼产品经理或者用户参与最后方案的确认。会后PO与UI一同将反馈的修改意见进行优化调整,然后将这些原型图与相关的故事关联,以便后续开发人员有针对性的查看,另一方面也是一种存档。

故事的详细描述和原型图

▌工作2:编写测试用例

一旦功能确认后,测试人员或者PO需要开始编写这个迭代各个功能点的测试用例。比如这个迭代的功能均属于1.0版本发布的,PO可以在猪齿鱼测试管理找到0.9版本的用例,将其克隆一份到1.0版本,再针对新增的功能在1.0下创建新的用例,对于变更的功能找到之前的用例,在此基础上进行修改。最后,继续测试计划,指派测试人员。

▷ 系统工具:猪齿鱼测试管理

每个版本的测试用例管理

▌工作3:按故事进行测试

在团队自己的活跃冲刺看板中,有一列“验证测试”。团队成员达成共识:当卡片流动到这一列时,默认开发已经完成并且测试通过,这时PO和UI可以针对故事进行多方面测试,有功能的、样式的。一旦发现问题,若与开发人员确定是bug,则将相关的子任务打回给开发人员,然后创建缺陷,随即关联该故事或子任务。

所以开发人员在迭代中除了功能开发,还要进行bug修复。只有当bug验证通过后,这个故事才能算开发完成,并拖动到看板的已完成列。

活跃冲刺待测试问题

团队所有成员在迭代后期协作完成的工作内容——针对测试用例进行全量测试

之前提到过,猪齿鱼产品的节奏是2周一个迭代,2个迭代一个版本。通常在第一个迭代,团队的开发任务会计划到第2周的周四,这个时候只是PO做增量测试,预留1天修改bug。而第二个迭代往往只会计划到第2周的周三,因为在剩下的2天时间,是全员一起对整个敏捷管理做全量测试和bug修复。

也许你们会问,开发人员也参与测试吗?答案是:是的,并且是交叉测试。

猪齿鱼测试管理,支持在每个用例的步骤创建bug,并可以直接关联到当前冲刺,显示在看板上,这时相关的开发人员会停下手头的测试工作优先修复bug。修复完后,各自负责对自己提出的bug进行回归测试,随后做上线前的准备。

在实际的开发工作中,总是不会那么理想。比如,距离上线发布还有2天,仍然有开发任务没有完成,且不是几个小时就可以完成的。这时,PO需要做的决定是果断放弃还没有做完的任务,投入测试中。而不是推迟发布时间,更不是缩短测试的时间,甚至不做测试去开发功能。因为大家一直遵循着敏捷的思想,交付的产品功能一定是有价值的,是可用的。

在敏捷管理团队的日常项目管理中,还有很多其他的环节,比如评审会、回顾会、技术研讨会、技术分享、论坛需求评估以及回复、与其他团队沟通讨论等等,后续再跟大家分享。

猪齿鱼敏捷小组可能不是敏捷项目管理的最佳实践,每个环节也不可能适用于一个团队的整个项目管理生命周期,更不可能适合每个团队。但大家需要勇敢去尝试,经过一段时间的磨合,通过不断的调整,相信总能找到适合于自己团队的敏捷管理方法。

希望那些想尝试敏捷转型、还在敏捷中摸索的团队能获得一些参考。更希望猪齿鱼敏捷管理能帮助大家改善团队,提升效率,交付更多的价值。

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 17 分钟阅读

“我们使用敏捷开发。”在与软件开发团队交流时,你会听到很多这样的说法。根据统计,2018年全球约有90%的开发人员在使用敏捷开发。Choerodon猪齿鱼团队也是其中之一。

但是,敏捷并不统一。作为组织工作流程的一般方法,敏捷软件开发设定了共同的价值观和原则,旨在精简开发流程,敏捷有效地响应变化。这些价值观和原则可以在敏捷宣言中找到,当中就提供了一些建立开发流程的建议。

在实际应用中,几种软件开发框架已经体现了这些敏捷原则。其中Kanban 和 Scrum是最受欢迎和经常使用的。这两种方法都有一个共同的目标,即创建一个有效的工作流程。今天这篇文将围绕他们之间的差异进行讨论。

Scrum和Kanban的基础知识

在深入研究Scrum和Kanban之间的差异之前,先看一下两个框架的主要概念,以便之后可以更轻松地进行Kanban与Scrum的比较。虽然它们都旨在建立一个自组织团队的流动,但是有些方法不同。

▌Scrum是什么?

Scrum的名字来自橄榄球术语,表示“争球”的动作。在软件开发中,Scrum是指采取组织团队合作的方法,以便更高效地开发复杂的软件产品。

Scrum基于开发团队在开始时不知道项目的结果这个假设,他们会随着工作的进展不断学习和适应。Scrum旨在通过每次迭代开始时重置优先级来简化这种适应,这在Scrum术语中称为“Sprint”。

在这里,大家来看一个Scrum的核心概念——冲刺(Sprint),冲刺是用2到4周的时间来完成一定量的工作。Sprint有助于将项目范围分解为更容易管理的任务,并更频繁地交付组件正常运行的软件。

在冲刺阶段,开发团队只需专注于每个sprint中应完成的任务,这为项目规划阶段提供了极大的灵活性。新冲刺开始时,开发团队会重新规划本次冲刺的任务,规划时需要考虑到上个冲刺任务的完成情况以及项目的新需求。

▌Kanban是什么?

Kanban最先由Toyota 提出,旨在优化其工厂库存。在日语中,“Kanban”是指板或卡。在最初的应用中,一个库存越来越少的工厂部门会向仓库发送“Kanban”来请求补货。然后,仓库将“Kanban”发送给供应商以订购更多库存。

从这个例子中,大家可以看到Kanban专注于当前容量,这也是用在软件开发的主要概念。与Scrum不同,Kanban没有时间限制,相反,它限制了可以同时执行的工作量。

看板的主要指标之一是“正在进行的工作” 。Kanban表示,为了实现最高效率,正在进行的工作应进行限制以与团队的能力相匹配,从而降低任何出现瓶颈的风险。

看板也能很好地响应变化,因为可以在项目的任何阶段进行更改并添加到要执行的任务列表中。

Choerodon猪齿鱼团队就是使用敏捷Kanban方法来提升交付效率,具体如何使用可参考Choerodon猪齿鱼博客文章:猪齿鱼团队如何使用敏捷Kanban方法提升交付效率

Scrum与Kanban的主要区别

如果想比较Scrum和Kanban,大家需要看两个框架组织工作流的方式以及它们使用的主要实例和定义。

角色

角色的分配是Scrum和Kanban之间的第一个重大区别。在Scrum中,团队主要分三个角色:

  • 产品负责人:负责产品的人。产品负责人分析客户对产品的需求,并将其转化为团队的任务。产品负责人还确定任务的优先级,并决定何时可以发布特定的功能组件。

  • Scrum Master:负责Scrum过程的人。首先,Scrum Masters将Scrum原则引入其团队成员并协助他们实施。此外,Scrum Masters管理冲刺所需的人力资源分配。

  • 开发团队:负责实际开发工作的人。由具备自我管理能力的人组成的跨职能团队。

反过来,Kanban对团队角色没有严格的要求,可能有一个产品负责人监督项目中积压的任务,但除此之外,团队是自组织的。

工作流程

正如上文提到的,Scrum开发在迭代中进行,定义了每次迭代中要完成的工作,而Kanban旨在限制当前正在进行的工作,没有特定的时间限制。下面列出这两种方法在实际应用中的含义。

▌Scrum流程

项目规划从定义项目待办事项开始,即为了交付产品而需要完成的用户故事列表。在这种情况下,Scrum使用以下主要概念来帮助使用者理解工作的计划和分配方式:

Product backlog:代表团队的主要“待办事项”列表。Product Backlog包括项目中需要完成的所有功能和bug修复。待办事项列表根据新需求或检测到的错误而不断更新。产品负责人负责Product backlog的工作,以便客户的反馈和建议与团队的工作进度保持同步。Product backlog的一些任务可以根据优先级排序,一些可以在需求改变时添加,一些可以删除。

Sprint backlog:要在冲刺中完成的任务清单。Sprint backlog的任务需要在sprint结束时交付已完成的功能或组件。虽然sprint backlog也允许一定的灵活性和修改,但sprint的目标应该保持不变,并且应该将更改保持在最低限度。

increment:sprint结束后可交付的可用产品。通常,sprint以已完成的功能或组件的演示为结束。在这方面,一个重要的概念是“完成”,它指的是每个用户故事都要考虑其完整性。“完成”的定义可能根据用户故事而有所不同:它可能包括多个任务,例如开发,测试,设计,文档和演示,也可能涉及不同的团队成员。

每个sprint都从规划阶段开始,在该阶段中计划下一个sprint的任务。对于规划,通常会有整个团队,包括产品负责人和Scrum Master在场。团队决定在sprint结束时他们可以提供什么,并从Product backlog中选择相应的用户故事。这样就形成了Sprint backlog 。

在冲刺期间,团队每天会开“daily scrum”(即每日站立会议),讨论他们的进展以及他们可能遇到的问题。站立会议的目的是尽早发现问题并快速找到解决方案,以免破坏冲刺流程。

在冲刺之后,利益相关者将审查完成的功能。在sprint review期间 ,团队有机会收到有关其工作的反馈或变更建议。

与此同时,团队进行sprint retrospective meeting(回顾会议),分析他们所完成的冲刺并找到需要改进的问题。回顾之后,重置过程,新的sprint从规划阶段开始。

▌Kanban流程

在Kanban中,没有规定时间段来完成一定量的工作,相反,Kanban专注于平衡团队的能力与当前正在进行的工作。

Kanban流程从待办事项清单开始,包括应该完成的所有任务。每个团队成员从待办事项中领取一个任务,并专注于完成它。任务完成后,成员选择下一个,依此类推,直到待办事项完成为止。待办事项按照优先级排序,最紧急的任务放在最顶层,由团队优先选择。

在Kanban中, 项目期间正在进行的工作量不超过团队的能力这点至关重要 。即kanban中的限制在制品原则。出于这个目的,可以根据成员工作能力为任何类型的工作设置限制。

产品负责人可以根据需要尽可能多地设置和更改待办事项中的优先级,因为backlog management对团队的绩效没有影响。团队只需要关心正在进行的工作,只有在当前任务完成后才返回待办事项。

每项任务都沿着“待办事项” - “正在进行的工作” - “完成”路线行进。当然,Kanban也支持“完成”概念(即每个任务被接受的标准)的自定义。

最终,完成的任务形成完成的组件,可以计算交付它们所需的时间。在Kanban中,它被称为 “cycle time”,周期的计算能提供许多优化点。当然,所有团队都在努力缩短周期,并寻找解决瓶颈的方法(如果有的话)。

在Choerodon猪齿鱼中,可对任务进行故事点与时间的评估,并在燃尽图中能很好的看到迭代工作时间的预期值与剩余值。

在这种情况下,让团队成员具有重叠技能至关重要。如果只有一个人拥有某种技能,例如如果你只有一个测试人员,那就是瓶颈,所有测试任务将排队等待产品交付过程中的延迟。

总而言之,Scrum目标是在指定时间内完成预定工作,而Kanban监控以确保正在进行的工作永远不会超过设定限制。

选择哪一个?

如果您一直在等待这个问题的确定答案,答案可能会让您失望。到目前为止,本文列出这两种方法都有其优点,并且两者都有助于建立敏捷开发流程。下面将提供了一些指南,可以帮助您选择最适合您团队的方法。

使用Scrum:

  • 你可以相对轻松地将工作范围划分为可在两周内完成的逻辑块。
  • 你需要对整个项目具有高度的可预测性。Scrum专注于将sprint中的更改保持在最低限度。
  • 团队中有很多新成员。使用Scrum,他们将更容易理解团队纪律并进行改进。

使用Kanban:

  • 你希望项目中有很多频繁的更改。
  • 很难离析出可在两周内交付的产品组件。
  • 你的团队训练有素,可以信任地在没有严格截止日期的情况下安排他们的活动。

然而,好消息是你可以随时结合!甚至还有一种名为 Scrumban 的方法,其中包含了Scrum和Kanban的方法。在Scrumban,你可以在短期迭代中完成工作,并将进行中的任务数量保持在一定限度内,一旦进行中的任务低于限度时会触发新的迭代。

如你所见,选择项目管理方法可以像你希望的那样灵活和自由。没有固定规则,您可以根据自己的项目进行调整,混合和匹配。实际上,选择过程中的主要标准应始终是您的项目成功和团队对工作流程的满意度。

Choerodon猪齿鱼团队就结合了Scrum和Kanban方法,关于团队的敏捷实践相关信息,组织Sprint计划会议、每日站立会议、评审会、回顾会等敏捷会议可参考Choerodon猪齿鱼敏捷管理实践(三):敏捷会议,结合Choerodon平台敏捷管理模块进行冲刺管理可参考Choerodon猪齿鱼敏捷管理实践(二)——冲刺管理

本文译者 | 林岩芳 原文地址:https://da-14.com/blog/kanban-vs-scrum-choosing-best-agile-project-management-framework

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 26 分钟阅读

React在前端界一直很流行,而且学起来也不是很难,只需要学会JSX、理解StateProps,然后就可以愉快的玩耍了,但想要成为React的专家你还需要对React有一些更深入的理解,希望本文对你有用。

这是Choerodon的一个前端页面

在复杂的前端项目中一个页面可能包含上百个状态,对React框架理解得更精细一些对前端优化很重要。曾经这个页面点击一条记录展示详情会卡顿数秒,而这仅仅是前端渲染造成的。

为了能够解决这些问题,开发者需要了解React组件从定义到在页面上呈现(然后更新)的整个过程。

React在编写组件时使用混合HTMLJavaScript的一种语法(称为JSX)。 但是,浏览器对JSX及其语法一无所知,浏览器只能理解纯JavaScript,因此必须将JSX转换为HTML。 这是一个div的JSX代码,它有一个类和一些内容:

<div className='cn'>
文本
</div>

在React中将这段jsx变成普通的js之后它就是一个带有许多参数的函数调用:

React.createElement(
'div',
{ className: 'cn' },
'文本'
);
React.createElement(
'div',
{ className: 'cn' },
['Content 1!', React.createElement('br'), 'Content 2!']
)
它的第一个参数是一个字符串,对应html中的标签名,第二个参数是它的所有属性所构成的对象,当然,它也有可能是个空对象,剩下的参数都是这个元素下的子元素,这里的文本也会被当作一个子元素,所以第三个参数是 `“文本”`

到这里你应该就能想象这个元素下有更多`children`的时候会发生什么。
```html
<div className='cn'>
文本1
<br />
文本2
</div>
React.createElement(
'div',
{ className: 'cn' },
'文本1', // 1st child
React.createElement('br'), // 2nd child
'文本1' // 3rd child
)

目前的函数有五个参数:元素的类型,全部属性的对象和三个子元素。 由于一个child也是React已知的HTML标签,因此它也将被解释成函数调用。

到目前为止,本文已经介绍了两种类型的child参数,一种是string纯文本,一种是调用其他的React.createElement函数。其实,其他值也可以作为参数,比如:

  • 基本类型 false,null,undefined和 true
  • 数组
  • React组件

使用数组是因为可以将子组件分组并作为一个参数传递:


当然,React的强大功能不是来自`HTML`规范中描述的标签,而是来自用户创建的组件,例如:

```js
function Table({ rows }) {
return (
<table>
{rows.map(row => (
<tr key={row.id}>
<td>{row.title}</td>
</tr>
))}
</table>
);
}

组件允许开发者将模板分解为可重用的块。在上面的“纯函数”组件的示例中,组件接受一个包含表行数据的对象数组,并返回React.createElement对table元素及其行作为子元素的单个调用 。

每当开发者将组件放入JSX布局中时它看上去是这样的:

<Table rows={rows} />

但从浏览器角度,它看到的是这样的:

React.createElement(Table, { rows: rows });

请注意,这次的第一个参数不是以string描述的HTML元素,而是组件的引用(即函数名)。第二个参数是传入该组件的props对象。

将组件放在页面上

现在,浏览器已经将所有JSX组件转换为纯JavaScript,现在浏览器获得了一堆函数调用,其参数是其他函数调用,还有其他函数调用......如何将它们转换为构成网页的DOM元素?

为此,开发者需要使用ReactDOM库及其render方法:

function Table({ rows }) { /* ... */ } // 组件定义

// 渲染一个组件
ReactDOM.render(
React.createElement(Table, { rows: rows }), // "创建" 一个 component
document.getElementById('#root') // 将它放入DOM中
);

ReactDOM.render被调用时,React.createElement最终也会被调用,它返回以下对象:

// 这个对象里还有很多其他的字段,但现在对开发者来说重要的是这些。
{
type: Table,
props: {
rows: rows
},
// ...
}

这些对象构成了React意义上的Virtual DOM

它们将在所有进一步渲染中相互比较,并最终转换为真正的DOM(与Virtual DOM对比)。

这是另一个例子:这次有一个div具有class属性和几个子节点:

React.createElement(
'div',
{ className: 'cn' },
'Content 1!',
'Content 2!',
);

变成:

{
type: 'div',
props: {
className: 'cn',
children: [
'Content 1!',
'Content 2!'
]
}
}

所有的传入的展开函数,也就是React.createElement除了第一第二个参数剩下的参数都会在props对象中的children属性中,不管传入的是什么函数,他们最终都会作为children传入props中。

而且,开发者可以直接在JSX代码中添加children属性,将子项直接放在children中,结果仍然是相同的:

<div className='cn' children={['Content 1!', 'Content 2!']} />

在Virtual DOM对象被建立出来之后ReactDOM.render会尝试按以下规则把它翻译成浏览器能够看得懂的DOM节点:

  • 如果Virtual DOM对象中的type属性是一个string类型的tag名称,创建一个tag,包含props里的全部属性。
  • 如果Virtual DOM对象中的type属性是一个函数或者class,调用它,它返回的可能还是一个Virtual DOM然后将结果继续递归调用此过程。
  • 如果props中有children属性,对children中的每个元素进行以上过程,并将返回的结果放到父DOM节点中。

最后,浏览器获得了以下HTML(对于上述table的例子):

<table>
<tr>
<td>Title</td>
</tr>
...
</table>

重建DOM

接下浏览器要“重建”一个DOM节点,如果浏览器要更新一个页面,显然,开发者并不希望替换页面中的全部元素,这就是React真正的魔法了。如何才能实现它?先从最简单的方法开始,重新调用这个节点的ReactDOM.render方法。

// 第二次调用
ReactDOM.render(
React.createElement(Table, { rows: rows }),
document.getElementById('#root')
);

这一次,上面的代码执行逻辑将与看到的代码不同。React不是从头开始创建所有DOM节点并将它们放在页面上,React将使用“diff”算法,以确定节点树的哪些部分必须更新,哪些部分可以保持不变。

那么它是怎样工作的?只有少数几个简单的情况,理解它们将对React程序的优化有很大帮助。请记住,接下来看到的对象是用作表示React Virtual DOM中节点的对象。

▌Case 1:type是一个字符串,type在调用之间保持不变,props也没有改变。

// before update
{ type: 'div', props: { className: 'cn' } }

// after update
{ type: 'div', props: { className: 'cn' } }

这是最简单的情况:DOM保持不变。

▌Case 2:type仍然是相同的字符串,props是不同的。

// before update:
{ type: 'div', props: { className: 'cn' } }

// after update:
{ type: 'div', props: { className: 'cnn' } }

由于type仍然代表一个HTML元素,React知道如何通过标准的DOM API调用更改其属性,而无需从DOM树中删除节点。

▌Case 3:type已更改为不同的组件String或从String组件更改为组件。

// before update:
{ type: 'div', props: { className: 'cn' } }

// after update:
{ type: 'span', props: { className: 'cn' } }

由于React现在看到类型不同,它甚至不会尝试更新DOM节点:旧元素将与其所有子节点一起被删除(unmount)。因此,在DOM树上替换完全不同的元素的代价会非常之高。幸运的是,这在实际情况中很少发生。

重要的是要记住React使用===(三等)来比较type值,因此它们必须是同一个类或相同函数的相同实例。

下一个场景更有趣,因为这是开发者最常使用React的方式。

▌Case 4:type是一个组件。

// before update:
{ type: Table, props: { rows: rows } }

// after update:
{ type: Table, props: { rows: rows } }

你可能会说,“这好像没有任何变化”,但这是不对的。

如果type是对函数或类的引用(即常规React组件),并且启动了树diff比较过程,那么React将始终尝试查看组件内部的所有child以确保render的返回值没有更改。即在树下比较每个组件 - 是的,复杂的渲染也可能变得昂贵!

组件中的children

除了上面描述的四种常见场景之外,当元素有多个子元素时,开发者还需要考虑React的行为。假设有这样一个元素:

// ...
props: {
children: [
{ type: 'div' },
{ type: 'span' },
{ type: 'br' }
]
},
// ...

开发者开发者想将它重新渲染成这样(spandiv交换了位置):

// ...
props: {
children: [
{ type: 'span' },
{ type: 'div' },
{ type: 'br' }
]
},
// ...

那么会发生什么?

当React看到里面的任何数组类型的props.children,它会开始将它中的元素与之前看到的数组中的元素按顺序进行比较:index 0将与index 0,index 1与index 1进行比较,对于每对子元素,React将应用上述规则集进行比较更新。在以上的例子中,它看到div变成一个span这是一个情景3中的情况。但这有一个问题:假设开发者想要从1000行表中删除第一行。React必须“更新”剩余的999个孩子,因为如果与先前的逐个索引表示相比,他们的内容现在将不相等。

幸运的是,React有一种内置的方法来解决这个问题。如果元素具有key属性,则元素将通过key而不是索引进行比较。只要key是唯一的,React就会移动元素而不将它们从DOM树中移除,然后将它们放回(React中称为挂载/卸载的过程)。

// ...
props: {
children: [ // 现在react就是根据key,而不是索引来比较了
{ type: 'div', key: 'div' },
{ type: 'span', key: 'span' },
{ type: 'br', key: 'bt' }
]
},
// ...

当状态改变时

到目前为止,本文只触及了props,React哲学的一部分,但忽略了state。这是一个简单的“有状态”组件:

class App extends Component {
state = { counter: 0 }

increment = () => this.setState({
counter: this.state.counter + 1,
})

render = () => (<button onClick={this.increment}>
{'Counter: ' + this.state.counter}
</button>)
}

现在,上述例子中的state对象有一个counter属性。单击按钮会增加其值并更改按钮文本。但是当用户点击时,DOM会发生什么?它的哪一部分将被重新计算和更新?

调用this.setState也会导致重新渲染,但不会导致整个页面重渲染,而只会导致组件本身及其子项。父母和兄弟姐妹都可以幸免于难。

修复问题

本文准备了一个DEMO,这是修复问题前的样子。你可以在这里查看其源代码。不过在此之前,你还需要安装React Developer Tools

打开demo要看的第一件事是哪些元素以及何时导致Virtual DOM更新。导航到浏览器的Dev Tools中的React面板,点击设置然后选择“Highlight Updates”复选框:

现在尝试在表中添加一行。如你所见,页面上的每个元素周围都会出现边框。这意味着每次添加行时,React都会计算并比较整个Virtual DOM树。现在尝试按一行内的计数器按钮。你将看到Virtual DOM如何更新 (state仅相关元素及其子元素更新)。

React DevTools暗示了问题可能出现的地方,但没有告诉开发者任何细节:特别是有问题的更新是指元素“diff”之后有不同,还是组件被unmount/mount了。要了解更多信息,开发者需要使用React的内置分析器(请注意,它不能在生产模式下工作)。

转到Chrome DevTools中的“Performance”标签。点击record按钮,然后点击表格。添加一些行,更改一些计数器,然后点击“Stop”按钮。稍等一会儿之后开发者会看到:

在结果输出中,开发者需要关注“Timing”。缩放时间轴,直到看到“React Tree Reconciliation”组及其子项。这些都是组件的名称,旁边有[update][mount]。可以看到有一个TableRow被mount了,其他所有的TableRow都在update,这并不是开发者想要的。

大多数性能问题都由[update][mount]引起

一个组件(以及组件下的所有东西)由于某种原因在每次更新时重新挂载,开发者不想让它发生(重新挂载很慢),或者在大型分支上执行代价过大的重绘,即使组件似乎没有发生任何改变。

修复mount/unmount

现在,当开发者了解React如何决定更新Virtual DOM并知道幕后发生的事情时,终于准备好解决问题了!修复性能问题首先要解决 mount/unmount。

如果开发者将任何元素/组件的多个子元素在内部表示为数组,那么程序可以获得非常明显的速度提升。

考虑一下:

<div>
<Message />
<Table />
<Footer />
</div>

在虚拟DOM中,将表示为:

// ...
props: {
children: [
{ type: Message },
{ type: Table },
{ type: Footer }
]
}
// ...

一个简单的Message组件(是一个div带有一些文本,像是猪齿鱼的顶部通知)和一个很长的Table,比方说1000多行。它们都是div元素的child,因此它们被放置在父节点的props.children之下,并且它们没有key。React甚至不会通过控制台警告来提醒开发者分配key,因为子节点React.createElement作为参数列表而不是数组传递给父节点。

现在,用户已经关闭了顶部通知,所以Message从树中删除。TableFooter是剩下的child。

// ...
props: {
children: [
{ type: Table },
{ type: Footer }
]
}
// ...

React如何看待它?它将它视为一系列改变了type的child:children[0]的type本来是Message,但现在他是Table。因为它们都是对函数(和不同函数)的引用,它会卸载整个Table并再次安装它,渲染它的所有子代:1000多行!

因此,你可以添加唯一键(但在这种特殊情况下使用key不是最佳选择)或者采用更智能的trick:使用 && 的布尔短路运算,这是JavaScript和许多其他现代语言的一个特性。像这样:

<div>
{isShowMessage && <Message />}
<Table />
<Footer />
</div>

即使Message被关闭了(不再显示),props.children父母div仍将拥有三个元素,children[0]具有一个值false(布尔类型)。还记得true/false, null甚至undefined都是Virtual DOM对象type属性的允许值吗?浏览器最终得到类似这样的东西:

// ...
props: {
children: [
false, // isShowMessage && <Message /> 短路成了false
{ type: Table },
{ type: Footer }
]
}
// ...

所以,不管Message是否被显示,索引都不会改变,Table仍然会和Table比较,但仅仅比较Virtual DOM通常比删除DOM节点并从中创建它们要快得多。

现在来看看更高级的东西。开发者喜欢HOC。高阶组件是一个函数,它将一个组件作为一个参数,添加一些行为,并返回一个不同的组件(函数):

function withName(SomeComponent) {
return function(props) {
return <SomeComponent {...props} name={name} />;
}
}

开发者在父render方法中创建了一个HOC 。当React需要重新渲染树时,React 的Virtual DOM将如下所示:

// On first render:
{
type: ComponentWithName,
props: {},
}

// On second render:
{
type: ComponentWithName, // Same name, but different instance
props: {},
}

现在,React只会在ComponentWithName上运行一个diff算法,但是这次同名引用了一个不同的实例,三等于比较失败,必须进行完全重新挂载。注意它也会导致状态丢失,幸运的是,它很容易修复:只要返回的实例都是同一个就好了:

// 单例
const ComponentWithName = withName(Component);

class App extends React.Component() {
render() {
return <ComponentWithName />;
}
}

修复update

现在浏览器已经确保不会重新装载东西了,除非必要。但是,对位于DOM树根目录附近的组件所做的任何更改都将导致其所有子项的进行对比重绘。结构复杂,价格昂贵且经常可以避免。

如果有办法告诉React不要查看某个分支,那将是很好的,因为它没有任何变化。

这种方式存在,它涉及一个叫shouldComponentUpdate的组件生命周期函数。React会在每次调用组件之前调用此方法,并接收propsstate的新值。然后开发者可以自由地比较新值和旧值之间的区别,并决定是否应该更新组件(返回truefalse)。如果函数返回false,React将不会重新渲染有问题的组件,也不会查看其子组件。

通常比较两组propsstate一个简单的浅层比较就足够了:如果顶层属性的值相同,浏览器就不必更新了。浅比较不是JavaScript的一个特性,但开发者很多方法来自己实现它,为了不重复造轮子,也可以使用别人写好的方法

在引入浅层比较的npm包后,开发者可以编写如下代码:

class TableRow extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
const { props, state } = this;
return !shallowequal(props, nextProps)
&& !shallowequal(state, nextState);
}
render() { /* ... */ }
}

但是你甚至不必自己编写代码,因为React在一个名为React.PureComponent的类中内置了这个功能,它类似于React.Component,只是shouldComponentUpdate已经为你实现了浅层props/state比较。

或许你会有这样的想法,能替换ComponentPureComponent就去替换。但开发者如果错误地使用PureComponent同样会有重新渲染的问题存在,需要考虑下面三种情况:

<Table
// map每次都会返回一个新的数组实例,所以每次比较都是不同的
rows={rows.map(/* ... */)}
// 每一次传入的对象都是新的对象,引用是不同的。
style={ { color: 'red' } }
// 箭头函数也一样,每次都是不同的引用。
onUpdate:{() => { /* ... */ }}
/>

上面的代码片段演示了三种最常见的反模式,请尽量避免它们!

正确地使用PureComponent,你可以在这里看到所有的TableRow都被“纯化”后渲染的效果。

但是,如果你迫不及待想要全部使用纯函数组件,这样是不对的。比较两组propsstate不是免费的,对于大多数基本组件来说甚至都不值得:运行shallowCompare比diff算法需要更多时间。

可以使用此经验法则:纯组件适用于复杂的表单和表格,但它们通常会使按钮或图标等简单元素变慢。

现在,你已经熟悉了React的渲染模式,接下来就开始前端优化之旅吧。

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 15 分钟阅读

本文是从 0 到 1 使用 Kubernetes 系列第六篇,上一篇《从 0 到 1 使用 Kubernetes 系列(五):Kubernetes Scheduling》介绍了 Kubernetes 调度器如何进行资源调度,本文将为大家介绍几种常用储存类型。

默认情况下 Pod 挂载在磁盘上的文件生命周期与 Pod 生命周期是一致的,若 Pod 出现崩溃的情况,kubelet 将会重启它,这将会造成 Pod 中的文件将丢失,因为 Pod 会以镜像最初的状态重新启动。在实际应用当中,开发者有很多时候需要将容器中的数据保留下来,比如在 Kubernetes 中部署了 MySql,不能因为 MySql 容器挂掉重启而上面的数据全部丢失;其次,在 Pod 中同时运行多个容器时,这些容器之间可能需要共享文件。也有的时候,开发者需要预置配置文件,使其在容器中生效,例如自定义了 mysql.cnf 文件在 MySql 启动时就需要加载此配置文件。这些都将是今天将要实战解决的问题。

今天这篇文将讲解下面几种常用存储类型:

  • secret
  • configMap
  • emptyDir
  • hostPath
  • nfs
  • persistentVolumeClaim

SECRET

Secret 对象允许您存储和管理敏感信息,例如密码,OAuth 令牌和 ssh 密钥。将此类信息放入一个 secret 中可以更好地控制它的用途,并降低意外暴露的风险。

▌ 使用场景

鉴权配置文件挂载

▌ 使用示例

在 CI 中 push 构建好的镜像就可以将 docker 鉴权的 config.json 文件存入 secret 对象中,再挂载到 CI 的 Pod 中,从而进行权限认证。

  • 首先创建 secret
$ kubectl create secret docker-registry docker-config  --docker-server=https://hub.docker.com --docker-username=username --docker-password=password
secret/docker-config created
  • 新建 docker-pod.yaml 文件,粘贴以下信息:
apiVersion: v1
kind: Pod
metadata:
name: docker
spec:
containers:
- name: docker
image: docker
command:
- sleep
- "3600"
volumeMounts:
- name: config
mountPath: /root/.docker/
volumes:
- name: config
secret:
secretName: docker-config
items:
- key: .dockerconfigjson
path: config.json
mode: 0644
  • Docker Pod 挂载 secret
$ kubectl apply -f docker-pod.yaml
pod/docker created
  • 查看挂载效果
$ kubectl exec docker -- cat /root/.docker/config.json
{"auths":{"https://hub.docker.com":{"username":"username","password":"password","auth":"dXNlcm5hbWU6cGFzc3dvcmQ="}}}
  • 清理环境
$ kubectl delete pod docker
$ kubectl delete secret docker-config

ConfigMap

许多应用程序会从配置文件、命令行参数或环境变量中读取配置信息。这些配置信息需要与 docker image 解耦 ConfigMap API 给我们提供了向容器中注入配置信息的机制,ConfigMap 可以被用来保存单个属性,也可以用来保存整个配置文件。

▌ 使用场景

配置信息文件挂载

▌ 使用示例

使用 ConfigMap 中的数据来配置 Redis 缓存

  • 创建 example-redis-config.yaml 文件,粘贴以下信息:
apiVersion: v1
kind: ConfigMap
metadata:
name: example-redis-config
data:
redis-config: |
maxmemory 2b
maxmemory-policy allkeys-lru
  • 创建 ConfigMap
$ kubectl apply -f example-redis-config.yaml
configmap/example-redis-config created
  • 创建 example-redis.yaml 文件,粘贴以下信息:
apiVersion: v1
kind: Pod
metadata:
name: redis
spec:
containers:
- name: redis
image: kubernetes/redis:v1
env:
- name: MASTER
value: "true"
ports:
- containerPort: 6379
resources:
limits:
cpu: "0.1"
volumeMounts:
- mountPath: /redis-master-data
name: data
- mountPath: /redis-master
name: config
volumes:
- name: data
emptyDir: {}
- name: config
configMap:
name: example-redis-config
items:
- key: redis-config
path: redis.conf
  • Redis Pod 挂载 ConfigMap 测试
$ kubectl apply -f example-redis.yaml
pod/redis created
  • 查看挂载效果
$ kubectl exec -it redis redis-cli
$ 127.0.0.1:6379> CONFIG GET maxmemory
1) "maxmemory"
2) "2097152"
$ 127.0.0.1:6379> CONFIG GET maxmemory-policy
1) "maxmemory-policy"
2) "allkeys-lru"
  • 清理环境
$ kubectl delete pod redis
$ kubectl delete configmap example-redis-config

EmptyDir

当使用 emptyDir 卷的 Pod 在节点创建时,会在该节点创建一个新的空目录,只要该 Pod 运行在该节点,该目录会一直存在,Pod 内的所有容器可以将改目录挂载到不同的挂载点,但都可以读写 emptyDir 内的文件。当 Pod 不论什么原因被删除,emptyDir 的数据都会永远被删除(一个 Container Crash 并不会在该节点删除 Pod,因此在 Container crash 时,数据不会丢失)。默认情况下,emptyDir 支持任何类型的后端存储:disk、ssd、网络存储。也可以通过设置 emptyDir.medium 为 Memory,kubernetes 会默认 mount 一个 tmpfs(RAM-backed filesystem),因为是 RAM Backed,因此 tmpfs 通常很快。但是会在容器重启或者 crash 时,数据丢失。

▌ 使用场景

同一 Pod 内各容器共享存储

▌ 使用示例

在容器 a 中生成 hello 文件,通过容器 b 输出文件内容

  • 创建 test-emptydir.yaml 文件,粘贴以下信息:
apiVersion: v1
kind: Pod
metadata:
name: test-emptydir
spec:
containers:
- image: alpine
name: container-a
command:
- /bin/sh
args:
- -c
- echo 'I am container-a' >> /cache-a/hello && sleep 3600
volumeMounts:
- mountPath: /cache-a
name: cache-volume
- image: alpine
name: container-b
command:
- sleep
- "3600"
volumeMounts:
- mountPath: /cache-b
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
  • 创建 Pod
kubectl apply -f test-emptydir.yaml
pod/test-emptydir created
  • 测试
$ kubectl exec test-emptydir -c container-b -- cat /cache-b/hello
I am container-a
  • 清理环境
$ kubectl delete pod test-emptydir

HostPath

将宿主机对应目录直接挂载到运行在该节点的容器中。使用该类型的卷,需要注意以下几个方面:

  1. 使用同一个模板创建的 Pod,由于不同的节点有不同的目录信息,可能会导致不同的结果
  2. 如果 kubernetes 增加了已知资源的调度,该调度不会考虑 hostPath 使用的资源
  3. 如果宿主机目录上已经存在的目录,只可以被 root 可以写,所以容器需要 root 权限访问该目录,或者修改目录权限

▌ 使用场景

运行的容器需要访问宿主机的信息,比如 Docker 内部信息/var/lib/docker 目录,容器内运行 cadvisor,需要访问/dev/cgroups

▌ 使用示例

使用 Docker socket binding 模式在列出宿主机镜像列表。

  • 创建 test-hostpath.yaml 文件,粘贴以下信息:
apiVersion: v1
kind: Pod
metadata:
name: test-hostpath
spec:
containers:
- image: docker
name: test-hostpath
command:
- sleep
- "3600"
volumeMounts:
- mountPath: /var/run/docker.sock
name: docker-sock
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
type: Socket
  • 创建 test-hostpath Pod
$ kubectl apply -f test-hostpath.yaml
pod/test-hostpath created
  • 测试是否成功
$ kubectl exec test-hostpath docker images
REPOSITORY IMAGE ID CREATED SIZE
docker 639de9917ae1 13 days ago 171MB
...

NFS 存储卷

NFS 卷允许将现有的 NFS(网络文件系统)共享挂载到您的容器中。不像 emptyDir,当删除 Pod 时,nfs 卷的内容被保留,卷仅仅是被卸载。这意味着 nfs 卷可以预填充数据,并且可以在 pod 之间共享数据。 NFS 可以被多个写入者同时挂载。

  • 重要提示:您必须先拥有自己的 NFS 服务器然后才能使用它。

▌ 使用场景

不同节点 Pod 使用统一 nfs 共享目录

▌ 使用示例

  • 创建 test-nfs.yaml 文件,粘贴以下信息:
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-nfs
spec:
selector:
matchLabels:
app: store
replicas: 2
template:
metadata:
labels:
app: store
spec:
volumes:
- name: data
nfs:
server: nfs.server.com
path: /
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- store
topologyKey: "kubernetes.io/hostname"
containers:
- name: alpine
image: alpine
command:
- sleep
- "3600"
volumeMounts:
- mountPath: /data
name: data
  • 创建测试 deployment
$ kubectl apply -f test-nfs.yaml
deployment/test-nfs created
  • 查看 pod 运行情况
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
test-nfs-859ccfdf55-kkgxj 1/1 Running 0 1m 10.233.68.245 uat05 <none>
test-nfs-859ccfdf55-aewf8 1/1 Running 0 1m 10.233.67.209 uat06 <none>
  • 进入 Pod 中进行测试
# 进入uat05节点的pod中
$ kubectl exec -it test-nfs-859ccfdf55-kkgxj sh
# 创建文件
$ echo "uat05" > /data/uat05
# 退出uat05节点的pod
$ edit
# 进入uat06节点的pod中
$ kubectl exec -it test-nfs-859ccfdf55-aewf8 sh
# 查看文件内容
$ cat /data/uat05
uat05
  • 清理环境
$ kubectl delete deployment test-nfs

PersistentVolumeClaim

上面所有例子中我们都是直接将存储挂载到的 pod 中,那么在 kubernetes 中如何管理这些存储资源呢?这就是 Persistent Volume 和 Persistent Volume Claims 所提供的功能。

● PersistentVolume 子系统为用户和管理员提供了一个 API,该 API 将如何提供存储的细节抽象了出来。为此,我们引入两个新的 API 资源:PersistentVolume 和 PersistentVolumeClaim。

  • PersistentVolume(PV)是由管理员设置的存储,它是群集的一部分。就像节点是集群中的资源一样,PV 也是集群中的资源。 PV 是 Volume 之类的卷插件,但具有独立于使用 PV 的 Pod 的生命周期。此 API 对象包含 Volume 的实现,即 NFS、iSCSI 或特定于云供应商的存储系统。
  • PersistentVolumeClaim(PVC)是用户存储的请求。它与 Pod 相似。Pod 消耗节点资源,PVC 消耗 PV 资源。Pod 可以请求特定级别的资源(CPU 和内存)。声明可以请求特定的大小和访问模式(例如,可以以读/写一次或 只读多次模式挂载)。虽然 PersistentVolumeClaims 允许用户使用抽象存储资源,但用户需要具有不同性质(例如性能)的 PersistentVolume 来解决不同的问题。集群管理员需要能够提供各种各样的 PersistentVolume,这些 PersistentVolume 的大小和访问模式可以各有不同,但不需要向用户公开实现这些卷的细节。对于这些需求,StorageClass 资源可以实现。

● 在实际使用场景里,PV 的创建和使用通常不是同一个人。这里有一个典型的应用场景:管理员创建一个 PV 池,开发人员创建 Pod 和 PVC,PVC 里定义了 Pod 所需存储的大小和访问模式,然后 PVC 会到 PV 池里自动匹配最合适的 PV 给 Pod 使用。

▌ 使用示例

  • 创建 PersistentVolume
apiVersion: v1
kind: PersistentVolume
metadata:
name: mypv
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
storageClassName: slow
mountOptions:
- hard
- nfsvers=4.0
nfs:
path: /tmp
server: 172.17.0.2
  • 创建 PersistentVolumeClaim
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 5Gi
volumeName: mypv
  • 创建 Pod 绑定 PVC
kind: Pod
apiVersion: v1
metadata:
name: mypod
spec:
containers:
- name: myfrontend
image: nginx
volumeMounts:
- mountPath: "/var/www/html"
name: mypd
volumes:
- name: mypd
persistentVolumeClaim:
claimName: myclaim
  • 查看 pod 运行情况验证绑定结果
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
mypod 1/1 Running 0 1m 10.233.68.249 uat05 <none>
$ kubectl exec -it mypod sh
$ ls /var/www/html
  • 清理环境
$ kubectl delete pv mypv
$ kubectl delete pvc myclaim
$ kubectl delete po mypod

总结

本次实战中使用了 secret 存储 docker 认证凭据,更好地控制它的用途,并降低意外暴露的风险。

使用 configMap 对 redis 进行缓存配置,这样即使 redis 容器挂掉重启 configMap 中的配置依然会生效。接着又使用 emptyDir 来使得同一 Pod 中多个容器的目录共享,在实际应用中开发者通常使用 initContainers 来进行预处理文件,然后通过 emptyDir 传递给 Containers。然后再使用 hostPath 来访问宿主机的资源,当网路 io 达不到文件读写要求时,可考虑固定应用只运行在一个节点上然后使用 hostPath 来解决文件读写速度的要求。

NFS 和 PersistentVolumeClaim 的例子实质上都是试容器挂载的 nfs 服务器共享目录,但这些资源一般都只掌握在了管理员手中,开发人员想要获取这部分资源那么就不是这么友好了,动态存储类(StorageClass)就能很好的解决此类问题。

更多关于 Kubernetes 系列的文章:

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 10 分钟阅读

Choerodon猪齿鱼全场景效能平台,是基于Kubernetes,Istio,knative,Gitlab,Spring Cloud来实现本地和云端环境的集成,实现企业多云/混合云应用环境的一致性。平台通过提供精益敏捷、持续交付、容器环境、微服务、DevOps等能力来帮助组织团队来完成软件的生命周期管理,从而更快、更频繁地交付更稳定的软件。

2019年4月1日,Choerodon猪齿鱼发布0.15版本,本次更新新增了问题管理高级搜索、活跃冲刺在制品限制、自动部署、组织层应用页等诸多新功能,欢迎各位更新体验。

  • 发布版本:0.15
  • 发布时间:2019年4月1日
  • 功能范围:知识管理、敏捷管理、持续交付、测试管理以及微服务开发框架。

下面就为大家带来详细的版本更新介绍。

新增功能

知识管理

  • 新增页面创建按钮,可快速创建新页面。
  • 新增空间弹出框全部空间列表分页
  • Wiki空间可以修改名称。

敏捷管理

  • 新增问题的导入、导出。

  • 新增问题管理的高级搜索功能。
  • 在组织层中,新增优先级自定义以及按照实际顺序排列的功能。
  • 新增活跃冲刺看板在制品限制。

持续交付

  • 部署流水线模块新增自动部署功能,支持预设应用部署所需元素来创建自动部署任务。
  • 项目设置模块新增组件设置功能,目前支持创建 Docker 仓库与 Helm 仓库以供项目中的应用选择。
  • 项目层创建应用与编辑应用页面新增高级设置功能,支持修改默认的Docker仓库与Helm仓库。

测试管理

  • 新增执行详情中创建缺陷时关联故事的功能。

  • 新增测试计划中测试循环下的测试阶段拖拽排序功能。

  • 测试用例的筛选维度新增按标签筛选。

微服务开发框架

  • 组织层新增应用页,组织管理员可以创建应用,应用分为普通应用、组合应用,组织管理员可以根据需求向组合应用添加应用。

  • 平台层新增项目类型页,平台管理员可以自定义项目类型。
  • 组织层新增组织信息页,组织管理员可以在这里修改组织信息。

功能优化

知识管理

  • 优化了空间弹出框查询全部空间的性能问题。
  • 优化了匿名用户登录wiki系统无权限访问匿名分享文章的功能。

敏捷管理

  • 优化搜索查询时,对有值列表的字段进行字段的显示。
  • 问题管理支持自定义筛选显示的字段。
  • 优化待办事项的排列以及显示性能。
  • 在故事中创建子任务时,页面中会显示故事信息。
  • 优化问题详情页,登记工作日志的必填项提示。
  • 只允许自己和项目所有者才能修改报告人。
  • 部分页面样式和报表优化。

持续交付

  • 优化了Values组件的diff效果,支持切换编辑器模式来对比查看代码行的 增、删、改。
  • 优化了应用与环境权限分配模块,被分配权限的项目成员在 gitlab 中的角色统一改为developer。
  • 优化了平台里执行创建操作时出现熔断后的报错提示。
  • 优化了组织层的集群列表的显示。
  • 优化了组织层集群的删除逻辑,仅能删除没有关联环境的集群。
  • 优化了实例中操作日志页面的显示。

测试管理

  • 优化删除版本操作对于测试数据的风险提示。
  • 优化测试用例导入功能。
  • 优化测试循环克隆操作对于时间的处理。
  • 优化测试计划页面显示细节。

微服务开发框架

  • 优化事务实例加载缓慢的问题。
  • 优化平台菜单点击数导出文件显示信息,添加统计时间范围,显示点击数为0的菜单。
  • 优化平台统计中导出csv按钮的位置。
  • 优化事务实例中圆圈的数字为完成数/总数。

缺陷修复

知识管理

  • 修复了用户未加入的组,XWikiAllGroup不能访问wiki系统的问题。
  • 修复了wiki权限分配错误的问题。
  • 修复了wiki首次登陆跳转$user的问题。
  • 修复了项目名称修改,查询不到项目下wiki文档的问题。
  • 修复了访问页面报错的问题。

敏捷管理

  • 无泳道看板,取消收起功能。
  • 修复快速搜索时后端sql报错问题。
  • 修复问题详情页问题链接同一关系可以关联同一问题多次的问题。
  • 修复燃尽图和待办事项中的剩余问题数/剩余时间数不相符的问题。
  • 修复待办事项中版本的问题计数问题。

持续交付

  • 修复了导入应用时,选择某些模板进行导入后会缺少Dockerfile的问题。
  • 修复了分支界面内的没有权限时的报错信息不当的问题。
  • 修复了agent与devops-service已断开连接但集群仍然显示正常的问题。
  • 修复了网络编辑中IP选择器的问题。
  • 修复了操作实例时提示信息不当的问题。
  • 修复了部署流水线中切换顶部环境,并进行页面跳转后出现的问题。
  • 修复了构建报表部分pipeline里点击重试按钮错误跳转的问题。
  • 修复了组织层集群页面的分页问题。

测试管理

  • 修复在测试循环中进入执行详情后翻页错误的问题。
  • 修复自动化测试中报告过长导致的数据库存储错误。
  • 修复测试计划树状图中与右侧详情进度条不一致的问题。
  • 修复测试用例跳转链接错误的问题。
  • 修复测试计划中无法更新指派人字段的问题。
  • 修复执行详情中新建缺陷失焦的问题 。

微服务开发框架

  • 修复ldap同步历史显示信息异常的问题,不显示当前正在同步的同步记录信息。

删除

持续交付

  • 移除了删除失败应用的功能。
  • 移除了停用应用的限制:不能停用有关联实例的应用。

社区参与

感谢以下这些朋友在社区论坛中提出反馈和意见,在此次版本更新中作出突出贡献。

  • @phoenix
  • @8049
  • @nicky_x
  • @niu810
  • @quzhongquan
  • @felix

更加详细的内容,请参阅Release Notes官网

欢迎通过猪齿鱼的GitHub猪齿鱼社区进行反馈与贡献,帮助Choerodon猪齿鱼不断成长,猪齿鱼将持续迭代优化,敬请期待。

· 14 分钟阅读

本文是Choerodon 的微服务系列推文第五篇,上一篇《Choerodon 的微服务之路(四):深入理解微服务配置中心》介绍了配置中心在微服务架构中的作用,本篇将介绍微服务监控的重要性和必要性。

▌文章的主要内容包括:

  • 为什么要监控
  • 开发者需要监控哪些
  • 猪齿鱼的解决方案

在前面的几期的文章里,介绍了在 Choerodon 的微服务架构中,系统被拆分成多个有着独立部署能力的业务服务,每个服务可以使用不同的编程语言,不同的存储介质,来保持最低限度的集中式管理。

这样的架构决定了功能模块的部署是分布式的,不同的业务服务单独部署运行的,运行在独立的容器进程中,彼此之间通过网络进行服务调用交互。一次完整的业务流程会经过很多个微服务的处理和传递。

在这种情况下,如何监控服务的错误和异常,如何快速地定位和处理问题,以及如何在复杂的容器拓扑中筛选出用户所需要的指标,是 Choerodon 在监控中面临的首要问题。本文将会分享 Choerodon 在微服务下的监控思考,以及结合社区流行的 Spring Cloud、Kubernetes、Prometheus 等,打造的 Choerodon 的监控方案。

为什么要监控

在谈到 Choerodon 的监控之前,大家需要清楚为什么需要微服务下的监控。

传统的单体应用中,由于应用部署在具体的服务器上,开发者一般会从不同的层级对应用进行监控。比如猪齿鱼团队常用的方式是将监控分成基础设施、系统、应用、业务和用户端这几层,并对每一层分别进行监控。如下图所示。

而在微服务系统中,开发者对于监控的关心点是一样的,但是视角却发生了改变,从分层 + 机器的视角转化为以服务为中心的视角。在 Choerodon 中,传统的分层已经不太适用。服务是部署在 k8s 的 pod 中,而不是直接部署在服务器上。团队除了对服务器的监控之外,还需要考虑到 k8s 中容器的监控。同样,由于一个业务流程可能是通过一系列的业务服务而实现的,如何追踪业务流的处理也同样至关重要。

所以在微服务中,大家同样离不开监控,有效的监控能够帮开发者快速的定位故障,保护系统健康的运行。

平开发者需要监控哪些

在 Choerodon 中,将系统的使用人员分为应用的管理人员,开发人员,运维人员。而不同的人员在平台中所关心的问题则分别不同。

  • 作为应用的管理人员,需要查看到系统中各个节点实例的运行状态以及实例中应用的状态。
  • 作为开发人员,需要查看自己开发的服务在运行中的所有信息,也需要跟踪请求流的处理顺序和结果,并快速定位问题。
  • 作为运维人员,需要查看系统集群中服务器的 CPU、内存、堆栈等信息。需要查看K8S集群的运行状态,同时也需要查看各个服务的运行日志。

除了这些以外,还需要监控到如下的一些信息:

  • 服务的概览信息:服务的名称,相关的配置等基本信息。
  • 服务的拓扑关系:服务之间的调用关系。
  • 服务的调用链:服务之间的请求调用链。
  • 服务的性能指标:服务的CPU,内存等。
  • 接口的调用监控:接口的吞吐量,错误率,响应时间等。
  • 服务的日志数据:服务运行中产生的日志异常。

简而概之,对于 Choerodon 而言,开发者将监控聚焦在指标监控,调用监控和日志监控。

猪齿鱼的解决方案

在社区中,有很多对监控的解决方案,比如指标监控有 Prometheus,链路监控有 zipkin、pinpoint,skywalking,日志则有 elk。

Choerodon 具有多集群多环境管理能力,Choerodon 为需要监控的集群配置监控组件,并与Choerodon 所在集群的监控组件互通以及过滤多余数据,可以最大限度地减少多集群非同一局域网的外网带宽需求。在多集群环境中仍然可以感知所管理应用的运行状态和配置预警信息。

▌指标监控

Spring Boot 的执行器包含一系列的度量指标(Metrics)接口。当你请求 metrics 端点,你可能会看到类似以下的响应:

{
"counter.status.200.root": 20,
"counter.status.200.metrics": 3,
"counter.status.200.star-star": 5,
"counter.status.401.root": 4,
"gauge.response.star-star": 6,
"gauge.response.root": 2,
"gauge.response.metrics": 3,
"classes": 5808,
"classes.loaded": 5808,
"classes.unloaded": 0,
"heap": 3728384,
"heap.committed": 986624,
"heap.init": 262144,
"heap.used": 52765,
"nonheap": 0,
"nonheap.committed": 77568,
"nonheap.init": 2496,
"nonheap.used": 75826,
"mem": 986624,
"mem.free": 933858,
"processors": 8,
"threads": 15,
"threads.daemon": 11,
"threads.peak": 15,
"threads.totalStarted": 42,
"uptime": 494836,
"instance.uptime": 489782,
"datasource.primary.active": 5,
"datasource.primary.usage": 0.25
}

这些系统指标具体含义如下:

  • 系统内存总量(mem),单位:KB
  • 空闲内存数量(mem.free),单位:KB
  • 处理器数量(processors)
  • 系统正常运行时间(uptime),单位:毫秒
  • 应用上下文(应用实例)正常运行时间(instance.uptime),单位:毫秒
  • 系统平均负载(systemload.average)
  • 堆信息(heap,heap.committed,heap.init,heap.used),单位:KB
  • 线程信息(threads,thread.peak,thead.daemon)
  • 类加载信息(classes,classes.loaded,classes.unloaded)
  • 垃圾收集信息(gc.xxx.count, gc.xxx.time)

有了这些指标,我们只需要做简单的修改,就可以使这些指标被 Prometheus 所监测到。Prometheus 是一套开源的系统监控报警框架。默认情况下 Prometheus 暴露的metrics endpoint为/prometheus。

在项目的pom.xml文件中添加依赖,该依赖包含了 micrometer 和 prometheus 的依赖,并对监控的指标做了扩充。

<dependency>
<groupId>io.choerodon</groupId>
<artifactId>choerodon-starter-hitoa</artifactId>
<version>${choerodon.starters.version}</version>
</dependency>

Prometheus提供了4中不同的Metrics类型:CounterGaugeHistogramSummary。通过Gauge,Choerodon对程序的线程指标进行了扩充,添加了 NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED 这几种类型,具体代码如下。

@Override
public void bindTo(MeterRegistry registry) {

Gauge.builder("jvm.thread.NEW.sum", threadStateBean, ThreadStateBean::getThreadStatusNEWCount)
.tags(tags)
.description("thread state NEW count")
.register(registry);
Gauge.builder("jvm.thread.RUNNABLE.sum", threadStateBean, ThreadStateBean::getThreadStatusRUNNABLECount)
.tags(tags)
.description("thread state RUNNABLE count")
.register(registry);

Gauge.builder("jvm.thread.BLOCKED.sum", threadStateBean, ThreadStateBean::getThreadStatusBLOCKEDCount)
.tags(tags)
.description("thread state BLOCKED count")
.register(registry);

Gauge.builder("jvm.thread.WAITING.sum", threadStateBean, ThreadStateBean::getThreadStatusWAITINGCount)
.tags(tags)
.description("thread state WAITING count")
.register(registry);

Gauge.builder("jvm.thread.TIMEDWAITING.sum", threadStateBean, ThreadStateBean::getThreadStatusTIMEDWAITINGCount)
.tags(tags)
.description("thread state TIMED_WAITING count")
.register(registry);

Gauge.builder("jvm.thread.TERMINATED.sum", threadStateBean, ThreadStateBean::getThreadStatusTERMINATEDCount)
.tags(tags)
.description("thread state TERMINATED count")
.register(registry);
}

▌调用监控

在微服务架构中,一个请求可能会涉及到多个服务,请求的路径则可能构成一个网状的调用链。而如果其中的某一个节点发生异常,则整个链条都可能受到影响。

针对这种情况,团队需要有一款调用链监控的工具,来支撑系统监控分布式的请求追踪。目前社区中有一些工具:Zipkin、Pinpoint、SkyWalking。Choerodon 使用的是 SkyWalking,它是一款国产的 APM 工具,包括了分布式追踪、性能指标分析、应用和服务依赖分析等。

Skywalking 包含 Agent 和 Collecter,具体的部署和原理在这里不在做具体的介绍,Choerodon 的服务在每个服务的 DockerFile中,添加了对 Skywalking Agent 的支持。具体如下:

FROM registry.cn-hangzhou.aliyuncs.com/choerodon-tools/javabase:0.7.1
COPY app.jar /iam-service.jar
ENTRYPOINT exec java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap $JAVA_OPTS $SKYWALKING_OPTS -jar /iam-service.jar

部署时通过配置容器的环境变量 SKYWALKING_OPTS 来实现客户端的配置。

▌日志监控

日志是程序在运行中产生的遵循一定格式(通常包含时间戳)的文本数据,通常由Choerodon的服务生成,输出到不同的文件中,一般会有系统日志、应用日志、安全日志等等。这些日志分散地存储在不同的容器、机器中。当开发者在排查故障的时候,日志会帮助他们快速地定位到故障的原因。

Choerodon 采用了业界通用的日志数据管理解决方案,主要包括elasticsearchfluent-bitfluentdKibana。对于日志的采集分为如下几个步骤。

  • 日志收集:通过 fluent-bit 读取 k8s 集群中 cluster 的日志,并进行收集。
  • 日志过滤:通过 fluentd 将读取到的日志进行过滤,并进行缓存。
  • 日志存储:将过滤后的日志存储至 elasticsearch 集群中。
  • 日志展示:通过 kibana 查询 elasticsearch 中的日志数据,并用于展示。

通过端对端可视化的日志集中管理,给开发团队带来如下的一些好处:

  • 故障查找:通过检索日志信息,定位相应的 bug ,找出解决方案。
  • 服务分析:通过对日志信息进行统计、分析,了解服务器的负荷和服务运行状态。
  • 数据分析:分析数据,对用户进行行为分析。

写在最后

回顾一下这篇文章,介绍了微服务监控的重要性和必要性,以及 Choerodon 是如何应对指标监控,调用监控和日志监控这三种监控的。微服务架构下的服务规模大,系统相对复杂,也使得众多开发者成为了微服务的受害者。如何做好微服务下的监控,保障系统健康地运行,我们仍有许多需要继续努力的。

更多关于微服务系列的文章,点击蓝字即可阅读 ▼

总结

回顾一下这篇文章,整体介绍了配置中心在微服务架构中的作用,并提出了Choerodon对于配置管理的一些心得和规范。服务配置是服务运行起来的基础,而配置中心是整个微服务技术体系中的关键基础保障,如何做好服务配置规划,并推动项目的持续交付,则是Choerodon仍需持续考虑的问题。

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 16 分钟阅读

Choerodon平台中的开发和部署都是围绕应用来进行的,那Choerodon平台中的应用有什么样的特性?又是怎样来进行管理的呢?本文旨在深入地介绍Choerodon平台中应用的功能特性及其生命周期的管理。

微服务架构中应用的特征

在谈起Choerodon平台中的应用时,就不得不提微服务。正是因为微服务的出现,之前的单体应用架构带来的问题才得以解决,具体问题如下:

(1)耦合程度随时间推移而逐渐提高。

(2)扩展性差,冗余能力差

(3)模块划分不清晰,无法细化对系统资源的需求

(4)维护成本随着系统的日益臃肿而不断增加

而下图也更为直观地指出了单体应用架构与微服务架构的区别。

由此可见,微服务架构中的应用会被分解为更小、完全独立的组件,这使得它们拥有更高的敏捷性、可伸缩性和可用性。换句话说,微服务架构的基本思想就是“围绕业务领域组件来创建应用,让应用可以独立地开发、管理和加速。

Choerodon平台中应用生命周期的管理

由于Choerodon平台采用的是微服务架构,因此系统内每个功能模块均被解耦为多个应用,其中每个应用都支持独立地开发和独立地部署。而Choerodon平台中一个应用的生命周期一般从创建或导入应用开始;接着在开发流水线中按照需求对应用进行开发,待提交代码跑完CI之后,会生成一个应用版本,而之后的部署与发布操作均是围绕应用版本来进行的;首先用户可以将生成的应用版本部署至对应的环境中;此外,用户在测试环境中测试无误后,可以将此应用版本发布至应用市场与全平台或全组织共享。

以上便是Choerodon平台中应用的生命周期与相关功能的大致流程,下面就按照这个流程进行展开,带大家了解Choerodon对于DevOps的实践。

应用的创建或导入

在平台中创建或导入应用时,系统会默认在对应的 gitlab group 中创建一个 project 作为此应用的初始代码库,而后再通过相应的页面功能实现对此应用的管理。从gitlab或github中导入应用库时,目前步骤和创建应用步骤类似,在导入的过程中,依然可以选择对应的模板库,以便于在平台上进行后续的开发与部署。

在创建或导入应用的过程中,选择应用模板与配置应用权限是其中不可或缺的步骤,同时也是应用的重要特性,以下为这两个模块的详细介绍:

▌应用模板

为了让开发者能将更多的精力用于应用的开发之中,在创建应用时,Choerodon平台提供了全面的预置应用模板供各个项目团队选择。其中的应用模板是由同类型应用的代码库整理而成的,引用了相应的应用模板后,即可在gitlab中快速地创建初始代码库。

目前猪齿鱼平台预置了微服务前端、微服务后端、Javalib(jar库)、Go语言、SpringBoot等多个开发常用的应用模板,此外,还有Mocha与TestNg的测试应用模板。这些应用模板均统一预置于github里面。而在这些模板中,至少都包含了 Dockerfile 文件、CI 文件以及 Chart 目录文件。

其中Dockerfile是由一系列命令和参数构成的脚本,这些命令应用于基础镜像并最终创建一个新的镜像,主要用于控制应用容器化的进程。其次是CI文件,模板中的CI文件主要用于设置在提交代码后,自动集成时要经历的所有阶段。而其中的Chart目录文件则用于将平台中的容器打包,统一置于K8S平台进行管理。

除了可以使用平台提供的预置模板,用户还可以根据实际情况自定义符合自己需求的应用模板。而用户自定义的模板将会统一置于gitlab中的应用模板库中,便于用户进行统一的管理。

▌应用权限

众所周知,所有的项目团队均是由不同的角色构成的。而在Choerodon平台的项目层中,先是简单地将角色分为项目所有者与项目成员。顾名思义,这里的项目所有者对应的是项目中项目经理的角色,而项目成员即为团队中的其他角色。项目所有者默认拥有此项目下所有应用的开发权限和部署权限,并可以对项目成员们进行应用权限的配置(目前平台内的的应用权限仅限于为项目成员配置应用的开发权限)。

在使用微服务的团队中,由于需要涉及到对多个单元应用的管理、开发与部署,而且不同的应用可能还需要不同的开发人员,在这种情况下,对每个应用进行权限的配置就变得更为必要了。在平台内的项目层创建应用或者导入应用时,Choerodon都提供了灵活的权限设置功能,以便项目所有者为相应的应用配置特定的开发人员。

应用开发

当应用创建成功后,开发人员即可在Choerodon平台的开发流水线中按照需求进行应用的开发。开发流水线是根据开发人员的日常操作整理而来的,主要通过对gitlab的封装来实现持续集成与持续交付。

平台中的开发流程从基于目标应用创建对应的分支开始,而之后的开发操作就在此分支上进行。待开发完成,提交代码之后,会触发CI,并依次执行CI文件中定义的各个阶段。CI通过后,会依据版本生成规则(详见应用版本管理部分)生成一个应用版本。此时,就需要在开发流水线中创建合并请求,而指定人员审核代码通过后,会将此分支合并至目标分支。在开发的最后过程,用户可以使用标记功能为这个版本创建一个标记,以便更好地进行版本管理。

下图为开发流水线模块主要流程:

应用版本管理

灵活的版本控制与规范的版本管理是Choerodon平台的一大特色,因为采用了语义化版本的命名规则,所以Choerodon平台内通过CI生成的版本,一般格式为:C7N_COMMIT_TIME-C7N_BRANCH;比如:若分支名为feature-demo,提交时间为2019年3月7日20:14:54,那么得到的C7N_VERSION值为:2019.3.7-201454-feature-demo。若通过标记功能创建的标记为0.15.0,那么得到的版本号就是0.15.0。此处的标记功能通常用于对外发布新版本时使用,而此时的版本格式通常为:主版本号.次版本号.修订号,版本号递增规则如下:

  1. 主版本号:当你做了不兼容的 API 修改,

  2. 次版本号:当你做了向下兼容的功能性新增,

  3. 修订号:当你做了向下兼容的问题修正。

此外,Choerodon还支持将先行版本号及版本编译信息加到“主版本号.次版本号.修订号”的后面,使得版本号更加灵活易懂,例如:0.15.0-alpha.1。而具体的规则可以至语义化规则进行详细了解。

应用发布与应用市场

很多时候,在开发完某个应用后,可能恰好另一个项目或者另一个组织也正好需要这个应用。在Choerodon平台内,只需将此应用对应的应用版本发布至全组织或全平台,便能实现组织内或者平台内的应用共享,同时,还可对应用的具体版本进行控制,可选的将目标应用版本发布到应用市场,而将应用的流水版本保留下来。

而应用市场则集合了本项目下可访问的已发布应用,并能将所需应用的对应版本部署至此项目下的环境中使用。此外,应用市场还提供了应用的导入与导出功能(目前支持zip格式的导入与导出)。

应用部署

应用部署是部署流水线中的主要功能,应用部署的过程就是实现应用容器化的过程。

在生成应用版本之后,我们可以将其部署到对应的环境中去进行测试验收或投入使用(此处的环境可以理解为我们平时使用的Staging环境、UAT环境和生产环境)。而部署应用成功后,会生成一个实例,我们通过管理这个实例及其相关的资源来管理容器中的应用(具体的原理和步骤将在后续的部署流水线模块相关的文章中进行详细介绍)。

以下为部署流水线模块的流程示意图:

应用维度的指标监控

管理大师彼得德鲁克:如果你不能度量它,你就无法改进它。

正如彼得德鲁克所说,如果想改进某个东西,首先得有一个明确的指标。Choerodon平台内的DevOps指标均能以应用为维度进行查看,便可知道每个应用的开发与部署的情况。

(1)首先可以通过代码提交图了解到某个应用的代码提交情况,以此实时反应出该应用的开发情况。

(2)在Choerodon平台中,还可以从应用的维度了解到某个应用的所有构建情况,包括了构建的时长与构建的次数。

(3)最后可以通过平台从应用的维度了解到某个应用在某个或是所有环境中的部署情况,包括部署时长图与部署次数图。

写在最后

在微服务架构的基础上,Choerodon平台内的功能模块被解耦为多个独立的应用。因此管理好这些应用及其对应的代码库很有必要,而Choerodon平台中提供了完善的功能来帮助用户更好的实现多应用的管理与共享。以应用为中心进行开发和部署是Choerodon平台实践DevOps的重要步骤,所以应用管理作为实践DevOps的基础,理应受到重视。

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 15 分钟阅读

团队敏捷化转型过后,该如何进行项目的测试以及验收?敏捷测试与传统测试有什么不同?自动化测试在其中又担任着怎样的角色?本文将给大家介绍敏捷自动化测试,以及Choerodon猪齿鱼自动化测试的落地。

敏捷测试的定义和意义

在传统开发模式中,开发人员和测试人员往往各司其职:开发人员了解到产品需求后开始编写代码,测试人员拿到产品需求说明书后开始编写测试用例,等到开发完成,再开始对照测试用例进行人工测试工作。

但是在软件生命周期的需求、设计、开发、测试这四个阶段中,后面的阶段一般是依赖于前面的阶段的,所以越往后期响应变化的难度越大。比如,在开发过程中不仅需要响应需求变更,而且需要响应设计上的变化。从这个角度来看,处于最后阶段的测试必须及时响应前面三个阶段的所有变化。可是在传统的开发模式中,当需求变更产生的时候测试人员所编写的测试用例往往已经完成,需要对其进行推倒重构,整个测试流程就是在重复一些没有用的工作。

传统测试流程图:

而敏捷测试是遵从敏捷软件开发原则的一种测试实践。敏捷开发模式把测试集成到了整个开发流程中而不再把它当成一个独立的阶段,因此测试变成了整个软件开发流程中必不可少,需要考虑人力、时间成本的一环。敏捷测试包含了具备专业测试技能人员在内的跨职能团队,这使得这种组合式的团队能更好地交付价值,满足项目的业务、质量和进度目标。

在敏捷开发模式中,所有的开发人员同时也是测试人员,对自己的业务负责,对团队的代码负责,是一种边开发边测试的模式。敏捷模式中一到四周的短开发周期,克服了传统模式中项目周期长、生命周期的工作内容不好分配、后期变更影响大等困难。

由于敏捷方法中迭代周期短,测试人员应尽早开始测试,包括及时对需求、开发设计的评审,更重要的是能够及时、持续的对软件产品质量进行反馈。敏捷测试中通常包含以下几个阶段来保证敏捷测试的可靠性、时效性:

1. 验收测试:对于这个迭代中新增、修改的功能按照迭代初期提出的需求内容进行测试验收。可采用冒烟测试的方法对新版本的业务流程进行测试。

2. 回归测试:对于模块进行全量测试,保证其全部功能的可用性,验证当前迭代新增、修改过的功能对于其他内容没有不良影响。

3. 系统测试:运用场景法测试和相互交叉测试保证整个系统在迭代结束后处于一个可发布状态。

简单地说,敏捷测试就是持续地对软件质量问题进行及时的反馈与响应。

敏捷测试流程图:

自动化测试能带来什么

自动化测试在敏捷测试中占有绝对的主导地位。虽然在传统项目开发周期的测试阶段也推荐自动化测试,但由于整体的项目周期比较长,人员上的资源也较为充足,不使用自动化测试也可以控制测试人员人天成本并且达到测试目标。一般来说,回归测试能够获得几周甚至上月的时间,而在敏捷的开发模式中则迫切要求测试的高度自动化,一个迭代通常是两三周的时间,那也就意味着你只有两三天的时间来进行整个项目的全套测试流程。没有自动化,也就谈不上敏捷。在敏捷测试流程中,主要分为以下几个主要测试阶段:

▌单元测试(Unit Test,UT)

是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。

▌集成测试(Integration Test,IT)

整合测试又称组装测试,即对程序模块采用一次性或增值方式组装起来,对系统的接口进行正确性检验的测试工作。整合测试一般在单元测试之后、系统测试之前进行。实践表明,有时模块虽然可以单独工作,但是并不能保证组装起来也可以同时工作。

▌用户验收测试(User Acceptance Test,UAT)

用户验收测试,也叫用户可接受测试,一般在项目流程的最后阶段,这时相关的产品经理、业务人员、用户或测试人员根据测试计划和结果对系统进行测试和验收,来决定是否接收系统。它是一项确定产品是否能够满足合同或用户所规定需求的测试。

▌回归测试(Regression Test)

回归测试是软件测试的一种,旨在检验软件原有功能在修改后是否保持完整。回归测试主要是以检查退化为目的的测试。退化主要指由于系统的版本更新,在之前的版本中正常运行的功能变得无法正常运行,或者紧急修正了某个问题,但引发了其他的问题的现象。

以上这四种测试模式,目前都可以找到适用的主流自动化测试框架来满足需求。单元测试和集成测试可以使用如今比较流行的JUnit、TestNG、Spock等框架进行测试。而用户验收测试与回归测试可以使用一些api接口测试框架 REST Assured + TestNG 、mocha + chai 等。还可以使用一些针对UI界面的测试框架例如selenium和appium来编写测试脚本。

Choerodon自动化测试落地

在敏捷测试的支持过程中,Choerodon平台支持了基于手动测试的操作结构(项目版本->用例文件夹,测试循环->测试阶段)的主流自动化测试框架。目前包括 mocha + chai 的api测试框架,以及 TestNG + REST Assured 的api测试框架。未来迭代中会新增支持 TestNG + selenium 的前端测试UI框架。从而完整地支持了敏捷测试中包含四个测试方面的主流框架(单元测试与集成测试在持续交付模块中落地实现)。

在自动化测试的运行模式上,我们沿用了持续交付模块所使用的基于不同的k8s平台定义环境,然后通过页面修改运行配置,达到可在不同环境中嵌入运行测试的目的。运行结束后将测试报告回传并加以解析,生成测试用例、测试阶段和测试执行。并尽可能保留并重新设计了不同框架的原生html报告,给了PO多种查看测试结果的途径,且支持定时任务等运行方式。

类原生的 Mocha + chai 测试报告:

虽然在技术上支持了敏捷化的自动化测试,但是在自动化测试落地的过程中,也遇到了一些问题的。

1. 重新定义工作量

在敏捷的开发模式中,迭代规划会议尤为重要。因为敏捷团队中没有专门的测试人员,所以当一个团队决定去实践自动化测试的时候,意味着相同难度的任务工作量基本变成了原来的两倍。作为PO在规划故事的过程中,应该充分考虑到测试脚本的编写工作量,并计算到成员的工作内容当中,从而给团队成员”减负“,鼓励他们更高质量地完成测试任务(毕竟在测试脚本上偷懒可比在业务代码上偷懒容易多了)。

2. 鼓励互相帮助

从敏捷测试的理论上来说,每个开发人员负责维护自己功能的测试代码。但是在实际的迭代过程中,难免会造成任务分配有轻微失衡的情况出现。这种情况下应鼓励队员主动帮助其他成员分担测试代码维护等琐碎任务,促进团队成员间的互帮互助,作为团队的一份子应当把整个项目当成自己分内的工作内容。而不是说这个功能是”李四“写的,事不关己高高挂起。

3. 留出足够的测试时间

在实践自动化测试以后,可能在迭代结束前的测试阶段会更容易测出Bug,这个时候应比以前多预留出一段时间给团队成员以确保在迭代结束前可以求证全部缺陷,鼓励团队成员把这个迭代发现的问题就在这个迭代中解决,从而使用自动化测试提高交付质量。

写在最后

手动测试和自动化测试对于敏捷项目来说同等重要,人工手动测试才是保证交付结果的最后一关。不能因为使用了自动化测试而忽视传统测试,这两者只是在开发的不同阶段中扮演了不同的角色。

关于敏捷的自动化测试的实践,太多人有不同的见解,正所谓一千个观众眼中有一千个哈姆雷特。每个团队的敏捷落地都有各自的特色与道路,所以实践出最适合自己团队的自动化测试方案,提高团队的凝聚力与生产力才是敏捷的最终目的,祝大家都找到一个适合自己团队的敏捷方法。

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 13 分钟阅读

在敏捷开发中,大家经常会提到看板(kanban)这个名词,故名思义就是可视化的板。看板作为一个敏捷方法,与其他方法相比具有更强的可实施性,也更易让团队理解和执行。

下面将结合猪齿鱼团队的敏捷故事,给大家讲述下如何来使用看板,以及Choerodon猪齿鱼敏捷管理又是怎么辅助项目成员落地看板方法的。

第一原则:可视化

Choerodon猪齿鱼还没有发布第一个版本时,猪齿鱼的总设计师已经非常确定团队一定要使用敏捷的方式,去做一个敏捷开发工具来帮助企业提升系统交付的价值。

在猪齿鱼开发前期,团队需要去整理需求、收集需求、排列哪个故事先做哪个故事后做。那时候整个猪齿鱼开发团队被分成不同的敏捷小组,在办公室摆了4、5块白板。大家对照着看板方法中的图片模样,依葫芦画瓢地在看板上用线条进行分割,绘制出列和泳道,并买来各种颜色的便利贴和磁贴,猪齿鱼各个敏捷开发小组就这样用起来了看板。

首先需要让任务在板上呈现出来。

团队定好一个开发周期时,产品负责人(PO)会将这个周期内的所有需求都整理出并分别写在一张张大号便利贴上,按照优先级高低将卡片从上到下依次放在story这一列,剩下的故事卡片继续留在backlog这列里,直到有故事(卡片)做完再去backlog中将优先级最高的移动到story这列进行开发。团队根据故事的描述、对象和目的等信息将其拆分成一个个的开发任务,写在小号卡片上贴在doing状态列中。每个团队成员通过磁力贴颜色或其他方式标记出自己,粘在自己所负责的任务卡片上。

在使用看板后的第一个迭代中,团队里几乎听不到“A功能是谁做的?”“B任务做到什么程度了?”“为什么C功能还没开始?”“张三李四王五你们在干嘛?”等等这样的声音了,每个成员只要抬头看看白板,就能大概知晓以上这些信息,而且是即时的。这样一来,看板可视化在猪齿鱼团队算是做到了。

图为猪齿鱼团队物理看板

既然已经将产品功能和开发任务都贴在了看板上,接下来就需要让任务流动起来。管理流动的目的很明确,就是要将所有的卡片从backlog中运送到部署,并能让这个过程在板子上体现出来。

管理流动

每一天的工作从各个敏捷团队成员站在白板前开始,在猪齿鱼产品开发团队现场,每天早上都会看到一副盛景——开站会。

在绘制成多列(分别是待办、分析、开发、测试和待发布或者部署)的看板面前,开发人员依次陈述自己昨天做了什么,并将对应工作的卡片进行状态的更新,也就是将做完的任务卡片从doing状态移动到完成的状态。然后再接着说今天要做什么,并将板上backlog中的任务移动到doing的状态,贴上自己的名帖。接下来测试人员根据板上已完成开发的卡片来安排今日任务或对之前还没进入测试列的卡片进行测试工作,将开发完成的卡片从开发完成这列中移动到测试doing。

在迭代的前1到2天,可能看不出明显的变化,从第三天开始,你会发现卡片动起来了,白板上任何列中都有卡片,从开发doing到开发done,从测试doing到测试done,再到部署。所有到doing的卡片,都不是直接贴上来的,一定是从backlog中经过了分析、开发、测试的各个阶段才挪到了这个位置。

这只是整个流程持续优化之旅的开始,在这个过程中,总是存在某个瓶颈会拖延你的工作,庆幸的是,这些问题在白板上很容易显现出来,比如某个卡片在板上停留了2天了还没有动静,比如谁的名帖在板上最多,谁一个名帖都没有。往往越严重的问题越早暴露,一旦解决掉,工作的流动就会明显改进。

当基于这一原则开展工作时,你能够从精益思想中找到灵感来消除过程中的浪费以便工作能够顺畅流动起来。

限制在制品(WIP)

限制在制品指的是对进行中的任务数进行最多数量的限制。首先限制在制品并不是一个目的,它只是用来驱动改进的手段。

猪齿鱼敏捷团队在前期开发中,也无法理解如何去做到限制在制品。平台设计师张礼军说,“我们就先按一个人最多同时进行3个任务去执行吧”。

于是大家按人员数量去限制在制品,用磁贴来表示工作的分配。我们为团队成员每个人制作3个代表他们自己的磁贴,上面写上代号或者名字。然后将其贴在自己负责的任务卡片上,这样也很容易看出每个人在做什么,并且手头有多少正在进行的工作项。

这样的好处是每个人只有当看板上自己的头像少于3个的时候,才可以去领取新的任务,避免多任务并行而忽略了交付质量。这样实践下来,很容易发现团队中某些人是不是工作项过多,任务一直停滞不前,导致整个团队的在制品过多,影响了整体进度。

但这个原则的目的侧重于确保每个人都有足够多的工作可做,对工作流动的完成状态没有什么大的帮助,因为客户不关注团队是否每个人都有事情做,他们只希望能交付成果。

随着敏捷思想的不断实践,团队尝试不断改善方式,比如在每列上方写上数字标记在制品的数量,开始实施基于列的在制品限制原则。通过在瓶颈之前的步骤设置在制品限制,可以防止瓶颈处工作泛滥,并且促使团队解决瓶颈,进而改善整个流程。

举个例子,比如开发列中的卡片数已经到了在制品上限,可是测试列里的任务也存在堆积,测试人员没精力进行更多的测试。这个时候,开发的卡片无法流动进测试列,开发人员便不能进行新的开发任务,瓶颈就很明显在测试列了,那么团队的开发人员可以去帮助测试人员进行测试,从而解除瓶颈,让板子上的卡片重新流动起来。

猪齿鱼团队运用了人员限制、列限制几个方案,而在敏捷方法里也提供了许多的方法以便你了解如何设置在制品。

限制在制品就是通过条件限制把流程改进的机会呈现在表面,使团队能直接观察到流动迟滞(卡片在白板上的移动非常缓慢)、任务积压(在某列中堆积了很多卡片)、项目停滞(工作项一直等待)的等些问题,以便及时作出调整。

这些看板实践经验后来也在Choerodon猪齿鱼平台的敏捷管理上有所体现,前两个原则自不必说,在限制在制品方面,猪齿鱼敏捷管理采用列限制的方案。支持用户自行对列进行配置,设置该列任务最大数量和最小数量。

数量会在看板上直接显示,当任务数量已经达到最大时,新的任务无法拖入该列。

说到底,敏捷管理是一个方法也是一种心态,选择哪条路改进你的系统完全取决于你,最重要的一点是当你的工作向你发出改进信息时,你要响应并改善它。

猪齿鱼敏捷团队故事看板实践就介绍到此,敬请期待下篇《电子与物理看板的差异化分析》。

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 15 分钟阅读

Choerodon猪齿鱼全场景效能平台,是基于Kubernetes,Istio,knative,Gitlab,Spring Cloud来实现本地和云端环境的集成,实现企业多云/混合云应用环境的一致性。平台通过提供精益敏捷、持续交付、容器环境、微服务、DevOps等能力来帮助组织团队来完成软件的生命周期管理,从而更快、更频繁地交付更稳定的软件。

2019年3月1日,Choerodon猪齿鱼发布0.14版本,本次更新新增了看板任务停滞预警、自动化测试框架-TestNG、文档注释编辑和集群模块节点列表等诸多新功能,欢迎各位更新体验。

  • 发布版本:0.14
  • 发布时间:2019年3月1日
  • 功能范围:知识管理、敏捷管理、持续交付、测试管理以及微服务开发框架。

下面就为大家带来详细的版本更新介绍。

新增功能

知识管理

  • 新增了浏览界面上添加注释的功能,且针对弹出的注释进行删除或修改。

  • 新增了查看页面回到顶部的快捷按钮。

敏捷管理

  • 新增了看板中卡片停留时间预警提示。

  • 新增了故事点、任务时间支持0.5小数点。
  • 待办事项中对已完成的issue以编号中线划掉的形式进行显示。

  • 新增了冲刺名称重复提示。
  • 新增了看板配置的权限限制(项目管理员:配置看板;项目成员:查看配置数据)。
  • 新增了创建问题时,支持关联关系。
  • 新增了设置中问题链接和快速搜索的筛选过滤器。
  • 新增了问题链接的名称重复校验。
  • 新增了富文本框字体颜色选择。
  • 看板中史诗泳道下新增子任务的显示,便于展示史诗、故事与子任务的层级关系。

持续交付

  • 集群模块新增节点列表,支持查看集群节点的详细信息。

  • 实例详情部分新增实例操作日志,支持查看各个实例的详细操作记录。

  • 开发控制台页面流水线模块里的持续集成卡片上新增构建报表的入口。
  • 部署总览页面新增部署报表的入口。

测试管理

  • 新增了自动化测试框架支持-TestNG。
  • 新增了测试执行进度报表(色块展示),用于统计测试用例执行情况,可以筛选得到各个迭代的不同测试循环中的用例执行状态记录。

微服务开发框架

  • 导航栏顶部添加横幅,显示最近系统公告。
  • 平台层平台统计新增统计表格,可查看具体时段内的菜单点击次数。
  • 平台层平台统计新增导出表格CSV文件功能。
  • 组织层LDAP添加同步记录,可查看历史同步LDAP记录及失败详情。
  • 平台层可执行程序添加层级字段。
  • 平台层系统公告中,创建、编辑公告页,公告详情页添加字段“是否在顶部显示”,如果是则显示“结束显示时间”。
  • 平台层可执行程序添加删除功能, 可删除可执行程序。
  • 平台层发送设置添加删除功能,可删除触发类型。
  • 组织层LDAP添加“uuid属性”字段,测试连接时可查看uuid属性的匹配结果。
  • 组织层LDAP添加“同步用户saga每次发送用户的数量”字段。
  • 组织层LDAP添加“ldap服务器连接超时时间”字段。
  • 组织层组织管理列表页添加图标。
  • 项目层项目管理列表添加图标。
  • 组织层项目管理支持点击项目名称或者跳转按钮跳转至对应项目。
  • 网页title随页面内容变更而改变。

功能优化

知识管理

  • 匿名分享取消了全局搜索、空间格式、验证码。
  • 修改了在所有更新等页面下创建页面的路径,默认选择路径改为在我的空间
  • 优化了对用户可见注释的设置记忆,下次无需重复设置。
  • 优化了最新和所有更新页面的icon。
  • 优化了页面标题的字数限制。
  • 优化了全局语言显示。
  • 调整文章顶部显示,由文章名更换为上级组织或项目名称。

敏捷管理

  • 细化了活动日志的描述。
  • 优化了看板中的故事泳道下的任务排序,已完成任务会自动移动到看板底部。
  • 优化了问题管理、版本详情、发布版本、模块等页面的搜索。
  • 优化了归属不同经办人的子父任务卡片在筛选后的显示。
  • 优化了创建模块时的问题逻辑。
  • 扩大了影响版本的选择范围,可选择全部版本。
  • 优化了部分报表的轮动条。
  • 优化了问题详情的评论显示。
  • 优化了统计图的版本维度,可选择归档版本。

持续交付

  • 优化了实例详情内增减Pod数量的逻辑,Pod数量不可减少至零。
  • 优化了开发控制台页面流水线部分的逻辑与自动刷新的问题。
  • 将LoadBalancer中添加NodePort的填写输入框变为非必填。
  • 修改无分支时的空界面提示文案。
  • 优化实例详情中ingress的address值的空值显示。
  • 优化了GitLab同步用户的逻辑,在同步前添加了查询操作。

测试管理

  • 优化了测试用例、测试计划、测试执行页面的样式布局和交互体验。

  • 优化了测试用例表格和分栏显示。
  • 优化了测试计划点击选中版本节点后再创建测试循环直接选中相应版本的流程。
  • 优化了导出测试计划表格数据排序,修改为按导出时间排序。
  • 优化了测试步骤分页显示。
  • 优化了复制测试用例执行步骤后的排序。
  • 优化了提示信息的样式。
  • 创建用例后,会自动加载对应版本或文件夹用例。
  • 优化了测试用例中添加步骤的默认数据,在编辑时直接清除提示语。
  • 优化了克隆测试循环时,记住最新版本。
  • 优化了测试步骤克隆操作排序。
  • 优化了测试计划导出功能数据排序和操作。
  • 优化了自定义状态中取色卡组件。
  • 优化测试计划页面甘特图的边缘滚动。
  • 优化了table的UI样式。

微服务开发框架

  • 优化项目名称命名规则。
  • 优化登录名命名规则。
  • 优化发送设置“应用模板”名称为“默认模板”。
  • 优化授权管理页面重定向地址支持跳转。
  • 优化系统公告中,删除已发送公告后的提醒弹窗。
  • 优化修改组织、项目头像及上传时的名称为“图标”。
  • 优化Root用户设置添加用户为选择用户的形式。

缺陷修复

知识管理

  • 修复了侧边栏加载时间长、容易报错的问题。
  • 修复了空间移动、复制在空间弹出框的层级显示问题。
  • 修复了创建、编辑收藏夹字数显示仅10字的问题。

敏捷管理

  • 修复了富文本粘贴网络图片时数量错误的问题。
  • 修复了关闭冲刺的时候,燃尽图问题数量重复计算的问题。
  • 修复了统计图版本迭代为空时报错问题。
  • 修复了工作日历非节假日计算bug。
  • 修复了故事点统计的状态颜色错误。
  • 修复了高级筛选中填入不存在用户还能查出数据的错误。
  • 修复了由于状态脏数据导致创建看板报错问题。
  • 修复了冲刺报告图无法翻页问题。
  • 修复了待办事项修改史诗名称,同步更新所有Issue的问题。
  • 修复了累计流量图色块与报表中线条的颜色不对应。

持续交付

  • 修复了开发流水线模块应用选择框中,“最近”部分的缓存问题。
  • 修复了创建实例时,实例名称为空的问题。
  • 修复了过滤表内无法叉掉已选选项的问题。
  • 修复了网络模块外部IP更新失败的问题。
  • 修复了应用管理界面中过滤表筛选的逻辑问题。
  • 修复了平台更新用户邮箱后,GitLab同步邮箱失败的问题。
  • 修复了不同集群版本StatefulSet的版本不通,Agent Controller兼容不同集群版本时产生的问题。
  • 修复了开发控制台的请求问题。
  • 修复了pipeline中sonarqube的stage丢失的问题。
  • 修复了导出应用时出现的后缀名格式问题。
  • 修复了创建环境时由于名称校验引起的报错问题。
  • 修复了开发控制台中流水线内job顺序显示异常的问题。
  • 修复了删除存在关联环境的集群时,会先弹出删除指令的问题。

测试管理

  • 修复了测试阶段关联用例文件夹版本展示的错误。
  • 修复了测试摘要页面表格换页滚动的错误。
  • 修复了测试计划页面甘特图中改变时间产生的页面错误。
  • 修复了创建缺陷时经办人无法进行搜索的问题。
  • 修复了测试计划中拖拽修改特定日期报错的问题。
  • 修复了测试缺陷报告特定数据量展示错误的问题。
  • 修复自定义状态创建问题。
  • 修复代办事项中创建版本时的预计发布日期可以比开始日期早的问题。
  • 修复复制子任务的时候没有父任务信息一起复制,导致复制后的子任务没有父级的问题。

微服务开发框架

  • 修复站内信接口请求次数太多的问题。
  • 修复系统公告富文本编辑器显示异常的问题。
  • 修复项目层任务详情中,创建任务跳转异常的问题。

删除

敏捷管理

  • 删除了统计图去掉“解决结果”统计类型。
  • 删除了累计流量图详情中列选项。
  • 删除了史诗燃尽图 & 版本燃尽图 已完成问题链接去掉。

持续交付

  • 移除了平台中点击步骤条进行跳转的功能。

社区参与

感谢以下这些朋友在社区论坛中提出反馈和意见,在此次版本更新中作出突出贡献。

  • @phoenix
  • @apache
  • @Tranglezyx
  • @lisen2023
  • @deepMan
  • @kevin
  • @unbelievable

更加详细的内容,请参阅Release Notes官网

欢迎通过猪齿鱼的GitHub猪齿鱼社区进行反馈与贡献,帮助Choerodon猪齿鱼不断成长,猪齿鱼将持续迭代优化,敬请期待。