本章涵盖
云原生应用程序是高度分布式的系统,它们位于云中,并且能够灵活应对变化。系统由多个服务组成,这些服务通过网络进行通信,并部署在动态环境中,一切都在不断变化。
在深入研究这些技术之前,定义什么是云原生至关重要。就像我们领域的其他流行语(如敏捷、DevOps 或微服务)一样,云原生有时会被误解,并且可能会造成混淆,因为它对不同的人意味着不同的东西。
在本章中,我将为您提供本书其余部分所需的概念工具。我将首先定义云原生的含义,以及将应用程序标识为云原生所需的条件。我将解释云原生应用程序的属性,检查云计算模型的特征,并讨论何时以及为何可能想要迁移到云。我还将介绍云原生拓扑和体系结构的一些基本概念。图 1.1 概述了我将在本章中介绍的不同元素,因为我定义了云原生系统并对其进行了鉴定。在本章结束时,您将准备好开始使用 Spring 构建云原生应用程序并将其部署到 Kubernetes 的旅程。
图 1.1 云原生是一种旨在利用云技术的应用程序开发方法。
25 年 2010 月 1 日,云行业的资深人士 Paul Fremantle 在他的博客上写了一篇题为“云原生”的文章。2 他是最早使用术语云原生的人之一。在微服务、Docker、DevOps、Kubernetes 和 Spring Boot 等概念和技术还不存在的时候,Fremantle 与他在 WSO<> 的团队讨论了“应用程序和中间件在云环境中运行良好”(云原生)需要什么。
Fremantle解释的关键概念是,云原生应用程序应专门为云设计,并具有利用云环境和云计算模型的属性。您可以将传统应用程序(设计为在地面上运行)移动到云中,这种方法通常称为“直接迁移”,但这不会使应用程序原生到云中。让我们看看有什么作用。
专门为云设计应用程序意味着什么?云原生计算基金会 (CNCF) 在其云原生定义中回答了这个问题:2
云原生技术使组织能够在现代动态环境(如公有云、私有云和混合云)中构建和运行可扩展的应用程序。容器、服务网格、微服务、不可变基础结构和声明性 API 就是这种方法的例证。
这些技术支持松散耦合的系统,这些系统具有弹性、可管理和可观察性。结合强大的自动化功能,它们使工程师能够以最少的工作量频繁且可预测地进行高影响力的更改。
从这个定义中,我确定了我喜欢称之为云原生的三个P的三点:
什么是云原生计算基础?
云原生计算基金会(CNCF)是Linux基金会的一部分,它“建立可持续的生态系统并促进社区,以支持云原生开源软件的增长和健康。CNCF 托管许多云原生技术和项目,以实现云可移植性,而不会被供应商锁定。如果您想发现解决任何云原生方面的许多项目,我建议您查看 CNCF 云原生交互式景观。一个
云原生计算基金会,“CNCF 云原生交互式景观”,
https://landscape.cncf.io/。
在以下各节中,我将进一步研究这些概念。但是,我首先希望您注意云原生的定义如何与任何特定的实现细节或技术无关。CNCF在其定义中提到了一些,例如容器和微服务,但它们只是示例。开始迁移到云时,常见的误解之一是您必须采用微服务架构,构建容器并将其部署到 Kubernetes。事实并非如此。弗里曼特尔在2010年的帖子就是证明。他没有提到任何一个,因为它们不存在。然而,他描述的应用程序不仅仍然被认为是云原生的,而且还符合八年后CNCF给出的定义。
在关注主要角色云原生应用程序之前,我想通过描述云原生应用程序运行的上下文来设置场景:云(图 1.2)。在本节中,我将定义云及其主要特征。毕竟,如果云原生应用程序被设计为在云环境中运行良好,我们应该知道那是什么样的环境。
图1.2 云是一种IT基础设施,其特征是不同的计算模型,由提供商根据消费者需要的控制程度作为服务提供。
云是一种IT基础设施,支持根据云计算模型向消费者交付计算资源。美国国家标准与技术研究院 (NIST) 对云计算的定义如下:3
云计算是一种模型,用于支持对可配置计算资源(例如,网络、服务器、存储、应用程序和服务)的共享池进行无处不在、方便、按需的网络访问,这些资源可以通过最少的管理工作或服务提供商交互快速配置和发布。
就像您从提供商那里获得电力而不是自己发电一样,使用云,您可以将计算资源(例如服务器、存储和网络)作为商品获得。
云提供商管理底层云基础架构,因此消费者无需担心机器或网络等物理资源。迁移到云的公司可以通过一组 API 通过网络(通常是 Internet)获取所需的所有计算资源,这些 API 允许他们按需、自助服务地按需配置和扩展资源。
弹性是该模型的主要特征之一:可以根据需求动态配置和释放计算资源。
弹性是指系统能够通过自主方式预配和取消预配资源来适应工作负载变化的程度,以便在每个时间点,可用资源尽可能与当前需求匹配。四
传统的 IT 基础架构无法提供弹性。公司必须计算所需的最大计算能力,并建立一个支持该能力的基础设施,即使其中大部分只是偶尔需要。通过云计算模型,计算资源的使用情况受到监控,消费者只需为他们实际使用的内容付费。
对于云基础架构应该在哪里或谁应该管理它没有严格的要求。有几种用于交付云服务的部署模型。主要是私有云、公共云和混合云。
图 1.3 描述了五种领先的云计算服务模型,每个模型中的平台提供了什么,以及向消费者提供了哪些抽象。例如,使用基础结构即服务 (IaaS) 模型,平台提供和管理计算、存储和网络资源,而使用者则预配和管理虚拟机。关于选择哪种服务模型的决定应由消费者对基础结构的控制程度以及他们需要管理的计算资源类型来驱动。
图 1.3 云计算服务模型的不同之处在于它们提供的抽象级别以及谁负责管理哪个级别(平台或消费者)。
在基础架构即服务 (IaaS) 模型中,使用者可以直接控制和配置服务器、存储和网络等资源。例如,他们可以预配虚拟机并安装操作系统和库等软件。尽管这种模式已经使用了一段时间,但直到 2006 年,亚马逊才通过亚马逊网络服务(AWS) 使其流行并广泛访问。IaaS 产品的示例包括 AWS Elastic Compute Cloud (EC2)、Azure 虚拟机、Google Compute Engine、Alibaba Virtual Machines 和 DigitalOcean Droplets。
使用容器即服务 (CaaS) 模型,使用者无法控制原始虚拟化资源。相反,他们预配和管理容器。云提供商负责预配满足这些容器需求的底层资源,例如启动新的虚拟机并配置网络以使其可通过 Internet 访问。Docker Swarm,Apache Mesos和Kubernetes是用于构建容器平台的工具示例。所有主要的云提供商都提供托管的 Kubernetes 服务,该服务已成为 CaaS 产品的实际技术:Amazon Elastic Kubernetes Service (EKS)、Azure Kubernetes Service (AKS)、Google Kubernetes Engine (GKE)、Alibaba Container Service for Kubernetes (ACK) 和 DigitalOcean Kubernetes。
在平台即服务 (PaaS) 模型中,该平台提供开发人员可用于构建和部署应用程序的基础结构、工具和 API。例如,作为开发人员,您可以构建一个 Java 应用程序,将其打包为 Java 归档 (JAR) 文件,然后将其部署到根据 PaaS 模型工作的平台。该平台提供 Java 运行时和其他必需的中间件,还可以提供数据库或消息传递系统等额外服务。PaaS 产品的示例包括 Cloud Foundry、Heroku、AWS Elastic Beanstalk、Azure App Service、Google App Engine、Alibaba Web App Service 和 DigitalOcean App Platform。在过去的几年里,供应商一直在Kubernetes上融合,为开发人员和运营商构建新的PaaS体验。新一代服务的例子是VMware Tanzu Application Platform和RedHat OpenShift。
功能即服务 (FaaS) 模型依赖于无服务器计算,让消费者专注于实现其应用程序的业务逻辑(通常以功能的形式),而平台负责提供服务器和基础结构的其余部分。无服务器应用程序由事件触发,例如 HTTP 请求或消息。例如,您可以编写一个函数,该函数在消息队列中可用的数据集时分析数据集,并根据某些算法计算结果。商业FaaS产品的例子包括Amazon AWS Lambda,Microsoft Azure Functions,Google Cloud Functions和Alibaba Functions Compute。开源FaaS产品的例子是Knative和Apache OpenWhisk。
具有最高抽象性的服务是软件即服务 (SaaS)。在这种模型中,消费者以用户身份访问应用程序,而云提供商管理整个软件和基础设施堆栈。许多公司构建他们的应用程序,使用 CaaS 或 PaaS 模型来运行它们,然后将其使用量作为 SaaS 出售给最终客户。SaaS 应用程序的使用者通常使用 Web 浏览器或移动设备等瘦客户端来访问它们。作为SaaS提供的应用程序示例包括Proton Mail,GitHub,Plausible Analytics和Microsoft Office 365。
平台与 PaaS
平台是一个术语,可能会在云原生讨论中产生一些混淆,所以让我们澄清一下。通常,平台是用于运行和管理应用程序的操作环境。Google Kubernetes Engine(GKE)是一个根据CaaS模型提供云服务的平台。Microsoft Azure Functions 是一个按照 FaaS 模型提供云服务的平台。在较低级别,如果您直接在 Ubuntu 机器上部署应用程序,那将是您的平台。在本书的其余部分,每当我使用术语平台时,我指的是刚刚解释的更广泛的概念,除非另有说明。
场景设置好了:你在云中。您应该如何设计应用程序以利用其特性?
CNCF 确定了云原生应用程序应具有的五个主要属性:可伸缩性、松散耦合、弹性、可观测性和可管理性。云原生是一种用于构建和运行具有这些属性的应用程序的方法。Cornelia Davis总结道:“云原生软件是由你的计算方式定义的,而不是你计算的位置。5 换句话说,云是关于在哪里,而云原生是关于如何。
我已经介绍了位置部分:云。让我们继续探索如何。为了快速参考,图 1.4 列出了带有简短说明的属性。
图1.4 云原生应用的主要属性
云原生应用程序旨在扩展,这意味着如果提供额外的资源,它们可以支持不断增加的工作负载。根据这些额外资源的性质,我们可以区分垂直可扩展性和水平可扩展性:
传统系统通常会在工作负载增加的情况下采用垂直可扩展性。添加 CPU 和内存是一种常用方法,它使应用程序能够支持更多用户,而无需重新设计它以实现可伸缩性。在特定场景中,这仍然是一个不错的选择,但我们需要云的其他东西。
在云中,一切都是动态的并且不断变化,水平可扩展性是首选。由于云计算模型提供的抽象级别,可以简单地启动应用程序的新实例,而不是增加已经运行的计算机的计算能力。由于云具有弹性,因此我们可以快速动态地扩展和缩减应用程序实例。我讨论了弹性作为云的主要特征之一:可以根据需求主动配置和释放计算资源。可伸缩性是弹性的先决条件。
图 1.5 显示了垂直和水平可扩展性之间的差异。在第一种情况下,我们通过向现有虚拟机添加更多资源来进行缩放。在第二种情况下,我们添加另一个虚拟机来帮助现有虚拟机处理额外的工作负载。
图 1.5 当您需要支持不断增加的工作负载时,垂直可伸缩性模型会向计算节点添加硬件资源,而水平可扩展性模型会添加更多的计算节点。
正如我们在讨论 Kubernetes 时将看到的那样,该平台(无论是 CaaS、PaaS 还是其他东西)负责动态扩展应用程序。作为开发人员,我们有责任设计可缩放的应用程序。可伸缩性的最大障碍是应用程序状态,这本质上是应用程序是有状态还是无状态的问题。在整本书中,我将介绍构建无状态应用程序并使它们能够毫无问题地扩展的技术。除此之外,我将向您展示如何将应用程序状态从Spring推送到PostgreSQL和Redis等数据存储。
松耦合是系统的基本属性,其中零件彼此之间的了解尽可能少。目标是独立发展每个部分,以便在更改一个部分时,其他部分不需要相应地更改。
几十年来,耦合及其孪生概念的凝聚力在软件工程中发挥了至关重要的作用。将系统分解为模块(模块化)是一种很好的设计实践,每个模块对其他部分的依赖性最小(松散耦合),并封装一起变化的代码(高内聚力)。根据体系结构样式,模块可以对整体组件或独立服务(例如微服务)进行建模。无论哪种方式,我们都应该致力于通过松散耦合和高内聚来实现适当的模块化。
Parnas确定了模块化的三个好处:6
上述好处通常属于与微服务相关的好处,但事实是你不需要微服务来实现它们。在过去几年中,许多组织决定从单体架构迁移到微服务。其中一些失败了,因为它们缺乏适当的模块化。由紧密耦合的非内聚组件组成的整体式架构在迁移时会产生紧密耦合的非内聚微服务系统,该系统有时称为分布式单体。我不认为这是一个好名字,因为它意味着根据定义,单体是由紧密耦合的非内聚组件组成的。那不是真的。建筑风格并不重要:糟糕的设计就是糟糕的设计。事实上,我喜欢 Simon Brown 提出的模块化单体术语,它提高了人们对单体可以促进松耦合和高内聚的认识,并且单体和微服务最终都可能成为“大泥球”。
在整本书中,我将讨论一些在应用程序中强制松散耦合的技术。特别是,我们将采用基于服务的架构,并专注于构建具有清晰接口的服务,以相互通信,对其他服务的依赖最小,并且具有高内聚力。
如果系统即使在出现故障或环境变化的情况下也能提供服务,则系统具有弹性。弹性是“硬件软件网络在面对故障和正常运行挑战时提供和维持可接受的服务水平的能力”。7
在构建云原生系统时,我们的目标应该是保证我们的应用程序始终可用,无论基础架构还是软件出现故障。云原生应用程序在动态环境中运行,其中一切都在不断变化,并且可能会发生故障。这是无法阻止的。过去,我们曾经将更改和故障视为例外,但对于像云原生系统这样的高度分布式系统,更改不是例外:它们是规则。
在讨论弹性时,值得定义三个基本概念:故障、错误和故障:8
故障可以变成错误,这可能会导致故障,因此我们应该将应用程序设计为容错的。弹性的一个重要部分是确保故障不会级联到系统的其他组件,而是在修复时保持隔离。我们还希望系统能够自我修复或自我修复,而云模型可以实现这一点。
在整本书中,我将向您展示一些允许故障并防止其影响传播到系统其他部分并传播故障的技术。例如,我们将使用断路器、重试、超时和速率限制器等模式。
可观测性是来自控制理论世界的属性。如果考虑一个系统,可观测性是衡量从其外部输出推断其内部状态的程度的度量。在软件工程上下文中,系统是单个应用程序或整个分布式系统。外部输出可以是指标、日志和跟踪等数据。图 1.6 显示了可观测性的工作原理。
图 1.6 可观测性是指从外部输出推断应用程序的内部状态。可管理性是指通过外部输入更改内部状态和输出。在这两种情况下,应用程序项目都不会更改。它是不可变的。
Twitter 的可观测性工程团队确定了可观测性的四大支柱:9
在控制理论中,可观测性的对应物是可控性——外部输入在有限时间间隔内改变系统状态或输出的能力。这个概念将我们引向云原生的最后一个主要属性:可管理性。
再次从控制理论中得出,我们可以说可管理性衡量的是外部输入改变系统状态或输出的难易程度和效率。用不太数学的术语来说,它是修改应用程序行为而无需更改其代码的能力。这不要与可维护性混淆,可维护性衡量通过更改代码从内部更改系统的难易程度和效率。图 1.6 显示了可管理性的工作原理。
可管理性的一个方面是部署和更新应用程序,同时保持整个系统的正常运行。另一个元素是配置,我将在整本书中深入讨论。我们希望使云原生应用程序可配置,以便我们可以修改它们的行为,而无需更改其代码并构建新版本。通常进行可配置的设置,例如数据源 URL、服务凭据和证书。例如,根据环境的不同,您可以使用不同的数据源:一个用于开发,一个用于测试,一个用于生产。其他类型的配置可以是功能标志,用于确定是否应在运行时启用特定功能。我将在整本书中向您展示配置应用程序的不同策略,包括使用 Spring Cloud Config Server、Kubernetes ConfigMaps and Secrets 以及 Kustomize。
可管理性不仅与特定更改本身有关,还与应用这些更改的难易程度和效率有关。云原生系统非常复杂,因此设计能够适应功能、环境和安全性方面不断变化的要求的应用程序至关重要。由于这种复杂性,我们应该致力于通过自动化进行尽可能多的管理,这将我们引向云原生三个P中的最后一个:实践。
本节将重点介绍 CNCF 提供的云原生技术定义的最后一句话:“结合强大的自动化,它们使工程师能够以最少的工作量频繁且可预测地进行高影响力的更改。我将讨论三个概念:自动化、持续交付和 DevOps(图 1.7)。
图1.7 云原生开发的文化和实践
自动化是云原生的核心原则。这个想法是自动执行重复的手动任务,以加速云原生应用程序的交付和部署。许多任务都可以自动化,从构建应用程序到部署应用程序,从配置基础架构到管理配置。自动化最重要的优点是它使流程和任务可重复,整个系统更加稳定可靠。手动执行任务容易出错且需要花钱。通过自动化,我们可以获得更可靠、更高效的结果。
在云计算模型中,计算资源在自动化的自助服务模型中预配,并且可以弹性地增加或减少它们。云自动化的两个重要类别是基础结构预配和配置管理。我们称它们为基础结构即代码和配置即代码。
Martin Fowler将基础设施定义为代码,即“通过源代码定义计算和网络基础设施的方法,然后可以像对待任何软件系统一样对待。10
云提供商提供方便的 API,用于创建和配置服务器、网络和存储。通过使用 Terraform 等工具自动执行这些任务,将代码置于源代码管理中,并应用用于应用程序开发的相同测试和交付实践,我们获得了更可靠和可预测的基础架构,该基础架构可重现、更高效且风险更小。自动化任务的一个简单示例是创建一个具有 8 个 CPU、64 GB 内存和 Ubuntu 22.04 作为操作系统的新虚拟机。
预配计算资源后,我们可以管理它们并自动执行其配置。解释前面的定义,配置即代码是通过源代码定义计算资源配置的方法,可以像任何软件系统一样对待。
使用 Ansible 等工具,我们可以指定应如何配置服务器或网络。例如,在配置上一段中的 Ubuntu 服务器后,我们可以自动执行安装 Java 运行时环境 (JRE) 17 并从防火墙打开端口 8080 和 8443 的任务。配置即代码也适用于应用程序配置。
通过自动化所有基础设施配置和配置管理任务,我们可以避免不稳定、不可靠的雪花服务器。当手动配置、管理和配置每台服务器时,结果是一片雪花:一个脆弱的、独特的服务器,无法复制并且有更改的风险。自动化有助于避免雪花,有利于凤凰服务器:在这些服务器上执行的所有任务都是自动化的,每个更改都可以在源代码管理中跟踪,从而降低风险,并且每个设置都是可重现的。通过将这一概念发挥到极致,我们实现了所谓的不可变服务器,CNCF 在其云原生定义中也提到了不可变的基础设施。
注意在比较传统的雪花基础设施(需要大量的照顾和关注,如宠物)和不可变的基础设施或容器(以一次性和可更换为特征,如牛)时,您可能听说过“宠物与牛”的说法。我不会在书中使用这个表达方式,但它有时会在讨论这个主题时使用,所以你应该意识到这一点。
在初始配置和配置之后,不可变服务器不会更改:它们是不可变的。如果需要进行任何更改,则将其定义为代码并交付。然后,在销毁以前的服务器时,从新代码预配和配置新服务器。
例如,如果您当前的基础结构由 Ubuntu 20.04 服务器组成,并且您想要升级到 Ubuntu 22.04,则有两种选择。第一个选项是通过代码定义升级并运行自动化脚本以在现有计算机(凤凰服务器)上执行操作。第二种选择是自动配置运行 Ubuntu 22.04 的新计算机并开始使用这些计算机(不可变服务器),而不是在现有计算机上执行升级。
在下一节中,我将讨论用于构建和部署应用程序的自动化。
持续交付是“一门软件开发学科,您可以在其中以这样的方式构建软件,以便软件可以随时发布到生产环境。11 通过持续交付,团队可以在短周期内实现功能,确保软件可以随时可靠地发布。根据 CNCF 的云原生定义,这样的规则是“以最少的工作量频繁且可预测地进行高影响力更改”的关键。
持续集成 (CI) 是持续交付的基础实践。开发人员连续(至少每天一次)将更改提交到主线(主分支)。每次提交时,软件都会自动编译、测试并打包为可执行工件(如 JAR 文件或容器映像)。这个想法是在每次新更改后获得有关软件状态的快速反馈。如果检测到错误,应立即修复,以确保主线始终是进一步发展的稳定基础。
持续交付 (CD) 基于 CI 构建,专注于保持主线始终正常运行并处于可发布状态。在与主线集成的过程中生成可执行工件后,软件将部署到类似生产的环境中。它通过其他测试来评估其可发布性,例如用户验收测试、性能测试、安全测试、合规性测试以及任何其他可能增加软件发布信心的测试。如果主线始终处于可发布状态,则发布新版本的软件将成为业务决策,而不是技术决策。
持续交付鼓励通过部署管道(也称为持续交付管道)实现整个过程的自动化,如Jez Humble和David Farley(Addison-Wesley Professional,2010)的基础著作《持续交付》中所述。部署管道从代码提交到可发布的结果,这是生产的唯一途径。在整本书中,我们将构建一个部署管道,以使应用程序的主要分支始终处于可发布状态。最后,我们将使用它来自动将应用程序部署到生产环境中的 Kubernetes 集群。
有时,持续交付与持续部署相混淆。前一种方法可确保在每次更改后,软件都处于可以部署到生产状态的状态。当它真正完成时,这是一个商业决策。通过持续部署,我们会向部署管道添加最后一步,以便在每次更改后自动在生产中部署新版本。
持续交付与工具无关。这是一门涉及组织中文化和结构变化的学科。设置自动化管道来测试和交付应用程序并不意味着您要进行持续交付。同样,使用 CI 服务器自动执行生成并不意味着要执行持续集成。12 这就引出了下一个话题,这个话题也经常被误认为是关于工具的。
持续交付与 CI/CD
由于持续集成是持续交付的基础实践,因此这种组合通常称为 CI/CD。因此,部署管道通常称为 CI/CD 管道。我对这个术语有一些保留,因为持续集成并不是持续交付学科中包含的唯一实践。例如,测试驱动开发 (TDD)、自动化配置管理、验收测试和持续学习同样重要。
Jez Humble和Dave Farley在他们的持续交付书中没有使用CI / CD术语,也没有在他们写的任何其他关于该主题的书中使用CI / CD术语。此外,它可能会令人困惑。CD 代表持续交付还是持续部署?在本书中,我将把交付“更快的软件更快”的整体方法称为持续交付,而不是CI / CD。
a D.法利,持续交付管道,2021 年。
DevOps 是另一个流行语,这些流行语现在非常流行,但经常被误解。在转向云原生时,DevOps 是一个需要掌握的重要概念。
DevOps的起源是奇特的。奇怪的一个方面是,这个概念的创造者最初并没有提供定义。结果是有几个人使用了他们自己的解释,当然,我们最终使用 DevOps 来表示不同的事情。
注意如果你有兴趣了解更多关于DevOps的起源,我建议你观看YouTube上的Ken Mugrage演讲:“DevOps和DevOpsDays——它开始的地方,它在哪里,它要去哪里”(http://mng.bz/Ooln)。
在DevOps的所有定义中,我发现Ken Mugrage(ThoughtWorks的首席技术专家)提出的定义特别丰富和有趣。他强调了我认为DevOps的真正含义。
一种文化,人们,无论头衔或背景如何,共同想象、开发、部署和操作系统。13
因此,DevOps 是一种文化,它完全是为了一个共同的目标而共同努力。开发人员、测试人员、操作员、安全专家和其他人,无论其职称或背景如何,都共同努力将想法投入生产并创造价值。
这意味着孤岛的终结 — 功能团队、QA 团队和运营团队之间不再有隔离墙。DevOps 通常被认为是敏捷的自然延续,敏捷是 DevOps 的推动因素,其概念是小团队经常向客户提供价值。描述DevOps的一个简洁方式是亚马逊首席技术官Werner Vogels在2006年发表的一句名言,当时DevOps还不是一个东西:“你构建它,你运行它。14
在定义了DevOps是什么之后,我将简要提及它不是什么:
在实现云原生时,开发人员和运营商之间的协作至关重要。您可能已经注意到,设计和构建云原生应用程序需要始终牢记要部署这些应用程序的位置:云。与运营商合作使开发人员能够设计和构建更高质量的产品。
它被称为DevOps,但请记住,该定义不仅适用于开发人员和运营商。相反,它通常是指人,无论头衔或背景如何。这意味着协作还涉及其他角色,如测试人员和安全专家(尽管我们可能不需要像DevSecOps,DevTestOps,DevSecTestOps或DevBizSecTestOps这样的新术语)。它们共同负责整个产品生命周期,是实现持续交付目标的关键。
我们行业中最大的错误之一是决定采用一项技术或方法,因为它是新的,每个人都在谈论它。关于公司将其单体迁移到微服务并最终导致灾难性故障的故事数不胜数。我已经解释了云和云原生应用程序的属性。这些应该为您提供一些指导。如果您的系统不需要这些属性,因为它没有他们试图解决的问题,那么“走向云原生”可能不是您项目的最佳选择。
作为技术人员,我们很容易陷入最新、最时尚、最闪亮的技术中。关键是要弄清楚特定的技术或方法是否可以解决您的问题。我们将想法转化为软件,交付给客户,并为他们提供一些价值。这是我们的最终目标。如果某项技术或方法可以帮助您为客户提供更多价值,则应考虑它。如果它不值得,并且您决定无论如何都要使用它,那么您最终可能会遇到更高的成本和许多问题。
什么时候迁移到云是个好主意?为什么公司采用云原生方法?采用云原生的主要原因(如图 1.8 所示)是速度、规模、弹性和成本。如果您的业务愿景包括这些目标,并且面临云技术试图解决的相同问题,那么最好考虑迁移到云并采用云原生方法。否则,最好留在地面上。例如,如果您的公司在维护阶段通过整体式应用程序提供服务,而该应用程序不会通过新功能进一步扩展,并且在过去十年中表现良好,则可能没有充分的理由将其迁移到云中,更不用说将其转换为云原生应用程序了。
图 1.8 采用云原生可以帮助您实现与速度、弹性、规模和成本优化相关的多个目标。
能够更快地交付软件是当今企业的一个重要目标。尽快将创意投入生产,从而缩短上市时间,是一项关键的竞争优势。在正确的时间带着正确的想法投入生产可能会决定成败。
客户希望实现越来越多的功能或修复错误,他们现在就希望实现这些功能。他们不会乐意等待六个月来发布您的软件的下一个版本。他们的期望不断提高,你需要一种方法来跟上他们的步伐。最后,这一切都是为了为客户提供价值并确保他们对结果感到满意。否则,您的企业将无法在激烈的竞争中幸存下来。
更快、更频繁地交付不仅与竞争和客户截止日期有关。这也是为了缩短反馈周期。频繁和小规模的发布意味着您可以更快地获得客户的反馈。反过来,较短的反馈循环可以降低与您发布的新功能相关的风险。与其花费数月时间尝试实现完美的功能,不如更快地将其推出,从客户那里获得反馈,并对其进行调整以符合他们的期望。此外,较小的版本包含较少的更改,从而减少了可能失败的部分数量。
灵活性也是必需的,因为客户期望您的软件不断发展。例如,它应该足够灵活,以支持新类型的客户端。如今,我们日常生活中越来越多的物体已经连接到互联网,例如各种移动和物联网系统。您需要对将来的任何扩展和客户端类型持开放态度,以便能够以新的方式提供业务服务。
传统的软件开发方法不支持这一目标。它的特点往往是大量发布、灵活性小和发布周期延长。云原生方法与自动化任务、持续交付工作流和 DevOps 实践相结合,可帮助企业加快运营速度并缩短上市时间。
一切都在变化,失败一直在发生。我们试图预测失败并将其视为例外的时代已经一去不复返了。正如我之前提到的,变化并不例外。他们就是规则。
客户希望软件 24/7 全天候可用,并在新功能发布后立即升级。停机或故障可能导致直接的资金损失和客户不满。它们甚至可能影响一个人的声誉,损害组织未来的市场机会。
无论基础架构还是软件出现故障,您的目标都是保证系统的可用性和可靠性,即使只是在降级的操作模式下也是如此。为了保证可用性,您需要准备好一些东西来面对故障,处理它们,并确保整个系统仍然可以为其用户提供服务。处理故障或升级等任务所需的任何操作都应要求零停机时间。客户期望如此。
我们希望云原生应用程序具有弹性,而云技术提供了实施弹性基础架构的策略。如果业务要求始终可用、安全和弹性,那么云原生方法是您的理想选择。反过来,软件系统的弹性又能提高速度:系统越稳定,您就越能安全地发布新功能。
弹性是关于能够根据负载扩展软件。您可以扩展弹性系统,以确保为所有客户提供足够的服务级别。如果负载比平时更高,则需要启动更多服务实例以支持额外的流量。或者,也许发生了一些可怕的事情,某些服务失败了 - 您需要能够启动新实例来替换它们。
预见将要发生的事情即使不是不可能,也是困难的。仅仅构建可扩展的应用程序是不够的,您需要它们来动态扩展。每当存在高负载时,您的系统都应动态、快速且轻松地横向扩展。当高峰期结束时,它应该会再次缩小。
如果您的企业需要快速有效地适应新客户,或者需要灵活地支持新型客户端(这会增加服务器上的工作负载),那么云的本质可以为您提供所需的所有弹性,并结合根据定义可扩展的云原生应用程序。
作为软件开发人员,您可能不会直接处理资金问题,但在设计解决方案时,您有责任考虑成本。云计算模型通过其弹性和按需即用即付策略帮助优化 IT 基础架构成本。不再需要永远在线的基础架构:您可以在需要时配置资源,为实际使用量付费,然后在不再需要它们时销毁它们。
最重要的是,采用云原生方法可以进一步优化成本。云原生应用程序设计为可扩展,因此可以利用云的弹性。它们具有弹性,因此与生产中的停机时间和硬故障相关的成本更低。它们松散耦合,使团队能够更快地进入市场,并具有显着的竞争优势。这样的例子不胜枚举。
迁移到云的隐性成本
在决定迁移到云之前,还必须考虑其他类型的成本。一方面,您可以通过仅为使用的内容付费来优化成本。但另一方面,您应该考虑迁移的成本及其后果。
迁移到云需要员工可能尚未具备的特定能力。这可能意味着投资于他们的教育以获得必要的技能,并可能聘请专业人士作为顾问来帮助迁移到云。根据所选的解决方案,组织可能需要承担一些额外的责任,例如处理云中的安全性,这反过来又需要特定的技能。还有其他注意事项,例如迁移期间的业务中断、重新培训最终用户以及更新文档和支持材料。
我对云原生的解释不涉及特定的技术或架构。CNCF在其定义中提到了一些,例如容器和微服务,但这些只是示例。您不必使用 Docker 容器即可使应用程序成为云原生应用程序。考虑无服务器或 PaaS 解决方案。为 AWS Lambda 平台编写函数或将应用程序部署到 Heroku 不需要您构建容器。尽管如此,它们仍被归类为云原生。
在本节中,我将介绍一些常见的云原生拓扑(参见图 1.9)。首先,我将介绍容器和编排的概念,稍后我将在讨论Docker和Kubernetes时进一步探讨。然后,我将介绍无服务器技术和功能 (FaaS) 的主题。在本书中,我不会过多地关注FaaS模型,但我会介绍如何使用Spring Native和Spring Cloud Function构建无服务器应用程序的基础知识。
图 1.9 主要的云原生计算模型是容器(由业务流程协调程序管理)和无服务器。
假设您加入一个团队并开始处理应用程序。您要做的第一件事是按照指南设置与同事使用的类似本地开发环境。开发新功能,然后在质量保证 (QA) 环境中对其进行测试。验证后,可以将应用程序部署到暂存以进行一些额外的测试,最后部署到生产环境。该应用程序构建为在具有特定特征的环境中运行,因此必须使所有不同的环境尽可能相似。你会怎么做?这就是容器进入场景的地方。
在容器出现之前,您将依靠虚拟机来保证环境的可重现性、隔离性和可配置性。虚拟化的工作原理是利用抽象硬件的虚拟机管理程序组件,从而可以以隔离的方式在同一台计算机上运行多个操作系统。虚拟机管理程序将直接在计算机硬件(类型 1)或主机操作系统(类型 2)上运行。
另一方面,OS 容器是一个轻量级的可执行包,其中包含运行应用程序所需的一切。容器与主机共享相同的内核:无需引导完整的操作系统来添加新的隔离上下文。在 Linux 上,这可以通过利用 Linux 内核提供的几个功能来实现:
注意仅使用虚拟化时,硬件是共享的,而容器也共享相同的操作系统内核。两者都提供用于隔离运行软件的计算环境,即使隔离程度不同。
图 1.10 显示了虚拟化和容器技术之间的差异。
图 1.10 虚拟化和容器技术在隔离上下文中共享的内容有所不同。虚拟机仅共享硬件。容器也共享操作系统内核。容器更轻巧便携。
为什么容器在云原生应用程序中如此受欢迎?传统上,您必须在虚拟机上安装和维护 Java 运行时环境 (JRE) 和中间件才能使应用程序运行。相反,容器可以在几乎任何计算环境中可靠运行,独立于应用程序、其依赖项或中间件。它是哪种类型的应用程序、用哪种语言编写或使用哪些库并不重要。所有集装箱从外面看都有相似的形状,就像用于运输的集装箱一样。
因此,容器可实现敏捷性、跨不同环境的可移植性和部署可重复性。它们重量轻,对资源的要求较低,非常适合在云中运行,其中应用程序是一次性的,并且可以动态快速地扩展。相比之下,构建和销毁虚拟机的成本和耗时要高得多。
器皿!到处都是容器!
容器是那些可能意味着不同事物的词之一。有时这种歧义会产生混乱,所以让我们看看它在特定上下文中的含义。
虚拟化和容器并不相互排斥。您可以在云原生上下文中使用它们,并拥有一个由运行容器的虚拟机组成的基础架构。基础结构即服务 (IaaS) 模型提供了一个虚拟化层,可用于引导新虚拟机。最重要的是,您可以安装容器运行时并运行容器。
应用程序通常由不同的容器组成,这些容器可以在开发期间或执行早期测试时在同一台计算机上运行。但是,您很快就会达到管理许多容器变得过于复杂的地步,主要是当您开始复制它们以实现可扩展性并将它们分布在不同的机器上时。届时,您将开始依赖容器即服务 (CaaS) 模型提供的更高级别的抽象,该模型提供了在计算机集群中部署和管理容器的功能。请注意,在后台,仍然有一个虚拟化层。
即使您使用Heroku或Cloud Foundry等PaaS平台,也会涉及容器。只需提供 JAR 工件即可在这些平台上部署应用程序,因为它们负责 JRE、中间件、操作系统和任何所需的依赖项。在幕后,他们用所有这些组件构建了一个容器,并最终运行它。不同之处在于,构建容器不再是您,而是平台为您完成。一方面,这很方便,因为开发人员的责任更少。另一方面,您将放弃对运行时和中间件的控制,并且可能会面临供应商锁定。
在本书中,您将学习如何使用Cloud Native Buildpacks(CNCF项目)来容器化Spring应用程序,并使用Docker在本地环境中运行它们。
所以你决定使用容器,太好了!您可以依靠它们的可移植性将它们部署到提供容器运行时的任何基础架构。您可以实现可重现性,因此在将容器从开发移动到暂存再到生产时不会出现任何不良意外。您可以快速扩展它们,因为它们非常轻量级,并且可以为您的应用程序获得高可用性。您已准备好在下一个云原生系统中采用它们。还是你?
在一台计算机上配置和管理容器非常简单。但是,当您开始处理在多台机器上扩展和部署的数十或数百个容器时,您需要其他东西。
当您从虚拟服务器(IaaS 模型)移动到容器集群(CaaS 模型)时,您也在转换您的视角。15 在 IaaS 中,您专注于单个计算节点,即虚拟服务器。在 CaaS 中,底层基础架构是抽象的,您专注于节点集群。
借助 CaaS 解决方案提供的新视角,部署目标将不再是计算机,而是计算机集群。CaaS 平台(例如基于 Kubernetes 的平台)提供了许多功能来解决我们在云原生环境中寻找的所有重大问题,跨机器编排容器。两种不同的拓扑如图 1.11 所示。
图 1.11 容器的部署目标是计算机,而业务流程协调程序的部署目标是群集。
容器编排可帮助您自动执行许多不同的任务:
业务流程工具以声明方式进行指示,例如通过 YAML 文件。按照特定工具定义的格式和语言,您通常会描述要达到的状态;例如,您希望在群集中部署 Web 应用程序容器的三个副本,从而向 Internet 公开其服务。
容器编排器的例子是Kubernetes(CNCF项目),Docker Swarm和Apache Mesos。在本书中,你将学习如何使用 Kubernetes 来编排 Spring 应用程序的容器。
从虚拟机迁移到容器后,我们可以进一步抽象基础架构:这就是放置无服务器技术的地方。无服务器计算模型使开发人员能够专注于实现其应用程序的业务逻辑。
名称“无服务器”可能会产生误导。当然有服务器。不同之处在于,您不需要管理它或协调应用程序在其上的部署。现在,这是一个平台的责任。使用 Kubernetes 等业务流程协调程序时,仍必须考虑基础结构预配、容量规划和缩放。相比之下,无服务器平台负责设置应用程序所需的底层基础结构,包括虚拟机、容器和动态缩放。
无服务器体系结构通常与函数相关联,但它们包含两个经常一起使用的主要模型:
无服务器应用程序通常是事件驱动的,仅在有事件要处理(如 HTTP 请求或消息)时运行。该事件可以是外部的,也可以由另一个函数生成。例如,每当将消息添加到队列、处理它,然后退出执行时,就可能触发一个函数。
当没有什么可处理的时,无服务器平台会关闭与该功能相关的所有资源,因此您可以真正为实际使用量付费。在CaaS或PaaS等其他云原生拓扑中,始终有一台服务器24/7全天候运行。与传统系统相比,它们具有动态可扩展性的优势,可减少在任何给定时间配置的资源数量。尽管如此,总有一些东西在运行,而且是有代价的。在无服务器模型中,仅在必要时预配资源。如果没有要处理的内容,则所有内容都将关闭。这就是我们所说的缩放到零,它是无服务器平台提供的主要功能之一。
除了成本优化之外,无服务器技术还将一些额外的责任从应用程序转移到平台。这可能是一个优势,因为它允许开发人员专注于业务逻辑。但是,考虑您希望拥有多大程度的控制以及如何处理供应商锁定也很重要。每个 FaaS 以及一般的无服务器平台都有自己的功能和 API。一旦开始为特定平台编写函数,就无法像使用容器那样轻松地将它们移动到另一个平台。使用 FaaS,您可能会比任何其他方法做出更多的妥协 — 以控制和可移植性为代价来确定责任和范围的优先级。这就是 Knative 迅速流行的原因:它基于 Kubernetes 构建,这意味着您可以轻松地在平台和供应商之间移动无服务器工作负载。归根结底,这是一个权衡的问题。
我们已经到达了定义云原生之旅的最后一步,我已经介绍了我们将在整本书中依赖的主要特征。在上一节中,您熟悉了主要的云原生拓扑,尤其是容器,它们是我们的计算单元。现在,让我们看一下里面的内容,并探讨架构和设计云原生应用程序所涉及的一些高级原则。图 1.12 显示了本节中介绍的主要概念。
图1.12 云原生架构元素
IT 基础架构一直影响着软件应用程序的架构和设计方式。最初,单体式应用程序作为单个组件部署在大型机上。当互联网和PC变得流行时,我们开始根据客户端/服务器范式设计应用程序。依赖于该范式的多层体系结构广泛用于桌面和 Web 应用程序,将代码分解为表示层、业务层和数据层。
随着应用程序复杂性的增加和对敏捷性的需求,人们探索了进一步分解代码的新方法,一种新的架构风格进入了舞台:微服务。在过去的几年里,这种架构风格变得越来越流行,许多公司决定根据这种新风格重构他们的应用程序。微服务通常与单体应用程序进行比较,如图 1.13 所示。
图 1.13 整体式应用程序与微服务。整体式体系结构通常是多层的。微服务由可以独立部署的不同组件组成。
主要区别在于应用程序的分解方式。整体式应用程序与使用三个大层相关联。相比之下,基于微服务的应用程序与许多组件相关联,每个组件仅实现一项功能。已经提出了许多模式来将整体式架构分解为微服务,并处理由许多组件而不是一个组件而产生的复杂性。
注意本书不是关于微服务的,所以我不会详细介绍它们。如果您对该主题感兴趣,可以查看Sam Newman(O'Reilly,2021)的构建微服务,第二版和Chris Richardson(Manning,2018)的微服务模式。对于更面向 Spring 的分析,您可以在 Manning 目录中找到 Spring 微服务在行动,第二版,作者是 John Carnell 和 Illary Huaylupo Sanchez(Manning,2021 年)。如果您不熟悉微服务,请不要担心。这些知识不需要跟随本书。
经过多年的名声和失败的迁移,开发人员社区中出现了关于这种流行架构风格的未来的激烈讨论。一些工程师开始谈论宏服务,以减少组件的数量,从而减少管理它们的复杂性。Cindy Sridharan讽刺地提出了“宏服务”一词,但它已被业界采用,并被Dropbox和Airbnb等公司用来描述他们的新架构。16 其他人提出了一种城堡式建筑风格,由一个被微服务包围的中央巨石组成。还有一些人主张以模块化单体的形式回归单体应用程序。
最后,重要的是选择一个可以为我们的客户和业务带来价值的架构。这就是我们首先开发应用程序的原因。每种架构风格都有其用例。没有灵丹妙药或一刀切的解决方案。大多数与微服务相关的负面体验是由其他问题引起的,例如糟糕的代码模块化或组织结构不合适。单体架构和微服务之间不应该有战斗。
在这本书中,我有兴趣向您展示如何使用Spring构建云原生应用程序,并将它们作为容器部署到Kubernetes。云原生应用程序是分布式系统,就像微服务一样。通常在微服务上下文中讨论的一些主题实际上属于任何分布式系统,例如路由和服务发现。根据定义,云原生应用程序是松散耦合的,这也是微服务的一项功能。
即使它们有一些相似的方面,也必须了解云原生应用程序和微服务是不一样的。您绝对可以对云原生应用程序使用微服务样式。许多开发人员都这样做,但这不是必需的。在本书中,我将使用一种我们可以称之为基于服务的架构风格。也许这不是一个朗朗上口的名字或花哨的名字,但它足以满足我们的目的。我们将处理服务。它们可以是任何大小,并且可以根据不同的原理封装逻辑。没关系。我们想要的是设计服务以满足我们的开发、组织和业务需求。
在本书中,我们将根据基于服务的架构设计和构建云原生应用程序。
我们的中心工作单元将是一个可以以不同方式与其他服务交互的服务。使用Cornelia Davis在她的《云原生模式》(Manning,2019)一书中提出的区别,我们可以确定架构的两个元素:服务和交互。
服务是非常通用的组件 - 它们可能是任何东西。我们可以根据它们是否存储任何类型的状态对它们进行分类,区分应用程序服务(无状态)和数据服务(有状态)。
图 1.14 显示了云原生架构的元素。用于管理图书库清单的应用程序将是应用程序服务。用于存储有关书籍信息的PostgreSQL数据库将是一个数据服务。
图 1.14 云原生应用的基于服务的架构主要元素是以不同方式相互交互的服务(应用程序或数据)。
应用服务
应用程序服务是无状态的,负责实现任何类型的逻辑。它们不必像微服务那样遵守特定规则,只要它们公开您在本章前面了解的所有云原生属性即可。
在设计每项服务时,必须考虑到松散耦合和高内聚力,这一点至关重要。服务应尽可能独立。分布式系统很复杂,因此在设计阶段应格外小心。增加服务数量会导致问题数量增加。
您可能会自己开发和维护系统中的大部分应用程序服务,但您也可以使用云提供商提供的一些服务,例如身份验证或支付服务。
数据服务
数据服务是有状态的,负责存储任何类型的状态。状态是在关闭服务或启动新实例时应保留的所有内容。
数据服务可以是关系数据库(如 PostgreSQL)、键/值存储(如 Redis)或消息代理(如 RabbitMQ)。您可以自行管理这些服务。由于保存状态所需的存储,这样做比管理云原生应用程序更具挑战性,但您将获得对自己数据的更多控制。另一种选择是使用云提供商提供的数据服务,在这种情况下,提供商将负责管理与存储、弹性、可扩展性和性能相关的所有问题。在这种情况下,您可以利用许多明确为云构建的数据服务,例如 Amazon DynamoDB、Azure Cosmos DB 和 Google BigQuery。
云原生数据服务是一个引人入胜的话题,但我们将在本书中主要讨论应用程序。与数据相关的问题,如聚类、复制、一致性和分布式事务,在书中不会详细介绍。我很想,但他们应该有自己的书来充分覆盖。
相互 作用
云原生服务相互通信以满足系统要求。这种通信的发生方式将影响系统的整体属性。例如,选择请求/响应模式(同步 HTTP 调用)而不是基于事件的方法(通过 RabbitMQ 流式传输的消息)将为应用程序带来不同级别的弹性。在本书中,我们将使用不同类型的交互,了解它们之间的差异,并查看何时使用每种方法。