Openshift-DevOps-文化与实践指南-全-

Openshift DevOps 文化与实践指南(全)

原文:annas-archive.org/md5/06d228355d48b61adc3e335db69be824

译者:飞龙

协议:CC BY-NC-SA 4.0

序言

关于

本节简要介绍了作者、全书内容概览、入门所需的技能以及完成所有技术主题所需的硬件和软件。

关于《DevOps 文化与实践(OpenShift 篇)》

DevOps 文化与实践(OpenShift 篇) 涉及了许多不同的实际操作——包括一些与人相关的、一些与流程相关的、一些与技术相关的——以促进 DevOps 的成功实施,从而推动 OpenShift 在组织中的采用。它介绍了许多 DevOps 概念和工具,通过持续的发现、调整和交付循环,将文化和实践联系在一起,并以协作和软件工程为基础。

容器及容器化应用生命周期管理已成为行业标准,而 OpenShift 在基于 Kubernetes 的企业产品市场中占据领先地位。DevOps 文化与实践(OpenShift 篇) 提供了一条在组织内构建高效能产品团队的路线图。

本指南将精益、敏捷、设计思维、DevOps、文化、引导和实践技术实现结合在一本书中。通过结合真实案例、实践性案例研究、引导指南和技术实施细节,DevOps 文化与实践(OpenShift 篇) 为在 Red Hat 的 OpenShift 容器平台上构建 DevOps 文化提供了工具和方法。

关于作者

蒂姆·比蒂是 Red Hat Open Innovation Labs 的全球产品负责人及高级首席参与顾问。他在产品交付方面的职业生涯已有 20 年,担任过敏捷和精益转型教练——一位持续交付和设计思维的倡导者,他将人们凝聚在一起,构建有意义的产品和服务,同时帮助大型公司向业务敏捷转型。他和妻子及狗杰拉德(一只拉布拉多犬,生活中的另一只“实验室犬”)一起住在英国温彻斯特,30 岁时他从猫派转变成了狗派。

迈克·赫本是 Red Hat Open Innovation Labs 的全球首席架构师,帮助客户转变工作方式。他的大部分工作时间都在帮助客户和团队通过 OpenShift 转变交付应用的方式。他是《DevOps 与 OpenShift》一书的合著者,喜欢户外活动、家人、朋友、好咖啡和好啤酒。迈克喜欢大多数动物,但不喜欢澳大利亚的巨大毛蛛(猎蛛),通常他是猫派,除非是星期二,那天他是狗派。

Noel O'Connor是 Red Hat EMEA 解决方案实践的高级首席架构师,专注于云原生应用和集成架构。他曾与 Red Hat 的众多全球企业客户合作,遍及欧洲、中东和亚洲。他是《DevOps 与 OpenShift》一书的合著者,并不断尝试学习新事物,取得了不同程度的成功。Noel 更喜欢狗而非猫,但最终被团队其他成员否决了。

Donal Spring是 Red Hat Open Innovation Labs 的高级架构师。他在交付团队中袖手高卷,处理任何需要解决的问题——从指导和辅导团队成员、设定技术方向,到编写代码和测试。他热爱技术,喜欢动手探索新技术、框架和模式。周末时,他经常可以在个人项目中编程并自动化所有事务。猫或狗?他两者都喜欢 😃

关于插画师

Ilaria Doria是 Red Hat Open Innovation Labs 的参与领导和首席顾问。2013 年,她进入了敏捷领域,成为一名教练,帮助大型客户进行数字化转型。她的背景是终端用户体验和咨询,运用开放实践引领复杂的转型,并在大规模组织中推广敏捷。彩色便签和涂鸦一直是她生活的一部分,这也是她为本书提供所有插图并构建所有数字模板的原因。她绝对是一个狗的爱好者。

关于评审员

Ben Silverman目前是 Cincinnati Bell 技术服务公司全球客户团队的首席架构师。他还是《OpenStack for Architects》、《Mastering OpenStack》、《OpenStack – Design and Implement Cloud Infrastructure》一书的合著者,并且是《Learning OpenStack》(Packt 出版)的技术审阅者。

当 Ben 不在写书时,他活跃于 Open Infrastructure Superuser 编辑委员会,并且是 Open Infrastructure Foundation 文档团队(架构指南)的技术贡献者。他还领导着位于亚利桑那州凤凰城的 Open Infrastructure 用户小组。Ben 经常受邀在客户活动、聚会和特别供应商会议上发表关于云计算和 Kubernetes 的采用、实施、迁移以及文化影响的演讲。

学习目标

  • 在你的组织中实施成功的 DevOps 实践,并进而实现 OpenShift

  • 在持续交付的世界中处理职责分离

  • 通过以应用为中心的视角理解自动化及其重要性

  • 管理持续部署策略,如 A/B 测试、滚动发布、金丝雀发布和蓝绿发布

  • 利用 OpenShift 的 Jenkins 功能执行持续集成流水线

  • 管理并将配置与静态运行时软件分离

  • 掌握沟通与协作,通过持续发现和持续交付在规模上交付卓越的软件产品

受众

本书适用于任何对 DevOps 实践(无论是 OpenShift 还是其他 Kubernetes 平台)感兴趣的读者。

本书为软件架构师、开发人员和基础设施运维工程师提供了对 OpenShift 的实用理解,教您如何高效使用 OpenShift 进行应用架构的有效部署,并与用户和利益相关者合作,交付具有业务影响的成果。

方法

本书将简洁的理论解释与实际案例相结合,帮助您发展成为一名 DevOps 实践者或倡导者。

硬件和软件要求

有五个章节深入探讨技术。第六章开放技术实践——起步,正确开始第七章开放技术实践——中期 专注于启动技术环境。第十四章构建它第十五章运行它,以及 第十六章拥有它 讲解了将功能开发和运维到我们在 OpenShift 平台上运行的应用程序中。

我们建议所有读者,无论其技术水平如何,都深入探索本章节中解释的概念。您也可以选择亲自尝试一些技术实践。这些章节提供了相关的指导。

运行这些练习的 OpenShift 配置要求已在附录 A 中列出。

约定

文本中的代码字、数据库名称、文件夹名称、文件名以及文件扩展名如下所示:

我们将涵盖使用 Jest 对 PetBattle 用户界面进行组件测试的基础知识。用户界面由多个组件构成。您第一次进入应用程序时看到的组件是首页。对于首页组件,测试类被命名为 home.component.spec.ts

describe('HomeComponent', () => {
  let component: HomeComponent;
  let fixture: ComponentFixture<HomeComponent>;
  beforeEach(async () => {...
  });
  beforeEach(() => {...
  });
  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

下载资源

所有技术资料都可以在本书的 GitHub 仓库中找到:github.com/PacktPublishing/DevOps-Culture-and-Practice-with-OpenShift/

所有视觉资料的高分辨率版本,包括照片、图表和数字化工件模板,均可在 github.com/PacktPublishing/DevOps-Culture-and-Practice-with-OpenShift/tree/master/figures 上找到。

我们还提供了来自我们丰富书籍和视频目录的其他代码包,您可以在 github.com/PacktPublishing/ 中查看。快来看看吧!

我们意识到技术会随时间变化,API 也会不断演进。有关技术内容的最新变更,请查看上述本书的 GitHub 仓库。如果您在使用过程中遇到任何问题并希望直接与我们联系,请在该仓库中提一个问题。

第一部分:实践成就完美

在这一节中,我们将介绍本书的来源以及它是如何组织的。

第一章,介绍——从“为什么”开始 关注的是本书的目的和目标读者。第二章,引入 DevOps 及一些工具 用我们的话解释了什么是 DevOps,以及它如何帮助加速产品开发的价值链。我们将探讨这个链条以及 DevOps 文化和实践所解决的瓶颈。我们还将介绍几种重要工具,这些工具将在整本书中帮助我们应对我们将应用的多种不同类型的实践。在第三章,前进的旅程中,我们将介绍如何使用真实世界的故事和案例研究,这将贯穿整本书,并概述本书剩余六个章节的组织结构。

这将为我们奠定基础,开始一段持续发现、选择和持续交付的旅程。

第一章:1. 引言 — 从“为什么”开始

你已经拿起这本书并开始阅读,真是太感谢了!

也许你阅读了封底,它给了你足够的信息,激起了你进一步翻开书本的好奇心。也许是朋友或同事推荐给你的。也许你因为其他原因偶然发现了它。不管是什么原因,我们非常高兴你抽出时间开始阅读这本书,并希望你能从中获得价值,并且想要继续阅读下去。

在深入讨论本书内容及其涵盖范围之前,我们希望先从“为什么”开始。这是我们用来创建共同愿景的一个实践。我们为什么要写这本书?它要解决什么问题,目标读者是谁?

图 1.1:创建共同的目标愿景

为什么 — 出于什么原因或目的?

尽管这本书可能被定位为一本关于技术的书,但它最多只有三分之一是关于技术的。DevOps 实际上是关于协作的。我们写这本书是因为我们希望提高你对 DevOps、协作以及在像 OpenShift 这样的容器平台上应用文化和工程实践的理解。我们希望使迁移到 DevOps 变得更加容易,并为你提供一条更加清晰的路径,让你能在 OpenShift 上应用 DevOps。我们希望你在阅读时感到兴奋,并获得一些灵感,了解如何应用 DevOps 的原则和实践。我们希望装备你去尝试这些新的技术和实践。

随着你逐渐阅读本书,我们希望你不断衡量使用这些新技术的有效性(影响/价值)。事实上,每次尝试某种方法时,我们希望你思考并衡量它带来的影响。

这种影响可能是在个人层面:尝试这个方法对我、客户或用户有何影响? 例如,它是否减少了我完成一组交付活动的周期时间? 另一方面,它也可能是对你所在团队或部门的影响:团队满意度是否提高了? 作为一个团队,我们达成了什么? 这种影响甚至可能在组织层面或社会层面体现:它是否减少了影响客户的操作事故数量? 我们相信你会迅速看到这些方面的积极效果。结果,也许你会给我们写下好评,并告诉所有朋友这本书。如果没有,也许你可以改变用途,将这本书当作门挡或显示器支架,当然,这也会给你带来另一种价值!

如果你不知道从哪里开始衡量价值,继续往下读 — 我们承诺会涵盖这个内容。

我们刚才做的,就是开始使用我们写这本书时采用的一种实践和技巧。我们使用了从“为什么”开始的实践,这也是我们在与每个团队或组织合作时总是力求做到的。

那么,什么是实践?实践是一种帮助团队实现特定目标的活动。它不仅仅是一个想法,它是你反复做的事情,以磨练或提升技能。实践具有以下特点:

  • 授权:本书中的实践将帮助团队发现并迭代交付。

  • 简洁:它们可以在几分钟内读完。

  • 无关框架:实践不要求团队遵循特定的框架。

  • 经过验证:实践在真实世界中得到了检验。

  • 可重复:实践可以多次使用。

希望在本书中,你会通过我们分享的真实交付经验中的经历、故事和技巧,看到我们实践所宣扬的内容,这些内容包括如下故事:

  • 我们与一家保险公司合作,使用 DevOps 和 OpenShift 重建他们的一个应用程序,但在我们意识到我们在重新开发一个用户不需要且未使用的应用时,出现了停顿全局的时刻(这个实践我们将在下一节讨论)。

  • 我们与一家欧洲汽车公司合作,帮助其团队启动现代应用开发和敏捷实践,结果产品负责人质疑他们如何向管理层证明这是一种更好的工作方式,而管理层只使用电子表格和数字

  • 有一家电信公司在节假日期间遭遇了巨大的故障和功能问题,并急于学习新的文化和工程实践,以推动其基础设施和应用程序实现自动扩展和自愈。

为什么我要听这些人的话?

在你继续阅读这本书中的四位作者的内容之前,也许值得稍微停下来,分享一下我们的所有轶事、理论、故事和技巧的背景来源。

我们都在 Red Hat 工作,特别是我们都是 Red Hat 服务组织的一部分,这意味着我们定期与 Red Hat 客户互动并提供专业服务。这包括从帮助安装和支持 Red Hat 技术的早期采用,到推动基于 Red Hat 技术和 Red Hat 文化的大规模转型计划。

Red Hat 的文化相对独特,因为它完全基于开源文化和开放组织(Red Hat 就是其中最大的例子之一)。这意味着 Red Hat 组织的运作方式具有与开源文化和哲学紧密相关的特点。它们包括协作、社区、包容性、适应性和透明度。我们强烈推荐通过阅读Jim Whitehurst 的《开放组织》1 来深入了解 Red Hat 的开放组织哲学。

本书所涵盖的大部分经验以及我们将分享的故事和建议,源于由 Red Hat Open Innovation Labs(简称 Labs)主导的项目。Labs 提供了一种沉浸式和开放的方法,旨在创造新的工作方式,帮助我们的客户及其团队利用开放技术和开放文化开发数字解决方案,并加速业务价值的实现。Labs 提供的主要服务被称为驻场,这是一个为期四到十二周的时间限制项目,在此期间,客户的工程师将与 Red Hat 的技术和文化专家一对一配对。

四位作者中,我们已经参与了全球超过 50 次 Open Innovation Labs 的驻场项目,此外还参与了许多其他专业服务项目。由于 Labs 驻场项目时间相对较短,我们能够迅速学习不同的技术、方法和实践。我们能看到哪些方法有效,哪些方法效果不佳。我们积累了大量的故事和建议。本书的核心内容就是分享这些故事和建议。

这本书从哪里来?

这本书的标题是作者们所开发的一个培训启蒙项目的演变,名为DevOps 文化与实践启蒙。这是一个由 Red Hat 主办的沉浸式培训课程,旨在为 Red Hat 的客户、合作伙伴和员工提供能力提升。

我们最初创建这门课程是因为我们所在的 Red Hat 服务领域正在增长,我们需要一种方法来持续提高全球范围内的客户、员工和我们自己组织中的实践和文化背后的热情与共同理解。我们希望通过探索所有我们认为在与客户共同将许多产品推向市场时取得成功的核心实践来实现这一目标。这包括帮助理解原因并推动产品发现的实践,以及帮助我们以迭代和增量的方式安全、保密和自信地交付的实践。然后是第三个结果,那就是享受乐趣。如果在这一过程中不能有一些乐趣、玩笑和享受,我们实在看不出这一切的意义——这是那个神秘词“文化”的关键组成部分。

1 https://www.redhat.com/en/explore/the-open-organization-book

其中一个成功的关键因素是将大量的经验和真实的故事融入到我们的交付过程中,并且在课程的实施中广泛地应用了我们自身的实践。每次我们运行课程时,我们都会使用完成定义 2 实践,向参与者解释我们将在课程中教授的每项实践都会以一致的方式呈现,遵循以下流程:

  1. 通过理论介绍实践,并概述它是什么、为什么要使用它以及如何使用它

  2. 一个实践性的操作练习,让每个参与者都能在课程结束时亲自尝试使用这些实践,并从中获得一些学习和经验。

  3. 一个实际的案例,展示了这些实践如何在真实客户交付项目或产品开发计划中得以应用。

本课程中教授的核心实践包括从发现实践(如影响映射和事件风暴)到交付实践(如冲刺计划和回顾)的各个方面。它们包括一套我们认为在建立高效、持久的产品团队中非常有力的实践,如社交契约、团队情感实践以及集体编程和配对编程。它们还包括许多参加课程的人最强烈联想到“DevOps”一词的工程实践,如持续集成、持续交付、测试驱动开发和基础设施即代码。

这门课程的独特之处之一在于它能吸引广泛的受众。它不仅仅面向技术人员或设计师。事实上,我们接受了一个跨职能团队的概念,团队成员从工程师到项目经理,从基础设施专家到用户体验设计师都有。我们认为这门课程为打破部门壁垒提供了机会。我们故意不为不同类型的人设置不同的课程轨道。目标是让参与者对所有可以应用的实践有共同的理解,从而真正欣赏和赋能 DevOps 文化。

在全球范围内举办了超过一百次这门课程后,我们从中学到了大量经验,并在不断改进它。

面对有机会撰写一本关于 DevOps 和 OpenShift 的新书,并应用来自 Stefano Picozzi、Mike Hepburn 和 Noel O'Connor 现有书籍《DevOps with OpenShift – Cloud Deployments Made Easy》中的新学习和更先进的技术,我们考虑了让 DevOps 和 OpenShift 在任何选择采用该技术的组织中取得成功的关键因素。

2 https://openpracticelibrary.com/practice/definition-of-done/

成功的因素都基于人员、流程和技术,通过我们在全球客户中应用的多种实践,特别是我们使用 DevOps 文化和实践赋能的实践。

本书的目的是帮助你理解并准备应用多种不同的实践——其中有些与人员相关,有些与流程相关,有些与技术相关——这些实践将使 DevOps 文化和与 OpenShift 的实践在你的组织中取得成功。

这本书到底是为谁准备的?

本书面向广泛的受众——任何对 DevOps 实践和/或 OpenShift 或其他 Kubernetes 平台感兴趣的人。我们要开展的第一个活动之一是集合起来,列出我们打算为其编写的不同角色和读者类型。这些包括以下几类:

图 1.2:预期受众

  • Caoimhe,一位技术负责人,管理一支软件开发团队。她希望了解更多关于 DevOps 的内容,以便帮助团队采纳优秀的 DevOps 实践。

  • Fionn,一位项目经理,负责一组遗留软件应用程序,并希望更新自己团队的工作方式,以便利用他听到许多人讨论的 DevOps

  • Padraig,一位敏捷教练,经验丰富,擅长应用 Scrum 等敏捷交付框架,并希望在 DevOps 方面进一步提升自己的技能和经验。他认为这将真正为他所辅导的团队增加价值。

  • Tadhg,一位用户体验设计师,想更好地了解公司开发团队其他成员如何利用他的设计,以及他如何与他们合作以交付产品。

  • Séamus,一位 IT 领导者,负责执行公司采纳容器和云原生技术的技术战略,覆盖公司整个 IT 体系。他选择了 OpenShift 容器平台 (OCP) 作为支持这一战略的产品。他希望确保 OCP 能够快速产生投资回报,并确保公司所有 IT 团队都能广泛采用。

  • Aroha,公司首席信息官 (CIO)。她希望确保公司员工与公司战略保持一致,并能够充分利用正在推动战略的技术和组织决策。她希望业务能变得更加敏捷,并能够在市场条件变化时迅速适应。她希望了解其他行业中规模相似的组织(包括她所在行业的组织)成功做了什么,以及他们认为的关键成功因素是什么。

  • Siobhán,一位基础设施工程师,已经使用 Kubernetes 多年,现在是一个团队的一员,该团队正在向她的组织引入 OCP。她希望确保平台配置能够支持她团队的目标,并希望了解如何与开发团队协作,以便最大化地发挥技术的价值。

  • 艾玛,一位项目经理,拥有二十年通过前期规划、跟踪交付物与计划的对比,以及管理风险、问题和依赖关系,具备强大项目报告和利益相关者管理技能的 IT 项目交付经验。她对软件交付所需的时间感到沮丧,无法快速解决用户需求和修复问题。她意识到,与其继续以项目为中心的方式,不如转向以产品为中心的方式。她希望通过转型成为产品经理,来重新提升自己的技能。在这一过程中,她希望能快速测试和调整,快速交付成果,适应市场变化,同时提高性能、正常运行时间、恢复时间等。

  • 芬恩,一位系统测试员,他非常自豪于在软件交付给客户之前进行质量保证。他的业务分析背景帮助他制定全面的测试方法和脚本,近年来,他还领导了性能测试、安全测试和可操作性测试。他渴望了解如何在工作中引入更多的自动化,并扩展到其他测试形式。

从 I 到 T 到 M

通过这本书,我们希望人们能够摆脱I 型,不再只是专注于一个技能或一个领域的专家。我们希望他们能成为更具T 型特征的人才,他们在某一特定领域(如基础设施或用户体验设计)仍然有深厚的技能和经验,但也能理解并跨越所有其他技能领域,进而构建跨职能团队。这可能是一个前端工程师的例子,他也与 API 工程师紧密合作。

一个优秀的跨职能团队是指整个团队具备所有必要的技能和经验,能够从用户或业务方的需求出发,直到最终的生产交付。一个团队可能由许多I 型人才组成,但这种团队很快会依赖于某些特定个体,而当这些个体不可用时,团队可能会陷入困境。例如,如果需要进行数据库变更来暴露一个新的 API,而只有一个团队成员掌握这项知识,团队就可能陷入停滞。如果团队中有更多的T 型成员,那么团队之间的协作、共享和合作的机会就更大,对个体的依赖也会减少:

图 1.3:技能转型

我们希望这本书能帮助I 型人才转变为T 型人才,甚至成为M 型人才。M 型人才受到启发,深化他们的学习,将其带入其他领域,掌握多项技能,从而建设更强的跨职能团队。

结论

本章简要介绍了我们为什么写这本书以及它的目标读者是谁。

我们介绍了自己,以及如何利用我们的应用知识、经验和所学写这本充满故事和实例的书。

我们分析了本书中所针对的不同人物形象,并探讨了如何帮助这些专注的 I 型人才转变为 T 型或 M 型人才,以建立更强大的跨职能团队。

在下一章,我们将介绍 DevOps 以及我们在本书中将使用的一些工具,用于组织和解释 DevOps 实践。

第二章:2. 介绍 DevOps 及一些工具

在容器化的世界中,成为 DevOps 的意义是什么?人们对于 DevOps 有不同的看法,关于它的定义和运作方式也存在不同的理解。

在本章中,我们将解释我们对于 DevOps 的看法,以及 DevOps 关注的瓶颈和挑战。我们将介绍软件产品交付中的价值链概念,并展示如何利用精益、敏捷和 DevOps 社区中的不同技术来优化并加速这一价值链。

我们还将介绍一些工具,例如 Mobius 循环和 Open Practice Library,帮助我们在本书的其余部分中导航并理解许多实践。

DevOps 目前是一个流行的术语!似乎每个技术十年都会有一个新的流行术语与之相关。

在 2010 年代,敏捷(Agile)是流行的术语——这个将是一个敏捷项目我们将使用敏捷交付这个项目,或是我们将使用敏捷方法论,这些都是我们经常听到的常用语。它通常(而且至今仍然)被错误地理解为是加快软件交付的方式。事实上,敏捷更加关注的是更早、更频繁地交付商业价值,并推动持续学习的文化。敏捷现在已经正式“成年”——它在 2019 年 2 月迎来了 18 岁生日。即便经过这么长时间,我们依然热衷于使用 2001 年发布的《敏捷宣言》中的价值观和原则 1。

容器现在是另一个流行的术语。我们看到很多人使用这个词,但他们未必完全理解容器的含义,以及为什么个人、团队和组织会通过使用容器受益。

因为本书是关于 DevOps 和 OpenShift(一种容器管理平台)的,我们将去除这些术语的噪音,讨论一些非常实际的、来自真实世界的经验和案例,讲解 DevOps 和 OpenShift 容器背后的真正价值。

让我们回顾一下过去,看看我们认为这些现象是从哪里来的。

我们都在 IT 行业工作了几十年(有些人工作年限更长!)。在一次喝啤酒时回顾我们交付 IT 项目的经历时,我们发现所有 IT 项目都有一些共同的特点,而这些特点始终存在。我们还识别出了一些 IT 项目交付中的价值链空白,这些空白在我们看来是导致进度缓慢的原因。

价值链

我们参与的每一个项目都有某种形式的最终客户或用户。有时他们是外部用户,比如一位在线购物者希望使用手机应用购买最新款的贾斯汀·比伯床单!有时,他们是组织内部的团队,比如运营团队或公司某个特定部门。我们都能达成一个共识,那就是:我们工作的目标始终是拥有笑脸、快乐的客户:

图 2.1:满意的客户——组织的终极目标

1 www.agilemanifesto.org

在我们之间,我们帮助了许多组织,从公共部门、金融行业到零售和慈善组织。我们见识过一切!在回忆时,我们讨论了我们一些项目的最终结果;我们思考了我们的目标——几乎总是与某种货币价值相关联,这也是我们存在的原因。当然,也有其他动机,比如提高客户满意度、减少风险、改善安全性和性能,但关键是,我们任何商业客户的业务核心部分都是赚钱和降低成本。

所以,最终,价值往往与某种形式的金钱联系在一起。我们三位作者是爱尔兰人,第四位来自新西兰,因此我们觉得用金锅来体现这一点非常合适!

图 2.2:利润——每个商业组织的共同目标

1990 年,James Womack, Daniel Jones, 和 Daniel Roos 合著的《改变世界的机器》一书首次提出了“价值流”一词。这个概念在同一作者所著的《精益思维》一书中得到了进一步推广。根据他们的观点,价值流是一个组织为了满足客户需求而进行的一系列活动。更广泛地说,价值流是设计、生产和交付产品或服务给客户所需的一系列活动,它包括信息流和物资流。大多数价值流都是高度跨职能的:客户需求的转化到产品或服务会经过组织内许多职能部门或工作团队:

图 2.3:客户梦想中的金锅

让我们把这个想象成我们的客户梦想中的金锅。他们不断思考如何从他们的产品或想法中获得最大的回报,以便创造更多的财富。那么,他们是如何做到这一点的呢?

差距

我们将探讨客户与组织中的商业人员之间、商业人员与开发人员之间、开发人员与运营人员之间的价值链差距。

待办事项的大列表

我们在软件开发过程中始终看到的第一个差距是从最终客户收集信息并形成客户需求清单的过程:

图 2.4:理解和收集客户需求

我们早期的项目通常涉及业务分析师长时间记录他们能想到的每一个可能的需求,形成大量的业务需求文档。目标是预先考虑每个可能的客户旅程或场景,通过构建包含所有可能情况的规范来覆盖所有基础。听起来很死板,对吧?如果我们做出了错误的假设呢?

展示价值与构建正确的东西

第二个空白围绕着向客户展示价值展开。通常,所进行的项目是为了包括所有所需的功能和想法,以便能够将它们一起发布。一旦项目进入生产阶段,它通常只有一个较小的运营预算,用于支持小范围的增强和问题解决。听起来可能需要很长时间才能将应用交付给最终用户,对吧?

我们之所以称这些为空白,有两个原因。首先,过程漫长——从开始一个项目到确认需求之间,可能要经过数月,甚至几年。其次,在交付任何东西之前,试图收集所有可能的需求会意味着用户在多年内无法获得实际的好处,且往往会构建出错误的功能并交付给不满意的客户:

图 2.5: 使用以人为本的实践来理解客户需求

近年来,没有构建正确的东西这一空白已经通过以人为本的设计和设计思维的出现得到了填补。这些是一套将最终用户置于捕捉产品需求和要求核心的实践方法。

我们通过直接与用户交谈,形成更强的同理心2 来收集信息:

图 2.6: Merriam-Webster 对“同理心”的定义

本书将探讨如何通过影响映射、事件风暴和以人为本的设计等技巧来帮助软件开发过程。我们还将探讨其他实践,帮助我们定义解决方案和功能,最重要的是确保解决方案与业务价值相连接。我们将展示如何将用户界面原型和技术难题等研究活动与实验相结合,以便形成根据交付的业务价值优先排序的产品待办事项列表。我们将展示如何通过使用足够的信息来使产品更容易理解。

我们如何完成清单上的事项?

让我们考虑第二个关于向用户交付价值的空白。这个空白专注于如何将待办事项清单中的项目转化为可用的软件。

传统方法是签署并确认经过长期商业分析和捕捉过程的有限需求集。项目的范围已经锁定,制定了严格的变更控制过程和治理机制,以应对与文档化需求的任何偏差。

2 www.merriam-webster.com/dictionary/empath

然后,一支软件设计师和架构师的团队开始工作,制定一个高层设计HLD),以根据指定的业务需求提供解决方案或一系列解决方案。这些需求还会经过关键项目利益相关者的正式审查流程,一旦签署确认,便成为解决方案范围的参考来源。

通常,不同的设计文档会在下一阶段编写——详细设计文档、程序规范、数据设计、逻辑架构蓝图、物理架构解决方案等。这些文档的编写是为了支持一个已定义、已定日期并且已签署的高层设计(HLD),而高层设计本身是基于一组已定义的业务需求规格进行签署的:

图 2.7:传统应用开发生命周期

任何对早期文档的修改都会直接影响重新评估和更新后续设计文档的时间和成本。软件开发团队可能参与了部分文档的编写或审查。然而,通常会鼓励他们在这些设计未确定之前,不开始任何编码或开发活动。一些组织通过在此阶段才将开发团队引入来降低项目成本。开发通常按功能划分孤立进行,且对大局缺乏了解,自动化测试也有限。

在项目计划中预定的某个时点,所有开发人员都应该将自己的编码组件交付到测试环境中。也许每个开发人员会手动构建并部署自己的代码到测试环境中。一些较大的项目通过设立构建基础设施团队来寻求规模经济,由该团队为所有开发人员完成这项工作。一旦所有组件交付完毕,一个单独的测试团队开始执行他们在前几周和几个月里编写的数百个测试脚本,以根据业务需求和高层设计文档来测试解决方案。这是一些组件第一次被集成并一起测试。当然,问题和缺陷会驱动开发团队和设计师进行返工,修复这些问题。

正如设计文档有不同的层次,测试也通常经历不同的层次,每个测试阶段都会在前一个阶段完成后开始。测试经理会签署一组测试结果,标志着下一级别的测试可以开始。测试范围从组件集成测试到更广泛的系统集成测试、安全性和渗透测试、性能测试、故障恢复和可操作性测试,最后是用户验收测试!

在解决方案大规模上线之前的最后阶段,通常会进行用户验收测试,涉及一组焦点用户和测试系统。在许多情况下,这些用户第一次见到实施的系统可能会是几个月甚至几年之后。一旦解决方案的用户验收获得批准,就会授予部署到生产环境的绿灯。最终,软件交到真正的最终用户手中,希望可以从这一切工作中获得业务收入。

你可能觉得这个过程听起来漫长而冗长 — 实际上确实如此!许多项目在不同阶段都遇到了延迟,一个最初作为多月项目计划开始的计划最终变成了多年项目。对于好奇的人来说,维基百科上甚至有一些史诗级失败案例的列表:en.wikipedia.org/wiki/List_of_failed_and_overbudget_custom_software_projects

在开发过程中,业务条件经常会发生变化。会产生新的功能请求。在测试过程中,可能会出现分析和需求捕捉阶段未考虑到的需求差距。市场在开发过程中并未停滞不前,竞争对手公司可能已经开始更快地进行创新。竞争甚至会提出更多的功能请求,这种过程类似于功能比较战。

当然,总是有某种变更控制程序来处理这样的新范围。在复杂的工作程序中,将特性添加到工作计划的前导时间可能会从几个月到几年不等。为了将某些内容投入生产,项目执行人员可能只会对任何更改说“不”,并专注于完成项目计划的最后阶段。

这意味着最终交付到生产环境的解决方案在几年后对用户来说有些令人失望。时间和行业在不断进步。这些程序的最大挫折在于经常延迟交付、超出预算,并且通常交付的解决方案缺乏用户满意度或质量。

稍微退后一步,我们面对将特性列表转化为软件交付的巨大鸿沟。由于各个阶段的工作依次进行,这一过程被称为瀑布模型,因其工作阶段的独立性质而闻名,与非常长的时间相关联:

图 2.8:传统交付成果及其缺点未能达到客户满意度

让我们考虑如何用更现代的软件开发流程填补第二个缺口。与以往的工作方式相比,现代开发者如何更快地将用户需求转化为工作软件解决方案?

2001 年,由 17 位 IT 人士编写的《敏捷宣言》标志着敏捷运动的诞生,这也引发了交付软件的替代方法和思维方式的改变。许多参与编写《敏捷宣言》的个人曾经解决过瀑布开发所描述的许多问题。Jeff Sutherland 和 Ken Shwaber 创建了 Scrum 框架,用于软件开发,其中包括更频繁地交付小规模增量的价值——他们使用了“冲刺”这一术语,这是一个固定时间段,通常为一到四周(通常为两周),在这个时间段内,一组事件和角色会协同工作,使得大型解决方案能够以迭代和增量的方式交付。Kent Beck 和 Ron Jefferies 主导了许多 极限编程XP)运动的工作,专注于更快地交付价值,并致力于那些有助于提高回顾、测试和发布效率的关键实践,利用更好的协作和增加的自动化:

图 2.9:DevOps 实践的实施导致更快的交付和更好的产品

在本书中,我们将展示不同的软件交付实践,以及我们如何通过使用来自 Scrum、看板、XP、精益和一些扩展框架的混合实践,帮助更快地交付价值。所有这些底层实践只是帮助缩小从创意或需求捕捉到交付之间差距的工具。这一直是我们不断改进的领域,目标是将差距最小化,并进入持续交付的模式。

开发到运维

在我们优化软件交付流程的努力中,还有一个缺口需要填补。第三个缺口是开发团队和运维团队之间的缺口。

在我们的瀑布流程中,我们已经达到了签署的解决方案完成用户验收测试并经过大爆炸上线的阶段。那么,接下来发生了什么呢?

通常,负责维护和支持的全新团队会接手这个解决方案。这个新团队的成员并未参与任何设计、开发或测试工作,因此,项目计划中会额外增加时间用于知识传递。交付团队会编写冗长的文档,希望这些文档能成为未来运维团队的有用资源。

在这一阶段,软件包可以比喻为被从开发团队“扔”到运维工程师的“墙”那边。运维团队常常需要通过调查生产事故、解决之前未发现的 bug 以及处理需求规划阶段未考虑到的新场景,来“硬性”学习软件。

图 2.10:致力于打破开发与运维团队之间的墙

为了弥补这一缺口,我们必须将开发和运维团队聚集在一起。拆除那堵墙,消除那些壁垒!拆除墙壁后,形成了专注于开发和运维活动的新团队。这些团队共同负责整个解决方案,并可以根据彼此的需求来设计解决方案。

DevOps 这个术语的诞生源于我们不再拥有孤立的开发和运维团队。近年来,我们看到从这个理念中衍生出了其他各种术语,如 DevSecOps、BizDevOps、DesOps,甚至是 BizDesDevSecOps!

BizDesDevSecOps有点拗口,因此在本书中我们将使用产品团队这个术语来描述它。它解决了填补软件开发过程中的所有空白并拆除所有壁垒的最终目标。

图 2.11:填补软件交付过程中的空白

请注意,我们不会使用DevOps 团队这个术语——拥有一个专注于 DevOps 的团队或个人的想法与 DevOps 的哲学背道而驰——即协作、跨职能和消除壁垒。你有多少次在 LinkedIn 或其他网站上看到招聘 DevOps 工程师的广告?DevOps 工程师或 DevOps 团队的出现,可以被视为又一个壁垒的形成。

人员、流程和技术

DevOps 的核心其实就是协作。它关乎于通过拆除壁垒和打破隔阂、去除瓶颈和障碍,来为你正在构建的解决方案负责,并为其感到骄傲。这加速了连接客户感知需求与产品交付之间的价值流。

技术单独无法解决你所有的业务问题。不管你正在评估或被销售的是什么平台或软件产品,除非你的组织学会了采纳正确的人员、流程和技术的平衡,否则目标将无法实现。

本书的内容旨在寻找人员、流程和技术变革的正确组合,以在持续的基础上最大化业务成果。这需要心态的转变和行为的改变。本书将探讨我们与数百个组织合作时,所见到的最有效的行为改变。我们观察到,这种心态和行为的改变在所有角色中都是必要的,而且我们需要打破组织内部的壁垒,正如我们之前所看到的,这正是驱动软件开发中的差距和低效的原因。

图 2.12:人员、流程和技术之间的健康平衡

组织中的每个人都应该关注人员、流程工程和技术,以推动期望的结果。我们希望打破这三大支柱之间的壁垒,使它们更加紧密。对于那些可能更专注于其中一个领域的读者来说,了解其他两个领域同样能获得同等(如果不是更多的话)价值。

这意味着,一位硬核软件工程师或架构师可以获得一些深刻的见解和指导,了解为什么人员、文化和协作对他们的角色同样重要。

曾经是项目管理方法论专家,并且现在正在学习更具敏捷性的交付实践(如 Scrum)的人员,也可以利用本书了解现代技术方法,如 GitOps、CI/CD 和无服务器架构。他们可以学习这些方法为何重要,并理解它们带给组织的商业价值,从而能够清晰地表达这些方法的意义。

一位关注员工留存的领导者可以了解到,掌握这些现代技术实践,如迭代和增量交付策略,可以通过交付被满意客户使用的高价值产品,最大化组织成功的机会。

莫比乌斯环与开放实践库

在本书中,我们将探索许多不同的实践。我们将解释它们是什么,以及为什么要使用它们。我们将为你提供一些如何使用它们的指导。我们将分享一些我们如何使用它们的真实案例,并且在可能的情况下,我们甚至会展示它们的实际应用。通过我们的宠物对战案例研究(稍后详细介绍),我们将以一种有趣的方式使它们栩栩如生,并分享我们在实践中获得的最佳经验。

我们在几年前与客户的新团队合作时遇到的一个问题是,如何解释你可能在什么情况下以及以什么顺序使用不同的实践。我们应该从哪种实践开始?哪种实践与之前实践产生的输出相得益彰,依此类推?

为了帮助这一点,我们利用了一个名为 Mobius 的开源导航工具。这个工具由 Gabrielle Benefield 和 Ryan Shriver 创建。包括许多开源画布和文献在内的大量优秀材料可以在www.mobiusloop.com找到。Red Hat 开放创新实验室在其所有驻地以及 DevOps 文化和实践启用课程中都使用了这些开源材料。3 我们将在本书中使用它来构建内容和章节。

图 2.13:莫比乌斯环 4

3 github.com/rht-labs/enablement-docs

4 本书中引用的 Mobius 循环资源(mobiusloop.com/)由 Gabrielle Benefield 和 Ryan Shriver 提供,并采用 CC BY 3.0 许可。书中的后续图像包含基础层的额外修改。更多信息请参见 creativecommons.org/licenses/by/3.0/

Mobius 是一个连接发现与交付的框架,可以用来将战略与产品、运营连接起来。共同点是可衡量的成果。Mobius 用于理解、对齐和分享可衡量的目标成果,以便进行测试和验证。

有许多原则支撑着 Mobius 导航器:

  • 成果重于输出:我们专注于为人们交付可触及的影响或成果,而不是交付可能无法推动成果的众多功能。

  • 多选项策略选项转变):我们旨在构建一个选项列表,一个研究计划、实验和实施特性的列表,用于测试假设,验证这些研究计划、实验和实施特性是否确实能推动预期的成果。

  • 快速交付:我们旨在通过短周期的交付和定期反馈与衡量,朝着持续交付的理念迈进。

  • 持续学习和改进:贯穿整个周期,以便我们的下一个选项集能对成果产生更好的影响。

Mobius 方法包含 七个核心元素,它们贯穿于一个连续且永无止境的流动中。这些元素可以在一个单一的画布上进行可视化,该画布是开源的,并通过创作共享许可提供,网址为 www.mobiusloop.com

  • 为什么描述了目标。我们为什么要这样做?我们要解决什么问题?我们要追求的目标是什么?

  • 关注最终用户。我们要为谁解决问题?

  • 成果是我们希望通过这些人达到的目标,是他们行为上的变化,这些变化影响着大结果,我们将如何衡量客户和业务的影响。

  • 选项是可能交付这些成果的潜在解决方案。它们帮助定义我们可以测试的假设,并帮助我们找到用最少的努力或输出实现预期成果的最简方式。

  • 交付是我们进行实验以向用户交付解决方案或一系列解决方案的周期,这样我们就可以衡量其影响。

  • 衡量是我们评估交付解决方案或一系列解决方案后发生了什么。我们检查解决方案的影响是否达到了预期成果,并评估我们取得了多大的影响。

  • 学习是反馈循环,它将我们带回到选项枢纽。我们从交付中学习,并评估下一步该做什么。我们交付的内容是否足够做出评估?我们是否要重新进入交付循环?我们是否达到了目标结果或通过学习验证了假设的无效性?我们是否回到发现循环?

像 Tadhg(我们的用户体验设计师)这样的角色通常会在发现循环中花费大量时间。像 Caoimhe(我们的技术负责人)这样的角色则传统上会专注于交付循环。像 Fionn(我们的项目经理)这样的角色通常会在这里花费大量时间,确立结果并收集选项。但随着我们推动跨职能团队的 T 型或 M 型人才模式,所有人在 Mobius 循环的每个阶段的参与都会带来极大的好处。Mobius 为基于可衡量的目标结果创建了一个共同的语言。

你可以将同样的结果驱动思维原则应用于战略、产品和服务交付,以支持业务和技术操作——我们将在本书后面回到这个想法。

Mobius 之所以强大,是因为它与框架无关。它能够与许多你可能已经熟悉的现有框架和方法进行集成——如 Scrum、Kanban、设计思维、精益用户体验、商业模式生成、精益创业以及过去几十年出现的许多其他优秀框架。你不必重新发明轮子,也不需要替换掉你已经喜欢并且有效的所有方法。

你可以在发现地图、选项地图和交付地图上捕捉关键信息——这些都是开源文档,依据创意共享协议www.mobiusloop.com上提供:

图 2.14:使用 Mobius 循环的发现、选项和交付画布

当红帽开放创新实验室开始使用 Mobius 时,我们将所有的实践活动围绕 Mobius 循环展开。一些实践与发现循环清晰对接,尤其是发现循环中的为什么和谁部分。像影响力映射、从结果开始和同理心映射等实践非常适合揭示该部分提出的问题。我们将在本书的后续章节中详细介绍这些实践。

诸如事件风暴和用户故事映射等实践在确立并可视化发现循环另一端的结果时非常有帮助。同样,我们将详细探讨这些实践,并分享它们效果的一些精彩示例。

像设计冲刺、如何做(how-might-we)和产品待办事项梳理等实践,有助于确定和组织可供选择的系列选项,以推动实现结果。

像冲刺规划等实践有助于规划并执行产品的增量交付,以推动实现结果。我们将探讨这些迭代交付实践,以及如何将不同的敏捷框架与 Mobius 结合使用。

像展示会和回顾会这样的实践有助于捕捉来自增量交付的度量与学习数据。

我们仍然有大量的实践方法,感觉它们并不自然地适合某个环节或选项的转换。当我们将所有与多个客户合作中非常有效的剩余实践列出时,发现它们可以归入两个领域。其中一组实践专注于创建文化和协作,另一组实践则是支持持续交付概念的技术工程实践。

在向他人解释这些实践时,我们提到这些是非常重要的实践,但不一定是你会安排在时间表上的实践。例如,你会了解到,像发现环节中的影响映射这样的实践是重要的计划性工作坊,你会执行并偶尔在未来回顾。而像交付环节中的冲刺计划、展示会和回顾会等实践,则是在迭代交付框架中严格安排的。但是,与文化和协作相关的实践或与技术工程相关的实践,更像是你随时、持续使用的实践。

像社会契约和完成定义这样的实践,不是一次性的实践,你不会在某个时间表上安排它们。这些是活生生的工作成果,团队会在日常工作中持续使用它们。同样,持续集成、测试自动化和基础设施即代码 —— 这些都不是你每周安排一次或两次的实践。这些是你时刻都在做的实践,它们构成了我们工作方式和环境的基础。为了有效实践莫比乌斯环中所呈现的持续交付和持续发现,我们需要有一个强大的文化、协作和技术工程实践的基础。

为了可视化这一过程,我们将基础添加到了莫比乌斯环中:

图 2.15:为莫比乌斯环添加基础

这个图形已成为一个简单的可视化工具,帮助我们导航不断增长的实践和技术列表,以实现数字产品的持续发现和持续交付:

图 2.16:通过莫比乌斯环实践持续发现和交付

开放创新实验室驻留涉及多次环绕莫比乌斯环旅行,通常从发现开始,然后进入交付阶段,再根据需要多次调整,可能会回到发现阶段,或者继续向交付推进。我们发现,为了使这个过程具有可持续性,必须建立文化和协作的基础,并且需要建立强大的技术工程实践基础。

开放创新实验室启动了一个名为开放实践库的开源社区驱动项目。开放实践库是一个由社区驱动的实践和工具的存储库。这些工具是由当前日常使用它们的人们共享的,旨在为寻求新创意和经验的人员提供灵感。

本书中你读到的所有实践都已被贡献到开放实践库中,在整个书中,我们将使用 Mobius 循环以及文化、协作和技术实践的基础作为参考点,确定我们所有开放实践在何时何地以及如何结合起来,以便与 OpenShift 一起交付优秀的 DevOps 文化和实践。

Mobius 和开放实践库的一个重要特点是它并不是强制性的,它不是一种方法论。它不会告诉你具体在何时何地使用哪些实践。可以把开放实践库看作是一个工具箱——一个非常有组织的工具箱,里面有很多隔间和架子。这些实践已经被组织成多个隔间,以帮助发现,尤其是为什么,然后推导出结果。工具箱中有一个抽屉,里面放着所有有助于形成、组织和优先排序选项的工具,以及如何在后期调整循环的工具。工具箱中有一部分包含所有与交付相关的工具——无论是与敏捷实践相关的迭代式和增量式交付,还是与瀑布式交付相关的单次交付。还有一些工具有助于捕捉和理解交付过程中的测量和学习。最后,还有一个巨大的抽屉,里面放着用于建立文化、协作和技术工程卓越的工具。这些通常是我们开始工作时首先拿到的工具。

结论

在本章中,我们介绍了软件产品交付中的价值链,并探讨了传统工作方式如何带来了低效、瓶颈和用户、业务利益相关者、开发团队以及运营团队之间的空白。

我们探讨了用于填补这些空白的一些技术,以及所有参与者需要平衡关注人员、流程和技术的方式。

最后,我们介绍了一个名为 Mobius 的开源导航工具,它在无限循环中连接发现与交付,并且可以将战略与产品和运营连接起来,所有这些都有一个共同的可衡量结果的分母。开放实践库使用 Mobius,在文化和技术的基础上,导航不断发展的开放实践——其中许多将在后续章节中进行解释。

在下一章,我们将概述我们将如何通过介绍我们的案例研究和剩余部分的结构来处理本书的其余部分。

第三章:3. 接下来的旅程

当我们结束本书的第一部分时,本章将阐明我们打算如何引导你通过剩余部分的旅程。

这将包括我们如何不仅仅是讲述实践和技术,还将它们付诸实践并展示。我们将引入一个有趣的案例研究和真实世界的故事来实现这一目标。

编写一本面向不同技能和背景群体的书籍的挑战之一是如何以一种让所有人都能理解、接受和欣赏的方式来写它。从技术主管、基础设施工程师、OpenShift 专家,到敏捷教练、用户体验设计师、项目经理,再到 IT 领导和高层管理人员,我们希望你能理解所有实践背后的共同理念,以及支撑这些实践的原则。

本书将涵盖从如何使用以人为本设计实践捕捉同理心地图中的行为,到在应用程序中使用性能指标考虑可观测性等话题。它将探讨帮助产品负责人在优先考虑价值与风险的同时,如何处理应用程序的仪表化、镜像标签和元数据等问题!

类似于我们在 DevOps 文化和实践启蒙课程中使用的“完成定义”实践,本书将采用几种不同的方法来帮助你开启这一旅程:

  1. 解释文化和实践

  2. 展示文化和实践

  3. 应用文化和实践

为了解释文化和实践,我们将介绍实践是什么,为什么以及在哪里选择使用它,并给出一些如何使用它的指导。在某些方面,这是相对简单的部分。

我们有一句话,喜欢通过展示,而不是讲述。研究和写一堆文字很容易,真正令人信服的是通过视觉展示一个实践的实施以及它所带来的效果。为了展示文化和实践,我们有几种技术:

  1. 尽可能地,我们将尽量利用可视化技术,如速记笔记、图表和其他图形。你可能已经在这一部分看到过由 Ilaria Doria 精心绘制的几幅图,希望它们能让文字更加生动。

  2. 在我们能够通过照片或复制的实物来展示实践时,我们将这么做。在可能的情况下,我们已将图表和其他视觉资料开源,并可以在github.com/PacktPublishing/DevOps-Culture-and-Practice-with-OpenShift/上获取。

  3. 我们发现,故事和真实世界的例子是解释实践及其带来价值的最佳方式。所以,时不时地,我们会插入一个故事,讲述作者之一或多位作者与这些实践相关的亲身经历。我们将通过在故事周围加框的方式,单独展示这些故事。让我们现在开始讲一个:

讲述实践的故事

2005 年 12 月,我在英国的一项大型电信计费系统替换项目中工作,负责计费工作流。当时我已经在这个项目中工作了 18 个月。这是一个为期 10 年的项目,旨在用更现代的 COTS 软件替换所有遗留的计费系统,并引入一些新的业务能力,支持灵活且可变的产品管理。

我负责计费接口工作流,并负责交付计费系统与第三方(如银行、BACS 和财政部门)之间的接口。

我们的工作团队在办公室附近的一家酒吧举办了圣诞晚宴。我们选择这家酒吧,是因为大多数人 12 个月前也曾在那里举行过去年的圣诞晚宴。看到 12 个月后我们如此多的人再次聚集在同一个地方,真是有些好笑。

当时,我反思了过去 12 个月,以及我们的昂贵顾问团队在这段时间里所取得的成果。我突然意识到,12 个月前,我们正处于程序一个重大版本的设计阶段。我们在几周内举办了一系列研讨会,以绘制出发布所需的不同工作产品和交付物。

12 个月过去了,我们仍然处于设计阶段。一个超过 60 人的工作团队花费了一年时间编写、重写、优化和再次编写设计文档、更多的设计文档、设计文档的变种、与设计文档相关的技术澄清说明,甚至是针对设计文档的变更请求。到此时为止,尚未编写任何代码,未运行任何测试,也没有发布任何软件。我们 12 个月的成果仅仅是大量的设计文档和无数的会议。

我记得在过去的一年中,我们的成果让我感到有些失望。我当时对自己说,一定有更好的软件交付方式

最后,我们希望真正地应用一些文化和实践。为了帮助我们做到这一点,我们将使用一个简单有趣的案例研究,讲述一个小型初创公司如何应对创建 DevOps 文化和建立 DevOps 实践过程中遇到的挑战和难题。这个故事将代表我们在与客户合作时,见到的一些真实场景的匿名化叙述。

我们会定期回顾这个应用 DevOps 文化和实践的故事,并通过 OpenShift 以阴影框的形式呈现。让我们从背景故事开始吧——希望你准备好了!

PetBattle — 背景故事

家猫的照片是互联网上观看最多的内容之一 1。这是真的吗?谁知道!也许是真的。我们所知道的是,它们是我们在本书中用于帮助解释多个 DevOps 实践的示例应用程序的一个绝佳背景故事:

图 3.1:PetBattle — 背景故事

PetBattle 是一个爱好者应用程序,最初为了娱乐而创建,作者通过它互相进行简单的在线对战。一个我的猫比你的猫更厉害那种类型的事情。最初的架构几乎没有什么花哨的东西——有一个简单的基于网页的用户界面和一个 API 层,配合一个 NoSQL 数据库。

PetBattle 开始时部署在一台虚拟机上。它已经上线,但并未吸引大量访问者。它主要被作者的朋友和家人使用。

在一个异国情调的度假天堂,作者之一偶然遇到了一位在线影响者。他们开始约会,发生了一段假日恋情,PetBattle 突然变得在 Instagram 上爆红!几乎一夜之间,玩家人数急剧增加,PetBattle 服务器崩溃,恶意图片开始出现在本应适合儿童的应用程序上——这些图片不是猫。

假期结束后,作者们突然发现自己需要从 PetBattle 中谋生,并决定开发一个商业版和可生产的版本,使得这个爱好者应用现在变成了一个可行的项目。

1 zh.wikipedia.org/wiki/%E7%8C%AB%E4%B8%8E%E4%BA%92%E8%81%94%E7%BD%91

PetBattle 创始人们踏上了一段激动人心的旅程,拥抱 DevOps 文化和实践,并与 OpenShift 一起合作。

那么,遗留系统呢?

人们常常将敏捷开发和 DevOps 与绿地开发、新创公司联系在一起,认为这仅适用于那些有机会从零开始的公司。那遗留系统怎么办?是我们经常被问到的问题。

本书将贯穿始终地展示 Mobius 循环和基础可以适用于任何类型的项目和技术;无论是全新开发还是已有开发,都是适用的——小型的 Web 应用或大型主机,内部基础设施交付或混合云技术。

我们往往从 Mobius 循环中的发现部分开始(在建立文化、协作和技术实践的基础基础后)。但你不必从那里开始。事实上,你可以从循环中的任何地方开始。最重要的提示是确保定期走遍循环的所有部分。不要陷入交付循环,永远不要回到发现阶段重新审视之前做出的假设和假设。不要陷入发现阶段,导致进展过慢,陷入分析性瘫痪,从而错过市场机会,永远无法交付价值。最重要的是,永远不要忘记在文化、协作和技术实践的基础上不断构建。

借用智慧

在我们开始深入细节之前,我们需要花一点时间指出,本书中提到的任何实践并不是我们自己编写或构思的。书中的实践以及开放实践库中的内容,是一些杰出思想者不断贡献的成长清单。我们借用了这些智慧,并将其归功于这些创造者。我们希望能够正确归属每一位贡献者,任何遗漏都纯属偶然。

我们在这本书中所尝试的,是展示这些实践如何通过相互连接,带给组织一些具有深远影响的成果,并展示实现这些成果所需的一些基本原则。

本书接下来的内容你可以期待什么?

所以,你几乎已经完成了第一部分的阅读。感谢你一路坚持!我们希望你现在能感到充满热情和动力,继续阅读下去,并且对我们所写的内容充满信任。

如果你需要更多关于本书内容的预期,以下是一个简短的概述。

第二部分 —— 建立基础

在本部分中,我们将更加深入地探讨建立文化和技术基础的重要性。我们将再次回顾我们开篇时提到的从为什么开始的目的动机,并强调这应该是任何产品或团队的出发点。我们将介绍一些我们最喜欢的、最强大的实践,帮助创建协作的基础和文化——社会契约、停止世界的安灯线、实时回顾、创建团队身份、养成可视化一切并进行检查与适应的习惯。在建立基础时,持续关注创造心理安全的环境是一个关键成功因素。我们将解释什么是心理安全,以及我们如何帮助实现这一目标。

我们将探讨高层赞助如何推动或阻碍成功的基础建设,并更深入地探讨在技术和文化方面的开放性意味着什么。我们还将研究敏捷决策是如何运作的,并介绍一些有助于此的有用工具和实践。最后,我们将讨论采用方法以及如何说服怀疑者和持怀疑态度的人!

从技术基础的角度出发,我们将分享一些我们最成功的做法,包括通过大局观来可视化技术、从一开始就注重绿色的哲学,以及如何将一切视为代码。我们还将介绍一些我们用来衡量 DevOps 文化和实践成功与影响的基准指标。我们甚至会为一些技术实践的权衡和方法设定场景,以便你在构建基础时考虑——GitFlow——与基于 Trunk 的开发、设置开发工作流、考虑不同类型的测试、以及为配对编程和集体编程设置环境。

为了“展示而非阐述”,建立基础是将左侧的图像转化为右侧的图像:

图 3.2:组织内部的协作

第三章 — 发现它

在这里,我们将深入探讨 Mobius 的发现循环,并查看一些最佳的使用方法。我们将分享一些我们最喜欢和最具影响力的实践,来自《开放实践库》,它们帮助我们在发现循环中取得成功,包括影响映射、人本设计和事件风暴。

我们将看看这与技术以及新兴架构和实现真正的持续交付的理念之间的关系。

从商业角度来看,我们将探讨成果与产出的区别,以及我们如何努力从“更多功能即更好”这一想法转变为通过更少的功能创造强大的成果。我们将探讨如何持续衡量成果,并通过开放源代码画布将整个发现循环中的信息传播开来。

为了“展示而非阐述”,我们将看看如何将发现过程从左侧所见的样子转变为右侧所见的样子:

图 3.3:通过影响映射、人本设计和事件风暴实践发现

第四章 — 优先排序它

在这里,我们将深入探讨 Mobius 的选项 pivot,并了解为什么“活的、呼吸的、始终变化的选项”如此重要。我们将探索一些实践,如用户故事映射和价值切片,帮助我们理解这一点,并分享一些我们遇到的误解和误用的案例。我们将讨论如何通过发现过程构建初步的产品待办事项,并采用选项 pivot 的实践。我们还将研究最终进入产品待办事项的不同类型的项目,从研究工作到实验工作,再到实施工作。我们将看一些经济优先排序模型,以及如何以持续实验和持续学习的思维方式评估价值和风险之间的权衡。我们有很多故事要分享——其中一些聚焦特定领域,有些则是跨多个领域的学习故事。

为了“展示而非阐述”,我们将看看优先级排序如何从左侧的样子变成右侧的样子:

图 3.4:使用选项 pivot 优先排序待办事项

第五章 — 交付它

在本节中,我们将探讨敏捷交付,以及它在不同复杂性和简易性水平下的适用性。我们还将探讨瀑布模型及其相对优点以及何时适用。我们将探索现有的不同敏捷框架,并讨论它们如何与开放实践库(Open Practice Library)和莫比乌斯循环(Mobius loop)相关。我们将探讨可视化的重要性,以及如何捕捉度量和学习成果。在技术方面,我们将探讨如今可用的先进部署技术如何支持一些实验和学习方法的实施。

为了展示而非仅仅描述,我们将探讨如何将交付从左侧的样子转变为右侧的样子:

图 3.5:通过可视化和度量进行交付实践

第六部分 — 构建、运行、拥有

本节真正关注的是技术作为推动因素的重要性,以及为何拥有一个应用平台至关重要。

我们将回到“一切皆代码”的理念,并将 Git 和 Helm 作为实现这一理念的工具。我们将深入探讨容器和云原生(云、平台和容器)生态系统。我们将探索 OpenShift 和 Cloud IDE,以及实现持续集成的管道,包括 Jenkins 和 Tekton。我们将探索新兴的部署和配置方法,如通过 ArgoCD 实现的 GitOps,并提供关于如何以及在哪里存储配置的指导。我们将探索高级部署技术,如 A/B 测试、功能开关、金丝雀部署和蓝绿部署,以及这些技术如何与业务结果实验结合使用。我们还将关注 DevOps 实践中的非功能性方面,包括开放策略代理OPA)、镜像扫描、DevSecOps、基础镜像(BaseImage)和链式构建。我们将探讨一些功能性和非功能性的测试。我们将探索操作方面,如应用程序底盘、镜像标记、元数据和标签化工具、Knative 和无服务器架构,以及从业务与应用性能指标角度看可观察性。我们将提到服务网格(Service Mesh),并专注于运维和第二天操作的运维工具。

为了展示而非仅仅描述,我们将探索将构建和运行从左侧的样子转变为右侧的样子:

图 3.6:为 DevOps 创建合适的环境

第七部分 — 改进、持续

当我们退出交付循环时,我们会问,我们学到了足够多的东西吗?我们是否应该调整方向,还是再进入交付循环一遍?我们将看到如何进入持续学习的循环——而非一次性的事件。假设在交付循环中被验证或推翻。我们将探索技术债务的世界,并了解如何从平台、功能和应用开发工作流程中获得定性和定量的度量标准,以帮助揭示这一点。我们将寻求将交付中的测量和学习反馈到发现的文献中,如事件风暴、基于度量的过程图和用户研究。

我们将学习如何将发现循环、选项转变、交付循环和基础内容融合在一起,以帮助保持这种工作方式。这就是实现双环学习(double-loop learning)进行持续发现和持续交付的关键。

长期存在的跨职能产品团队学会了如何构建、运行和拥有它。在这一部分,我们将探讨一些有助于他们维持这些实践的做法。

在这一切中,领导力扮演什么角色?我们将展示如何可视化领导战略、产品开发和平台运营之间的联系,这一切都由意图驱动,并通过信息和度量标准进行指导。

我们将探索扩展书中所述所有内容的方法,以及如何让遵循原则的文化比纯粹的宗教性使用实践更为重要。

那么,分布式团队呢?

当你看到上一部分中的照片时,可能会注意到我们正在朝着一个让人们共同协作的世界前进。我们身处同一个空间,围绕着大白板,四周贴满了五颜六色的便签纸。

我们常常收到来自领导、执行官和客户的问题,那就是当人员不在同一地点时,像事件风暴(Event Storming)或社会契约(Social Contracting)这样的实践是否容易应用。

在过去的几十年里,离岸和近岸开发模式稳步增长。一些组织经历了重组,导致组织的不同部分被分布到世界的不同地区。在某些情况下,这导致了一个更加“孤岛式”的组织,各部分之间由于地理位置、时区差异和缺乏面对面的合作,墙壁变得更高。

我们对本书中将要探讨的人、流程和技术实践能否适用于分布式团队的回答是肯定的,它们可以

然而,分布式团队发现、交付和学习的速度很可能永远不会和同一个团队在同一地点共事时一样快。快速学习的能力是这种工作方式的基础。因此,我们始终寻找能够最快速学习的方法,并消除可能拖慢团队学习进度的瓶颈。我们观察到,在几乎所有的分布式工作情境中,瓶颈的存在都是显而易见的。

直到 2020 年,关于这些实践是否可以应用于分布式团队的问题,一直是由离岸团队、技能的可获得性以及最终的成本推动的。大型系统集成商在 2000 年代花费了数十亿美元投资近岸和离岸开发中心,因此可以理解他们为什么要尽一切可能帮助这些中心的团队能够使用敏捷、精益和 DevOps 实践。对于敏捷工作者来说,这可能令人沮丧,因为敏捷的核心焦点是将话题转向价值而非成本,并不断学习和改进以优化价值。

2020 年,分布式团队迎来了一个全新的、显著增强的动力——COVID-19 全球大流行。当疫情被宣布为全球大流行,并且各国政府开始讨论封锁和其他严厉的限制措施时,我们正处于写这本书的初期阶段。从 2020 年 3 月起,阅读本书的大多数人会记得他们的工作和个人生活发生了显著变化。绝大多数 IT 开发和运维工作突然转移到了员工的家中。任何形式的面对面协作的机会都受到公司、政府的严厉限制,并且由于健康风险,个人也不愿意参与。

和大多数人一样,我们在疫情期间不得不调整自己的工作方式,并修改与客户合作的方式。无论对分布式工作的偏好或之前的思考如何,我们现在都必须探索并投资分布式工作实践。对我们来说,这意味着启动了开放创新实验室虚拟驻地计划以及其他远程和分布式提供的服务。

当我们将本书的第一部分作为早期预览发布以征求反馈时,我们受到了读者的强烈建议,要求我们比最初计划的更多地探索实践的分布式应用。因此,我们会这么做。在本书的每一部分中,我们都会加入一个环节,分享我们在分布式团队中应用实践的经验。这将包括我们在 2020 年进行此操作时的现场故事和经验,以及我们从中获得的许多教训。相关的地方,我们还会包括细节和链接,指向支持我们的数字模板。

一点警告。仅仅投资购买像 Mural 或 Miro 这样的工具,并且能访问一堆模板,并不足以让你像在房间里一样继续正常使用所有的实践和技术。我们已经概述了平衡人员、过程和技术变革的重要性,以实现成功的 DevOps 文化和实践。在切换到分布式模式时,团队需要更多的持续关注人员、过程和技术。

关于“开放”世界的一些话

"开放"一词在本书中已经使用了多次,并且还会继续使用很多次。我们为一个开放的组织工作,这是一个基于开源原则和特征的公司。我们借用开放创新实验室的经验来讲述我们的许多故事,所有我们使用的实践都被记录在案,并将在开放实践库中不断发展。

图 3.7:默认使用开放

我们坚信,使用开放技术的开放文化和开放实践,是成功转型的最佳组合。

结论

在本章中,我们介绍了 PetBattle 以及这款业余开发者应用的背景故事,它将作为我们整个书籍中的有趣案例研究。

我们还介绍了如何定期通过实际案例和我们与客户合作的故事来展开讨论。

最后,我们列出了本书剩余的章节,并介绍了我们将在每一章中探讨的内容。

我们的介绍部分已经完成。让我们开始围绕莫比乌斯环展开,并探索我们最常用的实践。在进入环的内容之前,我们先来看看支撑这个环的基础。在下一章中,我们将从构建文化这一非常重要的基础开始。

第二部分:建立基础

第一部分,实践造就完美中,我们介绍了 DevOps 以及我们将在 Mobius 循环中使用的实践和工具,这个循环我们也进行了介绍。在进入循环之前,我们需要为循环搭建一个基础。这个基础专注于构建文化和技术:

图 4.0.1:基础——设定场景

当你听到基础这个词时,你会想到什么?许多人会想到你现在所处的建筑的基础。正如你所知道的,你不能仅仅到一个地方就开始在上面建房子。理论上,你当然可以。但如果你想要建造一个能经得起时间考验的房子,且最重要的是符合建筑规范,那可不行!首先,你需要建立一个坚实的基础。这意味着你需要深入到你要建设的地方的表面以下。建筑越高,基础就需要越深且越强。

想一想,这种情况适用于生活中的任何事情。当你奠定了坚实的基础,逐步在其上建设时,成功的几率会大大提高。想想你的教育、健康、健身、职业和生活。你所享有的成功正是因为那些基础以及你在它们之上能够构建的东西。

当你在一个脆弱的基础上构建时会发生什么?通常来说,这并不是一个好消息:

图 4.0.2:在脆弱的基础上构建

在与组织合作,构建运行于OpenShift 容器平台OCP)的应用时,当这些组织投入时间和专业知识来构建基础并为其开发和运营建立稳固的方式时,我们会看到更大的成功和更高的商业价值回报。

第一部分中,我们介绍了 DevOps 和本书中将使用的一些工具——即 Mobius 循环和开放实践库。Mobius 循环作为一个导航工具,帮助团队在持续发现和持续交付的旅程中应用 DevOps。我们刚才讨论的需要在构建任何东西之前先建立坚实基础的原则,也适用于软件设计领域。因此,我们在开放实践库中添加了一个基础。在我们考虑将任何团队、人员或产品放入 Mobius 循环之前,我们需要先建立一个基础。并且不仅仅是一个脆弱的、最低限度的基础——我们需要建立一个坚不可摧的基础。这个基础将支持快速的推进和越来越多的人加入到它上面的循环中。那么我们所说的“基础”是什么意思?我们指的是文化和技术实践的基础:

图 4.0.3:专注于支撑 Mobius 循环的基础

在红帽开放创新实验室,我们严格推进基于结果的交付方法。莫比乌斯循环充当我们的导航工具。它是一个可视化工具,帮助我们在 DevOps 旅程的不同阶段导航、识别并阐明我们使用的实践。

当我们处于发现循环时,我们识别并使用有助于回答“为什么”的实践——为什么我们要开始这段旅程?我们试图解决什么问题?我们想为谁解决这些问题,我们了解他们什么?我们的伟大创意是什么?我们还使用发现循环中的实践来帮助我们识别并设定商业和客户的可衡量成果目标。

当我们处于选项转换时,我们使用实践来识别如何实现可衡量的成果。我们有哪些可以实施的创意,帮助我们实现目标?从发现中得出的假设是什么,我们可以对这些假设进行测试、实验和研究?我们如何优先排序这些选项,以便更快速地交付价值?

当我们处于交付循环时,我们使用实践来完成选项转换中识别的工作——实施特性、进行实验、开展研究。关键是,我们还使用实践来进行度量,并捕捉关于交付影响的学习。当我们回到选项转换时,我们根据这些度量和学习评估接下来该做什么。

莫比乌斯循环是一个无限、持续的旅程,包含不断的发现、选项转换和持续交付重要的、可衡量的商业成果。我们在莫比乌斯循环中使用的实践通常有明确的开始和结束点。这些实践旨在帮助团队围绕循环前进——例如,在发现过程中,帮助他们走向选项和决策点。

在创建开放实践库(一个用于存储所有这些实践的工具箱)时,我们发现其中许多实践不一定适合放在发现或交付循环中。它们的性质是持续的,或是不断进行的。例如,我们使用了许多实践和技巧,帮助建立并推动协作文化。我们使用的工具可以帮助提升产品团队的能量、自主性和赋权感。我们还使用了一些实践,帮助营造一个基于信息传播、透明性和持续学习的环境。基础概念被添加到莫比乌斯循环下方,以解释这些类型的实践。这些实践旨在使团队能够默认地轻松做出正确的选择。它们是我们用来建立文化基础的实践。我们也有许多技术性实践,是我们持续使用的。最初的直觉可能是将这些实践与交付挂钩,认为它们应属于交付循环。然而,实际上存在一个微妙的区别,因为交付循环上的实践往往是有时间限制的、定期进行的活动,旨在推动交付。像冲刺规划、展示会、用户验收测试等实践(我们将在第五部分:交付它中详细探讨)通常是在每周或每次迭代的特定时间安排的。而有些实践,你不会像安排冲刺规划会议那样进行时间安排。例如,持续集成和持续交付、基础设施即代码(或实际上是“一切皆代码”)以及自动化相关的实践,如测试自动化,你不会每周三早上做几个小时的持续集成!你也不会在每周末安排一段时间进行基础设施即代码的工作。这些事情是你随时都在做的,正是因为你一直在做它们,才使得它们如此强大。莫比乌斯循环下的基础也用于解释这些类型的实践——这些实践帮助建立技术工程卓越的基础。

类似地,发现类型的实践往往是集中进行的事件,在特定时间进行,有时团队会多次进行迭代来执行发现循环。在第三部分:发现它中,我们将详细探讨的实践,都是由一群人聚集在一起,专注于某一活动一段时间来执行的。更多关注生成文化的实践,如制定团队章程和社交契约,以定义工作方式(将在下一个章节第四章:开放文化中介绍),并不属于发现循环。这是因为团队从这些实践中获得最大的价值,是在他们始终如一地使用它们时。团队不会在每周一早上拿出社交契约讨论半小时,然后直到下周一早上才再次考虑它!这就是为什么我们将它们构建在基础之上的原因。

现在,基础和整个开放实践库的一个重要方面是,它并没有规定或强制某一实践比其他实践更为重要。这不是另一种方法论或框架,它不会规定或强制某个人或某个团队必须使用哪些工具和实践才能达到某种结果。我们使用 Mobius 循环和基础来可视化我们选择使用的不同实践。在构建文化和协作基础时,没有固定的答案说明你应该使用多少实践。

我们发现,如果你完全不专注于或优先考虑这些实践,团队将难以取得成果,陷入旧的工作方式,或在尝试变革时回到现状。许多组织会让员工参加为期两天的 Scrum 培训课程,并尝试按书本内容实施,但却难以看到朝着业务敏捷性迈进的影响。Scrum 主要聚焦于交付循环和选项调整,而组织往往忽视了维持和促进 Scrum 实践带来的好处所需的文化变革和技术实践。

在本书的这一部分,我们将向你展示如何构建文化、协作和所需技术实践的初步基础,让你的“房子”能够长时间稳固屹立!

你可能在第一章,实践成就完美中已经瞥见了开放实践库。如果你应用了筛选器查看开放实践库中的所有基础实践,你会发现有很多!你可能会想,我是否需要使用所有这些实践?

答案是否定的。开放实践库的基础是一个不断增长的工具和实践选择,它们将帮助建立开放文化。你不需要使用其中所有工具,但至少应该从一些开始。你甚至可能已经有了一些自己的工具!问问自己,是否正在实现开放文化,使用的工具是否对你有效。

第四章:4. 开放文化

许多开发团队、运维团队,甚至跨职能产品团队往往直接跳入项目的技术层面。让我们搭建流水线,自动化我们的基础设施,写点代码! 这种冲动很自然,大家会不假思索地开始,而不是先投入一些时间去建立文化基础。这也可能导致低效和不必要的复杂性。

别担心,我们将在第六章,开放技术实践——开始,正确起步中讨论技术。但在此之前,我们想谈谈开放文化和开放领导力。在本章中,我们将探讨什么是开放文化,以及为什么它被证明是开发和运维的一个关键推动因素。

开放文化源于一种以绩效为导向的协作工作方式。它为充满动力的团队提供了一个环境,使他们能够不断受到激励,携手合作,并感受到自主、精通和目标感。我们将探讨这三种元素的含义,并提供一些好的和不好的例子。

我们将深入探讨我们的开放实践库,探索我们用来建立文化基础的一些最喜欢的实践,例如社会契约、停世界电缆和实时回顾。这些实践有助于建立团队身份、理解团队情绪,并以非常透明的方式可视化我们所有的工作。

我们有许多关于使用这些和其他实践的故事要分享,我们将回到我们的宠物战斗组织,看看他们如何从兼职爱好者转变为建立一个高绩效团队,并准备好接管宠物战斗世界!

为什么它很重要?

文化指的是一群人中共享的习俗或行为。开放文化指的是默认遵循透明度、协作、社区、包容性和适应性的开放原则。研究表明,高绩效团队需要心理安全,而开放文化强化了能够促进这一点的行为模式。

我们所说的心理安全是什么意思?威廉·卡恩(William Kahn)在 1990 年提出了这个概念,1 他说过,心理安全是指能够在不担心对自我形象、地位或职业产生负面后果的情况下,展示和使用自己的感觉艾米·埃德蒙森博士在她 1999 年的论文中进一步解释了这一点,2 她表示,心理安全是一种信念,即一个人不会因提出想法、问题、关切或错误而受到惩罚或羞辱。这一概念出自她 1999 年的论文,后来在 2013 年被谷歌的阿里士多德项目所引用。那项谷歌研究发现,心理安全(有些令人惊讶的是)是构建高绩效团队的最重要因素。

Tom Geraghty,Red Hat Open Innovation Labs 的转型负责人,最近在他自己的研究和转型服务工作中重新定义了这一点。他解释道,在一个小组的背景下,心理安全表现为一种信念,即其他成员重视你的贡献和关切,并且不会因为表达它们而主动或被动地伤害你。这为小组成员提供了空间,去冒险、承认脆弱并承认错误,而无需担心负面后果。

开放文化和心理安全是任何组织的必备成分。它们为一个环境提供了支持,在这个环境中,人们和团队感受到信任的共鸣,能够自由探索新想法,进行实验,通过实践学习,并与同事、同行、领导者、客户、用户……实际上是与每个人自由分享!

1 Kahn, W.A., (1990). 工作中的个人投入与退出的心理条件。管理学会期刊,33(4),第 692-724 页。

2 Edmondson, A., (1999). 工作团队中的心理安全与学习行为,艾米·爱德蒙森。行政科学季刊,44(2),第 350-383 页。

相反,封闭文化到处充满了阻碍和障碍,阻止了这些行为的发生。团队会回到保护性和保密模式。当害怕分享坏消息,或者请求反馈的想法让某人感到恐惧时,我们最终会处于一个保护自己所做事情免受外界干扰的环境中。我们不想与别人分享我们的代码,生怕他们否定我们或者嘲笑我们做得不对。我们不敢向经理展示自己落后于计划的情况,因为害怕被精神上“打击”并被告知必须加班加点才能赶上进度。我们不敢向业务人员或最终用户展示功能,因为他们可能会告诉我们我们做的不对,这将引发更多的返工(以及更多愤怒的经理)。

通过创造这些类型的环境,我们实际上是在推迟不可避免的事情。想一想有多少 IT 项目在开发进度的最后阶段才暴露出问题。团队突然需要付出更多努力才能赶上最后期限。缺少的正是一个开放、诚实和透明的工作进展视角,随着项目的推进逐渐呈现出来。

你听说过 IT 项目的西瓜效应吗?外面看起来又绿又健康,但一旦开始剥开皮,里面到处都是红色!我们曾见过一个例子,团队领导和项目经理会根据自己的 RAG(红色、琥珀色、绿色)状态写报告,这些报告会传递给更高级的项目经理,后者会用自己的总结 RAG 状态汇总每个人的报告。然后这些报告会传递给更高级的执行治理人员,最终呈现给高级客户利益相关者的是一个更高级(或更稀释)的 RAG 状态信息。

图 4.1:西瓜效应

信息辐射器

鼓励开放文化的最佳方式是让所有团队成员和其他相关利益相关者都能公开获取信息。

信息辐射器这个术语由阿利斯泰尔·科克本提出,指的是团队放置在显眼位置的手写、绘制、打印或电子显示器,团队成员以及路过的人都可以一目了然地看到最新信息。在科克本 2001 年出版的书籍《敏捷软件开发》中,信息辐射器是一个扩展的隐喻,代表信息的传播像热量和气体的扩散。我们将探讨与客户和团队一起使用的各种信息辐射器——自动化测试的计数、速度、事故报告、持续集成状态等。

我们有时也使用信息冰箱这个术语——信息变得冷漠,甚至过时。信息冰箱是一个你需要去寻找信息的地方,而不是信息随时可用且容易获取。冰箱甚至可能是锁着的!存储在共享驱动器上的文件、JIRA 仓库和其他数字工具中的信息都可能变成冰箱里的信息,因此我们有责任保持它们的“温暖”和辐射。

能否让那些红灯变成绿灯?

在与一家英国零售组织合作时,我的团队提出了透明信息辐射的概念。

项目已经向业务利益相关者报告了几个月的绿灯状态。但实际上,存在一些关键依赖关系和障碍,阻碍了交付团队的进展。我们的团队设置了电视显示器,突出显示我们的服务无法访问预生产或生产服务器。一些简单的自动化测试显示网络基础设施没有流量通过,因此所有测试都失败了。这些测试结果显示在团队房间的大屏幕上。这个屏幕非常显眼,很多人都能看到,屏幕上只显示了大块的红色。

由于利益相关者迄今为止一直报告着一个健康、成功且没有大问题的项目,显示许多红色的巨大屏幕反而传达了相反且令人担忧的信息。我被问到是否可以因为首席信息官要来,暂时更换这些交通信号灯信息显示器。如果不行,能不能把显示器关掉?

这是一个典型的西瓜效应。

不要——不要篡改团队设置的实时信息显示器。

不要隐藏信息。相反,要解决信息显示器所凸显的基础设施问题。下一次自动化测试运行时,这样做会自动将仪表板变为绿色。

为了继续赋能我们的团队,我们选择忽视了这个请求。

当我们拥有开放文化时,我们有机会定期检查一切的健康状况——产品、团队、投资、利益相关者、用户和技术的健康状况。开放文化意味着,当我们进行健康检查时,如果得不到积极的反馈,我们会以参与和对话的方式欢迎坏消息。我们不会因为得到坏消息而责怪他人。相反,我们会讨论我们学到了什么,以及需要做什么才能改善现状。我们需要改变什么?我们应该适应什么?检查和适应是开放文化的健康指标。文化越开放,我们就越有自由去检查和适应。

在我们深入探讨一些帮助建立开放文化基础的具体实践之前,先分享一下我们对“文化”这个词的理解,以及团队中的动力如何真正推动文化向上发展。

文化

文化是一种神秘的能量。你看不见它,但你肯定能感受到它。你能感受到它在一个团队或物理空间内特别强烈的时候。如果你曾经体验过伟大的开放文化,你也会迅速意识到当你感受不到它时的情况。

我们常将开放文化比作《星际大战》中的力量。力量在《星际大战 IV:新希望》中年轻的卢克·天行者身上尤为强大:他没有穿写着这句话的 T 恤,但别人靠近他时能感受到那股力量。开放文化就像这样。你拥有它时会知道,且你也能知道它何时变得更强。你的任务是定期评估、感知并检查开放文化的强度,如果你觉得它可以更强,探索更多文化基础实践来强化它。我们将在第十三章,衡量与学习中探讨不同的技术和实践,帮助衡量和学习这些变化的影响。

在本章的剩余部分,我们将分享一些在开放创新实验室驻地期间,我们在建立文化基础方面最成功的实践。

动力

根据《纽约时报》畅销书《驱动力:关于我们动机的惊人真相》的作者丹·平克,人们的动机并不像我们预期的那样。他认为,历史上,组织通过提供奖励(钱)并通过恐惧和惩罚的文化来激励员工,这种做法是错误的。当工作需要任何认知技能或知识时,这些方法并不起作用。

通过他的研究,他表明有三件事能激励人们超越基本任务完成:

  • 自主性:渴望自我引导和拥有自由支配的权利

  • 精通:渴望在某方面变得更好

  • 目标:渴望做一些有意义或对你重要的事情

图 4.2:自主性、精通和目标

在你的组织中创建一种开放文化应当体现这些原则。开源软件开发建立在自主性、精通和目标这三大支柱上。例子包括使用开源代码库、在公共讨论论坛上进行协作以及拥有透明的决策记录;这些做法使得人们更容易找到目标,获得专业知识,并对自己的工作负责。它消除了对个人以及稀缺或隐藏信息的依赖。全球各地的人们都在积极地为像OKD(Kubernetes 的社区发行版,支撑着 OpenShift)这样的开源项目做贡献,从而改善所有人的产品。有时候,他们贡献是因为这是他们的工作的一部分,但更多时候,他们贡献是因为他们愿意。他们参与这些项目是因为通过帮助社区建立更好的东西、为大家带来益处、被自我引导或倾听,以及为未来创造某些东西而获得的深刻个人满足感;他们也希望通过在公共待办事项中实施和测试一些东西,成为更好的程序员。

为了在组织中建立这种精神,领导层需要创造一个环境,使员工能够实现这些目标。他们应该赋予团队决策的权力,并鼓励自我组织和自我修正,从而提供自主性。他们应该拥抱并鼓励持续改进,使团队成员能够不断学习和提高自己及团队,提供精通性。领导层应该消除阻碍团队成员与业务目标对接的障碍和壁垒。领导层应该使团队能够定期直接接触到业务利益相关者、客户和领导层,从而增强员工对工作的目标感。让员工专注于自主性、精通性和目标感将产生双重效果。团队将拥有创造出色产品的自由和动力。由于对产品负有更大的责任,员工将开始更加努力工作,因为他们真正相信自己被倾听,并与团队目标紧密相连,他们也会想要努力让产品更好。

让我们回到 PetBattle 的案例研究,看看一些员工是如何感受到自主性、精通性和目标感的。具体来说,看看在 PetBattle 成立初期发生的一些事件,这些事件帮助实现了这种开放的文化。

PetBattle — 创造自主性、精通性和目标感

将工程师与最终用户连接起来是创造目标感的一个好方法。PetBattle 的工程师不希望功能或任务无缘无故地丢给他们,而且没有看到这些功能如何与产品愿景相连接。为了解决这个问题,他们定期与最终用户进行演示并收集反馈。这种互动让他们能够建立同理心,看到应用在实际生活中的使用方式。

玛丽(一个自认的猫咪爱好者,经常玩 PetBattle)在 Ciarán(PetBattle 工程团队的一员)的观察下使用应用。玛丽正在努力理解为什么她上传的猫咪没有立即出现在比赛中。Ciarán 知道她必须先为另一只猫投票,自己的猫才会出现在投票列表中,这对他来说似乎是显而易见的。几天后,Ciarán 在他的待办事项中看到一个任务——让上传的猫咪立即出现在投票列表中。Ciarán 立刻理解了这个任务的目的,并开始思考这个功能对玛丽(一个经常玩游戏的玩家)产生的影响。

Ciarán 和其他参与 PetBattle 项目的工程师——Aidan、Emma 和 Jen——意识到,能够获得 Mary 的反馈是非常有价值的,尤其是在他们有机会向 Mary 交付新功能时。Jen 在最近的一次回顾会议中说:“这比我之前的项目好多了,在那些项目中,我被要求按照规范编写代码,却并不真正理解自己所帮助构建的是什么。”Aidan 建议,如果他们能时不时地见到像 Mary 这样的用户,甚至与她们一起做些测试,听听她们对未来发展的其他想法,那就更好了。

在同一场回顾会议上,Aidan 分享了自己最近几周在 PetBattle 工程团队中的个人亮点。他说,他喜欢可以在自己准备好时直接发布新功能和修复 bug。他不必等待发布窗口,也不需要变更审查委员会的会议。他可以直接按下按钮,尤其是 Ciarán 和 Emma 着重建立的那些优秀的技术基础实践,让他能够充满信心地发布。他正在学习如何为自己的新代码创建功能开关,这更好,因为它使得业务产品负责人可以决定何时启动这个功能。Aidan 认为,团队应该进一步探索像这样的更先进的部署能力。

与此同时,Emma 的亮点是有机会与更广泛的社区分享她所掌握的一些技术基础技巧。她已经写了几篇博客文章,并受邀在新的一年参加一个 DevOps 大会的演讲。她表示,在之前的项目中,她并不被允许如此公开地分享她所做的工作。她觉得自己有一些很棒的技巧可以回馈给更广泛的技术社区。这个社区中的许多人曾经帮助并激励了她自身的发展,因此她非常高兴能够分享回去。大家一致认为,他们应该寻求机会做这些事情——谁知道,或许这甚至能帮助宣传和推广 PetBattle 作为一款产品,随着团队的成长,也会成为一个非常酷的工作场所!

Drive: The Surprising Truth About What Motivates Us的作者 Dan Pink 在 2010 年在皇家艺术学会RSA)做了一个精彩的演讲,讲述了真正激励我们的是些什么。RSA 制作了一个来自这次演讲的短视频 3,非常棒——我们已经向许多团队和领导展示过。它在 YouTube 上公开可用,我们强烈推荐至少观看一次,以进一步探索自主性、精通性和目标感这一领域。

在 Red Hat,自治、精通和目标感是支撑我们开放文化的核心价值观。我们将探索一些战术性实践,帮助创造这一文化。当我们进入产品发现和产品交付阶段时,你会发现这里的所有实践也都在帮助实现这一文化。

社会契约

我们四位作者非常幸运,能够帮助新产品团队启动。我们使用一系列实践来帮助这些团队并确保它们从一开始就能有最好的开端。我们逐渐意识到,特别重要的一项实践是——社会契约。

3 youtu.be/u6XAPnuFjJc

社会契约实践是一种简单但极其有效的方式,能够让团队实现自主性和自我责任感。社会契约由团队创建并服务于团队,它旨在明确团队的行为规范和期望。它还为团队提供了一种机制,能够将其期望的行为与管理层和其他利益相关者进行可视化和分享。

社会契约并不是某个昂贵的咨询项目,而是团队提出的一套简单的准则和行为规范,用来说明他们希望如何相互互动。它促进了团队的自主性和自我管理。

我们从之前的经验中学到的一个教训是,如果在与新团队合作时,我们没有花时间使用像社会契约这样的工具,最终会面临各种文化、团队协作和沟通问题。图 4.3 显示了一个社会契约的示例。

图 4.3:社会契约的示例

我需要一个社会契约吗?如果需要,如何建立一个?

在现代软件开发中,我们的目标是打破一切壁垒。开发和运维团队之间的壁垒仅仅是开始!开放型组织希望他们的团队能更加自主,因此围绕产品为团队提供共同的目标。团队对此的回应通常是尽力践行你构建它,你拥有它,你运营它的格言。但我们如何推动这种行为变化的起步呢?尤其是当我们启动一个全新的团队,而团队成员彼此完全不了解时,如何加速团队的高效运作?这时,我们友好的社会契约就派上了用场。

当我们与一个新团队合作时,我们会让他们为团队起个名字。团队身份对于建立高效能团队文化中的所有权部分至关重要。我们将在本章稍后进一步探讨团队身份。

下一步很简单:让团队花几分钟回想一下他们曾经合作过的最好的团队,或者他们参与过的最佳产品开发。想到这些之后,他们应该列出所有他们记得的、令人印象深刻的特质和行为,并将它们记录在便签纸上。

相反,想一想那些糟糕的项目,以及是什么因素在为团队创造一个有毒的工作环境。通过一些想要遵循的行为准则,团队可以进一步讨论,从而达成共识并聚焦于使用的具体语言。

一个好的社交契约应该包含具体可执行的条款,而不是空泛的声明。为了真正挖掘这些内容,领导者可以通过举例说明某个人如何践行他们提出的声明来引导团队,并尽量捕捉到这些行动的具体表现。例如,保持开放可以作为社交契约中的一项条款,但可能缺乏具体性。我们可以通过举例说明我们在哪些地方遵守,或者没有遵守这一条款,来进一步探讨。团队可能会提出类似的条款,如每个人的意见和贡献都很重要给较为安静的人更多空间,以及像我希望别人听到我一样,积极倾听他人

对于合同中的某些条款,团队成员签署合同并将其张贴在显眼的位置。现在,遵守合同是团队的责任,并且需要在别人没有遵守时提出警告。

图 4.4:另一个社交契约的例子

前面的例子来自我们与一家网络安全公司合作的四周时间。这支团队包括开发人员、设计师和站点可靠性工程师。以下是他们所包含的一些内容:

  • 核心工作时间(10:00 至 16:00):这是团队商定的协作时间。这不是他们工作的时间,而是他们进行同步、开会、配对编程和其他协作的时间。在这次合作中,团队中的一位成员希望避开早高峰交通,因此如果她稍晚到达,通勤时间将大大减少。此外,团队中的另一位成员有照顾孩子的责任,所以在合理时间离开会让他的生活更轻松。

  • 团队学习 / 配对构建:这是一个简单的口号,描述了团队在编写代码、测试甚至文档时如何互动。如果需要解决新的问题,比如使用新的语言或框架开发那个令人生畏的微服务,那么就应该全员一起处理。从一开始就让每个人保持一致,确保团队中没有单打独斗的“英雄”,确保技能在团队中普及。通过配对开发功能,提升全员的技能。

  • 每周社交:庆祝成功对许多团队来说非常重要。作为一个团队,花时间远离桌面,一起社交、一起用餐,有助于建立积极的关系。这些活动可以提高团队士气,并在从事产品工作的人群中创造积极的文化。

现在我们已经考虑了社交契约的关键原则,接下来让我们看看如何将它们与其他职场实践结合。

为了支持对想法和行为达成共识,可以使用分组技巧,如亲和图法五指法点投票法。这些是简单但非常强大的实践方法,也是开放式引导技巧,能够帮助推动一致性、共识并确保所有相关人员的参与。我们将在下一章关于开放领导力的内容中深入探讨这些方法。

图 4.5:回顾性学习推动社会契约更新

当社会契约首次创建时,它代表的是一个时间点的快照。它是对团队如何互动的最佳猜测,通常是在我们彼此还不熟悉或没有形成习惯时编写的。它是一个有助于加速团队建立在信任和心理安全基础上的舒适位置的有用工具。然而,它并不是一成不变或静态的。契约中的条目可能已经失效或有所遗漏。一位优秀的 Scrum Master 或敏捷教练可以做的一件事就是在团队的回顾会议中带上社会契约(这是第五章 - 交付它中将进一步探讨的实践)。这为团队提供了一个很好的机会来检查和调整社会契约,可能会更新其中的新想法或改进措施。

犯错是可以的

在与网络安全公司合作时,我们发现了一个很好的机会来更新我们的社会契约。那个团队里有两位开发者性格比较冲动,而且两人总是希望自己是对的。通常,他们无法就任何给定解决方案的实施方法达成一致,这种情况在团队内产生了有毒的氛围,降低了士气。通过一次回顾,团队发现了这个问题。经过讨论并将问题公开后,我们用一句简单的话更新了我们的社会契约:犯错是可以的

这对团队今后的发展产生了深远的影响。团队内部的竞争开始消失,因为我们不再过多关注谁是对的,而更多地关注这个解决方案是否适用于我们的最终用户。我们寻找机会让两位开发者都有机会发声,尝试多种实现方式来解决一个特性。构建并评估两种方案后,两位开发者开始互相配对,协同开发对方的解决方案,从而加深了彼此的联系。这也带来了一个明显的好处,即通过经常结合两人想法,写出了更好的解决方案。

社会契约是一项强大且易于实施的实践——它促进了团队自治、自我管理和心理安全。如果你的团队还没有社会契约,建立一个也不算晚。最重要的是,要让它对团队可见,并在需要时不断回顾更新。

分布式团队的社会契约

我们在第一章,实践成就完美的最后提到过,我们会考虑在一个更加分布式的环境中,实践如何运作,尤其是当人们并不在同一地点时。使用社会契约实践与一个新的虚拟团队合作可能会有些棘手,因为我们通常在关系、信任和默契尚未建立时就使用社会契约。

我们遇到的挑战包括并非每个人都参与或贡献于社会契约。当团队中只有相同的声音时,那些不太参与的人往往会被忽视。以下是一些建议,帮助如何在分布式团队成员之间建立社会契约。

使用 Miro 或 Mural 等虚拟白板工具,确保每个人都连接并查看相同的内容,将为你提供最佳的成功机会。如图 4.6所示,我们已经创建了一些模板,这些模板是公开可用的,并可以作为你的社交契约的起点。你可以从本书的 GitHub 仓库中下载。

图 4.6:适用于分布式团队的数字社交契约模板

考虑拥有一个适用于远程会议的预设社交契约,然后根据大家的想法允许人们进行添加或更改:

  • 每个人都应该用便签或虚拟笔上自己的名字。

  • 将远程工作元素添加到社交契约中(例如,加入通话时静音、开启摄像头等)。

  • 建立全球沟通规范和各个团队的例外:响应时间、写作风格、语气等。

在我们的虚拟合作中,我们看到的一些良好的沟通规范包括:

  • 一次只进行一个对话。

  • 假设正面意图。

  • 不要删除或移动他人的贡献。

  • 保持专注于我们的目标,逐步推进。

  • 尊重休息时间。

  • 避免其他干扰,如电子邮件和聊天。

  • 根据需求可以更新契约。

图 4.7展示了一个由分布式团队创建的示例社交契约,包含了他们的建议和签名:

图 4.7:分布式团队使用的社交契约示例

你可以通过访问openpracticelibrary.com/practice/social-contract了解更多关于社交契约的实践。

在视频通话中与每个人一起创建社交契约,需要额外的促进技能。Ilaria DoriaMarcus Maestriopenpracticelibrary.com/blog/facilitation-tips-for-remote-sessions/提供了一些非常棒的远程协作技巧,帮助你更好地进行远程会议和其他实践。

停止世界

停止世界事件或安灯绳是我们在合作中经常使用的另一个我们喜爱的实践,它是 DevOps 的一项超级能力。

约翰·威利斯在他的ITRevolution 博客文章4 中解释了安灯一词的起源——在日语中,这个词源于传统的照明设备,使用的是由纸和竹子做的火灯。这个想法后来被翻译并用于日本的制造业中。安灯成为了一个信号,用来突出显示异常情况(即,闪烁的灯光)。这个信号用于放大可能出现的质量缺陷。

4 itrevolution.com/kata/

安灯绳与心理安全

我第一次听到Andon Cord是阅读《凤凰项目》时——这本书由Gene KimKevin BehrGeorge Spafford合著——如果生产线上的员工怀疑出现了问题,他们会拉动绳索,整个生产线会立即停下来。通过避免将失败传递到下游,丰田认为这种颠覆性行为是我们每天能生产 2,000 辆车的唯一方法——那就是每 55 秒完成一辆车

大约八年后,我听到 Gene Kim 谈论 Andon Cord 与团队心理安全之间的关系。当你在丰田工厂拉动 Andon Cord 时,生产线经理会跑过来并感谢你停止生产,以便解决任何出现的问题。如果他们认为拉动这根绳索会受到惩罚,就不会有人去拉它。

是的……好的 DevOps 你借用,伟大的 DevOps 你偷!

当怀疑系统有缺陷时停止系统的过程可以追溯到最初的丰田系统公司以及所谓的 Jidoka。Jidoka 背后的理念是,通过停止系统,你能立即获得改进的机会,或者找到根本原因,而不是让缺陷继续传递下去而没有得到解决。Jidoka 的概念是由丰田的创始人丰田佐吉(Sakichi Toyoda)开创的,他被称为日本工业革命之父,也是丰田系统公司(在制造汽车之前)的创始人。

我们在与客户的互动中使用停止全世界事件。当团队中的某个人发现项目偏离了预定方向时,整个团队会一起合作找到一个可行且各方都能接受的解决方案,然后进度可以继续恢复。

在团队中使用停止全世界事件有很多隐性好处。在丰田工厂,生产线经理会亲自去看看拉动绳索的站点,并询问:我能帮你做些什么?问题立即成为优先事项,且通过直接去问题首次出现的区域,过程变得证据明确且基于事实。通过感谢拉动绳索的团队成员,这鼓励了一种安全文化:工厂管理层在说,你挽救了一个客户免受缺陷产品的困扰。每当你的应用测试在管道中失败时,想想这个!在丰田看来,失败创造了学习的机会,失败本身是一件好事。

我们只是在重建相同的体验。停止全世界!

我们在 2018 年为一位位于北欧的客户提供了为期 6 周的双轨开放创新实验室驻场服务。(双轨驻场有一条轨道/团队专注于构建全新的生产 OpenShift 平台,而另一条团队则构建并维护在平台上运行的第一个业务应用。这两个团队紧密合作,并在同一个物理空间中工作。)

对于这个客户,正在开发的应用是现有移动应用的现代化版本,客户通过该应用购买新的旅游保险产品。此次双轨驻场的目标是学习 DevOps 新技能、开发微服务,探索 OpenShift 产品,并提升客户的整体用户体验。

驻场的第四天,团队正在使用一种叫做事件风暴的方法(我们将在第三部分,发现它中详细探讨),来绘制端到端的业务流程并发现新兴的架构。这项工作已经进行了一两天。

Riley,我们的用户体验设计师,在查看进行中的事件风暴时突然有所领悟。所有的对话和可视化都集中在捕捉现有应用流程上。他们走上了一条只是在用一些新技术和方法重新构建相同体验的道路。

Riley 拉响了 Andon 铃,在这个领域,这是一口大钟。他停止了整个进程。这声音又大又有冲击力,旨在打断所有人的工作并立刻吸引他们的注意。大家停止了手中的工作,围成一圈。Riley 解释了他的担忧,大家一致认为,如果他们只是重新设计完全相同的应用,单纯采用新的技术和方法,这种做法对提升客户整体用户体验的目标帮助不大。

因此,作为一个团队,他们决定使用事件风暴来捕捉需要验证的假设、创新实验的想法以及需要与最终用户探索的未知问题。

世界重新启动后,接下来的六周采取了以假设和实验为基础的方法。

这个故事展示了以用户为中心的动机背后为什么要停止整个进程。另一个故事解释了一个更技术性团队成员如何停止世界运转的原因。

偏离原本的目标

团队在这里进行了一系列实验、技术挑战和研究工作,旨在最大限度地提升在为期 6 周的开放创新实验室驻场期间的学习成果,为使用 OpenShift 推出新云平台做准备。

团队已经就驻场期间的多个架构原则达成了一致。

在第三次冲刺期间,团队成员 Tom 注意到,白板上出现了越来越多与当前冲刺无关的旁谈和绘图。这些内容与团队当前开发用户故事的业务流程部分无关。

团队的燃尽图明显偏离了轨道,看起来越来越不可能实现当前迭代的目标。当汤姆进一步调查这些闲聊内容时,他发现讨论的话题涵盖了完全不同的场景,并且有些人开始考虑 2-3 年后的架构问题。

稍感沮丧,汤姆暂停了世界。实验室空间里有一个暂停世界的铃声,他把它响了起来。

图 4.8:一名团队成员暂停了世界

团队成员都熟悉这个实践,但听到铃声真正响起时,还是稍感震惊。他们聚集在一起,团队展开了开放、坦诚的对话。汤姆分享了他的担忧和沮丧。团队重新审视了他们当前的承诺,并提醒自己之前在优先级滑块和目标结果实践中做出的一些决定。他们同意将一些事项放入产品待办事项列表,以便暂时搁置这些讨论。

图 4.9:世界暂停后,团队聚集在一起解决问题

仅仅 15 分钟后,团队重新调整了方向,重新聚焦了优先事项,并充满了新的活力和承诺。

人们不禁想,如果汤姆没有表达他的担忧和沮丧,而是让它们在脑海里积压,事情会持续多久?会有什么连锁反应?整体影响会如何?

你可以通过访问openpracticelibrary.com/practice/stop-the-world-event/来了解更多并协作探讨停止世界的实践。

实时回顾

在寻求建立开放文化并在我们工作空间中推动自主、精通和目标意识的过程中,我们需要建立反馈循环,以便我们能够感知自己是否朝着正确的方向前进。

到目前为止,我们只介绍了几个非常简单的实践,帮助建立基础文化。但它们会有效吗?你怎么知道呢?如果新团队在背后低声抱怨社会契约过于空洞,并且认为那个暂停世界的做法在这样的地方永远行不通,该怎么办?希望这种情况没有发生,但如果发生了,那你很可能正朝着与开放文化背道而驰的方向发展,创造了更多的侧面孤岛和封闭行为与思维。

我们需要为大家创建一个渠道,反馈他们自己对实践的使用情况,甚至是初步印象。他们喜欢这些做法吗?他们认为这些做法有助于建立所需的基础吗?他们是否已经预见到一些障碍和阻碍因素?

我们已经提到了回顾实践几次,接下来本书会多次提到它,因为这些实践背后的理念带来了持续学习和持续改进的最重要哲学。

但这里介绍的做法有些不同。与其在回顾会议中安排时间开始收集我们早期基础活动的反馈,不如采用一种更加持续、始终在线的自助式反馈收集方式?

Emily Webber 分享的实时回顾技术为任何人提供了一个简单、直观的工具,让大家可以随时为任何事项添加回顾反馈。与其等待正式的反馈事件或某种调查(说实话,并不是每个人都喜欢这种方式),实时回顾使得所有参与者能够更快、更准确、更直接地提供反馈。

为了使用这种技术,找到一面足够长的墙或表面,以容纳你预期的反馈量。画一条长线来代表时间。

在时间轴上方画一个笑脸,在下方画一个悲伤的脸,在时间轴上画一个惊讶的脸(这代表你希望收到的反馈类型——积极的、负面的和惊讶的)。

向参与者解释使用这一做法的目标,以及如何使用你准备的区域。以下是需要分享的关键事项:

  • 告诉他们可以在哪里找到贡献材料。

  • 时间轴从左开始,直到你指定的结束时间。

  • 欢迎任何形式的反馈。

  • 每一条反馈项用一张便签纸。

不断循环并监控,回顾你的反馈并基于这些反馈做出适当的改进。

我们发现,基于实时回顾的反馈,早期进行一些简单调整,实际上可以为自主性、精通度和目标感播下种子。即使是对温控做出反应、改进餐饮服务或调整工作空间中人们的位置,也能产生很大的积极影响,因为反馈来自团队成员,他们看到自己的想法得到了快速回应。这就是我们所说的赋能团队。

一如既往,我们喜欢通过展示而非单纯说明,因此让我们来看一个实际的例子,看看我们是如何使用实时回顾的。

图 4.10:在五天 DevOps 文化与实践启用工作坊中使用的实时回顾

这一方法用于为期五天的沉浸式启用工作坊——DevOps 文化与实践启用。考虑到时间的紧凑性,实时回顾尤为有用,因为如果有某个简单的改进或解决方案可以更早应用,你可不希望等到一周结束(甚至某一天结束时)才收到反馈。

你会发现,在这个例子中,许多便签都位于中线下方,代表了悲伤的反馈。这是否意味着这次交付非常糟糕?不,不一定。当我们主持实时回顾时,我们实际上会告诉大家不用担心给我们提供大量的负面反馈。虽然阅读愉快积极的反馈很不错(而且有助于确认我们应该做得更多的事情),但我们真正想要捕捉的是改进的建议。我们如何才能变得更好?又该如何快速做到这一点?

你可能无法阅读到低层次的细节,但一些在第二天已经解决的早期反馈的优秀例子,在实时回顾中已被记录下来,包括以下内容:

  • 更频繁地休息,减少冗长的讲座时间

  • 订购更多的素食食品

  • 采用一种小组政策,在讨论开始进入死胡同继续前进(另外,团队提出了视觉信号,以便在这些情况发生时能及时提醒)

  • 购买更多显示器,以帮助技术练习的协作性

  • 每天至少出去几次呼吸新鲜空气

这些都是与会者给出的很好的反馈点,并且非常容易处理。持续的反馈和持续改进之旅就从这里开始!

使实时回顾实践如此强大的其中一件事是,它始终展示在参与者面前,始终在他们眼前。我们通常使用大型的 8 英尺×4 英尺的便携泡沫板来展示实时回顾,这样我们可以将其移到房间的各个位置,并故意放置在团队成员必须经常经过的地方。这鼓励他们贡献、重新组织,或仅仅是看到其他人提供的反馈。

如果你和一个分布式团队一起工作,使用数字白板和视频会议,我们可以轻松开始实时回顾。我们有一个不错的模板,你可以像图 4.11中展示的那样使用,并且可以从本书的 GitHub 仓库下载。

图 4.11:分布式团队的数字化实时回顾画布

这里的挑战是如何让采用和使用继续下去。像这样的数字化工件有可能变成信息冰箱,信息死在其中,只有有人打开它时才会被发现,结果变得冷冰冰且过时!也许信息应该有使用期限最佳食用日期!为了避免这种情况,我们需要强有力的引导。我们应该鼓励团队成员始终将这些数字工件保持可见。作为一个团队,我们应该定期进行虚拟的“走墙”环节,参与者和引导者应该在合适的时机鼓励大家为实时回顾提供反馈。

您可以通过访问openpracticelibrary.com/practice/realtime-retrospective/了解更多关于实时回顾实践的信息和合作。

社会契约、停止世界的安东线和实时回顾是三种非常好的实践,可以开始构建文化基础。如果得到良好的引导,单从这些实践中产生的对话应该开始推动团队自主性和团队身份感。我们将进一步探讨团队身份,因为它是构建优秀团队文化的重要组成部分。

团队身份

回想一下你过去与之合作过的团队。有多少是非常棒的经历,每天早上都期待着工作?有多少是糟糕的,你迫不及待地希望一切结束?你曾经与一个团队合作过,觉得,“那太棒了 - 我希望它能永远持续下去!” 如果是这样,那么使你觉得与那支团队合作如此令人惊叹的因素是什么?

团队认同感、士气和凝聚力都是紧密相连的,对于构建高绩效团队至关重要。它们都是一种心态,这意味着创建出色团队的公式有些抽象,形式上则少得多。你不能仅仅通过将人们拼凑在一起,加上一部分外向的个体和两部分勤奋的个体,就能迫使团队拥有高凝聚力!它必须比这更有机地形成。

个体团队身份可以具有传染性的热情,并传播到组织中的其他团队。当有一定能量、团队文化和共享身份时,其他团队希望成为那支团队。新团队应该形成,以成为那支团队。为了避免团队身份变成他们自己的孤立组织,我们可以通过兴趣社区和实践社区的方式连接团队。我们将在第七部分 - 改进它,维持它中进一步探讨这一点。

社交

有许多小文化要点和实践,我们用来尝试加速团队形成,使我们更快地达到执行状态。如果团队成员之间自然相处或有共同的兴趣,这可以感染整个团队,并在每个人之间创建联系。

图 4.12:团队远离办公室的社交

在个人层面了解人们并分享兴趣很容易延伸到日常工作中。人类天生是社交动物。这就是为什么我们总是试图与团队一起社交的原因。抽出时间远离办公室放松一下。有时候可以是下班后的酒吧,甚至是在白天分享午餐时。事实上,在以前的住所里,我们甚至有过像每周咖喱自助餐这样的事情,团队利用这段时间一起社交。有一次,我们在 Curry Day 上,因为团队中的一位成员病假了,我们甚至在一周内去了两次!

如果下班后去酒吧不适合,那么就完全颠倒一下!我们经常让团队在早上第一件事就是一起去吃一顿丰盛的早餐!这是以全新视角和新鲜鸡蛋开始新一轮冲刺的绝佳方式!社交有很多种形式,但重要的是停下来庆祝已经完成的工作。一起去玩游戏,或者去保龄球馆玩一个小时,有助于团队建立友谊并相互了解。

这听起来可能很空泛,但参与此类活动的团队总是能在日常工作中互相帮助,即使他们不熟悉所处理的任务。当团队成员建立了这种关系时,他们会为彼此付出额外的努力。

图 4.13:办公室中的团队社交

社交音乐播放列表为团队提供了一种简单的方式来互相了解。创建一个共享的播放列表,所有人都可以添加自己喜欢的歌曲,或者对其他人的歌曲进行点赞和点踩,可以在团队中营造出非常积极的氛围。了解别人对音乐的品味,或者在某些情况下,缺乏品味,可以与那些可能比较害羞或不太愿意参与社交活动的人建立新的联系。我们曾经在培训活动中使用过 Outloud.dj 来创建社交播放列表!

网络映射

网络映射是一个方便的实践,帮助个人彼此了解。这个做法很简单——将小组中的所有人排成一行,每个人都有一个自己从未见过的伙伴。

每个人在便签纸上写下自己的名字。设置一个三分钟的计时器,让配对的两个人在新的便签纸上写下他们的共同点。这样做之后,重新调整小组,并与新伙伴进行下一轮,捕捉他们的共同点。

每个人通常需要两张便签纸来开始这个活动,但如果小组较小,你也可以再进行一轮。选择一个人先开始,让他们介绍他们遇到的人。将他们的名字便签纸和共同点贴在画布上,并画上一条线将它们连接起来。然后交给下一个人,让他们介绍自己遇到的另一个人,继续连接这些人和他们的兴趣。所有人完成后,让团队为他们和其他人之间的关系画上额外的线条,形成一个巨大的意大利面图!这是一项简单的实践,我们知道,但它有助于加速新团队的相互了解。

图 4.14:网络图示例

你可以进一步通过一个隐喻展示,当打散一个已经高效的团队时会发生什么。让小组成员围成一圈,用线或毛线将每个有共同点的人连接起来。这应该形成一个巨大的网络;然后将社会契约放在这个网络上方。为了展示更换团队成员或移除团队成员对团队内部联系的负面影响,可以切断连接这些成员的所有线。你会看到,社会契约不再得到支持,最终掉落到地板上,这象征着频繁更换团队成员对集体的影响。

你可以通过访问openpracticelibrary.com/practice/network-mapping页面,了解更多有关网络映射实践的内容并进行讨论。

团队标志和首要指令

另一个看起来可能有些空泛的做法——但我们坚信——是创建团队名称和标志。这有助于增强团队对其身份和文化的认同感。随着团队成员相互了解并开始建立内部笑话,不妨花时间创建一个团队口号或设计一个可以印在 T 恤上的标志!

图 4.15:团队身份,配有定制的 T 恤和团队名称

让我们看一个现实中的例子,看看一个团队名称和标志如何为团队身份开个好头:

团队名称 + 建立团队标志 = 团队身份的起点

我们合作过的一些最佳团队曾共同创造了他们的团队标志,并自豪地穿着它们。在为一家航天与防务客户工作时,我们举办了一场团队标志设计比赛。我们拿到了一枚徽章,把它分成几个部分,每个团队成员都在其中画上了适合团队的元素。我们对所有喜欢的部分进行点票,然后合作挑选出最受欢迎的四个部分,最终构建了徽章。

团队“太空力量”就这样诞生了!

图 4.16:团队“太空力量”标志

拥有强烈身份感的团队会觉得自己有目标。所有这些小的做法和更多的做法都可以帮助团队经历塔克曼的团队发展阶段 5,即形成、风暴、规范和表现。让团队有足够的空间和带宽去互相了解是很重要的。给予空间后,团队会乐于自我组织、分享问题,并渴望作为一个整体取得成功。

领导者的责任就是为团队让路并帮助促进团队运作。如果每周花费一点披萨钱和几瓶啤酒就能让产品更好,或者团队更开心,那么从长远来看,这算不上什么成本。支持团队建立这种联系有助于培养积极的文化。创造这样的环境对于团队采纳一种口号至关重要——你建造它,你运营它,你拥有它

为分布式团队创建团队身份

不得不承认,在 COVID-19 大流行期间,这一直是我们面临的最大挑战之一。我们知道团队身份有多么特殊和魔力,它在为我们创造一个出色且心理安全的工作环境中所起到的作用。与一群我们从未见过面的人,且在短期内不太可能见面,去创造这种身份,感觉几乎是不可能的!

各种在线破冰活动有助于启动分布式文化,且在每次会议开始时都会广泛鼓励。做一些有趣的活动,让每个人都参与进来,前期为大家带来几次微笑!一个例子是让每个人介绍自己,分享自己拥有的超能力和希望拥有的超能力。毕竟,所有团队成员都是超级英雄!

定期的分组讨论和非正式聊天的机会不容小觑。想想看,你在饮水机或咖啡机旁、午餐时或下班后与同事聊的那些话题。这些对话在团队分布式工作时依然需要进行。

构建集体身份可能看起来只是锦上添花,但它实际上驱动了团队内部的社会纽带。之前提到的团队名称、团队标志、T 恤等小技巧,再加上为生日等事件举办虚拟庆祝活动,对于建立一个强大、高效的虚拟团队,甚至比传统方式更加重要。即便是进行这些活动,也可以以合作和包容的方式进行。

5 塔克曼的团队发展阶段

最后,不要低估在一起吃午餐或早餐时社交的价值和重要性。这是我们在开放创新实验室(Open Innovation Labs)驻地体验中建立的一个环节,因为我们看到它在关系中所带来的力量。因此,即使有些许尴尬,我们依然在虚拟团队中保持这个习惯。我们让团队成员带我们参观他们的房子,介绍他们的孩子和宠物,还会制作有趣的自拍视频。我们会举办虚拟社交活动——点个披萨,一边喝酒一边聊天,在线看电影,甚至参加虚拟逃脱室活动。

一个强大的团队身份和高效的团队通常源于在一个充满活力、色彩和信息的优秀团队空间中工作,这个空间是团队参与其中的。我们在本节早些时候介绍了信息辐射器(information radiator)概念(以及信息冰箱)。让我们进一步探讨信息辐射的这个概念。

散发一切

你有没有走进一个高效团队的房间,感觉到这里与其他地方不同?很难用文字描述为什么有这种感觉,但你确实能感受到一种凝聚力和开放性并存的氛围。你可能会看到墙上的便签,显示着正在进行的工作,或者追踪工具,如可供所有进入者查看的燃尽图,显示团队目前的进度。也许你会看到一些显示构建监控仪表盘的屏幕,展示代码的构建和部署过程,或者执行中的测试,甚至是应用程序和平台当前使用情况的实时统计数据!

所有这些都是信息辐射器,它们可能是我们工具包中最重要的东西之一。

信息辐射器的唯一目的是——向路过的人展示最新的信息。从测试分数到回顾总结、架构图,甚至是可能影响团队的事情,都可以辐射出来!要为这些信息感到骄傲,并将它们作为开放环境中的讨论话题。在一个真正敏捷的环境中,我们强调开放和诚实的沟通。隐藏在工具后面或埋藏问题不符合透明度原则。信息冰箱正是如此——它是信息被埋没的深冻库。你有多少次需要请求访问权限或登录才能找到你需要的信息?这些信息有多少次已经过时或没有更新?

除了我们喜欢展示的仪表盘和便签,基础工具包里还应该有一些其他的信息辐射器:

  1. 业务流程设计的可视化: 下图展示了一个利用了事件风暴(event storming)方法的示例。我们将在发现它部分详细讨论这个方法,但作为一个预告,你可以看到辐射出的信息量——所有这些信息都是通过协作和对话生成的。

    图 4.17:事件风暴

  2. 可视化团队当前正在进行的工作、之前完成的工作以及未来可能进行的工作: 下图展示了大量的便签板,提供了这些信息给团队。

    图 4.18:每面墙和每个窗户上的信息辐射器

  3. 我们的工具(我们将在第六章,开放技术实践——开始,正确起步中介绍)可以辐射大量关于我们所构建的软件产品状态和健康状况的实时信息。构建状态、测试分数,甚至展示源代码提交演变的动画都能为团队成员提供有用的信息。下图展示了我们用来辐射这些信息的一些大屏幕!

图 4.19:聚焦于技术的实时信息辐射器

在接下来的部分中,我们将看到团队如何在分布式环境下实践这一点。

分布式环境下的全面辐射

再次强调,当团队的每个人都在不同地点时,模拟这种充满信息辐射器和工件的环境可能是具有挑战性的。它确实具有挑战性,但并非不可能。只要投入一些时间和资源来精心布置工作空间,并配备合适的工具和设施,将会带来巨大的收益和回报。

图 4.20:在家设置信息辐射器

拥有第二个(甚至第三个)显示器可以帮助信息辐射器随时向你传递温暖的信息。你可以设置窗口,使不同的应用程序为你提供实时信息,同时也能运行你的数字白板和视频会议工具。使用数字白板的好处是你永远不会用完墙面空间!

在前面的示例中,您还会看到一个便携式扬声器(如果您有一个团队社交播放列表,那就太棒了!),一台小型平板电脑,用来展示实时构建监控,墙面空间和便签——仍然对构思和规划自己的工作非常有用。甚至还有一盆植物,帮助提供氧气,促进创造力和清晰思维!

访问 openpracticelibrary.com/blog/guidelines-for-setting-up-a-home-working-space/ ,查看更多小贴士(并添加您自己的小贴士)。

我们用来可视化团队健康和情绪的另一组信息辐射器,得到了团队情绪实践的支持,并且是高效能团队的关键推动力。

团队情绪

团队情绪实践跟踪团队的情绪,并为团队成员提供一个机制,以便在他们的情绪发生变化时,迅速进行反馈,可以是公开的,也可以是匿名的。团队情绪实践使得问题能够被早期识别、辐射并解决。它们使得团队能够快速讨论一位或多位成员是否突然因为工作方向感到困扰,同时还可以提供一个信息辐射器,用来评估整个团队的健康状况和情绪。

引入团队情绪信息辐射器的一个流行方法是情绪玻璃珠。使用这种方法时,你需要几个容器,容器里要放足够的玻璃珠,确保每个参与的团队成员都有一个。当我们说容器时,它可以是一个简单的透明盒子(在 Red Hat,我们喜欢容器,OpenShift 就是围绕容器展开的,因此我们甚至找到了一种方式让容器来追踪我们的情绪!),也可以仅仅是大张翻转图纸上的容器图示。你还需要两种不同颜色的玻璃珠(也可以是便签)作为开始。之后可以加入其他颜色。

情绪玻璃珠容器被放置在一个战略位置,所有团队成员都会定期看到并经过这些容器(例如,靠近门口或通往卫生间的路上)。

鼓励团队成员思考他们当前的情绪。从积极(通常是绿色)和消极(通常是红色)开始,每个团队成员都将一颗小球放入容器中,反映出他们的情绪。

图 4.21:情绪小球容器传递团队情绪信息

定期提醒团队成员,他们可以随时改变自己的情绪小球,只需取下之前的小球并换上另一种颜色。通过所有团队成员情绪小球的容器,传递出的信息就是团队的整体情绪。

将团队情绪与其他实践相结合

如果没有使用其他实践来识别和应对情绪变化,容器中的小球从绿色迅速变为红色,可能会带来更多的伤害而非好处。这个变化可以通过实时回顾中的信息来解释,也许这足够严重,值得进行“停止世界”。

无论如何,回顾会议是一个极好的平台,用于呈现、讨论并解决可能出现在团队情绪框中的问题。查看趋势并观察团队情绪随时间变化也很有趣。团队情绪可能还会受到工作环境或技术变动的影响。

随着时间的推移,汇总团队情绪数据可以是一个有趣且非常有力量的做法。我们可以利用这些数据来识别趋势和模式,并得出结论,例如,调整技术实践、其他文化实践或领导行为如何影响团队的情绪和表现。图 4.22展示了情绪分布随时间变化的情况。

图 4.22:团队情绪分析

让我们来看一个我们作者的另一个真实故事,他在其中了解到,有时这些实践可能会带来不同的结果,但仍然是非常有帮助的!

团队情绪实现不同的目的——开玩笑!

我发现,在实践中衡量团队情绪可能是一个非常困难的事情,特别是当你面对一个性别单一的团队时——例如,全是男性。我是个男性,我可以自信地说,特别是男性通常不愿意分享他们的情感,尤其是在团队情绪方面。

在一次特别的项目中,我们有一位出色的女性项目负责人,她描述了情绪小球的实践,并且完全预期几乎全是男性的团队会全心参与。然而,在接下来的几天里,情绪小球板上没有任何活动,左边贴纸上显示了这样的内容。

经过一番解释,大家才明白,情绪小球的颜色并不代表一位沮丧队员深藏的情感,它只是单纯的玩笑而已!在随后的几天里,情绪小球容器里出现了许多不同的颜色和有趣的画作。所以,实践(算是)有效了!

许多人可能不得不尝试与那些从未见过面或不在同一地点的团队成员进行团队建设。这在 COVID-19 大流行期间尤其如此。让我们来看一个例子,看看我们是如何通过数字方式使用相同的实践。

分布式团队的团队情感

使用数字工具可以执行相同的操作,这再次强调了保持这种信息发布器温暖并定期检查的重要性。

图 4.23:一个用于捕捉分布式团队情感的数字情绪珠子容器

你可以通过访问 openpracticelibrary.com/practice/team-sentiment/ 了解更多并参与关于团队情感实践的合作。

传递失败信息

拥有信息发布器、仪表盘和情绪珠子容器显示绿色可以令人满意,并且是积极的验证。但当它们变成红色时,它们的力量更为强大。当它们传递出某些问题或故障的信号时,它为周围的人提供了一个反馈循环,让大家能够反应并作出响应。

传递失败信息——与传递成功信息一样有用(甚至更有用)

并非所有信息发布器都需要始终闪烁绿色或显示正面信息。有时候,负面信息是启动对话的一个很好的方式。

许多年前,在为零售商工作时,有多个供应商共同参与解决方案的开发。每个供应商都负责架构的不同层次,而我的团队负责某些中间件,用于以适合移动设备的方式从代理中提供数据。

我们被零售商一再挑战,因为我们迟迟没有部署生产服务器——然而,其他团队也都没有准备好!当我们部署我们的技术堆栈时,代理层也没有连接,因此我们的服务器在和一个不存在的东西对话!

为了将讨论带回到那些现在造成阻塞的团队,我们创建了一个简单的 TrafficLight 工作面板。这是一个简单的自动化工具,主要是对每个服务进行冒烟测试,检查其连接性。我们将这个面板命名为“生产监控”,并将其投影到我们的项目会议室中——这样每当有人进入时,他们会看到一个显示大量红色的面板。

我从中得到的教训很简单——推动事情进展的一个方法是有一个标注为“生产监控”的面板,面板上充满了红色!这个面板帮助我们将讨论引向这样一个事实:我们这部分架构单独存在是没有用的——而作为零售商的多个供应商,我们需要合作才能让端到端的解决方案正常运作,而不仅仅是完成我们自己的小部分。

图 4.24:开始使技术环境信息可视化

如果你在寻找可以以创意方式可视化的精彩点子,我们强烈推荐这本书——96 种可视化示例,由 Jimmy Janlén 编写!6 它提供了一些展示对团队至关重要的可视化信息的绝妙点子。而且它还有一个超级附赠部分,教你如何正确剥开便利贴!

你可以通过访问openpracticelibrary.com/practice/visualisation-of-work/的开放实践库页面,了解更多关于工作实践可视化的内容并进行协作。

6 visualizationexamples.com/

检查和适应

到目前为止,我们已经介绍了很多帮助团队协作和彼此了解的实践和技巧。但一次性的做了这些后走开,若没有从中学习,那就毫无意义。

如果没有对数据作出回应,就无法从中获得任何有价值的信息。这是很容易滑入的模式。我们以前见过很多次——团队进行回顾,提出了很棒的讨论,但没有记录任何改进行动。更糟的是,他们记录了行动项,但却没有执行,或者根本没有时间去解决。

接下来会发生什么?相同的问题会出现在下一个回顾会议中,团队士气会慢慢下滑,因为问题没有得到解决。是时候打破这个循环了!对信息作出回应,成为一个执行者。

你已经有了这些工具,但如果忽视它们,它们将完全没有用。它们可能看起来很漂亮,但如果不采取行动,它们就是无用的。我们需要在工具或实践告诉我们该怎么做时作出回应,如果信息告诉我们要改变方向,我们也需要准备好调整并转变方向。

要能够检查某些事物(技术),你必须将监控和持续收集指标作为每个服务和每个工具内建的基本原则。

指标已内置于 OpenShift 和其他产品中。深刻的仪表盘允许你在客户发现之前就找到问题。它们还让你能够以一种不容忽视的方式展示关于产品、技术和团队的真实信息。数据不会撒谎,而公开透明地展示这些数据可以帮助解锁并保持团队的自主性和掌控力。当团队遇到阻碍并通过传达信息来解释原因时,定期与能够做出决策的利益相关者和管理层进行走访墙面会议,来解锁并改变信息,能真正推动广泛的检查和适应。如果有问题、有障碍或遇到瓶颈,确保信息被清晰传递出去,并让能够解决问题的人定期看到这些信息。如果信息没有被看到,就放大它,更多地分享,加入一些声音——这是确保这些事情被看到和听到的机会。

PetBattle — 建立文化基础

Aidan、Ciarán、Emma 和 Jen 这四位工程师承诺负责 PetBattle 下一阶段的发展,他们与 Dave(一位 UX 设计师)、Susan(一位质量保证顾问)以及 Eoin 见面,Eoin 将负责推动 PetBattle 提升到下一个层级。Eoin 带来了咖啡和甜甜圈,并将会议安排为一个成功启动的环节。团队中的一些人之前曾一起工作过,而 Jen、Dave 和 Susan 是全新的成员。

Eoin 租了一个很酷的市中心办公室空间,接下来几个月,PetBattle 的总部就设在这里。房间里有很多可移动的桌子、便携白板和旋转椅,还有豆袋椅和舒适的泡沫座椅。房间的一端堆放着一些巨大的泡沫板(大约几英寸厚,8 英尺×4 英尺大小)。所有家具都被推到房间的另一边,Eoin 指出团队可以根据自己的需求自由布置空间,一切都是轮子的,可移动的,甚至植物也是放在轮子上的!

还有几个崭新的大显示器和其他 IT 配件,以及一堆未拆封的来自亚马逊的盒子,Eoin 说这些盒子里装满了各种颜色和尺寸的便利贴。他还买了 Sharpie 笔、计时器、彩色卡片、油漆胶带——你能想到的东西,他都买了。Eoin 一直都很忙!

在一面墙上,Eoin 准备了一个启动待办事项列表,上面有许多便利贴,表示他接下来几天想要涵盖的项目。团队成员查看了待办事项,并添加了一些自己希望讨论的内容。

喝完咖啡并聊了一会儿后,他们开始了一个简短的破冰活动,每个人都要介绍自己并说出三件事——其中两件是真的,一件是假的。在接下来的一周里,团队成员们将不得不猜出每个人的谎言是什么。

下一个议题是制定社会契约。Eoin 解释了它是如何运作的,并要求每个人写下至少一条他们希望加入的便利贴。社会契约从讨论核心工作时间开始,大家一致认为工作时间是上午 10 点到下午 4 点——所有的协作活动都将在这个时间框架内安排。Emma 提出了一个准时到达的社会契约条款,大家都同意了。Dave 提出了一个宽松的着装规定,大家也都非常高兴。接下来的 30 分钟里,团队进行了有趣的讨论,最终社会契约还包括了以下内容:

  • 玩得开心!

  • 确保每个人的声音都被听到

  • 展现同理心

  • 错误没关系

  • 集体学习,配对构建

  • 保持开放心态

  • 每周举行一次社交晚会

  • 每周三次作为团队一起享用早餐和午餐

  • 赋予权力

最终,团队一致认为这是一个良好的开始,但他们都会继续反思并思考如何改进社会契约。他们都签了字,Eoin 将它挂在了靠近团队的墙上。

Jen 建议团队尝试使用实时回顾——这是她在最近的一次会议上看到的,并且很想尝试。每个人之前都做过回顾,但他们并不熟悉实时回顾的方式。这个主意看起来不错,大家同意试一试。

待办事项中的下一个项目是房间介绍。Eoin 在门口设置了一个情绪玻璃球容器,并解释了这个活动如何进行。团队成员看起来有些怀疑(Aidan 评论说这些并不是他今天以为会使用的容器),但出于开放和实验的精神,他们决定尝试一下。

房间的另一端有一根大绳子,顶端挂着一个旧铃铛。旁边有一块大牌子,上面写着停止。Susan 提出了可以解释这个装置如何工作以及如何用它来停止世界。团队似乎对这个非常满意——它非常有道理。事实上,他们中的大多数人都在想,他们在之前的工作中曾遇到过很多场景,如果那时有这个装置,将会非常有用!

团队介绍了房间的其他部分。墙上的不同区域有很多占位符标牌,其中一块已经被填写上了社会契约。墙上还标有影响力图、事件风暴、价值切片、产品待办事项和第一个 Sprint 等标题。Eoin 解释说,虽然房间现在看起来空旷,但仅仅几天后,它将充满信息。

待办事项中的下一个项目是配置空间。这是一个很棒的团队活动,真的体现了赋能的精神。团队推来了桌子,并讨论他们希望如何设置带有集体工作区和配对站的空间。他们设置了 Eoin 购买的每一台显示器,还为每台显示器连接了一些 Raspberry Pi 迷你计算机。Aidan 为每一台显示器增加了一个 PetBattle 数字标牌!音乐响起,空间被配置好,看起来很棒。午餐时,团队的声音开始增大,笑声和打趣声不断。

团队开始形成。文化基础已经到位,并且将从这里开始不断加强。

结论

在这一章中,我们从文化和协作的角度介绍了基础内容,并强调了在进行任何技术工作或尝试发现和交付产品开发之前,建立这一基础的重要性。为了在使用 DevOps 和 OpenShift 时取得强大的业务成果,我们需要有高效的团队来开发和运营软件产品。

创建高效团队的起点是营造一个开放的文化,让团队成员感到心理安全,并能在他们所做的一切中实现自主性、掌握感和目标感。

为了帮助我们实现这一目标并建立基础,我们探讨了几种我们最喜欢的实践,它们在启动新团队时最为成功——社会契约、停止世界系统、获得团队认同、尽可能多地传播信息,包括团队情感,并开始一个定期检查和调整的循环。

我们讲了很多故事,并分享了一些应用这些实践的回忆。然后我们回到了正在新组建的宠物战斗团队,看看他们是如何在一起的第一天开始建立他们的文化基础的。

本章中的一些实践可能适合你和你的团队,另一些则可能不适用,届时你可以将这些工具放回箱子里,选择其他的。你可以在openpracticelibrary.com/tags/foundation探索更多的实践,或者你也可以贡献你自己的实践。记住,重要的不是你使用的具体实践和工具,而是你投入时间去建立文化基础这一点。

图 4.25:添加开放文化实践的基础

当然,基础是可以改进和加强的。在下一章中,我们将探讨领导力如何从一开始就帮助这一过程,以及开放的物理环境和空间如何强化这一基础。在第七节,改进并维持中,我们将进一步探讨如何加强基础,以支持采纳开放工作方式的产品团队的可扩展性和可持续性。

第五章:5. 开放环境与开放领导力

在前一章中,我们探讨了开放文化的含义,以及如何使团队从基础开始构建这种工作方式。

当我们谈论赋予授权、自我组织和自主指导的团队时,许多热心者会说,管理应该只是走开并消失!有些人会说,如果团队真正获得授权,那么他们显然不需要被管理,也不需要经理。

在这里,区分领导力和管理至关重要。我们希望团队能够自下而上地管理组织,领导者则制定方向和意图,促使整个组织表现出这种行为。这就是像红帽这样的开放组织的领导方式。

吉姆·怀特赫斯特,前红帽公司 CEO,将开放组织定义为一个能够积极参与社区内外的组织——更快地响应机遇,获得组织外部的资源和人才,并激励、激励和赋予各个层次的人们行动责任感。

他的下面的图解明确了传统组织从上而下管理和开放组织从下而上领导和组织的区别。

图 5.1:传统组织与开放组织的差异

那么,在所有这些中,领导的主要角色是什么?领导可以通过什么方式在学习型组织中实现持久变革?我们将通过首先看看柯达的案例研究来探讨这些问题,柯达公司是发明个人摄影但最终错过了数字革命的公司。我们能从中学到什么教训?在改变组织部分,我们将发现领导可以通过将决策权下放到信息源头来实现更好的执行。然后我们将学习如何使用优先级滑块和强制排名来帮助我们确定我们可以集中精力的地方。在最后的空间部分,我们将探讨为我们的团队创造适宜的物理环境。

柯达问题

你并不需要成为 CEO 或商业管理顾问,才能理解每个人都能通过分享不同组织的故事以及它们在快速变化的世界中成功或失败的教训。公共公司的寿命在过去 50 多年中显著缩短了。1 这是为什么?历史上有很多组织未能适应不断变化的客户需求的例子。让我们更详细地看一看可能是其中最著名的这些故事,了解究竟出了什么问题。

东曼柯达公司发明了个人摄影。直到 20 世纪初,拍照是需要进入摄影棚由人来拍摄的。柯达将这种极好的用户体验以盒子的形式售出,价格仅为 1 美元——说实话,真的不算多。真正的利润来自于处理胶片并制作照片,这通常需要一到两周的时间。到 20 世纪末,柯达已经作为一家公司和品牌取得了巨大的成功。

然而,他们错失了一个机会——到 21 世纪初,数码摄影开始崭露头角。柯达在数码领域有所涉足,但他们认为摄影是一项化学过程——赚钱的方式一直是通过化学处理胶片。当他们意识到市场已经发生变化时,已经为时过晚。日本和亚洲崛起的科技巨头在数码技术上远远超过了他们。柯达于 2012 年 1 月申请了第十一章破产保护。

你知道吗,有一个名叫 Steve Sasson 的人发明了电荷耦合器件(Charged Couple Device),这为他的另一项发明——数码相机奠定了基础。猜猜看——他曾在柯达工作!

没错。1975 年,他发明了数码相机,但当时他的上司却将其埋没——他们说没人会想在电视上看自己的照片。Steve 还在 1989 年发明了第一款数码单镜头相机,但当时柯达的营销部门认为推出该产品会干扰他们的胶片处理业务——因此这项发明也被埋没了。

图 5.2:1976 年数码相机专利图

现在,柯达凭借其数码专利赚取了数十亿美元;然而,这些专利在 2007 年到期。公司根本无法足够迅速地转型,改变其竞争对手通过数码技术所提供的客户体验,最终在 2012 年申请了破产保护。

1 www.amazon.com/Creative-Destruction-Underperform-Market-Successfully/dp/038550134X

这为我们提供了一个很好的例子,说明了持续学习、持续措施和持续转变的渴望的重要性。

向历史学习

我们可以从柯达的故事中汲取许多教训:

  • 组织必须适应客户需求的变化。

    这说起来容易,做起来难。人类的理解和认知深受我们的文化信仰和成长背景的影响。我们看到的与别人看到的往往不同。在某些方面,这就是柯达的遭遇。他们的背景和公司历史源于化学工程角度;实际上,东曼化学(从柯达分拆出来的公司)今天仍然是一家非常成功的公司。当时,柯达的管理层无法看到数码摄影所代表的用户体验的变革性变化。

  • 柯达的故事告诉我们,创新性变革可以来自组织内部的任何地方。

要让内部视角成功地作为商业战略推进,领导层通常需要从不同的角度来看待问题。旧有的命令与控制结构本身也需要改变,以便更好地鼓励和拥抱创新性变革。

开放领导力

Shabnoor Shah,开放领导力全球负责人及开放创新实验室教练,向我们解释了开放领导力如何在数字化转型的世界中成为一种崭新且富有变革性的领导方式。领导力的基础源于开源的思维、工作和行为方式。

开放领导力的一个独特方面是,因为它实质上是一种心态和存在方式,它并不局限于等级结构中的高级管理层。任何人、任何级别的员工都可以实践开放领导力。然而,当领导者以开放的方式领导时,其影响是显著且可感知的,能够塑造一个开放、积极和进步的组织文化。其结果体现在员工的幸福感、福祉和参与度、客户满意度以及公司整体的盈利能力和成功上。

开放领导力和开放组织的指导原则包括透明性、包容性、合作、社区和参与、适应性、优胜劣汰以及早期和频繁的发布。开放领导力心态(在红帽公司)的核心信念是:默认开放,因为开放是一种更好的方式。这一理念由四个支持性信念支撑:每个人都有所贡献,每个人都有未被发掘的潜力,每个人都有责任领导,且当我们(所有人)把组织放在首位时,大家都会受益。

改变组织

组织架构图通常是自上而下的等级结构,它们无法告诉你公司本质或其互动方式。让我们重新绘制我们的组织架构图,看看能否更好地呈现可能发生的互动。我们的客户被描绘为我们的根基,是支撑整个组织生存的基础。接下来,我们将不同的业务单元画作花瓣,利用业务流程互动以实现各自的目标。公司总裁被描绘为一朵雨云,俯视着公司,保护着组织免受外部董事会和利益相关者的影响,而外向的公司面孔则由 CEO 代表。

图 5.3:重新思考的组织架构图

人类是生物有机体。组织也应被视为有机体,而不是仅仅由组织架构图构成的产物。这是有道理的——组织是由其员工构成的。因此,当我们观察组织如何变化时,我们应该问一个问题:人类如何经历变化?如果大多数人会觉得变化令人不安,那是什么促使一个人经历变化,而不是接受现状?

当人们感到不安、不满或不满意当前的情况时,他们才会做出改变。同样,处于压力中的公司也会被迫实施变革以求生存。值得注意的是,当将本书中描述的实践付诸实践时,会感到不舒服。团队成员经常会形容这些变化为困难难以实现。这其实是件好事!变革是困难的。公司(就像人类一样)需要拥抱创造力才能创新——从而为他们的付费客户提供出色的体验。一次成功的转型不是仅在某段集中的时间内发生,之后没有任何变化。将创新变革作为一种持续运营的方式来拥抱,才是挑战所在。

领导可持续的变革

组织内部的变革如果没有资金和高层支持,是无法持续的。柯达未能培养和实施内部涌现的伟大想法。在一个组织内,正是组织领导者制定了大家必须遵循的游戏规则。重要的是,领导者设定了团队必须在其中运作的环境和背景。这就像农耕类比。要成为一名成功的农民,你必须为作物的生长创造一个合适的环境。组织领导者必须创造正确的条件,让创新转型蓬勃发展,成为常态。在最近的一次演讲中,我们听到西蒙·塞内克,这位英美籍作家和励志演讲者说道:“有一个事实是无可争议的——你的客户、利益相关者和员工都是人。你不能领导一家公司,你能领导的是人。”

通常并不清楚,领导者必须冒险改变当前的工作方式。往往这种风险也是领导者的职业风险。他们将自己的职位置于风险之中,去接受变革。因此,首次或初步的改进决策必须谨慎做出。变革不能过大,以免带来组织失败的风险,同时也必须具有意义,才能让客户和利益相关者感受到真正的商业影响——你的产品交付时会让人发出的赞叹。

这些听起来在你曾工作过的组织中熟悉吗?

  • 我们在组织内部有许多不愉快的孤岛。

  • 我们是一个团队,但有许多部落。

  • 存在过多的交接。

  • 责任过少。

  • 我们有不对齐的激励机制。

  • 我们因为失败而受到惩罚。

  • 我们有一些自尊心强、不愿分享的人。

  • 存在变革障碍——这就是我们一直以来的做事方式。

如果你认同这些,它们都是需要高层支持才能改变的特征和特点。领导层的职责是:

  • 创建一个共同的目标和愿景。

  • 允许改变规则

  • 消除不必要的看门人和障碍。

  • 鼓励那些完成工作的行动

  • 下放决策权。

  • 展示透明的沟通。

  • 打破部门之间的壁垒。

  • 从整体上衡量系统。

  • 变革成为每个人的工作。

  • 根据组织成果的交付而非流程遵循来评估价值。

  • 帮助建立一个在业务与技术之间存在信任的工程圣地。

  • 通过指标和数据展示结果。

  • 鼓励团队拥有完整的工程堆栈。

  • 将透明度融入整个开发过程。

  • 冒险开始第一次变革。

这是一个长长的清单。在接下来的部分中,我们将通过借鉴潜艇舰长的一些建议,探讨如何带来这些特质!

实现卓越

在大卫·马尔凯特(David Marquet)舰长的书《转变舰船》中,2 他将领导力定义为将卓越的能力嵌入到组织中的人和实践中,并将其与个性分离

赋予意图

在书中,他谈到自己作为核潜艇舰长的经历,以及他们如何学会不跟随领导进入灾难性的局面。他发誓再也不会发出直接命令,而是设定意图。与其给出指令,不如给出意图。军官们不再一直请求许可,这意味着心理上的归属感转向了他们。马尔凯特谈到了支持这一控制权赋予理念的两个支柱——技术能力和组织清晰度。

将决策移交到信息所在的地方。

将决策的权力移交到信息所在的地方,这意味着在软件领域,软件工程师可以决定何时发布软件,并在准备好时发布。做决定的最佳时机是必须做出决策的最后一刻。通过延迟决策,我们可以获得更多的机会来收集尽可能多的信息。通过这样做,你能比由中央领导做决定时,获得更快、更高质量的执行结果。

设置环境

对许多人来说,这可能是一个巨大的思维转变,但是在阅读马尔凯特的书籍并观看 YouTube 上的十分钟卓越视频 3 时(这是我们喜欢向客户展示,尤其是他们的领导层的另一个视频),这一切都变得非常有道理。这种心态为至少一个团队提供了独立工作并富有目的感的领导基础。我们将在书中稍后探讨这一心态的可扩展性。

3 youtu.be/OqmdLcyES_Q

2 davidmarquet.com/turn-the-ship-around-book/

图 5.4代表了在十分钟视频期间制作的草图,并包含了几个重要的讯息。

图 5.4:Inno-Versity 呈现:《卓越》由大卫·马尔凯特

如果你还没有观看上面制作图表的视频,请抽时间现在去观看。它充满启发性,提供了深刻的思考,并为本书接下来的内容奠定了领导力的背景。

我们(作为领导者)如何说服怀疑者?

每家公司都是 IT 公司,无论他们认为自己在哪个行业 – 克里斯托弗·利特尔,软件高管,《DevOps 纪实》作者

如果你不在 IT 领域,可以说,IT 常常被认为是妨碍组织商业目标实现的因素。正如《凤凰项目》一书中的 CEO 史蒂夫所说 – IT 很重要。IT 不仅仅是一个我可以外包的部门。IT 处于我们所有重大公司努力的核心,它对日常运营的几乎每一个方面都至关重要。 通常,组织中非 IT 的部分最难接受一种新的工作方式,而这种方式将他们与 IT 部门紧密合作,帮助推动提升客户成功的成果。

4 《凤凰项目:IT 与帮助企业成功的小说》 – 吉恩·金, 凯文·贝尔, 乔治·斯帕福德

公司里没有电脑!是 1990 年代,还是 1890 年代?

在 90 年代,我曾在一家软件公司工作,为伦敦的银行编写交易系统。我的合作伙伴则在一家传统的伦敦律师事务所的会计团队工作。她给我讲了一个我简直不敢相信的故事:公司里居然没有电脑。所有的业务都通过纸质文件处理,B2B 交易仍然通过传真机完成。我简直不敢相信居然可以在没有 IT 的情况下做生意!

你今天能想到类似的例子吗?不,我也想不出。

商业产品负责人和业务 SMEs 是支持我们跨职能团队的关键人物。他们的主要任务之一是代表并与所有产品利益相关者和客户进行沟通,并将这些对话和结果分享给团队。通过帮助团队决定应该交付的内容,尤其是应该避免交付的内容,产品负责人大大提高了客户满意度,因为最具客户价值的软件可以优先开发和交付。

通过成为跨职能团队的一部分,产品负责人能够共享有关编写和运营向客户交付业务服务的应用程序的技术挑战的理解。

最终,正是通过技术团队成员与非技术团队成员之间的协作和共享理解,我们才能让商业领袖与 IT 团队达成共识。通过成功交付客户和组织的成果,产品负责人可以向管理层展示成功,从而与技术伙伴共同承担 IT 挑战。

现在领导层正在支持并推动我们产品团队的工作,我们接下来将切换话题,来看看使用优先级滑块这一练习,它可以帮助我们团队优先排序最重要的产品领域或团队建设话题。

优先级滑块

优先级滑块是你工具包中一个非常棒且简单的工具!就像我们正在探索的其他许多练习一样,它们实际上只是一个帮助我们促进对话并推动共同理解的工具。我们使用它们来推动团队就某一时间段内应该采取的方向达成共识。

进行这个练习非常简单。只需要拿出白板的一小块区域,围绕你们的参与重点进行一些头脑风暴。它们可能包括以下内容:

  • 功能完整性:在某个应用功能上做到 100% 完整有多重要?还是我们在各个功能区域之间寻找某种薄弱的联系?

  • 安全性:我们知道安全性很重要,但我们现在应该投入多少时间来加固我们的软件呢?

  • 技能获取:开心、积极的团队成员是伟大的团队。确保团队具备构建、运营和拥有其软件的所有专业技能可能非常重要。

  • 用户体验:我们是在构建我们想要的东西,还是我们认为客户想要的东西?

  • 测试完整性:总会有一些测试。我们现在自动化这些测试有多重要?还是应该从一开始就专注于测试自动化?

需要强调的是,这些话题不是产品功能。它们是产品领域或团队建设的主题,缺少它们我们就无法做出优秀的产品。例如,也许你希望通过你的应用推动更多的销售,因此你决定实施某种形式的推送通知,将客户直接引导到你的应用。推送通知并不是你会添加到优先级滑块中的内容,但也许市场渗透率可以。这一话题可能包括一些额外的功能或你可以执行的实验。最好事先准备好这些例子,并确保团队在开始之前清楚每个滑块项的含义。

有了这些清单之后,拿出便签和记号笔,将它们写成一列。例如,假设我们使用上面的五个标题。现在,我们需要为每个标题列出一个从 1 到 5 的数字比例。你团队正在优先排序的项目越多,比例就会越高。

图 5.5:优先级滑块

有了比例尺后,团队需要决定相对于其他项目,哪个项目是最重要的。优先级滑块的关键是没有两个项目可以占据相同的优先级——所以如果 5 是你的最高优先级,那么用户体验和安全性不能处于同一水平。每个项目必须与另一个项目进行权衡,因此有些团队将这个练习称为使用 权衡滑块

图 5.6:优先级滑块——协作实践

在上面的图片中,我们可以看到一个团队正在讨论一个正在进行的优先级滑块环节。作为团队,尝试决定如何强制排序选项,往往能引发很多有意义的讨论。下面我们可以看到两组完整的滑块。每个人都必须就“1”或“10”哪个是最高优先级达成一致,这一点非常重要!

图 5.7:优先级滑块示例

一个团队达成共识的方式有很多种——他们可以就每一项进行传统的讨论,并逐一达成一致。对于某些团队,这种方式可能很有效,但对于其他团队,采用更正式的方法可以确保所有人都能参与进来。以下是一些建议:

表 5.1:鼓励利益相关者参与的方法

完成的画布可以帮助我们了解即将到来的优先事项。这些优先级可以设定为几周或更长时间,但像所有的实践一样,这不是一次性任务!它应当在任何一个领域的工作完成后回顾,检查是否足够,或者我们的优先级是否发生了变化。

优秀的产品负责人和敏捷教练可以将优先级滑块带到迭代计划会议中。当决定哪些内容应该进入迭代,哪些内容应该省略时,能够通过参考画布来加快决策,可以节省时间。像所有这些实践一样,始终保持其可视化非常重要,当然,当大家都在同一个房间时,这会更容易实现。当然,仍然有很好的方法可以与分布式团队一起进行优先级滑块环节,我们接下来将讨论这些方法。

与分布式人员一起使用优先级滑块

如果你有一个好的数字白板工具,并且所有需要参与优先级滑块的利益相关者都通过视频会议工具连接,那么进行这项练习相对简单。我们提供了一个有用的模板,你可以从本书的 GitHub 仓库下载并在你喜欢的工具中使用,比如 Miro、Mural、PowerPoint、Google Slides 等。

图 5.8:分布式人员使用的数字优先级滑块画布

当墙上有滑块,大家都站着并且需要添加自己的点时,环境非常适合对话和合作,大家都会不由自主地参与进来!而在视频通话中,有些人可能会消失,不参与进来。所以,强有力的引导非常重要。采用像1-2-4-all这样的解放性结构,将有助于从一开始就获得参与。1-2-4-all是一种简单的引导技术,首先要求个人独立并私下提供自己的意见或看法。然后,他们与另一个人配对,讨论并合并他们的想法。接下来,两个小组聚集在一起,汇总彼此的想法,最后全体成员聚集在一起,整合所有输入。

你可以通过访问 openpracticelibrary.com/practice/priority-sliders 页面,了解更多并合作实践优先级滑块。

图 5.9:用于分布式人员的数字优先级滑块示例

无论我们是在实体空间还是虚拟工作空间中,本书迄今为止介绍的所有实践(包括优先级滑块)都需要一个良好的空间来组织,以便团队进行有效合作。

空间

帮助领导为团队建立坚实基础的一个重要方面是为团队成员找到一个合适的工作空间。

在上一章中,我们探讨了动机、自治、掌握和目标的重要性。物理工作空间是一个重要的支持因素。优秀的团队在优秀的空间中工作。

当我们举办开放创新实验室驻留项目时,我们会在我们自己的实验室中进行,这些实验室是专门为这种工作方式设计的。通常,这是我们客户驻留者最关注的问题之一——他们表示自己的建筑并没有为这种工作方式做准备,并且会遇到许多来自设施、安全、健康与安全等部门的障碍。我们旨在利用在实验室中的时间领导者和利益相关者展示,这种工作方式能够带来多少参与度、活力和价值,以及物理空间在其中所起的作用。

我们根据一系列最佳实践和从全球、常设开放创新实验室设施建设中获得的经验,以及构建临时快闪团队空间的经验,提出了一些空间建议。Val Yonchev,开放创新实验室 EMEA 负责人Mike Walker,全球高级总监,开放创新实验室 在他们为开放实践库提供的贡献中,提供了大量的思路领导,您可以在 openpracticelibrary.com/practice/team-spaces/ 阅读。

让我们来看几个例子。首先,下面这张图片中的实验室空间非常开放。没有任何隔断或墙壁分隔。它非常灵活,所有的桌子、椅子、显示器,甚至是可移动的植物都可以自由配置!

图 5.10:开放式工作区示例

开放式工作空间光线充足,理想情况下有一些自然光,桌椅在房间内合理分布。墙面空间也非常宽裕,适合放置各种信息展示板。

图 5.11:有大量墙面空间的开放式工作区

信息展示板应易于访问,并在团队成员的视线范围内。如果团队成员不能定期看到这些信息,那么这些信息的存在就毫无价值。

图 5.12:工作可视化的示例

最重要的是,空间应该尽可能促进对话和合作。

图 5.13:为配对和集体协作设置的可配置工作空间

我们需要在空间上投入多少资金,最重要的是什么?

最小可行空间

我们经常被问到的是,是否需要在空间上投资数万美元,按照例如《开放实践库》文章中概述的内容,先把所有东西准备好,才能开始让团队在这些空间中工作?答案是否定的。尽管我们希望所有团队都能拥有完美的空间,但现实是,要实现这一目标有很多障碍和挑战。采用敏捷方法,逐步建设团队空间并应用持续改进,同样有效,而且可以让团队参与到这个建设过程中。

团队的最小可行空间是一些没有隔断的开放空间和一些墙面空间。为了绕过墙面空间的问题,我们的一些客户通过购买 25 块泡沫板(每块 4 英尺×8 英尺,厚度 5 毫米)来解决这些问题。这些板非常便于搬运,可以靠在家具、墙壁、窗户上,或者用磁铁悬挂在天花板上等。我们喜欢称它们为“便携墙”!

我们的客户对于引入巨大的泡沫板的想法有何反应?我们来看一个近期的例子。

“我们理解你们想做什么,明白原因,我们会帮助你们实现目标”仅用 4 周时间

我在客户的临时空间内领导了一次实验室驻地活动,客户是一家国际石油和天然气公司。可以理解的是,安全和健康问题至关重要,我们意识到定制家具和可移动板的审批不会很快通过。

所以,我们采取了只使用便携设备的方法。我们带来了上述提到的泡沫板、几卷静电魔术白板、几盒便签纸(这些便签纸仅能贴在我们的板子或魔术白板上),还有一台投影仪。

这意味着我们把空间从以下样子转变为:

图 5.14:传统办公室空间

如此:

图 5.15:现有空间迅速转变

在不触动任何设施、家具、不移动任何家具,或不在墙壁或窗户上固定任何物品的情况下。

它为我们提供了足够合适的团队工作环境,帮助一些快乐且充满活力的团队实现目标:

图 5.16:通过临时空间促进协作

在四周的合作结束后,我收到了现任 CIO 的反馈。他说,安全团队全程关注了这一过程,并且从最初的不行,你们什么都不能做的心态,转变为我们理解你们想做什么,明白原因,我们会帮助你们实现目标

你可能在看上面的图片和示例时会觉得,大家在同一时间待在同一个空间里是不可能的。我们常常继续探讨这样的说法,因为我们非常看重共驻办公和充满可视化以及信息辐射的工作空间的价值。当然,随着我们最近经历了 COVID-19 大流行,这种物理空间上的限制对大多数人来说确实变得不可能,而我们都已经转向使用虚拟空间。

虚拟空间

在本章和前一章中,我们探讨了个人实践,并给出了一些关于如何在分布式团队中运行这些实践的想法和指导。对于虚拟协作和分布式团队,通常没有需要考虑的物理空间。我们面临的挑战是,没有创造出足够的自主性和心理安全空间,无法促进良好的协作,这本身就是一个风险。

我们仍然需要考虑和投入的两个空间是:

  • 首先是每个人的空间。在前一章中,我们解释了如何辐射一切,即使在虚拟环境下,个体也需要良好的空间,多个显示器、可以作为构建显示器使用的平板电脑、他们自己的墙面空间等等。前期投入时间和资金,为每个团队成员配备合适的工具,将为整个团队的成功奠定基础。

  • 其次是数字空间。这通常意味着在强有力的工具上进行投资,现在有越来越多的供应商提供支持。我们的建议是,思考所有在物理空间中效果良好的实践和工作方式,并找到一款或一套工具,能够模拟这些实践。所以,如果你使用燃尽图的实践,找到一种方式在数字环境中实现(比如 JIRA5)。如果你有一个大家一起工作的产品待办清单,可以使用一种工具来管理(例如 Trello)。如果你们经常合作时,会移动便签纸并进行标注,找到一款支持这种操作的工具(例如 Miro6 或 Mural7)。不要只停留在一个工具上,也不要一开始就决定使用哪个工具。采用敏捷的方式,鼓励人们进行实验并赋能实际使用工具的人。

    5 www.atlassian.com/software/jira

    6 miro.com/

    7 www.mural.co/

突然间,空间变成了这样:

图 5.17:数字化墙面

你可以看到物理空间和数字空间之间的区别,如果你在这两种环境中都工作过,你会知道它们在感觉和能量上的不同。它们非常不同,我们已经了解到这两者各自的好处。虽然虚拟工作空间无法提供你在 360 度周围所能拥有的相同文化、能量和多维可视化,但它确实为你提供了无限的空间。你不会用尽墙面空间。不使用成千上万的便利贴也有环境上的好处。从世界另一端邀请主题专家来审查和评论你团队的协作活动变得更加容易且便宜。你的安全和审计人员也可能会因使用这些数字工具而感到更加放心。未来几年,虚拟和物理空间是否能够共存,是否有一方会成为常态,或者它们是否会融合成某种混合体,将会是非常有趣的议题。

结论

在本章中,我们进一步探讨了“开放”意味着什么,特别是重点介绍了开放领导力和开放空间的引入。《第四章 开放文化》中介绍的开放文化实践帮助团队变得自主并能自我管理。领导力在创建促进和支持这种工作方式的环境中起着重要作用。

我们强调了领导力在为团队建立一个稳固的基础时设定意图的重要性,这个基础将支持团队的产品发现和持续交付。这对于 DevOps 文化的成功至关重要。当我们进一步深入本书,探讨技术实践以及像 OpenShift 这样的平台如何交付持续的商业价值时,我们需要我们的团队拥有一个强大的文化基础,这个基础由开放领导文化支持,并推动自上而下的开放组织行为。

我们观察了一些强有力的开放领导力的例子——及时决策、协作优先级滑块,以及它们在帮助找到适合团队的工作空间(无论是物理空间还是虚拟空间)中的作用。

我们探索了设计团队空间时需要考虑的一些主要推荐因素,以及开始团队工作时,在开放、协作空间中与信息辐射互动的最基本需求。

图 5.18:在基础上增加更多开放文化实践

通过本章和上一章所采用的心态和实践,我们建立了一个强大的文化基础。在下一章中,我们将探讨基础的另一个方面——技术实践和技术环境,以及我们为在开始产品开发之前建立强大的技术基础所做的工作。

第六章:6. 开放技术实践——开始,正确起步

到目前为止,我们已经讨论了支持我们发现、选项和交付莫比乌斯环的基础文化和协作实践。接下来的两章将建立团队应实施的技术实践,以使基础更加牢固。

把莫比乌斯环想象成一个从发现到选项生成再到交付的引擎。这个循环通过更多的交付不断进行,直到我们需要重新审视我们所设定的结果。交付是我们将概念变为现实的过程。在交付的过程中,我们会学到很多,并从利益相关者和团队中获得反馈。某个时刻,我们将需要重新审视循环中的发现环节,调整我们所知道的内容或重新对齐我们接下来要交付的内容。

图 6.1:莫比乌斯环

假设我们已经完成了一次迭代并为我们的产品构建了一些新功能,比如我们为 PetBattle 应用程序增加了一个排行榜——很可能我们会觉得可以展示并发布这个功能,而不需要投入时间和精力来建立一个扎实的技术基础。但是随着迭代的继续,应用的复杂性增加,我们可能会发现自己开始淹没在手动测试或技术债务中。当我们进行到第五或第六次迭代时,以前有效的做法很可能会开始崩溃,我们预测能做多少事的能力也会开始瓦解。这可能会让团队失去动力,并让人们对我们的产品或执行能力失去信心。在这个阶段失去信任是很难恢复的。

要成功地逐步构建软件,我们需要确保能够顺利且可持续地运作。不断的重写和修复将阻碍这一过程。

出于这些原因,我们必须通过一套基础技术实践来支持我们的交付能力,就像我们在协作和文化方面所做的那样。配置即代码基础设施即代码,甚至一切皆代码等实践可以帮助确保团队的工作具有可重复性。确定团队如何进行测试并自动化测试,可以提高输出质量并简化缺陷管理。选择合适的开发工作流和工具将加快团队交付软件的能力,而不是把所有时间都花在管理工具上。

向基础中添加实践并不是一次性的活动。随着应用程序数量和复杂性的增加,重要的是通过新方法和更全面的实践来加强基础。

在接下来的两章中,我们将分享在基础层面实施的技术实践,这些实践帮助我们取得了最佳的成功。它们构成了基础的一部分,因为它们不是时间限制的实践;相反,它们是我们日常工作的一部分,不断进行。书中的后续部分,我们将探讨如何通过充分利用平台来加强这些实践,从而实现持续交付的可持续性和可扩展性。

在本章中,我们希望通过涵盖以下内容来正确开始:

  • Green from Go!

  • 配对编程与集体编程

  • 容器原生方法

  • 管道:CI-CD²

  • 一切皆代码

  • 开发者工作流

Green from Go!

Green from Go! 实际上意味着在开始新的工作时,以正确的方式为自己铺路。对我们来说,这意味着在编写任何应用程序软件之前,先确保所有基础软件组件都已启动并运行。这是我们另一个座右铭。就像展示,而非讲述一样,这个理念的核心是通过轻松的方式使事物达到可用状态并开始运作。

例如,选择我们想要用来自动化构建代码的工具,如 Jenkins 或 Tekton,或者选择如何管理机密或访问私密信息。Green from Go! 的目标应该很明确——清除障碍,赋能开发人员,让他们专注于做他们最擅长的事情——编写出令人惊叹的高质量软件,给最终用户带来愉悦的体验。

当我们开始任何新的软件交付任务时,我们总是确保完成工作的工具已经到位。我们会选择一些工具,帮助我们自动化代码的提取、编译,并将其交付到可以进行测试的环境中。这意味着所有工具都需要能够通过脚本或其他机制重新创建,这样我们可以轻松启动每次工作,并保持一致性,同时将每次运行的学习成果带入下一个任务。

Green from Go! 还将包括使用这些工具的任何参考应用程序或管道。这可能是一个简单的 AngularJS 应用程序框架,配有完整的构建过程,确保我们能够将代码交付给用户。这个过程的复杂性应足够低,以便团队根据特定需求进行演化。例如,一个团队可能希望在其管道中进行更多的非功能性测试,或者另一个团队可能想尝试一个新的酷炫测试框架。这里的优先级是,确保有足够的基础,以便在启动新工作时不会拖慢团队执行这些可重复任务的速度,而是让他们专注于编写新功能。

重要的是不要让工具包过于复杂——我们的经验是,当引入新团队使用加速器时,它可能带来的技术负担会导致一些团队成员不愿参与其中。为了让团队真正拥有并维护一个加速他们交付的工具,他们通常需要感觉到自己参与了构建过程。如果某样东西过于复杂,无法投入使用,它就变成了“布莱恩非常了解的东西,所以我不需要了解”。这种行为在团队中是危险的,因为它可能导致孤岛效应,破坏团队的集体所有权和责任。当构建系统出现故障或破裂时,那个了解这一切的人就会成为单点故障,恢复过程中的瓶颈。

在接下来的章节中,我们将探讨两种有助于建立集体所有权和理解的实践。

配对编程与集体编程

配对编程和集体编程帮助我们应对人们所称的独角兽开发者现象。这个现象在不同地区和公司有不同的名字,例如英雄开发者明星开发者。但当我们看到他们时,我们都能认出他们。

对于那些不太了解的人来说;独角兽开发者是那种掌握所有知识却将其保留给自己的人。他们是写出最华丽代码的人,而这些代码通常是最难理解的。他们的脑袋里充满了所有的钥匙和秘密,包括所有的想法和知识。他们往往是产出大量新工作的那个人,以至于没有时间进行文档记录,这意味着当他们不在时,其他人无法继续工作。此时,你可能已经能够识别出你的团队是否有一个独角兽开发者;甚至这可能是你自己!

图 6.2:独角兽

那么,为什么我们会有独角兽的问题呢?

独角兽开发者是瓶颈,是失败项目的象征。他们是系统中的单点故障。当独角兽休假时,项目就会停滞不前。当出现问题时,独角兽必须介入解决问题,这意味着在他们忙于解决问题时,新的工作无法完成。

组织希望围绕其产品创建高效能的团队——他们希望拥有一整支明星团队。实现这一目标的一个重要理念是“集体学习,配对构建”:

  • 配对编程是指两名工程师共同使用一台计算机来解决一个问题。

  • 集体编程 1 是指整个团队共同使用一台机器来解决一个问题。

成对编程和集体编程能够促进更多的知识传递,并且帮助大家共同理解问题和解决方案的过程。

集体学习,配对构建

为了理解配对编程的不同,我们可以用一个类比。假设你是一个木匠,你看到了一把精美的摇椅。你从看到这件完成的作品中学到了什么?可能不多;你也许会看到一两个部件如何连接,但不会了解所有的连接方式。现在想象你和木匠一起组装和制作这些部件。你会体验到隐藏的部分、使用的榫接技术、是如何制作的以及它们如何连接的。你会发现拼接部件时遇到的问题,并理解它们为什么按照特定的顺序组装。当你在建造的过程中能退一步审视这件家具时,你能更好地理解整个创作过程。同样的道理适用于软件的编写和工程设计。配对编程能培养更好的程序员。

我可以听到那些怀疑论者在心里想,嗯,这听起来像是两个开发者做一个人的工作。对我来说,这并不具有成本效益……嗯,配对编程和集体编程有很多有趣的优势:

  • 团队成员的指导:当新成员与其他人一起工作,解决与熟悉代码的人共同面对的问题时,可以很快跟上进度。分享技巧、窍门或快捷方式可以增加配对成员的技能深度。这种分享也能帮助初级开发者迅速提升。

  • 代码量减半:当你要求一个组织让两个开发者共同解决一个问题时,通常的思维方式是“那是不是意味着只有一半的代码能写出来?”实际上,理想情况下,写出的代码甚至会更少!两个大脑共同解决同一个问题能写出更高效的代码,因此避免了过度复杂的代码。

  • 没有拉取请求:配对编程意味着你必须与配对者分享你的思考过程。这种同步意味着代码在编写时就会被审查。通常,审查拉取请求的人太忙于编写自己的代码,他们只能给出非常表面的审查。当你进行配对编程时,你会在过程中进行审查,因此写出的代码更简洁、理解更深刻。配对编程时,你不会偷工减料,因为有人在看着你。

  • 团队凝聚力:人类是社会性动物,我们总是进行交流和互动。配对编程和集体编程促进了这种互动。与其一个人戴着耳机坐在房间里忽视周围的世界,配对编程的开发者看起来更开心。进行配对和集体编程的房间会更热闹。快乐的程序员能写出更好的代码。

  • 知识留在团队中:通过多个人共同解决同一个问题,理解和逻辑将会留在团队中。当配对成员自然地从任务到任务转换时,知识的深度将留在团队,而不是某个个体身上。这意味着,当假期或流感季节来临时,团队仍然可以保持工作进度,因为团队不会因为某个“独角兽”离开而失去关键信息。

1 由 Woody Zuill 倡导的一种相对较新的实践 – woodyzuill.com/

图 6.3:集体编程实践

在解决新问题时(无论是新的框架、新的语言,还是特别难的问题),我们都会聚集在一起。大家围坐在一台大屏幕电脑前,探索我们要解决的问题。我们围绕问题进行集体协作,直到跨职能团队确认他们已经掌握了足够的知识或拥有了解决问题的大致框架。然后,团队会拆分成小组,每组两人,从待办事项中提取任务并开始实施。

图 6.4:配对编程实践

集体编程和配对编程使团队能够跨领域学习。经验和专业知识的共享能促进更好的团队合作。像这样高效运作的团队可以持续并迅速地推动产品建设,专注于成果而非单纯的输出。

你可以通过访问 Open Practice Library 网站上的openpracticelibrary.com/practice/pair-programming/openpracticelibrary.com/practice/mob-programming/来了解更多并合作交流这些实践。

容器与容器原生

在我们能准确地定义什么是容器(提示:它们是 Linux 进程!)以及什么是容器原生之前,我们需要回顾一下历史,看看是什么推动了容器的出现。

容器的历史

如果你已经超过了某个年龄(超过 30 岁!),很可能你写的第一段程序涉及编译源代码,并将其与操作系统中的库静态链接。之后,计算机科学家发明了动态链接技术——这很好:你只需修补一个库,所有你写的程序在重新启动后都会自动采用该变更。当然,这也带来了新的问题——如何管理所有依赖。像 RPM 和 YUM 这样的打包技术被创建出来,以帮助解决在分发和管理 Linux 操作系统时的依赖问题。操作系统的发行版是一个协作共享和管理大量不同软件包的机制,最终围绕这些软件包形成的软件社区解决了实际问题。

当然,运行一个应用程序在一台物理机上没问题,但当你需要在多台机器上运行大量应用程序时,随着规模的扩大,这就成为了标准需求。虚拟化技术解决了如何在一台机器上以隔离方式运行多个操作系统的问题。实际上,云计算的主流形式就是在他人的硬件上运行虚拟机。

虚拟化基础设施解决了大规模运行应用程序的许多问题。然而,管理一大批虚拟机VMs)所需配置的所有组件,导致了与配置管理相关的工具和技术的爆炸性增长。还有“虚拟机泛滥”的问题——大量虚拟机到处都是,占用了太多资源,且很难整体修补和管理。每个应用程序都是独立管理的,可能由不同的团队负责。为了减少每个应用程序之间的相互依赖,每个应用程序也被部署在自己的虚拟机中。为了控制虚拟机的扩展,这些虚拟机由基础设施和运维团队集中管理。团队之间的隔阂也因此形成!许多工具被创建出来帮助配置虚拟机。每个虚拟机都有运行系统进程和守护进程的开销,因此,许多精力被投入到构建工具上,帮助避免过度分配机器资源,从而节省成本。

对于开发人员来说,部署在组织内部的虚拟机(VM)用户界面并不特别具备自助服务功能。请求提供虚拟机的过程需要时间。工作流、票务和供应系统已自动化,以帮助加速此服务请求过程。然而,通过公共云服务,API 驱动的基础设施使得虚拟机的提供只需要几分钟,而且开发人员可以真正实现自助服务。不过,控制和虚拟机泛滥的问题仍然存在。

开发人员使用的应用堆栈仍然依赖于包含在虚拟机中的操作系统和库,这些虚拟机是与内核一起提供的(例如 libc、libssl)。并且开发人员通常不允许更改虚拟机配置,可能是出于安全性或稳定性考虑。这通常是基础设施或运维团队的责任。通常,虚拟机更新、修补和管理起来并不容易。基础设施或运维团队不清楚更新和重启一台机器会对他们支持的应用程序产生什么影响。

容器工作原理

人们常说,容器的发展历程是进程隔离的历程。容器的概念可能始于 1979 年的 chroot,随后在 2000 年代初期发展成了 BSD Linux 监狱,2004 年 Solaris 容器也采用了这一技术。2 Solaris 区域是一种技术形式,结合了系统资源控制和边界隔离。从外部来看,它们像虚拟机,但实际上并不是虚拟机。

2 www.section.io/engineering-education/history-of-container-technology/

运行大量工作负载的科技公司总是寻找节省资源和最终提高效率的方法。回到 2006 年,核心 Linux 内核中进行了多项与 Linux 进程隔离相关的技术增强。谷歌最初引入了一项名为“进程容器”的技术,后来更名为 cgroups。它旨在限制、计量和隔离一组进程的资源使用(CPU、内存、磁盘 I/O 和网络)。

2013 年,Docker 提出了一个解决容器依赖问题的新方案。将应用程序及其依赖项打包成容器镜像,导致容器的普及爆炸式增长。容器镜像被免费提供并通过容器注册中心在线分发,如dockerhub.ioquay.io

运行中的容器实际上只是具有额外保护和数据结构支持的 Linux 进程,这些数据结构支持内核中的进程。在单台机器上运行容器很容易;在计算农场中跨越成千上万个容器则是一个更加棘手的问题。于是,容器编排引擎登场,其中 Kubernetes 项目无疑是目前使用最广泛的。OpenShift 容器平台是一个结合了 Linux、Kubernetes 和容器技术的产品,允许企业在规模化环境中安全、可靠地运行容器。

当然,要获得真正的商业价值,仅仅将应用程序打包为容器并部署一个像 OpenShift 这样的 Kubernetes 平台是不够的。仅仅因为你构建了它,并不意味着所有用户会立即涌向该平台!使用受信任供应链的现代应用交付迫使你的团队采用新的工具和工作方式。新的行为方式是必须的。

使用容器后,开发者的用户体验发生了彻底变化。开发者现在可以自助服务他们的应用程序,而无需经过虚拟机的配置。当然,仍然有人需要配置平台!容器的配置和启动只需几秒钟到几分钟,而今天,借助专注于无服务器技术栈的支持,甚至可以在毫秒级别完成。

开发者可以轻松控制应用程序的打包、运行和升级,使用容器镜像即可实现。应用程序不再依赖于内核中打包的库版本。你可以将一个应用程序的所有代码和依赖项提取到一个容器镜像中。你可以同时运行同一个应用程序的多个版本,而不必依赖于内核中相同版本的库。

容器镜像的不变性还提高了应用程序的整体服务质量。团队可以确保在不同的环境中运行完全相同的容器镜像,比如开发环境和生产环境。为了能够在不同环境中运行这种不变的容器镜像,开发人员开始学习通过外部化应用程序配置,他们可以轻松地在任何地方运行相同的容器。应用程序配置管理现在作为容器部署过程和平台的一部分构建在其中。这使得开发人员控制的部分(他们的应用程序和配置)与ITOps 控制的部分(平台本身)之间的边界更加明确。

图 6.5:容器化提供清晰的边界

在多租户环境中,不同的用户组可以通过项目进行隔离,从而提高底层基础设施的利用率。在 OpenShift 中,内置了控制网络流入和流出、基于角色的访问控制和安全性等机制,以及开箱即用的指标、监控和警报功能。该平台支持将持久数据存储挂载到容器中。平台支持这些有状态的应用程序,以便当容器停止/重启或迁移到另一个计算节点时,持久卷也会随之移动。

在容器生态系统中,团队角色的划分与虚拟化基础设施不同。InfraOps 团队可以管理 OpenShift 平台及其支持的基础设施,而开发团队则可以自助服务来部署和运行平台上的应用服务。这是一种“设置好后就不干涉”的心态。当然,在达到这一目标之前,仍然存在需要讨论和达成一致的复杂问题。何时运行集群级别的服务和操作员,如何在管理业务应用服务级别变化的同时执行滚动平台升级,安全性、存储、高可用性以及负载均衡/网络问题通常需要每个人的参与。正是这些团队的汇聚和他们之间的 DevOps 对话,构成了当今现代 DevOps 实践的骨架。

你可以通过访问 openpracticelibrary.com/practice/containers 页面来了解更多并与他人合作容器实践。

流水线 — CI、CD 还是 CD²?

“流水线的工作是证明你的代码不可发布。” – Jez Humble

好的——让我们设置场景,并先解决一些基本问题。我们如何将代码从笔记本上的单行文本转化为在生产环境中运行的容器中的应用程序?嗯,有很多方式,名字也各不相同!团队将软件的旅程称为流水线,但实现方式有很多种。

让我们暂停片刻,借助我们的朋友 Derek,DevOps 恐龙,来思考一下什么才是软件管道!

DevOps 恐龙 Derek

在加入 Red Hat Open Innovation Labs 团队之前,我是一名在大型系统集成商工作的开发人员。那时,有人问我解释什么是管道——指的是我创建的某个构建自动化工具。问我的那位是位执行合伙人,他的技术知识非常有限。他想了解什么是管道,用简单的语言解释,让他能理解并在与客户沟通时使用。他的问题很基础,比如管道长什么样,应该做些什么?

在考虑如何以简化、易于理解的方式描述管道时,我一直在想,是否能用三岁孩子能理解的方式来解释——我应该可以解释给他听。因此,DevOps 恐龙 Derek 就这样诞生了。

先暂时忘记软件吧…

想象一下,暂时我们不是在构建软件。我们不是在配置 Jenkins、处理 Shell、Ansible 或任何其他自动化工具。让我们想象一下,我们在构建恐龙!大而可怕、坚韧、怪异、凶猛的恐龙,拥有很多锋利的牙齿!闭上眼睛,想象一下那个可怕的恐龙。也许你正在想象某种混合型侏罗纪公园恐龙。想想你想要构建的恐龙的部件——它有多少颗牙齿?多少只手臂和腿?当我想到我的可怕恐龙时,我会想到 Derek。️

图 6.6:介绍 Derek

那么,我们怎么知道 Derek 够大、够可怕呢?让我们从他的部件开始。首先,我们可能需要分析恐龙的每一个部件,快速检查一下,确保它们符合我们设定的标准。例如,我的恐龙有两只手臂和两只腿吗?它有足够的牙齿吗?如果一切看起来没问题,我们就可以把这些部件放进 Dino-Constructor 5000™ 里。

在完成 Dino-Constructor 5000™ 后,我们应该能够制造出我们的恐龙,Derek。

图 6.7:介绍 Dino-Constructor 5000™

我怎么知道我的恐龙够凶猛?

所以,我们有了一只恐龙。但记住,我们的目标是打造强壮、可怕的恐龙,既坚韧又健康。我们怎么知道 Derek 够强壮呢?嗯,我们可以让他通过一系列的障碍。让我们为 Derek 建造一个障碍课程吧。

图 6.8:恐龙障碍课程

我们将让他从一个攀岩墙开始,类似于你在军队电影中看到的新兵们所用的那种。如果他足够幸运地跨过了这个障碍,他将进入下一个障碍,在那里他必须像印第安纳·琼斯一样跳过一些尖刺!接下来,我们检查一下我们的恐龙有多健康;如果它能够在跑步机上快速奔跑,他就可以进入下一步。在这一阶段,它必须尽力游过去,躲避那些试图咬它的鱼。一旦通过这一关,也许它还得跳过一圈火焰。如果德里克足够能干,并顺利通过这些障碍,他就可以跑向他的围栏——然而,如果德里克不小心,他可能会被一直悬在他上方、威胁重重的摆动刀片挡住,就像马里奥关卡中的场景一样。任何时候,刀片都可能掉下来,直接把德里克挡住。假设德里克小心翼翼,最终进入了围栏,那里有其他的恐龙。

图 6.9:恐龙围栏

德里克现在可以和其他恐龙一起在恐龙宠物园里度过余生,和丹尼、黛比一起生活。然而,不幸的是,黛比这只恐龙对德里克相当刻薄。她总是抢走德里克喜欢吃的宝贵草和水(在这个比喻中,德里克是素食者!)。因此,为了给德里克提供必要的隔离和足够的资源,使他保持强壮和健康,饲养员过来,把他移到了一个新围栏里。

事实证明,德里克是动物园里相当受欢迎的恐龙,所以饲养员决定为他克隆出几个,并把它们都放进和德里克同一个围栏里。他在这里过得很开心,拥有生存所需的一切。

图 6.10:饲养员把德里克移到一个新围栏

但等等——我们在构建软件,而不是恐龙!

对不起打破这个幻想,但实际上,我们(遗憾的是)并不从事恐龙制造业。我们在这里是为了构建软件应用程序。我们刚刚对我们的恐龙所做的,就是我们在每次提交代码时对代码库所做的事情。我们构建代码,将其通过一系列的障碍,然后部署给我们的用户使用。这就是一个流水线,其实非常简单!

让我们更详细地看一下我们的恐龙流水线。在第一步,我们评估组成恐龙的各个部件——它的手臂、腿、牙齿等。我们会问一些问题,比如部件是否足够?每只手是否有三根手指?我常把这一步看作是流水线中的静态代码分析部分。在 JavaScript 世界里,这可能就像是对代码库进行简单的 lint 检查,甚至可能运行一些更复杂的工具,比如 SonarQube 来检查代码质量。Dino-Constructor 5000™代表了任何编程语言中的编译步骤。

我们为 Derek 构建的障碍赛道代表了我们应当采取的步骤,以进一步评估我们的代码质量。Derek 必须跨越的初始障碍可能代表一些单元测试。重要的是,这些障碍应该具有足够的挑战性,同时又不能太简单,否则就没有价值。例如,如果 Derek 能轻松越过攀岩墙,那么这可能并没有全面测试他的各个方面。假设我们决定给 Derek 添加一只额外的手臂。现在我们有了一个可怕的三臂恐龙!如果我们让他再爬一次墙,他会发现比以前简单得多。在这个方面,增加难度是很重要的,可能需要加大缝隙的宽度或让墙更陡峭,这样才能提出更多的挑战。回想代码,逻辑也是一样的。当我们向应用程序中引入新功能时,我们需要提升测试覆盖率,以包括这些新功能。编写测试并非一次性工作,它必须随着应用程序的开发不断演变。

其他障碍代表了额外的测试类型。Derek 必须游过的小食人鱼池可能代表一些早期的集成测试。他必须跑的跑步机可能是一种性能测试。Derek 必须通过的最后一个障碍是悬挂在他头上的巨大刀片。这个障碍一直悬而不决,在我看来,往往是最容易被忽视的测试类型。Derek 可能会以为自己已经自由,朝着围栏跑去,结果刀片突然落下,意味着他无法继续前进——这就是安全测试的一个例子。通常在最后一刻才被想到,但在许多情况下,它可能成为最终部署的“拦路虎”。

图 6.11:移动到新围栏时未能通过测试

一旦 Derek 到达恐龙围栏,他必须与其他恐龙共享空间。也许此时,代码已经部署到公共云或虚拟机中,并且资源在竞争中共享。希望到此时,运维团队已经注意到应用程序内存不足或计算资源不足。为了解决这个问题,团队可能会自动化应用程序的容器化。一旦代码进入容器,它就变得可以发布。我们可以在云服务提供商之间甚至仅在环境之间移动容器。在这一阶段,代码已经与其运行所需的所有依赖项一起打包。这种无需重新构建和测试即可移动代码的能力,通过构建不可变容器镜像可以安全地实现。将应用程序配置与已构建的软件版本分开,使我们能够轻松水平扩展软件,基于用户需求运行更多的实例。

关于构建恐龙的最终思考

所有这些测试类型都可以并且应该作为软件管道的一部分进行自动化。在每个应执行构建、测试和部署的自动化过程中,代码应该检查每个后续步骤是否成功。通过这个过程,团队可以更快地交付新功能。团队可以在不担心回归问题的情况下引入新代码。像 Red Hat OpenShift 和 Kubernetes 这样的容器平台可以确保应用始终处于期望的状态。这些平台还可以用来运行我们的软件管道,使用构建工具如 Jenkins 来运行各个阶段。通过动态提供测试工具,如 Zalenium 执行浏览器测试,以及使用 Jenkins 构建,使得创建管道变得可重复和可重用。

通过自动化管道中的所有步骤,我们最终能够更快地将开发和运维团队的出色成果交到用户手中。

多亏了 Derek,我们现在知道管道是我们用来构建、打包、测试和部署软件的一系列步骤。现在,让我们来看一些描述软件交付管道时人们使用的术语。

持续集成

持续集成CI)是一种软件开发实践,最初由极限编程的作者推广。虽然关于它已经有无数的书籍问世,但最简短的定义有时恰恰是最简单的!CI 的三词定义就是“持续集成代码”。也就是说,开发人员和团队应该定期将他们的代码提交并推送到仓库,并有一些自动化过程来编译、打包和测试这些代码。这个过程应该频繁发生——为了达到最大效果,每天进行多次。

图 6.12:持续集成

更多团队在这个 CI 难关上失败的情况比你想象的要多。通常,团队认为他们正在实践 CI,但实际上并没有。

持续集成

几年前,我在一个安全公司做了一个 Labs 驻场。这个团队相当年轻,几个团队成员刚刚毕业。团队决定在编写代码时创建功能分支,以免破坏其他人的工作流程。不幸的是,这导致我们创建了这些在冲刺期间存在的分支。我们有很多自动化流程,当代码合并时会被触发,但我们合并的频率不够。

在两个冲刺周期中,我们每次都在冲刺结束前,紧急地将所有功能合并,以便进行每周演示——可以说是非常混乱!这导致了每两周一次的“迷你集成”。我们已经设立了大量的自动化来验证我们的代码,但我们并没有足够频繁地使用它。可以想象,这个过程根本不算是持续的——我们并没有进行持续集成!

为了解决这个问题,我们在回顾会上进行了讨论。如果你使用的工具(在我们的案例中是 Jenkins)能够提供构建频率或使用统计数据,那么这些数据可以是很有价值的信息,可以打印出来或带到回顾会议上。我曾与一位非常出色的 Scrum Master 合作,他总是这么做,这帮助团队在回顾会上专注于我们可以采取的实际行动,从而加快进度。在我们这个周期里,我们实行了每周一次的迭代。这意味着实际的开发时间只有四天!通过回顾会,我们根据提供给团队的数据识别出了一些可行的改进措施:

  1. 持续集成 – 这是我们的一次重大变化,尽可能频繁地将特性合并,并获得我们所需的验证,以避免在演示过程中遇到的合并地狱。

  2. 更小的特性 – 团队意识到工作被拆解成了过大的任务块,每个任务块需要花费大部分迭代周期才能完成。将每个特性的任务拆分得更小意味着我们可以更快地验证每个小任务是否可行。

你可以通过访问openpracticelibrary.com/practice/continuous-integration页面来了解更多并参与持续集成的实践。

持续交付

持续交付CD)是一种开发流程,在每次代码更改时,团队会构建、测试并打包代码,使其能够一直部署到生产环境。它以自动化的方式将代码交付到生产环境的门口,但并未真正部署到生产环境。许多团队能够达到这种状态,这本身是一个很好的目标,但通常由于组织发布节奏或需要额外批准,导致它们无法完全发布到生产环境。这里需要注意的是,如果有需要,它们是可以发布到生产环境的。

图 6.13:持续交付

建立对软件交付管道质量的信心

在我职业生涯的早期,当自动化测试和持续交付的概念对我来说还很陌生,而在一些行业里仍然处于前沿时,我曾为一家英国的大型零售商工作。他们采取了非常传统的软件部署方法,每季度最多发布一次版本。

部署对他们来说是一件令人害怕的事情——这通常需要一组专家团队在周日凌晨的黑暗时刻进场,开始他们的手动任务。他们会先停掉网站,放上一个临时页面,然后开始执行他们被要求运行的脚本。大多数情况下,这个过程是成功的,但有时在事情出错时,他们可能会遇到几天的停机时间!

这个项目的目标是为零售商建立一个移动渠道,以便他们与客户互动。我的角色是编写移动应用与电商平台之间的一些集成服务,并编写一套自动化集成测试。我们合作的零售商非常传统,因此他们的项目计划中包含了一个为期三周的时间块,在此期间将进行所有测试。零售商认为我们浪费时间编写自动化测试,并将分数展示在墙上供大家查看——他们确信三周的时间足够了!

我们的团队不愿等到最后才发现所有问题;他们希望在整个过程中得到反馈。我们在 Jenkins 中创建了一系列自动化任务,用于构建应用程序和 API,并将其部署到用户验收测试环境。这意味着,在测试团队参与之前的几个月,我们已经开始交付应用程序的修订版供他们测试。我们的自动化测试模拟了来自移动应用的用户行为,测试了正常路径以及所有已知的错误路径或悲观路径,通过不同输入参数调用 API。我们还获取了用户验收测试团队的回归测试脚本,这些脚本将被手动执行,并将其编码为一组测试,以执行相同的 API 调用。这让业务方感到兴奋,因为他们开始看到应用程序的不断演变。功能被逐步添加,问题也在内部展示时被修复。这对他们来说是一次全新的体验,因为他们习惯于只在最后才看到完整的结果。

项目结束时,业务方已经开始意识到我们编写的测试的价值。每次更改时,我们都自动化了移动应用的构建、部署到应用商店,并运行了一大套集成测试。他们继续在最后进行手动测试阶段,确实发现了一些错误(然后我们为其编写了自动化测试并修复了)。然而,当他们将此阶段发现的问题数量与其他类似项目进行对比时,发现问题明显更少。

在上线当天,团队已经准备好将应用程序推送到应用商店并完成 API 的最终部署。零售商将营销活动和其他事件与上线日期对接,所以压力非常大!团队一直在做小范围的应用修复,直到这一时刻。每次更改都需要业务部门批准发布,这意味着必须涉及到手动测试团队。由于发布窗口的压力,业务部门决定仅对应用程序进行快速的冒烟测试,以确认特定发布候选版本上的问题是否已解决。冒烟测试通过了,因此他们准备好进行发布——然而,我们的自动化测试在一个服务中发现了两个失败,该服务负责应用程序中的产品评论。这是由于在架构中更下层的记录系统中对数据格式进行了轻微更改,导致一些数据转换功能无法正常工作。手动测试团队没有捕捉到这一问题,因为他们并未测试此功能。我们标记出我们的测试发现了回归问题,发布因此暂停,直到解决该问题。

这看起来可能是一个微不足道的例子,但对零售商来说,这标志着一个重大转折点。他们亲眼见证了我们自动化测试套件的速度、可靠性和有效性,以及我们构建、验证并交付生产就绪应用程序的速度。编写和运行自动化测试的过程在更广泛的组织内建立了巨大的信任,促使他们彻底改变做法,转向更多的自动化和更多的测试自动化。

你可以通过访问openpracticelibrary.com/practice/continuous-delivery了解更多关于持续交付(CD)实践的内容并进行协作。

持续部署(CD²)

持续部署CD²)是在持续交付(CD)过程中更进一步的做法,它将应用程序交付到生产环境,并最终交到最终用户手中。我将持续部署(CD)比作一列大火车——这列火车按照非常可靠的时间表运行。它将所有更改打包起来,将我们的代码库中的所有内容进行编译、打包、测试,并通过各个环境进行推广,确保在每个阶段都经过验证。

图 6.14: CI、CD 和 CD²

通过持续交付到生产环境,你可以加速将功能和修复交付给最终用户,相比于等待大型发布的“爆炸性发布”方式。更快速的交付促成了业务敏捷性——即响应客户和市场需求变化的能力,并更早从功能中获取反馈。开发者不再需要等待数周或数月,才能让最终用户尝试他们编写的代码。快速的反馈循环至关重要,应该投入时间和金钱考虑最好的工具,以支持这种快速交付。

完成工作后,就发布吧!

想到快速交付的能力,每个变更都能部署到生产环境中,我们必须设置好技术架构,让变更能够自由流动并充满信心。这需要团队周围的人,特别是领导层和产品负责人的强力支持,因为他们往往会阻止这种努力,认为过多的变更会对质量或最终用户体验造成伤害。这些观念通常来自于过去在交付失败时的糟糕经历。所以,这是一条双向街道——信任建立在团队能够出色执行的基础上。

我们合作过的最优秀的产品负责人之一是在一家欧洲汽车制造商那里。他们正在替换一款供经销商和维修工使用的知识库应用,用来诊断问题并订购零件。历史上,这款应用的更改工作外包给供应商,每个供应商都在自己的基础上进行修改。他们会聘请系统集成商添加新功能,但这样做往往会引入新的 BUG 或问题。这种外包开发意味着架构设计决策是由客户的产品团队之外的人做出的,最终导致技术债务,并且从长远来看是不可持续的。团队决定重新开始,重新构建应用,并将开发工作带回公司内部。我们受邀帮助这个团队以正确的方式启动,采用驻场方式,并帮助他们建立一个与最终用户紧密联系的产品团队。

在参与项目的多个冲刺后,尽管开发工作还处于初期阶段,团队正在为用户创建身份验证流程。我和一名工程师进行配对编程,我们编写了注销功能。我们编写了测试,并在我们的测试环境中向产品负责人展示了这个功能。团队一致同意的完成定义是:我们必须向产品团队的某个成员展示这个功能,以便他们接受。因此,就工程工作的进展来说,我们已经完成了。产品负责人进行了快速测试,测试环境中一切正常,于是,在冲刺结束时,我们将所有更改推送到生产环境,我们的功能也发布了。

当用户体验团队在对应用的最新增量版本进行可用性测试时,他们注意到注销功能在某个页面上出现了故障。这一问题被报告给了我和最初处理此事的工程师,我们立即发现了问题所在。这个问题很小,所以我们编写了另一个测试并进行了修改。

我们向产品负责人展示了这个过程——先编写一个失败的测试,再编写能让测试通过的代码,进行迭代,然后将修复后的注销代码交付到生产环境。能够在最终用户准备好接收时,将小的增量改进交付给他们,为持续交付铺平了道路。

这里的教训是,Scrum 可能会妨碍团队不断向生产环境交付小规模增量更改的能力,因为 Scrum 是在冲刺结束时交付的。“当工作完成时,直接将其发布到生产环境。”

我们已经了解了软件管道的作用,它将构建、打包、测试和部署我们的应用程序代码到不同环境(但不一定包括生产环境)所需的步骤进行了编码——即 CD 的实践。然后我们又看到了一个将小规模增量更改持续部署到生产环境的做法。

你可以通过访问openpracticelibrary.com/practice/continuous-deployment页面,了解更多关于 CD²的信息。

一切皆代码

你可能之前听说过这个: [插入软件术语]-即代码。

示例包括基础设施即代码、配置即代码、测试即代码,现在还有一切皆代码。这种做法已经存在很长时间了,但一些组织还没有迅速采纳。

问题来了——历史上,组织必须聘请昂贵的专家来部署复杂的环境。他们会花几个小时逐行阅读操作手册,最终才能让部署成功。几个星期过去了,组织想要创建另一个环境,完全像这个一样进行进一步的测试。现在该怎么办?打电话给专家,请他们回来,但这代价不小!如果你喜欢经常雇佣昂贵的专家,那倒没问题。

那么,解决方案是什么?一切皆代码的做法很简单:你将系统的每个部分都像对待其他代码行一样对待。你将其写下并存储在版本控制系统中,比如 Git。我们真的是要自动化系统的每个部分吗?是的。

我们从自动化基础设施层开始,这是最低层次的,从裸金属服务器到操作系统、网络、应用程序配置,直到应用程序部署。

这项自动化工作听起来像是非常繁重的工作,而且在人的时间成本上可能非常昂贵——为什么要投入其中呢?原因如下:

  • 可追溯性:将环境描述和结构存储在版本控制系统中,使我们能够审核对系统所做的更改,并追踪到具体的执行人。

  • 可重复性:从一个云提供商迁移到另一个云提供商应该是一个简单的任务。选择部署目标应该像是每周为最优惠的价格进行购物一样。通过将所有内容存储为代码,系统可以在不同的提供商中迅速重新创建。

  • GitOps:单一的事实来源意味着不再需要部落知识或专家来设置电缆或连接硬盘。

  • 凤凰服务器:不再担心配置漂移。如果服务器需要打补丁或突然宕机,也没关系。只需使用存储的配置从头开始重新创建它。

  • 跨职能团队:将一切写成代码有助于改善组织内各个部门之间的协作。开发团队能够为环境创建做出贡献,或者在沙盒中重建相同的环境。

  • 降低风险:可以将更改应用到环境或应用程序部署中,并迅速恢复到先前的状态,从而降低任何类型的大规模升级风险。

有许多方法可以实现万物即代码:

  • 网络与基础设施:Ansible 可以用来声明式地定义你正在实施的系统,Istio 则能帮助管理应用程序和服务之间的网络流量。

  • 应用环境:容器化提供了一种经过验证的、可重复的方法,将应用程序及其依赖项打包,既满足开发人员的需求,也符合运维人员的喜好。

  • 开发者工作流/构建自动化:使用 Jenkins 的 Pipeline as Code 或 Tekton 来描述你的应用程序是如何从源代码编译、测试并转化为可运行的程序的。

  • 配置漂移:ArgoCD 是一个实现 GitOps 模式的工具,适用于你的应用程序及其支持工具。

  • 作为代码的测试:以行为驱动开发形式编写的 Selenium 测试作为验收标准,可以让业务分析师和开发人员更紧密地合作。

  • 安全与合规:Open Policy Agent 和 Advanced Cluster Manager 是在整个堆栈上强制执行策略的工具。

视整个系统为代码的团队更强大、更高效,也更优秀。我们不应该再仅仅考虑基础设施即代码,而是要自动化整个系统——从应用程序属性到网络和安全策略的所有内容。然后,我们将其编码化!

你能为我再建一个那样的服务器吗?

花时间自动化创建测试环境?“听起来成本高,浪费时间”——我能听到一些人心里在这么想。

在为英国的一位客户工作时,我在构建移动应用程序以及一堆 JavaScript 服务,用以提供优化过的、便于移动设备消费的数据。适配器的服务层部署在 IBM 的 MobileFirst(当时是 Worklight)上,这是一个庞大的 Java 应用,需要专家来配置和安装。我们有多个环境,从开发环境到系统集成测试环境,再到用户验收测试环境和生产环境。你能想象的所有常见环境,都在这个非常传统的生态系统中。

专家花了两周时间来配置和安装用户验收测试服务器。其中两个服务器被设置好,允许我们在任何给定时间同时进行多个测试。你想要第三个服务器吗?嗯,那就意味着要请回那位昂贵的专家来建立第三台服务器,并且再花上一周的时间。生产环境中我们有八台服务器,每一台都需要手动配置和部署!

当我回顾这次合作,思考我们面临的压力,要求在配置和部署服务器时所花费的时间,每个服务器所花费的时间,看起来真是疯狂。顾问会出现,花整天在终端上胡乱修改,并手动测试结果。当时的配置文件都没有存储在 Git 中,甚至没有转化为可以执行的脚本来加快下一个服务器的创建。所有信息都是口口相传的,保存在她的脑海中。如果我们想要第三台服务器?我们得雇她回来再做一遍!

几年后,在为一个公共部门客户服务时,我看到了类似的行为。我原以为这种创建服务器的方式只是一个局部现象,但在政府合同中,有些团队为开发人员创建了服务器,而这些团队没有使用任何脚本或自动化工具。如果你想要一台服务器,你需要提交工单并等待一周。如果你想要与那台服务器完全相同的服务器,你需要再提交一张工单,有时得到的复制品是完全相同的。在这种情况下,团队在每台虚拟机内手动执行 Shell 命令,并且常常忘记执行一两个命令!

这些例子现在可能感觉有些陈旧了——但现实是,我仍然看到一些组织采用传统的基础设施、自动化和可重复性的方法。无法在代表性硬件上测试变更,对那些想要快速发展的团队来说是一个挑战。团队需要能够按需启动和关闭应用堆栈。我们打包应用程序的现代方法,如容器,确实有助于打破这一壁垒。开发人员不再需要通过数据库调用来模拟测试案例,因为他们可以在容器中启动一个真实的数据库并进行测试。

你可以通过访问openpracticelibrary.com/practice/everything-as-code页面,了解更多并与大家合作,关于“万物皆代码”实践。

那么,PetBattle 团队在实践“万物皆代码”时采取了什么样的方法?

为 PetBattle 建立技术基础

本节将介绍 PetBattle 的起步阶段,开发团队在尝试使用我们将在后续章节中讨论的工具来建立技术基础。像这样放在框中的部分将更侧重于技术方面。

PetBattle 最初是一些工程师的业余爱好——如果你愿意的话,它是一个宠物项目。这个项目为团队提供了一个真实的应用场景,让他们可以尝试新的框架和技术。为了将一些现代软件实践应用到 PetBattle 中,他们增强了应用的构建和测试自动化。随着 PetBattle 需求的增加,我们将考虑自动扩展以及如何借鉴开放实践库的做法,来确定我们应该如何构建这些东西。

对于 PetBattle,我们拥抱现代软件开发范式——我们监控并响应配置漂移,以便团队可以实施 GitOps 来监控这种漂移。我们的环境应该像凤凰一样,能够从灰烬中重生!换句话说,我们可以自信地摧毁它们,因为我们可以通过代码重新创建它们。

让我们来看看 PetBattle 想要部署的第一款软件——Jenkins。本节将探讨如何使用 Jenkins 在 OpenShift 上部署和管理 Jenkins。

PetBattle 团队正在使用 OpenShift 来部署他们的应用程序。他们选择使用 Jenkins 来自动化一些构建和部署软件的任务。Jenkins 是一个开源自动化服务器,可以运行许多任务,并且支持在 OpenShift 上运行。Jenkins 还有一个强大的社区支持,并且拥有一个庞大的插件生态系统,使得自动化几乎所有你能想到的任务都变得轻而易举!

现在我们已经建立了 PetBattle 的技术基础,接下来让我们更深入地探讨 Jenkins 及其在强化基础中的作用。

Jenkins——我们最好的朋友!

我们喜欢把 Jenkins 当作我们的朋友。我们还记得曾经的日子,当时团队里会有人在本地机器上构建应用程序,然后通过电子邮件发送给运维团队。为了进行部署,通常会有一个专门的团队,在晚上进入并完成部署,以减少对日常工作的干扰。

部署曾被视为一项风险大、让人害怕的事情。有一次,我们合作的一个团队在一次大型部署前夕出去庆祝。当他们在清晨迷迷糊糊地来到公司时,他们的思维状态并不是最清晰的。正如你能想象的那样,在进行升级时,他们跳过了一步,导致了故障。我们之所以认为 Jenkins 是我们的朋友,是因为它不会做这种事。它不会在前一天晚上出去玩,第二天疲惫不堪地到达公司(当然,除非你忘记给它提供足够的内存和 CPU)。Jenkins 也不会忘记在脚本中执行某一行代码;它在这一点上做得很好。但它在其他方面也相当笨拙;Jenkins 只有你给它的指令有多聪明。Jenkins 的原始版本相对基础,因此我们为它赋予了额外的超级能力,能够使用代理来运行特定技术的构建,并通过插件以机器可读的方式报告测试结果。但一旦它完成了某个任务,它会一次又一次地重复执行,几乎不会失败——尤其是当你将它配置为代码时。

Helm 概述

接下来的部分将更详细地介绍技术方面的内容。准备好一些代码片段和相关内容吧!如果这不是你的兴趣,随时跳过这部分,直接进入下一节,关于 Git 和开发者工作流的内容。我们会用这个方便的标志标记任何涉及代码片段和较低层次内容的部分!

Jenkins 已经与 OpenShift 一起提供,团队有多种方法来安装和配置它。跨职能团队的任何成员都可以去 OpenShift 控制台,从目录中安装它。只需在 UI 中点击几个按钮并选择是否添加持久硬盘即可。这是一个帮助团队快速启动的好方法,但也不符合我们一切皆代码的技术基础实践!

PetBattle 现在有两种选择,可以在遵循一切皆代码的实践下创建 Jenkins 实例。它们可以使用包含所有 Kubernetes 和 OpenShift 对象的 OpenShift 或 Helm 模板,这些对象是部署一个工作 Jenkins 所必需的。在本书中,我们将专注于使用 Helm 作为我们的 Kubernetes 包管理器。

Helm 是一个 Kubernetes 应用程序包管理器,使开发人员和运维人员可以轻松地将构成应用程序的资源和配置打包成发布版本。Helm 用于应用程序生命周期管理,包括安装、升级和回滚应用程序部署,从而简化了在 OpenShift 集群上安装应用程序的过程。在 Helm 中,应用程序被打包并作为 Helm 图表分发。一个 Helm 图表由多个 YAML 文件和模板组成。这些 Helm 模板在处理后应输出 Kubernetes YAML。让我们来看一个 Helm 图表的示例。

根据我们与客户在 OpenShift 上使用 Jenkins 的经验,我们编写了一个图表来部署 Red Hat 版本的 Jenkins,并赋予它一些超级功能。稍后我们将介绍这些功能。首先,让我们探索图表的结构:

jenkins
├── Chart.yaml
├── README.md
├── templates
│   ├── PersistentVolumeClaim.yaml
│   ├── buildconfigs.yaml
│   ├── deploymentconfig.yaml
│   ├── imagestreams.yaml
│   ├── rolebinding.yaml
│   ├── route.yaml
│   ├── secret.yaml
│   ├── serviceaccount.yaml
│   └── services.yaml
└── values.yaml

Jenkins 图表与所有 Helm 图表一样,由多个 YAML 文件组成:

  • Chart.yaml:这是我们的 Jenkins 图表的清单。它包含元数据,如名称、描述和维护者信息。清单还包括应用程序版本和图表版本。如果图表依赖于其他图表或图表,它们也会在这里列出。

  • README.md:图表的说明,包括如何安装它以及如何自定义它。

  • templates/*:此文件夹包含所有需要部署的资源,以安装和配置一个正在运行的 Jenkins 实例,如部署、服务、路由和 PVC。

  • values.yaml:这些是图表可以使用的合理(默认)值,用户可以直接安装图表并快速启动。可以通过命令行提供自定义值,或在安装图表时提供自己的 values.yaml 文件。

红帽实践社区CoP)是一个基于与客户合作的经验和教训创建可重用软件的组织。然后,这些软件被开源并共享。我们可以添加 CoP Helm Charts 仓库,其中包含一个 Jenkins Helm 图表供我们使用。

首先,我们需要 helm 命令行工具。从你的笔记本电脑上,按照 helm.shhelm.sh/docs/intro/install/)网站上的说明来安装 helm 工具。然后按如下方式添加 Red Hat CoP helm 仓库:

helm repo add redhat-cop \
     https://redhat-cop.github.io/helm-charts

我们可以在这个 helm 仓库中搜索可以使用的 Jenkins chart 版本:

图 6.15:在 helm 仓库中搜索 Jenkins chart

使用 Helm 安装 Jenkins

获取你自己 OpenShift 集群的最快方式是安装 CodeReady Containers 到你的笔记本电脑上。支持 Linux、Windows 和 macOS。你需要登录并按照此处的说明操作:developers.redhat.com/products/codeready-containers/overview,你应该能看到类似 图 6.16 的两步安装过程:

图 6.16:安装 CodeReady Containers

你可以访问的其他 OpenShift 集群也可以使用,只要你有足够的资源和权限。CodeReady Containers 安装为你提供集群管理员权限(最高权限),并且受限于你的笔记本电脑的 RAM、CPU 和磁盘空间。我们推荐至少 8GB RAM、4 个 vCPU 和 31 GB 的磁盘空间,这样的配置适用于在 Linux 上启动 CRC:

crc start -c 4 -m 12288

附录中有更详细的 OpenShift 配置说明。

为了安装 Jenkins chart,我们将登录到 OpenShift,创建一个新项目,并安装 Helm chart。如果你缺少运行这些命令所需的工具,不用担心,它们可以从 OpenShift 控制台直接下载并安装,以匹配你的 OpenShift 集群版本。点击 ? 图标,然后选择命令行工具,找到最新的安装说明。

图 6.17:从 OpenShift 下载命令行工具

安装后字符串 my-jenkins 是 Helm 模板引擎使用的发布名称:

oc login <cluster_api> -u <name> -p <password>
oc new-project example
helm install my-jenkins redhat-cop/jenkins  

它允许我们在一个命名空间中创建多个发布,这对测试非常有用:

图 6.18:在一个命名空间中创建多个发布

Helm charts 可以通过多种方式安装。你还可以针对 chart 的本地副本运行 helm template。如果你有兴趣这样做,可以获取 chart 并运行以下命令:

helm fetch redhat-cop/jenkins --version 0.0.23
helm template test jenkins-0.0.23.tgz

如果你想在将其应用到 OpenShift 集群之前查看输出,或者在调试或测试 chart 配置时验证内容,这会非常有用。你还可以在 helm install 命令中添加--dry-run参数来验证 chart,而不进行实际安装。

让我暂停一下,说明这本书并不是关于 Helm 的!市面上有很多专门介绍 Helm 的好书,比如 Andy BlockAustin Dewey 所著的《Learn Helm》(www.packtpub.com/product/learn-helm/9781839214295)。我们的目的是仅仅触及表面,展示如何以可重用和可重复的方式轻松开始使用 Helm 和 OpenShift。

如前所示,使用 helm install 非常好,因为它将创建一个由 Helm CLI 管理的生命周期,能够在需要时进行升级和回滚。这些版本已集成到 OpenShift 中,可以通过 UI 或命令行查看。每次将新版本部署到集群时,都会创建一个新的 secret,使回滚变得非常简单:

oc get secrets -n example | grep helm

要查看 Jenkins chart 启动的所有 Pods,可以运行以下命令:

oc get pods --watch -o wide -n example

你应该会看到大量的 Pods 被创建——这是因为这个 Helm chart 包含了许多用于 Jenkins 的附加配置代码。一次编写,多次部署:

图 6.19:正在创建的 Pods

你可能会注意到输出中有一堆代理构建 pod。Jenkins 本身有些无用。Jenkins 的一项超级能力是它可以通过所谓的插件进行扩展——这些插件是提供新功能和特性的代码小块。为了安装这些插件,我们本可以等到 Jenkins 部署完成后,通过 UI 手动配置插件——但这是一个“一切皆代码”的世界,所以我们不想那样做!

Jenkins Helm chart 配置了预安装一堆有用的 Jenkins 代理插件。这些代理知道如何使用各种特定语言栈来构建容器镜像。代理插件的配置定义在 Helm chart 的 values.yaml 文件中,你可以通过以下命令查看:

helm show values redhat-cop/jenkins
buildconfigs:
# Jenkins agents for running builds etc
  - name: "jenkins-agent-ansible"
    source_context_dir: "jenkins-agents/jenkins-agent-ansible"
    source_repo: *jarepo
    source_repo_ref: "master"
...

Helm chart 定义了一系列构建配置,用于构建每个代理镜像。Jenkins 代理镜像使用一个叫做 Source-to-ImageS2I)的 OpenShift 项目来完成针对特定语言的应用构建。S2I 是一个工具包和工作流,用于从源代码构建可重现的容器镜像;你可以在这里了解更多:github.com/openshift/source-to-image。你基本上是通过 Git 仓库 URL 将源代码输入给 S2I,之后它会处理剩下的事情。

使用特定语言的代理使得 Jenkins 更容易扩展。我们不需要将工具安装到基础 Jenkins 镜像中,而是定义一个代理并将其添加到 Helm 图表代理插件值列表中。OpenShift 让为 Jenkins 创建代理变得非常容易。我们可以使用任何我们想要在流水线中使用的二进制文件扩展基础镜像,并将标签 role=jenkins-slave 应用于使其在 Jenkins 中可被发现。这使得 Jenkins 能够在需要时动态提供代理,例如,一个 Pod 被启动并且 Jenkins 将连接到它,执行其任务,并在完成后销毁它。这意味着没有代理处于空闲状态等待执行,每次运行构建时都是一个干净的状态。

在 CoP 中有大量的 Jenkins 代理可用;您可以使用它们或者自己创建:github.com/redhat-cop/containers-quickstarts/tree/master/jenkins-agents

除了代理插件外,Jenkins 镜像还可以通过多种不同的方式从基础镜像进行扩展。您可以在构建 Jenkins 镜像时指定要安装的插件列表。我们使用 S2I 构建我们的 Jenkins 镜像,并从此 Git 仓库添加我们的 plugins.txt 列表:github.com/rht-labs/s2i-config-jenkins

一旦 Jenkins 构建完成,Jenkins 部署和运行的容器实例将可用。

图 6.20:可用的 Jenkins 部署和运行的容器实例

所有的 S2I 插件和代理都已配置好。您可以使用其路由登录到 Jenkins,在 OpenShift Web 控制台中可用,或者通过运行以下命令:

oc get route jenkins 

通过运行这个单一的 helm install 命令,我们可以得到一个合理的起点,可以在我们的构建服务器 Jenkins 中执行很多操作。通过将 Jenkins 配置进行编码化,我们可以在许多环境中重复部署 Jenkins,而无需触摸 Jenkins UI。

现在我们已经有了我们的构建服务器,在开始开发之前,我们应该熟悉开发者使用的代码工作流类型。如果您是一位经验丰富的开发者,您可能已经对下一节的内容非常熟悉。

开发者工作流

Git 是由 Linus Torvalds(Linux 内核的作者)创建的版本控制系统VCS),用于跟踪源代码的变化,并轻松地在多个文件类型和开发人员之间管理这些变化。Git 与其他版本控制系统的不同之处在于它是去中心化的。这意味着,与例如 Subversionsvn)不同,每个开发者在检出代码时都会保留一份源代码的完整副本。在本地,每个开发者都有所有历史的副本,并可以根据需要回滚或快进到不同的版本。工程师会进行更改,并将这些更改作为增量应用到他人的工作上,这就是所谓的提交。Git 可以被概念化为一棵树,树干是这些更改或提交依次叠加在一起。分支可以从树干中分出,作为独立的功能,或者尚未准备好的工作可以被合并回树干。一旦某个东西被提交到 Git,它将永远存在于历史记录中,随时可以找到——因此,请小心不要错误地添加一些机密信息,如密码!

Git 是一些大型公司背后的基础技术,如 GitHub 和 GitLab。它们在 Git 产品的基础上添加了一些社交功能和问题追踪能力,帮助管理代码库。

开发团队在编写代码时可以使用许多 Git 工作流,选择正确的工作流可能看起来是一项艰巨的任务。有些工作流旨在为团队提供安全感,特别是在大型复杂项目中,而有些则促进团队内部的速度和信任。最流行的 Git 源代码管理工作流包括 Trunk、GitFlow 和 GitHub Flow。让我们详细探索每个工作流,并看看我们如何使用它们来促进 CD。

GitFlow

GitFlow 最早由 Vincent Driessen 在大约 10 年前发布。这个工作流是基于他使用 Git 的经验构建的,而 Git 当时是一个相对较新的工具。当团队从非分支式的代码仓库迁移到 Git 时,一些新的概念和核心实践需要被定义。GitFlow 尝试通过为分支名称和约定增加一个深思熟虑的结构来解决这个问题。

明确的分支策略是 GitFlow 的核心。根据变更的类型,代码会被提交到不同命名的分支。新功能在被称为 feature-* 的分支上开发。hotfixes-* 分支用于修复生产环境中的漏洞和发布分支。GitFlow 描述了两个预留的、长期存在的分支:

  • Master:这是包含我们发布版本或生产就绪代码的分支。有时这个分支也被称为主分支。

  • Develop:这个分支是我们的集成分支。它通常是最混乱的,很可能包含 bugs 或其他问题,因为它是团队首次将代码汇集到一起的地方。

GitFlow 中定义的命名和使用规范使得新开发人员能够轻松发现每个分支的作用。开发人员可以在需要时通过合并新更改,将其他团队成员的更改带入他们的功能分支。以这种方式分支可以避免破坏其他工程师的工作,因为它确保功能实现完整,才会请求将代码从功能分支合并到开发分支中。当一组功能准备好推送到主线主分支时,开发人员会通过发布分支将代码合并到主分支。

你可能在读到这里时会觉得,这听起来很复杂!在某些方面,确实如此。但是在一个拥有单一代码库的大型项目中,这可能正是确保开发人员能够自由编写代码而不必管理代码所需的方式。

GitHub Flow

GitHub Flow 与 GitFlow 类似,因为它们的名字中有些相同的词。分支是 Git 的核心支柱,GitHub Flow 利用这一点,保持一个长久存在的主分支(main 或 master)。然后,开发人员在主分支上创建自己的分支,在这些分支中可以提交更改并进行实验,而不会影响主分支。

这些分支可以像 GitFlow 中的功能分支一样,但没有必须遵循的命名规范。重要的是要使用描述性名称为分支命名,例如 sign-up-form 或 refactor-auth-service。请不要命名为 another-new-feature-branch 之类的分支!

在任何时刻,开发人员都可以提出拉取请求,其他工程师可以讨论代码及其方法,并通过提供反馈来对仍在进行中的更改进行设计。原作者可以将这些讨论整合到软件中。当团队满意并且代码已被审查后,可以批准更改并合并到主分支。

GitHub Flow 非常有助于促进工作成果的同行评审,并提高决策过程的透明度。Git 本身就是可搜索的,而合并请求中的讨论提供了有关如何做出架构和编码决策的宝贵见解和可追溯性。

基于主干的开发

GitHub Flow 和 GitFlow 都使用分支策略和合并模式来将独立的开发活动结合在一起。Git 中的分支操作非常简单。然而,在合并所有分支时,仍然可能发生冲突,需要人工干预。

根据分支的使用频率,一些团队最终会陷入合并地狱,在这种情况下,所有人都会试图同时将他们的更改合并,这就导致了一个复杂且通常令人沮丧的事件——试图解开所有更改,并在保持可工作的代码库的同时解决冲突!

基于主干的开发(trunkbaseddevelopment.com/)通过拒绝使用分支,采用了与众不同的方式来解决这个问题!

图 6.21:合并地狱

在主干开发中,开发者们在一个单一的主分支上协作,这个分支被称为主干。开发者在其中进行更改,并直接将更改应用到主干中。在理想的情况下,提交内容是小规模且频繁的。在这一过程中,黄金法则是绝不破坏构建,并始终保持可发布状态。在这方面,开发者必须始终确保这一点。开发可以通过一些持续集成(CI)流程自动化,但关键是团队内部必须建立信任。

在大型企业中,这种不断合并到主分支的方式听起来可能会带来麻烦。例如,如何进行代码同行评审?对于有许多工程师和团队的大型应用开发,建议使用非常短生命周期的功能分支,这能够在评审过程中提供决策日志,但关键是分支的生命周期要短。短生命周期的功能分支应该最多只存在一两天(绝对不超过一个迭代周期),并在代码合并后删除,以防它们变成功能发布分支。

选择过多——告诉我该做什么

每种 Git 工作流都经过多年的团队实践和验证。一些团队选择其中一个作为标准,而其他团队则根据自身的上下文采用一种或多种工作流。

GitFlow 的原始作者最近修订了他的观点,建议 GitFlow 并不适用于“持续交付的应用程序”,例如 Web 应用。分支可能会在生产代码和正在进行的工作之间产生距离,而 GitFlow 要求代码在发布之前需要在多个分支间流动。如果我们想象开发者在工作他们的功能,他们将其合并到开发分支中,然后继续创建新的功能分支。

新功能会等待(可能还包括一些其他已完成的功能),直到开发周期结束。此时,它们会通过发布分支打包并移到主分支,可能是在工作完成后两周。这些额外的步骤意味着开发者在开发完成后很长一段时间内无法从用户那里获得所需的反馈。在反馈循环方面,如果需要对项目进行返工或出现 bug,可能需要几周才能解决。再加上开发者的上下文切换,他们需要重新回顾之前所做的工作,这可能会影响团队的开发速度。

在持续集成(CI)中,功能分支会拖慢我们速度吗?开发者很容易在他们的分支上独立工作,直到开发完成。我们过去与一些声称在做持续集成的团队合作过,但他们的构建系统在审查日之前一直处于空闲状态。到那时,所有开发者会赶在最后一刻进行集成,常常会暴露出设计上的误解或软件故障。长期存在的功能分支很难与持续集成兼容。

短期存在的功能分支是解决这些问题的好方法。开发者可以在独立的环境中处理小块功能,并且仍然频繁地进行合并。短反馈周期是改善软件交付度量的关键。如果分支增加了这个周期的时间,我们怎么能进一步缩短它呢?同行评审有时会成为团队的负担,因为它依赖某个个体,或打断其他工程师的专注力来完成功能。通过配对工程师,你能实现隐性的同行评审。作为一对配对工程师,直接将更改推送到主干是提高速度的好方法。在容器生态系统中,你只希望构建一次并验证应用程序是否工作,然后再将其部署到多个地方。基于主干的开发通过鼓励频繁的小变更直接推送到主干,支持了这一点,此时持续集成(CI)和持续交付(CD)可以接管流程。

根据我们在启动具有不同技能组合的产品团队的经验,选择合适的团队应该视为一种路径,是从不成熟到成熟团队的一个滑动尺度。对于初次接触 Git 的团队,使用功能分支可能是一个令人放心的方式,可以避免踩到其他开发者的脚。书籍《加速》3 衡量了多个团队的软件交付表现,得出的结论是高效能团队使用基于主干的开发。

无论你选择哪种管理代码的方法,关键在于交付的频率。你需要多长时间才能将软件交付给最终用户?使用功能分支会拖慢速度吗?还是这些分支为你的团队提供了一个安全的起步点?随着团队的成熟以及彼此和工具的熟悉,软件输出可以增加。

这里的关键行动是让团队选择最适合他们的方式,并围绕工作流和工具构建自动化。这使得开发者能够专注于难题——编写代码,而不是管理代码。最初,这只是一种猜测。团队应该通过回顾来评估是否有效,并根据需要调整。重要的是不要为所有团队的所有开发活动制定统一的教条,因为每个团队都不同。一种鞋码并不适合每个人!

3 itrevolution.com/book/accelerate/

结论

在本章中,我们学到通过从一开始就采取绿色开发的方式,我们能够有一个良好的开端!通过自动化我们的应用构建和打包工具的部署,Jenkins 和 Helm 可以建立一个技术基础,使我们的团队能够持续集成(CI)并持续部署(CD)我们的代码到生产环境中。

我们学到,团队间可以对齐开发者的代码工作流,并开始迭代我们的 CI/CD 流水线,帮助我们更快地交付应用程序。我们可以通过让开发者结对编程,帮助缩短代码评审反馈周期,从而提升代码质量和理解度。

作为一个团队,我们通过尝试集体编程(mob programming)一起学习了所有这些新的技能和技术,并在此过程中告别了我们对独角兽开发者的热爱。

图 6.22:将技术实践添加到基础中

开放技术实践 的后半部分,我们将学习更广泛的视角,了解 GitOps 的本质,通过测试大幅提升我们的代码质量,并最终通过一些关于新兴架构的课程做总结。

第七章:7. 开放技术实践——中期

在本章中,我们将继续上一章中开始的基础技术实践。我们将通过大局观实践,获得对我们软件交付管道的共享理解。即便是技术不太熟悉的团队成员,也能跟得上我们的软件在编写和交付过程中的变化。

接下来,我们将解释一种技术,允许 DevOps 团队使用 Git 作为驱动工具来交付软件变更。GitOps 的实践有助于提升我们系统中变更的可见性,使团队能够更快地调试和解决问题。我们将探讨如何通过自动化测试提高代码质量,并通过提问我们的架构是否优秀?来总结本章内容。

本章将涵盖以下主题:

  • 大局观

  • GitOps

  • 测试

  • 新兴架构

大局观

一种成本低廉、但能很好地创建系统共享理解的开放技术实践是大局观工作坊。这是一个简单的实践,用来可视化软件管道中所有步骤,从源代码(例如 Git)开始,到编译和测试,再到最后交付给我们的用户。团队协作地构建大局观是一个极好的活动,能够弥合技术人员与业务人员之间的差距。它非常适合阐明持续交付的重要性以及其中的复杂性。

图 7.1:大局观

大局观可以通过简单的便签和一个清晰的板子或空间轻松创建。当然,如果你更具艺术天赋,也可以用涂鸦方式画出来!

图 7.2:一个大局观示例

你可能在读到这里时会想,这听起来很空泛——我为什么要费心做一个大局观? 下面是原因:

  • 共享理解:当整个团队围绕大局观进行协作时,他们会对管道如何将代码与用户连接产生共享的理解。

  • 快速原型:在实现任何一行代码之前,先写下来或画出来更便宜!快速原型设计,用标记笔和便签纸,移动笔和纸管道的各个阶段。

  • 简化复杂性:大局观通过展示管理软件生命周期所需的组件,帮助非技术人员更好地理解整个过程。一步步构建,展示复杂性,同时保持简单直观的视觉流。

  • 信息辐射器:和所有这些实践一样,大局观是一个不断发展的成果。随着软件交付管道的复杂性增长,大局观应当及时更新以反映这一变化。它是一个可以公开展示的图形,不应被隐藏。

图 7.3:协作以达成大局观的共享理解

大局观也可以使用在线协作工具绘制。我们使用了 Miro 来绘制以下数字化的大局观。

图 7.4:数字“大图”

创建“大图”所需的材料相当简单:一些便签、马克笔、油漆胶带以及一面大空白墙或画布,这些都是我们常见的工具!创建“大图”有一些简单的步骤,但让我们通过 PetBattle 示例来展示团队如何在实际中使用它。

PetBattle – 构建“大图”

PetBattle 技术团队决定构建一个“大图”来展示他们关于软件如何通过一些自动化构建、测试和部署的提案。

首先,他们邀请团队中的其他成员帮助解释一些自动化技术和复杂性。

他们用油漆胶带形成一个大框,代表云环境,框内再画一个小框,代表他们将使用的 OpenShift 集群(部署在云中)。在这种情况下,比喻是:OpenShift 就是一个大框,我们可以将一些在云中运行的东西放进去。这个框非常大,可以填满我们可能需要的所有东西,从沙箱、工具到生产应用。

图 7.5:启动 PetBattle“大图”

他们在左侧画一个框,表示他们的本地开发环境。目前这是他们的笔记本电脑,但它也可以是一个云托管的 IDE,开发团队可以在其中编写代码,并且该 IDE 被部署在集群内。CodeReadyWorkspaces 就是一个这样的产品,它是一个云托管的 IDE,运行在 OpenShift 集群内,对团队可能非常有用。使用这样的 IDE 可以通过提供作为代码工件的开发环境来进一步推动我们的“万物皆代码”实践。

接下来,他们将 OpenShift 集群划分成更小的部分。每个部分代表一个 OpenShift 项目(或 Kubernetes 命名空间)。我们可以将这些项目视为将一个应用集合与另一个应用集合分开的房间。为了简化问题,团队决定最初使用四个命名空间:

图 7.6:PetBattle“大图”项目

  • 开发:开发团队用来验证他们的应用或快速获取反馈的沙箱项目。

  • 测试:一个用于部署所有应用并进行系统测试的项目。

  • 生产:PetBattle 客户在通过我们的测试后用来访问应用的项目。

  • CI-CD:包含所有支持 持续集成CI)和 持续交付CD)工具的项目。

在将 OpenShift 集群逻辑划分为团队将使用的项目后,团队在每个项目中绘制出他们将使用的工具。

图 7.7:PetBattle“大图”初步框架

从他们的本地开发环境开始——换句话说,就是他们的笔记本电脑或云托管的工作空间——现有的 PetBattle 是使用 Angular(一个用于构建 Web 应用的 JavaScript 框架)来构建前端的。API 层使用 Quarkus(超声速 Java),持久化层使用 MongoDB,因此他们将这些工具添加到工作空间,并写下一行定义,说明该团队是如何使用这些工具或框架的。

对于 PetBattle,我们将使用 Helm 将所有用于管理应用拓扑的 Kubernetes 资源(部署、配置映射等)打包。我们还将使用 ArgoCD,这是一种 GitOps 工具,用于管理我们的配置即代码。

图 7.8:PetBattle 大图源代码和注册表

PetBattle 将使用 GitHub 存储其源代码。在构建镜像时,团队很可能需要使用内部注册表将构建好的镜像存储在 OpenShift 集群内。团队还希望将镜像对外提供,因此决定同时使用Quay.io,一个托管在公共云中的外部注册表。

图 7.9:PetBattle 大图管道工具

然后,团队开始在其 CI/CD 命名空间中添加他们将用于创建管道的工具。他们使用更多便利贴来绘制这些工具,并为每个工具写一个简短的定义,说明它是什么或者他们将如何使用它。

例如,团队将使用 Jenkins 进行构建和测试自动化。为了存储和缓存应用程序构建依赖项和工件,团队选择使用开源的工件库——Nexus。对于 Nexus,他们添加了一个简单的一行定义,强调它用于存放他们的软件工件以及 Helm 库。共享理解是关键,因此团队需要确保每个人都清楚每个项目的目的——这包括产品负责人、设计师和所有其他相关方。他们不需要成为专家,但了解工具的用途有助于他们与开发团队建立更好的共鸣,亲身了解需要做些什么才能迅速将代码交付给用户。

在大图上放置了一些工具后,PetBattle 团队现在可以开始实施他们已经设定的设计。

这个大图可以通过在一个充满五颜六色便利贴的实际房间里创建,或者使用 Mural、Miro、PowerPoint 或 Google Slides 等工具进行远程协作。我们提供了一个有用的模板,其中包含我们使用的所有图标,应该能帮助你开始。你可以从本书的 GitHub 仓库下载这个模板。

图 7.10:大图模板

你可以从本书的 GitHub 仓库下载此文件。

大局观使我们能够在高层次上对使用技术工具达成共识并进行团队对齐。就像我们在本书中介绍的所有实践一样,大局观不是一次性的。大局观是一个我们将不断回顾和完善的工具,随着我们向架构中添加更多复杂性并开始实施流水线,我们将不断完善它。我们将在第六部分:构建、运行、拥有中继续探讨大局观。

你可以通过访问openpracticelibrary.com/practice/the-big-picture/页面,了解更多关于大局观的实践并进行合作。

GitOps

到目前为止,我们已经讨论了 Git 以及为团队提供的开发工作流。我们谈到了全代码化,从基础设施到工具,再到整个堆栈中的应用程序。现在,让我们通过 GitOps 将这一切结合起来。

GitOps 听起来有点像流行词,就像 DevOps 在最初被提出时一样。事实上,我们曾听到有人这样描述它:GitOps 是 2020 年的 DevOps。GitOps 是通过 Git 管理所有系统、环境和应用程序的简单过程。Git 代表了所有应用程序、工具甚至集群的唯一真实来源。对这些任何内容的更改都可以通过拉取请求来提交并进行讨论,之后自动化过程会应用这些更改。

基础设施即代码IaC)和 GitOps 的区别在于管理配置的方式。IaC 与配置存储的位置无关;它可以存储在你抽屉里的 U 盘上,也可以存储在云中的共享驱动器上。而 GitOps,顾名思义,是指将完整的系统规范存储在 Git 中。

IaC 和 GitOps 的原则是相同的——理想情况下,每个操作应该是幂等的。每个操作或行为可以应用多次,产生完全相同的结果。这在许多情况下非常有用,因为它意味着可以根据需要重复或重试操作,而不会产生意外效果。配置应该声明性地创建。也就是说,你写下配置来描述应用程序或一组应用程序的期望状态。

GitOps 可以看作是一个面向开发人员的运维方法。它教会开发人员在代码离开他们的机器后如何接管代码,并指导他们如何部署和监控运行中的代码。

作为开发人员,我们讨厌重复自己,甚至为此创造了一个缩写——DRY = Don't Repeat Yourself(不要重复自己)!当遇到需要做多次的事情时,我们的第一反应应该是尽量将其自动化。一旦某件事被自动化或变得可重复,下一步就是简单的操作。将其提交到 Git 中,这样它就可以被审计、共享和管理。

例如,每当我们想要将一个新的应用部署到 OpenShift 时,我们可以运行一些手动命令来启动应用、创建服务和路由,甚至绑定一个 ConfigMap。但花时间为此创建一个 Helm 图表是可重用且可重复的。我们可以在代码中设计应用的最终状态,然后将其检查到 Git 中。这是一种更符合云原生方式的应用代码编写和管理方法。

要将 GitOps 方法应用于我们的 Helm 图表示例,我们只需将一个工具连接到 Git 仓库,该工具可以被提醒或监视变化。当变化到来时,该工具可以评估当前状态与所需状态之间的差异,并自动为我们应用这些变化。进入 ArgoCD。

ArgoCD

根据 ArgoCD 网站的描述,这是一个被定义为:

自动化在指定目标环境中部署所需的应用状态。应用部署可以跟踪分支、标签的更新,或固定到某个特定的 Git 提交版本的清单。1

当某个状态在 Git 中与所需状态不匹配时,应用会变得不同步。根据你实施 GitOps 的方式,ArgoCD 可以重新同步更改,立即应用 Git 中的内容,或者触发警告以启动其他工作流。在 ArgoCD 实现的持续交付世界中,Git 是唯一的真实来源,因此我们应该始终按 Git 中的内容应用更改。

ArgoCD 可以应用哪些类型的内容?ArgoCD 识别传统的 Kubernetes YAML、Kustomize,2 Helm 以及其他各种内容。与 heavily 使用模板的 Helm 不同,Kustomize 允许你使用 YAML 文件,并以无需模板的声明方式输出文本。你可以修补 Kubernetes 资源,并使用基于文件夹的结构应用所谓的覆盖或 YAML 覆盖,这会输出文本,保持原始 YAML 不变。就我们而言,我们将主要使用 Helm 和适当的 Kustomize。

ArgoCD 是一个工具(还有像 Flux 这样的其他工具)在我们实现 CI 和 CD 时所需要的一长串工具中的一员。与我们也可以用来管理应用部署的 Jenkins 不同,ArgoCD 专注并且非常擅长仅管理和维护我们的部署。

1 argo-cd.readthedocs.io/en/stable/

2 github.com/kubernetes-sigs/kustomize

Jenkins 可以一次性应用我们的 Helm charts,类似于“完成后就结束”的方式。它没有能力持续监控我们的 Kubernetes 资源,以确保 Git 中的期望状态在集群中保持不变。如果有人决定改变集群中的某些内容,比如向正在运行的应用程序添加一个新环境变量,ArgoCD 会检测到这一变化并覆盖它。这意味着一旦部署后,就不会再有独一无二的部署或手动调整。

ArgoCD 使团队能够强制执行这一黄金法则——如果它不在 Git 中,那就不是真的。这对于审计任务非常完美——你所需要做的就是检查 Git 日志,看看是谁提交并推送了代码。

如果不在 Git 中,那就不是真的!

我们与世界卫生组织合作进行虚拟驻地,帮助管理 COVID-19 危机。我们正在建设一个新的平台,以帮助现场人员进行教育,并加速信息传播。我们决定使用 GitOps 进行持续交付,特别是使用 ArgoCD 来管理我们的 Helm charts。

要真正做到云原生,命名空间和环境应该是短暂的。我们应该能够从代码中的描述重新创建所有对我们有用的内容。这包括命名空间、配额和角色绑定,以及应用程序和数据库。为了在一个冲刺周期内证明这一点,我们创建了一个清理任务,用来删除 OpenShift 中的开发和测试项目。我们的配置库链接到 ArgoCD,ArgoCD 监控集群,并在任何变化发生时,重新应用 Git 中描述的资源。这个任务的执行时间定在周三下午午餐时间,约在冲刺评审前一个小时。可能出现什么问题呢?

团队像往常一样准备进行演示,但在演示开始前约 20 分钟,其中一名团队成员喊道构建失败,他的演示坏了,而且他找不出原因。团队迅速行动,大家都加入了电话会议,共同解决问题。在回顾过去一小时可能发生的变化时,唯一执行的任务就是我们写的清理工作。我们立刻认为是我们在工作中写错了什么,于是去调试它,但一切正常。接下来的步骤是更加仔细地查看构建和错误信息。

到这时,我们发现团队中的某个人手动将数据库部署到了开发环境。他们正在连接到它进行演示,并将其作为我们 Jenkins 流水线中的测试数据库。实际上,团队中的某人创建了一个“宠物”——一个由某个人精心照料和培养的服务器,其他人对此一无所知。在短命环境的世界中,我们真正需要的是“牛”。“牛”是大规模生产的,通过自动化创建,并在不再需要时被销毁。因此,当我们的工作运行并清理项目时,所有资源都被销毁了。

团队从这次经验中学到了一个宝贵的教训,并且他们将这一点非常显著地展示了出来:

图 7.11:如果它不在 Git 中,它就不真实

这产生了一句口号,我们将其加入到我们的社会契约中,来自 第四章,开放文化

实现 GitOps

让我们用一些真正可工作的代码来构建大局!在这一节中,我们将进行一个技术绕行!准备好一些代码片段。如果这不是你的兴趣,随时可以跳过,直接进入下一个全是关于测试的章节!我们会用这个方便的标志标记任何包含代码片段的部分。

让我们探索 ArgoCD,并从代码中创建我们大局的组件。为此,我们将首先探索一个可以作为开发起点的示例项目。

在 Red Hat 开放创新实验室,我们已经实现了 Labs Residency CI-CD 工具的自动化引导,以加速设置和入职。代码库名为 Ubiquitous Journey,因此从这里开始是合适的。我们将探索这个代码库,并使用它建立我们的技术基础。在本书的后续章节中,我们将使用新技术和工具扩展它。这个代码库可以在 PetBattle GitHub 组织中找到 – github.com/petbattle/ubiquitous-journey

当我们在 OpenShift 集群上部署 Jenkins 时,我们通常执行的第一个任务是使用命令行创建一个新项目。我们可以再次按照这种手动方式进行,添加角色绑定和配额,并为我们大局中的每一部分重复这些步骤。但我们决定以一种符合我们一切皆代码实践的方式来做。

从你的笔记本电脑上,fork 这个示例项目,并在你喜欢的代码编辑器中打开它。

我们将对项目进行更改,因此保持你自己的副本是 GitOps 所必需的。从现在开始,当我们遇到一个新的代码库时,你可能会发现通过 fork 它来进行更改会更容易。为了本书的后续内容,我们将继续使用 PetBattle 组织,所以你可以将它等同于你自己的组织或用户。

git clone https://github.com/petbattle/ubiquitous-journey.git

Ubiquitous Journey 项目被分为两个主要组件(为简化起见,下面的分解中移除了一些文件),分别是 Bootstrap 和 Ubiquitous Journey。如果你在想我们为什么给这个项目命名为 Ubiquitous Journey……嗯,实际上我们并没有命名!我们按下了 GitHub 上的 生成随机名称 按钮,它为我们选择了这个名字。就像大多数软件中的事情一样,命名真的很难!我们本来打算在某个阶段重命名这个代码库,但现在这个名字已经有点粘住了,我们也挺喜欢它的!

$ tree ubiquitous-journey
ubiquitous-journey
├── argo-app-of-apps.yaml
├── bootstrap
│   ├── charts
│   ├── Chart.yaml
│   └── values-bootstrap.yaml
├── docs
├── ...
├── README.md
└── ubiquitous-journey
    ├── Chart.yaml
    ├── templates
    │   ├── argoapplicationdeploy.yaml
    │   └── _helpers.tpl
    ├── values-day2ops.yaml
    ├── values-extratooling.yaml
    └── values-tooling.yaml

启动文件夹包含一个 Helm 图表定义,带有一个 values-bootstrap.yaml 文件和 Chart.yaml 清单。这个图表没有模板,因为它实际上只是其他 Helm 图表的包装器。如果我们查看 Chart.yaml 清单,可以看到它依赖于 ArgoCD 图表,另一个称为 bootstrap 的图表和一个名为 sealed-secrets 的辅助图表。启动文件夹 Helm 图表充当包装图表,允许我们控制传递给这些依赖项的变量。在这种情况下,我们的变量存储在 values-bootstrap.yaml 文件中:

bootstrap-project:
  enabled: true
  ci_cd_namespace: &ci_cd "labs-ci-cd"
  pm_namespace: &pm "labs-pm"
  ops_namespace: &ops "labs-cluster-ops"
  dev_namespace: &dev "labs-dev"
  test_namespace: &test "labs-test"
  staging_namespace: &stage "labs-staging"
  bindings: &binds 
  # this labs-devs is the GROUP NAME in IDM
    – name: labs-devs
      kind: Group
      role: edit
  # this labs-admins is the GROUP NAME in IDM
    – name: labs-admins
      kind: Group
      role: admin
    – name: jenkins
      kind: ServiceAccount
      role: admin
      namespace: *ci_cd
  namespaces:
    – name: *ci_cd
      bindings: *binds
    - name: *pm
      bindings: *binds
    - name: *ops
      bindings: *binds
    - name: *dev
      bindings: *binds
    - name: *test
      bindings: *binds
    - name: *stage
      bindings: *binds

启动图表负责在我们的 OpenShift 集群中创建列出的项目。在示例中,这些项目包括 labs-ci-cd、labs-dev、labs-test、labs-staging、labs-pm 和 labs-cluster-ops。Dev、Test、Staging 和 CI/CD 可能会很容易理解;如果不明白,请查看前一章,我们在那里深入讨论了 CI/CD。labs-pm 命名空间用于部署其他项目管理工具(例如协作工具如 etherpad)。labs-cluster-ops 命名空间用于操作性工作和任务。

OpenShift 中的资源应用了 基于角色的访问控制 (RBAC)。3 RBAC 决定用户是否被允许在项目内执行给定的操作。我们将列出的用户组绑定到这些项目内的服务帐户上。如果您的集群目前尚未设置 labs-dev 和 labs-admin 组,不要担心。只要您以具有集群管理员特权的用户登录到您的集群中即可。

argocd-operator:
  enabled: true
  name: argocd
  namespace: *ci_cd
 argocd_cr:
    applicationInstanceLabelKey: petbattle.app/uj
  # operator manages upgrades etc
  version: v1.8.6
  operator:
    version: argocd-operator.v0.0.14
    channel: alpha
    name: argocd-operator

3 docs.openshift.com/container-platform/4.6/authentication/using-rbac.html

该文件的第二部分覆盖了 ArgoCD 图表中的一些变量。这个 Helm 图表安装了 ArgoCD 操作员,并使用合理的默认值进行配置。关于可以传递给此图表的所有可能变量的列表,您可以查看 ArgoCD 操作员文档 - argocd-operator.readthedocs.io/en/latest/。在这本书中没有重新创建这些文档的意义,但如果您想进行一些探索,保存它们是很有用的。

需要特别注意 applicationInstanceLabelKey 变量。这个变量在你的集群中必须是唯一的。如果你在一个集群上部署了多个 ArgoCD 实例,并且它们的实例标签相同,那么这两个 ArgoCD 实例将尝试管理相同的资源,然后它们会争夺到底是谁真正拥有这些资源,并且会给你带来很多麻烦,所以确保 applicationInstanceLabelKey 是唯一的!

让我们部署这个设置,看看它给我们带来了什么。如果您希望更改创建的项目名称,请编辑值文件,但现在我们将使用默认值。在您的笔记本电脑终端上,尝试以下命令:

$ helm template bootstrap --dependency-update -f \
bootstrap/values-bootstrap.yaml bootstrap

运行像这样的 Helm 模板应该会下载我们的图表依赖项并处理我们的模板。这是验证 YAML 文件是否符合预期的便捷方式。让我们将引导 Helm 图表安装到它自己的命名空间中。

$ helm upgrade --install bootstrap-journey \
  -f bootstrap/values-bootstrap.yaml \
  bootstrap --create-namespace --namespace labs-bootstrap

此命令的输出应为成功安装引导 Helm 图表:

图 7.12:使用 Helm 引导 ArgoCD

你可以通过以下命令检查正在启动的 Pod:

oc get pods -n labs-ci-cd  

你应该会在一两分钟后看到 ArgoCD 服务器开始启动:

图 7.13:Labs-ci-cd 命名空间中的 Pod 启动

或者,如果你查看 UI,你应该能看到包含所有 ArgoCD 组件的拓扑图:

图 7.14:OpenShift 开发者拓扑视图,展示 labs-ci-cd 项目

让我们通过点击 UI 中的链接来查看 ArgoCD,或者你也可以使用以下命令从命令行获取 URL:

oc get routes argocd-server -n labs-ci-cd

使用 OpenShift 凭证登录。我们应该会看到一个空的 ArgoCD 实例:

图 7.15:通过 Web 界面查看空的 ArgoCD 实例

在这一点上,我们应该问自己一个问题:当有人更改我们集群的引导配置值时会发生什么? 比如,添加更多项目或更改角色或组?我们能以自动化和可追踪的方式完成吗?换句话说,使用 GitOps 可以吗?别担心,ArgoCD 来拯救我们!我们现在可以将 ArgoCD 指向我们一直在使用的 Git 仓库。

我们可以通过在 ArgoCD Web 界面选择 +New App -> Edit as YAML 来创建一个 ArgoCD 应用程序,并复制粘贴以下定义:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: bootstrap-journey
  namespace: labs-ci-cd
spec:
  destination:
    namespace: labs-bootstrap
    server: https://kubernetes.default.svc
  project: default
  source:
    helm:
      parameters:
      - name: argocd-operator.ignoreHelmHooks
        value: "true"
      valueFiles:
      - values-bootstrap.yaml
    path: bootstrap
    repoURL: https://github.com/[YOUR FORK]/ubiquitous-journey.git
    targetRevision: main
  syncPolicy:
    automated: {}

点击保存,然后点击创建。你应该能看到bootstrap-journey应用程序已同步:

图 7.16:通过 Web 界面查看引导 ArgoCD 应用程序

我们也可以在终端中创建相同的应用程序。你可以使用单点登录通过终端登录到 OpenShift,使用以下一行命令。此命令需要一个非无头的终端,换句话说,它可以连接到你的屏幕和浏览器:

$ argocd login $(oc get route argocd-server --template='{{ .spec.host }}' \
-n labs-ci-cd):443 \
--sso --insecure

创建新应用并同步更改。完成这些操作后,argocd 将会主动跟踪我们 Git 仓库中的更改并为我们部署它们!很简单!

argocd app create bootstrap-journey \
  --dest-namespace labs-bootstrap \
  --dest-server https://kubernetes.default.svc \
  --repo https://github.com/[YOUR FORK]/ubiquitous-journey.git \
  --revision main \
  --sync-policy automated \
  --path "bootstrap" \
  --helm-set argocd-operator.ignoreHelmHooks=true \
  --values "values-bootstrap.yaml" 

你可以在 Web 界面中选择应用程序,进入其详细信息:

图 7.17:通过 Web 界面查看引导应用程序的详细信息

很棒——我们正在完成将“大图景”转化为代码,并奠定我们的技术基础!我们已经创建了项目,并将第一个工具 ArgoCD 添加到工具包中。现在,让我们更进一步,填充我们的集群,加入一些我们认为最初有助于构建 CI/CD 管道的应用程序。在任何项目的开始阶段,这通常是一个最好的猜测。随着我们开始构建产品,我们必须不断发展我们使用的工具集。这不是一次性过程;它是一组工具,在需要时需要扩展,或者如果不再有用就丢弃。这里重要的是确保事物能够以可重复的方式进行部署。

让我们添加一些工具。打开你在 ubiquitous-journey 项目中的编辑器。在 ubiquitous-journey/values-tooling.yaml 中,我们有一些有用的变量,引用了 Helm 图表,供我们选择使用,包括我们之前手动部署的 Jenkins!

##############
# 🛎 Argo App of Apps declaration
#############
# enabled true on an app is to tell helm to create an argo app cr for this item
# Custom values override the default values in Helm Charts
applications:
  # Nexus
  – name: nexus
    enabled: true
    source: https://redhat-cop.github.io/helm-charts
    chart_name: sonatype-nexus
    source_path: ""
    source_ref: "0.0.11"
    sync_policy: *sync_policy_true
    destination: *ci_cd_ns
    ignore_differences:
      – group: route.openshift.io
        kind: Route
        jsonPointers:
          – /status/ingress
  # Jenkins
  – name: jenkins
...
  # Sonarqube
  – name: sonarqube
...

这个文件的布局很简单。对于应用程序数组中的每一项,它期望找到一个 Helm 图表或一个指向 Git 仓库的引用,该仓库包含某个特定版本的 Kubernetes yaml(或 Kustomize)文件。

使用 Helm 时,任何对图表提供的默认值的覆盖都可以在此处添加,但对于显示的 Nexus 图表,我们使用的是默认值,因此不需要对 Nexus 进行值覆盖。每个应用还有其他字段,这些字段大多与 ArgoCD 的操作有关。例如,您可以配置应用同步策略(sync-policy),当设置为自动时,它会告诉 ArgoCD 始终保持应用的同步。可以指定目标命名空间。对于一些 Kubernetes 和 OpenShift API 对象,ArgoCD 需要被要求忽略它发现的差异;尤其是在控制器和操作员将状态及其他字段写回对象本身时,这一点尤其如此。我们随着时间的推移发现,随着每个 ArgoCD 版本的发布,指定这些 忽略 的需求逐渐减少,因为生成的差异会自动处理。

每个应用条目的另一个重要字段是 enabled: true | false —— 只需沿着列表跑一遍,启用我们立刻需要的工具就很容易。目前,我们将从四个工具开始:Jenkins、Nexus、Tekton 和 Code Ready Workspaces。这些是我们应用程序和管道框架的基础。此时,值得提到另外两个 values 文件,extratoolingday2ops

└── ubiquitous-journey
    ├── Chart.yaml
    ├── ...
    ├── values-day2ops.yaml
    ├── values-extratooling.yaml
    └── values-tooling.yaml

就像我们在 values-tooling.yaml 中的 CI/CD 应用列表一样,它们包含了用于在我们的集群中部署的有用 Helm 图表和 YAML 文件的引用。额外的工具包含了项目管理和协作工具,而 day2ops 包含了用于保持集群整洁的有用修剪任务。现在,我们将禁用所有额外的工具和 day2ops 应用。这为我们提供了一个最小的设置来开始使用。

如果你正在运行 CRC,请在部署工具之前查看附录中的任何细节。让我们通过命令行使用 Helm 和oc来部署这些工具:

$ helm template -f argo-app-of-apps.yaml ubiquitous-journey/ \
| oc -n labs-ci-cd apply -f-

如果你查看 ArgoCD 网页,你应该现在能看到这些应用开始部署并同步到你的集群中。它们会花一些时间来完全同步。例如,Jenkins 会构建我们在运行管道作业时可能需要的所有默认代理镜像。

图 7.18:安装了所有工具后的完整图景

我们现在已经成功引导了我们的 CI/CD 工具!我们将在发现需要添加和更新开发、测试和交付 PetBattle 所需工具时重新访问这些配置。通过实践一切皆代码,我们可以轻松地将这些工具重新部署到任何 Kubernetes 集群中,跟踪我们可能做出的更改,并管理工具的生命周期(随着版本和功能的变化,升级它们)。

测试,测试,测试!

到目前为止,我们已经讨论了可以用来将应用代码从构思到编译再到部署的一些工具。但我们怎么知道我们构建的东西真的按预期工作呢?如果我们创建一个仅仅编译代码并将其推送到生产环境的管道——这样就完成了吗?不,还需要在我们的软件管道中加入测试质量步骤和关卡!

测试自动化金字塔

我们怎么知道我们的功能是否按预期工作?我们应该进行测试并查看结果!测试我们的功能的方式并不总是很明确,什么时候测试得足够多或过多也不清楚。我们是否应该创建测试指令并手动测试这个功能?我们是否应该将功能单独测试?我们是应该测试所有的组成部分,还是只测试整个功能?那么,单元测试的定义究竟是什么?

说实话,测试是复杂的。我们将主张创建的不仅仅是任何测试,而是自动化测试!测试自动化金字塔,由迈克尔·科恩(Michael Cohn)撰写,是我们进入自动化测试世界的一个良好起点。让我们简化地看看原作者所提出的传统的测试自动化金字塔:

图 7.19:测试金字塔

在标准的三层测试三角形中,底部的内容(上文列为单元测试)是我们应该做得更多的事情。单元测试是我们在应用程序中可以测试的最小代码单元。这些单元应该尽可能少依赖其他项,因此,当我们运行它们时,它们会给我们即时且精确的反馈。单元测试应该直接指出代码中问题的具体位置。此外,单元测试的思路是它们编写成本低、易于维护、执行速度快。因此,我们希望更多的进行单元测试。这也是为什么它们位于测试金字塔的底部。

服务测试有时被视为集成测试,是测试三角形中的下一个层级。这些是 API 测试,用于验证你应用中的服务是否按预期运行。这可能包括单一服务调用,以及服务调用链,当一个服务调用另一个服务时。测试层级的宽度与代码库中应该有多少类型的特定测试相关。根据金字塔的理念,服务测试应该比单元测试少,因为它们的执行成本较高。

测试三角形的最顶层是专门用于用户界面UI)测试,或端到端系统测试。这些测试负责验证系统作为其各个组件和部分的总和,是否按预期运行。通常,UI 测试在面对变化时容易变得脆弱,出现故障的频率较高,并且需要维护以保持其有效性。因此,测试金字塔的理由是,我们应该减少这类测试的数量,因为它们难以执行,且提供的反馈较少。

实践中的测试

测试自动化金字塔是思考自己测试的一个很好的起点。和所有模型和模式一样,人们有时过于简化了其原始含义。事实上,如果你进行测试金字塔的图片搜索,你会发现大多数结果缺少一个最重要的关键词——自动化!通常,组织会忽视这一点,认为为这些层级进行手动测试就足够了。

测试很重要;事实上,它对于能够快速交付至关重要!如果你想象一下没有投入时间编写自动化测试,可能会在完成一个 sprint 时不会出问题。也许我们能够完成两个 sprint 而不出现问题。然而,一旦进入第三或第四个 sprint,软件系统就开始出现异常。第一 sprint 编写的应用程序现在会出现错误,因为它们的功能行为不再按预期工作。曾经被认为正常的函数和 API,实际上完全崩溃了!能够快速发布软件是一回事,但能够快速发布高质量的软件才是区分的关键。

在考虑测试时,重要的是要应用上下文。你不必盲目遵循像测试金字塔这样的模型。实际上,这是一种很好的起点,但它并不是在所有环境中都适用的万能法则。例如,你可能正在构建一个包含静态内容或第三方服务的 Web 应用,因此 UI 测试可能是最重要的事情。

重要的是要理智地看待你打算执行的测试类型以及它们所提供的价值。你可能会发现,对于你的产品而言,覆盖服务层可能是更好的选择。如果你无法访问代码,那么编写评估服务的黑盒测试,通过明确定义的输入和输出进行测试,更适合你的质量控制。同样,像金字塔建议的那样,衡量测试数量并不能告诉我们测试的质量。高质量的测试会在用户发现之前捕捉到错误。当生产环境出现故障或用户报告了一个 bug 时,很可能你需要编写更多的自动化测试。

图 7.20:测试衡量

另一种看待此问题的方式是计算不对某项功能进行测试的风险。也许你写的应用程序是一次性的,或者只是一个不需要严格测试的简单技术验证。然而,如果你产品中的某个功能被频繁使用,并且根本没有编写自动化测试,那么这个功能可能是你自动化测试工作应该重点关注的地方。

在你的产品开发团队内创建一种测试是持续实践的文化。测试不应该是开发过程的事后思考。我们经常看到,测试是在开发团队将包丢给测试团队后才开始的。对我们来说,每个冲刺中的每个项目都会有一定程度的测试。这不是由某个第三方团队完成的,而是由工程师自己完成的。开发人员通常更偏向单元测试和集成测试,但质量保证QA)团队则常常偏向自动化 UI 测试,从用户的角度验证应用程序。有时,如果文化不对,团队在被压迫加速推出新功能时,测试质量可能下降,导致几乎是反向的测试金字塔:底部有少量单元测试,接着是更多的服务测试,然后是大量脆弱的 UI 测试堆积在上面!这会影响软件交付管道的质量。从开发到 QA 的反馈循环可能非常长,单元测试几乎没有价值,而昂贵的 UI 测试反馈又过慢,无法及时提供反馈。

在交付过程中通过反转测试金字塔来降低质量可能会对团队造成很大伤害。如果缺陷数量显著增加,团队的信任将动摇。如果团队失去信任,那么自主性可能是下一个被破坏的方面,这将导致重度的命令与控制驱动文化。以这种方式运作的团队将很快失败,顶尖人才也将离开。

测试与完成定义

最近与世界卫生组织合作时,我们在一开始就有着很大的雄心,计划为每个冲刺项目编写测试。我们通过在每个冲刺项目的“完成定义”中加入测试,开始得相当顺利。我们一致认为,每个进入冲刺的项目必须包含一些自动化测试。

第一个冲刺在一周的迭代中迅速过去。由于我们是一个新团队,每个人都充满动力,渴望尝试新事物。到了第三个冲刺,我们已经接手了超出团队能力范围的故事。

我们所做的工作变得越来越复杂,错过了一些自动化测试。我们宣称某些功能已经完成。在当周的演示中,我们向产品负责人承认,这项工作在功能上已完成,但根据我们自己的标准还没有完成。

我们尽力对自己诚实,但发现我们在接下来的一个星期又犯了错。此时,我们清楚地意识到,在做冲刺规划时,我们没有正确地考虑编写测试所需的容量。虽然已经有了“完成定义”,但我们仍然没有做到诚实。我们是一个渴望不断向前推进、在前一个任务未完成时就急于开始新任务的团队。

在一次回顾会议上,我们决定一个好的前进方向是,在编写任务时记录测试工作量。当从待办事项中提取项目时,我们会为所有自动化测试添加子任务。通过这种方式,与测试自动化相关的所有工作变得对团队可见,因为这些子任务出现在冲刺看板上。为你的功能编写测试的任务使得在任务还在进行中的时候,很难继续处理下一个任务!

你可以通过访问 openpracticelibrary.com/practice/test-automation/ 来了解更多内容并协作进行持续集成实践。

TDD 或 BDD 或 DDT

关于测试以及如何编写有意义且能提供价值的好测试,已经有许多书籍。我们的目标不是重写这些书籍,而是给你一些提示,帮助你进一步研究这个话题,如果它真的引起了你的兴趣。团队在三角形的不同层级上发现有用的一些测试方法,包括 行为驱动开发 (BDD)、测试驱动开发 (TDD),以及 开发者驱动测试 (DDT)。

图 7.21:测试驱动开发

TDD 是一个简单的过程,但一些团队可能误解了它。过程相当简单。首先为你正在构建的功能编写一些测试。此时,测试应该是失败的(RED)。如果测试没有失败,那么说明你的测试写得不好,或者功能已经存在!然后开发者将编写代码使测试通过(GREEN)。当测试通过后,可以进行重构,或者正如极限编程的创造者、美国软件工程师 Kent Beck 所说,重构以去除重复。删除重复代码或使代码更加简洁并整理它,同时保持测试通过的状态。这个过程很简单:红色 > 绿色 > 重构。先编写测试是一项艰难的实践,需要时间和毅力来掌握技巧,但它可以减少混乱的代码。因为测试是首先编写的,它们引导了代码的设计和实现。

图 7.22:执行测试驱动开发

一个适合团队尝试 TDD 而不涉及代码的练习是通过 Gargoyle Software 网站上的乐高 TDD 模拟:gargoylesoftware.com/articles/lego_tdd

你可以通过访问openpracticelibrary.com/practice/test-driven-development页面来了解更多关于 TDD 并进行协作。

DDT 很容易实现,可能是如果你没有编写任何测试时,开始的地方。这里的重点是确实有一些测试被编写了!DDT 侧重于开发者编写代码并同时编写测试。简单来说,开发者先编写代码,写一些自动化测试,然后再回到编码和测试。这听起来有点像 TDD,但关键的区别在于顺序。先编码再测试,导致代码影响测试,而不是测试引导软件设计。DDT 的目标是让开发者对自己的代码负责,且每个人都应对测试负责。

图 7.23:开发者驱动的测试

BDD 是你工具箱中一个很好的工具,因为它让人们在共同理解一个正在开发的故事或特性的范围时凝聚在一起。它不太像工程工具,更像是一种方法,侧重于业务和开发者在编写功能时的对话。BDD 是通过使用共享语言编写应用程序行为的具体示例。

测试的实现方式由开发人员决定。但更重要的是,开发人员和产品负责人之间可以使用一种通用语言来划定故事的范围,而不需要引导软件设计。BDD 可以作为一种有用的方式,共同编写故事的验收标准。BDD 测试的编写有一种常用的语法或方法,基于 Dan North(敏捷教练和 BDD 创始人)的工作 4:

Scenario  1: Title
Given [context]
And [some more context]...
When  [event]
Then  [outcome]
And [another outcome]...

4 dannorth.net/introducing-bdd/

例如:

Scenario  1: Buying an Ice Cream to cool off on a hot day
Given I have ten pounds in my pocket
When  I purchase a Choc Ice for two pounds
Then  I have only eight pounds left
And have a Choc Ice

对于正在开发的任何功能,可能会有多个场景需要测试。这些场景使用常见的语法——Given, When, Then 来定义。使用通用语法编写验收标准可以简化测试的编写,并帮助大家达成对活动范围的共同理解。Dan North 多年前提出了这种以故事为驱动的方法,并且从那时起,许多测试框架(如 Cucumber)都采用了这种语法。

我们的运维工具 Python 库的 BDD

几年前,我参与了一个公共部门的项目。我是团队的一员,帮助他们自动化一些操作能力。以前,他们有团队成员手动配置虚拟机(VM),而且这种方式无法重复使用。我的工作之一是为团队构建一个命令行界面,帮助自动化创建和添加团队成员(用户)及其角色到 Free IPA 服务器(Red Hat 身份管理)。下图显示了添加现有用户和删除用户的一个 BDD 场景模板:

图 7.24:一个 BDD 场景

团队中的架构师是 BDD 写作方法的坚定支持者。我们所有的验收标准都是按照这种方式编写的,这让我们更好地理解自己所做工作的范围。当我与另一位工程师配对时,我们会将按照 BDD 语法编写的验收标准作为起点。我们直接从 Jira 导入语法,通过 Python Behave 来搭建测试用例。对于我们这些工程师来说,这使得功能编码变得轻松。我们已经得到了规范,所以可以轻松地实现我们的代码以通过测试。

BDD 可以帮助工程师更好地理解功能的背景。它还帮助弥合与业务专家和产品负责人的对齐差距:

产品负责人看到他们的想法以代码形式呈现!

当我们指导团队时,我们鼓励他们利用冲刺评审或展示活动,作为展示他们所有工作成果的机会。包括设置和改进测试自动化。

我从我曾合作的几个团队中注意到一个特别的现象,当产品负责人或业务领域专家第一次看到 BDD 自动化运行时,他们会回想起一两周前的冲刺规划会议,当时团队正在确认将要进入冲刺的故事的验收标准。通常,这些标准会使用 BDD 语法书写,输入的内容则来自产品负责人或业务专家。

当他们在冲刺评审或展示会上看到测试自动化套件运行时,他们会看到控制台显示已自动化的测试,相同的思路、相同的指令,以及相同的业务逻辑都已被编码。

BDD 打破了技术人员与业务人员之间的壁垒。

Example Mapping

由 Cucumber 首席执行官 Matt Wynne 提出的 Example Mapping,5 是另一个非常有用的工具,值得添加到工具箱中。再一次,很多这些实践实际上是非常有用的方式来阐明并推动对话。在这种情况下,Example Mapping 主要用于在编写故事和创建验收标准时推动共同理解。我们认为它对于帮助团队编写行为驱动的测试非常有帮助。这个过程简单,且只涉及四种颜色的便签:

  • 黄色:用于故事本身(作为示例图的标题)

  • 蓝色:用于与故事相关的具体规则

  • 绿色:用于规则示例

  • 红色:用于讨论中出现的问题或未知项

5 cucumber.io/blog/bdd/example-mapping-introduction/

图 7.25:Example Mapping

首先选择一个故事并将其写在黄色便签上,将其放在示例图的顶部作为标题。在其下方的横向行中,开始写下蓝色便签上的业务规则。蓝色业务规则下方,创建一列绿色便签,列出这些业务规则的具体示例。这些示例可以是相对不太结构化的朋友标记The one where... 示例,也可以是完整的 Given, When, Then 标准。

当在讨论中出现对单个示例或整个业务规则的误解时,添加红色便签并写上问题。

当有足够的示例,大家都能理解时,它们可以被重写为自动化测试和验收标准。

实际应用中的 Example Mapping

在世界卫生组织的驻地项目中,我发现这种做法非常简单易用,但它是一个很好的工具,用来阐明故事的范围,并就我们将编写的验收测试达成一致。

我们使用 Event Storming(稍后会详细介绍)来建模新用户加入应用程序的过程。我们有一个命令,写着提交相关的兴趣话题,并将其添加到我们的待办事项列表中。我们选择这个命令是为了更好地了解用户感兴趣的事物,从而更好地为他们提供推荐。

我们使用示例映射来分解这个故事,首先编写一些规则。此时我们并没有严格遵循统一语言,因为我们知道团队会在之后将这些规则转换为 BDD 风格的语法。

图 7.26:示例映射示例

团队内部的转换引发了一些关于活动范围的误解。开发人员更关心边缘情况,比如,如果页面被刷新或返回会发生什么? 我们能够将这些问题作为示例映射的一部分捕捉下来,并添加了新的规则和一些示例。随后,团队能够将这些示例转换为 BDD 语法。

与所有这些实践一样,与正确的人进行这样的对话并捕捉示例意味着我们获得了良好的团队对齐,并能够将其转化为验收测试,并作为我们开发工作流的一部分实施。

你可以通过访问openpracticelibrary.com/practice/example-mapping页面,了解更多关于示例映射实践的内容,并进行协作。

非功能测试

虽然测试的重要性不容小觑,但关注其他可能进一步揭示我们代码质量的指标同样至关重要。例如,你怎么知道你的测试覆盖了所有代码?如果我的测试都通过了,但应用的响应时间很差呢?传统的单元测试和集成测试可能捕捉不到这些问题。我们可以使用一些工具来识别代码库中的原因和问题,更重要的是,尽早修复这些问题,而不是等到更晚。

代码覆盖率报告器易于实现,通常与许多现代测试框架一起捆绑。其思想很简单:在运行我们的测试用例时,代码库会被监视。一旦测试执行完成,就会生成一份报告,显示哪些代码行已经被执行,哪些地方存在空白。这些报告对帮助团队识别改进空间非常有用,但不应被视为绝对真理。像所有这些工具一样,覆盖率报告是有可能被欺骗的,但优秀的开发者和同行评审流程应该能发现这些问题。通常,如果团队的测试覆盖率还不高,他们会努力提升覆盖率。将这些报告带到回顾会上,对团队来说是一个很好的分析工具,并可以设定更高的目标。一些进取心强的团队甚至会将覆盖率低于某一阈值的情况视为管道不稳定,导致失败!

静态代码分析是另一个可以提供单元测试未能检测到的代码库洞察的工具,它创建了代码应如何编写和执行的规则。如何编写代码的一致性对非编译语言(如 JavaScript)尤为重要。JavaScript 在不同浏览器中的行为也有所不同,因此编写一套规则,比如所有字符串都使用单引号而非双引号,有助于避免任何意外的行为。如果我们将这些规则编码化,那么我们不妨确保每个人都遵守这些规则,因此将它们添加到我们的流水线中!编码标准在多团队合作的环境中也非常重要。如果代码库符合标准结构和设计,也会使其维护和更新变得非常简单。

性能测试 Sam 的代码

大约在 2014 年,我们为一家零售公司工作,构建了移动后台服务及一些相关的自动化工具。这一服务层负责聚合来自不同后台系统的数据,例如产品列表、类别和评论。这些服务还进行了一些非常基础的数据处理,以使数据负载更加适合移动端使用。适配器响应的及时性至关重要,因为与现代移动网络相比,移动端的延迟较高,而快速的 API 响应时间能够带来显著的差异。

我们的团队始终意识到,我们应该跟踪适配器响应所需的时间。我们知道组织会在程序结束时进行传统的负载测试,但我们不想等到那时才发现问题。我们认为,一定有一种方法可以持续验证我们对适配器层所做的更改,以便及早发现任何性能下降的问题。

我们在 Jenkins(我们的自动化工具)中创建了一个夜间任务,每晚检查适配器的性能。这是一个相当简单的任务,模拟了成千上万的并行请求到 API。通过此任务,我们绘制了每天服务的响应时间,并通过 Jenkins 报告了这些数据。这使我们能够为正常响应的基准值创建一个标准,并在值超出预期范围时让任务执行失败!

图 7.27:自动发现性能瓶颈

有一天,我们来到办公室,发现我们的夜间任务变成了红色!我们心想,太好了,让我们停止所有正在进行的工作,检查一下自昨晚以来系统发生了什么变化。快速检查系统中所做的更改后,我们发现团队成员 Sam 尝试提交了某个数据转换函数的新逻辑。Sam 在一个循环内嵌套了另一个循环,导致了代码执行时间的剧增。这是我们的传统单元测试没能捕捉到的问题,因为逻辑本身是正常的,只是计算所需时间变长了。

我们迅速响应并立即修复了问题。如果我们没有发现这个性能瓶颈,可能需要几个星期甚至更长时间才能意识到发生了什么。我们本可以在这段问题代码上继续构建更多功能,这会使得以后更难以拆解。

能够像这样快速响应反馈至关重要。我们并不是说产品上的大负载测试不必要,但这个简单的自动化任务通过捕捉这个问题,给我们带来了大量价值。它编写和维护成本低,而且可能比我们其他方式更早发现了这个错误。几周后,Sam 再次尝试编写代码,我们遇到了类似的失败。

图 7.28:更多轻量级自动化性能测试

不过,别担心——Sam 是我们的一位好朋友,现在已经不再从事写代码的工作,他转行做了技术销售角色。我们工作中没有“归咎文化”,我敢肯定,如果 Sam 在讲这个故事,他会说是我们其中一个人提交了那段愚蠢的代码。我让你自己决定是谁。

还有许多其他类型的测试,我不会一一列出;如果要列完这些内容,我们得再写一本书来装下它们。我们将在下一部分中详细探讨我们软件的非功能性特征,发现它

关于测试的一些最终思考

我们无法低估测试在快速交付功能中的重要性,特别是自动化测试。无论你是遵循测试金字塔还是其他某种范式,这取决于你——只要记住,一切都关乎交流。如果 TDD(测试驱动开发)不适合你,确保仍然进行业务与技术团队之间的对话,利用示例来识别合适的测试。我们常用的方法是使用 BDD(行为驱动开发),因为它能将业务与技术的世界结合起来。

最后,我们并不是说世界上没有独立 QA 团队的空间。绝对不是,关键是自动化所有事情并尽早获得反馈。如果 QA 是你公司中的一个独立职能,而且只在上线前几周才参与,那么这是一个问题。将 QA 的技能带入团队,并将这个能力“左移”到团队中,这样他们可以更频繁地获得早期反馈。

新兴架构

希望不是一种设计方法。6

我们如何知道我们的架构是好的?什么是好的架构?好的架构能否衡量?你是否曾经操作、支持或修复过一个架构不良的系统?

识别什么是糟糕架构可能会更容易:

  • 一个不稳定且不可靠的系统,经常以未知和意外的方式失败。

  • 从用户的角度来看,系统很慢。

  • 它无法随着用户或负载的增加而良好扩展。

  • 它很难升级,因为一个小的变动就需要重新部署整个系统,这既慢又昂贵。

  • 它依赖于客户端或其他系统,且不能轻易修改或改变,除非同时改变其他系统。

  • 它有很多复杂的业务功能,这些功能被埋藏在数据库中,可能涉及触发器,且由于复杂的数据库架构,在修改时可能会产生未知的副作用,难以轻易改变。

  • 这个系统难以管理和运维。

6 Michael T. Nygard, 《Release It!:设计和部署生产就绪的软件》

列表还在继续。

在一线软件支持和运维中,没有什么比在凌晨 3 点被持续呼叫去扑灭一个反复发生的复杂系统崩溃问题更糟糕的了,恢复服务后,根本原因分析却指向了一个复杂的失败架构——除了重新设计或重写软件外,没有其他简便的修复方法。

许多深层次的软件问题源于不明智的架构决策。很多时候,这些决策在产品开发的初期就已做出,当时大的架构已经设计好并固定不变,可能是石头、混凝土,甚至泥巴做成的。通常,系统架构师会向开发团队展示他们的架构杰作,并从此致力于确保问题适配架构,而不是架构适配问题。

开发过程继续使用这座“世界第八大奇迹”的架构,最初一切进展顺利,甚至可能在生产环境中运行。但有一天,业务部门要求一个与架构方法不太契合的功能,然后为了完成这个变更,付出了巨大的代价。

在开发过程中,技术决策往往必须基于最好的意图做出,但此时的信息往往不完整或过于稀疏。团队中经验更丰富的成员在设计讨论中的智慧往往是非常宝贵的。那些在之前项目中经历过糟糕架构的人,绝对值得倾听和学习。然而,正如我们在《锤子》章节中看到的那样,这也可能存在一个弊端。

作为产品团队,我们必须愿意且能够在需求发生重大变化或系统故障告诉我们现有架构的极限时,适应并改变架构。

注意

一般来说,最好在合理的范围内尽可能晚些做出架构和技术决策,以便为做出决策的人提供更多的信息。

新兴架构是指拥有足够的架构,以便产品开发能够持续推进,同时又足够灵活,能够在获得更多信息时做出架构调整。

多年来,已经有大量优秀的书籍和文章讨论了什么是良好的架构和模式。我们个人的推荐是Martin Fowlermartinfowler.com/books/)、Chris Richardsonmicroservices.io/)和Sam Newmansamnewman.io/books/)的作品,但还有很多其他优秀的作者。

现场观察

在本节中,我们的目的是概述一些我们遇到的反复出现的模式/方法,包括好的和不好的。虽然这些并不新鲜,但我们认为在这里提出来是有益的。

每平方米的模式

应用的软体模式数量从来不是一个好的质量指标。软件模式是解决特定问题的著名且可重用的模板。不要犯这样的错误:假设一个包含大量软件模式的系统比那些包含较少模式的系统更优越。

预期故障并应对

灾难性的系统故障显然是不可取的。在面对子系统故障时,一个从未完全停止工作的系统通常是更可取的。它可以在子系统故障时优雅地恢复,并且即使某些组件不可用,仍然可以支持部分功能。我们可以明智地应用架构模式,例如,使用水密舱来减少单个故障造成的损害。与容量、延迟或“看起来有点慢”的问题相比,绝对故障通常更容易应对。

在审查架构时,特别是分布式架构时,我曾从一位经验丰富的架构师那里收到一条非常宝贵的建议,那就是——总是要问这个问题:如果这个组件发生故障或变慢,会发生什么?如果这个问题没有一个好的答案,那么可能需要更多的设计工作来防止故障场景的发生。

锤子

我们多年来看到的一个常见模式或行为是黄金锤子模式,换句话说,如果你手里只有一把锤子,那么所有东西看起来都是钉子。这个现象更正式的名称是“工具法则”。

所有开发人员都有他们最喜欢的工具和架构方法。例如,作者们是反应式流式架构(Mike)、异步事件驱动消息传递(Noel)和任何与 Node.js 或表情符号相关的技术(Donal)的粉丝。这里的风险是,你自己的偏好可能会引导你走上一条最终是错误的架构道路。

如果你发现自己在讨论一个业务问题的前 10 到 20 秒时,忍不住插话说,“哦,产品、架构或工具 X 可以帮助解决这个问题”,那么面对现实吧,你可能正准备拿出你的金锤子。

简历驱动开发

很多时候,我们参与了关于某种技术在客户解决方案中使用的讨论,而这项技术要么不合适,要么根本无法解决他们要解决的问题。我们经常发现,有人引入了这项技术,因为他们急于学习它,结果它从一个技术实验或试探变成了核心技术组件。技术实验本身没有任何问题,应该鼓励这种做法,但我们应谨慎,确保所选的技术不会导致死胡同或变成一个找不到问题的技术解决方案。我们看到的一些技术包括服务网格等,如图 7.29所示:

图 7.29:采用街区上最酷的技术

戴上不同的帽子

软件架构必须从多个角度良好运行,而不仅仅是从设计和构建的角度。不同的团队/角色会有不同的视角,例如部署、测试和运维管理。一种好的软件架构将尽量解决尽可能多的这些问题。

社交媒体驱动开发 —— 跟上潮流

像 Google、Amazon 和 Microsoft 这样的公司生产并利用一些令人惊叹的技术和方法。这些技术通常集中于这些超大规模公司所面临的问题规模。我们大多数人从未与如此复杂或用户需求如此庞大的系统打交道,因此应该谨慎判断技术,依据的是你的特定业务需求,而不是那些流行的孩子们正在使用的技术。我们经常在单体与微服务的讨论中观察到这一点。两者都是非常相关且有效的软件架构方法。它们各有优缺点,但正确的做法是问自己,什么对业务和客户最有价值。

良好的服务设计

良好的服务设计很难实现。最终,我们应该始终降低应用程序的操作负担。我们可以通过设计应用程序来实现这一点,从而最小化任何给定应用程序的变更成本。现代应用程序通常被拆分为不同的组件或服务,暴露方法或功能。良好的系统架构的核心是服务设计。这通常基于领域驱动设计DDD)等实践,DDD 的核心是理解业务问题,并在团队中以明确无误的方式传达这种理解。属于同一业务领域的服务会被归为一组,例如我们在《PetBattle V2》中的比赛服务,或我们在爱好者应用中的猫服务。我们可以通过遵循以下两条原则来实现良好的服务设计:

  • 松耦合性:当对一个服务的更改不需要对另一个服务进行更改时。通过设计松耦合的服务 API,我们可以轻松地部署服务更改。服务的内部设计可以完全改变,而不会影响 API 的使用者。

  • 高内聚性:我们希望相关的系统行为聚集在一起,而无关的行为则分开。我们的订单管理系统与我们的运输和交付系统是独立的。这减少了开发人员的认知负担,因为相关的系统功能聚集在一起。这里通常存在设计上的张力,例如在定义相关的业务系统领域(比如使用 DDD)和可重用的技术功能(如库或 API,可能跨多个系统)之间的平衡。

技术设计集体思维

我们已经讨论了一些设计系统时的反模式。另一条建议是,技术领导者应设定方向,而不仅仅是描述应该做什么或已经做了什么。实现这一点的一种方法是进行平行思维练习,让每个人同时并协作地贡献他们的想法,而不是仅仅跟随团队中最高级别成员的思维方式。重点是未来可以做到什么,而不是当前是什么,以帮助设计前进的道路。这不是关于谁对谁错。

人力资源和时间是你最宝贵的资产

在知识型工作中,人类通常是最昂贵的资源。因此,努力减少琐碎或无差异的手工劳动是有意义的。这是一个永无止境的趋势,即自动化一切,从而为我们的产品带来更好的质量和反馈。

信息泄露 – 数据中心性至关重要

在技术领域,我们被大量数据淹没。但我们是否充分利用了在我们的应用程序和系统中可用的所有数据?在进行架构设计时,我们必须仔细考虑应用程序和基础设施中可用的数据的数量和质量。通常,工程决策和取舍必须做出,将数据处理靠近边缘设备,仅仅因为将所有数据发送回中央处理核心在物理上是不可能的,原因可能是带宽或延迟限制,或者移动这些数据的成本过高(想象一下云计算!)。因此,在设计系统时,考虑以下情况:

  • 数据在捕获过程中丧失了完整性

  • 数据根本没有被流式传输或存储

  • 数据无法供其他用途访问

  • 数据根本没有被分析

  • 数据没有被传输,且保持隐藏状态

  • 数据未被用于决策

我们常常忘记思考丢失的数据量——丢失的数据可能是我们业务中巨大机会的损失源。无论是在云计算、物联网、工业应用,还是在我们的手机上处理数据的移动网页用例中,这种情况都会发生。

一些关于架构的最后思考

架构在构建软件系统时是一个至关重要的考虑因素。做对架构是一个持续的平衡过程,需要审视当前和潜在的未来需求,并评估架构如何适应这些需求。

在任何情况下,都会需要一定程度的前期架构和设计工作,并且应该伴随着灵活性和诚实,以确保初步架构能够随着不确定问题的答案被发现,以及随着更多信息被纳入对当前问题的集体理解,而发生变化。能够在整个产品生命周期中不断改进架构,是另一个重要目标。

当我们谈论重大的前期决策时,脑海中浮现出以下普鲁士陆军元帅的著名名言:没有任何计划能经受住与敌人接触的考验。 – 赫尔穆特·冯·莫尔特克。或者,换个现代的说法:每个人都有计划,直到他们被打了嘴巴。– 麦克·泰森

灵活性、适应性和改变的意愿是成功应对动态环境的关键特征。总而言之,团队在应用程序扩展并适应变化时需要考虑许多架构因素。通过在业务需求变化时进行实验和调整架构,他们将更好地提供承诺的服务 SLA,并最终使用户体验达到最佳状态。

结论

在本章中,我们继续探索了技术实践,为我们能够以快速和统一的团队交付打下坚实的基础。通过使用“大局观”技术,帮助我们对交付流程有共同的理解,我们进一步识别了测试方法,并探讨了如何以更加开发者友好和业务友好的方式将业务与验收测试连接起来。

图 7.30:在基础上添加更多的技术实践

在我们探索带领我们走向新兴架构方法的课程时,我们也意识到,很多的“魔力”来自于首先进行对话。将整个 IT 组织当作一个卫星来对待是行不通的;我们必须创造一个能让我们整体成功的环境。关键是要将拥有知识的人和拥有权威与权力的人聚集在一起。

在后面的章节中,我们将更深入地探讨 PetBattle 的技术实现。

为了结束这一部分,我们已经建立了文化、领导力和技术卓越的坚实基础。我们已经建立了包括以下内容的原则和实践:

  • 自主性、精通度和目标感

  • 心理安全

  • 社会契约、停机事件、实时回顾、团队认同和信息辐射

  • 领导意图与团队授权

  • 优先级滑动条

  • 团队空间

  • 一切皆代码

  • 容器

  • 持续集成、持续交付和持续部署

  • 测试自动化

  • 新兴架构

图 7.31:文化和技术实践的基础

我们的基础很强大。随着我们在其上构建产品,它将需要持续的培养和加强。然而,我们已经准备好启动我们的第一个产品团队。在下一章中,我们将探索一些可以用于持续产品发现的实践。

第三部分:发现它

第二节,建立基础中,我们为我们的第一个团队建立了初步的基础。这包括一种能够促进心理安全、支持团队成员自主性、掌握能力和目标感的文化。它还包括我们为使团队能够自信地开始应用程序开发而引导的技术基础,给予他们保持持续交付的最佳机会,并避免陷入早期技术债务。

现在我们将围绕 Mobius 循环展开工作,从发现开始:

图 8.0.1:发现循环 — 场景设置

第八章,发现“为什么”和“谁”中,我们将探讨团队和利益相关者如何合作发现结果,并理解支撑应用程序开发的共同目标。为了帮助导航这个过程,我们将使用 Mobius 的发现循环:

图 8.0.2:发现循环

当我们处于发现循环时,我们识别并使用那些帮助我们回答“为什么”问题的实践——我们为什么要开始这段旅程?我们试图解决什么问题?我们为谁解决这些问题,又知道他们什么?我们的伟大想法是什么?我们还使用发现循环中的实践来帮助我们识别和设定可衡量的商业和客户目标结果。第八章,发现“为什么”和“谁”,专注于循环的上半部分,帮助回答为什么和谁的问题。

第九章,发现“如何”中,我们将把重点转向一些可以帮助我们开始构建解决方案的实践。我们解决问题的方法是渐进式的,并以实验为驱动。特别是,我们专注于一组通过可视化和建模技术实现共同理解的实践。本章中不会浪费任何一个便签!

第十章,设定结果,将重点讨论这些结果,它们是什么,我们如何推导它们,以及我们如何使用它们。在本章结束时,您将能够清晰地表述和定义团队的目标和交付的结果。

第八章:8. 发现“为什么”和“谁”

软件交付团队往往直接投入到交付功能中,专注于输出和交付物。他们没有花足够的时间去理解他们试图解决的业务问题以及他们想要为谁解决这些问题。

知识往往在业务流程和领域知识方面存在分裂,通常这些知识都集中在一个人的头脑中。团队内部以及不同利益相关者群体之间的知识错位,导致了错误的决策、误解和不正确的假设,最终团队交付了错误的解决方案,解决了错误的问题。

图 8.1:知识分布

这就是为什么我们从使用一系列探索实践开始,以便使所有团队成员和利益相关者达成关于我们为什么要做这个?为什么要投入时间?我们为什么选择这个,而不是做别的事情?的共同理解。

此外,我们需要探讨我们正在解决的问题以及我们为谁解决这些问题。在开始为他们定义目标成果之前,理解这些人、他们的痛点以及改进的机会是至关重要的。

我们将在本章探讨的探索实践非常依赖于全员参与的方法,并且需要包括所有相关利益相关者。收集广泛的观点,并鼓励来自公司各个职能领域的不同人员群体之间的参与和协作,有助于实现对齐,并形成一个致力于共同解决我们要解决问题的团队。

我们有意愿包括技术和非技术人员。促进技术侧人员与非技术或更偏向业务人员之间的对话和对齐的实践,特别具有强大的力量。

探索实践的使用并不打算做到面面俱到——我们使用这些实践来获取足够的信息,以使团队成员达成共识,获得共同的理解,并建立足够的信心,以便开始进行早期实验或其他活动,这些活动反过来推动更多的学习。这不会是唯一一次进行探索并使用这些实践的机会——实际上,我们的目标是定期回顾并基于交付过程中的学习来进一步推进探索。

在本章中,我们将涵盖以下主题:

  • 北极星:一种帮助达成“为什么”以及整体目的对齐的实践。

  • 影响图谱:一种帮助达成“谁”以及我们希望对其产生可衡量影响的实践。

  • 以人为本的设计:一组有助于与用户和其他重要利益相关者建立共情的实践。

让我们看看我们的第一个探索实践,它有助于使每个人朝着一个方向前进——北极星。

北极星

通过设定北极星,可以为产品创建共同的目标。北极星框架是一种通过识别一个称为北极星指标的单一关键指标来管理产品的模型。1 根据提出这一术语的 Sean Ellis 的说法,它是最能捕捉产品为[其]客户提供的核心价值的单一指标

Amplitude2 是一家数字分析公司,免费发布了关于北极星框架的优秀手册——它是什么,为什么你可能会使用它,以及如何使用它。手册列出了关于北极星指标的以下七个检查点:

  1. 它表达了价值。我们可以理解它为什么对客户很重要。

  2. 它代表了愿景和战略。我们公司的产品和商业战略都体现在其中。

  3. 它是成功的领先指标。它预测未来的结果,而不是反映过去的结果。

  4. 它具有可操作性。我们可以采取行动来影响它。

  5. 它易于理解。它以简单的语言表述,非技术性合作者也能理解。

  6. 它是可衡量的。我们可以为产品配备工具来跟踪它。

  7. 它不是虚荣指标。当它发生变化时,我们可以确信这个变化是有意义且有价值的,而不是仅仅让团队感觉良好的东西,尽管它无法预测长期的成功。

1 growthhackers.com/articles/north-star-metric

2 amplitude.com/

北极星框架与 Mobius 非常契合。在发现循环(本节)中,我们专注于定义北极星,并使所有利益相关者和团队成员达成一致,理解为什么选择了这个北极星指标。在下一节中,我们将探讨“选项枢转”,这完全是关于我们可以采取的可操作性研究、实验和实施,以影响北极星指标。在第五节,交付它中,我们将探讨交付循环,包括衡量和学习的实践——我们用于追踪产品成功与北极星的工具:

图 8.2:北极星

在发现循环中使用北极星实践,可以帮助团队朝着正确的方向前进,并实现团队成员与利益相关者之间的对齐。它还可以帮助团队在没有北极星的情况下进行课程调整或重新对齐。将北极星的信息张贴在墙上,确保团队成员和相关利益方都能清楚看到,可以帮助保持专注,并引导团队朝着北极星前进。

我们在前一部分中解释的许多做法,旨在建立文化和协作的基础,这些做法将有助于定义和使用北极星指标的过程。例如,制定社交契约和回顾性实践,帮助团队在贡献战略性重要信息时获得自主性和心理安全感,将有助于促进更广泛的团队对这一指标的共同拥有。促进工作可视化的做法将有助于分享、检查和集中精力实现北极星目标。

所以,北极星是第一个发现实践,突显了建立文化基础的必要性。如果存在开放的文化,人们感觉心理上安全,可以互相贡献和协作,那么其效果会更好。我们将看到这一模式持续下去。

为了帮助找到北极星,我们使用一个简单的画布,类似于 Amplitude 指南中提供的画布:

图 8.3:来自 Amplitude 的北极星指南

使用北极星框架的团队识别出一个单一、重要的指标和一些关键的输入。产品团队致力于影响这些输入,这些输入反过来推动该指标。北极星是可持续增长的领先指标,并充当产品与更广泛业务之间的联系纽带。

让我们来看一个北极星指标的例子,以及它是如何从输入中推导出来的。OpenTable 是一个流行的餐厅预订平台。发现不同领先输入的推荐方法是考虑广度、深度和频率;例如:

  • 广度:有多少用户在参与?一个衡量这一点的输入指标可以是月活跃用户MAUs)数量——即至少在网站上进行过一次搜索的用户。

  • 深度:用户的参与程度如何?一个衡量这一点的输入指标可以是每次搜索中的预订转化率。

  • 频率:用户参与的频率如何?一个衡量这一点的输入指标可以是用户搜索之间的平均时间间隔。

这些都是我们希望在产品设计决策中推动改进的输入,但我们需要一个指标,这个指标是代表产品价值的关键指标。OpenTable 的北极星指标是每月就座客户的数量——换句话说,每月支付客户使用餐桌的次数。如果这一指标很高,我们的产品就成功了,推动着良好的业务发展。这是唯一一个不会撒谎的指标,也是我们应该不懈关注的成功标志:

图 8.4:OpenTable 的北极星画布

这个画布还捕捉到北极星指标对业务的中长期影响;在这种情况下,每次预订座位时收取的固定费用为 OpenTable 创造了收入。

让我们回到 PetBattle 团队,看看他们是如何找到他们的北极星指标的。

PetBattle 作为一项业务

PetBattle 最初是作为一个有趣的创意,用来实验新技术。然而,由于猫是互联网用户最喜爱的消遣,网站很快就变得病毒式传播。由于它是实验性质的,因此并没有以最健全或专业的方式构建。这导致由于访客数量过多,网站崩溃——这是一场“完美”的灾难!恶意用户在互联网上上传不当内容,必须手动删除,因为该站点旨在适合家庭使用。

因此,创始人决定尝试在保持最初简洁方法的同时实现网站的货币化。

PetBattle 的创始人决定在建立跨职能产品团队时体现丹·平克(Dan Pink)书籍《驱动力》(Drive)的精神,因此首先建立了一个文化基础。每个新团队成员都会为 PetBattle 的文化增添他们独特的视角、个性和动机。为了留住人才并增强这种优秀的文化,PetBattle 必须让他们感觉到能够实现自主、精通和目标。

PetBattle 的员工需要感受到与产品愿景的连接。他们举行了一个北极星工作坊,将所有员工与产品的愿景连接起来,并确定他们的北极星,以便他们可以定期检查和衡量其进展。

该工作坊持续了两个小时,包括以下内容:

  • 关于北极星原因的开场讨论

  • 确定 PetBattle 用户正在玩的游戏

  • 关于什么是好的(以及不好的)北极星的讨论

  • 关于北极星的结构及其输入的讨论

  • 查看其他产品的北极星指标

  • 起草 PetBattle 的北极星

Amplitude 将数字产品分为三类游戏:关注度游戏、交易游戏和生产力游戏。关注度游戏侧重于顾客愿意花多少时间在产品上。交易游戏侧重于顾客在产品中的交易次数。生产力游戏侧重于团队成员完成工作的效率和效果。在经过一些讨论后,关于 PetBattle 是更偏向关注度游戏还是交易游戏,团队最终决定它是关注度游戏。最终,顾客希望花更多时间享受 PetBattle 产品,目标是保持他们的参与度。

在收集 PetBattle 北极星候选项时,员工首先采用了静默头脑风暴技术,然后与其他人配对讨论他们的想法。两对人员形成四人小组,将他们的集体想法合并。最后,整个小组将所有想法汇聚在一面墙上。这种引导方法叫做 1-2-4-全员(1-2-4-all),是一个出色的解放结构(liberating structure)示例。

北极星的候选指标包括以下内容:

  • 网站访客数量

  • 已参与的站点访客数量(例如,至少为一只宠物投过票)

  • 在网站上停留的时间

  • 增强对(最终)广告商的跟进

  • PetBattle 比赛的数量

  • 上传到网站的宠物数量

  • 网站的跳出率

  • 网站的粘性率(每月回访三次及以上的用户百分比)

  • 使用移动应用的百分比

在所有员工的想法汇聚过程中,关于什么才是真正的成功,开始出现一些精彩的对话,特别是关于 PetBattle 的未来。是的,他们希望更多的人访问网站,也希望最终能有广告收入,但似乎大家的共识是,推动 PetBattle 向下一阶段发展的关键指标是宠物上传数量。大多数其他指标都是可以帮助提升上传数量的输入因素。如果用户增加,最终会有更多用户参与。随着更多的参与,最终会有更多用户希望进一步参与和互动。参与的中长期影响将推动广告收益的提升。

如我们在 Red Hat 开放创新实验室中所展示的那样,我们可以在产品和服务型组织中使用北极星指标。

我们在开放创新实验室的北极星

开放创新实验室是一家专业服务机构。我们经常被问到,是否可以将产品思维应用到服务型组织中。

答案是肯定的!我们在本书中讨论的所有实践都应用到自己身上,以培养我们业务的产品思维。

这也包括北极星指标。我记得我们在制定实验室概念时的团队会议,讨论了我们的北极星指标可能是什么。我们的客户来自商业和公共部门。我们的整体愿景是通过利用开放技术、开放流程和开放文化,解锁客户的最大潜力。我们的业务价值链涉及与大量客户的售前合作,了解痛点和优化机会,识别适合参与我们驻场计划的合适使用案例。我们组织会议来确认、发现、规划、互动和交付这些服务。所有这些都有各自的指标。

使用类似于上面 PetBattle 故事的协作方式,我们得出了实验室的多个合格使用案例作为我们的北极星。像北极星一样,这个关键指标变得越来越明确,我们可以用它来评估我们业务的健康状况。当我们确定了一个使用案例时,我们对我们的客户非常有信心,认为实验室的驻场计划非常适合他们的业务问题。我们已做了充分的调研,因此我们集体认同它将带来的价值。我们对销售和交付的参与有很高的信心。

知道、理解并对你的北极星目标达成共识,为进入下一个发现实践提供了极好的基础,该实践探讨了战略目标与人员、可衡量影响和交付物之间的联系。这一实践被称为影响力映射。

影响力映射

影响力映射是一种帮助组织战略领导者与产品交付团队之间建立共享理解和协调的方法。戈伊科·阿兹奇首次在 2011 年的小册子中记录了这一技术,3 它为那些希望主持工作坊的人提供了一个极好的指南。

阿兹奇将影响力映射定义为一种轻量级、协作的规划技术,适用于那些希望通过软件产品产生重大影响的团队。它基于用户互动设计、结果驱动的规划和思维导图。影响力映射帮助交付团队和利益相关者可视化路线图,解释交付物如何与用户需求相连接,并沟通用户结果如何与更高层次的组织目标相关联

在影响力映射练习结束时,你应该有:

  • 对目标或问题陈述的共享理解。

  • 用户和利益相关者必须发生(或不发生)的人类行为变化的清单,以确保你的项目能够成功。这些就是该技术名称中所提到的影响

  • 可选地,列出为了实现上述影响而可能交付的项目交付物。

  • 一种优先排序范围的方式——一个或多个交付物——和/或影响。

  • 呈现前述信息的图形。

3 www.impactmapping.org/

使用这种方法可以让我们转向实验性和假设驱动的功能开发模型——一种能够快速验证或推翻假设的方式。也就是说,我们假设构建一个交付物将对某个特定参与者产生可衡量的影响,从而帮助实现战略目标。运行这些实验以验证或推翻假设所获得的学习是推动我们持续改进,以交付重要成果的动力。

影响力映射使我们能够聚焦于业务目标,并引导产品团队朝着交付业务价值而不仅仅是交付更多功能的方向前进。这是一种流行的做法,也是我们在大多数合作初期都会采用的,因为它提供了一个非常简单、以人为本的发现过程。你不需要复杂的工具或软件来执行它——一些便签、笔、一片空地,以及最重要的,人们,都是你所需要的。最终的产物是所有对话的图形化思维导图。

如何做到这一点呢?首先,阅读阿兹奇的小册子。它非常容易理解,并会引导你开始。接下来是我们在多个合作项目中使用影响力映射的经验。我们将把它分解为组成影响力映射的四个部分:

  1. 目标

  2. 参与者

  3. 影响

  4. 交付物

对于这些组成部分,我们将进行简要概述,然后通过我们的 PetBattle 案例研究来查看他们的影响力图是如何发展的。

构建影响力图可以通过与一群共同工作的人,在大墙面上进行。也可以通过使用协作工具,如 Miro、Mural、Google Slides 或 PowerPoint,进行虚拟操作。我们提供了一份 PDF 模板,可能对你起步时有所帮助。

图 8.5 影响力图数字模板

接下来,我们将汇总影响力图,展示如何使用它来形成假设声明,并驱动实验。

从 WHY 开始——目标

在本书的开头,我们说过,我们总是要从“为什么”开始。我们为什么要做这个?我们为什么在这里?通过合作、对齐并达成一致,确立目标就是在做这件事——从为什么开始。

影响力图的第一部分通常是最难的。利益相关者和团队成员应该从公司可衡量的成功角度来框定目标,而不是从技术解决方案出发。作为技术人员,我们往往专注于“做什么”,却忽略了“为什么”。

通过专注于功能 A 为何重要,我们可以将工程实践和优先事项与公司成功联系起来。目标应该是可衡量的,这样我们才能量化我们是否成功。设定目标时,团队的关键输出是达成对为何要启动某项倡议的共同理解和一致性。

目标可以使用SMART首字母法则来构建:

图 8.6:SMART 目标

  • 具体:模糊或易被误解的目标成功完成的机会较低。

  • 可衡量:我们如何知道自己已经达成了目标?目标应该是可衡量的。

  • 可实现:我们是否能现实地完成我们的目标?我们是否具备所需的技能和资源?

  • 现实:在时间和资金的限制下,我们能完成我们的目标吗?

  • 基于时间:完成目标需要多久?

设定目标往往是一件难以达成共识的事情。通常,我们对于作为一个组织,在产品或业务领域的方向会有不同的看法。然而,明确的目标能逐渐渗透到公司各个层级,进而为每个人创造共同的目标。

让我们来看看 PetBattle 团队设定的目标。

PetBattle——目标

在我们的 PetBattle 应用案例中,我们正在从一个业余爱好者应用转变为一个可以(希望)创造收入的应用。团队分成了两组,并在白板上分享了所有的想法,反复讨论他们提议的目标陈述。两组提出了以下目标:

  1. 在今年年底前,创造 100,000 美元的广告收入。

  2. 在第三季度末,将我们的活跃用户基数增加 100 倍。

在全组讨论后,大家达成一致决定使用第一个目标,并在讨论过程中对其进行了补充和调整。因此,在我们的 PetBattle 示例中,我们将目标定为:通过现有和新用户群体,到年底实现 $100K 的收入。我们可以将目标与当前收入进行对比,目前收入为零,甚至可能是负数(如果考虑到当前的托管费用)。我们正处于亏损状态!

在一个小组内设定目标可能相对简单。那么,对于更大的团队呢?让我们来看一些反模式和获得共识与包容性的建议。

注意

在进行任何小组活动时,通常声音最大的人是最容易被听到的。 有时,房间里最资深的人会在讨论中获得最多的发言时间——这就是所谓的最高薪水者的观点HIPPO)效应。 这导致片面的观点被视为常态,而安静或害羞的人常常觉得自己的声音没有被听见。如果你刚开始制定目标,一个能让每个人都发声的好方法是使用解放结构的 1-2-4-全体实践(www.liberatingstructures.com/1-1-2-4-all/)。这是一个简单但极其有效的做法。首先设定一到两分钟的计时器,让小组中的每个人写下他们认为的业务目标。接着,进行配对,让两人小组用两分钟时间商定共享目标。然后,在四人小组中重复这个过程,最后,全体成员聚在一起,集中讨论一个共同的目标:

图 8.7:1-2-4-全体解放结构

当团队中的所有成员在目标上达成一致时,将目标写在大白板或挂图纸上会非常有帮助。拥有每个人都能点头同意的东西,并且大家都能认同这就是我们在这里的原因,这就是我们的 Why,这就是我们的共同目标,能够有效地传递信息。让我们在一张大便签上回顾一下 PetBattle 的目标:

图 8.8:来自影响图的目标

影响映射过程的下一部分是考虑谁可以帮助我们实现目标。

谁能帮助我们实现预期效果?参与者

作为一个小组,我们识别出所有可以帮助我们实现目标的人员或团队。这包括将直接或间接与我们的新应用互动的人。

这是一个很好的机会,采用发散/收敛方法来识别新角色。我们可以先提出多个可能的想法(发散性思维),然后再通过精炼和缩小范围,选出最好的想法(收敛性思维)。我们可以聚焦于那些能帮助或阻碍我们实现目标的人(发散性思维),然后集中精力在一些高价值或关键的角色上(收敛性思维)。决定哪些角色是高价值的可能需要一定的推测,但没关系;我们正在对谁能帮助我们实现预期效果进行下注。

角色不仅仅是用户组。它们也可以是特定行为类型的组合,例如外向型或深夜购物者。

让我们来看一下 PetBattle 中的角色。

PetBattle – 角色

经过一些头脑风暴和发散性思维,团队提出了以下角色列表:

  • 上传者:这些是我们 PetBattle 游戏的玩家,他们贡献新的猫咪图片,并与其他玩家以及从互联网上下载的随机猫咪进行对战。

  • 动物爱好者:这一群人喜爱猫咪、可爱的游戏,并有时间在我们的网站上消磨时光。他们通过投票来玩游戏,但不会提交自己的猫咪照片参加对战。

  • 普通观众:这些是偶然访问我们网站的人群,我们希望将他们转化为回访用户。

  • 广告商:宠物食品、玩具和配件公司,他们可以从我们活跃的用户群体中受益。

  • 家庭:由父母和孩子组成的群体,他们在网站上玩耍,并可能会创建内容。

影响图通过将这些角色与目标连接并进行可视化,开始成形。这些人群是利益相关者和感兴趣的各方,他们将帮助我们实现目标:

图 8.9:目标与角色

我们现在已经将目标与所有能够实现或阻碍目标的角色之间的联系可视化了。

注意

PetBattle 的创始人在定义初始角色时遇到了困难,错误地将用户分为认证和非认证用户组,而没有将其与目标和问题陈述联系起来。这是一个典型的例子,试图在没有明确这些分类存在的原因之前就过早地设计解决方案。识别组之间的边界非常有用。有时,你可以有粗略的分组。通过拆分这些组,你可能会发现一些只适用于这些子组的额外影响。

我们认为,影响映射练习的最大好处之一是它带来的所有对话和共享理解。在命名行为者群体时,我们发现不同的人可能有不同的理解或者使用不同的术语。在前面的例子中,我们可能会进行简短的对话来区分普通观众上传者。有些人可能只是把这些人称为普通用户。从一开始就使用统一的语言,实际上可以大大帮助软件开发过程。这对于专注于产品技术和业务方面的团队尤其重要。我们已经看到了在软件设计中区分不同用户群体的需求。

下一阶段可以说是最重要的,也是影响映射实践的关键——定义我们计划对每个行为者群体产生的可衡量影响。

我们的行为者的行为应该如何改变?这些影响是什么?

我们的行为者需要展现哪些可衡量的行为变化,以推动我们的业务成果?我们希望我们的行为者开始或停止做什么,从而帮助我们实现目标?

什么可能会阻碍我们实现目标?

让我们来看一下为 PetBattle 考虑的影响。

PetBattle——影响

对于 PetBattle,团队花了 30 分钟头脑风暴所有可能的影响。理想情况下,它们是可衡量的。团队为每个识别出的行为者提出了几个影响:

  • 上传者: 增加网站的互动性,并促进与社交网络分享成果。

  • 动物爱好者: 我们希望增强网站的易访问性。

  • 广告商: 提高广告商对网站的兴趣,并增加赞助比赛的数量。

  • 家庭用户: 减少网站的误用(不当内容)。

转到图形化影响图,我们可以可视化我们希望对每个行为者产生的影响,以帮助实现目标:

图 8.10:目标、行为者和影响

有时,你可能会发现一个影响适用于多个行为者群体。例如,在 PetBattle 中,动物爱好者、上传者和普通观众的共同影响是增加对我们广告商网站的访问量。

一个跨越多个行为者的影响可能是一个很好的下注点,因为你可以在各个群体中产生更大的影响,且可能需要更少的努力。需要注意的是,这个影响的交付物对于每个行为者可能会有所不同。

我们现在已经将目标、所有能够实现或阻碍目标的行为者以及我们希望对这些行为者产生的可衡量影响可视化了。

注意

始终以增加减少(或减少)开始影响声明。这样做,我们可以避免在专注于行为变化时,不小心进入捕捉特性或输出的领域。这也意味着影响应该是可衡量的。通过思考我们想要增加或减少的内容,我们正在量化它。我们可以现在就对影响应用某种度量(甚至是任意度量),然后未来再回来看看这种影响是上升还是下降。

目标和影响定义好之后,我们就接近了我们通常最为熟悉的方面——可交付成果。可交付成果可以是特性或特性组,其中特性是应用程序功能的一部分。我们能做些什么具体的事情来创造这种影响?我们有时会迫不及待地跳到功能,但通过将影响描述为人类行为的可衡量变化,我们被迫问自己功能 X为何重要,并将其与我们的业务目标联系起来。

现在让我们来看看宠物战斗的可交付成果。

我们应该构建什么?可交付成果

我们认为我们可以构建哪些东西,能够为特定演员群体/演员集体带来期望的影响?这是我们可以将所有希望实施的事项添加到思维导图中,并且更关键的是,将其与推动该功能的商业价值相联系的地方。

这一部分的影响映射并非本次实践的重点,我们也看到过一些团队没有完成这一部分。影响映射的核心是通过对话和合作捕捉对演员群体的影响,以便开始衡量重要的事项。我们后面会探讨的其他实践更适合定义输出和可交付成果。话虽如此,几乎不可能不去思考为实现这些影响我们可能构建的内容,因此在对话中记录下来是有益的。

宠物战斗 – 可交付成果

从一开始,宠物战斗团队就充满了可以用来构建宠物战斗应用程序和组织宠物战斗业务的创意。它们被写在便签纸上,并放置在功能创意板上。包括:

  • 社交媒体分享链接

  • 忠诚度计划

  • 玩家记分板

  • 不同动物的过滤器

  • 每日锦标赛

  • 赞助商折扣

  • 从网站打印明信片

  • 搜索引擎优化

  • 点击计数与跟踪

  • 跨平台

  • 新的猫咪战斗通知

  • 激励措施,慈善捐赠

  • 脱离基于猫的战斗

  • 社交登录

  • 个性化

  • 定向广告

…还有更多!

很多很棒的功能创意。但是我们能否将这些创意与能够产生可衡量的影响、推动整体目标的联系起来?这正是影响映射过程的这一部分所要帮助我们实现的目标。

当我们能识别出一个我们认为能够引发明确影响的点时,我们会将交付物和影响放在影响图的相邻位置。例如,团队认为每日锦标赛可能会增加站点参与度:

图 8.11:第一个完整的影响声明

我们可以将其转化为一个假设声明:

我们假设,如果我们举办每日锦标赛,它将提高上传者的站点参与度。反过来,这将帮助我们实现扩大用户基础和筹集 100K 美元收入的目标

第二个假设声明源自为竞赛赞助商提供更多广告位的想法:

图 8.12:第二个完整的影响声明

如果我们提供优质的广告位,我们可以增加赞助商赛事的数量,并将广告商转变为赞助商,从而帮助我们产生 100K 美元的收入

有些功能无法与影响声明相关联,团队也想不出合适的声明。因此,他们决定将该想法列为低优先级,因为没有可追踪的收益。

结果产生的影响图很大,充满了交付物的创意,这些创意与预期的可衡量影响紧密相连:

图 8.13:PetBattle 影响图

我们的影响图已经完成。它将交付物什么)与影响如何)以及参与者)连接起来,从而帮助实现总体的目标为什么):

图 8.14:影响图总结

在这一点上,我们可以开始思考一些优先级排序——至少是针对参与练习的利益相关者。通过影响映射练习,我们已经提出了一些假设声明。我们不知道这些声明是否会是真的,它们目前只是智能的猜测。为了验证假设的正确性,我们需要进行实验。每个假设声明都可以转化为一个实验。但是我们首先进行哪个实验,且实验的顺序如何安排呢?

现在是时候下注了!一个有趣的方式是问利益相关者他们希望在哪些地方下注。为了模拟这一过程,我们可以给每个利益相关者一些虚拟货币:$10,000 比蒂币或 $10,000 唐纳尔元或 $10,000 迈克币。(第四位作者诺埃尔决定他不想进入货币市场!)这是一个简单的练习,模拟每个房间里的成员都是公司的所有者,且使用的是他们自己的钱。

现在,会议的参与者用他们的虚拟货币下注。他们下注选择他们希望首先在实验中投资的影响声明。他们可能选择只投资一个影响,或将他们的赌注分散到多个影响上。这使每个人都能选择他们的首要交付物,显示出团队应该优先构建的内容的汇总视图。

让我们通过一个例子来更好地理解这一点:

PetBattle – 下注

投资于 PetBattle 的四位利益相关者每人被分配了五个 Donal Dollar,并被要求查看影响图:

图 8.15:Donal Dollar

他们被问及希望投资哪些影响陈述,并被鼓励要么将资金分散在不同的影响项上,要么如果某个影响项对他们来说非常有价值,则将多个 Donal Dollar 投放在一个影响项上。

一些利益相关者选择将所有的 Donal Dollar 投资在一个影响项上,而其他人则将其分散投资。带有 Donal Dollar 的影响图显示了一个明确的赢家:

图 8.16:Donal Dollar – 影响图

很明显,投资的利益相关者希望投资于增加站点互动。影响图中已经有七个交付项与此相关,因此团队已经准备好进行一些实验来验证或反驳相关的假设陈述。

利益相关者期待回到这个影响图,看看他们的投资是否带来了回报(也许还会再投入一些 Donal Dollar!)

在本书的下一节第四部分,优先排序中,我们将讨论如何优先处理从我们在发现循环中使用的实践中获得的所有学习和成果。一个视角将是基于影响图上的投资赌注的结果。

在我们分享几个关于影响映射的现实世界案例之前,最后提到一点:影响图永远不会完成。它不是静态的,也不是一次性完成的。它会一直演变。团队在交付一些功能、进行一些实验并从用户那里获取反馈后,应该定期回顾他们的影响图。他们应该讨论实验结果,并得出结论,现有的假设陈述是被验证了,还是被证伪了。

我们在前一节介绍了心理安全。测试当前心理文化的一个有效方法是检查当影响图中的假设陈述被证明是错误时会发生什么。一个失败的假设可能会影响团队士气,导致缺乏心理安全感。如果假设成功并为进一步的学习和即兴发挥创造了空间,它可能会提高士气,并在团队中建立开放文化。

假设示例

让我们看看我们如何在开放创新实验室的驻留期间使用影响映射,帮助一家全球银行改进其人力资源系统。

业务目标是使人力资源部门在人力资源系统之间实现集成工作流,提供更好的同事体验。在目标层面上确定了一些指标,包括同事满意度和更换服务负责人所需的时间。

团队在头脑风暴时确定了“同事”这一词汇,大家一致同意将其作为包括所有员工、经理、二线经理、合作伙伴、人力资源运营以及管理层和技术利益相关者在内的普遍术语。

对每个参与方群体,明确了可衡量的影响陈述和可能的交付物,进而提出了一些强有力的假设陈述,包括以下内容:

我们假设,提供单一登录解决方案将加快服务速度,从而帮助同事和管理者实现上述目标。

我们假设,集成数据中心将提高人力资源操作员获取同事数据的质量,从而有助于实现上述目标。

我们假设,开发定制的仪表盘功能将增加对管理信息组的洞察力。

我们假设,设计一个新的治理模型将帮助人力资源专家加快重塑过程。

这是一个很好的示例,展示了影响映射如何帮助推动更大的企业系统(远远超出独立应用程序的范围),并且还能够识别软件以外的输出(例如,治理模型是一种组织特征)。

这是部分影响图:

图 8.17:影响图示例

现在让我们看看另一个影响映射的实例。

将工程师与业务成果连接起来

我最喜欢的影响映射时刻之一发生在最初的影响图产生几周之后。

我们与一家欧洲汽车公司合作,帮助重新构想并启动他们的技术支持知识管理系统的开发,我们使用了影响映射来开始发现。

在三次开发迭代之后,我们的冲刺回顾让团队采取了一些措施,为下一次冲刺做好准备。团队决定,在经历了三次开发迭代后,重新探索影响图是值得的,因为我们已经有一个正在与最终用户进行测试的应用。

我们把大块的便携白板搬到我们在举办驻地活动的场地阳台上。那天风很大,我从中学到的一课是:风大时不要把粘性便签板带到户外!

这张照片展示了我感到骄傲的一刻,一位工程师正主导关于他开发的功能的讨论。他正在评估预期的影响是否已经实现,以及他们共同学到了什么:

图 8.18:重新审视影响图

我思考了我曾合作过的多个开发团队。看到技术人员如此紧密地与“为什么”联系在一起,令人耳目一新。他们积极投入,确保他们的输出能够带来可衡量的结果。

欲了解更多信息和示例,并与社区成员就影响映射实践进行进一步对话,请访问openpracticelibrary.com/practice/impact-mapping页面

影响映射无疑是启动发现对话的最佳实践之一。我们还能做些什么来增强这一点呢?接下来,我们将聚焦于参与者的视角,也就是使用以人为中心的设计,探讨产品能为他们做些什么。

以人为中心的设计

影响映射识别了所有能够帮助我们实现目标的不同参与者和群体。它还识别了我们希望对他们产生的可衡量影响。如果我们能与其中一些人见面并互动,那不是很棒吗?我们可以与他们一起验证我们的影响映射。我们可以检查我们所设想的交付物是否会得到他们的支持。在写下一行代码之前,我们就能向他们学习!我们可以与他们建立起一种信任关系,定期与他们互动,进行测试、衡量并从他们身上学习,同时开始构建应用程序。

以人为中心的设计是一种系统开发方法,旨在通过关注用户、他们的需求和要求,使系统既可用又有用。

有几个与用户体验UX)相关的主要主题。每个主题都足够大,足以成为一本书,并包括:

  • 设计思维

  • 以人为中心的设计

  • 双钻石流程模型

  • 精益 UX

在软件开发领域,我们可以从人开始,最终开发出量身定制、适应用户需求的创新应用程序。通过理解我们的用户并从他们的视角来看问题,我们最终可以开发出他们喜爱使用和参与的产品。

人们对他们所看到的东西做出反应——视觉设计。然而,UX 设计师必须考虑视觉下的许多层面,才能使应用程序既吸引人又有用:

图 8.19:UX 设计冰山模型

让我们来探索一些在产品开发中应考虑的 UX 设计不同组成部分:

  • 产品发现的促进涉及领导寻找合适的产品或服务的过程,重点关注产品的可欲性、商业模式的可行性以及技术可能性的可行性。

  • 定性用户研究包括与用户进行访谈,进行情境询问和其他观察研究,在这些过程中,设计师通过观察用户完成当前任务的方式来学习。

  • 概念设计在产品或服务的初期阶段促进并贡献于创意工作。

  • 原型设计从纸质原型到低保真原型,再到高保真模型,这些都可以用来与用户和其他利益相关者测试创意。

  • 交互设计开始通过使用视觉故事板和线框图来展现新体验可能的样子。

  • 以人为中心的服务设计采用更广泛、宏观的方式来进行客户旅程映射、服务蓝图绘制,并提高服务交付中的所有接触点的质量。

  • 定量数据分析处理来自 Google Analytics 和 A/B 测试等工具的反馈。

  • 视觉设计是设计用户将看到的视觉元素,涵盖了许多上述实践的学习。

  • IT 服务的可访问性考虑了 Web 内容可访问性指南和其他非功能性方面。

上述所有实践的共同点是通过更好的信息和对用户利益相关者的理解来学习。这将验证并改进在例如寻找北极星和影响力映射过程中做出的预测。

我们在 UX 设计的许多不同阶段使用的一个流行实践是同理心图。同理心图是一种视觉画布,捕捉了利益相关者在特定情境下的所见、所想、所做和所感。同理心图可以用来揭示痛点和改进的机会。这些机会随后成为我们在产品开发中可以考虑的价值主张。

我们在产品发现的促进、用户研究(例如在用户访谈期间捕捉笔记)、获取概念设计、原型和视觉设计的早期反馈时使用同理心图。它还可以用于捕捉关于开发的应用特性进行用户测试时的反馈:

图 8.20:同理心图

其中一种简单的人本设计实践是采用快速原型制作的“思考、制作、检查”方法。这包括创建一个快速原型并对其进行用户访谈。我们通过迭代步骤来达到一个设计目标。在用户访谈过程中,团队可以倾听并帮助完成同理心图:

图 8.21:思考、制作、检查

第二部分:建立基础中,我们介绍了 Mary,并探讨了 PetBattle 组织如何创造自主性、精通感和目标感。Mary 是一个自认喜欢猫的人,在 PetBattle 还是一个简单的业余爱好应用时,她就经常玩。她与 Ciarán(PetBattle 工程团队的成员)建立了联系,后者观察了她使用该应用的情况。现在,让我们看看 Ciarán 是如何使用同理心图来捕捉他的所有学习的。

UX 设计与同理心图:PetBattle 用户

Ciarán 在 Mary 在 Twitter 上分享她有多喜欢 PetBattle 之后遇见了她。他们交换了几条直接消息,在这些消息中,Mary 提到她并没有完全理解一些用户界面上奇怪的地方。她还觉得 PetBattle 有很大的潜力,可以在在线时间消费方面迈上一个新台阶!

Ciarán 认为 Mary 可以帮助塑造 PetBattle 下一次迭代的思维,并验证团队的一些早期假设。所以,Mary 答应参加用户访谈,作为交换,她获得了一张价值 20 美元的星巴克礼品卡。PetBattle 工程团队的其余成员和产品负责人也参与了这次访谈。

Ciarán 通过问了一些简单的问题开始了访谈:

  1. 你叫什么名字?

  2. 你是做什么工作的?

  3. 你住在哪里?

  4. 你有宠物吗?

  5. 你是如何了解到宠物对战的?

  6. 你通常在哪里以及如何玩宠物对战?

  7. 你想在宠物对战中使用什么宠物?

  8. 在线时,你做什么来表示你喜欢某样东西?(当玛丽难以回答时,Ciarán 举了“点赞”或“喜欢”按钮的例子。)

  9. 你想如何与他人的宠物对战?

  10. 什么会让你更频繁地使用宠物对战?

  11. 主题使用对你来说重要吗?

然后,Ciarán 让玛丽玩老版本的宠物对战,这样团队可以看到她与应用程序的互动。

她上传了一张手机上猫咪的照片。然后,她很困惑为什么她的猫没有在上传后立即出现在比赛中。Ciarán 和团队知道,她需要为另一只猫投票,自己的猫才会出现在列表上,这对他来说似乎显而易见。

团队观察到玛丽与应用程序互动时的一些其他令人惊讶的事情(以及她没有注意到的一些问题!)。

Ciarán 和团队感谢玛丽的时间,并询问她是否有兴趣在一周左右后回来帮助进行一些原型设计。玛丽很高兴能提供帮助。

与此同时,团队一直在制作玛丽的同理心地图,并且会不断完善,之后再回放给她确认他们的理解和学习成果。

图 8.22:宠物对战用户的同理心地图

一周后,玛丽回到了宠物对战。团队一直很忙,制作了很多纸质原型,涵盖了一些从影响力映射会议中得出的想法。这些包括讨论论坛、定向广告、今日特惠、排行榜功能以及忠诚度计划。

原型从带有草图的便利贴到更高保真度的线框图不等。排行榜功能实际上是通过 Sketch 视觉设计应用程序制作的。

团队的不同成员解释了每个想法和原型。玛丽参与了讨论,充分的时间也给她提供了评论、反馈、提问、挑战以及添加自己想法的机会。没有分享原型的团队成员则通过一块大移动白板跟进讨论,这上面有玛丽的同理心地图。

再次,玛丽提供了巨大的帮助。Ciarán 认为她分享的细节非常宝贵,他们如果没有她的反馈,可能永远不会考虑到她所提出的一些方面。

玛丽同意从今以后每两周回访一次,这样她可以率先看到并测试开发的新功能。这将与每个交付迭代的结束时间相吻合,团队决定继续制作玛丽的同理心地图,以确定她喜欢新功能的哪些方面,以及哪些功能不太适用,应该改进。

这是另一个非常有效实践的案例研究:

用户做出奇怪且出乎意料的行为

作为一名软件开发人员,你可能会看着像同理心地图这样的实践,觉得它有点空泛,那么我为什么要关注它呢?我第一次听说这个实践时也很怀疑,无法理解为什么作为开发人员,我应该关心它。但当我意识到自己错过了关于用户行为的丰富背景信息时,我完全信服了。

我与一位顶级设计师 Bríd 合作,为一家银行客户开发一个 HR 工作流引擎替代方案,该系统即将停止支持。我们对现有应用程序进行了这项实践,Vijay 是 HR 运维团队的一员,负责响应提交的工单并将其分配给团队成员。

当 Bríd 采访 Vijay 时,其他团队成员则记录下笔记,并将其放到同理心地图上。我们记录了 Vijay 使用该工具时所做的活动以及他对这些活动的感受。对我们来说,关键是识别当前工具中的空白点和他可能采取的快捷方式。通过这项研究,我们发现了他们直到目前为止使用的解决方案中存在的一个巨大空白。

Vijay 在日常使用当前应用程序时,会从应用中剪切数据,然后打开一个 Excel 电子表格。在那里,他会粘贴一个大数据集,进行批量排序和处理,然后再把它复制回工具中!我们大家都非常惊讶,看到这一点让我们意识到,我们对当前工具所服务的活动类型存在巨大的理解差距。如果我们没有和 Vijay 一起进行这项练习,我想我们永远也不会发现这一点。Vijay 和他的团队会跳出应用程序,因为他们需要某些关键功能才能有效工作。

当我看到这一点时,确实是一个转折点。我亲眼看到,深入了解用户的想法对于设计师和我团队中的技术人员同样重要。

后来,在我们为应用程序构建批量更新功能时,我脑海中一直在想着 Vijay。如果我们没有使用这种以用户为中心的实践,去揭示真实的用户行为,我坚信我们不会发现我们需要设计和构建的新功能。

在为一家芬兰电信公司工作时,我们的团队使用这项实践来弥合开发与运维团队之间的差距:

为组织绘制同理心地图——开发与运维的对比

同理心地图是一个非常有效的工具,用于识别现有或新建应用程序的痛点与收获,尤其是那些具有用户界面或某些人机交互元素的应用程序。它确实可以帮助深入了解某些用户的想法,以及他们如何响应新的功能。我还发现了一个可以使用此方法的领域——在你的组织内进行同理心地图绘制。

Dev 和 Ops 传统上是许多组织中的两个孤岛。开发人员常被认为将他们的工作“扔”过围墙,由 Ops 团队负责接手并继续进行。有时,这会在两方之间产生摩擦,通常会形成一种相互指责的文化,当事情出错时,双方互相指责。在为一家芬兰电信公司工作时,我结合 Ops 和 Dev 团队展示的如何构建、部署和监控他们的应用程序的示范,尝试了这种做法。结果非常好,也让我对每个团队如何被另一方评判有了些许了解。

这里的图像是来自 Kalle——我们的开发人员的共情图。从我们的会议中,我们发现他感到自己工作过度,并且由于另一方的缓慢反应,他无法迅速推进工作。

图 8.23:为一家芬兰电信公司创建共情图

他会提出请求,而 Ops 团队回应缓慢,影响了他以任何敏捷方式推进工作的能力。

当然,在分歧的另一方是 Timo,Ops 团队的成员,他的观点与 Kalle(我们的开发人员)不同。Timo 的共情图揭示了他觉得必须不断重复自己的话给开发人员,且他接收到的请求从未准确无误,因此他不得不反复推翻或者做大量的返工。

我们将两个共情图反馈给每个团队,开始构思如何共同合作来解决这种分歧。

这是一种简单易用的做法,尽管它揭示的信息可能看似可预测,但从中我开始看到公司结构中痛点的所在。很明显,团队没有共同承担交付新功能或修复的责任,而是在事情出错时互相指责。

共情图在深入理解最终用户方面起着至关重要的作用。通过以下案例研究,我们讨论了我们对这一实践的看法。

工程师在用户访谈中构建共情图

认为 UX 设计是 UX 设计师独立完成的,这是一个误解。认为 UX 设计应该作为项目的独立阶段进行,也是一个误解。

我见过的一些最强大的 UX 设计,都是工程师社区、开发人员和运维人员完全参与的过程。

一个例子发生在我们位于伦敦的 Red Hat 开放创新实验室。在这次驻场活动中,我们的 UX 设计师在 Red Hat 大楼内一个设施完备的访谈室里主持访谈。经过访谈对象的同意,整个访谈过程通过屏幕实时传输到实验室空间,开发团队的所有成员都在观看并在共情图上记录关键信息。

在接下来的这张照片中,你会看到团队分成了两组,每组观看两场不同的现场用户访谈并各自构建他们的共情图:

图 8.24:工程师创建同理心图

在开放实践库中,有几个与人本设计相关的实践页面,包括:

这些页面为探索这些实践提供了一个很好的概述和起点。

在下一章,我们将介绍一种发现实践,帮助我们的团队以每个人都能理解的方式可视化端到端的业务流程——事件风暴(Event Storm)。

结论

在本章中,我们一直处于发现循环中,重点是发现产品的“为什么”和“谁”——我们为什么要构建这个产品,我们为谁构建这个产品?我们从找到我们的北极星指标开始——一个能够衡量产品成功的关键指标。

我们介绍了影响映射(Impact Mapping)实践,它帮助我们聚焦于整体目标,并了解所有可能帮助或阻碍我们实现目标的不同角色(Actors)。我们还学习了如何定义和绘制可衡量的影响力和交付物,并与我们的角色小组一起形成假设陈述,作为实验的基础。

我们探索了可以与角色小组一起使用的人本设计实践,以促进同理心并支持用户体验设计。

图 8.25:将实践加入交付循环,以发现为什么和谁

第九章,发现如何做中,我们将继续进行我们的发现循环,但将更多地聚焦于发现我们如何构建解决方案。具体来说,它侧重于如何开始架构设计,架构会随着时间的推移而逐渐成型。我们将探索领域驱动设计(domain driven design)和事件风暴(event storming)作为帮助我们实现这一目标的实践。我们还将关注一些帮助解决架构非功能性方面的实践——非功能性映射和基于度量的过程映射。

第九章:9. 探索“如何做”

在上一章中,我们开始了发现循环的第一次迭代。我们专注于那些帮助我们发现我们为何以及为谁解决问题或实现机会的实践。

在本章中,我们将把重点转向共同学习如何开始构建一个解决方案,以解决这些人面临的问题或为他们带来新的机会。我们将探索的帮助实现这一目标的实践包括:

  • 事件风暴:一种富有色彩的实践,它将业务过程可视化为事件驱动的领域,并展现新兴架构。

  • 新兴架构:我们遵循的一项原则,即通过从事件风暴和其他文档中获取足够的信息来启动我们的架构,并随着我们增加更多复杂性,架构逐步展开。

  • 非功能性地图:一种实践,促进讨论,探索解决方案的不同非功能性方面。

  • 基于指标的过程图:一种实践,它允许我们捕捉遗留系统的一些基准测量,并识别系统中的瓶颈,同时为转向更连续交付模式提供有力依据。

总的来说,我们最喜欢的实践之一就是事件风暴,因为它非常具有协作性和包容性。它为团队中的每个角色提供了价值。客户常常告诉我们,使用事件风暴在几天内实现的成果,在业务分析项目中可能需要数个月才能完成。

事件风暴

事件风暴是一种敏捷和精益的方法,旨在让团队在技术领域项目中进行合作、集思广益,并学习如何在共享理解的基础上工作。

图 9.1:事件风暴色块拼图

事件风暴是一个建模工作坊,用于可视化业务或系统内的过程。它能够提供来自组织不同层级的视角。在业务操作层面,大局观事件风暴可以识别组织结构之间的差距和困扰,突出交易中的权衡,并指明需要改进的领域。我们可以通过业务过程建模或应用功能建模放大到更低层次的细节。这些实践使得开发人员、设计师、最终用户和业务利益相关者能够使用共享语言进行交流,无需依赖复杂的技术术语。

虽然一开始过程可能显得复杂,但它已经成为我们在开放创新实验室与客户合作时最喜欢的实践之一。通过这种技术迅速打破知识孤岛并可视化业务流程的短时间,是我们最为印象深刻的地方。事件风暴是我们用来获得对业务流程运作的共同理解,同时可视化潜在问题和解决方案的技术。经过多次引导事件风暴后,本章这一部分是我们实施事件风暴的指南,并分享我们如何最佳应用它的经验。

首先,你需要一个大的建模表面,理想情况下是一面很长的墙,比你想象中的空间要多。你还需要足够的空间让人们围绕建模表面站立并观看。

图 9.2:一群人在建模表面周围进行风暴讨论

什么是事件风暴?

事件风暴由阿尔贝托·布兰多里尼创建 1,并在许多博客网站、维基百科和 YouTube 上有所记录。我们将尽量总结我们在使用和引导这一实践过程中所学到的知识。

1 medium.com/@ziobrando

图 9.3:完整的事件风暴,详细描述了两个客户旅程

从本质上讲,事件风暴是一个群体进行对话,讨论业务目标,并将这些内容记录在便签上的过程。如果邀请了合适的人,讨论和视觉化映射将有助于突出之前可能隐藏的依赖关系。突出这些依赖关系可以帮助团队避免在技术和业务层面做出错误的产品决策。

事件风暴是一种探索和发现的模式。它源自于领域驱动设计DDD)。DDD 是一种面向复杂领域的软件设计方法,侧重于语言和领域。我们喜欢把事件风暴看作是一种简化版的 DDD——DDD-Lite——它更加专注于业务,减少了术语和复杂性。通过将业务领域专家与开发人员聚集在一起,我们可以通过应用一种共同的、通用的语言,协作构建过程模型,帮助开发人员对事件驱动系统建模并发现微服务。

图 9.4:事件风暴方法与人员

事件风暴工作坊汇聚了来自组织各个部门的人。它不专注于任何特定的技术,因此与会者可以暂时不关注这些技能。与会者的唯一要求是他们的精力、注意力和愿意尝试的态度。在事件风暴工作坊中,每个人都会拿到橙色便签——即事件——以及他们所带来的关于自己公司部分的知识。

软件创作是一项探索性任务,在探索过程中,会发生更多的学习。捕捉这些学习至关重要。事件风暴的目的是将所有这些知识可视化成一个基于事件的思维导图,并识别其中的空白、未知和痛点。通过合适的受众参与事件风暴,你可以在那些传统上从不接触的群体之间建立起和谐,更重要的是,在之前可能存在误解的地方达成共识。

在一场事件风暴工作坊中,可能会有了解业务需求和要求的业务分析师,与开发人员一起识别命令事件,这些命令和事件将作为特性来实现。再加上 UX 设计师(与最终用户合作)进行 UI 设计和数据模型验证来支持这一过程,突然间,你就从头到尾达成了一致。你还可以在写下一行代码之前,提前验证什么是可行的,什么是不可行的。

如果你想了解更全面的事件风暴背景,可以查阅《开放实践库》,在这里你可以找到更多的链接、文章以及我们在实际工作中与客户一起使用事件风暴的例子。我们将重点关注过程和功能层面的事件风暴。

让我们来看看进行一场成功的事件风暴所需的材料。

材料

事件风暴的材料是进行一场成功事件风暴所需的一切。

首先,你需要便利贴,并且需要大量的便利贴。想象一下一个非常大的数量,然后再翻倍。事件风暴使用一个非常特定的颜色编码规则。坚持使用作者指定的颜色非常重要,因为这能在你从事件风暴 1 到事件风暴 2 的过程中保持一致性。

接下来,你需要充沛的精力。事件风暴是一项高度集中的工作坊,需要参与者全程保持注意力。澄清误解需要合适的人,并且这些人能够大胆发言。带上大量的好咖啡、充足的水和水果!

你需要一个宽敞的开放空间。给自己提供一个无限制的建模表面。可以铺开一些绘图纸,或者如果墙面空间有限,使用可以移动和添加的大号泡沫板。你不希望因为墙面空间不足而限制收集到的信息量。

人是最重要的材料。邀请合适的人。邀请最终用户、业务分析师、架构师、业务负责人、产品负责人和开发人员。尽可能多的人参与,事件风暴会更加完整。一个优秀的事件风暴教练和主持人会努力邀请所有了解产品的人以及所有参与产品建设的人。如果人数太多,他们会寻找来自各部门或团队的代表,这些部门或团队组成了跨职能小组。

我们为想要体验购物清单式事件风暴的朋友们准备了一个亚马逊购物清单 2。一旦你有了所有材料,接下来就可以参考配方,烹制一场精彩的事件风暴了。

2 amzn.eu/dViputa

配方

事件风暴的配方就是做事的清单以及按照顺序完成它们!

首先,你需要预热烤箱。我们的意思是“黑客”空间。把房间里的椅子都移走,并在一个大空白墙面上标出一个区域,准备铺设建模表面。椅子会让人坐下来,进而导致人们不参与,最终可能会让人打瞌睡!给自己留足够的空间,铺开你的绘图纸/泡沫板。如果可能的话,不要从角落开始,尽量从房间的中央开始。若从角落开始,只有 50%的人能够围绕它,这样会导致一半的知识无法传达给大家。

图 9.5:铺设建模表面

拿出事件风暴流程图,它是事件风暴的关键或图例。我们通常会在工作坊前一晚制作海报(用大号的翻页纸粘纸),因为这会占用不少时间。将它挂起来,确保所有与会者都能看到。对于初次接触事件风暴的人来说,他们一开始会频繁查看它作为指导。

图 9.6:事件风暴流程

如果你打算与分布式团队成员一起进行事件风暴,更多数字化的版本可以添加到数字工具中(如 Miro 或 Mural)。

我们通常会在此阶段和与会者一起走一遍事件风暴的流程。这个过程可能会让人感到非常压倒,因为要介绍的内容很多。我们会基于一个虚构但与大家相关的例子来创建并演示,帮助强调关键点。通常,我们会选择一个待办事项应用程序,或者更具体一些,如在线亚马逊购买。每个人都能与这些内容产生共鸣。

所有食材准备好,烤箱预热完毕,现在是时候开始制作混合物了!我们从事件开始。

图 9.7:事件图例

我们首先要做的是设定事件风暴工作坊的目标。这可能是流程的入口点、出口点,或两者都是。介绍之前图片中显示的事件海报。在最简单的形式下,事件就是过去发生的某些事情,是某人关心的内容。设定好起始点后,要求与会者分成两到三人一组,找出他们能想到的系统中的所有事件。给这个活动设定时间限制。我们通常一开始设定为 15 到 20 分钟,并在过程中时刻关注,确保大家专注并进行讨论,必要时澄清一些事情。如果小组中有领域专家(SMEs),我们确保他们被平均分配,而不是集中在一个小组中。领域专家通常是最适合引导这个初始事件脊柱构建的,因为他们对相关领域了解较多。

确定了团队的事件后,要求自愿的小组将他们的事件呈现给大家,将事件添加到建模面板上并开始讲述故事。严格遵循事件的时间线,从左到右进行。第一个自愿展示事件脊柱的小组成员将获得金奖以表彰他们打破僵局。我们通常会在便签上画一个奖章并颁发给这个人,让他们带着骄傲佩戴!一个小技巧是,不要从建模空间的最左侧完全开始。当随着更多知识的揭示,事情开始被移动时,这个空间可以用于进一步扩展。

图 9.8:代表她小组挂上事件

让其他小组将他们的事件添加到时间线上。鼓励与会者将自己的事件沿表面重新排列,为更多事件腾出空间。如果各组用不同的词汇来描述相同的事件,尽量达成语言上的共识。明确那些尚未完全发展的事件。例如,如果一个小组有一个非常高层次的事件,如物品已订购,将其细化为更低层次的细节,比如物品已加入购物车结账已开启。如果有任何问题或假设需要被确认,若无法立即回答,请标记出来。

很重要的一点是,要将那些无法自信回答的问题暂时搁置并继续前进,否则你很快就会陷入细节之中。确保给足讨论时间,以防可以解答这些问题,但如果无法解决,请用粉色便签标记为问题。最初,问题标签会使用得非常频繁,这是可以接受的;因为在这个阶段,我们对系统的集体知识最少。当标记一个区域为问题时,意味着这段对话不会被忘记,必要时可以再回到这个问题上。一个好的做法是,直到讨论充分后再揭示这个问题卡片,以便能够顺利推进。如果需要的话,确保用多个便签记录下讨论的各个方面。

在创建了事件的主干之后,让团队从前到后,再从后到前地进行故事演绎。通过过程中出现的对话,添加任何遗漏的事件。在这一阶段,我们通常会用胶带从单个事件垂直标记关键事件。这些在框定关键部分的边界时非常有用,并且能帮助快速识别时间线中的重要部分。

下一部分介绍了事件风暴谜题中的大部分关键要素。现在是时候介绍参与者命令读取模型系统了。为每个部分添加图表,以便它们在视线范围内。与小组一起逐步讲解,并澄清是否有任何误解。

命令代表用户根据从读取模型中获取的信息做出的决策。参与者是发出命令的人,而系统是接收命令的对象。系统有责任对命令做出响应,并因此触发一个事件。

这一部分介绍的内容比较多,因此,重要的是确保大家不会被接下来的谜题内容压倒。如果听众需要更多相关的例子,可以考虑通过一个简单的例子来帮助理解。我们倾向于将这些要素添加到主干中,这样例子就可以在更广泛小组创建的流程上下文中展开。

我们的下一个关键部分提供了一种机制,用于捕捉问题、假设以及外部系统。

图 9.9:问题和外部系统键

接下来是命令和参与者。

图 9.10:命令键

现在(暂时)需要添加到关键部分的还有读取模型。

图 9.11:读取模型键

事件风暴海报应详细说明关键部分并附上示例。现在,团队应准备再次添加流程中的新部分!分成小组,但成员要轮换。如果某些领域有专家,再次确保他们分布在组内。

要求小组提出命令被发出给哪个参与者、命令是什么、以及哪个系统接收了命令。再次将此任务时间限定为 15 分钟。若事件数量较多且小组成员较多,团队可以选择先集中在两个关键点之间的事件,并详细展开。这可以加速进程,之后所有内容将一起回放,从而达成共享的理解。

我们尽量避免让小组陷入系统是内部/外部还是应该如何命名的问题。最好保持模糊,直到最后我们真正需要了解低级别的细节。对我们来说,系统在这一阶段的主要任务只是确认有一个东西,命令被发出到这个东西,从而触发相应的事件。根据我们的经验,这种模糊性有助于防止人们陷入困境,并保持讨论的高层次,使每个人都感到参与其中。

让小组将他们对事件风暴的补充部分呈现给其他小组。通过这样做,会激发更多讨论,并且会发现更多空白。捕捉假设并在这次重现过程中添加缺失的部分。

图 9.12:三组围绕关键事件进行头脑风暴

如果团队围绕关键事件进行了划分,并且小组希望有更多时间来完善流程,我们通常会将小组轮换到不同的关键事件之间的新部分,设定新的时间框,并重新开始。这有助于验证其他小组的工作,并且通过新鲜的视角进一步完善流程,揭示更多知识。

在下一个小组重现完整事件风暴时,我们引入了下一个关键部分。政策和程序与子流程一同被引入。

图 9.13:政策和程序关键

政策和程序是填补命令与事件之间一些空白的好方法。当小组在重现故事时,提出类似这个总是会发生吗?这样的问题,或说出每当发出命令时我们总是做这个事件等话语。这样做可以揭示事件之间一些尚未想到的小细节。我们认为这就是为什么 Brandolini 称这个卡片为谎言探测器的原因。

子流程是停放那些在工作坊范围内不会被深入讨论的内容的好方法,但它们会在以后被回到,通常是在单独的事件风暴会议中。

我们曾与一个客户合作,他们有一个第三方履约流程,这个流程超出了他们试图揭示的流程范围。紫色子流程的便签被用来标记这一未探索的区域,并在后来的研讨会中回到这个部分。一段简单的胶带被用来连接两个流程,当它们被嵌套在一起时,以确保两个流程之间的视觉连接不会丢失。一个重要的提示是不要使用标记笔或圆珠笔,因为这个流程可能会在以后被(重新)移动!

图 9.14:围绕一些停放的子流程进行头脑风暴

流程中不可避免地会出现分支。我们使用视觉方式标记这些分支,用快乐或悲伤的贴纸表示流程的正向和负向。我们通常先建模快乐路径。通常,每个快乐流程都会对应一个悲伤流程。在我看来,子流程是一个很好的方法,可以在不被流程中不相关分支困扰的情况下捕捉这些内容,尤其是在工作坊的范围内我们并不打算探讨这些分支,或者希望稍后再详细回顾。

如果一个事件导致有大量命令可用,我们将它们垂直堆叠,以便逐一展开流程。如果时间紧迫,最好只选择一两个流程进行展开,其他的可以稍后再处理。在分支发生时,重要的是要集中精力关注事件风暴的目标,而不是被低质量的信息所困扰。

随着大部分拼图块就位,事件风暴此时应该开始讲述一个更详细的故事。继续前后反复播放这个故事,从中间开始,并向各个方向展开。挑战所有已出现的条目,不要害怕加入或推翻错误的观念。随着知识的增加,你会开始注意到问题便签的数量逐渐减少。

如果你正在建模的系统有 UI 组件,一个很好的补充是将一些高层次的 UI 加入到阅读模型中。用便签上的简单草图可以快速验证 UI 可能需要哪些组件,并验证所需的数据是否能被提取。如果你邀请了应用程序的终端用户参加事件风暴,和他们一起回顾事件风暴并结合 UI 设计进行验证,将非常有助于进一步验证流程假设。

图 9.15:团队在绿色便签上为事件风暴添加 UI

最后,是时候确定并命名聚合根了。聚合根是系统的状态机。

图 9.16:聚合根

它是接收命令并决定是否执行该命令的部分。我们仅在流程的早期引入系统,因为聚合根可能是导致最大混乱的部分,也是最大的时间消耗者——尤其是在尝试命名它时!

到最后再处理,可以清除掉大部分未知的部分。当流程更加清晰时,我们让团队重新回顾事件风暴,找出系统是已经存在还是需要构建的。如果需要构建,我们添加一个黄色的聚合根卡片,并给它起一个无意义的名字,比如“组合器”。这样做有助于让大家不被困在该聚合根应该定义的边界上。

继续回放流程,团队会自然而然地开始识别出一些命令是发送给组合器的,其他的则是发送给其他组件的。把这些标记为“其他”。当事件风暴完成一轮之后,再返回去为其命名。理解的深度应该足够高,以至于聚合体的名称和边界应该自然而然地显现出来。

与怀疑者进行事件风暴

这不是秘密——事件风暴是我最喜欢的实践。我认为它是一种极其多用途的实践,可以在不同的细节层次上使用,和公司各个部门的人用一致的语言沟通。你可以将其反馈给最终用户来验证用户旅程,同时也可以通过识别所需的系统设计来让技术人员参与其中。

当我为一家瑞典汽车制造商工作时,我们使用事件风暴来为我们的应用程序设计解决方案。我们合作的一位 Java 工程师最初对这个过程持极大怀疑态度。他曾告诉我,他看到我们进来时心里想,哦,来了些便签小队。但一旦我们开始了,他很快就被说服了,他看到随着设计师、业务分析师、开发人员和用户的共同参与,解决方案逐渐在我们面前展开。

几天之后(是的,真的几天!),他走到我面前,告诉我他有多么震惊,在这么短的时间里,我们竟然揭示了如此多的领域知识,并且已经揭示了足够的解决方案,让我们可以开始尝试技术并进行构建。他信服了!

事件风暴是如此多功能——在同一次合作中,我们围绕用户如何在他们的应用程序中创作新内容来构建我们的事件风暴。它是一个基于知识的应用程序,因此内容操作是一个关键组件——可以将其看作是一个汽车版的维基百科。在建模时,我们遇到了流程的一个部分,其中用户在旅程中编辑一些文本并保存他们的进度。产品负责人一直说用户将保存他们的文章然后发布,这让我产生了一些警觉。一个用户需要点击的保存按钮以便继续进度,似乎有点奇怪。在我看来,应用程序应该是透明地保存用户的编辑内容,而不是像微软 Word 97 那样设计,而是像 2020 年代的现代 Web 应用程序。

所以,我挑战了产品负责人,并说:“我们是不是应该让应用自动保存?”他不同意,认为用户可能希望手动保存他们的进度。我想,既然如此,让我们使用事件风暴来做个实验吧!我们列出了支持和反对手动保存的两方观点,并将它们写在粉色便签纸上,以表示我们正在做出的假设。我倾向于在需要进一步探索或实验的便签纸上贴上圆点贴纸,这样在查看完整模型时会更容易识别。我们在模型上放置了一个占位符事件,表示进度已保存,直到我们做了更多研究。

图 9.17: 使用事件风暴讨论自动保存功能

然后,这一假设被写入了我们的待办事项列表,设计师们能够通过一个可点击的原型进行实验,看看用户需要什么或想要什么。在这个可点击的原型中,没有保存按钮,在测试后询问参与者他们是否觉得缺少保存按钮时,所有受访者都表示,他们认为在更新内容时,进度已经被保存了。

回到事件风暴,现在我们验证了我们做出的假设。这意味着我们可以继续建模,深入理解用户流程。它还意味着我们可以根据这个理解,扩展从技术角度看需要构建的内容。

我总觉得,事件风暴是在特定时间点上,以我们房间里人的知识为基础,做出的最佳猜测。通过设置一个社会契约来确保心理安全,意味着我们可以自由地探索我们的系统。我们能够做出猜测,并且可以返回这些猜测,或者在地图上放置占位符,直到我们做了进一步的研究。这也是为什么事件风暴,特别是与其他实践相结合时,是发现和设计系统的绝佳工具。

让我们回到 PetBattle 的故事,看看他们的团队是如何构建他们的事件风暴的。

PetBattle 事件风暴

PetBattle 团队决定使用事件风暴来设计他们的系统的一部分。像所有优秀的团队一样,他们从定义他们将要映射的示例开始。这一点很重要,因为它框定了端到端的旅程,避免他们将应用程序的模型做得太大。

!TOP-TIP => 团队正在使用《老友记符号》作为他们的示例。这是基于美国情景喜剧《老友记》3,每一集的标题都以“The One Where”开头。他们从影响图中提取了一些交付成果,深入到系统设计中,并提出了《玛丽参加每日锦标赛并赢得奖品》

3 en.wikipedia.org/wiki/Friends

他们将这一点添加到他们的事件风暴中,放在左上角,然后加入时间,接着构建出事件的主干。

图 9.18: 《玛丽参加每日锦标赛并赢得奖品》

每个团队成员根据示例定义的边界提出了所有事件。从地图的中心开始,一位团队成员将他们的事件放到地图上,并向团队描述故事。

其他团队成员填补了空白,加入了他们的事件,从而形成了故事的整体主线。这些是宠物对战事件风暴的第一个便签。可能还有其他事件位于这些事件之间,但团队目前对他们所挖掘的深度感到满意。

图 9.19:添加到板上的第一个事件

当两名团队成员书写了不同的事件但内容相似时,最初将两者都放到板上,并进一步讨论,最终就事件的措辞达成一致。

在这个例子中,猫咪照片已审阅猫咪照片已验证是讨论的重点。理顺事件的语言对于确保团队使用一致的术语非常重要。

图 9.20:捕捉第一个未知/问题

如果他们无法达成一致,或者需要对流程做出假设,我们使用粉色便签来突出讨论,并将其记录在建模表面上。例如,团队决定此阶段不需要详细描述图像验证的工作方式,但可能会在以后返回讨论这个问题。

团队继续探索事件主线。他们通过从左到右再到从右到左地阅读故事来填充事件主线,并根据需要添加新事件。一旦他们对事件主线感到满意,就开始处理命令和数据。

图 9.21:宠物对战事件主线

团队现在在画布上添加了三个新元素:

  • 演员 – 系统中的“谁”

  • 命令 – 由演员执行的操作或决策

  • 读取模型 – 执行某个操作所需的数据

我们可以开始讲述用户旅程的叙事。在这个例子中,我们的角色玛丽,打开了宠物对战。每当发生这种情况时,我们会展示当前的领导者,包括每只猫的投票数、猫的图片以及主人的名字等信息。

图 9.22:通过事件风暴叙述玛丽的宠物对战之旅

在查看排行榜时,玛丽有许多选项可供选择。在事件风暴中,事情是按照时间顺序发生的。有些命令可能会并行执行,或者在流程中的任何时刻进行。一个好的惯例是,尽快将命令添加到地图上,以便演员能够执行。

当玛丽进入排行榜页面时,她可以选择许多不同的操作。她可以登录并开始游戏,投票支持正在进行中的猫咪比赛,或者直接离开该网站。我们的惯例是将这些命令进行堆叠,因为每个命令可能代表需要映射的另一个流程。团队需要避免过于偏离正在映射的示例,因此这些命令被捕捉并添加到看板上,但不会进一步探索。

图 9.23:堆叠命令

读取模型可能不仅仅是数据,它还可以包括一个低保真的 UI 原型。小规模的 UI 实验为收集用户反馈提供了极好的机会。这使我们能够在不写一行代码的情况下获得产品方向。

例如,当排行榜已显示事件发生时,我们可以勾画出其样子。这为我们的参与者提供了一个概览,展示了他们可以使用哪些选项以及如何与系统互动。

图 9.24:勾画 UI 想法

PetBattle 团队的事件风暴正在通过添加命令、读取模型和参与者逐渐成型。

图 9.25:完整的事件风暴正在成形

PetBattle 团队现在添加了他们集成的任何外部系统以及政策/流程和聚合。

紫色的政策便签是将规则或习惯添加到事件风暴中的好方法,它将命令与未由参与者发起的事件相连接。

图 9.26:添加第一个政策

在我们的示例中,每当我们打开应用程序时,我们都会立即显示排行榜。参与者并没有请求显示它们,因此我们可以通过显示排行榜程序将网站已打开事件与显示领导者命令相连接。

命令必须始终发给一个系统或一组系统。该系统必须决定是否接受或拒绝命令,并传播相应的事件。这会导致两种情况:顺利路径事件(所有过程顺利)或异常路径事件(发生错误)。一般来说,顺利路径事件的价值更大,因此我们通常会对顺利路径进行事件风暴。然而,捕捉和质疑异常路径事件也有价值,例如,猫照片无效事件——我们现在需要关注这个问题吗?

在我们的示例中,显示领导者命令被发给了比赛服务。比赛服务是一个新的实体(一个聚合),负责保存当前(以及可能是之前)比赛的赢家状态。

图 9.27:添加到事件风暴中的流程、政策和服务

PetBattle 团队继续沿着流程进行,并进行事件风暴,探索 Mary 如何参加比赛。现在,事件风暴可以像故事一样阅读,捕捉团队现在或稍后可以探索的关键内容。从左到右阅读,Mary 使用她的图像和 ID 将她的猫咪加入比赛,然后该命令被发出到一个猫咪服务,负责保存猫咪的属性,如图像和所有者信息。

每当照片被上传时,我们会执行验证图片策略。团队知道他们正在进行一个关于图片上传的顺利路径(happy path)风暴,因此他们假设这张图片是一只猫咪。为了不去思考图片验证如何工作,他们用一个紫色的菱形便签做标记,稍后再处理。不适合家庭观看NSFF)过程是确保互联网上使用的图片对儿童是安全的过程。这个需求可以追溯到我们影响地图中的交付物。团队本质上在此处用紫色便签标记,知道在其他验证完成后可以返回处理。完整实现 NSFF 过程可能需要大量信息,可能还需要进一步的发现步骤。现在,我们已经收集了足够的信息,继续进行我们的事件风暴。

假设猫咪照片是有效的,我们立即通过锦标赛报名策略将 Mary 的猫咪加入比赛。锦标赛服务将来自多个来源的信息聚合成一个新的有意义的对象。它使用刚刚上传的cat.id、Mary 的 ID 和 Mary 的猫咪获得的投票数。锦标赛服务处理 Mary 的猫咪在排行榜上的位置,并与其他参赛猫咪进行比较。当动物爱好者们现在开始游戏时,他们将会修改当前比赛中每只猫的总票数。

在 PetBattle 的早期版本中,当没有足够的玩家上传自己的猫咪时,系统会从互联网随机抓取猫咪。在事件风暴(Event Storm)中,这个外部系统被捕获在一个粉色便签上。下面的示例展示了我们如何建模这种交互。我们从锦标赛服务中获取比赛猫咪,这时未找到猫咪,触发了空竞赛策略。每当发生这种情况时,我们总是从互联网随机抓取猫咪并将其添加到游戏中。在事件风暴中捕获第三方系统可以突出我们应用程序中存在的依赖关系。

图 9.28:使用粉色便签捕获外部元素

在 PetBattle 的事件风暴中,跨职能团队包括了他们的 UX 团队的代表。用户界面卡片为我们提供了创建高保真原型的能力。如果小组无法就某个问题达成一致,或者必须做出假设,则可能需要与一些最终用户进行验证。

在宠物对战的情况下,我们围绕排行榜的设计展开了一些激烈的讨论。我们无法就展示多少只猫达成一致,即是展示前 10 名还是仅展示前三名。我们也无法决定是否应该始终展示玛丽上传的猫,即使它没有吸引足够的投票进入前列。

我们决定把这个讨论记录在一张粉色便签上。这张便签代表着需要进一步探索的工作,并且可能需要一些用户研究,以了解游戏玩家希望在这里看到哪些功能。像所有工作一样,这可以被记录下来并加入我们的待办事项列表。团队也可以构建多个版本的应用程序,展示前三名猫和前十名猫。使用 OpenShift,我们可以部署这两个版本的应用,并收集一些关于用户参与度指标的反馈,帮助确定玩家更偏好的版本。在本书的下一部分,我们将探讨一些关于部署策略的选项,如 A/B 测试,这将帮助我们基于行为和影响来学习最佳解决方案。

图 9.29:使用粉色便签表示尚未最终确定的功能

在事件风暴练习结束时,流程代表了团队在仅知道现有信息的基础上做出的最佳猜测。它不是一个静态文档,而是一个在团队构建图上识别的一些功能时会被重新审视的文档。我们现在有足够的信息来设计系统组件之间如何交互。

我们将会看到所有这些信息是如何被其他实践在《Mobius Loop》中的选项透视部分第四部分,优先考虑它中使用的。

事件风暴的最终思考

事件风暴是一种出色的技术。它提取的领域知识以及完成的时间非常有力。它可以在商业层面上用来识别痛点和改进领域,就像价值流图一样。较低层次的事件风暴是一个很棒的、沉浸式的、协作性强的软件设计过程。我们强烈建议你尝试,它虽然很难做到完美,但多加练习就能成功。

在下一部分,我们将讨论我们发现技术架构的方法。现在我们已经完成了第一次事件风暴的运行,我们应该准备进行一些实验并编写一些代码!

新兴架构

此时,传统的架构方法通常是编写一个高层次或详细的设计文档。这通常需要几个月的时间,并且是由企业或解决方案架构师来执行的活动。它通常涉及大量的 UML(统一建模语言)图示和很多技术讨论与验证的页面。让我们看看一种更符合我们目前发现实践的方法。

渐进式架构的理念是,我们只需掌握足够的业务领域知识和足够的逻辑架构理解,以便交付高商业价值的功能。在这个阶段,可以决定主要的架构方法,比如解耦服务、事件驱动架构、API 和流媒体应用——接下来,这些方法将在随后的迭代中得到完善。

在我们的事件风暴中,我们深入研究了一个或两个 TOW(The One Wheres)。我们希望确认这些聚合体、命令和读取模型能提供正是该业务领域部分所描述的能力——不多也不少。

通过采取渐进式架构方法,我们不会在尚未带来商业价值的全面架构上浪费时间。也许未来产品的方向或特性优先级的调整意味着这个架构实际上永远不会被使用,从而避免了时间的浪费。

随着团队逐步交付解决方案,他们将获得更高水平的共享理解和对整体解决方案的信心。这意味着,任何未来的解决方案讨论都能从这种额外的背景和理解中受益。

在我们进行第一次事件风暴后,架构的初步草图如下所示。

图 9.30:宠物战斗事件风暴的第一次迭代

我们尚未完善 AUTH(身份验证)、NSFF 和通知流程的细节。这些通常会在后续的事件风暴实践中进行扩展。然而,考虑到书籍的线性结构,我们决定在这里添加这些内容,以便给你一个未来架构可能呈现的样子。

将事件风暴转变为渐进式架构

事件风暴应该很好地支持一个渐进式架构。事件风暴识别了需要构建的系统和聚合体。这可以给你一个很好的思路,告诉你软件设计应该朝哪个方向发展,但并不意味着所有内容都需要提前构建。你可能会通过用户实验了解到某些功能是不必要的,或者你对事件风暴的初步想法还不够完善。这可能听起来像是不良设计,但实际上这只是通过实验演化出来的更优质的设计。

我们与世界卫生组织WHO)4 进行了一次合作,旨在验证他们的架构并围绕其新的学习体验平台建立一个强大的跨职能团队。这是全球卫生专业人员将用来从世界卫生组织获取学习内容的平台。

与团队一起进行的初始事件风暴提出了一些有趣的需求服务。课程服务,这是一种目录类型的服务,以及一个高度可用且可扩展的身份验证提供者,被确定为关键组件。作为读者,您可能无法看到下面图中的所有细节,但这并不重要——它只是用来展示已经收集到的信息量。

图 9.31:我们 WHO 项目的事件风暴

我们的事件风暴并不复杂,但在经过多周和信息修订后,它看起来像这样。每个项目的细节并不是特别重要;可以说,长的粉色和黄色便签代表了我们需要从技术角度构建的独特 API。最终,通过我们的事件风暴,我们发现了大量的组件,但并非所有组件都与最终用户验证过:

图 9.32:在 WHO 事件风暴中发现的组件

我们知道我们有很多工作要做,但也知道我们不需要一次性完成。从我们的事件风暴中,我们迭代地构建功能和特性,根据需要将其添加到架构中。这意味着我们可以保持专注和精简,始终关注大局,同时避免使目标过于复杂。

图 9.33:第一次冲刺中出现的架构

我们在每次冲刺中都不断发展解决方案设计。我们将设计与事件风暴进行重新验证,只有在需要时才会添加新组件。我们的第一次冲刺引入了基本元素:一个简单的前端应用程序与 Red Hat 单点登录服务器的实例进行通信(上游项目称为 Keycloak)。

第二次冲刺中,我们添加了第一个微服务,这些服务在事件风暴中作为系统或聚合被发现。在随后的冲刺中,我们增加了更多的功能,推出了新组件,并对以前的组件进行了加固。我们为之前构建的服务增加了缓存,以提高查找性能。我们能够逐步构建,然后在打好基础后加入像缓存这样的复杂功能。

图 9.34:第二次和第三次冲刺中出现的架构

图 9.35:第五次冲刺中出现的架构

我们通过不断回顾事件风暴并建模更多的“某人之旅”,不断演化我们的系统架构。每次这样做时,都会发现新的服务。

在一个例子中,WHO 想要创建一个丰富的推荐引擎,帮助卫生保健工作者更快地找到相关的健康信息课程。我们需要收集偏好数据,以便能够提供推荐。因此,我们创建了一个入职流程来帮助收集这些信息。

在我们添加新功能时,我们不断与最终用户进行连接,提供了一个反馈循环,帮助我们验证最初的设计假设。到了最后一个迭代周期,我们系统的复杂性增加了,但我们架构中的每个项目都可以追溯到其在事件风暴中为用户服务的目的。

4 www.redhat.com/en/success-stories/world-health-organization

在前面的实际案例中,我们利用缓存加速了 API 调用。接下来,我们将查看一种实践,这种实践有助于揭示解决方案中的非功能性方面。

非功能性地图

非功能性属性有助于定义整个解决方案的可用性和有效性。非功能性地图是一个工具,用于捕捉并阐明团队努力交付的系统的非功能性需求。到目前为止,大多数实践集中在与业务特性或最终用户能够直接关联的功能性方面。

非功能性需求被划分为在画布上每个区域表示的部分,这些部分会被逐个检查并由小组成员逐步填充。

图 9.36:非功能性轮盘

我们可以使用这个映射工具,以可视化结构化的方式来获得对系统非功能性方面的共享理解。完成后,你应该能够清楚地看到解决方案的关键非功能性考虑因素。将这些内容可视化有助于突出显示需要工作的地方,以实现它们。

这是一个非常简单的实践,执行起来非常容易,且只需在合适的人员参与下一个小时就能完成:

  1. 确保所有参与者都对标题及其含义感到舒适。

  2. 对于每个标题,捕捉系统或解决方案的重要考虑因素或需求,并为每个项目添加便签。根据参与者的数量,你可以使用诸如 1-2-4-all 或分成小组等引导技巧来捕捉项目。

  3. 对于每个标题,将便签作为一个小组进行回顾。

  4. 在回顾时,思考每个便签是否应该是:

  5. 被添加到待办事项并优先排序的项目,因为它需要工作才能实现。

  6. 应该包含在其他待办事项完成定义中的一个项目。

  7. 其他待办事项的接受标准的一部分。

从非功能性地图到待办事项

诸如事件风暴或用户故事映射等发现实践是促进对话并为待办事项生成工作的出色工具。然而,当试图揭示解决方案的非功能性需求时,它们并不理想,因为它们通常更侧重于业务流程,推动功能特性和最终用户方面的解决方案。我们在最近的一个项目中与一个大团队一起使用了这一实践。我们分成了三个小组,每个小组都有自己独特的便签颜色。

图 9.37:非功能性需求图

这项活动为技术人员提供了一个很好的机会,讨论我们应用程序的非功能性方面。这个工具证明是一个有用的平台,可以邀请其他利益相关者参与其中,这些人并不直接参与构建解决方案,但他们在设计中有利益关系,比如安全团队的成员。团队对他们认为最重要的项目进行了投票,以便进一步讨论。

图 9.38:聚焦非功能性需求图中的部分项目

聚焦于轮图中的某些部分,如可靠性,团队提出了一些想法,如健康检查和就绪检查。从安全角度出发,高可用性和漏洞扫描成为需要探索的话题。我们讨论了我们认为最重要的项目,然后将其转录到我们的待办事项列表中。我们图上的几乎所有项目都是团队可以完成的工作。

一些项目被放入了我们的待办事项列表,作为技术难题(或未知因素)以便进一步探索,其他则很容易写成用户故事,供团队实现,例如 Kubernetes 的就绪探针和存活探针。

图 9.39:非功能性需求图导致的 Sprint 待办事项

非功能性轮是一种很好的实践,可以启动关于解决方案所有不同非功能性方面的对话。它可能不会在交付速度和将代码交付到生产环境的前置时间方面促进对话。这是 DevOps 的一个重点,因此我们希望在我们的发现循环中关注这一点。为了将焦点转向持续交付,捕捉一些交付过程的基准度量是非常有帮助的。

探索持续交付的案例

在本章中,我们探索了一些我们最喜欢的实践,帮助我们进行产品发现:通过影响力映射发现“为什么”,通过以人为本的设计实践发现“谁”,通过事件风暴和新兴架构发现“什么”。我们开始探索如何做的部分,使用非功能性图。我们想要介绍的最后一组实践,旨在促进我们如何开发软件,并且是基于度量的。

我们从客户那里得到的最常见问题之一是,他们如何能更快、更频繁地将软件发布给用户。通常,他们已经采用了敏捷交付技术(例如,他们可能正在使用 Scrum 框架),但似乎每年只能进行一到两次生产发布。这是为什么呢?

第一个线索可能是过度关注交付框架。正如我们在第一部分,《实践成就完美》中介绍的,当我们介绍 Mobius 循环时,成功的 DevOps 文化和实践要求在持续发现和持续交付之间保持平衡,同时需要强大的文化和技术实践基础。

要真正理解为什么将软件发布到生产环境需要这么长时间,我们需要了解瓶颈在哪里。我们需要可视化从业务利益相关者提出新功能请求的那一刻起,到该功能在生产环境中运行并被使用的那一刻为止的所有环节。

第一部分,《实践成就完美》中,我们介绍了价值链的概念。价值链专注于从生成初步业务需求列表开始,直到软件的大规模发布在生产环境中运行为止的所有环节。DevOps 的目标是加速这个价值链。一个有助于可视化所有这些步骤并捕捉关于它们所需时间的指标的实践被称为基于指标的过程映射MBPM)。

我们在第一部分《实践成就完美》中介绍了价值流图vsm),所以你知道它是一个宏观级别的图,展示了某项服务或产品从开始到交付给客户的端到端流程。另一方面,MBPM 是一个更为详细或微观级别的视图,展示了 vsm 中某些阶段或单个流程如何交付价值。这些详细视图展示了流程和指标,帮助提供瓶颈的洞察。

基于指标的过程图

让我们从一个简单的软件开发流程例子开始。在现实中,这张图会更为细致,展示更多的步骤和角色,但为了清晰起见,我们先来看这个简化的例子:

图 9.40:MBPM

地图的布局应该从左到右阅读:

  • 每个演员或角色都有一个水平泳道。角色位于最左侧的列(示例中的黄色便签,如 BA、开发、QA 等)。

  • 在开始练习之前,需要定义一个明确的起点和终点。在这个例子中,起点定义为收集需求,终点为将新代码部署到生产环境。

  • 每个非角色的便签都有一个与事件对应的标题。

  • 执行该事件的演员角色与演员便签水平对齐。

  • 事件从左到右流动,较早的事件位于左侧,较晚的事件位于右侧。

  • 并行发生的事件垂直排列。如果角色之间有共享的工作,那些也应当是分开的便签并垂直排列。

  • 每张卡片包含三个指标:

    • 提前时间LT):在正常工作时间内,从开始到完成完成任务所需的总时间。这包括任何空闲时间或等待其他角色完成工作的时间。

    • 处理时间PT):某人积极参与任务处理的总时间。这是交付时间的一个子集。

    • 完成度和准确度百分比PCA):任务首次尝试完成并且准确的时间百分比。这是一个随着先前依赖步骤的完成而累积的百分比。

    • 关于过程执行的任何有用信息,例如是否是批量处理或有调度(如果有,何时调度),以及有多少人参与执行任务。

图 9.41:交付时间与处理时间

在这个例子中,我们有一个软件交付流程,看起来像是传统的瀑布模型——收集需求、开发、测试和部署代码。通常,MBPM 会更为细化;例如,不是说开发者 实现代码,而是可以将其细化为:

  • 编写单元测试

  • 编写实现代码

  • 构建代码

  • 部署代码

  • 开发者的集成测试

  • 同行评审

  • 修复同行评审中识别出的问题

  • 文档编写

寻找和进行改进

一旦完成流程图,你就有了一个供团队和利益相关者吸收的沟通工具。流程图还能帮助管理层看到痛点。这些成本具有时间价值,而时间价值伴随着成本,因为这些问题会不断发生。如果你的管理层具有前瞻性思维,他们可能还会考虑到失去的机会成本。你可能会面临竞争对手更快、更便宜地交付价值,这正在侵蚀你的市场份额。

要进行改进,请寻找以下方面:

  • 交付时间远高于处理时间:这可能意味着某些任务被滞留在队列中或在等待其他人完成,这通常发生在交接时。有没有办法减少这种等待时间?

  • 完成度和准确度低:为什么这么低?如果答案不明显,这是与团队进行无责追溯的好时机。我敢打赌有人能够提供为什么会发生这种情况的见解。

  • 不必要的步骤:你能在没有某些步骤或者简化某些步骤的情况下交付价值吗?

  • 可以自动化的事务:减少人工步骤可以提高速度和准确性。

  • 改变操作顺序是否有帮助?也许提前完成某个步骤或者并行进行可能会提高准确性。

你可能会看到许多问题,可能会诱使你尝试一次性解决所有明显的问题。更好的策略是先从最大和最容易解决的问题入手,并从中优先列出一系列想法。记住,要在改进过程中保持灵活:专注于小范围的工作,以确保更快的工作交付。获得快速反馈循环,并根据反馈重新调整优先级。

一旦做出更改,你需要相应地调整你的流程图。你应该密切关注指标:在当前工作量下,指标是否有所改善?

图 9.42:MBPM

通过迭代进行改进

像这样的工具不应该是一次性的改进,它需要作为一个迭代性的努力来使用,不断进行改进。你的流程图应该是墙上的艺术品(如果你有一个物理上共处的团队),随着你的改进,它是活的、变化的。它在那里提醒并激励未来的改进。它对于新团队成员的入职也非常有帮助,因为你可以通过视觉化的方式向他们展示流程是如何运作的。

在我们的例子中,也许可以在测试方面增加一些改进,因此,下一步可能是某种形式的自动化测试。

当你解决了显而易见的问题并实施了更现代的行业实践时,你可能会发现自己进入了一个改进不再那么显而易见甚至有些值得质疑的阶段。这时候,度量标准可以帮助你判断改进是否让你变得更快。然而,为了弄清楚这一点,你需要进行实验。从技术角度来看,当你在早期处理显而易见的变化时,你实际上是在进行实验,但由于这些变化成功的可能性非常高,所以它可能并没有被看作是实验。随着你进入那些不那么显而易见的改动时,你可能会做错,并根据度量标准实施一个较慢的变化。这是可以接受的;你在学习(顶尖的表现者总是在学习)——只需要停下来反思。这是一个适合复盘的好时机,也许你可以改变想法,或者你可能需要回滚那个变化并尝试一些全新的东西。

我喜欢通过分层的方式来思考自动化改进。如果你在一家非技术公司的 IT 部门工作,该部门可能最初是为了自动化你所服务行业的业务流程而成立的。然而,随着时间的推移,IT 部门本身也需要被自动化,以更快地交付业务流程自动化。这是第二层次的改进。在这一第二层次中,你可以引入像 MBPM 这样的工具,帮助识别 IT 部门内可以改进的领域。例如,你可能最终会采用一些常见的行业解决方案,如自动化测试或 CI/CD 管道。你甚至可以将自动化推进得更远;你可能很快会意识到你需要围绕度量标准收集进行自动化。手动收集 MBPM 度量标准可能会耗时且重复,并且容易出错。为了加速反馈循环并释放人员处理其他工作,你可能希望自动化度量标准的收集。

当然,所有这些自动化工作是有成本的,但也可以从中受益,这就是为什么度量标准如此重要,能够帮助你判断收益是否值得付出成本。而且,你不应该等到度量标准收集自动化后才开始使用 MBPM。首先从简单的做起,了解流程以及你想收集的信息,然后在进入正轨后再进行自动化。

使用 MBPM 定义整个参与过程

在开放创新实验室,我们与客户进行时间限制的驻场合作。它们持续时间在 4 到 12 周之间。

在与芬兰电信公司合作时,他们的重点是改善特性开发的提前期,采用 OpenShift 的一些自愈和自动扩展功能,并深入了解 Kubernetes 和站点可靠性工程。

与我主导的其他大多数项目不同,我们在这个驻场项目中不会进行任何应用程序开发。没有新特性,没有新的业务功能,没有新的功能发布。这个项目纯粹是非功能性的,专注于提高团队负责的服务的运行效率。

这感觉是一个完美的场景来展示 MBPM 实践。这不是我之前在新的绿地产品开发项目中使用过的实践。但是,考虑到这是一个遗留的棕地应用程序,且专注于改进和优化,这种发现实践会非常有帮助。

团队由应用开发团队的代表、应用运维团队的代表以及基础设施专家组成。在几天的时间里,这三个人将他们在开发和运维过程中的所有知识结合在一起,创建了一个 MBPM。

图 9.43:跨职能团队构建他们的 MBPM

从中得出的几个关键学习点包括:

  • 由于预定虚拟机时间的服务级别协议(SLA),任何开发的开始都有较长的提前期。

  • 由于依赖第三方团队来触发这些操作,等待构建和部署完成时浪费了大量时间。

  • 由于进行大量的手动测试,一些质量保证工作存在较低的完整性和准确性。

下图中的黄色便签纸显示了所有映射到 MBPM 的流程。粉色便签纸代表了我们如何优化指标或甚至使流程完全过时的想法。

图 9.44:为芬兰电信公司创建的 MBPM

三周后我们回到 MBPM(在三次一周交付迭代之后),我们在蓝色便签纸上记录了由于引入了持续交付基础设施而带来的基于指标的改进。

图 9.45:为芬兰电信公司更新的 MBPM

这可视化并量化了引入容器、持续集成、持续交付、基础设施即代码和自动化所带来的好处。

让我们看看 PetBattle 如何执行 MBPM 练习,以捕获一些基准指标。

PetBattle – MBPM

当前的《PetBattle》应用部署过程非常痛苦,需要花费很多小时手动复制和部署工件到一个树莓派集群,该集群还是一个媒体服务器集合,安装在其中一位开发人员桌子下。

这让新的工程团队感到担忧,他们建议将现有的软件交付过程中的所有环节可视化。这样可以形成共享的理解,帮助他们更好地了解《PetBattle》这款爱好者应用是如何运作的,并思考如何在应用开始扩展时进行优化和改进。

由 Tim 领导的团队在墙上创建了一个 MBPM(管理业务流程图)。关于谁参与了发布《PetBattle》、相关人员以及某些事件发生的时间,进行了大量讨论。团队最终确定了以下流程,代表了发布周期中的实际情况。

图 9.46:《PetBattle》MBPM 的开始阶段

其中一位《PetBattle》的开发人员(Mike)会通过检出所有代码来开始发布周期。接下来的步骤是检查所有需要合并的功能分支是否都已正确合并。在找到所有正确的代码分支时,会有一些延迟。打包代码时失败的概率为 25%,因为更新配置值总是手动完成,且容易出错。部署到树莓派集群可以开始,通常需要 1 小时,因为这是一个 CPU 密集型任务。因为只有一个集群,所以部署过程中会暂停《PetBattle》服务。

通常,另一位对树莓派硬件最了解的《PetBattle》工程师(Noel)会花时间修补并更新集群操作系统和工具。在做这项工作时,通常会发现一些硬件问题,需要订购新零件。等待零件到货最多需要三天,且常常导致整个集群不可用,从而导致测试发布的等待时间过长。

接下来,团队中最优秀的测试人员(Donal)会执行手动测试计划。由于发布已经部署,用户通常在不知情的情况下使用新版本,常常出现大量故障。Donal 的测试通常会有一半的失败,通常是因为测试计划需要更新,或者有很多 BUG!Donal 认真地将发现的 BUG 录入电子表格,并与其他开发人员分享。通常,他会搞混细节,导致 20%的报告缺陷信息记录错误。

图 9.47:《PetBattle》MBPM 的结束阶段

开发人员会花费几天时间修复 Donal 电子表格中报告的所有缺陷。代码再次被合并、打包和部署,通常与首次部署时相似的故障率。

现在,最终用户可以使用经过完善的代码。通常,最终用户和 Donal 同时测试功能,他们会将缺陷和故障报告到 PetBattle 的邮件列表中。

通过这项实践,团队确定了几个他们一致认为应该实施的措施:

  • 使用云基础设施中的容器

  • 基础设施自动化

  • 持续集成

  • 持续交付

  • 测试自动化

  • 安全自动化

MBPM 是一种提供基准指标、识别流程瓶颈并推动改进案例的卓越实践。它确实需要一个心理安全的环境,因为这些指标可能并不特别令人愉快。团队成员不应害怕传播这些信息,也应该在共享信息时感到安全和诚实。

我们将在第五部分 – 交付它中回到 MBPM,届时我们将衡量并了解在交付循环中所做的改进。

结论

在本章中,我们继续了 Discovery 循环的旅程,重点讨论了我们将如何交付解决方案——或者至少,我们将如何开始。

我们研究了 DDD,特别是事件风暴实践,这有助于可视化业务流程。我们的事件驱动架构开始从事件风暴中逐渐显现。我们开始通过进行假设来进一步推动用户研究,以便形成我们的功能开发待办事项。

我们还研究了一些有助于形成我们需要考虑的非功能性工作的实践——非功能性地图和 MBPM。后者使我们能够获取与遗留软件交付过程相关的关键度量,并识别一些我们可以采用的持续交付实践,以帮助改进和优化这些过程。

本章和上一章为开发/配置工作提供了许多想法和候选项。它们都在实践中产生的工件中得到了可视化。

图 9.48:Mobius 循环的发现阶段

在下一章中,第十章,设定结果,我们将探讨如何将所有这些信息提炼成可衡量的结果。然后,我们将研究如何以一种能够开始逐步交付价值并且定期进行的方式来组织和优先考虑所有这些想法。

第十章:10. 设定成果

在上一章中,我们使用实践来帮助提取大量细节,以确认我们将解决哪些问题、为谁解决这些问题以及我们采用何种实验性和渐进式的方法来解决这些问题。

通过使用影响映射(Impact Mapping)和以人为本的设计等实践,我们现在应该对客户和业务痛点有了较好的理解。在开始开发软件解决方案之前,我们需要将这些问题转化为潜在的业务和客户成果。我们还需要利用从这些工件中获取的所有信息,以及从这些实践中获取的信息,确保我们交付的成果是真正有意义的。

在本章中,我们将探索到目前为止在 Mobius 循环的发现部分所进行的所有学习,以明确界定并清晰地表达团队致力于为满意的利益相关者和最终用户交付的目标和成果。这包括:

  • 解释输出与成果之间的区别

  • 为什么我们应该设定目标成果

  • 如何捕捉目标成果

  • 一些目标成果的示例

  • 如何可视化目标成果

  • 如何将目标成果与其他实践进行优化和链式连接

设定目标成果可以包括以最终用户(客户)为基础的成果,也可以包括以业务和能力为基础的成果。在使用 Mobius 时,成果应该是可衡量的。我们应该始终能够为当前状态和目标状态分配一个数值。通过定期检查这些度量标准,我们可以确定我们是否朝着成果前进,是否已经达成目标,或者如果没有达成目标,是否需要调整并改变方法。

什么是成果?

第一部分,实践成就完美中所述,Mobius 循环鼓励通过使用持续发现和持续交付的创新流,采用迭代的实验方法来实现成果。成果是通过做某事而产生的结果。在我们的案例中,成果是人类行为的变化,这推动了文化的改变,进而影响了长期的业务结果。

目标成果实践帮助团队发现、编写、对齐和共享他们希望通过应用产品实现的行为变化。共享目标成果有助于团队交付可衡量的结果,并与利益相关者和团队成员对齐。参考目标成果有助于优先排序和筛选工作,使我们始终专注于交付可衡量的影响。

目标成果实践涉及创建一个画布,总结团队和利益相关者所捕捉到的预期和希望的可衡量变化。这个文档作为一个信息展示板,强化了在其他实践、活动和讨论中使用的目标成果,这些内容来自 Mobius Loop 的其他部分。交付循环中的“衡量与学习”环节关注于评估成果。我们将在第五部分,交付它中深入探讨这一点。本章将讨论在发现循环(Discovery Loop)中设定成果。

图 10.1:发现循环

在前一章中,我们介绍了影响映射(Impact Mapping),并描述了影响(Impact)与交付(Delivery)之间的区别,影响是我们希望在行动者身上实现的可衡量变化,而交付则是可实施的工作任务。让我们更深入地探讨成果与输出之间的区别,因为理解这一点非常重要。

成果与输出

输出是团队完成的具有明确范围的工作成果。例如,实现一个功能、执行一个实验或进行一些研究、一个 UI 原型或技术探索。组织有时会推动增加输出,因为他们认为更多的功能意味着更高的价值。但事实并非总是如此。实际上,更多时候,少即是多,并不是所有的输出都能直接转化为价值。例如,如果顾客从未使用某个功能,或者根本没有请求过这个功能,那么实现这个功能并不会增加价值。想想你曾使用过的那些功能过载、但从未使用的系统,然而其中一两个关键功能却是一直在使用的。当团队将重点放在成果而非输出时,团队可以迭代出那些真正能带来价值的功能。

团队有时会发现很难识别共享的成果,因为成果与输出之间的区别不清晰,或者他们无法看到自己提出的输出与组织希望他们实现的成果之间的联系。

Joshua Seiden在他的书《成果高于输出》1 中描述了成果是驱动商业结果的人类行为变化。我们可以通过下图展示输出、成果与影响之间微妙但非常重要的区别。

图 10.2:输出、成果与影响

1 www.senseandrespondpress.com/managing-outcomes

通过将对话焦点转向成果,我们是在思考我们希望对用户和其他利益相关者产生的影响,正如通过影响映射(Impact Map)所展示的那样,影响与行动者之间的联系。从我们的影响映射中,我们已经识别出可以积极参与的不同行动者群体,通过实验、交付和反馈来测试我们的假设并学习。

为什么要设定目标成果?

目标成果实践能够与团队、利益相关者和客户建立共享的对齐和目标。

通过不断在显眼的信息展示板上展示目标成果,团队可以被提醒项目的成功标准。这些提醒引导团队在日常活动中朝着项目成功的方向努力,例如:

  • 对工作项的评估与优先排序

  • 其他 Mobius Loop 实践中的讨论

在开放实践库中,强调结果而非产出是一个基本概念。在新项目的早期就确定目标成果,以确保从一开始就对齐。现有项目可以运行目标成果实践,以评估团队的对齐情况,并验证项目是否具有共享的成果。

以下任何一种情况可能表明团队将从确立目标成果中受益:

  • 团队因成功在给定的时间框架和预算内交付了新产品或功能而受到赞扬。然而,交付后,团队惊讶地发现客户并未使用新产品。因为项目已经完成,团队无法迅速根据反馈调整产品或功能。

  • 日常的团队互动聚焦于完成功能,而不是完成一个能实现预期结果的功能版本。

  • 功能或解决方案的过度工程化,使其精雕细琢,远超实际所需。

  • 团队在客户与产品互动或提供反馈之前,庆祝了成功交付。

  • 最终的产品交付在市场上失败,尽管投入了大量的人力并遵循了敏捷交付方法。在交付过程中使用迭代并未成功交付一个成功的产品。

  • 团队交付的范围发生了意外变化。

让我们来看一下如何开展一个简短的工作坊来捕捉目标成果。

如何捕捉目标成果

我们通常在进行完 Discovery 阶段——例如创建北极星(North Star)和/或影响地图(Impact Map)等“为什么”和“谁”实践后,立即开展目标成果实践。我们常常在这些会议的最后,通过所有参与者的信心投票,来建立一个共享的视角,看看我们作为一个团队,觉得自己有多大程度的对齐。

这应该导致团队之间对上下文的共享理解。如果这没有起作用,可以考虑开放实践库中其他一些实践,例如“从终点开始(Start at the End)”2、“新闻头条(News Headlines)”3 或“从为什么开始(Start with Why)”4,来建立共享的团队目标或上下文。记住,开放实践库是一个工具箱,因此,如果你尝试过的工具没有完全奏效,不妨尝试其他工具!

基于小组从这些活动中获得的学习,使用静默协作技巧或其他头脑风暴技巧,创建一个潜在目标成果的集合。

使用亲和图 5 来识别潜在结果的模式和分组。通过小组讨论或其他技术(如点投票)达成共识,选出一小部分潜在结果。

审查潜在结果并将其结构化,确保它们是可衡量的结果。确定如何量化和衡量这些结果。识别一个共同的结构来表达这些结果。审查可衡量结果的列表。与所有利益相关者讨论结果,确保这些结果范围不会过于狭窄,并且满足每个人的需求。确保会议中的每个人都同意这些可衡量的结果。

为了帮助阐明有效的结果并评估提议的结果,Gabrielle BenefieldRyan Shriver在 Mobius 快速入门指南 6 中建议集中关注以下五个属性:

  1. 名称提供了一个简单的描述性语句,例如减少结账时间。动词通常是改进的方向(增加或减少),而名词是改进的主体。

  2. 衡量内容(规模)提供了一个一致的衡量单位。示例可以是完成结账所需的秒数。

  3. 如何衡量(方法)指明了如何以及何时进行衡量,例如使用每日的网络分析报告,其中包括谁负责收集和发布数据,以及这一过程的频率。

  4. 基准度量提供了当前水平和比较未来测量值的起点。示例可能是结账所需时间为 200 秒。

  5. 目标度量是期望的成功水平,既可以是绝对值,也可以是相对值。示例可以是结账时间为 60 秒或在第四季度结账速度提高 75%。目标度量可以包括一个日期,明确期望在何时实现目标。

2 openpracticelibrary.com/practice/start-at-the-end/

3 openpracticelibrary.com/practice/news-headlines-aka-cover-story/

4 openpracticelibrary.com/practice/start-with-why/

5 openpracticelibrary.com/practice/affinity-mapping/

6 mobiusloop.com/kit/

将这五个属性表示为类似于图 10.3的箭头图可以帮助可视化结果。

图 10.3:结果的箭头表示

存在其他格式,可以帮助定义结果。尝试不同的格式,看看团队是否能够更有效地使用它们来创建结果。例如,SMART 标准(在前面的图片中介绍过,下面再次总结)定义了目标或结果的五个特征:具体的、可衡量的、可实现的、现实的和基于时间的。

图 10.4:SMART 结果

更强的结果陈述是可以衡量的,或者可以分解为多个衡量标准。我们的交付方法以转向迭代式方法为中心,定期发布增量价值。我们希望先衡量目前的结果,然后在开始进行功能实验时,再继续衡量结果。

在开始交付时,我们应该能够定期暂停并衡量实验或倡议的影响,以确保我们从中学到东西,并且我们所做的工作要么朝着目标可衡量结果前进,要么触发调整或重新思考。

让我们来看一些例子。

目标结果示例

为了为本节中的目标结果示例提供背景,考虑问题解决时间的情境。

在早期的 Discovery Loop 活动中,软件产品团队使用亲和图分析客户反馈数据。分析显示,许多客户对解决软件问题所需的时间感到不满。团队认为,响应时间的不满正在影响客户留存率。

团队使用基于指标的流程映射MBPM)实践,正如上一章所介绍的,来识别对响应时间产生负面影响的重要瓶颈。通过这一练习,团队识别出几个效率低下的流程环节。

其他 Discovery Loop 活动揭示,运营相关方对更频繁的产品部署感到紧张。频繁的部署不能导致运营人员的额外计划内或计划外工作。

团队使用目标结果实践来:

  • 讨论 MBPM 实践结果和其他发现的 Discovery 实践。

  • 列出团队项目的潜在目标。

作为目标结果实践的结果,团队创建了信息辐射器,展示了三个目标结果。

在这个例子中,服务提供商此前有一项服务水平协议,承诺始终在 30 天内解决复杂的配置问题(如果需要,还包括现场访问)。随着即将进行的产品开发,目标是将解决时间缩短到 20 天。通过这一举措,组织希望将客户满意度调查分数(满分 5 分)从 3.2 提升至 3.5。希望在不增加操作人员额外手动工作的情况下实现这一目标。

图 10.5:目标结果示例

目标结果文档提醒团队:

  • 聚焦于寻找和设计能够减少客户问题解决时间的方案。成功的解决方案能够将平均解决时间减少至少 10 天,并且不会增加运营人员的工作负担。

  • 团队认为,成功的解决方案还将至少使客户满意度得分提高 0.3。

  • 如果团队能够交付一个实现部分(但不是全部)目标成果的解决方案,那么团队将利用选项调整的实践来决定是否需要调整方向。他们可能决定进行更多的交付(另一个交付循环),尝试将度量进一步推进到目标。或者在反馈之后,他们可能会认为新的度量已经足够,可以转向下一个目标成果。或者可能会进行一些重新排序。这些选项将在下一章中详细探讨。

  • 团队可能决定:

    • 重新审视发现实践,并根据团队从上一季度活动中学到的内容,更新下一季度的目标成果。

    • 追求不同的解决方案来实现相同的三个目标成果。

最好用可视化来表达和理解产品在实现目标成果方面所处的位置。

可视化目标成果

一个展示成果关键特征的单一视觉图帮助每个人清晰理解当前状态和期望的未来状态。在 Mobius 中,这些被称为成果跟踪器。随着想法的规划和实施,这些视觉图会更新,展示随时间推移的进展。公共空间中的可视化,如团队房间或社区区域,有助于在规划和设计活动中让成果随时可见。这种方式轻柔地提醒每个人成功的样貌,使其更加易于接触和理解。展示迄今为止取得的进展能给团队带来一种正向前进的满足感。展示缺乏进展或朝错误方向发展的情况,能促使团队做出更快的反应,甚至可能与实时回顾更新或停止工作事件结合使用——这些都是我们在第二章,建立基础中介绍的实践,旨在构建我们的开放文化基础。

图 10.6:可视化目标成果

可视化目标成果有助于达成一致、共享理解,并展开关于如何改进和优化的讨论。

优化目标成果

就像我们在本书中的所有实践成果一样,我们永远不会完成,随着我们从后续交付和更多发现活动中学到更多内容,我们总能改进。目标成果实践旨在为实践的实施提供方向。在我们衡量并从交付周期的输出中学习时,我们可能会发现一些我们在发现循环中做出的假设已经无效,并且我们可以利用这些新的学习重新进行发现实践,从而产生改进版的目标成果。

团队可以在整个循环中参考已达成的目标结果来指导决策。使用目标结果来优先处理工作,并拒绝不贡献于目标的计划外工作。

目标结果必须有一个时间框架。定期衡量每个目标结果的进展情况,包括在指定的时间段结束时。当每个目标结果的时间期结束时,团队将评估情况,并在需要时决定添加新的目标结果,并为其设定相应的时间周期。

随着我们从客户那里获得更多反馈,随着不断变化的产品增量,它可能会导致方向调整,甚至可能需要新的或修订过的目标结果。我们将在后面的章节中详细探讨这一点,特别是在探索(Discovery)阶段的选项转变(Options Pivot)和交付(Delivery)阶段的选项转变(将在《第五章,交付它》中讨论)。

目标结果不是一种可以单独进行的实践。它需要由多个其他实践来支持,并且其结果会推动新的实践输入。

将目标结果与其他实践串联起来

目标结果实践可以单独进行,但我们强烈建议在一些其他探索性实践之后进行。通常,利益相关者会在一开始就明确表示他们知道产品或项目的目标结果应该是什么。然而,在明确了产品或组织的北极星愿景,进行影响映射会议,使用以人为本的设计实践如设计思维,考虑通过事件风暴或 MBPM 设计未来状态架构之后,他们的看法可能会发生重大变化。由更深入的背景探索和对用户与利益相关者增加的共情支持的目标结果,可以更清晰地展示究竟要实现什么以及为什么要实现这些目标。

因此,我们开始看到将这些实践串联起来的影响。将所有实践的产物清晰展示给从业者意味着,他们可以随时关注他们从实践中获得的所有精彩学习,以便在他们将所有学习提炼为一组可以衡量的目标结果时,保持一种清晰的视角。

另一个不应被忽视的优秀实践例子是优先级滑块。在这个过程中,我们获得了许多利益相关者的对齐与共识,达成了一致的产品或项目优先级。因此,当我们阐述目标结果时,我们应该时刻提醒大家我们在此处达成的共识。

这是卓越引导真正发生的地方。一个优秀的引导者不会成为讨论背景和细节的中心,而是会针对在先前实践中产生的信息提出开放性问题。他们还会牢记团队应该聚焦的目标以及他们要努力实现的内容。他们希望达成可以衡量的目标结果。

准备一个有大量便签和笔的工作空间,有助于促进一个很棒的目标结果会议。这也可以通过使用如下模板,在虚拟环境中实现,模板可以从本书的 GitHub 仓库下载。

图 10.7:目标结果画布

让我们回到《PetBattle》团队,探索他们在“发现循环”之旅中产生的目标结果。

《PetBattle》目标结果

《PetBattle》团队对以下目标结果达成了一致:

  1. 《PetBattle》通过增加活跃用户基础产生了收入。

  2. 《PetBattle》始终在线。

  3. 一个充满热情的团队,激动地构建和运营《PetBattle》。

这里展示了三个不同的类别。#1 – 产生收入 – 显然是一个与用户活跃度增加直接相关的商业目标(这是我们可以验证的假设)。目标结果 #2 指的是产品《PetBattle》的稳定性和可用性 – 当该产品突然火爆,导致系统崩溃时,用户体验非常差,并且由于《PetBattle》无法上线供用户访问,根本没有机会产生收入!目标结果 #3 是基于团队文化的结果 – 拥有一支真正拥有产品所有权感的参与团队,他们会在工作中更加快乐,并且会更加积极响应、充满动力地在未来持续成长和改进产品(这是我们可以验证的另一个假设!)。

让我们来看第一个目标结果:即《PetBattle》通过增加活跃用户基础产生了收入。这显然是一个可衡量的结果。我们可以在任何时间量化系统中注册的用户数量、网站访问量、参与用户数量、用户进行的互动活动量,当然,还有通过广告商获得的收入。

我们可以很好的可视化我们目前拥有的基准指标,以及希望通过产品的短期增量达到的目标指标。

图 10.8:目标结果 – 活跃用户基础增长(以千为单位)

让我们来看第二个目标结果:即《PetBattle》始终在线。从应用程序的早期版本开始,就因其巨大的人气而出现了稳定性问题。我们现在可以收集一个指标,代表现有网站的正常运行时间,并可视化如何将其改善,达到始终在线的目标可衡量结果。

图 10.9:目标结果 – 始终在线

我们的第三个目标结果是:我们有一个充满热情的团队,激动地构建和运营《PetBattle》,并专注于员工的参与度和满意度。这也可以通过量化员工的幸福感和参与度来衡量和可视化。

图 10.10:目标结果 – 员工参与度和满意度提升

大量的对话和合作有助于达成关于目标成果的共识。当我们最终与所有利益相关方达成共识,确认这些是我们要努力达成的成果时,我们喜欢将它们保持高度可见且易于访问。它们应该成为我们从这一点开始做的所有事情的参考点。当我们开始交付功能和产品增量时,它们应该引领我们朝着已达成一致的目标成果迈进。通过从我们的产品中捕捉度量数据,实时获取我们的迭代交付数据,能够让我们审视、评估、调整和优化之前做出的决策。

在上面的示例中,我们看到三种非常不同的成果焦点。它们不仅仅涉及应用或业务,而是跨越了应用、平台的功能,以及团队的表现和动力。这是我们与许多客户合作时观察到的模式。

三者平衡:人/流程/技术

在我们的开放创新实验室驻场项目中,我们在 4-12 周的时间框架内,将客户的产品团队沉浸到 DevOps 文化和实践中,并使用 OpenShift。

我们开始这些合作时,会使用影响力映射(Impact Mapping)和以人为本的设计实践,旨在为合作设定一组目标成果。

我们发现,当成果不仅仅专注于技术或应用或文化,而是三者的结合时,最终合作的影响力会更强大。

我们早期的一次合作确立了以下三个成果:

  • 已经设立了与 DevOps 合作的新标准,包括团队的技能组合。

  • 一个新的应用程序正在生产环境中运行。

  • 团队在使用这个产品时感到非常棒、兴奋,并且充满力量。

其中一个成果与技术和平台(DevOps)相关。另一个与我们在驻场期间要构建的一个创收应用程序相关。最后一个是关于文化和团队的。

这是组织中第一个团队朝着 DevOps 发展时,目标成果的一个良好平衡。

上述成果确实缺乏衡量标准,也许没有达到 SMART 原则的要求。

最近与阿联酋客户的成果如下:

  • [客户]可以每天多次无缝部署工作负载,以证明自助服务能力。

  • [应用程序]作为示例无缝部署,证明了一个可复制的模式,用于部署其他 N 个应用程序,并减少变更的上市时间。

  • 开发是第一等公民。多职能团队通过使用站点可靠性工程(Site Reliability Engineering)最佳实践,作为一个产品团队获得赋能。

这些结果开始引入可衡量的概念。第二个结果中的魔法词是“减少”。这里有一个可衡量的结果,涉及减少市场变化的时间。这是一个可以在合作前、合作中以及合作后进行衡量的结果。

最后的例子来自一个专注于医疗保健的非政府组织:

  • 提高授权和技能水平,使得[应用程序]可以自信地拥有和管理。

  • 为[应用程序]创建一个更加开放的数据模型,使得[用户]能够越来越多地被我们的系统吸引。

  • 在一个可扩展、安全、稳定的平台上,明确的开发生命周期通过增强的安全性、自动化和监控(可审计性)来衡量。

再次,我们看到在这三个结果中,团队、应用程序和平台的平衡。所有三个结果也都可以进行衡量。

现在让我们来看一个简单的例子,说明我们是如何衡量一个结果的。

电信产品的目标结果——准备好秒表!

我最喜欢的一个以用户为中心的目标结果来自我们与爱尔兰一家电信公司合作的经验。

我们正在为企业客户建立一个新的企业门户,供他们浏览和购买公司手机。

团队使用设计思维实践与前三大企业客户的员工建立同理心。通过对这些用户进行同理心映射,并使用情景映射实践可视化他们的现有流程,突出了几个痛点和在新解决方案中提高用户体验的机会。

目标结果以一种涵盖“谁、什么和惊艳”的格式编写——谁将从这些结果中受益,结果是什么,什么是惊艳因素,或者说是衡量标准。

我们从发现实践中提炼出的三个结果是:

  • 员工可以在 30 秒内搜索、查看并选择他们想要的手机。

  • 员工可以下单升级他们的设备,并在不到 48 小时内收到新手机,且能继续使用现有号码。

  • 员工可以使用他们的公司凭证登录门户,因此他们不需要设置或记住另一个用户名和密码。

这些结果非常微观,专注于特定产品。前两个结果非常可衡量,你可以想象我们的团队在用户测试时已经拿出秒表来测试第一个!

我喜欢这些结果,因为当它们写在一个大而显眼的图表上时,它总结了我们团队空间旁边每个经过的人可以看到的目标。我们需要一个应用程序,让时间有限的用户可以快速完成他们需要做的事情,他们会迅速收到订单,它会顺利运行,并且他们不会觉得需要与另一个公司打交道——他们的体验将始终与他们所在公司的一致。

我们现在已经看到了几个在与不同客户合作过程中设定的目标结果示例。你可能注意到的一件事是,目标结果可以从非常关注最终用户到更关注用于开发应用程序的技术、平台和团队。这引导我们去探索主要结果(更多关注最终用户)和支持性结果(由平台和团队提供)之间的区别。

区分主要结果和支持性结果

在前两章中,我们使用了几种不同的实践来发现目标结果。它们中的一些更加关注功能性、应用产品和业务(为什么和谁),而一些则更关注非功能性(如何)。

当我们发现这些不同类型的结果时,我们可以探索它们之间的关系,看看是否存在层级结构。

Gabrielle BenefieldRyan Shriver 解释道 7,支持性结果,例如减少新开发人员的入职时间、提高测试自动化和减少构建及部署代码的时间,将有助于改善主要结果,比如提高客户体验。

这可能只是一个假设,需要通过实验和交付来验证。

7 mobiusloop.com/kit/

让我们来看一些常见的结果示例,它们是主要结果和支持性结果的混合体。

  • 提高客户的速度和质量

    • 提高客户满意度:使用诸如净推荐值(Net Promoter Score, NPS)等工具来衡量客户的幸福感。

    • 减少交付时间:从客户首次请求或联系到触发客户满意度的交付时间。

  • 提高质量

    • 减少停机时间:导致客户无法获得服务的运营事故数量。

    • 减少缺陷:影响客户的产品缺陷数量。

  • 可持续性

    • 提高团队满意度:衡量产品团队幸福感的指标。

    • 提高员工留任率:员工流失率。

  • 工作

    • 增加产出:每单位时间完成的工作项总数。

    • 提高可预测性:团队完成承诺的时间比例。

    • 提高敏捷性:调整到新优先级的时间。

  • 效率

    • 减少周期时间:完成交付活动的时间,诸如构建、测试和部署新特性等。

    • 减少在制品(WIP):团队在任何时刻正在处理的项目数量。

    • 减少技术债务:你在重构代码库上花费的时间和资源。

组织的具体背景将决定它们是否更关注这些结果中的一两个,并将其作为主要目标结果。如果动机更多地偏向于业务,主要结果可能会围绕产品的表现以及客户的满意度展开。

上述几乎所有的例子也都可以作为促进性结果,我们可以假设,改进这些方面将推动更多业务导向结果的改善。这些例子也引导我们关注探索软件交付技术(如 DevOps)和平台(如 OpenShift)。

软件交付度量

2018 年,由Jez HumbleGene KimNicole Forsgren所著的《加速:精益软件与 DevOps 的科学:构建和扩展高性能技术组织》首次出版。我们强烈推荐这本书,帮助解释如何有效地衡量软件交付团队的表现以及其广泛研究背后的科学和统计数据。

本书介绍了四个可以用来评估任何 DevOps 组织健康状况的指标。其中两个更偏重开发,衡量市场敏捷性;另外两个则更侧重运营,衡量可靠性。

  1. 变更交付时间:这是指从代码提交到源代码仓库到代码部署到生产环境的时间。较短的变更交付时间更好,因为它能加快反馈周期,使组织更能适应市场变化。

  2. 部署频率:这是指应用程序部署到生产环境的频率,是批量大小的一个指标。较小的批量大小意味着可以更频繁地进行部署,这也有助于提高市场敏捷性。

  3. 恢复时间平均值:这是指系统从生产环境故障中恢复所需的时间。我们希望将其减少,因为这很重要,因为我们需要确保加快交付的同时,不会牺牲因故障导致的差劲客户体验。

  4. 变更失败率:这是指需要回滚和/或修复生产环境中的问题的部署百分比,显然,我们希望将这一比率降低,因为它是稳定性的一个次要指标。

当我们将这四个因素视为软件产品的促进结果时,可以看到一个强有力的假设,即优化这些因素将对产品在市场上的表现和整体客户满意度产生积极影响。能够在保持低平均恢复时间和低变更失败率的同时,实现快速的变更交付和高频率的部署,将意味着一个整体高效的产品。这最终将转化为更高的 NPS、更高的转化率,甚至是市场份额的增加,因为客户通常更喜欢使用快速更新但又可靠的软件。

正如本书已经开始展示的那样,成功采用所介绍实践的团队将在这些软件交付指标中看到积极的效果。文化实践,结合基于指标的过程映射等其他实践,帮助识别瓶颈和交接环节,去除这些瓶颈可以加快变更的前置时间。当我们构建流水线时,使用的持续集成和持续交付实践使我们能够增加部署次数。而且,随着自动化程度的提高,以及通过回顾等实践进行的持续学习,我们可以将修复平均时间MTTR)和变更失败率保持在最低水平。

像 OpenShift 这样的平台提供了更高层次的支持。如果没有这些平台,我们将无法为在平台上部署的软件产品实现市场的敏捷性和可靠性。

那么,如果平台是最低级别的支持,是否存在可以基准化并定期检查的指标,以确保这种支持能够传递到软件团队,并最终传递到业务层面?让我们看一下几个平台采用指标。

平台采用指标

你可能想要捕捉的一些平台指标包括:

  1. 增长与采用率: 平台上提供的产品发生了多大变化?有多少服务在运行?

  2. 引导时间: 引导一名新开发者或运营人员加入平台并确保他们拥有完成工作所需的一切需要多长时间?

  3. 项目配置的前置时间: 在平台上配置一个新项目并使其在生产环境中运行并可用需要多长时间?

  4. 平台团队效率: 支持的应用程序数量与所需全职员工数量的比率是多少?考虑到平台采用应该带来规模经济,我们预计随着平台采用的增加,这一比率将大幅提高。

  5. 站点可靠性工程指标: 由于平台为站点可靠性工程思维方式提供了支持,一些指标和相关的实践可以被传播:

    • 服务可用性/服务级别目标

    • 剩余错误预算

    • 开发时间与问题/支持时间

我们再次看到这些度量标准对软件交付和平台上运行的应用程序操作的影响,这反过来又可以通过四个软件交付指标来衡量。这些指标可能会对软件质量、组织的产品以及客户和员工的幸福感产生滞后效应。因此,捕捉、衡量、可视化和分享这些指标的价值不容小觑。

让我们看看我们如何培养这种以指标为中心的思维方式。

持续的指标检查

我们从小处着手,专注于一个应用程序、一个团队、一个产品。我们不希望通过收集成百上千的 IT 指标来让事情变得复杂。相反,我们希望拥有一个由这些指标激励、支持并充满热情的团队。

首先,我们需要通过举办一个“目标结果”工作坊来基准化我们能做到的。这应包括捕捉关键的主要结果——理想情况下是两个或三个团队专注于实现的目标。

接下来,我们查看其他我们已知的或能找到的关于现有软件交付和平台的指标。如果我们能获得这些信息,就让它们变得可见。如果不能,我们就承诺开始收集这些数据。

我们使这些关于产品、软件交付和平台的结果和已知指标变得非常透明和可见。你越能透明地展示当前的表现,就越容易展示改进。使用“大可见信息辐射器”,改进是没有尽头的。信息的持续辐射将使基于数据的决策变得可能。这是一个持续进行的过程。一旦组织投入收集指标的工作,就该开始不断改进它们了。关键在于一种实验模型,这可能涉及流程变化和工作方式、新的架构,以及团队构成或组织结构的变更。我们将在下一章中探索这一点,并考虑我们的选择。

在此之前,我们将创建一个最终的信息辐射器,用于本轮发现循环(Discovery Loop)中的这一迭代,以提供我们在本轮发现循环中学到的所有内容的单页总结。

创建发现图

本节中描述的发现实践都围绕着促进组织内不同角色和职能之间的卓越对话展开。如果你曾使用我们在第八章,发现为什么和谁,以及第九章,发现如何中描述的发现实践,你将已经拥有大量的信息辐射器——一个北极星、一个影响图、人本设计文物,如同理心图、事件风暴、非功能性图、MBPM、目标结果画布以及基准指标。当然,还有许多其他在开放实践库中的发现实践你可能已经尝试过,每个实践通常都会产生至少一个信息辐射器画布。

每个这些文档中都包含着非常强大且有价值的信息,这就是为什么我们希望将它们全部保持可见,并让任何感兴趣的人(无论是团队成员还是利益相关者)都能访问它们。毫无疑问,会有错误的信息和不完整的文档——这正是这个过程的意义所在。我们只进行足够的发现,以便推进到选项转换阶段并开始交付的迭代。通过这一点,我们的意思是,利益相关者之间达成了共识,产品方向上有了共享的理解,并且交付团队对自己拥有开始交付所需的一切充满信心。随着我们不断学习,获得反馈,增加衡量指标,我们可以进一步改进和完善发现文档(或者甚至尝试一些新的做法)。

我们确实发现,将所有发现实践中的信息提炼并汇总成一个单一的摘要,并将其公开展示,作为信息辐射器供那些可能没有时间或兴趣深入了解细节的人使用,非常有帮助。拥有一张描述产品发现和目标成果的单页,提供了一个极好的机会来对齐和达成产品方向的一致。

目前有许多公开可用的画布,包括商业模型画布和精益画布。Mobius Loop 也开源了一个发现地图,这是一个简单的图形,用于总结:

  • 我们刚刚在做什么发现循环?

  • 为什么我们要这样做?

  • 当前情况如何?

  • 我们为谁做这些?

  • 什么是目标成果,我们如何衡量它们?

  • 障碍和最大挑战是什么?

图 10.11:Mobius 的发现地图

我们使用这张地图总结了 PetBattle 所有的学习成果。

图 10.12:PetBattle 发现地图

结论

在这一章中,我们重点关注了为之前章节中发现的业务问题和机会设定目标成果。

我们解释了什么是成果,以及它与输出的区别,如何捕捉它们、记录它们、衡量它们并将其可视化。

我们深入探讨了可衡量的成果和指标,并探索了主要成果与促进性成果之间的区别。在 DevOps 世界中,促进性成果主要集中在软件交付指标和平台指标上。

你可以在 openpracticelibrary.com/practice/target-outcomes/ 阅读更多内容并讨论设定目标成果的实践。

图 10.13:为交付循环添加进一步的实践,以设定目标成果

我们通过总结所有在 Mobius 发现地图上的学习,结束了这一发现部分。

现在,我们已经建立了开放文化和开放技术实践的基础,并在产品、软件交付和平台的一些基准指标下进行了产品发现;接下来,我们需要决定首先交付什么。

图 10.14:在开放文化和技术基础上完成发现循环的实践

在下一章,我们将进入选项转折点,探讨优先级实践,并规划我们可以增量交付的内容。我们将审视特性交付与非功能性工作的权衡,以改善软件交付和平台。我们还将探索 OpenShift 及其他平台提供的一些先进部署能力,帮助我们进一步决定如何为客户交付最佳价值和产品。

第四部分:优先排序

第三部分,发现它中,我们围绕发现循环展开工作。我们从为什么开始——为什么要开展这个计划?我们的伟大想法是什么?我们使用了北极星方法来帮助我们框定这一点。我们通过使用影响力映射实践来定义问题,并进一步了解背景,从而对齐我们的战略目标。影响力映射帮助我们汇聚了所有不同的参与者,这些参与者可以帮助我们实现目标或阻碍目标的实现。影响力映射捕捉了我们想要产生的可衡量影响以及我们希望为这些参与者创造的行为变化。从中,我们形成了关于不同交付物的假设陈述,探讨它们如何帮助实现这些影响。

我们通过使用以人为本的设计技巧和设计思维实践,如同理心映射和情境调查,进一步完善了对这一理解的认识,以便观察并与我们的参与者建立联系。我们使用事件风暴实践,生成对事件驱动流程的共同理解,探索了业务流程和领域模型。通过使用事件风暴符号,基于微服务的架构开始逐渐浮现。我们还通过使用非功能性映射和进行基于度量的流程映射,发现了设计中的非功能性方面。

发现循环提出了许多我们可以在交付周期中执行的想法——我们可以实现的特性;随着我们通过反复的事件风暴演练精炼和开发解决方案而出现的架构;通过用户界面原型或技术尖刺进行的研究,进一步验证我们的想法;与用户进行的实验,以帮助更好地理解他们的动机、痛点以及他们对价值的理解;以及我们可以建立的流程,以收集数据并优化指标。

图 11.0.1:选项转折——设置场景

仅仅通过第一次迭代的发现循环,我们就很容易从所有这些实践所带来的对话和互动中想出数百个不同的任务。从这些想法中构建任务可能像踩雷一样复杂,甚至可能需要数周,甚至数月的时间,仅仅为了从一次简短的发现循环迭代中为一个小团队生成任务!因此,我们需要小心,确保我们保持专注于交付价值、关注真正重要的结果,而不是在一个充满忙碌的世界中陷入分析瘫痪!

在离开发现循环之前,我们花时间将所有这些学习转化为可衡量的目标结果。这一过程始于与业务产品相关的主要目标结果,但我们也花时间识别一些次要目标和支持性结果,它们有助于支持开发,尤其是那些可以通过软件交付流程和基础平台(如 OpenShift)实现的目标。

通过大尺寸的可视化信息辐射器展示这些结果,并基准化和辐射支持性度量指标后,我们可以开始考虑那些源自探索环节的任务和想法。但我们只能通过始终关注这些结果,并确保我们所做的一切都直接或间接地推动我们朝着实现这些结果的方向前进来做到这一点。这才是最有趣的部分,因为我们将要探索如何实现这些可衡量的结果。

Mobius 使用选项一词,而不是解决方案或令人头痛的需求一词。在我们验证我们的想法之前,它们只是纯粹的猜测,所以称它们为解决方案或说它们是必需的并不合逻辑,也没有证据支持它们。相反,我们称它们为潜在的解决方案、选项,并且我们将在交付循环中测试它们,以验证或推翻我们围绕这些选项形成的假设。这推动我们采取一种更加数据驱动的方法,而不仅仅是猜测。

图 11.0.2:选项枢纽

当我们处于选项枢纽时,我们决定接下来要针对哪些结果进行定位。我们选择需要构建、测试、验证和学习的思想或假设,同时探索我们如何交付选项。我们还需要确定优先级。我们从来没有无限的时间和资源,因此优先级始终是实现商业价值和快速学习的关键。快速学习在这里是一个重要方面。我们希望生成能够验证或否定我们从探索环节(Discovery Loop)中得到的想法的选项,以便最终重新审视并改进这些想法。快速反馈是将探索成果与验证原型连接的关键。

第十一章,选项枢纽将重点介绍我们在开始交付循环(Delivery Loop)之前所采用的实践方法。在第七部分,改进与持续中,我们将在交付循环之后再次回到选项枢纽,在那里我们将根据最新交付循环迭代的学习和测量结果,决定基于这些发现接下来应该做什么。

第十一章:11. 选项转折点

在发现循环过程中,我们开始提出许多实施的想法。影响力图为我们提供了交付物,形成了假设陈述。以人为中心的设计和同理心映射方法直接为我们提供了来自用户的想法。事件风暴为我们提供了可以通过独立微服务实现的独立功能(由命令触发)(即编纂聚合)。基于度量的过程图和非功能性图给了我们如何加速开发周期、提升安全性、可维护性、可操作性、可扩展性、可审计性、可追溯性、可重用性等方面的想法,几乎涉及任何以“ability”结尾的内容!

发现循环后的下一步是选项转折,在这一阶段,我们使用的所有实践中的信息将汇聚成一个行动选项列表,用于决定接下来要交付的内容。

选项转折是 Mobius 循环的核心。在它的左侧是我们吸收所有学习和目标结果的地方,这些内容是我们在发现循环中对齐的。我们生成更多的想法,细化要交付的内容,然后选择要执行的选项。在本书的第十七章,改进它中,我们将探讨选项转折的右侧。在这里,我们根据交付循环已完成的迭代中的测量结果和学习进行调整。我们决定是进行更多的发现、更多的交付,还是完全转折。我们会细化下一步要发现或交付的内容。

请记住,我们在完全自治的跨职能团队中工作。我们没有单独的性能或可用性测试团队,因此不能假设与这些职能相关的工作项目会在墙的另一边处理!这让我们的工作变得更加困难,因为我们必须权衡不同选项的相对价值。我们需要在新功能开发、紧急 bug 修复、加速开发的平台改进、可用性改进、安全性增强等多方面做出决定,以提高我们的产品质量。

在本章中,我们将做以下工作:

  1. 使用用户故事映射方法可视化我们可能执行的所有工作。

  2. 使用价值切片方法将我们的工作组织成小而薄的价值切片,以便我们能够持续交付价值。

  3. 开始实验设计实践,以测试在发现循环过程中出现的假设。

  4. 通过探索帮助我们进行选项转折的不同实践进行优先排序,包括影响与努力优先级如何/现在/惊叹优先级设计冲刺

  5. 根据所有前述方法的可追溯性,形成初步的产品待办事项

  6. 设置产品待办事项精炼,使其持续进行。

  7. 经济优先级应用于产品待办事项。

  8. 解释产品负责人角色在实现上述目标中的重要性。

  9. 探讨如何通过 OpenShift 平台启用的一些先进部署能力来支持实验,并且我们如何计划利用这些能力,确保我们最大化从交付循环中获得的学习。

让我们从我们最喜欢的可视化实践之一开始,通过切片价值来辐射并规划大量小的增量发布。

价值切片

我们正接近 Mobius 心智模型的部分,在这一阶段我们将开始交付我们解决方案的增量。它们可能包括运行短期原型和技术实验或“尖峰”测试,进行明确的用户研究,或者实现通过事件风暴和其他发现实践得出的功能。

交付循环的迭代没有规定具体的时长。如果你正在使用如 Scrum 这样的流行迭代敏捷交付框架,那么交付循环的迭代可以很好地转换为一个 sprint(一个固定的时间框,通常为一到四周)。如果你使用的是如 Kanban 这样的更为持续的交付方式来支持价值的持续流动,那么每个交付循环可能仅代表处理一个产品待办事项并将其交付到产品中。你甚至可能使用非敏捷的交付方法,如瀑布模型,在这种情况下,交付循环可能更加单一且移动缓慢。Mobius 循环对交付方法是中立的。但无论交付方法如何,一致的理念是:我们追求尽早交付高价值工作,更快地建立重要的学习,并以小批量交付的方式工作,这样我们可以衡量并学习影响,从而为下一轮决策提供参考。

为了帮助我们将所有工作项拆解并确保它们被分组到可以形成小增量价值的程度,我们使用流行的可视化和规划实践。

简单的路径映射技术通过从目标结果反向映射到实现该结果所需的最少步骤来拆解工作。还有许多其他的实践,如旅程映射、故事映射、未来状态映射、服务蓝图等。Mobius 更关注的是结果,而不在乎具体怎么做,只要你专注于找到交付结果的最简方式。我们发现非常有效的技术之一叫做价值切片。

让我们看看如何进行价值切片。

首先,我们记录下所有通过发现实践生成的独立工作想法。我们现在关注的是输出(而非结果),因为我们希望将所有交付物进行分组,并形成一个增量发布策略以实现这些结果。一个起点是从现有文档中复制以下内容:

  • 在影响地图上捕捉的交付物

  • 在事件风暴中捕捉的命令

  • 在同理心地图上捕捉的想法和反馈

  • 支持在非功能性地图上做出的决策所需的非功能性工作

  • 在讨论基于指标的流程地图MBPM)时捕捉到的想法和非功能性特性

  • 在任何其他发现循环实践中产生的所有其他特性和想法,以及发生的许多讨论。

以下是我们从经验中总结出的一些技巧。首先,不要仅仅把便签从一个工件移动到这个新空间。你应该保持影响图、事件风暴、同理心图、MBPM 和其他工件作为独立的工件,完整地保留原始形式。我们在进行交付循环时会再次使用它们,它们会非常有用。

其次,逐字逐句地复制你从这些实践中提取的内容。正如我们在接下来的章节中将看到的,当我们能够追溯工作项通过发现循环、选项转折和交付循环时,我们将从中获益良多,因此保持语言一致性对这一点很有帮助。有些团队甚至从一开始就投入于一个关键或编码系统,以展示这种可追溯性。

图 11.1:从发现循环实践中收集信息和想法

首先,将所有物品放在一个大工作面上。站在远处看到我们所知的所有可能工作内容会让人感到非常满足。看到那些少数的实践竟然产生了这么多想法,真是令人惊叹。当然,这也可能会有些混乱和令人畏惧。因此,我们需要开始整理这些工作内容。

如果你与分布在不同地点的人们进行虚拟合作,拥有如下的画布(并且可以从本书的 GitHub 仓库下载)可能会有所帮助:

图 11.2:用户故事和价值切片图模板

接下来,删除任何重复项。例如,你可能已经在影响图中识别了一个可交付成果,而同样的特性可能也出现在你的事件风暴中。你的用户访谈可能还发现了同样的特性,这些特性已被记录在同理心图上。对于那些重复的特性,删去重复项。如果某个想法可以拆分为更小的独立想法,就重构并重新编写便签,包含这些多个想法。越多越好,这在这个实践中非常重要!

下一步是将每个项目分类为某种共同的主题,并为该主题命名。我们要寻找的是能够将所有项目集中在一起的东西。如果你将每个项目放入一个桶中,这个桶的标签会是什么?一个顶级技巧是,从发现循环中得出的目标结果开始,并将它们设置为分类每个项目的标题。我们这样做的原因是,我们想要采用以结果为驱动的思维方式。我们已经确定了一些目标结果,因此实际上我们正在考虑的每个工作项应该都在帮助我们实现一个或多个这些结果。如果我们选择任何一项工作,并且无法轻松地看到它将有助于实现什么结果,那么我们应该质疑做这件事的价值。(有些情况下,这些无法映射到结果的项目仍然很重要,所以如果发生这种情况,就给它们单独留一堆。)

我们应该最终将所有项整齐地排列成一列,直接位于它们所归类的目标成果下方。

如果我们有一组良好且经过深思熟虑的主要成果和支持成果,那么将所有特性、实验、研究想法等映射到成果上应该是一个非常积极的练习。这个练习应该是协作性的,涉及跨职能团队的所有成员。开发人员、运维人员、设计师、产品负责人、业务专家等等,都会参与并为之前的发现循环实践提供输入。他们应该继续参与选项调整,以确保他们的想法和倡议被理解并包含在图中。

最终的工作可视化应该包括功能特性和非功能性举措。所有可以在平台上进行的工作,以促进更快速、更安全的开发,以及更快速的产品特性发布,都应该显示出来。如果我们在练习结束时回顾,我们应该开始看到我们的交付循环开始浮现。

图 11.3:根据目标成果对任务进行集群

下一步是优先排序板上的所有任务和项目。这从来都不是一件容易的事,但几乎总是需要的。如果你曾经参与过一个项目,时间不是问题,且很明显团队将有足够的时间来自信地交付所有要求的内容,那么你就处于一个独特的位置!这种情况从未发生过,我们总是需要优先排序工作并决定不做哪些事情!这可以从产品负责人决定他们对优先级的看法开始。然而,随着我们深入本章,我们将探讨一些可以在协作环境中帮助优先排序并推动达成共识的实践和工具。执行这些实践可以反映在我们正在创建的价值图上。

我们喜欢尝试对每一列进行优先级排序。因此,拿出每一个目标成果,并将所有我们认为能够实现它们的特性和其他项进行排序。最重要和最有说服力的项应该排在最上面。这些项是如果你想实现目标,必须优先于其他一切的项。那些理解较少或者“可有可无”的项应该排在下方。

最后的阶段是从价值图中提取价值。我们使用一些胶带(理想情况下是有色的,比如画家胶带),请负责整体工作优先级排序并阐明价值的人(通常是使用 Scrum 的团队中的产品负责人)来横向切分他们认为是整个产品价值的一部分。这意味着查看每个主题中最重要的项,并将它们与其他主题中的一些高度重要项结合起来。

图 11.4:在集群中优先排序工作

在这一点上,我们的产品负责人拥有巨大的权力。他们可以在给定的结果中进行优先排序。他们可以对整个结果进行优先排序,并将所有事项向上或向下调整。他们可以将来自不同结果的项结合起来,形成提议的发布版本。他们可以切出一、二、三或五十个价值切片——每个切片包含一项、两项或更多项。最重要的是,他们可以与所有利益相关者和团队成员进行对话,以达成共识,完成这个二维价值切片图。

图 11.5:二维价值切片图

在多年的实践中,我们积累了一些促进活动的小窍门,帮助正确地解释这些方法。第一个窍门是如何将两项有价值的活动进行可视化和规划。

啤酒与咖喱

2017 年,我带领了一项与全球石油公司的合作。第一周结束时,团队已经很疲惫。这一周很忙碌且紧张。我们组建了团队并建立了文化基础。我们已经进行过几轮探索环节,包括用户同理心地图和事件风暴,这些活动涉及了很多站立、思考和讨论。

星期四下午,我正在主持基于事件风暴和同理心地图上所有捕获项的用户故事映射和价值切片。这项实践对团队来说是新的。在我们将所有工作展示在墙上,并根据结果对其进行整理后,我谈到了切片和优先级排序的必要性。

我开始时说,显然,我们希望完成所有这些工作,之后,一位高级利益相关者打断了我,说,是的!我们需要做所有这些工作。我能感觉到利益相关者之间有些不安,仿佛我在做典型的咨询工作,试图确定范围,而利益相关者希望将所有东西都做出来。也许我选择的措辞可以更好一些。

图 11.6:解释价值切片

但我并不是在决定哪些属于范围,哪些不属于范围。我的整个敏捷思维模式基于灵活的范围,能够随着我们学习的深入调整和改变范围,并始终确保我们在交付下一个最有价值、最重要的工作。

为了说明我的思维过程,我的思绪迅速转到我们当天计划的团队社交活动。那一周非常漫长,我们计划去喝几杯,吃点咖喱——通过这种方式进一步增强我们的文化基础,让团队放松,彼此更好地了解。

我很期待在喝完那杯啤酒后再吃一份咖喱。事实上,我真的非常期待那杯啤酒。我觉得那一周我们真的赚到了那杯酒,举杯庆祝和我的新团队一起说声“干杯”一定会很棒!但这并不意味着咖喱不重要,也不意味着咖喱不会发生。我们会先喝啤酒,然后吃咖喱。这就是我们当晚的优先顺序。我们没有剔除任何工作,也没有计划去做。啤酒在我最重要的价值切片中,咖喱则在我第二个价值切片中。

团队在了解到我们并没有去剔除任何工作,而只是按价值进行组织后,感到更加轻松。团队成员也非常放松,并享受了一杯啤酒和一份咖喱!

我们还学到了一些简单的技巧,可以帮助有效地设定价值切片的实践。

从一到少到多的价值切片——持续交付

多年来,在主持这个活动的过程中,我学到了各种技巧。

在我第一次进行这个活动时,我们将所有工作按照目标成果组织成了几列,并进入了切片环节。我把一条胶带放在墙上,要求产品负责人和利益相关者将他们认为最有价值的便利贴放在胶带线以上,将较不重要的便利贴放在胶带线以下。

当我观察团队在这个过程中工作的时,我意识到那一条胶带线产生了一个误导性的实践观点。大家不愿意将任何东西放在那条线以下,因为有一种误解认为这意味着超出范围。我解释说情况并非如此,我试图做的是划分出最小可行产品MVP)。MVP 定义了可以构成产品的最小特性集,这个产品可以发布给用户,从中学习并进行迭代。实际上,许多利益相关者将定义 MVP 视为一种负面事情,因为这是他们失去所有创新性特性的时候,这些特性他们可能希望拥有,但并没有被认为是重要的。我实际上尽量避免使用 MVP 这个术语,因为它通常会引发一些负面情绪。

我从这次引导中学到,切片永远不应该只使用一条,因为我们并不是在定义哪些内容是“在范围内”或“超出范围”,也不是仅仅定义 MVP。

在与芬兰的另一位客户合作时,我将这些经验教训应用到我的引导方式中。对于所有从发现循环中捕获的项,我在地图上制作了三条胶带切片。希望现在产品负责人和相关方不会再掉入范围内/范围外的陷阱。然而,现在出现了一个新的误解!对于这次特定的合作,这是一场为期四周的沉浸式开放创新实验室驻场,专注于改进运营,我们计划了三次为期一周的冲刺。巧合的是,我制作了三条胶带进行价值切片。因此,相关方和产品负责人认为,我们在第一条切片中放入的内容将形成冲刺 1 的范围,第二条切片是冲刺 2,第三条切片是冲刺 3。

图 11.7:从发现循环中捕获的项的价值切片

我解释了情况并非如此。我们目前还不知道团队需要多长时间才能交付每个切片中的每一项。我们将使用交付循环中的其他实践来帮助我们了解这一点。最终,我们可能会在一个冲刺中交付多个切片,或者,交付一个切片可能需要多个冲刺。我们现在还不确定。

从那时起,我进一步调整了我的引导方式。当制作切片时,我现在会产生大量的切片——至少 10 个,有时会超过 20 个。我还会让胶带卷易于获取,并告诉产品负责人使用他们想要的任意数量的切片——事实上,越多越好!我发现现在的价值切片图通常会有更多的切片。

一位来自英国国防公司的产品负责人曾经对我说,每个价值切片板上的项目都可以看作是它自己的价值切片。听到这个我笑得非常开心。是的!当我们达到这种思维方式和方法时,我们真正实现了持续交付的目标。

图 11.8:多个切片的价值切片

可视化并切片增量价值的做法源自 Jeff Patton 在其 2008 年出版的《用户故事映射》一书中的惊人思考和工作。1 用户故事映射是一种有效的实践,用于创建轻量级的发布计划,可以推动迭代和增量交付实践。我们强烈推荐阅读 Patton 的书并尝试他在第五章中描述的练习,关于如何可视化并切片出一些非常简单的价值,比如你早上起床、准备并去上班的所有过程。我们在我们的赋能研讨会中使用这个练习,发现它确实能让这个实践生动起来。

让我们看看 PetBattle 团队是如何进行价值切片的。

1 www.jpattonassociates.com/user-story-mapping/

PetBattle —— 朝着持续交付方向的价值切片

PetBattle 团队回顾了他们在第一次发现环中产生的所有工件。

影响地图(Impact Map)确认了“提高上传者的站点参与度”是他们希望投资于实验和构建初始功能的地方。玛丽(Mary)的用户共情图进一步支持了构建比赛服务和实时排行榜的想法。团队通过事件风暴(Event Storming)探索了玛丽参与每日比赛并赢得奖品的流程,分解了事件流程,识别了命令、读取模型、一些 UI 设计和聚合点。基于指标的流程图(Metrics-Based Process Map)识别出了现有 PetBattle 部署步骤中的一些瓶颈,主要是由于缺乏自动化。最后,团队集思广益,探讨了所有非功能性考虑因素。

他们将所有从这些结果中得出的功能复制到新的便签上,并将其分散在墙上。

然后是考虑价值切片图(Value Slicing Map)的标题的时刻。团队回忆起他们将所有发现环(Discovery Loop)的信息和学习提炼为三个主要目标结果:

  • PetBattle 通过增加的活跃用户群体正在产生收入。

  • PetBattle 始终在线。

  • 提高团队满意度,激发建设和运营 PetBattle 的热情。

他们还确定了一个额外的支持性结果:

  • 减少对客户有影响的操作事故。

这四个结果形成了 PetBattle 价值切片图的骨架。

图 11.9:目标结果骨架

当团队进一步探索这四个结果时,他们认为将其进一步拆解可能有助于与利益相关者共享理解。影响地图(Impact Map)聚焦于四个结果:

  • 提高休闲观众的参与率

  • 上传量增加

  • 增加上传者的站点参与度

  • 增加赞助竞赛的数量

总体而言,这些都将有助于第一个主要结果,即 PetBattle 将通过增加的活跃用户群体产生收入。因此,这些被添加到价值切片图(Value Slice Map)中:

图 11.10:第一个目标结果的细分

第二个主要结果是 PetBattle 将在线。

团队反思了他们的非功能性图(Non-Functional Map)部分,并认识到三个结果将有助于实现这一目标:

  • 改善站点可靠性

  • 提高可维护性和可支持性

  • 增强可审计性和可观察性

图 11.11:第二个目标结果的细分

当团队讨论第三个主要成果——提升团队满意度并激发建设和运营宠物战斗的热情时,他们的对话都集中在实现出色的可测试性上。拥有一套技术实践基础,能够让他们自动化不同层次的测试,并进行用户测试,利用先进的部署技术,这将使他们非常高兴。他们还回顾了在团队形成和文化基础建设过程中提出的一些想法——社交活动、一起吃早餐和午餐、以及成立读书俱乐部,这些都是有助于改善团队文化的想法。因此,他们添加了以下重要的标题:

图 11.12:第三个目标成果的拆解

最后,他们得出了“启用成果”,即通过减少对客户产生影响的操作事件,帮助推动其他所有成果。这也可以分为三个领域:

  • 降低安全风险

  • 提高可重用性

  • 增强性能和可扩展性

图 11.13:第四个目标成果的拆解

因此,他们在一面墙上展示了大量的输出,并在另一面墙上有组织地列出了成果标题:

图 11.14:所有目标成果的两级拆解

现在是时候通过在每个成果下方形成列,将输出与成果连接起来了。

他们从第一个主要成果开始。这里的输出主要来源于事件风暴中的命令,得到了对影响图中的聚焦影响和同理心图中捕获的高度动机的支持。

图 11.15:交付第一个目标成果的输出

移动到第二和第四个成果下的输出来自 MBPM 和非功能性地图。第三个成果也如此,其中还包括了在建立文化基础时通过早期社会契约和实时回顾所捕捉的一些想法。

团队最终得到了一个用户故事地图,展示了初步的宠物战斗旅程,以及团队将要进行的交付和支持之旅:

图 11.16:宠物战斗用户故事地图

这是第一个信息展示板,展示了一个应用程序的功能特性、构建、运营和改进平台的工作,并激发了一个充满热情的高效团队。

最后的步骤是开始切分整个工作中有价值的增量。与产品负责人合作,团队急切希望确保所有成果在某种最小的形式下早早被解决,并且随着更多增量的交付,他们将持续改进每个成果。

图 11.17:宠物战斗用户地图的价值切片

观察价值的顶部切片带来了一种激动的感觉。团队可以看到他们将要做的第一项工作,以使宠物战斗的愿景成为现实!

openpracticelibrary.com/practice/user-story-mapping的开放实践库中,有越来越多关于这些实践的有趣链接、对话和更多信息。请查看一下,如果你有故事或经验可以分享,你也可以帮助进一步改善这项实践。

现在我们已经了解了强大的用户故事映射和价值切分技术,接下来我们将探讨一些其他实践,帮助使这一过程更加成功和协作。我们常常发现,人们在使用用户故事映射时面临两个挑战。首先,他们不知道如何将所有内容放到用户故事地图上。其次,优先排序和切分价值的方法对一些人来说可能很困难,也可能缺乏协作。

让我们首先看看第一个挑战。

当我们介绍用户故事映射实践时,我们说过我们从复制所有输出、交付物、功能以及在探索循环中使用的实践所浮现的想法开始。这听起来非常简单和直接。事实上,这实际上只需要一个人的复制粘贴功能,将在影响地图中捕获的所有交付物、在事件风暴中捕获的所有命令,以及在讨论 MBPM 时捕获的所有想法和非功能性工作复制过来。

但这就足够了吗?仅仅依赖几天前的灵感,是不是关掉了增加创新的潜力呢?一种稍微不同的方法是,不仅仅将用户故事映射和价值切分看作是交付功能。我们可以尝试转向更具实验性的思维方式,在选项转向(Options Pivot)期间,我们真正想设计一些可以在交付循环中执行的实验。

实验设计

所有关于新产品、服务、功能的想法,甚至任何可以引入的改变,以使事物变得更好(更多增长、增加收入、增强体验等等),都从假设或前提开始。在传统的规划方法中,团队可能会根据某种形式的投资回报分析来下注选择执行哪个实验,同时在过程中做出更多的假设。

实验设计是这种方法的另一种替代方式,我们尝试尽早验证我们正在做的许多重要想法/假设/前提。一些实验的对象可能我们希望保持开放,直到我们获得一些现实世界的证据,这可以通过一些高级部署功能(如 A/B 测试)来完成,我们将在本章稍后讨论这些内容。

实验设计是一种实践,我们用它将想法、假设或前提转化为具体且明确定义的实验集合,这些实验可以进行,以便实现验证或否定——也就是说,为我们提供宝贵的学习。

实验设计是一种万无一失的推进解决方案并快速学习的方法。它可以提供一种快速演化产品的方式,推动现有和新产品的创新,并通过小赌注帮助自主团队实现领导意图。

你可能需要为每个项目(想法、假设、假设)进行多次实验。一个实验通常只改变产品或服务的一小部分,以了解这种变化如何影响我们的目标结果。实验的数量实际上是根据你想学到什么,以及你将引入多少种不同变化来定义的。

定性反馈与定量反馈

在 2018 年与一家道路和旅行保险提供商进行实验室驻留期间,我们有机会设计关于早期原型的实验,旨在重新构建移动应用程序,提升用户体验并增加对话量。

我们希望衡量当前应用与与业务利益相关者共同集思广益的改善体验的一些想法,因此我们为测试用户设计了一个实验。我们建议他们表现得像在真实生活中一样,按他们通常的习惯停下,按他们通常的方式阅读,等等,在使用应用程序时。

该应用程序的作用是引导用户完成汽车保险预订过程。为了完成此过程,他们需要提供车牌号码、社会安全号码和居住邮政编码。

每个用户被引导访问一个网址(理想情况下是在移动设备上),以部署运行在 OpenShift 上的应用程序。他们被指示选择一辆车,并尝试比较并购买看似最适合用户的保险。实验在用户购买保险时结束。(备注——每个用户都被告知这是一个测试,提供的支付详情是测试信用卡信息,不会有任何资金转移。)

A/B 测试意味着可以对不同的用户使用不同的页面展示方式,从而在用户访谈中测试不同的原型。

来自实验的定量数据展示了应用程序使用过程中的时长、流失率和完成率。

来自相关用户的定性数据突出了用户体验中的痛点,指出了一些仍然存在的困惑,并验证了一些积极的体验。

结合定性和定量反馈提供了对最合适方法的确认。这意味着产品团队可以基于数据的验证,自信地编码“最佳”方法。

这个过程从开始到结束,耗时一周。

实验文档的格式与内容同样重要。正是内容告诉你实验设计得有多好;例如,实验设计是否允许过多的机会,使得结果可能模糊不清?

好的实验需要以下最低细节才能成功:

  • 假设:通常以一句话形式表述,常常表达一种假设。

  • 当前条件:现在的情况如何(尽可能可度量)?

  • 目标条件:我们试图实现什么(尽可能可度量)?

  • 障碍:什么可能会阻止我们达到目标条件?什么可能引起干扰或噪音?

  • 通过:我们如何定义一个积极的通过?如果目标条件可能无法始终达到,那么我们认为足够显著的变化是什么,以得出实验正在确认假设的结论,即通过并获得积极结果?

  • 衡量标准:我们如何衡量进展?

  • 学习:始终记录结果和学习内容,这些内容理应引导出更高阶的实验。

一旦描述完毕,实验就可以实施、跟踪并测量,以分析结果。在理想的世界里,实验将有二元的成功/失败标准,但大多数情况下,我们需要使用统计方法分析数据,找出实验引入的变化与目标结果的变化之间是否有显著的相关性。

注意

成功的实验不是那些证明我们的假设是正确的实验。成功的实验是那些提供有效和可靠数据,显示统计上显著结论的实验。

实验设计如果没有发现或交付,那就毫无意义,这也是将此实践与其他实践结合的主要原因。实验来源于在发现循环中的实践中收集的信息。假设在影响映射中形成。假定在事件风暴、同理心映射和其他以人为本的设计实践中记下。创意在所有发现实践中被捕捉。

实验需要优先排序,因为我们在有限的时间内能做的事情有限。将此实践与各种优先排序矩阵结合使用,如影响-努力优先排序现在-如何-惊艳优先排序,甚至是像加权最短作业优先这样的经济优先排序方法,帮助很大。我们将在本章的下一部分探讨每一个。

实验通常首先通过快速原型实现,这需要用户研究和用户测试,我们在交付循环中进行这些工作。这样的组合即使在编写任何代码之前,也能提供快速学习。

实验也可以在生产环境中进行。事实上,生产环境中的测试是验证想法/假设/假定的最终形式,因为验证是由真实数据和真实客户行为支持的。A/B 测试实践提供了非常有价值的组合。

通常,你可能会有一组实验经历快速原型制作/用户研究原型制作的过程,然后,一些成功的实验会继续进行生产并通过 A/B 测试。有其他部署控制机制可以帮助衡量并从实际客户行为中学习——我们将在本章稍后介绍所有这些内容。

你可以在openpracticelibrary.com/practice/design-of-experiments上进一步阅读和讨论这个实践。

设计好的实验应该出现在用户故事地图和价值切片图中,以便与其他所有工作进行优先级比较。

让我们看看一些其他可以帮助优先级讨论的工具,从影响力和努力优先级矩阵开始。

影响力和努力优先级矩阵

影响力和努力优先级矩阵是一个决策/优先级确定实践,用于选择想法(如功能性特性想法、性能想法、其他非功能性想法、平台增长想法等)。

这个实践让整个团队(真正理解努力的人)参与到产品开发中,并将他们与利益相关者(真正理解影响力的人)连接起来。开发新产品与产生想法、假设及其测试/验证密切相关。不幸的是,几乎不可能测试和评估我们能想出的所有想法和假设。这要求我们过滤并优先考虑要处理的想法。

这个矩阵简单、易懂且非常直观,可以让整个团队参与到透明选择要首先处理的想法和假设的过程中。它还帮助产品负责人和产品经理构建产品路线图和产品待办事项,并向利益相关者解释优先级。

这个实践在帮助从可视化中识别方向和转型想法方面非常强大。

图 11.18:对比影响力与努力的二维矩阵

通过这个实践,出现了四组不同的想法:

  • 最值得关注的好想法:高影响力/低努力 – 这些应考虑放在价值切片图的较高部分。

  • 需要研究:高影响力/高努力 – 这些应考虑放在价值切片图的较高部分,但在后续的产品待办事项细化实践中,可能会被视为优先级较低,因为它们需要更长时间才能实现价值。

  • 跟进:低影响力/低努力 – 这些应位于价值切片图的较低部分,时间允许时可以跟进,否则完全删除。

  • 绝不采用 – 不好的想法:低影响力/高努力 – 这些应位于价值切片图的较低部分,或完全删除。

创意和假设的来源都是发现实践,如事件风暴(Event Storming)、影响图(Impact Mapping)和同理心图(Empathy Mapping)。在我们执行这些前述实践时,通常会产生可能的改进想法或形成新的假设。

在将它们作为项目添加到产品待办事项列表之前,这些创意和假设通常需要一些研究、分析和进一步阐述。

一旦优先级确定,这些创意和假设可能会导致:

  • 新特性通过用户故事地图和价值切片板进行添加

  • 将完整的新特性拆解为较小的特性或用户故事,并在价值切片板上进行重构

  • 用户研究

  • 实验设计

  • 技术难题与 UI 原型

影响与努力优先级矩阵在openpracticelibrary.com/practice/impact-effort-prioritization-matrix/有自己的开放实践库页面——这是一个继续学习和讨论这一优先级实践的好地方。

采用如何—现在—哇优先级实践可以实现一个稍微不同的优先级视角。与之前的实践用于筛选和优先排序高影响特性不同,这个实践用于识别和优先排序产品所需的快速获胜和基础特性。

如何—现在—哇优先级

如何—现在—哇(How-Now-Wow)是一个创意选择工具,通常与头脑风暴(Brainstorming)、如何做到2 (HMW)和实验设计(Design of Experiments)结合使用。它通过比较创意实施的难度与其创新性/原创性,来对创意进行对比并绘制到一个 2x2 矩阵中。

类似于影响与努力优先级矩阵,如何—现在—哇优先级简单、易于理解且非常直观,并且可以让整个团队参与到创意/假设的透明选择过程中,确定首先要进行的工作。

图 11.19:如何—现在—哇 优先级地图

再次强调,创意和假设的来源都是发现实践,如事件风暴(Event Storming)、影响图(Impact Mapping)、如何做到(HMW)和同理心图(Empathy Mapping)。在我们执行这些前述实践时,通常会产生可能的改进想法或形成新的假设。

我们可以通过评估每个项目并考虑其实施的难易程度(通过团队成员合作并就此达成共识)以及特性的新颖性和创新性,将这些项目绘制到如何—现在—哇矩阵中。

2 openpracticelibrary.com/practice/hmw/

从这项实践中出现了三组不同的创意。我们特别感兴趣的是其中的三组:

  1. 现在的创意:易于实施并被认为是产品中的常规创意。这些创意应该考虑到价值切片图中的较高部分,并且是我们期望交付的创意。

  2. 哇想法:易于实现,并且被认为是产品中高度创新或全新的。如果创新和市场差异化被认为是高优先级的关注点,这些想法应该被考虑为价值切片图中更高层次的潜在想法。如果已经使用了优先级滑块实践,它可能会为此提供一些方向。

  3. 如何实现的想法:这些想法实施起来困难,并且被认为是产品中高度创新或全新的。它们需要进一步的研究,以便深入了解实施难度和潜在影响。实验设计、原型制作以及更多的用户研究将有助于验证这些创新是否能获得良好的接受度。技术难点和研究将帮助建立信心,并可能找到更容易实施的解决方案。

  4. 其他想法:实施起来困难,并且对于产品而言并不是特别创新或全新。我们对这些想法完全不感兴趣。

一旦将这些想法和假设放入如何-现在-哇矩阵中,它们可能会导致:

  • 通过用户故事图和价值切片板新增功能

  • 完整的新功能被拆解成更小的功能或用户故事,并在价值切片板上进行重构

  • 用户研究

  • 实验设计

  • 技术难点和 UI 原型

欲了解更多关于如何-现在-哇优先级排序实践的信息,并开始讨论如何最好地使用它,请查看openpracticelibrary.com/practice/how-now-wow-prioritization-matrix

我们使用影响与努力优先级排序以及如何-现在-哇优先级排序等实践的主要动机是促进对话。能够让业务人员相互交流并对某种方法达成共识的实践是非常好的。能够让技术人员协作并达成对实施方法和复杂性的共同理解的实践也是非常好的。能够让业务人员和技术人员共同合作,达成共识并对业务背景和实施方法有共同认识的实践则是非常棒的。这两种实践就是这样的例子。

图 11.20:不同利益相关者合作,以更好地理解产品

两种实践都突出了需要进行更多研究的功能。位于如何象限的实践将受益于更多的研究。位于影响和努力优先级矩阵中高努力/高影响象限的实践将受益于更多的研究。

本书第八章《发现为什么和谁》中概述的许多人本设计实践将有助于这项研究。这包括同理心映射、定性用户研究、概念设计、原型设计和交互设计。如果某个功能领域非常重要,可能值得投资于一种专门的实践来进一步加深对该功能的理解——即设计冲刺。

设计冲刺

设计冲刺已经成为支持产品研究的一种流行实践。它是一个为期五天、以客户为中心的过程,用于快速解决关键挑战、创造新产品或改进现有产品。设计冲刺使你能够:

  • 明确当前的问题,并识别潜在用户的需求。

  • 通过头脑风暴和草图练习来探索解决方案。

  • 将你的想法提炼成一个或两个可以测试的解决方案。

  • 原型化你的解决方案并让其实现。

  • 与潜在用户一起测试原型。

该过程阶段包括理解、定义、草图、决策、原型和验证。

目标是快速前进,预见未来的成品和客户反应,在做出任何昂贵的承诺之前。这是一种简单且廉价的方式,用于验证主要假设和关键问题,并指引出不同的选项,以便在交付过程中进一步探索。这些实践可以减少在将新产品、服务或功能推向市场时的风险。设计冲刺是最快了解一个产品或项目是否值得进行、一个功能是否值得投入、或你的价值主张是否真实有效的方式。对于后者,你还应该考虑进行研究冲刺。3 它将工作压缩到一周内,最重要的是快速测试设计理念并提供真实的用户反馈。

到现在为止,设计冲刺的格式已经有了许多不同的变种。你可能会遇到谷歌风险投资的变种——设计冲刺 2.0——其议程如下所示。最好的做法是尝试不同的变种,并判断哪个适用于具体的情境。

3 library.gv.com/the-gv-research-sprint-a-4-day-process-for-answering-important-startup-questions-97279b532b25

表 11.1:为期五天的设计冲刺

实际上,我们使用的仍然是本书中介绍的相同的莫比乌斯环思维模型,不过这次是聚焦于某个具体选项,以便优化理解并进行进一步的研究,探索其价值。对相对价值的这种改进理解,随后会反馈到整个莫比乌斯环中,从而将其归类为值得进行设计冲刺的选项。

图 11.21:设计冲刺——莫比乌斯环的快速巡回

设计冲刺将有助于完善对一个功能或一组功能在产品中可能提供的价值的共同理解。它们可以是功能性用户特性,也可以是非功能性特性,更侧重于提升平台和开发体验。与上面相同的议程也可以适用,其中“用户”是开发人员或运营人员,设计冲刺专注于研究一些潜在的工作,这些工作将改善他们的开发或运营体验。

这个实践将有助于阐明和完善通过用户故事地图和价值切片地图传递的信息。我们将其视为“选项枢轴”上的一种实践,因为它将帮助决定是否继续交付相关功能。

阅读更多关于此实践的内容,分享你的经验或提出你可能有的任何问题,请访问openpracticelibrary.com/practice/design-sprint/

用户故事映射实践帮助我们将工作可视化成一个有清晰主干的故事。价值切片帮助我们形成可以分阶段交付的增量发布计划。影响和努力优先级排序以及如何-现在-哇优先级排序实践提供了不同的视角,以帮助进行价值切片。设计冲刺使我们能够深入研究特定功能领域,进一步探索,以便能够以更高的信心进行优先级排序。

所有这些实践(以及你在开放实践库中找到的其他许多实践)都在帮助我们能够创建一个初步的产品待办事项列表——一个我们将要投入交付循环的单一、一维列表。

现在,让我们看看如何将来自价值切片的信息转化为产品待办事项列表,以及我们如何继续对其进行优先级排序。

形成初步产品待办事项列表

有个好消息要告诉你。形成产品待办事项列表其实非常简单。如果你已经进行了一些发现循环实践,然后进行用户故事映射和价值切片,所有的深思熟虑、协作和对齐工作都已经完成了。

图 11.22:通过价值切片驱动初步的产品待办事项列表

这是一种很好的实践融合,如果在整个过程中有强烈的协作和对齐感(这得益于开放文化的坚实基础),那么价值切片地图应该能够代表一个共同的、共享的工作视图,并展示如何进行增量发布。

要创建产品待办事项列表,我们只需要将每个粘性便签从左到右复制到顶部切片,并将其放入单列中。

图 11.23:初步的产品待办事项列表

顶部切片左侧的便签将被复制并放置为产品待办事项的第一个项目。右侧的便签将成为产品待办事项的第二个项目。一旦我们复制完顶部切片中的所有项目,就转到第二个价值切片,并再次将每个项目从左到右复制到产品待办事项中。

图 11.24:价值切片实践总结

图 11.25:从价值切片画布创建产品待办事项

我们最终得到一个单列的产品待办事项,这些事项通过一系列有效的实践进行了来源和优先级排序。这种可追溯性很重要,因为我们可以追溯到生成该想法及其预期交付价值的发现环实践。

让我们通过 PetBattle 组织来看看这种可追溯性的实际应用。

PetBattle — 通过发现和交付实践追踪价值

我们在本章前面看到,如何从用户故事地图中创建价值切片。我们也看到,用户故事地图完全是通过发现环实践中捕获的学习内容构建的。

将其转化为产品待办事项非常简单。

图 11.26:将价值切片转化为产品待办事项

这是 PetBattle 产品待办事项生命周期的开始。只要 PetBattle 产品在运营,它将始终保持活跃、持续更新。

实际上,团队立刻发现了一些需要优先处理的事项,并建议将 CI/CD 工作坊和团队午餐/早餐项目移到顶部。他们一致同意,在 CI/CD 设置好之前,团队没有必要编写任何代码或构建任何功能,必须先保证团队的基本需求!

产品待办事项是一个活生生的工件,它永远不应静止不变,也永远不应完成。它是一个工具,始终可以供团队和利益相关者参考,并且与产品负责人合作,是一个可以添加新想法、详细说明现有想法,并继续对工作项进行相对优先级排序的地方。

从这一刻起,我们将开始并持续进行产品待办事项细化实践。

产品待办事项细化

产品待办事项细化实践位于莫比乌斯环的核心。我们在发现环后使用它,在交付环后使用它,我们一直都在使用它。

它可能是莫比乌斯环中唯一一个没有建议或指示的时间限制来执行的实践。也没有建议或指示执行的次数或执行的时机。产品待办事项细化应该根据需要进行,不论频繁与否,以便达成利益相关者和团队成员都有信心的待办事项列表。你甚至可以使用信心投票实践(如《第四章 开放文化》所介绍)来衡量这一点。

产品待办事项清理会议没有明确的议程,参与者可以是不同角色的人,如开发和运营团队成员、业务相关方和领导层。产品待办事项清理的活动包括:

  • 讨论并精炼对待办事项项的集体共识,明确它对用户的价值,以及需要满足的实施需求

  • 重新编写并精炼产品待办事项的标题,以更好地反映集体的理解

  • 为产品待办事项中的特定项编写验收标准

  • 对从待办事项中交付特性所需的努力进行相对估算,以满足验收标准

  • 将待办事项中的一项拆分为两项(或更多)较小的事项

  • 将多个事项组合成一个更独立的事项,以提供更强的价值单元

  • 捕获关于产品待办事项的新想法和反馈

  • 对产品待办事项中的事项进行优先级排序和重新排序

我们在发现循环(Discovery Loop)和选项转换(Options Pivot)中生成的所有工件,在进行产品待办事项清理时都非常有用,可以用来查看、协作并进一步完善。它们也是动态的、不断变化的工件,且通常在产品待办事项清理时的对话会促使这些工件进行更新。例如,我们可能会在影响图(Impact Map)中添加一个新的交付项,并将其与某个影响和参与者联系起来进行测试。我们也可能会在事件风暴(Event Storm)中进一步阐述一些细节,以便开始考虑与某个待办事项相关的实施细节。当从产品待办事项清理中捕获新项时,影响和努力优先级矩阵(Impact and Effort Prioritization Matrix)、如何-现在-哇(How-Now-Wow)优先级矩阵和价值切片板(Value Slice board)等工件都可以用来相对地将新项与现有项进行对比。在第十七章,改进它中,我们将回到选项转换,在交付循环迭代之后,查看如何通过交付过程中的测量和学习推动进一步的产品待办事项清理。

可以说,产品待办事项清理最重要的方面之一就是优先级排序,特别是对待办事项列表顶部的内容进行排序。这是团队在规划下一个交付循环迭代时提取的内容。因此,确保待办事项列表顶部的内容真正反映出最具价值的事项,并帮助产生重要的成果是非常重要的。

想了解更多关于产品待办事项清理的详细信息并与社区进行交流,请查看openpracticelibrary.com/practice/backlog-refinement页面。

我们已经看过一些帮助初步生成产品待办事项并确定首批优先级的工具。接下来我们来看看一些帮助持续进行产品待办事项优先级排序的工具。

优先级排序

在本章中,我们使用了功能产品待办事项这两个术语来解释我们通过探索阶段捕捉的不同工作单元,并在选项转换阶段优先排序并决定首先处理哪些工作。需要澄清的是,这不仅仅意味着功能特性。我们不仅仅在决定终端用户下一个要得到哪个炫目的新功能。我们需要平衡客户价值与风险缓解;我们需要平衡功能性与非功能性工作。我们通过平衡研究、实验和实施来实现这一点。

价值与风险

当我们为产品待办事项排序时,我们是在相对评估所有可用的选项。这包括我们要实施的新特性。它还包括需要修复的生产中的缺陷和问题。它还包括对架构的非功能性改进,以使未来的开发和运营更加简便和强大。它包括我们可能想要执行的实验,或以用户界面原型或技术性探索的形式进一步研究的内容。实际上,它包括任何会占用跨职能产品团队成员时间的事项。

当我们进行优先级排序时,我们需要考虑交付某项工作的相对价值,并与通过获取更多学习和信心可能缓解的风险进行对比。风险有多种类型,包括:

  • 业务风险:我们是否在构建正确的东西?

  • 技术风险:这个东西在平台上能工作吗?它能扩展吗?

  • 成本和进度风险:我们能在合适的时间范围内交付,以满足市场需求吗?我们能满足任何成本限制吗?

运行技术性探索和提前验证平台的一些非功能性方面可以提供知识和信心价值,这可能与通过交付功能性特性所获得的客户价值一样,甚至更为重要。事实上,这些非功能性工作帮助我们实现了第十章,设定结果中概述的“支持性结果”,而功能实现则更侧重于实现主要结果。

让我们看看一个经济优先级模型,它可以帮助我们量化风险、知识价值和客户价值。产品负责人可以与更广泛的团队成员和利益相关者合作使用该模型,并将其呈现给更广泛的组织。

延迟成本与 WSJF

加权最短作业优先WSJF)是一种经济优先级模型。这是Scaled Agile FrameworkSAFe)中非常流行的做法,但作为独立的实践,它在任何规模的产品或组织中都能很好地发挥作用。

许多组织在使用任何科学方法来优先考虑风险缓解措施、学习计划或交付价值时都遇到困难。相反,优先级的确定通常是在一个房间里开会时,最响亮的声音主导LVD)和/或由最高收入者的意见HIPPO)来做决策。

图 11.27:决策基于 HIPPO(最高收入者的意见)

那么,什么是 WSJF 呢?它基于 Don Reinertsen 关于延迟成本的研究以及他所著的书《产品开发流的原则——第二代精益产品开发》中的主题。Reinertsen 曾著名地说过,如果你量化一件事,就量化延迟的成本。Josh Arnold 解释了如何通过评估在需要时没有某个东西所带来的影响来计算延迟成本。举一个典型的例子,可能就是等待交付一项提高效率的解决方案所产生的成本。它是现在拥有同样东西和稍后获得它之间的机会成本。4

4 来源:Mark Richards, SAFe 城市模拟版 2.0

延迟成本背后的核心思维是随着时间推移失去的价值。每一天我们没有将某个项目投放市场,这对组织的成本是什么?如果该项目的价值是一个节省成本的措施,组织因为没有实施该功能而失去了多少资金?如果该项目的价值与收入相关,那么因为没有实施它,错失了多少额外收入?

延迟成本对时间非常敏感。存在季节性因素——例如,零售行业的运输在假期季节可能非常紧迫。法律和合规的变化可能是必要的。如果某个项目未能在某个日期前交付,而新的法规生效,成本可能非常高。在这个日期之前,延迟成本可能为零,而在这个日期之后,成本将非常高。

延迟成本的三个主要组成部分是:

  • 直接商业价值无论是对客户和/或组织的影响。这反映了用户可能有的偏好,从而提高了他们的客户满意度。它还将反映预期的财务奖励或成本节约。

  • 时间紧迫性,即现在或以后实施解决方案的紧迫性。这包括可能影响时间紧迫性的任何季节性或法规因素,以及客户是否愿意等待解决方案,或者是否有迫切需求。

  • 风险降低与机会促进是它可能给组织带来的间接商业价值。它考虑到未来可能带来的隐性收益,以及降低的风险水平。

在敏捷积压工作中使用延迟成本来优先处理工作项,将使得工作项根据价值和时间敏感性进行优先排序。它还使我们能够关注直接的商业价值(如新功能开发)和间接的商业价值(如对 OpenShift 平台的非功能性改进)。

延迟成本 = 商业价值 + 时间价值 + 风险降低/机会启用价值

WSJF 通过考虑实施成本,增加了这一维度。Reinertsen 说过 记住,我们在处理工作时会阻塞资源这一点至关重要。立即为任何工作提供服务的好处是节省了延迟成本,而成本则是我们阻塞资源的时间(持续时间)。成本和收益都必须在经济上正确的顺序中考虑。5

加权最短工作优先(WSJF) = 延迟成本(COD) / 持续时间

5 《产品开发流的原则 – 第二代精益产品开发》作者:Donald G Reinertsen

在延迟成本和持续时间中的三个组成部分,我们使用什么单位?这是任意的。实际数字本身没有意义。我们支持 COD 和 WSJF 的敏捷实践是相对估算,6 我们通过相对评估产品积压工作中每个项目的商业价值、时间价值以及风险降低/机会启用的大小,从而相对比较各个项目。这使我们能够根据 WSJF 对产品积压工作进行优先排序。

我们现在已经在首次进入选项枢纽时引入了几种实践,帮助我们从发现中生成更多的想法,精炼它们,优先排序,并最终决定将哪些选项带入交付循环。那么,谁来做这个决定呢?本章中多次使用了 我们 这个词,强调了协作的重要性。但当我们无法达成共识时,怎么办?谁有最终发言权?这就是伟大的生产所有权重要性的体现。

PetBattle – 使用 WSJF 进行优先排序

PetBattle 团队聚集在一起,进行了一次关于他们在价值切分板上收集的所有功能和工作的 WSJF 会议。

他们选择将这种做法与影响与努力优先排序和价值切分结合使用,因为他们认为需要一种方法来量化即将进行的工作中的风险降低和及时性。由于他们有几个更多依赖非功能性目标的支持性结果,团队认为延迟成本和 WSJF 可以帮助他们正确表达这些工作相对于功能性特性的价值。

对于每个项目,团队会花一分钟时间讨论他们对特性的理解。然后,每个团队成员会写下四个值——商业价值、时间紧迫性、风险与机会启用、持续时间。前三个值会给出 1 到 10 的评分。持续时间则使用修改过的斐波那契数列 7,可选值为 1、2、3、5、8 或 13。

团队成员会互相透露各自的评分,并随之进行对话,以达成共识并对每个评分进行团队评估对齐。

这会为每个项目生成延迟成本值和 WSJF 值。

表 11.2:计算 PetBattle 工作的 WSJF

在对话中得出的一些结论包括:

  • 大多数功能性特性在商业价值上较高,并且在推出该应用的最简版本时,时间紧迫性也较高。

  • 由“启用成果”驱动的非功能性工作被认为对风险减少具有高价值,且具有较低(但仍有)商业价值。

  • 一些非功能性工作显然比其他工作更具时间紧迫性,比如自动化部署。

  • 大多数工作在规模上相对相似,唯一例外是验证图像功能,它的解决方案存在一些不确定性。

团队一致认为这次产品待办事项梳理是一次有益的练习,有助于整体的优先级排序。

6 openpracticelibrary.com/practice/relative-estimation/

7 www.mountaingoatsoftware.com/blog/why-the-fibonacci-sequence-works-well-for-estimating

关于形成产品待办事项、梳理待办事项及优先级排序的前几节内容,都是产品所有权的关键职责,我们将进一步探讨这些内容。

产品所有权

本章的所有内容都与产品所有权相关。前几章关于发现的所有内容都是产品所有权的一部分。优先考虑早期的工作,建立开放文化、开放领导力和开放技术实践的基础,要求从一开始就具备强大的产品所有权。

有整本书和培训课程专门讲解产品所有权、产品负责人和产品经理的相关内容。我们的许多思考都受到了 Henrik Kniberg 出色工作的启发。如果你还没看过他在 YouTube 上的 15 分钟视频《产品所有权概述》8,请现在放下这本书,去泡一杯茶,观看视频。也许还可以看两到三遍。我们这本书的四位作者认为,我们四个人加起来已经看过这段视频超过 500 次了!

8 www.youtube.com/watch?v=502ILHjX9EE

有人说这是互联网上最棒的 15 分钟视频,这可是一个相当高的评价!它在这么短的时间里,涵盖了许多关于产品所有权的重要哲学观点。我们通常在我们的 DevOps 文化和实践赋能会议中展示这个视频,特别是在我们与新团队开始新的合作时,或者仅仅是为了和利益相关者启动一次关于敏捷的真正含义的对话。

结果的图表非常值得打印出来并装裱挂在墙上!

图 11.28:敏捷产品所有权概述,感谢 Henrik Kniberg

在我们处理数百个不同合作项目的过程中,我们见过一些令人惊叹的产品所有权实例,也见过一些非常糟糕的例子。

让我们看看一些我们观察到的模式,从产品负责人开始。

尝试不同的产品负责人

在 2010 年代中期,我们与一家英国零售公司合作,使用 Scrum 框架交付了一个四层的 IBM WebSphere Commerce 和 MobileFirst 平台。

这是该组织第一次尝试敏捷交付,由于涉及多个 Scrum 团队、一个瀑布团队、多个供应商、多个时区、多个技术供应商(包括一些不成熟的技术)、多个业务单元等,因此变得非常复杂。

最初分配给该合作项目的产品负责人是一名承包商,之前与该组织没有任何历史或合作。我们很快发现,他并没有权力做决策,因为每当 Scrum 团队需要澄清或要求进行产品待办事项改进时,他总是不得不召集利益相关者开会。产品负责人需要拥有决策权。

该组织确实进行了调整,重新安排了产品负责人角色,聘任了一位非常资深、以业务为主的人员,他是 CIO 的直接下属。这种安排更好,因为他显然有权做出快速决策和优先级选择。这次的挑战是如何定期与他取得联系。他承诺每周为我们提供一小时时间。但产品待办事项列表需要大量的改进和向开发团队澄清,这一点时间远远不够。产品负责人需要对他们的团队(s)可用。

后来进行了进一步的调整,产品负责人角色被分配给了一位长期且备受尊敬的技术架构师,他全身心投入到这个项目中。这个安排效果非常好,因为他与技术和业务相关者都有很好的关系,深刻了解组织的战略和优先事项,并且拥有非常受欢迎的个性,大家都非常愿意与他合作。产品负责人需要了解业务。

随着时间的推移,对产品负责人直接访问的需求减少了。这是我在与几个组织合作时发现的一个模式,特别是在产品负责人非常强大的情况下。伟大的产品负责人会使产品所有权民主化,并为团队和利益相关者之间提供直接连接。产品负责人应该将自己当前的角色视为一种自我消失的角色,长远来看不再需要。

接下来,我们来看看伟大的产品负责人如何处理他们的第一次迭代,以及他们在第一次迭代目标中优先考虑的事项。

早期冲刺模式和行走骨架

在下一章中,我们将切换到交付循环,讨论我们用于规划、交付、展示和从交付循环的迭代中学习的实践。

专门考虑团队第一次为新产品进行交付迭代时,我注意到伟大的团队在第一次交付目标上有一个一致的模式。我在多个项目中工作过,目标几乎完全相同。也许是我的辅导影响了这一点,但总有一种共识和信心,认为这是最好的方式,能够降低风险、学习,并为后续迭代的更快交付奠定基础。

一个来自欧洲汽车公司的第一次迭代目标示例如下:

建立我们的工作空间,提供一个连接前端、API 和数据库的行走骨架,并通过 CI/CD 来支撑。

其他几个项目的目标几乎完全相同,而且这个模式非常强,因为:

  • 团队想要建立他们的工作空间。这可能是他们的实体工作空间,配备了大量信息散播器和协作空间,也可能是一个虚拟工作空间,包含数字工具。它还可能是一个开发环境,使用适合编码的工作空间,并熟悉所有将要使用的工具。

  • 建立行走骨架的计划。这是整个架构的一个薄片,在一次迭代中交付。不会有任何复杂的前端或后端处理。他们将证明全栈开发,并且代表整个逻辑架构各部分的跨职能团队能够共同交付工作软件。这是一个行走骨架,因为它是一个完全可工作的产品,只是目前做的事情还不多!

  • 他们的工作将由持续集成和持续交付支撑。这种从一开始就采取的绿色实践意味着,他们在自动化构建、测试和部署方面已经为成功做好了准备。如果他们在一个薄片上证明并学习到这一点,当我们开始将所有的细节和功能融入行走骨架时,它将变得越来越有价值!

本章的最后部分将焦点从我们决定接下来交付什么转移到我们如何衡量和从我们的实验以及交付的功能中学习。OpenShift 平台使我们的团队能够考虑多种先进的部署能力。

高级部署考虑因素

本章前面我们解释了实验设计的实践,以及我们如何将实验思维应用于开发过程中,特别是在我们的发现循环实践中已形成假设和假设的地方。

OpenShift 平台提供了多种不同的部署策略,支持实验的实施。当我们处于“选项转换”阶段时,我们应该考虑这些策略,以及在交付相关产品待办事项时(如果有的话)应该选择哪些策略。我们可以考虑的高级部署策略包括:

  • A/B 测试

  • 蓝绿部署

  • 金丝雀发布

  • 黑暗发布

  • 特性开关

在这里引入这些概念,是因为从选项规划的角度来看,这是我们需要关注它们的地方。我们将在第六部分,构建它,运行它,拥有它中回到具体的实施细节,并将在第七部分,改进它,维持它中探讨我们如何使用这些结果指标。

A/B 测试

这是一种随机化实验,在该实验中,我们将比较和评估不同版本的产品在两者之间的表现。两个产品版本都可在生产环境(实时)中使用,并随机提供给不同的用户。我们会收集有关流量、互动、花费时间和其他相关指标的数据,这些数据将用于根据用户行为的变化来评判两个不同版本的有效性。该测试将决定哪一版本在您所设定的目标成果方面表现更好。

A/B 测试容易应用、执行快速,通常可以通过比较两个版本之间的转化率/活动数据来得出结论。它也有限制,因为两个版本之间的差异不应过大,产品中更显著的变化可能需要执行大量的 A/B 测试。这是其中一种允许你调优引擎的实践,正如《精益创业》9 中埃里克·里斯所描述的那样。

9 theleanstartup.com/

图 11.29:A/B 测试

有关此实践的更多信息,并与社区成员讨论或贡献您自己的改进,请访问 openpracticelibrary.com/practice/split-testing-a-b-testing/

蓝绿部署

蓝绿部署是一种软件开发技术,依赖于为团队提供两个可用的生产环境。其中一个环境,我们称之为绿色环境,是正常运行的,并从反向代理(负载均衡器/路由器)中接收流量。另一个环境,我们称之为蓝色环境,是已升级到新版本的副本。在升级完成之前,它会与负载均衡断开连接。

图 11.30:蓝绿部署

团队可以在蓝色环境中执行产品版本升级所需的所有任务,而无需担心停机的压力。一旦蓝色环境准备就绪并通过了所有测试和检查,团队只需将反向代理(负载均衡器/路由器)从绿色环境切换到蓝色环境。

如果蓝色环境一切正常,过时的绿色环境可以准备好回收,作为下一次发布的蓝色环境。如果出现问题,团队可以通过反向代理/负载均衡器/路由器立即切换回稳定的环境。

这是一种反馈循环实践,允许团队从他们的更改在实际使用中的效果中获得及时反馈。它支持持续交付,并为执行复杂的发布提供安全保障。它消除了时间压力,并将停机时间减少到几乎为零。这对技术团队和最终用户都有益,前提是新版本的表现与预期一致,用户不会察觉到任何故障或服务/产品的不可用性。如果出现不良效果,它允许团队立即回滚并将负面影响限制在最小范围。

若要进一步探索这一实践,请访问Open Practice Library 页面

金丝雀发布

在软件开发中,这是一种持续交付形式,其中只有少数实际用户会接触到新版本的产品。团队监控回归、性能问题以及其他不良影响,并且如果发现问题,能够轻松地将用户恢复到正常运行的旧版本。

该术语源自于在煤矿中使用笼中的鸟来提前发现危险气体的积聚。这些气体会杀死鸟类,远在它们对矿工造成生命威胁之前。就像矿井中的金丝雀一样,这种发布实践为避免更大问题提供了早期预警机制。

金丝雀发布为持续交付团队提供了安全保障,使他们能够执行逐步发布,逐渐增加使用新版本产品的用户数量。在发布新版本的同时,团队将密切监控平台的性能,尝试了解新版本的影响,并评估回归、性能问题甚至停机等不良影响的风险。这种方法允许团队在观察到此类不良影响时立即回滚发布,而不会影响大多数客户,即使是短时间内。

Canary Release(金丝雀发布)类似于 A/B 测试,因为它只是将新特性暴露给一部分用户群体,但与 A/B 测试不同,金丝雀发布的新特性通常是一个完全新的特性,而不仅仅是现有特性的一个小修改。目的也不同。A/B 测试旨在改善产品性能,达到商业成果,而金丝雀发布则完全专注于技术性能。

图 11.31:金丝雀部署

你可以在openpracticelibrary.com/practice/canary-release阅读更多关于这一实践的信息,贡献改进,或与更广泛的社区讨论。

黑暗发布

Dark Launches(黑暗发布)是另一种持续交付实践,它将新特性发布给部分终端用户,并收集他们的行为和反馈。它们使团队能够理解这些新特性在现实生活中的影响,这种影响可能对用户来说是意外的,因为没有用户提出过这些需求。这是验证新特性是否符合产品/市场契合度的最后步骤之一。与其一次性将特性发布给整个用户群体,这种方法可以让你测试水域,确保应用程序在正式上线之前按照计划运行。

Dark Launches(黑暗发布)通过将新特性的影响限制到一部分用户来提供安全保障。它们使团队能够更好地理解新特性带来的影响以及用户与之交互的方式。通常,交互方式会出现一些新颖的方式,这些方式最初可能并非团队所预见。这既可能是积极的,也可能是消极的,而有限的可用性让团队能够从真实使用中得出结论,并决定是否将特性广泛发布、进一步开发或停止使用。

图 11.32:黑暗发布

Dark Launches(黑暗发布)实践有自己独立的 Open Practice Library 页面,地址是openpracticelibrary.com/practice/dark-launches/,可以前往获取更多信息,开始对话或改进实践。

特性标志

Feature Flags(特性标志,也称为特性位/切换/切换控制)是一种工程实践,可以用来在不改变和重新部署代码的情况下更改软件功能。它们允许应用程序的特定功能在测试和维护时进行开关控制。

在软件中,flag(标志)是用来存储二进制值的一个或多个位。所以,它是一个布尔值,可以为真或假。可以通过 if 语句来检查 flag。软件中的特性是提供某种价值的功能。最简单形式下,Feature Flag(特性标志,也叫切换标志)只是一个if语句,它将某个功能包裹在其中。

功能切换是一个基础的工程实践,它提供了一种管理产品行为的好方法,以便在发布新功能时进行实验或保障性能。

图 11.33:功能标志

这个实践在开放实践库中有一个页面,所以可以查看那里以获取更多信息或与社区互动。

在引入了这些高级部署考虑和它们如何支持实验设计后,我们来看一下我们的宠物战斗团队如何使用这些方法。

宠物战斗 – 技术突破、原型、实验和功能实现

团队聚集在新的产品待办事项旁边,那个待办事项从地板到天花板,堆满了大量便签!实际上,它们并没有完全适应一个列,因此底部的部分向外扩展,形成了一个像大漏斗一样的形状。顶部很整洁有序,但底部需要进行一些整理。不过没关系,因为团队认为待办事项中的工作量足够,在接下来的几周内可以忙碌,所以他们可以在继续工作时对产品待办事项进行进一步的优化。

在参加了一个关于高级部署考虑的研讨会后,他们决定对产品待办事项中的功能特性进行一些优化。他们在白板上画了三列,标题分别是:研究、实验和实施。他们将讨论时间限制为 45 分钟。共有 15 个特性需要讨论,因此平均每个特性讨论时间为 3 分钟。他们的目标是将每个特性放入其中一列,并简短地说明他们的实施方法。

  • 打开宠物战斗: 这个很简单。任何使用该应用程序的人都需要打开它。 实施。

  • 显示领导者: 关于显示什么内容以及如何显示的问题很多。要显示多少个领导者?我们应该添加分页还是滚动?他们决定需要进行一些研究——也许是一个 UI 原型,并进行用户测试。

  • 请让我进去: 团队不得不回到事件风暴中,提醒自己这到底是什么!同样,这是一个简单的功能,允许用户进入查看上传的宠物。实施。

  • 为猫投票: 这引发了一些讨论。他们是投赞成票还是反对票?还是只是投个票(或者什么都不投)?团队意见分歧,并且从用户访谈中听到了不同的看法。他们决定进行 A/B 测试进行实验。

  • 添加我的猫: 不需要太多的研究或实验。只需要一个标准的上传工具。直接实施。

  • 验证图像: 这听起来有点复杂。可以使用合并的 AI/ML 模式。这需要一些技术研究,并可能需要技术突破。

  • 将猫加入比赛: 这里没有太多模糊性。实施。

  • 显示比赛猫:目前不清楚这是否会受到欢迎。团队认为可以通过实验特性开关进行尝试,然后如果需要,可以轻松关闭。

  • 禁用“添加我的猫”:有些用户有不止一只猫,他们可能会想添加多个。我们来进行一个小范围用户的暗启动实验,看看效果如何。

  • 为指定的猫投票:一旦团队从 A/B 测试中得到结果,他们可以进一步进行实验,并以金丝雀测试的方式上线。

  • 更新排行榜:实施

  • 结束比赛:实施

  • 通知玩家:尚不清楚如何通知玩家——短信?电子邮件?其他方式?团队决定进行一些用户研究。

  • 向获胜者发放奖品:奖品策略仍在考虑中,因此需要更多的研究。

  • 开始下一个比赛:这可以立即进行,或者第二天开始。也许可以进行另一个 A/B 实验,看看等待后,团队的流失率会如何变化。

团队在 40 分钟内完成了任务。这是一次很棒的讨论,他们感觉自己已经准备好进行第一次迭代规划,并做出这些决策。

让我们看看另一个现实世界的案例,看看这种简单而有效的实验思维是如何运作的。

重新定义问题 – 我能借多少钱,或者我能买多大房子?

重新创造你已经拥有的东西,只是换上一个闪亮的新前端,可能看起来非常简单,但最终这可能并没有解决一些更为根本的问题。在为银行工作时,我们一个小团队的任务是找出为什么人们使用完在线抵押贷款计算器后,抵押贷款申请量会急剧下降。银行网站上的计算器非常简单:你输入薪水,它会告诉你能从他们那里借多少钱。它无法添加首付,也无法指定期限长度来查看如何影响还款率或贷款标准。计算器的运行速度也非常慢且笨重,不适合移动端,也不具备响应式设计。银行的快速解决方案是简单地重做界面,进行这些表面上的修复,但这并没有真正解答为什么有成百上千的人使用了计算器,但只有很小一部分人继续申请贷款。

团队非常小,只有一名设计师,两名工程师,一名业务分析师和一名产品负责人。作为一个小型的、位于银行核心位置的团队,我们能够快速行动!我们采访了最近在银行申请过抵押贷款的人,了解他们使用该工具的动机。我们进行了大量的研究,通过走进银行分行,向正在使用现有工具的人提出开放性问题。我们收集了这些信息,以及他们如何访问计算器,若要完成申请,他们会使用什么设备——是手机还是笔记本电脑。

通过这项研究,我们发现了一个有趣的事实——人们并不关心我能借多少,而是我能负担多少房子。这个简单的差异看似无关紧要,但它极大地影响了我们如何重新构建银行的在线按揭计算器。它意味着人们希望能够根据自己的需求量身定制计算方式,看看他们的利率和贷款条件是否会受到,比如说,收入增加的影响。或者,如果他们再存一年,存更多的首付,是否能获得更好的利率?这种思维的转变意味着人们使用这个工具来查看他们能负担多少房子,而不是是否能负担某个特定的房子,以及在什么时间点能负担。

对我们来说,仅仅重新创建一个在移动设备上运行的银行现有计算器,换个皮肤看起来非常简单——但这并没有解决核心问题。通过重新定义问题,我们现在可以创建一个简单的计算器,专为银行的首次购房者的需求量身定制。

所有这些先进的部署考量为我们提供了强大的工具,用于选项规划,帮助我们如何进行研究、实验和实施。

研究、实验、实施

本章强调了,在考虑我们的选项时,问题不仅仅是优先处理功能并实施它们。我们需要平衡功能实施与持续的研究和实验。

总结和可视化这一切的一个好方法是使用Mobius Loop 的选项地图

创建选项地图

我们在第三部分,发现它的结尾,使用了一个发现地图——这是一个信息展示板,总结了发现循环的迭代过程。

我们将在第四部分,优先处理它的最后,使用一个选项地图——这是 Mobius 工具包中另一个开源文档,遵循创意共享协议,你可以用它来总结在选项转折过程中所有的学习和决策。

这个地图应该紧密地与您的发现地图相对接,用来总结:

  • 我们刚刚在选项转折中关注了哪些内容?

  • 我们的目标是哪些结果?

  • 我们需要验证哪些想法或假设?

  • 我们将如何交付这些选项?

  • 我们希望优先处理哪些选项?

当我们在交付循环的一个迭代之后返回到选项转折时,我们将完成这个地图的最后部分:

  • 我们学到了什么?

图 11.34:选项地图

现在让我们来看看宠物对战的选项地图。

宠物对战——选项地图

这个地图包含了很多细节,所以请随时访问书籍的 GitHub 仓库中的在线版本进行探索。

图 11.35:宠物对战选项地图

选项地图提供了清晰的方向,帮助确定产品的优先级以实现预定结果。它帮助我们形成交付策略。

结论

在本章中,我们重点讨论了如何交付上一节中设定的成果。

图 11.36:添加实践以帮助我们通过选项

我们探讨了用户故事映射和价值切分的实践,以及如何将发现实践中收集到的所有信息推送通过这些工具。我们还展示了如何通过不同的视角来看待相同的信息——影响与努力优先级排序、以及如何/现在/惊讶优先级排序——可以帮助改进价值切分。在建议的功能领域需要更深入了解其价值时,我们推荐设计冲刺作为一个选项。

我们展示了这些实践如何推动初步的产品待办事项清单,按价值优先排序,并且这一清单将成为一个生动、持续演变的文档,随着我们在交付过程中收集更多的学习、反馈和度量数据,它将不断接受产品待办事项的精炼。基于延迟成本的经济优先级模型 WSJF 提供了一种可重复、可量化的工具来推动这一过程。这是许多优先级排序工具之一,能够帮助产品所有权功能高效顺利地运作。

最后,我们探讨了设计实验时应考虑的高级部署因素,以及像 OpenShift 这样的平台如何使得能够在生产环境中与用户一起进行基于证据的强大测试。A/B 测试、蓝绿部署、金丝雀发布、暗启动和功能标记等概念从商业角度进行了介绍。我们将在第六章,构建它、运行它、拥有它中回到这些内容的实现细节,并在第七章,改进它、持续它中探讨我们如何解读这些措施的结果。

图 11.37:在文化和技术基础上完成发现循环和选项转换的实践

在下一章,我们将转向交付循环。我们将探讨敏捷交付以及它在不同复杂度和简单度下的适用性。我们还将讨论瀑布模型及其相对优势以及可能的适用场景。我们将探讨不同的敏捷框架以及它们如何与开放实践库和莫比乌斯循环相关联。我们还将探讨在交付循环的迭代过程中可视化的重要性,以及捕捉测量和学习的意义。

第五部分:交付它

第四部分优先排序中,我们第一次经历了选项枢纽。我们将从发现循环中得到的所有学习和想法应用到更多的实践中,根据其价值进行组织。我们使用了优先排序技术来帮助我们切分价值,并考虑如何规划和设计可以通过利用 OpenShift 平台提供的先进部署功能来运行的实验。

正如 Gabrielle Benefield 和 Ryan Shriver 在 Mobius Outcome Delivery QuickStart Guide 中概述的那样(可以从 mobiusloop.com 免费下载),Mobius 鼓励通过持续的发现、验证和学习来推动产品开发。以这种方式构建产品需要扎实的文化和技术实践基础,这些基础能够支持持续的实验、快速发布和快速反馈方法。事实上,持续的研究和实验与功能交付一样,都是产品开发的一部分。

研究、实验和实施之间的主要区别在于需要投入的时间和金钱,这些投入是为了引导团队朝着构建正确的、与目标成果一致的事物前进。Mobius 鼓励从最少的投资中交付最大价值的理念。

图 12.0.1:交付循环 – 设置场景

当我们进入交付循环时,我们将进行研究、执行实验并实施功能。我们如何做到这一点?我们是要使用像 Scrum 这样的框架,还是大规模敏捷框架SAFe),或者是大规模 Scrum 框架LeSS),或者 Lean?我们是要遵循一种规定的做法或方法论吗?不,我们不这样做!正如本书中所述——事实上,这也是 Mobius 和开放实践库背后的心态——我们将提供一套实践工具箱,帮助你在交付循环中游刃有余。我们不会说有一种正确的方式来做事,因为这种方式并不存在。我们的方法是根据背景、学习和经验进行调整。我们将介绍一些我们发现非常有效的流行实践,并分享一些背后的故事和技巧。

你选择哪些实践取决于你,但在几乎所有情况下,我们强烈建议使用能够实现快速交付、快速反馈并持续学习的实践。

为此,本节被分为两个章节。第十二章执行交付,完全讲解了执行交付——瀑布式方法和敏捷方法之间的差异,以及用于增量和迭代交付的流行敏捷实践。第十三章衡量与学习,则专注于衡量指标并学习它们所传递的信息。我们可以使用各种不同类型的指标,应用于小规模的增量交付,不断地进行改进。

图 12.0.2:交付循环

第十二章:12. 做交付

最终,代码没有价值,直到它被交付到生产环境,并且其相关功能被使用。是时候将我们迄今为止学到的内容与交付联系起来。

我们已经进入本书的下半部分,除了像第三部分《发现它》中那样建立技术基础外,我们还没有编写任何应用代码,也没有交付任何内容。我们所做的是通过一系列巨大的实践,帮助我们发现、设定结果、评估和优先排序选项,并决定接下来要交付什么。现在,我们已经到了编写代码、交付功能、执行实验并从进一步的研究中学习的阶段。

在本章中,我们将探讨不同的交付方法。你可能会立即预期本章将全是关于敏捷,并且使用敏捷方法论是交付工作软件的唯一方式。然而,除了回顾敏捷的起源和我们可以使用它的不同方式外,我们还将强调它并不是唯一的选择。仍然存在适合采用传统交付方法的场景,比如瀑布模型。在本章中,我们将:

  • 解释瀑布模型和敏捷方法论。

  • 看看复杂、复杂性和清晰系统之间的区别,以及我们如何利用它们推动敏捷方法的适用性。

  • 探讨 Mobius 循环是与框架无关的,通过不同的视角、速度和迭代频率,可以用来解释各种类型的交付。

  • 分享一些工具和技巧,帮助你为交付迭代做好准备

  • 描述我们常用的一些实践和示例,帮助完成交付迭代;例如,迭代(或冲刺)计划、每日站会、展示会和回顾会。

  • 看看随着团队的成熟和持续改进,他们如何调整自己的工作实践和方法。

  • 分享一些工具和技巧,帮助你完成工作软件功能的交付,重点关注功能性和非功能性的接受标准。

  • 第一次看到我们所有的实践如何结合在一起,形成一个持续交付引擎。

图 12.1:瀑布模型和敏捷团队

让我们先来看一下这两种现象——瀑布模型和敏捷方法。如图 12.1所示,瀑布模型通常与甘特图、依赖关系以及组织中一个部门到另一个部门的交接工作相关。敏捷则采取不同的思维方式,更加跨职能。有人可能会说瀑布模型已经过时,正在消亡。但我们将在本章后面看到,瀑布模型依然有一些有效的应用场景。

瀑布模型

瀑布项目被分解为线性步骤;每个步骤都依赖于前一步的完成才能开始下一步。第一步是收集用户需求,接下来是设计和规划,然后是软件开发。一旦测试和部署完成,我们就进入了维护和运营阶段。

图 12.2:瀑布流程

这种交付方法在软件开发之外有着悠久的历史,尤其是在传统工程领域。在建造桥梁或工厂时,项目计划会列出所有人员、资源、任务和整体项目的时间表。瀑布方法可以被描述为一种计划驱动的工程过程,其中成功的衡量标准是开发是否能够跟上计划的进度。

通过遵循计划,个体差异得到了最小化。在工业领域,这有助于使交付和成本可预测。由于传统的瀑布模型不允许循环,需求和设计通常会被收集在大量的文档中以开始规划。对于团队来说,修改和理解这些文档变得非常棘手,因为除了重新阅读所有文档外,几乎没有其他方法来达成共享理解。你是否参加过书籍评审会议?当一群人读同一本书时,你会得到多少不同的观点?将这一点与组织提出昂贵的变更请求,并且修改文档的可能性结合起来,很快就会看到这种方法对公司来说可能是多么灾难性。那么,这种方法在软件领域如何呢?

我们常常发现,在软件领域中,需求与最终用户的期望不符。这是因为在我们开发软件的过程中,关于原始需求和设计的问题以及误解变得难以解决。由于设计和业务需求团队在开发阶段的参与较少,因此没有机会更新设计或理念。

使用瀑布方法交付客户和业务价值要花费更长时间——因为工作软件通常要到瀑布过程的最后阶段才交付。想想看,在等待你的优秀应用程序到来时,你浪费了多少时间。如果它永远不会到来呢?或者更糟,来得太晚,错过了市场机会?

在瀑布执行过程中,变更需求通常会被留到另一个瀑布阶段。如果变更非常紧急,它们可能会通过某种变更请求机制来处理。然而,这实际上是一种循环形式——这引导我们走向一种更迭代的方法。在瀑布模型中,这些循环仍然可能非常耗时且成本高昂。如果只有更快的方法就好了!

温斯顿·沃克·罗伊斯是美国计算机科学家,也是德克萨斯州奥斯丁市洛克希德软件技术中心的主管。他是软件开发领域的先驱,因其 1970 年的论文而闻名,从中错误地引出了单次通过的瀑布模型。没错,瀑布模型被误解了!

图 12.3:瀑布模型并不是为了单次通过而设计的!

Barry Boehm 在 1987 年写道:“罗伊斯 1970 年的论文通常被认为是定义了 阶段性瀑布模型的论文。然而,令人惊讶的是,早期的 Benington 和 Hosier 的论文已经很好地接近了瀑布模型,而且罗伊斯的论文早已将原型开发作为与瀑布模型兼容的一个重要步骤。”1

罗伊斯展示了,虽然大型软件系统的开发需要更为彻底的方法,但单次顺序执行方法中存在固有的风险。他提出了迭代方法,并倡导项目应该至少经过两次这样的过程。然而,许多瀑布项目开始时就只有单次的执行流程,从左到右,仅执行一次迭代,这并非罗伊斯所推荐或预期的方式——瀑布计划从来不应只执行一次。

瀑布模型确实推动了其他方法的创新,而敏捷模型则纠正了对迭代的关注。

1 Barry W. Boehm (1987)。在《ICSE '87 软件工程国际会议论文集》中发表的文章《软件过程管理:从历史中得到的经验教训》,第 296-298 页

敏捷的诞生

敏捷开发方法已经讨论并实践了超过二十年。敏捷宣言的诞生源于一群软件开发领域的著名人士,他们在犹他州的滑雪度假胜地聚集,进行了一次关键分析,探讨为何 IT 项目名声如此糟糕。他们回顾了 90 年代过去 10 年的软件交付情况,得出结论认为,IT 项目的执行周期过长,超出预算,且常常未能为最终用户提供价值。于是,这些人在旅行结束时坐下来,写下了敏捷软件开发宣言

我们知道你在想什么:这个宣言一定是一个庞大的文档吧!一本填满了关于如何编写软件以及如何管理需求和成本的详细说明书。事实上,它比这简单得多——简单到几乎可以写成一条推文!

图 12.4:敏捷软件开发宣言

敏捷宣言 2 是一套简单的价值观,突出了某些价值高于其他价值的重要性。

工作软件优于详细文档——“这听起来不错,我就不用写文档了”,这可能是一些人的想法。但他们错了。这些价值观并不是说右边的东西不重要,而是左边的东西更有价值。这是人们在引用宣言时常常忘记的部分。

2 agilemanifesto.org/

除了这组价值观之外,这个团队还提出了一套 12 项原则 3 来支持这些价值观。

图 12.5:敏捷宣言背后的原则

这些原则为我们预见到的敏捷行为提供了更多细节。将这些原则一起阅读,它们为讨论什么是敏捷提供了极为有用的资源。如今,敏捷已经成为一个过度使用的流行词,常常被插入到句子中。例如,“我们将使用敏捷来交付这个项目”或者“我们将安装敏捷”。通过阅读并讨论这 12 条声明,可以为我们对敏捷的理解提供清晰和一致性。

3 agilemanifesto.org/principles

敏捷宣言与传统的瀑布式方法相比,强调的是:

  • 更加适应性强,而不是规定性强

  • 更加以人为本,而不是以过程为本

在工业革命初期,弗雷德里克·泰勒(Frederick Taylor),一位广为人知的美国机械工程师,曾描述在工厂车间工作的工人是懒惰的贪婪的愚蠢的。工厂是由不在车间工作的建筑师和工程师设计建造的。敏捷的一个核心理念是,做实际工作的人员和团队决定如何完成工作。

敏捷的一个重要部分是其聚焦于跨职能团队交付工作软件。设计师和业务相关方持续参与这个过程。与瀑布式不同,当出现有关最终用户体验的问题时,团队可以根据最先进的信息进行研究和回答。

敏捷方法论关注于同时进行的工作流,这些工作流构成了一个基准,帮助我们控制变化。计划会不断修订,以反映产品开发过程中获得的经验教训。成功的关键在于由工作软件所提供的价值。

敏捷关注于直接与最终用户连接的产品,而不是那些可能与用户无关的努力和产出。团队通过协作来推动成功的交付,使用过程和工具来优化产品交付。

敏捷方法最显著的特点之一是将项目交付分解成更小的迭代阶段。这对于需要定期修订和测试的软件产品非常有效。由于工作是逐步进行的,团队可以轻松调整正在处理的内容。他们可以快速调整工作重点,专注于高价值的功能,并将这些功能尽早且迅速地交付给客户。

一些敏捷方法的批评者恰当地指出,敏捷项目也会失败。然而,失败的并不是方法论,而是团队和组织。通常,原因可以清楚地表述为团队承受了过多的技术债务——添加新功能或修改现有软件变得很难快速完成,因为代码库混乱,或者架构根本就不合理。

从根本上讲,敏捷是一种意识形态或哲学——一种思维方式。它通过《敏捷宣言》中的四个价值声明得以阐述,并通过十二条原则进一步定义。敏捷通过一系列实践得以实现,其中许多实践都在开放实践库中。这些实践被归类为流行的敏捷框架,如 Scrum、看板和SAFeScaled Agile Framework)。所有这些构成了敏捷生态系统。

图 12.6:将敏捷思维分解为价值观、原则、实践和框架

在我们深入探讨敏捷方法之前,先来考虑一下 OpenShift 如何与敏捷方法相辅相成并推动业务敏捷性。

OpenShift 如何帮助?

技术是使敏捷方法高效并能应对变化的基础支柱之一。OpenShift 是一个协作平台,它允许拥有不同技能的团队成员汇聚在一起,安全地编写、测试、交付和运营他们的应用软件。该平台提升了团队动态,因为团队成员可以快速、轻松地在一个统一的环境中工作——他们不会被迫孤立工作。在不同的 OpenShift 项目中同时运行多个工作流非常适合敏捷交付。不同的集群可以用来隔离生产和非生产工作负载。尽管每个环境的规模可能不同,但它们在软件构成上是相同的。这通过消除各个环境之间的版本和配置漂移,提升了质量。

正如我们在引言中提到的,我们仍然认为在交付过程中,较少敏捷的方法(包括瀑布方法)也有其存在的价值。现在,让我们来看一下不同项目的特点,这些特点可以决定最佳的方法是什么。

决策制定背景

2020 年,Red Hat 出版了一本名为变革需要实践的电子书。4 这是回应商业领袖们反复提出的一个问题:为什么如此多的数字化转型努力失败?在这本电子书中,Red Hat Open Innovation Labs 的全球总监 Mike Walker 解释道:“在复杂的社会技术系统中,创造创新变革的是一群人,而不是个人或经理。这些团队必须通过持续的探测、感知和响应结果的循环来调整系统。”

为了探索探测、感知和响应结果的循环,让我们引入一个非常有帮助的框架,将这种方法与不同系统中使用的其他方法进行比较。

Cynefin 框架

Cynefin 框架是由 Dave Snowden 在 1999 年创建的,当时他在 IBM 全球服务部门工作。Cynefin是威尔士语中的“栖息地”一词,这一框架提供了五种决策情境或领域

  • 清晰(直到 2014 年以前被称为简单,但最近由 Snowden 重新命名为清晰

  • 复杂

  • 复杂

  • 混乱

  • 无序

4 www.redhat.com/en/resources/transformation-takes-practice-open-practice-library-ebook

该框架旨在帮助经理识别他们如何看待情况,并理解自己和他人的行为。该框架借鉴了系统理论的研究,5 复杂性理论的研究,6 网络理论的研究,7 以及学习理论的研究。8

图 12.7:Cynefin 框架

清晰,或称显而易见的领域代表了已知的已知。这意味着已有规则(或最佳实践),情况是稳定的,因果关系是明确的——如果做 X,期望得到 Y。这是法律结构、标准操作程序和经过验证的有效实践的领域。

5 en.wikipedia.org/wiki/Systems_theory

6 en.wikipedia.org/wiki/Complexity_theory_and_organizations

7 en.wikipedia.org/wiki/Network_theory

8 en.wikipedia.org/wiki/Learning_theory_(education)

复杂领域由已知的未知组成。因果关系需要分析或专业知识,其中有一系列正确答案。该框架建议感知分析响应,即首先评估事实,然后进行分析并据此应用适当的良好操作实践。9

复杂领域代表了未知的未知。因果关系只能事后推断,且没有正确答案。Snowden 和 Boone 写道,“有启发性的模式...可以浮现出来,” “如果领导者进行可失败的实验。”Cynefin10 将这一过程称为探测感知回应

混乱领域,因果关系不明确。Patrick Lambe 写道,在这个领域,“事件是‘太过混乱以至于无法等待基于知识的反应’”。“行动任何行动是回应的首要也是唯一方式”。11 在这种情况下,管理者必须行动感知回应行动以建立秩序,感知稳定所在,回应以将混乱转变为复杂。

我们与客户合作的大部分工作都位于复杂和复杂领域之间。为了进一步区分这两个领域,我们来看一下《转型需要实践》电子书中使用的一个有帮助的例子,来说明法拉利与雨林的区别。

法拉利与雨林

哈佛商业评论的一项最新研究 12 显示,80%的企业领导者表示他们的数字化转型努力无效。对于追求数字化转型的组织来说,关键的一步是认识到复杂系统和复杂性系统之间的区别。想象一辆法拉利。它由成千上万个部件组成,但它们的总和以及各部件如何互动以形成整体是可以基本了解的。诊断和修理一辆故障的法拉利需要感知、分析和回应反馈与学习。一辆法拉利是一个复杂的系统。

9 hbr.org/2007/11/a-leaders-framework-for-decision-making

10 hbr.org/2007/11/a-leaders-framework-for-decision-making

11 Lambe, Patrick (2007). 《组织知识:分类法、知识与组织效能》。牛津:Chandos 出版公司,136.

12 www.redhat.com/cms/managed-files/cm-digital-transformation-harvard-business-review-analyst-paper-f22537-202003-en_0.pdf

现在想象一下雨林。它是一个动态的生态系统,有数十亿个相互作用的生物和元素。我们在某种程度上理解这些动态,但它们基本上是不可预测的,因果关系只在事后显现。威胁雨林的问题最好通过探测、感知和响应来解决。换句话说,需要持续的假设、实验和测量周期来达到期望的结果。雨林是一个复杂的系统。

如今的大型组织更像是雨林,而不是法拉利。这是一个复杂的社会技术系统,有许多动态——包括人类和技术——在发挥作用,抵制量化。这种区别很重要,因为许多企业领导人认为组织是复杂的系统,分析是达到最佳解决方案的关键。但在复杂系统中,探测和感知,或者假设和实验,通过快速和迭代的周期,更有效地识别最佳解决方案和想法。

这一切与 PetBattle 等案例研究的应用交付有什么关系?交付方法学附带大量术语。我们何时使用 Scrum 而不是 Kanban?燃尽图只是反向甘特图吗?在使用瀑布模型时如何展示交付的价值?最终,每种方法或方法都试图达到类似的目标:在任何给定产品中完成一个特性集,可以释放给我们的最终用户。

在尝试理解使用哪种方法论时,一些良好的起点指南包括以下内容:

  • 瀑布模型最适合按线性方式完成的项目,在这种项目中,清晰地感知-分类-响应是最佳实践。因此,遵循标准操作规程安装和测试软件产品将是一次性的工作,不会从敏捷性或迭代中获益。不需要返回先前的工作阶段。这通常被视为传统的工作方式。

  • 诸如 Scrum 之类的迭代敏捷框架关注通过完成和展示小的工作增量并根据反馈进行检查和适应。换句话说,我们采用了 Cynefin 复杂领域中表达的探测-感知-回应方法。其他敏捷框架如 Kanban 主要关注持续交付。接下来完成优先级最高的项目,并将一定量的工作从积压清单中拉到较小的工作流中。这也支持探测-感知-回应的思维方式。

让我们回到我们一直在本书中使用 Moebius 环的可视化和心智模型。

什么时候使用 Moebius 环思维方式才有意义?

我们喜欢 Moebius 环的一个原因是我们可以用它来解释我们曾参与的任何项目、计划或工作。

由于它是框架无关的,因此不要求使用 Scrum 或 Kanban。它没有规定团队或组织应通过多少次迭代,无论是围绕一个循环还是两个循环。它符合 Cynefin 框架的四个既定领域中的任何一个。

图 12.8:Cynefin 框架与 Mobius 循环

复杂领域的项目非常适合采用敏捷方法。通过 Mobius 循环可视化,这意味着从发现循环开始,确立首批目标成果。选项转向涉及细化接下来要发现或交付的内容,并决定要交付的选项。交付循环创建一个小的增量设计并进行构建,在生产中运行该增量以收集数据,并在学习和反思已实现的成果之前进行度量。然后,有一个适应点,可以选择再次进入发现循环,进行另一个交付循环,或返回选项转向以重新优先排序和调整方向。只要产品交付和/或运营持续进行,这个循环将永无止境。通过持续改进,将会越来越渴望加速发现和交付的迭代速度。

复杂领域的项目非常适合敏捷方法。通过 Mobius 循环可视化,这意味着从发现循环开始并确立目标成果。由于复杂领域的项目在回应之前需要进行感知和分析,因此这一过程可能会更慢且更为详细。对于复杂项目,通过选项进入交付,再到发现、交付或选项的循环,都会经历类似的模式。

最后,混乱领域的项目需要迅速行动,并尽可能快地脱离该领域。这可能意味着从交付循环开始进行紧急行动,随着混乱程度的减轻,开始重新组合并组织选项和发现循环。完成向发现循环的转变后,项目将从混乱状态转变为更复杂的项目。如今,大多数应用程序开发都被视为复杂的。

那么,像 PetBattle 这样的应用程序在 Cynefin 框架中处于何种位置,应该考虑什么样的最佳交付方法?

PetBattle——复杂、繁琐,还是清晰?

我们的 PetBattle 客户在我们将代码构建、测试并部署到生产环境之前,无法使用任何创新功能的想法。旧的爱好者应用程序是一次性完成并发布的。新的 PetBattle 组织是否应该继续这种方法?

团队走访了他们在发现循环和选项过程中所产生的所有成果。他们讨论了如何以最佳方式传递这些精彩的想法。在这次会议中,提出的一些评论包括:

  • 第三部分,探索它中解释的探索环节里,我们只对少部分功能创意进行了深入探索,即与影响图中的增加上传者网站参与度部分相关的内容。那部分是投资者投入大部分 Donal Dollars 的地方!我们最终需要考虑其他影响,或在看到早期功能构建的结果后深入探索这一部分。

  • 事件风暴中有很多粉色便签——这些代表了我们需要回答的疑问、假设和其他未知问题,最终我们需要解答并去除它们。

  • 到目前为止进行的事件风暴集中在玛丽参加每日锦标赛并赢得奖品这一情节。随着我们考虑新的功能和/或未知问题的答案,我们将需要进行更多的事件风暴。

  • 我们有一些好的目标结果和一些出色的假设声明,支持所有选项。但如果局面没有改变,而这些假设被证明是错误的怎么办?那我们就得重新开始。

  • 当我们在第四部分,优先排序中细化我们的选项时,在价值切片板上,我们确定了几个需要进行的实验。一些将通过 A/B 测试、功能切换和金丝雀发布进行,而一些则需要 UI 原型制作和技术探索,在进行这些操作后,我们才能推进。

  • 我们通过采访玛丽制作的同理心地图非常好。但它也显示了我们对用户的许多未曾想到的地方。当我们为其他用户制作同理心地图时会发生什么?或者,当我们开始与广告商和合作伙伴接触并为他们制作同理心地图时,他们会说些什么?

  • 平台上仍然有许多技术未知和待学习的内容。

有许多已知的未知问题,可能还有更多的未知未知问题!面对如此多的未知问题,我们如何以一种确保质量(不会破坏宠物大战锦标赛!)、速度(团队能否快速按时交付变化?)和效率(团队能否以低成本和便捷方式反复部署变化)的方式交付这些变化?

像大多数以用户为中心的应用程序一样,PetBattle 是一个复杂的社会技术系统,具有许多动态因素,包括人类和技术方面的因素。随着实践的不断发展,我们现在可以在接近首次(以及许多次)交付迭代时,转向探测-感知-响应的思维方式。

将一个复杂的产品带入首次交付的迭代意味着我们必须决定从选项清单中优先交付什么内容。我们还需要检查团队是否准备好处理所选项目,并且有信心他们已经具备足够的信息和理解来交付它们。这引出了另一个基础实践,即定义和评估交付准备度。

准备好的定义

Scrum 框架的共同创始人杰夫·萨瑟兰指出,许多 Scrum 项目失败的主要原因之一,是团队在处理根本没有准备好进行工作的项目。它们要么太模糊,无法被业务和技术利益相关者理解,要么太大,或者项目的范围不明确。

许多团队选择采用就绪定义(Definition of Ready)来减少这种风险。完工定义(Definition of Done)已经被敏捷团队广泛使用多年——我们将在本章稍后探讨这一点。而就绪定义的应用则较为少见。像开放实践库中的所有实践一样,定义就绪是一种工具,可以从工具箱中取出,用来解决问题。如果一个团队因为不明确且缺乏共同理解而在开始工作之前就遇到困难,添加一个就绪定义可能会提升团队未来的成功率。

就绪定义是一项团队用于设定与他人共同理解标准和期望的实践,目的是明确什么样的产品待办事项(Product Backlog item)才算是准备好可以进行工作的。最终的成果是由团队共同创建、维护和执行的,并公开展示,方便团队成员和其他人轻松参考。它通常基于经验,就像本书中其他实践所生成的成果一样。它是一个活生生的工具,随着学习和经验的积累,会不断改进。我们常常发现,在回顾会议期间的讨论,会促使就绪定义得到改进。例如,当一个团队遇到阻碍,未能完成他们在冲刺中拉取的事项时,他们可能会问自己,如果可以做得不同一点,是否能防止这些事项在冲刺中被阻塞。如果出现了共同的模式,并且能够通过改进流程来准备未来的事项,使其能够按时交付,那么这些标准可以写成就绪定义标准。

就绪定义通常从编写用户故事和/或验收标准开始。当我们查看产品待办事项时,它们通常都是相当高层次的,例如登录选择项目提交订单。乍一看,你可能会觉得这些事项的范围已经很明确,可以按预期交付。然而,开发团队成员提出的几个简单问题就揭示了其中存在的模糊性:

  • 当你说“登录”时,你是指用用户名和密码登录吗?

  • 如果我没有注册用户名,会发生什么?

  • 如果我输入错误的密码,会发生什么?

  • 登陆后应该立即发生什么?

编写用户故事的实践在敏捷团队中变得非常流行,因为它将一个功能的想法转化为对话(或者至少是创建对话的承诺)。

Mountain Goat Software 的 Mike Cohn 推动了用户故事的流行,并解释了这一做法如何帮助我们将关注点从编写需求转移到讨论需求上。所有敏捷用户故事都包含一两句话,且更重要的是,一系列关于期望功能的对话。13

用户故事通常遵循以下模板:作为一个<用户类型>,我想要<某个目标>,以便<某个原因>。

因此,在开发团队成员、业务利益相关者和产品负责人之间进行简短对话后,登录功能可以被重新编写为:“作为一个零售银行客户,我想通过用户名和正确的密码登录,以便查看我的最新银行余额。”这场对话可能还促使了将这个功能拆分为几个其他用户故事,分别聚焦于如何获取用户名/密码、如果输入了错误的凭证该怎么办、登录后可用的其他信息和选项、其他类型的客户应该能够查看和执行的操作等。

用户故事实践促进了三个结果,也就是所谓的三 C:

  1. 它生成了一个卡片——用户故事足够简短,可以写在一张小的索引卡片上(或数字等效物)。

  2. 它生成了对话——通过编写这个用户故事并捕捉需要进一步澄清或未知的内容,同时为处理该项提供了足够的方向。

  3. 它提供了确认,确认该单个项目的范围和期望,能够为最终用户提供一小部分价值。

13 www.mountaingoatsoftware.com/agile/user-stories

INVEST 标准是检查用户故事的一个良好模式。

图 12.9:INVEST 标准

你会注意到,将一个功能重新编写为用户故事有助于回答一些示例问题,但并不是所有问题都能解答。虽然我们可以通过编写许多用户故事来回答开发人员可能有的每个问题,但这可能导致用户故事过于细化,开发人员会将它们组合起来写代码。因此,为了准备交付项,第二种常见做法是编写验收标准。

验收标准是对理解的进一步阐述,通常写在记录用户故事的索引卡背面。验收标准是产品负责人和利益相关方在项完成时的满意条件。这些是低层次的二元澄清点——基本上是产品负责人可以逐一检查的复选框列表,以确保某个功能已经交付并符合业务期望。因此,我们的登录功能的一些验收标准可能包括以下内容:

  • 有一个用于输入用户名和密码的文本框。

  • 在下面,有一个超链接,用于创建新的用户名。

  • 用户名输入时将以纯文本形式显示。

  • 密码输入时将以星号形式隐藏。

  • 如果用户名/密码正确,客户详情页面将显示页面顶部的银行余额。

  • 如果用户名/密码错误,将出现错误信息。

  • 如果在 15 分钟内连续三次输入错误密码,系统将再锁定 15 分钟。

这些条目代表小型的、独立的测试(可以自动化)。它们特定于正在构建的功能。在本章后面,我们将展示如何使用另一个做法——完成定义,来推动其他类型的测试。

编写用户故事和接受标准有助于在团队成员、产品负责人和利益相关者之间生成共享的理解。这些通常是在产品待办事项梳理会议中使用的做法,前一章节已经讨论过这些会议。

如果团队认为这些做法有价值,他们可以选择将其添加到他们的就绪定义中,以便始终按流程编写用户故事和接受标准,以确保产品待办事项准备好进行处理。如果某个项目没有接受标准,那么它就不准备好,团队需要进行一些优化并编写接受标准,或者将该项目降级到产品待办事项列表中,寻找已准备好的替代项目。

现在,必须指出通过使用就绪定义可能出现的一些误用和问题。首先,这不是引入大量开发前阶段门槛的机会。就绪定义不应包括编写长篇规格说明、签署设计文档、获得架构审查委员会批准等内容。就绪定义的重点是让团队有信心,如果他们将某项工作带入交付迭代中,他们能够完成它。

其次,就绪定义不是团队的强制性做法。像本书中的所有做法一样,它是一个有用的工具,值得尝试。如果团队在交付迭代中遇到工作难以开展的问题,建议尝试使用就绪定义这一做法。

当我们查看团队的就绪定义时,我们寻找专注于团队及其舒适度或信心的声明,因为这些显然是促使团队前进的标准。

让我们来看看 PetBattle 团队制定的就绪定义标准。

PetBattle – 就绪定义

PetBattle 团队在他们的第一次交付迭代前几天开会。他们查看了从价值切片和发现循环实践中产生的产品待办事项。显然,团队对功能具体交付内容和涉及的工作存在一些不适和不一致。因此,团队安排了一系列 40 分钟的产品待办事项梳理会议。

在第一次会议中,他们集体头脑风暴出了他们的准备好定义,并一致同意在未来的所有产品待办事项精炼会议中使用此定义,以验证和确保任务准备好进行工作。

准备好定义包括:

  1. 团队已与产品负责人达成一致,编写并同意了验收标准,并在必要时与业务或技术相关方达成一致。

  2. 团队认为该项任务可以在开发后的几天内交付。

  3. 团队已经使用相对估算技术共同对该项任务进行了大小估算。

  4. 团队理解该项任务在现有或新兴架构中的位置。

  5. 团队已看到并能够访问 UI 和技术研究资料。

  6. 团队已满足完成该项任务所需的所有依赖关系,或者团队有信心在迭代内能够满足这些依赖关系。

  7. 团队认为进行这些工作的价值是显而易见的。

团队达成一致,他们将定期使用“五指信心投票”或“罗马投票”来评估他们对上述七个要点的舒适度和信心。他们一致认为,绝不应将未准备好的任务带入交付迭代。为了保持待办事项的准备状态,他们需要大量短时间的产品待办事项精炼会议。

若要与更广泛的社区讨论准备好定义,或阅读更多信息甚至改进实践,可以查看openpracticelibrary.com/practice/definition-of-ready/上的开放实践库页面。

现在我们有一些准备好交付的任务,接下来我们来看看最流行的迭代交付敏捷框架——Scrum。

Scrum

当你听到有人说他们现在在做敏捷,或者他们的团队在使用敏捷,或者他们的组织已经采纳了敏捷方法时,极有可能他们实际上是在做某种形式的 Scrum。

Scrum 并不等同于敏捷。正如前面提到的,敏捷是一种理念,它由敏捷宣言中规定的价值观和原则支撑。Scrum 是将敏捷理念转化为现实的若干框架之一,并通过一系列实践使这些原则成为可能。它也是目前最受欢迎的敏捷框架。

它是由 Jeff Sutherland 和 Ken Shwaber 在 1990 年代创建的,并且受到 Takeuchi Hirotaka 和 Nonaka Ikujiro 在他们发布的The New New Product Development Game一书中的启发。14

已经有大量关于 Scrum 的书籍出版,正如任何优秀的框架一样,它在学习和经验的基础上不断发展和完善。我们强烈推荐阅读 Sutherland 和 Schwaber 所呈现的 Scrum 指南的最新版本,网址为scrumguides.org/。这是我们建议定期重新阅读的文献,以便刷新对这一强大框架的记忆和知识。尤其是在框架进行更新或发布新版本时,这一点尤为重要。

我们在本书中不会教授 Scrum,因为有很多更好的文章和书籍,最重要的是,Scrum 指南能够做得更好。我们将展示的是我们如何在项目中使用 Scrum,它如何融入 Mobius 循环,并分享一些关于 Scrum 采纳的成功与失败故事。

我们喜欢 Scrum 的原因之一是它的简洁性。人们常常将与敏捷相关的许多(现在已经有几百种)实践误认为是 Scrum。事实上,Scrum 的实践非常少。我们通常将它们描述为 3-5-3(听起来有点像体育队的阵型!)。

14 hbr.org/1986/01/the-new-new-product-development-game

图 12.10:Scrum 框架

这个 Scrum 图可以通过将角色、事件和工件分组来最好地描述。

3-5-3 阵型

3-5-3 阵型代表了 3 个角色、5 个事件(以前称为仪式)和 3 个工件。这三个角色分别是产品负责人、ScrumMaster 和开发团队。五个事件包括 Sprint 计划会议、每日 Scrum(通常称为每日站会)、Sprint 评审(有时称为展示会)、Sprint 回顾会议,以及作为固定时间框架的 Sprint 本身。三个工件是产品待办事项、Sprint 待办事项和产品增量。就这样!这就是 Scrum 的全部内容。这三个角色、五个事件和三个工件。你所听到或看到的其他内容,比如“就绪定义”和“完成定义”、燃尽图和燃起图、用户故事和故事点等等,都是很好的辅助实践,但不是 Scrum 框架的一部分。接下来,我们将分享一些关于 Scrum 框架每个组成部分的建议和示例,从角色开始。

产品负责人角色

第十一章选项转折中,我们介绍了产品所有权的概念,并强烈推荐观看 Henrik Kniberg 的简明产品所有权视频 15。如果你还没有看过,请先放下这本书,马上去看。即使你看过,我们仍建议再看一遍。那 15 分钟的视频内容简直是金玉良言。

"产品负责人应当在整个发现循环和选项转折过程中发挥关键作用,并提供支持," Kniberg 解释道,他们的主要关注点是促进开发团队成员和利益相关者之间的沟通与协作。他们通过利用许多选项转折的实践来传达产品的愿景,并为团队设定优先级。

在交付迭代中,这种沟通更为重要。拥有一个能够代表业务、代表用户(甚至代表技术利益相关者)并且能够与开发人员定期沟通的产品负责人,是促使 Scrum 团队高效运作的关键因素。这意味着面对面的互动、谈判、对齐,以及在开发团队与产品负责人之间推动共同理解。

图 12.11:产品负责人和开发团队之间的合作

15 www.youtube.com/watch?v=502ILHjX9EE

产品负责人需要定期与开发团队保持联系,尤其是在产品待办事项梳理期间,例如,编写和商定接受标准,并在 Scrum 事件中,如冲刺计划会议中。

图 12.12:开发团队成员与产品负责人和业务专家澄清理解

第十一章的案例研究故事中,选项转换与不同产品负责人实验,我们提到产品负责人应该把当前的角色视为一种自我毁灭的角色,不需要长期存在。他们可以通过成功地帮助开发团队成员,直接将他们与利益相关者和用户连接,并建立这些关系到一个程度,使得他们的引导角色不再需要,从而使自己变得多余。

ScrumMaster 角色

我们没有包括 ScrumMaster 实际操作的照片,因为可以说,ScrumMaster 不应该总是处于前线行动中。其角色是一个支持性的角色。ScrumMaster 应该创造一个环境和一个安全空间,使得开发团队能够看到所有的行动。当然,这一切并不会立刻发生,尤其是对于一个新组建的团队或一个不熟悉敏捷或 Scrum 的团队。在这种情况下,他们需要辅导团队掌握 Scrum 流程,帮助团队朝着更具生产力的环境迈进。

通过出色的引导、实践的充分准备和有效的实践辅导,ScrumMaster 应该促进团队的自主性、自我纠正和持续改进以及自组织。这是另一个角色,在这个角色中,扮演者应该努力消除对自己的需求。最终,自主、 自组织、自我纠正的团队应该不再需要被引导或领导。

开发团队角色

如我们所见,产品负责人应该努力消除对自身的依赖,因为开发团队与用户和利益相关者之间有着良好的连接,且具备强大的心理安全感,能够进行双向反馈和交流。ScrumMaster 应该努力消除对自身的依赖,因为他们已经创造了一个充满信息辐射的环境,团队完全自治,自我修正、自我组织,并且不断改进。

这就只剩下 Scrum 框架中的一个角色没有消除对自身的依赖——开发团队。"开发团队"这一术语可能会有些混淆。首先,正如我们在本书中已经提到过的,DevOps 旨在打破开发与运维之间的壁垒,但 Scrum 术语似乎仍然偏重于开发。实际上,开发团队应该包括具备宽度和深度的 T 型人才,团队成员之间应该能够从产品积压工作中的每个功能设计、构建、测试、部署、运营到维护,涵盖所有环节。

一个优秀的开发团队很可能是由优秀的 ScrumMaster 为其创造了成功的条件。团队的工作空间将充满信息辐射器和由发现循环、选项转换、交付循环及基础实践生成的文档。团队的环境会经过精心设计,确保开发团队能够在最佳的条件下工作,尽可能消除所有预见的和不可预见的障碍。他们会组织团队远离电脑的时间,确保团队有足够的食物和水,组织社交活动,并持续监控和改善团队的开放文化和心理安全感。团队的动力水平、情绪以及赋权感、自主性、掌握感和目的感应定期进行评估和衡量(使用第四章开放文化中介绍的实践,如社会契约、停止世界事件、情绪弹珠和实时回顾等)。

图 12.13:跨职能开发团队

在本书的后续章节中,第十八章维持它,我们将探讨更大规模的多团队组织及其帮助团队间协作的新模式。但首先,让我们看看每个使用 Scrum 的团队应该始终拥有的文档。

产品积压工作文档

"积压工作"这一术语在敏捷开发中经常使用。如果团队采用的是 Scrum 框架,那么很重要的一点是要认识到存在不同类型的积压工作,它们的用途也各不相同。产品积压工作展示了整个产品的所有工作和创意,按照产品负责人确定的价值优先级进行排序,并且对利益相关者可见;例如,下一步将要开发的优先级最高的功能。

第十一章,选项枢轴中,我们介绍了产品待办事项,并展示了它如何从价值切片和用户故事映射中产生,并通过诸如影响与努力优先级排序、如何-现在-哇优先级排序、延迟成本和加权最短工作优先等实践进行优先级排序。从前面的章节中,我们知道,产品待办事项中的所有条目都是通过发现循环中的实践和对话生成的,例如影响映射、事件风暴、人本设计、非功能映射和基于指标的过程映射。

产品待办事项是一个如此重要的工件,应该显眼地展示,供所有人访问,并且应该定期由产品负责人、开发团队成员、ScrumMaster 以及其他利益相关者和任何对该产品下一步感兴趣的人使用。

产品待办事项是动态的,可以随时更新。所有导致产品待办事项条目的实践也都有动态的工件,随时可以更新。因此,任何时候,利益相关者可能会有新的影响或交付想法,这些想法可以直接添加到影响图上。开发团队可以在 Sprint 期间举行一次时间盒的事件风暴会议,以完善并回答现有事件风暴中的一些未解答问题,或者他们可能会针对业务的全新领域开始一个事件风暴。更多的用户访谈、人本设计和同理心映射应该继续进行。所有这些都会导致改进、优化和新的想法,可以通过价值切片进行处理、优先级排序,并最终进入产品待办事项。

图 12.14:公开展示在墙上的产品待办事项

Sprint 待办事项是一个不同的工件,更加聚焦于特定的 Sprint 或交付迭代。

Sprint 待办事项工件

Sprint 待办事项是在 Sprint 计划会议期间创建的,展示了团队从产品待办事项中提取到 Sprint 待办事项中的所有条目(在评估它们符合就绪定义后)。Sprint 计划会议的结果是一个 Sprint 待办事项,展示在一个高度使用且非常重要的信息辐射器——Sprint 看板上。

图 12.15:Sprint 看板上的 Sprint 待办事项

Sprint 看板是交付循环迭代中使用最频繁的工件之一,也是我们最喜欢的信息辐射器之一。在前面的示例中,展示了以下信息:

  1. Sprint 编号。

  2. Sprint 目标——用一到两句话描述产品在下一个增量中应该做的,但当前尚未完成的功能。理想情况下,这应该与在发现循环中达成的总体目标成果相连接。

  3. 关键日期——Sprint 计划会议、Sprint 回顾会议和 Sprint 迭代回顾日,以及该 Sprint 所需的时间。

  4. 从产品 Backlog 中提取的 Sprint Backlog 项目。两者之间是 1 对 1 的匹配,应该能够轻松从产品 Backlog 追溯到 Sprint Backlog。

  5. 已达成的每个 Sprint Backlog 项目的接受标准。

  6. 按照已达成的接受标准交付特性的低级任务。这些任务通过颜色编码来突出特性开发的跨职能性,不同的颜色代表前端和后端开发、数据、基础设施、运营和设计。

  7. 各列代表每个任务的工作流——Sprint Backlog、进行中、被阻塞、待审查和完成。

  8. 头像,用于显示哪些配对或多人小组正在进行中的列上工作某个特定任务。

  9. Burndown 图,16,一种流行的可视化实践,展示了任务完成的进度——在这种情况下,任务进度与 Sprint 的时间线(按天计算)进行对比。

    16 openpracticelibrary.com/practice/burndown/

以下模板可在本书的 GitHub 仓库 17 中找到,并可下载以虚拟方式生成相同的 Sprint Board。

图 12.16:分布式团队的数字 Sprint Board 模板

Sprint Board 最令人印象深刻和强大的方面之一是,它提供了开发给定特性的整个生命周期中(几乎)最后的可追溯性部分。如果你能参照所有 Discovery、Options 和 Delivery 工件,你应该能看到一个低级任务(通常是由一对开发团队成员执行、需要几个小时的开发活动)及其与以下内容的连接:

  1. Sprint Backlog 中的整体特性/用户故事以及接受标准。

  2. 该特性在产品 Backlog 中的位置。

  3. 使用价值切片和其他优先级工具来对该特性进行优先级排序。

  4. 该特性如何融入事件驱动系统和整体业务流程中的事件风暴。

  5. 在影响图中假设的该特性对可衡量影响的预测。

  6. 在影响图中捕获的演员,特性可以通过这些演员进行用户测试。

  7. 在影响图和目标成果中捕获的整体目标,特性应当朝着这一目标努力。

最终的可追溯性部分是工作软件,这是 Scrum 框架的第三个增量。

17 github.com/PacktPublishing/DevOps-Culture-and-Practice-with-OpenShift

产品增量工件。

《敏捷宣言》的第七条原则指出,工作的软件是进度的主要衡量标准。当《敏捷宣言》的原始作者大约十年后再度聚会时,他们进行了回顾性会议,看看他们的学习和经验是否会推动对价值观或原则的任何更新。普遍共识是,这些原则依然成立,除了在某些情况下强调工作的软件的重要性外,其他没有需要改变的地方。

许多 Scrum 团队在这一环节失败,无法在 Sprint 评审会上展示工作的软件。即使他们确实有部分构建好的软件在此次评审会上展示,这也不足够。展示时应该始终有工作的软件。

展示给我产品!

我们在 Red Hat Open Innovation Labs 中用于提高意识和分享工作方式的众多技巧之一,是在驻场合作期间分享简短的总结报告。

通常会将类似的总结分享给客户,附带工件链接、实践过程中的照片以及捕获或采取的反馈。

Mark O'Callaghan 是位于纽约的 Open Innovation Labs 驻场负责人。在 2018 年 1 月,他领导了一项面向 UNICEF18 的合作,重点是数据科学,尤其是将一些遗留应用的数据可视化,以支持联合国儿童基金会在哥伦比亚的工作。

Mark 会每周向 Red Hat 社区的 Labs 战略团队及其他相关方分享此合作的电子邮件更新。在他的每周总结中,他总是会包括指向最新版本应用的链接。这是运行在 OpenShift 上的工作软件。

这是一个很棒的例子:总是一个好主意拥有一个产品增量,一个人们可以点击的应用链接——来查看、使用并提供反馈。

18 www.redhat.com/en/proof-of-concept-series

我们对 Scrum 框架的最后一站,是探索五个 Scrum 事件——Sprint 规划、每日 Scrum、Sprint 评审、Sprint 回顾,以及 Sprint 本身。

Sprint 规划事件

在本章的前一部分,我们展示了 Sprint 看板,这是一种了不起的信息显示工具,提供了大量关于团队当前在做什么以及在此次交付循环中的计划内容。Sprint 规划事件正是产生该工件的事件。Scrum 指南详细说明了 Sprint 规划事件应该如何进行、谁应该参加、应持续多久等等。以下是我们通常如何操作的。

首先,我们确立一个目标。这应该是一个简短的一到两句话,总结冲刺的重点是什么。在一周的时间内,产品将做些什么当前做不到的事情?它可能会取得什么成果?这通常是一个与产品负责人进行对话的好时机,产品负责人可以站在白板旁准备清晰地阐述其动机并写下来。但他或她总是会得到开发团队的大力支持,并且有大量的文档可以参考。回顾我们在发现环节中确立的目标成果是非常有帮助的,这些成果通常会在旁边并且可见。查看产品待办事项列表也是有用的。如果它已经被很好地优化和优先排序,那么列表顶部的项目应该能提供一些线索,指示我们在下一次迭代中可以工作哪些内容。当然,回顾和提醒所有其他文档上的有价值信息,如同理心地图、用户研究、影响地图、事件风暴、回顾反馈、行动项等等也是非常有帮助的。有时,我们会草拟目标,然后在冲刺规划会议中进一步澄清和确认。

接下来,团队将开始从产品待办事项列表中拉取项目,按照优先级从顶部开始 – 产品负责人或项目经理不会做这个工作。重要的是要摆脱传统交付模式中常见的“推动现象”,并让团队参与拉取所需的项目,以实现目标。

在将项目从产品待办事项列表中拉入冲刺待办事项列表时,团队应确认这些项目是否真的准备好进行开发,可以通过查阅他们的“已准备好定义”(Definition of Ready)来确认。如果你们像我们一样,采用短周期的一周冲刺,可能很难在一周内找到足够的时间进行精炼,以满足所有“已准备好定义”标准,尤其是在冲刺规划之前。因此,我们通常会花一些时间确保项目已经准备好,验收标准已经编写并且被理解,团队也通常对该项目感到舒适和有信心。

第三步是将这些项目拆解为低层次的任务。我们需要捕捉完成该功能所需做的所有事情,以满足验收标准并符合“完成定义”(Definition of Done,稍后会讲到)。这是一个很好的机会,采用另一种发散-聚焦模式或解放结构,如第四章中介绍的开放文化,例如 1-2-4-全体(1-2-4-all)。让每个人思考实现这一增量所需的工作,并将其可视化是这个方法的一个非常强大的方面。这是超级详细设计!这是在我们甚至还没有编写代码之前就解决了 Bug!正如我们在示例冲刺看板中看到的那样,使用不同颜色的便利贴来表示不同类型的任务,如前端、后端、数据、设计、基础设施、运维等,可以很好地展示团队的跨职能特性。

图 12.17:产品负责人 Paul 继续在他的目标上进行迭代,而 MakMakMakMak 开发团队的一名成员将项目从产品待办事项列表中拉入到冲刺待办事项列表中。

经过大量协作,将个人的任务想法合并成对,再将这些对合并成成对的对,最后合并成一个完整的团队,我们应该得到一个冲刺看板,这将是未来一周工作的完整可视化。团队应该完全一致,知道需要完成哪些工作。那么,我们能做到这一点吗?我们通过信心投票结束冲刺规划活动——我们有多有信心?我们能通过交付冲刺待办事项中的特性来实现冲刺目标吗?我们能满足所有已商定的验收标准并在冲刺评审的日期之前完成所有任务吗?团队成员通过手指投票——0 或 1 表示非常低的信心,我们需要重新规划或讨论如何提高信心。4 或 5 的集体共识是我们想要的,这表明从一开始就充满信心。

图 12.18:关于实现冲刺目标的信心投票

如你从照片中看到的,那些大约 8 英尺乘 4 英尺的泡沫板,正如我们在第五章中解释的,开放环境与开放领导力,作为冲刺看板非常强大。它们轻便且便于携带,可以轻松地运输到不同的区域,例如每日 Scrum 或每日站立会议。

迭代或冲刺计划在开放实践库中有自己的条目,这是一个很好的资源,可以进一步了解、贡献改进并与更广泛的社区讨论该实践的使用。你可以在openpracticelibrary.com/practice/iteration-planning/找到这个页面。

每日 Scrum 事件

这个事件是最流行的敏捷实践,通常也是敏捷团队开始采用敏捷实践的方式。每天仅花 15 分钟同步活动,对于保持团队联系和推动自主性非常有价值。

我们发现与看板交谈的方式非常有效。每个成员总结他们昨天的工作(以帮助团队实现冲刺目标),今天计划做什么(以帮助团队实现冲刺目标),以及任何阻碍或障碍(阻止团队实现冲刺目标),看板可以被更新,以确保它传达的信息与团队成员所说的相符。个人的头像是否显示他们正在处理的任务?我们是否已经达成共识,哪些任务将组成对或小组?阻碍是否清晰地显示在“阻塞”栏目中?

图 12.19:每日 Scrum 实践

每日 Scrum 通常是Scrum-but反模式开始出现的地方。Scrum-but 代表着 Scrum 团队所表现出的不符合 Scrum 框架的活动或行为。例如:

  • 我们使用 Scrum,但我们的所有管理人员都会参加 Daily Scrum 以获取状态更新。

  • 我们使用 Scrum,但我们的 Daily Scrum 通常会持续 30 到 60 分钟。

  • 我们使用 Scrum,但我们会变动 Daily Scrum 的时间和参与人员。

这些都是与一个极其简单的实践相反的做法,简单的实践仅仅是为了让开发团队互相同步并突出任何阻碍因素。Jeff Sutherland 常说,如果你的 Daily Scrum 超过 15 分钟,那你就做错了。

如果你有关于这个实践的经验分享或改进建议,请访问 openpracticelibrary.com/practice/daily-standup/ 页面。

让我们快速跳到 Sprint 结束时,看看一个非常有价值的实践——Sprint 回顾会或展示会。

Sprint 回顾会

Scrum 框架称这个事件为 Sprint 回顾会,并提供展示新产品增量和邀请反馈的机会。这个 Open Practice Library 条目被称为展示会。

关于展示会的一些提示和建议如下:

首先,邀请全世界!这是一个展示产品增量给重要利益相关者和用户的绝佳机会。最好将这个活动安排在每个迭代的同一时间和地点,并且让远程加入变得更加便捷——投资于良好的视频会议系统可以真正提高参与度。

这是一个与现有利益相关者和发现环路实践的贡献者重新连接的好机会。看看你影响图上的参与者。看看我们为之制作同理心图的那些人。我们能把他们请回来,看看新兴的产品并提供进一步的反馈吗?

展示会不仅仅是展示闪亮的新功能特性,而是展示团队所做的所有工作,并展示给更广泛的利益相关者群体。我们的展示会通常包括:

  • CI/CD 基础设施(也许还包括展示如何交付一个小功能特性,以及如何触发 CI/CD 管道)。

  • 关于 UI/UX 的更新,包括任何新的用户研究或用户测试,原型的演变,以及由此产生的学习成果。

  • 最新版本的全局视图和新兴架构。

  • 展示团队如何工作以及他们使用的实践。这通常包括我们称之为“实践角”的内容——一个信息展示板,包含 Möbius 循环和基础结构,以及团队使用的所有实践。

  • 更新的产品待办事项列表,包含产品负责人关于下一步的期望。

  • 回顾整体目标结果和启用结果,以及最新的产品增量是否在这些衡量指标上有所改进。

  • 在与团队通常工作所在的相同物理工作空间中进行展示,开放墙面走动的选项,检查所有发现、选项、交付和基础工件以及信息辐射器。

图 12.20:冲刺回顾/展示

在下一章,我们将深入探讨更多关于度量和学习的内容,包括那些从展示活动中获得的反馈。展示不应仅限于一个活动。我们需要寻找其他创意方式,向世界展示我们在这次最新迭代的交付循环中交付了什么,以及我们是如何交付的。

如前所述,我们的参与负责人通常会简短写一篇总结,概述进展并分享最新的产品增量和其他工件。他们通过电子邮件将其发送给所有利益相关者以及对产品和/或团队感兴趣的人。从视觉上看,可以通过加入一些活动的照片或团队行动的短视频蒙太奇,展示产品从上一个冲刺中不断演变的过程。我们的参与几乎总是会制作一系列每周的视频,当这些视频一个接一个地观看时,会呈现出一个令人惊叹的故事。

正如我们在产品增量工件中所解释的,拥有可工作的软件是展示的最重要组成部分。能够提供应用程序链接并邀请展示观众触摸和体验用户界面非常吸引人。在工作空间中放置一台始终运行最新增量的工作软件产品的笔记本电脑,对访问工作空间的利益相关者来说非常有意义,大家可以在墙上走动,回顾所有实践的功能历史,亲身体验它们在产品中的呈现。

最终,如果我们没有从展示中获得反馈,它的价值是有限的。优秀的 ScrumMaster 和参与负责人会在这方面进行创意发挥。不要仅仅将其局限于结尾时的任何问题或反馈环节。可以在 Google 文档中允许在线反馈,或在 Slack 或短信上开设反馈渠道。邀请利益相关者进行价值投票,或使用净推荐值(Net Promoter Score)来捕捉基于指标的新反馈,了解利益相关者推荐产品和/或团队的可能性。

如果我们采取传统交付模式,我们何时会发现这些问题?

几年前,我与一个团队一起为爱尔兰的一家电信公司构建一个新的购物门户。我们使用设计思维框架,通过同理心映射、情境图、痛点和机会来收集要开发的功能列表。从用户那里直接获得信息是非常宝贵的,我们将从他们那里学到的所有内容提炼成三个目标结果,用于产品的首次发布。我们构建了一个产品待办事项列表,来完善我们的门户产品。

然后,在五周的发现阶段后,我们采用了 Scrum。每两周的迭代交付一个产品增量。每个 Sprint 都包括 Sprint 计划会、每日 Scrum、Sprint 回顾和 Sprint 回顾活动。

如果你看这张照片,其实它来自于 Sprint 2 回顾!我之所以选择这张照片,是因为在我拍完这张照片的几秒钟后,画面外的一位女士叫出了我们完全忽略的定价管理中的一些关键复杂性。这是我们直到这一刻才考虑到的事情。

图 12.21:Sprint 2 回顾

这一事件凸显了早期和定期反馈在不断发展的产品中的积极作用。如果我们没有尝试 Scrum,这一问题何时才会被发现呢?

它还突出了这样一种反馈,触发了进一步发现的需求(例如,通过设计思维)。

这个例子很好地展示了从交付循环中获得的学习和反馈,如何触发需要回到发现循环的需求。我们将在第十八章《保持它》中回到这个话题,并探讨其他相关模式。

要分享经验、学习,贡献改进,或者仅仅是阅读更多关于此的内容,请查看openpracticelibrary.com/practice/showcase/。我们还将在下一章中回到如何捕获基于度量的反馈的其他方式。Showcase 是收集这些反馈的主要论坛之一。另一个是 Sprint 回顾活动。

Sprint 回顾活动

这是本书中最重要的实践。若我们不进行回顾,我们就无法学习;若我们不学习,我们就无法改进;若我们不改进,那这一切又有何意义呢?

第四章,开放文化中,我们将回顾作为反馈机制引入,特别是我们介绍了实时回顾实践,作为将开放文化融入团队基础的好方法。希望实时回顾实践在通过发现循环、选项转折和交付循环的过程中发挥了良好的作用,并继续使用。它提供了一个立即添加反馈的地方,以回应实时发生的事情。

回顾是短暂的活动,团队可以暂时从当前的工作中抽身,花些时间检查他们的工作方式,并反思哪些方面做得好,哪些做得不好,以及哪些方面可以调整和改进。

如果你参与过一个长期的传统瀑布项目,你可能在最后做过某种形式的经验教训练习。这是所有人坐下来捕捉所有他们如果现在要重新开始可能会做得不同的事情的地方。所有这些都会被写下来并存档在某个共享驱动器中。如果我们幸运的话,其他团队或项目可能会从这些学习中受益,但往往情况是这些东西再也不会被看到了。嗯,回顾就像这样,但它们发生得更加频繁,并且涉及团队在学习过程中从中受益!

回顾可以随时进行。事实上,你可能想在一个长时间的事件风暴会议之后或者第一次产品 Backlog 优先级会议之后安排一个回顾。这是《开放实践库》的基础概念,因为它是一个可以随时使用的工具。Sprint 回顾是我们在 Sprint 或交付循环结束时进行的活动。Scrum 框架规定它应该在 Sprint Review 之后立即进行。

有数百种不同的回顾格式、风格和主题。人们不断想出新的、有趣的、创造性的和充满活力的方式来进行回顾。请查看《开放实践库》的回顾页面 19,其中包含许多这样的链接。如果你有自己喜欢的链接或者自己进行过回顾,你可以提交一个拉取请求并添加进去!

我们将分享几种我们进行过的回顾方式,并区分广度回顾深度回顾

广度回顾主要检查活动集中的时间段,并询问哪些工作得很好,哪些不好,以及下一次如何做得更好,以解决那些做得不够好的事情。

我们推荐的回顾方法是使用分散-聚合技术或解放结构,以确保每个人都有机会贡献他们的反馈意见。因此,请允许每个人默默地写下他们对工作情况、问题所在以及可能采取的纠正措施的反馈意见的几分钟时间。

然后我们汇聚并合并每个人的想法到一个信息辐射器上。我们对共同主题进行分类,并进行短期讨论以达成一致。最后,我们就一组修正行动达成一致意见,对它们进行优先排序,并同意负责推动它们的责任人。

19 openpracticelibrary.com/practice/retrospectives/

下一个回顾应始终通过回顾上一个回顾的行动来开启。以下是一些询问这些问题和提供回顾信息辐射器标题的替代方式:

  • 在下一个冲刺中,我们应该开始做什么(目前没有做的)?我们应该停止做什么(目前在做的)?我们应该继续做什么?我们应该做更多什么?我们应该做更少什么?

  • 我们应该增加什么?我们应该删减什么?我们应该保留什么?我们应该改进什么?

  • 在上一个冲刺中,是什么让我们开心?是什么让我们生气?是什么让我们难过?

  • 在上一个冲刺中,我们喜欢什么?我们学到了什么?我们缺少什么?我们渴望什么?这种格式被称为4Ls

为其中一种方法准备一个简单的画布,将帮助团队收集和组织反馈,并将所得到的学习和行动展示给所有人看。

图 12.22:流行的回顾格式

一种稍微更具创意和趣味的方法已经变得非常流行,那就是使用类比来反思和检查上一个冲刺。可以准备带有以下主题的画布:

  • 帆船:我们的岛屿是什么——我们所追求的目标或方向?风是什么——能带我们到达目的地的因素?哪些因素代表着锚,将我们拖回并减慢速度?有哪些岩石或风险是我们可能忽视的?

  • 热气球:热气代表那些帮助我们上升的因素。什么代表可能会拖慢我们的重量?我们想要驶向哪个阳光明媚的方向,以保证顺利前行?哪些恶劣天气可能让我们的旅程充满颠簸?

  • 赛车:是什么让我们前进?是什么让我们放慢速度?

还有许多其他类似的格式:三只小猪回顾,其中房屋分别由稻草、木棒和砖块建成;星球大战回顾,其中有光明面和黑暗面。我们甚至跟随过一些以音乐为主题的回顾,比如猫王和 ABBA,其中歌曲标题代表不同的检查方面。

回顾会议的一些常见陷阱包括过分关注负面问题,而忽视了已经取得的成功。一个好的方法是,如果某些事情做得很好,我们该如何加强这种行为,做得更好?另一个常见的陷阱是未能捕捉回顾事项。我们很容易在一小时内抱怨所有让我们烦恼的事情,但我们能做什么切实的事情来解决问题,或至少让情况变得更好?

另一方面,有时我们会提出太多想要解决的问题和改进的想法。使用优先级实践(如前一章所述)并将行动计划展示在自己的待办事项列表上,让大家都能看到并协助推进。

图 12.23:回顾画布和行动列表示例

回顾会议应该是有趣且引人入胜的。它们应该是团队期待的东西,让每个人都充满动力投入其中。这是团队持续改进的一个机会。

为了帮助保持团队的参与感,特别是对于那些长期运作的产品团队,调整形式非常有帮助。可以尝试为回顾会议引入新的创意。轮流让团队成员担任主持人,让他们共同分享这一实践的所有权,找出哪些方法在回顾会议中效果良好,哪些我们应该多做,哪些则不行。换句话说,有时候我们应该对我们的回顾会议进行回顾!

最后一个小建议来自多年的经验,关于如何在常规工作场所以外举办回顾会议,也就是我们所称的酒吧回顾

酒吧回顾!

2007 年,我第一次体验到了一个优秀的 Scrum 团队。在过去的几年里,我曾接触过敏捷方法,但那时的实践很不规范,充满了很多所谓的 Scrum-buts(“Scrum 但是”)。一位团队成员(他来自一家高效能的软件公司,且在 Scrum 方面非常有经验)建议我们将回顾会议搬到外面的地方,去当地的酒吧举办。他认为,远离日常工作空间能够让团队成员更好地回顾和反思前一个冲刺的情况。

我们还认识到,在传统项目中,经过数月的努力,软件最终上线,我们通常会通过某种活动来庆祝这一时刻。而在 Scrum 中,我们应该在每次冲刺结束时发布软件,并力求在每次冲刺后实现上线,朝着持续交付的目标前进。因此,庆祝是合适的,而最好的庆祝地点,莫过于当地的酒吧了!

最后一点是,人们在放松的酒吧氛围中可以更诚实,也能更有创意地提出想法。必须说,经过多年的经验,一些最好的改进想法正是来自酒吧回顾!

图 12.24:酒吧回顾

当然,将所有捕捉到的文档和学习成果带回到实际工作空间,并将回顾会议的行动项纳入待办事项列表是非常重要的,这样它们才能被优先处理并付诸实践。

尝试改变回顾会议的地点。我曾经带领我的团队突破了酒吧的局限,做过的活动包括十人保龄球、城市高尔夫,甚至在一个不错的咖啡馆里聚会,一起享受回顾早餐

关于回顾会议有很多要分享的内容。请查看openpracticelibrary.com/practice/retrospectives。页面上有几个很棒的资源链接。如果你发现了一个绝妙的回顾会议形式,欢迎通过点击页面上的改进此实践按钮添加进去。

现在,我们已经了解了 Scrum 的 3-5-3 框架,并从实际使用中获得了一些轶事和经验教训,让我们看看 PetBattle 团队是如何实践 Scrum 的。

PetBattle 团队的一次冲刺:准备阶段

PetBattle 团队决定采用一周为期的 Sprint 节奏。Aidan、Emma 和 Jen 以前使用过 Scrum,但总是采用两周的 Sprint;他们把这次机会视为缩短反馈周期和更快交付结果的机会。

提醒自己 Scrum 的价值观和框架后,团队在一个未使用的白板上列出了 Scrum 团队成员。现在将只有一个 Scrum 团队。Valerie 当然是产品负责人(Product Owner)。跨职能的开发团队将由四名工程师(Ciaran、Aidan、Emma 和 Jen)、UX 设计师(Dave)和质量保证专家(Susan)组成。Eoin 将担任 ScrumMaster。

Valerie 对她的产品待办事项列表感到非常自豪。她通过使用所有的发现循环(Discovery Loop)和选项调整(Options Pivot)实践,认为她的利益相关者对产品待办事项有了充分的理解,并且使用 WSJF 经济优先级模型进行了很好的优先级排序。

图 12.25:PetBattle 产品待办事项列表

团队将他们的准备定义(Definition of Ready)挂在墙上,并且一直在进行简短的产品待办事项精炼会议,确保根据准备定义将产品待办事项中的前十项准备好。这意味着要与 Valerie 一起讨论这些事项(她偶尔会邀请一两位利益相关者来支持讨论),并编写接受标准。

团队使用了信心投票(Confidence Voting)实践来评估他们是否认为某个事项能够在几天的开发周期内交付。他们还会讨论并更新正在形成的逻辑架构图,以确保他们对功能的构建有共同的理解。Dave 已经做了很多 UI 草图和线框图,并会在适当的时候将这些加入讨论中。

Sprint 节奏已经设定。Sprint 规划会议将在每周四上午 10:00 到 12:00 之间进行。每日 Scrum 会议将在每天早晨的 09:30 到 09:45 之间安排。Sprint 展示会将在每周三下午 15:00 举行,紧接着是 Sprint 回顾会议(Sprint Retrospective),该会议将在 16:30 召开,地点在外部场所。

PetBattle 的一周 Sprint:Sprint 1 规划

第一次冲刺规划会议由 Eoin 精心主持,他把议程贴在墙上并有效地管理时间。团队花了 15 分钟讨论 Sprint 1 的目标。Valerie 希望看到 PetBattle 应用程序能够正常运行,这样她就能将链接分享给 Mary 和其他几位自愿担任用户测试者的人。为了做到这一点,她认为一个好的起点是能够添加自己的宠物并启动一个比赛。如果应用程序能在一周内做到这一点,她会很满意。Jen 非常希望能够利用他们在建立技术基础时所做的所有绿色工作,确保所有组件都通过 CI/CD 进行构建,并实现自动部署和跟踪。四位工程师非常希望采取以运营为优先的思维方式,并确保从一开始就拥有良好的自动化恢复框架以及可配置的日志记录功能。

将这些想法汇总后,团队确定了 Sprint 1 的目标:构建 PetBattle 的“行走骨架”,将前端与 API 和数据库连接,并通过 CI/CD 提供操作基础。

接下来的议程是从产品待办事项中拉取条目,最终检查它们是否准备就绪,并将其放入冲刺待办事项列表。Eoin 提议团队复制便签,并保持产品待办事项的信息展示完整。他会在将条目添加到 Sprint 1 待办事项列表时,在产品待办事项上标记这些条目。Eoin 还提出了一个想法,在 Sprint 待办事项上显示该条目所关联的结果。这可以通过参考“价值切片”板轻松查看。总体而言,这意味着我们有一个很好的信息展示板,显示了团队多么注重结果导向。

团队将产品待办事项列表中的前十项拉入了冲刺待办事项列表。他们讨论并对验收标准进行了最终修改。Valerie 感到兴奋,因为这将使她通过“价值切片”方法在短短一周内交付出所有顶端的价值!Aidan 和 Ciaran 开始有些紧张,因为冲刺待办事项列表逐渐填满了。他们很高兴看到,冲刺的内容平衡了功能开发与非功能开发,以及关注操作的工作。团队也很高兴能进行一些关于 CI/CD 的教育研讨会,并将长期期待的早餐和午餐计划落实,以帮助改善文化。但也有一些关于 TLS 安全性和确保性能的担忧。这将需要一些技术性的突破和研究,而距离第一次展示会只有四个工作日,Aidan 和 Ciaran 担心他们没有足够的时间完成所有任务。

他们继续进行了 Sprint 计划活动的第二部分,将所有事项分解为任务。他们决定为任务进行颜色编码,所有任务将写在方形的便签纸上。粉色便签代表应用开发,蓝色便签代表 UI/UX 和前端开发,浅绿色便签代表数据配置,紫色便签代表自动化,深绿色便签代表基础设施和平台工作,黄色便签代表测试。还有一些任务专注于文化建设、团队建设和其他软性/模糊任务!他们决定将这些任务称为“软任务”,并将其写在橙色便签上。

团队分成了若干对,每一对专注于两个功能。每五分钟,他们就轮换一次故事,以便每一对能够思考每个功能的任务。然后,所有任务都被添加到 Sprint 看板中,重复的任务被移除,之后团队对每个任务进行了讨论,直到大家确信他们已经掌握了每个 Sprint 待办事项的低级细节,并能够按照约定的接受标准交付。

最后,Eoin 使用五指投票法(Fist of Five)来进行信心投票。在三秒倒计时后,每个人用手指(从一到五)展示他们有多自信能够实现 Sprint 目标,即按接受标准和任务要求,在下周三下午 3 点之前交付 Sprint 待办事项。投票结果从二到四不等,其中 Ciaran、Aidan、Emma 和 Jen 都只投了两根手指。关于完成所有十个功能的紧张情绪在不断增加。经过讨论,Valerie 提议将与安全性和性能相关的任务移除,并重新放回产品待办事项中。第二次信心投票后,所有人投了四或五票,团队的气氛也变得更加轻松和愉快!

Sprint 看板已经准备好,其中包括一个燃尽图,用来可视化看板上 26 个任务的进展。

图 12.26:Sprint 1 计划后的 PetBattle Sprint 看板

现在我们已经看完了 Sprint 1 计划,让我们看看团队在交付过程中表现如何。

PetBattle 的 Sprint 生活:Sprint 1 交付

Sprint 1 正式开始。团队遵循他们的社会契约,在第一次学习某些内容时作为一个整体进行合作,而其他任务则以配对的方式进行。他们每天早晨都举行日常 Scrum 会议,虽然不总是在 09:30 开始,有时会议会延时 15 分钟。这一情况将会在 Sprint 回顾会议上进行讨论。

在 Eoin 的不断催促下,团队开始逐渐熟悉更新 Sprint 看板,头像更加清晰地显示了每个人在做什么。他们也逐渐熟悉了燃尽图,燃尽图显示了在 Sprint 过程中不同时间点,完成的任务数量。对于团队何时可以宣告一个功能或任务完成,仍然存在一些混淆。这个问题将在 Sprint 总结会议中再次提到。

在此之前,星期三下午 15:00,团队一直在紧张地完成最后的任务;虽然差不多完成了,但仍有一些任务在进行中。CI/CD 一直运作良好,Sprint 回顾的当天上午,应用程序的版本已经被推广到他们的测试环境中。

图 12.27:PetBattle Sprint 1 看板完成

PetBattle 生命中的一次冲刺:Sprint 1 回顾与总结

有一些利益相关者参加了 Sprint 回顾,但并没有 Valerie 预期的那么多。团队需要思考如何最好地分享更新。Sprint 回顾过程中出现了一些小问题,但总体来说,作为第一次 Sprint,进展还算顺利。Valerie 解释了她为团队设定的目标,Eoin 介绍了 Sprint 看板。团队成员依次展示了他们所做的功能。一些功能展示了应用程序的新前端界面,另一些功能则更具技术性,展示了配置文件的讲解和一些测试的运行。他们甚至展示了早餐和午餐轮换表,并且说明了他们如何与 Deliveroo 建立工作关系!Dave 分享了用户体验的更新,以及他计划在下一个 Sprint 中进行更多的用户测试。Susan 展示了自动化测试框架并成功运行了自动化测试。

展示环节结束时,Ciaran 展示了基于最新 CI/CD 基础设施的更新大图。这次演示是基于这个基础设施进行的。Eoin 展示了一个新的信息显示板,名为实践角落。这是一个巨大的莫比乌斯环和基础结构,展示了团队迄今为止用来构建他们基础架构的所有实践,包括如何让他们顺利进入发现循环、完成选项转折,并且现在完成了第一个交付循环迭代。这一切真的让人感受到团队所使用的所有实践是如何紧密结合的。利益相关者们被邀请走到墙边,进一步探索这些实践的细节。

开发团队与 Eoin 一起去了当地的酒吧,带了一堆便利贴。他们进行了 Sprint 1 总结会议,采用了简单的开始-停止-继续格式。团队集体总结了本次 Sprint,大家一致认为他们应该:

  • 继续进行配对和集体协作

  • 停止迟到参加每日 Scrum

  • 开始在每日 Scrum 更新时拿着哑铃,以防更新时间过长

  • 开始创建一个社交播放列表,下午有些音乐可听

  • 开始使用“完成定义”实践(包括在 Sprint 2 中编写完成定义)

  • 继续使用并改进 CI/CD 解决方案

  • 开始拥有更多信息显示器,如构建监视器和测试分数

团队举杯庆祝:冲刺一结束!第二天,他们将重新开始进行 Sprint 2 的规划。

这些 PetBattle 故事展示了完整的 Scrum 交付周期的示例。当团队选择执行更多的交付循环时,同样的过程会重复进行。

使用 Scrum 与分布式团队

与本书前面介绍的实践类似,本章以及交付循环中介绍的实践都是高度协作和可视化的。一个成功的关键因素是让 Scrum 团队共地办公,并能够使用诸如产品待办事项列表和冲刺待办事项列表等物理工具。

但许多 Scrum 团队在人员分布的情况下也取得了成功,尤其是在他们投资了强大的数字协作和视频会议工具时。第四章《开放文化》中提到的文化和心理安全的重要性在这里更加显著,至关重要的是学习和持续改进的能力。这是 Scrum 团队成功的关键,因此,花时间促进持续学习的环境可以为分布式 Scrum 团队的成功奠定基础。

有许多工具可以模拟在房间内实际使用的每个实践和工作方式。我们力求复制我们在房间中使用的每个实践,以便在线实时工作。因此,如果一个团队在 Sprint 规划期间使用便签和白板进行协作,他们需要能够使用工具完成相同的操作。如果他们使用一个大白板来管理产品待办事项,他们需要一个工具来在线托管它。如果他们使用信心投票或计划扑克进行相对估算,他们需要一个工具来继续这样做。如果他们在房间中使用燃尽图,他们需要找到一种方法在线上继续使用它。

为帮助您入门,我们提供了一个简单的 Sprint Board,可用于在 Sprint 规划过程中构建冲刺待办事项,计划 Sprint 评审时的功能演示,并在 Sprint 回顾中传播学习成果。您可以在github.com/PacktPublishing/DevOps-Culture-and-Practice-with-OpenShift下载此工具。

图 12.28:分布式团队使用的数字 Sprint Board

为了完成关于 Scrum 的这一部分,我们将抛出一个重磅消息。Scrum 是次优的。Scrum 不符合持续交付(Continuous Delivery)的要求。Scrum 实际上可能在交付过程中造成瓶颈,而不是消除瓶颈!

你现在可能会好奇,为什么我们花了这么多时间和篇幅解释 Scrum,分享使用 Scrum 的故事和经验,并让我们的 PetBattle 团队采纳 Scrum。那么,Scrum 为我们提供了一个急需的节奏和一套保障措施,以便以更加敏捷的方式开始工作。通常情况下,新的或不成熟的团队需要这些保障措施和框架来确保走在正确的道路上,并确保交付价值。但随着团队的成熟和提升,可能会有某个时刻,我们可以去除一些或全部的这些保障措施。

我们什么时候应该停止使用 Scrum?

许多团队和利益相关者都喜欢 Scrum。能够更频繁地看到成果提前交付的节奏是非常令人满足的,它解决了传统瀑布式开发复杂系统中遇到的许多问题。

第十一章选择权转变中,我们讨论了产品负责人应该如何努力消除自己在团队中的角色,因为开发团队与用户和利益相关者有着非常紧密的联系,并且具备强大的心理安全感,能够进行双向反馈和对话。在本章的前面部分,我们讨论了 ScrumMaster 应该如何努力消除自己在团队中的角色,因为他们已经为团队提供了一个充满信息辐射的环境,且团队完全自主、自我纠正、自我组织并不断改进。产品负责人和 ScrumMaster 有两个非常强大的保障措施来促进 Scrum 框架的实施。这些保障措施可能永远存在,但我们应该力求做到。

让我们看看其他一些我们可能希望去除的保障措施的例子。

团队提出的疑问表明我们可能已经超越了 Scrum 的范畴

在与长期存在的 Scrum 团队合作了几个月之后,看到他们不断改进并随着时间的推移变得更加自主,真是令人非常高兴。我注意到高效能团队有一个共同的模式,那就是一段时间后,他们开始提出一些非常好的问题,关于他们使用的流程和实践。一些 ScrumMaster、产品负责人和开发团队成员提出的问题示例如下:

  • 为什么我们要等 X 天才进行演示/发布?Scrum 规定每个冲刺的最后一天举行冲刺评审会议。但有时,团队会在冲刺的第一天就完成一个功能,并准备好进行演示和发布!

  • 为什么我们不持续进行回顾会议?实时回顾会议是一种很棒的实践,能够更实时地捕捉回顾反馈和行动,而不是等到冲刺结束后再进行。如果团队有效地运用了这一方法,或许就不需要等到冲刺结束再达成改进行动的共识。

  • 为什么我们要每天做 15 分钟的站立会议,当我们已经知道每个人在做什么时?当团队越来越多地使用集体编程时,这种情况尤其成立。团队成员彼此紧密联系,合作得非常紧密,以至于每天正式同步的需求大大减少。

我在一家欧洲汽车公司与一位产品负责人合作时,在他第五个冲刺中有了一个大的恍然大悟时刻,他问道:“为什么我要等到冲刺结束才能看到特性 X?”他希望将团队引导到一种心态:“当你完成一个特性时给我展示它如果我满意的话我们就直接发布它吧。

上面的示例展示了 Scrum 如何可能会阻碍持续交付。当然,它们可以比每个冲刺仅发布一次更加频繁地发布。Scrum 框架并没有说只能或应该只发布一次。

当团队达到这些认识和成熟的阶段时,团队成员可能会觉得,如果转向不同的方法,采用看板,他们可以更持续地交付。

看板

看板是日语中的标志牌。看板起源于汽车行业,特别是丰田汽车中,作为一种精益制造的调度系统。

在敏捷环境中,看板是另一种方法论。它特别致力于更好地协调和平衡团队成员之间的工作。最著名的是,它使用看板来帮助可视化整个过程。看板分为待办工作进行中的工作已完成的工作几个类别。每个任务都会写在卡片上,随着任务的进展,卡片会从一个栏目移动到另一个栏目。任务按优先级排列。看板让每个人都保持在同一页,并且非常显眼,因此可以轻松进行修正。

看板!

在 2018 年与我们开放创新实验室团队成员一起举办 DevOps 文化与实践启用工作坊时,我们有幸遇到了我们的日本同事。我们书中描述的许多精益文化和流程都源自日本,因此我们总是从与那里的同行合作中学到很多。

在课程中,我们多次提到看板,结果似乎引发了一些笑声!我们的日本同事最终告诉我们不要再说看板板,因为我们其实是在说标志牌板。因此,我们现在称这个工具为“看板”。

看板要求对任何时刻进行中的任务数量进行严格限制。这被称为进行中的工作(Work In Progress,简称 WIP)限制。一旦达到 WIP 限制,新的工作就不能进入该栏目。团队需要协作修复问题、识别瓶颈并完成任务。这种协作工作方式有助于团队流程的持续改进。团队可以定期开会讨论需要改变的地方,并将这些变化展示在看板上。

图 12.29:示例看板

Scrum 和 Kanban 不是相互排斥的。事实上,在我们的 PetBattle 示例中,我们讲解了团队使用 Scrum,但在 Sprint 看板上,他们使用 Kanban 来可视化和跟踪工作。

PetBattle – 提前发布,频繁发布,持续发布

实现并交付 PetBattle 比赛功能需要快速完成。现有的业余爱好者应用程序是以非常临时的方式开发的,因此当前几乎没有任何流程。采用敏捷 Scrum 或 Kanban 看起来是跨职能团队的一个很好的起点。

团队能决定何时将PetBattle 比赛功能部署到生产环境吗?YES!团队能决定如何构建、测试和部署PetBattle 比赛功能吗?YES!通过让团队具备自主权,决策会在团队内有信息的地方进行。这种信任和将 PetBattle 应用交付的职责从创始人授权给团队的做法,对产品未来的成功至关重要。

看板在开放实践库中有自己的页面,可以用于获取更多信息、讨论和改进,网址是openpracticelibrary.com/practice/kanban/

我们还没有介绍的一个重要实践,适用于同时使用 Scrum 和 Kanban 的团队,那就是完成定义。

完成标准

在编写软件特性的过程中,Done 代表什么?是指你的代码能在电脑上编译并运行吗?是指已经进行了测试吗?是指代码已经提交了吗?文档呢?操作准备呢?这些都是很好的问题,而且如果你问不同的人,你可能会得到非常不同的答案。

当我们在 Scrum 中使用看板和/或 Sprint 看板时,右侧有一列标为 DONE。那么,Done 在这里意味着什么呢?这就是我们使用完成定义实践的原因。完成定义是一个跨团队达成一致并与其他人共享的标准,必须在任何工作项被任何团队成员视为完成之前满足。它是由团队共同创建、维护并执行的,其中涉及每个工作项必须执行的非功能性和功能性工作。

在本章早些时候,我们讨论了如何针对产品待办列表中的特性或故事编写验收标准。这是一个特定的标准,适用于相关特性的具体上下文,仅限于该特性。完成标准是额外的标准,也需要在每个特性的规划和交付过程中予以考虑。

因此,如果我们说我们始终将代码提交到源代码控制系统中以触发 CI 构建,并且在此之前没有完成的特性不应被视为完成,那么我们可以将这一点添加到完成定义中。如果团队一致同意,我们应始终有一定比例的代码覆盖自动化测试,并且绝不低于该比例,我们可以编写完成定义标准来管理这一点。

宠物战斗 - 完成定义

在 Sprint 1 中,宠物战斗团队已经制定了完成准备的定义,并且它有效地确保了他们接受进入 Sprint 的项目已经被充分理解,且团队有信心能够在 Sprint 内完成它们。

然而,他们并没有编写完成定义。这导致了 Sprint 中的一些挑战,因为在什么情况下特性才算完成存在模糊性。这个问题在 Sprint 回顾讨论中提出,团队同意在 Sprint 2 中举行完成定义工作坊。

Eoin 主持了这次讨论,并提出了一个问题:无论处理什么内容,处理 Sprint 待办事项时应该始终做些什么。团队提供了几个建议:

  • 代码和其他任何工件必须始终提交到 Git 中的特性分支。

  • 特性分支应该已经合并到主分支中。

  • CI 构建应该始终运行,并且所有测试都通过。

  • 我们将检查文档文件夹,并对产品文档进行更新,以支持正在进行的工作。

  • 产品负责人应该已经看到功能正常,并且验收标准得到满足。

这为工作坊提供了一个很好的起点,特别是 Aidan 和 Emma 认识到,完成定义是一个很好的工具,可以确保他们通过非功能性轮头脑风暴出的非功能性方面能被考虑到每个项目中。

其中一些项目因涉及特定的配置工作而出现在了价值切分板上。但许多项目需要团队行为或一些自动化工具,持续专注于解决非功能性需求NFRs)。

团队重新审视了非功能性轮中的所有项目,并将每个项目分类到三个列表中的一个:

  1. 需要平台设置/配置/探索

  2. 随平台免费提供

  3. 团队行为/原则

图 12.30:将非功能性需求转移到完成定义标准

第一列中的项目被视为要包括在产品待办列表中的特性,因为它们需要特定的工作来使其在 Sprint 中启用。第三列中的所有项目应该被视为完成定义的一部分。然而,由于它们过于底层和详细,团队决定在完成定义中添加一些额外的项目,以确保它们得到适当的考虑:

  • 必须至少有一个自动化集成测试覆盖验收标准。

  • 将进行检查(尽可能自动化),以确保所有非功能性原则得到遵守。

  • 该功能正在演示用的笔记本电脑上运行,并随时准备向相关利益方展示。

“完成定义”提供了一个有效管理非功能性需求和质量的地方。虽然它最初是一个大的可视化信息展示器,但它会将越来越多的质量注入到产品中。它还提供了许多自动化的机会。鉴于“完成定义”标准是每个进入冲刺的待办事项都需要测试的内容,满足这些标准的相同测试应一遍又一遍地运行。因此,自动化无疑是我们在这里的好帮手。要进一步了解“完成定义”的实践,请访问openpracticelibrary.com/practice/definition-of-done/页面。

现在我们已经引入了“完成定义”(Definition of Done),在计划冲刺或任务以交付特定功能时,我们可以改进团队应该问每个待办事项的问题。为了满足商定的验收标准并遵守“完成定义”,需要哪些任务来交付这个功能?而且,随着回顾会议不断检查如何改进,团队应该考虑将什么内容加入到“完成定义”中,以改进整体产品,并考虑什么内容需要自动化,使“完成”成为 CI/CD 的一部分。

当一个团队没有可见的“完成定义”或者未遵循该定义,或未将其融入到他们的 CI/CD 流水线中时,这通常会成为一种糟糕的敏捷气味,往往导致技术债务上升和团队速度减慢。让我们来看一下我们曾见过的其他几种糟糕的敏捷气味。

糟糕的敏捷气味

多年来,我们在敏捷团队中发现了几种反模式,这些都是我们在与客户合作时早期就会关注和测试的事项。以下是我们的十大反模式:

  1. 没有产品负责人,或者产品负责人缺乏三个重要属性——有决策权、能与团队保持联系、并且理解业务。

  2. 没有可工作的软件:要么 CI/CD 工作不充分(或者根本没有实施),要么自动化测试不足。应该始终有一个可以随时演示或发布的工作版本。它可能功能不多,业务方可能希望在发布前增加更多功能,但这必须始终是一个业务决策,而不是技术限制。

  3. 冲刺是提前规划好的。由于范围可变,我们需要让团队控制他们加载到冲刺中的内容。随着时间的推移,他们将建立关于速度和可预测性的度量——这两者都应该不断改进,并且有强大的工具帮助他们准确地估算和预测。

  4. 回顾会议没有进行,或没有产生任何行动。持续改进应该是每个参与该工作系统的人的 DNA 的一部分。回顾会议是实现这一目标的基本构建模块。

  5. 每日站会超过 30 分钟且会议过多。为了让敏捷交付有效,我们必须允许团队专注于工作并自己找到解决方案。过多的会议只会拖慢他们的进度。让我们将会议限制在 Scrum 的基本事件和产品待办事项精炼(Product Backlog Refinement)上。

  6. 水瀑模型(Water-Scrum-Fall):前期规划和设计,特性的交付分阶段进行,但只能到预生产阶段,因为需要一个尾部操作准备阶段。这会剥夺敏捷的所有优势,并且无法提供持续交付。可以考虑使用莫比乌斯环(Mobius Loop)来阐明我们实际在这些循环中运行的速度和频率。

  7. 团队不是跨职能的。这会导致依赖关系、阻塞点和无法持续交付。

  8. 团队缺乏对不准备好的项目说“不”的授权。使用像准备好定义(Definition of Ready)这样的工具,并赋予团队自主权,能够提升文化、动机以及持续交付的能力。

  9. 功能性特性与非功能性特性在冲刺中被拉入的工作不平衡。我们需要持续平衡来自团队的需求和反馈(例如,回顾会议),包括持续改进架构和 CI/CD 的需求,并保持团队充满自主性、精通感和目标感。否则,技术债务上升,动机下降,持续交付减慢或停止。

  10. 推动而非拉动。我们所建立的系统是为了赋能团队拉取工作,而不是让工作为他们规划并强加给他们。

这些不良敏捷气味应当牢记,如果它们开始出现,我们建议使用回顾会议作为讨论和调整方向的起点。

结论

在本章中,我们从发现循环(Discovery Loop)和选项转折(Options Pivot)开始,重点介绍了如何使用交付循环的实践将特性交付到产品中。我们通过 Cynefin 框架探索了不同的交付领域,发现瀑布模型在明确领域仍然有效,而敏捷更适合复杂复杂性领域的工作。

我们探讨了敏捷的起源,敏捷宣言,并详细了解了 Scrum 和 Kanban 方法、它们所采用的实践,以及支持性实践,如准备好定义(Definition of Ready)和完成定义(Definition of Done)。

图 12.31:将交付循环实践和更多实践添加到基础架构中,以支持交付

我们现在可以看到,敏捷框架和实践如何在使用像 OpenShift 这样的平台时帮助实现持续交付,并且当与高绩效团队和自主文化结合时,我们可以更早、更频繁地交付真正重要的成果。

在接下来的章节中,我们将深入探讨交付循环中的“测量与学习”部分,并探讨促进测量和学习的不同机制和工具。

第十三章:13. 衡量与学习

通过遵循这一过程可以工程化创业成功,这意味着它可以被学习,这也意味着它可以被教授。 – 埃里克·里斯

在他的书《精益创业》中,埃里克·里斯(Eric Ries)将初创公司描述为一个旨在 在极度不确定的条件下创造新产品或服务的人类机构。他概述了一个应对这种不确定性的过程,在这个过程中围绕最小可行产品MVP)的创建形成了一个紧密的反馈循环。他认为,能够迅速反应、快速失败并利用数据驱动的度量方法有助于基于理性而非情感做出决策。这种从小实验中学习的能力可以看作是一种商业敏捷性——在不断变化的环境面前快速调整的能力。在精益术语中,这个反馈循环可以总结为构建、衡量、学习

这个过程中的文化和人文方面不容忽视。不确定性和人性往往是共生的。美国社会学家罗恩·韦斯特鲁姆(Ron Westrum)认为,具有更好“信息流”的组织运作更高效。他认为,一个良好的文化需要组织内各人之间的信任与合作,因此它反映了组织内部的协作与信任水平。

其次,较好的组织文化可以表明更高质量的决策。在具有这种文化的团队中,不仅有更好的信息可以用来做决策,而且如果决策错误,团队也更容易反转这些决策,因为团队更可能是开放和透明的,而不是封闭和僵化的。

那么,我们如何将这些想法应用到我们的交付过程中呢?当交付的时间表紧张,截止日期迫在眉睫时,团队交付和运营软件系统的能力对业务表现至关重要。

在软件领域,通常有两股相互竞争的力量在起作用。创新本质上伴随着系统的变化,而运行软件则是为最终客户提供服务,意味着系统必须保持稳定。在这里,我们可以识别出两个需要关注的重要领域:

  • 帮助衡量团队开发和交付实践的有效性

  • 开始衡量和监控能够快速诊断问题的活动

本章我们将探讨不同的机制和技术,从我们的交付环节中提取度量数据,并利用这些数据推动决策和下一步行动。我们将这种方法称为指标驱动的转型。我们还将学习什么是值得衡量的,以及如何获取这些度量数据并使其可见,最终帮助解答我们在转型过程中是否取得了任何进展的问题。

指标驱动的转型

基于度量的转型侧重于使用基于价值的业务指标来理解与技术相关的投资如何影响组织绩效,并提供特定的工具和指导,以帮助改善这些指标。

在上一章中,我们研究了不同的交付方法,无论是瀑布方法,还是使用敏捷框架如 Scrum 或 Kanban。随着我们完成交付循环,我们希望采取基于价值的度量来验证假设,确认实验结果,澄清特性交付的影响,确定我们是否朝着设定的目标结果前进,并根据结果决定下一步的行动。

在我们的交付生态系统中,我们可以采取许多不同层次的度量方法,并且可以从越来越多的来源收集这些数据。本章将探讨我们可以从软件和平台自动收集的指标,以及我们可以用来收集来自用户、客户、员工和更广泛组织的指标的实践。我们先从重新审视我们已经使用过的一些实践开始,看看我们如何利用这些实践收集度量数据和学习。

在哪里进行衡量和学习

第十章设定目标中,我们介绍了基于从发现循环中的实践中获得的所有学习来设定目标结果的做法。我们展示了如何使这些目标具有可衡量性,以及如何将其可视化为信息辐射器,以便每个人都能检查它们。通过使用度量,我们可以检查当前的量化状态,我们的过往轨迹,以及我们希望达到的每个可衡量结果。

我们解释了主要(以业务为中心)结果和支持性促进性结果之间的区别,后者是基于非功能性结果的。从那时起,我们围绕这些目标结果组织了所有工作,并将其辐射到其他实践成果中,包括交付循环中的选项轴和 Scrum 看板上的价值切片。这些结果应该是我们衡量的起点,重新可视化它们将帮助我们衡量我们在实现目标价值过程中取得的进展(或没有进展):

图 13.1:衡量目标

在衡量结果时,我们需要注意任何可能形成历史基线的先前测量,并且要考虑任何价值估算与实际价值之间的差异。思考这些不同形式的度量让我们提出了这样一个问题:我们应该在哪里和何时进行度量和学习,又应该在哪里检查这些度量?

展示

理想情况下,我们在交付项目完成后立即进行度量。这可能是在 Scrum 的一个冲刺结束时,或者是在 Kanban 中交付一个完成的特性后。或许我们可以将度量数据收集嵌入到应用程序代码中,并在展示会前或展示会期间运行报告。例如,如果我们的目标结果是将用户基数增加到 10,000 人,那么每个展示会都可以提供当前用户基数的更新,并展示其是否朝着目标的正确方向发展。

由于数据可用性存在延迟,可能无法在展示会活动中呈现。在这种情况下,有两种应对方案。首先,我们可以利用展示会本身收集一些度量和来自利益相关者的反馈。或者,我们也可以收集来自利益相关者和用户对最新产品增量的度量和反馈。但该如何进行呢?

或许我们的目标结果是围绕利益相关者的信心和/或用户在我们逐步构建的应用程序中的满意度。如果是这样,那在展示会后询问利益相关者他们对所见内容的信心或满意度,还有什么比这更好的机会呢?这可以是定量的,例如我们可以要求利益相关者在 1-10 的评分范围内对产品进行评分。也可以是定性的,通过围绕评分展开的对话以及收集反馈数据时的附加反馈。

或许我们的目标结果是围绕员工参与度和团队技能。再次强调,在交付循环的每次迭代结束时,调查团队成员的幸福感并让他们对不同的技能进行自我评分,又有什么比这更好的机会呢?这种可视化不仅可以让我们看到趋势,还能帮助我们识别由于团队教育和文化活动所产生的正负效应。

展示会活动提供了展示各种度量标准的机会:软件交付度量、平台度量和团队速度度量。我们稍后会深入探讨这些内容。如果这些内容对观众有吸引力并帮助他们理解团队所做工作的影响,那么一定要包括它们。然而,能够进行更深入对话的活动通常是紧随展示会之后的回顾性。

回顾性

在上一章中,我们介绍了回顾性实践并探讨了多种实施格式。接下来,让我们进一步考虑度量标准,并从工程师的角度看回顾性。

回顾性 – 工程师视角

回到 1980 年代末,我清晰地记得在化学工程课程中第一次接触到反馈回路控制理论,那时软件工程还没有完全占据我的职业生涯!如果你想控制油箱中的液位、管道中的流量,或者几乎任何动态变化的系统,你需要学习如何保持系统的稳定。软件工程中的回顾实践总是让我想起那些二阶反馈回路,它们是我们软件设计和开发过程中的物理连接,让我们能够学习和适应,从而使系统变得更加稳定。

回顾会议的共同目标是让团队检查刚刚发生的事情,同时让他们能够调整和改进未来的工作。这是一个至关重要的功能。如果没有反馈或者反馈没有得到处理,团队可能会开始对整个交付过程失去信心。在工程领域,如果反馈失败,油箱就会溢出!

没有什么比度量指标应当作为回顾会议中的热议话题更不令人意外了。讨论表现不佳的 SDO 或 CI/CD 度量指标的团队,可以深入了解软件交付过程中出了什么问题。因此,当构建开始需要 20 分钟才能完成时,应该庆祝。这意味着度量指标的测量和趋势已到位,团队现在可以采取行动找出为何变得如此缓慢,并寻求改进。

通过及时听取反馈,能够节省大量浪费的时间和资源。如果忽视反馈,可能会在接下来的一年里,花时间做补救项目,而不是推出新的创收功能。这个故事的寓意是:回顾,回顾,再回顾!你真的永远不会得到足够的反馈。

在我们创建技术基础(在第六章,开放技术实践 - 开始,正确启动,和第七章,开放技术实践 - 中期中提到)时,所有的工具和技术都已经到位,我们可以收集和进行大量的数据、度量和分析。如果你进行酒吧回顾(如前一章所介绍),没有什么比拿几份工具报告打印件,带到酒吧去,边喝着吉尼斯啤酒边分析它们更好的了!

图 13.2:酒吧回顾 – 讨论度量指标

导出你的构建统计数据、最新的测试结果、静态代码分析报告、燃尽图,以及你能找到的其他任何数据,把它们都摆在桌上,问问自己:这些数据告诉我们什么是我们之前不知道的?我们能学到什么?存在哪些模式?我们能在下一个迭代中做些什么来改进这些测量?

常见的回顾会议行动包括:更加关注 CI/CD 基础设施,提升代码覆盖率的阈值,并为团队增加更多或更快速的反馈循环,以便从中学习。

在回顾会议中检查构建统计

这是我最喜欢用来突出回顾会议(Retrospective)强大影响力的一个例子。我在 2008 年为一家英国电信公司提供访问控制管理解决方案时收集了这个图表。我的团队使用 Scrum 和持续交付方法,构建了一个基于 Java 的应用程序。他们使用 Hudson 进行持续集成,并且有大量的自动化测试,这些测试作为团队"完成定义"的一部分,逐步集成到应用程序中。

每两周导出几个构建图表,并带到团队的回顾会议中。下图展示了构建的持续时间以及构建的成功情况(红色表示构建失败,黄色表示构建成功但部分自动化测试失败,蓝色表示构建成功且所有自动化测试通过):

图 13.3:在回顾会议中检查构建时间统计(图表已加注标签,反映了数据所涉及的冲刺)

正如我们所见,在第 1 次冲刺(Sprint 1)中,构建非常不稳定。该图表的一个早期版本在第 1 次冲刺结束时被带到了酒吧回顾会上。团队进行了检查,并一致认为,在第 2 次冲刺中,他们将花一些时间调查构建稳定性问题。

两周后,第 2 次冲刺完成,ScrumMaster 带来了 Hudson 上打印出的这个图表。好消息是,第 1 次冲刺回顾的行动得到了回报,构建变得更加稳定。然而,团队注意到有时构建完成的时间超过了 15 分钟。这是一个比所需时间更长的反馈循环,因此在第 3 次冲刺中采取了进一步的回顾行动来解决这个问题。从第 3 次冲刺开始,我们可以看到构建大多数时候保持稳定并且相对快速。

想象一下,如果我们没有在回顾会议中查看这些数据。想象一下,如果我们让缓慢的构建问题继续拖延下去。想象一下,会浪费多少时间。这就是为什么我们要进行以指标为驱动的回顾会议。

当我们走出冲刺后,我们将有机会学习我们设计的实验结果。

实验——结果!

第十一章选项枢纽(Options Pivot)中,当我们介绍选项枢纽时,我们介绍了一些可以在实验中设计的高级部署策略。如果我们在设计实验时决定使用其中一种或多种策略,那么现在就是衡量和学习实际结果的时刻。

如果我们设计了 A/B 测试,我们会查看收集到的关于流量、互动、时间花费以及其他相关指标的数据,以便根据用户行为的变化来评估这两个版本的有效性。

如果我们设计了金丝雀发布,我们是否从参与金丝雀发布候选的用户行为中学到了经验,以验证是否应该将该功能推广到更广泛的用户群体?同样地,通过暗启动和功能切换,我们从使用中收集指标来评估是否应该将功能发布扩展到更大的用户群体,或者应该关闭并回滚暗启动。

从这些实验和相应的分析中获得的所有学习都是定量的。这意味着你可以研究数据,观察趋势,决定是否延伸实验,或者创建全新的实验。随着你运行更多实验并收集更多数据,你的决策能力变得更加强大,并且是基于从中收集的度量标准。

当然,并不是所有事情都能通过数字验证。通过与最终用户交谈获得进一步的学习和理解是必要的,这称为定性反馈。让我们探索几种帮助我们做到这一点的实践,从用户测试开始。

用户测试

基于用户的测试是一种专注于用户与产品互动的技术。这些评估类型直接涉及最终用户,并专注于个人。让我们通过查看两种用户测试实践来深入了解一下:可用性测试和灵活测试。

可用性测试

在可用性测试会议中,团队观察真实用户与产品的互动。通常,一个主持人与用户坐在一起,要求他们完成任务并在操作过程中解释他们的思考过程。团队则坐在另一个房间,通过视频链接观察测试过程。

可用性测试不是焦点小组;它专注于用户在真实世界中的想法和行为。如第八章“发现为什么和谁”,所介绍的同理地图,可以是一个非常有用的支持实践。

可用性测试可以运行在现有产品、原型甚至竞争对手的产品上。原型可以是工作代码,也可以是几个可点击的图像。早期和频繁地测试,以创建让用户喜爱并解决真实需求的产品。

可用性测试经常会突出一些对于产品开发人员来说显而易见但对用户可能会令人困惑的事情。我们认为用户需要的可能并不是他们实际需要的。实际上,用户认为他们需要的也许并不是他们实际需要的!可用性测试可以帮助回答诸如,“我们是否在正确的轨道上?”“我们仍然需要解决哪些问题?”或者“我们接下来应该开发哪些功能?”通过从真实用户获得早期反馈,团队可以避免浪费时间在一个令人困惑或没有用处的功能上。

“我们不是我们的用户”

我曾与一位非常出色的设计师共事,他曾告诉我:“我们不是我们的用户。”由于我在开发团队中花了相当多的时间在代码编辑器前,我认为我明白他的意思。但直到后来发生了一件事,我才真正理解他的意思。

我们正在为一个抵押贷款应用程序构建一个轻量级的原型。前端使用了一个现有的银行 API 来计算抵押贷款,我们正在构建的应用程序在提示用户输入收入估算之前,会先询问几个初步问题。用户最初填入的文本框中有一个占位符,内容是例如 100,000.00

在与产品负责人和用户体验设计师一起进行开发和测试时,我们一定填写了这个表单上百次!每次使用表单时,我们都是以完全相同的方式填写的,只是将 100000 填入那个框中。我们从来没有任何人往框中填过小数点!

几周后,我们开始进行一些真实世界的测试。当然,第一个使用我们应用程序的人填写了表单,把"120000.00"输入到框中并点击了提交。正如你可能想象的那样,我们原本预期一切都会顺利进行,用户反馈环节会继续进行;但实际上,应用程序崩溃了。

图 13.4:抵押贷款应用程序

进行可用性测试的业务分析师立即打电话告诉我们应用程序坏了。我们重新回放了用户的操作,结果发现银行的 API 无法接受小数点。但对我们来说,真正的惊讶是我们团队没有人注意到这个问题。作为一个小团队,我们迅速通过更新占位符文本并仅向 API 发送整数来解决了这个问题。

这个例子总是提醒我,我们不是我们的用户。用户在应用程序中会做出奇怪又神奇的事情。即使你试图测试所有的场景,几乎总会有一些你没有想到的边缘情况,而且很有可能第一个使用你应用程序的人会做出那个奇怪的事情!

openpracticelibrary.com/practice/usability-testing的开放实践库中,您可以深入了解可用性测试实践,分享自己的经验,提出问题,并进一步改进这一实践。

在组织可用性测试时,您可能遇到的一些挑战包括让实际客户访问遗留系统,或者没有足够的时间、资金或专家来进行这种程度的测试。游击测试提供了一种低成本的替代方案或补充形式的定性用户测试。

游击测试

游击测试是一种低成本、精益和敏捷的方法,用于在短时间内针对特定任务收集数据来测试和验证假设。参与者不会提前招募,而是由团队在多种环境中接触,目标是类似的人群,例如在咖啡馆的顾客,或在办公室环境中的管理员。

这种测试提供了一种简单的方法来收集足够的数据,以做出明智的战略设计决策。它还可以帮助高级利益相关者和产品团队理解可用性测试和客户反馈的重要性。团队中的每个人都可以在没有研究专家的情况下参与。它是一种灵活的方法,可以在产品开发的任何阶段实施。

在繁忙的都柏林银行进行游击测试,带着一盒甜甜圈!

进行任何可用性测试或从控制组生成反馈不必是一个涉及大量焦点小组的庞大昂贵的活动;它可以是一个简单的活动!在前一个示例中,我们在每个迭代后进行游击测试。这是一种产生真实世界反馈的惊人方式,而且几乎不花费什么。

我们会进入一家银行,每周选择不同的分行,分布在城市的不同区域,以获得不同的观点。我们带的东西非常简单——只有应用程序和一大盒甜甜圈!计划也很简单:我们会引诱人们来到我们的摊位,邀请他们参与一个简单的反馈环节,作为交换,我们提供免费的甜甜圈!用户会实际体验应用程序,并给我们一些宝贵的见解,帮助我们发现遗漏或未考虑到的地方。甜甜圈的盒子成本很低,但它为我们提供的反馈却是无价的。

了解更多关于游击测试的实践,分享你自己的经验,提问,进一步完善这个实践,可以访问openpracticelibrary.com/practice/guerilla-testing/

宠物对战可用性测试

通过观察变更的顺利交付和生产系统的整体稳定性,宠物对战团队希望避免当应用程序的业余版本迅速传播时出现的失败类型,同时快速向软件产品中引入新的修复和功能。

团队希望收集数据并测量的第一个领域是:将新特性投入测试环境所需的时间。通过自动化并减少将修复和特性投入可进行测试的环境所需的时间,将缩短反馈循环,使团队能够快速获得对其更改的反馈,而不是等待发布。PetBattle 团队不确定该测量哪些正确的指标,但他们认为从这个开始有助于发现更多可能有用的软件交付指标。

团队希望关注的第二个测量领域是一些简单的性能测试。在测试环境中,计划是同时加载 PetBattle 应用程序,观察其表现并尝试识别系统中的任何错误或瓶颈。提前进行检查希望能够揭示团队在应用程序的业余版中看到的任何问题。同样,团队还不确定该将哪些系统部分作为性能测试的重点,或者应该关注哪些指标,但将 PetBattle 套件部署到可以进行此类测试的环境中是第一步。

采取度量与学习的方法来进行展示、回顾、实验和用户测试,使我们进入一个持续循环:构建一些小的东西,找出衡量方法,收集数据并从中学习,这将激发更多的想法。

精益创业中闻名的构建、衡量、学习反馈循环,是平台、技术和文化工具赋予我们的一项最强大的工具和思维模式,这些工具贯穿了本书的内容:

图 13.5:精益 – 构建、衡量、学习

让我们更详细地探讨一下该测量什么。

测量什么?

你测量的就是你得到的。 – H. Thomas Johnson

在评估作为构建、衡量、学习反馈循环的一部分时,我们将站在巨人的肩膀上。我们可以利用大量关于 DevOps 指标的文献,并将在这里指出我们当前的最爱。榜单最上面的是 DevOps DORA 报告 1 和书籍《加速》2,这两部作品描述了科学家们如何通过数据驱动的方法来衡量 DevOps 文化和实践。在DORA报告中,卓有成效的 IT 交付组织从代码提交到主干(Git 中的代码)到“运行在生产环境中”大约需要一个小时。这听起来很棒!因此,让我们来看一下可以帮助我们集中精力实现这一目标的一些详细指标。

衡量服务交付和运营绩效(SDO)

对于任何团队来说,一个关键问题是,什么是好的表现?

根据《Accelerate》中的研究,领先的组织每天多次更新软件,而不是每几个月更新一次,这提高了他们利用软件探索市场、响应事件并比竞争对手更快发布功能的能力。然而,这种响应速度的巨大提升并没有以稳定性或质量为代价,因为失败被迅速发现并修复。

衡量软件交付性能是一项艰巨的任务。我们可能很容易认为基于输出的度量已经足够。一些人经常声称,度量可能看起来类似于以下内容:

  • 开发人员每天编写并提交的代码行数

  • 以团队利用率作为衡量团队生产力的标准

  • 团队的速度(或每个冲刺交付的故事/功能数量)

不幸的是,如果我们稍微深入分析一下这些内容,我们可以迅速发现所有这些度量的缺陷。

1 www.devops-research.com/research.html#reports

2 itrevolution.com/book/accelerate

是不是可以通过一段 100 行的代码写出比 200 行代码更好的解决方案?表面上看似如此,但哪种解决方案更容易随着时间推移进行维护呢?哪种方案更清晰且更容易让新开发者发现和理解?可能是 200 行的代码对于团队来说更易于维护和学习,而不是那段只有专家理解的精心编写的黑魔法代码。如果一个团队的资源 100%都用于功能开发,那么他们何时才能有时间进行学习、处理临时工作或管理技术债务?没有时间做这些活动最终会导致交付和创新的停滞。如果一个团队每个冲刺交付 10 个故事,这比每个冲刺交付 5 个故事的团队更好吗?当不同团队在处理无关的工作项时,我们真的能比较他们的输出吗?可能不能。我们的用户故事是否对业务和最终客户的价值和大小相同?跨不同产品和所有权的团队之间很难做出这样的判断。

这些度量标准我们称之为基于输出的度量。我们可以将重点从团队产生的内容转移到目标或目标结果上。通过专注于整体结果,团队成员不会因为度量错误的东西而互相竞争。一个经典的例子是奖励开发团队成员推动新特性上线的速度,而奖励运维团队成员的服务稳定性。这些度量标准鼓励开发人员尽可能快速地将低质量代码投入生产,而运维人员则通过设置繁琐的变更管理流程来阻碍变更。

如果不专注于测量或测量正确的内容,而是专注于输出而非结果,可能很快就会陷入困境。幸运的是,DORA 报告开始列出团队可以用来评估 SDO 的一些关键指标:

图 13.6:DORA – 性能指标

软件开发指标是衡量开发和交付过程有效性的指标,通过以下方式进行测量:

  • 前置时间:从代码提交到发布到生产环境的时间

  • 部署频率:团队将代码发布到生产环境的频率

前置时间是精益理论的一个关键元素:通过缩短将产品特性交付给最终用户的时间,团队可以缩短反馈循环,了解最终用户的喜好与不喜欢。如果团队构建了错误的东西,当前置时间较短时,他们可以迅速调整方向(或转变)以纠正问题。

另一个因素是交付工作的大小,即批量大小。通过快速向最终用户交付小而增量的价值,可以保持较低的前置时间。随着交付节奏的加快,如何确保系统稳定性不受影响?

软件部署指标是衡量系统稳定性和发布质量的指标。它们通过以下方式进行测量:

  • 恢复时间:从检测到影响用户的事件到修复或解决的时间

  • 变更失败率:已发布的变更中失败或导致用户影响事件的数量

生产环境中有多少变更失败?发生故障时,恢复服务到最终用户的时间是多久?如果发生变更时故障能迅速修复,开发团队的客户会更满意,理想情况下变更应该完全没有失败!

服务操作指标通过称为服务可用性的指标捕捉操作性能。假设你的产品销售网站崩溃了,公司会计可能会问,我们的应用现在有产生收入吗? 测量服务可用性是将技术目标与期望的业务成果联系起来的好方法。

目前为止定义的指标在帮助团队理解关键的软件、交付和操作指标方面发挥了重要作用,这些指标有助于更好的组织结果。为了帮助捕捉和传播这些指标,Red Hat 投资了一个开源项目,开发出了一个名为 Pelorus 的仪表板工具 3。

3 github.com/konveyor/pelorus/

Pelorus

Pelorus 是一个执行仪表板,帮助可视化我们在 SDO 成功指标上的进展。它利用了 Prometheus 和 Grafana 等开源工具来跟踪进展,既可以在团队内部(局部)跟踪,也可以在组织内(全球)跟踪:

图 13.7:Pelorus – SDO 仪表板和指标

Pelorus 由一组导出器组成,可以自定义数据点,从不同的提供者收集指标。随着更多人参与,导出器自动收集指标的来源在不断增加。目前包括 OpenShift(作为部署时间导出器)、Git 提供商 GitHub、GitLab 和 Bitbucket(作为提交时间导出器),以及 JIRA 和 ServiceNow(作为已建立的问题追踪器)。

使用从提供者收集的数据点,计算指标来表示一个衡量标准。每个结果通过一组代表性指标来衡量:变更周期时间部署频率恢复时间平均值变更失败率

Pelorus 为团队提供了一个绝佳的机会,可以在团队旁边展示这些重要指标的实时信息。团队还可以定期在展示会和回顾会议上检查和讨论这些指标,并问自己可以采取哪些改进措施或实验来进一步提升这些指标。我们还应该衡量什么?

测量精益指标

精益运动还提出了一些指标,帮助衡量软件交付绩效。最简单的形式下,你只需要每个工作项或故事的两个信息:开始日期和完成日期。这听起来很容易衡量!但当然,你需要一些关于这些定义的政策。通过这两个度量,我们可以开始衡量许多不同的事情,特别是:

  • 过程中的时间:每单位工作所需的时间。

  • 交付周期时间:从请求功能到其交付到生产环境之间的时间。

  • 流动效率(或触点时间/交付周期时间):产品实际被团队工作并且增加价值的时间。

  • 截止日期绩效:功能按时交付的频率。

这些指标大多数是性能的滞后指标。换句话说,它们衡量的是已经发生的事情。也可以制定前瞻指标,利用这些数据帮助预测未来的绩效。其中一个例子是基于工作项或故事进出团队的流动情况。工作项的净流动量可以预测截止日期交付的信心。当团队接受越来越多的工作时,这会减慢他们按时交付项的能力。因此,团队内外工作项的净流动量成为衡量工作项交付绩效的前瞻性指标。

启动和结束日期能够告诉我们很多信息。随着时间的推移,项目的分布也有助于对团队所承担的工作类型进行分类。例如,正常工作项可能与因生产故障而转到团队的优先工作项不同。另一个例子可能是需要长时间等待安全或合规团队批准的工作。这些工作项相比于不需要此类批准的正常工作项,通常会有更长的前置时间。

衡量 SLO、SLA 和 SLI

这些服务级别SL)指标的目标是让客户、供应商和用户在系统行为和性能方面达成一致。特别是,每个人都需要知道并同意一些共同的 SL 问题,例如以下问题:

  • 系统将可用多久,多久可用一次?

  • 如果发生故障,恢复服务的响应速度会有多快?

  • 如果我们发出单个请求,系统的响应速度会有多快?

  • 如果我们发出许多并发请求怎么办?

  • 服务的用户希望知道这些问题的答案,以便他们能够规划和设计如何使用任何给定的服务。例如,如果您正在构建一个必须始终可用的解决方案,并且它依赖于一个仅在工作时间可用的第三方服务,您可能需要不同的设计,或者实现一个缓存解决方案,或使用另一个具有更高可用性的服务。

SL 缩写可以大致定义如下:

  • SLA:通常是您组织与客户、供应商和用户之间签订的正式合同。

  • SLO:SL 目标是您的团队必须交付的结果,以满足协议。

  • SLI:SL 指标是用来衡量团队表现的实际指标数值。

正确衡量 SLA 可能非常困难。例如,服务可能可用,但性能降级。若只有部分用户经历部分中断,情况也会变得更复杂。捕捉这种 SLA 复杂性并准确衡量它是非常困难的。

SLAs 的一个好处是,它们可以让 IT 经理定量地衡量业务结果。因此,他们不需要处理诸如“我的应用程序无法加载,速度非常慢”等通用的定性投诉,而是可以衡量应用程序的可用性(正常运行时间)和百分位页面加载速度。

SLO 是关于在特定 SLA 中的一个指标的协议。一个简单的例子是,我们同意将搜索结果快速返回给最终用户,平均搜索延迟为 200 毫秒。相反,SLI 通常衡量 SLO。因此,我们可能会定义一个搜索目标的上限,例如规定 99%的搜索延迟必须小于 300 毫秒。

通过定量地指定和发布我们服务的 SLA、SLI 和 SLO,最终用户可以设定对服务性能的期望。这可以避免用户因服务缓慢而提出的定性抱怨,或者过度依赖某项服务,而用户期望它的可用性超出了实际情况。

PetBattle 服务级别

当前的 PetBattle 爱好者应用没有 SLA——零,完全没有。PetBattle V2 将被构建为高可用性系统。考虑到该应用的受欢迎程度以及它是我们初创公司唯一的潜在收入来源,它被指定为“关键任务”应用。冗余将从各个层面进行构建:云基础设施层(网络、存储和计算),以及应用层(可扩展的冗余高可用服务)。PetBattle V2 将重新设计为“无共享架构”,以便最大化水平扩展而不会出现瓶颈。PetBattle V2 将要求满足 99.99%的可用性 SLA。这意味着每月允许的停机时间不超过 4 分钟!

任何外部系统如果有较低的可用性 SLA,在失败时都会对 PetBattle V2 的 SLA 产生负面影响。PetBattle 团队编写代码并运行服务,理想情况下,在开发和交付环节中,外部服务提供商应尽可能少。任何纳入应用程序的新服务都必须设计以满足这些 SLA。

通过定量地指定和发布我们服务的 SLA、SLI 和 SLO,最终用户可以设定对服务性能的期望。这可以避免用户因服务缓慢而提出的基于指标的定性抱怨,或者过度依赖某项服务,而用户期望它的可用性超出了实际情况。

测量安全性

恶意用户随时可能出现。我们如何确保保存在应用程序中的用户数据不会被滥用?或者如何避免我们的应用服务被用于非预期的用途,从而对组织或声誉造成损害?如今,数据泄露和与软件应用程序及服务相关的安全漏洞在媒体上屡见不鲜。回答这些问题通常是信息安全InfoSec)分析师或团队的主要关注点。应对这些安全问题的现代方法被称为将安全性左移。这一术语与将信息安全整合进软件交付流程的团队相关,而不是将其作为一个独立的阶段,发生在开发过程的下游。

将安全性集成到软件开发中,不仅可以提高交付性能,还能提高安全质量。通过在系统中设计和实现安全性及合规性指标,可以测量对安全标准的持续合规性:

图 13.8:基于标准的系统合规性

PetBattle 安全性

PetBattle 团队已要求创始人聘请一位信息安全专业人员,参与开发过程。他们担心 PetBattle V2 中用户详细信息的潜在数据泄露,特别是当我们开始为站点盈利时,如果需要开始收集支付详情。PetBattle 团队已教育他们的开发人员常见的安全风险,如 OWASP Top 10 及其预防方法。

团队计划将安全扫描和测试度量整合到他们的构建流水线中。通过使用 OpenShift 平台和受信任的软件供应链,团队可以显著减少修复可能出现的安全问题所花费的时间。

团队已经确定,每个人都需要非常熟悉整体解决方案的详细网络设计。这应该有助于避免恶意攻击者的攻击。团队还希望确保所有平台和应用程序都易于修补——这样他们就可以在常见漏洞与暴露CVE)4 出现时,轻松地保持框架的更新。

4 cve.mitre.org

拥有一个安全的系统意味着确保栈中的所有层都本身是安全的——包括确保硬件环境安全、操作系统安全、所使用的容器镜像层安全、应用程序所依赖的组件安全、应用程序代码安全、暴露应用服务的网络安全,最终确保最终用户能够安全地与应用程序进行交互。

衡量这些安全层,确保它们符合各种行业标准,并为新漏洞的出现制定具体行动计划,要求团队在软件交付生命周期的每个阶段都紧密合作。安全不应只是首席信息安全官CISO)的工作。做得对的话,安全是无处不在的,并被设计进平台和软件系统中,安全专业人员应积极参与作为核心交付团队的一部分,而不仅仅是在渗透测试时充当“拒绝者”5。

安全是一个需要独立成书的话题。通过将安全向左移动,我们将在本书后续部分涉及技术话题,包括容器镜像和漏洞扫描、容器健康指数、CVE 补丁、OpenShift 合规性操作员、OpenShift 容器安全操作员以及通过 ACM 执行安全策略。这些工具可以帮助你为自己构建和补充一个持续合规的平台和应用程序套件。

性能衡量

软件开发人员常用一句格言——先让它工作,再让它工作得更快。这句话的真理在于,某些功能必须先存在,才能做得更快!性能和安全性等特性通常是我们应用程序最重要的非功能性需求,必须从一开始就设计到系统中,才能确保成功。让我们来定义一下什么是性能。

性能 衡量系统处理单个事务的速度。这可以在孤立状态下或者负载下进行测量。系统的性能对其吞吐量有重大影响。当最终用户谈论性能时,他们通常指的是吞吐量;他们只关心自己事务的性能,而不关心其他人的事务。对他们来说,如果系统的响应时间超过了他们的预期,那么系统就算是崩溃了。

吞吐量 描述了系统在给定时间内能够处理的事务数量。系统的性能显然会影响其吞吐量,但不一定是线性关系。吞吐量总是受到系统中某个约束的限制,这就是所谓的瓶颈。试图优化或提高系统中非瓶颈部分的性能并不会增加吞吐量。

5 Mr. No 是一本仅在法国发行的 Mr. Men 系列书籍。Mr. No 总是与每个人和一切事物意见相左。Mr. No 在法国出版时的标题是 Monsieur Non。这是两本未以英文出版的 Mr. Men 书籍之一。

如果我们衡量最终用户请求的数量,吞吐量将根据请求的多少而变化。这是一个衡量可扩展性的标准。当一个系统具有水平可扩展性时,意味着我们可以通过增加容量(更多的服务器、更多的 pods 或容器)来处理更多的吞吐量。在无共享架构中,我们可以增加容量,直到达到已知的瓶颈。例如,在 OpenShift 中,这可能是每个节点的 pods 数量,或者每个集群的最大节点数量。

还值得注意的是,对于任何给定的系统,“可接受的响应时间”可能是不同的!对于移动/网页应用程序来说,任何超过一两秒的响应时间都会让用户用手指离开,去浏览其他网站或应用。对于银行的交易系统来说,响应时间可能在毫秒级别甚至更短。要了解一个系统所需的容量,我们需要先全面了解系统,再将其拆解成各个部分。这被称为系统思维。通过从整体到细节地思考一个系统,我们可以找出系统中的瓶颈。

在任何时刻,系统容量都由一个约束因素决定。假设是数据库限制了事务吞吐量。一旦我们改善了这个瓶颈——例如,通过使用更快的存储、添加索引或使用更好的数据库技术——系统中的下一个瓶颈就会成为性能限制因素——例如,应用服务器的容量现在限制了吞吐量。

《宠物对战》性能

当《宠物对战》发布到全世界时,用户们会联手对抗我们。有时,用户群体会非常庞大。试想一下,Instagram 的编辑们在指着《宠物对战》网站时咯咯笑着说:“放出猫咪大军!”大规模的用户群体可能会触发应用程序的卡顿、死锁和难以察觉的竞态条件。

《宠物对战》团队希望运行特殊的压力测试,重点测试 UI 和 API 层中的深度链接或热点 URL。目前,用户和数据库之间有着直接的耦合,因此开发人员已经知道,在进行扩展时,可能需要某种形式的缓存。

如果我们将监控作为优先事项,就可以优化我们的基础设施和应用监控系统,确保收集到关于正确服务的信息,并有效利用这些信息。有效监控带来的可见性和透明度是无价的。主动监控是强大技术基础的关键组成部分。

团队计划将《宠物对战》应用套件部署到一个环境中,在负载下开始对系统的部分进行性能测试。他们希望从简单的目标开始,针对系统的各个部分进行测试,例如 API,以了解系统在负载下的行为。通过早期且频繁地进行此类测试,他们可以发现瓶颈,并从中制定解决方案。

就像安全性一样,将性能测试提前放到构建和部署过程中,可以提供更快的反馈,并有机会在开发生命周期的早期发现并修复问题。对于整个应用套件的复杂系统测试,通常要等到开发接近尾声时才能进行;然而,测试单独的 API、端点,甚至 UI 的部分可以通过自动化的方式频繁且尽早地进行。进行这种自动化性能测试的主要好处之一是能够建立一个关于系统性能的基线理解。如果没有进行这种自动化测试,任何改变系统性能特征的代码更改往往会被忽视,而你最终可能会在准备上线前才去应对性能问题。

测量部署的痛点

如果你曾经需要在夜班或周末工作,帮助将软件产品的大规模发布部署到生产环境中,你就会理解在上线前工程师和技术人员所感受到的焦虑和担忧。在文献中,这种现象被称为部署痛苦,它能够突出开发与测试任务和发布与运维任务之间的脱节。

从根本上讲,大多数部署问题都源于一个复杂且脆弱的部署过程,其中发生了以下情况:

  • 软件通常在编写时并没有考虑到可部署性。

  • 部署到生产环境需要手动操作步骤。

  • 部署阶段涉及多个交接环节。

中断是所有系统中的常态,OpenShift 也不例外。有许多 Kubernetes 原语可以帮助我们的业务服务保持运行并保持可用(包括副本控制器、滚动部署、健康检查、Pod 中断预算、水平 Pod 自动扩展器和集群自动扩展器)。但即使拥有最好的基础设施,故障仍然可能发生。云服务中断、硬件故障、资源耗尽和配置错误仍然可能威胁到业务服务。

测量文化

指标与转型之间是有联系的。测量有助于指导所采用的实践。例如,通过收集 CI/CD 指标(如 PetBattle 应用程序的构建和部署时间),团队可以采用并发展出简化的 CI/CD 实践,从而实现更快、更频繁的发布,并且确信失败将是罕见的。随着团队逐渐习惯于定期将软件发布到生产环境,业务也开始相信发布可以以低风险对最终用户进行。这样就能加速新功能的发布,缩短反馈周期,并最终培养出一种鼓励快速变化和创新的文化。现在我们已经真正开始为公司解锁转型性的变革:

图 13.9:指标与转型之间的联系

运营指标通常衡量非常复杂的自适应系统中的失败。当出现故障时,权威式的责备文化会试图寻找“人为错误”并归咎于此。这种对失败的反应不仅不好,还应该被视为有害的。

任何故障分析的目标都应该是发现我们如何改善信息流动,让人们拥有更好、更及时的信息,或者找到更好的工具来帮助防止在看似正常的操作后发生灾难性故障。

测量应用程序指标

到目前为止收集的所有指标都集中在非功能性参数上。那么,关于业务应用程序呢?我们能否获取一些关于应用程序使用情况的实时指标,并将其与这些其他指标一起展示?

PetBattle 应用程序指标

应用程序开发人员发现,通过利用 PetBattle API 服务器中的 MicroProfile 指标扩展,生成自定义应用程序指标是多么容易。创始人想知道实时上传了多少只猫。开发人员将指标扩展添加到他们的应用程序中,该扩展提供了默认的 Java 虚拟机(VM)指标,如堆、CPU 和线程。它还允许团队暴露来自应用程序的自定义指标。例如,通过在代码中添加 Java 注解,他们可以轻松地测量上传猫的频率。

现在可以使用 Prometheus 和 Grafana 抓取这些指标并在仪表盘上显示:

图 13.10:使用 Prometheus 和 Grafana 分析 PetBattle 应用程序的指标

我们可以监控内部指标,也可以监控外部指标。可以设置一个外部虚拟用户(通常称为合成用户)定期监控 PetBattle 应用程序。这个合成客户端体验与真实用户相同的系统视图。当该客户端无法处理一个虚拟交易时,例如尝试将其虚拟猫进入锦标赛时,无论内部监控是否显示问题,这都是一个问题!

团队认为有许多可能的稳定性指标,他们可以轻松地测量并在超出某个阈值时发出警报。慢响应通常会从一层传播到另一层,形成一种渐进式的级联故障。当网站变慢时,用户往往会更频繁地点击刷新按钮,导致流量越来越大。如果我们赋予系统监控自身性能的能力(换句话说,它变得可观察),那么系统也能告诉团队它何时未能满足其服务级别协议(SL 协议)。以下是一些示例:

  • 用户数量和活跃会话

  • API 层的线程阻塞

  • API 或数据库层的内存不足事件

  • 用户界面中的慢响应

  • 高数据库 CPU

衡量基础设施平台的成本和利用率

OpenShift 带有一个成本管理和计量应用程序,可用于显示基础设施使用情况。您可以在 OpenShift 中部署成本管理指标操作器,报告和 API 作为 SaaS 解决方案的一部分提供,网址为cloud.redhat.com

它允许 PetBattle 团队执行以下操作:

  • 可视化、理解和分析资源和成本的使用情况。

  • 预测他们未来的消耗并与预算进行比较。

  • 优化资源和消耗。

  • 识别需要调查的使用模式。

  • 与可以从成本和资源数据中受益的第三方工具集成。

有许多可用的可视化和仪表板。图 13.11展示了一个大规模演示环境中的概览仪表板作为示例。可以在基础设施和业务层面跟踪成本和使用情况。用户可以标记项目和应用程序,以获得详细的拆分数据和历史趋势。这些仪表板可以帮助回答常见问题,例如:

  • 显示按使用情况和成本排序的顶部项目和顶部集群:

    • 哪些项目的成本最高?

    • 哪些集群的成本最高?

  • 显示度量标准如何影响成本:

    • 成本的驱动因素是什么?CPU、内存、存储?

    • 下一个日历月的预测成本是多少?

可以在累计成本和日成本之间切换,并且可以跨集群、云和项目进行筛选和深入分析:

图 13.11:cloud.redhat.com 上的成本管理概览仪表板

您可以查看成本管理产品文档,6,其中有关于此服务的更多细节,包括跨混合云的常见配置选项。

6 access.redhat.com/documentation/zh-cn/cost_management_service/2021

资源和服务的测量

有许多简单的高级检查应适用于我们所有的资源和服务。USE方法是一个简单的检查清单,可以总结为对于每个资源,监控以下项目

  • 利用率:资源忙碌的时间百分比。

  • 饱和度:资源执行的额外或超负荷的工作量,通常是队列长度或类似的指标。

  • 错误:发生的错误事件的数量。

应该调查错误,因为它们可能会影响性能,并且在失败模式可恢复时可能不会立即被发现。这包括失败并重试的操作,以及从冗余设备池中故障的设备:

图 13.12:USE 度量标准

我们可以将 USE 度量标准映射到常见资源,并以表格形式展示,如图 13.12所示,便于快速识别系统中发生的问题类型。OpenShift 度量堆栈支持在集群和节点级别的预配置 USE 方法仪表板:

图 13.13:OpenShift 监控 USE 仪表板

类似地,RED方法可以总结为对于每个服务,监控请求

  • 速率:每秒请求数

  • 错误:失败的请求数量

  • 持续时间:请求完成所需的时间

RED 方法是一个很好的基准,可以应用于大多数基于请求的服务。它减少了 SRE 的认知负担;换句话说,他们可以用相同的方式思考大量支持的服务的基准指标。RED 方法在批处理导向或流式服务中会出现问题。在《Google SRE 手册》中,原始的“四个黄金信号”包括了 RED 和饱和度指标。

用户体验分析

宠物战斗用户界面中蕴藏着大量的用户分析数据。最明显的分析涉及非功能性性能度量,例如页面加载时间和响应延迟。希望通过测量并基准化基本的用户界面性能,团队能够防止应用服务被大量客户涌入所冲击!

标准做法是使用直方图(百分位数)来进行测量,这可以更好地理解异常值的表现。可靠地测量和聚合来自多个来源的高速度指标的分位数/百分位数可不是一项简单的任务。

宠物战斗用户体验分析

鉴于将宠物战斗货币化的主要目标,加入提供网络分析的全球企业之一,帮助向最终用户销售定向广告,可能比从头开始构建定制的分析和广告解决方案更有效地利用资源。分析服务可以快速提供页面加载时间、点击率、广告、站点搜索优化和建议,并帮助识别增销机会——当然是收费的。

作为测试最终用户体验的一部分,对用户界面进行自动化负载测试可以为团队提供一个关于运营宠物战斗 V2 站点的预览。

我们在这一部分展示了我们可以使用指标执行的多种不同层次的定量分析。现在,让我们看看这些如何转化为我们关注的结果。

可视化可测量的结果

现在我们有很多可以测量的东西。我们如何判断自己是否推动了变化,并对现状产生了可察觉的影响?通常情况下,关键的指标和数据并不是所有人都能看到的;它们隐藏在计算机上的登录页面后面。

为了解决这个隐藏数据的问题,我们可以利用更多的信息辐射器,将所有信息展示在团队工作区域的墙壁上,使用仪表板和大屏幕,以便直观地展示交付工作各个方面。我们可以分享对团队成员、利益相关者和用户都很有用的各种信息。它可以被物理地展示在墙壁、窗户、门和其他平面表面上,并且放置在那些能够从中获得价值的人眼前。

使信息更加易于获取会带来一些有趣的后果。这些后果包括团队合作的速度和准确性的提高。因为信息现在是随时可见的,人们经常会被提醒。并且,如果信息始终显示出来,就不需要浪费时间去寻找重要信息。由于人们不断地被提醒,信息的准确性也更有可能得到保证,如果信息不准确,就会有人提出问题!

当不属于核心团队的人进入团队空间时,墙面上展示的信息可以立即被读取。利益相关者和对团队工作感兴趣的人可以立即更好地理解和意识到正在进行的工作。这一活动通常被称为走墙。相关方可以检查墙上的资料,并与团队成员就这些资料进行交流。这与信息隐藏在系统背后,需要登录才能查看的情况截然不同。

主动通知

那么,当事情开始出问题时,会发生什么呢?当系统本身无法通过自动重启 Pod 或扩展集群节点来解决问题时,会发生什么?这就进入了警报的领域。

警报可以有多种形式。它可能是凌晨 03:00 时出现的那个可怕的短信,表明事情已经严重出问题,或者是一个更为微妙的警报,比如 Slack 消息提示某项更新成功。这里的关键是信息是被推送的,而不是被拉取的。OpenShift 内置了警报功能,我们将在第十六章中更详细地探讨这个问题,掌控它

经典的警报形式之一是当应用程序的内存使用量出现激增时。这可能导致应用程序失败或不断重启。在这些情况下,团队可能会在仪表板上看到内存激增的情况,并开始调查问题。当然,我们可以通过将来自不同来源的数据结合起来并基于这些数据发出警报,从而使这个反馈循环变得更加迅速。例如,如果我们的应用程序内存激增,我们可以捕获那个时刻的日志,并将这两个事件推送给团队,这可能有助于更快地诊断问题。智能通知的真正优势在于能够对事件作出更加迅速的反应。

向开发团队发送关于故障的警报非常重要。通知可以来自系统的各个层面;它不仅仅是深夜接到的那个可怕的电话,告诉你网站宕机了!每当有任务运行来构建我们的代码或部署新版本的应用程序时,向团队的即时通讯软件发送一个快速的通知是通知相关利益方的一个好方法。如果信息是及时的,我们可以更有效地作出反应。这可能意味着拉动安东绳并停止生产线,以便我们集合起来解决问题。

改变客户

我们日常依赖的软件有时会出现停机。及时通知开发人员和 SRE 关键故障信息对于解决问题至关重要。透明地通知客户系统故障以及解决路径,能够让你在众多公司中脱颖而出。许多公司都会为他们的软件提供状态页面,让用户了解服务是否正常。例如,Quay 容器仓库的状态页面 7 为你提供了服务哪些部分仍然正常运行的情况。这是一个很好的信息展示平台!一些全球顶级的软件公司不仅会发布停机状态,还会几乎实时地发布他们为解决问题所采取的步骤。像 GitHub 和 GitLab 这样的公司会通过 Twitter 发布消息,或者开设 Google Doc 发布更新,尽可能为用户提供接近实时的更新。这种透明度非常值得称赞,尤其是当他们遇到的问题可以让其他使用相同技术的人深入讨论时。这让大家可以变得更加主动,在下次重大升级时避免犯同样的错误!

7 status.quay.io/

警报只有在及时触发时才有用。如果警报在条件发生后才触发,且没人能及时采取措施,那就没有任何意义。设计警报阈值时往往需要在平衡中找到一个点,以确保负责产品或系统的人可以主动采取行动,而不是一到达到某个限制就频繁地“狼来了”似的发送警报。

玩转通知和构建!

作为一个自认懒惰的开发者,我知道当我推送代码时,我很可能不会去检查自动构建的结果。我知道这是自大,但请听我解释。通常,我的想法是,它在我本地没问题,构建过程中还能出什么问题呢?

事实上,很多时候,事情确实会出错,比如我忘记连接代码,或者在提交前忘记添加文件。事情就是会出错。这也是为什么无论我在哪里工作,我都喜欢在 Jenkins 上查看大仪表盘,就像图 13.14所示,或者监控器,这样每个人都能看到构建状态。

它们是反馈环路中如此重要的一个元素。我看到这样一个仪表盘时,能立刻看到一些关键信息,这些信息能让我更轻松地完成工作,比如当前构建的步骤、是谁触发了构建、或者有多少个测试失败了!这些是简单的信息,但作为一个懒开发者,我几乎无法忽视那些闪烁着红色警告的东西!

我喜欢任何可以帮助缩短反馈周期的东西。我特别喜欢一个与 Jenkins 仪表盘配合良好的插件,叫做 Build Fail Analyzer。它会解析构建日志中的特定正则表达式,如果找到匹配项,它可以显示一条消息。在项目中,这张截图来自一个团队,他们已经养成了试图将失败问题规范化的习惯,因此当检测到问题时,它可以提示我们解决,而不需要翻阅日志。

在这个仪表盘上,在左中侧,我可以看到 dev-portal-fe-e2e-tests 测试套件失败,问题是 selenium 没有启动。有了这个信息,我不需要打开 Jenkins 去查看日志发生了什么 —— 我可以直接去 OpenShift 查看为什么 pod 没有启动:

图 13.14:Jenkins 中的 Build Fail Analyzer

构建失败有时被视为一种诅咒状态。我们尝试鼓励无责文化,但有时玩玩“责任游戏”也是一种乐趣。我肯定是这个文化的粉丝!每当你构建失败,你就会被认为是牛仔程序员,那个人骑进小镇,肆意开枪,根本不顾后果。或者你可能只是代码里留了个拼写错误!

无论如何,如果仪表盘变红,那么你就得像个牛仔一样打扮。这支团队更进一步:不仅你需要戴上粉色的牛仔帽,直到问题解决,还得骑上木制的 hobby horse!即使是去洗手间或者喝咖啡,马和帽子也得随你一起去!你会惊讶地发现,穿着这种装备走到餐厅时,你会收到多少好奇的目光:

图 13.15:牛仔程序员

更多傻乎乎的例子来自我几年前在一个项目中参与的工作。那时还没有使用容器平台,我们有几台手动配置的虚拟机,它们在我们发布软件时至关重要。这是一个庞大的项目,全球分布了七个 Scrum 团队。我们正在构建一个包含 50 个产品微服务的套件,因此我们的构建农场总是忙个不停!

有一次,我们不得不让管理我们基础设施的 IT 公司回滚到之前的某个备份,因为有人以 root 身份执行了大量命令,导致 Jenkins 发生了几项故障。即使是提交 sev1 故障单,也花了几天才解决!我们不能让这种事情再发生,因为它严重影响了团队的士气和生产力。因此,我们决定动脑筋想个办法。我们使用 Slack 作为消息客户端,知道你可以通过 webhook 向频道发送消息。我们还知道,如果有人以 root 用户登录机器,我们可以执行 bash 脚本。结合这些,我们在 Slack 实例中创建了 Kenny Loggins 频道...因为当你以 root 身份登录服务器时,你就进入了 危险地带

图 13.16:Kenny Loggins 频道

本节展示了我们可以通过多种方式来可视化结果,并利用衡量标准触发主动通知,以加快学习过程。让我们看看如何将这一切与我们在交付环中学到的其他内容进行总结。

创建交付地图

我们通过一张发现地图结束了第三部分发现它,这张地图是一个信息展示板,概述了发现环的迭代过程。我们通过一张选项地图结束了第四部分优先排序它,该地图总结了我们希望验证的想法和假设,如何交付这些选项,以及我们计划首先处理哪些选项。

我们将通过一张交付地图结束第五部分交付它。这是一个开放源代码的工件,可以在莫比乌斯工具包中找到,您可以使用它来总结您在交付环中学习到的所有内容和做出的决策。

这张地图应该与您的发现地图紧密结合,并用于总结以下内容:

  • 行动:我们本周可以做些什么来改善结果?

  • 进行中:我们当前正在进行的工作是什么?

  • 完成:什么准备好进行审查?

  • 影响:我们在实现结果方面取得了什么进展?

  • 学习:我们学到了什么?

  • 洞察:我们的下一步是什么?

当我们离开交付环并返回第七部分改进它,保持它中的选项枢纽时,我们将通过询问我们的下一步是什么?来完成这张地图的最后部分。

现在,让我们看看 PetBattle 在交付环第一次迭代结束时的交付地图。

PetBattle – 交付地图

图 13.17包含了许多细节,可能在打印时无法完全读取。要查看完整内容,您可以访问本书在 GitHub 上的仓库:github.com/PacktPublishing/DevOps-Culture-and-Practice-with-OpenShift

图 13.17:PetBattle 交付地图

交付地图提供了一个强有力的总结,概述了我们在交付环中经历的整个旅程。像所有其他工件一样,它是一个动态的、不断变化的总结,应定期回顾并在每次后续迭代后更新。

结论

我们现在已经完成了莫比乌斯环的旅程。在本章中,我们重点关注了从我们发布的功能、运行的实验和进行的研究中可以获得的衡量标准和学习成果。持续关注衡量标准使我们能够做出更具实质性的决策,这些决策有数据支持的证据。

Scrum 和其他敏捷团队经常举办的展示和回顾活动提供了充分的机会来展示指标并强调学习成果。我们借此机会重新审视我们在选项枢轴(Options Pivot)上的实验,并调查实际发生了什么。这通常涉及查看平台提供的高级部署功能所提供的分析数据——A/B 测试结果、金丝雀发布、功能切换和暗发布的结果。

我们还强调了与整个团队一起进行可用性测试的重要性,同时与最终用户直接连接,培养更多的共情,并观察他们测试不断发展的应用程序。游击测试也提供了一种低成本且简单的方式来从用户那里获取反馈和学习:

图 13.18:交付环

我们探索了平台、软件和我们的团队提供的许多不同指标。由 DORA 和 Accelerate 推广的服务交付和运营绩效指标,以及像 Pelorus 这样的开源工具提供的指标,能够为 DevOps 文化和实践的成功提供领先指标。这些指标可以通过进一步收集关于安全、性能、文化、应用程序本身以及基础设施的度量来支持。实时、公开和透明地展示这些指标的重要性不容小觑,同时,采取行动以便对指标的变化做出反应并迅速调整行为和实践,也同样至关重要。

当我们结束第五部分交付它时,我们可以看到许多实践使我们能够在文化和技术基础上顺利地航行于莫比乌斯环:

图 13.19:映射到莫比乌斯环的实践

尽管我们已经完成了莫比乌斯环的一个循环,但我们并未完成这段旅程。我们永远不会完成这段旅程,直到整个产品关闭并退役。这是因为莫比乌斯环是无限的,永无止境。当我们从交付环回到选项枢轴时,我们将在第十七章改进它中探讨我们绕环旅行的洞察,并询问我们学到了什么,接着探讨下一步要做什么。

在那之前,我们将花费几章时间深入探讨技术解决方案。在本章中,我们已经开始了这一过程。在第十四章构建它中,我们将探讨我们如何构建解决方案的其他方面。在第十五章运行它中,我们将重点关注如何运行解决方案。在第十六章拥有它中,我们将探讨什么意味着拥有解决方案。这三章构成了我们书籍的第六部分,它们讲述了产品团队如何构建它运行它拥有它

第六部分:构建它、运行它、拥有它

在之前的章节中,我们讨论了如何发现和优先处理工作,以交付像 PetBattle 这样的应用程序。这包括在构建解决方案组件时需要考虑的各个方面。现在,是时候实际交付可用的软件了:

图 14.0.1:聚焦于“如何做”

本节内容涵盖了以下内容:

  • 使用我们首选的自动化工具链构建解决方案组件(API、前端等)。

  • 以可重复的方式将这些组件部署到 OpenShift 容器平台。

  • 拥有并管理这些组件,让我们能够看到网站在一个激励开发者社区文化中茁壮成长。

一旦我们的“猫王计划”完成,我们将通过这个过程变得非常富有!作为 PetBattle 的投资者,我们可以购买岛屿,投资航天工程,或者整天坐在泳池旁喝马提尼,直到感到无聊,开始一种名为Pet-Coin的新加密货币,并最终把所有的钱都赔光。

为了正确设定预期,我们不会详细讲解所有的应用程序代码。所有的源代码都可以通过书籍中的 Git 仓库访问,您可以根据自己的节奏进行查看。逐行分析代码是非常有价值的,尤其是 Helm、Quarkus 和 AI 代码。

我们的重点是如何使用 OpenShift 中提供的工具来构建、部署和运行应用程序。我们将深入探讨一些高级功能,例如 Operators、Serverless、Service Mesh 以及 CI/CD 工具的示例和技巧。我们的目的是提供示例和建议,解释为什么选择使用某些工具或技巧和方法,以及它们如何在 PetBattle 项目中协同工作。然后,您可以根据自己的项目需求,选择您想要使用的工具和方法。

本节分为三部分:

  1. 第十四章构建它:在这一章中,我们介绍如何使用 Git 作为唯一的真理来源。我们将讨论如何使用 Tekton 或 Jenkins 对源代码进行打包。

  2. 第十五章运行它:本节讨论了测试、将 Knative 作为新组件引入我们的应用、运行 A/B 测试,并使用 OpenShift 中的一些高级部署功能捕获用户数据。

  3. 第十六章拥有它:本节内容涉及如何通过监控和警报反馈循环保持系统运行。我们还将讨论 Kubernetes 中 Operator 的重要性。

在这些章节中,我们强调一个核心信念,即所有团队成员都应该对自己的代码感到自豪。团队成员应该希望去“构建它、运行它”并最终“拥有它”。

如果您觉得低级技术细节对您来说有些过于复杂,或者这不是您特别感兴趣的内容,也没关系!接下来的三章可能会感觉像是换了个节奏,但我们建议不要完全跳过这些章节。

不必过于担心低级细节、代码片段和截图,重要的是每个人都能掌握本书中概念的价值。这包括团队想要进行的任何技术改进的价值。接下来的章节将有助于阐明这一点。

第十四章:14. 构建它

"在我的机器上运行正常"——这是开发者、测试人员和运维人员在编写、测试和验证代码时经常听到的一句话。在我的机器上运行正常是源自孤岛式团队的一种说法,在这种团队中,问题的责任像温布尔登网球场上的网球一样来回传递。存在于这些孤岛式团队之间的隐形墙,导致工作在团队之间传递,但没有承担从头到尾的完整责任,这个问题已经存在了几十年。我们需要打破这种行为!从现在起,不再是“在我的机器上运行正常”,而是,“你的代码在构建系统中的进展如何?”我们代码所经过的构建和部署管道是共同的责任。为了实现这一点,所有团队成员都必须为管道做出贡献,并准备在管道出现故障时修复它。

图 14.1:在我的机器上运行正常

如果你的代码构建失败,因为你忘记检查某个依赖项,或者如果你的测试没有通过,那么修复它是你的责任!部署管道的目的是创建一个可重复的流程,使我们的代码能够通过它,并加速发布的同时降低风险。如果我们知道每次提交到代码库时,所有测试都会执行,我们就会对最终的应用程序有很大的信心。如果我们随着应用程序复杂性的增加,不断增加测试量,这也应该提高我们的信心。团队要想拥有自己的软件管道是至关重要的。

拥有像 OpenShift 这样的平台,有点像披头士在萨维尔街的苹果公司大楼屋顶上唱歌,唱的是人们团结起来。开发者、运维、测试人员、架构师、设计师、数据库管理员和分析师,大家共同使用像 OpenShift 这样的平台,提供了一个共享的协作空间。在这个平台上构建应用程序和业务服务,开发人员可以以安全、受控的方式自服务满足他们的所有需求,打破团队之间的壁垒,消除等待权限才能部署应用程序等瓶颈——这使得每个人都能用相同的语言,通过现代应用交付和技术解决方案推动业务成果的实现。

集群资源

本书的这一部分将是最具技术性的之一。如附录中所述,本章中使用CodeReady ContainersCRCs)运行代码示例的最低要求如下:

表 14.1:使用 CRCs 运行代码示例的最低要求

随着所需内存的增加,技术内容也已经讲解完毕,让我们更详细地深入探讨。我们将从现有 PetBattle 应用程序的组件开始,随着它从一个业余周末项目转变为一个高可用的、以生产为基础的设置,这一设置是由一个强大的跨职能团队构建和维护的。

现有的 PetBattle 架构

初始的 PetBattle 架构相当基础,围绕在单个虚拟机VM)中运行的应用组件进行部署。初始架构有三个主要组件:一个 JavaScript 前端;一个基于 Java 的后端,提供 API 和数据库;以及一个单实例的 MongoDB。这里的内容并不令人兴奋或复杂,但其中隐藏着技术债务和糟糕实现的雷区,这导致了网站流量增长时出现各种问题。

这种架构的问题似乎包括:

  • 单体架构——一切都必须作为一个整体进行部署和扩展,没有独立的可移动部分。

  • 身份验证和访问控制不存在。

  • 测试?单元测试?但说真的,没多少。

  • 它需要大量的数据维护,因为所有数据都存储在数据库中。

  • 不良行为者向我们友好的猫咪应用添加不当图片。

  • 脆弱的应用——如果出现问题,应用程序会崩溃,且一切都需要重启。

第九章发现方式中,我们进行了一个事件风暴练习,帮助推动了一个新提议的架构。它由一个 UI 组件和一个支持服务组成,后者向 UI 提供不同的 REST API,如图 14.2所示:

图 14.2:PetBattle 的初始业余架构

现在,让我们来看看 PetBattle 的各个组件。

PetBattle 组件

在接下来的章节中,我们将探索自动化、测试,以及将 PetBattle 扩展到包括监控、警报、Knative Serving 和 Service Mesh 等方面。但首先,让我们设想 PetBattle 团队已经完成了几个开发冲刺。他们从事件风暴中构建了资源,现在拥有了如图 14.3所示的组件和架构。通过事件风暴,我们还发现需要身份验证来管理用户。为此,我们选择了 Keycloak 作为工具。

图 14.3:PetBattle 的演进架构

现在,PetBattle 的架构正变得越来越复杂。它有一个连接到两个服务的 UI,以提供数据。单点登录SSO)和用户管理由 Keycloak 提供。让我们更详细地看看架构的每个组件。

用户界面

用户界面使用 Angular1 v12 编写,这是 Google 提供的一个完整的 JavaScript 框架,用于构建 Web 和移动应用程序。该应用程序会进行转译,静态站点代码随后从运行 Nginx(一个 Web 服务器)实例的容器中提供,由 Red Hat 提供。应用程序在启动时会拉取其配置,配置中设置了所有依赖服务的端点,例如 Keycloak 和 API。此配置作为 ConfigMap 在 OpenShift 中进行管理。

宠物服务

宠物服务是一个简单的服务,使用 Java Quarkus2 作为框架,并通过 MongoDB 数据库来检索和存储上传到平台参加比赛的宠物详情。

比赛服务

比赛服务也使用 Quarkus 框架,并将状态存储在 MongoDB 和 Infinispan 分布式缓存中。MongoDB 用于存储比赛的详细信息,例如哪个宠物赢得了比赛——但为什么我们使用缓存呢?

那么,答案是比赛只存在有限的时间,使用数据库存储时间数据并不适合我们的用例。而且,Infinispan 将缓存数据存储在内存中,访问速度比磁盘上的数据要快得多。其缺点是,如果 Infinispan 的 pod 死掉或崩溃,数据就会丢失。然而,我们计划在生产环境中通过至少保留两个副本来规避这一问题,数据将在这些 pod 之间进行复制。

用户管理

用户管理、身份验证和访问控制是架构中需要解决的其他几个关键部分。我们使用的是 Keycloak,3 一个开源身份和访问管理工具,用于提供这些功能。我们本可以自己编写一些代码来实现这些功能,但安全性是一个需要大量专业知识才能做到正确的领域,而 Keycloak 在使用开放标准来正确完成这项工作方面做得非常出色。

1 angular.io/

2 quarkus.io/

3 www.keycloak.org/

攻击计划

最初,我们将以相对手动的方式将核心 PetBattle 应用组件和服务部署到 OpenShift 上。我们希望能够在本地开发,增加新功能,展示 Helm 和 OpenShift 结合使用的简便性,使我们能够反复部署代码。一旦完成,我们将通过各种工具(包括 Tekton/Jenkins、Argo CD 和 GitOps)来自动化设置和部署过程。我们还将探讨如何使用 Knative 添加新组件到架构中,并尝试一些更高级的部署能力。最后,在第十六章掌控它中,我们将关注应用监控与告警,以及使用服务网格进行可追溯性的相关内容。图 14.4展示了架构中添加的额外组件,包括通过服务网格代理的 Knative 猫侦测服务。

图 14.4:PetBattle 的目标架构,包括最终的 OpenShift 部署

我们将尽可能使用命令行来展示和解释相关命令。每个步骤也可以通过 OpenShift 网页控制台来执行。如果你是 OpenShift 新手,网页控制台是一个很好的起点,里面有许多提示和教程!

运行 PetBattle

第六章开放技术实践 - 起步正确开始中,我们讨论了 Helm 及其作为应用生命周期管理工具的使用,用于安装、升级和回滚应用部署。我们将从命令行开始,但如果你更喜欢使用网页控制台的方法,可以跳到本节的末尾。如果你需要帮助安装 Helm 命令行工具,可以回顾一下第六章内容。现在,让我们来看一下如何轻松地将 PetBattle 应用套件作为 Helm 图表部署到 OpenShift 中的单个项目中。在你的终端中,添加 PetBattle Helm 仓库:

$ helm repo add petbattle \
     https://petbattle.github.io/helm-charts

有三个主要应用组成了 PetBattle,可以在 Helm 仓库中搜索到:

表格 14.2:组成 PetBattle 的三个主要应用

基础设施 Helm 图表通常作为锦标赛 Helm 图表的依赖项进行部署,但也可以选择单独部署。这在调试过程中非常有用。不适合家庭观看NSFF)组件是一个可选图表,提供了一项功能,可以通过 API 检查上传的图像是否符合我们面向家庭友好应用的安全内容标准。

表格 14.3:基础设施和 NSFF Helm 图表

我们可以使用以下命令搜索这些图表的最新版本:

$ helm search repo pet-battle

现在让我们将主 PetBattle 应用部署到 OpenShift 集群中。我们需要更新 PetBattle 前端的 values.yaml 文件本地副本,以匹配我们的集群 URL。这是为了在部署时连接到正确的后端服务集群。我们可以在部署 PetBattle 应用套件时,将这些值提供给我们的 Helm 图表。让我们下载一个 values.yaml 文件示例进行编辑:

$ wget https://raw.githubusercontent.com/petbattle/pet-battle/master/chart/values.yaml/tmp/values.yaml

打开 values.yaml 文件,并将 config_map 中列出的五个 URL 替换为你 OpenShift 集群的 URL(例如,如果你使用 CRC,则将 apps.cluster.com 域名改为 apps-crc.testing)。例如:

# custom end point injected by config map
config_map: '{
  "catsUrl": "https://pet-battle-api-petbattle.apps.cluster.com",
  "tournamentsUrl": "https://pet-battle-tournament-petbattle.apps.cluster.com",
  "matomoUrl": "https://matomo-labs-ci-cd.apps.cluster.com/",
  "keycloak": {
    "url": "https://keycloak-petbattle.apps.cluster.com/auth/",
    "realm": "pbrealm",
    "clientId": "pbclient",
    "redirectUri": "https://pet-battle-petbattle.apps.cluster.com/*",
    "enableLogging": true
   }
}'

pet-battlepet-battle-apipet-battle-tournament 集成到你的集群中。为此,你需要登录到 OpenShift 集群。例如:

# Login to OpenShift
$ oc login -u <username> --server=<server api url>
$ helm upgrade --install pet-battle-api \
petbattle/pet-battle-api --version=1.0.15 \
--namespace petbattle --create-namespace
$ helm upgrade --install pet-battle \
petbattle/pet-battle --version=1.0.6 \
-f /tmp/values.yaml --namespace petbattle
$ helm upgrade --install pet-battle-tournament \
petbattle/pet-battle-tournament --version=1.0.39 \
--set pet-battle-infra.install_cert_util=true \
--timeout=10m \
--namespace petbattle

如果 pet-battle-tournament 安装超时,只需重新运行它。

每个 Helm 安装图表命令应返回类似以下的信息:

    NAME: pet-battle-api
    LAST DEPLOYED: Thu Feb 25 19:37:38 2021
    NAMESPACE: petbattle
    STATUS: deployed
    REVISION: 1
    TEST SUITE: None

使用 helm list 命令可以列出已安装的 Helm 图表。你应该能在你的 petbattle 项目中看到以下的 Pod。示例如 图 14.5 所示:

图 14.5:宠物战斗 Pod

比赛服务将需要几分钟才能部署并稳定下来。这是因为其依赖的基础设施图表正在为 Keycloak、Infinispan 和 Grafana 部署运维订阅。进入 OpenShift Web 控制台,你现在应该能够看到如 图 14.6 所示的 PetBattle 应用套件。访问 PetBattle 前端,开始使用应用。

图 14.6:在 OpenShift 开发者视图中部署的 PetBattle Helm 图表

现在你已经学会了如何通过命令行安装 PetBattle Helm 图表——有人可能会说这是比较困难的方式!接下来,我们将演示 OpenShift 中 Helm 的一些集成功能——有人可能会说这是一种更简便的方式!我们可以创建一个 HelmChartRepository 自定义资源对象,它指向我们的 PetBattle Helm 图表仓库;可以把它看作是 OpenShift 中的 helm repo add。运行以下命令来安装图表仓库:

cat <<EOF | oc apply -f -
apiVersion: helm.openshift.io/v1beta1
kind: HelmChartRepository
metadata:
  name: petbattle-charts
spec:
  name: petbattle
  connectionConfig:
    url: https://petbattle.github.io/helm-charts
EOF

完成此操作后,我们可以进入 OpenShift 的开发者视图,选择添加 Helm 图表,并通过菜单和表单驱动的方式安装我们的 Helm 图表——只需选择一个图表并进行安装:

图 14.7:通过 OpenShift 中的 HelmChartRepository 视图添加 PetBattle

这可以为团队之间共享服务提供极好的开发者体验。后端团队可以将新应用发布到仓库,而下游团队可以通过单击操作将其部署到开发环境中。事实上,如果你添加了一个 Helm 值模式文件,OpenShift 将构建一个 所见即所得WYSIWYG)表单,便于配置值文件。

Argo CD

当我们在第二部分中建立我们的基础时,建立基础,我们使用 Helm 和 Argo CD 启动了所有的构建、部署和工具。我们在运行启动自动化时做出了一些有主见的选择,值得详细讨论我们做出的一些权衡。我们在建立技术基础时遵循了我们的行动号召,并规划了适合我们作为 PetBattle 产品团队的工作方案,同时回顾并讨论了哪些有效,哪些效果不太好。

结果表明,对于我们的开发团队来说,启动所有的 CI/CD 工具变成了一项极为重要的任务。我们被赋予了一个任意的(但必要的)约束条件,即我们的开发 OpenShift 集群每两周必须从头开始重建。因此,我们需要确保我们的 CI 和 CD 能够快速且反复地启动。例如,更新 SonarQube Helm 图表版本变得非常简单,只需更改一行并推送到 Git。通过遵循我们的 "一切皆代码" 实践,所有 OpenShift 基础设施定义、CI/CD 工具以及流水线定义都存储在 Git 中。声明的 CI/CD 工具状态由 Argo CD 持续同步到我们的开发集群,因此这种更改会在一分钟内同步并部署到我们的集群。

能够有效地生命周期管理所有与构建应用程序相关的支持工具需要付出努力并关注细节,但从长远来看,这是值得的,因为你将建立一个能够轻松且反复应对变化的系统。我们围绕变更的成本优化了应用生命周期,使得变更的成本(即人力成本)尽可能小。毕竟,人力时间是我们最大的资源成本!

我们所有工具的版本都已检查并存入 Git,使用 MAJOR.MINOR 版本标签来标记小的 bug 修复和安全补丁。MAJOR.MINOR.PATCH 版本不是标签,但它们指定了一个固定的版本(理想情况下!)。选择一个策略,不会让团队被困在旧的且不再支持的版本中。当然,如果你通过自动化优化了较小的变更成本,那么更改版本的问题就会变得不那么突出!

4 semver.org/

我们为软件交付生命周期选择了推送(CI)和拉取(CD)模型。构建镜像和工件(Helm 图表和配置)以及单元和集成测试的任务是推送 CI 模型的一部分。在每次代码提交时,构建管道触发器(Tekton 或 Jenkins)会被激活。Argo CD 控制器的任务是将我们在 OpenShift 集群中部署的内容与 Git 仓库中声明的应用状态保持同步。这是一个 GitOps 拉取模型用于 CI。关键点是,Git 是唯一的可信来源,所有内容都可以从这个来源重新创建。

图 14.8:持续交付的 GitOps 推送和拉取模型

我们看到这种方法的主要好处是它以开发者为中心。代码库中的任何变化都会触发管道构建和部署。这为团队提供了快速反馈,能迅速发现任何故障,因为自动化测试总是会针对新代码运行。拉取 CD 模型将构建和测试管道的同步性解耦。构建的工件(容器镜像和配置)可以一次构建,然后通过整个生命周期进行标签化和推广,所有这些都由 Git 控制。这对审计非常有帮助,可以清楚地追踪是谁、在何时更改了什么。我们可以轻松地追溯提交和推送的代码,以及与构建、测试和部署相关的内容。这也是一种灵活的方法,因为并非所有工件都需要被构建。配置可以通过相同的模型进行更改和部署。该模型也非常灵活,能够支持不同的开发工作流模型。例如,Gitflow 和基于主干的开发可以根据团队的工作方式轻松支持。

基于主干的开发和环境

在设计初始管道时,我们绘制了基本的构建、打包、部署、集成测试、标签和发布阶段。在图 14.9中,我们可以看到 MultiBranchPipeline 插件、分支和命名空间。

图 14.9:分支和命名空间

这帮助我们明确了 Git 分支、持续集成任务、持续交付任务之间的职责划分,以及这些任务将在哪些 OpenShift 项目中执行。

图 14.10:分支和活动建模

因为我们遵循基于主干的开发,5,我们的主分支(main/master)会经历完整的生命周期,包括构建、标记和推广。也就是说,镜像会被构建、单元测试和功能测试,然后在部署到labs-staging项目之前,首先在labs-test进行端到端测试。对于任何短期存在的功能分支或拉取请求,我们决定仅进行单元测试、构建,并将这些源代码部署到我们的labs-dev OpenShift 项目中。通过这种方式,我们可以定制不同代码分支上发生的管道任务。每次代码提交都会消耗一定的时间和资源,这在我们的管道中是一个权衡,必须根据管道中包含的内容进行调整,以帮助提升整体产品质量。

5 trunkbaseddevelopment.com

应用间模式的结构

我们选择使用 Helm;记住,最基本的 Helm 只是一个用于打包我们基于 Kubernetes 的应用程序资源的模板语言。每个 PetBattle 应用程序都有自己的 Git 仓库和 Helm 图表,使得独立于其他应用进行开发变得更容易。这个每个应用程序都有自己 Helm 图表 的框框如 图 14.11 所示。开发者可以通过运行 helm install 安装应用图表,获得与我们的完全自动化管道相同的体验和最终结果。这在可用性角度非常重要。Argo CD 对所有适合 Kubernetes 部署的打包格式(如 Kustomize、Helm,以及原始 YAML 文件)提供了极好的支持。因为 Helm 是一种模板语言,我们可以通过不同的值来改变 Helm 图表模板及其生成的 Kubernetes 对象。

图 14.11:应用打包、Helm 和 Argo CD 结合应用间模式

GitOps 的一个严格观点是,改变状态并不像仅仅提交填写过的模板和其中的值那样纯粹。例如,Kustomize 没有模板化,采用了这种方法。我们使用 Kustomize 来部署我们的 CI/CD 自动化与 Argo CD,因为我们认为它更适合这个用例。这意味着我们不太可能为我们的 PetBattle 产品拥有大量的 CI/CD 环境——目前只有一个。

这里的权衡是,虽然我们使用 GitOps 来同步 Helm 图表本身,但应用程序值的供应可能来自多个地方,因此你必须小心理解在哪里发生值的覆盖和优先级问题,如下所示:

  • 提供的values.yaml文件(或其子图表依赖项)——这些文件由 Argo CD 控制器保持同步。

  • 在命令行上使用 helm template --set。这些可以在模板或触发器中指定,具体取决于管道的运行方式。

我们使用 Argo CD 应用定义来部署每一个应用。我们为每一个希望部署的环境使用一个 Argo CD 应用定义。这是 图 14.11 中显示的红色框。我们使用 Argo CD 采用应用套件模式 6 将这些全部打包;有些人可能称之为应用套件!在 PetBattle 中,我们通过 Helm chart 生成应用套件定义。这是 图 14.11 中的第三个外部绿色框。该外部框的配置保存在与我们的应用不同的 Git 仓库中。

应用套件模式是我们声明性地指定一个仅包含其他应用的 Argo CD 应用。在我们的例子中,这就是 pet-battle-suite 应用。我们选择将所有从主分支/master 构建的应用放在这个 pet-battle-suite 伞形下。我们有一个用于 测试阶段 环境的 PetBattle 套件。图 14.12 显示了阶段环境的应用套件:

图 14.12:Argo CD,已部署的应用套件

6 argoproj.github.io/argo-cd/operator-manual/cluster-bootstrapping/#app-of-apps-pattern

在 Git 中,我们使用以下模式建模不同的分支:

  • labs-test, labs-staging)

  • 仅限 labs-dev 命名空间

我们应用的 Argo CD 同步策略设置为 自动化 + 修剪,这样子应用会在清单更改时自动创建、同步和删除。如果需要,你可以更改或禁用此设置。我们还会配置一个针对 CI/CD Git 仓库的 webhook,以便任何更改都会触发 Argo CD 同步所有应用;这避免了当 CI/CD 代码更改时需要等待三分钟的同步周期。

Git 修订版可以为每个子应用设置为特定的 Git 提交 安全哈希算法 (SHA) 值。Git SHA 是为每次提交到仓库时计算出的独特的 40 字符代码,因此是不可移动的,不像标签。这样可以确保即使子应用的仓库发生变化,只有当父应用更改该修订版时,应用才会发生变化。或者,你可以将其设置为 HEAD/master/main 或分支名称,以保持与特定分支同步。越接近生产环境,使用 Git 提交 SHA 来管理应用版本是一个好主意。将生产环境固定为确切版本可以确保在出现问题时更容易追踪。此结构是灵活的,可以根据你的产品团队的需求进行调整。

构建它 – PetBattle 的 CI/CD

现在让我们动手深入了解一些更技术性的话题。我们如何以可重复和安全的方式将代码从开发阶段推向生产环境?如果你还记得在第二部分建立基础中,我们了解了 DevOps 恐龙 Derek,以及我们为测试他威慑力所经历的种种困难。现在,我们将以相同的方式开始处理我们的 PetBattle 应用,首先从前端开始。

大致架构

在我们接触任何一行代码之前,我们总是喜欢关注大致架构。这有助于我们框定工具的作用和使用原因,并从高层次构建出我们的流水线。让我们以相同的方式构建我们管理 PetBattle 源代码的细节。顺便提醒一下,PetBattle 前端是一个 Angular 应用程序。它使用 Node.js v12 构建,并部署到 Red Hat Nginx 镜像上。

在从基础开始的大致架构基础上,让我们添加一些我们考虑在 PetBattle 前端实现的步骤,以便为频繁的变更推送做好准备。

图 14.13:大致架构,包括团队认为将使用的工具

快速提醒一下,我们在第二部分建立基础中定义的大致架构,识别了我们可能使用的所有工具,如图 14.13所示,包括:

  • Jenkins:用于自动化构建和测试我们的软件

  • Nexus:用于托管我们的二进制文件和 Helm 图表

  • Argo CD:用于管理我们的部署

  • SonarQube:用于评估我们的代码质量

  • Zalenium:用于自动化浏览器测试

现在工具已到位,让我们思考一下代码在部署过程中应经过哪些阶段。团队应从小处开始——我们需要多少自动化才能使代码编译并部署?团队开始时非常重要,要有一个简单的端到端流程,否则,事情会很快变得混乱,导致不必要的复杂性,甚至可能什么都不交付。它还很重要,因为我们正在创建的反馈回路需要是快速的。我们不想要一个很复杂、考虑周全的过程,但需要几个小时才能运行!这不是我们想要创建的反馈回路。

我们总是使用三个简单的阶段:构建 > 烘焙 > 部署。对于工程师来说,一个好的模式是保持对他们流水线的抽象定义,这样他们可以在任何应用程序中更好地复用这个模式,无论使用何种技术。每个阶段应该有一个明确的接口,包含输入和输出。以这种方式复用流水线定义可以减少在后端和前端之间切换的上下文切换。考虑到这一点,我们可以以以下方式定义我们构建的各个阶段。

构建

输入代码库

输出一个“已编译”并且经过单元测试的软件工件

图 14.14:大致架构中的 BUILD 组件

构建过程应该始终采用我们的源代码,编译它,并在生成某种制品之前运行一些单元测试,这些制品将存储在 Nexus 中。通过将构建过程的接口定义得如此简单,我们可以在每种技术或应用程序类型中替换实现。例如,当构建 PetBattle 前端 Angular 应用程序时,我们将使用 Node 包管理器 (npm) 在构建阶段完成这些步骤,但 Java 应用程序可能会使用 Gradle 或 Maven 来实现相同的效果。最困难的工作将在这一阶段完成,通常它对所使用的框架或语言的依赖性最大。我们将在后续阶段看到,最初使用的技术变得不那么重要,因此代码的重用性可以更高。

烘焙阶段

输入: 一个“已编译”软件制品

输出: 一个已标记的 容器镜像

图 14.15:大图中的 BAKE 组件

这是将我们在前一步作为输出创建的软件制品打包成一个盒子,即 Linux 容器镜像的过程。然后,这个镜像会被标记并存储在容器注册中心中,可以是 OpenShift 内置的,也可以是外部的。在 OpenShift 中,我们有多种方式可以实现这一点,例如使用 source-2-image、二进制构建或提供 Containerfile/Dockerfile。

部署阶段

输入: 一个已标记的镜像

输出: 在特定环境中运行的应用程序

图 14.16:大图中的 DEPLOY 组件

将刚刚推送到注册中心的镜像以及任何其他运行所需的服务或配置一起部署。我们的应用程序将打包为 Helm 图表,因此部署可能需要修补我们应用程序图表中引用的镜像。我们希望我们的流水线支持多种工作流。对于功能开发,我们可以直接 helm install 到开发命名空间。但对于发布候选版本,我们应该将新的发布信息提交到 Git,以触发变更的发布。这一工作流的实现是步骤的责任,即正在执行的低级内容。部署的抽象视图应该导致一个已验证的应用程序被部署到我们的集群上(并最终推广到生产环境)。

团队通过在他们的大图中添加一些漂亮的涂鸦来捕捉他们构建的应用程序的这些阶段。接下来,他们开始考虑如何将应用程序从测试环境推广到生产环境。在容器中构建应用程序时,我们希望确保该应用程序可以在任何环境中运行,因此单独控制应用程序配置至关重要。团队也不希望为了不同的环境而重新构建应用程序,因此一旦镜像构建并部署,它在推广之前需要进行验证。让我们进一步探讨这些阶段。

系统测试

输入: 正在测试的应用程序名称和版本

输出: 成功的测试 报告和经过验证的应用程序

图 14.17:来自大图的系统测试组件

通过前端驱动应用程序中的用户行为,验证应用程序是否按预期行为运行。如果构成应用程序的所有连接部分(微服务、身份验证和前端)都按预期工作,那么应用程序可以通过审核,并且不需要重新构建。我们为 PetBattle 编写的系统测试用例将是团队已经同意的验收标准。因此,我们可以将应用程序签署为准备好交给真实用户使用。任何在栈中发生变化的组件都应该触发此阶段;这不仅仅是前端的责任。

推广

输入: 经过验证的镜像名称和版本

输出: 在生产环境中运行应用程序

图 14.18:来自大图的推广组件

在应用程序按预期工作(基于我们通过的系统测试用例)后,我们现在可以将构成我们应用程序的镜像及其配置推广到新环境中。当然,在 GitOps 的世界中,这并不是手动推出新的部署,而是将新版本和任何自定义配置提交到我们的配置仓库,在那里它们将被 Argo CD 接收并部署。

图 14.19:包含所有管道阶段的大图

图 14.19中,我们可以看到包含管道各个阶段的大图。现在,团队已经知道他们的软件将在跨越集群时会经历哪些阶段,他们可以填充更低层次的细节,即每个步骤。在这个阶段,团队正试图找到如何构建通用的管道步骤,而不考虑他们使用的技术。这将使他们的软件栈可以更好地复用,但更重要的是,减少为多种技术编写软件的工程师的认知负担。为此,把使用的技术标注在大图上是一个好主意。在 PetBattle 的案例中,使用的是 Angular 和 Quarkus(构建工具是 Node.js 和 Maven)。他们使用新的颜色便签写下每个服务为了完成每个阶段定义的接口将经过的步骤。

图 14.20中,我们详细描述了管道构建阶段可能的步骤。首先,我们安装应用程序依赖项。接着,我们测试、校验并编译代码。最后,我们将成功的构建工件存储到 Nexus 仓库中,以便在下一个阶段——烘焙阶段中使用。

图 14.20:构建阶段及其步骤的细分

团队继续完善各个阶段的步骤。最终,他们为每个阶段添加了一些示例容器,部署到每个命名空间,以展示为使 PetBattle 系统正常运行而部署的所有组件的视图。具体内容请参见图 14.21

图 14.21:我们软件交付过程的完整大局观

大局观是一种有助于团队统一工具链内容及其使用方式的实践。这也是一个很好的工具,可以向非技术团队成员展示,帮助他们理解能够反复构建和测试代码的复杂性和实用性。和我们所有的实践一样,它也永远不会完成;当有新的工具进入我们的工具链,或者我们在管道中添加了新阶段时,我们首先将其添加到大局观中。它是我们软件交付过程的活文档。现在大局观已完成,我们继续实现它所描述的组件。

选择你自己的冒险

我们理解,做任何事都有很多方法,软件开发中通常有成百上千种甚至更多的方法。考虑到这一点,我们希望在此与亲爱的读者相遇。我们的意思是,接下来的章节将介绍两种实现同一目标的方法,请根据自己所在的上下文选择更适合的方式。

Jenkins 是许多公司和开发者首选的构建工具。它已经存在一段时间了,当然它也有一些特点。当它最初构思时,Jenkins 并没有考虑以容器的形式部署。为了保持与时俱进并展望未来,我们决定使用 Tekton 和 Jenkins 编写大局观的代码。两者都可以轻松调整用于前端和后端开发,但为了本书的目的,我们将使用 Jenkins 来自动化 Angular 应用程序的大局观部分。对于用 Java(Quarkus)编写的 API,我们将使用 Tekton,并以更符合 Kubernetes 的方式来设置相同的内容。这两条路径都可以供热衷的读者进行尝试并运行,但我们将以这种方式拆分叙述,以便更好地说明。

所以,就像你在选择你自己的冒险书籍中一样,现在你可以选择接下来想要跟随的路径。如果你对 Jenkins 自动化不感兴趣,可以直接跳到 Tekton 部分。本书中两个选项的代码都可以在 Git 仓库中找到。

在尝试本章中的内容之前,请确保已完成第七章中的引导步骤,开放技术实践—— 中点部分,位于实施 GitOps——让我们用一些真实的工作代码来构建大局观!章节下。这些步骤使用 GitOps 将 CI/CD 工具部署到您的集群中。接下来的章节我们将使用的主要工具包括 Jenkins、Argo CD 和 Tekton。

Jenkins–前端

Jenkins 是我们值得信赖的伙伴,它将为我们处理繁重的代码工作——编译、测试等。为了充分利用工具包中的所有工具,我们首先需要配置一些项目。这包括但不限于管理机密信息并添加 webhook,以便开发人员提交代码时,能够触发我们的 Jenkins 自动化。

将 Argo CD 连接到 Git

让我们来谈谈 GitOps。我们希望我们的 Git 仓库成为唯一的真理源,并且 Argo CD 控制器分析当前部署到我们集群中的内容与存储在 Git 仓库中的内容之间的差异。Argo CD 可以根据它看到的所需状态(在 Git 中)与实际状态(在集群中)之间的差异,执行一些操作,比如自动同步它们,或发送通知表明这两种状态不符合预期。例如,在 Git 中我们可能设置了版本 123 的应用程序,但集群中当前部署的是版本 122。

为了在我们的配置仓库和 Argo CD 之间建立这种连接,我们需要创建一个指向仓库的 Argo CD 应用程序-应用程序的集合。应用程序-应用程序的集合模式是一种描述系统所有元素的简洁方式。假设我们有一个名为App-1的应用程序,它代表我们的完整系统。这个App-1由可独立部署的服务组成,例如App-1aApp-1bApp-1c等。对于 PetBattle,我们有整个系统,包含前端、API 和其他服务。我们也为我们的暂存和测试环境创建了类似的应用集合;这使得我们可以将应用程序-应用程序的集合视为一套应用程序。

如果我们克隆我们在第七章中建立的ubiquitous-journey7 项目,以启动我们的集群,那么这里会有另一组用于我们的应用栈的图表,这些应用栈位于applications/deployments中。应用这些定义将创建一个指向我们的 Helm 图表的 Argo CD 应用程序自定义资源,这些图表将在 Tekton 或 Jenkins 的构建过程中创建。

配置文件(values-applications-stage.yaml)包含 Helm 图表版本和应用程序版本,这些将在 Jenkins 成功构建后更新。我们希望 Argo CD 在应用更改到集群时监控这些值。这些值文件还包含我们对基本 Helm 图表在特定环境中的覆盖内容,例如,前端配置的配置地图,用于与它所需的服务进行通信(tournament-svccats-svc等)。以下代码片段展示了这一点。这些值在开发、测试和暂存环境之间有所不同,因此这种模式使我们能够对应用程序启动时使用的配置进行版本控制。

pet_battle_stage:
    name: pet-battle-stage
    enabled: true
    source: *helm_repo
    chart_name: pet-battle
    sync_policy_automated: true
    destination: labs-staging
    source_ref: 1.0.6
    values:
      fullnameOverride: pet-battle
      image_repository: quay.io
      image_name: pet-battle
      image_namespace: petbattle
      config_map: '{ "catsUrl": "https://pet-battle-api-labs-staging.apps.hivec.sandbox1405.opentlc.com", "tournamentsUrl": "https://pet-battle-tournament-labs-staging.apps.hivec.sandbox1405.opentlc.com", "matomoUrl": "https://matomo-labs-ci-cd.apps.hivec.sandbox1405.opentlc.com/", "keycloak": { "url": "https://keycloak-labs-staging.apps.hivec.sandbox1405.opentlc.com/auth/", "realm": "pbrealm", "clientId": "pbclient", "redirectUri": "https://pet-battle-labs-staging.apps.hivec.sandbox1405.opentlc.com/*", "enableLogging": true } }'
      image_version: "master"
    project:
      name: pet-battle-stage
      enabled: true

7 github.com/petbattle/ubiquitous-journey

因此,当我们部署一个指向这个 Git 仓库的 Argo CD 应用时,它会找到额外的应用并创建我们的应用的应用模式。仓库的结构被简化了,但你可以看到这个图表非常基础,只有两个模板:一个用来在 Argo CD 中创建项目,另一个用来在项目中定义应用。

ubiquitous-journey/applications
├── README.md
├── alerting
│   └── ....
├── build
│   ├── ...
└── deployment
    ├── Chart.yaml
    ├── argo-app-of-apps-stage.yaml
    ├── argo-app-of-apps-test.yaml
    ├── templates
    │   ├── _helpers.tpl
    │   ├── argoapplicationdeploy.yaml
    │   └── argocd-project.yaml
    ├── values-applications-stage.yaml
    └── values-applications-test.yaml 

我们可以去 Argo CD UI 手动将它连接到这个仓库,或者使用 Argo CD CLI 创建 Argo CD 应用的自定义资源,但我们不如直接运行这个便捷的一行命令,连接我们预生产和测试的应用模式:

# from the root of ubiquitous-journey
$ cd applications/deployment
# install an app-of-apps for each test and staging
$ helm upgrade --install pet-battle-suite-stage -f \
argo-app-of-apps-stage.yaml \--namespace labs-ci-cd .
$ helm upgrade --install pet-battle-suite-test -f \
argo-app-of-apps-test.yaml \--namespace labs-ci-cd .

配置好这些后,我们应该能在 UI 中看到 Argo CD 创建了应用的应用定义,但它无法与子应用同步。这是因为我们还没有构建它们!一旦它们可用,Argo CD 会启动并为我们同步它们。

图 14.22:Argo CD 同步 PetBattle 应用套件到预生产环境

现在扩展这个应用的应用模式非常简单。我们只需连接一次 Git 和 Argo CD。如果在接下来的几个 Sprint 后,PetBattle 团队意识到他们需要添加新的组件或服务,他们只需扩展 values.yaml 文件,即 values-applications-stage.yamlvalues-applications-test.yaml,为其预生产或测试环境添加对新组件图表位置和版本的引用。例如,对于 cool-new-svc

cool_new_svc_stage:
    name: cool-new-svc-stage
    enabled: true
    source: *helm_repo
    chart_name: cool-new-svc
    sync_policy_automated: true
    destination: labs-staging
    source_ref: 1.0.1 # version of the helm chart
    values:
      fullnameOverride: cool-new-svc
      image_repository: quay.io
      image_name: pet-battle
      image_namespace: petbattle
      image_version: "2.1.3" # version of the application image
    project:
      name: pet-battle-stage
      enabled: true

我们流水线中的密钥

Jenkins 将负责编译我们的代码,将镜像推送到注册中心,并将值写入 Git。这意味着 Jenkins 需要一些密钥!在我们的案例中,我们使用 Quay.io 来托管镜像,因此 Jenkins 需要访问权限,以便能够将我们的打包容器镜像推送到该仓库,这需要身份验证。如果你正在跟随 PetBattle 仓库的分支并希望创建你自己的 pet-battle 实例,可以去 quay.io/ 注册一个免费的账户。你可以通过 GitHub、Google 或你的 Red Hat 账户登录。

Quay.io

在 Quay 上创建三个新仓库,每个仓库对应我们将要构建的一个应用组件。你可以将它们标记为公开仓库,因为私有仓库需要付费。

图 14.23:Quay.io 中的 PetBattle 镜像

这些仓库作为我们推送镜像的空镜像存储,但我们需要提供正确的 Jenkins 访问权限来推送它们,因此点击 UI 上的机器人图标创建一个新的服务账户,供 Jenkins 使用。给它起个合适的名字和描述以提高可读性。

图 14.24:Quay.io 中的机器人

我们将把之前创建的所有仓库标记为由这个机器人写入。点击添加权限:

图 14.25:Quay.io 中的机器人 RBAC

现在仓库和机器人账户已经创建,我们可以下载将用于管道的密钥!点击密钥名称旁边的齿轮图标,选择查看凭证。

图 14.26:如何在 Quay.io 查看机器人凭证

在弹出的页面中,下载 Kubernetes YAML 文件并将其存储在你自己的 pet-battle 分支中。

图 14.27:下载 Kubernetes 密钥

我们可以将其应用于我们的集群(首先确保你已经登录):

$ oc apply -n labs-ci-cd -f petbattle-jenkinspb-secret.yml

GitHub

Jenkins 还需要一个密钥,以便能够推送更新到我们存储在 Git 中的 Helm 值文件。我们应用程序的值文件将包含我们希望传递给模板的属性,例如 ConfigMap 变量,或者图像的位置,例如 Quay.io。我们应用程序部署的值文件还将包含一个指向要通过补丁更新我们的 DeploymentConfigs 进行部署的图像版本的引用(即我们应用程序的 SemVer,例如 1.0.1)。我们不希望手动更新这个,而是希望由机器人(Jenkins)在构建成功后更新它。因此,这个密钥将用于将这些版本更改写入我们的配置仓库,这些仓库由 Argo CD 指定。我们以这种方式跟踪所有环境中的版本变化,因为毕竟,如果不在 Git 中,它就不是真实的。

要为 GitHub 创建密钥,只需进入开发者设置视图。在登录 GitHub 的情况下,路径为 设置 > 开发者设置 > 个人访问令牌,或者直接访问 github.com/settings/tokens 以便快捷操作。创建一个新的 个人访问令牌PAT);这个令牌可以用来进行身份验证并将代码推送到仓库。为它取个合适的名字,并允许它访问仓库。

图 14.28:GitHub 个人访问令牌权限

保存令牌的值,因为没有生成新令牌之前,你将无法再次访问它。获取令牌后,我们可以通过将其添加到 basic-auth 密钥中来在 Jenkins 中创建一个密钥。为了使与该密钥将在同一命名空间中运行的 Jenkins 能够使用该密钥的值,我们可以应用一个特殊的注解,credential.sync.jenkins.openshift.io: "true"。这一小段魔法将允许通过仅更新密钥来在 Jenkins 中更新任何凭证!

如果你在自己的分支中操作并跟随本教程,请更新密钥中的 GITHUB_TOKENGITHUB_USERNAME 值,并将其应用到集群中:

$ cat <<EOF | oc apply -f- 
apiVersion: v1
stringData:
  password: GITHUB_TOKEN
  username: GITHUB_USERNAME
kind: Secret
metadata:
  labels:
    credential.sync.jenkins.openshift.io: "true"
  name: git-auth
  namespace: labs-ci-cd
type: kubernetes.io/basic-auth
EOF

SealedSecrets

你可能会认为这些机密应该保存在某个安全的地方——你是对的!如果你想探索将机密存储在 Git 中的想法,以便它们也能变得GitOpsy(是的,我刚才确实又发明了一个词),那么我们可以使用 Bitnami 的 SealedSecrets。它提供了一个加密机密的控制器,允许我们以明文形式存储它们。这意味着我们可以将它们提交到 Git!通过 SealedSecret 自定义资源的魔力,它会解密 SealedSecret,并代表你创建一个常规的 Kubernetes 秘密。我们已经编写了 Jenkins Helm 图表来接受 SealedSecrets,正是为了这个原因!

你可以通过在 Ubiquitous Journey Git 项目中启用它,将 SealedSecrets 部署到集群中。打开 bootstrap/values-bootstrap.yaml。只需将 enabled 标志更改为 true,然后当然是 Git 提交这些更改。这样将会与 Argo CD 同步,并在你的集群中创建一个 Bitnami SealedSecrets 实例,默认情况下在 labs-ci-cd 命名空间中。由于这是我们正在向工具中添加的新组件,当然我们也应该更新我们的 Big Picture,并添加描述我们如何使用它的句子。

sealed-secrets:
  # Disabled by default
  enabled: true
  nameOverride: sealed-secrets

一旦控制器创建完成,我们可以通过以下几个步骤来加密我们的机密:

  1. 使用他们的 GitHub 发布页面上的说明安装 kubesealgithub.com/bitnami-labs/sealed-secrets/releases

  2. 登录到部署了 SealedSecrets 的集群并注意命名空间(在我们的例子中,默认为 labs-ci-cd)。

  3. 使用 kubeseal 命令行工具处理你现有的机密。设置正确的命名空间非常重要,否则机密将无法解密。在这种情况下,我们将其加密为 super-dooper-secret。它应该看起来像这样:

    # create secret file from step 3
    $ cat << EOF > /tmp/super-dooper.yaml
    ---
    apiVersion: v1
    kind: Secret
    metadata:
      name: super-dooper
      labels:
        credential.sync.jenkins.openshift.io: "true"
    type: "kubernetes.io/basic-auth"
    stringData:
      password: "myGitHubToken"
      username:  "donal"
    EOF 
    # encrypt the secret $ kubeseal < /tmp/super-dooper.yaml > /tmp/sealed-super-dooper.yaml \
      -n labs-ci-cd \
      --controller-namespace labs-ci-cd \
      --controller-name sealed-secrets \
      -o yaml
    
  4. 现在,你可以直接将该机密应用到集群中进行验证,但你应该通过将其提交到 Git 来使用 Argo CD 将其添加到集群中。如果它不在 Git 中,那就不算真正的机密。在这里,我们可以看到 SealedSecret 在应用到集群之前的样子。如你所见,对于我们加密的每个变量,它是一个非常大的加密字符串:

    # have a look at the sealed secret
    $ cat /tmp/sealed-super-dooper.yaml 
    apiVersion: bitnami.com/v1alpha1
    kind: SealedSecret
    metadata:
      creationTimestamp: null
      name: super-dooper
      namespace: labs-ci-cd
    spec:
      encryptedData:
        password: AgC6NyZa2to2MtKbXYxJBCOfxmnSQ4PJgV8KGdDRawWstj24FIEm5YCyH6n/BXq9DEPIJL4IshLb2+/kONKHMhKy0CW5iGVadi13GcvO7lxZpVLeVr4T3nc/AqDwPrZ2KdzAI62h/7O4o2htRWrYnKqzmUqdESzMxWCK9dl7HZyArGadvwrH98iR48avsaNWJRvhMEDD6EMjS5yQ2vJYFMcMzOVyMtbD4f8e3jK7OO+vqoXsHtiuHk4HB63BZZqreiDcFgZMGaD6Bo6FyMSs/tbkBjttiRvP5zZJ5fqC8IEgbZeuwhJ1eVOeKs/2xGBUMoEiYo6cKaUOqV9k130K2wcdXgN8B25phkRK9DpO23LoF/7/uLwNn01pCcxAxm1/2kvX24uPLtirmg1rQ03E9qrnlvykyJ+9G3QBNtIlsiuoYmEYogZCSRZX29Cm0GWLolYPhlhMDDN6VQI6ktKCH6ubMcbh888Gn2KF8NzpQvV5wN9mQVFMR8+wNVkLGsaN+EEdgAc2CmiajIXur3zu4Menq3iWzJcWHdyTNlROpJeFH9qyfJLzbkWinPyzyBZEXeiZVKZ/ZAYEvXpyHAUngbnNnUO8HBwsLHb//uYEzWRufIJezCy9PYxUVSBNIdfPybuCSeb87Bgry/+5D5aUjrqLuKJUhsLWIL3waHyvQswUjCQlcgFA7OZ9lwMqkDUYy9SnYatIZ98kf1Z6DA==
        username: AgDY4NgxKug07A+jZ63h0Rdisfm6o7kVaKaiaPek9ZOiHsox1A0P4klYaK/7cTEyOCpFVC/2nx00TX6F2KbA1GsRHkjnU/79nOkYWqsWWTU32c/0Re8sSEIPX7aVgR/sMXYeWyRediRogA23xFcFzIFSvw4fZ2XpeX0BZNPbMdwZv2b+j/cjW8Po75B5gqbjwhMyH36QUApnjmoWmutLONVgAnHVM2rBr1Kx4wgxyy+hdmj+6ZkgMBckd53lMVX0unRVW93Ij2eDcxTwN+HvVY7nBDmxVHuYAt6t31+DXpqBew10kNDxd8Xw2MpUFDb3JpMwIVtTntmgeoyCHmo7nCYzQkGhwdrEYzoLVQBq+jf0Wmu3YRpEzZbegdTU3QfS1J7XM+86pAF6gcgbmrhpguGkU+PwnzPMxGNkq445oEPpvRemftjyFf7A8C+bZ90lrvVzZsfOue8WdXKm66vZoYuMPqA2o2HQV0IraaNGYPt9FmiAuXqWhzKsSVsbURXUUOaZIPAyX1z5V1reRz+gs/cGHYKbmUua7XOFQr32siANI1IkRPi9cT+9iP9GGdq5RzZL75cJGFV8BorZ3CMADGC+skrFKOExFvSrvofBnODB/xnPuirzsnQPcxtdvIz+sCv4M8qG2j0ASH1DBLLF7vMP9rLBgA1sPtzqX0CBakjuOjYDqpbXaKqHrM6kdTuBvO7tTDpAYA==
      template:
        metadata:
          creationTimestamp: null
          labels:
            credential.sync.jenkins.openshift.io: "true"
          name: super-dooper
          namespace: labs-ci-cd
        type: kubernetes.io/basic-auth
    # apply it to the cluster
    $ cat /tmp/sealed-super-dooper.yaml | oc apply -f- -n labs-ci-cd sealedsecret.bitnami.com/super-dooper configured
    
  5. GitOpsify(是的,我刚才确实发明了这个词),打开 ubiquitous-journey/values-tooling.yaml 中的 Jenkins 配置。使用机密生成步骤的输出将加密信息添加到每个密钥,按照如下所示设置 Jenkins sealed_secrets 的值。此处示例已为提高可读性而进行了裁剪:

    - name: jenkins
      enabled: true
      source: https://github.com/redhat-cop/helm-charts.git
      ...
      values:
        ...
        sealed_secrets:
          - name: super-dooper
            password: AgAD+uOI5aCI9YKU2NYt2p7as.....
            username: AgCmeFkNTa0tOvXdI+lEjdJmV5u7FVUcn86SFxiUAF6y.....
    
  6. 如果你已经在步骤 4中手动应用了机密,请通过运行 cat /tmp/sealed-super-dooper.yaml | oc delete -f- -n labs-ci-cd 删除它。然后 Git commit 这些更改,这样它们就可以供 Jenkins 使用,更重要的是,保存在 Git 中。在 Argo CD 中,我们应该看到 SealedSecret 生成了一个常规的机密。

    图 14.29:来自 Argo CD 的 SealedSecrets

  7. 在 Jenkins 中,我们应该看到所有通过 magic 注释(credential.sync.jenkins.openshift.io: "true")同步的机密已经可用。

图 14.30:从 Kubernetes 自动加载的 Jenkins 密钥

为了简化起见,我们将继续进行,而不去封闭机密;机密和 GitOps 的话题仅用于说明目的。

Jenkinsfile 的结构

有些人可能熟悉 Jenkinsfile,但对于那些不熟悉的人,让我们来看一下它的结构。Jenkinsfile 只是一个简单的 Jenkinsfile。它是一种 一切皆代码 的实践,定义了我们希望管道按顺序执行的操作。

Jenkinsfile 由管道定义和一组块组成,以下是来自我们 PetBattle 前端的内容。如果你对这个文件的具体位置感到好奇,可以在 Git 中找到它,位于项目根目录。图 14.31 已简化了一些:

图 14.31:Jenkinsfile 的结构

Jenkinsfile DSL 的关键要素包括:

  • pipeline {} 是所有声明式 Jenkins 管道的开始。

  • environment {} 定义将在所有构建阶段使用的环境变量。可以在这里定义全局变量。

  • options {} 包含您希望在作业中全局运行的特定作业规格;例如,设置终端颜色或默认超时时间。

  • stages {} 封装了管道将经过的多个阶段块,即 stage

  • stage {}:所有作业必须至少有一个阶段。这是构建的逻辑部分,将被执行,例如 bake-image,并包含步骤、代理和其他阶段特定的配置。

  • agent {} 指定构建应该在哪个节点上运行,例如 jenkins-agent-npm

  • steps {}:每个阶段都有一个或多个步骤。这些步骤可能包括执行 shell 命令、脚本、Git 检出等。

  • post {} 用于指定构建后的操作。Jenkins 的声明式管道语法提供了非常有用的回调 successfailurealways,这些回调对于控制作业流程或在命令执行后处理报告非常有用。

  • when {} 用于流程控制。它可以在阶段级别使用,也可以防止管道进入该阶段;例如,当分支是 master 时,部署到测试环境。

  • parallel {} 用于同时执行一些块。默认情况下,Jenkins 按顺序执行每个阶段。如果某些任务可以并行完成,那么应该并行执行,因为这样可以加速开发团队的反馈循环。

对我们而言,我们正在创建大局中的组件,分别是构建(Build)> 烘焙(Bake)> 部署(Deploy)。

构建阶段应始终获取源代码,进行编译,执行一些静态代码检查(lint)和测试,然后生成软件包,并将其存储到 Nexus。我们应生成测试报告,并在决定是否失败构建时由 Jenkins 进行解读。我们正在构建一个 Angular 应用程序,但 Jenkins 不知道如何执行 npm 或其他基于 JavaScript 的命令,因此我们需要告诉它使用包含 npm 二进制文件的代理。这时,我们在 Jenkins 部署中启动的代理将派上用场。每个构建的代理都会将所需的二进制文件(即 npm)扩展到基础代理镜像中,并推送到集群中。这个 ImageStream 将被标记为 role=jenkins-slave,这样如果它们在同一命名空间中运行,Jenkins 就能自动发现它。为了使用这个,我们只需要配置 Jenkins 阶段使用 agent { label "jenkins-agent-npm" }

图 14.32:Jenkins 代理发现魔法

构建阶段将使用此代理并执行一些步骤。首先需要捕获应用程序的版本,以便在整个管道中使用,通过读取应用程序的清单文件(Java 使用 pom.xml,Node 使用 package.json)。此版本随后将用于所有生成的工件,包括我们的镜像和 Helm 图表版本,并应遵循语义化版本控制(SemVer)(例如,<major>.<minor>.<patch> = 1.0.1)。接下来,我们将拉取依赖项,运行测试,进行代码静态检查,构建代码,并将结果发布到 Jenkins,将软件包发布到 Nexus。

这将在 Jenkins 声明式流水线中显示如下:

stage("Build (Compile App)") {
  agent { label "jenkins-agent-npm" }
  steps {
    script {
        env.VERSION = sh(returnStdout: true, script: "npm run version --silent").trim()
        env.PACKAGE = "${APP_NAME}-${VERSION}.tar.gz"
    }
    sh 'printenv'
    echo '### Install deps ###'
    // sh 'npm install'
    sh 'npm ci --registry http://sonatype-nexus-service:${SONATYPE_NEXUS_SERVICE_SERVICE_PORT}/repository/labs-npm'
    echo '### Running linter ###'
    sh 'npm run lint'
    echo '### Running tests ###'
    sh 'npm run test:ci'
    echo '### Running build ###'
    sh '''
        npm run build
    '''
    echo '### Packaging App for Nexus ###'
    sh '''
        tar -zcvf ${PACKAGE} dist Dockerfile nginx.conf
        curl -v -f -u ${NEXUS_CREDS} --upload-file ${PACKAGE} http://${SONATYPE_NEXUS_SERVICE_SERVICE_HOST}:${SONATYPE_NEXUS_SERVICE_SERVICE_PORT}/repository/${NEXUS_REPO_NAME}/${APP_NAME}/${PACKAGE}
    '''
  }
  post {
    always {
      junit 'junit.xml'
      publishHTML target: [
        allowMissing: true,
        alwaysLinkToLastBuild: false,
        keepAll: false,
        reportDir: 'reports/lcov-report',
        reportFiles: 'index.html',
        reportName: 'Code Coverage'
      ]
    }
  }
}

我们的 Bake 阶段将始终获取上一步的输出,在这种情况下是存储在 Nexus 中的软件包,并将其放入容器中。在我们的案例中,我们将执行一个 OpenShift 构建。这将导致软件包被添加到基础容器中并推送到仓库。如果我们正在执行沙箱构建,比如在某个分支上开发新特性,那么我们不关心将镜像推送到外部——因此可以使用 OpenShift 的内部注册表。如果这个构建是发布候选版本,那么我们将推送到 Quay.io(我们用于存储镜像的外部注册表)。Bake 阶段的步骤细节可以在本书配套的 Git 仓库中找到:github.com/petbattle/pet-battle/blob/master/Jenkinsfile

从鸟瞰图来看,整个过程的思路是从 Nexus 获取软件包,然后创建一个 OpenShift BuildConfig,进行二进制构建,并将软件包传递给它。然后你应该能看到构建在 OpenShift 集群中执行。

图 14.33:Jenkins 阶段概述:Bake 和 Deploy

部署将把刚刚打包好的应用及其依赖项部署到我们的集群中。最初,我们会将应用推送到 labs-test 环境。我们希望将应用及其 Kubernetes 资源打包为 Helm 图表,因此在部署过程中,我们将使用最新的发布版本来更新 values 文件中引用的应用版本。因此,我们的部署阶段被分为两个部分。

第一个步骤是将新的镜像信息和任何仓库配置(例如,我们刚刚构建的镜像所在位置)补丁到 Helm 图表中!然后将其存储在 Nexus 中,可以用作 Helm 图表仓库。

其次,它将安装这个 Helm 图表。根据我们所在的分支,应用部署的行为有所不同。如果我们在 mastermain 上构建,它是发布候选版本,因此不再使用 oc 应用配置——这是 GitOps 环境!相反,我们可以将最新的更改提交到我们的 Argo CD 配置仓库(Ubiquitous Journey)。如果我们按正确的方式操作,这个仓库中的提交应该是大部分自动化的。通过这种方式管理我们的应用使得回滚变得容易——我们所需要做的只是 Git revert!

图 14.34:Jenkins 从管道运行自动提交新版本

分支

我们的管道设计用于在 多分支 环境中工作,为 Git 中提交的每个分支创建新的管道实例。每个分支的行为稍有不同。在我们的世界中,任何合并到 mastermain 的内容都被视为发布候选版本。这意味着,当开发人员准备合并代码时,他们会修改 package.json 版本(或 Java 项目的 pom.xml 版本),以便尝试通过管道并最终部署到生产环境。我们可以自动化版本管理,但因为我们的工作流程一直很简单,所以由开发人员来管理版本,他们最了解是否是补丁、次要版本或主要版本。

这意味着,任何不在 mainmaster 分支上的内容都被视为管道的沙箱执行。如果某个构建是沙箱构建,它的作用是为开发人员提供该功能当前开发状态的快速反馈。如果它失败了,它还可以作为其他工程师的警告,表明该内容还不准备合并。沙箱构建应该被看作是临时的——我们不希望它们一直存在——因此我们对管道做了一些关键修改以适应这一点:

  1. 内部注册表:如果我们将构建的镜像推送到外部仓库,它会变得拥挤且凌乱,充满了不必要的镜像。每次开发者提交到任何分支时都会创建新的镜像,这可能会引发清理的麻烦;因此我们使用内部注册表,它会自动为我们修剪旧镜像。只有在我们知道某个发布可能会进入生产环境时,才会使用外部注册表。

  2. Helm 安装:对于我们的部署,我们并不希望引入像 Argo CD 这样的重量级工具来管理开发/沙盒环境的部署。它并不是必须的,所以我们只是使用 Jenkins 来执行 Helm 安装。这样可以验证我们的应用程序能按预期进行部署。我们使用 Argo CD 和 GitOps 来管理测试和预发布环境中的部署,但对于任何低环境,我们也应该把它们视作临时环境(就像测试和预发布环境一样)。

这种方法允许我们支持多种不同类型的 Git 工作流。我们可以通过相同的流水线方法支持 GitHub Flow、Gitflow 和 Trunk。

Webhooks

在我们实际触发 Jenkins 为我们构建之前,添加一些 webhook 来加速我们的开发是非常重要的。我们需要两个,一个用于 Argo CD 配置仓库,一个用于 Jenkins,放在我们的源代码仓库中。

当我们向 Argo CD 监视的 Git 仓库提交新更改时,它会进行轮询。轮询时间是可以配置的,但谁愿意浪费时间等待呢?Argo CD 允许你配置 webhook 来通知它,在更改发生时启动同步操作。

如果我们希望在 Argo CD 完成操作后进行某些操作(例如系统测试),这尤其重要。我们在 Jenkins 中的流水线是同步运行的,而 Argo CD 是异步的,因此任何能减少这些行为之间等待时间的做法都是至关重要的。

在 GitHub 上,我们可以配置 Ubiquitous Journey 的 webhook,当仓库更新时触发 Argo CD。在 GitHub 上,添加一个 webhook,地址为我们的 Argo CD 服务器地址后面加上 /api/webhook

图 14.35:在 Git 提交时触发 Argo CD 的 Webhook

Jenkins

每次我们向源代码仓库提交时,都希望 Jenkins 执行构建。我们使用的是 Jenkins 的多分支插件,这意味着当我们向仓库提交时,webhook 会触发一个分支扫描,这应该会拉取任何新的功能分支来构建流水线或为任何新代码提交创建构建。

配置 pet-battle 前端的 Jenkins webhook 非常简单。在 GitHub 的 Hooks 页面上,添加我们的 Jenkins 实例的 URL,格式如下,其中触发令牌是我们 GitHub 项目的名称。作为一种约定,我倾向于使用 Git 项目的名称作为令牌,因此如果你使用 Jenkins 构建后端,后端也会采用相同的方法:JENKINS_URL/multibranch-webhook-trigger/invoke?token=[Trigger token]

例如,前端应用的 webhook URL 可能是这样的:

jenkins-labs-ci-cd.apps.petbattle.com/multibranch-webhook-trigger/invoke?token=pet-battle

将一切结合起来

我们现在已经了解了 Jenkins 文件是什么以及它为我们做了什么。我们讨论了分支和我们所说的构建成为发布候选版本的含义(即,版本提升并且在 master/main 上)。我们也简单介绍了如何使用 Helm 和 GitOps 部署,提交更改并让 Argo CD 为我们推出更改……但是我们如何将 Jenkins 与所有这些魔法连接起来呢?

就像所有这些事情一样,有几种方法。我们可以打开 Jenkins 并点击 seed-multibranch-pipelines 作业!你们中的一些人可能已经注意到,当我们从 Ubiquitous Journey 部署 Helm chart 时,Jenkins 被配置为指向我们组织的 PetBattle GitHub。我们在镜像中设置了一些环境变量(在 ubiquitous-journey/values-tooling.yaml 中)以指向我们的 GitHub 组织,如下所示:

- name: GITHUB_ACCOUNT
value: 'petbattle'
- name: GITHUB_ORG
value: 'true'

如果你正在跟随 Ubiquitous Journey 的 fork,并且希望查看整个管道的运行过程,更新 ARGOCD_CONFIG_REPO 为指向你的 fork,且 QUAY_ACCOUNT 为指向你在 Quay.io 上的用户。

这些是由内置于 Jenkins 镜像中的 seed-multibranch-pipelines 作业使用的,用于扫描组织中的仓库,查找包含 Jenkinsfile 并且没有归档的仓库。如果找到,它会自动为我们创建多分支 Jenkins 作业。在我们的案例中,Cats API 和 PetBattle 前端都有一个 Jenkinsfile,所以作业会为我们自动创建,无需配置任何东西!如果你跟随这个过程,但不是使用 GitHub 而是 GitLab,你可以设置 GITLAB_* 环境变量来实现相同的效果。

图 14.36:Jenkins seed 用于创建我们的 Jenkins 作业

如果你打开 Jenkins,并深入查看 pet-battle 文件夹中的前端代码库,你应该会看到构建任务;例如,一个名为 cool-new-cat 的 Git 分支和 master 分支,每个分支都有管道执行。打开 Blue Ocean 视图,我们可以更清楚地理解我们构建的流程控制,正如之前讨论过的那样。

图 14.37:Jenkins 发布候选管道

对于我们认为是发布候选版本的主分支,构建的工件可能会一直沿着流水线走。如果我们正在更新应用程序,我们会更新清单版本,并且提交 Git,这应该会触发构建。从这个点开始,我们的构建环境已经配置好,流水线应该开始执行。我们将目标指向外部仓库,构建的镜像将推送到 Quay.io,以便在多个集群之间进行移植。我们的 Helm chart 的值会被修补并推送到 Nexus 进行存储。如果我们需要更新 Helm chart 本身,例如添加一些新的配置到 chart 中或添加一个新的 Kubernetes 资源,我们当然也应该更新 chart 的版本。对于我们的部署,我们会修补 Argo CD 配置仓库(Ubiquitous Journey)并提供新的发布信息,它应该会自动同步并将我们的应用程序部署到labs-test命名空间!然后,我们会运行验证步骤,检查正在发布的版本是否与新版本匹配(基于标签),并且已经成功。

图 14.38:Jenkins 特性开发流水线

对于我们的功能分支,思路大致相同,但不需要外部仓库。我们的 charts 也会被修改,以覆盖名称并包括分支。这意味着,每次向功能分支提交时,我们都会部署一个新的应用程序,路由中包含分支名称。因此,对于我们的cool-new-cat分支,应用程序将作为cool-new-cat-pet-battle部署,并在开发环境中可用。

Big Picture 中新增的其余阶段,系统测试和推广,将在下一章中详细讨论,我们将更详细地了解 PetBattle 的测试。

Jenkinsfile 的下一步

Jenkins 已经存在一段时间了。它不是构建软件的最容器化方法,但周围有一个丰富的生态系统。它能够持续这么长时间,因为大家喜欢它!希望这能让你对我们 PetBattle 应用程序中 Jenkins 的应用有所了解,但这远不是终点。故事中还是有一些漏洞的,正如你们中的一些人可能已经注意到了。例如,一旦构建成功部署到测试环境,我该如何将其进一步推广?我是否需要做更多的测试?好吧,答案将在下一章给出,我们将探讨系统测试,并进一步扩展我们的流水线以包含推广镜像的步骤。在成功的流水线执行结束时,我们仓库中的值文件并未更新;我们应该考虑将成功构建的工件细节写回到仓库中,这样它总是能够设置一个合理的默认值,指向当前已部署的内容。

我们在这里写的阶段非常庞大,确实包含一些bash和其他逻辑。例如,如果你要构建一个非前端应用程序,你可能会想用 Golang 来构建。大部分情况下,唯一需要改变的是构建阶段,因为将某个东西放入容器以及我们如何打包我们的 Helm 图表并部署应用程序的方式保持不变。一旦应用程序(无论是使用什么语言或框架)被放入容器中,那么我们如何运输它也保持不变。这意味着在 Bake 和 Deploy 阶段重用代码的潜力很高,从而降低了在 OpenShift 等平台上采用新技术的门槛。但是要小心——在大量应用程序的多个任务中复制粘贴相同的步骤可能会导致某个错误被复制到各处。对管道的修改也可能变得很昂贵,因为你需要更新每个仓库中的每个Jenkinsfile

图 14.39:潜伏的龙,小心!

Jenkins 通过使用共享库来解决这些问题,最近还引入了Jenkins 模板引擎JTE)。JTE 通过从治理的角度强制执行管道方法来解决这个问题。虽然这看起来像是一种很好的方式来在企业范围内实现标准化,但这里有潜在的风险!

应用一个标准管道而没有正当理由,或者不给团队提供拉取请求并针对其特定用例做出更改的能力,就相当于将开发和运维分隔开来。我们与许多客户合作过,他们曾尝试过类似的方法,最终发现这让他们的速度变慢,而不是加快。那些实施管道的团队认为自己在提供帮助和优质服务,但当事情出错时,他们却成了修复的瓶颈。对于一些团队来说,采用强硬方法可能不适合他们的用例,因此管道就成了他们加速发展的障碍。

Tekton 是另一种让我们实现更高管道重用性的方式,同时也更好地支持我们的 GitOps 架构。现在让我们来探讨它在 Java 微服务中的应用。

Tekton—后端

Tekton8 是一个开源的云原生 CI/CD 工具,构成了 OpenShift 管道的基础。9

Tekton 基础知识

Jenkins 和 Tekton 之间有很多相似之处。例如,两者都可以用来将管道定义作为代码存储在 Git 仓库中。Tekton 作为一个运算符部署在我们的集群中,允许用户在 YAML 文件中定义PipelineTask。Tekton Hub10 是一个用于共享这些 YAML 资源的仓库,能够为标准工作流提供极高的重用性。

8 tekton.dev

9 docs.openshift.com/container-platform/4.7/cicd/pipelines/understanding-openshift-pipelines.html

10 hub.tekton.dev

图 14.40: Tekton Hub 和 OpenShift 集群任务

OpenShift 还将这些任务作为 ClusterTasks 在全局范围内提供。要编写一个流水线,你可以将这些任务定义串联在一起。OpenShift 提供了一个引导式的 Pipeline 构建器 UI 来完成这项任务。你将不同的任务连接在一起,并根据每个任务定义中的要求,定义参数和输出。

图 14.41: OpenShift 流水线构建器 UI

我们的流水线定义中有许多任务活动需要持久化存储。在使用 Maven 构建我们的后端 PetBattle API 和 Tournament 应用程序时,我们通过 Nexus 仓库管理器拉取 Java 依赖项。为了加速这个过程,我们可以在本地执行与笔记本电脑上相同的缓存操作,并将这些依赖项存储在 .m2/repository 文件夹中,跨构建共享它们。我们还使用持久化存储来存储构建后的工件,以便它们可以在流水线的不同步骤之间共享。另一个用例是将 Kubernetes 秘密挂载到我们的流水线中:

# maven pipeline 
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: maven-pipeline
  labels:
    petbattle.app/uj: ubiquitous-journey
spec:
  workspaces:
    - name: shared-workspace
    - name: maven-settings
    - name: argocd-env-secret
    - name: maven-m2
    - name: git-auth-secret 
# binding the workspace in the PipelineRun object
  resourcetemplates:
    - apiVersion: tekton.dev/v1beta1
      kind: PipelineRun
      metadata:
      ...        workspaces:
        - name: shared-workspace
          persistentVolumeClaim:
            claimName: build-images
        - name: maven-settings
          persistentVolumeClaim:
            claimName: maven-source
        - name: argocd-env-secret
          secret:
            secretName: argocd-token
        - name: maven-m2
          persistentVolumeClaim:
            claimName: maven-m2
        - name: git-auth-secret
          secret:
            secretName: git-auth

在 Tekton 中,我们通过命名的 workspaces 将这些 Kubernetes 对象链接起来,当我们创建所谓的 PipelineRun 时,后者代表一次流水线运行的代码。同样,单个任务的执行是 TaskRun。每个 workspace 随后会作为该 PipelineRun 中任务的资源,如下所示。

可重用流水线

在开始编写和设计 Tekton 流水线之前,有一些选择需要做出。第一个选择是决定是为每个应用编写一个流水线,还是编写可重用的流水线,这些流水线可以用于相似的应用程序。

在 PetBattle 中,我们开始时为每个应用程序编写一个流水线;这类似于在每个应用程序的 Git 仓库中都有一个 Jenkinsfile。API 和 Tournament PetBattle 应用程序都使用 Java、Quarkus 和 Maven 构建,因此将流水线代码整合并为这两个应用程序编写一个可重用的参数化流水线是有意义的,因为它们始终有相似的任务。我们在 PetBattle 中使用 maven-pipeline 来实现这一点。

图 14.42: PetBattle 的 Tekton 流水线

当然,你也可以将重用限制在 Task 级别,但我们在 PetBattle 的 UI、API 和 Tournament 应用程序之间共享公共任务。最终,开发团队必须权衡维护一个流水线和每个应用程序流水线自治之间的利弊。没有放之四海而皆准的答案。

使用 Tekton 进行构建、打包和部署

下一步是开始设计我们在流水线中使用的内容。这是一个非常迭代的过程!在我们的宏观图中,我们讨论了构建、打包和部署过程,因此增加遵循此方法论的流水线任务步骤是有意义的。

图 14.43:PetBattle 的 Tekton 流水线使用的任务定义列表

maven-pipeline 开始通过将应用程序和 CI/CD(普遍旅程)代码库克隆到共享的 workspace 中。我们通过调用 Maven 来构建和测试应用程序,从而检查代码质量,质量报告会被上传到我们的 SonarQube 镜像中。

我们检查 SonarQube 中的质量门是否通过,然后调用 Maven 来打包我们的应用程序。Tekton 提供了有用的构造,允许我们在任务步骤失败时通过指定 retries 数量以及使用 runAfter 任务名称列表来重新尝试任务步骤。

    - name: quality-gate-check
      retries: 1
      taskRef:
        name: sonarqube-quality-gate-check
      workspaces:
        - name: output
          workspace: shared-workspace
      params:
      - name: WORK_DIRECTORY
        value: "$(params.APPLICATION_NAME)/$(params.GIT_BRANCH)"
      runAfter:
      - save-test-results

在 Java Quarkus 中,打包格式可以是 fat JAR、爆炸式快速 JAR 或基于 GraalVM 的本地镜像。这些格式各有利弊。11 然而,在 PetBattle 中,我们使用的是爆炸式快速 JAR,这让我们在更快的构建时间和更快的启动时间之间做出权衡。这是构建阶段的结束。我们将单元测试向左移动到流水线中,这样在进入打包和部署阶段之前,我们可以快速反馈任何代码质量问题。

图 14.44:在 OpenShift 中查看 PipelineRun,显示正在执行的任务

11 quarkus.io/guides/maven-tooling

接下来是打包阶段。我们使用一个标准的 OpenShift BuildConfig 对象,并通过 Kustomize 加载它,因为我们没有将其与 Helm 图表一起打包。我们使用 oc start build 命令在打包的应用程序上执行二进制构建。我们决定不将构建好的应用程序包上传到 Nexus,因为我们希望使用容器镜像作为部署单元。如果我们正在构建需要支持我们服务的库,那么这些库应该在这个阶段被捕获到 Nexus 中。值得指出的是,我们也可以在此时将镜像推送到外部注册中心,这样它可以在 OpenShift 集群之间轻松共享。

图 14.45:流水线的打包部分

下一步是进行代码检查并打包应用程序的 Helm 图表。然后,将版本化的图表上传到 Nexus。如果我们在应用程序分支上,下一步流水线操作将是将 helm install 安装到 labs-dev 项目中。我们可以在 Tekton 流水线中利用 when 语句来配置这种行为:

- name: helm-install-apps-dev # branches only deploy to dev
      when:
        - Input: "$(params.GIT_BRANCH)"
          Operator: notin
          Values: ["master","trunk","main"]
      taskRef:
        name: helm-install-from-chartrepo

当在 trunk/HEAD 上时,ImageStream 会被版本化并打标签到我们将要部署应用程序的命名空间(labs-testlabs-staging)。由于我们在实践 GitOps,应用程序是通过 Argo CD 和 Git 部署的。Argo CD 的应用程序的值文件会更新为新的图表和镜像版本。这些文件会通过管道检查并执行 git commit。Argo CD 配置为自动同步我们在 labs-testlabs-staging 中的应用程序,管道的最后一步是确保同步任务成功。

图 14.46:管道的部署部分

在 OpenShift web 控制台中,开发人员可以访问到大量的管道信息,所有管道任务的日志也可以轻松查看。

图 14.47:Tekton 管道进度和状态悬浮

Tekton 还提供了一个非常好的命令行工具,叫做 tkn,它可以用于执行 OpenShift 控制台中所有可用的管道操作,例如查看日志、启动管道运行和定义 Tekton 对象。

$ tkn pr list -n labs-ci-cd

名称 启动时间 持续时间 状态

pet-battle-tournament-44vg5   1 days ago 27 minutes   Failed
pet-battle-9d9c7              2 days ago 19 minutes   Succeeded
pet-battle-api-jcgn2          2 days ago 21 minutes   Succeeded
pet-battle-kfch9              2 days ago 5 minutes    Failed
pet-battle-tournament-br5xd   2 days ago 24 minutes   Failed
pet-battle-api-5l4sd          2 days ago 23 minutes   Failed

现在让我们来看看如何触发构建。

触发器和 Webhooks

每次开发人员将代码推送到 Git 时,我们希望触发一个构建。这确保了我们可以最快地获得所有代码变更的反馈。在 Tekton 中,这是通过使用一个 EventListener pod 对象来实现的。创建时,会部署一个 pod,暴露我们定义的触发器操作。

图 14.48:Tekton 触发器流

Tekton 触发器通过让 EventListener 对象接收传入的 webhook 通知来工作,使用拦截器处理这些通知,并根据拦截器的允许从模板中创建 Kubernetes 资源,同时从 webhook 的正文中提取字段(假设正文是一个 JSON 文件):

apiVersion: triggers.tekton.dev/v1alpha1
kind: EventListener
metadata:
  name: github-webhook
  labels:
    app: github
spec:
  serviceAccountName: pipeline
  triggers:
    - name: pet-battle-api-webhook-all-branches ...
    - name: pet-battle-api-webhook-pr ...
    - name: pet-battle-tournament-webhook-all-branches ...
    - name: pet-battle-tournament-webhook-pr ...
    - name: pet-battle-webhook-all-branches ...
    - name: pet-battle-webhook-pr ...

在 OpenShift 中,我们将 EventListener 的 webhook 端点作为路由暴露,以便可以将其连接到 Git。不同类型的 TriggerBinding 被传递到我们的 TriggerTemplate。然后,TriggerTemplate 定义了要创建的 Tekton 资源。在我们的案例中,这是一个 PipelineRunTaskRun 定义。

triggers:
    - name: pet-battle-api-webhook-all-branches
      interceptors: # fixme add secret.ref
        - cel:
            filter: >-
              (header.match('X-GitHub-Event', 'push') &&
                body.repository.full_name ==
                                        'petbattle/pet-battle-api')
            overlays:
            - key: truncated_sha
              expression: "body.head_commit.id.truncate(7)"
            - key: branch_name
              expression: "body.ref.split('/')[2]"
            - key: app_of_apps_key
              expression: "body.repository.name.replace('-','_',-1)"
      bindings:
      - kind: TriggerBinding
        ref: github-trigger-binding
      template:
        ref: pet-battle-api-maven-trigger-template

Tekton 使用一种表达式语言,称为通用表达式语言CEL),13 用来根据 JSON 主体和请求头解析和过滤请求。这是必要的,因为 Webhook 的有效载荷和 Git 工作流可能会有所不同。例如,我们使用 GitHub,并且将拉取请求与对我们的 main/HEAD 分支的更改视为不同。我们在上述示例中所做的一项自定义是,根据 Git 仓库名称在触发绑定中定义 Argo CD 的 app-of-apps 键。这使我们可以在管道的部署阶段检查只更改了的那个应用的同步状态,而不是整个应用套件。虽然触发过程看起来复杂,但在处理各种 Git SCM 和开发团队可用的工作流时,这种灵活性是必需的。

12 docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads

13 github.com/google/cel-go

有一些便捷的模板已经被 Ubiquitous Journey 加载到labs-ci-cd项目中,可以用于手动触发PipelineRun——如果你还没有配置 GitHub webhook,这个功能非常有用。

$ oc -n labs-ci-cd process pet-battle-api | oc -n labs-ci-cd create -f-
$ oc -n labs-ci-cd process pet-battle | oc -n labs-ci-cd create -f-
$ oc -n labs-ci-cd process pet-battle-tournament | oc -n labs-ci-cd create -f-

你可以手动将 webhook 添加到你的 GitHub 项目中 14,指向在labs-ci-cd项目中暴露的EventListener路由。

$ oc -n labs-ci-cd get route webhook \     -o custom-columns=ROUTE:.spec.host --no-headers

否则,查看 PetBattle Ubiquitous Journey 文档,了解可以运行的 Tekton 任务,这些任务能够自动将这些 webhook 添加到你的 Git 仓库中。

GitOps 我们的管道

我们的管道、任务、触发器、工作空间和卷定义本身通过 GitOps 应用到labs-ci-cd项目中。这里的想法是最小化我们对管道进行适应的难度。例如,我们可能想要在管道步骤中增加一些安全检查。如果测试失败,或者生产环境中的服务失败,我们就需要调整管道,以适应更多的质量控制或测试步骤。添加新工具或修改任务步骤仅仅是将管道代码定义推送到 Git 中而已。

# PetBattle Tekton objects
  - name: tekton-pipelines
    destination: labs-ci-cd
    enabled: true
    source: https://github.com/petbattle/ubiquitous-journey.git
    source_path: tekton
    source_ref: main
    sync_policy: *sync_policy_true
    no_helm: true

在我们的 Tekton 源文件夹中,我们使用 Kustomize 来应用所有定义 Tekton 对象的 YAML 文件。这些管道对象通过 Argo CD 保持同步。

14 docs.github.com/en/developers/webhooks-and-events/creating-webhooks

我应该使用哪个?

CI/CD 工具的生态系统庞大 15,并且充满活力和健康。CNCF 在这个类别中的工具生态系统今天有不少于 36 个产品和项目。在试图回答“我应该使用哪一个?”这个问题时,最好考虑多个因素:

  • 你的团队是否拥有某些工具或语言的相关经验?例如,Jenkins 中的代码流水线使用 Groovy 语言,因此如果你的团队具有 Groovy 或 JavaScript 技能,这可能是一个不错的选择。

  • 该工具是否能与平台轻松集成?CNCF 中的大多数工具已与 Kubernetes 良好集成,并且具有云原生的血统。但这并不意味着所有工具在部署、平台集成或生命周期管理方面都是相同的——有些可能只是软件即服务SaaS)形式的工具,使用代理进行部署,而另一些则可以通过在集群中使用命名空间隔离为每个团队进行部署。还有一些工具,如 Argo CD 和 Tekton,可以使用操作器模式在集群范围内部署,并通过操作器生命周期管理器OLM)来管理它们的生命周期。由于 OpenShift Pipelines 操作器的存在,Tekton 与 OpenShift 的 Web 控制台集成非常出色。

  • 工具部署模型:Jenkins 和 Argo CD 都采用客户端-服务器模型进行部署。在大规模应用时可能会遇到问题,例如需要管理成千上万的流水线或数百个应用程序。可能需要使用多个部署来扩展到不同团队和集群。Argo CD 和 Tekton 使用 CRD 和操作器模式扩展 Kubernetes,因此它们的部署在扩展模型上更符合 Kubernetes 本地化。

  • 企业支持:大多数工具都提供供应商支持,但并非所有工具都有。这对于需要与供应商建立关系的企业组织至关重要,以涵盖认证、培训、安全修复和产品生命周期等问题。

15 landscape.cncf.io/card-mode?category=continuous-integration-delivery&grouping=category

图 14.49:CNCF CI/CD 工具生态图

  • 活跃的开源社区:一个充满活力的上游社区非常重要,它是合作、共享代码和知识的场所。功能和插件的快速开发通常源自一个基于实际用户问题和需求的社区。

  • 使用同一工具进行 CI 和 CD,还是使用不同的工具?正如我们在 PetBattle 中所展示的那样,有时 CI 使用推式模型,CD 使用拉式模型并使用不同的工具是有意义的。

  • 可扩展性模型:这对于工具周围的生态系统非常重要。Jenkins 拥有一个出色的插件模型,允许许多不同的扩展到核心。Tekton 有类似的模型,但它与 Jenkins 的不同之处在于,用户可以在任务中使用任何容器。评估这些扩展很重要,因为它们在核心工具之上提供了很多价值。一个好的例子是,Tekton 在管理测试仪表盘和结果方面不如 Jenkins 及其插件那样好,因此我们可能会依赖 Allure 来完成这项工作。报告和仪表盘扩展对于在 CI/CD 过程中尽量缩短反馈循环至关重要。

一旦你考虑了这些理想, hopefully 你会对适合你的产品和团队的工具组合达成共识。设计和规划是回答各个连续部署步骤如何进行以及应使用什么应用打包方法(例如,是否使用模板)的必要手段。到目前为止,我们已经培养了一种以实验为驱动的方法来回答这些问题,这里并不是非此即彼的工具选择,而是选择最适合当前任务的工具!

结论

在本章中,我们介绍了如何将 Git 作为唯一的可信来源。我们涵盖了如何使用它来打包源代码,选择使用模板或不使用模板的方式。

Tekton 或 Jenkins。在下一章中,我们将重点介绍测试,使用 Knative 向我们的应用程序添加新组件,进行 A/B 测试,并利用 OpenShift 中一些高级部署功能捕获用户指标。

第十五章:15. 运行它

有句话说,直到代码在生产环境中运行,它才有价值。这里的意思是,直到客户使用你的软件,它对你的业务或组织的价值是有限的。这当然是一个广泛的概括!然而,这确实反映了软件的本质——其效用直接与能够运行它以实现最终编写目的相关。为了在生产环境中达到客户预期的服务质量,所有的代码都必须经过严格的测试。

在本章中,我们将探讨 PetBattle 团队如何测试他们的软件,以便在生产环境中能够更有信心地运行。正如我们在第七章开放技术实践——中期中讨论的,测试是多方面的,我们将详细介绍测试的类型和范围,从单元测试到端到端测试,再到安全检查等。

当应用的爱好者版本上线后,PetBattle 的创始人很快发现恶意内容被上传到站点。作为本章的一部分,我们将探讨使用训练过的 AI-ML 模型来解决这个问题的现代方案。

在本章的最后部分,我们将探讨一些常见的云部署模式,并展示 A/B 测试和实验,以便深入了解如何安全地衡量和学习在生产环境中部署新特性所带来的影响。

不适合家庭的内容(NSFF)组件

正如我们之前提到的,在运行第一代 PetBattle 时,我们面临的一个主要问题是在线恶搞者上传不当图片到系统。这增加了平台的运营开销,因为 PetBattle 的创始人必须手动在 MongoDB 中查找并删除这些违规图片——非常繁琐!

一直在创新的团队决定尝试为这个问题提出一个自动化解决方案。我们决定调查的一个方法是使用人工智能AI)对上传的图片进行分类,并将其集成到平台中。

人工智能本身是一个极具吸引力的领域,在这里我们不会深入探讨,只是提到我们正在使用由开源 TensorFlow 机器学习平台提供的预训练图像分类模型。

很好,但我们如何在 OpenShift 上运行这个应用?

计划如下:

  1. 生成或获取一个预训练的图像分类模型。

  2. 构建包含 TensorFlow 服务组件的容器,可以为我们上传的图片提供模型服务并进行预测。

  3. 在 OpenShift 上以“按需扩展”部署模型(即无服务器架构)部署并运行容器。

为什么选择无服务器架构?

在基于 Kubernetes 的平台上部署容器时,例如 OpenShift,Kubernetes 负责管理运行中的容器,并且默认情况下,如果容器因错误终止,它会重新启动。基本上,容器始终处于运行状态。这对于那些持续接收和处理流量的容器来说是没问题的,但对于那些偶尔或突发性接收流量的容器来说,持续运行它会浪费系统资源。

我们希望实现的是,在需要时才启动容器,也就是说,在接收到请求时启动。容器启动后,我们希望它处理进入的请求,然后在一段时间没有流量后,优雅地关闭,直到收到进一步的请求。我们还希望容器实例在接收到大量请求时能够自动扩展。

我们可以使用 Kubernetes 水平 Pod 自动扩缩器(Horizontal Pod Autoscaler)自动化平台上容器实例数量的增加和减少;然而,这并不能扩展到零。我们也可以使用类似 oc scale 命令的工具,但这需要大量的脚本编写和组件集成。幸运的是,Kubernetes 社区考虑到了这一点,并提出了解决方案,称为 Knative。1

Knative 主要有两个组件,Knative ServingKnative Eventing。Serving 用于根据 HTTP 流量启动(和关闭)容器。Knative Eventing 功能类似,但它专注于基于事件启动容器,覆盖了更广泛的使用场景。本书的重点将放在使用 Knative Serving 上。不过,我们也会提供一个示例,展示如何使用 Knative Eventing。

生成或获取预训练模型

我们已经进行了图像分类实验一段时间了。我们开始使用来自 Open Data Hub 社区的一些组件(opendatahub.io/),并在现有的开源模型基础上训练我们的模型。最终,我们生成了一个训练好的数据模型,能够根据 Yahoo 的 Open NSFW 分类器 2(已用 TensorFlow 重写)对图像进行分类,判断其是否为 NSFF。尽管这个模型并不完美,但它足够用于入门。

数据科学社区中常见的模式是使用像 Seldon3 这样的工具来服务训练好的数据模型,而这些工具是 Open Data Hub 的一部分。然而,对于我们的需求来说,只需要一个简单的对象存储工具。因此,我们选择了 MinIO4,这是一个 Kubernetes 原生对象存储工具。我们决定,如果需要的话,稍后可以使用更先进的存储机制扩展它,比如 OpenShift 容器存储或 AWS S3。

1 knative.dev/

2 github.com/yahoo/open_nsfw

3 www.seldon.io/

4 min.io/

我们将训练好的数据模型加载到 MinIO 中,看起来是这样的:

图 15.1:TensorFlow 数据模型保存在 MinIO 中

所保存的模型是我们可以使用 TensorFlow Serving 提供的服务之一,5 这基本上为我们提供了一个调用我们保存模型的 API 端点。我们可以部署一个开源的 TensorFlow Serving 镜像,并且只需配置它来找到我们在 S3 存储位置中的保存模型即可。

我们已经忽略了大部分工程化过程,这些过程使得 AI、ML 和 Ops 管道变得更加完善,这并不是因为这不是一个有趣的主题,而主要是因为这需要另一本完全不同的书来充分阐述!如果这个主题对你很重要,那么请看一下 Open Data Hub 项目。6 这是一个基于 Kubeflow 的开源项目,提供工具和技术来构建和运行 OpenShift 上的 AI 和 ML 工作负载。

5 www.tensorflow.org/tfx/guide/serving

6 opendatahub.io/

7 www.kubeflow.org/

OpenShift Serverless Operator

在开始为 NSFF 服务部署我们的应用软件之前,我们需要将 OpenShift 无服务器操作员 8 添加到我们的 PetBattle 引导程序中。该操作员安装在集群范围内,以便任何想要使用 Knative 组件 Knative Serving 和 Knative Eventing 的项目都可以这样做。

让我们使用 GitOps、ArgoCD 和 Kustomize 来配置和安装无服务器操作员。首先,我们可以使用 ArgoCD 测试配置。使用命令行从 ArgoCD 登录。添加包含 Knative 无服务器操作员 YAML 订阅的 Git 存储库并创建应用程序:

# Login to ArgoCD
$ argocd login $(oc get route argocd-server --template='{{ .spec.host }}' \
-n labs-ci-cd):443 --sso --insecure
# Add our repository
$ argocd repo add \
  https://github.com/rht-labs/refactored-adventure.git
# Create the Knative Operator - this may have already been created for you but here is how to do it on the command line.
# Create the Knative Operator
$ argocd app create knative\
  --repo https://github.com/rht-labs/refactored-adventure.git \
  --path knative/base \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace openshift-serverless \
  --revision master \
  --sync-policy automated

8 github.com/openshift-knative/serverless-operator

安装完成后,您应该能够在openshift-serverless命名空间中成功看到此安装。

图 15.2:OpenShift 无服务器操作员(Knative)

我们还可以将其放入我们的 PetBattle UJ 引导程序中,第七章开放技术实践 - 中点,这样我们就不需要手动运行这些命令了。将以下内容添加到我们的values-tooling.yaml并将其提交到 Git 中:

# Knative stanza in values-tooling.yaml
- name: knative
    enabled: true
    destination: openshift-serverless
    source: https://github.com/rht-labs/refactored-adventure
    source_path: knative/base
    source_ref: master
    sync_policy: *sync_policy_true
    no_helm: true

现在,操作员已准备好供我们使用,以部署我们的 Knative 服务。

部署 Knative Serving 服务

有几种方法可以创建 Knative Serving 服务。我们可以创建 Knative 服务定义并将其安装到我们的集群中。我们已将其打包为 Helm 图表,以便轻松安装:

$ helm upgrade --install pet-battle-nsff \
petbattle/pet-battle-nsff \
--version=0.0.2 \
--namespace petbattle

容器启动并将模型数据加载到 MinIO 中可能需要一分钟左右;在此过程中它们可能会重启几次。oc get pods 命令的输出应该是这样的:MinIO S3 pod 和其完成的数据加载,以及一个 TensorFlow Knative 服务 pod:

$ oc get pods --namespace petbattle NAME                                   READY   STATUS   RESTARTS AGE
Minio-pet-battle-nsff-594fc7759-j7lwv  1/1     Running     0     88s
minio-pet-battle-nsff-dataload-7x8jg   0/1     Completed   2     88s
minio-pet-battle-nsff-gfjhz            0/1     Completed   3     88s
tensorflowserving-pet...-7f79956d9qfp  2/2     Running     2     85s

几分钟后,Knative Serving TensorFlow pod 将终止,因为它尚未被调用。这就是所谓的无服务器(Serverless)缩放至零,即当没有调用工作负载时,服务不需要运行。可以使用 Knative 命令行工具kn来创建等效的服务,该工具可以从 OpenShift9 控制台下载并安装。如果您希望创建新服务或从头开始开发服务,这是很有用的:

$ kn service create tensorflowserving-pb-nsff --namespace petbattle \
  --image=docker.io/tensorflow/serving:latest \
  --cmd "tensorflow_model_server" \
  --arg "--model_config_file=s3://models/models.config" \
  --arg "--monitoring_config_file=s3://models/prometheus_config.config" \
  --arg "--rest_api_port=8501" \
  --env S3_LOCATION=minio-pet-battle-nsff:9000 \
  --env AWS_ACCESS_KEY_ID=minio \
  --env AWS_SECRET_ACCESS_KEY=minio123 \
  --env AWS_REGION=us-east-1 \
  --env S3_REGION=us-east-1 \
  --env S3_ENDPOINT=minio-pet-battle-nsff:9000 \
  --env S3_USE_HTTPS="0" \
  --env S3_VERIFY_SSL="0" \
  --env AWS_LOG_LEVEL="3" \
  --port 8501 \
  --autoscale-window "120s"

9 docs.openshift.com/container-platform/4.7/serverless/serverless-getting-started.html

在这里,我们使用命令行参数和环境变量来告诉 TensorFlow serving 镜像如何运行。--image 字段指定我们希望运行的容器镜像及版本——在这种情况下,是最新的 TensorFlow serving 镜像。--cmd 字段指定我们希望运行的镜像中的二进制文件,例如,模型服务器命令 tensorflow_model_server--arg--env 变量指定配置。训练好的模型从 S3 minio 服务提供,因此我们指定如何访问 S3 端点。Knative Serving 提供了许多配置选项,如自动扩缩的全局默认设置、指标和追踪。--autoscale-window 定义了自动扩缩器在进行扩缩时所考虑的数据量,因此,在这种情况下,如果两分钟内没有流量,则将 pod 扩缩到 0。

Knative 网站 10 提供了关于使用 Knative Serving 时创建的服务资源以及这些资源配置的更多细节。要查找我们的服务的 URL,可以使用以下命令:

$ kn route list

这为我们提供了测试服务的 HTTP URL 端点。值得注意的是,我们可以拥有多个服务版本,并且在前面提到的路由中,可以在多个版本之间进行流量负载均衡。下图展示了这种做法的实际工作原理:

图 15.3:Knative 对多个应用版本的路由

10 knative.dev/docs/serving/

Kourier11 是一个基于 Envoy 网关的轻量级入口路由器。使用 Knative 服务配置,用户可以指定 Knative Serving 应用的路由规则。这在我们实验不同的 AI 模型或希望进行 A/B、蓝绿部署(Blue/Green)或金丝雀发布(Canary)时非常有用,例如 12。

调用 NSFF 组件

通过路由上的简单 HTTP GET 请求即可调用组件。Pod 会在几秒钟内启动并处理请求,然后在一段时间后(即--autoscale-window,即kn命令行参数中指定的 120 秒)关闭。使用kn list route命令的输出,让我们检查 AI TensorFlow 模型是否可用。state应为AVAILABLE

$ curl <url from kn route list>/v1/models/test_model
# For example
$ curl http://tensorflowserving-pet-battle-nsff-labs-dev.apps.hivec.sandbox882.opentlc.com/v1/models/test_model
{
 "model_version_status": [
  {
   "version": "1",
   "state": "AVAILABLE",
   "status": {
    "error_code": "OK",
    "error_message": ""
   }
  }
 ]
}

11 developers.redhat.com/blog/2020/06/30/kourier-a-lightweight-knative-serving-ingress/

12 medium.com/@kamesh_sampath/serverless-blue-green-and-canary-with-knative-kn-ad49e8b6aa54

我们还应该看到一个 Pod 正在启动来处理请求,使用:

$ oc get pods \
-l serving.knative.dev/configuration=tensorflowserving-pet-battle-nsff \
--namespace petbattle
NAME                          READY   STATUS    RESTARTS   AGE
tensorflowserving-pet…         2/2     Running   0          21s

然后在两分钟后缩小到 0。

我们希望通过发送一些图像来测试我们的 NSFF 服务是否有效。我们有两个经过编码的测试样本图像,可以上传到 NSFF 服务。

图 15.4:NSFF 测试图像

让我们下载这些图像进行测试:

$ wget https://raw.githubusercontent.com/petbattle/pet-battle-nsff/main/requests/tfserving/nsff-negative.json
$ wget https://raw.githubusercontent.com/petbattle/pet-battle-nsff/main/requests/tfserving/nsff-positive.json

现在使用简单的curl命令将它们提交到我们的 NSFF 服务:

$ HOST=$(kn service describe tensorflowserving-pb-nsff -o url)/v1/models/test_model:predict # Daisy Cat - Safe for Families
curl -s -k -H 'Content-Type: application/json \
  -H 'cache-control: no-cache' \
  -H 'Accept: application/json' \
  -X POST --data-binary '@nsff-negative.json' $HOST
{ "predictions": [[0.992712617, 0.00728740077]] }
# Not Safe For Families - Boxing
curl -s -k -H 'Content-Type: application/json' \
  -H 'cache-control: no-cache' \
  -H 'Accept: application/json' \
  -X POST --data-binary '@nsff-positive.json' $HOST
{ "predictions": [[0.30739361, 0.69260639]] }

我们模型的响应是一个包含两个数字的predictions数组。第一个是适合家庭的度量,第二个是不适合家庭的度量,它们加起来为 1。

因此,我们可以看到 Daisy Cat 与我们的摔跤手相比具有非常高的适合家庭评级(0.993),我们可以在 PetBattle API 中使用这一点,以确定是否可以显示任何给定的图像。通过任意测试,我们已经设定了对于我们认为适合在 PetBattle UI 中查看的图像,安全限制为>=0.6。

我们可以通过将nssf.enabled特性标志设置为true并在 bash shell 中使用 Knative 服务的主机名,使用以下命令行重新部署我们的 PetBattle API 服务,以调用 NSFF 服务:

$ HOST=$(kn service describe tensorflowserving-pet-battle-nsff -o url)
$ helm upgrade --install pet-battle-api petbattle/pet-battle-api \
--version=1.0.8 \
--set nsff.enabled=true \
--set nsff.apiHost=${HOST##http://} \
--set nsff.apiPort=80 --namespace petbattle

如果我们现在通过 UI 上传这些测试图像到 PetBattle 并检查 API 服务器,我们可以看到拳击图片具有ISSFF(适合家庭)标志,Daisy Cat 有一个true值:

图 15.5:PetBattle API 保存带有 ISSFF 标志的图像

API 代码将不会返回任何被视为 NSFF 的图片到 PetBattle UI。例如,返回 PetBattle 数据库中所有宠物的 API 代码会通过将ISSFF标志设置为true进行过滤:

@GET
@Operation(operationId = "list",
           summary = "get all cats",
           description = "This operation retrieves all cats from the
                          database that are safe for work",
           deprecated = false, hidden = false)
public Uni<List<Cat>> list() {
  return Cat.find(ISSFF, true).list();
}

现在我们的 API 已经运行起来了,是时候测试它,看它是否按我们的预期运行。

让我们谈谈测试

根据我们与许多开发团队合作的经验,没有什么能像讨论软件是否按预期工作的测试主题一样,能够阻碍许多开发人员的情绪。

对于许多开发人员来说,测试就像是进行牙科检查——很少有人喜欢做,但我们都知道必须做,而且得做得更多。这是一系列必须跨越的桥梁(通常是在压力下)才能让我们精心制作的、手工设计的、工艺精湛的软件作品被接受并进入生产环境。测试人员被视为另一个团队,他们确保我们已经检查了每一个细节,而他们并不欣赏我们为艺术所付出的辛苦。基本上,我们编写代码,把它扔给测试团队,然后他们检查它。

如果你正在阅读前面的段落,并且在心里想“是的,是的,就是我们,就是这样做的”,那我们有个非常坏的消息告诉你。你基本上是在做错事。当每 6-12 个月就进行一次大规模的软件发布时,这种做法或许是有意义的,但在如今更为敏捷、发布频率更高、更快速的组织中,这种做法被认为是笨重且过时的。当然,也有一些例外情况,比如关键控制系统、严格监管的环境等,但对于大多数企业开发人员来说,情况并非如此。

软件的质量是交付团队的责任,从产品负责人编写用户故事到工程师编写代码及相关测试。正如一位睿智的交付经理曾经说过,“测试是一项活动,而不是一个角色。”在高效的软件交付团队中,测试是贯穿整个软件开发生命周期的持续活动。关于测试,我们有一些原则是我们尽量遵守的:

  1. 尽可能进行自动化,但不要自动化到没有人工监督的程度。让用户与待测试的应用程序进行交互总是有价值的,尤其是在进行端到端测试、验收测试和探索性测试时。

  2. 测试代码与生产代码同样重要——两者都需要保持更新,并在不再添加价值时被移除或弃用。

  3. 一次有意义的测试可能比数百个脚本化的测试用例更有价值。

第七章开放技术实践——中点中,我们介绍了自动化测试金字塔的概念。对于金字塔中定义的每种不同类型的测试,我们在 PetBattle 应用程序中使用了多种测试工具和框架。

一般来说,我们选择使用各个应用技术栈中被认为是默认的测试工具,因为这些工具最简单易用,支持最好,用户文档齐全,而且如果有人是新手,通常也很容易上手:

表 15.1:使用的测试框架

让我们更详细地了解一些这些测试。

使用 JUnit 进行单元测试

在 API 和 Tournament 应用程序中,我们有不同的标准单元测试示例。Quarkus 测试 13 对标准单元测试框架 JUnit14 提供了很好的支持。使用此框架的所有单元测试的结构非常相似。让我们以 API 应用程序中的 CatResourceTest.java15 为例:

@QuarkusTest
class CatResourceTest {
  private static final Logger LOGGER = LoggerFactory
                                      .getLogger("CatResourceTest");
  @Test
  void testCat() {
    PanacheMock.mock(Cat.class);
    Mockito.when(Cat.count())
      .thenReturn(Uni.createFrom().item(23l));
    Assertions.assertEquals(23, Cat.count().await().indefinitely());

13 quarkus.io/guides/getting-started-testing

14 junit.org/junit5/

15 github.com/petbattle/pet-battle-api/blob/master/src/test/java/app/battle/CatResourceTest.java

在 Java 中,我们使用注解将 Java 类对象(POJO)转化为测试。我们使用 @QuarkusTest 注解将 JUnit 框架引入该类,并且我们可以将该类视为一个包含多个单独测试的测试套件。每个方法都是一个单独的测试,并使用 @Test 注解。在这个单元测试中,我们没有运行数据库,因此我们使用 Cat.class 的 mocks16 进行模拟。mock 是一个虚拟对象,它不会连接到真实的数据库,我们可以用它来测试 Cat 类的行为。在此案例中,我们在测试中断言,当我们调用方法 Cat.count()(该方法对应于 PetBattle 中宠物图片的点赞数量)时,我们会收到预期的数字(23)。我们使用 Uniawait() 函数,因为我们在 Quarkus 应用程序中使用了响应式编程模型。17

我们将这些单元测试作为自动化持续部署管道的一部分运行,并使用我们的 CI/CD 工具(包括 Jenkins、Tekton 和 Allure 等测试报告工具)来可视化和报告测试的成功和历史记录。18

图 15.6:使用 Allure 可视化测试

在下一节中,我们将继续使用 REST Assured 和 Jest 进行服务和组件测试。

使用 REST Assured 和 Jest 进行服务和组件测试

Jest 和 REST Assured 是 JavaScript 和 Java 的行为驱动开发BDD)框架。我们在第七章开放技术实践 – 中期中讨论了 BDD。这些框架使得开发者能够轻松编写测试,语法直观且容易跟随。

16 quarkus.io/guides/mongodb-panache

17 quarkus.io/guides/getting-started-reactive#mutiny

18 github.com/allure-framework

我们将使用 Jest 覆盖 PetBattle 用户界面的组件测试基础知识 19。该用户界面由多个组件组成。你在打开应用程序时看到的第一个组件是主页。对于主页组件,测试类 20 被命名为 home.component.spec.ts

describe('HomeComponent', () => {
  let component: HomeComponent;
  let fixture: ComponentFixture<HomeComponent>;
  beforeEach(async () => {...
  });
  beforeEach(() => {...
  });
  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

每个测试有一个相似的结构:

  • describe(): 测试套件的名称以及测试规格参数

  • beforeEach(): 在每个测试运行之前,执行作为参数传入的函数

  • it(): 定义一个单一的测试,要求有期望值,并且包含逻辑和断言的函数

  • expect(): 为测试结果创建期望,通常会配合 toEqual() 等匹配函数使用

因此,在这个情况下,单元测试会期望 HomeComponent 在测试运行时被正确创建。

19 angular.io/guide/testing

20 github.com/petbattle/pet-battle/blob/master/src/app/home/home.component.spec.ts

类似地,在 API 应用程序中,REST Assured 是一个测试工具,允许我们使用熟悉的 Given, When, Then 语法编写测试,语法来自 第七章开放技术实践——中期。我们来查看一下测试套件 CatResourceTest.java21 中的一个服务 API 测试:

@Test
@Story("Test pet create")
void testCatCreate() {
    CatInstance catInstance = new CatInstance();
    RestAssured.given()
            .contentType(ContentType.JSON)
            .body(catInstance.cat)
            .log().all()
            .when().post("/cats")
            .then()
            .log().all()
            .statusCode(201)
            .body(is(notNullValue()));
}

在这个测试中,我们创建了一个 Cat 对象。Cat 类是 PetBattle 中的数据对象,包含了宠物的上传图片以及其 PetBattle 投票数,并存储在 MongoDB 中。在测试中,给定一个 Cat 对象,我们使用 HTTP POST 请求 /cats 端点,并期望返回状态码为 (201),即 CREATED。我们还测试了 HTTP 响应体不为空,它应包含新创建的 Cat 的 ID:

@QuarkusTest
@QuarkusTestResource(MongoTestResource.class)
@Epic("PetBattle")
@Feature("PetEndpointTest")
class CatEndpointTest {

21 github.com/petbattle/pet-battle-api/blob/master/src/test/java/app/battle/CatResourceTest.java

在这个服务测试中,我们使用了 @QuarkusTestResource 注解来创建并启动一个嵌入式的 MongoDB 进行测试。因此,这个测试比仅使用 mock 的基本单元测试要更复杂一些。我们还使用我们的测试报告工具来跟踪这些服务测试的执行情况:

图 15.7:使用 Allure 可视化服务测试

现在我们已经看到了单元测试的样子,接下来我们将提升测试层级,看看服务级别的测试。

使用 Testcontainers 进行服务测试

集成测试通常比单元测试更为复杂,因为需要启动或模拟/虚拟化更多的组件。我们测试金字塔的下一个层级是使用一个名为Testcontainers的 Java 框架进行集成测试。22 Testcontainers 允许我们轻松地创建和启动 MongoDB、KeyCloakInfinispan等组件,并使用这些组件执行测试。以下类实例化并管理这些容器,并将其注入到 Quarkus 框架的测试生命周期中:

$ ls src/test/java/com/petbattle/containers/
InfinispanTestContainer.java KeycloakTestContainer.java   MongoTestContainer.java

ITPetBattleAPITest.java的集成测试代码中,我们仅仅是注入了之前创建的容器,并在测试期间将它们作为资源使用:

$ head src/test/java/com/petbattle/integration/ITPetBattleAPITest.java
package com.petbattle.integration;
...
@QuarkusTest
@DisplayName("API Test Cases")
@QuarkusTestResource(MongoTestContainer.class)
@QuarkusTestResource(InfinispanTestContainer.class)
@QuarkusTestResource(KeycloakTestContainer.class)
public class ITPetBattleAPITest {

22 www.testcontainers.org/

这是一个很好的例子,展示了如何在测试阶段使用容器。这些容器被启动,测试执行完毕后,容器被移除。唯一的真正前提是运行测试的机器上需要运行 Docker 守护进程。要运行集成测试,可以使用命令mvn clean verify -Pintegration

端到端测试

我们的应用程序由一个用 Angular 编写的前端组成,该前端向两个 API 发起数据请求。一个是用于比赛的,另一个是用于猫的。我们可以将这些组件之间的相互作用看作是整个系统的一部分。每次对这些独立应用程序中的任何一个进行更改时,都应要求重新验证整个系统。端到端的自动化测试主要在用户界面中执行,但也涵盖了底层服务层。

有很多工具可以从用户界面层面进行测试。比较流行的工具包括 Selenium 和 Cypress,它们用于驱动 Web 应用程序并模拟用户行为。每个工具都有优缺点——Selenium 仅仅是浏览器自动化,所以你需要自己带上测试框架,而 Cypress 是一个一体化的测试框架。当 Selenium Grid 在 Kubernetes 上运行时,可以通过在每次测试执行时动态地为每个测试分配浏览器来让我们并行地在多个浏览器上进行测试,这意味着我们不需要让浏览器闲置等待使用。

对于我们的端到端测试,我们使用了 Angular 团队的 Protractor。我们在部署工具时,已经部署了由 Zalando 团队为 Kubernetes 构建的 Selenium Grid 实例(称为 Zalenium opensource.zalando.com/zalenium/)。Zalenium 非常方便,因为它允许我们回放之前的测试并实时观看它们。在你的集群中,如果你获取 Zalenium 的路由(oc get routes -n labs-ci-cd)并附加/grid/admin/live,你可以在测试执行时跟踪它们,或者访问/dashboard观看历史测试执行。

图 15.8:Zalenium 仪表板,显示测试历史和视频回放

我们的system-tests项目(github.com/petbattle/system-tests)包含了在每次推送到前端或后端服务后需要执行的所有系统测试。测试是使用 Cucumber 风格的 BDD 编写的。实际上,我们应该能够将 BDD 与 PetBattle Sprint 项目的接受标准连接起来。

图 15.9:在 PetBattle 的 Sprint 看板上,作为接受标准编写的 BDD 示例

这是一个使用 Given, When, Then 语法编写的比赛功能测试:

Feature: Tournament features
  Scenario: Should only be prompted to login on navigating to the
            tournament
    Given I am on the home page
    When  I move to the tournament page
    Then  I should be redirected to keycloak

system-test 项目有自己的 Jenkinsfile,因此它已经通过我们的种子任务连接到 Jenkins。我们不会详细介绍这个 Jenkinsfile 的内容。简单来说,管道有两个阶段,符合我们的大致图景,一个用于运行测试,另一个用于在测试通过时提升应用程序。在随附的 Git 仓库中探索此代码:github.com/petbattle/system-tests。为了扩展我们 pet-battle 的 Jenkinsfile 以触发我们的系统测试任务,我们只需要添加另一个阶段来触发该任务。我们可以使用 Jenkins 的 post{} 块,但我们只希望在 mastermain 上并生成候选发布版本时触发系统测试。

图 15.10:在 Jenkins 中触发连接我们管道的触发器

有一些参数会在任务之间传递:

  • APP_NAME:传递给任务,因此如果测试成功,提升阶段就知道要部署哪个应用。

  • CHART_VERSION & VERSION:对图表或应用的任何更新都需要在 Git 中修补,因此这些信息会由触发系统测试的任务传递。

我们可以通过向任务提供这些信息手动运行系统测试任务,但每个具有 Jenkinsfile 的服务应该能够将这些信息传递给系统测试。此任务也可以从 Tekton 触发,如果我们混合使用管道方法。将两个管道连接起来后,只需在设置好 webhook 后运行以下命令即可触发其中一个:

$ git commit --allow-empty -m "🍌 kickoff jenkins 🦆" && git push

如果我们现在检查 Jenkins Blue Ocean Web UI,我们应该看到以下内容:

图 15.11:系统测试管道

在 Jenkins 中,我们应该看到系统测试管道在运行并在成功时进行提升。该任务还包括 Cucumber 报告。

图 15.12:Jenkins 中的 Cucumber 报告

这些报告提供了有关为哪个浏览器执行了哪些用例的见解,并报告任何可能发生的失败。现在让我们稍作调整,看看非功能性测试。

管道与质量门(非功能性测试)

质量通常只是关注测试是否通过。然而,也有代码质量的概念。代码可能按预期执行,但编写方式可能非常糟糕,以至于在添加新更改时,未来可能成为问题的源头。所以现在是时候检查我们代码的质量了。

SonarQube

作为普适之旅的一部分,我们已经自动化了 SonarQube 的 Helm 图表部署,利用它来测试和衡量代码质量。在 values-tooling.yaml 文件中,SonarQube 语句引用了 Helm 图表以及所需的额外插件。许多常见的语言配置插件已经随着 SonarQube 基础版本一同部署,例如 Java、JavaScript 和 Typescript。我们为 Checkstyle(我们的 Java 格式化检查工具)和一个用于检测项目依赖项中公开披露的漏洞的依赖检查器,添加了额外的插件条目:

  # Sonarqube
  - name: sonarqube
    enabled: true
    source: https://github.com/redhat-cop/helm-charts.git
    source_path: "charts/sonarqube"
    source_ref: "sonarqube-0.0.14"
    sync_policy: *sync_policy_true
    destination: *ci_cd_ns
    values:
      initContainers: true
      plugins:
        install:
          - https://github.com/checkstyle/sonar-checkstyle/releases/download/8.35/checkstyle-sonar-plugin-8.38.jar
          - https://github.com/dependency-check/dependency-check-sonar-plugin/releases/download/2.0.7/sonar-dependency-check-plugin-2.0.7.jar

在部署了基本的 SonarQube Pod 后,我们需要自动化的最后一部分配置——创建代码质量关卡。质量关卡是代码必须通过的门槛,才能被认为准备好发布。这归结为在代码中定义的一组条件,指定了特定的测量标准,例如:

  • 我们添加的代码是否有新的阻塞问题?

  • 代码的测试覆盖率是否高于指定的百分比?

  • 是否存在可识别的代码漏洞?

SonarQube 让我们通过其 REST API 定义这些质量关卡 23。对于 PetBattle,我们使用 Kubernetes 作业来定义我们的质量关卡 AppDefault,并将其打包为 Helm 图表进行部署。该图表通过普适之旅和 ArgoCD 部署。

图 15.13:SonarQube 质量关卡定义

SonarQube 服务器可以通过 REST API 查询,查看特定项目的最近报告是否通过了质量关卡。我们在管道中配置了一个 Tekton 步骤和任务,以便每次运行构建时自动检查这一点。

我们的 PetBattle Java 应用程序使用 Maven 配置,连接到我们的 SonarQube 服务器 Pod,并在每次构建、打包和部署时生成 SonarQube 格式的报告。在可重用的 maven-pipeline.yaml 文件中,我们调用以下目标来生成这些报告:

# code analysis step maven pipeline     - name: code-analysis
      taskRef:
        name: maven
      params:
        - name: MAVEN_MIRROR_URL
          value: "$(params.MAVEN_MIRROR_URL)"
        - name: MAVEN_OPTS
          value: "$(params.MAVEN_OPTS)"
        - name: WORK_DIRECTORY
          value: "$(params.APPLICATION_NAME)/$(params.GIT_BRANCH)"
        - name: GOALS
          value:
            - install
            - org.owasp:dependency-check-maven:check
            - sonar:sonar
        - name: MAVEN_BUILD_OPTS
          value:
            - '-Dsonar.host.url=http://sonarqube-sonarqube:9000'
            - '-Dsonar.userHome=/tmp/sonar'
# code analysis step nodejs pipeline     - name: code-analysis
      taskRef:
        name: nodejs
      params:
        - name: NPM_MIRROR_URL
          value: "$(params.NPM_MIRROR_URL)"
        - name: GOALS
          value:
            - "run"
            - "sonar"

23 docs.sonarqube.org/latest/user-guide/quality-gates/

类似地,对于使用 nodejs 的 PetBattle UI,我们可以配置客户端在其 Tekton 管道中调用 SonarQube。一旦这些步骤成功执行,我们可以探索 SonarQube Web UI,并深入了解任何区域以获取更多信息。

图 15.14:SonarQube 项目视图

在最近关于 PetBattle UI 的 A/B 测试支持的开发过程中,一些代码错误似乎悄悄出现了!开发人员可以深入查看并精确找出问题所在,并在代码库中进行修复。SonarQube 根据在语言质量配置文件中定义的严重性对问题进行排名,该配置文件可以根据您的开发代码质量需求进行调整。

图 15.15:SonarQube 挖掘一些错误详情

SonarQube 还会报告上次运行的代码测试覆盖率。在代码库方面,您可以使用 LCOV24 格式生成覆盖率报告,因此在 Java 中,这由 JaCoCo25 完成,而在 JavaScript 中,覆盖率报告由 mocha/jasmine 模块生成。这些报告被上传到 SonarQube,为团队提供了关于代码库哪些部分需要更多测试的可视化信息。查看这些信息的一个很好的方式是使用热力图,它将覆盖率接近 100%(绿色)的代码块与完全没有覆盖的代码块(红色)进行了可视化。统计信息也会被报告,包括整体覆盖率百分比、覆盖的行数等。

图 15.16:PetBattle 的 SonarQube 测试覆盖率热力图

24 github.com/linux-test-project/lcov

25 www.eclemma.org/jacoco/

我们用于 Java 应用程序的最后一个插件是 OWASP Dependency-Check 插件。26 我们将安全检查提前到开发流水线的“左侧”。换句话说,我们希望在开发过程的早期发现安全漏洞或 CVE 是如何潜入到我们应用程序的依赖项中的。通过在构建周期的早期识别出哪些依赖项存在 CVE 漏洞,开发人员能够更好地更新这些依赖项,而不是等到应用程序部署后才发现问题。

图 15.17:Dependency-Check 插件报告

该插件从多个开源资源获取数据,包括美国国家漏洞数据库 27 和 Sonatype OSS Index。28 与安全团队成员配合,开发人员可以验证已知漏洞,并通过配置文件抑制任何误报。报告非常详细,包含了这些网站的链接,以帮助 CVE 的识别和报告。

26 github.com/dependency-check/dependency-check-sonar-plugin

27 nvd.nist.gov/

28 ossindex.sonatype.org/

性能测试(非功能性)

我们最喜欢的命令行工具之一,用于快速反馈 REST API 性能的是一个叫做hey的工具。29 还有很多类似的工具可用。Apache Bench30 可能是最具历史的。

图 15.18:一个简单的 hey 运行

29 github.com/rakyll/hey

30 httpd.apache.org/docs/2.4/programs/ab.html

我们喜欢在命令行中使用 hey 来调用 PetBattle API 并列出所有的宠物。我们传入一些表示以下内容的参数:

  • -c: 要并发运行的工作者数量

  • -n: 要运行的请求数量

  • -t: 每个请求的超时时间(秒)

我们可以看到报告的汇总统计信息,而这正是我们喜欢的部分——延迟分布的直方图、HTTP 状态码分布以及 DNS 定时详细信息。这是非常丰富的信息。直方图是显示连续响应延迟数据分布的图表。直方图揭示了汇总统计无法提供的响应时间特性。在统计学中,汇总数据用于描述完整的数据集——例如,最小值、最大值、均值和平均值。Hey 在输出的顶部为我们提供了这些汇总统计信息。

图表让数据栩栩如生,因为我们可以开始理解在测试运行的时间内,延迟响应的分布情况。在发送 100 个请求所花费的 4.2 秒内,我们可以看到大多数数据集中在 0.4 秒左右,这几乎占了所有流量的 50%。通常,在服务性能设计中,我们关心的是 95% 或 99% 百分位数。也就是说,对于所有的样本数据,95%(或 99%)的流量响应延迟是多少。在此次测试中,测得值为 0.57 秒——换句话说,95% 的数据响应时间都在或低于这个值。

直方图的形状也很重要。响应延迟在哪里聚集?我们可以很容易地看出响应时间是否均匀分布在平均值(高斯分布)周围,或者它们是否有更长或更短的尾部。这有助于我们在不同负载下表征服务的性能。你可以使用多种负载配置文件,例如,在瞬时流量激增时,我们向 API 发起大量请求,与低负载下的长期浸泡测试相比。你甚至可能已经有类似应用程序的已知负载。这些类型的测试负载的设计非常适合使用开源工具 Apache JMeter31,它能很好地建模线程和负载增加。我们强烈推荐将它作为工具箱中的一个工具。为了简单起见,本文不会覆盖这个工具。

31 jmeter.apache.org/

图 15.19 中的两个图表展示了简单的负载测试。左边的是一个突发类型的测试——300 个连续用户对我们的 PetBattle API 进行 900 次调用。我们可以看到 95% 响应时间是 15.6 秒——对于用户来说,这个等待时间相当长!右边的是一个持续测试——50 个连续用户对我们的 PetBattle API 进行了 10,000 次调用。统计数据非常不同:测试时长为 461 秒,95% 响应时间是 2.8 秒——从最终用户的角度来看,这要好得多。

在这个阶段,重要的是要思考测试实际在做什么,以及它与 PetBattle 应用套件的关系。如果我们仔细想想,这个测试可能并不能完全反映当前用户界面的行为。例如,我们并不会一次性调用返回 MongoDB 中的所有图片,而是分页返回结果。当然,还有其他 API 端点需要测试,比如 topcats API,它返回最受欢迎的前三只宠物,并在每次访问主页时被调用。我们返回的是加载到 PetBattle 中的测试数据集,也就是大约 15 张宠物图片,所以数据量并不庞大。在进行性能测试时,始终要从整体上考虑,了解更广泛的背景,这样才能避免测试错误的内容!

图 15.19:针对 PetBattle API 的突发和持续测试

尽管如此,这些数据值得深思。一个好的结果是,突发和持续测试都只返回了 HTTP 200 响应状态——API 没有返回错误响应。这让我们有信心,说明我们没有破坏任何功能,也没有达到任何内部系统限制。我们还可以检查详细信息,确保 DNS 解析不会在客户端调用过程中造成问题。

现在我们已经熟悉了性能测试的客户端或调用端,让我们转到服务器端运行的 PetBattle API 应用。如果我们浏览到开发者视图,并选择 labs-test 命名空间中的 pet-battle-api Pod,就能看到一些重要的服务器端信息:

  • PetBattle API 自动扩展为两个 Pod。

  • Pods 的监控指标(如果你尚未启用 CRC,请查看附录)。

作为开发者,我们已经将 PetBattle API 应用配置为使用 水平 Pod 自动扩展器HPA)。这指定了 OpenShift 容器平台如何根据从属于我们应用的 Pods 收集的指标,自动增加或减少复制控制器或部署配置的规模,即运行的 Pods 数量。

图 15.20:实验室测试命名空间中的 PetBattle API Pod

在我们的 PetBattle API Helm 图表中,我们为 HPA 指定了最小 Pods、最大 Pods 以及平均 CPU 和内存目标的可配置值。通过使用 hey 工具,我们现在可以测试各种场景,帮助我们在负载下调整 PetBattle API 应用:

apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: {{ include "pet-battle-api.fullname" . }}
  labels:
    {{- include "pet-battle-api.labels" . | nindent 4 }}
spec:
  scaleTargetRef:
    {{- if .Values.deploymentConfig }}
    apiVersion: v1
    kind: DeploymentConfig
    {{- else }}
    apiVersion: apps/v1
    kind: Deployment
    {{- end }}
    name: {{ include "pet-battle-api.fullname" . }}
  minReplicas: {{ .Values.replicas.min }}
  maxReplicas: {{ .Values.replicas.max }}
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: AverageValue
          averageValue: {{ .Values.hpa.cpuTarget }}
    - type: Resource
      resource:
        name: memory
        target:
          type: AverageValue
          averageValue: {{ .Values.hpa.memTarget }}

我们最初在 HPA 中粗略猜测了这些设置,例如,min replicas = 2max replicas =6CPU = 200mmem = 300Mi,并在我们的 Deployment 中适当设置了资源限制和请求。我们始终保持至少两个 pod,以确保高可用性。HPA 配置为根据平均内存和 CPU 负载进行扩展。我们尚不清楚应用程序是内存密集型还是 CPU 密集型,因此选择基于这两个度量来扩展。

图 15.21:PetBattle API HPA 正在运行,在负载下扩展 pod

我们使用 hey 启动一个突发工作负载,进行 400 个并发请求,并观察 HPA 的行为,看看它如何启动更多的 pod 以保持指定的内存和 CPU 平均值。一旦测试结束,HPA 会将我们的工作负载缩减到最小,因为应用程序通过 Java 垃圾回收恢复资源。OpenShift 支持 HPA 的自定义指标以及其他类型的 pod 自动伸缩器,例如,垂直 Pod 自动伸缩器。32

结束这一部分时,我们要指出开发人员工具箱中还需要另一个 Kubernetes 对象——Pod 中断预算PDB)。同样,使用 Helm Chart 模板配置 PDB,我们可以限制 PetBattle API 应用程序所经历的并发中断次数。通过设置 PDB,我们可以在允许集群管理员管理集群节点生命周期的同时,提供更高的可用性。

32 docs.openshift.com/container-platform/4.7/nodes/pods/nodes-pods-using.html

如果集群正在更新并且节点正在重启,我们希望始终保持至少一个pet-battle-api pod 可用:

$ oc get pdb 
NAME       MIN AVAILABLE MAX UNAVAILABLE  ALLOWED DISRUPTIONS   AGE
pet-battle-api   1           N/A                  1             46h

这确保了我们的 PetBattle API 具有高水平的业务服务。我们可以看到ALLOWED_DISRUPTIONS被设置为 1——这是因为在此时,HPA 已将可用副本数量扩展至 3,且这一设置会随着可用 pod 数量的变化而变化。

在 OpenShift 上进行应用程序性能测试的一个优点是,开发人员可以轻松地配置、测试、测量和调整他们的应用程序,以便在负载下实现高可用性和高性能。每个应用程序服务都是独立可扩展、可调优和可部署的,这使得在处理规模和性能问题时,反馈循环更快、更具针对性。

在下一部分,我们将看看如何成为一个合格的 OpenShift Kubernetes 公民,自动化 Kubernetes 资源验证作为我们流水线的一部分。

资源验证

测试的一个方面目前尚未引起足够关注,那就是部署到集群中的 Kubernetes 资源的质量。为了让应用程序被视为 Kubernetes 中的好公民,必须遵循一系列部署最佳实践——包括健康检查、资源限制、标签等——我们将在第十六章,掌控它中详细讲解其中的多个方面。然而,我们需要验证应用于集群的资源定义,以确保高度符合行业推荐的最佳实践,以及我们认为适合添加的其他资源建议。这时,Open Policy Agent (OPA)33 及其相关工具就派上用场了。它使我们能够在 CI 流水线中以及在将资源应用于集群时验证资源定义。OPA 本身是一个策略验证器,策略是使用一种名为 Rego 的语言编写的。额外的 OPA 工具,如 Conftest34 和 Gatekeeper35,从可用性和部署的角度提供了很大的价值和治理。OPA 还可以嵌入到其他第三方工具中,如 KubeLinter36。

33 www.openpolicyagent.org/

34 github.com/open-policy-agent/conftest

35 github.com/open-policy-agent/gatekeeper

36 github.com/stackrox/kube-linter

我们并没有在 PetBattle 中使用 OPA 的服务器端验证组件 Gatekeeper37,但在 Red Hat 实践社区的 GitHub 仓库 38 中有一些示例 Rego 策略,值得深入探索。如果你对此感兴趣,强烈建议查看 OpenShift.com 上关于设置所有这些组件的博客 39。

然而,为了展示如何轻松使用客户端资源验证,以及为什么在流水线中至少应包含一些资源验证,我们创建了一个简单的 Rego 示例。Rego 策略足够容易编写,Rego playground40 是一个很好的地方来编写和验证策略,欢迎去看看。

让我们来看一个示例。在我们的非功能性需求地图中,我们提到过希望在标签命名上保持一致性。根据 Kubernetes 最佳实践,我们应该使用app.kubernetes.io/instance标签并将其应用到所有资源上。那么,接下来我们看看如何为此编写一个测试,并将其添加到我们的 Jenkins 流水线中。

一个拒绝创建资源的策略的构成相当简单。如果规则中的所有语句都为真,那么一条消息会形成并返回给解释器。例如,我们编写了一个策略,检查所有资源是否符合 Kubernetes 最佳命名惯例。此策略正在检查 app.kubernetes.io/instance 是否存在于提供给它的资源(input)上。如果每个语句都为真,那么将返回一条错误消息,指导用户修复问题:

deny[msg] {
  label := "app.kubernetes.io/instance"
  not input.metadata.labels[label]
  msg := sprintf("\n%s: does not contain all the expected k8s labels
                 in 'metadata.labels'.\n Missing '%s'. \nSee: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels", [input.kind, label])
}

37 github.com/open-policy-agent/gatekeeper

38 github.com/redhat-cop/rego-policies

39 www.openshift.com/blog/automate-your-security-practices-and-policies-on-openshift-with-open-policy-agent

40 play.openpolicyagent.org/

我们可以将这个规则与 Conftest 和 Helm 模板结合,创建一种静态验证资源的方式。在 PetBattle 前端代码中,有一个策略文件夹,里面有一些额外的策略,用于检查在运行 helm template 命令后,所有标准 Kubernetes 标签 41 是否已设置在我们生成的资源上。通过运行一些命令,我们可以验证这些资源是否已到位。首先,我们使用模板化图表来生成我们在部署软件时将应用的 Kubernetes 资源,其次,我们告诉 Conftest 根据规则检查每个生成的文件:

# from the pet battle front end repository (https://github.com/petbattle/pet-battle.git)
$ for file in $(ls policy/helm-output/pet-battle/templates/); do conftest test policy/helm-output/pet-battle/templates/$file; done
6 tests, 6 passed, 0 warnings, 0 failures, 0 exceptions
FAIL - policy/helm-output/pet-battle/templates/deploymentconfig.yaml - 
DeploymentConfig: does not contain all the expected k8s labels in 'metadata.labels'.
 Missing 'app.kubernetes.io/name'. 
See: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels
6 tests, 5 passed, 0 warnings, 1 failure,  0 exceptions
6 tests, 6 passed, 0 warnings, 0 failures, 0 exceptions
6 tests, 6 passed, 0 warnings, 0 failures, 0 exceptions

当从命令行执行规则时,我们可以清楚地了解图表中缺少的内容。当然,我们可以假设始终让我们的图表遵循最佳实践,但 jenkins-agent-helm 也包含了 Conftest 二进制文件,因此我们也可以在 Jenkins 流水线中执行前述语句。这个例子看起来可能很简单,但希望它能给你一些关于可以自动化和测试的事情的思路,尽管这些可能看起来不那么明显。

41 kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/#labels

图像扫描

Red Hat 在 OpenShift 中提供了 Quay 容器安全操作员,将 Quay 和 Clair 图像扫描及漏洞信息引入我们的 OpenShift 集群。任何托管在 Quay.io 上的容器镜像都会被 Clair 扫描。

图 15.22:Quay 容器安全操作员

任何图像漏洞数据都会通过 OpenShift Web UI 显示,用户和管理员可以轻松查看哪些镜像被认为是漏洞镜像,以及它们部署到哪个命名空间。

图 15.23:易受攻击的容器镜像

部署此操作符后,OpenShift 概览状态会显示镜像漏洞数据,操作员可以深入查看,了解平台上运行的容器镜像的状态。对于 PetBattle,我们并没有对集群中发现的镜像漏洞进行强制执行。如果我们想要将安全扫描工具“向左移动”到我们的部署流水线中,OpenSCAP 网站上有一些很棒的开源扫描工具可供使用。42

其他非功能性测试

还有许多其他类型的测试可以用来验证我们的应用程序。在本节中,我们列出了一些我们认为在流水线中重要的内容,但实际上,这个话题远不止这些内容,甚至可以专门写书来探讨!

Linting

Linter 是一种静态代码分析工具,可以检查代码库中常见的设计陷阱或风格错误。它不会检查已编译的应用程序,而是检查应用程序的结构。这对于那些没有编译的语言(如 JavaScript)非常重要。浏览器可以以不同的方式解释 JavaScript,因此一致性至关重要。

如果考虑到一个大型企业应用程序,可能会有数百个开发人员在同一个代码库上工作。这些开发人员甚至可能是全球分布的,不同的团队负责应用程序生命周期的不同部分。编写软件时保持一致性,可以显著降低维护成本。JavaScript 在编写方式上非常灵活,无论是从函数式编程的角度还是面向对象的角度,因此确保这种一致性至关重要。

PetBattle 前端使用 TSLint/ESLint43 来检查代码风格是否符合一套标准规则。这些规则可以由团队进行调整,但规则会被提交到 Git 中,因此如果有人禁用了它们或更改了它们,都会被发现。我们的 Jenkins 流水线配置为自动检查代码库,使用 npm lint 命令,如果开发人员没有遵守标准,构建会失败。

42 www.open-scap.org

43 eslint.org/

图 15.24:Linting PetBattle 的前端本地扫描 JavaScript 和 HTML

对于 Java Quarkus 应用,使用 Checkstyle44 来分析代码库。

对于 Kubernetes 资源,前述的 Open Policy Agent 可以提供帮助,Helm 也有 helm lint45 命令来验证你的图表。

代码覆盖率

所以,你已经编写了大量测试,并且认为一切顺利——但你怎么知道你的测试是否足够好,覆盖了代码库的所有部分呢?让我来介绍一下代码覆盖率指标!代码覆盖报告器是一个与单元测试套件一起运行的软件,用来查看测试执行了哪些代码行,以及这些行被执行了多少次。覆盖报告还可以突出显示应用程序中的 if/else 控制流是否未被测试。这一洞察可以提供有关系统中未测试区域的宝贵反馈,从而减少错误数量。

44 checkstyle.sourceforge.io/

45 helm.sh/docs/helm/helm_lint/

我们的 PetBattle 前端配置为在执行 Jest 测试时运行覆盖报告。Jest 使得生成报告变得非常简单,因为它有一个标志,可以传递给测试运行器来为我们收集覆盖率。每次构建执行时,都会运行覆盖报告,因此应该通过 Jenkins 进行报告。

图 15.25:来自前端单元测试的代码覆盖率报告(本地)

在 Jenkins 流水线中执行我们的测试时,我们已配置 Jest 生成 HTML 报告,Jenkins 可以在作业页面上显示该报告。对于每次构建执行,报告都会添加到作业的主页上。该报告将帮助我们发现哪些代码行没有被测试覆盖。能够深入查看这样的报告,可以让我们更好地了解测试的不足之处。

图 15.26:Jenkins 中的代码覆盖报告为我们提供了详细的洞察

那么,我应该如何处理这些结果呢?从历史上看,我们通常会集中精力在覆盖率较低的地方进行工作。这可以成为回顾会议中的一个很好的讨论点。打印出报告并作为团队讨论,是评估团队为何难以编写足够测试的一个很好的方式。有时,团队在压力下被迫快速交付功能,因此测试可能会被忽视。在构建过程中添加代码覆盖报告器,可以帮助团队保持透明度。你甚至可以设置阈值,确保如果测试覆盖率低于某个百分比(有些团队目标是 80% 或更高),构建将失败,从而阻止流水线,直到质量提高。

未测试软件水印

很久以前,我参与了一个结构很差的项目。我是 DevOps 团队的一员,现在我知道这在大多数实现中都是一种反模式!这个项目有很多问题,团队提前计划了三次 Sprint,但没有为测试分配足够的时间。测试总是最后被压缩的任务。

通过与团队的回顾,我们发现根本没有足够的时间进行测试。听起来可能让人难以接受,但问题的根本原因并不是团队的懒惰或技能不足,实际上就是时间问题。项目是按 8 周的周期进行的,从一开始就已经预先规划好,并且最终有一个固定的输出。团队以为他们在做 Scrum,但实际上,他们每个冲刺都在完成功能性里程碑,而没有反馈循环。当然,Scrum 团队的成员也没有参与到工作量估算或规划的环节中。这意味着团队一直处于交付压力之下。

通过回顾,我们决定尝试减轻团队面临的部分压力,因为我们不满意为了某些任意的截止日期而牺牲了质量。我们知道,简单地让管道失败对这些客户来说是行不通的,因此我们不得不在展示软件质量时发挥创意。我们决定在任何测试覆盖率低的应用程序中注入一个水印。这个水印类似于你在任何文档上看到的“DRAFT”标志,但我们的水印略有不同。

一条巨大的横幅上写着“UNTESTED SOFTWARE”(未经测试的软件),它被放置在未通过测试的应用程序上。这种水印不会影响应用程序的用户行为;它只是一个覆盖层,但它是一个让人们开始讨论的绝妙方式。看到一条巨大的“UNTESTED”横幅,肯定会让人们质疑为什么事情会变成这样。

让我们看一些其他方法,看看在持续交付过程中如何可视化风险。

OWASP Zed Attack Proxy (ZAP)

安全扫描始终是一个热门话题。从我们之前讨论的图像扫描,到我们管道中应用程序的依赖检查,从安全角度看,自动化的内容几乎是无穷无尽的。让我们看另一个可以在管道中包含的有用工具——OWASP Zed Attack Proxy。46

来自他们网站的信息:OWASP Zed Attack Proxy (ZAP)是世界上最流行的免费安全工具之一,它可以让你自动发现应用程序中的安全漏洞。这使得开发人员可以自动化 渗透测试和应用程序的安全回归测试,在 CI/CD 管道中进行。

将 ZAP 安全扫描工具添加到我们的管道中非常简单。只需添加以下stage并添加你想要测试的 URL 即可。此图像的源代码可以找到,像我们其他的 Jenkins 图像一样来自 Red Hat CoP。47 Jenkins 中的 ZAP 扫描将生成一个报告,显示我们应用程序中的一些潜在漏洞。

stage('🤖 OWASP Scan') {
  agent { label "jenkins-agent-zap" }
  steps {
    sh '''
      /zap/zap-baseline.py -r index.html -t http://<some website url> || return_code=$?
      echo "exit value was  - " $return_code
    '''
  }
  post {
    always {
      // publish html
      publishHTML target: [
          allowMissing: false,
          alwaysLinkToLastBuild: false,
          keepAll: true,
          reportDir: '/zap/wrk',
          reportFiles: 'index.html',
          reportName: 'OWASP Zed Attack Proxy'
        ]
    }
  }
}

46 www.zaproxy.org/

47 github.com/redhat-cop/containers-quickstarts/tree/master/jenkins-agents

通过这样做,可以在 Jenkins 中查看创建的 Web 报告,报告详细说明了安全漏洞的原因以及应采取的修复措施。

图 15.27:PetBattle 的 Zap 报告示例

在最终的非功能性测试部分,我们来看一下使用一种叫做混沌工程的技术故意破坏我们的代码。

混沌工程

混沌工程是故意破坏、限制或影响系统的过程,目的是观察其表现及是否能在随之而来的“混乱”中恢复。虽然大多数测试被视为了解系统在已知稳定状态下如何表现的尝试,但混沌工程则是计算机领域中的“放开公牛在精致的瓷器店中乱跑”——你知道结果会很糟,但不知道具体糟到什么程度。

混沌工程的目的是建立对系统韧性的信心。它还让你更好地理解故障点发生的位置以及任何故障的冲击范围。Kubernetes API 规范中内置了许多韧性特性。Pod 副本可能是最简单的机制,确保在任何给定时间都有多个应用程序实例在运行。使用应用程序特定的机制(如断路器)也很重要,这些机制可以防止故障在系统中蔓延。混沌工程将这些理念向前推进了一步,测试当一个或多个组件完全或部分失效时的系统表现,例如当 CPU 或内存资源不足时。

基本的前提是,在测试的系统处于稳定工作状态时进行观察,然后注入故障。接着观察系统是否能够成功从故障中恢复。通过这种测试,可以得出潜在的调优/修复领域,并了解系统的平均恢复时间MTTR)。需要注意的是,混沌工程关注的是整个系统——应用程序和基础设施性能都需要考虑并进行测试。

混沌工程背后的一个关键原则包含在其定义原则中 48——在系统出现大规模异常行为之前,需要识别其弱点

这是采用这种方法时需要考虑的最重要方面之一。你不希望在影响生产的事件中才发现系统的弱点。这类似于定期测试灾难恢复计划的原理。换句话说,Red Hat 的一位同事曾说过,“当屎飞到风扇上时,首先要做的就是关掉风扇!”那时没有时间去学习。

有许多工具和框架可以帮助设置混沌工程实践。以下是一些入门工具(尽管还有其他工具):

  • Litmus Chaos49

  • Kraken50

  • Chaos Mesh51

48 principlesofchaos.org/

49 litmuschaos.io/

50 github.com/cloud-bulldozer/kraken

51 chaos-mesh.org/

在一个像“万物皆代码”和 GitOps 这样的实践是我们构建软件以及支撑这些软件的系统的唯一方式的世界里,验证能否应对丢失项目的一种很好的方法就是每周或每晚从头开始重新部署一切,包括你的基础设施!这听起来可能很极端,但这是验证没有任何隐藏的魔法的好方法,确保没有人忘记写下来或编码。

意外混乱测试

这是一个我曾经不太愿意分享的故事,但随着时间的推移(而且已经经历了两次),我意识到其实做这件事是件好事。

在为一家航空公司工作时,我不小心删除了 labs-ci-cd 项目以及几个其他部署了我们应用的命名空间,包括我们集群的认证提供者。当时,我们的开发已经进行好几周了。我们习惯了重新部署应用程序,删除像 Nexus 或 Jenkins 这样的 CI 工具对我们来说并不是什么大问题,因为我们知道自动化会迅速启动并重新部署它们。

然而,在这次工作中,我们也使用了 GitLab,不幸的是,GitLab 和这些其他工具在同一个项目里!

我首先和团队确认,询问是否可以重建我们工具命名空间中的所有内容。我得到了队友们响亮的“可以”答复,于是我开始删除一些我认为需要清理的东西,并不小心删除了几个额外的项目。大约 30 秒后,团队中的某个人突然警觉地问道,Git 对其他人也无法使用吗? 紧接着又有人说,有人无法登录到集群吗? 我脸马上变得通红,意识到我刚才做了什么。正如我们在书中所说,Git 是我们唯一的真理来源。如果它不在 Git 里,那就不是真的 是我们的座右铭!我们甚至把它写在了墙上!但我刚刚把它删除了。

那么,当某个傻乎乎的人不小心删除了它会发生什么呢?在最初的震惊和恐慌过后,团队拉下了安灯线(Andon Cord)。我们迅速聚在一起,查看究竟发生了什么,以便规划如何恢复不仅仅是 Git,还包括我们添加到集群中的所有内容。幸运的是,所有的工作都存储在 Git 中,所以我们能够重新部署我们的工具,并将我们本地的分布式软件和基础设施副本推送回共享的 Git 仓库中。

团队是跨职能的,拥有我们响应这一问题所需的所有工具和权限。在 1 小时内,我们已经完全恢复了所有应用程序和工具,并且所有自动化都再次顺利运行。

我认为这个例子中的真正力量在于,给定合适的设备和所有权,赋能的团队可以做到一切。我们像一个整体一样,以闪电般的速度解决问题。我们没有被卡在队列中,也不需要向另一个团队提交工单来恢复我们的基础设施。我们能在几分钟内自己搞定——而不是几天或几周后。

我还学到了一点,那就是不要将 Git 与其他工具放在同一个项目中,以防像我这样的人再次出现。我还学会了要注意我们在集群中拥有的权限。作为管理员,我曾能够删除一些我本不应该动的东西。

所以我们已经写好了代码,进行了测试,质量检查,并扫描了漏洞。现在是时候将它部署到集群中了。让我们来探索 Kubernetes 的一个关键好处——根据需求进行应用部署的不同方式,并进行以用户为驱动的实验,以确定用户更喜欢哪些功能。

高级部署

从软件编写和测试完成到部署到生产环境的时间应该尽可能短。这样,您的组织就能尽快从软件变更中获得价值。现代解决这个问题的方法当然是通过自动化。部署到生产时需要更改的细节和配置项实在是太多了,甚至像 PetBattle 这样的小型应用套件,手动部署也变得容易出错且繁琐。减少手动劳动的驱动正是我们在本书中所探索的许多 DevOps 实践的核心。

通过采用合适的应用架构并结合 OpenShift 提供的众多平台功能,我们可以将软件部署变更过程中的停机时间降到最小(理想情况下为零!)。让我们来看看 OpenShift 支持的一些常见部署策略:

  • 滚动部署:

    • 启动新版本的 Pod,然后自动关闭现有旧版本的 Pod。这对于零停机时间的方案非常有用。
  • 金丝雀部署:

    • 启动新版本的单个 Pod,进行测试以确保一切正常,然后将所有旧的 Pod 替换为新的。
  • 蓝绿部署:

    • 创建一个并行部署,并在切换流量之前验证一切是否正常。

    • 服务网格流量镜像功能可以在这种方法中发挥作用,用来验证新版本是否按预期工作。

  • 重新创建部署:

    • 基本上,将现有的 Pods 缩放到零,然后启动新版本。

    • 用于必须重启应用程序的情况,例如迁移数据库架构或表。

    • 把这个想象成一次 Ripley 部署:“从轨道上起飞并摧毁整个站点。这是唯一能确保的方式。”52

我们可以使用 Helm 图表生命周期或开箱即用的oc rollback支持回滚到先前的部署版本。图像和配置在 OpenShift 中是版本化并缓存的,以便轻松支持回滚到先前的版本。

52 en.wikiquote.org/wiki/Aliens_(film)

A/B 测试

A/B 测试应用程序是测试或验证新功能在生产环境中表现的一个绝佳方式。这个过程非常简单:你将两个(或更多)不同版本的应用程序部署到生产环境,衡量某些方面的表现,然后看看哪个版本的表现更好。鉴于 A/B 测试主要是一种评估用户体验的机制,更好取决于你正在实验的方面/功能。例如,你可以对网页布局做一些微妙的调整,测量用户导航到某个按钮所花的时间,或者用户与页面上特定项目的互动持续多长时间。

这是一个非常聪明的方式,在发布到更广泛的群体之前,通过小范围的受众来减少新发布的风险或验证一些新的业务或 UI 功能。可以捕捉用户行为,并进行实验,以便做出有关产品方向的明智决策。

实验

让我们回想一下前面章节中提到的生成选项的部分。我们曾提到实验的重要性,我们的价值切片板中包括了一个可以进行 A/B 测试的项目。一个出现的实验是评估用户如何在比赛中为猫投票。他们是否只能点赞(👍)还是也应该能够点踩(👎)?我们可以构建并部署两个版本的应用程序:一个可以同时点赞和点踩,另一个仅能点赞。我们的实验很简单:追踪人们实际使用点踩按钮的频率,这样我们就可以决定它是否是一个必要的功能,或者我们是否应该专注于构建其他功能。

图 15.28:在价值切片板上定义的实验

现在让我们看看如何设置一个简单的实验,将两个版本的应用程序部署并在每个部署实例之间路由流量,生成一些数据来帮助我们做出决策。

Matomo – 开源分析

OpenShift 为我们提供了一种将流量推送到不同版本应用程序的机制。这本身是有用的,但它并未提供可以作为决策依据的信息。为此,我们需要衡量用户与平台的互动情况。为此,我们将引入用户分析,记录用户与网站互动的指标。我们将使用开源的 Matomo53 平台。虽然还有其他平台可以选择,但在撰写本文时,这是我们的选择,因为它是开源的,并且功能非常完善。为了保持一致性,让我们将 Matomo 加入到我们的整体图中。

图 15.29:包含 Matomo 的新增工具的整体图

53 matomo.org/

那么,我们如何安装 Matomo 平台呢?Helm 再次来救场。我们将此安装过程自动化,作为 PetBattle 平台的一部分,通过在我们的 Ubiquitous Journey 项目中启用它来实现。它默认部署到我们的 labs-ci-cd 命名空间,配置文件位于 ubiquitous-journey/values-tooling.yaml

# Matamo
  - name: matomo
    enabled: true
    source: https://github.com/petbattle/pet-battle-analytics.git
    source_path: charts/matomo
    sync_policy: *sync_policy_true
    destination: labs-ci-cd
    source_ref: main
    ignore_differences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas
        - /spec/template/spec/containers/0/image

然而,如果你只想安装这个工具而不涉及 ArgoCD,你可以直接克隆仓库并手动安装。这个图表是从现有的图表 54 派生出来的,旨在调整它以便更轻松地在 OpenShift 上安装。具体来说,MariaDB 和 Redis 依赖中的安全上下文已经被禁用,以便部署将自动使用目标命名空间的默认服务帐户和关联的 anyuid。此外,图表中已添加 OpenShift 路由,以允许应用程序接收流量:

$ oc login ...
$ git clone https://github.com/petbattle/pet-battle-analytics.git \
&& cd pet-battle-analytics
$ helm install pba charts/matomo

部署了 Matomo 分析后,我们只需要配置前端与之连接。为此,只需更新前端中 chart/values.yaml 配置文件中的 matomoUrl,让跟踪代码自动跟踪该网站。这将提供基本的站点跟踪,例如页面停留时间或访问的页面数量。

54 gitlab.com/ideaplexus/helm/matomo

图 15.30:配置 matomoUrl 的 config_map

为了进行更有意义的测试,我们可能希望捕捉特定的用户行为。该应用程序已经被设置为将某些事件报告回 Matomo 服务器,例如鼠标点击。每当用户点击按钮为猫投票时,它会捕捉该事件并将其报告给 Matomo。实现这一点非常简单——我们只需要在我们想要跟踪的事件中添加一行代码:

this.matomoTracker.trackEvent('A/B Tests', 'Voting', voting)

部署 A/B 测试

在宠物对战的环境中,让我们来看看如何配置前端的部署来进行这个简单的 A/B 测试。幸运的是,OpenShift 通过提供一种暴露 route 并将其连接到多个服务的方法,使得这一切变得非常简单,可以使用 alternateBackends 数组来配置额外的服务以便发送流量。我们可以给这里定义的每个服务应用 weights,从而设置流量分配给已部署的 A 或 B 服务的百分比。权重可以设置在 0 到 256 之间,如果某个服务的权重降到 0,它将继续服务现有连接,但不会接受新的连接。事实上,OpenShift 允许我们进行的不仅仅是 A 或 B 测试——它还支持 C 和 D,因为 alternateBackends 支持最多三个服务!

让我们为 pet-battle 部署我们的 A/B 实验。我们可以将这些步骤与 ArgoCD 集成,但为了方便演示,还是用我们熟悉的 Helm 来部署。我们已经预构建了一个镜像,它没有在主页上进行投票的能力 quay.io/petbattle/pet-battle:no-down-vote。让我们通过运行简单的 Helm 命令将这个镜像部署到我们的集群中(确保将配置映射设置为正确的集群端点):

$ git clone git@github.com:petbattle/pet-battle.git && cd pet-battle 
$ helm install nodownvote --set image_version=no-down-vote \
--set route=false chart --namespace petbattle

通过这个命令,我们通过将镜像设置为预构建的镜像并禁用 OpenShift 路由来部署 pet-battle 前端的新实例,因为这个路由不需要。我们将通过更新 prod 应用来配置我们的生产路由。

运行 oc get pods 应该能看到应用程序已启动,如果你检查路由,你应该会看到没有暴露的路由:

$ oc get pods

名称 准备 状态 重启次数 时长

nodownvote-pet-battle-1-deploy  0/1     Completed   0          2m47s
nodownvote-pet-battle-1-jxzhf   1/1     Running     0          2m43s

让我们部署 pet-battle 应用的 prod 版本,并将 no-down-vote 应用作为我们将连接的服务之一。我们的 Helm 图表配置为接受服务名称以及我们希望应用于实验特性的权重,通过 a_b_deploy.svc_namea_b_deploy.weight 来传递。它默认采用 50/50 的轮询分配。让我们按照这个配置进行部署:

# install prod version
$ helm install prod --set image_version=latest chart \
--set a_b_deploy.svc_name=no-down-vote-pet-battle --namespace petbattle
# list pods
$ oc get pods
NAME                            READY  STATUS              RESTARTS  AGE
nodownvote-pet-battle-1-deploy  0/1    Completed           0         4m53s
nodownvote-pet-battle-1-jxzhf   1/1    Running             0         4m49s
prod-pet-battle-1-6bbv8         0/1    ContainerCreating   0         12s
prod-pet-battle-1-deploy        1/1    Running             0         16s

进入 pet-battle 的 UI,刷新页面后,你应该会看到 50/50 的概率展示“仅支持投赞成票”版本。如果你打开隐身模式或使用其他浏览器并尝试访问前端,你应该会看到另一个版本。需要使用不同的浏览器会话,因为 OpenShift 路由器默认会将你返回到同一个 Pod,因此你总是会访问到相同的版本。

图 15.31:没有投票功能的 PetBattle 前端

运行 oc get routes 应该会显示一个路由,且该路由连接到多个服务,且显示 50/50 的 split prod-pet-battle(50%),no-down-vote-pet-battle(50%)。你可以通过运行 oc get route prod-pet-battle -o yaml 来查看设置的权重,每个权重都是 100:

# display the routes
$ oc get routes
NAME       HOST/PORT     PATH   SERVICES  PORT  TERMINATION  WILDCARD
prod-pet-battle      prod-pet-battle-labs-dev.apps…
      prod-pet-battle(50%),no-down-vote-pet-battle(50%)
             8080-tcp   edge/Redirect   None

可以使用 Helm 很轻松地更新分配给每个应用的流量权重:

# update route weights
$ helm upgrade prod --set image_version=latest chart \ 
    --set a_b_deploy.svc_name=no-down-vote-pet-battle \
    --set a_b_deploy.weight=10 --namespace petbattle

理解结果

如果我们操作已部署的两个版本,可以看到点击按钮的结果是如何被捕获的。如果你打开 Matomo 应用并登录,你会看到一些统计数据。Matomo 的默认密码,如图表中所设定的,是My$uper$ecretPassword123#。虽然这个密码一开始可能不是非常安全,但通过 Helm 图表的值可以轻松更改。

图 15.32: Matomo 显示 UP_VOTE 与 DOWN_VOTE 的点击数量

Matomo 渲染饼图可能需要几分钟的时间。我们的简单实验表明,更多的人使用了 UP_VOTE 功能,而不是 DOWN_VOTE 功能。通过将 A/B 测试与 Matomo 中捕获的数据连接,我们现在可以做出更有根据的决策,决定下一步该采取什么行动。

这个实验证明了设置 A/B 测试是多么简单。我们可以利用 OpenShift 平台动态地将用户路由到多个同时部署的应用版本,同时收集关于哪些有效、哪些无效的数据。虽然我们需要对如何使应用程序能够收集特定数据进行一些思考,但现有的开源工具使得这一切变得简单!

蓝绿部署

蓝绿部署策略是每个将应用部署到生产环境的团队都应了解的基本部署策略之一。使用这种策略,通过确保在部署过程中有两个版本的应用程序可用,可以最大程度地减少执行部署切换所需的时间。它的另一个优势是,您可以迅速回滚到应用程序的原始版本,而无需回滚任何更改。

图 15.33: 经典的蓝绿部署

这里的权衡是,你需要有足够的资源来运行你部署的应用堆栈的两个版本。如果你的应用程序有持久化状态,例如数据库或非共享磁盘,那么应用架构和约束必须能够容纳这两个并发版本。这通常对于较小的微服务来说不是问题,也是选择这种部署风格的一个好处。

让我们通过以 PetBattle API 为示例的应用堆栈来演示蓝绿部署。在这种情况下,我们将部署两个完整的堆栈,即应用程序和 MongoDB。首先部署我们的应用程序的蓝色版本:

# install the blue app stack $ helm upgrade --install pet-battle-api-blue \
    petbattle/pet-battle-api --version=1.0.15 \
    --namespace petbattle --create-namespace

现在部署绿色应用堆栈。请注意,我们为此有一个不同标签的镜像版本:

# install the green app stack
$ helm upgrade --install pet-battle-api-green \
  petbattle/pet-battle-api --version=1.0.15 \
    --set image_version=green \
    --namespace petbattle

接下来,我们将生产环境的 URL 端点暴露为指向蓝色服务的路由:

# create the production route
$ oc expose service pet-battle-api-blue --name=bluegreen \
--namespace petbattle

最后,我们可以通过oc patch命令在两者之间切换:

# switch service to green
$ oc patch route/bluegreen --namespace petbattle -p \
    '{"spec":{"to":{"name":"pet-battle-api-green"}}}'
# switch back to blue again
$ oc patch route/bluegreen --namespace petbattle -p \
    '{"spec":{"to":{"name":"pet-battle-api-blue"}}}'

如果你浏览到bluegreen路由端点,你应该能轻松地确定应用堆栈:

图 15.34: PetBattle API 的蓝绿部署

尽管这个例子有些牵强,你仍然可以看到允许开发者以自助方式操作 OpenShift 路由层的强大能力。类似的方法也可以用于部署 NSFF 功能,举个例子:使用 Helm chart 参数 --set nsff.enabled=true 来部署启用 NSFF 的版本。如果你想的话,还可以通过类似操作 Helm chart 值的方式,将两个应用指向同一个数据库。

如果你有更复杂的用例需要考虑原始蓝色堆栈中的长时间运行事务,即需要排空这些事务,或者你有需要与绿色发布一起迁移的数据存储,执行蓝绿部署的方式有几种更高级的方式。查看 ArgoCD 的发布能力,它具有大量高级功能,55,或者 Knative 蓝绿发布能力,甚至是 Istio56 以获得更多的思路。

部署预览

我们应该把 OpenShift 看作是一个可以用来部署应用的“游乐场”,从生产环境到开发者预览环境都能满足需求。过去,开发团队需要提交通知单来申请服务器,并手动配置服务器以展示他们的应用程序,这样的日子已经一去不复返了。将应用程序构建在容器中使我们能够创建可以反复部署在多个环境中的可交付应用。我们在 Jenkins 中为 PetBattle 配置了自动化,每次提交时都会自动运行。对于 Jenkins,我们使用了多分支插件,所以每当开发者向某个分支推送新功能时,系统会自动搭建一个新的流水线并部署该功能的最新更改。

在上一章中讨论到沙箱构建时,你可能觉得这有些过度,而且有点浪费。为什么不直接在拉取请求上构建呢?这是一个很有效的问题,根据你要实现的目标,直接在拉取请求上构建可能已经足够了。我们使用沙箱构建作为引入反馈回路的另一种方式。

55 argoproj.github.io/argo-rollouts

56 github.com/hub-kubernetes/istio-blue-green-deployment

开发者并非孤立存在;他们周围有团队的其他成员,包括产品负责人和设计师。我们能够动态地从管道中启动新功能的部署,这意味着我们可以很容易地将编码工作与设计团队连接起来。开发者可以通过共享最新更改或新功能实现的链接,与设计团队快速获得反馈。这一反馈循环可以在工程师失去工作背景之前迅速进行微小的修改和修订。从每次提交创建部署预览,还允许开发者非常快速地与产品负责人分享应用程序的两个版本,以便在他们做出选择时提供参考。

从我们的 Jenkins 管道中,有一个名为 cool-new-cat 的分支。当该分支被构建时,它将把应用程序的新版本推送到 dev 环境。应用程序中的变化为了说明目的而很细微,但我们可以看到横幅已经被更改。通过这个新版本的应用程序,我们可以在将其合并到主分支并生成发布候选版本之前,先获得一些反馈。

图 15.35:新功能已部署到沙箱,生成部署预览以收集反馈

图 15.35 显示了正在部署的沙箱版本,以及相关的路由、服务和配置映射。

结论

恭喜!你刚刚完成了本书迄今为止最具技术性的章节。请不要认为你必须使用书中提到的每一项技术和方法——这并不是重点。调查、评估并选择哪些技术适合你自己的用例和环境。

我们的一些测试实践是我们技术基础的一部分。单元测试、非功能性测试和代码覆盖率测量都是帮助从一开始就将质量构建到我们的应用程序和产品中的关键实践。我们涵盖了许多小而宝贵的技巧,比如资源验证、代码检查和格式化,这些技巧有助于让我们的代码库在维护时负担更小。

我们涵盖了多种部署方法,包括 A/B 测试、金丝雀发布、蓝绿部署和无服务器部署。这些核心技术使我们能够更可靠地将应用程序交付到不同的环境中。我们甚至简要介绍了使用人工智能减少上传到 PetBattle 产品中的不需要的图像。通过将精力集中在应对故障时,我们可以更轻松地接受并为大大小小的失败做好准备。

第十六章:16. 担当责任

"好啦,我们上线了!PetBattle 终于投入生产了,我们可以打开香槟,庆祝我们的成功。"但是现在怎么办?我们怎么知道网站是否按预期运行?更重要的是,我们怎么知道它什么时候没有按照我们的意图运行?我们是不是只能等着客户抱怨网站崩溃或发生错误?这显然不是一个好的用户体验模型——也不是我们时间的好用法。

在本章中,我们将讨论可以用来监控网站并在问题开始出现时通知我们的工具和技术,以便我们在整个网站崩溃之前作出反应。我们还将讨论一些高级技术,例如 Operators,帮助你自动化许多日常操作。

可观察性

可观察性 1 是对软件组件进行监控,以帮助提取数据的过程。这些数据可以用来确定系统的运行状况,并在发生问题时通知管理员。

1 en.wikipedia.org/wiki/Observability

当我们观察 PetBattle 应用程序的状态时,有许多方面需要考虑:

  • 我们怎么知道应用程序实例是否已经初始化并准备好处理流量?

  • 我们怎么知道应用程序是否失败而没有崩溃,比如死锁或被阻塞?

  • 我们怎么访问应用程序日志?

  • 我们怎么访问应用程序的度量指标?

  • 我们怎么知道当前运行的应用程序版本?

让我们从探索一些应用程序健康检查开始。

探针

哈喽,哈喽?…这个东西有声音吗? 在 Kubernetes 中,应用程序的健康状况由一组由 kubelet 定期调用的软件探针来决定。探针基本上是平台在每个 Pod 上调用的一个操作,它要么返回成功的值,要么返回失败的值。

探针可以配置为执行以下几种类型的操作:

  • 连接到容器监听的特定 TCP 端口。如果端口是开放的,则探针被认为是成功的。

  • 调用 HTTP 端点,如果 HTTP 响应代码大于或等于 200 但小于 400。

  • 进入容器并执行命令——这可能涉及检查目录中的特定文件。这使得我们可以在那些没有原生提供健康检查的应用程序上放置探针。如果命令以状态码 0 退出,则探针成功。

如果探针失败超过配置的次数,管理 Pod 的 kubelet 将采取预定的行动,例如从服务中移除 Pod 或重新启动 Pod。

Kubernetes 目前支持三种不同类型的探针:

  1. 就绪:决定 Pod 是否准备好处理传入的请求。如果应用程序需要一些时间来启动,该探针确保在该探针通过之前,不会向 Pod 发送任何流量。此外,如果探针在运行时失败,平台将停止向该 Pod 发送任何流量,直到探针再次成功。就绪探针对于确保在扩展或升级 Pod 时为用户提供零停机时间的体验至关重要。

  2. 存活:检查 Pod 是否存在进程死锁或崩溃而没有退出;如果是,平台将终止该 Pod。

  3. 启动:用于防止平台终止一个正在初始化但启动很慢的 Pod。当启动探针被配置时,就绪探针和存活探针会被禁用,直到启动探针通过。如果启动探针一直不通过,Pod 最终会被终止并重新启动。

大多数情况下,你可能只会使用就绪探针和存活探针,除非你有一个启动非常慢的容器。

在 PetBattle Tournament Service 组件中,存活探针和就绪探针的配置如下:

DeploymentConfig(或 Deployment)中,/health/live/health/ready URLs 是由 Quarkus 框架自动创建的:

        ...
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /health/live
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 0
          periodSeconds: 30
          successThreshold: 1
          timeoutSeconds: 10
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /health/ready
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 0
          periodSeconds: 30
          successThreshold: 1
          timeoutSeconds: 10

不同的探针可以调用相同的操作,但我们认为这是不好的做法。就绪探针的语义与存活探针的语义不同。建议存活探针和就绪探针调用容器上的不同端点或操作。

例如,就绪探针可以调用一个操作来验证应用程序是否能够接受请求。如果在 Pod 的生命周期内,就绪探针失败,Kubernetes 将停止向该 Pod 发送请求,直到该探针再次成功。

存活探针是验证应用程序是否能成功处理请求的探针;例如,如果应用程序被阻塞,或者正在接受请求但长时间等待数据库连接变得可用,那么探针会失败,Kubernetes 将重新启动 Pod。可以把存活探针看作是 Kubernetes 版本的 IT Crowd2 工作方式。

多米诺效应

我们经常被问到的一个问题是,健康检查是否应该反映应用程序下游依赖项的状态以及应用程序本身的状态?绝对的、明确的答案是 这取决于情况。大多数情况下,健康检查应该只关注应用程序,但总是存在一些例外情况。

如果你的健康检查功能对下游系统进行了深度检查,这可能会非常昂贵,并且可能导致级联故障,即下游系统出现问题时,上游 Pod 会因下游问题而被重启。一些遗留的下游系统可能没有健康检查,在这种情况下,更合适的方法是为你的应用程序和架构添加弹性和容错能力。

故障容错

这一点的关键是,在调用依赖时利用熔断器模式。当熔断器检测到之前的调用失败时,它可以短路下游系统的调用。这可以给下游系统留出恢复或重启的时间,而不必处理进入的流量。

熔断器的基本前提是,当下游系统出现故障时,上游系统应该假设下一个请求也会失败,并且不发送该请求。它还可能采取适当的恢复措施,例如返回默认值。

在一个给定的时间段(称为回退期)后,上游系统应该尝试向下游系统发送请求,如果成功,则恢复正常处理。回退期的目的是避免上游系统在下游系统启动时立即向其发送过多请求,导致下游系统被压垮。

2 www.quotes.net/mquote/901983

熔断器功能也可以在应用代码的单独级别上执行:多个框架,如 Quarkus、Netflix Hystrix 和 Apache Camel,都支持熔断器和其他故障容错组件。有关更多详细信息,请查看 Quarkus 故障容错插件。3

平台级熔断器功能由 OpenShift 中的服务网格组件提供。与应用级熔断器相比,它具有多个显著的优势:

  • 它可以用于任何通过 HTTP/HTTPS 通信的容器。侧车代理被用来注入熔断器功能,而无需修改代码。

  • 它提供熔断器功能的动态配置。

  • 它提供关于熔断器状态的指标和可视化,贯穿整个平台。

  • 服务网格还提供其他故障容错功能,如超时和重试。

日志记录

啊,日志记录! 没有真正的开发者 4 能在他们的职业生涯中获得应有的认可,直到他们在生产日志中花费无数小时,试图找出用户点击“确认”按钮时究竟出了什么问题。如果你曾经在多个日志文件中完成过这项工作,这些文件都托管在不同的系统上,并通过多个终端窗口进行查看,那么你在 IDE 依赖的开发者眼中就是个真正的英雄。

好消息是,Kubernetes 上的应用日志记录是该平台的一级公民——只需配置应用程序将日志写入 STDOUT,平台会自动捕捉并让你查看/筛选这些日志。OpenShift 更进一步,默认提供一个聚合的日志栈,包含 EFK(Elasticsearch、Fluentd 和 Kibana)。这使得开发人员可以跨多个节点上的多个容器搜索和查看日志。如果你想试试看,可以参考 docs.openshift.com/container-platform/4.7/logging/cluster-logging-deploying.html 中的文档。

3 quarkus.io/guides/smallrye-fault-tolerance

4 en.wikipedia.org/wiki/No_true_Scotsman

跟踪

所以,首先:不,跟踪并不是在跟踪日志级别下运行的应用日志记录。对于 OpenShift,跟踪是添加到 Kubernetes 平台的功能,它使开发人员能够跨分布式的应用组件集(这些组件运行在集群中不同节点的不同容器中)跟踪请求。跟踪是一个非常有用的工具,用于确定和可视化分布式系统中各服务/组件之间的依赖关系以及性能/延迟瓶颈。

跟踪功能是作为 OpenShift 服务网格组件的一部分提供的。底层的跟踪功能由 Jaeger5 分布式跟踪平台提供。为了支持跟踪,应用程序必须包含一个客户端库,该库将仪表化的请求元数据发送到 Jaeger 收集器,收集器处理并存储数据。然后,可以查询这些数据,以帮助可视化端到端请求工作流。Jaeger 客户端库是特定于语言的,并采用供应商中立的 OpenTracing 规范。

如果你在想,"哇! 为每个请求收集元数据将非常昂贵,需要存储和处理",你是对的。Jaeger 可以 这么做,但出于规模考虑,最好记录和处理一部分请求,而不是每一个请求。

指标

探针对于判断一个应用是否准备好接受流量,或者它是否卡住了,十分有用。跟踪对于提供分布式系统中的延迟度量非常有效,而日志记录则是一个非常有用的工具,能让你回顾并准确理解发生了什么以及何时发生。

然而,要理解一个系统的深层状态(不,不是那个深层状态!)并可能预测其在一段时间后的未来状态,你需要衡量系统的一些关键定量特征,并在一段时间内对其进行可视化/比较。

好消息是,指标相对容易获取;你可以从基础设施组件和软件组件(如 JVM)中获取指标,也可以向应用程序中添加特定领域/自定义的指标。

5 www.jaegertracing.io/

鉴于可用的指标众多,困难的部分是弄清楚哪些指标对你的角色有价值并需要保留;例如,对于应用程序操作员来说,连接池计数和 JVM 垃圾回收暂停时间是极为宝贵的。对于 Kubernetes 平台操作员来说,JVM 垃圾回收暂停时间不那么关键,但平台组件的指标,如与 etcd 相关的指标,则至关重要。

好消息是,OpenShift 提供了集群以及运行在其上的应用程序的指标。在本节中,我们将重点关注应用程序级别的视角。在 Kubernetes 社区中,事实上的做法是使用 Prometheus6 来收集指标,使用 Grafana7 来可视化指标。这并不意味着你不能使用其他的指标解决方案,实际上市场上有一些非常优秀的方案,提供了额外的功能。

OpenShift 默认提供 Prometheus 和 Grafana 作为指标栈。此外,它还附带了 Prometheus Alertmanager。Alertmanager 可以在指标值显示某些东西出现问题时(例如线程数量过高或 JVM 垃圾回收暂停时间过长),向操作员发送通知,表明“麻烦来了”。例如,线程数量过多或 JVM 垃圾回收暂停时间过长等情况。

很好,那么我们如何为 PetBattle 启用这个功能呢?其实非常简单:

  1. 在你的应用程序中使用一个记录指标并将指标暴露给 Prometheus 的指标框架。

  2. 配置 Prometheus 从应用程序中获取指标。

  3. 在 OpenShift 中可视化指标。

一旦指标被获取,最后一步是使用 Prometheus Alertmanager 配置一个警报。

在应用程序中收集指标

以 PetBattle Tournament 服务组件为例,它是使用 Quarkus Java 框架开发的。开箱即用,Quarkus 支持并推荐使用开源的 Micrometer 指标框架。8

6 prometheus.io/

7 grafana.com/oss/

8 micrometer.io/

要将此功能添加到 Tournament 服务中,我们只需要将依赖项添加到 Maven POM 文件中,并且添加 Prometheus 依赖。例如:

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-micrometer</artifactId>
</dependency>
<dependency>
   <groupId>io.micrometer</groupId>
   <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

然后,我们配置一个 Prometheus 注册表,用于在 Prometheus 收集器获取之前将指标存储在应用程序本地。这是在 src/main/resources/application.properties 文件中完成的。

# Metrics
quarkus.micrometer.enabled=true
quarkus.micrometer.registry-enabled-default=true
quarkus.micrometer.binder-enabled-default=true
quarkus.micrometer.binder.jvm=true
quarkus.micrometer.binder.system=true
quarkus.micrometer.export.prometheus.path=/metrics

使用此配置,Prometheus 端点由应用程序 Pod 暴露。我们继续进行测试:

# grab the pod name for the running tournament service
$ oc get pod -n petbattle | grep tournament 
$ oc exec YOUR_TOURNAMENT_PODNAME -- curl localhost:8080/metrics
...
# HELP mongodb_driver_pool_size the current size of the connection pool, including idle and and in-use members
# TYPE mongodb_driver_pool_size gauge
mongodb_driver_pool_size{cluster_id="5fce8815a685d63c216022d5",server_address="my-mongodb:27017",} 0.0
# TYPE http_server_requests_seconds summary
http_server_requests_seconds_count{method="GET",outcome="SUCCESS",status="200",uri="/openapi",} 1.0
http_server_requests_seconds_sum{method="GET",outcome="SUCCESS",status="200",uri="/openapi",} 0.176731581
http_server_requests_seconds_count{method="GET",outcome="CLIENT_ERROR",status="404",uri="NOT_FOUND",} 3.0
http_server_requests_seconds_sum{method="GET",outcome="CLIENT_ERROR",status="404",uri="NOT_FOUND",} 0.089066563
http_server_requests_seconds_count{method="GET",outcome="SUCCESS",status="200",uri="/metrics",} 100.0
# HELP http_server_requests_seconds_max
# TYPE http_server_requests_seconds_max gauge
http_server_requests_seconds_max{method="GET",outcome="SUCCESS",status="200",uri="/openapi",} 0.176731581
http_server_requests_seconds_max{method="GET",outcome="CLIENT_ERROR",status="404",uri="NOT_FOUND",} 0.0 ...

如果成功,你应该得到类似于上述的输出。注意,你不仅获得了应用程序级别的度量数据——MongoDB 连接池的度量数据也在其中。这些数据在application.properties文件配置后,由 Quarkus 框架自动添加。

配置 Prometheus 从应用程序中检索度量数据

Prometheus 在操作模式上有些不同寻常。它不像某些代理那样将度量数据推送到中央收集器,而是采用拉取模式,收集器从应用程序暴露的已知 HTTP/HTTPS 端点检索/抓取度量数据。在我们的例子中,正如上面所见,我们通过/metrics端点暴露度量数据。

那么,Prometheus 收集器如何知道何时、何地以及如何收集这些度量数据呢?OpenShift 使用一个 Prometheus 操作符 9,简化了配置 Prometheus 收集度量数据的过程。我们只需部署一个ServiceMonitor对象,来指导 Prometheus 如何收集我们的应用程序度量数据。

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
 labels:
   app.kubernetes.io/component: pet-battle-tournament
   k8s-app: pet-battle-tournament
 name: pet-battle-tournament-monitor
spec:
 endpoints:
 - interval: 30s
   port: tcp-8080
   scheme: http
 selector:
   matchLabels:
     app.kubernetes.io/component: pet-battle-tournament

9 github.com/prometheus-operator/prometheus-operator

在尝试理解此配置时,有几点需要注意,这可能为你节省一些时间:基本上,此配置将在每 30 秒抓取一次相关组件的数据,使用默认的 HTTP 路径/metrics。现在,port: tcp-8080映射到服务中的端口名称——见下文,以粗体突出显示。如果服务的端口名称是web,那么配置将是port: web

$ oc describe svc my-pet-battle-tournament
Name:              my-pet-battle-tournament
Namespace:         pet-battle-tournament
Labels:            app.kubernetes.io/component=pet-battle-tournament
                   app.kubernetes.io/instance=my
                   app.kubernetes.io/managed-by=Helm
                   app.kubernetes.io/name=pet-battle-tournament
                   app.kubernetes.io/version=1.0.0
                   deploymentconfig=my-pet-battle-tournament
                   helm.sh/chart=pet-battle-tournament-1.0.0
Annotations:       Selector:  app.kubernetes.io/component=pet-battle-tournament,app.kubernetes.io/instance=my,app.kubernetes.io/name=pet-battle-tournament,deploymentconfig=my-pet-battle-tournament
Type:              ClusterIP
IP:                172.30.228.67
Port:              tcp-8080  8080/TCP
TargetPort:        8080/TCP
Endpoints:         10.131.0.28:8080
Port:              tcp-8443  8443/TCP
TargetPort:        8443/TCP
Endpoints:         10.131.0.28:8443
Session Affinity:  None
Events:            <none>

用户工作负载监控需要在集群级别启用,才能使 ServiceMonitoring 生效。10 这也是 Kubernetes 两个强大但常被误解和未充分使用的主要特性之一的经典示例,标签标签选择器

10 docs.openshift.com/container-platform/4.7/monitoring/enabling-monitoring-for-user-defined-projects.html

以下行表示 Prometheus 将尝试从所有具有app.kubernetes.io/component: pet-battle-tournament标签的组件中检索度量数据。我们无需单独列出每个组件;只需要确保该组件具有正确的标签,并且使用选择器来匹配该标签。如果我们在架构中添加了新组件,那么只需确保它具有正确的标签。当然,所有这些都假设抓取度量数据的方法在所有选定组件之间是一致的;例如,它们都使用tcp-8080 端口

selector:
   matchLabels:
     app.kubernetes.io/component: pet-battle-tournament

我们非常喜欢标签及其关联的选择器。它们作为一种分组组件的方法非常强大:Pods、Services 等。它是一个隐藏的宝藏,你希望早些知道。

在 OpenShift 中可视化度量数据

一旦我们检索到度量数据,我们需要解释它们所传递的系统信息。

使用 Prometheus 进行查询

为了可视化度量数据,请进入开发者控制台并点击监控 (1),如图 16.1所示。然后点击下拉菜单中的自定义查询 (2),并使用Prometheus 查询语言 (PromQL) (3) 输入查询。在下面的示例中,我们使用了http_server_requests_seconds_count度量指标,但也有其他的度量指标。

图 16.1:PetBattle 比赛度量数据

让我们探索 OpenShift 提供的一些内置仪表盘,用于监控。

使用 Grafana 可视化度量数据

OpenShift 自带了用于集群监控的专用 Grafana 仪表盘。无法修改这些仪表盘并添加自定义的应用程序度量数据,但可以部署一个特定于应用程序的 Grafana 实例并根据需要进行定制。为此,我们首先需要确保 Grafana Operator 已安装在我们使用的命名空间中。

然后,我们将通过部署以下自定义资源来部署一个自定义的 Grafana 设置:

  • 一个Grafana 资源,用于在命名空间中创建自定义的grafana实例

  • 一个GrafanaDataSource 资源,用于从集群范围内的 Prometheus 实例中拉取度量数据

  • 一个GrafanaDashboard资源,用于创建仪表盘

好消息是,所有这些操作都是通过 Helm 图表完成的,所以你只需要做以下操作:

$ oc get routes
...                                                                
grafana-route     grafana-route-pb-noc.apps.someinstance.com     

在浏览器中打开grafana-route,登录,看! 它应该类似于图 16.2所示。如果出现没有数据的错误,请检查 BEARER_TOKEN 是否已经设置。可以通过运行以下命令手动修复:github.com/petbattle/pet-battle-infra/blob/main/templates/insert-bearer-token-hook.yaml#L80

图 16.2:Grafana 中的 PetBattle 度量数据

现在我们将看看一些可以帮助我们进一步进行可观察性的工具。

元数据和可追溯性

随着独立可部署的基于服务的架构的采用,管理这些组件及其相互关系的复杂性变得越来越有问题。在接下来的章节中,我们将概述一些可以帮助你解决这一问题的技术。

标签

如前所述,标签和标签选择器是 Kubernetes 中更强大的元数据管理功能之一。从核心来看,标签是基于文本的键/值对集合,可以附加到一个或多个对象上:Pods、服务、部署等等。标签旨在为用户相关的对象添加信息/语义,而不是核心 Kubernetes 系统。标签选择器是一种方法,用户可以通过它将具有相同标签的项分组在一起。

在 Kubernetes 中,标签和标签选择器最常见的用途之一是服务使用标签选择器将相关的 Pods 分组为服务的端点。

通过一个示例展示可能更容易理解。

那么,让我们从三个 Infinispan Pods 开始。鉴于 Infinispan 操作符通过 StatefulSets 部署 Pods,Pod 名称非常直接:infinispan-0infinispan-1infinispan-2。注意附加到 Pods 上的标签(以粗体显示)。

$ oc get pods --show-labels=true
NAME                                   READY   STATUS      RESTARTS   AGE     LABELS
infinispan-0                           1/1     Running     0          2m25s   app=infinispan-pod,clusterName=infinispan,controller-revision-hash=infinispan-66785c8f,infinispan_cr=infinispan,statefulset.kubernetes.io/pod-name=infinispan-0
infinispan-1                           1/1     Running     0          5m51s   app=infinispan-pod,clusterName=infinispan,controller-revision-hash=infinispan-66785c8f,infinispan_cr=infinispan,statefulset.kubernetes.io/pod-name=infinispan-1
infinispan-2                           1/1     Running     0          4m12s   app=infinispan-pod,clusterName=infinispan,controller-revision-hash=infinispan-66785c8f,infinispan_cr=infinispan,statefulset.kubernetes.io/pod-name=infinispan-2

当 Tournament 服务想要连接到这些 Infinispan Pods 之一时,它使用由操作符创建和管理的 Infinispan 服务。

 $ oc get svc
NAME         TYPE        CLUSTER-IP  EXTERNAL-IP   PORT(S)     AGE
infinispan   ClusterIP   172.30.154.122   <none>   11222/TCP   5d20h

如果我们进入服务的定义,就会看到选择器(以粗体显示):

$ oc describe service infinispan
Name: infinispan
Namespace: pet-battle-tournament
Labels: app=infinispan-service
        clusterName=infinispan
        infinispan_cr=infinispan
Annotations: service.alpha.openshift.io/serving-cert-signed-by:               openshift-service-serving-signer@1607294893
             service.beta.openshift.io/serving-cert-secret-name:
               infinispan-cert-secret
             service.beta.openshift.io/serving-cert-signed-by:
               openshift-service-serving-signer@1607294893
Selector:          app=infinispan-pod,clusterName=infinispan
Type:              ClusterIP
IP:                172.30.154.122
Port:              infinispan  11222/TCP
TargetPort:        11222/TCP
Endpoints: 10.128.2.158:11222,10.129.3.145:11222,10.131.0.25:11222
Session Affinity:  None
Events:            <none>

这将带有标签 app=infinispan-pod,clusterName=infinispan 的 Pods 添加到服务中作为端点。这里有两点需要注意:选择器并没有使用分配给 Pod 的所有标签;如果我们扩展了 Infinispan Pods 的数量,选择器会持续进行评估,并自动将新的 Pods 添加到服务中。上述示例是一个非常基础的选择器示例;事实上,选择器要强大得多,支持等式操作和基于集合的操作。请查看 Kubernetes 文档中的示例了解更多信息。11

11 kubernetes.io/docs/concepts/overview/working-with-objects/labels/

很好,那么接下来呢?你可以使用什么信息来标记一个资源?这取决于你的需求。如前面在监控部分所示,标签和选择器在配置 Prometheus 时非常有用。标签还可以用于将组件分组在一起,例如组成分布式应用程序的组件。

Kubernetes 提供了一组推荐的标签 12,我们在构建和部署 PetBattle 应用程序时使用了这些标签:

表 16.1:Kubernetes 推荐的标签

12 kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/

有了这些标签,就可以使用选择器检索并查看应用程序的组件,例如显示 PetBattle 应用的组件部分,而不包括支持应用程序基础设施,即 Infinispan 或 Keycloak。以下命令演示了这一点:

$ oc get all -l app.kubernetes.io/part-of=petbattleworld \
    --server-print=false
NAME                                                            AGE
replicationcontroller/dabook-mongodb-1                          2d18h
replicationcontroller/dabook-pet-battle-tournament-1            26m
NAME                                                            AGE
service/dabook-mongodb                                          2d18h
service/dabook-pet-battle-tournament                            2d18h
NAME                                                            AGE
deploymentconfig.apps.openshift.io/dabook-mongodb                2d18h
deploymentconfig.apps.openshift.io/dabook-pet-battle-tournament  26m

名称 年龄

imagestream.image.openshift.io/dabook-pet-battle-tournament     26m

名称 年龄

route.route.openshift.io/dabook-pet-battle-tournament           2d18h

让我们来看看其他可以用来增强可追溯性的方法。

软件可追溯性

多年来,我们从客户那里观察到的一个问题是,许多人依赖于他们投入生产的软件工件的名称,例如 super-important-app-1.2.99.0.bincritical-service-1.2.jar。虽然这种方式在 99.9%的情况下有效,但偶尔我们会发现有错误的版本被部署,产生了有趣的结果。

在容器领域,你的部署是一个版本化的工件,包含了你的软件版本,而这又可以通过 GitOps 方法使用版本化的 Helm chart 进行部署。一个良好的构建和部署流水线将确保这些工件版本的各个层级始终保持一致,并提供可追溯性。作为备份,我们还通过在资源上添加注解和在应用二进制文件中记录构建信息来增强已部署工件的可追溯性。

注解

注解类似于 Kubernetes 标签——即基于字符串的键/值对——不同之处在于它们并不用于通过选择器来分组或识别对象。注解可以用来存储不同类型的信息;在我们的案例中,我们将使用注解来存储 Git 信息,以帮助软件的可追溯性。

apiVersion: v1
kind: Service
metadata:
  annotations:
    app.openshift.io/vcs-url:      https://github.com/petbattle/tournamentservice.git
    app.quarkus.io/commit-id:a01a310aadd46911bc4c66b3a063ddb090a3feba
    app.quarkus.io/vcs-url:      https://github.com/petbattle/tournamentservice.git
    app.quarkus.io/build-timestamp: 2020-12-23 - 16:43:07 +0000
    prometheus.io/scrape: "true"
    prometheus.io/path: /metrics
    prometheus.io/port: "8080"

注解是通过使用 Quarkus Maven 插件,作为 Maven 构建过程的一部分自动添加的。同时请注意,注解也用于为 Prometheus 提供抓取信息,正如前面代码中所突出显示的那样。

构建信息

有一种与 Kubernetes 本身无关的方法,但我们强烈推荐一般使用,它是在应用启动时输出源代码控制和构建信息。一个例子嵌入在 Tournament 服务中。

$ java -jar tournament-1.0.0-SNAPSHOT-runner.jar
GITINFO -> git.tags:
GITINFO -> git.build.version:1.0.0-SNAPSHOT
GITINFO -> git.commit.id.full:b5d6bfabeea6251b9c17ea52f0e87e2c8e967efd
GITINFO -> git.commit.id.abbrev:b5d6bfa
GITINFO -> git.branch:noc-git-info
GITINFO -> git.build.time:2020-12-24T13:26:25+0000
GITINFO -> git.commit.message.full:Moved gitinfo output to Main class
GITINFO -> git.remote.origin.url:git@github.com:petbattle/tournamentservice.git

我们使用 Maven 插件 git-commit-id-plugin 来生成包含 Git 信息的文件,并将该文件打包为 Java 压缩包jar)的一部分。在启动时,我们只需读取该文件并将其内容输出到控制台。非常简单的操作,但非常有效,并且在需要时可以救命。当在 OpenShift 上运行时,这些信息将被 OpenShift 日志组件收集。

警报

所以我们拥有所有的度量指标,可以让我们对系统的表现有一些洞察。我们在 Grafana 中有壮观的图表和仪表,但我们几乎不可能整天盯着它们看,以查看是否发生了什么事情。现在是时候为解决方案添加警报功能了。

什么是警报?

警报是当某个测量阈值(观察到或计算出的)即将被突破或已经突破时生成的事件。以下是一些警报的例子:

  • 系统在过去五分钟内的平均响应时间超过了 100 毫秒。

  • 当前活跃用户数低于某个阈值。

  • 应用程序的内存使用量接近其最大限制。

警报通常会导致通知发送给人工操作员,无论是通过电子邮件还是即时消息等方式。通知还可以发送触发自动化脚本/流程来处理警报。服务所有者可以分析他们现有的警报,帮助提高服务和系统的可靠性,并减少修复问题时的手动工作量。

为什么需要警报?

当系统内发生无法自动处理的情况时,警报就需要人工干预。这可能包括自动解决问题被认为过于危险,或需要人工干预来帮助初步处理、缓解和解决问题的场景。警报也可能成为网站可靠性工程师的一个问题,特别是当警报数量众多、误导性强或对问题原因分析没有帮助时。它们可能会产生无害的警报,但不会引起任何行动。

有一些特点构成了良好的警报。警报应该是可操作的,意思是响应警报的人能采取行动。为了具有可操作性,警报必须及时到达,使得能够采取行动,并且应送达正确的团队或位置进行初步处理。警报还可以包含有助于加快初步处理的元数据,例如文档链接。

警报类型

我们可以将警报分为三类。13 第一类是主动警报,意味着您的业务服务或系统尚未处于危险之中,但在一段时间后可能会出现问题。一个好的例子是,当系统响应时间变差,但尚未到达外部用户会察觉的问题阶段。另一个例子可能是磁盘配额正在填满,但尚未 100%满,可能在几天内会达到最大值。

被动警报意味着您的业务服务或系统正处于紧急危险中。您即将违反服务级别协议,需要立即采取行动以防止违规。

调查性警报是指您的业务服务或系统处于未知状态。例如,系统可能正在经历某种形式的部分故障,或可能生成了异常错误。另一个例子可能是某个应用程序重启次数过多,这通常是异常崩溃的标志。

这些警报中的每一个也可以根据严重性定向到不同的团队,并非所有警报都需要以相同的紧急程度进行管理。例如,一些警报必须由值班人力资源立即处理,而其他警报可能可以在下一个工作日由应用程序业务支持团队在正常工作时间内处理。让我们探讨如何利用 OpenShift 平台功能轻松配置并为我们的应用程序添加警报。

13 www.oreilly.com/content/reduce-toil-through-better-alerting/

管理警报

OpenShift 具有平台监控和警报功能,支持内置的平台组件和用户工作负载。产品文档是配置这些功能时的最佳起点。14 正如我们之前概述的那样,监控和警报利用了 Prometheus 监控堆栈。它与一个名为 Thanos 的开源工具 15 结合使用,Thanos 聚合并提供对我们集群中多个 Prometheus 实例的访问。

PetBattle 应用程序套件的基本配置包括为用户工作负载监控和警报创建两个 ConfigMaps。我们使用 ArgoCD 和一个简单的 kustomize YAML 配置来通过 GitOps 应用这些 ConfigMaps。如果我们打开普遍使用的values-day2ops.yaml文件,我们可以为用户工作负载监控创建一个条目。

  # User Workload Monitoring
  - name: user-workload-monitoring
    enabled: true
    destination: openshift-monitoring
    source: https://github.com/rht-labs/refactored-adventure.git
    source_path: user-workload-monitoring/base
    source_ref: master
    sync_policy: *sync_policy_true
    no_helm: true

下一步是利用应用程序指标和 ServiceMonitor,并为我们的 PetBattle 套件配置特定的 Prometheus 警报。

用户定义的警报

Metrics部分,我们为 API 和 Tournament 应用程序创建了 ServiceMonitors,允许我们从 Quarkus 应用程序中收集 micrometer 指标。我们希望使用这些指标来配置我们的警报。最简单的方法是浏览到聚合我们所有 Prometheus 指标的 Thanos 查询端点。你可以在openshift-monitoring项目中找到它。

$ oc get route thanos-querier -n openshift-monitoring

14 docs.openshift.com/container-platform/4.7/monitoring/configuring-the-monitoring-stack.html#configuring-the-monitoring-stack

15 github.com/thanos-io/thanos

我们希望基于 PetBattle API、Tournament 和 UI Pods 是否在某个项目中运行,创建一个简单的反应式警报。我们可以利用 Kubernetes Pod 标签和 Prometheus 查询语言来测试我们的 Pods 是否在运行。

图 16.3:Thanos 查询界面

对于这个用例,我们将 kube_pod_status_readykube_pod_labels 查询结合起来,针对每个 Pod 和命名空间组合创建一个 PrometheusRule,当某个条件未满足时触发警报。我们将警报的生成封装在一个 Helm chart 中,以便我们可以轻松地模板化项目和警报严重性值 16,并将部署与我们的 GitOps 自动化连接。

16 github.com/petbattle/ubiquitous-journey/blob/main/applications/alerting/chart/templates/application-alerts.yaml

spec:
  groups:
  - name: petbattle.rules
    rules:
    - alert: PetBattleApiNotAvailable
      annotations:
        message: 'Pet Battle Api in namespace {{ .Release.Namespace }} is not available for the last 1 minutes.'
      expr: (1 - absent(kube_pod_status_ready{condition="true" ... for: 1m
      labels:
        severity: {{ .Values.petbattle.rules.severity }}

图 16.4 所示,可以在 OpenShift Web 控制台中看到触发的警报。在此示例中,我们已将 labs-dev 的警报配置为仅具有 info 严重性,因为在该环境中它们不被认为是至关重要的部署。严重性可以设置为 infowarningcritical,例如我们对 labs-test labs-staging 环境使用 warning。这些是任意但标准的严重性级别,我们可以用它们来路由警报,稍后我们将讨论这个问题。

图 16.4:PetBattle 警报在 OpenShift 中触发

我们可以使用相同的方法来创建调查性或主动性警报。这次我们希望测量 API 应用程序的 HTTP 请求时间。在测试过程中,我们发现如果 API 调用时间超过 ~1.5 秒,PetBattle 前端的用户体验会被终端用户认为太慢,可能会导致他们完全放弃使用该 Web 应用程序。

图 16.5:最大请求时间警报规则

在此警报中,我们使用 Prometheus 查询语言和 http_server_requests_seconds_max 指标来测试 PetBattle API 应用程序在过去五分钟内的最大请求时间是否超过了 1.5 秒的阈值。如果此警报开始触发,可能的修复措施包括手动扩展 API Pod 的数量,或者如果数据库某些原因表现慢,可能需要增加数据库资源。在未来的迭代中,我们甚至可能尝试通过使用水平 Pod 自动扩展器(Horizontal Pod Autoscaler)来自动化应用扩展,水平 Pod 自动扩展器是 Kubernetes 的一个构件,能够基于指标自动扩展我们的应用程序。

通过这种方式,我们可以继续为我们的 PetBattle 应用程序套件构建警报规则集,在不同的环境中运行应用程序时进行修改,并学习需要注意的条件,同时尽可能自动化修复过程。

OpenShift Alertmanager

正如我们所看到的,OpenShift 支持三种警报严重性级别:infowarningcritical。我们可以根据严重性以及自定义标签(即项目或应用标签)来对警报进行分组和路由。在 OpenShift 管理员控制台中,17 您可以在 集群设置 下配置 Alertmanager。

图 16.6:Alertmanager 路由配置

警报可以通过标签进行分组和过滤,然后路由到特定的接收者,如 PagerDuty、Webhook、电子邮件或 Slack。我们可以微调路由规则,以便根据警报的紧急程度,将正确的团队通过正确的渠道接收警报。例如,所有针对 PetBattle UI 应用的信息警告级别的警报可能会路由到前端开发者的 Slack 渠道,而所有严重级别的警报则会路由到值班 PagerDuty 端点以及 Slack 渠道。

17 docs.openshift.com/container-platform/4.7/monitoring/managing-alerts.html

警报是成功管理系统运维方面的关键组件,但你需要小心,确保运维团队不会因警报而不堪重负。过多的警报或许多次错误警报可能导致警报疲劳,这种情况会使团队养成忽略警报的习惯,从而剥夺警报在系统成功管理中的重要性。

服务网格

服务网格功能是 Kubernetes 在其短短历史中最大的新增功能之一。关于使用服务网格的额外复杂性以及是否需要所有功能,存在很多争论。

本书的目的是专注于 OpenShift 提供的内置服务网格,它基于开源 Istio 项目。还有其他实现,如 Linkerd、SuperGloo 和 Traefik,它们也非常出色,提供与 Istio 类似的功能。

OpenShift 服务网格提供以下功能:

  • 安全性:身份验证和授权、互信 TLS(加密)、策略

  • 流量管理:弹性功能、虚拟服务、策略、故障注入

  • 可观测性:服务指标、调用追踪、访问日志

为什么选择服务网格?

我们之前讨论过弹性和像断路器这样的模式如何帮助系统从下游故障中恢复。断路器可以通过框架如SmallRye Fault ToleranceSpring Cloud Circuit Breaker为 Java 项目添加到应用程序代码范围内;类似的框架如Polly18 存在于.NET 环境,PyBreaker19 适用于 Python,Opossum20 适用于 Node.js。这些框架的一个关键要求是,它们必须添加到应用程序的现有源代码中,并且需要重建应用程序。而在使用服务网格时,断路器是外部于应用程序代码的,不需要在应用程序级别做任何修改即可利用此功能。

18 github.com/App-vNext/Polly

19 pypi.org/project/pybreaker/

20 nodeshift.dev/opossum/

互信 TLSmTLS)也如此,它用于加密服务之间的流量。像 CertManager 或 CertUtil 这样的操作工具可以帮助管理和分发证书,但仍需要修改应用程序代码才能使用该功能。服务网格通过将组件间流量发送通过侧车代理来简化这一过程,像 mTLS 这样的功能会自动神奇地添加到其中——同样,无需更改应用程序代码。

服务网格的 Istio 组件还管理 TLS 证书的生成和分发,以帮助减少使用 mTLS 时的管理开销。

那么,服务网格是如何实现所有这些神奇功能的呢?基本上,服务网格操作员将一个服务代理容器(基于 Envoy 项目)添加到应用程序 Pod 中,并配置应用流量通过该代理进行路由。该代理向 Istio 控制平面注册,并获取配置设置、证书和路由规则,然后进行配置。Istio 文档对此有更详细的介绍。21

旁注 – 侧车容器

人们在听到侧车这个词时,通常会联想到一辆摩托车,车上附有一个单轮的乘客车厢——一个驾驶舱。由于与摩托车连接,驾驶舱随摩托车一起移动——除了在喜剧小品中,摩托车和侧车分离又重新连接,但那是另一个话题。

在 Kubernetes 中,侧车是与主应用容器在同一 Kubernetes Pod 中运行的容器。这些容器共享相同的网络和 ICP 命名空间,也可以共享存储。在 OpenShift 中,使用服务网格功能时,Pod 被正确注解为sidecar.istio.io/inject: "true",会自动注入一个 Istio 代理作为侧车,与应用容器一起运行。应用程序与外部资源之间的所有后续通信都会通过这个侧车代理,从而使得可以根据需要使用诸如电路断路器、追踪和 TLS 等功能。正如伟大的 Freddie Mercury 曾经说过的,"这是一种魔力。"

# Let’s patch the deployment for our pet battle apps 
# running in petbattle ns and istioify it
$ helm upgrade \
--install pet-battle-tournament \
--version=1.0.39 \
--set pet-battle-infra.install_cert_util=true \
--set istio.enabled=true \
--timeout=10m \
--namespace petbattle
petbattle/pet-battle-tournament
$ oc get deployment pet-battle-tournament -o yaml \
--namespace petbattle
...  
template:
    metadata:
      annotations:
...
        sidecar.istio.io/inject: "true"
      labels:
        app.kubernetes.io/component: pet-battle-tournament
        app.kubernetes.io/instance: pet-battle-tournament
        app.kubernetes.io/name: pet-battle-tournament

21 istio.io/latest/docs/

如果需要,可以有多个侧车容器。每个容器可以为应用程序 Pod 带来不同的功能:例如,一个用于 Istio,另一个用于日志转发,另一个用于检索安全凭证,等等。很容易知道一个 Pod 是否运行多个容器;例如,READY列显示每个 Pod 中有多少容器可用,以及有多少容器已经准备好——也就是说,它的就绪探针已通过。

$ oc get pods 
NAME                            READY   STATUS      RESTARTS   AGE
infinispan-0                    1/1     Running     0          4h13m
Pet-battle-3-68fm5              2/2     Running     0          167m
pet-battle-api-574f77ddc5-l5qx8 2/2     Running     0          163m
pet-battle-api-mongodb-1-7pgfd  1/1     Running     0          3h50m
pet-battle-tournament-3-wjr6r   2/2     Running     0          167m
pet-battle-tou.-mongodb-1-x7t9h 1/1     Running     0          4h9m

但是,要注意的是,存在一种诱惑,即尝试一次性使用所有服务网格功能,这被称为 哦…好亮眼 的问题。

这里有龙!

在涉及多个组件和开发团队的复杂解决方案时,采用服务网格并非一项简单的任务。了解服务网格的一件事是,它跨越了许多团队边界和责任范围。它包括专注于开发人员、运维和安全团队的特性;所有这些团队/人员需要协同工作,理解并充分利用网格提供的功能。如果你刚刚开始,我们的建议是从小做起,弄清楚生产环境中哪些功能是必要的,然后从那里逐步迭代。

在 PetBattle 的案例中,我们决定主要专注于使用流量管理和可观察性方面的一些功能。这样做的理由是,Keycloak 已经解决了许多安全需求,同时我们也希望在十年结束前完成这本书。

服务网格组件

服务网格的功能由多个独立的组件组成:

  • Jaeger 和 Elasticsearch 提供调用跟踪功能和日志记录功能。

  • Kiali 提供网格可视化功能。

  • OpenShift 服务网格提供核心的 Istio 功能。

好消息是,所有这些组件都是由操作员安装和管理的,因此安装相对简单。这些组件通过 Helm 安装,如果你想了解更多关于如何安装它们的信息,可以查看 Red Hat OpenShift 文档中的相关内容。

需要注意的一点是,在撰写本书时,OpenShift 服务网格包含一个下游版本的 Istio,名为 Maistra。这主要是由于 OpenShift 的开箱即用的多租户特性,以及限制 Istio 集群范围资源的使用。OpenShift 服务网格还包括一个 Istio OpenShift 路由 (IOR) 组件,该组件将 Istio 网关定义映射到 OpenShift 路由上。请注意,Istio 仍然是上游项目,必要时会修复/实现 bugs 或特性请求。

对于流量管理,Istio 提供了以下核心资源:

  • 网关:控制外部流量如何进入服务网格,类似于 OpenShift 路由。

  • 虚拟服务:控制流量如何在服务网格内路由到目标服务。这是配置超时、基于上下文的路由、重试、镜像等功能的地方。

  • 目标规则:流量应用规则后被路由到的服务位置。目标规则可以配置以控制诸如负载均衡策略、连接池、TLS 设置和异常检测(断路器)等流量方面的内容。

还有其他资源,如服务入口、过滤器和工作负载,但我们在这里不会涵盖它们。

PetBattle 服务网格资源

我们将简要介绍我们在 PetBattle 中使用的一些资源,并解释我们如何使用它们。

网关

如前所述,网关资源用于为进入服务网格的流量创建入口路由。

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: petbattle-gateway-tls
spec:
  selector:
    istio: ingressgateway 
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: "pb-ingressgateway-certs"
    hosts:     
    - "*"

关于此定义有几点需要注意:它将在istio-system命名空间中创建一个 OpenShift 路由,而不是本地命名空间。其次,路由本身将使用 SSL,但默认情况下无法使用 OpenShift 路由器证书。服务网格路由必须提供自己的证书。作为编写本书的一部分,我们采取了务实的方法,将 OpenShift 路由器证书复制到istio-system命名空间,并通过pb-ingressgateway-certs密钥提供给网关。请注意,这仅用于演示目的——不要在生产环境中尝试此操作。生产环境中的正确方法是使用“即服务”证书生成并管理 PKI。

虚拟服务

PetBattle 包含多个虚拟服务,如pet-battle-cats-tls、pet-battle-main-tlspet-battle-tournament-tls

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: pet-battle-cats-tls
spec:
  hosts:
  - "*"
  gateways:
  - petbattle-gateway-tls
  http:
  - match:
    - uri:
        prefix: /cats
      ignoreUriCase: true
    route:
    - destination:
        host: pet-battle-api
        port:
          number: 8080
    retries:
      attempts: 3
      perTryTimeout: 2s
      retryOn: gateway-error,connect-failure,refused-stream

这些虚拟服务的功能类似,都是配置为:

  1. 匹配特定的 URI;在上面的示例中是/cats

  2. 一旦匹配,便将流量路由到特定的目标。

  3. 通过执行固定次数的请求重试来处理特定错误。

目标规则

最终,流量被发送到一个目标,或根据配置分发到一组目标。这就是目标规则的作用。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: pet-battle-api-port
spec:
  host: pet-battle-api.prod.svc.cluster.local
  trafficPolicy: # Apply to all ports
    portLevelSettings:
    - port:
        number: 8080
      loadBalancer:
        simple: LEAST_CONN

在我们的示例中,发送到特定端口的流量会根据一个简单的策略进行负载均衡,该策略选择活动请求最少的 Pod。这里可以使用许多负载均衡策略,具体取决于应用的需求——从简单的轮询到高级的恒定哈希负载均衡策略,这些都可以用于会话亲和性。正如之前所提到的,文档中会详细讨论这一点。22

我们可以通过上面的示例来可视化流量的流动,如图 16.7所示:

图 16.7:PetBattle 流量流动

22 istio.io/latest/docs/

请注意,图 16.7显示了如何使用目标规则将流量发送到服务的备用版本的示例。这对于高级部署策略,如金丝雀发布、蓝绿部署等非常有用。虽然我们在本书中没有讨论如何使用 OpenShift Service Mesh 实现这一点,但鼓励读者深入探讨这个领域。一个很好的起点是上述的 Istio 文档。

当只有少数服务时,管理所有这些资源相对简单,且 PetBattle 在非常基础的方式下利用了服务网格功能。然而,当服务和功能增多时,例如在高级部署模型中使用多个目的地时,需要解析的设置和 YAML 文件量可能会变得非常庞大。这时,网格可视化功能就能派上用场,帮助可视化所有内容是如何协同工作的。为此,我们使用 Kiali 功能,它是 OpenShift 服务网格的一部分。图 16.8 展示了如何使用 Kiali 可视化 PetBattle。

图 16.8:PetBattle 的 Kiali 服务图

Kiali 对于诊断当前网格状态非常有用,它能够动态显示流量的去向以及使用的任何断路器的状态。它还与 Jaeger 集成,用于跨多个系统追踪请求。Kiali 还可以通过语义验证已部署的服务网格资源,帮助防止配置问题。

接下来,我们将探索 OpenShift 4 中最强大的功能之一——Operators(运维管理器)。

运维管理器无处不在

OpenShift 4 平台的核心概念是运维管理器。到目前为止,我们已经使用了它们,但没有讨论为什么我们需要它们,以及它们在 Kubernetes 平台(如 OpenShift)上实际代表什么。我们将简要介绍这一点,而不对该主题进行全面重写。23

从本质上讲,运维管理器是一种软件模式,它将关于特定软件应用程序的运行和操作的知识进行编码。这个应用程序可能是一个分布式的键值存储,例如 etcd。也可能是一个 Web 应用程序,如 OpenShift Web 控制台。运维管理器的根本作用是表示任何可以被编码的应用领域。运维管理器的一个很好比喻是专家系统,一种基于规则的软件,代表了对某个事物的知识,并以有意义的方式投入使用。如果我们以数据库为例,运维管理器可能会编码人类数据库管理员每天需要做的事情,如数据库的部署、运行、扩展、备份、修补和升级。

运维管理器的物理运行时不过是一个 Kubernetes Pod,即一组运行在 Kubernetes 平台(如 OpenShift)上的容器。运维管理器通过扩展或向现有的 Kubernetes 和 OpenShift 平台 API 添加新 API 来工作。这个新的端点称为自定义资源CR)。CR 是 Kubernetes 中许多扩展机制之一。

图 16.9:运维管理器模式

23 www.redhat.com/en/resources/oreilly-kubernetes-operators-automation-ebook

自定义资源定义CRD)定义了 CR 是什么。可以将其视为 CR 的定义或架构。Operator Pod 监控与其自定义资源相关的事件,并采取协调措施以实现系统的期望状态。当 Operator Pod 停止或从集群中删除时,它所管理的应用程序应该继续运行。从集群中删除 CRD 会影响它所管理的应用程序。事实上,删除 CRD 会进而删除其 CR 实例。这就是 Operator 模式。

通过 Operator,运行/管理一段软件所需的所有操作经验都可以打包并作为一组容器及相关资源交付。事实上,整个 OpenShift 4 平台本身就是一系列 Operator 的集合!因此,作为平台拥有者,你实际上是通过 Operator 获得了最先进的管理员知识。更好的是,随着新功能和能力的增加,Operator 可以随着时间的推移变得更加先进。OpenShift 平台管理员需要对如何配置 Operator 有很好的理解。这通常涉及在 OpenShift 集群全局配置网页控制台中设置属性,设置 CR 属性值,使用 ConfigMaps 或类似的方法。产品文档 24 通常是了解每个 Operator 设置的最佳来源。

图 16.10:OpenShift 集群设置—配置平台 Operator

24 docs.openshift.com/container-platform/4.7

在 OpenShift 中,Operator 本身的生命周期管理(升级、修补、管理)是通过Operator 生命周期管理器OLM)自动化的。这些组件使得从用户的角度来看,升级 OpenShift 平台变得更加可靠,且更容易管理;它大大减轻了操作负担。由于 Operator 本身是作为版本化镜像交付的,我们从不可变容器镜像中获得了与我们自己应用程序相同的好处,即相同的镜像版本可以在多个云环境中一致地运行,从而提高了质量,消除了雪花现象(针对特定环境的独特应用)。

不仅是 OpenShift 平台本身能够利用 Operator。通过 Operator Hub25 共享和分发软件对全球的开发者和供应商开放。我们使用 OLM 和 Operator 订阅将其部署到我们的集群中。构建和开发 Operator 所需的工具(SDK)是开源的,任何人都可以使用。26

那么,所有应用程序都应该作为 Operators 发布吗?简短的答案是“不”。编写、打包、测试和维护一个 Operator 可能对许多应用来说显得有些过度。例如,如果你的应用并不需要与他人共享或分发,只需要在几个集群中构建、打包、部署和配置应用,那么有许多更简单的方法可以实现这一目标,比如使用容器镜像、Kubernetes 和 OpenShift 原生构件(BuildConfigs、Deployments、ReplicaSets、ConfigMaps、Secrets 等)以及像 Helm 这样的工具来实现目标。

后台操作符

要完全理解 Operator 如何工作,你需要了解 Kubernetes 控制循环是如何工作的。

控制循环

简单来说,Kubernetes 核心只是一个 键值KV)存储——一个带有 API 的 etcd 数据存储。进程使用这个 API 对 KV 存储中的键执行 创建读取更新删除CRUD)操作。进程还可以向 KV 存储注册,以便在它们感兴趣的键或键集合发生变化时收到通知。

25 operatorhub.io

26 github.com/operator-framework/operator-sdk

当这些进程接收到变更通知时,它们会通过执行某些活动(例如配置 iptables 规则、配置存储等)来响应通知。这些进程了解系统的当前状态和期望状态,并朝着实现该期望状态的方向努力。换句话说,这些进程正在执行 控制循环 的角色,意味着它们试图将系统的状态从当前状态带到期望的状态。

在这个例子中,过程是一个控制器,它观察一个资源或一组资源的状态,然后进行更改,将资源状态向期望状态靠拢。作为 Kubernetes 的使用者,我们不断地使用控制器。例如,当我们指示 Kubernetes 部署一个 Pod 时,Pod 控制器就会为实现这一目标而工作。控制循环是 Kubernetes 操作的关键,它是一种声明式的,并最终一致的方法。要了解更多信息,请查看 Kubernetes 控制器文档 27 和 OpenShift 博客网站,获取如何构建自己 Operator 的推荐 28。

Operator 范围

操作符可以是集群范围的,也可以是命名空间范围的。集群范围的操作符在一个命名空间中安装一次,可以在其他命名空间中创建和管理资源;也就是说,作用于整个集群。OpenShift 服务网格操作符及其相关操作符,如 Kiali 和 Jaeger,都是集群范围的。它们默认安装在 openshift-operatorsopenshift-operators-redhat 命名空间中,并在部署相关 CRD 到其他命名空间时创建和管理资源,如 PetBattle。

命名空间范围的操作符是指在某个命名空间中部署,并且只管理该命名空间中的资源。我们在 PetBattle 中使用了多个这样的操作符,如 Cert-Utils 和 Keycloak。

所有操作符通过名为Subscription的 CRD 安装。简要来说(详细信息请参见官方文档),Subscription 描述了如何检索并安装操作符实例。以下是我们用来安装 Grafana 操作符的 Subscription 示例。

27 kubernetes.io/docs/concepts/architecture/controller/

28 www.openshift.com/blog/kubernetes-operators-best-practices

apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
 name: grafana-operator
spec:
 channel: alpha
 installPlanApproval: Automatic
 name: grafana-operator
 source: community-operators
 sourceNamespace: openshift-marketplace
 startingCSV: grafana-operator.v3.7.0

要查看 PetBattle 需要的一些命名空间范围的操作符,请运行以下命令。

$ oc get subscriptions
NAME                PACKAGE             SOURCE                CHANNEL
cert-utils-operator cert-utils-operator community-operators   alpha
grafana-operator    grafana-operator    community-operators   alpha
infinispan          infinispan          community-operators   2.1.x
keycloak-operator   keycloak-operator   community-operators   alpha

现在让我们来看一下操作符如何被我们的 PetBattle 团队使用。

PetBattle 中的操作符

我们使用操作符来创建和管理资源,如 Infinispan 缓存和 Keycloak 单点登录实例。我们只需安装 Infinispan 操作符并部署相关的自定义资源,告诉它创建和管理一个复制缓存。我们无需了解如何启动 Infinispan Pods、创建 SSL 证书或提供存储空间。操作符会为我们完成这些工作,如果出现故障或被意外删除,操作符会负责重新创建资源。在 Infinispan 示例中,如果我们删除了 Infinispan K8s 服务,操作符会收到删除通知并自动重新创建该服务。作为开发者,我们无需担心其管理。

将操作符视为代替你处理事务是更简单的理解方式。还可以将多个操作符组合使用,以实现自动化复杂的工作流。例如,我们使用 Keycloak 作为其 SSO 网关和用户管理功能。Keycloak 实例通过 Keycloak 操作符进行部署和管理。我们只需构建并将自定义资源发送到 API,操作符会处理其余部分。操作符管理的资源之一是包含 TLS 证书和密钥的 Kubernetes 密钥,客户端在与 Keycloak 实例交互时需要使用这些证书和密钥。鉴于 Keycloak 是我们应用程序的安全网关,确保所有通信都是加密的显得尤为重要。然而,这对基于 Java 的应用程序来说会造成问题;要使用 SSL,JVM 要求提供一个包含 SSL/TLS 证书和密钥的 Java 信任存储,以便 JVM 能够信任它们。

那么,如何将包含 TLS 证书和密钥的密钥转换为 Java 应用程序可以使用的信任库呢?我们可以使用 Bash 脚本、Java Keytool 以及可能的其他工具来提取证书/密钥,创建信任库,进行转换,最后将证书/密钥注入该信任库。这是手动的、复杂的且容易出错的工作。我们还需要为每个环境重新创建这些信任库,并处理证书过期等生命周期事件。

另外,我们可以使用一个操作符,在这种情况下是Cert-Utils操作符。我们首先在 PetBattle 命名空间中安装Cert-Utils操作符。这个操作符是由红帽咨询 PAAS 实践社区 29 开发的,旨在帮助管理证书和 JVM 密钥库以及信任库。

要使用此操作符,我们首先创建一个包含特定注解的 ConfigMap。Cert-Utils 操作符将检测这些注解,并创建一个包含相关证书和密钥的信任库;它还会将信任库添加到 ConfigMap 中。最后,我们可以将 ConfigMap 挂载到部署中,并指示 JVM 使用该信任库。以下资源定义将创建一个包含相关证书和密钥的信任库。

apiVersion: v1
kind: ConfigMap
metadata:
  annotations:
    service.beta.openshift.io/inject-cabundle : "true"
    cert-utils-operator.redhat-cop.io/generate-java-truststore: "true"
    cert-utils-operator.redhat-cop.io/source-ca-key: "service-ca.crt"
    cert-utils-operator.redhat-cop.io/java-keystore-password: "jkspassword"
  name: java-truststore

29 github.com/redhat-cop/cert-utils-operator

这样做的操作如下:

  • service.beta.openshift.io/inject-cabundle注解将把服务签名证书包注入到 ConfigMap 中,作为service-sa.crt字段。

  • cert-utils-operator.redhat-cop.io注解将在 ConfigMap 中创建名为truststore.jks的 Java 信任存储,并使用jkpassword密码。

在 Tournament 服务中,以下 Quarkus 配置将挂载java-truststore ConfigMap,并相应地配置 JVM。

# Mount the configmap into the application pod in the /tmp/config/ directory
quarkus.kubernetes-config.enabled=true
quarkus.openshift.config-map-volumes.javatruststore.config-map-name=java-truststore
quarkus.openshift.mounts.javatruststore.path=/tmp/config/
# Instruct the JVM to use the Truststore
quarkus.openshift.env-vars.JAVA_OPTS.value=-Djavax.net.ssl.trustStore=/tmp/config/truststore.jks -Djavax.net.ssl.trustStorePassword=jkspassword
# Tell Infinispan client to use the Truststore when connecting
quarkus.infinispan-client.trust-store=/tmp/config/truststore.jks
quarkus.infinispan-client.trust-store-password=jkspassword

我们只是略微触及了操作员的表面。OpenShift 配备了许多受支持的操作员,并且还有许多社区操作员可供使用。在本书中,我们使用了许多基于社区的操作员,例如 Infinispan 操作员和 Keycloak 操作员;这些操作员也有产品化版本。OperatorHub 中还提供了来自多个供应商的更多操作员。30

如果需要,也可以编写自己的操作员。OperatorFramework31 是一个开源 SDK,您可以使用 Go、Ansible 或 Helm 编写自己的操作员。

30 operatorhub.io/

31 operatorframework.io/

服务提供证书机密

Keycloak 使用 OpenShift 的一个功能,称为服务提供证书机密。32 该功能用于流量加密。使用此功能,OpenShift 会自动生成由 OpenShift 证书颁发机构签名的证书,并将其存储在机密中。应用程序(在此情况下是 Keycloak)可以挂载该机密并使用这些证书来加密流量。任何与 Keycloak 实例交互的应用程序只需信任这些证书。OpenShift 还会管理这些证书的生命周期,并在现有证书即将到期时自动生成新证书。

要启用此功能,只需将以下注释添加到服务中:

service.beta.openshift.io/serving-cert-secret-name=<NameOfMysecret>

在 Keycloak 的情况下,操作员会将此作为其处理的一部分:

$ oc get svc keycloak -o yaml
apiVersion: v1
kind: Service
metadata:
  annotations:
    description: The web server's https port.
    service.alpha.openshift.io/serving-cert-secret-name: sso-x509-https-secret
    service.alpha.openshift.io/serving-cert-signed-by: openshift-service-serving-signer@1615684126
    service.beta.openshift.io/serving-cert-signed-by: openshift-service-serving-signer@1615684126

该机密包含实际的证书和相关的密钥:

$ oc get secret sso-x509-https-secret -o yaml
apiVersion: v1
data:
  tls.crt: …...
  tls.key: …..
kind: Secret
metadata:
  annotations:

32 docs.openshift.com/container-platform/4.7/security/certificates/service-serving-certificate.html

它还包含证书详细信息:

$ oc get secret sso-x509-https-secret -o json \
    | jq -r '.data."tls.crt"' | base64 --decode \
    |openssl x509  -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 1283774295358672234 (0x11d0e1eb7ea18d6a)
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = openshift-service-serving-signer@1615684126
        Validity
            Not Before: Mar 15 08:34:54 2021 GMT
            Not After : Mar 15 08:34:55 2023 GMT
        Subject: CN = keycloak.labs-staging.svc
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption

这样的操作员模式简化了在 OpenShift 平台上运行复杂中间件基础设施应用程序的负担。

结论

要能够在生产环境中成功地大规模运行您的软件,需要对围绕软件堆栈的监控机制有很好的理解。OpenShift 是一个现代化平台,提供了所有所需的功能来观察,并且在很多情况下,可以在应用程序运行时自动修复它们。

在本章中,我们讨论了许多常见的技术模式,这些模式使应用程序开发人员能够利用这些常见的平台功能。例如,最简单的模式之一是始终记录到 STDOUT,这样平台的日志机制就可以被利用。对于容器来说,将日志记录到临时文件系统中的特定文件已成为一种反模式,因为这些文件不易查看。

更复杂的模式对于保持业务服务应用程序的运行也非常重要,即使在发生中断和变更时。正确配置存活性、就绪性和启动探针,以确保应用程序可以部署而不会中断服务,配置 Pod 中断预算,以应对节点重启。使用应用程序功能暴露 Prometheus 指标端点以便在平台上进行警报和监控,是一种很好的方式来提醒团队在需要人工干预时采取行动。

服务网格是 OpenShift 的高级扩展,提取了许多传统上会被打包到应用程序中的功能,以便能够在平台层面更高效地管理它们。这是一个常见的主题:将应用程序和开发的跨切面功能提取出来,并将其利用于所有平台服务的利益。

操作符模式简化了在 OpenShift 平台上运行复杂中间件基础设施应用程序的操作负担,将多年的专家知识封装成软件。毫不奇怪,OpenShift 本身就为其所有核心功能采用了这一出色的模式。真正的强大之处在于能够以自动化的方式管理这一复杂性。由于系统能够自愈并自动升级而无需干预,人力劳动大大减少。以更少做更多的理念依然是游戏的规则。

作为一个跨职能的产品团队,一旦你掌握了这些能力,实际上就能够把值班的任务交给开发人员。任何商业服务交付的质量都始于业务发现,接着转向应用软件,扩展到平台功能,最终进入网络世界和通过互联网连接的终端设备。一旦开发人员和跨职能产品团队被授权在每一个需要运行的环境中构建、运行和拥有他们的软件,只有那时,他们才能真正地将客户满意度与他们编码、自动化并持续交付的软件供应链联系起来。

第七部分:改进与维持

本书是一次旅程。在第二部分,建立基础中,我们为我们的应用产品团队建立了基础。我们将该团队带入了第三部分,发现它中的发现循环部分。接着,他们进入了第四部分,优先考虑它中的选项枢纽,然后进入了第五部分,交付它中的第一次交付循环迭代。

然后,我们在第六部分,构建它、运行它、拥有它中与 PetBattle 产品团队一起深入构建应用程序。

在 Mobius Loop 旅程的最后一部分,我们回到了选项枢纽,并问自己从交付循环中学到了什么。

图 17.0.1:改进与可持续性——设定场景

接下来我们应该做什么?我们应该重新进入交付循环,还是返回到发现循环?再回顾一下我们的选项怎么样?这些问题将在第十七章,改进它中得到回答。

图 17.0.2:选项枢纽

第十八章,维持它中,我们将回顾我们所走过的整个旅程,并探讨如何维持这种工作方式,包括如何维持我们在本书中所做的人、流程和技术的改进。然后,我们可以开始将这些工作方式在整个组织中扩展和传播到其他团队。

第十七章:17. 改进它

我们做到了!我们成功绕了一圈莫比乌斯环。

首先,我们建立了开放文化、开放领导和开放技术的基础。我们成功地绕过了发现循环,使用了北极星和影响力映射等实践来发现我们的“为什么”,并使用同理心映射和其他以人为中心的设计工具来发现我们的“谁”。我们甚至通过开始事件风暴、非功能性映射和基于度量的流程映射,开始了我们如何的发现。我们所做的一切都是为了收集足够的信息和集体共享的理解,以推导出一些可衡量的目标结果。

我们使用这些目标结果来引导我们通过选项枢纽。我们探讨了几种优先级排序技术和实践,例如用户故事映射、价值切片、影响与努力优先级排序、如何/现在/哇优先级排序、设计冲刺和加权短作业优先级排序,以产生我们初步的产品待办事项列表。我们设计了进入该待办事项列表的实验。

然后我们进入了交付循环的第一次迭代,功能被编码,应用被编写,我们进行了实验和研究。我们利用了既定的敏捷实践,以便尽可能快地实现对原始结果的度量和学习。

在之前的章节中,我们更深入地探讨了团队用来构建、运行和拥有解决方案的技术。

在莫比乌斯环的每一部分,我们收集了宝贵的信息,这些信息在每个章节的画布中进行了总结。如果我们将这三张画布拼接在一起,就能看到一切是如何连接的:

  • 发现:

    • 我们是为谁做的,为什么?

    • 他们的问题、需求或机会是什么?

    • 我们设定的客户和组织结果是什么?

    • 对结果的影响是什么?

  • 选项:

    • 我们同意交付的行动是什么?

    • 有哪些选项可以帮助实现这些结果?

    • 相对优先级是什么?

    • 我们学到了什么?

  • 交付:

    • 做了什么?

    • 我们说过要研究、实验和发布什么?

图 17.1:莫比乌斯环

本章名为改进它。也许整本书都应该叫这个,因为其实,持续改进才是重点。我们所做的一切都专注于如何持续改进;无论是技术、用户体验、文化,还是我们使用的度量指标。

在本章中,我们将探讨当我们完成交付循环的一个迭代时,我们做了什么:我们学到了什么?我们学得够吗?我们是否朝着可衡量的目标结果前进?最重要的是,我们接下来应该做什么?

我们学到了什么?

第十三章度量与学习中,我们探讨了我们用来通过以下方式度量和从我们交付的产品增量中学习的技术:

  • 来自展示和回顾会议的反馈

  • 从用户测试中学习

  • 捕捉实验结果

  • 服务交付与运营表现

  • 服务级别协议、服务级别指标和服务级别目标

  • 安全性

  • 性能

  • 文化

  • 应用指标

  • 基础设施平台和资源使用情况

这种学习非常重要,应该推动关于所学内容的对话、推论和结论。这就是为什么可视化这些指标如此强大的原因。我们可以立即看到当前的衡量标准是什么,上一轮交付循环之前的衡量标准是什么,以及为了实现预期成果和影响需要达到的目标衡量标准是什么。

如果对话表明我们无法从这些指标中学习,我们需要检查为什么会这样。进行一次深入的回顾,以探讨为什么我们没有从交付循环中学到足够的东西,这可能会非常有帮助。像五个为什么或石川图这样的技术是进行深入回顾的优秀方法,有助于推动这些讨论并推动团队实施改进措施,从而促进学习。

最终,团队需要决定是否正在衡量重要的指标,是否这些衡量标准准确并能够反映他们的工作,数据是否能可靠地引导他们朝着目标结果前进。团队应当问自己的最重要问题是:我们学到足够了吗?

我们学到足够了吗?

在莫比乌斯循环的这一阶段,我们有一个非常重要的决定要做。从交付循环中出来后,我们可以决定是否再绕一圈交付循环,或者根据在交付循环迭代中捕获的指标和学习返回选项枢纽,重新审视并重新优先排列我们的选项。否则,我们可以继续返回探索循环。

基于我们学习的内容,我们喜欢提出以下问题:

  • 根据我们交付的结果,我们是否达成了一个成果?还是我们需要从选项列表或产品积压中提取更多项目,进行更多的交付?如果是的话,向右转,再次绕过交付循环。

  • 最近的交付循环中捕获的指标是否表明我们已经达成了一个或多个可衡量的成果?如果是,回到探索阶段验证这一点,并朝着下一个成果努力。

  • 我们从交付中学到的内容是否验证、推翻或改善了在探索阶段做出的假设和假设的理解?如果是的话,让我们回到探索阶段,更新这些文档。

  • 我们从交付中学到的是否给了我们关于选项优先级的新信息?如果是的话,让我们回到选项枢纽,重新审视一些优先级设定的做法。

  • 实验结果是否为我们提供了新的或改进的实验思路?如果是的话,让我们回到选项枢纽,设计这些实验。

随着时间的推移,你会进行更多的发现和交付循环迭代,并在选项转折点上花费更多时间。Mobius 循环提供了一个极好的可视化,展示了你在每个循环上花费了多少时间,你绕每个循环的速度有多快,以及你在不同循环之间转变的频率。它还将帮助你了解你在持续发现和持续交付之间的平衡程度。

一些需要注意的警告信号包括:

  • 我们只是不断在交付循环中打转。这表明我们没有花时间重新审视和重新评估结果,而是朝着成为一个特性工厂的方向发展,盲目地构建输出。

  • 我们在发现循环上花费了太多时间。这表明我们处于分析瘫痪的状态。我们过度思考和过度分析我们的“为什么”和“谁”,但从未测试过我们的想法或假设。我们可能会错过市场机会,或者什么都不交付。

  • 我们从发现跳跃到交付。这表明我们没有从发现阶段中获取学习并进行提炼、组织,并作出关于下一步交付内容或如何更快地获取知识和学习(例如,通过研究或实验,而不是盲目地构建特性)的一些重要决策。

  • 我们从交付跳跃到发现。这表明我们没有花时间将学习反馈到我们的选项和优先级中。

  • 我们从未回到 Mobius 循环的其他部分。这表明我们没有以迭代或增量的方式工作,也没有将学习融入我们的工作体系中。这实际上是线性的工作,正如我们在第十二章《执行交付》中所看到的,当我们探讨Cynefin时,只有在工作处于简单领域时,它才是一个真正好的解决方案。

让我们来看一个故事,其中一个重要的转折点和循环之间的变化是由学习引发的。

我们需要两个应用,而不是一个!

这来自我们第一次欧洲开放创新实验室的驻地 1。一家瑞士初创公司希望通过一个远程签到应用来颠覆医疗保健领域,专为急诊室ER)服务。

我们与客户一起使用事件风暴(Event Storming)来理解业务流程并识别关键的用户流程。团队使用用户故事映射(User Story Mapping)来识别可以用于测试市场的早期切片,随后通过 Scrum 交付了一个非常早期的产品,分为三轮每轮一周的冲刺。

第一次交付的重点完全放在患者需要去急诊室时使用的应用程序上。它完全关注患者的体验,并且大部分用户研究都是针对这些患者角色的。

当利益相关者看到应用的第一个增量时,突然有了一个顿悟时刻。为了完全理解、领会并从这个解决方案中学习,我们需要为两个应用而不是一个应用提供原型。医生和护士筛选请求的体验将是关键。

这促使团队立即回到发现循环,专注于这些用户角色和流程,因为团队对他们的需求和问题缺乏深入了解。

1 www.redhat.com/en/blog/red-hat-welcomes-swiss-based-medical-company-easier-ag-waterford-its-emea-open-innovation-labs

这是许多例子中的一个,在这些例子中,团队收集了足够的信息和反馈,触发了思路的转变和方向的调整。

“足够的”促使了持续的一切

在本书中,我们多次使用了足够的这个词。就在开篇几段中,我们就曾想过,你是否从本书的封面信息中获得了足够的信息,去探索更多内容。在第七章《开放技术实践——中点》中,当我们构建技术基础时,我们说我们只构建了足够的架构,以便产品开发能够继续推进。当我们在第八章《发现为什么和谁》中进入发现循环时,我们使用了一些实践来获取足够的信息,以便使团队保持一致,达成共享的理解,并具备足够的信心去交付早期实验。在第九章《发现如何做》中,当我们开始通过事件风暴等实践来发现我们的“如何做”时,我们获得了足够的信息,能够设计出各个组件之间如何交互。在第十章《设定成果》中,我们解释了我们总是进行足够的发现,以推动选项转变并开始交付的迭代。

现在,我们已经经历了交付循环,最初的足够的信息已经增长。相比刚开始时,我们现在知道的更多了。通过交付实践、用户测试、实验结果和度量系统所捕获的措施和学习,我们现在有机会重新审视和更新发现实践。让我们看几个例子。

第八章《发现为什么和谁》中引入的影响力图生成了大量假设陈述。例如,在我们的 PetBattle 案例研究中,我们假设创建每日锦标赛功能将增加上传者的站点参与度,从而帮助我们实现年度目标,即通过现有和新客户群体在年底前创收 10 万美元。嗯,我们现在已经完成了这个功能。事实上,我们在第十一章《选项转变》中创建了 A/B 测试,并设计了一个关于下一个锦标赛何时开始的实验。结果告诉了我们什么?我们实现了更多站点参与度的影响吗?这个假设是对的还是错的?让我们更新影响力图。

我们遇到了玛丽,我们的其中一位用户。我们进行了同理心映射(Empathy Mapping)实践,并使用了第八章中其他以人为中心的设计技巧,发现“为什么”和“谁”。我们听到她说她非常喜欢前三名的排行榜。我们首先和玛丽进行了一些用户原型设计和测试。我们可以在最新的《宠物大战》(PetBattle)软件上再做一次同理心映射,捕捉她对软件最新增量的看法、听到的内容、看到的内容和她的言论。让我们与玛丽和其他用户一起更新或创建新的同理心映射。

我们使用事件风暴(Event Storming)让我们对玛丽参与日常比赛并赢得奖品的业务流程有了足够的了解。在事件风暴中有许多粉色方形便签,代表假设、问题和未知事项。现在,我们交付了一些功能,进行了研究,开展了实验,发展了对话,因此我们知道了更多信息。我们可以更新事件风暴,甚至可能开始生成系统的全新部分或功能领域。

通常,我们只有三个或四个目标成果(Target Outcomes)。也许我们现在已经达成了这些目标,或者接近达成它们;我们的学习和措施可能促使我们重新思考或重写目标成果。也许我们需要考虑下一个目标成果,以将我们的应用提升到一个新的水平,并保持竞争优势。

我们的用户故事地图(User Story Map)、价值切片板(Value Slice Board)和优先级实践已在第十一章选项转变中介绍。我们现在已经交付了价值最高切片或切片中的项目。我们的学习可能促使我们重新考虑现有的优先级,或者重新划分并重新规划价值交付。让我们更新这些文档,以反映我们最新的观点。

我们的产品待办事项(Product Backlog)始终准备好进行更多的产品待办事项细化。随着发现和选项转变(Discovery and Options Pivot)文档的所有更新,肯定需要再次查看和更新。让我们细化产品待办事项。

因此,我们从所有实践中产生的所有文档从未完成——它们是不断发展的、有生命的文档。它们应该始终对团队成员和利益相关者可见且易于访问。我们根据衡量标准和学习的频繁更新这些文档,我们的核心产品待办事项(Product Backlog)就会更有价值,我们的产品也能更快响应并适应用户、市场和利益相关者的需求。这就是所谓的业务敏捷性

向安全专家学习

在技术和敏捷社区中,关于安全如何融入敏捷流程存在许多争议。我记得曾和一家爱尔兰电信公司的安全负责人进行过长时间的对话。他说,“敏捷和安全永远不能融合,永远也不会融合。

我对此感到好奇,想要更深入地理解(并产生共鸣)。通常,他会在新系统或应用上线前的一到四周内参与到项目中。他会仔细审查每个使用的框架、逻辑架构、物理架构、托管和数据存储,最终会进行一系列的安全渗透测试。通常,他不得不加班到晚上和周末,因为他的安全签署是与相关产品商业发布之前的最后一个里程碑之一。

我们决心尝试将这个安全分析和相关检查左移,将安全控制员纳入到流程中,使渗透测试变得更加持续和自动化。

我们首先邀请安全控制员参加所有的 Sprint 评审展示活动。他没有参加前几次,但最终参加了一次。我认为他并不指望能从中得到什么,但他成了会议中最活跃的利益相关者之一。他向我们询问了代码中使用的框架版本、UI 中是否考虑过跨站脚本攻击,以及我们在 CI/CD 中使用了哪些开源工具。我们没有所有的答案,但我们确实在产品待办事项中记录了一些需要研究的内容,并在下一个 Sprint 中安排了精炼工作坊。

几个 Sprint 后,我们展示了从早期反馈中捕获的研究项的输出、成果和学习成果。我们还展示了如何通过自动化代码扫描和漏洞检查扩展了我们的 CI/CD 流水线——并且我们正在研究自动化解决方案,以应对跨站脚本攻击。

安全控制员意识到这不会是一个在上线前几天就让他承受巨大压力的项目。团队正在向他学习,他将自己的需求和知识注入到自动化中。他仍然会留出一周的时间来进行渗透测试,但他感到更加自信。最终,这成为了一个非常积极的经验,也是史上最迅速的安全审批之一。

这次经历突显了在重要利益相关者(如安全控制员)中增加信心的重要性,并且不断地在团队和利益相关者之间分享学习。当然,信心可以通过像信心投票这样的实践来衡量!

这个故事突显了始终改进度量和自动化的重要性。如果某人反复进行相同的活动(例如安全测试),那么它就是自动化的候选者。

始终改进度量和自动化

我们在第十三章衡量与学习中介绍了一系列度量标准。这并不是一个一次性、有限的清单作为指导,而是一个初步的建议集。当我们基于利益相关者反馈发现需要度量的地方时,我们应当将其加入,特别是当我们将其作为提高学习或信心的机会时。

自动化收集、分析和展示(或可视化)指标是将反馈闭环接近实时的关键。开发人员可以实时获取代码提交的影响反馈,而技术利益相关者则可以从对构建交付物进行的质量检查中获得实时反馈、保障和信心。与此同时,产品负责人和业务利益相关者可以获得关于应用功能使用和采纳的数据;而组织则可以看到其整体的交付新功能的能力和速度。

这与我们如何通过指标可视化和量化持续交付基础设施的全面影响紧密相关。

重温基于指标的过程图

第十章设定结果中,我们介绍了基于指标的过程映射实践,作为发现持续交付理由的工具。我们在许多不同的组织中多次使用了这一实践。在采用新实践、技术和文化后,产生的指标令人震惊。

最近,如果我们与一个全新的团队合作,我们选择不在发现阶段的第一次迭代中使用这个实践。虽然这似乎是捕捉遗留流程指标的好方法,但我们已经了解到,新团队缺乏文化基础或心理安全感,无法参与这一活动。你必须问非常深刻的问题,比如你做这个过程需要多长时间?以及你多长时间会犯错或出现不准确的地方,这些问题会在后续任务中被发现,意味着你需要重新做你的部分*?如果文化中没有安全感,可能会导致误导性答案,甚至损害文化。然而,我们发现,在通过一些交付循环后再进行这一实践非常棒。

我的管理层只真正理解数字和电子表格

当我与一家欧洲汽车公司合作时,我每天与产品负责人和 ScrumMaster 进行敏捷教练会议。这是一种“随便问我任何问题”的会议类型,通常是一个很好的机会,可以聊聊工作方式,并生成未来可以尝试的不同想法。

在我们参与的倒数第二周,产品负责人表示他非常满意团队和在过去五周中不断发展的产品。他喜欢信息显示器和可视化;他喜欢每周通过展示会看到产品如何变得生动,并且团队每周都与实际的最终用户见面,进行软件和方法的测试、实验和验证。技术非常棒,团队在 CI/CD 和其他自动化方面的工作令人惊叹!

但他也感到担忧。这个驻场项目是在一个临时的外部设施中进行的。他带了一些同行来“走墙”并亲眼看到工作方式的实际应用,但很多他的领导层并没有见过。他解释说,他的管理层只真正理解数字和电子表格——他们并不完全相信使用便签和五彩斑斓的墙壁。那他该如何证明这真的是一种更好的工作方式呢?

这让我想起了我们在开放实践库中还未使用过的一项实践:基于度量的流程映射。这正是解决这个特定问题的工具。所以,我们拿了一些便签和一块巨大的可移动白板。

图 17.2:为一家欧洲汽车公司使用基于度量的流程图

我们记录了从业务或用户请求一个功能到该功能在生产环境中运行之间的所有事情。我们捕捉了交付时间、过程时间以及完整性和准确性度量,完全按照第九章中描述的实践,发现工作方法

我们记录了他们过去交付软件增量的旧方式和新方式的度量数据。对于旧方式,他们会从一些工单系统中筛选出有用的度量数据。而新方式,则通过 Jenkins、GitHub、Ansible 以及其他在项目中使用的工具收集度量数据。他们还可视化了旧团队结构和新团队结构。最终的看板如图 17.3所示:

图 17.3:记录旧方式和新方式的不同度量数据

便签上的实际数字和信息对于本书来说并不重要,但这个流程和最终的可视化展示了很多关键的学习点。每一张粉色的便签代表着功能请求和功能上线之间发生的一个事件

所有位于红线下方的内容代表着旧有的功能交付方式。这些任务由不同部门和团队的人员完成——每个部门或团队用横向的黄色线条表示。

当你从左到右阅读粉色便签时,每一次你跨越一条黄色线去阅读右侧下一个便签,就代表着任务的移交或交接给另一个团队。这通常涉及到提交工单、预约资源、预留时间进行沟通以及等待或排队。

所有位于红线以上的内容代表了我们驻场项目启动后,这个新方法交付软件产品的方式。它们都位于同一条横线上,因为这是一个新的、长期存在的跨职能产品团队在完成所有工作。基础设施、设计、开发、测试、部署和运营都由这个新产品团队负责。

绿色线条表示上方粉色任务已被自动化。这就是持续集成/持续交付(CI/CD)的实际应用!这个管道实现了自动代码扫描、构建、容器化、部署、单元测试、端到端测试、合同测试、漏洞扫描、安全性测试和 UI 测试。当我们总结旧方法与新方法,并考虑不同类型开发工作可能带来的一些细微差别时,持续交付的可衡量影响令人震惊。这一点在一个高层领导小组面前展示时,引起了几声惊叹。

表 17.1:衡量新工作方式的影响

一位利益相关者问道,是什么主要促成了这些数字变化的?产品负责人解释说,原因有三。部分归因于所使用的技术,包括 OpenShift 平台;部分归因于工作方式,包括采用的 Mobius Loop 和 Foundation 实践;但最重要的是,这一切归功于团队。这个长期存在的产品团队证明了他们不仅能够实现这些指标,而且还会进一步提升它们。

前述故事中的三个贡献因素是我们应通过持续学习不断改进的内容:改进技术、改进工作方式和改进团队。

本章大部分内容讨论了工作方式,以及我们如何使用 Mobius Loop 模型促进持续改进。接下来我们将考虑技术和团队。

改进技术

编写任何关于技术的书籍所面临的挑战在于,它会很快变得过时——只需问问几年前编写《DevOps 与 OpenShift》的两位作者即可。在《DevOps 与 OpenShift》写作之后,许多模式、技术和产品都发生了变化,我们完全预期这种变化会在未来几年继续发生。

第六部分,构建它,运行它,拥有它,聚焦于 PetBattle 技术以及团队如何在 2021 年构建、运行和拥有它。如果你在 2025 年读这本书,我们希望本书中的许多章节仍然有效,但第六部分可能已经发生了变化。

对团队而言,至关重要的是要给予足够的时间、空间和能力,去学习新技术、进行实验、研究并实施新产品和框架,以便不断改进他们所构建解决方案中的技术。

对于一个长期存在的产品团队来说,这突显了优秀产品所有权的重要性,以及经过精心优化的产品待办事项,它们能够正确表达所有工作项的价值。这包括非功能性的工作,旨在改善和发展架构,同时利用新技术对其进行更新。

我们在 Red Hat 使用的一些方法,也一直鼓励我们的客户使用,具体包括:

  • 分配时间给技术、研究和实验

  • 对技术、培训的强有力定期投资,并在适当时提供认证

  • 围绕技术的实践与兴趣社区

  • 建立技术书籍俱乐部

  • 组织并主持定期的网络研讨会、午餐学习会以及其他社区活动

  • 参加外部技术会议

  • 参加内部会议(例如,Red Hat 每年为所有服务和技术预售人员举办为期一周的内部技术交流活动)

最重要的是,倾听并从团队成员那里学习,并在每个迭代中预留时间促进学习。持续投资于团队学习是高绩效、长期存在的团队最强大的成功因素之一。

团队万岁

从本书一开始,我们就提倡长期存在的跨职能团队的重要性。在第一章,导论—从为什么开始中,我们讲述了如何帮助 I 型人才(在某一特定技能上有深厚专长)变得更加 T 型(具备多功能的知识和单一技能深度),甚至是 M 型(具备多功能的知识和多项技能深度)。

上述内容我们探讨了如何改善技术。那些建议实际上并不是直接改善技术,而是集中在使用技术的人,并提升他们使用技术的能力。我们如何衡量并学习提升团队技能的影响?我们可以通过哪些方式将其可视化?

可视化从 I 型到 T 型再到 M 型的过渡

我们近年来在改进的一个实践是捕捉、衡量并可视化团队的技能。当我们组建一个新团队时,例如开始一个驻场项目时,了解个体的技能和经验非常有帮助。但我们希望以心理安全的方式来进行此项工作,而不是让新同事感到被威胁或是受到考核。所以,我们停止了技术测试,因为它影响了团队士气。取而代之的是,我们使用一个简单的可视化工具,比如蜘蛛图,来展示跨职能团队中不同能力所需的技能水平(0-5):

图 17.4:使用蜘蛛图可视化团队技能

当然,这也可以通过简单的电子表格软件来完成:

图 17.5:使用电子表格分析团队技能

它还可以通过不同种类的图表进行可视化:

图 17.6:使用直方图分析团队技能

作为一个长期存在的团队,这是一种值得养成的好习惯。团队可以定期检查是否有团队学习的发生,团队及个人是否正在变得更加跨职能。

巫师与牛仔

我们最近使用的另一个有趣的学习文化实践是“巫师与牛仔”的类比。在第六章,开放的技术实践——起步,正确开始中,我们解释了为什么我们试图根除独角兽开发者。我们不希望团队中有那种拥有所有知识并且高度依赖的英雄人物。

与一家地理空间公司合作时,我们在一次回顾中注意到,存在个体在学习英雄式的技能,但知识没有得到分享的风险。团队一致决定购买一顶巫师帽,当发生这种情况时,取得惊人成就的人需要戴上巫师帽,直到这些知识以对等方式,甚至更好的方式在群体会议中分享。

牛仔帽还用于突出反模式和一些不应该做的事情(例如,应用-DskipTests=true来部署构建,而不运行自动化测试)。如果发现此类行为,责任人必须戴上牛仔帽,直到反模式与全体团队分享并且得到教育。

这是一种有趣的做法,为更好的团队文化创造了一个安全的空间。同时,它也是一种教育方式,促进了持续学习。

结论

在这一章中,我们探讨了当我们走出交付循环的迭代时,应该怎么做,以确保我们在再次进入交付循环、回到选项转折点或返回发现循环时能够持续改进。我们考察了如何从交付中获取度量和学习,评估我们学到了什么,以及这些是否足够决定接下来的步骤。

我们现在正处于从发现到交付再到发现的持续创新流中。我们从刚好足够刚好及时的信息开始,推动进程。我们一直在不断学习。

我们回顾了如何通过回到基于度量的过程映射实践,来衡量我们新系统和工作方式的改进,以量化技术、工作方式和团队的进步。我们认识到在这些方面持续学习和持续改进的重要性。

在本书的最后一章,我们将探讨如何维持我们在本书中所涵盖的一切。我们专注于一个团队和一个梦想。这是有意为之。在我们寻求维持这一点时,我们还将看到如何重用本书中使用的所有模式、方法和实践,以便将产品中心思维扩展到应用和平台,甚至到领导力和战略。

第十八章:18. 维持它

在上一章中,我们从 Mobius 循环的一次性旅程过渡到持续不断、永无止境的学习与改进旅程,这个过程基于以度量为基础的结果,覆盖了第八章发现为什么和谁,到第十三章衡量与学习

本书重点讲述了一个团队如何在 Mobius 循环中从发现(Discovery)到选项(Options)和交付(Delivery),再回到发现(Discovery)的全过程。开放实践库(Open Practice Library)帮助将 Mobius 实现为一个真正基于结果的交付框架,建立在开放文化和开放技术的基础上。

PetBattle 案例研究讲述的是一个团队如何实现一个共同的目标:成为一个高效能的跨职能、长期存在的团队,交付出色的产品,并充分利用底层技术,包括 OpenShift。通过人员、流程和技术的结合,他们成为了灵感的种子和感染力十足的激情触发器。

Mobius 不仅仅是一个团队可以遵循的地图;它是整个工作系统的导航器。

在本章中,我们将提出这样一个问题:我们如何利用 Mobius 和 开放实践来推动这种 持续发现、持续交付以及在文化和技术基础上进行选项转变的思维方式,在更大规模的组织中推广? 换句话说,我们如何让 50 个团队像 PetBattle 团队那样协同工作?我们将探索如何使更多的应用产品团队以相同的方式工作,这对平台意味着什么,对领导力和组织战略又意味着什么。

让我们从可视化迄今为止的旅程开始。

迄今为止的旅程

让我们使用 Mobius Kit1 中的另一个开源工具,Mobius Navigator。它提供了一个宏观视角和 Mobius 背后的基本理念,可以应用于任何复杂系统。Mobius Navigator 是一种可以应用于组织各个层级的思维模型。

图 18.1:Mobius Navigator

1 www.mobiusloop.com/kit/

我们通常从左侧循环开始,尽管我们不一定非要这样做。有时,遗留系统和组织会在交付过程中从右侧循环开始,并会尽早寻求转向左侧循环。这是因为关于发现阶段有许多重要而强有力的问题需要提出,而组织越早开始回答这些问题,就越有利。

当我们从左侧开始环绕莫比乌斯导航员时,我们会提出强有力的为什么问题。我们为什么要这么做?我们为谁而做,这将如何帮助他们?我们发现真正发生了什么,以便得出成果。然后,我们产生想法,完善并规划如何交付选项,决定接下来要处理哪些选项。我们通过设计、创建和启动来交付,并在反思和改进中通过衡量影响来学习。最后,我们通过发现更多、交付更多或完全转变来适应。

莫比乌斯循环让我们能够将莫比乌斯导航员的思维方式深入到一个产品团队,探索他们如何使用发现地图、选项地图和交付地图来持续进化产品,交付具有实际意义的成果,同时内建持续的衡量和学习。我们在 PetBattle 和本书中穿插的许多故事中看到了这一过程的实际应用。

我们在第十章《设定成果》的末尾使用了发现地图来总结第三部分《发现它》,并捕捉在发现循环中所获得的所有学习成果。我们在第十一章《选项转折》的末尾使用了选项地图来总结第四部分《优先处理》,并捕捉在选项转折过程中所做出的所有学习和决策。我们在第十三章《衡量与学习》的末尾使用了交付地图来总结第五部分《交付它》,并捕捉在交付循环中所交付的影响与学习。如果将这些地图连接起来,我们可以看到莫比乌斯循环是如何将这三部分连接在一起,并总结产品演变过程中的学习成果。

图 18.2中有很多细节,你可以在上面提到的各章末尾查看单独的地图,或者访问本书在 GitHub 仓库中的 PDF 版本。

图 18.2:PetBattle 莫比乌斯地图:发现、选项、交付

现在,假设在一个组织中有 10 个不同的产品团队以这种方式工作,每个团队都拥有一套属于自己负责的产品或产品领域的地图,这会怎么样呢?

这个想法之所以变得可行,是通过传染性热情、平台的广泛采用,以及在整个组织不同层级上增强的产品思维。

这是一个旁注,但本书的大部分内容都是在 COVID-19 大流行期间写成的,那时感染传播是非常负面的现象。现在能够写下关于一些具有积极影响的“传染性”现象,真是令人愉快!

传染性热情

我们倡导从一个团队开始的原因之一是通过在组织或部门的一个小范围内进行测试、学习、检查和适应。以单个团队为起点,并展示快速成功的案例,可以在任何传播开始之前展现出非常强大的影响力。这个团队就是我们的“零号病人”2。通过首先采取这一团队的方法,我们实际上是在采取一种敏捷的方法,朝着更敏捷的工作方式迈进。

2 zh.wikipedia.org/wiki/案例

对于第一个团队,我们希望通过做书中所描述的一切,并采用开放实践库中的方法,帮助他们尽可能快速地学习。没有任何培训课程、YouTube 视频或书籍能够单独使一个团队立即按照本书设想的方式工作,并实现预期的结果。我们希望你从这本书中获得了很多价值,并且充满了灵感和信息,但我们并不认为你的团队会在读完整本书后立刻转变为这种工作模式。

人员和团队可以通过亲自经历整个过程来获得赋能。他们必须建立自己的文化和技术基础。他们必须经历发现循环、选项枢轴和交付循环,亲身体验 Mobius 导航员的思维方式。在这个过程中,他们需要一个应用程序、一个系统,一些能够为真正的终端用户创造商业价值的东西,这样他们才能有机会自己去构建、运行和拥有它。我们建议从一个具有实际商业价值但相对较小的项目开始,这样你可以通过一个小团队进行实验并学习。

使用本书中描述的所有方法和原则将一个应用程序投入生产是一项挑战,你的团队几乎肯定会在整个过程中发现瓶颈和阻碍。好消息是,导航员和思维模型的设计就是为了解决这些问题。

能够按照第十七章《改进它》中描述的方式进行改进,能使这些瓶颈和阻碍逐一被解决,并通过度量标准进行验证。许多根本性的阻碍来自于组织中的“信息孤岛”,这些孤岛需要被打破。产品团队需要通过自主、精通和目标感,能够打破这些“信息孤岛”,直到能够交付生产!

这样的团队在执行任务以展示新工作模式时所遇到的挑战需要及时解决,可以通过平台进化或领导力赋能来解决。例如,如果产品所有权不明确或缺失,这个阻碍需要通过回顾性事件(如第十二章《交付执行》中所解释)或使用实时回顾性甚至“停止全世界”事件(这些实践在第四章《开放文化》中有描述)来提出。最好在一开始就识别这些问题,并通过一个团队解决,而不是接受已知的非最优方式,并最终在整个组织中推广劣质的工作方式。

在本书的开头,我们解释了我们有一个喜欢的说法:我们更倾向于展示,而不是告知。展示,而不是告知,是我们创造感染性热情的方式。我们必须向世界展示人、过程和技术如何在辉煌的合作中共同运作。让我们来看看我们是如何做到这一点的。

演示日

在红帽公司,当我们进行开放创新实验室驻地项目(4-12 周的沉浸式合作,客户在真实的业务用例中体验文化和实践)时,我们总是在驻地项目结束时举办演示日活动。当然,我们会在整个驻地过程中举办多个演示。我们通常使用 Scrum(如第十二章所述,交付实践),这意味着我们每周会举办一次冲刺评审展示活动(我们通常采用一周的冲刺周期)。

虽然我们鼓励邀请全世界来参加这些每周的活动,但实际上并非每个开发人员、操作人员、领导者、工程师以及组织中其他所有相关人员都能参加。我们会录制这些活动,并尝试通过其他媒介传播这些每周展示的信息,但我们发现我们还需要一个特别的演示日活动。

演示日是整个工作方式的展示——包括人员、流程和技术。在我们的驻地计划中,几乎所有的演示日展示都由客户组织中的人员(也称为驻地成员)进行展示。到这个阶段时,客户的驻地成员已经感染了这种热情,这是他们展示(而非仅仅讲述)他们在驻地期间如何构建、运营和拥有产品的机会。这是一个展示他们为同行和同事们所做的工作、运营和拥有的机会;但更重要的是,这也是展示这些阶段的方法的机会。

这是展示驻地成员在这段时间内共同工作所使用的所有实践的机会,具体包括:

  • 他们的文化基础以及如何运用社会契约、停止世界事件和实时回顾等实践。他们展示了他们所取得的团队身份,如何使用团队情感工具,以及如何创建一个令人惊叹、蓬勃发展的团队工作空间。他们解释并展示了如何使用优先级滑块、准备和完成的定义、验收标准等工具,推动轻松的对话、达成共识和共享理解,营造强大的开放文化。

  • 他们的技术基础以及如何使用容器和 OpenShift 平台作为其技术基础的核心。加入如一切即代码、测试驱动开发、测试自动化、CI/CD 和持续部署等技术实践,才真正为持续交付提供了强大的平台和基础。他们展示了如何通过结对编程和集体编程实现团队对产品的共同拥有。并展示了如何从这个基础中实现自主性、精通性和目标感。

  • 他们走到墙前,解释影响图、同理心图、事件风暴、非功能图以及基于指标的流程图,并展示了这些工具如何共同推动目标可衡量成果,帮助组织朝着其北极星目标前进。

  • 他们展示了如何在冲刺中通过使用用户故事地图、价值切片板和他们选择使用的其他优先级排序实践来进行优先排序。

  • 他们展示了产品如何发展,架构如何形成,以及迭代和渐进交付中所使用的交付实践。

  • 他们展示了他们构建的应用程序,演示了所使用的平台功能,并展示了他们用来改进操作、推动更具实验性的功能开发方法以及实时数据指标的部分平台特性。

  • 他们展示了这项技术、这些实践和这个团队所取得的一些令人震惊的指标,并且展示了与传统工作方式的对比。

  • 他们直接与领导层分享他们需要什么来维持这种工作方式,以及当他们回到正常工作环境时的希望与担忧。

  • 他们以自豪的心情庆祝自己所取得的成就,以及作为一个团队所成为的样子。

当人们充满激情和能量地分享他们用所采用的技术和实践所取得的成就时,那种感染力是无法忽视的。即使是最内向的人,在展示日也能交付出令人难以置信的展示。这是无法用文字描述或解释的东西,你需要亲身到场去感受和体验那份能量和激动。

我们通常在展示日尝试展示的另一个方面是,展示团队日常工作或冲刺中的典型一天。为此,我们在整个参与过程中拍摄了大量的视频。

记录旅程

协作的力量很难通过文字或口头表述来说明。当一个团队在进行事件风暴(Event Storming)并且拥有强大的开放文化时,观察到讨论是如何对齐的真是令人惊叹。当一个团队聚集起来优先排序并优化产品待办事项时,达成的业务和技术上的澄清以及作出的决策是非常有力的,尤其是与传统的工作计划方式相比。所有那些使团队拥有如此强大文化的乐趣、能量和幽默是无法用言语传达的。

因此,我们尽力通过照片和视频尽可能多地记录这些。也正因如此,我们在本书中加入了大量照片,试图通过展示而非言辞,呈现文化和这些实践的实际运作。

我们鼓励每个人在团队中或附近拍摄尽可能多的照片和视频(当然需要得到周围人的同意!)。即便是在为期四周的项目中,我们也最终积累了成百上千的照片和视频。

为了激发团队的热情,我们的团队成员通常会制作一部每周展示视频。这不是一部专业制作的视频,而是一些有趣的内容,一两分钟的时间,包含照片和短视频的蒙太奇,简单地让其他人感受到团队空间中的氛围。我们会把这些展示给团队(通常是在星期五下午),这能带来一种自豪感和奖励感。我们得到的反馈是,团队成员会把这些视频展示给他们的家人,这通常是他们喜欢自己所做工作的一个好迹象!

我们鼓励客户在他们的组织中分享这些视频和照片,尤其是那些可能受益或受到启发的人,看到一个团队以这种方式工作的情况,即使他们没有机会亲自访问。有些甚至会被上传到 YouTube,3,这是一种很好的方式来向全世界展示!

虽然照片和视频可以提供团队在一段时间内的概况,但我们也在寻找创意方法来总结更长时间的过程或整个参与经历。一个非常有效的方法是通过绘制经历来呈现。

3 youtube.com/playlist?list=PLnqlDDtSH2A4e3dTIGHGyhCYmwIoaYxV5

绘制经历

希望你已经注意到素描和涂鸦在复杂领域中能够带来的价值。Ilaria Doria 在本书中提供了许多精彩的素描,帮助内容更加生动,并通过展示而非讲述的方式呈现实践。

这是一种非常有效的讲述故事和记录经验的方法。我们有时会聘请图形艺术家记录演示日,如图 18.3所示。图像中有很多细节,可能在印刷品中无法完全阅读。当然,你可以通过访问本书的 GitHub 仓库github.com/PacktPublishing/DevOps-Culture-and-Practice-with-OpenShift/并进行缩放来查看数字版。

还有一篇由 Eric Schabell 撰写的精彩三部分系列博客 4,完整记录了这次特别的参与经历,并包含了这些素描。

图 18.3:开放创新实验室驻留旅程的示例素描

4 www.schabell.org/2018/01/inside-my-open-innovation-labs-residency-part-1.html

在接下来的几周里,我们还逐步构建了这些人、过程和技术在实践中的照片。

图 18.4:具有视觉引导技能的参与领导者

这些展示图曾自豪地悬挂在组织总部的公共区域。它们激发了兴趣,提供了灵感,并成为另一种充满感染力的热情来源。这些视觉展示通常涵盖了你如果亲自走访团队空间时可能看到的一切。

走访墙面

我们发现的一种最有效的激发感染性热情的机制就是通过一种叫做“走访墙面”的实践。在第四章开放文化中,我们讨论了通过可视化工作以及创建我们在本书中学到的许多信息辐射器所带来的巨大价值。在第五章开放环境与开放领导力中,我们解释了组织的领导团队给予团队开放工作空间以展示并使用这些信息辐射器的重要成功因素。走访墙面将这两者结合在一起,为任何可能有兴趣亲眼见证这种工作方式的人提供了一个真正具有吸引力和沉浸感的体验。其理念是,参加走访墙面的人应该能够获得他们所需或想要了解的关于团队、产品、使用的技术以及工作方式的所有信息。仅仅通过查看信息辐射器的成果并与带领参观的人(通常是团队中的成员或与团队关系密切的人)交谈,就能提供一个诚实、充满活力且谦逊的体验,了解团队的人员、流程和技术。

走访墙面的参观并非排练过的,也不遵循剧本。我们只是谈论我们看到的内容,并讲述我们是如何构建我们所看到的东西:大图景、实践角落、影响地图、事件风暴、北极星、基于指标的过程图、价值切片板、产品待办事项、冲刺看板、回顾结果、构建监控、测试指标、部署统计数据、操作仪表板等等。一次成功的走访墙面体验不仅仅是展示,而非讲解—它是感受与展示,而非讲解。一个高效能的团队拥有自主性、精通度和目标感,采用了出色的开放文化实践,并在世界级平台上采用了最佳技术。当你看到这样的团队在实践这一切时,很难不感受到房间里的文化和能量。这才是真正具有感染力的地方。任何参与走访墙面的团队成员,感受到这种能量,通常都会想加入——并且希望像这个团队一样。

我们曾尝试为一个现场产品团队 5 和一个虚拟产品团队 6 带来一些走访墙面的体验。

当我们无法让每个团队和每个利益相关者亲自访问团队并与他们一起走访墙面时,我们会寻找其他方式,帮助展示团队和技术的实际运作,例如我们所说的书面展示

书面展示

书面展示正如其名——将展示活动以书面形式呈现!我们开始每周进行这些活动,最初我们会感到沮丧,因为每周的 Sprint Review Showcase 活动的出席率没有我们预期的那么高。我们邀请了高级领导、利益相关者和其他团队来参观和体验墙壁巡展,但在这个忙碌的世界里,很难让每个人都来参观,尤其是在一个可能只有几周时间的紧凑参与期内,要帮助并启动这个团队。

因此,我们将我们在 Sprint Review 展示活动和墙壁巡展中想要传达给利益相关者的所有内容写下来。我们尽可能通过多彩的照片、链接和蒙太奇视频展示出来。

这些书面展示通常以 HTML 邮件或 PDF 形式呈现(包括所有照片和视频),并且包含指向工件和信息辐射器的链接,以及从不断发展的应用程序、软件交付和平台中收集的最新指标。

给一份书面文档赋予与引人入胜、身临其境的墙壁巡展相同的视觉吸引力是很困难的。但我们可以尝试,而且这种文档的一个巨大优势是,它们可以在组织内部广泛共享,口碑传播成为激发传染性热情的触发器。

5 youtu.be/70y6SEz6bas

6 youtu.be/TtvDJIT6RYo

口碑传播

口碑在现场证明,一次又一次地,成为了维持和增长我们在某个团队中看到的文化的关键因素。在几周内,团队被邀请参加 Sprint Review Showcase 活动和墙壁巡展,收到了书面展示邮件,并看到了看似非常酷的团队和技术内容的照片和视频,这个团队就开始被大家讨论起来。

我们开始在走廊里听到这样的对话:“你看到五楼的那些人了吗?到处都是便利贴和显示器……不确定他们在做什么,但看起来真有趣”或者“我们需要去看看那个在地下室呆了几周的产品团队,他们好像在做一些很酷的技术事情,我敢肯定我们能从他们身上学到东西”甚至,“我刚和楼下的那个团队一起做了墙壁巡展——他们在做所有你在培训课程上读到和学到的事情,但他们做得更好,且是真实的!

当你看到一些关于软件交付和可操作性的指标时,讨论仍在继续。口碑传播开来,关于某个团队取得的惊人成果的消息四散传开。其他人怎么可能不想了解更多呢?

令人震撼的不可忽视的指标

第十七章改进它中,我们分享了一个故事,讲述了一位产品负责人如何使用指标向他的领导团队证明 DevOps 的价值,并且他如何利用基于指标的流程映射作为实践来实现这一目标。

表格 17.1 中的关键指标显示,通过使用长期存在的产品团队(与项目团队相对)、新的工作方式(与传统的瀑布式项目管理方法相对)和世界一流的容器平台(与虚拟机相对),将新特性投入生产的速度提高了 34 倍以上。

这些指标不言自明。它们是从帮助实现这些变化的基础系统和工具中收集的。当我们展示这些指标并展示所有实践和示范时,我们并不是在谈论成本节省。相反,我们将焦点转向了价值创造和价值实现。这使得利益相关者和领导层开始思考,如果他们能比以往更快地将最先进的特性、应用和能力交到用户手中,34 倍的速度将对他们的业务产生什么影响。

这只是一个团队。如果组织中的所有团队都能像这个团队一样工作会怎样?这个团队通过展示可能的艺术,提供了一个改变的激励蓝图。

从一个团队过渡到七个团队

继续讲述第十七章中的故事,改进它,关于需要通过度量指标来说明他们已经体验了六周的 DevOps 文化和实践的好处,接下来发生了什么?

团队在这个驻地项目期间一直在一个临时实验室中工作,所以第一步是将他们搬回自己的办公室。

而且,这不仅仅是移动人员。所有的看板、所有的工作成果、所有的信息展示器、所有的显示器、所有的便签——它们都需要被搬走!

我们聘请了搬运工来包装并搬运这些物品。

图 18.5:搬移看板和信息展示器,以展示和激励他人

在该组织领导团队的支持下,确保了一个场所,在那里许多 IT 团队开展工作。

这个团队已经在口碑中引起了很大的反响。每周都会分享书面展示,包括一段非正式的、有趣的每周视频。成千上万张照片和短视频被拍摄,并与所有在驻地期间创建的工作成果一起,全部分享回组织自己的 SharePoint 服务器。当然,这些指标也在组织内迅速传播!

将团队保持在一起并保留他们的工具(包括所有的看板)是最重要的第一步。他们做到了这一点。

他们工作所在的空间被称为“看板房”!

更多的墙上展示、更多的赋能和培训课程,以及与越来越多的其他团队和组织各部分进行的更多对话随之而来。

团队仍然需要进一步发展其产品,并将其部署到更多用户的手中,遍布全球。

大约六个月后,我收到了同一位产品负责人发来的邮件,我们在第十七章《改进它》中提到过他,邮件中提到了对度量指标的需求。首先,他谈到了这个应用:

是的,[这个应用]已经在全球所有 89 个市场上线。运行得相当好,除了少数小问题外,运行非常顺利。

他接着解释了他们的 CI/CD 实践如何持续改进——“我们持续几乎每天都将代码推送到生产环境;目前的记录是两小时内完成 3 次部署,且流水线执行时间已降至约 20 分钟,回归测试总共包含 128 个测试用例。”

最后,他告诉我这种方法是如何通过富有感染力的热情,逐步扩展到其他团队的——“成为转变更大一部分组织的一部分,真是让人激动。我现在负责 7 个团队。感觉我们已经起步迅速,虽然还有一些路要走,但进展顺利。

还有一件事。他给了我一个更新版的幻灯片,这是他之前在 Demo Day 上使用过的。将新功能交付到生产环境的过程改进已经不再是比传统实践和技术快 34 倍,而是现在快了 105 倍。

表 18.1:基于度量的使用新 DevOps 文化和实践的收益

这是持续改进的一个绝佳例子!

这个故事的精彩之处在于,文化和实践经历了即时的扩展。我们看到产品团队如何在度量指标的支撑下,继续表现和改进。我相信为了实现这一点,领导层需要解决一些障碍和瓶颈。证明这一点的案例是,团队的应用在 89 个市场上实现了运营扩展,同时兴趣和热情也在整个业务中蔓延。

最终,参与实现“一个团队,一个梦想”的关键人物,逐步推动七个团队以相同的方式工作、行为和思考。

更多的团队,更多的应用产品

当我们准备好释放这种富有感染力的热情,并让更多的应用产品团队以与第一个团队相同的方式运作时,这将是什么样子?

在本书中,我们看到一个团队如何建立自己的基础——由领导力赋能的文化基础和技术基础。我们看到这个团队如何在发现环路中游走,经历选项转折点,然后进入交付环路,并根据学习,要么再次进入交付环路,要么返回发现环路,或者回到选项转折点。这个过程永无止境。

这就是五个应用产品团队的做法,如图 18.6所示。每个团队都是一个自主团队,具有掌握能力和目标,专注于他们各自的产品(或如果他们在交付更大产品,则专注于产品区域)。因此,他们每个人都有自己的 Mobius 循环要经历。他们每个人都有自己的文化基础和独特的团队文化。他们每个人都有自己的技术基础,可以不断改进。

图 18.6:代表组织中许多产品的多个 Mobius 循环

有一个补充说明。所有团队都需要得到一个基础支撑。这一基础也由文化和技术两部分组成。文化部分使得团队能够安全地协作、分享和共同学习,采用的技术包括内部源代码和实践社区等。支撑基础的技术部分由平台提供强有力的支持,如 OpenShift,它支撑了采用团队应用的所有技术实践。我们如何将其他团队引入这个整个生态系统?我们如何将他们加入平台?

首先,每个团队都需要经历某种赋能过程。他们需要构建自己的文化和技术基础。他们需要理解自己的“为什么”。他们需要参与自己的发现循环和交付循环。我们坚信任何团队的赋能都需要三次迭代。

赋能中的三次迭代的力量

总是似乎是三次。当我们做一些新的事情,然后重复做,再做一次,第三次迭代时事情开始变得清晰。有时这需要更长的时间和更多的迭代(尤其是在复杂性较高的情况下),但三次是任何团队应考虑经历沉浸式、边做边学的赋能体验的最小迭代次数。这也是为什么我们的开放创新实验室驻场周期总是介于 4 到 12 周之间。四周的驻场意味着团队可以花几天时间构建他们的基础,几天时间完成发现循环和选项转变,然后使用 Scrum 进行三次一周的交付循环迭代。每次交付循环的迭代都会交付一个增量的工作软件,这个增量可能是可交付到生产环境的。这些迭代正在启动应用产品的持续增量交付。

现在,这可能不足以达成预期的成果,许多客户会希望回到探索阶段和/或进行更多的交付循环迭代,因此六周和八周的驻地时间更为常见。但总是到了第三次交付循环或第三次冲刺时,人们才真正开始理解。他们意识到,每个展示会都会在每周三下午 3 点举行。他们意识到,周五下午会有一个带有有趣视频的书面展示会。他们意识到,展示会会解释新冲刺中所加载的内容,而这些内容是在每周四上午进行的计划中设定的。这会变成一个模式和节奏,团队成员和利益相关者可以越来越有信心。而且最重要的是,我们开始看到在三次迭代后,持续改进在指标上的影响。

这个应用程序本周做了一些上周做不到的事!

在 2017 年,与一家全球石油客户合作时,我们通过四周的开放创新实验室驻地重新构想了他们的一款定价应用,采用了 DevOps 文化和实践。当然,这意味着更早地将用户纳入过程,并进行更多的合作。

我成功争取到了一个资产经济学家的支持,她是该应用的关键用户之一。她从未见过软件开发团队,更别提与他们合作了,因此这对她来说是一个全新的世界。

她没有参加冲刺 1 的展示会。她参加了冲刺 2 的展示会,并且非常沮丧,因为她期望看到的许多功能都没有出现在这个替代应用程序中,而这个应用程序是她工作中依赖的工具。当然,她并没有真正意识到这只是一个早期的产品增量,仍处于开发的初期阶段。但她确实对她看到的一些错误提出了反馈。

第二周是最后一次冲刺评审展示会,她再次参加了。这一次,她看到了一些她的反馈已经被解决,并且一些(不是全部)新功能已经开始被集成到应用程序中。

她恍然大悟,一切都清晰了:她意识到,应用程序正在随着她的参与而不断演进。这一周,它做了一些上周没有做的事情。团队已经在不到一周的时间里采纳了她的反馈并做出了调整!这与她通常在几个月的孤岛式开发后才进行的用户验收测试签署完全不同。

这是第三次迭代时发生了这种积极的认识转变。

所以,我们已经确定,每个新团队至少需要在 Mobius 循环中循环三次,以便真正投入并沉浸在使用平台和 DevOps 文化与实践中。那么,基础部分呢?

加强基础

在这里,我们会简单地复制第一个团队的基础。团队需要建立自己的基础。他们还需要定期培养和加强自己的基础。而当它们通过文化和技术相互连接时,它们都需要为共同的基础做出贡献。

当我们在第二章中介绍“开放实践库”时,我们解释了这是一个由社区驱动的工具库。在本书中,我们挑选了大约 50 个我们最喜欢的工具,介绍了它们是如何一起使用的,以便交付出基于指标的、真正重要的成果。

这些并不一定是每个团队都应该采用的做法。仅仅因为一套做法对某个团队非常有效,并不意味着它会对每个后续团队同样有效。用来建立基础并绕过莫比乌斯环的做法需要根据当时团队的具体情况和需求出现并做出反应。

这意味着每个团队可能会根据其他团队的做法进行调整。

为了做一个比较,我们可以想象一下,当你家里的马桶坏了时会发生什么。这可能是一次非常有压力的经历,因为失去了一个在家里至关重要的设备!假设你不是一个有经验的马桶修理工,你可能会打电话给水管工。现在,当水管工到达你家时,我们猜测你不会通过告诉他或她要使用哪些工具、顺序如何、需要多长时间来迎接他们。你期望水管工带着一辆货车和/或一个工具箱,里面包含他或她需要的所有工具来调查问题、确认问题的确切原因以及修复所需的内容,评估修复选项(也许还会给出一些建议)、进行修复、测试并确保问题解决且不会再次发生,同时进行清理。水管工很可能需要一些工具来完成这项工作。你不会告诉他们使用哪些工具。你期待他们这些专家自己知道该怎么做!

我们的产品团队也是如此。这就是为什么我们不应该给团队提供一套规范的、一步一步的工具和方法,并期望他们照做。在拥有自主权、掌握技能和明确目标的情况下,我们需要让他们选择自己想要使用的工具和方法,并且我们期望他们带来最好的工具和方法,以最大限度地发挥他们的能力完成工作。如果某个工具或方法不起作用(无论是一开始还是经过一段时间),就把它放回工具箱,尝试另一个更好的工具!通过团队之间的经验分享和社区建设,组织可以找到最适合的工具和方法,这些工具和方法是基于一线团队的实际情况得出的。

正如我们在第十二章《交付实践》中所解释的那样,我们还需要寻找何时需要放下某些实践所提供的保护措施。仅仅因为某个产品团队开始使用与 Scrum 框架相关的实践,并且在几个月内取得了很好的效果,并不意味着这些实践应当永远应用于该团队。寻求持续改进的团队需要自我检查,意识到他们已成功使用一段时间的实践可能会变得适得其反,成为瓶颈。

拥有一个共同的文化和技术基础,使得许多团队能够成功地在莫比乌斯环上循环往复。这意味着要创建一种文化,在这种文化中,团队相互学习,充满感染力的热情不断发展——不仅仅是第一支团队,后续的团队和组织也会继续推动这一进程。我们的大部分经验来自开源社区,在这些社区中,成千上万的人通过共同的目标连接起来,迭代和逐步交付成果。我们看到并帮助组织运用开源的原则,在内部组织社区。内源化的实践促进了组织内的协作、贡献和开放性。通过建立实践社区(Communities of Practice)和兴趣社区(Communities of Interest),组织能够实现这一目标。实践社区CoPs)为来自不同团队的拥有特定专业技能的人们提供一个合作、分享、思考和启发的空间。好的例子包括 UI/UX 实践社区、产品所有权实践社区或 OpenShift 实践社区。兴趣社区CoIs)可以围绕任何对人们感兴趣的主题展开,无论该主题是与组织内部还是外部相关。内源化和实践社区帮助提供并加强文化的基础,多个团队可以持续利用并从中获益。

让我们来看看一些关于如何维持技术的思路。

持续技术的创新

任何从事 IT 工作的人都会告诉你,其中一个最大挑战就是跟上所有技术的更新。在今天的世界里,我们中有很大一部分人始终保持联网状态。7 互联网的增长速度和用户数量令人震惊,尤其是当你回顾 1990 年代初期(本书作者刚刚大学毕业时),发达国家中只有大约 5% 的人接入了互联网。变化的速度是永无止境的。

7 en.wikipedia.org/wiki/Global_Internet_usage

正是在这样的背景下,我们将我们的应用和服务从 2014 年的第一次提交(见图 18.7)发展到了今天最普及的容器编排平台。

图 18.7:第一次 Kubernetes 提交

对于技术人员来说,挑战在于跟上这种变化的速度。改变是一种选择。然而,这可能是一个致命的选择;想想 Betamax、盒式磁带和 Perl(真令人伤心)!我们在本书中已经提到过几次,我们正在优化某个事物,以尽量减少变更的成本。我们需要尽量减少修改或更改软件部署管道、增强它或让我们获得更快反馈所需的时间。我们需要尽量减少从客户和业务获得想法并将其转化为生产中的工作软件解决方案所需的时间。我们需要尽量减少更新平台所需的时间。这些时间最小化的推动力源于一个认识:在商业和组织中,时间就是金钱,而且通常比起正确快速更为重要。获得更快的反馈能够让团队根据业务和客户需求更快速地调整和变化。

那么,技术人员如何保持这种状态呢?我们通过不断学习,发展长期团队共享的技术知识和对技术及解决方案的理解,这些技术和解决方案在不断发展和适应。像结对编程和集体编程这样的实践,生动地体现了这种持续学习的体验。它需要技术卓越,能够被团队共享和理解,再结合本书中描述的人员和流程实践,才能取得成功。

无论是大企业还是小企业,都在构建自己的商业平台,建立在各种各样的供应商平台之上。在今天的混合云环境中,这种平台思维至关重要。平台思维成功的主要原因之一是,平台可以成为不同团队之间共享和共同确立最佳实践的地方。如果公共云教会了我们什么,那就是正确的平台和基础设施才是真正可持续的竞争优势。OpenShift 被定位为一个平台,供你的组织和团队在其上开发并持续部署业务应用服务,无论你的基础设施部署在哪里。

生命周期管理这些平台架构是每个组织必须面对的核心技能。许多复杂性在混合云中作为服务来管理;这减轻了产品团队

采用学习心态意味着接受你今天所知道的并不一定足够应对未来的变化和挑战。对我们来说,创新、研究和开发发生在世界各地的开源社区。你可以通过商业开源进入并成为其中的一部分,这反过来会成为你组织的竞争优势。商业开源软件项目由单一公司拥有,该公司通过订阅、服务、培训和支持从软件中获得直接和显著的收入。

适应变化在任何 DevOps 或转型计划中都至关重要。我们遇到过很多做错了的 DevOps,很多时候问题归结为这七个方面。警惕这些反模式,并知道如何恰当地应对它们,因为如果不处理好,它们可能会让你走上错误的道路:

图 18.8:七种反模式以及 DevOps 不是什么

  1. DevOps 不是一人操作:"是的,我们做 DevOps,Mary 每周二和周四在这里管理我们的流水线。" 我们经常听到类似"DevOps 人员"这样的说法,但这其实是一种反模式。DevOps 不仅仅是一个团队;它是一种工作方式的追求。它意味着希望对软件的整个生命周期负责,打破妨碍反馈循环的壁垒。这不是某个可以雇佣的高手来修复一切问题,然后在修好后离开。

  2. DevOps 不是一次性任务:延续这个雇佣高手的比喻,DevOps 不是一次性任务。它不仅仅是配置一次流水线并且永远保持不变。像所有技术一样,这些东西需要培养和发展。当新的组件添加到你的架构中或新的软件出现时,你的流水线和流程也需要进化。

  3. DevOps 不是云计算:使用云服务或仅仅部署一个 Kubernetes 集群并不意味着你已经在做 DevOps。关键在于如何将平台作为一种促进因素,并将其视为一个能够加速任何转型之旅的产品。

  4. DevOps 不是给每个人提供 root/admin 权限:这个应该是显而易见的,但做 DevOps 并不意味着仅仅给开发人员和生产环境提供访问权限。它关乎开发人员和站点可靠性工程师的合作,倾听彼此的担忧,并通过可信赖且可靠的流水线优化交付能力。

  5. DevOps 不是仅仅是工具:仅仅使用 Jenkins 或 Tekton 并不意味着你在做 DevOps。工具只是帮助团队在共享平台和空间上共同协作的载体。真正的目标是创造对软件栈的所有权,并建立一个将用户与开发连接起来的反馈循环,从而加快市场速度。

  6. DevOps 不是灵丹妙药:这关乎人、过程和技术的结合与平衡。做 DevOps 不会解决你所有的问题;它只是其中一个方面。

  7. DevOps 不是一个独立的团队:如果你的软件开发生命周期SDLC)是开发人员编写代码,然后将代码丢给 DevOps 团队去部署/管理,那么你需要重新评估你的 SDLC。

现在,如果DevOps 团队的理念是反模式,那就需要问一个问题,“平台团队的理念是不是一样的反模式呢?”

我们的第一反应可能是确实如此。如果我们真心想打破所有的壁垒,组建一个完全跨职能的产品团队,那么这应该意味着平台专长要包含在团队中,对吧?我们在第一章引言 - 从“为什么”开始 中定义的 T 型或 M 型人才,难道不应该有一个或两个专注于平台的专家吗?

这是我们讨论了好几年的问题,当我们开始考虑多个产品团队共同使用一个平台时,视角会发生变化。这是一个规模问题。当然,拥有平台的一个目标就是解决扩展和重用的问题。它为多个团队提供了加速开发的技术基础。像 PetBattle 这样的单一产品团队可以从平台中获得很多好处。但是,如果 PetBattle 被 Pet Planet(一家有 50 个产品团队的新宠物科技公司)收购,平台带来的规模经济效益将成倍增长。

最近,我们深受 Matthew Skelton 和 Manuel Pais 的著作《团队拓扑》的启发,并与之产生了强烈共鸣。他们介绍了不同的团队拓扑,包括:

  1. 赋能团队,帮助培训、指导和引导团队学习新实践并获得新技能,就像我们在开放创新实验室驻留项目中所做的那样。

  2. 流对齐团队,即对一个单一且有价值的工作流对齐的团队。我们在本书中提到的应用产品团队与此紧密对齐。

  3. 平台团队,其目的是使流对齐团队能够以相当大的自主性交付工作。

我们支持平台团队的理念,并不认为它像 DevOps 团队那样是反模式,因为平台是一个产品,应该像产品一样对待。斯凯尔顿和派斯区分了流对齐团队和平台团队,而我们将这两种团队视为产品团队。

这本书的核心内容是关于产品团队如何运作,以及如何将产品思维带入团队的工作中。我们通过莫比乌斯环和基础框架来可视化任何产品的思维模型。

双莫比乌斯环 – 平台和应用产品

那么,平台的 Mobius Loop 是如何工作的呢?翻到本书的第 1 页!它完全一样!我们将平台视为一个复杂的产品,这意味着我们进行持续的发现、选项转变和持续交付。

图 18.9:应用产品和平台产品的双重 Mobius Loop

平台团队可以使用我们在第三部分发现它中介绍的相同的发现方法。与发现商业应用程序不同,他们使用这些方法来发现平台。例如:

  • 平台团队可以拥有自己的北极星指标,如第八章发现为什么与谁中所描述的那样。这提供了一个最佳捕捉平台为其客户或更具体地说,为应用产品团队交付核心价值的单一指标。

  • 平台团队可以拥有自己的影响力图,如第八章发现为什么与谁中所描述的那样,交付成果可以被构建到平台中,并且我们假设这些成果对参与者(如开发人员)产生的可衡量影响,以帮助实现平台的目标。

  • 平台团队可以使用同理心映射和其他以人为本的设计技术来与例如应用产品团队中的开发人员建立同理心。这在避免跨组织强制实施常见反模式时非常有效;它会激发开发社区的需求,他们希望有一个能帮助他们并解决痛点的平台。

  • 平台团队可以使用如第九章发现如何做中所描述的事件风暴法,来绘制出开发人员用来或希望用来充分利用平台的流程。这使得平台架构得以显现。我们还可以使用非功能性映射和基于度量的流程映射等实践,来激发一种持续交付的方法,并在平台上进行衡量。

  • 平台团队可以设定目标可衡量的结果,如第十章设定结果中所列出。

  • 平台团队可以使用如用户故事映射、价值切分、影响与努力优先级排序、如何-现在-哇优先级排序和最短任务优先等方法来组织和优先排序他们的工作,以构建一个按价值优先排序的产品待办事项列表,交付给应用产品团队。实验设计、设计冲刺,甚至考虑推进新的平台功能的高级部署概念,都可以被平台团队使用。

  • 平台是复杂系统,正如 Cynefin 所定义并在第十二章交付实施中解释的那样。这意味着平台通过迭代和增量交付的方式从中受益,并且内置了学习和反馈。像 Scrum 或 Kanban 这样的敏捷交付方法允许平台以迭代方式展示给开发人员和运营人员,并允许收集度量数据和学习。

  • 平台团队需要有自己的基础。平台团队需要具备自主性、掌握能力和目标。他们需要有自己的技术基础,以便能够应用诸如“Everything as Code”(一切皆代码)、“大局观”和 CI/CD 等实践,正如第二部分《建立基础》中所解释的那样。平台团队的技术基础至关重要;它不仅为新兴的平台开发提供基础,而且成为所有应用产品团队的技术基础。

  • 平台团队可以像应用产品团队一样,在充满信息辐射、协作和开放性的空间中工作。事实上,最重要的协作就是与这些应用产品团队的合作。

平台团队和应用产品团队是如何互相连接的?他们各自的反馈循环是如何交织在一起的?开发人员和应用产品团队的其他成员是平台团队的用户和利益相关者。使用平台的决策和动机必须来自这些用户的力量,而不是来自自上而下的管理决策。平台团队不断发展平台和基础设施,以满足开发人员和应用产品团队的需求、机会,并解决他们遇到的问题。

连接多层级的产品团队

我在伦敦的开放创新实验室看到的最佳反馈循环之一就是,那里我们常常能看到来自 Red Hat OpenShift 业务单元的工程师在实验室中工作。

我们还在同一空间中开展开放创新实验室的驻地项目,客户在这里使用 OpenShift,有时是第一次使用,同时也在学习如何使用 OpenShift 来实践 DevOps 文化和方法。

当有机会将 OpenShift 工程与客户连接时,我们会抓住机会。这带来了双重好处。我们的客户能够与使用的核心平台产品背后的工程师见面,直接听到他们关于即将推出的功能、路线图和更多内容的介绍;而我们的 OpenShift 工程师则能看到真实的客户在使用他们的产品!他们可以看到客户是如何以及为什么使用这个产品的,哪些方面运作良好,哪些方面存在问题。这是一个很棒的反馈循环。

我们看到的两个最糟糕的反模式是,管理层在没有与用户沟通的情况下做出产品决策,和管理层在没有与开发人员和运维人员沟通的情况下做出平台决策。

那么,管理层可以从中学到什么?领导团队又能从双重莫比乌斯心智模型中学到什么,以应用到平台和应用产品的持续发现和持续交付中呢?如果把组织的领导层和战略也看作是一个复杂的产品,会怎样?

图 18.10:领导力、产品与平台的莫比乌斯生态系统

如果一个组织的战略被视为一个复杂的产品,那么领导层必须采取持续发现、选项转变和持续交付的方法。

战略的产品所有权需要理解市场的背景和动机,这些由产品团队可视化的信息提供支持(就像应用产品团队的产品负责人需要理解用户和业务利益相关者的背景和动机一样;就像平台团队的产品负责人需要理解开发人员和运维人员的背景和动机一样)。

领导层需要自己的基础,并需要为产品和平台基础注入稳定性。在某些情况下,这意味着需要清除腐朽的基础,即旧有流程中的问题。我们看到的一些例子包括人力资源、财务、法律和风险管理流程的开展。所有这些都可以从产品思维中受益,并与其用户的同理心紧密相连。

如果我们再次回顾本书中各个章节所介绍的实践,我们可以看到它们如何被领导团队运用产品思维来推动战略。例如:

  • 每个战略都应该有一个北极星。

  • 影响映射(Impact Mapping)已被许多组织用于推动公司战略,通过目标、参与者、目标影响和交付物进行阐述。

  • 像事件风暴、同理心映射等以人为本的设计实践可以用来与股东、财务利益相关者、所有者、竞争对手及其他市场力量建立共同理解和同理心。

  • 第五部分《交付它》中的选项转变实践可以用来优先排序工作、设计实验,甚至考虑将产品高级部署到市场中。

  • 第六部分《构建它,运行它,拥有它》中的交付实践可以用来以敏捷方式交付战略增量,并举行展示(向公司和市场展示)和回顾会,以持续改进。衡量重要事项并持续学习。捕捉和辐射组织指标,推动学习和持续改进。

领导战略作为产品的结果是为应用产品团队设定目标。产品团队将目标设定给平台团队。所有团队都通过衡量、学习和可视化来获取信息——以便改进自身,并从平台向产品辐射,再到领导层。

帮助连接这些的最终实践叫做目标与关键结果,或称OKRs

OKRs 由目标(Objective)——一个明确定义的目标——以及 3-5 个关键结果(Key Results)组成,关键结果是用于跟踪目标达成的具体衡量标准。OKRs 的目的是通过具体、明确、可衡量的行动来定义如何实现目标。关键结果可以在 0 到 100%的范围内进行衡量。目标也应得到相关举措的支持,这些举措是帮助实现目标并推动关键结果向前发展的计划和活动。OKRs 可以在组织中共享,目的是为团队提供目标的可见性,并使努力对齐和集中。OKRs 通常在战略、团队和个人层面设定。

启动 OKRs 的一个不错方法是将其与第八章,发现目标与受众中介绍的影响映射(Impact Mapping)实践相结合。影响地图中的目标陈述转化为目标O)。与目标相关的可衡量影响陈述转化为关键结果KR)。因此,在战略与领导力的发现循环中使用的影响映射实践有助于为产品设定意图,每个产品都可以拥有自己的影响地图和 OKRs。平台团队也可以使用影响映射实践来捕捉和传播他们的为什么,并构建 OKRs 来支持上层的应用产品和战略。

图 18.11:通过 OKRs 以及信息、指标和学习连接的莫比乌斯环

本书中应用的双循环学习通过使用莫比乌斯环来演化产品,这同样适用于平台和领导力策略。它们都通过 OKRs(目标与关键成果)连接起来,OKRs 从组织中向下流动,并根据来自平台和产品的信息、指标和学习向上变化。

结论

在本章中,我们探讨了我们如何维持它——如何维持我们在本书中所涉及的一切,甚至可能使其成长!

很多这一切归结为充满感染力的热情——对技术和平台、对工作方式和实践、对团队和文化的热情。我们探讨了不同方式是如何激发这种热情的,并分享了一些关于同一个团队,同一个梦想口号如何发展到多个应用产品团队,并最终形成对平台团队需求的故事。

无论你组织中的团队是第一个、第二个、第十个还是第百个应用产品团队、平台团队或领导力团队,把所交付的主题视为一个产品是成功的关键因素。

让我们为你提供向 DevOps 文化与实践转型的十大建议,结合 OpenShift:

  1. 永远不要忘记基础——构建文化和技术实践的基础,能够促使业务灵活性。

  2. 永远不要停止改进——无论是基础、技术,还是产品。

  3. 永远不要停止学习——整个工作系统的核心就是在每个层级都能不断学习。无论是产品、团队、平台还是组织的战略,持续学习应该是每件事的核心。

  4. 永远没有完成。我们称之为#neverdone,因为我们正在进行无限循环——连续不断的学习双重循环,永无止境,直到产品、平台或组织关闭为止。

  5. 展示,而不是告诉。这听起来可能已经说得太多了,但真正的衡量标准是工作的软件。我们需要它来获取反馈,并再次循环改进。

  6. 文化至关重要。文化很难定义,但它是团队之间的伙伴关系感。它是那些内部笑话、调侃,这些让大家在困难时刻齐心协力。或者是愿意在这一轮冲刺中做那些繁琐的任务,因为你知道下一个冲刺,别人会轮到自己做。

  7. 投资于你的人。文化难以建立,但容易被破坏。失去动力会悄悄侵蚀信任。

  8. 听取工程师的意见;但他们也需要倾听并理解业务和用户的需求。

  9. 通过实践学习——实验、实验、再实验。安全地失败,快速地失败,最重要的是从中学习。通过实践来学习。

  10. 玩得开心!

附录 A – OpenShift 练习的规模需求

OpenShift 集群支持许多类型的足迹 – 云端、数据中心和本地。您可以通过浏览此链接了解在这些不同环境中安装 OpenShift 的最佳方法:cloud.redhat.com/openshift/create/

Red Hat 支持社区版本的 OpenShift 之间存在区别。如果您在做任何工作相关的事情,特别是在企业环境中,强烈建议您获取 Red Hat 版本的全面企业支持。社区版本由开源社区支持,您可以在这里了解更多信息:www.okd.io/

我们已经详细介绍了如何在本地机器上使用CodeReady ContainersCRC)来开始使用 OpenShift。这里有 CRC 的支持版本,cloud.redhat.com/openshift/create/local,还有社区版本,www.okd.io/crc.html,可供使用。要使用默认设置运行 CRC,请执行以下操作:

crc start

为了限制使用的系统资源,CRC 并未包含所有 OpenShift 的功能。如果执行本书的技术部分,本地运行 CRC 将需要更长时间。您可能会经常遇到超时情况,并且可能需要尝试一些命令两次。因此,请耐心等待,或者考虑使用具有更少资源限制的云托管 OpenShift。如果您有真正的财力用于云托管,或者您拥有可用的虚拟化基础设施,您可以使用二进制安装程序安装 OpenShift 4 集群:

openshift-install cluster create

这将为您提供一个运行的集群,包含多个主节点和工作节点,并且您可以在安装之前配置不同的选项。在此处阅读文档:docs.openshift.com/container-platform/4.7/installing/index.html

CRC 可以使用各种选项启动。重要的是,您可以设置以下内容:

  • -c, --cpus int:分配给 OpenShift 集群的 CPU 核心数量(默认值为 4)

  • -m, --memory int:分配给 OpenShift 集群的内存量(单位为 MiB,默认值为 9216)

  • -d, --disk-size uint:OpenShift 集群使用的总磁盘大小(单位为 GiB,默认值为 31)

您还可以配置 IP 地址 – 查看crc start --help获取详细信息。

我们已经使用 CRC 测试了本书中的以下场景,并推荐以下最低规模。如果您在本地有更多资源可用,建议您使用它们!这将改善运行代码时的性能和可用性。

表 A.1:各种书籍场景的最小规模

CRC 被优化为在单台机器上运行,因此默认情况下禁用了指标和监控。可以通过运行以下命令来启用:

crc config set enable-cluster-monitoring true

请注意,您需要比上面列出的更多内存才能运行监控栈、CI/CD 和 PetBattle。有关 CRC 的完整文档、选项和故障排除,请参见code-ready.github.io/crc/

如何调整 CRC 虚拟机中的存储大小

要扩展镜像磁盘大小,您需要停止并重新启动 CRC 以正确调整镜像磁盘的大小。这似乎是 CRC 的一个 bug/问题。 [1][2]

在 Linux 上,执行以下操作来调整磁盘大小:

$ crc start -c 4 -m 16384 -d 50
$ CRC_MACHINE_IMAGE=${HOME}/.crc/machines/crc/crc.qcow2
$ crc stop
$ cp ${CRC_MACHINE_IMAGE} ${CRC_MACHINE_IMAGE}.ORIGINAL
$ virt-resize --expand /dev/vda4 \
$ {CRC_MACHINE_IMAGE}.ORIGINAL ${CRC_MACHINE_IMAGE}
$ rm -f ${CRC_MACHINE_IMAGE}.ORIGINAL
crc start -c 4 -m 16384 -d 50

Tekton 持久存储

根据您的环境,OpenShift 集群中可用的存储类类型会有所不同。

CRC 特定事项

目前,OpenShift 中有两个 Kustomize 覆盖层用于PersistentVolumeClaims。这是因为在每个环境中都有不同的存储类可用。在第七章开放技术实践—中点中,我们使用 ArgoCD 引导工具。需要通过更改以下注释行来相应地设置 Ubiquitous Journey 文件tekton/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- persistent-volume-claims/aws
# - persistent-volume-claims/crc
- rolebindings
- tasks
- pipelines
- templates
- triggers

1 access.redhat.com/solutions/4969811

2 github.com/code-ready/crc/issues/127

云特定事项

在 AWS 中,我们使用aws-efsdocs.openshift.com/container-platform/4.4/storage/persistent_storage/persistent-storage-efs.html)进行 RWX 存储。更新版的 EFS 驱动程序正在开发中。如果您的环境中没有这个驱动程序,您仍然可以运行示例,但如果并行运行,可能无法正常执行所有流水线任务。

编辑tekton/persistent-volume-claims文件,使用您集群中可用的存储类(例如,AWS 中默认是 gp2)。您可以通过运行以下命令来查找存储类名称:

oc get storageclass 

附录 B – 额外学习资源

在本书中,我们提到了在写作过程中使用过的工具和技术,目的是希望你也能在自己的领域中使用它们。

然而,这个列表并不详尽,我们还使用了其他值得提及的工具:

  • www.konveyor.io/: Konveyor 社区正在开发帮助用户将应用程序重新托管、重新平台化和重构为 Kubernetes 的项目。

  • OpenShift.tv: Red Hat OpenShift 流媒体。体验实时、未经筛选的演示(没有安全网)。

  • www.telepresence.io/: 这是一个开源工具,允许你在本地运行一个服务,同时将该服务连接到远程的 Kubernetes 集群。使用你喜欢的调试工具在本地调试 Kubernetes 服务。

  • www.eclipse.org/jkube/: 这是一个插件和库的集合,用于使用 Docker、JIB 或 S2I 构建策略来构建容器镜像。JKube 还提供了一套工具,包括 watch、debug 和 log,以改善开发者体验。

  • www.openpolicyagent.org/: 这是一个开源的通用政策引擎,统一了整个技术栈的政策执行。Open Policy Agent (OPA) 提供了一种高级声明性语言,可以将政策指定为代码,并通过简单的 API 将政策决策从软件中卸载。你可以使用 OPA 在微服务、Kubernetes、CI/CD 管道、API 网关等中执行政策。

  • uncontained.io/: 该项目最初作为 Red Hat 容器实践社区中的一个项目,旨在与 Red Hat 咨询组织的成员分享有关 OpenShift 采用的知识。我们很快意识到,我们分享的指导和知识值得发布,这意味着我们可以将其分享给客户、合作伙伴和社区成员。

  • opendatahub.io/: Open Data Hub 是一个构建基于 Red Hat 的 Kubernetes 平台 OpenShift® 容器平台(www.openshift.com/)和 Ceph 对象存储(www.redhat.com/en/technologies/storage/ceph)的 AI 即服务平台的蓝图。它继承了上游项目,如 Kafka/Strimzi(strimzi.io/)和 Kubeflow(www.kubeflow.org/),并且是 Red Hat 内部数据科学和 AI 平台的基础。

  • developers.redhat.com/: 与 Red Hat 开发者相关的所有内容——博客、电子书等。

  • github.com/openshift/odo: OpenShift Do (ODO) 是一个面向开发者的 Kubernetes 和 OpenShift 命令行工具。

  • strimzi.io/: Strimzi 提供了一种在 Kubernetes 上运行 Apache Kafka 集群的方式,支持多种部署配置。

  • github.com/wercker/stern: Stern 允许你在 Kubernetes 上监视多个 Pod 及其内部的多个容器。

  • learn.openshift.com/: OpenShift 的互动学习门户。

  • www.redhat.com/en/services/training/learning-subscription/: 红帽学习订阅服务。

posted @ 2025-06-27 17:08  绝不原创的飞龙  阅读(9)  评论(0)    收藏  举报