云原生Spring实战Spring Boot与?Kubernetes实践

978-7-115-62440-6
作者: [美]托马斯·维塔莱 (Thomas Vitale)
译者: 张卫滨
编辑: 佘洁

图书目录:

详情

本书提供了一个以项目为导向的云原生Spring实践指南,将帮助你总揽日益复杂的云计算环境,并学习如何将模式和技术结合在一起,建立一个真正的云计算原生系统并将其投入生产。本书分为四个部分,共计16章。第一部分内容为此次从代码到生产的云原生之旅奠定了基础,帮助你更好地理解本书其他部分所涉及的主题。第二部分介绍了使用Spring Boot和Kubernetes构建生产就绪的云原生应用的主要实践和模式。第三部分涵盖了云中分布式系统的基本属性和模式,包括韧性、安全性、可扩展性和API网关,以及反应式编程和事件驱动架构。第四部分使你的云原生应用为生产做好准备,解决可观测性、配置管理、Secret管理和部署策略等问题,并涵盖了Serverless和原生镜像。

图书摘要

版权信息

书名:云原生Spring实战:Spring Boot与Kubernetes实践

ISBN:978-7-115-62440-6

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

版  权

著    [美]托马斯•维塔莱(Thomas Vitale)

译    张卫滨

责任编辑 佘 洁

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315

内容提要

本书将帮助你使用Spring Boot和Kubernetes来设计、构建和部署云原生应用程序。本书分为四部分,共计16章。第一部分的内容为此次从代码到生产的云原生之旅奠定了基础,帮助你更好地理解本书其他部分所涉及的主题。第二部分介绍了使用Spring Boot和Kubernetes构建生产就绪的云原生应用的主要实践和模式。第三部分涵盖了云中分布式系统的基本属性和模式,包括韧性、安全性、可扩展性和API网关,以及反应式编程和事件驱动架构。第四部分指导你为云原生应用生产化做好准备,解决了可观测性、配置管理、Secret管理和部署策略等问题,并涵盖了Serverless和原生镜像。

本书适合那些想了解如何使用Spring Boot和Kubernetes来设计、构建和部署生产就绪的云原生应用的开发人员和架构师阅读参考。

译者序

最近几年,在互联网和企业级开发领域最火热的一个词恐怕就是“云原生”了。

经过多年的发展和市场培育,对于不同规模的企业来讲,“上云”似乎已经成为理所当然的选择。但是,“上云”之后一切就会变好吗,云真的会带来成本的节省吗,在本地运行无误的应用可以原样迁移至云端吗?事实证明,如果没有细致的规划和谨慎的实施,云并不一定天然带来成本节省,这也是最近FinOps理念越来越火爆的原因。至于应用本身,为了保证可靠性和稳定性,我们需要处理的问题就更多了。

微服务开发模式能够很好地实现快捷交付和系统解耦,但这给运维带来了更艰巨的挑战。尽管在Spring生态系统中,Spring Cloud家族提供了众多的项目以解决微服务开发和运维中所面临的具体挑战,但是,对于采用微服务架构的完整系统,如何实现快捷且可靠的部署和运维;在面临像Kubernetes这样的容器编排环境时,如何一站式完成Spring应用的部署;Spring Cloud提供了众多子项目来支持服务发现、负载均衡、配置等特性,但这些特性在Kubernetes中提供了原生支持,我们如何在两者之间进行权衡和取舍呢?相信这些问题给很多架构师和开发人员造成了困扰。所以,我们一直希望能有一份翔实且权威的资料来指导我们将Spring应用安全、顺利地切换至Kubernetes云原生环境。

所以,当第一次看到这本书的时候,我是非常兴奋的,迫不及待地到Manning官网找到了MEAP版本先睹为快。书中的内容非常具有吸引力,不仅涵盖了具体的框架和细致的操作指南,还有单独的章节来介绍云和云原生的理论知识,这对于深刻了解这一领域是非常有价值的。所以,我第一时间将这本书推荐给了出版社,希望能够尽快引进到国内。在此期间,我还与本书作者就图书内容和发布进度进行了讨论。在确定了本书的版权后,我迅速基于MEAP版本开始了翻译工作,虽然最终的版本与MEAP版本有些差异,导致后续增加了工作量,但是通过这种方式,我们保证了以最快的速度把这本书呈现给国内读者。

本书涵盖了云原生应用开发和运维的各个方面,包括配置管理、安全性、数据持久化、韧性和可观测性,以及Docker和Kubernetes的基础知识,并将生产环境部署到了公有云环境中。除此之外,作者还介绍了持续集成和持续交付,并将测试理念贯穿各个特性的开发。毫不夸张地说,作者以实际样例展示了基于Spring的云应用开发实践,这是一份拿来即可参考落地的实践经验。

希望读者在阅读时也能有这种收获满满的兴奋感。在翻译的过程中,我力求准确,有些细节还与作者进行了多次交流,但是限于时间,再加上本书涵盖领域广泛,如有错误或问题,请不吝反馈。

最后,感谢我的爱人和儿子,容忍我又度过了没日没夜耗在电脑前的这几个月。

希望本书对读者有所帮助,如果您在阅读中遇到问题,可以通过levinzhang1981@126.com与我联系,祝阅读愉快。

张卫滨  

2023年3月于大连

多年来,我已经为几十本书写过前言和序,但这可能是我第一次为写一个序而感到苦恼。为什么呢?我觉得自己是个失败者!我为没有写作这样一本书而感到懊恼,同时懊恼是因为我也不知道是否能够写好它。

这本书非常棒!它的每一页都充满了用生动、深刻的经验来表达的宝贵思想。我一直想看到有人能够把所有这些理念聚集到一起,所以在本书出版后,我会向人们推荐它。

在构建具有生产价值的应用时,我们还需要同时构建生产环境本身(当前主要使用的是Kubernetes生产环境)。这是一个巨大的提升,就像这本书一样,它足有600多页[1]!但是,不要因为我对它的篇幅絮絮叨叨就望而却步,这是一本关于更宏大主题的书。

[1] 指原书厚度。——编辑注

本书涵盖了常见的问题,比如如何构建服务和微服务,以及处理持久化、消息传递、可观测性、配置和安全性等。此外,还有几章专门讨论这些概念。

构建一个不断完善的Spring Boot云原生应用程序(一个在线书店系统)贯穿全书,但本书涵盖的内容不局限于此。Spring Boot应用并不是本书唯一关注的内容。本书的重点很广泛且深入,这是一个很惊人的成就!我觉得可以列出一些具体的细节,这样你就可以了解这本书的内容描述是多么细致了。在此之前,请务必阅读本书。如下列出的几点并不是完备、详尽的,但是它涵盖了我在阅读本书时感到惊喜的内容。这些内容应该出现在一本关于Spring Boot和Spring Cloud的图书中,但不幸的是,它们中很少能够做到这一点。

本书对使用Loki、Fluent Bit和Grafana进行日志记录做了精彩的讲述。

让你的应用在Kubernetes中“运行起来”只是目标之一,读完本书,你将会轻松使用Kubernetes Deployment,并且能够使用Knative和Spring Cloud Function实现Serverless应用,以及使用GitHub Actions、Kustomize和Kubeval等工具构建流水线。而且,你将学会使用Tilt和Docker Compose等工具进行本地开发。

在单页应用(Single-Page Application,SPA)环境中讨论安全问题,这本身就可以支撑起一本优秀的图书。本书对该话题的介绍是卓越的、循序渐进和简洁高效的,并且充分考虑到了生产环境。这一章绝对不容错过。

所有的一切都考虑到了测试。Spring的各个项目都有互相补充的测试模块,这些模块在这里得到了优雅的展示。

本书介绍了GraalVM的原生镜像编译器和Spring Native项目。Spring Native是最近才加入生态系统的,所以即便不纳入书中,也不会有人责怪Thomas。但是他做到了,这多么了不起!

Thomas花了很多篇幅向我们介绍为什么要按照书中的做法行事,这与最新的技术理念保持了一致。我尤其喜欢他对敏捷和GitOps的阐述。

Spring Boot改变了世界,Thomas的这本书是勇敢的Bootiful新世界的绝佳地图。请购买它,阅读它,并按照它的建议采取行动,创建一些令人惊奇的东西,享受你的应用生产化之旅吧!

Josh Long 

Spring技术布道师

前  言

我清楚地记得第一次实地考察、了解护士和从业人员如何在日常工作中使用我所在公司开发的软件的场景,亲眼目睹我们的应用如何改善他们照顾病人的方式是一个令人不可思议的时刻。软件可以带来改变,这就是我们构建它的原因。我们通过技术解决问题,目的是为我们的用户、消费者和企业本身提供价值。

另一个让我无法忘记的时刻是初次接触Spring Boot时。在此之前,我非常享受使用核心Spring Framework。我特别喜欢自己写的,用以管理如安全性、数据持久化、HTTP通信和集成等各个方面的代码。这是一项艰巨的工作,但它是值得的,尤其对比当时Java领域的其他可选方案时。Spring Boot改变了一切。突然间,平台本身为我解决了所有这些问题:所有处理基础设施和集成的代码均不再需要了。

一想到所有处理基础设施和集成的代码均不再需要了,我就开始删除那些代码。当我删除所有这些代码的时候,我意识到与应用的业务逻辑相比,我在这些代码上花费了多少时间,而业务逻辑代码才是产生价值的部分。我还意识到,与所有的模板代码相比,真正属于业务逻辑的代码是多么少。这是一个重要的时刻!

多年以后,Spring Boot仍然是Java领域构建企业级软件产品的卓越平台,其受欢迎的原因之一是它对开发者生产力的关注。令每个应用程序与众不同的是它的业务逻辑,而不是如何暴露其数据或连接到数据库,也正是这种业务逻辑最终为用户、消费者和企业提供了价值。借助由框架、库和集成模式组成的广泛生态系统,Spring Boot能够让开发人员更专注于业务逻辑,同时兼顾项目骨架和模板代码。

在我们的领域中,云是另外一个游戏规则改变者,Kubernetes也是如此,它迅速成为云中的“操作系统”。借助云计算模型的特点,我们可以建立云原生应用,为我们的项目实现更好的可扩展性、韧性、速度和成本优化。最终,我们有机会增加通过软件所产生的价值,并以一种以前不可能实现的方式解决新的问题。

写作这本书的想法来源于我希望帮助软件工程师在他们的日常工作中交付价值的愿望,我也很高兴你决定加入这个从代码到生产的冒险之旅。Spring Boot以及整个Spring生态系统是这次旅程的支柱。其中,云原生原则和模式将指导我们实现各种应用,持续交付实践将支持我们安全、快速、可靠地交付高质量的软件,Kubernetes及其生态系统则将提供一个平台来部署和发布我们的应用。

在组织和编写本书时,我的指导原则是提供合适的、真实的样例,这样你可以立即将其应用于日常工作。书中所涉及的所有技术和模式都应该是为了在生产环境中提供高质量的软件,而这正是在一本书有限的篇幅内所应该包含的全部内容。我希望我成功地实现了这个目标。

再次感谢你加入这次从代码到生产的云原生旅程。我希望你在阅读本书时有一个愉快的体验,并且能够学到想学的知识,也希望它能帮助你用软件创造更大的价值,并有所成就。

致  谢

写书是一件很困难的事情,如果在整个过程中没有许多人的支持,是不可能成真的。首先,我要感谢我的家人和朋友,一路走来,他们不断鼓励和支持我。特别感谢我的父母Sabrina和Plinio、我的妹妹Alissa,以及我的祖父Antonio,感谢他们一直以来的支持和对我的信任。

我要感谢我的朋友和工程师伙伴,他们是Filippo、Luciano、Luca和Marco,他们从最初的提案阶段就一直支持我,并随时提供反馈和建议以改进本书。我想感谢Systematic公司的同事和朋友,他们在这一时期不断鼓励我,能与你们一起工作,我感到很幸运。

我要感谢都灵理工大学的Giovanni Malnati教授,他将我引入Spring生态系统,改变了我的职业生涯。也非常感谢Spring团队创造了这样一个高效和有价值的生态系统。特别感谢Josh Long,他卓越的工作教会了我很多,并且他为本书写了序,这对我来说意义非凡!

我要感谢整个Manning团队的巨大帮助,他们使本书成为有价值的资源。我尤其要感谢Michael Stephens(策划编辑)、Susan Ethridge(开发编辑)、Jennifer Stout(开发编辑)、Nickie Buckner(技术开发编辑)和Niek Palm(技术校对),他们的反馈、建议和鼓励对本书的顺利出版有着巨大的价值。同时感谢Mihaela Batinic ́(审稿编辑)、Andy Marinkovich(制作编辑)、Andy Carroll(文字编辑)、Keri Hales(校对)和Paul Wells(产品经理)。

感谢所有的审校者:Aaron Makin、Alexandros Dallas、Andres Sacco、Conor Redmond、Domingo Sebastian、Eddú Meléndez Gonzales、Fatih Mehmet Ucar、François-David Lessard、George Thomas、Gilberto Taccari、Gustavo Gomes、Harinath Kuntamukkala、Javid Asgarov、Joao Miguel、Pires Dias、John Guthrie、Kerry E. Koitzsch、Michał Rutka、Mladen Knežic ́、Mohamed Sanaulla、Najeeb Arif、Nathan B. Crocker、Neil Croll、Özay Duman、Raffaella Ventaglio、Sani Sudhakaran Subhadra、Simeon Leyzerzon、Steve Rogers、Tan Wee、Tony Sweets、Yogesh Shetty和Zorodzayi Mukuya。你们的建议使这本书变得更好!

最后,我要感谢Java社区和这些年来在这里遇到的所有友善的人:开源贡献者、演讲伙伴、会议组织者,以及为使这个社区如此特别而做出贡献的所有人。

关于本书

本书将帮助你使用Spring Boot和Kubernetes来设计、构建和部署云原生应用。它定义了一条通往生产的路线图,并讲授了有效的技术,你可以基于此立即尝试企业级应用的构建。它还带领你一步一步地从初始想法直到生产,展示云原生开发是如何在软件开发生命周期的每个阶段增加商业价值的。当开发一个在线书店系统时,你将学习如何使用Spring和Java生态系统中强大的库来构建和测试云原生应用。本书会逐章介绍REST API、数据持久化、反应式编程、API网关、函数、事件驱动架构、韧性、安全性、测试和可观测性。同时,本书阐述了如何将应用打包成容器镜像,如何配置像Kubernetes这样的云环境以便于部署,如何让应用为生产做好准备,以及如何利用持续交付和持续部署,设计从代码到生产的路径。

本书提供了一个以项目为导向的实践指南,帮助你总览日益复杂的云计算环境,并学习如何将模式和技术相结合,建立一个真正的云计算原生系统并将其投入生产。

谁应该读这本书

本书的目标读者是那些想了解更多关于如何使用Spring Boot和Kubernetes来设计、构建和部署生产就绪的云原生应用的开发人员和架构师。

为了从本书中获得最大的收益,你需要具备熟练的Java编程技能、构建Web应用的经验,以及Spring核心特性的基本知识。我假设你已经熟悉Git、面向对象编程、分布式系统、数据库和测试。阅读本书不要求有Docker和Kubernetes的经验。

本书的组织路线图

本书分为四部分,共计16章。

第一部分内容为从代码到生产的云原生之旅奠定基础,帮助你更好地理解本书其他部分所涉及的主题,并将它们正确定位在整个云原生的全景图中。

第1章是对云原生全景的介绍,包括云原生的定义、云原生应用的基本属性,以及支撑它们的流程。

第2章涵盖了云原生开发的原则,并指导你首次亲身体验构建一个最小的Spring Boot应用,并将其作为一个容器部署到Kubernetes中。

第二部分介绍了使用Spring Boot和Kubernetes构建生产就绪的云原生应用的主要实践和模式。

第3章涵盖了启动一个新的云原生项目的基础知识,包括组织代码库、管理依赖关系和定义部署流水线的提交阶段的策略。你将学习如何使用Spring MVC和Spring Boot Test实现和测试REST API。

第4章讨论了外部化配置的重要性,并介绍了Spring Boot应用的一些可用方案,包括属性文件、环境变量和Spring Cloud Config的配置服务。

第5章主要介绍了云中的数据服务,并展示如何使用Spring Data JDBC向Spring Boot应用添加数据持久化功能。你将学习如何使用Flyway管理生产环境中的数据以及使用Testcontainers进行测试的策略。

第6章是关于容器的,我们将学习关于Docker的更多知识,以及如何使用Dockerfile和Cloud Native Buildpacks将Spring Boot应用打包为容器镜像。

第7章讨论了Kubernetes,包括服务发现、负载均衡、可扩展性和本地开发工作流程。你还会学习将Spring Boot应用部署到Kubernetes集群的更多知识。

第三部分涵盖了云中分布式系统的基本属性和模式,包括韧性、安全性、可扩展性和API网关。这部分还介绍了反应式编程和事件驱动架构。

第8章介绍了反应式编程和Spring反应式技术栈(包括Spring WebFlux和Spring Data R2DBC)的主要特性,你还会学习如何使用Reactor项目使应用更具韧性。

第9章涵盖了API网关模式以及如何使用Spring Cloud Gateway构建边缘服务。你将学习如何使用Spring Cloud和Resilience4J构建有韧性的应用,在这个过程中会使用重试、超时、回退、断路器和限流器等模式。

第10章描述了事件驱动架构,并教你使用Spring Cloud Function、Spring Cloud Stream和RabbitMQ来实现该架构。

第11章是关于安全的,向你展示了如何使用Spring Security、OAuth2、OpenID Connect和Keycloak在云原生系统中实现认证。它还描述了当系统使用单页应用时,如何解决安全问题,如CORS和CSRF。

第12章继续安全之旅,涵盖了如何使用OAuth2和Spring Security在分布式系统中实现委托访问、保护API和数据,并根据用户的角色进行授权。

第四部分指导你完成最后几个步骤,使你的云原生应用为生产做好准备,解决了可观测性、配置管理、Secret管理和部署策略等问题。它还涵盖了Serverless和原生镜像。

第13章介绍了如何利用Spring Boot Actuator、OpenTelemetry和Grafana可观测性技术栈以使你的云原生应用支持可观测性。你将学习如何配置Spring Boot应用,以产生重要的遥测数据,如日志、健康状况、度量、跟踪等。

第14章涉及高级配置和Secret管理策略,包括Kubernetes原生方案(如ConfigMap和Secret)以及Kustomize。

第15章指导你完成云原生之旅的最后一个步骤,并讲解如何配置Spring Boot的生产环境。然后,我们将会为应用设置持续部署,采用GitOps策略将它们部署到公有云的Kubernetes集群中。

第16章涉及Serverless架构以及基于Spring Native和Spring Cloud Function的函数。你还会了解Knative及其强大的功能,它在Kubernetes之上提供了卓越的开发者体验。

一般来说,我建议从第1章开始,按顺序阅读每一章。如果你喜欢根据自己的兴趣以不同的顺序阅读各章,请确保先阅读完第1~3章,以便更好地理解全书使用的术语、模式和策略。然而,因为每一章都建立在前一章的基础之上,所以如果你决定不按顺序阅读,可能会缺少一些背景知识。

关于代码

本书给予读者基于实践和项目驱动的体验。从第2章开始,我们将为一个虚拟的在线书店建立由多个云原生应用组成的系统。

书中项目开发的所有源代码都可以在GitHub(https://github.com/ThomasVitale/cloud-native- spring-in-action)上找到,并基于Apache许可证2.0进行授权。对于每一章,你都会发现有一个“begin”和一个“end”目录。每一章都建立在前一章的基础上,但即便你没有关注前面的章节,也始终可以把某一章的“begin”目录作为一个起点。“end”目录包含完成本章所有步骤后的最终结果,你可以将其与自己的解决方案进行对比。例如,你可以在Chapter03目录中找到第3章的源码,其中包含了“03-begin”和“03-end”目录。

本书开发的所有应用都是基于Java 17和Spring Boot 2.7,并使用Gradle构建的。这些项目可以导入任何支持Java、Gradle和Spring Boot的IDE中,如Visual Studio Code、IntelliJ IDEA或Eclipse。除此之外,你还需要安装Docker。第2章和附录A提供了更多的信息,以帮助你建立本地环境。

这些样例已经在macOS、Ubuntu和Windows上进行了测试。在Windows上,我建议使用Windows Subsystem for Linux来完成本书中描述的部署和配置任务。在macOS上,如果你使用Apple Silicon机器的话,你可以运行所有的例子,但可能会遇到一些工具性能问题,在编写本书时这些工具没有提供对ARM64架构的原生支持。在相关章节中,我会添加额外的背景信息。

前面提到的GitHub资源库(https://github.com/ThomasVitale/cloud-native-spring-in-action)在主分支中包含了本书的所有源代码。除此之外,我还计划维护一个sb-2-main分支,在该分支中我将根据Spring Boot 2.x的未来版本保持源代码的更新;以及一个sb-3-main分支,在该分支下我将根据Spring Boot 3.x的未来版本对源代码不断演进。

本书包含许多源代码样例,有的使用已编号的程序清单,有的则直接嵌入正文。在这两种情况下,源代码都使用固定宽度的字体进行排版,以便将其与普通文本区分开。有时也会用粗体字来突出显示与本章前文步骤相比有变化的代码,例如,将一个新的特性添加到现有代码行。

在许多情况下,原始源代码会重新格式化,我们添加了换行符和重新缩进,以适应书中可用的页面空间。在极少数情况下,这样做依然是不够的,在这种情况下程序清单会包括换行符(➥)。此外,如果在正文中已经描述过代码,源代码中的注释通常会被移除。许多程序清单会有代码注释,用来突出强调重要的概念。

关于作者

托马斯·维塔莱(Thomas Vitale)是一名软件工程师和架构师,擅长构建云原生、有韧性和安全的企业应用。他在丹麦的Systematic公司设计和开发软件解决方案,他一直致力于为云原生领域提供现代化的平台和应用,并专注于开发体验和安全性。

他主要关注的领域包括Java、Spring Boot、Kubernetes、Knative和一般的云原生技术。托马斯支持持续交付实践,并相信协作的文化,致力于为用户、消费者和企业交付价值。他为Spring Security和Spring Cloud等开源项目做出了卓越贡献,并乐于在社区分享知识。

托马斯拥有意大利都灵理工大学的计算机工程硕士学位,主要研究方向是软件工程。他获得了CNCF Certified Kubernetes Application Developer、Pivotal Certified Spring Professional以及RedHat Certified Enterprise Application Developer认证。他的演讲遍布各大会议,如SpringOne、Spring I/O、KubeCon+CloudNativeCon、Devoxx、GOTO、JBCNConf、DevTalks和J4K。

译者介绍

张卫滨,2007年研究生毕业于天津大学,有着十多年的企业级软件研发和设计经验,热爱探索和研究新技术,目前主要关注云原生、微服务、自动化测试等领域。翻译出版十多本流行的技术图书,如《Spring实战》(翻译了该书的第3版至第6版)、《Spring Data实战》、《RxJava反应式编程》等。业余时间担任技术社区InfoQ的编辑,原创和翻译了数百篇技术文章和新闻。

服务与支持

本书由异步社区出品,社区(https://www.epubit.com)为您提供后续服务。

提交错误信息

作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。

当您发现错误时,请登录异步社区(https://www.epubit.com),按书名搜索,进入本书页面,单击“发表勘误”,输入错误信息,单击“提交勘误”按钮即可(见下图)。本书的作者和编辑会对您提交的错误信息进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。

与我们联系

我们的联系邮箱是contact@epubit.com.cn。

如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。

如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们。

如果您所在的学校、培训机构或企业想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。

如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接通过邮件发送给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。

关于异步社区和异步图书

“异步社区(www.epubit.com)是由人民邮电出版社创办的IT专业图书社区,于2015年8月上线运营,致力于优质内容的出版和分享,为读者提供高品质的学习内容,为作译者提供专业的出版服务,实现作者与读者在线交流互动,以及传统出版与数字出版的融合发展。

“异步图书”是异步社区策划出版的精品IT图书的品牌,依托于人民邮电出版社在计算机图书领域40余年的发展与积淀。

第一部分 云原生基础

原生全景内容宽泛,以至于刚开始就极具挑战性。第一部分内容将为从代码到生产的云原生之旅奠定基础。第1章是对云原生全景的理论介绍,包括云原生的定义、云原生应用的基本属性,以及支持它们的流程。第2章涵盖了云原生开发的原则,指导你首次亲身体验构建一个最小的Spring Boot应用,并将其作为一个容器部署到Kubernetes中。这些能够帮助你更好地理解本书其他部分所涉及的主题,并将它们正确定位在整个云原生的全景图中。

第1章 云原生简介

本章内容:

云和云计算模型

云原生的定义

云原生应用的属性

支撑云原生的文化和实践

何时以及为何要考虑采用云原生方式

云原生应用的拓扑结构和架构

云原生应用是高度分布式系统,它们存在于云中,并且能够对变化保持韧性。系统是由多个服务组成的,服务之间通过网络进行通信,并且会部署到一个一切都在不断变化的动态环境中。

在深入研究技术之前,很重要的一件事就是定义云原生到底是什么。就像我们这个领域中其他的流行词(比如敏捷、DevOps或微服务)一样,云原生有时会被误解,并且成为混乱的根源,因为对不同的人,它意味着不同的东西。

本章将介绍一些理念性工具,它们都是本书后续内容所需要的。我们首先定义云原生意味着什么,以及要采取哪些行动才能使应用可以称为是云原生的。我将会阐述云原生应用的属性,审视云计算模型的特征并讨论何时以及为何要将应用转移到云中。我还会展示云原生拓扑结构和架构的基本理念。图1.1展示了我将在本章中定义和鉴别云原生系统的所有元素。在本章结束时,我们将会为后续的旅程做好准备,以便于使用Spring构建云原生应用并将其部署到Kubernetes中。

图1.1 云原生是一种旨在利用云技术的应用开发方式

1.1 什么是云原生

2010年5月25日,云计算领域的资深人士Paul Fremantle撰写了一篇名为“云原生”的博客文章[1]。他是最早使用云原生这个术语的人之一。在微服务、Docker、DevOps、Kubernetes和Spring Boot等概念和技术尚未出现的年代,Fremantle和他在WSO2的团队讨论了“应用和中间件要在云中良好运行”所需的条件,也就是所谓的云原生。

[1] P. Fremantle, “Cloud Native”, http://pzf.fremantle.org/2010/05/cloud-native.html。

Fremantle所阐述的核心理念是应用要针对云环境进行专门的设计,并且要充分利用云环境和云计算模型的特点。我们可以将一个传统的(按照在本地运行所设计的)应用直接转移到云中,这种方式通常被称为“提升并转移”(lift and shift),但这并不能让应用“原生”适应云环境。接下来,我们看一下如何才能做到这一点。

云原生的3P

什么样的应用才能算是专门为云环境设计的呢?云原生计算基金会(Cloud Native Computing Foundation,CNCF)在对云原生的定义中回答了这个问题[2]

[2] Cloud Native Computing Foundation, “CNCF Cloud Native Definition v1.0”, https://github.com/cncf/toc/blob/ master/DEFINITION.md。

云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式API。

这些技术能够构建容错性好、易于管理和便于观测的松耦合系统。结合可靠的自动化手段,云原生技术使工程师能够轻松地对系统做出频繁和可预测的重大变更。[3]

[3] 此段定义来源于CNCF的官方译文(https://github.com/cncf/toc/blob/main/DEFINITION.md),只是根据目前的惯用说法,将“观察”一词改成了“观测”。——译者注

从这个定义中,我识别出了三组信息,并将其称为“云原生的3P”:

平台(Platform):云原生应用运行在基于动态化、分布式环境的平台上,也就是云(公有云、私有云和混合云)中。

属性(Property):按照设计,云原生应用是可扩展、松耦合、有韧性、可管理和可观测的。

实践(Practice):围绕云原生应用的实践包括可靠的自动化,以及频繁且可预测的变更,即自动化、持续交付和DevOps。

什么是云原生计算基金会?

云原生计算基金会(CNCF)是Linux基金会的一部分,致力于“构建可持续的生态系统和培育社区,以支持云原生开源软件的健康发展”。CNCF托管了许多云原生技术和项目,以实现云的可移植性,避免被供应商锁定。如果你想了解解决云原生各方面问题的项目,建议查阅CNCF的云原生交互式全景图(CNCF Cloud Native Interactive Landscape)[4]

[4] Cloud Native Computing Foundation, “CNCF Cloud Native Interactive Landscape”, https://landscape.cncf.io/。

在后续章节中,我将进一步阐述这些概念。但首先,我想让你注意的是,云原生的定义与任何具体的实现细节或技术没有关联。CNCF在定义中提到了一些技术,如容器和微服务,但它们只是示例。在向云迁移时,一个常见的误解是我们必须要采用微服务架构、构建容器并将其部署至Kubernetes中。这是不对的。Fremantle在2010年的博客文章就证明了这一点,他并没有提及这些技术,因为当时它们根本就不存在。然而,他所描述的应用不仅仍然被认为是云原生的,而且还符合多年后CNCF给出的定义。

1.2 云和云计算模型

在关注我们的主角(云原生应用)之前,我想要先介绍一下我们这个旅程的发生地,也就是云原生应用的运行环境——云(如图1.2所示)。在本节中,我将定义云及其主要特征。毕竟,如果云原生应用按照设计要在云环境中运行,我们应该知道这是一个什么样的环境。

图1.2 云是一种IT基础设施,其主要特征是具有不同的计算模型,供应商会按照消费者所需的控制程度以服务的形式提供

云是一种能够按照云计算模型向消费者提供计算资源的IT基础设施。美国国家标准与技术研究院(National Institute of Standards and Technology,NIST)是这样定义云计算的[5]

[5] NIST, “The NIST Definition of Cloud Computing”, http://mng.bz/rnWy。

云计算是一种模型,能够实现按需在任意位置对可配置的计算资源(如网络、服务器、存储、应用和服务)共享池进行便利的网络访问,这些计算资源可以快速获取和释放,并且要尽可能减少管理成本以及与服务供应商的沟通交流。

就像我们会从供应商那里获取电力,而不是自己发电一样,借助云,我们就能够以商品的形式获取计算资源(例如服务器、存储和网络)。

云供应商管理底层的计算基础设施,所以消费者不需要操心像机器或网络这样的物理资源。迁移到云的公司可以通过网络(通常是互联网)获取需要的所有计算资源,借助一组API,这些公司能够自助式地按需获取和扩展资源。

弹性是该模型的主要特点之一:计算资源可以根据需要动态获取和释放。

弹性指的是一个系统能够在多大程度上自主地获取和减少资源以适应工作负载的变化,确保在每个时间点可用的资源与当前的需求都是相互匹配的[6]

[6] N.R. Herbst, S. Kounev, R. Reussner, “Elasticity in Cloud Computing: What it is, and What it is Not” , http://mng.bz/BZm2。

传统的IT基础设施无法提供弹性。公司必须要计算出所需的最大计算能力,并建立能够支持该能力的基础设施,即便其中大多数的计算能力只是偶尔才会用到。在云计算模型下,计算资源的使用会受到监控,消费者只需要为实际使用的资源付费。

对于云基础设施应该放在哪里,以及由谁来管理,并没有严格的要求。交付云服务的部署模型有多种,主要是私有云、公有云和混合云。

私有云:提供的云基础设施只能由一个组织使用。它可以由组织自身或第三方进行管理,可以托管在企业内部或企业外部。对于处理敏感数据和高度关键系统的组织来说,私有云通常是首选方案。如果要完全控制基础设施的合规性,以符合特定的法律和要求,比如,通用数据保护条例(General Data Protection Regulation,GDPR)或加利福尼亚消费者隐私法案(California Consumer Privacy Act,CCPA),私有云也是一个常见的选择。例如,银行和医疗机构很可能会建立自己的云基础设施。

公有云:提供的云基础设施可公开使用。它通常属于某个组织,并由其进行管理,也就是所谓的云供应商,基础设施由供应商托管。公有云服务提供商如Amazon Web Services(AWS)、Microsoft Azure、Google Cloud、Alibaba Cloud和DigitalOcean。

混合云:由上述任意类型的两个或更多不同的云基础设施组合而成,并且在提供服务的时候,就像是来自一个环境一样。

图1.3描述了5种主要的云计算模型、在每种模型下平台提供了什么内容,以及向消费者提供了哪些抽象。例如,在基础设施即服务(IaaS)模型下,平台提供并管理计算、存储和网络资源,消费者则会设置和管理虚拟机。至于该选择哪种服务模型,取决于消费者需要对基础设施的控制程度以及他们需要管理的计算资源类型。

图1.3 云计算模型的差异在于提供的抽象层级以及由谁(平台或消费者)来管理不同的层级

1.2.1 基础设施即服务

在基础设施即服务(Infrastructure as a Service,IaaS)模型中,消费者可以直接控制和获取像服务器、存储和网络这样的资源。例如,它们可以获取虚拟机并安装软件,比如操作系统和库。尽管这种模型已经存在很久了,但直到2006年,亚马逊通过Amazon Web Services(AWS)才使其流行起来,并得到了广泛的使用。IaaS产品包括AWS Elastic Compute Cloud(EC2)、Azure Virtual Machines、Google Compute Engine、Alibaba Virtual Machines和DigitalOcean Droplets。

1.2.2 容器即服务

使用容器即服务(Container as a Service,CaaS)模型时,消费者无法控制原始的虚拟化资源。相反,他们会设置并管理容器。云供应商负责提供满足这些容器需求的底层资源,例如,通过启动新的虚拟机和配置网络使其能够通过互联网进行访问。Docker Swarm、Apache Mesos和Kubernetes都是用来构建容器平台的示例工具。所有主流的云供应商都提供了托管的Kubernetes服务,它已经成为CaaS的事实标准技术,比如Amazon Elastic Kubernetes Service(EKS)、Microsoft Azure Kubernetes Service(AKS)、Google Kubernetes Engine(GKE)、Alibaba Container Service for Kubernetes(ACK)和DigitalOcean Kubernetes。

1.2.3 平台即服务

在平台即服务(Platform as a Service,PaaS)模型中,平台会给开发人员提供构建和部署应用所需的基础设施、工具和API。例如,作为开发人员,我们可以构建一个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。

1.2.4 函数即服务

函数即服务(Function as a Service,FaaS)模型依赖Serverless计算,让消费者专注于应用业务逻辑的实现(通常遵循函数的方式),而平台负责提供服务器和其他基础设施。例如,你可能会编写一个函数,即每当消息队列中有数据集时,该函数就会分析该数据集并按照一些算法计算出结果。FaaS产品包括Amazon AWS Lambda、Microsoft Azure Functions、Google Cloud Functions和Alibaba Functions Compute。开源FaaS产品包括Knative和Apache OpenWhisk。

1.2.5 软件即服务

具有最高抽象水准的模型是软件即服务(Software as a Service,SaaS)。在这种模型下,消费者以用户的形式访问应用,云供应商管理整个软件栈和基础设施。很多公司会构建应用并使用CaaS或PaaS模型运行它们,然后将它们的使用权以SaaS的形式出售给终端客户。SaaS应用的消费者一般会以瘦客户端(如Web浏览器或移动设备)的形式来访问它们。SaaS的示例应用包括Salesforce、ProtonMail、GitHub、Plausible Analytics和Microsoft Office 365。

平台与PaaS

在云原生相关讨论中,“平台”这个术语可能会产生一些混淆。所以,我们来澄清一下。一般来讲,平台是一个用来运行和管理应用的运维环境。Google Kubernetes Engine(GKE)是一个按照CaaS模型提供云服务的平台。Microsoft Azure Functions是一个按照FaaS模型提供云服务的平台。在更低的层级上,如果我们直接在Ubuntu机器上部署应用,那么这就是我们的平台。在本书后文,当我使用“平台”这个术语时,我指的就是刚刚所述的这个更宽泛的概念,除非另有说明。

1.3 云原生应用的属性

场景已经搭建好了,那就是要在云中。我们该如何设计应用以充分利用云的特点呢?

CNCF定义了云原生应用应该具备的五个主要属性,即可扩展性、松耦合、韧性、可观测性和可管理性。云原生是一种构建和运行具有这些属性的应用的方法论。Cornelia Davis这样总结:“云原生软件是由如何计算定义的,而不是由在何处计算定义的。”[7]换句话说,云是关于“在何处计算”这个问题的,而云原生是关于“如何计算”这个问题的。

[7] C. Davis, “Realizing Software Reliability in the Face of Infrastructure Instability”, IEEE Cloud Computing, 2017, 4(5): 34-40。

我已经介绍了“在何处计算”这个问题,也就是在云中。现在,我们继续讨论“如何计算”的问题。作为快速参考,图1.4列出了这些属性以及它们的简单描述。

图1.4 云原生应用的主要属性

1.3.1 可扩展性

云原生应用是可扩展的,这意味着如果提供额外的资源,它们能够支持增加工作负载。根据这些额外资源的特点,我们可以将其划分为垂直可扩展性和水平可扩展性。

垂直可扩展性:垂直扩展,或者称为向上/向下扩展,意味着在计算节点添加/移除硬件资源,如CPU或内存。这种方式是有限制的,因为我们不能无限地增加硬件资源。另外,应用不需要按照特定的方式进行设计,就能实现向上或向下扩展。

水平可扩展性:水平扩展,或者称为向外/向内扩展,意味着向系统中添加或移除计算节点或容器。这种方式没有垂直扩展那样的限制,但它需要应用具有可扩展性。

传统系统在面临工作负载增加的时候通常会采用垂直可扩展方式。对于要支持更多用户的应用来说,添加CPU和内存是一种常见的方式,不需要针对可扩展性进行重新设计。在特定的场景中,这依然是一个很好的选择,但是对于云环境来说,我们需要一些其他东西。

在云中,所有的内容都是动态和不断变化的,水平可扩展是首选方案。借助云计算模型所提供的抽象等级,为应用补充新的实例是非常简单的,而不应该为已经处于运行状态的机器增加计算能力。因为云是有弹性的,我们可以在很短的时间内动态地扩展和收缩应用的实例。我之前已经讨论过,弹性是云的主要特征之一:计算资源可以根据需要主动提供和释放。可扩展性是实现弹性的先决条件。

图1.5展示了垂直可扩展性和水平可扩展性之间的差异。在垂直可扩展性中,我们通过向现有的虚拟机添加更多的资源来进行扩展。在水平可扩展性中,我们添加了另外一个虚拟机,它会帮助现有的虚拟机处理额外的工作负载。

图1.5 当需要支持不断增加的工作负载时,垂直可扩展性模型会向计算节点中增加硬件资源,
而水平可扩展性模型则会增加更多的计算节点

当讨论Kubernetes的时候,你将会看到,平台(可以是CaaS、PaaS或其他类型的平台)会根据上文所述的定义动态地扩展和收缩应用。作为开发人员,你有责任设计可扩展的应用。可扩展性的最大障碍是应用的状态,这将决定应用最终是有状态的还是无状态的。在本书中,我将介绍构建无状态应用的技术,并让它们没有任何问题地进行扩展。除此之外,我将会展示如何将应用的状态从Spring推送到像PostgreSQL和Redis这样的数据存储中。

1.3.2 松耦合

松耦合是系统的一个基本属性,根据该属性,系统的各个组成部分之间对彼此的了解要尽可能的少。它的目标是每个部分都能独立演进,这样当某一部分发生变化的时候,其他组成部分无须相应地变化。

几十年来,耦合及其伴生的内聚理念在软件工程中发挥了重要的作用。将系统分解成模块(模块化),这些模块对其他部分的依赖达到最小(松耦合),并且将联系紧密的代码封装在一起(高内聚),这是一种良好的设计实践。根据不同的架构风格,模块可以建模成一个单体组件或一个独立的服务(例如,微服务)。不管采用哪种方式,我们都应该以实现松耦合和高内聚的恰当模块化为目标。

Parnas指出了模块化的三个收益[8]

[8] D. L. Parnas, “On the criteria to be used in decomposing systems into modules”, Communication of the ACM, 1972: 1053–1058。

管理方面:鉴于每个模块都是松耦合的,所以负责团队应该不需要与其他团队进行过多的沟通和协调。

产品的灵活性:每个模块都可以独立于其他模块演进,所以这会形成一个非常灵活的系统。

可理解性:人们应该能够理解和运作单个模块,而不必学习整个系统。

上面所述的收益通常也是微服务所带来的部分收益。事实上,要实现它们,并不一定要采用微服务。在最近几年中,很多组织决定从单体迁移至微服务。但是,其中有些组织因为缺乏恰当的模块化而宣告失败。单体由紧耦合、非内聚的组件组成,当进行迁移的时候,会产生一个紧耦合、非内聚的微服务系统,它有时候也被称为分布式单体。我认为这并不是一个好的名字,因为它暗示着,按照定义单体是由紧耦合、非内聚的组件组成。而事实并非如此。架构风格并不重要:糟糕的设计就是糟糕的设计,与架构风格无关。事实上,我喜欢Simon Brown提出的“模块化单体”这个术语,它可以提高人们的认知,那就是单体也可以提升松耦合和高内聚,而且单体和微服务最终都有可能成为“大泥球”。

在本书中,我将会展示一些如何在应用中强制实现松耦合的技术。尤其是,我将会采用面向服务的架构,专注于构建具有明确接口的服务,以便于服务间的相互通信,并将与其他服务的依赖降至最低从而实现高内聚。

1.3.3 韧性

如果一个系统能够在出现故障或环境变化的情况下依然能够提供服务,那么我们就说它是有韧性(resilience)的。韧性是指“在面临故障以及挑战正常运维的情况下,硬件-软件网络提供和保持可接受的服务水平的能力”[9]

[9] J. E. Blyler, “Heuristics for resilience — A richer metric than reliability”, 2016 IEEE International Symposium on Systems Engineering (ISSE), 2016: 1-4。

在构建云原生应用时,我们的目标应该是,不管是基础设施还是我们的软件出现故障,都要确保应用始终是可用的。云原生应用在一个动态的环境中运行,在这种环境中所有的事情都在不断地发生变化,故障在所难免,这是无法预防的。过去,我们习惯于将变化和故障视为异常情况。但是,对于像云原生这样的高度分布式系统来说,变化不是异常情况,它们是常态。

当讨论韧性的时候,我们有必要定义三个基本概念,即过错(fault)、错误(error)和故障(failure)。

过错:指的是在软件或基础设施中会产生不正确的内部状态的缺陷。例如,某个方法调用返回了一个空值,但是规范要求它必须返回非空的值。

错误:指的是系统的预期行为和实际行为的差异。例如,因为上面所述的过错,抛出了NullPointerException异常。

故障:当出现过错并导致错误时,有可能会产生故障,这将使得系统无反应并且无法按照其规范行事。例如,如果这个NullPointerException没有被捕获的话,这个错误就会引发故障——系统对任何请求都会产生500响应码。

过错可能会变成错误,进而引发故障,所以我们应该设计能够容错(fault tolerant)的应用。韧性的一个重要组成部分就是要确保故障不会级联到系统的其他组件中,而是在修复时保持它是隔离的。我们可能还希望系统是自我修复(self-repairing)或自我治愈(self-healing)的,云模型实际上可以做到这一点。

在本书中,我将会展示一些容错技术,并且能够阻止故障的影响传播到系统的其他组成部分,导致故障传播。例如,我们将会使用断路器、重试、超时和限流器。

1.3.4 可观测性

可观测性(observability)是来自控制理论领域的一个属性。在考虑一个系统的时候,可观测性指的是我们能够在多大程度上根据它的外部输出推断其内部状态。在软件工程方面,系统可能是单个应用,也可能是作为一个整体的分布式系统。外部输出可以是度量指标、日志或跟踪信息。图1.6展示了可观测性是如何运行的。

图1.6 可观测性指的是从应用的外部输出推断其内部状态。可管理性指的是通过外部输入改变其内部状态和输出。在这两种情况下,应用制品没有发生任何变化,它是不可变的

Twitter的可观测性工程团队识别了可观测性的四大支柱[10]

[10] A. Asta, “Observability at Twitter: technical overview, part 1”, http://mng.bz/pO8G。

监控:监控指的是测量应用的特定方面,以获取其整体的健康状况并识别故障。在本书中,我们将会利用Spring Boot Actuator提供的监控特性,并将Prometheus与Spring集成,以导出应用的相关度量指标。

告警/可视化:只有将收集到的系统状态数据用于采取某些行动时,我们才能说这些数据是真正有用的。在监控应用的时候,如果识别出了故障,应该触发告警并采取一些操作来处理它。我们会使用特定的仪表盘将收集到的数据进行可视化,并将其绘制在相关的图表中,目标是在一个良好的画面中展示系统的行为。在本书中,我们将会学习如何使用Grafana来可视化从云原生应用中收集到的数据。

分布式系统的跟踪基础设施:在分布式系统中,仅仅跟踪每个子系统的行为是不够的,重要的是要跟踪流经不同子系统的数据。在本书中,我们将会集成Spring和OpenTelemetry,并使用Grafana Tempo来收集和可视化跟踪信息。

日志聚合/分析:对于推断软件的行为以及在出现问题时对其进行调试来说,跟踪应用的主要事件是非常重要的。在云原生系统中,日志应该进行聚合和收集,以便更好地了解系统行为,并且这样才有可能运行分析工具以挖掘这些数据中的信息。在本书中,我将会详细讨论日志。我们将使用Fluent Bit、Loki和Grafana来收集和可视化日志,并学习在云原生场景下使用日志的最佳实践。

1.3.5 可管理性

在控制理论中,与可观测性对应的概念是可控制性,它表示在有限的时间间隔内,外部输入改变系统的状态或输出的能力。这个概念将我们带到了云原生的最后一个主要属性,也就是可管理性。

再次借鉴控制理论,我们可以说可管理性是衡量外部输入改变系统状态和输出的便利程度和效率。用更少的数学术语来讲,它是在不改变代码的情况下修改应用行为的能力。不要将它与“可维护性”混淆,可维护性衡量了从内部通过修改代码来改变系统的便利程度和效率。图1.6展示了可管理性是如何运作的。

可管理性所涉及的一个方面就是在部署和更新应用的时候,要保持整个系统的正常运行。可管理性的另一个元素就是配置,我将在本书中深入探讨这个问题。我们希望让云原生应用具有可配置性,这样就可以在不改变代码和构建新发布版本的情况下改变它们的行为。将数据源URL、服务凭证和证书等设置信息变成可配置的是很常见的。例如,根据不同的环境,我们可以使用不同的数据源,分别用于开发、测试和生产环境。其他类型的配置还包括特性标记,它们用来决定是否在运行时启用特定的特性。在本书中,我将会展示配置应用的不同策略,包括使用Spring Cloud Config Server、Kubernetes ConfigMaps与Secret,以及Kustomize。

可管理性不仅涉及变更本身,还包括如何便利和高效地应用这些变更。云原生系统是非常复杂的,所以必须要设计出能够适应功能、环境和安全性变化的应用。鉴于其复杂性,我们要尽可能地通过自动化来进行管理,这样我们就来到了上文所述“云原生3P”的最后一项,也就是实践。

1.4 支撑云原生的文化与实践

在本节中,我们将讨论CNCF所提供的定义中的最后一句话:“结合可靠的自动化手段,云原生技术使工程师能够轻松地对系统做出频繁和可预测的重大变更。”在这里,我将讨论三个理念,即自动化、持续交付和DevOps(如图1.7所示)。

图1.7 云原生开发的文化和实践

1.4.1 自动化

自动化是云原生的一个核心原则。它的理念是将重复性的人工任务进行自动化,以加快云原生应用的交付和部署。很多任务都可以实现自动化,从应用的构建到部署,从基础设施的供应到配置管理均是如此。自动化最重要的优势在于,它能够将流程和任务变成可重复的,这样系统整体会更加稳定可靠。手动执行任务容易出错,并且会增加成本。通过将任务自动化,我们可以得到更加稳定和高效的结果。

在云计算模型中,计算资源会以自动化、自服务的模式供应,并且能够弹性增加或减少资源。云计算自动化的两个重要方面就是基础设施供应和配置管理,我们分别将其称为基础设施即代码(infrastructure as code)和配置即代码(configuration as code)。

Martin Fowler将基础设施即代码定义为“通过源代码的方式来定义计算和网络基础设施,就像任何其他软件系统代码一样”[11]

[11] M. Fowler, “Infrastructure As Code”, http://mng.bz/DD4。

云供应商提供了便利的API来创建和供应服务器、网络和存储。通过使用像Terraform这样的工具将这些任务进行自动化,将代码进行源码控制并采用与应用开发相同的测试和交付实践,我们可以得到一个更可靠和更可预测的基础设施,它是可重复、更高效并且风险更低的。比如,一个自动化此类任务的样例可能是创建一个新的虚拟机,它具有8个CPU、64GB的内存,并且安装了Ubuntu 22.04操作系统。

在供应完计算资源之后,我们就可以管理它们并对它们的配置实现自动化。套用前面的定义,配置即代码指的是通过源代码的方式来定义计算资源的配置,就像任何其他软件系统代码一样。

通过使用像Ansible这样的工具,我们能够声明服务器或网络该如何进行配置。例如,按照上文所述提供了Ubuntu服务器之后,我们可以自动完成安装Java Runtime Environment(JRE) 17,并在防火墙上打开8080和8443端口的任务。配置即代码的理念也适用于应用的配置。

通过将基础设施供应和配置管理相关的所有任务自动化,我们可以避免不稳定、不可靠的“雪花服务器”(snowflake server)。当每台服务器都是以手动的方式进行供应、管理和配置的时候,其结果就是服务器像雪花一样,即该服务器是脆弱且独一无二的,无法复制,并且变更起来也有风险。自动化能够避免出现雪花服务器,并形成“凤凰服务器”(phoenix server),即作用于这些服务器的所有任务都是自动化的,每个变更都可以在源码中进行跟踪,降低了风险,并且每个设置过程都是可重复的。如果将这种理念发挥到极致,我们就会实现所谓的不可变服务器(immutable server),CNCF在其云原生定义中也提到了不可变基础设施。

注意 在对比传统的雪花基础设施(需要很多的关注和照料,就像宠物一样)和不可变基础设施或容器(其特点是可拆卸和可替换,就像牲畜一样)时,你可能听过“宠物与牲畜”这种表述方式。在本书中,我不会使用这种表述,但是在关于该话题的讨论中,有人可能会用到这种方式,所以你需要注意一下。

在最初的供应和配置完成之后,不可变服务器不允许再进行任何变更,也就是说它们是不可变的。如果有必要进行改变的话,会以代码的方式进行定义和交付。最终,会根据新的代码供应和配置新的服务器,而之前的服务器则会被销毁。

例如,如果现在的基础设施包含Ubuntu 20.04服务器,你想要将其升级到22.04,那么你有两个方案。第一种方案是通过代码定义升级,并在现有的机器(凤凰服务器)上运行自动化脚本以执行操作。第二种方案是自动供应带有Ubuntu 22.04的新机器,并开始使用它们(不可变服务器),而不是在现有的机器上进行升级。

在下一节中,我们将会讨论构建和部署应用的自动化问题。

1.4.2 持续交付

持续交付(Continuous Delivery,CD)是“软件开发的一种理念,按照这种方式构建的软件能够在任意时间发布到生产环境”。[12]借助持续交付,团队能够在短周期内实现特性,确保软件在任意时间都能可靠地发布。它是确保“轻松地对系统做出频繁和可预测的重大变更”(来自CNCF的云原生定义)的关键。

[12] M. Fowler, “Continuous Integration Certification”, http://mng.bz/xM4X。

持续集成(Continuous Integration,CI)是持续交付中的一个基础实践。开发人员会持续(至少每天一次)将代码提交至主线(main分支)。在每次提交时,软件会自动编译、测试和打包成可执行制品(比如JAR文件或容器镜像)。它的理念是在每次新的变更后,得到软件状态的快速反馈。如果探测到错误的话,应该立即修正,确保主线是一个稳定的基础,以便于后续开发。

持续交付构建在CI之上,它的关注点在于,确保主线始终是健康的,处于可发布状态。在与主线集成所形成的可执行制品生成之后,软件会部署到一个类生产环境中。它会经历额外的测试以验证可发布性,比如用户验收测试、性能测试、安全性测试、合规测试,以及有助于增加软件可发布信心的其他测试。如果主线始终处于可发布状态,那么发布软件的新版本将会是一个业务决策,而不是技术决策。

正如Jez Humbleh和David Farley在合著的基础性图书Continuous Delivery(Addison-Wesley Professional,2010)中所述,持续交付鼓励整个过程通过“部署流水线”(也称为持续交付流水线)实现整个过程的自动化。部署流水线会从代码提交开始,一直延续到可发布的输出,它是通向生产环境的唯一方式。在本书中,我们将会构建部署流水线以保证main分支始终处于可发布状态。最后,我们会使用它将应用自动部署到Kubernetes生产环境中。

有时候,人们会将持续交付与持续部署(continuous deployment)相混淆。持续交付会确保在每次变更之后,软件都能处于一种可部署至生产环境的状态。至于何时进行真正的部署,这是一个业务方面的决策。而持续部署则是在部署流水线中添加最后一个步骤,在每次变更发生之后,将新的发布版本自动部署到生产环境中。

持续交付并不是工具,它是涉及组织中文化和结构变化的理念。搭建自动化的流水线来测试和交付应用并不意味着你在践行持续交付。与之类似,使用CI服务器来自动化构建并不意味着你在践行持续集成。[13]这就把我们引入到了下一个话题,它也通常被认为仅仅是一些工具而已。

[13] M. Fowler, “Continuous Integration Certification”, http://mng.bz/xM4X。

持续交付与CI/CD

由于持续集成是持续交付的基础实践,该组合通常被称为CI/CD。因此,部署流水线经常被称为CI/CD流水线。我对这个术语有一些保留意见,因为持续集成并不是持续交付的唯一实践。例如,测试驱动开发(TDD)、自动化配置管理、验收测试和持续学习同样重要。

Jez Humble和Dave Farley在他们合著的Continuous Delivery一书中没有使用CI/CD这个术语,在他们写的关于这个主题的任何其他书中也没有使用。此外,它还会造成困惑。CD是代表持续交付还是持续部署呢?在本书中,我将把“更快交付更好的软件”[14]这一整体方法称为持续交付,而不是CI/CD。

[14] D.Farley, Continuous Delivery Pipelines,2021。

1.4.3 DevOps

DevOps是最近另外一个非常流行的热词,但有时候它被人误解了。当转向云原生时,DevOps是一个需要掌握的重要概念。

DevOps的起源是非常奇特的。非常有意思的一点是,这个概念的创始人拒绝为其提供一个定义。这带来的结果就是很多人都使用它们自己的解释,当然,最终我们都在使用DevOps这个词来表示不同的东西。

注意 如果你有兴趣了解关于DevOps起源的更多信息,建议你观看Ken Mugrage的演讲,题目为“DevOps and DevOpsDays—Where it started, where it is, where it’s going”(http://mng.bz/Ooln)。

在所有关于DevOps的定义中,我发现Ken Mugrage(ThoughtWorks的首席技术专家)提出的定义特别有参考价值和有趣,他强调了DevOps的真正含义[15]

[15] K. Mugrage, “My definition of DevOps”, http://mng.bz/AVox。

DevOps是一种文化,在这种文化中人们不分头衔或背景,共同想象、开发、部署和运维一个系统。

所以,DevOps是一种文化,它指的是为了一个共同的目标而协作。开发人员、测试人员、运维人员、安全专家以及其他人,无论头衔或背景,团结协作,将理念带入生产中并产生价值。

这意味着孤岛(silo)时代的结束,特性团队、QA团队和运维团队之间不再有壁垒。DevOps通常被认为是敏捷(agile)的自然延续,敏捷是DevOps的助推器,其概念是以小团队的形式频繁地向客户提供价值。简洁描述DevOps可引用亚马逊的CTO Werner Vogels在2006年发布的一句名言,当时DevOps这个词尚不存在:“如果你负责构建它的话,那就要负责运行它。”[16]

[16] J. Barr, “ACM Queue: Interview with Amazon’s Werner Vogels”, http://mng .bz/ZpqA。

定义了DevOps是什么之后,我们再简单看一下它不是什么。

DevOps并不意味着没有运维(NoOps)。一个常见的错误就是认为开发人员负责运维,运维人员的角色就消失了。但实际上,现在是一种合作的形式。团队将包含这两种角色,他们都会向团队贡献技能,从而将产品从最初的想法带到生产环境中。

DevOps并不是一种工具。像Docker、Ansible、Prometheus这样的工具通常被称为DevOps工具,但这是错误的。DevOps是一种文化。我们无法仅通过工具就将一个组织变成DevOps组织。换句话说,DevOps不是一个产品,但工具是重要的推动者。

DevOps不是自动化。即便自动化是DevOps的重要组成部分,但是这并不是DevOps的定义。DevOps指的是开发人员和运维人员协同工作,从最初的理念阶段直至生产环境,在这个过程中,可能会将一些过程自动化,比如持续交付。

DevOps不是一个角色。如果我们将DevOps视为一种文化和思维方式,那么DevOps是一个角色的说法就不攻自破了。然而,我们对DevOps工程师的需求却越来越多。通常情况下,当招聘人员寻找DevOps工程师的时候,他们寻找的是熟练掌握自动化工具、脚本和IT系统等技能的人。

DevOps不是一个团队。如果组织没有完全理解上文所述内容,他们很可能最终依然像以前那样保持孤岛状态,只会有一个变化:使用名为DevOps的孤岛取代原来的Ops孤岛,或者仅仅增加了一个DevOps孤岛而已。

在迈向云原生时,开发人员和运维人员的合作是最重要的。你可能已经注意到,在设计和构建云原生应用的时候,需要始终记住一点,那就是这些应用的部署地点是在云中。与运维人员协同工作,能够让开发人员设计和构建更高质量的产品。

虽然它被称为DevOps,但是我们要注意,这个定义不只涉及开发人员和运维人员。相反,它涉及所有人,无论他们的头衔或背景是什么。这意味着协作也会涉及其他角色,比如测试人员和安全专家(不过,我们并不需要DevSecOps、DevTestOps、DevSecTestOps或DevBizSecTestOps这样的新术语)。他们一起对整个产品的生命周期负责,是实现持续交付目标的关键。

1.5 云是最佳方案吗

在我们的行业中,有一个最大的错误就是决定采用某项技术或方式仅仅因为它是新出现的,而且所有人都在谈论它。公司从单体迁移至微服务,最终以惨烈失败而告终的故事层出不穷。我已经阐述了云和云原生应用的属性。它们应该能够为你提供一些指导。如果你的系统不需要这些属性,因为你的系统根本不存在这些技术所试图解决的问题,那么对你的项目来说,“迈向云原生”可能并不是最佳选择。

作为技术人员,我们很容易被最新、最流行、最闪亮的技术所吸引。这里的关键在于,要弄清楚某项特定的技术或方式是否能够解决你的问题。我们将想法变成软件,然后将其交付给客户并为其提供价值,这才是我们的终极目标。如果某项技术或方式能够帮助我们为客户提供更多的价值,那么我们就应该考虑采用它。如果根本不值得这样做,你却一意孤行要采取这种方式的话,很可能最终会面临更高的成本和众多的问题。

迁移至云原生的最佳时机是什么时候呢?为什么公司要采用云原生方式?采用云原生的主要目标如图1.8所示,也就是速度、扩展、韧性和节省成本。如果你的业务愿景包含这些目标,并且要面对云技术所试图解决的问题,那么考虑迁移至云并采用云原生方式是很不错的。否则,请保持原样,在本地运行会更好一些。例如,如果你的公司通过一个单体应用来提供服务,而且该应用已经处于维护阶段,不会进一步扩展新的功能,在过去的几十年间该应用运行良好,那么就没有必要将其迁移到云中,更不用说将其变成云原生应用了。

图1.8 迈向云原生能够帮助我们实现速度、韧性、扩展和节省成本相关的目标

1.5.1 速度

对于当今企业来讲,能够更快地交付软件是一个重要的目标。尽可能快地将理念投入生产,从而缩短产品的上市时间是一个关键的竞争优势。能否在正确的时间将正确的理念投入生产,可能就决定了企业的成败。

客户希望得到越来越多的特性实现或缺陷修复,而且他们希望立即就要,根本不愿意等待六个月之后才能看到我们软件的下一个版本。他们的期望在不断地提高,我们需要有一种方法来跟上他们的节奏。归根到底,这一切都是为了给客户提供价值,并确保他们对结果感到满意。否则,我们的企业难以在激烈的竞争中生存下来。

更快、更频繁地交付不仅关乎竞争和客户的最后期限,它还能够缩短反馈周期。频繁和小规模的发布意味着我们能够更快地获取客户的反馈。更短的反馈周期反过来又会减少新特性相关的风险。与其花费几个月的时间实现完美的特性,还不如快速推出,从客户那里得到反馈,并对其进行调整以符合他们的期望。同时,较小的版本会包含更少的变更,因此发生故障的组件数量也会随之减少。

我们还需要灵活性,因为客户期望我们的软件能够不断演化。例如,它应该有足够强的灵活性以支持新类型的客户端。如今,日常生活中越来越多的物品都已经能够连接至互联网,比如各种移动和物联网(IoT)系统。我们希望能够对未来任何类型的扩展和客户端类型保持开放,从而能够以新的方式提供业务服务。

传统的软件开发方式并不支持这一目标。传统方式的典型特点是大规模的发布、微乎其微的灵活性以及漫长的发布周期。云原生方式与自动化任务、持续交付工作流和DevOps实践相结合,有助于实现业务更快速地发展,并缩短上市时间。

1.5.2 韧性

万事万物都在不断地发生着变化,故障也是一直存在的。试图预测故障并将其视为异常情况的时代已经成为历史。正如我在前文所述,变更并不是异常情况,它们是常态。

客户希望软件是7×24小时可用的,而且一旦有新特性,就能立即升级。停机或故障会导致金钱方面的直接损失以及客户满意度的下降,这甚至可能会影响到声誉,导致组织在未来的市场机会方面蒙受损失。

无论基础设施还是软件出现了故障,我们的目标都是确保系统的可用性和稳定性。哪怕是在降级的运维模式下,我们也希望能够继续为用户提供服务。为了保证可用性,我们需要采取一些措施来应对故障的发生,以对故障进行处理,从而确保整个系统依然能够为用户提供服务。在处理故障和执行升级这样的任务时,所有的操作都应该在零停机的情况下完成。客户的期望就是这样的。

我们希望云原生应用是具有韧性的,同时云技术提供了实现韧性基础设施的策略。如果你的业务需求包括始终可用、安全和韧性,那么云原生方式就非常适合你。软件系统的韧性反过来又能推进它的交付速度:系统越稳定,就能越频繁地安全发布新特性。

1.5.3 扩展

弹性指的是能够根据负载情况对软件进行扩展。我们可以扩展一个弹性系统,确保为所有的客户提供足够的服务水平。如果系统的负载比往常高,那么我们需要生成更多的服务实例来支持额外的流量。或者发生一些严重的事情,有些服务出现了故障,这样我们就需要生成新的实例来替换它们。

问题在于,预见会出现什么样的状况是很困难的,甚至是不可能实现的。仅仅构建可扩展的应用还不够,我们还需要它们能够动态扩展。每当出现高负载的时候,系统能够动态、快速且毫不费力地进行扩展。当高峰期结束的时候,它应该能够再次收缩回来。

如果你的业务需要快速、有效地适应新的客户,或者需要灵活支持新类型的客户端(这会增加服务器的工作负载),那么云的本质特点再结合云原生应用(按照定义,它就是可扩展的)能够为你提供所需的所有弹性。

1.5.4 节省成本

作为软件开发人员,我们可能不会直接和钱打交道,但是在设计解决方案的时候,我们有责任将成本考虑在内。凭借其弹性和按需付费的策略,云计算模型有助于优化IT基础设施的成本。我们不再有永远在线的基础设施:在需要的时候,我们会供应资源,并为实际使用付费;当不再需要的时候,我们就将其销毁。

在此基础之上,采用云原生方式会进一步优化成本。云原生应用被设计为可扩展的,所以它们可以充分利用云的弹性。它们是有韧性的,所以与停机时间和生产环境故障相关的成本都会更低。鉴于应用是松耦合的,它们能够让团队行动更快速,加快上市时间,从而具备明显的竞争优势。这样的例子不胜枚举。

迁移到云的隐性成本

在决定迁移到云之前,我们还必须考虑其他类型的成本。一方面,如上所述,我们可以优化成本,只为使用的资源付费。但另一方面,我们还应该考虑迁移的成本及其影响。

迁移到云需要特定的技术能力,而员工很可能还不具备这样的能力。这就意味着要对他们进行教育投资,以获得必要的技能,我们也许还需要聘请专业人员作为顾问,帮助我们往云中进行迁移。根据所选择的解决方案,组织可能还需要担负一些额外的责任,这又需要特定的技能(例如,处理云安全方面的问题)。除此之外,还有其他的一些考虑因素,比如迁移期间的业务中断、重新培训终端用户、更新文档和支持材料等。

1.6 云原生拓扑结构

我对云原生的阐述并不涉及特定的技术或架构。CNCF在其定义中提到了一些技术,比如容器和微服务,但是它们只是示例。要将应用变成云原生的,并不一定要使用Docker容器。比如,我们想一下Serverless或PaaS方案。为AWS Lambda平台编写的函数或部署到Heroku中的应用并不需要我们构建容器。但是,它们依然是云原生应用。

在本节中,我将会描述一些通用的云原生拓扑结构(参见图1.9)。首先,我将会介绍容器和编排的概念,当我们在后文讨论Docker和Kubernetes的时候,还会对它们进行详细介绍。随后,我将会介绍Serverless和函数(FaaS)技术。在本书中,我不会过多关注FaaS模型,但是会介绍如何使用Spring Native和Spring Cloud Function构建Serverless应用的基础知识。

图1.9 主要的云原生应用都基于容器(由编排器进行管理)和Serverless

1.6.1 容器

假设你加入了某个团队,并且要参与一个应用相关的工作。你所做的第一件事情就是按照指南搭建与其他同事类似的本地开发环境。你开发完了一个新的特性,并且在质量保证(Quality Assurance,QA)环境进行了测试。验证完成之后,应用就可以部署到staging环境进行额外的测试,并最终部署到生产环境。应用要在具有一定特征的环境中运行,我们在构建时也会考虑到这一点,所以上述所有环境尽可能相似是至关重要的。那么,如何实现这一点呢?这就是容器的用武之地了。

在容器出现之前,我们需要依赖虚拟机来保证环境的可重复性、隔离性和可配置性。虚拟化的原理是使用一个Hypervisor组件来抽象硬件,从而能够在同一台机器上以隔离的方式运行多个操作系统。Hypervisor直接运行在机器硬件(type 1)或宿主机操作系统(type 2)之上。

而操作系统容器是一个轻量级的可执行包,容器中包含了应用以及运行该应用所需的所有内容。容器间共享同一个内核,因此要添加新的隔离上下文时,没有必要启动完整的操作系统。在Linux上,这是通过Linux内核所提供的一些特性来实现的:

Namespace用来在进程之间划分资源,所以每个进程(或进程组)只能看到机器上可用资源的一个子集。

Cgroups用来控制和限制进程(或进程组)的资源使用。

注意 当仅使用虚拟化的时候,硬件是共享的,但是容器还会共享相同的操作系统内核。这两种情况都提供了隔离运行软件的计算环境,尽管隔离的程度有所不同。

图1.10展示了虚拟化和容器技术的差异。

图1.10 虚拟化和容器技术的差异在于隔离上下文之间所共享的内容。虚拟机只会共享硬件,
容器还会共享操作系统内核,后者更加轻量级和可移植

对于云原生应用,容器为什么这么流行呢?按照传统的方式,要使应用运行起来,我们需要在虚拟机上安装和维护Java运行时环境(JRE)以及中间件。相反,容器几乎可以在任何计算环境中可靠地运行,独立于应用及其依赖或中间件。应用是什么类型、使用哪种语言编写、使用了哪些库,都无关紧要。从外边来看,所有容器的外形都一样,就如同货运中的集装箱。

因此,容器实现了敏捷性、跨不同环境的可移植性以及部署的可重复性。鉴于其轻量级和较低的资源需求,它们非常适合在云中运行,因为云中的应用是用完即废弃的,需要能够动态和快速扩展。相比之下,建立和销毁虚拟机的成本要高得多,并且会更加耗时。

容器!无处不在的容器!

“容器”这个词在不同的语境中有不同的含义。有时候,这种模糊性可能会产生一些混乱,所以我们看一下在不同语境中它分别是什么意思。

操作系统:操作系统容器是一种在与系统其他部分隔离的环境中运行一个或多个进程的方法。在本书中,我们会主要关注Linux容器,但是需要注意Windows容器也是存在的。

Docker:Docker容器是Linux容器的一个实现。

OCI:OCI容器是由开放容器计划(Open Container Initiative,OCI)实现的Docker容器标准。

Spring:Spring容器指的是应用上下文,对象、属性和其他应用资源都会在这里被管理和执行。

Servlet:Servlet容器为使用Java Servlet API的Web应用提供了一个运行时。Tomcat服务器的Catalina组件就是Servlet容器的一个示例。

虚拟化和容器并不是互斥的。实际上,我们会在云原生环境中同时使用它们,也就是在虚拟机组成的基础设施上运行容器。IaaS(基础设施即服务)模型提供了一个虚拟层,我们可以使用它来引导新的虚拟机。在此基础上,我们可以直接安装容器运行时和运行容器。

一个应用通常是由不同的容器组成的,在开发阶段或执行一些早期的测试时,它们可以在同一台机器上运行。但是,我们很快就会遇到管理许多容器变得越来越复杂的问题,当我们需要复制它们以实现可扩展性以及跨不同的机器进行分布式部署时,这种问题就变得尤为突出。这时,我们就会开始依赖CaaS(容器即服务)模型所提供的更高层次的抽象,该模型提供了在机器集群中部署和管理容器的功能。需要注意的是,在幕后,它依然有一个虚拟化层。

即便在使用Heroku或Cloud Foundry这样的PaaS平台时,也会涉及容器。我们在这些平台上部署应用时,只需提供JAR制品,因为它们会负责处理JRE、中间件、操作系统和所需的依赖。不过,在幕后,它们会基于这些组件建立一个容器,并最终运行它。所以,区别在于,不再是我们负责建立容器,而是平台本身为我们实现这一点。一方面,这对开发人员来说是很方便的,可以减少相关的职责。但另一方面,我们放弃了对运行时和中间件的控制,并可能面临供应商锁定的问题。

在这本书中,我们将学习如何使用Cloud Native Buildpacks(它是一个CNCF项目)实现Spring应用的容器化,并使用Docker在本地环境中运行这些应用。

1.6.2 编排

你应该已经决定采用容器技术了,那就太好了!我们可以利用它们的可移植性,将其部署到任意提供容器运行时的基础设施中。我们还可以实现可重复性,所以把容器从开发环境转移至staging环境,再到生产环境时,我们不会遇到糟糕的意外情况。我们还可以对其进行快速扩展,因为它们是轻量级的,从而获取应用的高可用性。你是不是已经准备好在下一个云原生系统中就采用这项技术了呢?

在单台机器上供应和管理容器是非常简单的。但是,当我们开始处理几十或几百个容器,并在多台机器上进行扩展和部署时,我们就需要其他技术的辅助了。

当从虚拟服务器(IaaS模型)转换到容器集群(CaaS模型)时,我们也在转换自己的视角[17]。在IaaS中,我们关注单个计算节点,也就是虚拟机。在CaaS中,底层的基础设施已经被抽象了,我们所关注的是节点的集群。

[17] N. Kratzke, R. Peinl, “ClouNS—a Cloud-Native Application Reference Model for Enterprise Architects”, 2016 IEEE 20th International Enterprise Distributed Object Computing Workshop (EDOCW), 2016: 1–10。

伴随CaaS方案所提供的新视角,部署的目标不再是一台机器,而是一个机器集群。像Kubernetes这样的CaaS平台提供了很多特性来解决我们在云原生环境中所面临的所有重大问题,也就是跨集群编排容器。图1.11展示了这两种不同的拓扑结构。

图1.11 容器的部署目标是单台机器,而编排器的部署目标是一个集群

容器编排能够帮助我们实现很多任务的自动化:

管理集群,在必要的时候启动和关闭机器。

在集群中,将容器调度和部署到能够满足其CPU和内存需求的机器上。

利用健康监控,动态扩展容器,以实现高可用性和韧性。

为容器之间的通信搭建网络,定义路由、服务发现和负载均衡。

将服务暴露到互联网中,建立端口和网络。

根据特定的标准,为容器分配资源。

配置在容器中运行的应用。

确保安全,执行访问控制策略。

编排工具的指令是以声明式的方式实现的,例如,借助YAML文件。借助特定工具定义的格式和语言,我们通常会描述出想要达成的状态,比如我们想要在集群中部署Web应用容器的三个副本,并将它的服务暴露到互联网上。

容器编排的样例包括Kubernetes(它是一个CNCF项目)、Docker Swarm和Apache Mesos。在本书中,我们将学习如何使用Kubernetes来编排Spring应用的容器。

1.6.3 Serverless

继从虚拟机发展到容器之后,我们可以进一步抽象基础设施,这就是Serverless技术。借助该计算模型,开发人员只须关注应用的业务逻辑实现即可。

Serverless这个名字可能会有一定误导性:当然,服务器是存在的。不同的是,既不是我们在管理它,也不是由我们将应用部署编排到对应的服务器上。现在,这变成了平台的责任。当使用像Kubernetes这样的编排器时,我们依然需要考虑基础设施供应、容量规划和扩展的问题。而Serverless平台会负责搭建应用所需的底层基础设施,包括虚拟机、容器和动态扩展。

Serverless架构通常会与函数关联,但是它们包含两种经常一起使用的主要模型。

后端即服务(Backend as a Service,BaaS):在这种模型中,应用严重依赖于云供应商提供的第三方服务,比如数据库、认证服务和消息队列。它的关注点在于减少后端服务相关的开发和运维成本。开发人员可以只实现前端应用(比如单页应用或移动应用),而将大部分甚至全部的后端功能转移至BaaS供应商。例如,可以使用Okta来认证用户,使用Google Firebase来持久化数据,并使用Amazon API Gateway来发布和管理REST API。

函数即服务(Function as a Service,FaaS):在这种模型中,应用是无状态的,由事件触发并且完全由平台来管理。它的关注点在于减少编排和扩展应用相关的部署和运维成本。开发人员只须实现应用的业务逻辑,平台负责处理其他内容。Serverless应用并非必须要以函数的方式来实现并归类。目前主要有两种FaaS方案。第一种是特定供应商的FaaS平台,比如AWS Lambda、Azure Functions或Google Cloud Functions。另一种方案是选择基于开源项目的Serverless平台,它们可以运行在公有云或内建基础设施上,从而解决供应商锁定和缺乏控制的问题。这种项目的样例是Knative和Apache Open- Whisk。Knative在Kubernetes之上提供了一个Serverless运行时环境,我们会在第16章对其进行介绍。它被用来作为一些企业级Serverless平台的基础,包括VMware Tanzu Application Platform、RedHat OpenShift Serverless和Google Cloud Run。

Serverless应用一般是事件驱动的,仅在有事件(比如HTTP请求或消息)需要处理的时候才会运行。事件可以是外部的,也可以是由另一个函数生成的。例如,当一个消息添加到队列中时,某个函数可能会被触发,该函数处理事件,然后退出执行。

当没有任何要处理的事件时,Serverless平台就会关闭所有与该函数相关的资源,因此,我们可以真正为实际使用付费。在其他云原生拓扑结构中,如CaaS或PaaS,总会有一台服务器在7×24小时运行。与传统系统相比,它们提供了动态可扩展性的优势,以减少任意时刻资源供应的数量。不过,总会有一些资源在始终运行,这也是有成本的。而在Serverless模型中,只有在必要时才会供应资源。如果没有要处理的事件,所有的资源都会被关闭。这就是我们所说的伸缩至零(scaling to zero),这是Serverless平台提供的主要特性之一。

除了成本优化,Serverless技术还将一些额外的职责从应用转移到了平台中。这可能是一个优势,因为它能让开发人员完全专注于业务逻辑。但同样重要的是,我们必须要考虑控制权,以及如何处理供应商锁定的问题。

每个FaaS以及通用的Serverless平台都有自己的特性和API。一旦我们开始为某个特定的平台编写函数,就无法轻易地将它们转移到另一个平台上了,而对于容器,我们是能够实现这一点的。与其他方式相比,FaaS是以在控制权和可移植性方面的妥协换取职责方面的收益。这也是Knative得以迅速流行起来的原因,它构建在Kubernetes之上,这意味着我们可以很容易地在平台和供应商之间转移Serverless工作负载。归根到底,这就是一种权衡。

1.7 云原生应用的架构

在前面我们介绍了云原生的主要特点,这都是我们学习本书后面内容所需要用到的,现在我们到了定义云原生旅程的最后一站。在上一节中,我们熟悉了云原生的主要拓扑结构,尤其是容器,它将会是我们的计算单元。现在,我们看一下容器里面是什么,并探讨一些关于架构和设计云原生应用的高层次原则。图1.12展示了本节将要涵盖的主要概念。

图1.12 云原生架构元素

1.7.1 从多层架构到微服务和其他架构

IT基础设施一直在影响着软件应用的架构和设计方式。最初,我们曾将单体应用以单一组件的形式部署在庞大的大型机上。当互联网和PC流行起来之后,我们开始按照客户端/服务器的范式设计应用。依赖该范式的多层架构广泛用于桌面和Web应用中,该架构会将代码分解为展现层、业务层和数据层。

随着应用复杂性的增加以及敏捷性的需要,人们在探索进一步分解代码的新方式,一种新的架构风格应运而生,那就是微服务。在过去的几年中,这种架构风格变得越来越流行,许多公司决定按照这种风格重构它们的应用。微服务通常会拿来与单体应用进行对比,如图 1.13所示。

图1.13 单体应用与微服务。单体架构通常是多层的,微服务是由不同的组件组成的,
这些组件可以独立部署

两者之间的主要差异在于应用是如何分解的。单体应用会使用较大的三层,而基于微服务的应用则会使用众多组件,每个组件实现一部分功能。目前有很多关于如何将单体分解为微服务,并处理众多组件所带来的复杂性的模式。

注意 本书不是关于微服务的,因此,我不会讨论细节。如果你对这个话题感兴趣的话,可以参阅Sam Newman的Building Microservices(第2版)(O’Reilly, 2021年)以及Chris Richardson的Microservice Patterns(Manning, 2018年)。在Spring方面,可以参阅John Carnell和Illary Huaylupo Sanchez合著的Spring Microservices in Action(Manning, 2021年)。如果你不熟悉微服务的话也不要担心,要学习后面的内容,这些知识并不是必备的。

在经历了多年的热度和诸多失败的迁移之后,开发者社区围绕这种流行的架构风格展开了激烈的讨论。有些开发人员建议转向宏服务(macroservice),以减少组件的数量,从而降低管理的复杂性。“宏服务”这个术语是由Cindy Sridharan提出的,最初带有讽刺意味,但是它已经被行业所采用,Dropbox和Airbnb这样的公司已使用它来描述新的架构。[18]有些人则提议采用城堡式(citadel)架构风格,该风格由一个被微服务包围的中心化单体组成。还有人主张以模块化单体的形式重回单体应用架构。

[18] C. Sridharan, http://mng.bz/YG5N。

归根到底,我认为最重要的是选择一个能够支撑我们为客户和业务提供价值的架构。这是我们最初要开发应用的原因所在。每种架构风格都有其使用场景。没有所谓的“银弹”或者放之四海而皆准的方案。大多数与微服务相关的负面体验都是由其他问题导致的,比如糟糕的代码模块化或不匹配的组织结构。这不应该是单体和微服务之间的一场论战。

在本书中,我主要关注如何使用Spring构建云原生应用并将其以容器的形式部署到Kubernetes中。云原生应用是分布式系统,就像微服务一样。你会发现一些在微服务语境下讨论的话题,但它们实际上属于分布式系统领域,比如路由和服务发现。根据定义,云原生应用是松耦合的,这也是微服务的一个特点。

即便有一些相似之处,但很重要的一点是,我们需要明白云原生应用与微服务是不一样的。我们当然可以为云原生应用采用微服务风格。实际上,很多开发人员就是这么做的。但是,这并非必备条件。在本书中,我将会采用称为“基于服务”的架构风格。可能这并不是一个很响亮的名字,也不花哨,但对于我们的目的来讲,这已经足够了。我们会处理服务。它们的大小是随意的,可以根据不同的原则来封装逻辑。这并不重要,我们想要追求的是根据开发、组织和业务需求来设计服务。

1.7.2 基于服务架构的云原生应用

在本书中,我们将会按照基于服务的架构来设计和构建云原生应用。

我们的主要工作单元是服务,它能够以不同的方式与其他服务交互。按照Cornelia Davis在Cloud Native Patterns(Manning, 2019年)一书中所提出的划分方式,我们可以在这种架构中识别出两个元素,分别是服务和交互。

服务:一个能够向其他组件提供任意类型的服务的组件。

交互:为完成系统的需求,服务之间所产生的通信。

服务是一个非常通用的组件,它们可以是任何东西。根据是否存储任意类型的状态,我们可以将其区分为应用服务(无状态)和数据服务(有状态)。

图1.14展示了云原生架构的元素。用来管理图书馆库存的应用应该是应用服务。存储图书信息的PostgreSQL数据库应该是数据服务。

1.应用服务

应用服务是无状态的,负责实现各种类型的逻辑。它们不必像微服务那样遵循特定的规则,只需要具备我们在前文所述的所有云原生属性即可。

最重要的是,在设计每个服务的时候,我们要考虑到松耦合和高内聚。服务应该尽可能独立。分布式系统是很复杂的,所以在设计阶段要格外小心。增加服务的数量也会导致问题数量的增加。

图1.14 基于服务架构的云原生应用。主要的元素是服务(应用或数据),它们会以不同的方式进行交互

我们可能会开发和维护系统中大多数的应用服务,但也可能会使用云供应商提供的一些服务,如认证和支付服务。

2.数据服务

数据服务是有状态的,负责存储各种类型的状态。状态指的是关闭服务和启动新实例时,应该保存下来的所有内容。

它们可以是像PostgreSQL这样的关系型数据库,像Redis这样的键/值存储,或者像RabbitMQ这样的消息代理。我们可以自己管理这些服务。由于保存状态需要存储,所以这比管理云原生应用更具挑战性,但这能够获得对自己数据的更多控制权。另一个可选方案是使用云提供商提供的数据服务,它将负责管理所有与存储、韧性、可扩展性和性能相关的问题。在后一种方案中,我们可以使用很多专门为云构建的数据服务,如Amazon DynamoDB、Azure Cosmos DB和Google BigQuery。

云原生数据服务是一个非常有吸引力的话题,但在本书中,我们将主要处理应用。与数据有关的问题,如集群、复制、一致性或分布式事务,在本书中不会有太多的详细阐述。尽管我很想这样做,但这些话题应该有专门的图书来充分地介绍。

3.交互

云原生服务需要相互沟通以满足系统的需求。如何进行通信将影响系统的整体属性。例如,选择请求/响应模式(同步的 HTTP 调用)而不是基于事件的方法(通过 RabbitMQ 实现消息流)会为应用带来不同的韧性。在本书中,我们会使用不同类型的交互,并学习它们的差异,以及每种方式适合什么样的场景。

1.8 小结

云原生应用是高度分布式系统,专门为云环境设计,而且会在云中运行。

云是一种IT基础设施,以商品的形式提供计算、存储和网络资源。

在云中,用户只须为实际使用的资源付费。

云供应商以不同的抽象层次提供服务:基础设施(IaaS)、容器(CaaS)、平台(PaaS)、函数(FaaS)或软件(SaaS)。

云原生应用具有水平可扩展性、松耦合和高内聚性,并且对故障有韧性、可管理、可观测。

云原生开发得到了自动化、持续交付和DevOps的支持。

持续交付是一种综合的工程实践,可快速、可靠和安全地交付高质量的软件。

DevOps是一种文化,能够让不同的角色进行协作,共同交付业务价值。

现代企业采用云原生来生产软件,这些软件可以快速交付,能够根据需要动态扩展,并且在优化成本的同时保证始终可用、对故障有韧性。

可以将容器(比如Docker容器)作为计算单元来设计云原生系统。它们比虚拟机更加轻量级,并提供了可移植性、不变性和灵活性。

有专门的平台(比如Kubernetes)提供管理容器的服务,这样我们就不需要直接处理底层的问题。它们提供容器编排、集群管理、网络服务和调度功能。

在Serverless计算模型中,平台(比如Knative)负责管理服务器和底层基础设施,开发人员只关注业务逻辑。后端功能是按使用量付费的,以实现成本优化。

微服务架构可用于开发云原生应用,但这并不是必需的。

为了设计云原生应用,我们将使用基于服务的风格,其特点是服务以及服务间的交互。

云原生服务可以进一步分类为应用服务(无状态)和数据服务(有状态)。

相关图书

深入浅出Spring Boot 3.x
深入浅出Spring Boot 3.x
Spring实战(第6版)
Spring实战(第6版)
Java研发自测入门与进阶
Java研发自测入门与进阶
Spring核心技术和案例实战
Spring核心技术和案例实战
Java EE企业级应用开发实战(Spring Boot+Vue+Element)
Java EE企业级应用开发实战(Spring Boot+Vue+Element)
Spring Boot源码解读与原理分析
Spring Boot源码解读与原理分析

相关文章

相关课程