likes
comments
collection
share

《基于云原生的Spring实战:使用Spring Boot和Kubernetes》第二章:云原生模式和技术

作者站长头像
站长
· 阅读数 25

本章内容包括:

  1. 理解云原生应用的开发原则
  2. 使用Spring Boot构建云原生应用
  3. 使用Docker和Buildpacks将应用容器化
  4. 使用Kubernetes将应用部署到云端
  5. 介绍本书中使用的模式和技术

我们在设计云原生应用时与传统方法不同。《12-Factor》方法论包含了最佳实践和开发模式,是构建被视为云原生的应用的良好起点。我将在本章的第一部分解释这个方法论,并在本书的其余部分进行扩展。

在本章的后面部分,我们将构建一个简单的Spring Boot应用,并使用Java,Docker和Kubernetes运行它,如图2.1所示。在本书的其余部分,我将深入研究每个主题,所以如果有什么不太清楚的地方,不要担心。本章旨在为您提供一个从代码到生产的云环境之旅的思维导图,同时让您熟悉我们在本书中使用的模式和技术。

《基于云原生的Spring实战:使用Spring Boot和Kubernetes》第二章:云原生模式和技术

最后,我将向您介绍我们将在本书中使用Spring和Kubernetes构建的云原生项目。我们将应用本书第一部分介绍的所有云原生应用的特性和模式。

云原生开发原则:12因素及其他因素

Heroku云平台的工程师提出了“12-Factor方法论”,作为设计和构建云原生应用的开发原则的集合。他们从他们的经验中总结出了一些构建Web应用程序的最佳实践,具有以下特点:

  • 适合在云平台上部署
  • 具备可扩展性
  • 可在不同系统之间移植
  • 支持持续部署和敏捷性

其目标是帮助开发者构建适用于云端的应用程序,突出了在实现最佳结果时应考虑的重要因素。

后来,该方法论被Kevin Hoffman在他的书《Beyond the Twelve-Factor App》中进行了修订和扩展,刷新了原始因素的内容,并增加了三个额外因素。从现在开始,我将称这个扩展的原则集合为“15-Factor方法论”。

这15个因素将在本书中指导您,因为它们是开发云原生应用的良好起点。如果您正在从零开始构建一个新的应用程序或将传统系统迁移到云端,这些原则可以帮助您在这一过程中。在相关章节,我将更详细地阐述它们,并说明如何将它们应用于Spring应用程序。熟悉这些原则是非常重要的。

让我们深入了解这些因素。

一个代码库,一个应用程序

这段文本描述了"15-Factor方法论",它在应用程序和其代码库之间建立了一对一的映射,因此每个应用程序都有一个对应的代码库。任何共享的代码应该被跟踪在自己的代码库中,可以作为一个库以依赖项的形式被包含,或者作为一个可以独立运行的服务,用作其他应用程序的后备服务。每个代码库可以选择性地在自己的存储库中进行跟踪。

部署是应用程序的运行实例。在不同的环境中可以有多个部署,它们都共享相同的应用程序构件。在部署应用程序到特定环境时,无需重新构建代码库:在部署之间发生变化的任何方面(如配置)应该位于应用程序代码库之外。

API先行

一个云原生系统通常由通过API进行通信的不同服务组成。在设计云原生应用程序时采用“API先行”方法鼓励您考虑将其适应分布式系统,并支持将工作分配给不同的团队。通过首先设计API,其他团队可以使用该应用程序作为后备服务,并针对该API创建自己的解决方案。通过预先设计契约,与其他系统的集成将在部署流程中更加稳健和可测试。在内部,API的实现可以更改,而不会影响依赖于它的其他应用程序(和团队)。

依赖管理

所有应用程序的依赖关系都应在清单中明确声明,并且应该能够通过依赖管理器从中央仓库下载。在Java应用程序的情况下,我们通常可以使用像Maven或Gradle这样的工具来很好地遵循这个原则。应用程序对周围环境的唯一隐含依赖是语言运行时和依赖管理器工具。这意味着私有依赖关系应该通过依赖管理器来解析。

设计、构建、发布、运行

代码库在从设计到部署生产环境的过程中经历了不同的阶段:

  • 设计阶段:确定特定应用程序功能所需的技术、依赖和工具。

  • 构建阶段:代码库被编译,并与其依赖项一起打包成一个不可变的构建构件。构建构件必须具有唯一标识。

  • 发布阶段:将构建构件与特定部署的配置相结合。每个发布都是不可变的,应该具有唯一的标识,比如使用语义化版本号(例如3.9.4)或时间戳(例如2022-07-07_17:21)。发布应该存储在中央仓库中,以便轻松访问,比如当需要回滚到之前的版本时。

  • 运行阶段:应用程序在特定发布的执行环境中运行。

"15-Factor方法论"要求严格分离这些阶段,并且不允许在运行时更改代码,因为这会导致与构建阶段不匹配。构建和发布构件应该是不可变的,并带有唯一标识,以确保可复现性。

配置、凭据和代码

"15-Factor方法论"将配置定义为在部署之间可能发生变化的所有内容。每当需要更改应用程序的配置时,您应该能够在不更改代码的情况下进行更改,而且不需要重新构建应用程序。

配置可能包括用于连接后端服务(如数据库或消息系统)的资源句柄、访问第三方API的凭据和功能标志。请自问,如果您的代码库突然变为公共,是否会暴露任何凭据或特定于环境的信息。这将告诉您是否已正确地将配置外部化。

为了符合这个原则,配置不能包含在代码中或在同一个代码库中进行跟踪。唯一的例外是默认配置,可以与应用程序代码库一起打包。对于任何其他类型的配置,仍然可以使用配置文件,但应将其存储在一个单独的存储库中。

该方法论建议将配置存储为环境变量。通过这样做,您可以在不同的环境中部署相同的应用程序,但根据环境的配置具有不同的行为。

日志

一个云原生应用程序不涉及日志的路由和存储。应用程序应该将日志记录到标准输出,将日志视为按时间顺序发出的事件序列。日志的存储和轮换不再是应用程序的职责。一个外部工具(日志聚合器)将获取、收集并使日志可供检查。

可丢弃性

在传统环境中,您会非常关注您的应用程序,确保它们保持运行状态,永远不会终止。但在云环境中,您不需要那么在意:应用程序是短暂的。如果发生故障,应用程序不再响应,您可以终止它并启动一个新的实例。如果有高负载峰值,您可以启动更多应用程序实例以支撑增加的工作负载。我们称一个应用程序是可处置的,如果它可以随时启动或停止。

为了以这种动态的方式处理应用程序实例,您应该设计它们在需要新实例时能够快速启动,并在不再需要时优雅地关闭。快速启动可以实现系统的弹性,确保其健壮性和弹性。没有快速启动,您将会面临性能和可用性问题。

优雅的关闭是指应用程序在收到终止信号后,停止接受新的请求,完成已在处理中的请求,然后最终退出。对于Web进程,这很简单。但对于其他情况,比如工作者进程,它们负责的作业必须返回到工作队列,然后才能退出。

后备服务

后备服务可以定义为应用程序用于提供其功能的外部资源。后备服务的示例包括数据库、消息代理、缓存系统、SMTP服务器、FTP服务器或RESTful Web服务。将它们视为附加资源意味着您可以在不修改应用程序代码的情况下轻松更改它们。

考虑一下在软件开发生命周期中如何使用数据库。很可能在不同的阶段(开发、测试或生产)使用不同的数据库。如果将数据库视为附加资源,您可以根据环境使用不同的服务。附加是通过资源绑定来实现的。例如,数据库的资源绑定可能包括URL、用户名和密码。

环境一致性

环境一致性是指尽可能保持所有环境的相似性。实际上,这个因素试图解决三个差距:

  • 时间差距—代码变更和部署之间的时间可能相当长。该方法论努力促进自动化和持续部署,以缩短开发人员编写代码到生产部署的时间间隔。

  • 人员差距—开发人员构建应用程序,运维人员负责在生产环境中管理其部署。这个差距可以通过拥抱DevOps文化,改善开发人员和运维人员之间的合作,以及采用“你构建它,你运行它”的理念来解决。

  • 工具差距—环境之间的主要区别之一是如何处理后备服务。例如,开发人员可能在本地环境中使用H2数据库,而在生产环境中使用PostgreSQL。总体而言,在所有环境中应该使用相同类型和版本的后备服务。

管理流程

通常需要一些管理任务来支持应用程序。例如数据库迁移、批处理作业或维护作业等任务应被视为一次性的过程。与应用程序进程类似,管理任务的代码应该在版本控制中进行跟踪,与其支持的应用程序一起交付,并在与应用程序相同的环境中执行。

通常将管理任务作为小型独立服务进行设计是一个不错的主意,这些服务运行一次后就被丢弃,或者将其配置为在无状态平台上的函数,在某些事件发生时触发执行,或者将它们嵌入到应用程序本身中,通过调用特定的端点来激活它们。

端口绑定

遵循 "15-Factor方法论" 的应用程序应该是自包含的,并通过端口绑定导出其服务。在生产环境中,可能会有一些路由服务,用于将公共端点的请求转换为内部端口绑定的服务。

如果应用程序在执行环境中不依赖于外部服务器,则它是自包含的。例如,Java Web应用程序可能会在像Tomcat、Jetty或Undertow这样的服务器容器中运行。相比之下,云原生应用程序不需要环境中有可用的Tomcat服务器;它会像处理其他依赖项一样自己管理它。例如,Spring Boot允许您使用内嵌服务器:应用程序将包含服务器而不是依赖于执行环境中是否有可用服务器。这种方法的一个结果是应用程序和服务器之间始终存在一对一的映射,而不像传统方法中将多个应用程序部署到同一台服务器。

然后,应用程序通过端口绑定导出提供的服务。Web应用程序会将HTTP服务绑定到特定端口,并可能成为另一个应用程序的后备服务。这通常是云原生系统中的情况。

无状态进程

在前一章中,你了解到高可伸缩性是我们转向云端的原因之一。为了确保可伸缩性,我们将应用程序设计为无状态过程,并采用无共享架构:不应该在不同的应用程序实例之间共享状态。请自问,如果你的应用程序的一个实例被销毁并重新创建,是否会丢失任何数据。如果答案是肯定的,那么你的应用程序就不是无状态的。

无论如何,在大多数情况下,我们总是需要保存一些状态,否则我们的应用程序将毫无用处。因此,我们将应用程序设计为无状态,并且只在特定的有状态服务(如数据存储)中处理状态。换句话说,无状态应用程序将状态管理和存储委托给后备服务。

并发性(Concurrency)

仅仅创建无状态的应用程序并不足以确保可伸缩性。如果你需要扩展,那意味着你需要为更多用户提供服务。因此,你的应用程序应该允许并发处理,以同时为许多用户提供服务。

"15-Factor方法论"将进程视为一等公民。这些进程应该具有横向扩展性,即将工作负载分布在不同机器上的许多进程中,而这种并发处理只有在应用程序无状态的情况下才可能实现。在Java虚拟机(JVM)应用程序中,我们通过使用线程池中的多个线程来处理并发。

进程可以根据其类型进行分类。例如,你可能有处理HTTP请求的Web进程,以及在后台执行定时作业的工作进程。

遥测

可观察性是云原生应用程序的特性之一。在云中管理分布式系统是复杂的,唯一的管理方法是确保每个系统组件提供正确的数据,以便远程监视系统的行为。遥测数据的示例包括日志、指标、跟踪信息、健康状态和事件。Hoffman使用了一个非常引人注目的比喻来强调遥测的重要性:将你的应用程序视为太空探测器。为了远程监控和控制你的应用程序,你需要哪种类型的遥测数据呢?

认证(Authentication)和授权(Authorization)

安全性是软件系统的基本品质之一,但通常并未得到足够的关注。遵循“零信任”(zero-trust)方法,我们必须在系统的任何架构和基础设施层面保护系统内的所有交互。毫无疑问,安全性不仅仅涉及认证和授权,但这些是一个良好的起点。

通过认证,我们可以追踪使用应用程序的用户。有了这个信息,我们可以检查用户权限,以验证用户是否被允许执行特定的操作。有几个标准可用于实现身份和访问管理,包括OAuth 2.1和OpenID Connect,这本书中我们会使用它们。

使用Spring构建云原生应用程序

现在是时候更具体地讨论技术了。到目前为止,你已经了解了云原生方法和我们将遵循的主要开发实践。现在让我们来看看Spring。如果你正在阅读本书,你可能有一些之前使用Spring的经验,并且想学习如何使用它来构建云原生应用程序。

Spring生态系统提供了处理几乎任何应用程序可能具有的要求的功能,包括云原生应用程序的要求。Spring是迄今为止最广泛使用的Java框架。它已经存在了多年,非常强大可靠。Spring背后的社区非常出色,愿意推动它前进并不断改进。技术和开发实践不断演进,Spring非常擅长跟上这些变化。因此,将Spring用于你的下一个云原生项目是一个绝佳的选择。

本节将介绍Spring生态系统的一些有趣特性。然后我们将开始创建一个Spring Boot应用程序。

Spring生态系统概览

Spring包含多个项目,涵盖了软件开发的许多不同方面:Web应用程序、安全性、数据访问、集成、批处理、配置、消息传递、大数据等等。Spring平台的优势在于其设计是模块化的,因此你可以仅使用和组合你所需的项目。无论你需要构建哪种类型的应用程序,Spring都有可能帮助你。

Spring Framework是Spring平台的核心,也是启动整个项目的项目。它支持依赖注入、事务管理、数据访问、消息传递、Web应用程序等功能。该框架为企业应用程序建立了"管道",让你可以专注于业务逻辑。

Spring Framework提供了一个执行上下文(称为Spring上下文或容器),它在整个应用程序生命周期中管理着Bean、属性和资源。我假设你已经熟悉了该框架的核心功能,所以我不会花太多时间在此介绍。特别是,你应该了解Spring上下文的作用,并且能够熟练地使用Spring Bean、基于注解的配置和依赖注入。我们将依赖于这些特性,因此你应该已经掌握了它们。

基于Spring Framework,Spring Boot使得快速构建独立的、生产就绪的应用程序成为可能。Spring Boot对于Spring和第三方库有一种倾向性的看法,并且它预置了一个明智的默认配置,让开发人员能够以最少的前期工作开始,并且仍然提供了完整的自定义可能性。

在本书中,你将有机会使用多个Spring项目来实现云原生应用程序的模式和最佳实践,包括Spring Boot、Spring Cloud、Spring Data、Spring Security、Spring Session和Spring Native。

注:如果你有兴趣了解更多关于Spring核心功能的内容,你可以在Manning的图书目录中找到几本相关书籍,包括Laurențiu Spilcă的《Spring Start Here》(Manning,2021)和Craig Walls的第六版《Spring in Action》(Manning,2022)。你也可以参考Mark Heckler的《Spring Boot: Up & Running》(O’Reilly,2021)。

构建一个Spring Boot应用程序

假设你被聘用来为Polarsophia构建一个Polar Bookshop应用程序。这个组织管理着一家专业的书店,希望在网上销售关于北极和北极地区的图书。他们正在考虑采用云原生的方法。

作为一个试点项目,你的老板指派你向同事们演示如何从实现到在云端部署。你被要求构建的Web应用程序是"Catalog Service",暂时只有一个职责:欢迎用户访问图书目录。如果这个试点项目成功并得到好评,它将成为构建实际的云原生应用程序的基础。

考虑到任务的目标,你可能决定将该应用程序实现为一个RESTful服务,它只有一个HTTP端点,负责返回欢迎信息。令人惊讶的是,你选择采用Spring作为构建该应用程序的主要技术栈(Catalog Service)。系统的架构如图2.2所示,接下来的部分中,你将尝试构建和部署该应用程序。

《基于云原生的Spring实战:使用Spring Boot和Kubernetes》第二章:云原生模式和技术

在图2.2中,你可以看到我在整本书中使用的用于表示架构图的符号表示法,遵循由Simon Brown创建的C4模型(c4model.com)。为了描述Polar Bookshop项目的架构,我依赖该模型中的三个抽象:

  • Person(人)— 代表软件系统的一个人类用户。在我们的例子中,它是书店的顾客。

  • System(系统)— 代表你将构建的整体应用程序,用于向用户提供价值。在我们的例子中,它是Polar Bookshop系统。

  • Container(容器)— 代表一个服务,可以是应用程序或数据。请注意,这里的容器与Docker不同。在我们的例子中,它是Catalog Service。

对于这个任务,我们将使用Spring Framework和Spring Boot来完成以下工作:

  1. 声明实现应用程序所需的依赖项。
  2. 使用Spring Boot引导应用程序。
  3. 实现一个控制器以暴露一个HTTP端点,用于返回欢迎消息。
  4. 运行并测试应用程序。

本书中的所有示例都基于Java 17,这是撰写时的最新长期支持版本。在继续之前,请按附录A中A.1节的说明安装OpenJDK 17发行版。然后确保你的IDE支持Java、Gradle和Spring。我将使用IntelliJ IDEA,但你可以选择其他的,比如Visual Studio Code。最后,如果你还没有GitHub账号(github.com),请创建一个免费账号。你将使用它来存储你的代码并定义持续交付流水线。

初始化项目

在整本书中,我们将构建几个云原生应用程序。我建议你为每个应用程序定义一个Git存储库,并使用GitHub来存储它们。在下一章中,我将更多地讨论代码库的管理。现在,继续并创建一个名为catalog-service的Git存储库。

接下来,你可以从Spring Initializr(start.spring.io)生成项目,并将其存储在刚创建的catalog-service Git存储库中。Spring Initializr是一个方便的服务,你可以从浏览器中使用,也可以通过其REST API生成基于JVM的项目。它甚至集成到流行的IDE中,如IntelliJ IDEA和Visual Studio Code。Catalog Service的初始化参数如图2.3所示。

《基于云原生的Spring实战:使用Spring Boot和Kubernetes》第二章:云原生模式和技术

在初始化过程中,你可以提供关于你想要构建的应用程序的一些细节,如表2.1所示。

《基于云原生的Spring实战:使用Spring Boot和Kubernetes》第二章:云原生模式和技术

新生成的项目的结构如图2.4所示。在接下来的部分,我将引导你了解这个项目的结构。

《基于云原生的Spring实战:使用Spring Boot和Kubernetes》第二章:云原生模式和技术

在附带本书的代码仓库(github.com/ThomasVital…)中,你可以找到每一章的“begin”和“end”文件夹,这样你就可以始终从与我相同的设置开始,并查看最终结果。例如,你目前正在阅读第二章,因此你会在Chapter02/02-begin和Chapter02/02-end中找到相关的代码。

提示:在本章的“begin”文件夹中,你会找到一个curl命令,在终端窗口中运行该命令,可以下载一个包含所有所需代码的zip文件,以便你可以开始,而不必通过Spring Initializr网站进行手动项目生成。

探索构建配置

打开你刚刚初始化的项目,使用你喜欢的IDE,然后查看Catalog Service应用程序的Gradle构建配置,它在build.gradle文件中定义。在这里,你可以找到你提供给Spring Initializr的所有信息。

plugins {
  id 'org.springframework.boot' version '2.7.3'
  id 'io.spring.dependency-management'
➥ version '1.0.13.RELEASE'
  id 'java'
}
 
group = 'com.polarbookshop'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
 
repositories {                                     ❼
  mavenCentral()
}
 
dependencies {                                     ❽
  implementation 'org.springframework.boot:spring-boot-starter-web'
  testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
 
tasks.named('test') {
  useJUnitPlatform()                               ❾
}

❶ 在Gradle中提供对Spring Boot的支持,并声明要使用的版本

❷ 提供Spring的依赖管理功能

❸ 在Gradle中提供对Java的支持,设置任务来编译、构建和测试应用程序

❹ Catalog Service项目的组ID

❺ 应用程序的版本。默认情况下,为0.0.1-SNAPSHOT。

❻ 用于构建项目的Java版本

❼ 搜索依赖项的Artifact存储库

❽ 应用程序使用的依赖项

❾ 启用使用JUnit 5提供的JUnit Platform进行测试

该项目包含以下主要依赖项:

  1. Spring Web(org.springframework.boot:spring-boot-starter-web)提供了构建使用Spring MVC的Web应用程序所需的必要库,并默认包含Tomcat作为嵌入式服务器。
  2. Spring Boot Test(org.springframework.boot:spring-boot-starter-test)提供了多个用于测试应用程序的库和实用工具,包括Spring Test、JUnit、AssertJ和Mockito。它会自动包含在每个Spring Boot项目中。 注:Spring Boot提供了方便的Starter依赖项,将所有用于特定用例的库捆绑在一起,并确保它们之间的版本兼容。这个特性大大简化了构建配置。

项目的名称在一个名为settings.gradle的第二个文件中定义:

rootProject.name = 'catalog-service'

引导应用程序

在前面的部分中,你初始化了Catalog Service项目,并选择了JAR打包选项。任何打包为JAR的Java应用程序都必须有一个public static void main(String[] args)方法,在启动时执行该方法,Spring Boot也不例外。在Catalog Service中,一个CatalogServiceApplication类在初始化时被自动生成;这是main()方法所在的位置,也是运行Spring Boot应用程序的方式。

package com.polarbookshop.catalogservice;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class CatalogServiceApplication {
  public static void main(String[] args) {       ❷
   SpringApplication.run(CatalogServiceApplication.class, args);
  }
}

❶ 定义一个Spring配置类并触发组件扫描和Spring Boot自动配置。

❷ 用于启动应用程序的方法。它在应用程序的引导阶段将当前类注册为要运行的类。

@SpringBootApplication注解是一个快捷方式,包含了三个不同的注解:

  1. @Configuration将该类标记为Bean定义的源头。
  2. @ComponentScan启用组件扫描,自动在Spring上下文中找到并注册Bean。
  3. @EnableAutoConfiguration启用Spring Boot提供的自动配置功能。

Spring Boot的自动配置是由多个条件触发的,例如类路径中是否存在特定的类、是否存在特定的Bean,或者某些属性的值。由于Catalog Service项目依赖于spring-boot-starter-web,Spring Boot会初始化一个嵌入式的Tomcat服务器实例,并应用所需的最小配置,以几乎零等待时间启动Web应用程序。

至此,应用程序的设置就完成了。现在让我们继续,从Catalog Service中暴露一个HTTP端点。

实现控制器

到目前为止,我们已经查看了由Spring Initializr生成的项目。现在是时候为应用程序实现业务逻辑了。

目录服务(Catalog Service)将暴露一个HTTP GET端点,用于向用户返回友好的问候,欢迎他们访问图书目录。你可以在控制器类中定义一个处理程序(handler)来处理该请求。图2.5显示了交互流程。

《基于云原生的Spring实战:使用Spring Boot和Kubernetes》第二章:云原生模式和技术

在目录服务(Catalog Service)项目中,创建一个新的HomeController类,并实现一个方法来处理根端点(/)的GET请求。

package com.polarbookshop.catalogservice;
 
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class HomeController {
 
  @GetMapping("/")
  public String getGreeting() {
    return "Welcome to the book catalog!";
  }
}

❶ 用于定义REST/HTTP端点处理程序的类

❷ 处理根端点的GET请求

@RestController注解标识一个类作为处理传入HTTP请求的控制器。使用@GetMapping注解,你可以将getGreeting()方法标记为处理根端点(/)收到的GET请求的处理程序。任何发送到该端点的GET请求都将由此方法处理。在下一章中,我将更详细地介绍如何使用Spring构建RESTful服务。

测试应用程序

在从Spring Initializr创建Spring项目时,会包含一个基本的测试设置。在build.gradle文件中,你会自动获取用于测试Spring应用程序的依赖项。此外,还会自动生成一个测试类。让我们来看一下在初始化项目后,CatalogServiceApplicationTests类可能是什么样子的。

package com.polarbookshop.catalogservice;
 
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
 
@SpringBootTest
class CatalogServiceApplicationTests {
 
  @Test
  void contextLoads() {                  ❸
  }
}

❶ 提供了用于测试Spring Boot应用程序的设置

❷ 标识一个测试用例

❸ 空的测试用例,用于验证应用程序上下文是否正确加载

默认的测试类由@SpringBootTest注解标识,它为测试Spring Boot应用程序提供了许多有用的功能。我将在本书中详细介绍它们。目前,只需要知道它会加载完整的Spring应用程序上下文以便运行测试。目前只有一个测试用例,它是空的:用于验证Spring上下文是否正确加载。

打开终端窗口,导航到应用程序的根目录(catalog-service),然后运行测试Gradle任务以执行应用程序的测试。

$ ./gradlew test

该任务应该是成功的,测试应该是绿色的,这意味着Spring应用程序可以在没有错误的情况下启动。那么HTTP端点呢?让我们找出来。

运行应用程序

你已经完成了应用程序的实现,现在可以运行它了。有不同的方法可以运行应用程序,我稍后会向你展示其中的一些。目前,你可以使用Spring Boot Gradle插件提供的任务:bootRun。

在你运行测试的同一个终端窗口中,运行以下命令:

$ ./gradlew bootRun

应用程序应该在一秒内启动并准备好接收请求。在图2.6中,你可以看到启动阶段的日志流。

《基于云原生的Spring实战:使用Spring Boot和Kubernetes》第二章:云原生模式和技术

从图2.6的日志中,你会注意到启动阶段由两个主要步骤组成:

  1. 初始化和运行嵌入式Tomcat服务器(默认监听HTTP端口8080)
  2. 初始化和运行Spring应用程序上下文

此时,你终于可以验证你的HTTP端点是否按预期工作了。打开浏览器窗口,导航到http://localhost:8080/,准备好受到Polar Bookshop图书目录的欢迎。

Polar Bookshop应用程序的开发部分完成了:你有一个Catalog Service应用程序,欢迎用户访问图书目录。在继续之前,请记得终止bootRun进程(Ctrl-C)以停止应用程序的执行。

接下来的步骤是将应用程序部署到云端。为了使其在任何云基础设施上都具有可移植性,你首先需要将其容器化。这就是Docker派上用场的地方。

使用Docker将应用程序容器化

Catalog Service 应用程序已经可以工作。然而,在将其部署到云端之前,你应该将其容器化。为什么呢?容器可以提供对周围环境的隔离,并且它们包含了应用程序运行所需的所有依赖项。

在我们的情况下,大多数依赖项都由Gradle管理,并且与应用程序一起打包(JAR构建产物)。但是Java运行时环境并未包含在内。如果没有容器,你将需要在任何希望部署该应用程序的机器上安装Java运行时。容器化应用程序意味着它将是自包含的,并且在任何云环境下都可以便携式运行。使用容器,你可以以标准方式管理所有应用程序,无论使用的语言或框架是什么。

开放容器倡议(OCI),是Linux Foundation的一个项目,定义了与容器相关的行业标准(opencontainers.org)。特别是OCI镜像规范定义了如何构建容器镜像,OCI运行时规范定义了如何运行这些容器镜像,OCI分发规范定义了如何分发它们。我们将使用的与容器相关的工具是Docker(www.docker.com),它符合OCI规范。

Docker是一个开源平台,它“提供了在称为容器的松散隔离环境中打包和运行应用程序的能力”(docs.docker.com)。 Docker也是这项技术背后的公司的名称,它是OCI的创始成员之一。在一些商业产品中,也使用了这个名称。在我写Docker时,除非另有说明,我指的是我们将用于构建和运行容器的开源平台。

在继续之前,请按附录A中的A.2节的说明,在你的开发环境中安装和配置Docker。

介绍Docker:镜像和容器

当你在计算机上安装Docker平台时,你会得到一个具有客户端/服务器架构的Docker Engine软件包。Docker服务器包含了Docker守护进程,它是一个后台进程,负责创建和管理Docker对象,如镜像、容器、卷和网络。运行Docker服务器的机器称为Docker主机。每台你想要运行容器的机器都应该是一个Docker主机,因此它应该有一个运行着Docker守护进程的实例。正是守护进程使容器的可移植性成为可能。

Docker守护进程暴露了一个API,你可以使用它发送指令,比如运行一个容器或者创建一个卷。Docker客户端通过该API与守护进程进行交互。客户端是基于命令行的,你可以通过脚本(例如Docker Compose)或直接通过Docker CLI与Docker守护进程进行交互。

除了构成Docker Engine的客户端和服务器组件之外,该平台的另一个关键元素是容器注册表,它具有类似于Maven仓库的功能。Maven仓库用于托管和分发Java库,而容器注册表则用于托管和分发容器镜像,并遵循OCI Distribution规范。我们区分公共和私有注册表。Docker公司提供了一个名为Docker Hub(hub.docker.com)的公共注册表,它在默认情况下与你本地的Docker安装配置好,并托管了许多流行的开源项目的镜像,如Ubuntu、PostgreSQL和OpenJDK。

根据Docker文档中包含的架构描述(docs.docker.com),图2.7展示了Docker客户端、Docker服务器和容器注册表之间的交互。

《基于云原生的Spring实战:使用Spring Boot和Kubernetes》第二章:云原生模式和技术

Docker守护进程管理不同的对象。现在我们将重点关注镜像和容器。

容器镜像(简称镜像)是一个轻量级的可执行包,其中包含运行应用程序所需的一切内容。Docker镜像格式是创建容器镜像最常用的格式,并且已由OCI项目(在OCI镜像规范中)标准化。OCI镜像可以通过在Dockerfile中定义指令从头开始创建,Dockerfile是一个文本文件,包含生成镜像的所有步骤。通常,镜像是基于另一个镜像创建的。例如,你可以基于OpenJDK镜像构建一个镜像,然后在其上添加一个Java应用程序。创建完成后,镜像可以推送到像Docker Hub这样的容器注册表。每个镜像由基本名称和标签来标识,其中标签通常是版本号。例如,Ubuntu版本22.04的镜像称为ubuntu:22.04。冒号用于分隔基本名称和版本。

容器是容器镜像的可运行实例。你可以通过Docker CLI或Docker Compose管理容器的生命周期:启动、停止、更新和删除容器。容器的定义基于它们所基于的镜像以及启动时提供的配置(例如,用于自定义容器的环境变量)。默认情况下,容器彼此和主机机器是隔离的,但你可以通过特定端口进行端口转发或端口映射,使它们向外部世界提供服务。容器可以有任何名称,如果你没有指定名称,Docker服务器将分配一个随机名称,比如bazinga_schrodinger。要将OCI镜像作为容器运行,你需要Docker或任何与OCI规范兼容的其他容器运行时。

当你想要运行一个新的容器时,可以使用Docker CLI与Docker守护进程交互,它会检查指定的镜像是否已经存在于本地服务器。如果不存在,它将在注册表中查找该镜像,并将其下载,然后使用它来运行一个容器。工作流程如图2.7所示。

将Spring应用程序作为容器运行

让我们回到 Catalog Service 并看看如何将其作为容器运行。有不同的方法可以实现这一点,但在这里,你将使用 Spring Boot 与 Cloud Native Buildpacks(buildpacks.io)的开箱即用集成,Cloud Native Buildpacks 是由 Heroku 和 Pivotal 发起的项目,现在由 CNCF 托管。它提供了一个高级抽象,用于自动将应用程序源代码转换为容器镜像,而无需使用低级别的 Dockerfile。

Paketo Buildpacks(Cloud Native Buildpacks 规范的一种实现)与 Spring Boot 插件完全集成,适用于 Gradle 和 Maven。这意味着你可以将 Spring Boot 应用程序容器化,而无需下载任何额外的工具,提供任何额外的依赖项,或者编写 Dockerfile。

第6章将介绍 Cloud Native Buildpacks 项目的工作原理,以及如何配置它来将 Spring Boot 应用程序容器化。目前,我会为你提供一点关于其功能的预览。

打开一个终端窗口,导航到 Catalog Service 项目(catalog-service)的根目录,并运行 bootBuildImage Gradle 任务。这就是你需要做的全部,使用 Cloud Native Buildpacks 在幕后打包你的应用程序成为一个容器镜像。

$ ./gradlew bootBuildImage

警告:在我撰写本文时,Paketo项目正在添加对ARM64镜像的支持。你可以在Paketo Buildpacks项目的GitHub上跟踪此功能的进展:github.com/paketo-buil…。在此功能完全添加之前,你仍然可以使用Buildpacks来构建容器,并在苹果Silicon计算机上通过Docker Desktop运行它们,但构建过程和应用程序启动阶段将比平常慢。在官方支持添加之前,你还可以选择使用以下命令,它指向一个具有ARM64支持的实验性版本的Paketo Buildpacks:./gradlew bootBuildImage --builder ghcr.io/thomasvitale/java -builder-arm64。请注意,这是实验性的版本,尚未准备好用于生产环境。有关更多信息,你可以参考GitHub上的文档:github.com/ThomasVital…

第一次运行此任务时,将需要一分钟来下载Buildpacks使用的包以创建容器镜像。第二次运行只需几秒钟。生成的镜像默认将命名为catalog-service:0.0.1-SNAPSHOT(<项目名称>:<版本>)。你可以运行以下命令来获取新创建的镜像的详细信息:[命令内容未提供,可能是原文作者遗漏或下文中提供了相关命令]。

$ docker images catalog-service:0.0.1-SNAPSHOT
REPOSITORY        TAG              IMAGE ID       CREATED        SIZE
catalog-service   0.0.1-SNAPSHOT   f0247a113eff   42 years ago   275MB

注意:你可能已经注意到在上一个命令的输出中,镜像看起来像是在42年前创建的。这是Cloud Native Buildpacks用于实现可复现构建的惯例。如果在输入中没有更改,后续执行构建命令应该给出相同的输出。使用准确的创建时间戳将使这成为不可能,因此Cloud Native Buildpacks使用一个惯例时间戳(1980年1月1日)。

最后一步是运行镜像并验证容器化的应用程序是否正常工作。打开一个终端窗口,并运行以下命令:[命令内容未提供,可能是原文作者遗漏或下文中提供了相关命令]。

$ docker run --rm --name catalog-service -p 8080:8080 \
    catalog-service:0.0.1-SNAPSHOT

警告:如果你在苹果Silicon计算机上运行容器,前面的命令可能会返回类似“WARNING: The requested image’s platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested.”的消息。在这种情况下,在之前的命令中(在镜像名称之前)需要添加一个额外的参数,直到Paketo Buildpacks添加对ARM64的支持:--platform linux/amd64。

你可以参考图2.8中的命令描述。

《基于云原生的Spring实战:使用Spring Boot和Kubernetes》第二章:云原生模式和技术

打开一个浏览器窗口,导航到http://localhost:8080/,并验证你是否仍然能够看到之前收到的相同问候语。

完成后,使用Ctrl-C停止容器。

在第6章中,你将了解有关Docker的更多信息,以及如何从Spring Boot应用程序构建容器镜像以及如何使用容器注册表。我还将向你展示如何使用Docker Compose来管理容器,而不是使用Docker CLI。

使用Kubernetes管理容器

到目前为止,你已经使用Spring Boot构建了一个Web应用程序(Catalog Service),使用Cloud Native Buildpacks将其容器化,并使用Docker运行它。为了完成极地书店(Polar Bookshop)的试点项目,你必须执行最后一步:将应用程序部署到云环境中。为此,你将使用Kubernetes,它已成为容器编排的事实标准。我将在后面的章节中提供关于Kubernetes的更多细节,但我希望你先初步了解它的工作原理,以及如何使用它来部署Web应用程序。

Kubernetes(通常缩写为K8s)是一个用于自动化部署、扩展和管理容器化应用程序的开源系统(kubernetes.io)。当你在Docker中使用容器时,部署的目标是一个机器。在前面一节的示例中,这是你的计算机。在其他情况下,可能是虚拟机(VM)。无论如何,它都涉及将容器部署到特定的机器。然而,当涉及到在没有停机的情况下部署容器、通过利用云弹性来进行扩展,或者跨不同主机进行连接时,你需要更多的东西,而不仅仅是一个容器引擎。与其部署到特定的机器,你是在部署到一个机器集群中,Kubernetes在其他功能中为你管理机器集群。在前一章中,我在拓扑结构的上下文中介绍了这个区别。图2.9将提醒你在容器拓扑结构和编排拓扑结构中的不同部署目标。

《基于云原生的Spring实战:使用Spring Boot和Kubernetes》第二章:云原生模式和技术

在继续之前,请按附录A中A.3节的说明安装minikube并在你的本地开发环境中设置一个Kubernetes集群。一旦完成安装过程,你可以使用以下命令启动一个本地的Kubernetes集群:[命令内容未提供,可能是原文作者遗漏或下文中提供了相关命令]。

$ minikube start

介绍 Kubernetes:部署(Deployments)、Pods 和服务(Services)

Kubernetes是由CNCF(云原生计算基金会)托管的开源容器编排器。仅仅几年时间,它已成为最常用的容器编排解决方案,并且所有主要的云服务提供商都提供了Kubernetes作为一项服务。Kubernetes可以运行在台式机上、本地数据中心、云端,甚至物联网设备上。

在使用容器拓扑结构时,你需要一台带有容器运行时的机器。然而,使用Kubernetes时,你切换到了编排拓扑结构,意味着你需要一个集群。Kubernetes集群是一组运行容器化应用程序的工作节点(节点)。每个集群至少有一个工作节点。通过minikube,你可以轻松地在本地机器上创建一个单节点集群。在生产环境中,你将使用由云服务提供商管理的集群。

Kubernetes集群包含了被称为工作节点的机器,它们上面部署了你的容器化应用程序。它们提供计算资源(如CPU、内存、网络和存储),以便容器可以运行并连接到网络。

控制平面是容器编排层,用于管理工作节点。它提供API和接口,用于定义、部署和管理容器的生命周期。它提供了所有基本元素,实现了编排器的典型特性,如集群管理、调度和健康监控。

注意:在容器编排的上下文中,调度指的是将容器实例与将要运行它的节点进行匹配。匹配基于一组标准,包括节点上是否有足够的计算资源来运行容器。

你可以通过CLI客户端kubectl与Kubernetes进行交互,该客户端与控制平面通信,在工作节点上执行某些操作。客户端不直接与工作节点交互。图2.10显示了Kubernetes架构的高级组件。

《基于云原生的Spring实战:使用Spring Boot和Kubernetes》第二章:云原生模式和技术

Kubernetes可以管理许多不同的对象,包括内置对象和自定义对象。在本节中,你将使用Pods、Deployments和Services。

  • Pod(容器组)——最小的可部署单元,可以包含一个或多个容器。一个Pod通常只包含一个你的应用程序。它还可以包含支持主要应用程序的额外容器(例如,提供额外功能(如日志记录或在初始化阶段运行的管理任务)的容器)。Kubernetes管理Pod而不是直接管理容器。

  • Deployment(部署)——Deployment向Kubernetes提供关于你的应用程序的期望部署状态。对于每个实例,它会创建一个Pod并保持其健康状态。除此之外,Deployment还允许你将Pods作为一个集合来管理。

  • Service(服务)——Deployment(一组Pods)可以通过定义一个Service来向集群内部或外部的其他节点公开,并且Service还负责在Pod实例之间平衡负载。

注意:在整本书中,我将使用大写字母写Kubernetes资源,以将它们与使用不同含义的相同术语区分开来。例如,当涉及到应用程序时,我会使用service,而当指的是Kubernetes对象时,我会写成Service。

当你想要运行一个新的应用程序时,你可以定义一个资源清单,这是一个描述应用程序期望状态的文件。例如,你可以指定应用程序应该被复制五次,并通过端口8080向外部公开。资源清单通常使用YAML编写。然后,你可以使用kubectl客户端请求控制平面创建资源清单描述的资源。最终,控制平面使用其内部组件处理该请求,并在工作节点上创建资源。控制平面仍然依赖于容器注册表来获取资源清单中定义的镜像。工作流程如图2.10所示。

在Kubernetes上运行Spring应用程序

让我们回到极地书店项目。在前面的部分,你将Catalog Service应用程序容器化。现在是时候使用Kubernetes将其部署到集群中了。你已经在本地环境中拥有一个正在运行的集群。你需要的是一个资源清单。

与Kubernetes交互的标准方式是通过声明性指令,你可以在YAML或JSON文件中定义这些指令。我将在第7章向你展示如何编写资源清单。在那之前,你可以像之前使用Docker一样使用Kubernetes CLI。

首先,你需要告诉Kubernetes使用一个容器镜像部署Catalog Service。之前你已经构建了一个镜像(catalog-service:0.0.1-SNAPSHOT)。默认情况下,minikube使用Docker Hub注册表来拉取镜像,并且它没有访问你本地的镜像。因此,它无法找到你为Catalog Service应用程序构建的镜像。但不用担心:你可以手动将它导入到本地集群中。

打开一个终端窗口,并运行以下命令:[命令内容未提供,可能是原文作者遗漏或下文中提供了相关命令]。

$ minikube image load catalog-service:0.0.1-SNAPSHOT

部署单元将是一个Pod,但你将不直接管理Pod。相反,你将让Kubernetes来处理。Pods是应用程序实例,因此它们是临时的。为了实现云原生目标,你希望平台负责实例化Pods,这样如果一个Pod出现故障,它可以被另一个Pod替换。你需要的是一个Deployment资源,它将让Kubernetes创建应用程序实例作为Pod资源。

从一个终端窗口中运行以下命令:

$ kubectl create deployment catalog-service \
    --image=catalog-service:0.0.1-SNAPSHOT

你可以参考图2.11中的命令描述。

《基于云原生的Spring实战:使用Spring Boot和Kubernetes》第二章:云原生模式和技术

你可以通过以下方式验证Deployment对象的创建:

$ kubectl get deployment
NAME              READY   UP-TO-DATE   AVAILABLE   AGE
catalog-service   1/1     1            1           7s

在幕后,Kubernetes为在Deployment资源中定义的应用程序创建了一个Pod。你可以通过以下方式验证Pod对象的创建:

$ kubectl get pod
NAME                               READY   STATUS    RESTARTS   AGE
catalog-service-5b9c996675-nzbhd   1/1     Running   0          21s

提示:你可以通过运行kubectl logs deployment/catalog-service来检查应用程序日志。

默认情况下,在Kubernetes中运行的应用程序是不可访问的。让我们来解决这个问题。首先,你可以通过运行以下命令,将Catalog Service通过Service资源暴露给集群:

$ kubectl expose deployment catalog-service \
    --name=catalog-service \
    --port=8080

图2.12提供了该命令的描述。

《基于云原生的Spring实战:使用Spring Boot和Kubernetes》第二章:云原生模式和技术

Service对象将应用程序暴露给集群内的其他组件。你可以通过以下命令验证它是否已正确创建:

$ kubectl get service catalog-service
NAME              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
catalog-service   ClusterIP   10.96.141.159   <none>        8080/TCP   7s

然后,你可以将流量从你计算机上的本地端口(例如,8000)转发到集群内Service暴露的端口(8080)。还记得Docker中的端口映射吗?这个工作方式类似。命令的输出将告诉你端口转发是否配置正确:

$ kubectl port-forward service/catalog-service 8000:8080
Forwarding from 127.0.0.1:8000 -> 8080
Forwarding from [::1]:8000 -> 8080

你可以参考图2.13中的命令描述。

《基于云原生的Spring实战:使用Spring Boot和Kubernetes》第二章:云原生模式和技术

现在,每当你访问本地主机的端口8000时,你将被转发到负责暴露Catalog Service应用程序的Kubernetes集群内的Service。打开一个浏览器窗口,导航到http://localhost:8000/(确保使用8000而不是8080),并验证你是否仍然收到与之前相同的问候语。

干得好!你从一个使用Spring Boot实现的Java应用程序开始。然后,你使用Cloud Native Buildpacks将其容器化,并在Docker上运行它。最后,你使用Kubernetes将应用程序部署到一个集群中。当然,这是一个本地集群,但它也可以是云中的远程集群。这个过程的美妙之处在于,无论环境如何,它的工作方式都是相同的。你可以使用完全相同的方法将Catalog Service部署到公共云基础设施的集群中。这不是很棒吗?

在第7章中,你将更多地使用Kubernetes。现在,通过Ctrl-C终止端口转发过程,然后使用kubectl delete service catalog-service删除Service,并使用kubectl delete deployment catalog-service删除Deployment。最后,你可以使用minikube stop停止Kubernetes集群。

极地书店:一个云原生应用程序

在本书中,我致力于尽可能提供实际的代码示例。现在,你已经探索了一些关键概念,并尝试构建、容器化和部署了一个Spring应用程序,让我们来着手一个稍微复杂一些的项目:一个在线书店。在本书的其余部分,我将指导你开发一个完整的基于Spring应用程序的云原生系统,并将其容器化,并在公共云中使用Kubernetes进行部署。

对于我们在接下来的章节中涵盖的每个概念,我将向你展示如何将其应用于真实的云原生场景,让你获得完整的实践学习体验。请记住,本书中使用的所有代码都可以在书籍的GitHub存储库上找到。

本节将定义我们将构建的云原生项目的要求,并描述其架构。然后,我将概述我们将用于实现它的主要技术和模式。

理解系统的需求

极地书店是一家专门的书店,其使命是传播关于北极和北极地区的知识和信息,书店位于北极地区。该组织名为Polarsophia,已决定开始在线销售其图书,将它们传播到全球,但这只是一个开始。该项目非常雄心勃勃,其愿景包括一套软件产品,以实现Polarsophia的使命。在本章前面成功的试点项目之后,该组织决定踏上云原生之旅。

在整本书中,你将构建一个具有无限功能和集成可能性的系统的核心部分。管理团队计划通过短迭代交付新功能,缩短上市时间并获得用户的早期反馈。他们的目标是让书店与每个人都紧密相连,因此应用程序应具有高度的可伸缩性。由于面向全球受众,这样的系统需要具有高可用性,因此弹性是必不可少的。

Polarsophia是一个小型组织,他们需要优化成本,尤其是基础设施方面的成本。他们负担不起建设自己的数据中心,因此决定向第三方租用IT硬件。

到现在为止,你可能已经认识到企业迁移到云端的一些原因。这就是我们将为极地书店应用程序做的。当然,它将是一个云原生应用程序。

在应用程序中,书籍将可以进行销售。当顾客购买一本书时,他们应该能够查看订单状态。有两类人将使用极地书店应用程序:

  • 顾客可以浏览图书目录,购买一些书籍,并查看订单。
  • 员工可以管理图书,更新现有图书,并向目录添加新项目。

图2.14描述了极地书店云原生系统的架构。正如你所见,它由几个服务组成。其中一些将实现系统的业务逻辑,提供前面提到的功能。其他服务将实现共享关注点,如集中式配置。为了清晰起见,图中未显示负责安全性和可观察性问题的服务,你将在本书后面学习到它们。

在接下来的章节中,我将详细向你展示图2.14,添加更多关于特定服务的信息,并采用不同的视角来可视化系统在部署阶段的情况。现在,让我们看一下我们将在项目中使用的模式和技术。

探索项目中使用的模式和技术

在本书中,我将在每个新主题引入时向你展示如何将特定的技术或模式应用到极地书店项目中。在这里,我将为你概述我们将处理的主要问题以及我们将用来解决这些问题的技术和模式。

《基于云原生的Spring实战:使用Spring Boot和Kubernetes》第二章:云原生模式和技术

WEB和交互

极地书店由多个服务组成,它们需要相互通信以提供其功能。你将构建使用RESTful服务,通过HTTP同步地进行交互,既可以使用传统的阻塞方式(使用传统的servlet),也可以使用非阻塞方式(使用反应式编程)。Spring MVC和Spring WebFlux(基于Project Reactor)将是你实现这样的结果的主要工具。

在构建云原生应用程序时,你应该设计松耦合的服务,并考虑如何在分布式系统环境中保持数据一致性。当多个服务参与完成一个功能时,同步通信可能会产生问题。这就是为什么事件驱动编程在云端变得非常流行的原因:它允许你克服同步通信的问题。

我将向你展示如何使用事件和消息系统来解耦服务并确保数据一致性。你将使用Spring Cloud Stream来在服务之间实现数据流,使用Spring Cloud Function将消息处理器定义为函数。后一种方法可以自然地演变为部署在Azure Functions、AWS Lambda或Knative等平台上的无服务器应用程序。

数据

数据是软件系统的关键部分。在极地书店中,你将使用PostgreSQL关系数据库来永久存储应用程序处理的数据。我将向你展示如何使用Spring Data JDBC(命令式)和Spring Data R2DBC(反应式)将应用程序与数据源集成。然后,你将学习如何演进数据源,并使用Flyway管理模式迁移。

云原生应用程序应该是无状态的,但状态需要被存储在某个地方。在极地书店中,你将使用Redis将会话存储外部化到数据存储中,并保持应用程序无状态和可伸缩。Spring Session使得实现集群用户会话变得简单。特别地,我将向你展示如何使用Spring Session Data Redis将应用程序会话管理与Redis集成。

除了持久化数据和会话数据外,你还将处理消息,以实现事件驱动编程模式。Spring AMQP和RabbitMQ将是你用来实现这一目标的技术。

在本地,你将在Docker容器中运行这些数据服务。在生产环境中,你将依赖于由DigitalOcean或Azure等云服务提供商提供的托管服务,这些服务会处理关键问题,如高可用性、集群、存储和数据复制。

配置

在整本书中,我将向你展示如何以不同的方式配置极地书店中的服务。我将从探索Spring Boot属性和配置文件提供的选项开始,并说明何时使用它们。接下来,你将学习如何通过在作为JAR包和容器运行的Spring应用程序中使用环境变量来应用外部配置。然后,你将了解如何通过使用Spring Cloud Config实现配置管理的集中化。最后,我将教你如何在Kubernetes中使用ConfigMaps和Secrets。

路由

作为一个分布式系统,极地书店将需要一些路由配置。Kubernetes具有内置的服务发现功能,可以帮助你将服务与其物理地址和主机名解耦。云原生应用程序是可伸缩的,因此它们之间的任何交互都应该考虑到这一点:应该调用哪个实例?再次提醒,Kubernetes本身提供了负载均衡功能,因此你不需要在应用程序中实现任何东西。

使用Spring Cloud Gateway,我将指导你实现一个服务,它将作为API网关来保护外部免受任何内部API变化的影响。它还将是一个边缘服务,你将在其中处理跨切面的问题,如安全性和弹性。这样的服务将是极地书店的入口点,它必须具有高可用性、高性能和容错性。

可观察性

极地书店系统中的服务应该是可观测的,以被定义为云原生应用程序。我将向你展示如何使用Spring Boot Actuator设置健康和信息端点,并使用Micrometer暴露指标,以便Prometheus获取和处理。然后,你将使用Grafana在信息丰富的仪表板上可视化最重要的指标。

由于请求可以由多个服务处理,因此你需要分布式追踪功能来跟踪请求从一个服务到另一个服务的流程。你将使用OpenTelemetry来设置追踪。然后,Grafana Tempo将获取、处理和可视化追踪,为你提供系统如何完成其功能的完整图景。

最后,你将需要一个日志记录策略。我们应该将日志处理为事件流,因此你将使你的Spring应用程序将日志事件流式传输到标准输出,而不考虑它们如何被处理或存储。Fluent Bit将负责从所有服务收集日志,Loki将存储和处理这些日志,而Grafana则允许你浏览它们。

弹性

云原生应用程序应该具有弹性。对于极地书店项目,我将向你展示使用Project Reactor、Spring Cloud Circuit Breaker和Resilience4J等技术实现断路器、重试、超时和其他模式,从而使应用程序具备弹性的各种技术。

安全

安全性是一个广泛的主题,在本书中我无法深入探讨。但我建议你探索这个主题,因为它是当今最重要的软件问题之一。这是一个无处不在的关注点,应该从项目的最初阶段开始不断地加以处理。

对于极地书店项目,我将向你展示如何为云原生应用程序添加身份验证和授权功能。你将了解如何保护服务之间以及用户和应用程序之间的通信。OAuth 2.1和OpenID Connect将是你依赖的标准,用于实现这种功能。Spring Security支持这些标准,并与外部服务无缝集成,以提供身份验证和授权。你将使用Keycloak来进行身份验证和访问控制管理。

此外,我将介绍密钥管理和加密的概念。我无法深入探讨这些主题,但我会向你展示如何管理密钥来配置Spring Boot应用程序。

测试

自动化测试对于云原生应用程序的成功至关重要。Polar Bookshop应用程序将覆盖几个级别的自动化测试。我将向你展示如何使用JUnit5编写单元测试。Spring Boot添加了许多方便的工具,可以改进集成测试,你将使用这些工具来确保服务的质量。你将为Polar Bookshop中使用的各种功能编写测试,包括REST端点、消息流、数据集成和安全性。

保持各个环境之间的一致性对于确保应用程序的质量至关重要。特别是在涉及后端服务时更是如此。在生产环境中,你将使用诸如PostgreSQL和Redis之类的服务。在测试过程中,你应该使用类似的服务,而不是使用模拟或测试专用工具,比如H2内存数据库。Testcontainers框架将帮助你在自动化测试中将真实服务作为容器使用。

构建和发布

极地书店的主要服务将使用Spring。你将了解如何将Spring应用程序打包,以JAR文件运行,使用Cloud Native Buildpacks将其容器化,使用Docker运行,并最终通过Kubernetes部署容器。你还将了解如何使用Spring Native和GraalVM将Spring应用程序编译为本机镜像,并在无服务器架构中使用它们,充分利用它们的即时启动时间、即时性能峰值、减少内存消耗和减小镜像大小。然后,你将在构建在Kubernetes之上的无服务器平台Knative上部署它们。

我将向你展示如何通过设置GitHub Actions的部署流水线来自动化构建阶段。该流水线将在每次提交时构建应用程序、运行测试,并打包准备部署。这样的自动化将支持持续交付文化,以快速可靠地为客户提供价值。最后,你还将使用GitOps实践和Argo CD将极地书店自动部署到生产Kubernetes集群。

UI

本书的重点是后端技术,因此我不会教授任何前端内容。当然,你的应用程序需要一个前端供用户进行交互。对于极地书店,你将使用Angular框架来构建客户端应用程序。在本书中,我不会展示UI应用程序的代码,因为这超出了本书的范围,但我已将其包含在随书附带的代码库中。

总结

  • 15-Factor方法论确定了开发原则,用于构建在执行环境中具有最大可移植性、适合部署在云平台、可扩展、保证开发和生产环境之间的环境一致性,并实现持续交付的应用程序。

  • Spring是一套项目,为在Java中构建现代化应用程序提供了所有最常见的功能。

  • Spring Framework提供了一个应用程序上下文,用于在整个生命周期内管理Bean和属性。

  • Spring Boot为云原生开发奠定了基础,通过加速构建面向生产环境的应用程序,包括嵌入式服务器、自动配置、监控和容器化功能。

  • 容器镜像是轻量级的可执行包,包含运行应用程序所需的一切内容。

  • Docker是符合OCI规范的平台,用于构建和运行容器。

  • Spring Boot应用程序可以通过Cloud Native Buildpacks打包为容器镜像,Cloud Native Buildpacks是CNCF项目,规定如何将应用程序源代码转换为面向生产的容器镜像。

  • 在处理多个容器时,通常在云原生系统中,您需要管理这个复杂的系统。Kubernetes提供功能来编排、调度和管理容器。

  • Kubernetes Pod是最小的部署单元。

  • Kubernetes Deployment描述如何从容器镜像开始,创建应用程序实例作为Pod。

  • Kubernetes Service允许您在集群外部公开应用程序端点。

转载自:https://juejin.cn/post/7260110326115827768
评论
请登录