Istio-服务网格实现加速指南-全-

Istio 服务网格实现加速指南(全)

原文:annas-archive.org/md5/f6bc4444c11b1da2ce15155990ff807c

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Istio 是最广泛采用的 Service Mesh 技术之一。它用于管理应用程序网络,提供微服务的安全性和操作效率。本书逐层探讨 Istio,解释它如何用于管理应用程序网络、弹性、可观察性和安全性。通过各种实际操作示例,你将学习 Istio Service Mesh 的安装、架构及其各个组件。你将进行 Istio 的多集群安装,并集成部署在虚拟机上的遗留工作负载。你还将学习如何使用WebAssemblyWASM)扩展 Istio 数据平面,并了解 Envoy 及其作为 Istio 数据平面的作用。你将看到如何使用 OPA Gatekeeper 自动化 Istio 的最佳实践。你还将学习如何通过 Kiali、Prometheus、Grafana 和 Jaeger 观察和操作 Istio。你还将探索其他 Service Mesh 技术,如 Linkerd、Consul、Kuma 和 Gloo Mesh。全书通过轻量级应用程序构建的易于跟随的实践示例,帮助你集中精力实现和部署 Istio 到云端和生产环境,而无需处理复杂的演示应用程序。

阅读本书后,你将能够在应用程序之间执行可靠的零信任通信,解决应用程序网络挑战,并使用 Istio 在分布式应用程序中构建弹性。

本书适合的人群

本书适合有 Kubernetes 环境中使用微服务经验的软件开发人员、架构师和 DevOps 工程师,他们希望解决在微服务通信中出现的应用程序网络挑战。为了从本书中获得最大收益,你需要具备一定的云计算、微服务和 Kubernetes 的工作经验。

本书涵盖的内容

第一章介绍 Service Mesh,讲解了云计算、微服务架构和 Kubernetes 的基础知识。接着,它概述了为何需要 Service Mesh 及其所带来的价值。如果你没有在 Kubernetes、云计算和微服务架构中处理大规模部署架构的实际经验,那么本章将帮助你熟悉这些概念,并为理解后续章节中的更复杂主题打下良好的基础。

第二章Istio 入门,描述了为何 Istio 在众多可用的 Service Mesh 技术中会获得广泛的流行。该章节接着提供了安装和运行 Istio 的说明,并带你了解 Istio 的架构及其各个组件。安装完成后,你将启用 Istio 旁车注入,应用于与 Istio 安装包一起提供的示例应用程序中。章节详细介绍了在示例应用程序中启用前后的步骤,帮助你理解 Istio 的工作原理。

第三章理解 Istio 控制平面和数据平面,深入探讨了 Istio 的控制平面和数据平面。本章将帮助你理解 Istio 控制平面的工作原理,以便你能在生产环境中规划控制平面的安装。阅读完本章后,你将能够识别 Istio 控制平面的各个组成部分,包括 istiod,以及它们在 Istio 整体运作中的功能。本章还将让你熟悉 Envoy 及其架构,并学习如何将 Envoy 作为独立代理使用。

第四章管理应用程序流量,详细介绍了如何使用 Istio 管理应用程序流量。本章充满了实际操作示例,首先探索了使用 Kubernetes Ingress 资源管理 Ingress 流量,然后展示了如何使用 Istio Gateway 来实现这一点,同时确保通过 HTTPS 安全暴露 Ingress。本章还提供了金丝雀发布、流量镜像以及将流量路由到网格外部服务的示例。最后,我们将看到如何管理从网格流出的流量。

第五章管理应用程序弹性,详细介绍了如何利用 Istio 提高微服务的应用弹性。本章讨论了应用弹性的各个方面,包括故障注入、超时和重试、负载均衡、限流、断路器和异常流量检测,并说明了 Istio 如何应对这些问题。

第六章保障微服务通信安全,深入探讨了安全领域的高级话题。本章首先解释了 Istio 的安全架构,然后介绍了如何实现相互 TLS(双向 TLS)来确保服务之间以及与网格外部的下游客户端的通信安全。接着,本章将通过各种实践练习,带你创建自定义的身份验证和授权安全策略。

第七章服务网格可观察性,提供了关于为什么可观察性重要的见解,如何从 Istio 收集遥测信息,介绍了各种可用的度量指标以及如何通过 API 获取这些指标,并展示了如何为部署在网格中的应用程序启用分布式追踪。

第八章将 Istio 扩展到跨 Kubernetes 集群的部署,将带你了解如何使用 Istio 在多个 Kubernetes 集群之间提供无缝的应用程序连接。本章还介绍了多种 Istio 安装选项,以实现服务网格的高可用性和连续性。内容涵盖了 Istio 安装的高级话题,帮助你了解如何在多个网络上的主-远程配置、单一网络上的主-远程配置、不同网络上的多主配置以及单一网络上的多主配置中设置 Istio。

第九章扩展 Istio 数据平面,提供了多种扩展 Istio 数据平面的选项。本章节详细讨论了 EnvoyFilter 和 WebAssembly,并探讨了如何利用这些技术扩展 Istio 数据平面的功能,超出开箱即用的功能。

第十章为非 Kubernetes 工作负载部署 Istio 服务网格,介绍了为什么许多组织仍将大量工作负载部署在虚拟机上的背景。该章节随后介绍了混合架构的概念,即现代架构与遗留架构的结合,并展示了 Istio 如何帮助将这两种遗留和现代技术结合起来,并展示了如何将 Istio 扩展到 Kubernetes 之外,应用于虚拟机。

第十一章Istio 故障排除与操作,提供了在操作 Istio 时可能遇到的常见问题的详细信息,并讲解了如何区分和隔离这些问题与其他问题。该章节随后介绍了多种分析和排除运营和可靠性工程团队常见的 Day-2 问题的技术。该章节提供了多种最佳实践,用于部署和操作 Istio,并展示了如何使用 OPA Gatekeeper 自动化最佳实践的执行。

第十二章总结我们所学到的内容与下一步,帮助您通过实际部署和配置开源应用程序来复习本书所学内容,帮助您在将所学知识应用于实际应用中时增强信心。该章节还提供了各种资源,供您进一步探索,以提升在 Istio 方面的学习和专业知识。最后,本章介绍了 eBPF,这是一项有望对服务网格产生积极影响的先进技术。

附录 – 其他服务网格技术,介绍了其他正在获得流行度、认可和组织采纳的服务网格技术,包括 Linkerd、Gloo Mesh 和 Consul Connect。附录中提供的信息并不详尽,而是旨在让您熟悉 Istio 的替代方案,并帮助您形成对这些技术与 Istio 比较的看法。

为了充分利用本书

读者需要具备在基于 Kubernetes 的环境中使用和部署微服务的实践经验。读者需要熟悉使用 YAML 和 JSON,并执行 Kubernetes 的基本操作。由于本书大量使用了各种云服务提供商的服务,因此有一定的云平台使用经验会有所帮助。

书中涉及的软件/硬件 操作系统要求
至少配备四核处理器和 16GB 内存的工作站 macOS 或 Linux
访问 AWS、Azure 和 Google Cloud 订阅 不适用
Visual Studio Code 或类似工具 不适用
minikube,Terraform 不适用

如果你正在使用本书的数字版本,我们建议你自己输入代码,或者从本书的 GitHub 仓库获取代码(下节将提供链接)。这样做可以帮助你避免由于复制粘贴代码而可能出现的错误。

下载示例代码文件

你可以从 GitHub 上下载本书的示例代码文件,地址为 github.com/PacktPublishing/Bootstrap-Service-Mesh-Implementations-with-Istio。如果代码有更新,它将会在 GitHub 仓库中更新。

我们还提供了其他来自我们丰富书籍和视频目录的代码包,地址是 github.com/PacktPublishing/。欢迎查看!

下载彩色图像

我们还提供了一份包含本书中使用的截图和图表的 PDF 文件,你可以在此下载:packt.link/DW41O

使用的约定

本书中使用了一些文本约定。

文本中的代码:表示文本中的代码词、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 用户名。示例如下:“配置补丁应用于HTTP_FILTER,特别是应用于http_connection_manager网络过滤器的 HTTP 路由器过滤器。”

代码块的设置方式如下:

"filterChainMatch": {
                    "destinationPort": 80,
                    "transportProtocol": "raw_buffer"
                },

当我们希望你关注代码块的特定部分时,相关的行或项目会以粗体显示:

"filterChainMatch": {
                    "destinationPort": 80,
                    "transportProtocol": "raw_buffer"
                },

任何命令行输入或输出都按照如下方式编写:

% curl -H "Host:httpbin.org" http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/get

粗体:表示新术语、重要词汇或在屏幕上显示的单词。例如,菜单或对话框中的单词以粗体显示。示例如下:“云计算是一种公用事业型计算,商业模式类似于那些向我们家中提供液化石油气和电力等公用设施的企业。”

提示或重要注意事项

以这样的形式出现。

联系我们

我们始终欢迎读者的反馈。

一般反馈:如果你对本书的任何方面有疑问,请通过电子邮件联系我们,地址是 customercare@packtpub.com,并在邮件主题中注明书名。

勘误:虽然我们已尽最大努力确保内容的准确性,但错误难免发生。如果你发现本书中的错误,请向我们报告。请访问 www.packtpub.com/support/errata 并填写表单。

盗版:如果你在互联网上发现我们作品的任何非法复制形式,我们将非常感谢你提供位置地址或网站名称。请通过 copyright@packt.com 联系我们,并附上相关材料的链接。

如果您有兴趣成为作者:如果您在某个主题方面有专业知识,并且有兴趣撰写或为书籍做贡献,请访问authors.packtpub.com

分享您的想法

阅读完Bootstrapping Service Mesh Implementations with Istio后,我们非常希望听到您的想法!请点击这里直接进入亚马逊评论页面并分享您的反馈

)。

您的评价对我们和技术社区非常重要,将帮助我们确保提供卓越的内容质量。

下载本书的免费 PDF 副本

感谢购买本书!

您喜欢随时随地阅读,但无法随身携带纸质书籍吗?

您购买的电子书无法与您选择的设备兼容吗?

不用担心,现在每本 Packt 书籍都附带免费的无 DRM 版 PDF。

在任何地方、任何设备上随时阅读。可以搜索、复制并将您最喜欢的技术书籍中的代码直接粘贴到您的应用程序中。

福利还不止这些,您还可以每天在收件箱中获得独家折扣、时事通讯和精彩的免费内容。

按照以下简单步骤即可获得福利:

  1. 扫描二维码或访问以下链接

packt.link/free-ebook/9781803246819

  1. 提交您的购买凭证

  2. 就这样!我们将直接把您的免费 PDF 和其他福利发送到您的电子邮件。

第一部分:基础知识

在本书的这一部分,我们将介绍服务网格的基本概念、它为何需要以及哪些类型的应用程序需要它。您将理解 Istio 与其他服务网格实现的区别。此部分还将带您一步步配置和设置环境并安装 Istio,同时揭示 Istio 控制面和数据面,它们如何运作以及在服务网格中的角色。

本部分包含以下章节:

  • 第一章介绍服务网格

  • 第二章开始使用 Istio

  • 第三章理解 Istio 控制面和数据面

第一章:服务网格简介

服务网格是一个高级且复杂的话题。如果你有使用云、Kubernetes,及基于微服务架构开发和构建应用程序的经验,那么你会很容易理解服务网格带来的一些好处。在本章中,我们将熟悉并回顾一些关键概念,而不会深入讨论细节。我们将探讨在使用微服务架构构建的应用程序在云端或传统数据中心中部署和运行时遇到的问题。后续章节将重点讲解 Istio,因此花些时间阅读本章,对你后续的学习将大有裨益。

在本章中,我们将涵盖以下主要主题:

  • 云计算及其优势

  • 微服务架构

  • Kubernetes 及其对设计思维的影响

  • 服务网格简介

本章中的概念将帮助你建立对服务网格的理解,并说明它们为何必要。它还将指导你识别 IT 环境中一些迹象和症状,提示你需要实施服务网格。如果你没有在使用 Kubernetes、云和微服务架构的大规模部署中积累实践经验,那么本章将帮助你熟悉这些概念,为你后续理解更复杂的内容打下良好的基础。即使你已经熟悉这些概念,阅读本章也能帮助你刷新记忆和经验。

重新审视云计算

在本节中,我们将简要介绍什么是云计算,它带来了哪些好处,如何影响设计思维,以及软件开发流程。

云计算是一种类似于我们家中使用的公共事业服务(如液化气和电力)的计算方式。你不需要管理电力的生产、分配或运作。相反,你只需通过插入电源插座来有效、经济地使用设备,使用设备,并为你消费的电力付费。虽然这个例子非常简单,但作为类比,它仍然很有意义。云计算提供商通过互联网提供计算、存储、数据库以及其他众多服务,包括基础设施即服务IaaS)、平台即服务PaaS)和软件即服务SaaS)。

图 1.1 – 云计算选项

图 1.1 – 云计算选项

图 1.1 展示了最常用的云计算选项:

  • IaaS 提供了如网络等基础设施,以便将你的应用程序与组织内的其他系统连接,并且连接到你想要的所有其他资源。IaaS 让你可以访问计算基础设施来运行你的应用程序,类似于传统数据中心中的 虚拟机VMs)或裸机服务器。它还提供了存储用于主机数据的资源,支持你的应用程序运行和操作。一些最受欢迎的 IaaS 提供商包括 Amazon EC2、Azure 虚拟机、Google Compute Engine、阿里巴巴 E-HPC(在中国及大中华区非常受欢迎)和 VMware vCloud Air。

  • PaaS 是另一种服务,它为你提供了灵活性,让你可以专注于构建应用程序,而不必担心应用程序如何部署、监控等。PaaS 包含了你从 IaaS 中得到的一切,同时还包括用于部署应用程序的中间件、帮助你构建应用程序的开发工具、存储数据的数据库等。PaaS 对于采用微服务架构的公司尤其有益。在采用微服务架构时,你还需要构建一个支持微服务的底层基础设施。构建支持微服务架构所需的生态系统既昂贵又复杂。利用 PaaS 来部署微服务可以让微服务架构的采用变得更快、更容易。云服务提供商有许多流行的 PaaS 服务。我们将在这里使用 Amazon Elastic Kubernetes ServiceEKS)作为 PaaS 来部署我们将要通过 Istio 实践的示例应用程序。

  • SaaS 是另一种服务,它提供了一个完整的软件解决方案,作为一种服务供你使用。PaaS 和 SaaS 服务之间容易混淆,因此简单来说,你可以将 SaaS 看作是你无需编写或部署任何代码即可消费的服务。例如,你很可能正在使用像 Gmail 这样的邮件服务作为 SaaS。此外,许多组织也使用作为 SaaS 提供的生产力软件,流行的例子有 Microsoft Office 365 等服务。其他例子包括像 Salesforce 这样的客户关系管理(CRM)系统和 企业资源规划ERP)系统。Salesforce 还提供了一个 PaaS 服务,在这个平台上可以构建和部署 Salesforce 应用程序。Salesforce 为小型企业提供的 Salesforce Essentials、Sales Cloud、Marketing Cloud 和 Service Cloud 是 SaaS 服务,而 Salesforce Platform 是一个低代码服务,供用户构建 Salesforce 应用程序,它属于 PaaS 服务。其他流行的 SaaS 示例包括 Google Maps、Google Analytics、Zoom 和 Twilio。

云服务提供商还提供不同种类的云服务,拥有不同的商业模式、访问方式和目标受众。在这些服务中,最常见的有公共云、私有云、混合云和社区云:

  • 公共云是你最可能熟悉的云服务。这种服务可以通过互联网访问,任何人只要能够订阅,就可以使用,通常通过信用卡或类似的支付方式。

  • 私有云是一种可以通过互联网或限制性私有网络访问的云服务,通常面向一组受限的用户。私有云可以是组织为其 IT 用户提供 IaaS 或 PaaS 服务;也有服务提供商向组织提供私有云服务。私有云提供高度的安全性,广泛用于那些拥有高度敏感数据的组织。

  • 混合云指的是在一个环境中同时使用公共云和私有云。混合云通常用于同时使用多个云服务的场景——例如,一个组织同时使用 AWS 和 Azure,在这两个云平台之间进行应用程序部署和数据流转。混合云是一个很好的选择,当某些数据和应用程序因安全原因需要托管在私有云中时。相反,可能还有一些应用程序不需要驻留在私有云中,而可以从公共云的可扩展性和弹性功能中获益。与其局限于公共云或私有云,或者选择某一云服务提供商,你应该利用各种云服务提供商的优势,创建一个安全、韧性强、弹性十足且具有成本效益的 IT 环境。

  • 社区云是另一种面向一组组织和用户的云服务。一个好的例子是美国的 AWS GovCloud,它是为美国政府提供的社区云。这种云服务限制了使用者的范围——例如,AWS GovCloud 只能由美国政府部门和机构使用。

现在你已经理解了云计算的核心内容,接下来让我们在以下章节中看看它的一些关键优势。

云计算的优势

云计算使得组织能够轻松访问各种技术,而无需经历高额的前期投资去采购昂贵的硬件和软件。通过利用云计算,组织实现了敏捷性,因为它们可以通过获得高端计算能力和基础设施(例如负载均衡器、计算实例等)以及软件服务(例如机器学习、分析、消息传输基础设施、AI、数据库等)来加速创新,并且这些服务可以像插件一样集成,帮助构建软件应用程序。

例如,如果你正在构建一个软件应用程序,那么它很可能需要以下内容:

  • 负载均衡器

  • 数据库

  • 服务器用于运行和计算,服务器用于托管应用程序

  • 存储用于托管应用程序二进制文件、日志等

  • 用于异步通信的消息系统

你需要在本地数据中心采购、搭建并配置基础设施。尽管这项活动对于在生产环境中启动和运营应用程序至关重要,但它不会在你与竞争对手之间产生任何业务上的差异化。软件应用程序基础设施的高可用性和弹性是维持和生存于数字世界中的必要条件。要与竞争对手竞争并击败他们,你需要专注于客户体验,并不断为消费者提供价值。

在本地部署时,你需要考虑所有采购基础设施的前期成本,包括以下内容:

  • 网络设备和带宽

  • 负载均衡器

  • 防火墙

  • 服务器和存储

  • 机架空间

  • 运行应用程序所需的任何新软件

以上所有成本将会产生项目的资本支出CapEx)。你还需要考虑搭建成本,包括以下内容:

  • 网络、计算服务器和电缆

  • 虚拟化、操作系统和基础配置

  • 中间件的搭建,如应用服务器和 Web 服务器(如果使用容器化,则包括容器平台、数据库和消息中间件的搭建)

  • 日志记录、审计、报警和监控组件

以上所有活动将会产生项目的资本支出(CapEx),但可能会计入组织的运营****费用OpEx)。

除了前述的附加成本外,最重要的考虑因素是采购、搭建并使基础设施准备就绪所需的时间和人力资源。这将显著影响你将功能和服务推向市场的能力(这也被称为敏捷性市场时间)。

使用云计算时,这些成本可以通过按需付费模型进行采购。在需要计算和存储的地方,可以以 IaaS 的形式进行采购,而在需要中间件的地方,可以以 PaaS 的形式进行采购。你会发现,你需要构建的某些功能可能已经作为 SaaS 可用。这加快了软件交付和市场推向速度。在成本方面,某些成本仍将产生项目的资本支出(CapEx),但你的组织可以将其计入运营费用(OpEx),这在税务方面具有一定的优势。以前,部署应用程序所需的所有准备工作可能需要几个月的时间,而现在可以在几天或几周内完成。

云计算还改变了你设计、开发和运营 IT 系统的方式。在第四章中,我们将探讨云原生架构及其与传统架构的区别。

云计算使得构建和发布软件应用变得更加容易,并且前期投资较低。以下部分将描述微服务架构以及它如何用于构建和交付高度可扩展和具备弹性的应用程序。

理解微服务架构

在讨论微服务架构之前,我们先讨论一下单体架构。你很可能已经接触过或甚至参与过单体架构的构建。为了更好地理解它,我们通过一个场景来看一下它是如何通过单体架构传统地解决问题的。

假设有一家图书出版商想要开设一个在线书店。这个在线书店需要为读者提供以下功能:

  • 读者应该能够浏览所有可供购买的图书。

  • 读者应该能够选择自己想要购买的书籍,并将其保存到购物车中。他们还应该能够管理购物车。

  • 读者应该能够使用信用卡授权支付图书订单。

  • 读者应该能够在支付完成后,将书籍送到其配送地址。

  • 读者应该能够注册,存储包括配送地址在内的详细信息,并收藏喜欢的图书。

  • 读者应该能够登录,查看自己购买的图书,下载已购买的电子版,并更新配送信息及其他账户信息。

在线书店会有更多的需求,但为了理解单体架构,让我们将范围限制在这些需求上,尽量保持简洁。

值得一提的是康威定律,他指出,单体系统的设计往往反映了组织的沟通结构:

任何设计系统的组织(广义上定义)都会产生一个设计,其结构是组织沟通结构的复制。

— 梅尔文·E·康威

设计这个系统有很多种方式;我们可以遵循传统的设计模式,比如模型-视图-控制器MVC),但为了与微服务架构做公平的比较,我们将使用六边形架构。我们在微服务架构中也将使用六边形架构。

六边形架构的逻辑视图来看,业务逻辑位于中心。然后,有适配器来处理来自外部的请求以及向外部发送请求,这些适配器分别被称为内向适配器和外向适配器。业务逻辑有一个或多个端口,这些端口定义了一组操作,规定了适配器如何与业务逻辑交互,以及业务逻辑如何调用外部系统。外部系统与业务逻辑交互的端口被称为内向端口,而业务逻辑与外部系统交互的端口被称为外向端口。

我们可以通过以下两点来总结六边形架构中的执行流程:

  • 网站和移动端的用户界面与 REST API 适配器通过内向适配器调用业务逻辑。

  • 商业逻辑通过外向适配器调用面向外部的适配器,如数据库和外部系统。

关于六边形架构的最后一个但非常重要的点是,业务逻辑由模块组成,这些模块是领域对象的集合。要了解更多关于领域驱动设计的定义和模式,可以阅读 Eric Evans 撰写的参考指南,链接如下:domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf

回到我们的在线书店应用,以下是核心模块:

  • 订单管理:管理客户订单、购物车及订单进度更新

  • 客户管理:管理客户账户,包括注册、登录和订阅

  • 支付管理:管理支付

  • 产品目录:管理所有可用的产品

  • 配送管理:管理订单配送

  • 库存管理:管理最新的库存信息

考虑到这些,让我们绘制这个系统的六边形架构。

图 1.2 – 在线书店应用单体架构

图 1.2 – 在线书店应用单体架构

尽管架构遵循六边形架构和领域驱动设计的某些原则,但它仍然被打包成一个可部署或可执行单元,这取决于你所使用的底层编程语言。例如,如果你使用的是 Java,部署的制品将是一个 WAR 文件,然后部署到应用服务器上。

单体应用在绿地项目中看起来很棒,但当变成“棕地”项目时就变得非常糟糕,这时候需要更新或扩展,以融入新功能和变化。

单体架构难以理解、演化和增强,因为代码库庞大,随着时间推移,代码量和复杂度会变得非常巨大。这意味着代码更改需要很长时间,并且要将代码推送到生产环境也需要较长时间。代码更改成本高,并且需要彻底的回归测试。应用程序的扩展困难且成本高,而且没有办法为应用程序的各个组件分配专门的计算资源。所有资源都整体分配给整个应用程序,并被所有部分使用,而不管它们在执行中的重要性。

另一个问题是代码库对某一技术的锁定。基本上,这意味着你需要将自己限制在一种或少数几种技术上,以支持整个代码库。代码锁定对高效结果是不利的,包括性能、可靠性以及实现结果所需的努力。你应该使用最适合解决问题的技术。例如,你可以使用 TypeScript 来开发 UI,使用 Node.js 开发 API,使用 Golang 开发需要并发的模块,或者用它来编写核心模块,等等。使用单体架构时,你会被过去使用的技术所束缚,这些技术可能不适合当前的问题。

那么,微服务架构是如何解决这个问题的呢?微服务 是一个过载的术语,关于它有许多不同的定义;换句话说,微服务没有单一的定义。一些知名人物提出了他们自己对微服务架构的定义:

微服务架构这个术语在过去几年间出现,用来描述一种将软件应用程序设计为一组可以独立部署的服务的方式。虽然这种架构风格没有精确定义,但有一些共同的特点,比如围绕业务能力进行组织、自动化部署、端点智能化、语言和数据的去中心化控制等。

– 马丁·福勒(Martin Fowler)和詹姆斯·刘易斯(James Lewis)

该定义已发布在 martinfowler.com/articles/microservices.html 上,发布时间是 2014 年 3 月 25 日,因此你可以忽略描述中的“在过去几年间出现”,因为微服务架构已经成为主流并广泛应用。

另一种对微服务的定义来自亚当·科克罗夫特(Adam Cockcroft):“松耦合的面向服务架构,带有 界限上下文。”

在微服务架构中,micro 这个词是一个激烈讨论的话题,通常被问到的问题是:“微服务应该有多小?”或“我该如何分解我的应用程序?”。这个问题没有简单的答案;你可以通过遵循领域驱动设计,基于业务能力、功能、每个服务或模块的责任或关注点、可扩展性、界限上下文以及影响范围等多个维度来分解应用程序。有大量的文章和书籍讨论微服务及其分解策略,因此我相信你可以找到足够的资料来阅读,了解如何在微服务中确定应用程序的大小策略。

让我们回到在线书店应用程序,并使用微服务架构重新设计它。下图展示了使用微服务架构原则构建的在线书店应用程序。各个服务仍然遵循六边形架构,为了简洁起见,我们没有展示入站和出站端口及适配器。你可以假设端口、适配器和容器都在六边形内部。

图 1.3 – 在线书店微服务架构

图 1.3 – 在线书店微服务架构

微服务架构相比单体架构提供了几个好处。将基于功能独立划分并解耦的模块,可以解锁单体架构所带来的束缚,推动软件开发过程的进步。与单体架构相比,微服务可以更快地构建,成本也较低,并且非常适合于持续部署流程,因此具有更快的生产上线时间。采用微服务架构后,开发人员可以根据需要频繁地将代码发布到生产环境中。微服务的小型代码库容易理解,开发人员只需理解微服务本身,而不需要理解整个应用程序。此外,多个开发人员可以在应用程序中的微服务上协作工作,而不必担心代码被覆盖或互相影响。你的应用程序现在由微服务构成,可以利用多语言编程技术提高性能效率,减少付出而获得更多成果,并使用最优技术来解决问题。

作为自包含的独立可部署单元,微服务提供了故障隔离和较小的爆炸半径——例如,假设某个微服务开始出现异常、性能下降、内存泄漏等问题。在这种情况下,由于该服务作为一个自包含的单元并具有自己的资源分配,这个问题不会影响其他微服务。其他微服务不会受到内存、CPU、存储、网络和 I/O 过度消耗的影响。

微服务的部署也更为简单,因为你可以根据微服务的需求以及可用的资源使用不同的部署选项——例如,你可以将一组微服务部署在无服务器平台上,同时将另一组部署在容器平台上,再将另一组部署在虚拟机上。与单体应用不同,你不受限于一种部署选项。

虽然微服务提供了许多好处,但它们也带来了额外的复杂性。这种复杂性是因为现在你需要部署和管理的东西太多了。不遵循正确的分解策略也可能导致微型单体架构的产生,这将使管理和操作变得极其困难。另一个重要的方面是微服务之间的通信。由于需要大量微服务相互交流,因此微服务之间的通信必须快速、高效、可靠、具备弹性且安全。在了解服务网格部分中,我们将深入探讨这些术语的具体含义。

现在,掌握了微服务架构的基本概念后,是时候了解 Kubernetes 了,Kubernetes 也是部署微服务的事实标准平台。

理解 Kubernetes

在设计和部署微服务时,管理少量微服务相对简单。但随着微服务数量的增加,管理的复杂性也会增加。以下列表展示了采用微服务架构可能带来的一些复杂性:

  • 微服务在部署时会有特定的要求,包括基础操作系统、中间件、数据库以及计算/内存/存储资源的选择。此外,微服务的数量会非常庞大,这意味着你需要为每个微服务提供资源。而且,为了降低成本,你需要在资源分配和利用方面保持高效。

  • 每个微服务的部署频率都会不同。例如,支付微服务的更新可能是每月一次,而前端 UI 微服务的更新则可能是每周或每天一次。

  • 微服务需要相互通信,因此它们需要了解彼此的存在,并且应该具备应用程序网络功能以高效地进行通信。

  • 构建微服务的开发人员需要为开发生命周期的各个阶段提供一致的环境,这样在生产环境中部署微服务时,就不会出现未知或接近未知的行为。

  • 应该建立一个持续部署流程来构建和部署微服务。如果没有自动化的持续部署流程,那么你将需要一支庞大的团队来支持微服务的部署。

  • 随着部署的微服务数量增多,故障是不可避免的,但你不能把这些问题的解决责任都压在微服务开发人员身上。跨领域问题,如弹性、部署协调和应用程序网络功能,应该易于实现,并且不应分散微服务开发人员的注意力。这些跨领域问题应该由底层平台来处理,而不是嵌入到微服务代码中。

Kubernetes,也简称为 K8S,是一个源自 Google 的开源系统。Kubernetes 提供了自动化的部署、扩展和容器化应用的管理。它在不需要雇佣大量 DevOps 工程师的情况下,提供了可扩展性。它适应各种复杂情况——也就是说,无论是小规模还是企业级规模都能运行。Google 以及许多其他组织都在 Kubernetes 平台上运行着大量容器。

重要提示

容器 是一个自包含的部署单元,包含所有代码和相关的依赖项,包括操作系统、系统和应用程序库,打包在一起。容器是从镜像实例化出来的,镜像是轻量级的可执行包。Pod 是 Kubernetes 中的可部署单元,由一个或多个容器组成,每个容器共享资源,如存储和网络。Pod 的内容总是共同定位和共同调度,并在共享的上下文中运行。

以下是 Kubernetes 平台的一些优点:

  • Kubernetes 通过处理发布和回滚,实现自动化和可靠的部署。在部署过程中,Kubernetes 会逐步推出变更,并监控微服务的健康状况,以确保请求的处理不会中断。如果微服务的整体健康状况存在风险,Kubernetes 会回滚变更,恢复微服务的健康状态。

  • 如果你正在使用云服务,那么不同的云服务提供商有不同的存储类型。在数据中心运行时,你将使用各种网络存储类型。使用 Kubernetes 时,你无需担心底层存储,因为它会处理这些问题。Kubernetes 抽象了底层存储类型的复杂性,并为开发人员提供了一个基于 API 的机制,用于将存储分配给容器。

  • Kubernetes 负责 Pod 的 DNS 和 IP 分配;它还为微服务提供了一种机制,使其能够通过简单的 DNS 约定相互发现。当多个服务副本运行时,Kubernetes 还会负责它们之间的负载均衡。

  • Kubernetes 自动处理 Pod 的扩展需求。根据资源利用情况,Pod 会自动扩展,这意味着运行的 Pod 数量会增加,或者会缩减,这意味着运行的 Pod 数量会减少。开发人员无需担心如何实现扩展。他们只需要关注 CPU、内存和其他各种自定义指标的平均利用率以及扩展限制。

  • 在分布式系统中,故障是不可避免的。同样,在微服务部署中,Pods 和容器会变得不健康或无响应。Kubernetes 通过重启失败的容器、将容器重新调度到其他工作节点(如果底层节点出现问题),以及替换变得不健康的容器来处理这些情况。

  • 如前所述,微服务架构的资源消耗是其挑战之一,资源应该高效且有效地分配。Kubernetes 通过最大化资源分配,确保不影响可用性或牺牲容器的性能,承担了这一责任。

图 1.4 – 部署在 Kubernetes 上的在线书店微服务

图 1.4 – 部署在 Kubernetes 上的在线书店微服务

上图是通过微服务架构构建并部署在 Kubernetes 上的在线书店应用程序的可视化图示。

了解服务网格

在上一节中,我们阅读了关于单体架构的优缺点。我们还了解了微服务如何解决可扩展性问题,并提供灵活性,使软件更改能够快速部署到生产环境中。云平台使组织能够专注于创新,而无需担心昂贵且漫长的硬件采购过程和高昂的资本支出(CapEx)成本。云平台不仅通过提供按需基础设施促进微服务架构的实施,还通过提供各种现成可用的平台和构建模块(如 PaaS 和 SaaS)来加速这一过程。在组织构建应用程序时,他们不需要每次都从头开始,而是可以利用现成的数据库、各种平台(包括 Kubernetes)以及中间件即服务MWaaS)。

除了云平台,微服务开发者还利用容器,这使得微服务开发变得更加容易,因为容器提供了统一的环境和隔离机制,有助于实现微服务的模块化和自包含架构。在容器之上,开发者还应使用容器编排平台,如 Kubernetes,它简化了容器的管理,并处理网络、资源分配、可扩展性、可靠性和弹性等问题。Kubernetes 还通过提供更好的底层硬件利用率来帮助优化基础设施成本。当你将云、Kubernetes 和微服务架构结合在一起时,你就拥有了交付强大软件应用所需的所有要素,这些应用不仅能完成你希望它们完成的任务,还能以更具成本效益的方式做到这一点。

所以,你心里一定有个问题:“我为什么需要 Service Mesh?”或者“如果我使用云、Kubernetes 和微服务,为什么还需要 Service Mesh?”这是一个很好的问题,值得思考,一旦你到了一个阶段,能够自信地在 Kubernetes 上部署微服务,你会遇到一个拐点,那时微服务之间的网络复杂度已经超出了 Kubernetes 原生特性所能解决的范围。

分布式计算的谬误

分布式系统的谬误是由 L Peter Deutsch 及其他人于 Sun Microsystems 提出的八个假设。这些假设是软件开发者在设计分布式应用时常犯的错误。假设包括:网络是可靠的、延迟为零、带宽是无限的、网络是安全的、拓扑结构不变、只有一个管理员、传输成本为零、网络是同质的。

理解 Kubernetes部分的开始,我们探讨了开发者在实施微服务架构时面临的挑战。Kubernetes 提供了各种功能,用于部署容器化微服务以及通过声明式配置进行容器/Pod 生命周期管理,但它在解决微服务之间的通信挑战方面存在不足。当我们谈到微服务的挑战时,使用了诸如应用网络等术语来描述通信挑战。那么,首先让我们理解一下什么是应用网络,以及它为什么对微服务的成功运作如此重要。

应用网络也是一个宽泛的术语,具体的解释会根据上下文的不同而有所不同。在微服务的背景下,我们将应用网络定义为支持微服务之间分布式通信的工具。微服务可以部署在一个 Kubernetes 集群中,也可以跨多个集群,在任何类型的底层基础设施上运行。微服务还可以部署在云端、本地或两者兼备的非 Kubernetes 环境中。目前,我们将重点放在 Kubernetes 及其内部的应用网络上。

无论微服务部署在哪里,都需要一个强大的应用网络来让微服务之间进行通信。底层平台不仅要促进通信,还要确保通信的韧性。所谓的韧性通信,指的是在其周围生态系统处于不利条件下,依然能成功通信的高概率。

除了应用程序网络,你还需要了解微服务之间通信的可见性;这也叫做可观察性。可观察性在微服务通信中非常重要,因为它可以帮助我们了解微服务如何互相交互。微服务之间的通信还需要安全保障。通信应该是加密的,并且防止中间人攻击。每个微服务应该有自己的身份,并且能够证明它有权与其他微服务进行通信。

那么,为什么要使用服务网格?为什么这些需求不能在 Kubernetes 中解决?答案在于 Kubernetes 的架构以及它被设计的目的。正如前面提到的,Kubernetes 是应用程序生命周期管理软件。它提供应用程序网络、可观察性和安全性,但这些功能仅仅处于一个非常基础的层面,不足以满足现代动态微服务架构的要求。这并不意味着 Kubernetes 不是现代化的软件。事实上,它是一项非常复杂且前沿的技术,但仅限于容器编排的服务。

Kubernetes 中的流量管理由 Kubernetes 网络代理处理,也叫 kube-proxy。kube-proxy 在 Kubernetes 集群的每个节点上运行。kube-proxy 与 Kubernetes API 服务器进行通信,并获取关于 Kubernetes 服务的信息。Kubernetes 服务是另一层抽象,用于将一组 Pod 暴露为网络服务。kube-proxy 为服务实现了一种虚拟 IP 形式,并设置 iptables 规则,定义了该服务的所有流量如何路由到端点,这些端点本质上是托管应用程序的底层 Pods。

为了更好地理解,我们来看一下下面的例子。要运行这个例子,你需要在计算设备上安装minikubekubectl。如果你还没有安装这些软件,建议先不要安装,因为我们将在第二章中详细介绍安装步骤。

我们将通过minikube.sigs.k8s.io/docs/start/上的示例创建一个 Kubernetes 部署和服务:

$ kubectl create deployment hello-minikube --image=k8s.gcr.io/echoserver:1.4
deployment.apps/hello-minikube created

我们刚刚创建了一个名为hello-minikube的部署对象。让我们执行kubectldescribe命令:

$ kubectl describe deployment/hello-minikube
Name:                   hello-minikube
…….
Selector:               app=hello-minikube
…….
Pod Template:
  Labels:  app=hello-minikube
  Containers:
   echoserver:
    Image:        k8s.gcr.io/echoserver:1.4
    ..

从上面的代码块中,你可以看到一个 Pod 已经创建,包含了一个从k8s.gcr.io/echoserver:1.4镜像实例化的容器。现在让我们检查一下 Pods:

$ kubectl get po
hello-minikube-6ddfcc9757-lq66b   1/1     Running   0          7m45s

上面的输出确认了 Pod 已经创建。现在,让我们创建一个服务并暴露它,使其可以通过静态端口上的集群内部 IP 访问,也叫NodePort

$ kubectl expose deployment hello-minikube --type=NodePort --port=8080
service/hello-minikube exposed

让我们描述一下这个服务:

$ kubectl describe services/hello-minikube
Name:                     hello-minikube
Namespace:                default
Labels:                   app=hello-minikube
Annotations:              <none>
Selector:                 app=hello-minikube
Type:                     NodePort
IP:                       10.97.95.146
Port:                     <unset>  8080/TCP
TargetPort:               8080/TCP
NodePort:                 <unset>  31286/TCP
Endpoints:                172.17.0.5:8080
Session Affinity:         None
External Traffic Policy:  Cluster

从之前的输出中,你可以看到一个名为hello-minikube的 Kubernetes 服务已创建,并且可以通过端口31286访问,也称为NodePort。我们还看到有一个Endpoints对象,值为172.17.0.5:8080。接下来,我们将看到NodePortEndpoints之间的连接。

让我们深入探讨一下 iptables 发生了什么。如果你想查看前面的服务返回了什么,你可以直接输入minikube service。我们正在使用 macOS,minikube 本身作为虚拟机运行。我们需要通过ssh连接到 minikube,查看 iptables 的情况。在 Unix 主机上,这些步骤是不需要的:

$ minikube ssh

让我们检查一下 iptables:

$ sudo iptables -L KUBE-NODEPORTS -t nat
Chain KUBE-NODEPORTS (1 references)
target     prot opt source               destination
KUBE-MARK-MASQ  tcp  --  anywhere             anywhere             /* default/hello-minikube */ tcp dpt:31286
KUBE-SVC-MFJHED5Y2WHWJ6HX   tcp  --  anywhere             anywhere             /* default/hello-minikube */ tcp dpt:31286

我们可以看到与hello-minikube服务关联的两个 iptables 规则。让我们进一步查看这些 iptables 规则:

$ sudo iptables -L KUBE-MARK-MASQ -t nat
Chain KUBE-MARK-MASQ (23 references)
target     prot opt source               destination
MARK       all  --  anywhere             anywhere             MARK or 0x4000
$ sudo iptables -L KUBE-SVC-MFJHED5Y2WHWJ6HX -t nat
Chain KUBE-SVC-MFJHED5Y2WHWJ6HX (2 references)
target     prot opt source               destination
KUBE-SEP-EVPNTXRIBDBX2HJK   all  --  anywhere             anywhere             /* default/hello-minikube */

第一个规则KUBE-MARK-MASQ只是为所有目标端口31286的流量添加了一个名为packet mark的属性,值为0x400

第二条规则KUBE-SVC-MFJHED5Y2WHWJ6HX正在将流量路由到另一个规则KUBE-SEP-EVPNTXRIBDBX2HJK。让我们进一步了解它:

$ sudo iptables -L KUBE-SEP-EVPNTXRIBDBX2HJK -t nat
Chain KUBE-SEP-EVPNTXRIBDBX2HJK (1 references)
target     prot opt source               destination
KUBE-MARK-MASQ  all  --  172.17.0.5           anywhere             /* default/hello-minikube */
DNAT       tcp  --  anywhere             anywhere             /* default/hello-minikube */ tcp to:172.17.0.5:8080

请注意,这条规则有一个172.17.0.5:8080,这是我们创建服务时端点的地址。

让我们扩展 Pod 副本的数量:

$ kubectl scale deployment/hello-minikube --replicas=2
deployment.apps/hello-minikube scaled

描述服务以查找任何变化:

$ kubectl describe services/hello-minikube
Name:                     hello-minikube
Namespace:                default
Labels:                   app=hello-minikube
Annotations:              <none>
Selector:                 app=hello-minikube
Type:                     NodePort
IP:                       10.97.95.146
Port:                     <unset>  8080/TCP
TargetPort:               8080/TCP
NodePort:                 <unset>  31286/TCP
Endpoints:                172.17.0.5:8080,172.17.0.7:8080
Session Affinity:         None
External Traffic Policy:  Cluster

请注意,端点的值已发生变化;让我们也描述一下hello-minikube端点:

$ kubectl describe endpoints/hello-minikube
Name:         hello-minikube
…
Subsets:
  Addresses:          172.17.0.5,172.17.0.7
  NotReadyAddresses:  <none>
  Ports:
    Name     Port  Protocol
    ----     ----  --------
    <unset>  8080  TCP

请注意,端点现在也同时指向172.17.0.7172.17.0.5172.17.0.7是通过将副本数量增加到2后创建的新 Pod。

图 1.5 – 服务、端点和 Pods

图 1.5 – 服务、端点和 Pods

现在让我们检查一下 iptables 规则:

$ sudo iptables -t nat -L KUBE-SVC-MFJHED5Y2WHWJ6HX
Chain KUBE-SVC-MFJHED5Y2WHWJ6HX (2 references)
target     prot opt source               destination
KUBE-SEP-EVPNTXRIBDBX2HJK  all  --  anywhere              anywhere             /* default/hello-minikube */ statistic mode random probability 0.50000000000
KUBE-SEP-NXPGMUBGGTRFLABG  all  --  anywhere              anywhere             /* default/hello-minikube */

你会发现一个额外的规则KUBE-SEP-NXPGMUBGGTRFLABG已被添加,并且由于统计模式的随机概率0.5,每个由KUBE-SVC-MFJHED5Y2WHWJ6HX处理的数据包在KUBE-SEP-EVPNTXRIBDBX2HJKKUBE-SEP-NXPGMUBGGTRFLABG之间进行 50/50 的分配。

让我们快速检查一下在将副本数量更改为2后添加的新链:

$ sudo iptables -t nat -L KUBE-SEP-NXPGMUBGGTRFLABG
Chain KUBE-SEP-NXPGMUBGGTRFLABG (1 references)
target     prot opt source               destination
KUBE-MARK-MASQ  all  --  172.17.0.7           anywhere             /* default/hello-minikube */
DNAT       tcp  --  anywhere             anywhere             /* default/hello-minikube */ tcp to:172.17.0.7:8080

请注意,为172.17.0.7添加了另一个DNAT条目。所以,本质上,新的链和之前的链现在都在将流量路由到相应的 Pods。

所以,如果我们总结一下,kube-proxy 在每个 Kubernetes 节点上运行,并监视服务和端点资源。根据服务和端点的配置,kube-proxy 会创建 iptables 规则,负责在消费者/客户端和 Pod 之间路由数据包。

下图展示了通过 kube-proxy 创建 iptables 规则以及消费者如何连接到 Pods。

图 1.6 – 客户端基于 iptables 规则链连接到 Pod

图 1.6 – 客户端基于 iptables 规则链连接到 Pod

kube-proxy 还可以运行在另一种模式下,称为 IP 虚拟服务器IPVS)。为了方便参考,以下是官方 Kubernetes 网站上对此术语的定义:

在 IPVS 模式下,kube-proxy 监视 Kubernetes 服务和端点调用网络接口,按需创建 IPVS 规则,并定期与 Kubernetes 服务和端点同步 IPVS 规则。这个控制循环确保 IPVS 状态与期望状态匹配。当访问某个服务时,IPVS 会将流量引导到某个后端 Pod。

提示

要查找 kube-proxy 正在运行的模式,您可以使用 $ curl localhost:10249/proxyMode。在 Linux 上,您可以直接使用 curl,但在 macOS 上,您需要从 minikube 虚拟机本身进行 curl 操作。

那么,kube-proxy 使用 iptables 或 IPVS 有什么问题呢?

kube-proxy 不提供任何精细化配置;所有设置都应用于该节点上的所有流量。kube-proxy 只能进行简单的 TCP、UDP 和 SCTP 流转发,或在一组后端之间进行轮询 TCP、UDP 和 SCTP 转发。随着 Kubernetes 服务数量的增加,iptables 中的规则集数量也会增加。由于 iptables 规则是顺序处理的,因此随着微服务数量的增长,会导致性能下降。此外,iptables 只支持使用简单的概率来支持流量分配,这非常基础。Kubernetes 提供了一些其他技巧,但不足以支持微服务之间的弹性通信。为了实现微服务通信的弹性,您需要比基于 iptables 的流量管理更多的东西。

现在,让我们讨论一下实现弹性、容错通信所需的一些能力。

重试机制、断路器、超时和截止时间

如果一个 Pod 无法正常工作,则流量应自动发送到另一个 Pod。此外,重试需要在约束条件下进行,以避免使通信变得更糟。例如,如果调用失败,则系统可能需要等待一段时间后再进行重试。如果重试仍未成功,或许可以增加等待时间。即使这样,如果仍然失败,可能值得放弃重试并为后续连接断开电路。

断路器是通常涉及电路断路器的一种机制。当系统出现故障而不安全运行时,电路断路器会自动跳闸。类似地,考虑微服务通信,其中一个服务调用另一个服务,被调用的服务未响应,响应过慢以至于对调用服务有害,或者这种行为的发生已达到预定义的阈值。在这种情况下,最好是跳闸(停止)电路(通信),这样当下游服务(调用者)调用底层服务(上游)时,通信会立即失败。停止下游系统调用上游系统的原因是防止网络带宽、线程、IO、CPU 和内存等资源在极有可能失败的活动上浪费。断路器并不解决通信问题,而是阻止其跳出边界并影响其他系统。超时在微服务通信中也很重要,以便下游服务等待上游系统的响应,响应的有效时间段内等待。截止时间进一步建立在超时基础上;您可以将其视为整个请求的超时,而不仅仅是一个连接的超时。通过指定截止时间,下游系统告知上游系统关于处理请求的整体最大允许时间,包括对处理请求涉及的其他上游微服务的后续调用。

重要说明

在微服务架构中,下游系统是依赖于上游系统的系统。如果服务 A 调用服务 B,那么服务 A 将被称为下游,而服务 B 将被称为上游。在绘制北-南架构图以展示 A 和 B 之间的数据流时,通常会将 A 画在顶部,并向下指向 B,这样称 A 为下游、B 为上游会令人困惑。为了便于记忆,可以类比下游系统依赖于上游系统。这样,微服务 A 依赖于微服务 B;因此,A 是下游,B 是上游。

蓝/绿和金丝雀部署

蓝/绿部署是您希望将新版本(绿色)服务与先前/现有(蓝色)服务并行部署的情形。您进行稳定性检查以确保绿色环境能够处理实时流量,如果可以,则将流量从蓝色环境转移到绿色环境。

蓝色和绿色可以是集群中服务的不同版本,或者是独立集群中的服务。如果绿色环境出现问题,你可以将流量切换回蓝色环境。蓝色到绿色的流量迁移也可以逐步进行(金丝雀部署),有多种方式——例如,在前 10 分钟按 90:10 的比例,接下来的 10 分钟按 70:30,接下来的 20 分钟按 50:50,之后按 0:100 的比例迁移。另一个例子是将上述例子应用于某些特定流量,例如将之前的迁移比例应用于带有特定 HTTP 头值的所有流量——也就是某一类流量。在蓝绿部署中,你是并排部署相同的服务,而在金丝雀部署中,你可以只部署绿色部署中的一部分。这些特性在 Kubernetes 中难以实现,因为它不支持细粒度的流量分配。

下图展示了蓝绿部署和金丝雀部署。

图 1.7 – 蓝绿部署

图 1.7 – 蓝绿部署

为了处理像蓝绿部署和金丝雀部署这样的需求,我们需要一个能够处理第 7 层流量而不是第 4 层流量的工具。像 Netflix 开源软件OSS)等框架以及其他一些框架可以解决分布式系统通信中的挑战,但在此过程中,它们将解决应用网络问题的责任转移给了微服务开发者。在应用代码中解决这些问题不仅成本高、耗时,而且不利于最终目标的实现——交付业务成果。像 Netflix OSS 这样的框架和库是用某些特定编程语言编写的,这会限制开发者只能使用与之兼容的语言来构建微服务。这些框架限制了开发者使用特定框架支持的技术和编程语言,违背了多语言编程的概念。

所需的是一种可以与应用程序一起工作的代理,而无需应用程序了解代理本身。代理不仅仅是传递通信,还应具备对进行通信的服务及其上下文的深刻了解。这样,应用程序/服务可以专注于业务逻辑,而让代理处理所有与其他服务通信相关的事务。ss 就是这样的一个在第七层工作的代理,旨在与微服务一起运行。当它这样做时,它与其他在各自微服务旁边运行的 Envoy 代理形成了一个透明的通信网格。微服务仅与 Envoy 作为本地主机进行通信,Envoy 负责与其余网格的通信。在这种通信模型中,微服务无需了解网络。Envoy 可扩展,因为它具有一个可插拔的过滤器链机制,支持第 3、4 和 7 层的网络,可以根据需要添加新的过滤器,执行各种功能,如 TLS 客户端证书认证和流量限制。

那么,服务网格是如何与 Envoy 相关的呢?服务网格是一种负责应用程序网络的基础设施。下图描述了服务网格控制平面、Kubernetes API 服务器、服务网格 Sidecar 和 Pod 中其他容器之间的关系。

图 1.6 – 服务网格 Sidecar、数据和平面控制

图 1.6 – 服务网格 Sidecar、数据和平面控制

服务网格提供了一个数据平面,基本上是由应用感知的代理(如 Envoy)组成的集合,这些代理由一组名为控制平面(control plane)的组件进行管理。在基于 Kubernetes 的环境中,服务代理被作为 Sidecar 插入到 Pods 中,而无需对 Pod 内现有的容器进行任何修改。服务网格也可以添加到 Kubernetes 和传统环境(如虚拟机)中。一旦被添加到运行时生态系统中,服务网格就会处理我们之前讨论的与应用程序网络相关的问题,如负载均衡、超时、重试、金丝雀发布和蓝绿部署、安全性以及可观察性。

小结

在本章中,我们从单体架构开始,讨论了它在扩展新功能以及上市时间上的拖累。单体架构是脆弱的,且改变起来成本高昂。我们阅读了微服务架构如何打破这种惯性,并提供了所需的动力,以满足数字消费者日益变化且永无止境的需求。我们还看到微服务架构是模块化的,每个模块都是自包含的,并且可以独立构建和部署。使用微服务架构构建的应用程序利用了最适合解决单一问题的最佳技术。

然后我们讨论了云计算和 Kubernetes。云计算提供了一种按需付费的公用事业式计算模式。常见的云服务包括 IaaS、PaaS 和 SaaS。云计算为你提供了所需的所有基础设施,无需担心昂贵的硬件采购、数据中心成本等问题。云计算还提供了软件构建模块,帮助你缩短软件开发周期。在微服务架构中,容器是封装应用程序代码的方式。它们提供了一致的环境和服务之间的隔离,解决了邻居噪音问题。

另一方面,Kubernetes 通过提供容器生命周期管理并解决生产环境中运行容器的许多挑战,使得容器的使用变得更加简便。随着微服务数量的增加,你会开始面临微服务之间的流量管理挑战。Kubernetes 确实提供了基于 kube-proxy 和 iptables 规则的流量管理,但它未能提供应用程序网络功能。

最后我们讨论了服务网格,它是 Kubernetes 之上的基础设施层,负责应用程序网络。它的工作方式是通过提供数据平面,数据平面本质上是一个由应用感知的服务代理(如 Envoy)组成的集合,这些代理由一组被称为控制平面的组件来管理。

在下一章,我们将阅读 Istio,这是最流行的服务网格实现之一。

第二章:开始使用 Istio

在上一章中,我们讨论了单体架构及其缺点。我们还讨论了微服务架构及其如何为大型复杂应用程序提供模块化。微服务架构具有可扩展性、易于部署、具备弹性,并通过隔离和模块化实现故障容忍,利用云容器和 Kubernetes。容器是云原生应用程序的默认打包格式,而 Kubernetes 是容器生命周期管理和部署编排的事实标准平台。微服务能够分布式、高度可扩展,并与其他微服务并行工作,这加强了微服务之间的通信挑战,也带来了操作上的挑战,例如在微服务的通信和执行中的可见性问题。

微服务需要彼此之间有安全的通信,以避免中间人攻击等利用和攻击。为了解决这些挑战并以成本高效和性能优越的方式进行处理,需求一个应用网络基础设施,通常称为服务网格。Istio 就是这样一种服务网格实现,它由一些伟大的组织开发并支持,包括 Google、Red Hat、VMware、IBM、Lyft、Yahoo 和 AT&T。

在本章中,我们将安装并运行 Istio,同时我们也将深入了解其架构和各个组件。本章将帮助你理解 Istio 与其他服务网格实现之间的区别。到章节末尾,你应该能够配置和设置你的环境,并在深入理解安装过程后成功安装 Istio。安装完成后,你将启用 Istio sidecar 注入到一个与 Istio 安装一同提供的示例应用程序中。我们将一步一步地查看启用和禁用 Istio 对示例应用程序的影响,并了解 Istio 是如何工作的。

我们将通过以下主题进行探索:

  • 为什么 Istio 是最流行的服务网格?

  • 准备工作站环境以安装和运行 Istio

  • 安装 Istio

  • 安装可观察性工具

  • Istio 架构介绍

为什么 Istio 是最流行的服务网格?

Istio 源自希腊词 ιστίο,发音为 Iss-tee-oh。Istio 意为 ,它是一种不弯曲、不压缩的结构,通常由织物或类似材料制成。帆通过风产生的升力和阻力推动帆船前进。最初的贡献者选择 Istio 作为名字,可能与 Kubernetes 的命名有关,Kubernetes 同样来源于希腊语,发音为 koo-burr-net-eez,写作 κυβερνήτης,意思是 舵手——即站在船舵前并操控船只的人。

Istio 是一个开源的服务网格,采用 Apache License 2.0 进行分发。它是平台独立的,这意味着它不依赖于底层的 Kubernetes 提供商。它不仅支持 Kubernetes,还支持非 Kubernetes 环境,如虚拟机。话虽如此,Istio 在 Kubernetes 环境中的开发更为成熟,并且正在迅速适应和发展以支持其他环境。Istio 拥有一个非常成熟的开发社区、强大的用户基础,并且具有高度的可扩展性和可配置性,提供了对服务网格中的流量和安全的可靠操作控制。Istio 还通过先进和精细化的指标提供行为洞察。它支持 WebAssembly,这对于可扩展性和针对特定需求的定制非常有用。Istio 还为多集群和多网络环境提供支持和简易配置。

探索 Istio 的替代方案

Istio 还有许多其他替代方案,每种方案都有其优缺点。在这里,我们将列出一些可用的其他服务网格实现。

Kuma

在撰写本文时(2022 年),KumaCloud Native Computing Foundation (CNCF) 的沙盒项目,最初由 Kong Inc. 创建,Kong Inc. 还提供 Kong API 管理网关的开源和商业版本。Kuma 被 Kong Inc. 宣传为一个现代化的分布式控制平面,内置了 Envoy 代理集成。它支持多云和多区域连接,以适应高度分布式的应用程序。Kuma 数据平面由 Envoy 代理组成,然后由 Kuma 控制平面进行管理,支持不仅部署在 Kubernetes 上的工作负载,还支持虚拟机和裸金属环境中的工作负载。Kong Inc. 还提供了一种企业级服务网格产品,名为 Kong Mesh,它扩展了 CNCF 的 Kuma 和 Envoy。

Linkerd

Linkerd 最初由 Buoyant, Inc. 创建,后来被开源,现在采用 Apache V2 许可证。Buoyant, Inc. 还提供 Linkerd 的托管云服务,并为那些希望自行运行 Linkerd 但需要企业级支持的客户提供企业支持服务。Linkerd 通过提供运行时调试、可观测性、可靠性和安全性,使服务的运行更加简单和安全。与 Istio 类似,你无需更改应用程序源代码;相反,你只需在每个服务旁边安装一组超轻透明的 Linkerd2-proxy。

Linkerd2-proxy 是一个用 Rust 编写的微代理,并作为 sidecar 与应用程序一起部署在 Pod 中。Linkerd 代理专门为 Service Mesh 用例编写,并且被认为比 Envoy 更快,Envoy 被用作 Istio 和许多其他 Service Mesh 实现(如 Kuma)中的 sidecar。Envoy 是一个非常出色的代理,但它是为多个用例设计的——例如,Istio 使用 Envoy 作为 Ingress 和 Egress 网关,同时也作为 sidecar 与应用程序一起运行。许多 Linkerd 实现使用 Linkerd 作为 Service Mesh,并且 Envoy 基于的 Ingress 控制器。

Consul

Consul 是 Hashicorp 提供的 Service Mesh 解决方案;它是开源的,但也有来自 Hashicorp 的云和企业支持服务。Consul 可以部署在 Kubernetes 以及基于虚拟机的环境中。在 Service Mesh 之上,Consul 还提供了服务目录、TLS 证书和服务间授权的所有功能。Consul 的数据平面提供两种选择;用户可以选择类似于 Istio 的基于 Envoy 的 sidecar 模式,或者通过 Consul Connect SDK 的原生集成,这种方式无需注入 sidecar,且提供比 Envoy 代理更好的性能。另一个区别是,你需要在每个 Kubernetes 集群中的工作节点和非 Kubernetes 环境中的每个节点上运行一个 Consul agent 作为守护进程。

AWS App Mesh

App Mesh 是 AWS 提供的 Service Mesh 解决方案,当然,它可以用于 AWS 上部署的工作负载,包括 Elastic Container Service (ECS)、Kubernetes 的 Elastic Container Service 或在 AWS 中运行的自管 Kubernetes 集群。像 Istio 一样,App Mesh 也在 Pod 中使用 Envoy 作为 sidecar 代理,同时控制平面由 AWS 提供为托管服务,类似于 EKS。App Mesh 提供与其他 AWS 服务的集成,如 Amazon Cloudwatch 和 AWS X-Ray。

OpenShift Service Mesh

Red Hat OpenShift Service Mesh 基于 Istio;事实上,Red Hat 也是 Istio 开源项目的贡献者。该产品捆绑了 Jaeger 用于分布式追踪,以及 Kiali 用于可视化网格、查看配置和流量监控。与 Red Hat 的其他产品一样,你可以为 OpenShift Service Mesh 购买企业支持。

F5 NGINX Service Mesh

NGINX 是 F5 的一部分,因此其 Service Mesh 解决方案称为 F5 NGINX Service Mesh。它使用 NGINX Ingress 控制器与 NGINX App Protect 一起在边缘加密流量,然后通过 Ingress 控制器将流量路由到网格。NGINX Plus 作为应用程序的 sidecar 提供无缝透明的负载均衡、反向代理、流量路由和加密。使用 OpenTracing 和 Prometheus 进行指标收集和分析,同时提供内建的 Grafana 仪表板来可视化 Prometheus 指标。

这部分简要介绍了服务网格的实现,接下来我们将在 附录 A 中更详细地讨论其中的一些内容。目前,让我们重新聚焦到 Istio 上。接下来的章节以及全书将详细介绍 Istio 的好处,但首先让我们通过安装 Istio 并启用一个与 Istio 一起打包的应用来开始。

为 Istio 安装做好准备

我们将在前几章中使用 minikube 来安装和使用 Istio。在后续章节中,我们将把 Istio 安装到 AWS EKS 上,以模拟现实场景。首先,让我们准备好你的笔记本电脑/台式机与 minikube。如果你的环境中已经安装了 minikube,强烈建议升级到最新版本。

如果你还没有安装 minikube,请按照说明进行安装。minikube 是安装在工作站上的本地 Kubernetes,可以让你轻松学习和使用 Kubernetes 以及 Istio,而不需要一大堆计算机来安装 Kubernetes 集群。

系统规格

你需要 Linux 或 macOS 或 Windows。本书主要以 macOS 作为目标操作系统。如果 Linux 和 macOS 之间的命令有很大差异,你将会在小贴士中看到对应的步骤/命令。你至少需要两个 CPU,2 GB 可用内存,以及 Docker Desktop(如果是 macOS 或 Windows)或 Linux 的 Docker Engine。如果你还没有安装 Docker,请根据相应的操作系统,访问 docs.docker.com/ 安装 Docker。

安装 minikube 和 Kubernetes 命令行工具

我们将使用 Homebrew 安装 minikube。然而,如果你没有安装 Homebrew,可以使用以下命令来安装 Homebrew:

$/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

开始吧:

  1. 使用 brew install minikube 安装 minikube:

    $ brew install minikube
    Running `brew update --preinstall`...
    ..
    ==> minikube cask is installed, skipping link.
    ==> Caveats
    Bash completion has been installed to:
      /usr/local/etc/bash_completion.d
    ==> Summary
      /usr/local/Cellar/minikube/1.25.1: 9 files, 70.3MB
    ==> Running `brew cleanup minikube`...
    

安装完成后,在 Homebrew 的 Cellar 文件夹中创建一个指向新安装的二进制文件的符号链接:

$ brew link minikube
Linking /usr/local/Cellar/minikube/1.25.1... 4 symlinks created.
$ which minikube
/usr/local/bin/minikube
$ ls -la /usr/local/bin/minikube
lrwxr-xr-x  1 arai  admin  38 22 Feb 22:12 /usr/local/bin/minikube -> ../Cellar/minikube/1.25.1/bin/minikube

为了测试安装情况,请使用以下命令查看 minikube 版本:

$ minikube version
minikube version: v1.25.1
commit: 3e64b11ed75e56e4898ea85f96b2e4af0301f43d

注意,Linux 用户!

如果你在 Linux 上安装,可以使用以下命令安装 minikube:

$ curl -``LO storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64

$ sudo install minikube-linux-amd64 /usr/local/bin/minikube

  1. 下一步是安装 kubectl,如果你的机器上尚未安装它。

kubectl 是 Kubernetes 命令行工具的简称,发音为 kube-control。kubectl 允许你对 Kubernetes 集群运行命令。你可以在 Linux、Windows 或 macOS 上安装 kubectl。以下步骤将演示如何在 macOS 上通过 Brew 安装 kubectl:

$ brew install kubectl

你可以使用以下步骤在基于 Debian 的机器上安装 kubectl:

  1. sudo apt-get update

  2. sudo apt-get install -y apt-transport-https ca-certificates curl

  3. sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg

  4. echo "deb [signed-by=/usr/share/keyrings/****kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list

  5. echo “deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main” | sudo tee /etc/apt/sources.list.d/kubernetes.list

  6. sudo apt-get update

  7. sudo apt-get install -****y kubectl

以下步骤可以用来在 Red Hat 机器上安装 kubectl:

  1. cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo

  2. [****kubernetes]

  3. name=Kubernetes

  4. baseurl=https://packages.cloud.google.com /yum/repos/kubernetes-el7-x86_64

  5. enabled=1

  6. gpgcheck=1

  7. repo_gpgcheck=1

  8. gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpghttps://packages.cloud.google.com/yum/doc/****rpm-package-key.gpg

  9. ckages.cloud.google.com/yum/doc/rpm-package-key.gpg

  10. EOF

  11. sudo yum install -****y kubectl

现在你已经具备了本地运行 Kubernetes 所需的一切,接下来输入以下命令。确保你以具有管理员权限的用户身份登录。

你可以使用 minikube start 配合 Kubernetes 版本,方法如下:

$ minikube start --kubernetes-version=v1.23.1
  minikube v1.25.1 on Darwin 11.5.2
  Automatically selected the hyperkit driver
..
  Done! kubectl is now configured to use the "minikube" cluster and "default" namespace by default

你可以在控制台输出中看到 minikube 正在使用 HyperKit 驱动程序。—driver=hyperkit

对于 Linux 用户

对于 Linux,你可以使用 minikube start --driver=docker。在这种情况下,minikube 将作为 Docker 容器运行。对于 Windows,你可以使用 minikube start –driver=virtualbox。为了避免每次启动 minikube 时都输入 --driver,你可以通过使用 minikube config set driver DRIVERNAME 来配置默认驱动程序,其中 DRIVERNAME 可以是 Hyperkit、Docker 或 VirtualBox。

你可以使用以下命令来验证 kubectl 是否正常工作,且 minikube 是否已正确启动:

$ kubectl cluster-info
Kubernetes control plane is running at https://192.168.64.6:8443
CoreDNS is running at https://192.168.64.6:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

在输出中,你可以看到 Kubernetes 控制平面和 DNS 服务器正在运行。这标志着 minikube 和 kubernetes-cli 的安装完成。你现在拥有一个本地运行的 Kubernetes 集群,并且可以通过 kubectl 与其进行通信。

安装 Istio

这一部分是你一直迫不及待想读的内容。等待已经结束,你已经准备好安装 Istio。只需按照提供的说明进行操作。

第一步是从 github.com/istio/istio/releases 下载 Istio。你也可以使用 curl 下载,方法如下。建议在一个你希望下载二进制文件的目录中运行以下命令。我们可以将该目录命名为 ISTIO_DOWNLOAD,然后在该目录中运行以下命令:

$ curl -L https://istio.io/downloadIstio | sh -
Downloading istio-1.13.1 from https://github.com/istio/istio/releases/download/1.13.1/istio-1.13.1-osx.tar.gz ...
Istio 1.13.1 Download Complete!

上述命令将 Istio 的最新版本下载到ISTIO_DOWNLOAD位置。如果我们拆解这个命令,它有两部分:

$ curl -L https://istio.io/downloadIstio

命令的第一部分从raw.githubusercontent.com/istio/istio/master/release/downloadIstioCandidate.sh下载一个脚本(位置可能会变),然后第二部分的脚本被传递给sh执行。脚本分析处理器架构和操作系统,并根据这些信息决定适当的 Istio 版本(ISTIO_VERSION)、操作系统(OSEXT)和处理器架构(ISTIO_ARCH)。然后,脚本将这些值填充到以下 URL 中:github.com/istio/istio/releases/download/${ISTIO_VERSION}/istio-${ISTIO_VERSION}-${OSEXT}-${ISTIO_ARCH}.tar.gz,然后下载gz文件并解压。

让我们调查一下下载到ISTIO_DOWNLOAD位置的内容:

$ ls
istio-1.13.1
$ ls istio-1.13.1/
LICENSE  README.md bin  manifest.yaml manifests samples  tools

以下是对文件夹的简要描述:

  • bin包含istioctl,也叫做Istio-control,它是 Istio 的命令行工具,用于调试和诊断 Istio,还可以创建、列出、修改和删除配置资源。

  • samples包含一个我们将用于学习的示例应用程序。

  • manifest包含 Helm 图表,现在你不需要担心这些。当我们希望安装过程从manifest中获取图表,而不是使用默认图表时,它们才会起作用。

由于我们将使用istioctl来执行安装,所以让我们将其添加到可执行路径中:

$ pwd
/Users/arai/istio/istio-1.13.1
$ export PATH=$PWD/bin:$PATH
$ istioctl version
no running Istio pods in "istio-system"
1.13.1

我们距离安装 Istio 只差一个命令了。继续输入以下命令以完成安装:

$ istioctl install --set profile=demo
This will install the Istio 1.13.1 demo profile with ["Istio core" "Istiod" "Ingress gateways" "Egress gateways"] components into the cluster. Proceed? (y/N) y
 Istio core installed
 Istiod installed
 Egress gateways installed
 Ingress gateways installed
 Installation complete
Making this installation the default for injection and validation.
Thank you for installing Istio 1.13.

提示

你可以传递-y来避免出现(Y/N)问题。只需使用istioctl install --set profile=demo -y

哇!你已经通过八个命令成功完成了 Istio 的安装,包括平台设置。如果你已经在使用 minikube 和 kubectl,那么希望你应该能在三个命令内完成安装。如果你是在现有的 minikube 设置上安装的,那么在这个阶段建议在一个新的集群上安装 Istio,而不是在已有的集群中与其他应用一起安装。

让我们看看已安装了什么。我们首先从分析命名空间开始:

$ kubectl get ns
NAME              STATUS   AGE
default           Active   19h
istio-system      Active   88m
kube-node-lease   Active   19h
kube-public       Active   19h
kube-system       Active   19h

我们可以看到,安装过程中创建了一个名为istio-system的新命名空间。

让我们检查一下istio-system命名空间中的 Pods 和 Services:

$ kubectl get pods -n istio-system
NAME                           READY   STATUS    RESTARTS   AGE
pod/istio-egressgateway-76c96658fd-pgfbn   1/1     Running   0          88m
pod/istio-ingressgateway-569d7bfb4-8bzww   1/1     Running   0          88m
pod/istiod-74c64d89cb-m44ks                1/1     Running   0          89m

前面的输出部分显示了在istio-system命名空间下运行的各种 Pods,接下来的部分将显示istio-system命名空间中的 Services:

$ kubectl get svc -n istio-system
NAME           TYPE      CLUSTER-IP       EXTERNAL-IP    PORT(S)         AGE
service/istio-egressgate way    ClusterIP      10.97.150.168     <none>        80/TCP,443/TCP             88m
service/istio-ingressgateway   LoadBalancer   10.100.113.119    <pending>     15021:31391/TCP,80:32295/TCP,443:31860/TCP,31400:31503/TCP,15443:31574/TCP   88m
service/istiod          ClusterIP      10.110.59.167     <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP    89m

你可以通过以下命令查看所有资源:

$ kubectl get all -n istio-system

istio-system命名空间中,Istio 安装了istiod组件,这是 Istio 的控制平面。还有许多其他自定义配置,例如 Kubernetes 自定义资源定义、ConfigMaps、Admission Webhooks、服务帐户、角色绑定以及安装的 Secrets。

我们将在下一章更详细地了解istiod和其他控制平面组件。目前,让我们为带有 Istio 的示例应用启用 Istio。

为示例应用启用 Istio

为了将我们的工作与其他资源隔离开来,我们将首先创建一个名为bookinfons的 Kubernetes 命名空间。创建命名空间后,我们将在bookinfons命名空间中部署示例应用。

你需要在 Istio 安装目录中运行第二个命令——即$ISTIO_DOWNLOAD/istio-1.13.1

 $ kubectl create ns bookinfons
namespace/bookinfons created
$ kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml -n bookinfons

所有创建的资源都定义在samples/bookinfo/platform/kube/bookinfo.yaml中。

使用以下命令检查已创建的 Pod 和服务:

$ kubectl get po -n bookinfons
$ kubectl get svc -n bookinfons

请注意,每个detailsproductpageratings都有一个 Pod,而review有三个 Pod 版本。每个微服务都有一个服务。它们都很相似,除了kubectl review服务,它有三个端点。使用以下命令,让我们检查一下review服务的定义与其他服务的不同之处:

$ kubectl describe svc/reviews -n bookinfons
...
Endpoints:         172.17.0.10:9080,172.17.0.8:9080,172.17.0.9:9080
...
$ kubectl get endpoints -n bookinfons
NAME          ENDPOINTS                                           AGE details     172.17.0.6:9080                                     18h
productpage   172.17.0.11:9080                                    18h
ratings  172.17.0.7:9080                                     18h
reviews       172.17.17.0.10:9080,172.17.0.8:9080,172.17.0.9:9080   18h

现在bookinfo应用已成功部署,让我们使用以下命令访问bookinfo应用的产品页面:

$ kubectl port-forward svc/productpage 9080:9080 -n bookinfons
Forwarding from 127.0.0.1:9080 -> 9080
Forwarding from [::1]:9080 -> 9080
Handling connection for 9080

请在你的浏览器中输入http://localhost:9080/productpage。如果你没有浏览器,你可以通过curl来访问:

图 2.1 – BookInfo 应用的产品页面

图 2.1 – BookInfo 应用的产品页面

如果你能看到productpage,那么你就成功部署了示例应用。

如果我没有浏览器怎么办?

如果你没有浏览器,你可以使用以下方法:

curl -``sS localhost:9080/productpage

所以,现在我们已经成功部署了带有 Istio 的示例应用,让我们继续为其启用 Istio。

侧车注入

侧车注入是将istio-proxy注入到 Kubernetes Pod 中的一种方式,作为侧车运行。侧车是与主容器一起在 Kubernetes Pod 中运行的附加容器。通过与主容器一起运行,侧车可以与 Pod 中其他容器共享网络接口;这种灵活性被istio-proxy容器用来调解和控制所有与主容器之间的通信。我们将在第三章中更详细地了解侧车。目前,我们将通过为示例应用启用 Istio 来继续进行。

让我们在启用 Istio 之前和之后检查一些有趣的细节:

$ kubectl get ns bookinfons –show-labels
NAME         STATUS   AGE    LABELS
bookinfons   Active   114m   kubernetes.io/metadata.name=bookinfons

让我们来看一下其中一个 Pod,productpage

$ kubectl describe pod/productpage-v1-65b75f6885-8pt66 -n bookinfons

将输出复制到安全的地方。我们将使用这些信息,在你为 bookinfo 应用启用 Istio 后进行对比。

我们需要删除已部署的内容:

$ kubectl delete -f samples/bookinfo/platform/kube/bookinfo.yaml -n bookinfons

等待几秒钟,检查 bookinfons 命名空间中的所有资源是否已终止。之后,启用 bookinfonsistio-injection

$ kubectl label namespace bookinfons istio-injection=enabled
namespace/bookinfons labeled
$ kubectl get ns bookinfons –show-labels
NAME         STATUS   AGE   LABELS
bookinfons   Active   21h   istio-injection=enabled,kubernetes.io/metadata.name=bookinfons

手动注入 sidecar

另一种方式是手动注入 sidecar,通过使用 istioctl kube-inject 来增强部署描述符文件,然后使用 kubectl 应用它:

$ istioctl kube-inject -f deployment.yaml -o deployment-injected.yaml | kubectl apply -f –

继续部署 bookinfo 应用程序:

$ kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml -n bookinfons

让我们检查一下已经创建了什么:

$ kubectl get po -n bookinfons

我们可以看到,Pods 中的容器数量现在不是一个,而是两个。在我们启用 istio-injection 之前,Pods 中的容器数量为一个。稍后我们将讨论额外的容器是什么。我们还将检查服务数量是否有所变化:

$ kubectl get svc -n bookinfons

好的,Pod 行为发生了变化,但服务行为没有显著变化。让我们深入查看其中一个 Pod:

$ kubectl describe po/productpage-v1-65b75f6885-57vnb -n bookinfons

此命令的完整输出可以在 Output references/Chapter 2/productpage pod.docx 中找到,位于本章的 GitHub 仓库中。

请注意,productpage Pod 及 bookinfons 中的每个 Pod 的描述中,都有一个名为 istio-proxy 的容器和一个名为 istio-init 的初始化容器。它们在我们最初创建这些 Pod 时是不存在的,但在我们应用了 istio-injection=enabled 标签后添加了,使用的命令如下:

kubectl label namespace bookinfons istio-injection=enabled

Sidecar 可以手动或自动注入。自动注入是注入 sidecar 的更简单方式。然而,一旦我们熟悉了 Istio,我们将在本书的第二部分中讨论如何通过修改应用程序资源描述符文件手动注入 sidecar。现在,让我们简单看一下自动 sidecar 注入是如何工作的。

Istio 使用 Kubernetes 准入控制器。Kubernetes 准入控制器负责拦截请求到 Kubernetes API 服务器。拦截发生在身份验证和授权之后,但在对象的修改/创建/删除之前。你可以使用以下命令找到这些准入控制器:

$ kubectl describe po/kube-apiserver-minikube -n kube-system | grep enable-admission-plugins
--enable admission plugins=NamespaceLifecycle, LimitRanger,ServiceAccount,DefaultStorageClass, DefaultTolerationSeconds,NodeRestriction, MutatingAdmissionWebhook,ValidatingAdmissionWebhook, ResourceQuota

Istio 利用变更准入 webhook 实现自动 sidecar 注入。让我们来了解一下在我们的集群中配置了哪些变更准入 webhook:

$ kubectl get --raw /apis/admissionregistration.k8s.io/v1/mutatingwebhookconfigurations | jq '.items[].metadata.name'
"istio-revision-tag-default"
"istio-sidecar-injector"

以下图表描述了在 API 调用 Kubernetes API 服务器时准入控制器的作用。变更准入 Webhook 控制器负责 sidecar 的注入。

图 2.2 – Kubernetes 中的准入控制器

图 2.2 – Kubernetes 中的准入控制器

我们将在第三章中更详细地讨论 sidecar 注入。现在,让我们将注意力转回到 istio-injection 导致的 Pod 描述符变化上。

istio-iptables 在产品页面 Pod 描述文件的 istio-init 配置中提到,使用了以下命令:

kubectl describe po/productpage-v1-65b75f6885-57vnb -n bookinfons

以下是 Pod 描述文件中的一个片段:

istio-iptables -p 15001 -z 15006 -u 1337 -m REDIRECT -I '*' -x "" -b '*' -d 15090,15021,15020

istio-iptables 是一个初始化脚本,负责通过 iptables 设置 Istio sidecar 代理的端口转发。以下是在执行脚本时传递的各种参数:

  • -p 指定所有 TCP 流量将被重定向到的 Envoy 端口

  • -z 指定所有入站流量到 Pod 应该重定向到的端口

  • -u 是不应用重定向的用户的 UID

  • -m 是用于重定向入站连接的模式

  • -I 是需要重定向到 Envoy 的出站连接的 CIDR 块目标 IP 范围列表

  • -x 是需要免除从重定向到 Envoy 的出站连接的 CIDR 块目标列表

  • -b 是需要重定向到 Envoy 的入站端口列表

  • -d 是需要从重定向到 Envoy 中排除的入站端口列表

总结前面在 istio-init 容器中的参数,容器正在执行一个脚本 istio-iptables,它基本上在 Pod 层面创建 iptables 规则——也就是说,应用于 Pod 内的所有容器。该脚本配置了一个 iptables 规则,应用以下内容:

  • 所有流量应该重定向到端口 15001

  • 任何流量到 Pod 都应该重定向到端口 15006

  • 此规则不适用于 UID 1337

  • 重定向使用的模式是 REDIRECT

  • 所有到任何目标 (*) 的出站连接应该重定向到 15001

  • 没有出站目标可以免除此规则

  • 重定向需要发生在来自任何 IP 地址的所有入站连接上,除非目标端口是 150901502115020

我们将在第三章中深入探讨,但现在记住,初始化容器基本上会在 Pod 层面设置一个 iptables 规则,该规则将所有到达产品页面容器端口 9080 的流量重定向到 15006,而从产品页面容器发出的所有流量将被重定向到端口 15001。端口 1500115006 都由 istio-proxy 容器暴露,该容器来自 docker.io/istio/proxyv2:1.13.1istio-proxy 容器与产品页面容器一起运行。除了 1500115006,它还暴露端口 150901502115020

Istio-iptables.sh 可以在这里找到:github.com/istio/cni/blob/master/tools/packaging/common/istio-iptables.sh

你还会注意到,istio-initistio-proxy 都是从相同的 Docker 镜像 docker.io/istio/proxyv2:1.13.1 中创建的。可以在这里查看 Docker 文件:hub.docker.com/layers/proxyv2/istio/proxyv2/1.13.4/images/sha256-1245211d2fdc0f86cc374449e8be25166b9d06f1d0e4315deaaca4d81520215e?context=explore. Docker 文件能提供更多关于镜像构建方式的洞察:

# BASE_DISTRIBUTION is used to switch between the old base distribution and distroless base images
..
ENTRYPOINT ["/usr/local/bin/pilot-agent"]

入口点是一个名为 pilot-agent 的 Istio 命令/工具,它在传递 proxy sidecar 参数到 istio-proxy 容器时,启动 Envoy 作为 sidecar 运行。pilot-agent 还在初始化时设置 iptables,当传递 istio-iptables 参数时,它会在 istio-init 容器的初始化过程中执行。

关于 pilot-agent 的更多信息

你可以通过在容器外部执行 pilot-agent,并选择任何注入了 istio-proxy sidecar 的 Pod 来获得有关 pilot-agent 的更多信息。在以下命令中,我们需要使用 istio-system 命名空间中的 Ingress 网关 Pod:

$ kubectl exec -it po/istio-ingressgateway-569d7bfb4-8bzww -n istio-system -c istio-proxy -- /usr/local/bin/pilot-agent proxy router --help

和前面一样,你仍然可以通过 kubectl port-forward 从浏览器访问产品页面:

$ kubectl port-forward svc/productpage 9080:9080 -n bookinfons
Forwarding from 127.0.0.1:9080 -> 9080
Forwarding from [::1]:9080 -> 9080
Handling connection for 9080

到目前为止,我们已经研究了 sidecar 注入以及它对 Kubernetes 资源部署的影响。在接下来的部分,我们将了解 Istio 如何管理流量的 Ingress 和 Egress。

Istio 网关

我们不仅可以使用 port-forward,还可以利用 Istio 的 Ingress 网关来暴露应用程序。网关用于管理进出服务网格的流量,提供对进出流量的控制。请尝试以下命令,列出 istiod 命名空间中的 Pods,并查看在 Istio 安装过程中已经安装的网关:

$ kubectl get pod -n istio-system
NAME                        READY   STATUS    RESTARTS   AGE
istio-egressgateway-76c96658fd-pgfbn   1/1     Running   0          5d18h
istio-ingressgateway-569d7bfb4-8bzww   1/1     Running   0          5d18h
istiod-74c64d89cb-m44ks                1/1     Running   0          5d18h
$ kubectl get po/istio-ingressgateway-569d7bfb4-8bzww -n istio-system -o json  | jq '.spec.containers[].image'
"docker.io/istio/proxyv2:1.13.1"
$ kubectl get po/istio-egressgateway-76c96658fd-pgfbn -n istio-system -o json  | jq '.spec.containers[].image'
"docker.io/istio/proxyv2:1.13.1"

你可以看到,网关也是一组在服务网格中运行的 Envoy 代理。它们类似于在 Pod 中作为 sidecar 部署的 Envoy 代理,但在网关中,它们作为独立容器运行在通过 pilot-agent 部署的 Pod 中,并带有 proxy router 参数。让我们来研究一下 Egress 网关的 Kubernetes 描述符:

$ kubectl get po/istio-egressgateway-76c96658fd-pgfbn -n istio-system -o json  | jq '.spec.containers[].args'
[
  "proxy",
  "router",
  "--domain",
  "$(POD_NAMESPACE).svc.cluster.local",
  "--proxyLogLevel=warning",
  "--proxyComponentLogLevel=misc:error",
  "--log_output_level=default:info"
]

接下来,我们来看一下网关服务:

$ kubectl get svc -n istio-system
NAME                TYPE          CLUSTER-
IP          EXTERNAL-IP    PORT(S)                                              AGE
istio-egressgateway    ClusterIP      10.97.150.168    <none>        80/TCP,443/TCP                                    5d18h
istio-ingressgateway   LoadBalancer
   10.100.113.119   <pending>     15021:31391/TCP,80:32295/TCP,443:31860/TCP,31400:31503/TCP,15443:31574/TCP   5d18h
istiod   ClusterIP      10.110.59.167    <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP                                        5d18h

现在,让我们尝试使用以下命令理解 Ingress 网关的端口:

$ kubectl get svc/istio-ingressgateway -n istio-system -o json | jq '.spec.ports'
[
…
  {
    "name": "http2",
    "nodePort": 32295,
    "port": 80,
    "protocol": "TCP",
    "targetPort": 8080
  },
  {
    "name": "https",
    "nodePort": 31860,
    "port": 443,
    "protocol": "TCP",
    "targetPort": 8443
  },
  ….

你可以看到,Ingress 网关服务从集群外部的端口 3229531860 接受 http2https 流量。从集群内部,流量通过端口 80443 处理。然后,http2https 流量被转发到端口 80808443,转发到底层的 Ingress Pod。

让我们为 bookinfo 服务启用 Ingress 网关:

$ kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml -n bookinfons
gateway.networking.istio.io/bookinfo-gateway created
virtualservice.networking.istio.io/bookinfo created

让我们来看一下 bookinfo 的虚拟服务定义:

$ kubectl describe virtualservice/bookinfo -n bookinfons
Name:         bookinfo
..
API Version:  networking.istio.io/v1beta1
Kind:         VirtualService
...
Spec:
  Gateways:
    bookinfo-gateway
  Hosts:
    *
  Http:
    Match:
      Uri:
        Exact:  /productpage
      Uri:
        Prefix:  /static
      Uri:
        Exact:  /login
      Uri:
        Exact:  /logout
      Uri:
        Prefix:  /api/v1/products
    Route:
      Destination:
        Host:  productpage
        Port:
          Number:  9080

虚拟服务不限制任何特定的主机名。它将/productpagelogin/logout,以及任何其他以/api/v1/products/static为前缀的 URI 路由到端口9080productpage 服务。如果您还记得,9080也是 productpage 服务暴露的端口。spec.gateways 注解意味着该虚拟服务配置应该应用于 bookinfo-gateway,我们接下来会进行探讨:

$ kubectl describe gateway/bookinfo-gateway -n bookinfons
Name:         bookinfo-gateway
..
API Version:  networking.istio.io/v1beta1
Kind:         Gateway
..
Spec:
  Selector:
    Istio:  ingressgateway
  Servers:
    Hosts:
      *
    Port:
      Name:      http
      Number:    80
      Protocol:  HTTP
..

网关资源描述了一个负载均衡器,用于接收从网格进入和流出的连接。前面的示例首先定义了配置应该应用于带有Istio: ingressgateway标签的 Pod(istiod命名空间中的 Ingress 网关 Pod)。该配置不绑定到任何主机名,并且在端口80上接收 HTTP 流量的连接。

总结一下,您有一个以网关形式定义的负载均衡器配置,以及以虚拟服务形式定义的后端路由配置。这些配置应用于一个代理 Pod,在此案例中是 istio-ingressgateway-569d7bfb4-8bzww

在浏览器中打开产品页面时,检查代理 Pod 的日志。

首先,找到 IP 和端口(Ingress 网关服务中的 HTTP2 端口):

$ echo $(minikube ip)
192.168.64.6
$ echo $(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}')
32295

通过以下 URL 获取产品:192.168.64.6:32295/api/v1/products。您可以通过浏览器或 curl 执行此操作。

istio-ingressgateway Pod 的日志流式传输到 stdout

$ kubectl logs -f pod/istio-ingressgateway-569d7bfb4-8bzww -n istio-system
"GET /api/v1/products HTTP/1.1" 200 - via_upstream - "-" 0 395 18 16 "172.17.0.1" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36" "cfc414b7-10c8-9ff9-afa4-a360b5ad53b8" "192.168.64.6:32295" "172.17.0.10:9080" outbound|9080||productpage.bookinfons.svc.cluster.local 172.17.0.5:56948 172.17.0.5:8080 172.17.0.1:15370 - -

从日志中,您可以推断出一个传入请求 GET /api/v1/products HTTP/1.1 到达了 192.168.64.6:32295,然后被路由到 172.17.0.10:9080。这是终端节点,也就是 productpage Pod 的 IP 地址。

下图展示了带有注入的istio-proxy边车和 Istio Ingress 网关的 bookinfo Pods 的组成。

图 2.3 – 带有边车注入和 Istio Ingress 网关的 BookInfo 应用程序

图 2.3 – 带有边车注入和 Istio Ingress 网关的 BookInfo 应用程序

提示

如果您遇到 TLS 错误,如证书过期或任何其他 OpenSSL 错误,那么请尝试使用以下命令重新启动 BookInfo 应用程序和 Istio 组件:

$ kubectl rollout restart deployment --``namespace bookinfons

$ kubectl rollout restart deployment --``namespace istio-system

希望到目前为止,您已经熟悉了 Istio 的基本概念以及如何在工作站上安装它。在接下来的章节中,我们将继续介绍在 Istio 中安装附加组件。

可观察性工具

Istio 生成各种度量指标,这些指标可以输入到各种遥测应用程序中。开箱即用的安装包包含了附加组件,包括KialiJaegerPrometheusGrafana。接下来,我们将详细了解它们。

Kiali

第一个安装的组件是 Kiali,它是 Istio 的默认管理 UI。我们将通过运行以下命令开始启用遥测工具:

$ kubectl apply -f samples/addons
serviceaccount/grafana created
…….
$ kubectl rollout status deployment/kiali -n istio-system
Waiting for deployment "kiali" rollout to finish: 0 of 1 updated replicas are available...
deployment "kiali" successfully rolled out

一旦所有资源都创建完成并且 Kiali 成功部署,你可以通过以下命令打开 Kiali 的仪表盘:

$ istioctl dashboard kiali
http://localhost:20001/kiali

当你需要可视化或排除网格拓扑以及底层网格流量时,Kiali 非常方便。让我们快速看一下其中的一些可视化图表。

概览 页面提供了集群中所有命名空间的概览。

图 2.4 – Kiali 仪表盘概览部分

图 2.4 – Kiali 仪表盘概览部分

你可以点击右上角的三个点,进一步查看该命名空间的详细信息,并且可以更改它的配置。

图 2.5 – Kiali 仪表盘上的 Istio 配置

图 2.5 – Kiali 仪表盘上命名空间的 Istio 配置

你还可以查看单独的应用、Pods、服务等。最有趣的可视化之一是 图形,它表示在指定时间段内网格中流量的流动。

图 2.6 – Kiali 仪表盘上的版本化应用图

图 2.6 – Kiali 仪表盘上的版本化应用图

上面的截图显示的是一个版本化应用图,其中多个版本的应用被分组在一起;在这个例子中,是一个评价应用。我们将在第八章中详细讨论这一点。

Jaeger

另一个附加组件是 Jaeger。你可以通过以下命令打开 Jaeger 仪表盘:

$ istioctl dashboard jaeger
http://localhost:16686

上述命令应该会在你的浏览器中打开 Jaeger 仪表盘。Jaeger 是一个开源的端到端分布式事务监控软件。构建并部署一个实际应用时,第四章 中将会明确显示出对这种工具的需求。

在 Jaeger 仪表盘中的 bookinfons 命名空间下。

图 2.7 – Jaeger 仪表盘搜索部分

图 2.7 – Jaeger 仪表盘搜索部分

你可以点击任意条目以查看更详细的信息:

图 2.8 – Jaeger 仪表盘详情部分

图 2.8 – Jaeger 仪表盘详情部分

你可以看到整体调用花费了 69.91 毫秒。详情由 productpage 调用,返回响应花费了 2.97 毫秒。你可以进一步点击任何服务查看详细追踪信息。

Prometheus

接下来,我们将深入了解 Prometheus,它也是一个开源的监控系统和时间序列数据库。Prometheus 用来捕捉所有与时间相关的指标,以跟踪网格及其组成部分的健康状况。

要启动 Prometheus 仪表盘,使用以下命令:

$ istioctl dashboard prometheus
http://localhost:9090

这将会在你的浏览器中打开 Prometheus 仪表盘。在我们的安装中,Prometheus 被配置为从 istiod、Ingress 和 Egress 网关以及 istio-proxy 收集指标。

在以下示例中,我们查看 Istio 处理的productpage应用的总请求。

图 2.9 – 在 Prometheus 仪表板上的 Istio 总请求

图 2.9 – 在 Prometheus 仪表板上的 Istio 总请求

另一个需要关注的附加工具是 Grafana,和 Kiali 一样,它也是一个可视化工具。

Grafana

要启动 Grafana 仪表板,请使用以下命令:

$ istioctl dashboard grafana
http://localhost:3000

以下是 Istio 处理的productpage总请求的可视化:

图 2.10 – Grafana 仪表板 Explore 部分

图 2.10 – Grafana 仪表板的 Explore 部分

以下是 Istio 性能指标的另一个可视化。

图 2.11 – Grafana Istio 性能仪表板

图 2.11 – Grafana Istio 性能仪表板

请注意,仅通过应用标签istio-injection: enabled,我们便为 BookInfo 应用启用了服务网格。Sidecar 自动注入,mTLS 也默认启用于应用中不同微服务之间的通信。此外,众多监控工具提供有关 BookInfo 应用及其底层微服务的信息。

Istio 架构

现在我们已经安装了 Istio,为 BookInfo 应用启用了它,并且也分析了它的操作,是时候通过图表简化我们迄今为止看到的内容了。以下图为 Istio 架构的表示。

图 2.12 – Istio 架构

图 2.12 – Istio 架构

Istio 服务网格由数据平面和控制平面组成。本章中我们遵循的示例将它们都安装在一个节点上。在生产或非生产环境中,Istio 控制平面将安装在独立的节点集上。控制平面由 istiod 组件以及一些其他 Kubernetes 配置组成,这些组件和配置共同负责管理并提供服务发现给数据平面,传播与安全性和流量管理相关的配置,并向数据平面组件提供和管理身份与证书。

数据平面是服务网格的另一个部分,由部署在 Pod 中的应用容器旁的 Istio 代理组成。Istio 代理基本上是 Envoy。Envoy 是一个感知应用的服务代理,根据控制平面的指示调节微服务之间的所有网络流量。Envoy 还收集各种指标并将遥测数据报告回各种附加工具。

后续章节将专注于控制平面和数据平面,我们将深入理解它们的功能和行为。

摘要

在本章中,我们准备了一个本地环境来使用 istioctl 工具安装 Istio,这是 Istio 的命令行工具。然后,我们通过将 istio-injection: enabled 标签应用于托管微服务的命名空间,启用了 Sidecar 注入。

我们简要地了解了 Kubernetes 的准入控制器以及如何通过变更准入 Webhook 将 Sidecar 注入到部署 API 调用中,进而与 Kubernetes API 服务器进行交互。我们还了解了网关,并查看了与 Istio 一起安装的示例入口和出口网关。网关是一个独立的 istio-proxy,也叫 Envoy 代理,主要用于管理进出服务网格的流量。接着,我们了解了如何配置各个端口以便在入口网关上公开,并且如何将流量路由到上游服务。

Istio 提供了与各种遥测和可观察性工具的集成。我们首先了解的工具是 Kiali,这是一个可视化工具,能提供流量流动的洞察。它同时也是 Istio 服务网格的管理控制台。通过 Kiali,你还可以执行 Istio 管理功能,如检查/修改各种配置和检查基础设施状态。在 Kiali 之后,我们了解了 Jaeger、Prometheus 和 Grafana,这些都是开源工具,可以与 Istio 无缝集成。

本章的内容为你深入学习 Istio 打下了基础,并为后续章节的学习做了准备。在下一章中,我们将阅读关于 Istio 的控制平面和数据平面的内容,深入了解它们的各个组件。

第三章:理解 Istio 控制平面和数据平面

上一章给你概述了 Istio,展示了简单安装的样子,并且说明了如何将 Service Mesh 应用于示例应用程序。在本章中,我们将深入探讨 Istio 的控制平面和数据平面。我们将通过以下主题来了解这些组件的角色:

  • Istio 控制平面组件

  • Istio 控制平面的部署模型

  • Envoy,Istio 数据平面

本章将帮助你理解 Istio 控制平面,以便你能够规划在生产环境中安装控制平面。在阅读本章后,你应该能够识别 Istio 控制平面的各个组件,包括 istiod,并理解它们在 Istio 整体工作中所发挥的作用。

探索控制平面组件

下图总结了 Istio 架构以及各个组件之间的交互。我们在上一章中使用了 Ingress 网关和 istio-proxy,因此这里不再详细讲解这些内容。然而,我们将揭示一些在下图中没有直接描绘的 Istio 控制平面其他组件。

图 3.1 – Istio 控制平面

图 3.1 – Istio 控制平面

在深入探讨控制平面的组件之前,我们先把术语的定义搞清楚——控制平面是一组负责 Istio 数据平面操作的 Istio 服务。没有单一组件构成控制平面——而是由多个组件组成。

让我们来看看 Istio 控制平面的第一个组件——istiod。

istiod

istiod 是 Istio 控制平面组件之一,提供服务发现、配置和证书管理。在 Istio 的早期版本中,控制平面由名为 Galley、Pilot、Mixer、Citadel、WebHook Injector 等组件组成。istiod 将这些组件(Pilot、Galley 和 Citadel)的功能统一到一个二进制文件中,从而提供简化的安装、操作和监控,并实现了不同 Istio 版本之间的无缝升级。

让我们来看一下运行在 istio-system 命名空间中的 istiod Pod:

$ kubectl get po -n istio-system
NAME                        READY   STATUS    RESTARTS   AGE
istio-egressgateway-84f95886c7-5gxps    1/1     Running   0          10d
istio-ingressgateway-6cb4bb68df-qbjmq   1/1     Running   0          10d
istiod-65fc7cdd7-r95jk                  1/1     Running   0          10d
$ kubectl exec -it pod/istiod-65fc7cdd7-r95jk -n istio-system -- /bin/sh -c «ps -ef"
UID          PID    PPID  C STIME TTY          TIME CMD
istio-p+       1       0  0 Mar14 ?        00:08:26 /usr/local/bin/pilot-discovery discovery --monitoringAddr=:15014 --log_output_level=default:info --domain cluster.local --keepaliveMaxServerConnectionAge 30m

你一定注意到,Pod 本身正在运行 pilot-discovery 并且基于以下镜像:

$ kubectl get pod/istiod-65fc7cdd7-r95jk -n istio-system -o json | jq '.spec.containers[].image'
"docker.io/istio/pilot:1.13.1"

你一定也注意到,istiod Pod 的镜像与作为 sidecar 插入的 istio-proxy 镜像不同。istiod 镜像基于 pilot-discovery,而 sidecar 则基于 proxyv2

以下命令显示 sidecar 容器是由 proxyv2 创建的:

$ kubectl get pod/details-v1-7d79f8b95d-5f4td -n bookinfons -o json|jq '.spec.containers[].image'
"docker.io/istio/examples-bookinfo-details-v1:1.16.2"
"docker.io/istio/proxyv2:1.13.1"

现在我们知道 istiod Pod 是基于 pilot-discovery,让我们来看一下 istiod 执行的一些功能。

配置监视

istiod 监视 Istio 自定义资源定义CRDs)以及发送到 Kubernetes API 服务器的任何其他与 Istio 相关的配置。任何此类配置都将被处理并在内部分发到 istiod 的各个子组件。你通过 Kubernetes API 服务器与 Istio 服务网格进行交互,但并非所有与 Kubernetes API 服务器的交互都是为了服务网格而进行的。

istiod 监视各种配置资源,通常是由 Kubernetes API 服务器识别的资源,通过某些特征(如标签、命名空间、注解等)进行识别。然后,这些配置更新会被拦截、收集,并转换为 Istio 特定格式,通过 Mesh 配置协议MCP)分发给 istiod 的其他组件。istiod 还实现了配置转发,我们将在后续章节中介绍它,当我们进行 Istio 的多集群安装时。现在,我们可以先说,istiod 也可以通过 MCP 在拉取和推送模式下,将配置传递给另一个 istiod 实例。

API 验证

istiod 还添加了一个准入控制器,以确保在 Istio 资源被 Kubernetes API 服务器接受之前,进行验证。在上一章中,我们看到两个准入控制器:mutating webhookvalidation webhook

mutating webhook 负责增强诸如部署等资源的 API 调用,通过为 Istio sidecar 注入添加配置。同样,validation webhook 会自动向 Kubernetes API 服务器注册自己,以便在每次调用 Istio CRD 时被触发。当添加/更新/删除 Istio CRD 的请求到达 Kubernetes API 服务器时,它们会被传递给 validation webhook,后者会验证传入的请求,并根据验证结果决定是否接受或拒绝该 API 调用。

Istio 证书颁发机构

Istio 为网格中的所有通信提供全面的安全保障。所有 Pod 都通过 Istio PKI 分配身份,并且采用 x.509 证书/密钥,以 Spiffe 可验证身份文档SVID)格式进行表示。Istio 证书颁发机构CA)负责签署与 istio-proxy 一起部署的节点代理发送的请求。Istio CA 基于 Citadel 构建,负责批准并签署 Istio 节点代理发送的 证书签名请求CSRs)。Istio CA 还执行证书和密钥的轮换和吊销工作。它支持不同 CA 的插件化,以及使用 Kubernetes CA 的灵活性。

Istio 控制平面的一些其他功能和组件如下:

  • Sidecar 注入:Istio 控制平面还通过 mutating webhooks 管理 sidecar 注入。

  • Istio 节点代理:节点代理与 Envoy 一起部署,负责与 Istio CA 进行通信,向 Envoy 提供证书和密钥。

  • 身份目录和注册表:Istio 控制平面管理用于各种工作负载的身份目录,这些工作负载将由 Istio CA 用来为请求的身份颁发密钥/证书。

  • 终端用户上下文传播:Istio 提供了一种安全机制,在 Ingress 上执行终端用户认证,然后将用户上下文传播到服务网格内的其他服务和应用程序。用户上下文以 JWT 格式传播,这有助于在不需要传递终端用户凭证的情况下,将用户信息传递到网格中的服务。

istiod 是一个关键的控制平面组件,执行控制平面中的许多重要功能,但它并不是唯一值得记住的控制平面组件。在下一节中,我们将探讨其他不属于 istiod 但仍然是 Istio 控制平面重要组成部分的组件。

Istio 操作员和 istioctl

Istio 操作员和 istioctl 都是控制平面组件,并且是可选安装的。它们都提供管理功能,用于安装和配置控制平面和数据平面的组件。在上一章中,您已经使用了不少 istioctl 作为命令行工具与 Istio 控制平面进行交互并传达指令。这些指令可以用于获取信息、创建、更新或删除与 Istio 数据平面工作相关的配置。Istio 操作员和 istioctl 本质上执行相同的功能,不同之处在于 istioctl 是显式调用以进行更改,而 Istio 操作员则按照 Kubernetes 的 operator 框架/模式运行。

我们不会使用 Istio 操作员,但如果您愿意,您可以使用以下命令来安装它:

$ istioctl operator init
Installing operator controller in namespace: istio-operator using image: docker.io/istio/operator:1.13.1
Operator controller will watch namespaces: istio-system
 Istio operator installed
 Installation complete

Istio 操作员的两个主要组件是名为 IstioOperator 的客户资源,表示为高级 API,以及一个控制器,它具有将高级 API 转换为低级 Kubernetes 操作的逻辑。IstioOperator CRD 封装了第二个组件 IstioOperatorSpec、一个状态字段以及一些附加元数据。

您可以使用以下命令来查找 IstioOperator 自定义 资源CR)的详细信息:

$ kubectl get istiooperators.install.istio.io -n istio-system -o json

您可以在此处找到命令的输出:github.com/PacktPublishing/Bootstrap-Service-Mesh-Implementations-with-Istio/blob/main/Output%20references/Chapter%203/IstioOperator%20CR.docx

如您在输出中所见,API 的结构与围绕基础 Kubernetes 资源、Pilot、Ingress 和 Egress 网关以及最终可选的第三方插件的控制平面组件一致。

图 3.2 – Istio 操作员

图 3.2 – Istio 操作员

上面的图表描述了 IstioOperator 的操作,下面的内容描述了 istioctl 的操作:

图 3.3 – istioctl

图 3.3 – istioctl

istioctl 和操作员非常相似,除了在 执行 阶段。istioctl 是用户运行的命令,它以 IstioOperator CR 作为输入,而控制器则在集群内的 IstioOperator CR 更改时运行,但其余组件相似,甚至是相同的。

以下是 Istio 操作员和 istioctl 各个组件的简要总结:

  • 执行:在事件发生时触发验证器组件,例如 CR 更新请求。对于 istioctl,执行逻辑通过操作员调用 istioctl CLI 来触发,istioctl CLI 是用 Go 编写的,使用 Cobra 库来创建强大的 CLI 应用程序。

  • 验证器:验证输入(IstioOperator CR)是否符合原始 CR 的模式。

  • 配置生成器:在此阶段,会创建完整的配置。配置包括原始事件中提供的参数和数值,以及原始事件中省略的参数。配置中包含了省略的参数及其各自的默认值。

  • 翻译器和渲染器:翻译器将 IstioOperator 的 Kubernetes 资源规格映射到 Kubernetes 资源,而渲染器则在应用所有配置后生成输出清单。

  • 资源管理器:该组件负责管理集群中的资源。它将资源的最新状态缓存到内置缓存中,然后与输出清单进行比较,每当 Kubernetes 对象(命名空间、CRDs、ServiceAccounts、ClusterRoles、ClusterRoleBindings、MutatingWebhookConfigurations、ValidatingWebhookConfigurations、Services、Deployments 或 ConfigMaps)的状态与输出清单之间存在偏差或不一致时,资源管理器将根据清单更新它们。

卸载 IstioOperator 的步骤

由于我们接下来不再使用 IstioOperator,建议使用以下命令卸载它:

$ istioctl operator remove

正在删除 Istio operator...

已移除 Deployment:istio-operator:istio-operator。

已移除 Service:istio-operator:istio-operator。

已移除 ServiceAccount:istio-operator:istio-operator。

已移除 ClusterRole::istio-operator。

已移除 ClusterRoleBinding::istio-operator。

删除完成

$ kubectl delete ns istio-operator

命名空间 "istio-operator" 已删除

我们在上一章简要了解了 istio-proxy。接下来,我们将查看 Istio 代理,它是部署在 istio-proxy 中的容器之一。

Istio 代理

Istio 代理(也叫 pilot-agent)是控制平面的一部分,部署在每个 istio-proxy 中,帮助通过安全地将配置和机密传递给 Envoy 代理来连接到服务网格。让我们通过列出 details-v1 的 istio-proxy sidecar 中所有运行的进程,来查看 bookinfo 中的一个微服务的 istio-agent:

$ kubectl exec -it details-v1-7d79f8b95d-5f4td -c istio-proxy -n bookinfons --/bin/sh -c "ps -ef"
UID          PID    PPID  C STIME TTY          TIME CMD
istio-p+       1       0  0 Mar14 ?        00:02:02 /usr/local/bin/pilot-agent p
istio-p+      15       1  0 Mar14 ?        00:08:17 /usr/local/bin/Envoy -c etc/

你一定注意到,pilot-agent 也在侧车中运行。pilot-agent 不仅初始化了 Envoy 代理,还为 Envoy 代理生成密钥和证书对,以便在网格通信中建立 Envoy 代理的身份。

在讨论 Istio 代理在证书生成中的作用之前,让我们简单谈一下 Istio 秘密发现服务SDS)。SDS 简化了证书管理,并最初由 Envoy 项目创建,目的是提供一个灵活的 API,将秘密/证书传递给 Envoy 代理。需要证书的组件称为 SDS 客户端,生成证书的组件称为 SDS 服务器。在 Istio 数据平面中,Envoy 代理充当 SDS 客户端,Istio 代理充当 SDS 服务器。SDS 客户端和 SDS 服务器之间的通信是通过 SDS API 规范进行的,通常通过 gRPC 实现。

下列步骤,如 图 3.4 所示,在 Istio 代理、Envoy 和 istiod 之间执行,以生成证书:

  1. 在侧车注入过程中,istiod 会将关于 SDS 的信息传递给 Envoy 代理,包括 SDS 服务器的位置。

  2. Envoy 通过 SDS 协议向 pilot-agent(SDS 服务器)发送请求,要求生成证书,使用 Unix 域套接字UDS)进行通信。pilot-agent 生成一个证书签名请求。

  3. 然后,pilot-agent 与 istiod 进行通信,提供其身份信息以及证书签名请求。

  4. istiod 对 pilot-agent 进行身份验证,如果一切正常,便签署证书。

  5. pilot-agent 通过 UDS 将证书和密钥传递给 Envoy 代理。

图 3.4 – Envoy 通信的证书生成

图 3.4 – Envoy 通信的证书生成

在这一节及前面的章节中,我们讨论了 Istio 控制平面。现在是时候介绍部署 Istio 控制平面的各种选项了。

Istio 控制平面的部署模型

在前面的章节中,我们在 minikube 上安装了 Istio,minikube 是一个本地集群,主要用于开发目的,运行在本地工作站上。当在企业环境中部署 Istio 时,部署将不会在 minikube 上,而是在企业级的 Kubernetes 集群上。服务网格可能运行在一个 Kubernetes 集群上,或者分布在多个 Kubernetes 集群之间。也可能所有服务都在一个网络中,或者位于不同的网络中,彼此之间没有直接连接。每个组织的网络和基础设施布局不同,Istio 的部署模式也会因此而有所不同。

什么是集群?

集群的定义有很多,具体取决于所处的上下文。在本节中,当我们提到集群时,基本上是指一组计算节点,这些节点托管着彼此互联的容器化应用程序。你也可以将集群视为 Kubernetes 集群。

我们将在第八章讨论 Istio 的各种架构选项,但现在,让我们简要地回顾一下控制平面的各种部署模型。

单集群与本地控制平面

集群中所有命名空间中的所有边车代理连接到部署在同一集群中的控制平面。类似地,控制平面正在观察、监控并与其部署所在集群中的 Kubernetes API 服务器和边车进行通信。

图 3.5 – 数据平面和控制平面位于同一 Kubernetes 集群中

图 3.5 – 数据平面和控制平面位于同一 Kubernetes 集群中

上述插图描述了我们在上一章中用于部署 Istio 的部署模型。从图中可以看出,Istio 控制平面和数据平面都位于同一 Kubernetes 集群中;在我们的案例中,它是 minikube。istiod 安装在istio-system命名空间或您选择的其他命名空间中。数据平面由多个命名空间组成,应用程序与 istio-proxy 边车一同部署。

主集群和远程集群共享单一控制平面

数据平面和控制平面部署在同一 Kubernetes 集群中的服务网格集群,也称为主集群。控制平面与数据平面不共存的集群称为远程集群

在这种架构中,有一个主集群和一个远程集群,它们共享一个共同的控制平面。使用这种模型时,需要额外配置,以提供主集群中的控制平面和远程集群中的数据平面之间的互联。通过添加 Ingress 网关来保护并路由通信到主控制平面,可以实现远程集群与主集群控制平面之间的连接。这在以下图示中有所展示:

图 3.6 – 单集群控制平面,数据平面分布在多个 Kubernetes 集群中

图 3.6 – 单集群控制平面,数据平面分布在多个 Kubernetes 集群中

Istio 控制平面还需要配置,以建立以下通信:

  • 与远程平面 Kubernetes API 服务器通信

  • 将突变 Webhook 修补到远程平面,以监控配置为自动注入 istio-proxy 的命名空间

  • 为来自远程平面 Istio 代理的 CSR 请求提供端点

单集群与外部控制平面

在这种配置中,您可以将控制平面与数据平面分开,而不是在同一个 Kubernetes 集群中同时运行控制平面和数据平面。这是通过将控制平面远程部署到一个 Kubernetes 集群中,并将数据平面部署到其自己的专用 Kubernetes 集群来实现的。这个部署可以通过以下图示来查看:

图 3.7 – 控制平面和数据平面分别位于不同的 Kubernetes 集群中

图 3.7 – 控制平面和数据平面分别位于不同的 Kubernetes 集群中

为了安全性、关注点分离以及合规性要求(如联邦信息处理标准FIPS)),我们可能需要将 Istio 控制平面与数据平面分开部署。将控制平面与数据平面分离,可以在不影响数据平面流量的情况下,严格执行控制平面流量的流量和访问策略。此外,在企业环境中,如果有团队可以将控制平面作为托管服务提供给项目团队,那么这种部署控制平面的模型非常适合。

到目前为止,我们讨论的部署模型都位于共享网络内的一个或多个 Kubernetes 集群中。当网络不共享时,部署模型变得更加复杂。我们将在第十章中通过一些动手练习,详细讨论这些部署模型以及本章中涉及的模型。

在下一节中,我们将通过了解 Envoy 来研究 Istio 数据平面。

探索 Envoy,Istio 数据平面

Envoy 是 Istio 数据平面的关键组件。要理解 Istio 数据平面,了解和认识 Envoy 是非常重要的。

Envoy 是一个开源项目,并且是 CNCF 的毕业项目。你可以在www.cncf.io/projects/Envoy/找到更多关于 Envoy 作为 CNCF 项目的详细信息。在本节中,我们将学习 Envoy,并了解为什么它被选为 Istio 数据平面的服务代理。

什么是 Envoy?

Envoy 是一个轻量级、高性能的第 7 层和第 4 层代理,具有易于使用的配置系统,使其高度可配置,适合作为 API 网关架构模式中的独立边缘代理,也可以作为 Service Mesh 架构模式中的 sidecar 运行。在这两种架构模式中,Envoy 以单独的进程形式与应用程序/服务并行运行,这使得它更容易升级和管理,同时也使得 Envoy 可以在整个基础设施中透明地进行部署和升级。

为了理解 Envoy,我们来看一下以下三个使 Envoy 与今天市面上其他代理不同的特点。

线程模型

Envoy 架构的亮点之一是其独特的线程模型。在 Envoy 中,大多数线程异步运行,彼此不阻塞。与其为每个连接分配一个线程,不如让多个连接共享同一个工作线程,并以非阻塞的顺序运行。这个线程模型帮助异步处理请求,但以非阻塞的方式,从而实现非常高的吞吐量。

大致来说,Envoy 有三种类型的线程:

  • 主线程:负责 Envoy 和 xDS 的启动与关闭(更多关于 xDS 的内容将在下一节介绍),API 处理、运行时管理和一般的进程管理。主线程协调所有管理功能,通常不需要过多的 CPU 资源。因此,与一般管理相关的 Envoy 逻辑是单线程的,使得代码库更容易编写和管理。

  • 工作线程:通常,每个 CPU 核心或每个硬件线程(如果 CPU 支持超线程)运行一个工作线程。工作线程打开一个或多个网络位置(端口、套接字等),以供下游系统连接;Envoy 的这个功能称为 监听。每个工作线程运行一个非阻塞的事件循环,用于执行监听、过滤和转发操作。

  • 文件刷新线程:该线程负责以非阻塞的方式将数据写入文件。

架构

Envoy 架构的另一个亮点是其过滤器架构。Envoy 也是一个 L3/L4 网络代理,具有可插拔的过滤器链,可以编写过滤器来执行不同的 TCP/UDP 任务。过滤器链 本质上是一组步骤,每个步骤的输出作为输入传递到下一个步骤,就像 Linux 中的管道一样。你可以通过堆叠所需的过滤器来构建逻辑和行为,形成一个过滤器链。Envoy 提供了许多开箱即用的过滤器来支持任务,如原始 TCP 代理、UDP 代理、HTTP 代理和 TLS 客户端证书认证。Envoy 还支持额外的 HTTP L7 过滤器层。通过过滤器,我们可以执行不同的任务,如缓冲、速率限制、路由、转发等。

Envoy 支持 HTTP 1.1 和 HTTP 2,并且能够在这两种 HTTP 协议中作为透明代理运行。当你有支持 HTTP 1.1 的旧版应用程序时,这一点尤其有用;你可以将其与 Envoy 代理一起部署,从而实现协议转换——也就是说,应用程序可以与 Envoy 使用 HTTP 1.1 通信,而 Envoy 会使用 HTTP 2 与其他系统通信。Envoy 支持一个全面的路由子系统,提供灵活的路由和重定向功能,适用于构建 Ingress/Egress API 网关,也可以作为侧车模式下的代理部署。

Envoy 还支持现代协议,如 gRPC。gRPC 是一个开源的远程过程调用框架,可以在任何地方运行。它广泛用于服务间通信,性能优越且易于使用。

配置

Envoy 的另一个亮点是它的配置方式。我们可以使用静态配置文件来配置 Envoy,这些文件描述了服务及其通信方式。在一些静态配置 Envoy 不切实际的高级场景下,Envoy 支持动态配置,并且能够在运行时自动重新加载配置,无需重启。通过名为 xDS 的一组发现服务,可以通过网络动态配置 Envoy,并提供有关主机、集群、HTTP 路由、监听套接字和加密材料的 Envoy 信息。这使得为 Envoy 编写不同类型的控制平面成为可能。控制平面基本上实现了 xDS API 的规范,并保持各种资源和信息的最新状态,供 Envoy 通过 xDS APIs 动态获取。对于 Envoy,有许多开源的控制平面实现,以下是其中几个:

各种服务网格实现,如 Istio、Kuma、Gloo 等,它们将 Envoy 作为 sidecar,使用 xDS API 为 Envoy 提供配置信息。

Envoy 还支持以下内容:

  • 自动重试:Envoy 支持对请求进行任意次数的重试,或者在重试预算内进行重试。根据应用需求,可以为请求配置特定的重试条件。如果你想进一步了解重试机制,可以访问 www.abhinavpandey.dev/blog/retry-pattern

  • 断路器:断路器对于微服务架构至关重要。Envoy 在网络层面提供断路器功能,从而保护上游系统,确保所有 HTTP 请求执行的安全性。Envoy 提供多种基于配置的断路器限制,如最大连接数、最大待处理请求数、最大请求数、最大活动重试次数和上游系统支持的最大并发连接池等。有关断路器模式的更多详情,请访问 microservices.io/patterns/reliability/circuit-breaker.html

  • 全局速率限制:Envoy 支持全局速率限制,以防止下游系统对上游系统造成过载。速率限制可以在网络层面或 HTTP 请求层面执行。

  • 流量镜像:Envoy 支持将流量从一个集群镜像到另一个集群。这在测试以及众多其他应用场景中非常有用,比如机器学习。一个网络层面的流量镜像示例是 AWS VPC,它提供将所有流量镜像到 VPC 的选项。你可以阅读有关 AWS 流量镜像的内容,链接地址是 docs.aws.amazon.com/vpc/latest/mirroring/what-is-traffic-mirroring.html

  • 异常检测:Envoy 支持动态检测不健康的上游系统,并将其从健康的负载均衡集中过滤掉。

  • 请求预处理:Envoy 支持请求预处理,以通过向多个上游系统发出请求并将最合适的响应返回给下游系统来应对尾延迟。你可以在medium.com/star-gazers/improving-tail-latency-with-request-hedging-700c77cabeda了解更多关于请求预处理的信息。

我们之前讨论过,基于过滤器链的架构是 Envoy 的一个差异化特点。现在让我们了解一下构成过滤器链的那些过滤器。

HTTP 过滤器

HTTP 是最常见的应用协议之一,许多工作负载大多数情况下都通过 HTTP 进行操作。为了支持 HTTP,Envoy 附带了各种 HTTP 级别的过滤器。

配置 Envoy 时,您将主要处理以下配置:

  • Envoy 监听器:这些是下游系统连接的端口、套接字和任何其他命名的网络位置

  • Envoy 路由:这些是 Envoy 配置,描述流量如何路由到上游系统

  • Envoy 集群:这些是由一组相似的上游系统组成的逻辑服务,Envoy 将请求路由或转发到这些系统

  • Envoy 端点:这些是单个上游系统,用于处理请求

重要提示

我们现在将使用 Docker 来操作 Envoy。如果你正在运行 minikube,最好现在停止 minikube。如果你没有安装 Docker,可以按照docs.docker.com/get-docker/上的说明进行安装。

利用到目前为止获得的知识,我们来创建一些 Envoy 监听器。

下载envoy Docker 镜像:

$ docker pull envoyproxy/envoy:v1.22.2

一旦你拉取了 Docker 镜像,就可以从本章的 Git 仓库中运行以下命令:

docker run –rm -it -v $(pwd)/envoy-config-1.yaml:/envoy-custom.yaml -p 9901:9901 -p 10000:10000 envoyproxy/envoy:v1.22.2 -c /envoy-custom.yaml

在前面的命令中,我们将envoy-config-1.yaml文件挂载为卷,并通过-c选项将其传递给 Envoy 容器。我们还将10000端口暴露给本地主机,并将其映射到 Envoy 容器的10000端口。

现在让我们检查一下envoy-config-1.yaml的内容。Envoy 配置的根节点称为引导配置。第一行描述它是静态配置还是动态配置。在这个实例中,我们通过指定static_resources来提供静态配置:

Static_resources:
  listeners:
  - name: listener_http

在这个实例中,配置非常简单。我们定义了一个名为listener_http的监听器,它在0.0.0.0和端口10000上监听传入的请求:

Listeners:
  - name: listener_http
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 10000

我们没有应用任何特定于监听器的过滤器,但我们应用了一个名为HTTPConnectionManager的网络过滤器,简称 HCM:

Filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: chapter3-1_service

HCM 过滤器能够将原始字节转换为 HTTP 级别的消息。它可以处理访问日志记录,生成请求 ID,操作头部,管理路由表,并收集统计信息。Envoy 还支持在 HCM 过滤器内定义多个 HTTP 级别的过滤器。我们可以在 http_filters 字段下定义这些 HTTP 过滤器。

在以下配置中,我们已经应用了一个 HTTP 路由器过滤器:

http_filters:
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
          route_config:
            name: my_first_route_to_nowhere
            virtual_hosts:
            - name: dummy
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                direct_response:
                  status: 200
                  body:
                    inline_string: "Bootstrap Service Mesh Implementations with Istio"

路由器过滤器负责执行路由任务,也是在 HTTP 过滤器链中应用的最后一个过滤器。路由器过滤器在 route_config 字段下定义路由。在路由配置中,我们可以通过查看 URI、头部等元数据来匹配传入请求,并基于此定义流量应路由或处理到哪里。

路由配置中的顶层元素是虚拟主机。每个虚拟主机都有一个用于发出统计信息(不用于路由)的名称和一组域名,这些域名将被路由到它。在 envoy-config-1.yaml 中,对于所有请求,无论主机头如何,都将返回硬编码的响应。

要检查 envoy-config1.yaml 的输出,您可以使用 curl 来测试响应:

$ curl localhost:10000
Bootstrap Service Mesh Implementations with Istio

让我们在 envoy-config1.yamlroute_config 中操作虚拟主机定义:

          route_config:
            name: my_first_route_to_nowhere
            virtual_hosts:
            - name: acme
              domains: ["acme.com"]
              routes:
              - match:
                  prefix: "/"
                direct_response:
                  status: 200
                  body:
                    inline_string: "Bootstrap Service Mesh Implementations with Istio And Acme.com"
            - name: ace
              domains: ["acme.co"]
              routes:
              - match:
                  prefix: "/"
                direct_response:
                  status: 200
                  body:
                    inline_string: "Bootstrap Service Mesh Implementations with Istio And acme.co"

在这里,我们在 virtual_hosts 下定义了两个条目。如果传入请求的主机头是 acme.com,那么将处理在 acme 虚拟主机下定义的路由。如果传入请求是为 acme.co,那么将处理在 ace 虚拟主机下定义的路由。

停止 Envoy 容器并使用以下命令重新启动它:

docker run –rm -it -v $(pwd)/envoy-config-1.yaml:/envoy-custom.yaml -p 9901:9901 -p 10000:10000 envoyproxy/envoy:v1.22.2 -c /envoy-custom.yaml

通过将不同的主机头传递给 curl 来检查输出:

$ curl -H host:acme.com localhost:10000
Bootstrap Service Mesh Implementations with Istio And Acme.com
$ curl -H host:acme.co localhost:10000
Bootstrap Service Mesh Implementations with Istio And acme.co

在大多数情况下,您不会对 HTTP 请求发送硬编码的响应。实际情况是,您希望将请求路由到真实的上游服务。为了演示这种情况,我们将使用 nginx 模拟一个虚拟的上游服务。

使用以下命令运行 nginx Docker 容器:

docker run -p 8080:80 nginxdemos/hello:plain-text

使用 curl 从另一个终端检查输出:

$ curl localhost:8080
Server address: 172.17.0.3:80
Server name: a7f20daf0d78
Date: 12/Jul/2022:12:14:23 +0000
URI: /
Request ID: 1f14eb809462eca57cc998426e73292c

我们将通过使用集群子系统配置来将 Envoy 处理的请求路由到 nginx。而 Listener 子系统配置则处理下游请求处理和管理下游请求生命周期,集群子系统负责选择并连接到端点的上游连接。在集群配置中,我们定义集群和端点。

让我们编辑 envoy-config-2.yaml 并修改 acme.co 的虚拟主机:

                - name: ace
              domains: ["acme.co"]
              routes:
              - match:
                  prefix: "/"
                route:
                  cluster: nginx_service
  clusters:
  - name: nginx_service
    connect_timeout: 5s
    load_assignment:
      cluster_name: nginx_service
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 172.17.0.2
                port_value: 80

我们已删除 direct_response 属性,并替换为以下内容:

route:
                  cluster: nginx_service

我们已经在定义中添加了集群,它与监听器配置处于同一层级。在集群定义中,我们定义了端点。在这种情况下,端点是运行在80端口上的nginx Docker 容器。请注意,我们假设 Envoy 和 nginx 运行在同一个 Docker 网络上。

你可以通过检查容器来找到nginx容器的 IP 地址。配置保存在envoy-config-3.yaml文件中。请使用nginx容器的正确 IP 地址更新address值,并使用更新后的envoy-config-3.yaml运行 Envoy 容器:

$ docker run –rm -it -v $(pwd)/envoy-config-3.yaml:/envoy-custom.yaml -p 9901:9901 -p 10000:10000 envoyproxy/envoy:v1.22.2 -c /envoy-custom.yaml

执行curl测试,你会注意到请求目标为acme.co的响应来自 nginx 容器:

$ curl -H host:acme.com localhost:10000
Bootstrap Service Mesh Implementations with Istio And Acme.com
$ curl -H host:acme.co localhost:10000
Server address: 172.17.0.2:80
Server name: bfe8edbee142
Date: 12/Jul/2022:13:05:50 +0000
URI: /
Request ID: 06bbecd3bc9901d50d16b07135fbcfed

Envoy 提供了多个内置的 HTTP 过滤器。你可以在此查看完整的 HTTP 过滤器列表:www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/http_filters#config-http-filters

)

监听器过滤器

我们之前提到过,监听器子系统负责处理传入请求及与下游系统的响应。除了定义 Envoy 为接收传入请求而监听的地址和端口之外,我们还可以选择性地为每个监听器配置监听器过滤器。监听器过滤器作用于新接受的套接字,可以停止或继续执行进一步的过滤器。

监听器过滤器的顺序很重要,因为 Envoy 会按顺序处理它们:在监听器接受一个套接字之后,连接创建之前。我们使用监听器过滤器的结果来进行过滤器匹配,从而选择合适的网络过滤器链。例如,通过使用监听器过滤器,我们可以确定协议类型,并基于此运行与该协议相关的特定网络过滤器。

让我们来看一个简单的envoy-config-4.yaml中的监听器过滤器示例,位于listener_filters下。你会注意到,我们使用了以下类型的envoy.filters.listener.http_inspectortype.googleapis.com/envoy.extensions.filters.listener.http_inspector.v3.HttpInspector

HTTPInspector监听器过滤器可以检测底层应用协议,并判断它是HTTP/1.1还是HTTP/2。你可以在此阅读有关HTTPInspector监听器过滤器的更多信息:https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/listener_filters/http_inspector。

在这个示例中,我们使用监听器过滤器通过过滤器链找到应用协议。根据下游系统使用的 HTTP 协议,我们然后应用各种 HTTP 过滤器,如前面章节所讨论的。

你可以在envoy-config-4.yaml文件中找到这个示例。继续应用配置到 Envoy,但也记得关闭你为之前的示例创建的 Docker 容器:

$ docker run –rm -it -v $(pwd)/envoy-config-4.yaml:/envoy-custom.yaml -p 9901:9901 -p 10000:10000 envoyproxy/envoy:v1.22.2 -c /envoy-custom.yaml

使用curl命令并指定HTTP 1.1HTTP 2协议,你会发现 Envoy 能够自动识别应用协议,并将请求路由到正确的目标:

$ curl localhost:10000 –http1.1
HTTP1.1
$ curl localhost:10000 –http2-prior-knowledge
HTTP2

正如我在介绍 Envoy 时提到的,它是高度可配置的,并且可以动态配置。我相信 Envoy 的动态可配置性正是它如此受欢迎并且使它在今天的其他代理中脱颖而出的原因。接下来我们将深入探讨这一点!

通过 xDS API 进行动态配置

到目前为止,在我们之前的示例中,我们一直通过在配置文件开头指定static_resources来使用静态配置。每次我们想要更改配置时,都必须重新启动 Envoy 容器。为了避免这种情况,我们可以使用动态配置,让 Envoy 通过从磁盘或网络读取配置来动态重新加载配置。

对于动态配置,Envoy 通过网络获取配置,我们需要使用 xDS API,xDS API 基本上是一组与各种 Envoy 配置相关的服务发现 API。为了使用 xDS API,你需要实现一个应用程序,它能够获取各种 Envoy 配置的最新值,然后通过 gRPC # 按照 xDS protobuf 规范(也叫协议缓冲区;你可以在developers.google.com/protocol-buffers找到关于协议缓冲区的详细信息,以及更多关于 gRPC 的信息在grpc.io/)进行呈现。这个应用程序通常被称为控制平面。以下图表描述了这一概念。

图 3.8 – xDS API 控制平面实现

图 3.8 – xDS API 控制平面实现

让我们来看看服务发现 API 提供了什么:

  • 秘密发现服务SDS):提供密钥信息,如证书和私钥。这对于 MTLS、TLS 等是必需的。

  • 端点发现服务EDS):提供集群成员的详细信息。

  • 集群发现服务CDS):提供与集群相关的信息,包括对端点的引用。

  • 范围路由发现服务SRDS):当路由确认较大时,按块提供路由信息。

  • 监听器发现服务LDS):提供关于监听器的详细信息,包括端口、地址以及所有相关的过滤器。

  • 扩展配置发现服务ECDS):提供扩展配置,如 HTTP 过滤器等。此 API 帮助从监听器独立地获取信息。

  • 路由发现服务RDS):提供包括对集群的引用在内的路由信息。

  • 虚拟主机发现服务VHDS):提供有关虚拟主机的信息。

  • 运行时发现服务RTDS):该服务提供有关运行时的信息。运行时配置指定了一个虚拟文件系统树,包含可重载的配置元素。这个虚拟文件系统可以通过一系列本地文件系统、静态引导配置、RTDS 和管理控制台派生的覆盖层来实现。

  • 聚合发现服务ADS):ADS 允许通过一个单一的 API 接口交付所有 API 及其资源。通过 ADS API,你可以对涉及多种资源类型的变化(包括监听器、路由和集群)进行排序,并通过单一流传送它们。

  • 增量聚合发现服务DxDS):与其他 API 不同,每次资源更新时,API 需要将所有资源包含在响应中。例如,每次 RDS 更新时,必须包含所有路由。如果我们没有包含某个路由,Envoy 会认为该路由已经被删除。采用这种方式更新会导致高带宽使用和计算成本,尤其是在大量资源通过网络传输时。Envoy 支持 xDS 的增量变体,在这种方式下,我们只需包含我们想要添加/删除/更新的资源,从而改善这一场景。

我们在上一节中介绍了 Envoy 过滤器,但请注意,你并不局限于使用内建的过滤器——你可以轻松地构建新的过滤器,正如我们在下一节中将看到的那样。

可扩展性

Envoy 的过滤器架构使其具有高度可扩展性;你可以利用过滤器库中的各种过滤器作为过滤器链的一部分。当你需要某些过滤器库中没有的功能时,Envoy 还提供了编写自定义过滤器的灵活性,然后由 Envoy 动态加载并像任何其他过滤器一样使用。默认情况下,Envoy 过滤器是用 C++ 编写的,但它们也可以使用 Lua 脚本或任何其他编译为 WebAssemblyWasm)的编程语言编写。

以下是目前可用于编写 Envoy 过滤器的三种选项的简要描述:

  • 原生 C++ API:最理想的选项是编写原生的 C++ 过滤器,然后将其与 Envoy 一起打包。但这种方式需要重新编译 Envoy,如果你不是一个大企业,且不打算维护自己版本的 Envoy,这可能不是理想的选择。

  • envoy.filters.http.lua 允许你定义一个 Lua 脚本,可以是内联的,也可以是外部文件,并在请求和响应流程中执行。Lua 是一种自由、快速、便携且强大的脚本语言,运行在 LuaJIT 上,后者是一个为 Lua 提供即时编译的编译器。在运行时,Envoy 会为每个工作线程创建一个 Lua 环境,并以协程的形式运行 Lua 脚本。由于 HTTP Lua 过滤器在请求和响应流程中执行,你可以做到以下几点:

    • 在请求/响应流程中检查和修改头部和尾部

    • 在请求/响应流程中检查、拦截或缓冲主体

    • 异步调用上游系统

  • Wasm 过滤器:最后但同样重要的是基于 Wasm 的过滤器。我们使用自己喜欢的编程语言编写这些过滤器,然后将代码编译成类似低级汇编语言的编程语言,称为 Wasm,随后 Envoy 会在运行时动态加载这些过滤器。Wasm 被广泛应用于开放网络中,它在 Web 浏览器中的 JavaScript 虚拟机内执行。Envoy 嵌入了 V8 虚拟机的一个子集(v8.dev/),使每个工作线程能够执行 Wasm 模块。我们将在第九章中深入了解 Wasm 并进行实际操作。

写自定义过滤器的能力使 Envoy 足够可扩展,可以实现自定义用例。对基于 Wasm 的过滤器的支持降低了编写新过滤器的学习曲线,因为你可以使用你最熟悉的编程语言。我们希望随着 Envoy 的广泛应用,开发者可以获得更多工具,以便轻松使用自定义过滤器扩展它。

总结

本章向你详细介绍了 Istio 控制平面组件,包括 istiod 及其架构。我们还了解了 Istio 操作员、CLI 以及证书分发的工作原理。Istio 控制平面可以部署在多种架构模式下,我们也概述了一些这些部署模式。

在讲解 Istio 控制平面之后,我们了解了 Envoy,这是一款轻量级、高性能的 l3/l4/l7 代理。它通过监听器和集群子系统提供一系列配置来控制请求处理。基于过滤器的架构易于使用且可扩展,因为新的过滤器可以用 Lua、Wasm 或 C++ 编写,并且可以轻松地插入到 Envoy 中。最后,Envoy 支持通过 xDS API 进行动态配置的能力也是其一大亮点。由于其在充当代理时的灵活性和性能,以及通过由 Istio 控制平面实现的 xDS API 易于配置,Envoy 是 Istio 数据平面的最佳选择。前一章中讨论的 istio-proxy 由 Envoy 和 Istio 代理组成。

在下一章中,我们将暂时搁置 Istio,转而沉浸于体验一个实际的应用场景。我们将把该应用带入类似生产环境的环境,并讨论工程师在构建和运营此类应用时可能面临的问题。在本书的第二部分第三部分中,我们将通过实践操作使用这个应用。所以,系好安全带,准备迎接下一章。

第二部分:Istio 实践

本部分描述了 Istio 的应用及其如何用于管理应用流量、提供应用弹性,并确保微服务之间的通信安全。在众多实践案例的帮助下,你将了解各种 Istio 流量管理概念,并使用它们进行应用网络配置。本部分最后以可观测性章节作为总结,介绍了如何观察服务网格,利用它了解系统行为及故障背后的根本原因,从而能够自信地进行故障排除并分析潜在修复方案的效果。

本部分包含以下章节:

  • 第四章管理应用流量

  • 第五章管理应用弹性

  • 第六章确保微服务通信安全

  • 第七章服务网格可观测性

第四章:管理应用流量

微服务架构创建了一个由松散耦合的应用程序组成的扩展系统,这些应用程序以容器的形式部署在如 Kubernetes 等平台上。由于应用程序之间的松散耦合,服务间的流量管理变得复杂。如果暴露给外部系统不安全,可能导致敏感数据的泄露,从而使系统容易受到外部威胁。Istio 提供了多种机制来保护和管理以下类型的应用流量:

  • 来自外部的流量访问您的应用程序

  • 在应用程序各个组件之间生成的跨网格流量

  • 从您的应用程序到网格外其他应用程序的 Egress 流量

在本章中,我们将通过以下详细的主题来学习和实践管理应用流量。

  • 使用 Kubernetes Ingress 资源和 Istio Gateway 管理 Ingress 流量

  • 流量路由和金丝雀发布

  • 流量镜像

  • 将流量路由到网格外的服务

  • 通过 HTTPS 暴露 Ingress

  • 管理 Egress

删除 Istio 并重新安装它是个好主意,这样可以获得一个干净的环境,并且复习在第二章中学到的内容。Istio 每三个月发布一个小版本,如istio.io/latest/docs/releases/supported-releases/中所述;因此,建议使用 Istio 网站上的文档(istio.io/latest/docs/setup/getting-started/#download)保持 Istio 版本更新,并结合在第二章中学到的概念。

技术要求

在本节中,我们将创建一个 AWS 云设置,用于在本章和后续章节中进行动手操作练习。您可以使用任何您选择的云提供商,但为了在本书中引入一些多样性,我选择了 AWS 作为第二部分的练习环境,Google Cloud 作为第三部分的练习环境。您也可以使用 minikube 进行练习,但您需要至少一个四核处理器和分配给 minikube 16 GB 或更多的内存,以保证操作流畅且无延迟。

设置环境

让我们开始吧!

  1. 创建一个 AWS 账户。如果你还没有 AWS 账户,是时候使用portal.aws.amazon.com/billing/signup#/start/email注册 AWS 账户了。

  2. 设置 AWS CLI:

    1. 按照docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html中提供的步骤安装 AWS CLI。

    2. 按照docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html中提供的步骤配置 AWS CLI。

  3. 按照docs.aws.amazon.com/eks/latest/userguide/install-aws-iam-authenticator.html提供的步骤安装 AWS IAM 验证器。

  4. 安装 Terraform,它是一个基础设施即代码软件,可以自动化基础设施的配置。这有助于你创建与本书中的练习所用基础设施一致的基础设施。我希望这能为你提供一个无忧的体验,让你能更多时间学习 Istio,而不是解决基础设施问题。按照learn.hashicorp.com/tutorials/terraform/install-cli?in=terraform/aws-get-started提供的步骤进行操作。

创建 EKS 集群

接下来,你需要创建一个 EKS 集群。在本书的源代码仓库中的sockshop/devops/deploy/terraform目录下创建。

sockshop/devops/deploy/terraform/src文件夹中执行以下步骤:

  1. 初始化 Terraform。准备工作目录,使 Terraform 能够运行配置:

    % terraform init
    Initializing the backend...
    Initializing provider plugins...
    - Reusing previous version of hashicorp/aws from the dependency lock file
    - Using previously-installed hashicorp/aws v4.26.0
    Terraform has been successfully initialized!
    

你可以在developer.hashicorp.com/terraform/tutorials/cli/init上阅读有关init的更多信息。

  1. 通过修改sockshop/devops/deploy/terraform/src/variables.tf配置 Terraform 变量。默认值可以使用,但你也可以根据需求修改它们。

  2. 规划部署。在这一步中,Terraform 会创建一个执行计划,你可以检查该计划,以查找任何不一致之处并预览基础设施,尽管它还未被配置。

以下代码片段中的输出已被简化,以节省空间:

% terraform plan
………
~ cluster_endpoint       = "https://647937631DD1A55F1FDDAB99E08DEE0C.gr7.us-east-1.eks.amazonaws.com" -> (known after apply)

你可以在developer.hashicorp.com/terraform/tutorials/cli/plan上阅读更多关于plan的信息。

  1. 配置基础设施。在这一步中,Terraform 会根据前面创建的执行计划来创建基础设施:

    % terraform apply
    

一旦基础设施配置完成,Terraform 还会设置 sockshop/devops/deploy/terraform/src/outputs.tf中定义的变量。

你可以在developer.hashicorp.com/terraform/tutorials/cli/apply上阅读更多关于apply的信息。

设置 kubeconfig 和 kubectl

接下来,我们将配置 kubectl,以便通过 Terraform 连接到新创建的 EKS 集群。使用以下 aws cli 命令更新 kubeconfig 以包含集群详情:

% aws eks --region $(terraform output -raw region) update-kubeconfig --name $(terraform output -raw cluster_name)

接下来,检查 kubectl 是否使用正确的上下文:

% kubectl config current-context

如果值不符合预期,你可以执行以下操作:

% kubectl config view -o json | jq '.contexts[].name'
"arn:aws:eks:us-east-1:803831378417:cluster/MultiClusterDemo-Cluster1-cluster"
"minikube"

查找集群的正确名称,然后使用以下命令设置 kubectl 上下文:

% kubectl config use-context "arn:aws:eks:us-east-1:803831378417:cluster/MultiClusterDemo-Cluster1-cluster"
Switched to context "arn:aws:eks:us-east-1:803831378417:cluster/MultiClusterDemo-Cluster1-cluster".

在某些情况下,你需要使用 minikube。在这种情况下,只需使用以下命令切换上下文,反之亦然,切换回 EKS:

% kubectl config use-context minikube
Switched to context "minikube".

部署 Sockshop 应用程序

最后,为了增加实践练习的多样性,我们将使用一个名为 Sockshop 的演示应用程序,该应用程序可以在github.com/microservices-demo/microservices-demo找到。您可以在sockshop/devops/deploy/kubernetes/manifests中找到部署文件:

% kubectl create -f sockshop/devops/deploy/kubernetes/manifests/00-sock-shop-ns.yaml
% kubectl create -f  sockshop/devops/deploy/kubernetes/manifests/* -n sock-shop

这将部署 Sockshop 应用程序,您已准备好环境。接下来的步骤是按照istio.io/latest/docs/setup/install/istioctl/的说明安装 Istio 的最新版本,并应用您在第二章中学习的概念。

在本章和本书的其余部分,我们将使用 Sockshop 应用程序来演示各种 Service Mesh 概念。您也可以随意使用 Istio 附带的示例BookInfo应用程序,或者任何您喜欢的应用程序来进行实践练习。让我们开始吧!

使用 Kubernetes Ingress 资源管理 Ingress 流量

当构建需要由外部网络边界之外的其他应用程序访问的应用程序时,您需要创建一个 Ingress 点,消费者可以通过该点访问应用程序。在 Kubernetes 中,Service 是一个抽象,通过它可以将一组 Pod 暴露为网络服务。当这些服务需要被其他应用程序使用时,它们需要对外部可访问。Kubernetes 支持ClusterIP用于从集群内部访问服务,NodePort用于在集群外部但在网络内访问服务,LoadBalancer用于通过云负载均衡器外部访问服务,还可以选择暴露一个面向内部流量的负载均衡器,供 Kubernetes 集群外的内部流量使用。在本节中,我们将了解如何配置 Istio,通过 Kubernetes Ingress 资源暴露服务。

在前一章中,我们将前端服务暴露为NodePort类型,并通过 minikube tunnel 以及 AWS Loadbalancer访问它。这个方法剥夺了我们对如何管理前端服务流量的控制。

因此,除了使用Loadbalancer服务类型来暴露前端服务外,让我们将前端服务设置为内部服务,并改为使用 Kubernetes Ingress 资源。通过从 YAML 文件中删除以下行,更新前端服务的 Kubernetes 配置,移除NodePort(如果使用 minikube)或LoadBalancer(如果在 AWS 上部署):

type: NodePort
…..
    nodePort: 30001

之前的更改使得服务类型采用了ClusterIp的默认值。

更新后的文件也可以在Chapter4文件夹中找到,文件名为10-1-front-end-svc.yaml

继续使用以下命令将前端服务类型更改为ClusterIP

$ kubectl apply -f Chapter4/ClusterIp-front-end-svc.yaml

变更后,你会注意到,由于明显的原因,浏览器无法访问 Sockshop 网站。

现在,我们将利用 Kubernetes Ingress 资源来为 Sockshop 前端服务提供访问。Kubernetes Ingress 是为集群中的 ClusterIP 服务提供访问的一种方式。Ingress 定义了 Ingress 接受的目标主机,并列出了 URI 和请求需要路由到的服务。以下是突出此概念的图示:

图 4.1 – Kubernetes Ingress 资源

图 4.1 – Kubernetes Ingress 资源

在定义 Ingress 时,我们还需要定义 Ingress 控制器,Ingress 控制器是另一种 Kubernetes 资源,负责根据 Ingress 资源中定义的规范处理流量。

以下图示了 Ingress、Ingress 控制器和服务之间的关系。请注意,Ingress 是一个逻辑构造——也就是说,它是由 Ingress 控制器强制执行的一组规则。

图 4.2 – Ingress 控制器

图 4.2 – Ingress 控制器

接下来,我们将使用 Istio Gateway 控制器来处理 Ingress;我们在第三章中介绍了 Istio Gateway。

我们需要提供以下配置,也定义在 Chapter4/1-istio-ingress.yaml 中,以进行更改:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: istio
  name: sockshop-istio-ingress
  namespace: sock-shop
spec:
  rules:
  - host: "sockshop.com"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: front-end
            port:
              number: 80

在前面的配置中,我们正在执行以下操作:

  • 创建一个带有 kubernetes.io/ingress.class: istio 注解的 Ingress 资源,通过 admission controllers(我们在第三章中讨论过)告诉 Istio,该 Ingress 由 Istio Gateway 处理。

  • Ingress 资源在 sock-shop 命名空间中定义,因为我们的 Sockshop 前端服务就存在于这个命名空间中。

  • 规定任何指定为 / 路径并且目标主机为 "sockshop.com"(由 hostsockshop.com 值指定)的请求应由该 Ingress 处理的规则。

  • path 配置中,我们正在配置 pathTypePrefix,这基本意味着任何以 hostname/ 格式的请求都会被匹配。pathType 的其他值如下:

    • Exact:路径与 path 中指定的内容完全匹配

    • ImplementationSpecificpath 的匹配由 Ingress 控制器的底层实现决定

使用以下命令应用规则:

$ kubectl create -f Chapter4/1-istio-ingress.yaml

如果你在做这个练习时使用的是 minikube,请在单独的终端中运行 minikube tunnel,并从输出中获取外部 IP。使用以下命令找到 Istio Ingress 网关暴露服务的端口:

$ kubectl get svc istio-ingressgateway -n istio-system -o wide
NAME                   TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)                                                                      AGE    SELECTOR
istio-ingressgateway   LoadBalancer   10.97.245.106   10.97.245.106   15021:32098/TCP,80:31120/TCP,443:30149/TCP,31400:30616/TCP,15443:32339/TCP   6h9m   app=istio-ingressgateway,istio=ingressgateway

在此实例中,Ingress 网关将端口 80 的流量暴露到 Ingress 端口 31120,并将 443 暴露到 30149,但你的设置可能有所不同。

如果你按照第四章中的说明使用了 AWS EKS,那么 IP 和端口会有所不同;以下是 AWS EKS 中与 minikube 等效的配置:

$ kubectl get svc istio-ingressgateway -n istio-system
NAME                   TYPE           CLUSTER-IP       EXTERNAL-IP                                                               PORT(S)                                                                      AGE
istio-ingressgateway   LoadBalancer   172.20.143.136   a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com   15021:30695/TCP,80:30613/TCP,443:30166/TCP,31400:30402/TCP,15443:31548/TCP   29h

在此示例中,Ingress 网关通过 AWS 经典负载均衡器暴露,地址如下:

  • http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com:80 用于 HTTP 流量

  • https://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com:443 用于 HTTPS 流量

之后,请根据您选择的环境使用适当的 IP 地址和端口。接下来章节中的示例部署在 AWS EKS 集群上,但它们也适用于任何其他 Kubernetes 提供商。

继续,通过 curl 测试 Ingress 到前端服务:

curl -HHost:sockshop.com http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/

或者如果使用 Chrome 浏览器,则可以使用如 ModHeader 等扩展,访问 modheader.com/。无论哪种情况,您都需要提供 host 头,并将其值设置为 sockshop.com

所以,我们看到 Istio Ingress 网关可以配置为处理 Kubernetes Ingress。

让我们再添加一个 Ingress 规则,看看 Istio Ingress 控制器如何处理多个 Ingress 规则。我们将使用我们在 第三章 中做的 envoy 配置,其中我们使用了一个路由过滤器返回一个虚拟字符串:

  1. 在以下命令中,我们创建了一个 chapter4 命名空间,以便于管理,并且清理时也更方便:

    $ kubectl create ns chapter4
    
  2. 在此阶段,我们不需要自动侧车注入,但从可视化的角度出发,为了从 Kiali 获取有意义的信息,启用 Istio 侧车注入将是一个好主意,可以使用我们在 第二章 中讨论的以下命令:

    $ kubectl label namespace chapter4 istio-injection=enabled --overwrite
    
  3. 然后,我们将继续创建 configmap 来加载 envoy 配置(也在 第三章 中讨论),这是我们将在下一步创建的 Pods 所需要的:

    $ kubectl create configmap envoy-dummy --from-file=Chapter3/envoy-config-1.yaml -n chapter4
    
  4. 接下来,我们将创建服务和部署以运行 envoy,为所有 HTTP 请求返回一个虚拟响应:

    $ kubectl apply -f Chapter4/01-envoy-proxy.yaml
    
  5. 最后,我们创建一个 Ingress 规则,将所有目标为 mockshop.com 的流量路由到我们在前一步中创建的 envoy 服务:

    $ kubectl apply -f Chapter4/2-istio-ingress.yaml
    
  6. 继续使用 sockshop.commockshop.com 主机头进行测试;Istio Ingress 控制器将根据定义的 Ingress 规则管理路由到适当的目标。

下图描述了我们迄今为止配置的内容。请注意,Ingress 规则如何根据主机名定义流量路由到服务 A 和 B:

图 4.3 – Ingress 配置快照

图 4.3 – Ingress 配置快照

在本节中,我们讨论了如何使用 Kubernetes Ingress 资源和 Istio Ingress 控制器将服务暴露到 Kubernetes 集群外部。在这种 Ingress 配置中,尽管我们使用 Istio 来管理 Ingress,但我们受限于 Kubernetes Ingress 的规格,这使得 Ingress 控制器只能执行有限的功能,如负载均衡、SSL 终止和基于名称的虚拟主机。使用 Kubernetes Ingress 资源类型时,我们没有充分利用 Istio 提供的广泛功能来管理 Ingress。使用 Istio 时,建议使用 Istio 网关 CRD 来管理 Ingress;我们将在下一节讨论这一点。

在继续之前,让我们先进行一些环境的技术清理,以避免与接下来的练习产生冲突:

$ kubectl delete -f Chapter4/2-istio-ingress.yaml
$ kubectl delete -f Chapter4/1-istio-ingress.yaml

重要提示

在本书中,我们将给出一些提醒,让你反向或清理配置。你可以使用前述命令来执行清理操作。

使用 Istio 网关管理 Ingress

在管理 Ingress 时,建议优先使用 Istio 网关,而非 Kubernetes Ingress 资源。Istio 网关像一个负载均衡器,运行在服务网格的边缘,接收传入的 HTTP 和 TCP 连接。

在通过 Istio 网关配置 Ingress 时,你需要执行以下任务。

创建网关

以下代码块创建了一个 Istio 网关资源:

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: chapter4-gateway
  namespace: chapter4
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "sockshop.com"
    - "mockshop.com"

在这里,我们声明了一个名为chapter4-gateway的 Kubernetes 资源,该资源属于gateway.networking.istio.io类型的自定义资源定义,位于chapter4命名空间中。这也等同于定义了一个负载均衡器。

servers属性中,我们定义了以下内容:

  • hosts:这些是网关暴露的一个或多个 DNS 名称。在前面的例子中,我们定义了两个主机:sockshop.commockshop.com。除了这两个主机之外,任何其他主机将被 Ingress 网关拒绝。

  • port:在端口配置中,我们定义了端口号和协议,可以是HTTPHTTPSgRPCTCPTLSMongo。端口名称可以是任何你喜欢使用的名称。在这个例子中,我们通过HTTP协议暴露端口80

总结来说,网关将接受所有通过端口80的 HTTP 请求,且主机头为sockshop.commockshop.com

创建虚拟服务

虚拟服务是 Ingress 网关与目标服务之间的另一层抽象。通过虚拟服务,你可以声明如何将一个主机(如sockshop.com)或多个主机(如sockshop.commockshop.com)的流量路由到目标。例如,你可以在虚拟服务中定义以下内容,针对所有指向sockshop.com的流量:

  • 请求带有/path1 URI 的应发送到服务 1,带有/path2的应发送到服务 2。

  • 根据头信息或查询参数的值来路由请求。

  • 基于权重的路由或流量拆分——例如,60%的流量进入服务的版本 1,40%的流量进入另一个版本的服务

  • 定义超时——即,如果在X秒内未收到来自上游服务的响应,则请求应超时

  • 重试——即,如果上游系统没有响应或响应过慢,请尝试多少次请求

所有这些路由功能都是通过虚拟服务实现的,我们将在本章以及下一章中详细了解它们。

在以下配置中,我们定义了两个虚拟服务,这些服务包含关于流量匹配的规则以及流量应路由到的目标:

---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: sockshop
  namespace: chapter4
spec:
  hosts:
  - "sockshop.com"
  gateways:
  - chapter4-gateway
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        port:
          number: 80
        host: front-end.sock-shop.svc.cluster.local

在之前的配置中,我们定义了一个名为sockshop的虚拟服务。在spec属性中,我们定义了以下内容:

  • hosts:此虚拟服务中的规则将应用于目标为sockshop.com的流量,正如在host中定义的那样。

  • gateway:此虚拟服务与我们在第 1 步(创建网关)中创建的chapter4-gateway关联;这强制要求任何未与该网关关联的流量不会被此虚拟服务配置处理。

  • http:在这里,我们将定义 HTTP 流量的规则和路由信息。还有定义tlstcp路由的选项;tls用于透传 TLS 或 HTTPS 流量,而tcp用于不透明的 TCP 流量。

  • match:这些包含匹配标准,可以基于路径、头部等。在此示例中,我们指示所有流量都将根据本节中的指示进行路由。

  • route:如果流量匹配,则根据这里提供的信息路由流量。在此示例中,我们将流量路由到front-end.sock-shop.svc.cluster.local的端口80

你可以在Chapter4/3-istio-gateway.yaml中找到与envoy-dummy-svc对应的虚拟服务声明。该文件结合了网关和虚拟服务的声明。

下一步,如果尚未按照上一节的清理说明删除,则请删除上一节中创建的 Ingress 资源,以避免与我们将在本节应用的配置发生冲突。

应用新配置:

$ kubectl apply -f chapter4/3-istio-gateway.yaml

请测试你是否能够使用你喜欢的 HTTP 客户端访问sockshop.commockshop.com,并且不要忘记注入正确的host头。

如果你发现很难将端到端的配置可视化,那么可以参考以下插图:

  • 以下图表总结了本节中的配置:

图 4.4 – 虚拟服务

图 4.4 – 虚拟服务

  • 以下图表总结了各种 Istio CRD 和 Kubernetes 资源之间的关联:

图 4.5 – 虚拟服务与其他 Istio 资源之间的关联

图 4.5 - 虚拟服务与其他 Istio 资源的关联

在本节中,我们学习了如何使用 Istio Gateway 和虚拟服务来管理 Ingress。

提醒

请清理chapter4/3-istio-gateway.yaml文件,以避免与即将进行的练习发生冲突。

流量路由与金丝雀发布

在前一部分中,我们了解了虚拟服务的一些功能;在这一部分中,我们将介绍如何将流量分配到多个目标。

假设你已经配置了envoy-dummy配置映射,并且根据01-envoy-proxy.yaml文件启动了envoy Pod 和服务。如果没有,请按照前面的说明配置这些内容。

在接下来的练习中,我们将创建另一个名为v2envoydummy Pod 版本,它返回与v1不同的响应。我们将并行部署v2v1,然后配置两者之间的流量分配:

  1. 创建另一个版本的envoy模拟服务,但使用不同的消息:

    direct_response:
                      status: 200
                      body:
                        inline_string: "V2----------Bootstrap Service Mesh Implementation with Istio----------V2"
    
  2. 更改内容可以在Chapter4/envoy-config-2.yaml中找到;请继续创建另一个配置映射:

    $ kubectl create configmap envoy-dummy-2 --from-file=Chapter4/envoy-config-2.yaml -n chapter4
    
  3. 然后,创建另一个部署,但这次请按以下方式标记 Pods:

    template:
        metadata:
          labels:
            name: envoyproxy
            version: v2
    
  4. 应用更改:

    $ kubectl apply -f Chapter4/02-envoy-proxy.yaml
    
  5. 接下来,我们将创建另一个虚拟服务,但会做以下更改:

        route:
        - destination:
            port:
              number: 80
            subset: v1
            host: envoy-dummy-svc
          weight: 10
        - destination:
            port:
              number: 80
            subset: v2
            host: envoy-dummy-svc
          weight: 90
    

你一定注意到我们在同一路由下有两个目标。destination表示请求最终路由到的服务位置。在destination下,我们有以下三个字段:

  • host:这表示请求应该路由到的服务名称。服务名称会根据 Kubernetes 服务注册表或 Istio 服务入口注册的主机解析。我们将在下一部分讨论服务入口。

  • subset:这是目标规则定义的服务子集,接下来会进行说明。

  • port:这是服务可以访问的端口。

我们还为路由规则关联了权重,指定 10%的流量应发送到subset: v1,而 90%的流量应发送到subset: v2

在虚拟服务定义之后,我们还需要定义目标规则。目标规则是一组应用于流量的规则,这些流量在通过虚拟服务路由规则后执行。

在以下配置中,我们定义了一个名为envoy-destination的目标规则,这个规则将应用于指向envoy-dummy-svc的流量。它进一步定义了两个子集——subset: v1对应于带有version = v1标签的envoy-dummy-svc端点,而subset: v2对应于带有version = v2标签的端点:

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: envoy-destination
  namespace: chapter4
spec:
  host: envoy-dummy-svc
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2

应用更改:

kubectl apply -f Chapter4/4a-istio-gateway.yaml

你会注意到,10%的请求将返回Bootstrap Service Mesh Implementation with Istio,而 90%的请求将返回V2----------Bootstrap Service Mesh Implementation with Istio----------V2响应。

如果你觉得难以直观理解端到端的配置,可以参考以下插图,它总结了本节中的配置:

图 4.6 – 目标规则

图 4.6 – 目标规则

以下图表总结了各种 Istio CRD 与 Kubernetes 资源之间的关联:

图 4.7 – 目标规则与其他 Istio 资源的关联

图 4.7 – 目标规则与其他 Istio 资源的关联

你还可以在 Kiali 仪表板中查看流量是如何以 1:9 的比例在两个服务之间路由的:

图 4.8 – Kiali 仪表板显示流量分配

图 4.8 – Kiali 仪表板显示流量分配

提示

请清理Chapter4/4a-istio-gateway.yaml文件,以避免在接下来的练习中发生冲突。

本节中,你学习了如何在服务的两个版本之间路由或分配流量。这是与流量管理相关的多种操作的基础,其中金丝雀发布是一个例子。在下一节中,我们将了解流量镜像,也称为流量阴影;它是流量路由的另一个例子。

流量镜像

流量镜像是另一项重要功能,它允许你将发送到上游的流量异步地复制到另一个上游服务,也称为镜像服务。流量镜像是一个"无等待"的操作,即 sidecar/gateway 不会等待来自镜像上游的响应。

以下是流量镜像的示意图:

图 4.9 – 流量镜像

图 4.9 – 流量镜像

流量镜像有很多有趣的应用场景,包括以下几点:

  • 将流量镜像到预生产系统进行测试

  • 将流量镜像到接收流量并进行带外分析的目标系统

在以下示例中,在虚拟服务定义中的路由配置下,我们指定了 100%的流量应镜像到subset: v2

  route:
    - destination:
        port:
          number: 80
        subset: v1
        host: envoydummy
      weight: 100
    mirror:
      host: nginxdummy
      subset: v2
    mirrorPercentage:
      value: 100.0

在应用上述更改之前,首先使用以下命令创建一个 nginx 服务:

kubectl apply -f utilities/nginx.yaml

然后,部署虚拟服务:

kubectl apply -f chapter4/4b-istio-gateway.yaml

以下说明了虚拟服务和目标规则的配置:

图 4.10 – 通过虚拟服务实现流量镜像

图 4.10 – 通过虚拟服务实现流量镜像

当使用curl或浏览器带有mockshop.com主机头访问服务时,你会注意到你总是收到Bootstrap Service Mesh Implementation with Istio的响应。

但如果使用kubectl logs nginxdummy -c nginx -n chapter4命令查看 nginx 日志,你会发现 nginx 也收到了请求,这表明流量已被镜像到 nginx。

这部分简短地介绍了流量镜像功能,它是一个简单但强大的特性,尤其适用于事件驱动架构、测试以及使用机器学习和人工智能进行模型训练时。

提醒

请清理Chapter4/4b-istio-gateway.yaml文件,以避免在接下来的练习中出现冲突。

将流量路由到集群外的服务

在你的 IT 环境中,并非所有服务都将在 Kubernetes 集群内部署;一些服务将在传统的虚拟机或裸金属环境中运行,也有一些服务将由 SaaS 提供商或业务合作伙伴提供,另外还有一些服务将运行在外部或不同的 Kubernetes 集群中。在这些场景中,需要让网格中的服务能够访问这些外部服务。因此,接下来的步骤是尝试构建到集群外服务的路由。我们将使用位于httpbin.org/httpbin服务。

任何目标为mockshop.com/get的请求应路由到httpbin;其他请求应由我们在上一节中创建的envoy-dummy-svc处理。

在以下虚拟服务定义中,我们已定义任何带有/get的请求应路由到httpbin.org

- match:
    - uri:
        prefix: /get
    route:
    - destination:
        port:
          number: 80
        host: httpbin.org

接下来,我们将创建ServiceEntry,它是向 Istio 内部服务注册表添加条目的方法。Istio 控制平面管理着网格内所有服务的注册表。该注册表由两种数据源填充——一种是 Kubernetes API 服务器,后者使用 etcd 来维护集群中所有服务的注册表,第二种是由ServiceEntryWorkloadEntry填充的配置存储。现在,ServiceEntryWorkloadEntry用于填充有关 Kubernetes 服务注册表中未知服务的详细信息。我们将在第十章中阅读关于WorkloadEntry的内容。

以下是将httpbin.org添加到 Istio 服务注册表的ServiceEntry声明:

apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: httpbin-svc
  namespace: chapter4
spec:
  hosts:
  - httpbin.org
  location: MESH_EXTERNAL
  ports:
  - number: 80
    name: httpbin
    protocol: http
  resolution: DNS

ServiceEntry声明中,定义了以下配置:

  • resolution:在这里,我们定义如何解析主机名;以下是可能的值:

    • DNS:利用可用的 DNS 来解析主机名

    • DNS_ROUND_ROBBIN:在这种情况下,使用第一个解析的地址

    • NONE:不需要 DNS 解析;目标通过 IP 地址指定

    • STATIC:使用静态端点对应主机名

  • location:服务条目的位置用于指定请求的服务是属于网格内部还是外部。可能的值有MESH_EXTERNALMESH_INTERNAL

  • hosts:这是与请求的服务相关联的主机名;在本例中,主机是httpbin.orgServiceEntry中的主机字段与虚拟服务和目标规则中指定的主机字段匹配。

请继续应用更改:

$ kubectl apply -f Chapter4/5a-istio-gateway.yaml

执行 curl/get 时,你将收到来自 httpbin.org 的响应,而 /ping 应路由到 envoydummy 服务。

图 4.11 – Kiali 仪表板显示通过 ServiceEntry 连接到外部系统

图 4.11 – Kiali 仪表板显示通过 ServiceEntry 连接到外部系统

提醒

请清理 Chapter4/5a-istio-gateway.yaml 以避免在接下来的练习中发生冲突。

ServiceEntry 提供了多种选项,用于将外部服务注册到 Istio 注册表,以便网格内的流量能够正确路由到网格外的工作负载。

通过 HTTPS 暴露 Ingress

在本节中,我们将学习如何配置 Istio 网关通过 HTTPS 暴露 Sockshop 前端应用程序。

步骤 1步骤 3 如果你已经拥有证书颁发机构CA),则是可选的;通常,对于生产系统,这些步骤将由组织的 CA 执行:

  1. 创建一个 CA。这里,我们正在创建一个 CA,CN 为 (sockshop.inc):

    $openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=Sockshop Inc./CN=Sockshop.inc' -keyout Sockshop.inc.key -out Sockshop.inc.crt
    
  2. 生成一个 sockshop.com,并生成一个私钥:

    $openssl req -out sockshop.com.csr -newkey rsa:2048 -nodes -keyout sockshop.com.key -subj "/CN=sockshop.com/O=sockshop.inc"
    
  3. 使用以下命令通过 CA 签署 CSR:

    $openssl x509 -req -sha256 -days 365 -CA Sockshop.inc.crt -CAkey Sockshop.inc.key -set_serial 0 -in sockshop.com.csr -out sockshop.com.crt
    
  4. 将证书和私钥作为 Kubernetes Secret 加载:

    $kubectl create -n istio-system secret tls sockshop-credential --key=sockshop.com.key --cert=sockshop.com.crt
    
  5. 使用以下命令创建网关和虚拟服务:

    kubectl apply -f Chapter4/6-istio-gateway.yaml
    

这样,我们已经创建了证书并将其与私钥一起作为 Kubernetes Secret 加载。

最后,我们正在配置 Istio 网关使用 Secret 作为 TLS 通信的凭证。在 Chapter4/6-istio-gateway.yaml 文件中,我们将网关配置为 Ingress,并在 HTTPS 服务器协议上监听端口 443

  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: sockshop-credential
    hosts:
    - "sockshop.com"

在网关配置中,我们将协议版本从 HTTP 更改为 HTTPS,并在 servers>tls 下添加了以下配置:

  • Mode:指示是否应使用 TLS 来保护该端口。此字段的可能值如下:

    • SIMPLE:这是我们选择的标准 TLS 设置,用于暴露 Sockshop。

    • MUTUAL:这是用于网关与任何调用网关的系统之间的双向 TLS。

    • PASSTHROUGH:当连接需要路由到具有主机值的虚拟服务时,使用此选项,该主机值是通话期间呈现的服务器名称指示符SNI)。

SNI

SNI 是 TLS 协议的一个扩展,在 TLS 握手过程中共享目标服务的主机名或域名,而不是在第 7 层。SNI 在服务器托管多个域名时非常有用,每个域名都由其自己的 HTTPS 证书表示。通过在第 5 层握手时了解请求的主机名,服务器能够根据握手期间呈现的 SNI 提供正确的证书。

  • AUTO_PASSTHROUGH:这与 PASSTHROUGH 相同,唯一的区别是无需虚拟服务。连接将按照 SNI 中的详细信息转发到上游服务。

  • ISTIO_MUTUAL:这与 MUTUAL 相同,只是用于互信 TLS 的证书是由 Istio 自动生成的。

  • 凭证名称:这是持有私钥和证书的 Secret,用于在 TLS 连接期间为服务器端连接提供认证。我们在第 4 步创建了这个 Secret。

继续访问 sockshop.com;你需要在 curl 中使用 --connect-to 来绕过由替换名称与主机实际名称不同而引起的名称解析问题:

$ curl -v -HHost:sockshop.com --connect-to "sockshop.com:443:a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com" --cacert Sockshop.inc.crt  https://sockshop.com:443/

请注意,a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.comcurl 中的 --resolve,类似于以下内容:

$ curl -v -HHost:sockshop.com --resolve "sockshop.com:56407:127.0.0.1" http://sockshop.com:56407/

在前面的命令中,56407 是 Ingress 网关监听的本地端口。

在连接过程中,你会在输出中注意到网关正确地呈现了服务器端证书:

* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=sockshop.com; O=sockshop.inc
*  start date: Aug 12 06:45:27 2022 GMT
*  expire date: Aug 12 06:45:27 2023 GMT
*  common name: sockshop.com (matched)
*  issuer: O=Sockshop Inc.; CN=Sockshop.inc
*  SSL certificate verify ok.

这里有一个特别要注意的点是,我们暴露了 sockshop.com 作为一个 HTTPS 服务,而没有对托管该网站的前端服务进行任何更改。

提醒

请清理 Chapter4/6-istio-gateway.yaml,以避免与即将到来的练习冲突。

启用 HTTP 重定向到 HTTPS

对于仍然向非 HTTPS 端口发送请求的下游系统,我们可以通过在网关配置中对非 HTTPS 端口进行以下更改来实现 HTTP 重定向:

  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "sockshop.com"
    tls:
      httpsRedirect: true

我们仅仅添加了 httpsRedirect: true,使得网关对所有非 HTTPS 连接发送 301 重定向。应用更改并测试连接:

$ kubectl apply -f Chapter4/7-istio-gateway.yaml
$ curl -v -HHost:sockshop.com --connect-to "sockshop.com:80:a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com" --cacert Sockshop.inc.crt  http://sockshop.com:80/

在输出中,你会看到重定向到 sockshop.com

* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< location: https://sockshop.com/

提醒

如往常一样,请清理 Chapter4/7-istio-gateway.yaml,以避免与下一节练习冲突。

启用多个主机的 HTTPS

在上一节中,我们定义了网关上 sockshop.com 的设置。我们也可以在网关上为多个主机应用类似的设置。在本节中,我们将同时为 mockshop.comsockshop.com 启用网关上的 TLS:

  1. 我们将使用在上一节中创建的 CA。所以,作为下一步,让我们为 mockshop.com 生成一个 CSR:

    $openssl req -out mockshop.com.csr -newkey rsa:2048 -nodes -keyout mockshop.com.key -subj "/CN=mockshop.com/O=mockshop.inc"
    
  2. 使用 CA 签署 CSR:

    $openssl x509 -req -sha256 -days 365 -CA Sockshop.inc.crt -CAkey Sockshop.inc.key -set_serial 0 -in mockshop.com.csr -out mockshop.com.crt
    
  3. 将证书和私钥加载为 Kubernetes Secret:

    $kubectl create -n istio-system secret tls mockshop-credential --key=mockshop.com.key --cert=mockshop.com.crt
    
  4. 在网关的服务器配置下,添加以下配置来为 mockshop.com 配置:

    - port:
          number: 443
          name: https-mockshop
          protocol: HTTPS
        tls:
          mode: SIMPLE
          credentialName: mockshop-credential
        hosts:
        - "mockshop.com"
    
  5. 应用更改:

    kubectl apply -f Chapter4/8-istio-gateway.yaml
    

修改后,网关将根据主机名解析正确的证书。

  1. 现在让我们访问 sockshop.com

    curl -v --head -HHost:sockshop.com --resolve "sockshop.com:56408:127.0.0.1" --cacert Sockshop.inc.crt https://sockshop.com:56408/
    

在响应中,你可以看到正确的证书已被呈现:

* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=sockshop.com; O=sockshop.inc
*  start date: Aug 12 06:45:27 2022 GMT
*  expire date: Aug 12 06:45:27 2023 GMT
*  common name: sockshop.com (matched)
*  issuer: O=Sockshop Inc.; CN=Sockshop.inc
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
  1. 同样,测试 mockshop.com

    curl -v -HHost:mockshop.com --connect-to "mockshop.com:443:a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com" --cacert Sockshop.inc.crt  https://mockshop.com/
    
  2. 然后,检查网关呈现的证书是否属于 mockshop.com

    SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
    * ALPN, server accepted to use h2
    * Server certificate:
    *  subject: CN=mockshop.com; O=mockshop.inc
    *  start date: Aug 12 23:47:27 2022 GMT
    *  expire date: Aug 12 23:47:27 2023 GMT
    *  common name: mockshop.com (matched)
    *  issuer: O=Sockshop Inc.; CN=Sockshop.inc
    *  SSL certificate verify ok.
    * Using HTTP2, server supports multiplexing
    

通过这种方式,我们已配置 Istio Ingress 网关根据主机名提供多个 TLS 证书,这也称为 SNI。Istio Ingress 网关可以在 TLS 第 4 层解析 SNI,从而允许它通过 TLS 为多个域名提供服务。

启用 CNAME 和通配符记录的 HTTPS

关于 HTTPS 的最后一个主题是如何管理 CNAME 和通配符记录的证书。特别是对于内部暴露的流量,支持通配符是非常重要的。在本节中,我们将配置网关以支持使用 SNI 的通配符。我们将使用之前章节中创建的 CA:

  1. *.sockshop.com创建 CSR,并使用 CA 证书对其进行签名,然后创建 Kubernetes 密钥:

    $openssl req -out sni.sockshop.com.csr -newkey rsa:2048 -nodes -keyout sni.sockshop.com.key -subj "/CN=*.sockshop.com/O=sockshop.inc"
    $openssl x509 -req -sha256 -days 365 -CA Sockshop.inc.crt -CAkey Sockshop.inc.key -set_serial 0 -in sni.sockshop.com.csr -out sni.sockshop.com.crt
    $kubectl create -n istio-system secret tls sni-sockshop-credential --key=sni.sockshop.com.key --cert=sni.sockshop.com.crt
    
  2. 然后,将*.sockshop.com主机名添加到网关的服务器配置中:

    servers:
      - port:
          number: 443
          name: https-sockshop
          protocol: HTTPS
        tls:
          mode: SIMPLE
          credentialName: sni-sockshop-credential
        hosts:
        - "*.sockshop.com"
    
  3. 同时,使用*.sockshop.com修改虚拟服务:

    kind: VirtualService
    metadata:
      name: sockshop
      namespace: chapter4
    spec:
      hosts:
      - "*.sockshop.com"
      - "sockshop.com"
    
  4. 应用配置:

    $ kubectl apply -f Chapter4/9-istio-gateway.yaml
    
  5. 你可以测试mockshop.comsockshop.com或任何其他指向sockshop.com的 CNAME 记录。以下示例使用的是my.sockshop.com

    $ curl -v -HHost:my.sockshop.com --connect-to "my.sockshop.com:443:a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com" --cacert Sockshop.inc.crt  https://my.sockshop.com/
    

以下是第 5 步输出的片段,显示在握手过程中正确的证书被呈现:

* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=*.sockshop.com; O=sockshop.inc
*  start date: Aug 13 00:27:00 2022 GMT
*  expire date: Aug 13 00:27:00 2023 GMT
*  common name: *.sockshop.com (matched)
*  issuer: O=Sockshop Inc.; CN=Sockshop.inc
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing

如你所见,Istio 为 CNAME 提供了正确的通配符证书。这个示例演示了如何配置 Istio 网关来处理多个域名和子域名。

在本章及之前的章节中,我们了解了 Istio 如何管理网格内外的流量路由。了解网关、虚拟服务、目标规则和服务条目的概念,并通过本章提供的示例进行实验,以及思考其他示例并尝试实现它们,是非常重要的。在第六章中,我们将更深入地讨论安全性,并涵盖 mTLS 等主题。但目前,我们将了解 Istio 如何管理 Egress 流量。

提醒

别忘了清理Chapter4/8-istio-gateway.yamlChapter4/9-istio-gateway.yaml

使用 Istio 管理 Egress 流量

将流量路由到集群外的服务一节中,我们发现如何使用服务条目更新 Istio 服务注册表,介绍网格和集群外的服务。服务条目是将额外条目添加到 Istio 内部服务注册表中的一种方式,以便虚拟服务能够路由到这些条目。然而,Egress 网关用于控制外部服务流量如何离开网格。

为了熟悉 Egress 网关,我们将首先在网格中部署一个 Pod,从中我们可以调用外部服务:

$ kubectl apply -f utilities/curl.yaml

该命令创建了一个 Pod,你可以从中执行curl;这模拟了在网格内部运行的工作负载:

$ kubectl exec -it curl sh -n chapter4

从 Shell 访问httpbin.org,使用curl

$ curl -v https://httpbin.org/get

现在,我们将使用以下命令停止所有出站流量:

$ istioctl install -y --set profile=demo --set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY

在上一个命令中,我们正在修改 Istio 安装,以将出站流量策略从ALLOW_ANY更改为REGISTRY_ONLY,这强制要求只有通过ServiceEntry资源定义的主机才是网格服务注册表的一部分。

返回并再次尝试curl;你将看到以下输出:

$ curl -v https://httpbin.org/get
curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to httpbin.org:443

现在,让我们通过创建一个服务条目将httpbin.org列入 Istio 服务注册表,方法如下:

apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: httpbin-svc
  namespace: chapter4
spec:
  hosts:
  - httpbin.org
  location: MESH_EXTERNAL
  resolution: DNS
  ports:
  - number: 443
    name: https
    protocol: HTTPS
  - number: 80
    name: http
    protocol: HTTP

现在,您可以继续应用配置:

$ kubectl apply -f Chapter4/10-a-istio-egress-gateway.yaml

curl Pod 访问https://httpbin.org/get;这次您会成功。

ServiceEntryhttpbin.org添加到网格服务注册表中,因此我们能够从curl Pod 访问httpbin.org

虽然ServiceEntry非常适合提供外部访问,但它并不提供对外部端点访问方式的控制。例如,您可能希望只有特定的工作负载或命名空间能够向外部资源发送流量。如果需要通过验证证书来验证外部资源的真实性怎么办?

提醒

别忘了清理Chapter4/10-a-istio-egress-gateway.yaml

Egress 网关,结合虚拟服务、目标规则和服务条目的配置,提供了灵活的选项来管理和控制离开网格的流量。因此,让我们进行配置更改,将所有流量路由到 Egress 网关:

  1. 配置 Egress 网关,这与 Ingress 网关配置非常相似。请注意,Egress 网关附加到httpbin.org;您可以为其他主机提供配置,或使用*来匹配所有主机名:

    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: istio-egressgateway
      namespace: chapter4
    spec:
      selector:
        istio: egressgateway
      servers:
      - port:
          number: 80
          name: http
          protocol: HTTP
        hosts:
        - httpbin.org
    
  2. 接下来,配置虚拟服务。这里,我们将虚拟服务配置为同时连接到 Egress 网关和网格:

    spec:
      hosts:
      - httpbin.org
      gateways:
      - istio-egressgateway
      - mesh
    

在虚拟服务定义的以下部分中,我们配置了从网格内发出的所有流量将定向到 Egress 网关,这些流量来自httpbin.org主机:

http:
  - match:
    - gateways:
      - mesh
      port: 80
    route:
    - destination:
        host: istio-egressgateway.istio-system.svc.cluster.local
        subset: httpbin
        port:
          number: 80
      weight: 100

我们已经配置了subset: httpbin来应用目标规则;在这个示例中,目标规则是空的。

最后,我们将添加另一个规则,将流量从 Egress 网关路由到httpbin.org

  - match:
    - gateways:
      - istio-egressgateway
      port: 80
    route:
    - destination:
        host: httpbin.org
        port:
          number: 80
      weight: 100
  1. 创建一个占位符,用于实现任何可能的目标规则:

    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: rules-for-httpbin-egress
      namespace: chapter4
    spec:
      host: istio-egressgateway.istio-system.svc.cluster.local
      subsets:
      - name: httpbin
    
  2. 您还需要为httpbin.org添加ServiceEntry,我们在前面部分已经讨论过。

  3. 继续应用更改:

    kubectl apply -f Chapter4/10-b-istio-egress-gateway.yaml
    
  4. 尝试从curl Pod 访问httpbin.org;现在您应该可以访问它了。

检查响应中的头信息,以及istio-egressgateway pods的日志。您会在X-Envoy-Peer-Metadata-Id下找到关于 Egress 网关的信息。您也可以在 Egress 网关的日志中看到请求。

您会注意到,尽管我们在服务条目中定义了https,但您无法访问https://httpbin.org/get。尝试启用对httpbin.orghttps访问;您将在Chapter4/10-c-istio-egress-gateway.yaml中找到解决方案。

出口流量控制对于管理离开网格的流量非常重要。在第六章中,我们将重点讨论出口流量的其他安全方面。

提醒

删除chapter4/10-b-istio-egress-gateway.yaml以及chapter4命名空间。

使用以下命令将授权策略恢复为允许所有网格外发流量,而不需要 Egress 网关:

$ istioctl install -y --set profile=demo --set meshConfig.outboundTrafficPolicy.mode=ALLOW_ANY

总结

在本章中,我们了解了如何使用 Istio Ingress 网关管理进入 Service Mesh 的外部流量,以及如何使用 Istio Egress 网关管理离开网格的内部流量。

我们学习了虚拟服务和目标规则:虚拟服务是如何用于描述路由流量到网格中各个目的地的规则,目标规则是如何用于定义最终目的地,以及目标如何处理通过虚拟服务定义的规则路由的流量。使用虚拟服务,我们可以进行基于权重的流量路由,这也用于金丝雀发布和蓝绿部署。

此外,我们了解了ServiceEntry以及它是如何用于使 Istio 识别外部服务,以便网格中的工作负载可以将流量发送到网格外的服务。最后,我们还了解了 Egress 网关是如何用于控制流量出口到由ServiceEntry定义的终端,以便我们可以安全可靠地从网格访问外部服务。本章为下一章做了铺垫,下一章我们将讨论如何利用本章中的概念实现应用程序的韧性。

第五章:管理应用程序韧性

应用程序的韧性是指软件应用在面对故障和失败时,能够保持不明显的质量和服务水平下降的能力。从单体架构到微服务架构的转变,进一步加剧了软件设计和架构中对应用程序韧性的需求。在单体应用中,只有一个代码库和一个部署,而在基于微服务的架构中,则有多个独立的代码库,每个代码库都有自己的部署。当使用 Kubernetes 和其他类似平台时,还需要考虑部署的灵活性,以及多个应用实例的弹性部署和扩展;这些动态实例不仅需要相互协调,还需要与所有其他微服务协调。

本章将介绍如何利用 Istio 提高微服务的应用程序韧性。在我们深入每个部分时,我们将讨论应用程序韧性的各个方面,以及 Istio 如何解决这些问题。

总之,本章将涵盖以下主题:

  • 使用故障注入提高应用程序的韧性

  • 使用超时和重试提高应用程序的韧性

  • 使用负载均衡构建应用程序的韧性

  • 限制请求速率

  • 熔断器和异常检测

重要提示

本章的技术先决条件与上一章相同。

使用故障注入提高应用程序的韧性

故障注入用于测试应用程序在发生任何类型故障时的恢复能力。从原则上讲,每个微服务都应设计为具备内建的韧性,以应对内部和外部的故障,但实际上,往往并非如此。构建韧性最复杂和困难的工作通常是在设计和测试阶段。

在设计阶段,必须识别所有已知和未知的场景,并为其提供解决方案。例如,必须解决以下问题:

  • 在微服务内部和外部,可能发生哪些已知和未知的错误?

  • 应用程序代码应如何处理这些错误?

在测试阶段,应该能够模拟这些场景,以验证应用程序代码中构建的应急处理措施:

  • 实时模拟其他上游服务的不同故障场景,以测试整体应用程序的行为

  • 模拟不仅是应用程序故障,还包括网络故障(如延迟、停机等)以及基础设施故障

混沌工程是一个软件工程术语,用于指引通过在软件系统及其执行和通信环境及生态系统中引入不利条件来测试软件系统的学科。你可以在hub.packtpub.com/chaos-engineering-managing-complexity-by-breaking-things/了解更多关于混沌工程的内容。生成混沌(即故障)的各种工具大多数是在基础设施级别。一种流行的工具是 Chaos Monkey。它可以在netflix.github.io/chaosmonkey/找到。AWS 还提供了 AWS 故障注入模拟器,用于运行故障注入模拟。你可以在aws.amazon.com/fis/了解关于 AWS 故障注入模拟器的更多信息。另一个流行的开源软件是 Litmus,它是一个混沌工程平台,通过以受控的方式引入混沌测试来识别基础设施中的弱点和潜在的停机。你可以在litmuschaos.io/了解更多信息。

Istio 提供了细粒度的故障注入控制,因为它能够访问并了解应用程序流量。通过 Istio 故障注入,你可以使用应用层的故障注入;结合像 Chaos Monkey 和 AWS 故障模拟器这样的基础设施级别故障注入工具,提供了非常强大的能力来测试应用程序的韧性。

Istio 支持以下类型的故障注入:

  • HTTP 延迟

  • HTTP 中止

在接下来的章节中,我们将更详细地讨论这两种故障注入类型。

什么是 HTTP 延迟?

延迟是时间故障。它们模拟由于网络延迟或上游服务过载而导致的请求周转时间增加。这些延迟是通过 Istio VirtualServices 注入的。

在我们的袜子商店示例中,让我们在前端服务和目录服务之间注入一个 HTTP 延迟,并测试当前端服务无法从目录服务获取图片时,它的表现。我们将专门为一张图片而非所有图片进行此操作。

你可以在 GitHub 上的Chapter5/02-faultinjection_delay.yaml文件中找到 VirtualService 定义。

图 5.1 – 目录 VirtualService 中的 HTTP 延迟注入

图 5.1 – 目录 VirtualService 中的 HTTP 延迟注入

以下是来自 VirtualService 定义的代码片段:

spec:
  hosts:
  - "catalogue.sock-shop.svc.cluster.local"
  gateways:
  - mesh
  http:
  - match:
    - uri:
        prefix: "/catalogue/3395a43e-2d88-40de-b95f-e00e1502085b"
      ignoreUriCase: true
    fault:
      delay:
        percentage:
          value: 100.0
        fixedDelay: 10s
    route:
    - destination:
        host: catalogue.sock-shop.svc.cluster.local
        port:
          number: 80

首先需要注意的是fault的定义,它用于在将请求转发到路由中指定的目标之前,注入延迟和/或中止故障。在这种情况下,我们正在注入一种delay类型的故障,用于模拟慢响应时间。

delay配置中,定义了以下字段:

  • fixedDelay:指定延迟的持续时间,值可以是小时、分钟、秒或毫秒,分别由hmsms后缀指定。

  • percentage:指定延迟将注入的请求百分比

另外需要注意的是,VirtualService 与 mesh 网关关联;你可能已经注意到我们没有为 mesh 定义 Ingress 或 Egress 网关。那么,你一定在想这个是从哪里来的,mesh 是一个保留字,用来指代网格中的所有 sidecar。这也是网关配置的默认值,因此,如果你没有为网关提供值,VirtualService 默认会与网格中的所有 sidecar 关联。

那么,让我们总结一下我们配置的内容。

sock-shop VirtualService 与网格中的所有 sidecar 关联,并且应用于目标为 catalogue.sock-shop.svc.cluster.local 的请求。VirtualService 为所有以 /catalogue/3395a43e-2d88-40de-b95f-e00e1502085b 为前缀的请求注入了 10 秒的延迟,然后将其转发到 catalogue.sock-shop.svc.cluster.local 服务。没有该前缀的请求则直接转发到 catalogue.sock-shop.svc.cluster.local 服务。

使用 Istio 注入启用为 Chapter5 创建一个命名空间:

$ kubectl create ns chapter5
$ kubectl label ns chapter5 istio-injection=enabled

使用以下内容为 sockshop.com 创建 Ingress 网关和 VirtualService 配置:

$ kubectl apply -f Chapter5/01-sockshop-istio-gateway.yaml

之后,应用目录服务的 VirtualService 配置:

$ kubectl apply -f Chapter5/02-faultinjection_delay.yaml

然后,使用 Ingress ELB 和自定义主机头在浏览器中打开 sockshop.com。启用开发者工具并搜索以 /catalogue/3395a43e-2d88-40de-b95f-e00e1502085b 为前缀的请求。你会注意到这些特定的请求处理时间超过了 10 秒。

图 5.2 – HTTP 延迟导致请求耗时超过 10 秒

图 5.2 – HTTP 延迟导致请求耗时超过 10 秒

你还可以检查注入到 front-end Pod 中的 sidecar,以获取此请求的访问日志:

% kubectl logs -l name=front-end -c istio-proxy -n sock-shop | grep /catalogue/3395a43e-2d88-40de-b95f-e00e1502085b
[2022-08-27T00:39:09.547Z] "GET /catalogue/3395a43e-2d88-40de-b95f-e00e1502085b HTTP/1.1" 200 DI via_upstream - "-" 0 286 10005 4 "-" "-" "d83fc92e-4781-99ec-91af-c523e55cdbce" "catalogue" "10.10.10.170:80" outbound|80||catalogue.sock-shop.svc.cluster.local 10.10.10.155:59312 172.20.246.13:80 10.10.10.155:40834 - -

在前面的代码块中突出显示的是在本实例中处理时耗时 10005 毫秒的请求。

在本节中,我们注入了 10 秒的延迟,但你可能也注意到前端网页没有出现明显的延迟。所有图片都异步加载,任何延迟仅限于页面的目录部分。然而,通过配置延迟,你可以测试在网络或目录服务处理过程中出现任何不可预见的延迟时,应用程序的端到端行为。

什么是 HTTP 中止?

HTTP 中止是通过 Istio 注入的第二种故障类型。HTTP 中止会提前终止请求的处理;你还可以指定需要返回到下游的错误代码。

以下是 VirtualService 定义中的片段,包含为目录服务配置的 abort 配置。该配置可在 Chapter5/03-faultinjection_abort.yaml 文件中找到:

spec:
  hosts:
  - "catalogue.sock-shop.svc.cluster.local"
  gateways:
  - mesh
  http:
  - match:
    - uri:
        prefix: "/catalogue/3395a43e-2d88-40de-b95f-e00e1502085b"
      ignoreUriCase: true
    fault:
      abort:
        httpStatus: 500
        percentage:
          value: 100.0
    route:
    - destination:
        host: catalogue.sock-shop.svc.cluster.local
        port:
          number: 80

fault 下,还有一个名为 abort 的配置项,包含以下参数:

  • httpStatus:指定需要下游返回的 HTTP 状态码

  • percentage:指定需要中止的请求百分比

以下是可以应用于 gRPC 请求的额外配置列表:

  • grpcStatus:在中止 gRPC 请求时需要返回的 gRPC 状态码

图 5.3 – 目录 VirtualService 中的 HTTP 中止注入

图 5.3 – 目录 VirtualService 中的 HTTP 中止注入

Chapter5/03-faultinjection_abort.yaml 文件中,我们为所有来自网格内部的调用配置了一个 VirtualService 规则,要求它们访问 catalogue.sock-shop.svc.cluster.local/catalogue/3395a43e-2d88-40de-b95f-e00e1502085b 时会被中止,并返回 HTTP 状态码 500

应用以下配置:

$ kubectl apply -f Chapter5/03-faultinjection_abort.yaml

当从浏览器加载 sock-shop.com 时,你会注意到有一张图片未能加载。查看 front-end Pod 的 istio-proxy 访问日志,你将看到以下内容:

% kubectl logs -l name=front-end -c istio-proxy -n sock-shop | grep /catalogue/3395a43e-2d88-40de-b95f-e00e1502085b
[2022-08-27T00:42:45.260Z] "GET /catalogue/3395a43e-2d88-40de-b95f-e00e1502085b HTTP/1.1" 500 FI fault_filter_abort - "-" 0 18 0 - "-" "-" "b364ca88-cb39-9501-b4bd-fd9ea143fa2e" "catalogue" "-" outbound|80||catalogue.sock-shop.svc.cluster.local - 172.20.246.13:80 10.10.10.155:57762 - -

这部分内容结束了故障注入的讲解,现在你已经练习了如何将 delayabort 注入到服务网格中。

提醒

请清理 Chapter5/02-faultinjection_delay.yamlChapter5/03-faultinjection_abort.yaml 文件,以避免与本章节后续练习的冲突。

在本节中,我们了解了如何向服务网格中注入故障,以便测试微服务的弹性,并设计它们以应对由于上游服务通信导致的延迟和故障。在接下来的章节中,我们将了解如何在服务网格中实现超时和重试机制。

使用超时和重试机制实现应用的弹性

在多个微服务之间的通信中,许多问题可能会发生,网络和基础设施是导致服务降级和中断的最常见原因。响应过慢的服务可能导致其他服务发生级联故障,并对整个应用产生连锁反应。因此,微服务的设计必须通过设置 超时 来应对向其他微服务发送请求时可能发生的意外延迟。

超时是指服务等待其他服务响应的最大时间;超出超时期限,响应对请求方没有意义。一旦发生超时,微服务将按照应急方法进行处理,这可能包括从缓存提供响应或让请求优雅地失败。

有时,问题是短暂的,重新尝试获取响应是有意义的。这个方法称为 重试,即微服务可以基于某些条件重试请求。在本节中,我们将讨论如何在不需要修改微服务代码的情况下,使用 Istio 实现服务超时和重试。我们将先从超时开始。

超时

超时是 istio-proxy sidecar 应等待某个服务回复的时间。超时有助于确保微服务不会无故等待过长时间的回复,并且调用能够在可预测的时间框架内成功或失败。Istio 让你可以轻松地通过 VirtualServices 动态调整每个服务的超时设置,而不需要修改服务代码。

作为示例,我们将在 order 服务上配置 1 秒的超时,并在 payment 服务中引入 10 秒的延迟。order 服务在结账时调用 payment 服务,因此我们正在模拟一个缓慢的支付服务,并通过在调用 order 服务时配置超时来实现前端服务的弹性:

图 5.4 – 订单超时与支付服务中的延迟故障

图 5.4 – 订单超时与支付服务中的延迟故障

我们将首先在 order 服务中配置超时,这也是通过 VirtualService 实现的。你可以在 GitHub 上的 Chapter5/04-request-timeouts.yaml 文件中找到完整的配置:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: orders
  namespace: chapter5
spec:
  hosts:
  - "orders.sock-shop.svc.cluster.local"
  gateways:
  - mesh
  http:
  - timeout: 1s
    route:
    - destination:
        host: orders.sock-shop.svc.cluster.local
        port:
          number: 80

在这里,我们创建了一个新的 VirtualService,名为 orders,并且为从网格内部发往 orders.sock-shop.svc.cluster.local 的任何请求配置了 1 秒的超时。该超时是 http 路由配置的一部分。

接下来,我们还将在所有请求到 payment 服务时注入 10 秒的延迟。详情请参见 Chapter5/04-request-timeouts.yaml 文件。

继续并应用更改:

% kubectl apply -f Chapter5/04-request-timeouts.yaml

从 sockshop 网站添加任何商品到购物车并结账。观察在应用本节更改前后的行为。

检查订单 Pod 中 sidecar 的日志:

% kubectl logs --follow -l name=orders -c istio-proxy -n sock-shop | grep payment
[2022-08-28T01:24:31.968Z] "POST /paymentAuth HTTP/1.1" 200 - via_upstream - "-" 326 51 2 2 "-" "Java/1.8.0_111-internal" "ce406513-fd29-9dfc-b9cd-cb2b3dbd24a6" "payment" "10.10.10.171:80" outbound|80||payment.sock-shop.svc.cluster.local 10.10.10.229:60984 172.20.93.36:80 10.10.10.229:40816 - -
[2022-08-28T01:25:55.244Z] "POST /paymentAuth HTTP/1.1" 200 DI via_upstream - "-" 326 51 10007 2 "-" "Java/1.8.0_111-internal" "ae00c14e-409c-94b1-8cfb-951a89411246" "payment" "10.10.10.171:80" outbound|80||payment.sock-shop.svc.cluster.local 10.10.10.229:36752 172.20.93.36:80 10.10.10.229:52932 - -

请注意,实际请求到 payment Pod 的处理时间是 2 毫秒,但由于注入了 delay 故障,整体耗时为 10007 毫秒。

此外,检查 front-end Pods 中 istio-proxy 的日志:

% kubectl logs --follow -l name=front-end -c istio-proxy -n sock-shop | grep orders
[2022-08-28T01:25:55.204Z] "POST /orders HTTP/1.1" 504 UT upstream_response_timeout - "-" 232 24 1004 - "-" "-" "b02ea4a2-b834-95a6-b5be-78db31fabf28" "orders" "10.10.10.229:80" outbound|80||orders.sock-shop.svc.cluster.local 10.10.10.155:49808 172.20.11.100:80 10.10.10.155:55974 - -
[2022-08-28T01:25:55.173Z] "POST /orders HTTP/1.1" 504 - via_upstream - "-" 0 26 1058 1057 "10.10.10.217" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36" "a8cb6589-4fd1-9b2c-abef-f049cd1f6beb" "sockshop.com" "10.10.10.155:8079" inbound|8079|| 127.0.0.6:36185 10.10.10.155:8079 10.10.10.217:0 outbound_.80_._.front-end.sock-shop.svc.cluster.local default

在这里,我们可以看到请求在大约 1 秒后返回,并且 HTTP 状态码为 504。尽管底层的支付请求在 10 秒内处理完毕,但对 order 服务的请求在 1 秒后超时。

在这种情况下,我们可以看到网站没有优雅地处理错误。它没有返回像“order 服务”的友好信息。

提醒

别忘了清理 Chapter5/04-request-timeouts.yaml 文件,以避免与后续练习发生冲突。

重试

超时是防止延迟蔓延到应用程序其他部分的良好防火墙,但延迟的根本原因有时是暂时性的。在这些情况下,可能需要重试请求至少几次。重试次数和重试间隔取决于延迟的原因,该原因由响应中返回的错误代码确定。

在本节中,我们将学习如何将重试机制注入到服务网格中。为了简化操作,使我们能够专注于学习本节中的概念,我们将利用前一章中创建的 envoydummy 服务。Envoy 提供了许多过滤器来模拟各种延迟,我们将利用这些过滤器来模拟应用程序故障。

首先,配置 envoydummy 服务:

$ kubectl create ns utilities
$ kubectl label ns utilities istio-injection=enabled

然后部署 envoy 服务和 Pods:

$ kubectl apply -f Chapter5/envoy-proxy-01.yaml

然后,我们部署网关和 VirtualService:

$ kubectl apply -f Chapter5/mockshop-ingress_01.yaml

测试服务,查看它是否正常工作。

之后,我们将配置 envoy 配置文件来中止一半的调用,并返回错误代码 503。注意 Istio 配置和 envoy 配置之间的相似性。

配置 envoydummy 以中止一半的 API 调用:

  http_filters:
              - name: envoy.filters.http.fault
                typed_config:
                  "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
                  abort:
                    http_status: 503
                    percentage:
                      numerator: 50
                      denominator: HUNDRED

完整的文件可以在 Chapter5/envoy-proxy-02-abort-02.yaml 中找到。

应用更改:

$ kubectl apply -f Chapter5/envoy-proxy-02-abort-02.yaml

进行几次测试后,你会注意到 API 调用正常工作,尽管我们已将envoydummy配置为使一半的调用失败。这是因为 Istio 已经启用了默认的重试机制。尽管请求被envoydummy中止,但 sidecar 会默认重试两次,最终得到成功的响应。重试之间的间隔(超过 25 毫秒)是可变的,由 Istio 自动确定,从而防止被调用的服务被过多请求压垮。这得益于 sidecar 中的 Envoy 代理,它使用完全抖动的指数回退算法进行重试,并提供可配置的基础间隔,默认值为 25 毫秒。如果基础间隔为 C,N 为重试次数,则重试的回退范围为 0, (2^N−1)C)。例如,间隔为 25 毫秒,重试次数为 2 时,第一个重试会随机延迟 0-24 毫秒,第二个重试会延迟 0-74 毫秒。

要禁用 retries,请在 mockshop VirtualService 中进行以下更改:

  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        port:
          number: 80
        host: envoydummy.utilities.svc.cluster.local
    retries:
      attempts: 0

在这里,我们已将 retries 的次数配置为 0。应用更改后,你会注意到一半的 API 调用返回 503

我们将按照下图所示进行更改:

![图 5.5 – 请求重试

图 5.5 – 请求重试

retry 块进行以下更改。你可以在 Chapter5/05-request-retry.yaml 中找到完整配置:

    retries:
      attempts: 2
      perTryTimeout: 2s
      retryOn: 5xx,gateway-error,reset,connect-failure,refused-stream,retriable-4xx

retry 块中,我们定义了以下配置:

  • attempts: 给定请求的重试次数

  • perTryTimeout: 每次尝试的超时时间

  • retryOn: 请求应重试的条件

应用配置后,你会注意到请求按预期正常工作:

$ kubectl apply -f  Chapter5/05-request-retry.yaml

提醒

请清理Chapter5/05-request-retry.yaml以避免与后续练习发生冲突。

本节到此结束,你已经学会了如何设置服务网格中的超时和重试机制,以提高应用程序的韧性。在下一节中,我们将探索不同的负载均衡策略。

使用负载均衡构建应用程序的韧性

负载均衡是提高应用程序韧性的另一种技术。Istio 的负载均衡策略通过有效地将网络流量分配到微服务或底层服务,帮助你最大化应用程序的可用性。负载均衡使用目标规则,目标规则定义了在路由完成后,服务如何处理流量的策略。

在前一章中,我们使用了目标规则进行流量管理。在本节中,我们将介绍 Istio 提供的各种负载均衡策略,以及如何使用目标规则进行配置。

部署另一个envoydummy Pod,但这次添加一个标签version:v2,输出为V2----------Bootstrap Service Mesh Implementation with Istio----------V2。配置文件可在Chapter5/envoy-proxy-02.yaml中找到:

$ kubectl apply -f Chapter5/envoy-proxy-02.yaml

Istio 支持以下负载均衡策略。

循环轮询

循环轮询是最简单的负载分配方式之一,请求会依次转发到底层后端服务。虽然易于使用,但它们不一定能导致最有效的流量分配,因为在轮询负载均衡中,每个上游都被视为相同的流量类型,具有相同的性能,并且承受类似的环境约束。

Chapter5/06-loadbalancing-roundrobbin.yaml中,我们已经创建了如下的目标规则,trafficPolicy如下:

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: envoydummy
spec:
  host: envoydummy
  trafficPolicy:
       loadBalancer:
         simple: ROUND_ROBIN

DestinationRule中,你可以定义多个参数,我们将在本节及后续章节中逐一揭示。

对于轮询负载均衡,我们在trafficPolicy中定义了如下的目标规则:

  • simple:定义要使用的负载均衡算法—可能的值如下:

    • UNSPECIFIED:Istio 将选择一个合适的默认值

    • RANDOM:Istio 将随机选择一个健康的主机。

    • PASSTHROUGH:此选项允许客户端请求特定的上游,并且负载均衡策略将把请求转发到请求的上游。

    • ROUND_ROBIN:Istio 将按轮询方式向上游服务发送请求。

    • LEAST_REQUEST:此策略根据每个端点上未处理的请求数量来分配负载。该策略是所有负载均衡策略中效率最高的。

使用以下命令应用配置:

$ kubectl apply -f Chapter5/06-loadbalancing-roundrobbin.yaml

测试端点后,你会注意到从envoydummy的两个版本接收到了相等数量的响应。

RANDOM

使用RANDOM负载均衡策略时,Istio 会随机选择一个目标主机。你可以在Chapter5/07-loadbalancing-random.yaml文件中找到RANDOM负载均衡策略的示例。以下是使用RANDOM负载均衡策略的目标规则:

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: envoydummy
spec:
  host: envoydummy
  trafficPolicy:
       loadBalancer:
         simple: RANDOM

应用配置:

$ kubectl apply -f Chapter5/07-loadbalancing-random.yaml

向端点发送几个请求,你会发现响应没有任何可预测的模式。

LEAST_REQUEST

如前所述,在LEAST_REQUEST负载均衡策略中,Istio 会将未完成请求最少的流量引导到上游。

为了模拟这种情况,我们将创建另一个服务,专门将所有请求发送到envoydummy版本 2 Pod。配置文件可以在Chapter5/08-loadbalancing-leastrequest.yaml找到:

apiVersion: v1
kind: Service
metadata:
  name: envoydummy2
  labels:
    name: envoydummy2
    service: envoydummy2
  namespace: chapter5
spec:
  ports:
    # the port that this service should serve on
  - port: 80
    targetPort: 10000
  selector:
    name: envoydummy

我们还修改了DestinationRule,将请求发送到活动连接最少的主机:

  trafficPolicy:
       loadBalancer:
         simple: LEAST_REQUEST
    version: v2

应用配置:

$ kubectl apply -f Chapter5/08-loadbalancing-leastrequest.yaml

使用kubectl port-forward,我们可以从localhostenvoydummy2服务发送请求:

$ kubectl port-forward svc/envoydummy2 10000:80 -n utilities

之后,我们将使用以下命令生成一个目标为envoydummy版本 2 服务的请求:

$ for ((i=1;i<1000000000;i++)); do curl -v "http://localhost:10000"; done

当负载处理进行时,通过mockshop端点访问请求,你会发现大多数(如果不是所有)请求都是由envoydummy Pods 的版本 1 提供服务,因为使用了LEAST_REQUEST负载均衡策略:

$ curl -Hhost:mockshop.com http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/
V1----------Bootstrap Service Mesh Implementation with Istio----------V1
V1----------Bootstrap Service Mesh Implementation with Istio----------V1
V1----------Bootstrap Service Mesh Implementation with Istio----------V1
V1----------Bootstrap Service Mesh Implementation with Istio----------V1

在前面的示例中,你看到了 Istio 如何将所有对mockshop的请求引导到envoydummy版本 1,因为v1的活动连接最少。

定义多个负载均衡规则

Istio 提供了为每个子集应用多个负载均衡规则的功能。在Chapter5/09-loadbalancing-multirules.yaml文件中,我们定义了默认的负载均衡策略为ROUND_ROBIN,为v1子集定义了LEAST_REQUEST策略,为v2子集定义了RANDOM策略。

以下是从Chapter5/09-loadbalancing-multirules.yaml配置文件中提取的配置片段:

  host: envoydummy
  trafficPolicy:
       loadBalancer:
         simple: ROUND_ROBIN
  subsets:
  - name: v1
    labels:
      version: v1
    trafficPolicy:
      loadBalancer:
        simple: LEAST_REQUEST
  - name: v2
    labels:
      version: v2
    trafficPolicy:
      loadBalancer:
        simple: RANDOM

在前面的代码块中,我们为v1子集应用了LEAST_REQUEST负载均衡策略,为v2子集应用了RANDOM负载均衡策略,而对代码块中未指定的其他子集应用了ROUND_ROBIN策略。

能够为工作负载定义多个负载均衡规则,允许你在目标规则子集的级别上进行精细的流量分配控制。在下一节中,我们将介绍另一个关于应用程序弹性的重要方面——限流,以及它如何在 Istio 中实现。

限流

另一个重要的应用程序弹性技术是限流和断路器。限流有助于提供以下控制,来处理来自消费者的流量而不至于崩溃提供者系统:

  • 激增保护,防止系统因流量的突然激增而被超载

  • 将传入请求的速率与可用的处理请求的能力对齐

  • 保护慢速提供者免受快速消费者的影响

速率限制通过配置目标规则与连接池来执行,以连接到上游服务。连接池设置可以应用于 TCP 层以及 HTTP 层,如在Chapter5/10-connection-pooling.yaml中的以下配置所示:

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: envoydummy
  namespace: chapter5
spec:
  host: envoydummy
  trafficPolicy:
      connectionPool:
        http:
          http2MaxRequests: 1
          maxRequestsPerConnection: 1
          http1MaxPendingRequests: 0

以下是连接池配置的关键属性:

  • http2MaxRequests:每个目的地的最大活跃请求数;默认值为1024

  • maxRequestsPerConnection:每个连接到上游的最大请求数。值为1时禁用保持连接,而0表示无限制。

  • http1MaxPendingRequests:在等待来自连接池的连接时,将排队的最大请求数;默认值为1024

我们已经配置了每个连接最多1个请求到上游,在任何时刻最多1个活跃连接,并且不允许连接请求排队。

测试速率限制、熔断器和异常值检测并不像测试应用程序弹性功能的其他特性那样直接。幸运的是,有一个非常方便的负载测试工具叫做fortio,它可以在github.com/fortio/fortio找到,并且已经打包在 Istio 示例目录中。我们将使用fortio来生成负载并测试速率限制。

从 Istio 目录部署fortio

$ kubectl apply -f samples/httpbin/sample-client/fortio-deploy.yaml -n utilities

应用其中一种负载均衡策略以测试正常行为:

$ kubectl apply -f Chapter5/06-loadbalancing-roundrobbin.yaml

使用fortio生成负载:

$ kubectl exec -it fortio-deploy-7dcd84c469-xpggh -n utilities -c fortio -- /usr/bin/fortio load -qps 0 -c 2 -t 1s -H "Host:mockshop.com" http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/

在之前的请求中,我们配置了fortio以生成一个1秒的负载测试,使用2个并行连接,最大查询每秒(qps)为0,意味着没有等待/最大qps速率。

在输出中,你会注意到所有请求都成功处理了:

Code 200 : 486 (100.0 %)
Response Header Sizes: count 486 avg 151.01235 +/- 0.1104 min 151 max 152 sum 73392
Response Body/Total Sizes : count 486 avg 223.01235 +/- 0.1104 min 223 max 224 sum 108384
All done 486 calls (plus 2 warmup) 4.120 ms avg, 484.8 qps

在这种情况下,共进行了486 次调用,成功率为 100%。接下来,我们将应用更改以强制执行速率限制:

$ kubectl apply -f Chapter5/10-connection-pooling.yaml

使用1个连接重新运行测试:

kubectl exec -it fortio-deploy-7dcd84c469-xpggh -n utilities -c fortio -- /usr/bin/fortio load -qps 0 -c 1 -t 1s -H "Host:mockshop.com" http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/
Code 200 : 175 (100.0 %)
Response Header Sizes : count 175 avg 151.01143 +/- 0.1063 min 151 max 152 sum 26427
Response Body/Total Sizes : count 175 avg 223.01143 +/- 0.1063 min 223 max 224 sum 39027
All done 175 calls (plus 1 warmup) 5.744 ms avg, 174.0 qps

再次运行测试,这次使用两个连接:

Code 200 : 193 (66.6 %)
Code 503 : 97 (33.4 %)
Response Header Sizes : count 290 avg 100.55517 +/- 71.29 min 0 max 152 sum 29161
Response Body/Total Sizes : count 290 avg 240.45517 +/- 24.49 min 223 max 275 sum 69732
All done 290 calls (plus 2 warmup) 6.915 ms avg, 288.8 qps

你会看到,33.4%的调用因 503 错误代码失败,因为目标规则强制执行了速率限制规则。

在本节中,你看到了一种速率限制的示例,速率限制在某种程度上也是基于速率限制条件的熔断。在下一节中,我们将学习如何通过检测异常值来进行熔断。

熔断器和异常值检测

在本节中,我们将讨论异常值检测和熔断器模式。熔断器是一种设计模式,它持续监控上游系统的响应处理行为,当行为不可接受时,停止向上游发送任何进一步的请求,直到该行为恢复到可接受状态。

例如,您可以监控上游系统的平均响应时间,当其超过某个阈值时,您可以决定停止向该系统发送更多请求;这就叫做触发断路器。一旦断路器被触发,您可以保持这一状态一段时间,以便上游服务自我修复。在断路器持续时间过去后,您可以重置断路器,让流量重新通过。

当断路器负责流量控制时,异常检测是一组策略,用于识别何时触发断路器。

我们将配置一个envoy Pod,随机返回一个503错误。我们将重用Chapter5/envoy-proxy-02-abort-02.yaml,在其中我们配置了一个版本的envoydummy,以 50%的概率返回503错误。

为避免混淆,请删除utilities命名空间中所有之前部署的envoydummy以及在此部分之前执行的任何 Istio 配置。

按照以下顺序执行:

Kubectl apply -f Chapter5/envoy-proxy-02.yaml
Kubectl apply -f Chapter5/envoy-proxy-02-abort-02.yaml

此时,我们有两个envoydummy Pods。对于标记为version:v1的 Pod,返回V1----------Bootstrap Service Mesh Implementation with Istio----------V1,我们已将其修改为中止 50%的请求并返回503

执行以下命令来禁用 Istio 的默认自动重试:

$ kubectl apply -f Chapter5/11-request-retry-disabled.yaml

测试请求,你会发现响应是v1v2503的混合结果。

现在,任务是定义一个异常检测策略,将v1识别为异常,因为它返回了一个503错误码。我们将通过目标规则来实现这一点,如下所示:

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: envoydummy
  namespace: utilities
spec:
  host: envoydummy.utilities.svc.cluster.local
  trafficPolicy:
      connectionPool:
        http:
          http2MaxRequests: 2
          maxRequestsPerConnection: 1
          http1MaxPendingRequests: 0
      outlierDetection:
        baseEjectionTime: 5m
        consecutive5xxErrors: 1
        interval: 1s
        maxEjectionPercent: 100

outlierDetection中,提供了以下参数:

  • baseEjectionTime:每次弹出的最小弹出持续时间,然后根据发现上游不健康的次数进行乘法计算。例如,如果某个主机被判定为异常五次,那么它将在连接池中被弹出baseEjectionTime*5

  • consecutive5xxErrors:需要发生的 5xx 错误次数,以判定上游为异常。

  • interval:Istio 扫描上游健康状态的检查间隔时间。间隔时间可以以小时、分钟或秒为单位。

  • maxEjectionPercent:连接池中可以被弹出的最大主机数。

在目标规则中,我们配置了 Istio 以 1 秒的间隔扫描上游。如果连续返回 1 次或更多的 5xx 错误,则该上游将在 5 分钟内从连接池中弹出,如果需要,所有主机都可以从连接池中弹出。

也可以为异常检测定义以下参数,但我们在本例中未使用它们:

  • splitExternalLocalOriginErrors:此标志告诉 Istio 是否应考虑本地服务的行为来判断上游服务是否为异常节点。例如,可能会返回 404,这是一个有效响应,但如果返回过于频繁,也可能意味着存在问题。也许上游服务出现了错误,但由于错误处理不当,导致上游返回 404,这进而使下游服务返回 5XX 错误。总之,此标志使异常检测不仅基于上游返回的响应代码,还基于下游系统如何感知这些响应。

  • consecutiveLocalOriginFailures:这是在上游服务从连接池中被驱逐之前,发生的连续本地错误次数。

  • consecutiveGatewayErrors:这是在上游服务被驱逐之前,发生的网关错误次数。通常由网关与上游服务之间的连接不健康或配置错误引起。当通过 HTTP 访问上游主机时,由于网关和上游服务之间的通信问题,通常会返回 HTTP 状态码 502503504

  • minHealthPercent:此字段定义了启用异常检测所需的负载均衡池中可用的最小健康上游系统数量。一旦健康上游系统的数量低于此阈值,异常检测将被禁用,以保持服务可用性。

Chapter5/12-outlier-detection.yaml 中定义的配置使我们能够快速观察异常检测的效果,但在非实验场景下部署时,需要根据弹性要求调整和配置相应的值。

应用更新后的目标规则:

$ kubectl apply -f  Chapter5/12-outlier-detection.yaml

在应用更改后,测试请求几次。你会注意到,除了少数响应包含 V1----------Bootstrap Service Mesh Implementation with Istio----------V1,大部分响应包含 V2----------Bootstrap Service Mesh Implementation with Istio----------V2,这是因为 Istio 检测到 v1 Pod 返回了 503 错误,并将其标记为连接池中的异常节点。

总结

在本章中,我们学习了 Istio 如何通过提供注入延迟和故障的选项来实现应用程序的弹性和测试。故障注入有助于验证当底层服务、网络和基础设施出现意外故障时,应用程序的弹性。在故障注入之后,我们学习了请求超时以及它们如何改善应用程序的弹性。对于瞬时故障,在放弃请求之前进行几次重试可能是一个明智的选择,因此,我们实践了配置 Istio 进行服务重试。故障注入、超时和重试是 VirtualServices 的属性,它们在将请求路由到上游服务之前执行。

在本章的第二部分,我们阅读了各种负载均衡策略,以及如何根据上游服务的动态行为配置负载均衡策略。负载均衡有助于将流量分配到上游服务,LEAST_REQUEST策略是最有效的流量分配策略,它基于任何时刻上游服务处理的请求数量进行流量分配。负载均衡在目标规则中配置,因为它是请求路由到上游服务的一部分。在负载均衡之后,我们阅读了速率限制以及它如何基于目标规则中的连接池配置。在本章的最后部分,我们阅读了如何配置目标规则来实现异常检测。

本章中我们阅读的最显著的内容是能够通过超时、重试、负载均衡、断路器和异常检测等方式实现应用程序的弹性,而无需更改应用程序代码。应用程序只需成为服务网格的一部分,就能从这些弹性策略中受益。软件工程师使用各种类型的软件和工具来执行混沌工程,以了解在遭遇故障时应用程序的弹性。你可以使用这些混沌工程工具来测试服务网格提供的应用程序弹性。

下一章非常激动人心且紧张,因为我们将阅读如何利用 Istio 为运行在网格中的应用程序实现坚如磐石的安全性。

第六章:微服务通信的安全性

Istio 可以在微服务之间保障通信安全,而无需对微服务进行任何代码修改。在第四章中,我们简要介绍了安全性的话题。我们通过将我们的 sockshop 应用暴露为 HTTPS 来配置传输层安全性。我们创建了证书,并配置 Istio Ingress 网关将这些证书绑定到主机名,采用 SIMPLE TLS 模式。我们还为由单个 Ingress 网关管理的多个主机实现了基于 TLS 的安全性。

本章将深入探讨一些安全方面的高级主题。我们将首先了解 Istio 安全架构,然后实现服务与网格内其他服务之间的互相 TLS 认证,并实现与网格外下游客户端的互相 TLS 认证。接着,我们将进行各种实践操作,创建自定义安全策略用于身份验证和授权。我们将按以下顺序进行这些主题的讲解:

  • Istio 安全架构

  • 使用互相 TLS 认证

  • 如何配置自定义的身份验证和授权策略

重要提示

本章的技术前提与第四章第五章相同。

理解 Istio 安全架构

第三章中,我们讨论了 Istio 控制平面如何负责注入 sidecar 并建立信任,使得 sidecar 可以安全地与控制平面通信,同时安全策略最终由 sidecar 强制执行。Istio 在 Kubernetes 中的部署依赖 Kubernetes 服务账户来识别服务网格中工作负载的角色。Istio CA 监视 Kubernetes API 服务器,监听启用 Istio 注入的命名空间中任何服务账户的添加、删除或修改。它为每个服务账户创建密钥和证书,并在 Pod 创建时将证书和密钥挂载到 sidecar 上。Istio CA 负责管理分发到 sidecar 上证书的生命周期,包括私钥的轮换和管理。通过使用面向所有人的安全生产身份框架SPIFFE)格式身份,Istio 为每个服务提供强大的身份及服务命名,表示分配给该服务的身份所能扮演的角色。

SPIFFE 是一套针对软件身份的开源标准。SPIFFE 提供与平台无关的可互操作软件身份,并提供获取和验证加密身份所需的接口和文档,实现完全自动化的身份管理。

在 Istio 中,每个工作负载都会自动分配一个身份,表示为 X.509 证书格式。如第三章中所述,证书签名请求CSR)的创建和签署由 Istio 控制平面管理。X.509 证书遵循 SPIFFE 格式。

让我们重新部署 envoydummy 服务并检查 envoydummy Pods:

$ kubectl apply -f Chapter6/01-envoy-dummy.yaml
$ istioctl proxy-config all po/envoydummy-2-7488b58cd7-m5vpv -n utilities -o json | jq -r '.. |."secret"?' | jq -r 'select(.name == "default")' | jq -r '.tls_certificate.certificate_chain.inline_bytes' | base64 -d - | step certificate inspect  --short
X.509v3 TLS Certificate (RSA 2048) [Serial: 3062...1679]
  Subject:     spiffe://cluster.local/ns/utilities/sa/default
  Issuer:
  Valid from:  2022-09-11T22:18:13Z
          to:  2022-09-12T22:20:13Z

步骤 CLI

你需要安装 CLI 步骤工具才能运行前面的命令。要安装它,请参考smallstep.com/docs/step-cli中的文档。

在前面命令的输出中,你会注意到 spiffe://cluster.local/ns/utilities/sa/default。这是 SPIFFE ID,作为唯一标识符:

  • spiffe 是 URI 协议

  • cluster.local 是信任域

  • /ns/utilities/sa/default 是标识与工作负载关联的服务账户的 URI:

    • ns 代表命名空间

    • sa 代表服务账户

服务账户的默认值来自于与工作负载关联的服务账户。在我们的 envoydummy 示例中,我们没有关联任何服务账户,因此,默认情况下,Kubernetes 关联了 default 服务账户。你可以使用以下命令找到与 Pod 关联的服务账户名称:

kubectl get po/envoydummy-2-7488b58cd7-m5vpv -n utilities -o json | jq .spec.serviceAccountName
"default"

你会注意到,default 是与所有命名空间中的所有 Pod 关联的默认服务账户名称,例如 sock-shoputilities 等。Kubernetes 会在每个命名空间中创建一个名为 default 的服务账户:

% kubectl get sa -n utilities
NAME      SECRETS   AGE
default   1         13d
% kubectl get sa -n sock-shop
NAME      SECRETS   AGE
default   1         27d

Kubernetes 服务账户

服务账户是 Kubernetes 中分配给工作负载的身份。当工作负载内运行的进程尝试访问其他 Kubernetes 资源时,它们会根据其服务账户的详细信息进行身份验证和认证。你可以在kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/中找到有关服务账户的更多详细信息。

安全命名是一种技术,它将服务的名称与服务运行的身份解耦。在前面的示例中,spiffe://cluster.local/ns/utilities/sa/default 是服务在 mutual TLS 中由 envoydummy-2-7488b58cd7-m5vpv 工作负载中的 istio-proxy sidecar 提供的身份。从 SPIFFE ID 中,MTLS 会话中的另一方(另一个 Pod 中的 istio-proxy)可以验证该端点是否具有名为 default 的服务账户身份,并且该服务账户位于 utilities 命名空间中。Istio 控制平面将安全命名信息传播到网格中的所有 sidecar 容器,在 mutual TLS 过程中,sidecar 容器不仅验证身份是否正确,还验证相应的服务是否采用了正确的身份。

以下图表总结了 Istio 的安全架构:

图 6.1 – Istio 安全架构

图 6.1 – Istio 安全架构

这些是需要记住的关键概念:

  • Istio CA 管理密钥和证书,并且证书中的 SANs 采用 SPIFFE 格式

  • Istiod 将认证和授权安全策略分发到网格中的所有 sidecar 容器

  • Sidecar 容器根据 Istiod 分发的安全策略强制执行认证和授权

提醒

请确保清理 Chapter6/01-envoy-dummy.yaml 文件,以避免后续练习中的冲突。

在接下来的部分中,我们将学习如何在服务网格中保护微服务之间的传输数据。

使用双向 TLS 进行身份验证

双向 TLS (mTLS) 是一种在网络连接两端验证双方身份的技术。通过 mTLS,双方可以验证对方是否如其所声明的那样。证书颁发机构在 mTLS 中扮演着关键角色,因此我们在前一部分中介绍了 Istio 安全架构,讲解了证书颁发机构和 Istio 中的安全命名。

mTLS 是实现零信任安全框架中最常用的身份验证机制之一,其中任何一方默认都不信任另一方,无论该方在网络中的位置如何。零信任假设没有传统的网络边界,因此每一方都需要进行身份验证和授权。这有助于消除基于假设信任模型所带来的许多安全漏洞。

在接下来的两个小节中,我们将了解 Istio 如何帮助你实现服务到服务的 mTLS 身份验证,也就是在网格内的东西-东西流量,以及在网格外的客户端/下游系统与网格内的服务之间的 mTLS,即南北向通信。

服务到服务的身份验证

Istio 通过使用 mTLS 进行传输认证来提供服务到服务的身份验证。在流量处理过程中,Istio 执行以下操作:

  • 所有来自 Pods 的外发流量都将被重新路由到 istio-proxy。

  • istio-proxy 与服务器端 istio-proxy 发起 mTLS 握手。在握手过程中,它还会进行安全命名检查,以验证服务器证书中呈现的服务账户是否可以运行该 Pod。

  • 服务器端的 istio-proxy 以相同的方式验证客户端的 istio-proxy,如果一切正常,则在两个代理之间建立安全通道。

Istio 在实现 mTLS 时提供以下两种选项:

  • 宽松模式:在宽松模式下,Istio 允许 mTLS 和非 mTLS 模式的流量。此功能主要是为了改善客户端向 mTLS 过渡的过程。尚未准备好通过 mTLS 进行通信的客户端可以继续通过 TLS 进行通信,预计它们最终会在准备好时迁移到 mTLS。

  • 严格模式:在严格模式下,Istio 强制执行严格的 mTLS,任何非 mTLS 流量都不被允许。

双向 TLS 流量可以在尝试访问网格内工作负载的网格外客户端与尝试访问网格内其他工作负载的网格内客户端之间建立。对于前者,我们将在下一部分中讨论详细信息。对于后者,我们将在本部分中通过一些示例进行讲解。

让我们使用 mTLS 设置服务到服务的通信:

  1. 创建一个名为chapter6的命名空间,并启用 Istio 注入,然后部署httpbin服务:

    $ kubectl apply -f Chapter6/01-httpbin-deployment.yaml
    

这个部署中的大部分配置都是常规配置,唯一不同的是我们在Chapter6命名空间中创建了一个名为httpbin的默认 Kubernetes 服务账户:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: httpbin
  namespace: chapter6

然后按照以下规范将httpbin身份分配给httpbin Pod:

  Spec:
      serviceAccountName: httpbin
      containers:
      - image: docker.io/kennethreitz/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        ports:
        - containerPort: 80
  1. 接下来,我们将创建一个以curl Pod 形式的客户端来访问httpbin服务。创建一个禁用 Istio 注入的utilities命名空间,并创建一个带有自己服务账户的curl Deployment:

    $ kubectl apply -f Chapter6/01-curl-deployment.yaml
    

确保没有应用istio-injection标签。如果有,可以使用以下命令将其移除:

$ kubectl label ns utilities istio-injection-
  1. curl Pod,尝试访问httpbin Pod,应该能收到响应:

    $ kubectl exec -it curl -n utilities – curl -v http://httpbin.chapter6.svc.cluster.local:8000/get
    {
      "args": {},
      "headers": {
        "Accept": "*/*",
        "Host": "httpbin.chapter6.svc.cluster.local:8000",
        "User-Agent": "curl/7.87.0-DEV",
        "X-B3-Sampled": "1",
        "X-B3-Spanid": "a00a50536c3ec2f5",
        "X-B3-Traceid": "49b6942c85c7c1f2a00a50536c3ec2f5"
      },
      "origin": "127.0.0.6",
      "url": "http://httpbin.chapter6.svc.cluster.local:8000/get"
    
  2. 到目前为止,我们已经在网格中运行了httpbin Pod,但默认情况下,它处于宽松 TLS 模式。现在我们将创建一个PeerAuthentication策略,以强制执行STRICT mTLS。PeerAuthentication策略定义了如何通过侧车隧道传输流量:

    apiVersion: security.istio.io/v1beta1
    kind: PeerAuthentication
    metadata:
      name: "httpbin-strict-tls"
      namespace: chapter6
    spec:
      mtls:
        mode: STRICT
      selector:
        matchLabels:
          app: httpbin
    

PeerAuthentication策略中,我们定义了以下配置参数:

  • mtls:这定义了 mTLS 设置。如果未指定,则该值从默认的网格级别设置继承。它有一个名为mode的字段,可以具有以下值:

    • UNSET:此值表示 mTLS 设置从父级继承,如果父级没有任何设置,则将值设置为PERMISSIVE

    • MTLS:此值使侧车接受 mTLS 和非 mTLS 连接。

    • STRICT:这会强制执行严格的 mTLS—任何非 mTLS 连接都会被拒绝。

    • DISABLE:禁用 mTLS,连接不进行隧道传输。

  • Selector:这定义了工作负载必须满足的条件,才能成为此身份验证策略的一部分。它有一个名为matchLabels的字段,用于以key:value格式接受标签信息。

总结一下配置,我们创建了httpbin-strict-tls,这是Chapter6命名空间中的一个PeerAuthentication策略。该策略对所有具有app=httpbin标签的工作负载强制执行 mTLS 连接。该配置文件位于Chapter6/02-httpbin-strictTLS.yaml

  1. 通过以下命令应用更改:

    $ kubectl apply -f Chapter6/02-httpbin-strictTLS.yaml
    peerauthentication.security.istio.io/httpbin-strict-tls created
    
  2. 现在,尝试从curl Pod 连接到httpbin服务:

    $ kubectl exec -it curl -n utilities – curl -v http://httpbin.chapter6.svc.cluster.local:8000/get
    * Connected to httpbin.chapter6.svc.cluster.local (172.20.147.104) port 8000 (#0)
    > GET /get HTTP/1.1
    > Host: httpbin.chapter6.svc.cluster.local:8000
    > User-Agent: curl/7.87.0-DEV
    > Accept: */*
    >
    * Recv failure: Connection reset by peer
    * Closing connection 0
    curl: (56) Recv failure: Connection reset by peer
    command terminated with exit code 56
    

curl无法连接,因为curl Pod 运行在禁用 Istio 注入的命名空间中,而httpbin Pod 运行在启用了PeerAuthentication策略并强制执行STRICT mTLS 的网格中。一种解决方案是手动建立 mTLS 连接,这相当于修改应用程序代码来执行 mTLS。在这种情况下,由于我们正在模拟网格内的服务通信,我们可以简单地启用 Istio 注入,让 Istio 处理客户端的 mTLS。

  1. 使用以下步骤为curl Pod 启用 Istio 注入:

    1. 删除由Chapter6/01-curl-deployment.yaml创建的资源。

    2. 修改 Istio 注入的值以启用。

    3. 应用更新的配置。

一旦 curl Pod 处于 RUNNING 状态,并且与 istio-proxy sidecar 一起,您可以对 httpbin 服务执行 curl,并看到以下输出:

$ kubectl exec -it curl -n utilities -- curl -s http://httpbin.chapter6.svc.cluster.local:8000/get
{
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.chapter6.svc.cluster.local:8000",
    "User-Agent": "curl/7.85.0-DEV",
    "X-B3-Parentspanid": "a35412ed46b7ec46",
    "X-B3-Sampled": "1",
    "X-B3-Spanid": "0728b578e88b72fb",
    "X-B3-Traceid": "830ed3d5d867a460a35412ed46b7ec46",
    "X-Envoy-Attempt-Count": "1",
    "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/chapter6/sa/
httpbin;Hash=b1b88fe241c557bd1281324b458503274eec3f04b1d439758508842d6d5b7018;Subject=\"\";URI=spiffe://cluster.local/ns/utilities/sa/curl"
  },
  "origin": "127.0.0.6",
  "url": "http://httpbin.chapter6.svc.cluster.local:8000/get"
}

httpbin 服务的响应中,您会注意到所有由 httpbin Pod 接收到的头信息。最有趣的头信息是 X-Forwarded-Client-Cert,也叫 XFCCXFCC 头值的两个部分揭示了 mTLS 的信息:

  • By:这里填写的是 SAN,即 httpbin Pod 的 istio-proxy 客户端证书的 SPIFFE ID(spiffe://cluster.local/ns/chapter6/sa/httpbin

  • URI:它包含 SAN,这是在 mTLS 过程中呈现的 curl Pod 客户端证书的 SPIFFE ID(spiffe://cluster.local/ns/utilities/sa/curl

还有 Hash,它是 httpbin Pod 的 istio-proxy 客户端证书的 SHA256 摘要。

您也可以在端口级别选择性地应用 mTLS 配置。在以下配置中,我们暗示所有端口都严格执行 mTLS,除了 8080 端口,它应该允许宽松的连接。配置文件位于 Chapter6/03-httpbin-strictTLSwithException.yaml

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: "httpbin-strict-tls"
  namespace: chapter6
spec:
  portLevelMtls:
    8080:
      mode: PERMISSIVE
    8000:
      mode: STRICT
  selector:
    matchLabels:
      app: httpbin

所以,在本节中,我们学习了如何在网格内的服务之间执行 mTLS。mTLS 可以在服务级别以及端口级别启用。在下一节中,我们将学习如何与网格外的客户端执行 mTLS。

提醒

确保清理 Chapter6/01-httpbin-deployment.yamlChapter6/01-curl-deployment.yamlChapter6/02-httpbin-strictTLS.yaml,以避免在接下来的练习中发生冲突。

与网格外客户端的身份验证

对于网格外的客户端,Istio 支持通过 Istio Ingress 网关进行 mTLS。在第五章中,我们已在 Ingress 网关上配置了 HTTPS。在本节中,我们将扩展该配置以同时支持 mTLS。

现在我们将为 httpbin Pod 配置 mTLS。请注意,前五个步骤与第五章通过 HTTPS 暴露 Ingress步骤 1-5非常相似。步骤如下:

  1. 创建一个 CA。在这里,我们正在用 sock.inc 创建一个 CA:

    $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=sock Inc./CN=sock.inc' -keyout sock.inc.key -out sock.inc.crt
    
  2. httpbin.org 生成 CSR。在这里,我们正在为 httpbin.org 生成 CSR,这也会生成一个私钥:

    $ openssl req -out httpbin.org.csr -newkey rsa:2048 -nodes -keyout httpbin.org.key -subj "/CN=httpbin.org/O=sockshop.inc"
    Generating a 2048 bit RSA private key
    .........+++
    ..+++
    writing new private key to 'httpbin.org.key'
    
  3. 使用第 1 步中创建的 CA 签署 CSR:

    $ openssl x509 -req -sha256 -days 365 -CA sock.inc.crt -CAkey sock.inc.key -set_serial 0 -in httpbin.org.csr -out httpbin.org.crt
    Signature ok
    subject=/CN=httpbin.org/O=sockshop.inc
    Getting CA Private Key
    
  4. 将证书和私钥作为 Kubernetes 密钥与必须验证客户端证书的 CA 证书一起加载:

    $ kubectl create -n istio-system secret generic httpbin-credential --from-file=tls.key=httpbin.org.key --from-file=tls.crt=httpbin.org.crt --from-file=ca.crt=sock.inc.crt
    secret/httpbin-credential created
    
  5. 配置 Ingress 网关以强制所有传入连接使用 mTLS,并使用第 4 步中创建的密钥作为包含 TLS 证书和 CA 证书的密钥:

      tls:
          mode: MUTUAL
          credentialName: httpbin-credential
    
  6. 部署 httpbin Pod、Ingress 网关和虚拟服务:

    $ kubectl apply -f Chapter6/02-httpbin-deployment-MTLS.yaml
    
  7. 要执行 mTLS,您还需要生成客户端证书,以证明客户端的身份。为此,请执行以下步骤:

    $ openssl req -out bootstrapistio.packt.com.csr -newkey rsa:2048 -nodes -keyout bootstrapistio.packt.com.key -subj "/CN= bootstrapistio.packt.com/O=packt.com"
    $ openssl x509 -req -sha256 -days 365 -CA sock.inc.crt -CAkey sock.inc.key -set_serial 0 -in bootstrapistio.packt.com.csr -out bootstrapistio.packt.com.crt
    
  8. 通过在请求中传递客户端证书来测试与 httpbin.org 的连接:

    % curl -v -HHost:httpbin.org --connect-to "httpbin.org:443:a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com" --cacert sock.inc.crt --cert bootstrapistio.packt.com.crt --key bootstrapistio.packt.com.key https://httpbin.org:443/get
    

提醒

别忘了清理,使用kubectl delete -n istio-system secret httpbin-credentialkubectl delete -f Chapter6/02-httpbin-deployment-MTLS.yaml

配置 RequestAuthentication

像服务到服务的身份验证一样,Istio 也可以验证终端用户,或者基于终端用户提供的声明验证终端用户是否已被验证。RequestAuthentication策略用于指定工作负载支持的身份验证方法。该策略识别经过身份验证的身份,但不强制要求请求是否应该被允许或拒绝。相反,它将经过身份验证的身份信息提供给授权策略,我们将在下一节中介绍。在本节中,我们将学习如何利用 Istio 的RequestAuthentication策略来验证已经通过 Auth0 身份验证并提供承载令牌作为 Istio 安全凭证的终端用户。如果你不熟悉 OAuth,可以在auth0.com/docs/authenticate/protocols/oauth查看更多信息。

我们将按照动手操作步骤配置 Auth0,并执行 OAuth 流程,同时揭开所有幕后发生的事情:

  1. 注册 Auth0:

图 6.2 – Auth0 注册

图 6.2 – Auth0 注册

  1. 注册后,在 Auth0 中创建应用:

图 6.3 – 在 Auth0 中创建应用

图 6.3 – 在 Auth0 中创建应用

  1. 创建应用后,你需要创建一个 API。你可以提供 Ingress URL 作为标识符:

图 6.4 – 在 Auth0 中创建 API

图 6.4 – 在 Auth0 中创建 API

  1. 声明 API 消费者需要具备的权限,才能访问该 API:

图 6.5 – Auth0 中的 API 作用域

图 6.5 – Auth0 中的 API 作用域

  1. 常规设置启用 API 的 RBAC:

图 6.6 – 在 Auth0 中启用 API 的 RBAC

图 6.6 – 在 Auth0 中启用 API 的 RBAC

  1. 创建 API 后,返回应用程序并授权应用访问 EnvoyDummy API,同时也请配置作用域:

图 6.7 – 在 Auth0 中授予应用权限

图 6.7 – 在 Auth0 中授予应用权限

  1. 最后一步,进入应用页面,获取可以用来获取访问令牌的请求:

图 6.8 – 在 Auth0 中获取访问令牌的快速入门示例

图 6.8 – 在 Auth0 中获取访问令牌的快速入门示例

复制curl字符串,包括client_idclient_secret等,至此我们完成了所有 Auth0 的步骤。

现在,使用你在前面步骤中复制的curl字符串,从终端获取访问令牌:

$ curl --request POST --url https://dev-0ej7x7k2.us.auth0.com/oauth/token --header 'content-type:application/json' --data '{"client_id":"XXXXXX-id","client_secret":"XXXXX-secret"," "audience":"http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/","grant_type":"client_credentials"}'
{"access_token":"xxxxxx-accesstoken" "scope":"read:dummyData","expires_in":86400,"token_type":"Bearer"}%

一旦我们收到访问令牌,我们将应用 RequestAuthentication 策略。RequestAuthentication 策略指定了如何验证身份验证过程中提供的 JWT。以下是 RequestAuthentication 策略:

apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
  name: "auth0"
  namespace: chapter6
spec:
  selector:
    matchLabels:
      name: envoydummy
  jwtRules:
  - issuer: "https://dev-0ej7x7k2.us.auth0.com/"
    jwksUri: "https://dev-0ej7x7k2.us.auth0.com/.well-known/jwks.json"

在之前的配置中,也可以在 Chapter6/01-requestAuthentication.yaml 中找到,我们在 chapter6 命名空间中声明了一个 auth0,其规格如下:

  • issuer: 这是 Auth0 应用程序域的值。你可以从以下屏幕中获取该值:

图 6.9 – 应用程序域

图 6.9 – 应用程序域

  • jwksUri: 这是 JWKS 端点,Istio 可以用它来验证签名。Auth0 为每个租户暴露一个 JWKS 端点,地址为 https://DOMAIN/.well-known/jwks.json。该端点将包含用于验证该租户所有 Auth0 发放的 JWT 的 JWK。请将 DOMAIN 的值替换为应用程序中的值。

在使用 RequestAuthentication 策略时,最佳实践是将 RequestAuthenticationAuthorizationPolicy 一起配置,并强制执行一个规则,要求任何没有主体的请求都不允许。以下是一个示例授权策略——你将在下一节中阅读更多关于授权策略的内容:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: auth0-authz
  namespace: chapter6
spec:
  action: DENY
  selector:
    matchLabels:
      name: envoydummy
  rules:
  - from:
    - source:
        notPrincipals: ["*"]

配置 RequestAuthorization

在上一节中,我们配置了一个 RequestAuthentication 策略,用于根据 JWKS 位置验证 JWT 令牌的颁发者和 JWK 细节。我们将 Auth0 配置为身份验证提供程序,并生成承载令牌。在本节中,我们将学习如何使用身份验证策略提供的信息,例如对等身份验证和请求身份验证,以授权客户端访问服务器(请求的资源、Pod、工作负载、服务等)。

我们将首先专注于实现与上一节中的 RequestAuthentication 策略结合的授权策略。

为了让 curl 使用 Auth0 发放的访问令牌访问 envoy 模拟器,我们需要创建一个 AuthorizationPolicy

apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "envoydummy-authz-policy"
  namespace: utilities
spec:
  action: ALLOW
  selector:
    matchLabels:
      name: envoydummy
  rules:
  - when:
    - key: request.auth.claims[permissions]
      values: ["read:profile"]

AuthorizationPolicy 包含以下数据:

  • action: 这定义了当请求匹配定义的规则时应采取的操作类型。action 的可能值有 ALLOWDENYAUDIT

  • selector: 这定义了该策略应应用于哪些工作负载。在这里,你需要提供一组标签,这些标签应与工作负载的标签匹配,才能成为选择的一部分。

  • rules: 在这里,我们定义了一组规则,这些规则应与请求匹配。规则包含以下子配置:

    • source: 这是关于请求来源的规则。

    • to: 这是关于请求的规则,比如请求是发往哪个主机、方法名是什么以及 URI 标识的资源是什么。

    • when:此字段指定一组附加条件。你可以在istio.io/latest/docs/reference/config/security/conditions/找到所有参数的详细列表。

在此示例中,我们定义了一个授权策略,允许访问带有标签name:envoydummy的 Pods,如果请求包含带有read:profile声明的已认证 JWT 令牌。

在应用更改之前,请确保你可以访问虚拟数据,并确保你已经在utilities命名空间中部署了 Ingress 网关和envoydummy Pods——如果没有,你可以通过以下命令来实现:

$ curl -Hhost:mockshop.com http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/
V1----------Bootstrap Service Mesh Implementation with Istio----------V1%

继续应用这两个策略:

% kubectl apply -f Chapter6/01-requestAuthentication.yaml
requestauthentication.security.istio.io/auth0 created
% kubectl apply -f Chapter6/02-requestAuthorization.yaml
authorizationpolicy.security.istio.io/envoydummy-authz-policy created

检查你是否能够访问mockshop.com

% curl -Hhost:mockshop.com http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/
RBAC: access denied

访问被拒绝,因为我们需要提供有效的访问令牌作为请求的一部分。复制你从上一个请求中获得的访问令牌,并按以下方式重试:

$ curl -Hhost:mockshop.com -H "authorization: Bearer xxxxxx-accesstoken " http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/
RBAC: access denied%

尽管 JWT 验证成功,但由于 RBAC 控制,请求失败。该错误是故意的,因为我们在Chapter6/02-requestAuthorization.yaml中没有提供read:dummyData,而是提供了read:profile。这些更改已更新在Chapter6/03-requestAuthorization.yaml中。应用更改并测试 API:

% kubectl apply -f Chapter6/03-requestAuthorization.yaml
authorizationpolicy.security.istio.io/envoydummy-authz-policy configured
$ curl -Hhost:mockshop.com -H "authorization: Bearer xxxxxx-accesstoken " http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/
V2----------Bootstrap Service Mesh Implementation with Istio----------V2%

总结一下,我们做了以下操作,包括前面的部分:

  1. 我们将 Auth0 配置为身份验证提供者和 OAuth 服务器。

  2. 我们创建了一个RequestAuthentication策略,用于验证请求中提供的承载令牌。

  3. 我们创建了一个AuthorizationPolicy,验证 JWT 令牌中呈现的声明以及声明是否匹配所需的值,然后允许请求通过上游。

接下来,我们将学习如何将请求授权配置与我们在服务到服务 身份验证部分配置的PeerAuthentication结合使用。

我们将修改curl Pod,使用另一个服务账户,并将其命名为chapter6sa

apiVersion: v1
kind: ServiceAccount
metadata:
  name: chapter6sa
  namespace: utilities

由于你无法更改现有 Pod 的服务账户,因此需要删除之前的部署,并使用新的服务账户重新部署:

Kubectl delete -f Chapter6/01-curl-deployment.yaml
kubectl apply -f Chapter6/02-curl-deployment.yaml

你可以检查curl Pod 是否使用chapter6sa服务账户身份运行。之后,让我们创建一个AuthorizationPolicy,允许对httpbin Pod 的请求,如果请求方的主体是cluster.local/ns/utilities/sa/curl

apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "httpbin-authz-policy"
  namespace: chapter6
spec:
  action: ALLOW
  selector:
    matchLabels:
      app: httpbin
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/utilities/sa/curl"]
    to:
    - operation:
        methods: ['*']

之前我们看过AuthorizationPolicy,你会对这个示例中的大部分配置很熟悉。在这个示例中,我们在对等身份验证的基础上构建了AuthorizationPolicy,而不是请求身份验证。最有趣的部分是rules部分中的source字段。在source配置中,我们定义了请求的源身份。源请求中的所有字段需要匹配,规则才会成功。

以下字段可以在source配置中定义:

  • principals:这是一个接受的身份列表,这些身份是通过 mTLS 的客户端证书派生的。值的格式为<TRUST_DOMAIN NAME>/ns/<NAMESPACE NAME>/sa/<SERVICE_ACCOUNT NAME>。在本例中,principals的值将是cluster.local/ns/utilities/sa/curl

  • notPrincipals:这是一个列出将不接受请求的身份的列表。值的派生方式与principals相同。

  • requestPrincipals:这是一个接受的身份列表,请求主体来自 JWT,并且格式为<ISS>/<SUB>

  • notRequestPrincipals:这是一个列出将不接受请求的身份的列表。主体来自 JWT,格式为<ISS>/<SUB>

  • namespaces:这是一个列出请求将被接受的命名空间的列表。命名空间是从对等证书详细信息中派生的。

  • notNamespaces:这是一个列出不允许请求的命名空间的列表。命名空间是从对等证书详细信息中派生的。

  • ipBlocks:这是一个列出将接受请求的 IP 或 CIDR 块的列表。IP 是从 IP 数据包的源地址中填充的。

  • notIpBlocks:这是一个列出将拒绝请求的 IP 块的列表。

  • remoteIpBlocks:这是一个 IP 块的列表,从X-Forwarded-For头部或代理协议中填充。

  • notRemoteIpBlocks:这是remoteIpBlocks的负面列表。

请继续并应用配置,测试是否能够使用curl访问httpbin

$ kubectl apply -f Chapter6/04-httpbinAuthorizationForSpecificSA.yaml
authorizationpolicy.security.istio.io/httpbin-authz-policy configured
$ kubectl exec -it curl -n utilities -- curl -v http://httpbin.chapter6.svc.cluster.local:8000/headers
*   Trying 172.20.152.62:8000...
* Connected to httpbin.chapter6.svc.cluster.local (172.20.152.62) port 8000 (#0)
> GET /headers HTTP/1.1
> Host: httpbin.chapter6.svc.cluster.local:8000
> User-Agent: curl/7.85.0-DEV
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 403 Forbidden
< content-length: 19
< content-type: text/plain
< date: Tue, 20 Sep 2022 02:20:39 GMT
< server: envoy
< x-envoy-upstream-service-time: 15
<
* Connection #0 to host httpbin.chapter6.svc.cluster.local left intact
RBAC: access denied%

Istio 拒绝curl Pod 到httpbin的请求,因为curl Pod 提供的对等证书包含cluster.local/ns/utilities/sa/chapter6sa,而不是cluster.local/ns/utilities/sa/curl作为主体。虽然curl Pod 是网格的一部分并且包含有效证书,但它没有被授权访问httpbin Pod。

请继续并通过将正确的服务帐户分配给curl Pod 来解决问题。

提示

你可以使用以下命令来解决这个问题:

$ kubectl delete -f Chapter6/02-curl-deployment.yaml
$ kubectl apply -f Chapter6/03-curl-deployment.yaml

我们将实现另一个授权策略,但这次策略将强制要求使用utilitiescurl服务帐户时,请求者只能访问httpbin/headers

以下是授权策略:

apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "httpbin-authz-policy"
  namespace: chapter6
spec:
  action: ALLOW
  selector:
    matchLabels:
      app: httpbin
  rules:
  - from:
    - source:
        requestPrincipals: ["cluster.local/ns/utilities/sa/curl"]
  - to:
    - operation:
        methods: ["GET"]
        paths: ["/get"]

在此策略中,我们在规则的to字段中定义了HTTP方法和HTTP路径。to字段包含将应用规则的操作列表。操作字段支持以下参数:

  • hosts:此项指定将接受请求的主机名列表。如果未设置,则允许任何主机。

  • notHosts:这是一个负面主机列表。

  • ports:这是一个列出将接受请求的端口的列表。

  • notPorts:这是一个列出端口的负面匹配列表。

  • methods:这是一个列出 HTTP 请求方法的列表。如果未设置,则允许任何方法。

  • notMethods: 这是一个包含方法负匹配的列表,方法是按 HTTP 请求指定的。

  • paths: 这是一个按 HTTP 请求指定的路径列表。路径会根据istio.io/latest/docs/reference/config/security/normalization/进行规范化。

  • notPaths: 这是一个包含路径负匹配的列表。

应用更改:

kubectl apply -f Chapter6/05-httpbinAuthorizationForSpecificPath.yaml

然后尝试访问httpbin

kubectl exec -it curl -n utilities -- curl -X GET -v http://httpbin.chapter6.svc.cluster.local:8000/headers
……
RBAC: access denied%

由于授权策略仅允许使用 HTTP GET 方法发出的/get请求,因此访问请求被拒绝。以下是正确的请求:

kubectl exec -it curl -n utilities -- curl -X GET -v http://httpbin.chapter6.svc.cluster.local:8000/get

本节课讲解了如何为请求认证和请求授权构建自定义策略。为了更熟悉这些内容,我建议多读几遍本章的示例,或者尝试自己构建一些变体,以学习如何有效使用这些策略。

总结

在本章中,我们学习了 Istio 如何提供身份验证和授权。我们还了解了如何使用PeerAuthentication策略在服务网格中实现服务到服务的身份验证,以及通过 Ingress 网关使用mutual TLS 模式与服务网格外部的客户端进行互认证 TLS。然后我们学习了如何使用RequestAuthentication策略进行终端用户身份验证。我们配置了 Auth0,获得了使用身份验证和身份提供商的真实经验。

最后,我们阅读了关于AuthorizationPolicy以及如何使用它来强制执行各种授权检查,以确保经过身份验证的身份被授权访问请求的资源。

在下一章中,我们将学习 Istio 如何帮助使微服务具备可观察性,以及如何将各种可观察性工具和软件与 Istio 集成。

第七章:服务网格可观测性

使用微服务架构构建的分布式系统复杂且不可预测。无论你在编写代码时多么细心,失败、崩溃、内存泄漏等问题都是很可能发生的。应对此类事件的最佳策略是主动观察系统,以识别任何故障或可能导致故障的情况,或其他任何不利的行为。

观察系统可以帮助你了解系统行为以及故障背后的根本原因,从而使你能够自信地排除问题并分析潜在修复的效果。在本章中,你将阅读为什么可观测性很重要,如何从 Istio 收集遥测信息,不同类型的指标以及如何通过 API 获取它们,以及如何启用分布式追踪。我们将通过讨论以下主题来进行:

  • 理解可观测性

  • 使用 Prometheus 抓取指标

  • 自定义 Istio 指标

  • 使用 Grafana 可视化遥测数据

  • 实现分布式追踪

不再耽搁,让我们从理解可观测性开始。

重要提示

本章的技术前提与前几章相同。

理解可观测性

可观测性的概念最初作为控制理论的一部分提出,控制理论处理的是自我调节动态系统的控制。控制理论是一个抽象的概念,具有跨学科的应用;它基本上提供了一个模型,指导系统输入的应用,以将系统驱动到所需状态,同时最大化其稳定性和性能。

图 7.1 – 控制理论中的可观测性

图 7.1 – 控制理论中的可观测性

系统的可观测性是衡量我们如何根据外部输出的信号和观察来理解系统内部状态的能力。然后,控制器利用这些信息对系统进行补偿控制,以将其驱动到所需状态。如果一个系统发出信号,控制器可以使用这些信号来确定系统的状态,那么该系统就被视为可观测的。

在 IT 世界中,系统是软件系统,控制器是其他软件系统或有时是人工操作员(例如站点可靠性工程师SREs)),他们依赖可观测系统提供的测量数据。如果你希望你的软件系统具备韧性并自我调节,那么确保软件系统的各个部分都是可观测的非常重要。

另一个需要记住的概念是遥测数据,它是系统用于观察系统可观测性的传输数据。通常,这些数据是日志、事件追踪和指标:

  • 日志:这些是由软件系统以详细格式输出的信息。日志通常是应用程序发出的数据,并且在设计应用程序时就已经预先设定。开发人员常常通过将日志与生成它们的代码块关联,来使用日志进行代码故障排除。日志可以是结构化的,这意味着所有日志条目都遵循特定的模式,使得观察系统更容易获取和理解它们。日志也可以是非结构化的,这不幸的是,大多数日志都是这种情况。Istio 会生成每个请求的完整记录,包括源和目标的元数据。

  • 追踪:在分布式系统或应用程序中,追踪是查找请求或活动如何跨多个组件处理和执行的手段。追踪由描述系统内执行/软件处理的跨度组成。多个跨度被组合在一起,形成一个请求执行的追踪。追踪描述了不同系统之间的关系,以及它们如何合作完成一个任务。为了使分布式系统中的追踪工作,所有系统之间共享上下文非常重要,这些上下文通常以关联 ID 或类似的形式存在,所有参与的系统都可以理解并遵守。Istio 为每个服务生成分布式追踪跨度,提供请求流和各个服务之间的相互依赖关系的详细信息。

  • 500 响应码。饱和度显示了系统资源(如内存、CPU、网络和存储)被系统使用的程度。Istio 会生成数据平面和控制平面的度量数据。

所有这些遥测数据结合使用,以提供系统的可观察性。有各种类型的开源和商业软件可用于观察软件系统;Istio 提供了多种现成的工具,我们在第二章中简要讨论过。Prometheus 和 Grafana 是 Istio 默认提供的;在下一节中,我们将安装 Prometheus 和 Grafana,并配置它们以收集 Istio 的度量数据。

使用 Prometheus 进行度量抓取

Prometheus 是一个开源的系统监控软件,它存储所有度量信息及其记录的时间戳。Prometheus 与其他监控软件的区别在于其强大的多维数据模型和一个强大的查询语言 PromQL。它通过从各个目标收集数据,然后分析和处理这些数据以生成度量。系统还可以实现提供度量数据的 HTTP 端点;这些端点随后被 Prometheus 调用,以收集来自应用程序的度量数据。收集来自不同 HTTP 端点的度量数据的过程也称为 抓取

如下图所示,Istio 控制平面和数据平面组件暴露了发出指标的端点,Prometheus 被配置为抓取这些端点以收集指标数据并将其存储在时间序列数据库中:

图 7.2 – 使用 Prometheus 进行指标抓取

图 7.2 – 使用 Prometheus 进行指标抓取

我们将在接下来的章节中详细描述这个过程。

安装 Prometheus

Istio 已经提供了一个示例安装文件,位于 /sample/addons/prometheus.yaml,这个文件足够作为起点。我们对文件进行了少量修改,以适应仅支持严格 mTLS 模式的应用程序:

% kubectl apply -f Chapter7/01-prometheus.yaml
serviceaccount/prometheus created
configmap/prometheus created
clusterrole.rbac.authorization.k8s.io/prometheus created
clusterrolebinding.rbac.authorization.k8s.io/prometheus created
service/prometheus created
deployment.apps/prometheus created

我们文件 01-prometheus.yaml 中的更改与原始文件相比,主要是我们通过注入一个 sidecar 为 Prometheus 配置了 Istio 证书,并配置其将证书写入共享卷,再将其挂载到 Prometheus 容器中。这个 sidecar 只是用于挂载和管理证书,不会拦截任何进出请求。您可以在 Chapter7/01-prometheus.yaml 中找到更改内容。

您可以检查在 istio-system 命名空间中安装了哪些内容:

% kubectl get po -n istio-system
NAME                       READY   STATUS    RESTARTS   AGE
istio-egressgateway-7d75d6f46f-28r59   1/1     Running   0          48d
istio-ingressgateway-5df7fcddf-7qdx9   1/1     Running   0          48d
istiod-56fd889679-ltxg5                1/1     Running   0          48d
prometheus-7b8b9dd44c-sp5pc            2/2     Running   0          16s

现在,我们将看看如何部署示例应用程序。

部署示例应用程序

让我们部署启用了 istio-injectionsockshop 应用程序。

使用以下代码修改 sockshop/devops/deploy/kubernetes/manifests/00-sock-shop-ns.yaml

apiVersion: v1
kind: Namespace
metadata:
  labels:
    istio-injection: enabled
  name: sock-shop

然后,部署 sockshop 应用程序:

% kubectl apply -f sockshop/devops/deploy/kubernetes/manifests/

最后,我们将配置一个 Ingress 网关:

% kubectl apply -f Chapter7/sockshop-IstioServices.yaml

现在,从浏览器进行一些调用,向前端服务发送流量,正如您在前几章中所做的那样。然后,我们将检查 Prometheus 抓取的一些指标,以访问仪表盘,使用以下命令:

% istioctl dashboard prometheus
http://localhost:9090

从仪表盘中,我们首先检查 Prometheus 是否正在抓取指标。可以通过点击 Prometheus 仪表盘中的 Status | Targets 来查看:

图 7.3 – Prometheus 配置

图 7.3 – Prometheus 配置

您将看到 Prometheus 正在抓取指标的所有目标。

在仪表盘中,我们将发起一个查询,以获取 istio- Ingress 网关和前端服务之间的总请求数,使用以下代码:

istio_requests_total{destination_service="front-end.sock-shop.svc.cluster.local",response_code="200",source_app="istio-ingressgateway",namespace="sock-shop"}

图 7.4 – PromQL

图 7.4 – PromQL

在前面的截图中,指标的名称是 istio_requests_total,大括号中的字段被称为 istio_requests_total 指标,其维度分别是 destination_serviceresponse_codesource_appnamespace,用于匹配 front-end.sock-shop.svc.cluster.local200istio-ingressgatewaysock-shop 的值。

作为响应,我们收到一个指标计数 51 和其他作为指标的一部分的维度。

让我们再执行一个查询,检查从前端服务到目录服务的请求数量,使用以下代码:

istio_requests_total{destination_service="catalogue.sock-shop.svc.cluster.local",source_workload="front-end",reporter="source",response_code="200"}

在查询中注意到,我们提供了 reporter = "source",这意味着我们希望由前端 Pod 上报的指标。

图 7.5 – 从前端到目录的 PromQL istio_request_total

图 7.5 – 从前端到目录的 PromQL istio_request_total

如果你更改 reporter = "destination",你将看到类似的指标,但由目录 Pod 上报。

图 7.6 – 从前端到目录的 PromQL istio_request_total,由目录 sidecar 上报

图 7.6 – 从前端到目录的 PromQL istio_request_total,由目录 sidecar 上报

让我们还检查一下目录服务与 MySQL 目录数据库之间的数据库连接,使用以下查询:

istio_tcp_connections_opened_total{destination_canonical_service="catalogue-db",source_workload="catalogue", source_workload_namespace="sock-shop}

图 7.7 – 目录与目录-db 之间的 PromQL TCP 连接

图 7.7 – 目录与目录-db 之间的 PromQL TCP 连接

指标数据显示,目录服务建立了七个 TCP 连接。

到目前为止,我们使用的是默认的指标配置。在下一节中,我们将介绍这些指标是如何配置的,以及如何通过添加新的指标来定制它们。

定制 Istio 指标

Istio 提供了灵活性,可以观察到除了开箱即用的指标之外的其他指标。这为观察特定应用程序的指标提供了灵活性。考虑到这一点,让我们首先来看一下由 sidecar 暴露的 /stats/prometheus 端点:

% kubectl exec front-end-6c768c478-82sqw -n sock-shop -c istio-proxy -- curl -sS 'localhost:15000/stats/prometheus' | grep istio_requests_total

以下截图显示了该端点返回的示例数据,这些数据也被 Prometheus 抓取,与你在上一节中使用仪表板看到的数据相同:

图 7.8 – Istio 指标、维度和数值

图 7.8 – Istio 指标、维度和数值

该指标按以下结构组织:

  • 指标名称:这是由 Istio 导出的指标名称。开箱即用的 Istio 生成了许多指标详情,可以在 istio.io/latest/docs/reference/config/metrics/#metrics 中找到。

  • 指标维度:这些是指标的一部分的各个字段。在 Prometheus 上下文中,这些字段称为维度,在 Istio 指标上下文中称为标签。有关 Istio 指标标准标签部分的详细信息,请访问 istio.io/latest/docs/reference/config/metrics/#labels

  • 指标值:这是指标的值,可以是计数器、仪表或直方图。

  • 计数器用于跟踪事件的发生。计数器是持续增加的值,并以时间序列的形式呈现。计数器类型值的指标示例包括请求计数、接收字节和 TCP 连接。

  • 仪表是单个时间点上测量的快照。它用于衡量如 CPU 消耗和内存消耗等指标。

  • 顾名思义,直方图用于衡量一段时间内的观测数据。它们也是最复杂的度量类型。

Istio 的遥测组件由 proxy-wasm 插件实现。我们将在第九章中深入了解这个插件,但目前只需理解它是用来为 Envoy 构建扩展的工具。你可以使用以下命令查找这些过滤器:

% kubectl get EnvoyFilters -A
NAMESPACE      NAME                               AGE
istio-system   stats-filter-1.16                  28h
istio-system   tcp-stats-filter-1.16              28h

这些过滤器在请求执行的不同阶段运行 WebAssembly,并收集各种指标。使用相同的技术,你可以通过添加/删除新的维度轻松自定义 Istio 指标。你还可以添加新的指标或覆盖任何现有的指标。我们将在接下来的章节中讨论如何实现这一点。

为 Istio 指标添加维度

istio_request_total 指标没有请求路径的维度——也就是说,我们无法统计各个请求路径的请求数量。我们将配置一个 EnvoyFilter,将 request.url_path 包含到 request_total 指标中。请注意,istio_ 是 Prometheus 添加的前缀;在 Istio 的上下文中,实际的指标名称是 request_total

我们将在第九章中讨论 EnvoyFilter,因此如果你想跳转到该章节了解扩展 Istio 的各种方式,请随时去阅读;或者,你也可以在 EnvoyFilter 参考 中了解此过滤器。

在以下配置中,我们创建了一个 EnvoyFilter,应用于前端 Pods,并使用 workloadSelector 中的条件,代码如下:

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: custom-metrics
  namespace: sock-shop
spec:
  workloadSelector:
    labels:
      name: front-end

接下来,我们将 configPatch 应用到 HTTP_FILTER,以便为侧车的入站流量进行配置。其他选项包括 SIDECAR_OUTBOUNDGATEWAY。此补丁应用于 HTTP 连接管理器过滤器,特别是 istio.stats 子过滤器;这是我们在前一部分讨论的过滤器,负责 Istio 的遥测功能:

configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_INBOUND
      listener:
        filterChain:
          filter:
            name: envoy.filters.network.http_connection_manager
            subFilter:
              name: istio.stats
      proxy:
        proxyVersion: ¹\.16.*

请注意,代理版本(1.16)必须与你安装的 Istio 版本匹配。

接下来,我们将用以下内容替换 istio.stats 过滤器的配置:

patch:
      operation: REPLACE
      value:
        name: istio.stats
        typed_config:
          '@type': type.googleapis.com/udpa.type.v1.TypedStruct
          type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
          value:
            config:
              configuration:
                '@type': type.googleapis.com/google.protobuf.StringValue
                value: |
                  {
                    "debug": "false",
                    "stat_prefix": "istio",
                    "metrics": [
                      {
                        "name": "requests_total",
                        "dimensions": {
                          "request.url_path": "request.url_path"
                        }
                      }
                    ]
                  }

在此配置中,我们通过添加一个名为 request.url.path 的新维度来修改 metrics 字段,其值与 Envoy 的 request.url.path 属性相同。要删除任何现有维度——例如 response_flag——请使用以下配置:

"metrics": [
                      {
                        "name": "requests_total",
                        "dimensions": {
                          "request.url_path": "request.url_path"
                        },
                        "tags_to_remove": [
                          "response_flags"
                        ]
                      }

然后,应用配置:

% kubectl apply -f Chapter7/01-custom-metrics.yaml
envoyfilter.networking.istio.io/custom-metrics created

默认情况下,Istio 不会将新添加的 request.url.path 维度包含到 Prometheus 中;需要应用以下注解以包含 request.url_path

spec:
  template:
    metadata:
      annotations:
        sidecar.istio.io/extraStatTags: request.url_path

将更改应用到前端部署:

% kubectl patch Deployment/front-end -n sock-shop --type=merge --patch-file Chapter7/01-sockshopfrontenddeployment_patch.yaml

你现在将能够看到新维度已添加到 istio_requests_total 指标中:

图 7.9 – 新的指标维度

图 7.9 – 新的指标维度

您可以将任何 Envoy 属性作为指标的维度,并且可以在 www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes 找到所有可用属性的完整列表。

创建新的 Istio 指标

您还可以使用 EnvoyFilter 创建一个新的 Istio 指标,类似于您用于创建自定义指标的方式。

在以下示例中,我们使用 definitions 创建了新的指标,并且添加了另一个维度:

              configuration:
                '@type': type.googleapis.com/google.protobuf.StringValue
                value: |
                  {
                    "debug": "false",
                    "stat_prefix": "istio",
                    "definitions": [
                      {
                        "name": "request_total_bymethod",
                        "type": "COUNTER",
                        "value": "1"
                      }
                    ],
                    "metrics": [
                      {
                        "name": "request_total_bymethod",
                        "dimensions": {
                          "request.method": "request.method"
                        }
                      }
                    ]
                  }

接下来,应用更改:

% kubectl apply -f Chapter7/02-new-metric.yaml
envoyfilter.networking.istio.io/request-total-bymethod configured

我们还必须为前端 Pod 添加 sidecar.istio.io/statsInclusionPrefixes 注释,以便将 request_total_bymethod 指标包含在 Prometheus 中:

% kubectl patch Deployment/front-end -n sock-shop --type=merge --patch-file Chapter7/02-sockshopfrontenddeployment_patch.yaml
deployment.apps/front-end patched

最好重新启动前端 Pod 以确保注释已应用。应用更改后,您可以使用以下代码抓取 Prometheus 端点:

% kubectl exec front-end-58755f99b4-v59cd -n sock-shop -c istio-proxy -- curl -sS 'localhost:15000/stats/prometheus' | grep request_total_bymethod
# TYPE istio_request_total_bymethod counter
istio_request_total_bymethod{request_method="GET"} 137

同时,使用 Prometheus 仪表盘,检查新指标是否可用:

图 7.10 – 新指标

图 7.10 – 新指标

通过此步骤,您现在应该能够创建一个带有维度的新 Istio 指标,并且更新任何现有指标的维度。在下一节中,我们将了解 Grafana,这是另一个强大的可观察性工具。

使用 Grafana 可视化遥测数据

Grafana 是一种开源软件,用于可视化遥测数据。它提供了一个易于使用和交互的选项,用于可视化可观察性指标。Grafana 还帮助将来自各种系统的遥测数据集中统一显示,提供跨所有系统的统一可观察性视图。

Istio 安装提供了 Grafana 的示例清单,位于 samples/addons 中。使用以下命令安装 Grafana:

% kubectl apply -f samples/addons/grafana.yaml
serviceaccount/grafana created
configmap/grafana created
service/grafana created
deployment.apps/grafana created
configmap/istio-grafana-dashboards created
configmap/istio-services-grafana-dashboards created

安装 Grafana 后,您可以使用以下命令打开 Grafana 仪表盘:

% istioctl dashboard grafana
http://localhost:3000

这应该会打开 Grafana 仪表盘,如下图所示:

图 7.11 – Grafana 仪表盘

图 7.11 – Grafana 仪表盘

Grafana 已经为 Istio 包含了以下仪表盘:

  • Istio 控制平面仪表盘:此仪表盘提供显示 Istio 控制平面组件资源消耗的图表。它还提供关于控制平面与数据平面之间交互的指标,包括 xDS 推送、配置同步中的错误以及数据平面和控制平面之间的配置冲突。

  • Istio 网格仪表盘:此仪表盘提供网格的汇总视图。仪表盘提供请求、错误、网关和策略的汇总视图,并详细说明服务及其在请求处理期间的相关延迟。

  • Istio 性能仪表盘:此仪表盘提供显示 Istio 组件资源利用率的图表。

  • Istio 服务和工作负载仪表板:提供每个服务和工作负载的请求-响应指标。使用此仪表板,您可以找到有关服务和工作负载行为的更详细信息。您可以根据不同维度搜索指标,如在使用 Prometheus 抓取指标一节中讨论的那样。

图 7.12 – Istio 服务仪表板

图 7.12 – Istio 服务仪表板

Grafana 的另一个强大功能是告警,您可以基于某些事件创建告警。在接下来的示例中,我们将创建一个这样的告警:

  1. 创建一个告警,当 response_code 不等于 200 时,基于过去 10 分钟内的 istio_request_total 指标。

图 7.13 – 在 Grafana 中创建告警

图 7.13 – 在 Grafana 中创建告警

  1. 配置一个告警,当过去 10 分钟内响应码为 ~=200 的请求数量超过 3 时触发,这也叫做阈值。我们还将配置该告警的评估频率以及触发告警的阈值。在以下示例中,我们将告警设置为每分钟评估一次,但在 5 分钟后触发。通过调整这些参数,我们可以避免告警过早或过晚触发。

图 7.14 – 配置触发告警的阈值

图 7.14 – 配置触发告警的阈值

  1. 接下来,您配置告警规则的名称以及告警应存储的位置:

图 7.15 – 添加告警详情

图 7.15 – 添加告警详情

  1. 配置完规则名称后,接下来配置标签,标签是用来将告警与通知策略关联的方式:

图 7.16 – 告警通知

图 7.16 – 告警通知

  1. 接下来,您配置需要在告警触发时通知的联系人:

图 7.17 – 配置联系人

图 7.17 – 配置联系人

  1. 最后,您创建一个通知策略,指定将在告警触发时通知的联系人。

图 7.18 – 配置通知策略

图 7.18 – 配置通知策略

最终,您的告警已配置完成。现在,您可以禁用 sockshop.com 上的目录服务,进行几次请求,您将看到以下告警在 Grafana 中被触发:

图 7.19 – 因目录服务中断引起的故障触发的告警

图 7.19 – 因目录服务中断引起的故障触发的告警

在本节中,我们展示了如何使用 Grafana 来可视化 Istio 产生的各种指标。Grafana 提供了全面的工具来可视化数据,帮助发现新的机会以及揭示系统中可能出现的问题。

实现分布式追踪

分布式追踪帮助你理解请求在各种 IT 系统中的流转过程。在微服务的背景下,分布式追踪帮助你理解请求在不同微服务之间的流动,帮助你诊断请求可能遇到的任何问题,并帮助你快速诊断任何故障或性能问题。

在 Istio 中,你可以启用分布式追踪,而无需在应用程序代码中做任何更改,只要你的应用程序将所有追踪头部转发给上游服务即可。Istio 支持与多种分布式追踪系统的集成;Jaeger 就是其中一个支持的系统,并且作为 Istio 的附加组件提供。Istio 的分布式追踪基于 Envoy,在这里,追踪信息直接从 Envoy 发送到追踪后端。追踪信息包括x-request-idx-b3-trace-idx-b3-span-idx-b3-parent-spanidx-b3-sampledx-b3-flagsb3。这些自定义头部由 Envoy 为每个经过 Envoy 的请求创建。Envoy 将这些头部转发到 Pod 中相关的应用容器。应用容器然后需要确保这些头部不会被截断,而是转发给网格中的任何上游服务。代理的应用程序随后需要在所有从应用程序发出的外部请求中传播这些头部。

你可以在 www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/observability/tracing 阅读更多关于头部的信息。

在接下来的部分,我们将学习如何安装 Jaeger 并为sockshop示例启用分布式追踪。

使用 Jaeger 启用分布式追踪

Jaeger 是一个开源的分布式追踪软件,最初由 Uber Technologies 开发,后来捐赠给 CNCF。Jaeger 用于监控和故障排除基于微服务的系统,主要用于以下几个方面:

  • 分布式上下文传播和事务监控

  • 微服务依赖分析与故障排除

  • 了解分布式架构中的瓶颈

Jaeger 的创建者 Yuri Shkuro 出版了一本名为 Mastering Distributor Tracing 的书(www.shkuro.com/books/2019-mastering-distributed-tracing),书中解释了 Jaeger 设计和操作的许多方面。你可以在 www.jaegertracing.io/ 阅读更多关于 Jaeger 的信息。

接下来,我们将在 Istio 中安装和配置 Jaeger。

用于部署 Jaeger 的 Kubernetes 清单文件已经可以在 samples/addons/jaeger.yaml中找到:

% kubectl apply -f samples/addons/jaeger.yaml
deployment.apps/jaeger created
service/tracing created
service/zipkin created
service/jaeger-collector created

这个代码块将在istio-system命名空间中安装 Jaeger。你可以使用以下命令打开仪表盘:

$ istioctl dashboard jaeger

不幸的是,sockshop 应用没有设计成传播头部,因此在此场景中,我们将使用 Istio 示例中的 bookinfo 应用。但在此之前,我们将部署 httpbin 应用以理解 Istio 注入的 Zipkin 追踪头部:

% kubectl apply -f  Chapter7/01-httpbin-deployment.yaml

让我们向 httpbin 发送请求并检查响应头部:

% curl -H "Host:httpbin.com"  http://a858beb9fccb444f48185da8fce35019-1967243973.us-east-1.elb.amazonaws.com/headers
{
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.com",
    "User-Agent": "curl/7.79.1",
    "X-B3-Parentspanid": "5c0572d9e4ed5415",
    "X-B3-Sampled": "1",
    "X-B3-Spanid": "743b39197aaca61f",
    "X-B3-Traceid": "73665fec31eb46795c0572d9e4ed5415",
    "X-Envoy-Attempt-Count": "1",
    "X-Envoy-Internal": "true",
    "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/Chapter7/sa/default;Hash=5c4dfe997d5ae7c853efb8b81624f1ae5e4472f1cabeb36a7cec38c9a4807832;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"
  }
}

在响应中,请注意 Istio 注入的头部 – x-b3-parentspanidx-b3-sampledx-b3-spanidx-b3-traceid。这些头部也称为 B3 头部,用于跨服务边界传播追踪上下文:

  • 1 表示允许,0 表示禁止。

  • x-b3-traceid:这是 8 字节或 16 字节长,表示追踪的整体 ID。

  • x-b3-parentspanid:这是 8 字节长,表示父操作在追踪树中的位置。每个 span 都会有一个父 span,除非它本身就是根 span。

  • x-b3-spanid:这是 8 字节长,表示当前操作在追踪树中的位置。

httpbin 的响应中,请求会经过 Ingress 网关,然后到达 httpbin。B3 头部在请求到达 Ingress 网关时就被 Istio 注入。Ingress 网关生成的 span ID 是 5c0572d9e4ed5415,它是 httpbin span 的父 span,后者的 span ID 是 743b39197aaca61f。Ingress 网关和 httpbin 的 span 会有相同的 trace ID,因为它们属于同一个追踪。由于 Ingress 网关是根 span,它不会有 parentspanid。在这个示例中,只有两个跳数,因此只有两个 span。如果有更多跳数,它们也都会生成 B3 头部,因为 x-b3-sampled 的值是 1

您可以在www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers.html了解更多关于这些头部的信息。

现在,您已经熟悉了 Istio 注入的 x-b3 头部,让我们部署示例应用 bookinfo 并配置 Ingress。如果您还没有创建 Chapter7 命名空间,请在启用 Istio 注入的情况下创建它:

% kubectl apply -f samples/bookinfo/platform/kube/bookinfo.ya
ml -n Chapter7
% kubectl apply -f Chapter7/bookinfo-gateway.yaml

请注意,Chapter7/bookinfo-gateway.yamlbookshop.com 配置为主机;我们这样做是为了使其能够与 sock-shop.com 一起运行。部署 Ingress 配置后,您可以通过 istio-ingress 网关服务的外部 IP 访问 bookinfo。请使用 /productpage 作为 URI。接下来,您可以向 bookinfo 应用发送一些请求,然后查看 Jaeger 仪表盘,并选择 productpage.Chapter7 作为服务。选择服务后,点击 Find Traces,然后将显示该服务的最新追踪的详细视图:

图 7.20 – Jaeger 仪表盘

图 7.20 – Jaeger 仪表盘

在 Jaeger 中,跟踪(trace)是一个请求执行的表示,由多个 span 组成;跟踪记录了请求所经过的路径。一个跟踪由多个 span 组成;每个 span 表示一个工作单元,用于跟踪请求所做的特定操作。第一个 span 表示 根 span,即请求的开始到结束;每个后续的 span 提供了请求执行过程中该部分发生情况的更深入上下文。

你可以点击仪表盘上的任何一个跟踪。以下是一个包含八个 spans 的跟踪示例:

图 7.21 – Jaeger 中的跟踪和 spans

图 7.21 – Jaeger 中的跟踪和 spans

在以下截图中,你可以观察到以下内容:

  • 请求在 istio-ingressgateway 中花费了 78.69 毫秒,这也是根 span。请求随后被转发到位于端口 9080productpage 上游服务。如果你查看下一个子 span,你会发现 istio-ingressgateway 中花费的时间是 78.69 – 76.73 毫秒 = 1.96 毫秒。

图 7.22 – BookInfo 的根 span

图 7.22 – BookInfo 的根 span

  • 请求随后在整体处理时间轴的 867 微秒时被 productpage 服务接收。处理该请求花费了 76.73 毫秒。

图 7.23 – 请求到达产品页

图 7.23 – 请求到达产品页

  • productpage 服务在 867 微秒到 5.84 毫秒之间进行了处理,之后,它调用了位于端口 9080details 服务。来回往返到 details 服务花费了 12.27 毫秒。

图 7.24 – 从产品页到详情服务的请求

图 7.24 – 从产品页到详情服务的请求

  • 请求在 7.14 毫秒后被 details 服务接收,并花费了 1.61 毫秒来处理该请求。

图 7.25 – 请求到达详情服务

图 7.25 – 请求到达详情服务

我没有展示其余的 spans,但我希望你能了解进行此练习的好处。我们刚才讲解的示例引出了几个有趣的观察结果:

  • 通过比较第三个和第四个 spans 的开始时间,可以清楚地看到,请求从产品页离开并到达详情页花费了 1.3 毫秒。

  • 详情页只用了 1.6 毫秒来处理请求,但产品页接收到请求并将其发送到详情页却花费了 12.27 毫秒,这突显了产品页实现中的一些低效。

你可以通过点击仪表盘右上角的下拉菜单来进一步探索。

图 7.26 – 查看跟踪详情的其他选项

图 7.26 – 查看追踪详情的其他选项

Trace Spans Table 选项在呈现多个跨度处理请求所花时间的摘要视图时非常有用:

图 7.27 – Jaeger 中的追踪跨度表

图 7.27 – Jaeger 中的追踪跨度表

追踪会带来性能损耗,因此不适合追踪所有请求,因为它们会导致性能下降。我们以 demo 配置文件安装了 Istio,默认情况下会采样所有请求。这可以通过以下配置映射来控制:

% kubectl get cm/istio -n istio-system -o yaml

你可以通过为 tracing 中的 sampling 提供正确的值来控制采样率:

apiVersion: v1
data:
  mesh: |-
..
      tracing:
       sampling: 10
        zipkin:
          address: zipkin.istio-system:9411
    enablePrometheusMerge: true
..
kind: ConfigMap

这也可以在部署级别进行控制——例如,我们可以通过在 bookinfo.yaml 中添加以下内容来配置产品页面仅对 1% 的请求进行采样:

  template:
    metadata:
      annotations:
        proxy.istio.io/config: |
          tracing:
            sampling: 1
            zipkin:
              address: zipkin.istio-system:9411

整个配置文件可以在 Chapter7/bookinfo-samplingdemo.yaml 中找到。

在本节中,我们了解了如何使用 Jaeger 执行分布式追踪,而无需在应用程序代码中进行任何更改,前提是你的应用程序能够转发 x-b3 头信息。

总结

在本章中,我们了解了 Istio 如何通过生成各种遥测数据使系统可观察。Istio 提供了各种度量指标,这些指标可以被 Istio 操作员用来对系统进行微调和优化。这一切都是通过 Envoy 实现的,Envoy 生成的各种度量数据随后被 Prometheus 抓取。

Istio 允许你配置新的度量指标,以及为现有的度量指标添加新的维度。你已经学习了如何使用 Prometheus 通过 PromQL 查询各种度量指标,并构建可以提供系统以及业务运营洞察的查询。随后,我们安装了 Grafana 来可视化 Prometheus 收集的度量指标,尽管 Istio 提供了多个开箱即用的仪表盘,你也可以轻松添加新的仪表盘,配置告警,并创建如何分发这些告警的策略。

最后,我们安装了 Jaeger 来执行分布式追踪,以了解请求在分布式系统中的处理过程,并且我们做这一切时不需要修改应用程序代码。本章提供了对 Istio 如何使系统可观察的基础理解,从而实现不仅健康而且最优的系统。

在下一章中,我们将学习在操作 Istio 时可能遇到的各种问题以及如何进行故障排除。

第三部分:扩展、扩展与优化

本部分带你深入学习 Istio 的高级主题。你将了解部署 Istio 到生产环境的各种架构。你还将探索扩展 Istio 数据平面的不同选项,并学习为什么这是 Istio 非常有用和强大的功能。Istio 为基于虚拟机的工作负载提供了极大的灵活性,因此,在本部分中,你将阅读如何将 Istio 扩展到虚拟机。接近本部分结尾时,你将学习如何排除 Istio 故障以及在生产环境中操作和配置 Istio 的最佳实践。最后,我们将总结所学内容,并将其应用到另一个示例应用中,同时讨论 eBPF 以及如何进一步了解 Istio。附录将提供有关其他服务网格技术的宝贵信息,并帮助你获得与其他选项相比的 Istio 相关知识。

本部分包含以下章节:

  • 第八章将 Istio 扩展到多个集群的部署——一个 Kubernetes 的案例

  • 第九章扩展 Istio 数据平面

  • 第十章为非 Kubernetes 工作负载部署 Istio 服务网格

  • 第十一章排除故障与操作 Istio

  • 第十二章总结我们所学的内容与下一步计划

第八章:扩展 Istio 至跨 Kubernetes 的多集群部署

容器不仅改变了应用程序的开发方式,还改变了应用程序的连接方式。应用程序网络,即应用程序之间的网络连接,对于生产部署至关重要,必须是自动化的、弹性可扩展的且安全的。实际应用程序部署在本地、多云、Kubernetes 集群以及集群中的命名空间中。因此,有必要在传统环境和现代环境之间提供一个服务网格,实现应用程序之间的无缝连接。

第三章中,我们简要讨论了 Istio 控制平面的部署模型。我们讨论了带有本地控制平面的单个集群、带有单个控制平面的主集群和远程集群,以及带有外部控制平面的单个集群。单网格/单集群部署是最简单的,但也是一种不实际的部署模型,因为您的生产工作负载将包括多个 Kubernetes 集群,可能分布在多个数据中心。

在本章中,我们将通过以下主题,学习如何在多个 Kubernetes 集群之间部署 Istio,并如何将它们联合起来:

  • 在多集群部署中建立相互信任

  • 多网络上的主-远程集群

  • 同一网络上的主-远程集群

  • 不同网络上的多主集群

  • 同一网络上的多主集群

本章内容极具实践性,因此请特别注意技术要求部分。此外,在每个部分,请特别注意设置集群和配置 Istio 的说明。

技术要求

在本章中,我们将使用Google Cloud进行实际操作。如果您是第一次使用,您可能符合获得免费信用额度的条件,详情请参考cloud.google.com/free。您需要一个 Google 帐户进行注册;注册后,请按照 Google 文档中的说明安装Google CLI,具体说明见cloud.google.com/sdk/docs/install。安装 Google CLI 后,您需要按照cloud.google.com/sdk/docs/initializing中的步骤进行初始化。初始化步骤将完成必要的配置,以便您可以通过 CLI 与您的 Google Cloud 帐户进行交互。

设置 Kubernetes 集群

一旦您设置好帐户,我们将使用Google Kubernetes Engine 服务创建两个 Kubernetes 集群。为此,请按照以下步骤操作:

  1. 使用以下命令创建集群 1:

    % gcloud beta container --project "istio-book-370122" clusters create "cluster1" --zone "australia-southeast1-a" --no-enable-basic-auth --cluster-version "1.23.12-gke.100" --release-channel "regular" --machine-type "e2-medium" --image-type "COS_CONTAINERD" --disk-type "pd-standard" --disk-size "30" --num-nodes "3"
    

在此示例中,我们正在创建一个名为cluster1的集群,位于australia-southeast1区域的australia-southeast1-a可用区。所使用的机器类型为e2-medium,默认池大小为3。你可以将区域更改为最接近你位置的区域。你也可以更改实例类型和其他参数,但请注意可能产生的费用。

  1. 接下来,创建集群 2。过程与前一步相同,但我们使用了不同的区域和子网:

    % gcloud beta container --project "istio-book-370122" clusters create "cluster2" --zone "australia-southeast2-a" --no-enable-basic-auth --cluster-version "1.23.12-gke.100" --release-channel "regular" --machine-type "e2-medium" --image-type "COS_CONTAINERD" --disk-type "pd-standard" --disk-size "30" --max-pods-per-node "110" --num-nodes "3"
    
  2. 现在,设置环境变量以引用已创建的集群。从kubectl配置中找到集群引用名称:

    % kubectl config view -o json | jq '.clusters[].name'
    "gke_istio-book-370122_australia-southeast1-a_primary-cluster"
    "gke_istio-book-370122_australia-southeast2-a_primary2-cluster"
    "minikube"
    

在本章中使用的每个终端窗口中设置以下上下文变量:

export CTX_CLUSTER1="gke_istio-book-370122_australia-southeast1-a_primary-cluster"
export CTX_CLUSTER2="gke_istio-book-370122_australia-southeast2-a_primary2-cluster"

这完成了在 Google Cloud 中设置 Kubernetes 集群的步骤。在下一部分,你将在工作站上设置 OpenSSL。

设置 OpenSSL

我们将使用 OpenSSL 生成根证书和中间证书授权机构CA)。你需要使用 OpenSSL 3.0 或更高版本。Mac 用户可以按照formulae.brew.sh/formula/openssl@3上的说明进行操作。

你可能会看到以下响应:

openssl@3 is keg-only, which means it was not symlinked into /opt/homebrew,
because macOS provides LibreSSL.

在这种情况下,手动将 OpenSSL 添加到PATH中:

% export PATH="/opt/homebrew/opt/openssl@3/bin:$PATH"
% openssl version
OpenSSL 3.0.7 1

请确保路径反映了你将执行与证书相关命令的终端。

额外的 Google Cloud 步骤

以下步骤对于在两个 Kubernetes 集群之间建立连接非常有用。请暂时不要执行本部分中的步骤,我们将在后续的实践环节中参考这些步骤:

  1. 计算集群 1 和集群 2 的无类域间路由CIDR)块:

    % function join_by { local IFS="$1"; shift; echo "$*"; }
    ALL_CLUSTER_CIDRS=$(gcloud container clusters list –format='value(clusterIpv4Cidr)' | sort | uniq)
    ALL_CLUSTER_CIDRS=$(join_by , $(echo "${ALL_CLUSTER_CIDRS}"))
    

ALL_CLUSTER_CIDR的值将类似于10.124.0.0/14,10.84.0.0/14

  1. 获取集群 1 和集群 2 的NETTAGS

    % ALL_CLUSTER_NETTAGS=$(gcloud compute instances list –format='value(tags.items.[0])' | sort | uniq)
    ALL_CLUSTER_NETTAGS=$(join_by , $(echo "${ALL_CLUSTER_NETTAGS}"))
    

ALL_CLUSTER_NETTAGS的值将类似于gke-primary-cluster-9d4f7718-nodegke-remote-cluster-c50b7cac-node

  1. 创建一个防火墙规则,允许集群 1 和集群 2 之间的所有流量:

    % gcloud compute firewall-rules create primary-remote-shared-network \
      --allow=tcp,udp,icmp,esp,ah,sctp \
      --direction=INGRESS \
      --priority=900 \
      --source-ranges="${ALL_CLUSTER_CIDRS}" \
      --target-tags="${ALL_CLUSTER_NETTAGS}" –quiet
    
  2. 通过执行以下步骤删除 Google Cloud Kubernetes 集群和防火墙规则:

    • 删除防火墙:

      cluster1:
      
      

      cluster2:

      %gcloud container clusters delete cluster2 –zone "australia-southeast2-a"
      
      
      

这完成了为接下来的部分准备所需的所有步骤。在下一部分,我们将开始介绍多集群部署所需的基础知识。

在多集群部署中建立相互信任

在设置多集群部署时,我们还必须在集群之间建立信任。Istio 架构基于零信任模型,假设网络是恶意的,并且服务之间没有隐式信任。因此,Istio 对每个服务的通信进行身份验证,以验证工作负载的真实性。集群中的每个工作负载都会被分配一个身份,服务与服务之间的通信通过 sidecar 上的 mTLS 完成。此外,sidecar 与控制平面之间的所有通信也都通过 mTLS 进行。在前面的章节中,我们使用了带有自签名根证书的 Istio CA。在设置多集群时,我们必须确保工作负载分配的身份能够被网格中所有其他服务理解和信任。Istio 通过将 CA 捆绑包分发到所有工作负载来实现这一点,捆绑包包含一条证书链,sidecar 可以利用这条证书链来识别通信另一端的 sidecar。在多集群环境中,我们需要确保 CA 捆绑包包含正确的证书链,以验证数据平面中的所有服务。

有两种方法可以实现这一点:

  • 插件 CA 证书:使用此选项,我们在 Istio 外部创建根证书和中间证书,并配置 Istio 使用创建的中间证书。此选项允许您使用已知的 CA,甚至是您自己的内部 CA 作为根证书颁发机构,为 Istio 生成中间 CA。您将中间 CA 证书和密钥与根 CA 证书一起提供给 Istio。然后,Istio 使用中间 CA 和密钥来签署工作负载,并将根 CA 证书作为信任根嵌入其中。

图 8.1 – 作为 Istio 插件 CA 的中间 CA

图 8.1 – 作为 Istio 插件 CA 的中间 CA

  • 外部 CA 证书颁发机构:我们使用外部 CA 来签发证书,而无需将私钥存储在 Kubernetes 集群内。当 Istio 使用自签名证书时,它会将自签名的私钥作为一个 Secret 存储在 Kubernetes 集群内。如果使用插件 CA,它仍然需要在集群中保存中间密钥。如果 Kubernetes 的访问权限没有受到限制,那么将私钥存储在 Kubernetes 集群中并不是一个安全的选择。在这种情况下,我们可以使用外部 CA 来作为证书签发机构。

图 8.2 – cert-manager

图 8.2 – cert-manager

一种这样的证书管理软件是cert-manager。它将外部证书和证书颁发者作为资源类型添加到 Kubernetes 集群中,并简化了获取、续订和使用这些证书的过程。它可以与多种支持的源集成,包括 Let's Encrypt 和 HashiCorp Vault。它确保证书有效并及时更新,并在证书过期之前的配置时间尝试续订证书。cert-manager 软件通过 Kubernetes CSR API 与 Kubernetes 集成;您可以在 kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/ 上阅读相关内容。当使用 cert-manager 时,Istio 批准来自服务工作负载的 CSR,并将请求转发给 cert-manager 进行签名。cert-manager 签名该请求并将证书返回给 istiod,之后这些证书将传递给 istio-agent。

在本章中,我们将使用插件 CA 证书选项,这是一个更简单、易用的选项,这样我们就可以专注于 Istio 的多集群设置。在接下来的部分中,我们将介绍在不同集群配置中设置 Istio。

多网络上的主远程

在主远程配置中,我们将在集群 1 上安装 Istio 控制平面。集群 1 和集群 2 位于不同的网络之间,Pods 之间没有直接连接。集群 1 将承载 Istio 控制平面以及数据平面。集群 2 仅承载数据平面,并使用集群 1 的控制平面。集群 1 和集群 2 都使用由根 CA 签发的中间 CA。在集群 1 中,istiod 观察集群 1 和集群 2 中的 API 服务器,以监控 Kubernetes 资源的任何变化。我们将在两个集群中都创建一个 Ingress 网关,用于在工作负载之间进行跨网络通信。我们将这个 Ingress 网关称为东西网关,因为它用于东西方向的通信。东西网关负责处理集群 1 和集群 2 之间的身份验证工作负载,并充当两个集群之间所有流量的中心枢纽。在下图中,数据平面流量中的虚线箭头表示来自集群 1 到集群 2 的服务请求,通过东西网关传递。在集群 2 中,虚线箭头表示数据平面流量从集群 2 到集群 1。

图 8.3 – 不同网络上的主远程集群

图 8.3 – 不同网络上的主远程集群

在下一部分中,我们将从配置两个 Kubernetes 集群之间的相互信任开始。我们将使用上一部分中描述的插件 CA 选项,在多集群部署中建立相互信任

在两个集群之间建立信任

我们需要在两个集群上配置 Istio CA 以相互信任。如前一部分所述,我们将通过创建根 CA 并使用它为两个集群生成中间 CA 来实现这一点。

转到 Istio 安装目录,并创建一个名为 certs 的文件夹,用于存放生成的证书。然后,从 cert 目录执行以下指令:

  1. 生成根证书:

    % mkdir -p certs
    % cd certs
    % make -f ../tools/certs/Makefile.selfsigned.mk root-ca
    generating root-key.pem
    generating root-cert.csr
    generating root-cert.pem
    Certificate request self-signature ok
    subject=O = Istio, CN = Root CA
    

这将生成 root-key.pem(私钥)和 root-cert.pem(根证书)。

  1. cluster1 生成中间 CA:

    % make -f ../tools/certs/Makefile.selfsigned.mk cluster1-cacerts
    generating cluster1/ca-key.pem
    generating cluster1/cluster-ca.csr
    generating cluster1/ca-cert.pem
    Certificate request self-signature ok
    subject=O = Istio, CN = Intermediate CA, L = cluster1
    generating cluster1/cert-chain.pem
    Intermediate inputs stored in cluster1/
    done
    rm cluster1/cluster-ca.csr cluster1/intermediate.conf%
    % ls cluster1
    ca-cert.pem     ca-key.pem cert-chain.pem  root-cert.pem
    

这将为 cluster1 生成一个中间 CA,CA 密钥存放在 ca-key.pem 中,证书存放在 ca-cert.pem 中,链存放在 cert-chain.pem 中。

  1. cluster2 生成一个中间 CA:

    % % make -f ../tools/certs/Makefile.selfsigned.mk cluster2-cacerts
    generating cluster2/ca-key.pem
    generating cluster2/cluster-ca.csr
    generating cluster2/ca-cert.pem
    Certificate request self-signature ok
    subject=O = Istio, CN = Intermediate CA, L = cluster2
    generating cluster2/cert-chain.pem
    Intermediate inputs stored in cluster2/
    done
    rm cluster2/cluster-ca.csr cluster2/intermediate.conf
    % ls cluster2
    ca-cert.pem     ca-key.pem  cert-chain.pem  root-cert.pem
    

这将为 cluster2 生成一个中间 CA,CA 密钥存放在 ca-key.pem 中,证书存放在 ca-cert.pem 中,链存放在 cert-chain.pem 中。

  1. 按照《技术要求》部分 设置 Kubernetes 集群 子部分中的第三步和第四步描述设置环境变量。这有助于执行针对多个 Kubernetes 集群的命令:

     % export CTX_CLUSTER1=" gke_istio-book-370122_australia-southeast1-a_primary-cluster"
    % export CTX_CLUSTER2=" gke_istio-book-370122_australia-southeas1-b_remote-cluster"
    
  2. 在主集群和远程集群中创建命名空间。我们将在此命名空间中安装 Istio:

    % kubectl create ns istio-system --context="${CTX_CLUSTER1}"
    namespace/istio-system created
    % kubectl create ns istio-system --context="${CTX_CLUSTER2}"
    namespace/istio-system created
    
  3. cluster1 中创建 secret,该 secret 将作为中间 CA 供 Istio 使用:

    % kubectl create secret generic cacerts -n istio-system \
          --from-file=cluster1/ca-cert.pem \
          --from-file=cluster1/ca-key.pem \
          --from-file=cluster1/root-cert.pem \
          --from-file=cluster1/cert-chain.pem --context="${CTX_CLUSTER1}"
    secret/cacerts created
    
  4. cluster2 中创建 secret,该 secret 将作为中间 CA 供 Istio 使用:

    % kubectl create secret generic cacerts -n istio-system \
          --from-file=cluster2/ca-cert.pem \
          --from-file=cluster2/ca-key.pem \
          --from-file=cluster2/root-cert.pem \
          --from-file=cluster2/cert-chain.pem --context="${CTX_CLUSTER2}"
    secret/cacerts created
    
  5. cluster1cluster2 中的 namespace 添加网络名称标签:

    % kubectl --context="${CTX_CLUSTER1}" label namespace istio-system topology.istio.io/network=network1
    namespace/istio-system labeled
    % kubectl --context="${CTX_CLUSTER2}" label namespace istio-system topology.istio.io/network=network2
    namespace/istio-system labeled
    
  6. 按照以下方式配置 cluster1

    1. 创建 Istio 操作员配置:
    apiVersion: install.istio.io/v1alpha1
    kind: IstioOperator
    spec:
      values:
        global:
          meshID: mesh1
          multiCluster:
            clusterName: cluster1
          network: network1
    

该文件可以在 GitHub 上的 Chapter08/01-Cluster1.yaml 找到。

  1. 安装 Istio:
% istioctl install --set values.pilot.env.EXTERNAL_ISTIOD=true --context="${CTX_CLUSTER1}" -f Chapter08/01-Cluster1.yaml"
This will install the Istio 1.16.0 default profile with ["Istio core" "Istiod" "Ingress gateways"] components into the cluster. Proceed? (y/N) y
 Istio core installed
 Istiod installed
 Ingress gateways installed
 Installation complete
Making this installation the default for injection and validation.
  1. 在此步骤中,我们将在 cluster1 中安装东西向网关,它将把 cluster1 中的所有服务暴露给 cluster2 中的服务。此网关可供 cluster2 中的所有服务访问,但只能由具有信任的 mTLS 证书和工作负载 ID 的服务访问,也就是说,只有属于网格中的服务才能访问:
% samples/multicluster/gen-eastwest-gateway.sh \
    --mesh mesh1 --cluster cluster1 --network network1 | \
    istioctl --context="${CTX_CLUSTER1}" install -y -f -
 Ingress gateways installed
 Installation complete
  1. 东西向网关还用于将 istiod 端点暴露给 cluster2。这些端点由 cluster2 中的变更 webhook 和 istio-proxy 使用。以下配置创建一个名为 istiod-gateway 的网关,并通过 TLS 暴露端口 1501215017
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: istiod-gateway
spec:
  selector:
    istio: eastwestgateway
  servers:
    - port:
        name: tls-istiod
        number: 15012
        protocol: tls
      tls:
        mode: PASSTHROUGH
      hosts:
        - "*"
    - port:
        name: tls-istiodwebhook
        number: 15017
        protocol: tls
      tls:
        mode: PASSTHROUGH
      hosts:
        - "*"
  1. 以下虚拟服务将入站流量从端口 1501215017 路由到 cluster1 上的 istiod.istio-system.svc.cluster.local 服务的端口 15012443
  tls:
  - match:
    - port: 15012
      sniHosts:
      - "*"
    route:
    - destination:
        host: istiod.istio-system.svc.cluster.local
        port:
          number: 15012
  - match:
    - port: 15017
      sniHosts:
      - "*"
    route:
    - destination:
        host: istiod.istio-system.svc.cluster.local
        port:
          number: 443

配置文件可以在 Istio 安装文件夹中的 samples/multicluster/expose-istiod.yaml 找到。使用以下命令应用该配置:

 % kubectl apply --context="${CTX_CLUSTER1}" -n istio-system -f "samples/multicluster/expose-istiod.yaml"
gateway.networking.istio.io/istiod-gateway created
virtualservice.networking.istio.io/istiod-vs created
  1. 为了将工作负载服务暴露给 cluster2,我们需要创建另一个网关。该配置与 istiod-gateway 非常相似,不同之处在于我们暴露的是专门为网格内服务设计的端口 15443

    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: cross-network-gateway
    spec:
      selector:
        istio: eastwestgateway
      servers:
        - port:
            number: 15443
            name: tls
            protocol: TLS
          tls:
            mode: AUTO_PASSTHROUGH
          hosts:
            - "*.local"
    
  2. 一个示例文件可以在 Istio 安装目录的 samples/multicluster/expose-services.yaml 中找到:

    % kubectl --context="${CTX_CLUSTER1}" apply -n istio-system -f samples/multicluster/expose-services.yaml
    gateway.networking.istio.io/cross-network-gateway created
    
  3. 在这一步,我们将配置cluster2。为此,您需要记下在前一步中创建的东西方网关的外部 IP。在接下来的步骤中,我们将首先准备 Istio 的配置文件,然后使用该配置文件在cluster2中安装 Istio:

    1. 配置 Istio 操作员配置。以下是示例配置,它包含两个需要注意的配置:

      • injectionPath:构造为/inject/cluster/REMOTE_CLUSTER 的 CLUSTER_NAME/net/ REMOTE_CLUSTER 的 NETWORK_NAME

      • remotePilotAddress:东西方网关的 IP,暴露端口1501215017,并且网络可以访问cluster2

示例文件可以在 GitHub 上的Chapter08/01-Cluster2.yaml中找到:

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  profile: remote
  values:
    istiodRemote:
      injectionPath: /inject/cluster/cluster2/net/network2
    global:
      remotePilotAddress: 35.189.54.43
  1. 标记并注解命名空间。将topology.istio.io/controlPlaneClusters命名空间注解设置为cluster1,指示在cluster1上运行的 istiod 管理 cluster2,当它作为远程集群附加时:
% kubectl --context="${CTX_CLUSTER2}" annotate namespace istio-system topology.istio.io/controlPlaneClusters=cluster1
namespace/istio-system annotated
  1. 通过向 istio-system 命名空间添加标签来设置 cluster2 的网络。网络名称应与您在前一步中 01-Cluster2.yaml 文件中配置的名称相同:
% kubectl --context="${CTX_CLUSTER2}" label namespace istio-system topology.istio.io/network=network2
namespace/istio-system labeled
  1. cluster2中安装 Istio:
istioctl install --context="${CTX_CLUSTER2}" -f " Chapter08/01-Cluster2.yaml"
This will install the Istio 1.16.0 remote profile with ["Istiod remote"] components into the cluster. Proceed? (y/N) y
 Istiod remote installed
 Installation complete
Making this installation the default for injection and validation.
  1. 提供主集群访问远程集群 API 服务器:

    % istioctl x create-remote-secret \
      --context="${CTX_CLUSTER2}" \
      --name=cluster2 \
      --type=remote \
      --namespace=istio-system \
      --create-service-account=false | \
      kubectl apply -f - --context="${CTX__CLUSTER1}"
    secret/istio-remote-secret-cluster2 created
    

执行此步骤后,cluster1中的 istiod 将能够与cluster2中的 Kubernetes API 服务器通信,从而能够看到cluster2中的服务、端点和命名空间。只要 API 服务器对 istiod 可访问,它将修补cluster2中的 webhooks 中的证书。现在,在第 13 步之前和之后执行以下操作:

% kubectl get mutatingwebhookconfiguration/istio-sidecar-injector --context="${CTX_CLUSTER2}" -o json

您会注意到以下内容已在边车注入器中更新:

                "caBundle": "..MWRNPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==",
                "url": https://a5bcd3e72e1f04379a75247f8f718bb1-689248335.us-east-1.elb.amazonaws.com:15017/inject/cluster/cluster2/net/network2
  1. 创建东西方网关以处理从主集群到远程集群的流量入口:

    % samples/multicluster/gen-eastwest-gateway.sh --mesh mesh1 --cluster "${CTX_CLUSTER2}" --network network2 > eastwest-gateway-1.yaml
    % istioctl manifest generate -f eastwest-gateway-remote.yaml --set values.global.istioNamespace=istio-system | kubectl apply --context="${CTX_CLUSTER2}" -f -
    
  2. 安装 CRDs,以便您可以配置流量规则:

    % kubectl apply -f manifests/charts/base/crds/crd-all.gen.yaml --context="${CTX_CLUSTER2}"
    
  3. 暴露远程集群中的所有服务:

    % kubectl --context="${CTX_CLUSTER2}" apply -n istio-system -f samples/multicluster/expose-services.yaml
    

这完成了在两个集群中安装和配置 Istio。

部署 Envoy 虚拟应用

在本节中,我们将首先部署两个版本的 Envoy 虚拟应用,然后测试虚拟应用的流量分配。让我们从部署两个版本的 Envoy 虚拟应用开始:

  1. 创建命名空间并启用istio-injection

    % kubectl create ns chapter08 --context="${CTX_CLUSTER1}"
    % kubectl create ns chapter08 --context="${CTX_CLUSTER2}"
    % kubectl label namespace chapter08 istio-injection=enabled --context="${CTX_CLUSTER1}"
    % kubectl label namespace chapter08 istio-injection=enabled --context="${CTX_CLUSTER2}"
    
  2. 创建配置映射:

    % kubectl create configmap envoy-dummy --from-file=Chapter3/envoy-config-1.yaml -n chapter08 --context="${CTX_CLUSTER1}"
    % kubectl create configmap envoy-dummy --from-file=Chapter4/envoy-config-2.yaml -n chapter08 --context="${CTX_CLUSTER2}"
    
  3. 部署 Envoy 应用:

    % kubectl create -f "Chapter08/01-envoy-proxy.yaml" --namespace=chapter08 --context="${CTX_CLUSTER1}"
    % kubectl create -f "Chapter08/02-envoy-proxy.yaml" --namespace=chapter08 --context="${CTX_CLUSTER2}"
    
  4. 使用网关和虚拟服务暴露 Envoy。您可以使用任何集群作为context;istiod 将把配置传播到另一个集群:

    % kubectl apply -f "Chapter08/01-istio-gateway.yaml" -n chapter08 --context="${CTX_CLUSTER2}"
    

我们现在已经成功地在两个集群中部署了envoydummy应用。现在,让我们继续测试虚拟应用的流量分配:

  1. IP 是 Ingress 网关的外部 IP,请注意,它与东西方网关不同。东西方网关用于服务工作负载之间的集群间通信,而 Ingress 网关用于南北向通信。由于我们将从集群外部使用 curl,我们将使用南北向网关:

    % kubectl get svc -n istio-system --context="${CTX_CLUSTER1}"
    NAME              TYPE              CLUSTER-IP    EXTERNAL-IP   PORT(S)     AGE
    istio-eastwestgateway   LoadBalancer   10.0.7.123   35.189.54.43   15021:30141/TCP,15443:32354/TCP,15012:30902/TCP,15017:32082/TCP   22h
    istio-ingressgateway   LoadBalancer   10.0.3.75   34.87.233.38   15021:30770/TCP,80:30984/TCP,443:31961/TCP                        22h
    istiod  ClusterIP      10.0.6.149   <none>         15010/TCP,15012/TCP,443/TCP,15014/TCP                             22h
    
  2. 继续使用以下命令调用 Envoy 虚拟机:

    curl -Hhost:mockshop.com -s "http://34.87.233.38";echo "\\n"; done
    V2----------Bootstrap Service Mesh Implementation with Istio----------V2
    Bootstrap Service Mesh Implementation with Istio
    Bootstrap Service Mesh Implementation with Istio
    V2----------Bootstrap Service Mesh Implementation with Istio----------V2
    Bootstrap Service Mesh Implementation with Istio
    V2----------Bootstrap Service Mesh Implementation with Istio----------V2
    V2----------Bootstrap Service Mesh Implementation with Istio----------V2
    Bootstrap Service Mesh Implementation with Istio
    V2----------Bootstrap Service Mesh Implementation with Istio----------V2
    Bootstrap Service Mesh Implementation with Istio
    

正如你在输出中观察到的,流量在两个集群间分布。cluster1中的 Ingress 网关能够识别cluster2中的 Envoy 虚拟机的v2,并能够在v1v2之间路由流量。

这就完成了不同网络中主远程集群的设置。在下一节中,我们将设置同一网络中的主远程集群。

同一网络上的主远程集群

在同一网络集群中的主集群和远程集群,服务可以访问其他集群间服务,因为它们位于同一网络中。这意味着我们不需要东-西网关来实现集群间服务的通信。我们将把cluster1设置为主集群,cluster2设置为远程集群。我们仍然需要一个东-西网关来代理 istiod 服务。所有从集群 2 到集群 1 的控制平面相关流量将通过东-西网关进行传输。

图 8.4 – 主远程集群共享同一网络

图 8.4 – 主远程集群共享同一网络

在这里,我们将使用上一节中设置的基础设施,多网络上的主远程集群,但如果你愿意,你也可以创建一个单独的基础设施。

让我们开始吧!

  1. 如果你使用的是上一节中的 Kubernetes 集群,你需要先使用以下代码块卸载远程集群上的 Istio:

    $ istioctl uninstall --purge --context="${CTX_CLUSTER2}"
    All Istio resources will be pruned from the cluster
    Proceed? (y/N) y
     ..::istio-reader-clusterrole-istio-system.
     Uninstall complete
    % kubectl delete ns istio-system --context="${CTX_CLUSTER2}"
    namespace "istio-system" deleted
    
  2. 然后,我们将通过以下额外的 Google Cloud 步骤部分提供的步骤打开两个集群之间的防火墙:

    % function join_by { local IFS="$1"; shift; echo "$*"; }
    ALL_CLUSTER_CIDRS=$(gcloud container clusters list --format='value(clusterIpv4Cidr)' | sort | uniq)
    ALL_CLUSTER_CIDRS=$(join_by , $(echo "${ALL_CLUSTER_CIDRS}"))
    

步骤 1将分配类似于10.124.0.0/14,10.84.0.0/14的值,如下所示:

% ALL_CLUSTER_NETTAGS=$(gcloud compute instances list --format='value(tags.items.[0])' | sort | uniq)
ALL_CLUSTER_NETTAGS=$(join_by , $(echo "${ALL_CLUSTER_NETTAGS}"))

步骤 2将分配类似于gke-primary-cluster-9d4f7718-nodegke-remote-cluster-c50b7cac-node的值。

步骤 3创建一条防火墙规则,允许集群 1 和集群 2 之间的所有流量:

% gcloud compute firewall-rules create primary-remote-shared-network \
  --allow=tcp,udp,icmp,esp,ah,sctp \
  --direction=INGRESS \
  --priority=900 \
  --source-ranges="${ALL_CLUSTER_CIDRS}" \
  --target-tags="${ALL_CLUSTER_NETTAGS}" –quiet
Creating firewall...⠹Created
Creating firewall...done.
NAME  NETWORK  DIRECTION  PRIORITY   ALLOW                     DENY  DISABLED
primary-remote-shared--network   default   INGRESS      900       tcp,udp,icmp,esp,ah,sctp        False

执行这些步骤后,cluster1cluster2将具有双向网络访问。

  1. 执行步骤 7,这是多网络上的主远程集群一节中的步骤。此步骤将在cluster2中创建secret,它将被 Istio 作为中间 CA 使用。我们还需要使用以下步骤注释istio-system命名空间:

    % kubectl --context="${CTX_CLUSTER2}" annotate namespace istio-system topology.istio.io/controlPlaneClusters=cluster1
    
  2. cluster2中安装 Istio。我们将在安装中使用以下配置:

    apiVersion: install.istio.io/v1alpha1
    kind: IstioOperator
    spec:
      profile: remote
      values:
        istiodRemote:
          injectionPath: /inject/cluster/cluster2/net/network1
        global:
          remotePilotAddress: 35.189.54.43
    

请注意,injectionPath的值为network1,而不是network2remotePilotAddresscluster1的东-西网关的外部 IP。你将在Chapter08/02-Cluster2.yaml中找到此配置。以下命令将使用配置文件在集群 2 中安装 Istio:

% istioctl install --context="${CTX_CLUSTER2}" -f  Chapter08/02-Cluster2.yaml -y
✔ Istiod remote installed
✔ Installation complete                                                                                                                                                      Making this installation the default for injection and validation.
Thank you for installing Istio 1.16

这将完成在cluster2中安装 Istio。

  1. 接下来,我们将创建一个远程 Secret,提供 Istiod 在cluster1中访问cluster2的 Kubernetes API 服务器:

    % istioctl x create-remote-secret --context="${CTX_CLUSTER2}" --name=cluster2 |    kubectl apply -f - --context="${CTX_CLUSTER1}"
    secret/istio-remote-secret-cluster2 configured
    

这就完成了同一网络中主远程集群的设置。

接下来,我们将通过部署 Envoy 虚拟应用程序来测试设置,就像我们在多网络上的主远程部分中做的那样。按照多网络上主远程部分中部署 Envoy 虚拟应用程序子部分的步骤 1–4来安装 envoydummy 应用程序。部署完成后,我们可以测试是否 Envoy 虚拟服务的流量会在两个集群之间分配:

% for i in {1..10}; do curl -Hhost:mockshop.com -s "http://34.129.4.32";echo '\n'; done
Bootstrap Service Mesh Implementation with Istio
Bootstrap Service Mesh Implementation with Istio
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
Bootstrap Service Mesh Implementation with Istio
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
Bootstrap Service Mesh Implementation with Istio
Bootstrap Service Mesh Implementation with Istio
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
V2----------Bootstrap Service Mesh Implementation with Istio----------V2

从响应中可以观察到,流量在 cluster1cluster2 之间分配。两个集群都能识别彼此的服务,服务网格能够在两个集群之间分配流量。

这标志着通过共享网络设置主远程集群的过程结束。由于我们对 Kubernetes 集群进行了多次修改,建议在执行后续任务之前删除这些更改,以及防火墙规则,以确保一个干净的环境。以下是删除集群的示例,请根据您的配置更改参数值:

% gcloud container clusters delete remote-cluster --zone "australia-southeast2"
% gcloud container clusters delete primary-cluster --zone "australia-southeast1-a"
% gcloud firewall delete primary-remote-shared-network

在下一节中,我们将执行一个在不同网络上的主-主集群设置。

不同网络上的多主集群

在多主集群中,控制平面具有高可用性。在前面讨论的架构选项中,我们有一个主集群,其他集群没有使用 istiod,因此如果主控制平面由于不可预见的情况发生故障,可能会导致控制丧失。在多主集群中,我们有多个主控制平面,即使其中一个控制平面发生短暂故障,网格仍能不间断提供服务。

图 8.5 – 在不同网络上的主-主集群

图 8.5 – 在不同网络上的主-主集群

我们将首先设置集群,然后在两个集群之间建立信任。执行以下步骤以建立多主集群:

  1. 按照设置 Kubernetes 集群部分中的初始步骤设置这两个集群。这将完成集群的创建并设置上下文变量。由于这两个集群都是主集群,我们在 Google Cloud 中创建集群时,将它们命名为 primary1primary2

  2. 执行步骤 1–7,这些步骤位于多网络上的主远程部分,以建立集群之间的信任。这些步骤将创建证书、创建命名空间,然后在命名空间中创建一个 Secret。

  3. 在两个集群中为 istio-system 命名空间标记其网络名称。

  4. 首先,将 topology.istio.io/network 标签应用到 cluster1istio-system 命名空间,标签值为 network1

    % kubectl --context="${CTX_CLUSTER1}" get namespace istio-system && kubectl --context="${CTX_CLUSTER1}" label namespace istio-system topology.istio.io/network=network1
    NAME           STATUS   AGE
    istio-system   Active   3m38s
    namespace/istio-system labelled
    
  5. 接下来,将 topology.istio.io/network 标签应用到 cluster2istio-system 命名空间,标签值为 network2

    % kubectl --context="${CTX_CLUSTER2}" get namespace istio-system && kubectl --context="${CTX_CLUSTER2}" label namespace istio-system topology.istio.io/network=network2
    NAME           STATUS   AGE
    istio-system   Active   3m45s
    namespace/istio-system labeled
    
  6. cluster1 的 Istio 操作符配置类似于主远程配置,因此我们将使用 01-cluster1.yaml 文件来安装 Istio 在 cluster1 中:

    % istioctl install --context="${CTX_CLUSTER1}" -f " Chapter08/01-Cluster1.yaml" -n istio-system -y
     Istio core installed
     Istiod installed
     Ingress gateways installed
     Installation complete
    
  7. cluster1中安装东西向网关:

    % samples/multicluster/gen-eastwest-gateway.sh --mesh mesh1 --cluster cluster1 --network network1 | istioctl --context="${CTX_CLUSTER1}" install -y -f -
     Ingress gateways installed
     Installation complete
    
  8. 创建一个网关配置,以通过东西向网关公开cluster1中的所有服务:

    % kubectl --context="${CTX_CLUSTER1}" apply -n istio-system -f samples/multicluster/expose-services.yaml
    gateway.networking.istio.io/cross-network-gateway created
    
  9. 我们将使用以下 Istio 操作员配置来配置cluster2并安装 Istio:

    apiVersion: install.istio.io/v1alpha1
    kind: IstioOperator
    spec:
      values:
        global:
          meshID: mesh1
          multiCluster:
            clusterName: cluster2
          network: network2
    

请注意,我们没有在配置中提供配置文件,这意味着将选择默认配置文件。在默认配置中,istioctl会安装 Ingress 网关和 istiod。要了解更多有关配置文件的信息,以及每个配置文件中包含的内容,请使用以下命令:

% istioctl profile dump default

示例文件可以在Chapter08/03-Cluster3.yaml中找到。使用以下命令在cluster2中安装 Istio:

% istioctl install --context="${CTX_CLUSTER2}" -f " Chapter08/03-Cluster3.yaml" -y
 Istio core installed
 Istiod installed
 Ingress gateways installed
 Installation complete
  1. 安装东西向网关并公开所有服务:

    % samples/multicluster/gen-eastwest-gateway.sh --mesh mesh1 --cluster cluster2 --network network2 | istioctl --context="${CTX_CLUSTER2}" install -y -f -
     Ingress gateways installed
     Installation complete
    % kubectl --context="${CTX_CLUSTER2}" apply -n istio-system -f samples/multicluster/expose-services.yaml
    gateway.networking.istio.io/cross-network-gateway created
    
  2. 创建一个远程密钥以便cluster1能够访问cluster2中的 API 服务器:

    % istioctl x create-remote-secret --context="${CTX_CLUSTER2}" --name=cluster2 | kubectl apply -f - --context="${CTX_CLUSTER1}"
    secret/istio-remote-secret-cluster2 created
    
  3. 最后,创建一个远程密钥以便cluster2能够访问cluster1中的 API 服务器:

    % istioctl x create-remote-secret --context="${CTX_CLUSTER1}" --name=cluster1 | kubectl apply -f - --context="${CTX_CLUSTER2}"
    secret/istio-remote-secret-cluster1 configured
    

现在是时候部署并测试我们的设置了。

通过 Envoy 虚拟服务进行部署和测试

接下来,我们将通过部署 Envoy 虚拟应用程序来测试设置,正如我们在前几节中所做的那样。请按照步骤 1-4,在Primary-remote on multi-network子节下的部署 Envoy 虚拟应用程序部分进行操作。

测试 Envoy 虚拟应用程序:

% for i in {1..5}; do curl -Hhost:mockshop.com -s "http://34.129.4.32";echo '\n'; done
Bootstrap Service Mesh Implementation with Istio
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
Bootstrap Service Mesh Implementation with Istio
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
V2----------Bootstrap Service Mesh Implementation with Istio----------V2

另一个需要执行的测试是验证控制平面的高可用性。你可以关闭任何集群中的 istiod,但这不会影响控制平面的操作。你仍然可以将新服务发布到服务网格中。

执行以下测试以验证控制平面的高可用性。我已经省略了命令说明,因为我们在本书中已经执行过这些步骤多次:

  1. 关闭cluster1中的 istiod。

  2. 使用01-envoy-proxy.yamlcluster1删除 Envoy 虚拟应用程序。

  3. 测试 Envoy 虚拟应用程序。

  4. 使用01-envoy-proxy.yamlcluster1中部署 Envoy 虚拟应用程序。

  5. 测试 Envoy 虚拟应用程序。

因为我们已经设置了一个多主控集群,即使cluster1的控制平面不可用,控制平面的操作也不会中断。

在下一节中,我们将设置一个多主控控制平面,其中cluster1cluster2共享同一网络。

同一网络上的多主控

在本节中,我们将设置一个具有共享网络的多主控 Istio 集群。在这种架构中,cluster1中的工作负载可以直接访问cluster2中的服务,反之亦然。在多主控集群中,我们不需要东西向网关,原因如下:

  • 服务可以跨集群边界直接相互通信

  • 每个控制平面都会观察两个集群中的 API 服务器

图 8.6 – 同一网络上的多主控

图 8.6 – 同一网络上的多主控

由于我们在上一节中将多主集群设置在一个独立网络中,首先需要进行一些清理工作以设置环境。为此,我们需要执行以下步骤:

  1. 卸载主集群和远程集群中的 Istio:

    % istioctl uninstall --purge --context="${CTX_CLUSTER2}" -y
      Uninstall complete
    % istioctl uninstall --purge --context="${CTX_CLUSTER1}" -y
      Uninstall complete
    
  2. 删除cluster2istio-system命名空间的所有标签:

    % kubectl label namespace istio-system topology.istio.io/network- --context="${CTX_CLUSTER2}"
    namespace/istio-system unlabeled
    
  3. 然后,我们将在初始部署步骤中,按照附加 Google Cloud 步骤部分打开两个集群之间的防火墙。

完成上述步骤后,两个集群已准备好进行下一步操作来安装 Istio。请按照以下步骤在两个集群上安装 Istio:

  1. 使用01-Cluster1.yaml安装 Istio。cluster1的配置与其他架构相同:

    % istioctl install --context="${CTX_CLUSTER1}" -f  "Chapter08/01-Cluster1.yaml"
    This will install the Istio 1.16.0 default profile with ["Istio core" "Istiod" "Ingress gateways"] components into the cluster. Proceed? (y/N) y
    ✔ Istio core installed
    ✔ Istiod installed
    ✔ Ingress gateways installed
    ✔ Installation complete                                                                                                                                                      Making this installation the default for injection and validation.
    
  2. 对于cluster2,我们将使用默认配置文件,它将安装 istiod 和 Ingress 网关。由于cluster2cluster1共享网络,我们将在clusterNamenetwork参数中分别使用cluster2network1值:

    apiVersion: install.istio.io/v1alpha1
    kind: IstioOperator
    spec:
      values:
        global:
          meshID: mesh1
          multiCluster:
            clusterName: cluster2
          network: network1
    

示例文件位于Chapter08/04-Cluster2.yaml。使用以下命令通过示例文件安装 Istio:

% istioctl install --context="${CTX_CLUSTER2}" -f "Chapter08/04-Cluster2.yaml"
This will install the Istio 1.16.0 default profile with ["Istio core" "Istiod" "Ingress gateways"] components into the cluster. Proceed? (y/N) y
✔ Istio core installed
✔ Istiod installed
✔ Ingress gateways installed
✔ Installation complete
  1. 创建远程 Secret,以便cluster1控制平面可以访问cluster2中的 Kubernetes API 服务器:

    % istioctl x create-remote-secret --context="${CTX_CLUSTER2}" --name=cluster2 | kubectl apply -f - --context="${CTX_CLUSTER1}"
    secret/istio-remote-secret-cluster2 created
    
  2. 创建远程 Secret,以便cluster2控制平面可以访问cluster1中的 Kubernetes API 服务器:

    % istioctl x create-remote-secret --context="${CTX_CLUSTER1}" --name=cluster1 | kubectl apply -f - --context="${CTX_CLUSTER2}"
    secret/istio-remote-secret-cluster1 created
    

接下来,我们将通过部署 Envoy 虚拟应用程序来测试设置,就像我们在之前的章节中所做的那样。要安装envoydummy应用程序,请按照步骤 1-4,即在多网络上部署 Envoy 虚拟应用程序部分下的主控-远程章节操作。类似地,按照步骤 5-6进行测试。以下代码块演示了如何在两个集群之间分配流量:

% for i in {1..5}; do curl -Hhost:mockshop.com -s "http://34.129.4.32";echo '\n'; done
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
Bootstrap Service Mesh Implementation with Istio
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
Bootstrap Service Mesh Implementation with Istio
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
  1. 另外,通过关闭主集群中的 istiod 并重新部署应用程序来测试虚拟应用程序,验证即使一个主集群的控制平面不可用,网格操作是否也不受影响。

这就完成了在独立网络上设置多主集群。共享网络上的多主集群无疑是最简单的 Istio 设置,它不需要东西向网关来协调不同 Kubernetes 集群之间的流量。

提醒

删除集群和防火墙规则。以下是如何删除集群的示例。请根据您的配置更改参数值:

% gcloud container clusters delete primary1-cluster --zone "australia-southeast2"
% gcloud container clusters delete primary2-cluster --zone "australia-southeast1-a"
% gcloud firewall delete primary1-primary2-shared-network

总结

本章内容非常实践性强,但我们希望你已经学会了如何在不同的集群配置中设置 Istio。每个部分都使用了两个集群作为示例来演示设置过程,但我们建议你通过增加更多的集群来扩展每个示例。通过部署一个 Envoy 虚拟应用程序和来自 utilities 命名空间的 curl Pod,并应用虚拟规则和目标规则,测试在多集群环境中服务的行为,来练习各种场景。通过配置东西向网关使其仅在跨集群之间可访问,来练习东西向流量场景,并根据本章中的说明观察其效果。

尽管我们讨论了四种部署选项,但选择正确的部署模型取决于具体的使用场景,你需要考虑底层基础设施提供商、应用隔离、网络边界和服务级别协议(SLA)等要求,以确定最适合的架构。通过实现多集群部署,你可以提高服务网格的可用性,并设定更严格的故障边界,从而避免某个故障导致整个集群宕机。在企业环境中,多团队协作可能需要隔离数据平面,但为了节省运营成本,可能可以共享控制平面。在这种情况下,像主备远程这样的多集群环境可以提供隔离和集中控制。

在下一章中,我们将了解 WebAssembly 以及它如何用于扩展 Istio 数据平面。

第九章:扩展 Istio 数据平面

Istio 提供了多种 API 来管理数据平面流量。有一个名为EnvoyFilter的 API 我们尚未使用。EnvoyFilter API 提供了一种定制 Istio 控制平面生成的 istio-proxy 配置的方式。使用EnvoyFilter API,您可以直接使用 Envoy 过滤器,即使它们没有被 Istio API 直接支持。

还有另一个 API 叫做WasmPlugins,它是扩展 istio-proxy 功能的另一种机制,WebAssemblyWasm)支持在 Envoy 等代理中变得越来越常见,以便开发人员构建扩展。

在本章中,我们将讨论这两个主题;然而,关于EnvoyFilter的内容会简要介绍,因为您已经在第三章中学习了 Envoy 的过滤器和插件。我们将重点讲解如何从 Istio 配置中调用 Envoy 插件。但我们会像往常一样通过动手活动深入探讨 Wasm。

本章将涵盖以下主题:

  • 为什么需要可扩展性?

  • 使用 EnvoyFilter 定制数据平面

  • 理解 Wasm 的基本原理

  • 使用 Wasm 扩展 Istio 数据平面

技术要求

为了简化操作,我们将使用 minikube 来进行本章的动手练习。到目前为止,您应该已经熟悉了安装和配置 minikube 的步骤,如果还不熟悉,请参阅第四章技术要求部分。

除了 minikube 外,最好在工作站上安装 Go 和 TinyGo。如果您是 Go 的新手,请按照go.dev/doc/install中的说明进行安装。根据tinygo.org/getting-started/install/macos/的说明为您的主机操作系统安装 TinyGo。然后,通过以下命令验证安装是否成功:

% tinygo version
tinygo version 0.26.0 darwin/amd64 (using go version go1.18.5 and LLVM version 14.0.0)

为什么需要可扩展性?

和任何好的架构一样,可扩展性非常重要,因为没有 一刀切 的技术方法能够适应每个应用程序的需求。可扩展性在 Istio 中尤为重要,因为它为用户提供了构建特殊情况和根据个人需求扩展 Istio 的选项。在 Istio 和 Envoy 的早期,两个项目采取了不同的方式来构建可扩展性。Istio 采用了构建通用的进程外扩展模型——Mixeristio.io/v1.6/docs/reference/config/policy-and-telemetry/mixer-overview/),而 Envoy 则专注于代理内扩展(www.envoyproxy.io/docs/envoy/latest/extending/extending)。Mixer 现在已被弃用;它是一种基于插件的实现,用于构建各种基础设施后端的扩展(也称为适配器)。一些适配器的例子包括 Bluemix、AWS、Prometheus、Datadog 和 SolarWinds。这些适配器使 Istio 能够与各种后端系统进行接口,以进行日志记录、监控和遥测,但基于适配器的扩展模型存在显著的资源低效问题,影响了尾延迟和资源利用率。这个模型本身也有局限性,应用场景有限。Envoy 的扩展方法要求用户用 C++ 编写过滤器,而 C++ 也是 Envoy 的本地语言。用 C++ 编写的扩展会与 Envoy 的代码一起打包、编译并进行测试,确保它们按预期工作。Envoy 的代理内扩展方法要求在 C++ 中编写扩展,随后进行单体构建过程,并且你必须自己维护 Envoy 的代码库。一些大型组织能够管理自己版本的 Envoy 代码库,但大多数 Envoy 社区成员认为这种方法不可行。因此,Envoy 采取了其他构建扩展的方法,其中一个是 基于 Lua 的过滤器,另一个是 Wasm 扩展。在基于 Lua 的扩展中,用户可以在现有的 Envoy HTTP Lua 过滤器中编写内联 Lua 代码。以下是一个 Lua 过滤器的示例;Lua 脚本已被突出显示:

http_filters:
name: envoy.filters.http.lua
typed_config:
  "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
  default_source_code:
    inline_string: |
     function envoy_on_request(request_handle)
...... -- Do something on the request path.
       request_handle:headers():add("NewHeader", "XYZ")
     end
     function envoy_on_response(response_handle)
       -- Do something on the response path.
      response_handle:logInfo("Log something")
     response_handle:headers:add("response_size",response_handle:body():length())
      response_handle:headers:remove("proxy")
    end

在这个示例中,我们使用了 envoy_on_request 和/或 envoy_on_response 函数,这些函数分别在请求和响应周期上作为协程执行。你可以在这些函数中编写 Lua 代码,以在请求/响应处理过程中执行以下操作:

  • 检查和修改请求和响应流的头部、主体和尾部

  • 异步 HTTP 调用上游系统

  • 执行直接响应并跳过进一步的过滤器迭代

你可以在 www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter.html?highlight=lua%20filter 阅读更多关于 Envoy HTTP Lua 过滤器的内容。这种方法适合简单的逻辑,但当编写复杂的处理指令时,编写内联 Lua 代码就不太实际。内联代码不容易与其他开发人员共享,也不容易与软件编程的最佳实践对齐。另一个缺点是缺乏灵活性,因为开发人员只能使用 Lua,这限制了非 Lua 开发人员编写这些扩展的能力。

为了给 Istio 提供可扩展性,需要一种带来更少权衡的方案。由于 Istio 的数据平面由 Envoy 组成,因此将 Envoy 和 Istio 的扩展性统一起来是有意义的。这可以将 Envoy 的发布与其扩展生态系统解耦,使 Istio 用户能够使用自己选择的语言构建数据平面扩展,利用最佳的编程语言和实践,然后部署这些扩展,而不会对生产环境中的 Istio 部署造成任何停机风险。在这个共同努力的基础上,Wasm 支持已被引入 Istio。在接下来的部分中,我们将讨论 Wasm。但在那之前,让我们快速回顾一下 Istio 对运行 Envoy 过滤器的支持。

使用 Envoy Filter 自定义数据平面

Istio 提供了 EnvoyFilter API,允许修改通过其他 Istio EnvoyFilter CRD 创建的配置,你可以直接更改这些低级配置。这是一个非常强大的功能,但也应该小心使用,因为如果使用不当,可能会导致更糟的结果。通过使用 EnvoyFilter,你可以应用 Istio CRD 中没有直接提供的配置,并执行更高级的 Envoy 功能。该过滤器可以在命名空间级别以及通过标签标识的特定工作负载级别上应用。

让我们通过一个示例来进一步理解这个问题。

我们将选取在 第七章 中执行的一个实际操作来将请求路由到 httpbin.org。不要忘记创建 Chapter09 文件夹并开启 istio-injection。以下命令将按照 Chapter09/01-httpbin-deployment.yaml 中的描述部署 httpbin Pod:

kubectl apply -f Chapter09/01-httpbin-deployment.yaml
curl -H "Host:httpbin.org" http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/get

仔细检查所有响应字段,确保包含请求中传递的所有头部信息。

使用 EnvoyFilter,我们将在请求发送到 httpbin Pod 之前添加一个自定义头部。对于这个示例,我们选择 ChapterName 作为头部名称,并将其值设置为 ExtendingIstioDataPlaneChapter09/02-httpbinenvoyfilter-httpbin.yaml 中的配置会将自定义头部添加到请求中。

使用 EnvoyFilter 应用以下配置:

$ kubectl apply -f Chapter09/02-httpbinenvoyfilter-httpbin.yaml
envoyfilter.networking.istio.io/updateheaderhorhttpbin configured

让我们分两部分来讲解Chapter09/02-httpbinenvoyfilter-httpbin.yaml

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: updateheaderforhttpbin
  namespace: chapter09
spec:
  workloadSelector:
    labels:
      app: httpbin
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_INBOUND
      listener:
        portNumber: 80
        filterChain:
          filter:
            name: "envoy.filters.network.http_connection_manager"
            subFilter:
              name: "envoy.filters.http.router"

在这一部分,我们将在 chapter09 命名空间中创建一个名为 updateheaderforhttpbin 的 EnvoyFilter,它将应用于具有 app 标签且值为 httpbin 的工作负载。对于该配置,我们正在为 httpbin Pod 的端口 80 上的所有传入流量应用配置补丁,该流量会传递到 Istio 侧车即 istio-proxy 即 Envoy。该配置补丁应用于 HTTP_FILTER,特别是应用于 http_connection_manager 网络过滤器的 HTTP 路由过滤器。

在 EnvoyFilter 配置的下一部分,我们在现有路由配置之前应用配置,特别是我们正在附加一个包含内联代码的 Lua 过滤器,正如 inlineCode 部分所指定的那样。Lua 代码在 envoy_on_request 阶段运行,并添加一个请求头,名称为 X-ChapterName,值为 ExtendingIstioDataPlane

 patch:
      operation: INSERT_BEFORE
      value:
        name: envoy.lua
        typed_config:
          "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
          inlineCode: |
            function envoy_on_request(request_handle)
              request_handle:logInfo(" ========= XXXXX ==========");
              request_handle:headers():add("X-ChapterName", "ExtendingIstioDataPlane");
            end

现在,继续使用以下命令测试端点:

% curl -H "Host:httpbin.org" http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/get

你将在响应中收到已添加的头部信息。

你可以使用以下命令查看应用的最终 Envoy 配置。要找到 httpbin Pod 的确切名称,可以使用 proxy-status

% istioctl proxy-status | grep httpbin
httpbin-7bffdcffd-l52sh.chapter09
Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED      NOT SENT     istiod-56fd889679-ltxg5     1.14.3

接下来是监听器的 proxy-config 详细信息:

% istioctl proxy-config listener httpbin-7bffdcffd-l52sh.chapter09  -o json

在输出中,查找 envoy.lua,这是我们通过配置应用的补丁和过滤器的名称。在输出中,查找 filterChainMatch 和设置为 80destinationPort

"filterChainMatch": {
                    "destinationPort": 80,
                    "transportProtocol": "raw_buffer"
                },

我们通过 EnvoyFilter 应用了该配置:

  {
                                    "name": "envoy.lua",
                                    "typedConfig": {
                                        "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua",
                                        "inlineCode": "function envoy_on_request(request_handle)\n  request_handle:logInfo(\" ========= XXXXX ==========\");\n  request_handle:headers():add(\"X-ChapterName\", \"ExtendingIstioDataPlane\");\nend \n"
                                    }
                                }

希望这给了你一些关于 EnvoyFilter 以及整体机制如何工作的想法。在本章的动手练习中,另一个示例应用相同的更改,但在 Ingress 网关层级。你可以在 Chapter09/03-httpbinenvoyfilter-httpbiningress.yaml 文件中找到该示例。在应用 Ingress 网关更改之前,请确保删除 Chapter09/02-httpbinenvoyfilter-httpbin.yaml 文件。

欲了解更多有关 EnvoyFilter 各种配置的详细信息,请参考 Istio 文档:istio.io/latest/docs/reference/config/networking/envoy-filter/#EnvoyFilter-EnvoyConfigObjectPatch

重要提示

清理时,请使用此命令:kubectl delete ns chapter09

在下一部分中,我们将学习 Wasm 基础知识,然后了解如何使用 Wasm 扩展 Istio 数据平面。

理解 Wasm 基础知识

Wasm 是一种便携式二进制格式,旨在运行在虚拟机VMs)上,使其能够在各种计算机硬件和数字设备上运行,并且在提升 Web 应用性能方面被广泛应用。它是为堆栈机设计的虚拟指令集架构ISA),旨在便携、紧凑且安全,具有较小的二进制文件大小,能够减少在 Web 浏览器执行时的下载时间。现代浏览器的 JavaScript 引擎能够比 JavaScript 解析和下载 Wasm 二进制格式快几个数量级。所有主要浏览器厂商都已支持 Wasm,Mozilla 基金会表示,Wasm 代码运行速度比等效的 JavaScript 代码快 10%到 800%。它提供了更快的启动时间和更高的峰值性能,且不会造成内存膨胀。

Wasm 也是构建 Envoy 扩展的首选且实用的选择,原因如下:

  • Wasm 扩展可以在运行时交付,而无需重新启动 istio-proxy。此外,扩展可以通过多种方式加载到 istio-proxy,而无需对 istio-proxy 进行任何更改。这使得能够以扩展的形式交付对扩展和代理行为的更改,而无需任何停机。

  • Wasm 与宿主机隔离,并在沙箱/虚拟机环境中执行,通过应用程序二进制接口ABI)与宿主机通信。通过 ABI,我们可以控制哪些内容可以或不能修改,以及哪些内容对扩展可见。

  • 在沙箱环境中运行 Wasm 的另一个好处是隔离性和定义的故障边界。如果 Wasm 执行出现问题,干扰的范围将仅限于沙箱内,不会扩展到宿主进程。

图 9.1 – Wasm 概述

图 9.1 – Wasm 概述

超过三十种编程语言支持编译为 Wasm 模块。一些示例包括 C、Java、Go、Rust、C++和 TypeScript。这使得大多数开发人员能够使用他们选择的编程语言来构建 Istio 扩展。

为了熟悉 Wasm,我们将使用 Go 构建一个示例应用程序。源代码位于Chapter09/go-Wasm-example文件夹中。

问题陈述是构建一个 HTML 页面,该页面接受一个小写字符串,并提供大写输出。我们假设你已经有一些 Go 的工作经验,并且 Go 已安装在你的实践环境中。如果你不想使用 Go,可以尝试使用你选择的语言实现该示例:

  1. Chapter09/go-Wasm-example复制代码,并重新初始化 Go 模块:

    % go mod init Bootstrap-Service-Mesh-Implementations-with-Istio/Chapter09/go-Wasm-example
    % go mod tidy
    

首先,让我们检查Chapter09/go-Wasm-example/cmd/Wasm/main.go

package main
import (
    "strings"
    "syscall/js"
)
func main() {
    done := make(chan struct{}, 0)
    js.Global().Set("WasmHash", js.FuncOf(convertToUpper))
    <-done
}
func convertToUpper(this js.Value, args []js.Value) interface{} {
    strings.ToUpper(args[0].String())
    return strings.ToUpper(args[0].String())
}

done := make(chan struct{}, 0)<-done是 Go 通道。Go 通道用于并发函数之间的通信。

js.Global().Set("WasmHash", hash)将 Go 哈希函数暴露给 JavaScript。

convertToUpper函数将一个字符串作为参数传入,参数随后使用syscall/js包中的.String()函数进行类型转换。strings.ToUpper(args[0].String())这一行将 JavaScript 提供的所有参数转换为大写字符串,并将其作为函数的输出返回。

  1. 下一步是使用以下命令编译Chapter09/go-Wasm-example/cmd/Wasm/main.go

    % GOOS=js GOARCH=Wasm go build -o static/main.Wasm cmd/Wasm/main.go
    

这里的秘密配方是GOOS=js GOARCH=Wasm,它告诉 Go 编译器将目标主机设为 JavaScript,目标架构设为 Wasm。如果没有这一步,Go 编译器将根据你的工作站规格编译成目标操作系统和架构。你可以在gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63查看有关GOOSGOARCH的更多信息。

然后,命令将在静态文件夹中生成名为main.Wasm的 Wasm 文件。

  1. 我们还需要在浏览器中获取并执行 Wasm。幸运的是,Go 通过Wasm_exec.js实现了这一功能。

JavaScript 文件可以在GOROOT文件夹中找到。要将其复制到静态目录,请使用以下命令:

% cp "$(go env GOROOT)/misc/Wasm/Wasm_exec.js" ./static
  1. 我们有 Wasm 和 JavaScript 来加载并在浏览器中执行 Wasm。我们需要创建一个 HTML 页面并从中加载 JavaScript。你将在Chapter09/go-Wasm-example/static/index.html中找到示例 HTML 页面。在 HTML 中你会看到以下代码片段来加载 JavaScript 并实例化 Wasm:

    <script src="img/Wasm_exec.js"></script>
    <script>
        const go = new Go();
        WebAssembly.instantiateStreaming(fetch("main.Wasm"), go.importObject).then((result) => {
            go.run(result.instance);
        });
    </script>
    
  2. 最后一步,我们需要一个 Web 服务器。你可以使用nginx或使用Chapter09/go-Wasm-example/cmd/webserver/main.go中的示例 HTTP 服务器包。使用以下命令运行服务器:

    % go run ./cmd/webserver/main.go
    Listening on http://localhost:3000/index.html
    
  3. 在浏览器中打开localhost:3000/index.html,并测试在文本框中输入的小写字母是否会被转换为大写字母:

图 9.2 – 使用 Go 创建 Wasm

图 9.2 – 使用 Go 创建 Wasm

本节介绍了 Wasm 的基本知识,希望你在阅读后已经对 Wasm 有了初步的了解。在下一节中,我们将学习 Wasm 如何帮助扩展 Istio 数据平面。

使用 Wasm 扩展 Istio 数据平面

Wasm 的主要目标是实现高性能的网页应用,因此 Wasm 最初是为在网页浏览器中执行而设计的。Wasm 有一个万维网联盟W3C)工作组,详情可以在www.w3.org/Wasm/查看。该工作组管理 Wasm 规范,具体内容可见www.w3.org/TR/Wasm-core-1/www.w3.org/TR/Wasm-core-2/。大多数互联网浏览器已经实现了该规范,Google Chrome 的相关信息可以在chromestatus.com/feature/5453022515691520找到。Mozilla 基金会还维护浏览器兼容性,详情请参见developer.mozilla.org/en-US/docs/WebAssembly#browser_compatibility。关于在第四层和第七层代理上支持 Wasm 执行的工作,大多数工作是最近才开始的。在代理上执行 Wasm 时,我们需要与主机环境进行通信的方式。与网页浏览器的开发类似,Wasm 应该只编写一次,之后可以在任何代理上运行。

介绍 Proxy-Wasm

为了使 Wasm 能够与主机环境通信,并使 Wasm 的开发与底层主机环境无关,存在一个Proxy-Wasm规范,也称为代理用 Wasm。该规范由Proxy-Wasm ABI 组成,属于低级别接口。然后,这些规范会在高级语言中抽象出来,形成Proxy-Wasm模块,即Proxy-Wasm ABI 规范。

Proxy-Wasm的概念可能很难理解。为了让它们更易于消化,我们将其分解成以下几个部分,并逐一讲解。

Proxy-Wasm ABI

ABI 是一种低级接口规范,描述了 Wasm 如何与虚拟机和主机进行通信。规范的详细信息可以在github.com/proxy-Wasm/spec/blob/master/abi-versions/vNEXT/README.md找到,规范本身可以在github.com/proxy-Wasm/spec查看。为了理解 API,最好先了解 ABI 规范中一些最常用的方法,从而理解它的作用:

  • _start:此函数需要在 Wasm 中实现,并且在 Wasm 加载和初始化时会被调用。

  • proxy_on_vm_start:当主机机器启动 Wasm 虚拟机时调用此方法。Wasm 可以使用此方法来检索虚拟机的任何配置详情。

  • proxy_on_configure:当主机环境启动插件并加载 Wasm 时调用此方法。通过此方法,Wasm 可以检索与插件相关的任何配置。

  • proxy_on_new_connection: 这是一个级别为 4 的扩展,当代理与客户端之间建立 TCP 连接时调用。

  • proxy_on_downstream_data: 这是一个级别为 4 的扩展,当从客户端接收到每个数据块时调用。

  • proxy_on_downstream_close: 这是一个级别为 4 的扩展,当与下游的连接关闭时调用。

  • proxy_on_upstream_data: 这是一个级别为 4 的扩展,当从上游接收到每个数据块时调用。

  • proxy_on_upstream_close: 这是一个级别为 4 的扩展,当与上游的连接关闭时调用。

  • proxy_on_http_request_headers: 这是一个级别为 7 的扩展,在从客户端接收到 HTTP 请求头时调用。

  • proxy_on_http_request_body: 这是一个级别为 7 的扩展,在从客户端接收到 HTTP 请求体时调用。

  • proxy_on_http_response_headers: 这是一个级别为 7 的扩展,在从上游接收到 HTTP 响应头时调用。

  • proxy_on_http_response_body: 这是一个级别为 7 的扩展,在从上游接收到 HTTP 响应体时调用。

  • proxy_send_http_response: 这也是一个级别为 7 的扩展,在主机环境 Envoy 中实现。使用这种方法,Wasm 可以指示 Envoy 在不实际调用上游服务的情况下发送 HTTP 响应。

此列表并未涵盖 ABI 中的所有方法,但我们希望它能让您对 ABI 的用途有所了解。以下图表说明了我们在本节中涵盖的内容:

图 9.3 – 代理 Wasm ABI

图 9.3 – 代理 Wasm ABI

如果我们在 Envoy 的背景下分析这个图表,我们可以得出以下解释:

  • 本地扩展按照配置文件中指定的顺序执行。

  • Envoy 还为加载 Wasm 提供了一个本地扩展,详见www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/Wasm/v3/Wasm.proto。该扩展负责加载并请求 Envoy 执行 Wasm。

  • Envoy 在虚拟机上执行 Wasm。

  • 在执行过程中,通过Proxy-Wasm ABI,Wasm 可以与请求、虚拟机和 Envoy 进行交互,我们在本节的早些时候看到了一些这些交互点。

  • 一旦 Wasm 执行完成,执行流程将返回到配置文件中定义的其他本地扩展。

虽然 ABI 很复杂,但它们也非常低级且不适合程序员使用,通常他们更喜欢在高级编程语言中编写代码。在下一节中,我们将了解代理 Wasm SDK 如何解决这个问题。

代理 Wasm SDK

Proxy-Wasm SDK 是 Proxy-Wasm ABI 的高级抽象,已在多种编程语言中实现。Proxy-Wasm SDK 遵循 ABI,这样在创建 Wasm 时,你不需要了解 Proxy-Wasm ABI。撰写本章时,Proxy-Wasm API 的 SDK 已在 Go(使用 TinyGo 编译器)、Rust、C++ 和 AssemblyScript 中提供。与我们对 ABI 所做的类似,我们将选择其中一种语言的 SDK,并进行分析,以了解 ABI 与 SDK 之间的关系。因此,让我们通过一些 Proxy-Wasm Go SDK 中的函数来感受它;该 SDK 可以在 pkg.go.dev/github.com/tetratelabs/proxy-Wasm-go-SDK/proxyWasm 找到。

首先,你需要了解 SDK 中定义的各种类型,因此我们提供了以下基础类型的列表:

  • VMContext:对应于每个 Wasm 虚拟机(VM)。对于每个 Wasm 虚拟机,只有一个 VMContextVMContext 具有以下方法:

    • OnVMStart(vmConfigurationSize int) OnVMStartStatus:当虚拟机创建时会调用此方法。在此方法内部,Wasm 可以检索虚拟机配置。

    • NewPluginContext(contextID uint32) PluginContext:为每个插件配置创建一个插件上下文。

  • PluginContext:对应于宿主中每个插件的配置。插件在 HTTP 或网络过滤器中为监听器配置。PluginContext 中的一些方法如下:

    • OnPluginStart(pluginConfigurationSize int) OnPluginStartStatus:此方法会为所有已配置的插件调用。一旦虚拟机(VM)创建完成,Wasm 可以通过此方法检索插件配置。

    • OnPluginDone() bool:当宿主删除 PluginContext 时会调用此方法。如果此方法返回 true,则表示可以删除 PluginContext,而 false 表示插件处于待处理状态,尚不能删除。

    • NewTcpContext(contextID uint32) TcpContext:此方法创建 TCPContext,对应于每个 TCP 请求。

    • NewHttpContext(contextID uint32) HttpContext:此方法创建 HTTPContext,对应于每个 HTTP 请求。

  • HTTPContext:此方法由 PluginContext 为每个 HTTP 流创建。以下是该接口中的一些可用方法:

    • OnHttpRequestHeaders(numHeaders int, endOfStream bool) Action:此方法提供访问作为请求流一部分的 HTTP 头部的功能。

    • OnHttpRequestBody(bodySize int, endOfStream bool) Action:此方法提供访问请求体数据帧的功能。它会为请求体中的每个数据帧多次调用。

    • OnHttpResponseHeaders(numHeaders int, endOfStream bool) Action:此方法提供访问响应头的功能。

    • OnHttpResponseBody(bodySize int, endOfStream bool) Action:此方法提供访问响应体帧的功能。

    • OnHttpStreamDone():该方法在HTTPContext删除之前被调用。在此方法中,Wasm 可以访问 HTTP 连接的请求和响应阶段的所有信息。

其他类型包括TCPContext,我们没有涵盖 SDK 中的所有方法和类型;你可以在pkg.go.dev/github.com/tetratelabs/proxy-Wasm-go-SDK@v0.20.0/proxyWasm/types#pkg-types中找到完整的列表和详细信息。

在了解了这些概述后,我们开始编写一个 Wasm,将自定义头信息注入到envoydummy Pod 的响应中。请注意,在使用 Envoy 过滤器自定义数据平面一节中,我们使用 EnvoyFilter 来修补 Istio,并应用了一个包含内联代码的 Lua 过滤器,将头信息注入到发送到httpbin Pod 的请求中。

创建chapter09-temp命名空间,并禁用istio-injection

% kubectl create ns chapter09-temp
namespace/chapter09-temp created

运行envoydummy,检查其是否按预期工作:

% kubectl apply -f Chapter09/01-envoy-dummy.yaml
namespace/chapter09-temp created
service/envoydummy created
configmap/envoy-dummy-2 created
deployment.apps/envoydummy-2 created

转发端口以便你可以在本地进行测试:

% kubectl port-forward svc/envoydummy 18000:80 -n chapter09-t
emp
Forwarding from 127.0.0.1:18000 -> 10000

然后,测试端点:

% curl  localhost:18000
V2----------Bootstrap Service Mesh Implementation with Istio----------V2%

所以,我们已经验证了envoydummy正在正常工作。下一步是创建 Wasm,将头信息注入响应中。你可以在Chapter09/go_Wasm_example_for_envoy找到源代码。

Go 模块中只有一个main.go文件,以下是代码的关键部分:

Go 模块的入口点是main方法。在main方法中,我们通过调用SetVMContext来设置 Wasm 虚拟机。该方法在github.com/tetratelabs/proxy-wasm-go-sdk/tree/main/proxywasm中的Entrypoint.go文件中有描述。以下代码片段展示了main方法:

func main() {
    proxyWasm.SetVMContext(&vmContext{})
}

以下方法将一个头信息注入到响应头中:

func (ctx *httpHeaders) OnHttpResponseHeaders(numHeaders int, endOfStream bool) types.Action {
    if err := proxyWasm.AddHttpResponseHeader("X-ChapterName", "ExtendingEnvoy"); err != nil {
        proxyWasm.LogCritical("failed to set response header: X-ChapterName")
    }
    return types.ActionContinue
}

另外,注意AddHttpResponseHeader,该方法定义在github.com/tetratelabs/proxy-Wasm-go-SDK/blob/v0.20.0/proxyWasm/hostcall.go#L395

下一步是为 Wasm 编译 Go 模块,我们需要使用 TinyGo。请注意,由于 Proxy-Wasm Go SDK 不支持标准 Go 编译器,因此我们无法使用标准 Go 编译器。

根据tinygo.org/getting-started/install/macos/中的说明,为你的主机操作系统安装 TinyGo。

使用 TinyGo,使用以下命令编译 Go 模块和 Wasm:

% tinygo build -o main.Wasm -scheduler=none -target=wasi main.go

一旦 Wasm 文件创建完成,我们需要将 Wasm 文件加载到configmap中:

% kubectl create configmap 01-Wasm --from-file=main.Wasm -n chapter09-temp
configmap/01-Wasm created

修改envoy.yaml文件,以应用 Wasm 过滤器并从configmap加载 Wasm:

http_filters:
              - name: envoy.filters.http.Wasm
                typed_config:
                  "@type": type.googleapis.com/udpa.type.v1.TypedStruct
                  type_url: type.googleapis.com/envoy.extensions.filters.http.Wasm.v3.Wasm
                  value:
                    config:
                      vm_config:
                        runtime: "envoy.Wasm.runtime.v8"
                        code:
                          local:
                            filename: "/Wasm2/main.Wasm"
              - name: envoy.filters.http.router
                typed_config:
                  "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

我们在配置文件中指定envoy,以使用v8运行时来运行 Wasm。相关更改也可以在Chapter09/02-envoy-dummy.yaml找到。按照如下方式应用更改:

% kubectl apply -f Chapter09/02-envoy-dummy.yaml
service/envoydummy created
configmap/envoy-dummy-2 created
deployment.apps/envoydummy-2 created

将端口80转发到18000

% kubectl port-forward svc/envoydummy 18000:80 -n chapter09-temp

测试端点,检查 Wasm 是否成功注入了响应头:

% curl -v localhost:18000
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-length: 72
< content-type: text/plain
< x-chaptername: ExtendingEnvoy
* Connection #0 to host localhost left intact
V2----------Bootstrap Service Mesh Implementation with Istio----------V2%

希望这一节让您对如何创建符合 Proxy-Wasm 的 Wasm 并将其应用于 Envoy 有了信心。我们建议您通过查看github.com/tetratelabs/proxy-Wasm-go-SDK/tree/main/examples上的示例,进行更多实践。

在我们结束这一节之前,让我们检查一下 Wasm 如何符合 Proxy-Wasm ABI。为此,我们将安装brew

% brew install wabt

WABT 提供了多种操作和检查 Wasm 的方法。其中一个工具,Wasm-objdump,可以打印 Wasm 二进制文件的信息。使用以下命令,您可以打印出所有在 Wasm 实例化后能访问到的主机环境函数列表:

% Wasm-objdump main.Wasm --section=export -x.

您会注意到输出是一个函数列表,这些函数在 Proxy-Wasm ABI 中有定义。

重要说明

要进行清理,您可以使用以下命令:

% kubectl delete ns chapter09-temp

这部分已完成关于 Proxy-Wasm 的内容,希望您现在理解了如何使用 Go SDK 创建符合 Proxy-Wasm 标准的 Wasm。在下一节中,我们将部署 Istio 中的 Wasm。

与 Istio 一起使用 Wasm

本节将扩展我们在前一节中构建的 Istio 数据平面 Wasm。我们将使用 Istio 的httpbin应用程序:

  1. 第一步是将 Go 模块中创建的main.Wasm上传到 HTTPS 位置,模块位置在Chapter09/go_Wasm_example_for_envoy。您可以使用 AWS S3 或类似服务来实现;另一种选择是使用 OCI 注册表,如main.Wasm到 AWS S3。托管该文件的 S3 存储桶 HTTPS 位置为https://anand-temp.s3.amazonaws.com/main.Wasm。请注意,出于安全原因,您在阅读本书时可能无法访问该链接,但我相信您可以自己创建 S3 存储桶或 Docker 注册表。

  2. 第二步是部署httpbin应用程序,相关文件已在Chapter09/01-httpbin-deployment.yaml中提供:

    % kubectl apply -f Chapter09/01-httpbin-deployment.yaml
    

检查以下命令的响应,并观察请求过程中添加的头信息:

% curl -H "Host:httpbin.org" http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/get
  1. 之后,我们将使用WasmPlugin应用以下更改:

    apiVersion: extensions.istio.io/v1alpha1
    kind: WasmPlugin
    metadata:
      name: addheaders
      namespace: chapter09
    spec:
      selector:
        matchLabels:
          app: httpbin
      url: https://anand-temp.s3.amazonaws.com/main.Wasm
      imagePullPolicy: Always
      phase: AUTHZ
    

使用以下命令应用WasmPlugin

% kubectl apply -f Chapter09/01-Wasmplugin.yaml
Wasmplugin.extensions.istio.io/addheaders configured

我们将在第 5 步之后进一步了解WasmPlugin。现在,让我们查看httpbin的响应头:

% curl --head -H "Host:httpbin.org" http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/get

您会注意到,正如预期的那样,响应中有x-chaptername: ExtendingEnvoy

  1. 让我们创建另一个 Wasm,向request添加自定义头信息,以便我们可以在httpbin负载的响应中看到它。为了这个目的,Chapter09/go_Wasm_example_for_istio中已经创建了一个 Wasm。请注意main.go中的OnHTTPRequestHeaders函数:

    func (ctx *httpHeaders) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
        if err := proxyWasm.AddHttpRequestHeader("X-Chapter", "Chapter09"); err != nil {
            proxyWasm.LogCritical("failed to set request header: X-ChapterName")
        }
        proxyWasm.LogInfof("added custom header to request")
        return types.ActionContinue
    }
    

将其编译成 Wasm 并复制到 S3 位置。另一个 Istio 配置文件Chapter09/02-Wasmplugin.yaml也可以用于部署此 Wasm:

apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: addheaderstorequest
  namespace: chapter09
spec:
  selector:
    matchLabels:
      app: httpbin
  url: https://anand-temp.s3.amazonaws.com/AddRequestHeader.Wasm
  imagePullPolicy: Always
  phase: AUTHZ
  1. 应用更改后,测试端点,你会发现两个 Wasm 都已执行,在响应中添加了一个头部,同时在请求中也添加了一个头部,这在httpbin响应中得到了反映。以下是响应的简短版本:

    % curl -v -H "Host:httpbin.org" http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/get
    < HTTP/1.1 200 OK
    ……
    < x-chaptername: ExtendingEnvoy
    <
    {
      "args": {},
      "headers": {
        "Accept": "*/*",
        "Host": "httpbin.org",
        "User-Agent": "curl/7.79.1",
    …..,
        "X-Chapter": "Chapter09",
    …
      },
      "origin": "10.10.10.216",
      "url": "http://httpbin.org/get"
    }
    

步骤 3步骤 4中,我们使用WasmPlugin在 Istio 数据平面上应用 Wasm。以下是我们在WasmPlugin中配置的参数:

  • selector:在selector字段中指定 Wasm 应应用的资源。它可以是 Istio 网关和 Kubernetes Pod。你需要提供标签,标签必须与应用 Wasm 配置的 Envoy sidecar 的工作负载匹配。在我们实现的示例中,我们应用了app:httpbin标签,这对应于httpbin Pod。

  • url:这是可以下载 Wasm 文件的位置。我们提供了 HTTP 位置,但也支持 OCI 位置。默认值是oci://,用于引用 OCI 镜像。要引用基于文件的位置,请使用file://,它用于引用在代理容器内本地存在的 Wasm 文件。

  • imagePullPolicy:此字段的可选值如下:

    • UNSPECIFIED_POLICY:除非 URL 指向带有最新标签的 OCI 镜像,否则与IfNotPresent相同。在这种情况下,字段将默认为Always

    • Always:我们将始终从 URL 指定的位置拉取最新版本的镜像。

    • IfNotPresent:仅当请求的版本在本地不可用时,才拉取 Wasm。

  • phase:此字段的可选值如下:

    • UNSPECIFIED_PHASE:这意味着 Wasm 过滤器将被插入到过滤器链的末尾。

    • AUTHN:这将在 Istio 认证过滤器之前插入插件。

    • AUTHZ:这将在认证和授权过滤器之间插入插件。

    • STATS:这将在授权过滤器后、统计过滤器前插入插件。

我们已经描述了在示例中使用的值,但可以在WasmPlugin中配置各种字段;你可以在istio.io/latest/docs/reference/config/proxy_extensions/Wasm-plugin/#WasmPlugin中找到详细列表。

对于生产部署,我们强烈建议使用sha256字段来确保 Wasm 模块的完整性。

Istio 通过利用 istio-agent 内的 xDS 代理和 Envoy 的扩展配置发现服务ECDS)为 Wasm 提供了可靠的开箱即用分发机制。有关 ECDS 的详细信息,请访问www.envoyproxy.io/docs/envoy/latest/configuration/overview/extension

应用WasmPlugin后,你可以检查istiod日志中的 ECDS 条目:

% kubectl logs istiod-56fd889679-ltxg5 -n istio-system

你会找到类似如下的日志条目:

10-18T12:02:03.075545Z     info ads  ECDS: PUSH for node:httpbin-7bffdcffd-4zrhj.chapter09 resources:1 size:305B

Istio 向 istio-proxy 发出 ECDS 调用,应用WasmPlugin。以下图示描述了通过 ECDS API 应用 Wasm 的过程:

图 9.4 – 向 Istio 数据平面分发 Wasm

图 9.4 – 向 Istio 数据平面分发 Wasm

部署在 Envoy 旁边的 istio-agent 拦截来自istiod的 ECDS 调用。然后它下载 Wasm 模块,保存到本地,并更新 ECDS 配置,指定下载的 Wasm 模块的路径。如果 WASM 模块无法访问 Istio-agent,它将拒绝 ECDS 更新。你可以在istiod日志中看到 ECDS 更新失败。

本节到此结束,希望它能为你提供足够的知识,帮助你开始将 Wasm 应用到生产工作负载中。

总结

在本章中,我们了解了 Wasm 及其用途。我们学习了 Wasm 如何因其高性能而在网页上使用,并且我们也熟悉了如何使用 Go 构建 Wasm,并通过 JavaScript 从浏览器中使用它。Wasm 也正逐渐成为服务器端的热门选择,尤其是在网络代理(如 Envoy)中。

为了获得一个标准化的接口来实现代理的 Wasm,有 Proxy-Wasm ABI 规范,这些是低级规范,描述了 Wasm 和托管 Wasm 的代理之间的接口。Envoy 的 Wasm 需要符合 Proxy-Wasm 规范,但 Proxy-Wasm ABI 很难使用;相比之下,Proxy-Wasm SDK 更易于使用。在写这章时,有许多编程语言中都有 Proxy-Wasm SDK 的实现,其中 Rust、Go、C++和 AssemblyScript 是最受欢迎的几种。我们利用 Envoy Wasm 过滤器,在 Envoy HTTP 过滤器链上配置 Wasm。然后,我们构建了一些简单的 Wasm 示例来操作请求和响应头,并通过WasmPlugin将它们部署到 Istio 上。Wasm 并不是扩展 Istio 数据平面的唯一选项,还有一个名为 EnvoyFilter 的过滤器,可以将 Envoy 配置作为补丁应用到由Istiod创建的 Envoy 配置上。

下一章非常有趣,因为我们将学习如何为非 Kubernetes 工作负载部署 Istio 服务网格。

第十章:为非 Kubernetes 工作负载部署 Istio 服务网格

Istio 和 Kubernetes 是互补的技术。Kubernetes 解决了将分布式应用程序以容器形式打包、相互隔离并在具有专用资源的统一环境中部署的问题。尽管 Kubernetes 解决了容器的部署、调度和管理问题,但它并没有解决容器之间的流量管理问题。Istio 通过提供流量管理能力、增加可观察性并执行零信任安全模型,补充了 Kubernetes。

Istio 像是 Kubernetes 的边车;话虽如此,Kubernetes 是一项相对较新的技术,大约在 2017 年左右开始广泛应用。从 2017 年开始,大多数企业在构建微服务和其他云原生应用程序时使用 Kubernetes,但仍有许多应用程序没有基于 Kubernetes 构建或迁移到 Kubernetes;这些应用程序通常部署在虚拟机VM)上。虚拟机不仅限于传统数据中心,而且也是云服务提供商的主流产品。组织最终会有一个基于 Kubernetes 的应用程序和基于虚拟机的应用程序并行部署在云端和本地的数据中心。

在本章中,我们将学习 Istio 如何帮助将传统技术与现代技术结合起来,以及如何将服务网格扩展到 Kubernetes 之外。在本章中,我们将涵盖以下主题:

  • 检查混合架构

  • 为混合架构设置服务网格

技术要求

使用以下命令,我们将在 Google Cloud 中设置基础设施,以供动手练习使用:

  1. 创建一个 Kubernetes 集群:

    % gcloud container clusters create cluster1 --cluster-version latest --machine-type "e2-medium" --num-nodes "3" --network "default" --zone "australia-southeast1-a" --disk-type "pd-standard" --disk-size "30"
    kubeconfig entry generated for cluster1.
    NAME      LOCATION                MASTER_VERSION   MASTER_IP      MACHINE_TYPE  NODE_VERSION     NUM_NODES  STATUS
    cluster1  australia-southeast1-a  1.23.12-gke.100  34.116.79.135  e2-medium     1.23.12-gke.100  3          RUNNING
    
  2. 创建虚拟机:

    % gcloud compute instances create chapter10-instance --tags=chapter10-meshvm \
      --machine-type=e2-medium --zone=australia-southeast1-b
      --network=default --subnet=default \
      --image-project=ubuntu-os-cloud \
      --image=ubuntu-1804-bionic-v20221201, mode=rw, size=10
    Created [https://www.googleapis.com/compute/v1/projects/istio-book-370122/zones/australia-southeast1-b/instances/chapter10-instance].
    NAME        ZONE                    MACHINE_TYPE  PREEMPTIBLE  INTERNAL_IP  EXTERNAL_IP   STATUS
    chapter10-instance australia-southeast1-b  e2-medium                10.152.0.13  34.87.233.38  RUNNING
    
  3. 检查你的kubectl文件以找到集群名称,并适当设置context

    % kubectl config view -o json | jq .contexts
    [
      {
        "name": "gke_istio-book-370122_australia-southeast1-a_cluster1",
        "context": {
          "cluster": "gke_istio-book-370122_australia-southeast1-a_cluster1",
          "user": "gke_istio-book-370122_australia-southeast1-a_cluster1"
        }
      }
    ]
    % export CTX_CLUSTER1=gke_istio-book-370122_australia-southeast1-a_cluster1
    
  4. 使用 Google Cloud 仪表盘中的SSH访问已创建的服务器——你可以在右下角找到SSH选项,如下图所示:

图 10.1 – Google Cloud 仪表盘

图 10.1 – Google Cloud 仪表盘

  1. 点击SSH,这将打开SSH 浏览器,如下图所示:

图 10.2 – SSH 浏览器

图 10.2 – SSH 浏览器

  1. 查找用户名,然后从终端使用 SSH:

    % gcloud compute ssh anand_rai@chapter10-instance
    
  2. 设置防火墙以允许 Kubernetes 集群和虚拟机之间的流量,具体步骤如下:

    1. 查找集群的无类域间路由CIDR):
    % CLUSTER_POD_CIDR=$(gcloud container clusters describe cluster1 --format=json --zone=australia-southeast1-a | jq -r '.clusterIpv4Cidr')
    % echo $CLUSTER_POD_CIDR
    10.52.0.0/14
    
    1. 创建防火墙规则:
    % gcloud compute firewall-rules create "cluster1-pods-to-chapter10vm" \
      --source-ranges=$CLUSTER_POD_CIDR \
      --target-tags=chapter10-meshvm  \
      --action=allow \
      --rules=tcp:10000
    Creating firewall...⠹Created [https://www.googleapis.com/compute/v1/projects/istio-book-370122/global/firewalls/cluster1-pods-to-chapter10vm].
    Creating firewall...done.
    NAME  NETWORK  DIRECTION  PRIORITY  ALLOW      DENY  DISABLED
    cluster1-pods-to-chapter10vm   default  INGRESS    1000      tcp:10000        False
    

这就是接下来部分所需的所有内容。我们将首先探索一些基础知识,然后进行实际设置。

检查混合架构

如本章介绍所述,组织已经采用 Kubernetes 并运行微服务及其他各种工作负载作为容器,但并非所有工作负载都适合容器。因此,组织必须遵循混合架构:

图 10.3 – 混合架构

图 10.3 – 混合架构

设备和传统应用通常部署在裸机服务器上。单体应用程序以及一些商业现成软件COTS)应用程序部署在虚拟机上。现代应用程序以及基于微服务架构的自研应用程序则作为容器部署,这些容器由 Kubernetes 等平台进行管理和编排。所有三种部署模式——即裸机、虚拟机和容器——分布在传统数据中心和各种云提供商之间。这些不同的应用架构和部署模式的交织引发了各种问题:

  • 服务网格与虚拟机(VM)之间的流量管理具有挑战性,因为它们彼此之间完全没有概念。

  • 无法查看虚拟机应用和服务网格内应用之间的流量操作。

  • 服务网格内虚拟机和应用程序的治理不一致,因为没有统一的方式来定义和应用虚拟机应用和网格内应用的安全策略。

以下是虚拟机和服务网格环境中流量流动的示例:

图 10.4 – 服务网格和虚拟机之间的流量管理是分开的

图 10.4 – 服务网格和虚拟机之间的流量管理是分开的

虚拟机被视为一个独立的宇宙。开发者必须选择其中一种部署模式,因为他们不能将系统组件分布在虚拟机和容器之间。这对于传统系统来说是可以接受的,但在基于微服务架构构建应用程序时,选择虚拟机和容器之间的限制性决策会成为约束。例如,你的系统可能需要一个数据库,虚拟机部署可能更适合,而应用程序的其他部分可能更适合容器部署。尽管有很多传统的解决方案可以在服务网格和虚拟机之间路由流量,但这样做会导致不同的网络解决方案。幸运的是,Istio 提供了为虚拟机建立服务网格的选项。解决方案是将虚拟机抽象为 Istio 构造,使得服务网格运维人员能够在容器和虚拟机之间一致地操作网络。

在下一节中,我们将学习如何为虚拟机配置服务网格。

为混合架构设置服务网格

在本节中,我们将设置服务网格。但首先,让我们先从设置概览部分高层次地看一下步骤,然后在在虚拟 机上设置演示应用程序部分进行实现。

设置概览

Envoy是一款出色的网络软件,也是一个优秀的反向代理;它也被广泛采用作为独立的反向代理。Solo.io和一些其他公司使用 Envoy 构建了 API 网关解决方案,Kong Inc.拥有 Kong Mesh 和 Kuma Service Mesh 技术,利用 Envoy 作为数据平面的边车。

当 Envoy 作为边车部署时,它并不知道自己是一个边车;它通过 xDS 协议与istiod通信。Istio init用正确的配置和有关istiod的详细信息启动 Envoy,并且边车注入将正确的证书挂载,这些证书随后由 Envoy 用于与istiod进行身份验证;一旦启动,它会通过 xDS API 持续获取正确的配置。

基于相同的概念,Istio 将 Envoy 打包为虚拟机的边车。Istio 操作员需要执行以下图示的步骤,将虚拟机包含到服务网格中:

图 10.5 – 将虚拟机加入网格的步骤

图 10.5 – 将虚拟机加入网格的步骤

以下是我们将在下一节中实施的步骤的简要概述:

  1. 为了使虚拟机边车能够访问 Istio 控制平面,我们需要通过东西向网关暴露istiod。因此,我们安装另一个 Ingress 网关,用于东西向流量。

  2. 我们通过东西向网关暴露istiod服务。这一步和前一步类似于多集群服务网格设置中所需的步骤,正如第八章中所讨论的那样。

  3. 虚拟机中的边车需要访问 Kubernetes API 服务器,但由于虚拟机不属于集群,因此无法访问Kubernetes 凭证。为了解决这个问题,我们将手动在 Kubernetes 中为虚拟机边车创建一个服务账户,以便它能够访问 API 服务器。我们在这里手动执行,但可以通过使用像HashiCorp Vault这样的外部凭证管理服务来自动化此过程。

  4. 下一步是创建 Istio WorkloadGroup,它提供了由边车(sidecar)使用的启动配置。它可以被运行相似工作负载的虚拟机集合共享。在WorkloadGroup中,您定义通过标签来标识工作负载在 Kubernetes 中的信息,以及其他细节,如暴露的端口、使用的服务账户以及各种健康检查探针。在某种程度上,WorkloadGroup类似于 Kubernetes 中的部署描述符。我们将在下一节的设置中更详细地了解它。

  5. 操作员需要手动生成用于配置虚拟机和边车的配置文件。对于自动扩展虚拟机来说,生成配置文件时会面临一些挑战。

  6. 在这一步中,我们需要将上一步骤中的配置复制到虚拟机的指定位置。

  7. 需要安装 Istio 边车。

  8. 最后,需要启动 Istio 侧车,并进行一些检查以确保它已经采纳了在步骤 5中创建的配置。

一旦 Istio 侧车启动,它将拦截出去的流量并根据服务网格规则进行路由,只要目标服务端点(可以是虚拟机或 Kubernetes Pod)在同一网络中,Ingress 网关就能完全识别虚拟机工作负载并进行路由,网格内的任何流量也适用,具体如下图所示:

图 10.6 – 虚拟机工作负载与网格中其他工作负载的处理方式相同

图 10.6 – 虚拟机工作负载与网格中其他工作负载的处理方式相同

Pod 连接性假定集群网络使用与独立机器相同的地址空间。对于云托管的 Kubernetes(Google GKE 和 Amazon EKS),这是默认的网络模式,但对于自托管的集群,需要使用 Calico 等网络子系统来实现一个平面可路由的网络地址空间。在下一节中,我们将执行 Istio 在虚拟机上的设置,因此请卷起袖子,并确保完成技术要求部分中描述的任务。

在虚拟机上设置一个演示应用程序

我们将首先在虚拟机上安装一个应用程序,模拟一个虚拟机工作负载/应用程序,用于测试整体设置。为此,我们需要执行以下步骤:

  1. 在虚拟机上设置 Envoy。按照 Envoy 提供的www.envoyproxy.io/docs/envoy/latest/start/install中的操作系统安装说明进行设置,选择用于创建虚拟机的操作系统。可以按如下方式进行:

    1. 安装envoy,如以下代码块所示:
    $ sudo apt update
    $ sudo apt install debian-keyring debian-archive-keyring apt-transport-https curl lsb-release
    $ curl -sL 'https://deb.dl.getenvoy.io/public/gpg.8115BA8E629CC074.key' | sudo gpg --dearmor -o /usr/share/keyrings/getenvoy-keyring.gpg
    # Verify the keyring - this should yield "OK"
    $ echo 
    a077cb587a1b622e03aa4bf2f3689de14658a9497a9af2c427bba5f4cc3c4723 /usr/share/keyrings/getenvoy-keyring.gpg | sha256sum --check
    $ echo "deb [arch=amd64 signed-by=/usr/share/keyrings/getenvoy-keyring.gpg] https://deb.dl.getenvoy.io/public/deb/debian $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/getenvoy.list
    $ sudo apt update
    envoy is properly installed:
    
$ envoy --version
envoy  version: d362e791eb9e4efa8d87f6d878740e72dc8330ac/1.18.2/clean-getenvoy-76c310e-envoy/RELEASE/BoringSSL
  • 配置 Envoy 以运行虚拟应用程序:

    1. 使用envoy-demo.yaml并复制Chapter4/envoy-config-2.yaml的内容。

    2. 检查envoy-demo.yaml的内容是否与您复制或创建的内容一致。* 使用在envoy-demo.yaml中提供的配置运行envoy

    $ envoy -c envoy-demo.yaml &
    [2022-12-06 03:46:31.679][55335][info][main] [external/envoy/source/server/server.cc:330] initializing epoch 0 (base id=0, hot restart version=11.104)
    
    • 测试应用程序是否在虚拟机(VM)上运行:
    $ curl localhost:10000
    V2----------Bootstrap Service Mesh Implementation with Istio----------V2
    

在虚拟机上运行应用程序后,我们可以继续进行其余设置。请注意,在虚拟机上安装 Istio 之前,设置应用程序并非强制性的。您可以在安装 Istio 侧车前或后随时在虚拟机上安装演示应用程序。

在集群中设置 Istio

假设集群中没有运行 Istio,但如果有的话,可以跳过本节。使用以下步骤进行设置:

  1. 配置IstioOperator配置文件进行安装,提供集群和网络名称。该文件也可以在Chapter10/01-Cluster1.yaml中找到:

    apiVersion: install.istio.io/v1alpha1
    kind: IstioOperator
    spec:
      values:
        global:
          meshID: mesh1
          multiCluster:
            clusterName: cluster1
          network: network1
    
  2. 安装 Istio,如以下代码块所示:

    % istioctl install -f Chapter10/01-Cluster1.yaml --set values.pilot.env.PILOT_ENABLE_WORKLOAD_ENTRY_AUTOREGISTRATION=true --set values.pilot.env.PILOT_ENABLE_WORKLOAD_ENTRY_HEALTHCHECKS=true --context="${CTX_CLUSTER1}"
    This will install the Istio 1.16.0 default profile with ["Istio core" "Istiod" "Ingress gateways"] components into the cluster. Proceed? (y/N) y
     Istio core installed
     Istiod installed
     Ingress gateways installed
     Installation complete                                                                                                                                                                        Making this installation the default for injection and validation.
    

这标志着 Istio 在集群中的基础安装完成。接下来,我们将配置 Istio,使其为与虚拟机上的 Istio 集成做好准备。

配置 Kubernetes 集群

在本节中,我们将为与虚拟机上的 Istio 集成准备网格:

  1. 安装东西向网关以暴露istiod验证 Webhook 和服务:

    % samples/multicluster/gen-eastwest-gateway.sh \
    --mesh mesh1 --cluster cluster1 --network network1  | \
    istioctl install -y --context="${CTX_CLUSTER1}" -f -
    ✔ Ingress gateways installed
    ✔ Installation complete
    
  2. 暴露istiod服务:

    % kubectl apply -n istio-system -f samples/multicluster/expose-istiod.yaml --context="${CTX_CLUSTER1}"
    gateway.networking.istio.io/istiod-gateway created
    virtualservice.networking.istio.io/istiod-vs created
    
  3. 按照以下步骤创建服务帐户:

    1. 创建一个命名空间来托管WorkloadGroupServiceAccount
    % kubectl create ns chapter10vm --context="${CTX_CLUSTER1}"
    istiod on the VM to connect with the Kubernetes API server:
    
% kubectl create serviceaccount chapter10-sa -n chapter10vm --context="${CTX_CLUSTER1}"
serviceaccount/chapter10-sa created
  • 按如下方式设置WorkloadGroup

    1. 使用以下配置创建工作负载模板;该文件也可在Chapter10/01-WorkloadGroup.yaml中找到:
    apiVersion: networking.istio.io/v1alpha3
    kind: WorkloadGroup
    metadata:
      name: "envoydummy"
      namespace: "chapter10vm"
    spec:
      metadata:
        labels:
          app: "envoydummy"
      template:
        serviceAccount: "chapter10-sa"
        network: "network1"
      probe:
        periodSeconds: 5
        initialDelaySeconds: 1
        httpGet:
          port: 10000
          path: /
    
    1. 应用配置。此模板将被 Istio 用于创建表示虚拟机上运行工作负载的工作负载条目:
    % kubectl --namespace chapter10vm apply -f "Chapter10/01-WorkloadGroup.yaml" --context="${CTX_CLUSTER1}"
    workloadgroup.networking.istio.io/envoyv2 created
    

在进入下一部分之前,让我们检查一下WorkloadGroup.yaml的内容。

WorkloadGroup是一种定义托管在虚拟机上工作负载特征的方式,类似于 Kubernetes 中的部署。WorkloadGroup具有以下配置:

  • metadata:这主要用于定义 Kubernetes 标签以标识工作负载。我们设置了一个值为envoydummyapp标签,您可以在 Kubernetes 服务描述中使用该标签来标识将被服务定义抽象化的端点。

  • template:这定义了将被复制到由 Istio 代理生成的WorkloadEntry配置中的值。最重要的两个值是服务帐户名称和网络名称。ServiceAccount指定将用于生成工作负载身份的帐户名称。网络名称用于根据其网络位置对端点进行分组,并了解哪些端点可以直接相互访问,哪些端点需要通过东西向网关连接,就像我们在第八章中为多集群环境设置的那样。在这个实例中,我们已分配了network1的值,这与我们在01-cluster1.yaml中配置的(即cluster1)相同,而且虚拟机在同一网络上,彼此可以直接访问,因此我们不需要为它们连接做任何特别的安排。

  • probe:这是用来了解虚拟机工作负载的健康状况和准备情况的配置。流量不会路由到不健康的工作负载,从而提供了一个有韧性的架构。在这个实例中,我们配置了在创建WorkloadEntry后进行一次 HTTP Get探测,延迟 1 秒,然后每 5 秒进行一次定期探测。你还可以定义成功和失败的阈值,默认值分别为1秒和3秒。我们已配置虚拟机上的端点暴露在10000端口,路径为root,并且应该用于确定应用程序的健康状况。

接下来,让我们开始在虚拟机上设置 Istio。

在虚拟机上设置 Istio

为了在虚拟机上配置和设置 Istio,我们需要执行以下步骤:

  1. 为 Istio 边车生成配置:

    % istioctl x workload entry configure -f "Chapter10/01-WorkloadGroup.yaml" -o . --clusterID "cluster1" --autoregister --context="${CTX_CLUSTER1}"
    Warning: a security token for namespace "chapter10vm" and service account "chapter10-vm-sa" has been generated and stored at "istio-token"
    Configuration generation into directory . was successful
    

这将在当前目录下生成以下五个文件:

% ls
hosts root-cert.pem istio-token cluster.env mesh.yaml
  1. 首先将所有文件复制到虚拟机的主目录,然后将它们复制到各个文件夹,如以下代码块所示:

    % sudo mkdir -p /etc/certs
    % sudo cp "${HOME}"/root-cert.pem /etc/certs/root-cert.pem
    % sudo  mkdir -p /var/run/secrets/tokens
    % sudo cp "${HOME}"/istio-token /var/run/secrets/tokens/istio-token
    % sudo cp "${HOME}"/cluster.env /var/lib/istio/envoy/cluster.env
    % sudo cp "${HOME}"/mesh.yaml /etc/istio/config/mesh
    % sudo sh -c 'cat $(eval echo ~$SUDO_USER)/hosts >> /etc/hosts'
    % sudo mkdir -p /etc/istio/proxy
    % sudo chown -R istio-proxy /var/lib/istio /etc/certs /etc/istio/proxy /etc/istio/config /var/run/secrets /etc/certs/root-cert.pem
    
  2. 安装 Istio VM 集成运行时。从storage.googleapis.com/istio-release/releases/下载并安装该软件包:

    $ curl -LO https://storage.googleapis.com/istio-release/releases/1.16.0/deb/istio-sidecar.deb
    $ sudo dpkg -i istio-sidecar.deb
    Selecting previously unselected package istio-sidecar.
    (Reading database ... 54269 files and directories currently installed.)
    Preparing to unpack istio-sidecar.deb ...
    Unpacking istio-sidecar (1.16.0) ...
    Setting up istio-sidecar (1.16.0) ...
    
  3. 在虚拟机上启动 Istio 代理,然后检查其状态:

    $ sudo systemctl start istio
    $ sudo systemctl status istio
    ● istio.service - istio-sidecar: The Istio sidecar
         Loaded: loaded (/lib/systemd/system/istio.service; disabled; vendor preset: enable>
         Active: active (running) since Tue 20XX-XX-06 07:56:03 UTC; 15s ago
           Docs: http://istio.io/
       Main PID: 56880 (sudo)
          Tasks: 19 (limit: 4693)
         Memory: 39.4M
            CPU: 1.664s
         CGroup: /system.slice/istio.service
                 ├─56880 sudo -E -u istio-proxy -s /bin/bash -c ulimit -n 1024; INSTANCE_IP>
                 ├─56982 /usr/local/bin/pilot-agent proxy
                 └─56992 /usr/local/bin/envoy -c etc/istio/proxy/envoy-rev.json --drain-tim>
    

这完成了 Istio 侧车代理在虚拟机上的安装和配置。

接下来,验证WorkloadEntry是否已在chapter10vm命名空间中创建:

% kubectl get WorkloadEntry -n chapter10vm
NAME                              AGE    ADDRESS
envoydummy-10.152.0.21-network1   115s   10.152.0.21

WorkloadEntry会自动创建,表示虚拟机已成功加入网格。它描述了虚拟机上运行的应用程序的属性,并继承了WorkloadGroup配置中的模板。

使用以下代码块检查WorkloadEntry的内容:

% kubectl get WorkloadEntry/envoydummy-10.152.0.21-network1 -n chapter10vm -o yaml

WorkloadEntry包含以下值:

  • address:这是应用在虚拟机上运行的网络地址,也可以是 DNS 名称。在这个例子中,虚拟机的私有 IP 是10.152.0.21

  • labels:这些标签继承自WorkloadGroup定义,用于标识由服务定义选择的端点。

  • locality:在多数据中心中,此字段用于标识工作负载在机架层级的位置。该字段用于基于位置/邻近性的负载均衡。

  • network:该值继承自WorkloadGroup条目。

  • serviceAccount:该值继承自WorkloadGroup条目。

  • status:该值指定应用程序的健康状况。

到目前为止,我们已经在虚拟机上配置了 Istio 代理,并验证了代理可以与 Istiod 通信。在下一节中,我们将把虚拟机上的工作负载与网格集成。

将虚拟机工作负载与网格集成

让我们开始进行配置,以便网格能够将流量路由到在虚拟机上运行的工作负载:

  1. 使用以下代码块,将envoydummy应用暴露为虚拟机上的 Kubernetes 服务:

    apiVersion: v1
    kind: Service
    metadata:
      name: envoydummy
      labels:
        app: envoydummy
      namespace: chapter10vm
    spec:
      ports:
      - port: 80
        targetPort: 10000
        name: tcp
      selector:
        app: envoydummy
    ---
    

配置是标准的,它将虚拟机视为需要通过服务暴露的 Pod。请注意标签,它们与WorkloadGroup定义中的元数据值匹配。在定义服务时,只需假设虚拟机不过是如WorkloadGroup配置文件中定义的 Kubernetes Pod。服务描述文件位于Chapter10/01-istio-gateway.yaml。使用以下命令应用配置:

% kubectl apply -f Chapter10/02-envoy-proxy.yaml -n chapter10vm
  1. 接下来,我们将在 Kubernetes 集群中部署envoydummy应用的 v1 版本:

    $ kubectl create ns chapter10 --context="${CTX_CLUSTER1}"
    $ kubectl label namespace chapter10 istio-injection=enabled --context="${CTX_CLUSTER1}"
    $ kubectl create configmap envoy-dummy --from-file=Chapter3/envoy-config-1.yaml -n chapter10 --context="${CTX_CLUSTER1}"
    $ kubectl create -f "Chapter10/01-envoy-proxy.yaml" --namespace=chapter10 --context="${CTX_CLUSTER1}"
    $ kubectl apply -f Chapter10/01-istio-gateway.yaml" -n chapter10 --context="${CTX_CLUSTER2}"
    

注意01-istio-gateway.yaml中的route配置:

    route:
    - destination:
        host: envoydummy.chapter10.svc.cluster.local
      weight: 50
    - destination:
        host: envoydummy.chapter10vm.svc.cluster.local
      weight: 50

我们将一半的流量路由到envoydummy.chapter10vm.svc.cluster.local,它代表在虚拟机上运行的应用,另一半路由到envoydummy.chapter10.svc.cluster.local,它代表在 Kubernetes 集群中运行的应用。

我们已经配置好了将虚拟机工作负载与网格集成的所有步骤。要测试虚拟机的连接性和 DNS 解析,请从虚拟机中运行以下命令:

$ curl envoydummy.chapter10.svc:80
Bootstrap Service Mesh Implementation with Istio

这表明虚拟机已经识别出服务网格中暴露的端点。你也可以反向操作,从本书 GitHub 仓库中utilities文件夹描述的 curl Pod 所在的 Kubernetes 集群进行测试,请确保它是网格的一部分,而不仅仅是一个在 Kubernetes 上运行的 Pod。

现在,从 Istio Ingress 网关进行测试:

% for i in {1..10}; do curl -Hhost:mockshop.com -s "http://34.87.194.86:80";echo '\n'; done
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
Bootstrap Service Mesh Implementation with Istio
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
Bootstrap Service Mesh Implementation with Istio
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
Bootstrap Service Mesh m bbhgc  Mesh Implementation with Istio----------V2
Bootstrap Service Mesh Implementation with Istio
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
Bootstrap Service Mesh Implementation with Istio

现在,让我们也来看一下 Kiali 仪表盘,看看图表是什么样的。请根据第七章中的说明安装 Kiali。

图 10.7 – Kiali 仪表盘显示流量分配给 VM 工作负载

图 10.7 – Kiali 仪表盘显示流量分配给 VM 工作负载

从前面的图中,你可以看到 chapter10vm 命名空间中的 WorkloadEntry 被表示为另一个 Pod,就像 chapter10 命名空间中的 envoydummyv1 一样。

现在设置已经完成,让我们总结一下本章所学内容。

总结

虚拟机(VM)是现代架构中的重要组成部分,并且与容器一起,预计会在可预见的未来继续存在。借助 Istio,你可以将运行在虚拟机上的传统工作负载集成到 Istio 服务网格中,并利用 Istio 提供的流量管理和安全性等所有优势。Istio 对虚拟机的支持使得可以纳入遗留应用程序以及那些由于某些约束无法在容器上运行的应用程序。

阅读完本章后,你应该能够为混合架构创建网格。你现在可以在虚拟机上安装 Istio,并将工作负载与网格以及基于 Kubernetes 的工作负载集成。为了巩固本章中的概念,练习创建多个虚拟机,并在不同版本的 envoydummy 应用程序上实现流量管理,通过虚拟服务和目标规则进行流量控制。

在下一章,我们将学习各种故障排除策略和技术来管理 Istio。

第十一章:排除故障与操作 Istio

部署微服务涉及许多活动组件,包括应用程序、底层的 Kubernetes 平台,以及由 Istio 提供的应用程序网络。网格在某些情况下表现异常并不罕见。Istio 在早期以复杂且难以排查故障而臭名昭著。Istio 社区非常重视这种看法,并一直在致力于简化其安装和 Day-2 操作,使其在生产环境中的部署更加简便和可靠。

在本章中,我们将讨论在操作 Istio 时可能遇到的常见问题,以及如何将它们与其他问题区分开来并加以隔离。然后,我们将学习如何在识别出问题后排除故障。我们还将探索部署和操作 Istio 的各种最佳实践,以及如何自动化实施最佳实践。

简而言之,本章将涵盖以下内容:

  • 理解 Istio 组件之间的交互

  • 检查和分析 Istio 配置

  • 使用访问日志排除故障

  • 使用调试日志排除故障

  • 调试 Istio 代理

  • 理解 Istio 的最佳实践

  • 使用 OPA Gatekeeper 自动化最佳实践

理解 Istio 组件之间的交互

在排除服务网格问题时,网格的异常行为很可能是由以下潜在问题之一引起的:

  • 无效的控制平面配置

  • 无效的数据平面配置

  • 异常的数据平面

在接下来的章节中,我们将探索如何借助 Istio 提供的各种诊断工具诊断任何异常行为的根本原因。但首先,让我们看看 Istio 中 istiod、数据平面和其他组件之间的各种交互。

探索 Istiod 端口

Istiod 暴露了多个端口,其中一些可以用于故障排除。在本节中,我们将介绍这些端口,了解它们的功能以及如何帮助排查故障。

让我们从查看这些端口开始:

  • 443 端口会被转发到 15017 端口,我们在设置主远程集群时已经看到过它的实际应用。

  • 15014 端口:该端口由 Prometheus 用于抓取控制平面指标。你可以使用以下命令查看该指标:

    % kubectl -n istio-system port-forward deploy/istiod 15014 &
    [1] 68745
    Forwarding from 127.0.0.1:15014 -> 15014
    

然后,你可以从 15014 端口获取指标:

% curl http://localhost:15014/metrics
Handling connection for 15014
# HELP citadel_server_csr_count The number of CSRs received by Citadel server.
# TYPE citadel_server_csr_count counter
citadel_server_csr_count 6
# HELP citadel_server_root_cert_expiry_timestamp The unix timestamp, in seconds, when Citadel root cert will expire. A negative time indicates the cert is expired.
# TYPE citadel_server_root_cert_expiry_timestamp gauge
citadel_server_root_cert_expiry_timestamp 1.986355163e+09
……….
  • 15010 端口是不安全的,而 15012 端口是安全的,因此可以用于生产环境。

  • istiod 实例。该端口用于通过网格内的 REST API 调用或仪表板访问 ControlZ 接口,可以使用以下命令进行访问:

    % istioctl dashboard controlz deployment/istiod.istio-system
    

以下是 ControlZ 接口的截图:

图 11.1 – Istio ControlZ 接口

图 11.1 – Istio ControlZ 接口

ControlZ 接口可用于检查日志范围、环境变量等。该接口也可以用来更改日志级别。

本节中,我们将了解 istiod 暴露的各种端口。接下来我们将阅读有关 Istio 数据平面暴露的端口。

探索 Envoy 端口

Envoy 作为 Istio 的数据平面,暴露了多个端口与 Istio 控制平面和可观察性工具进行交互。我们来看一下这些端口,它们的作用以及它们如何帮助故障排除:

  • 端口 15000:这是 Envoy 管理界面,可用于检查和查询 Envoy 配置。我们将在下一节中详细阅读此端口,检查和分析 Istio 配置

  • 端口 15001:此端口用于接收来自应用程序 Pods 的所有出站流量。

  • 端口 15004:此端口可用于调试数据平面配置。

  • 端口 15006:所有来自网格内部的入站应用流量都会被路由到此端口。

  • istio-agent,以及应用程序,随后由 Prometheus 抓取。

  • 端口 15021:此端口暴露用于执行数据平面健康检查。

  • 端口 15053:此端口用于提供 DNS 代理服务。

  • 端口 15090:此端口提供 Envoy 的遥测信息。

在下一节中,我们将探讨如何分析和检查 Istio 配置。

检查和分析 Istio 配置

在调试 Istio 数据平面时,检查 Istio 控制平面和数据平面之间是否存在配置不匹配非常有用。在处理多集群网格时,最好首先检查控制平面和数据平面之间的连接性;如果你的 Pod 支持 curl,可以使用以下命令检查二者之间的连接性:

$ kubectl exec -it curl -c curl -n chapter11 -- curl  istiod.istio-system.svc.cluster.local:15014/version
1.16.0-8f2e2dc5d57f6f1f7a453e03ec96ca72b2205783-Clean

要检查配置,第一个检查点是使用istioctl proxy-status命令,查看集群、监听器、路由和端点配置在 istiod 和 istio-proxy 之间的同步状态。

你可以使用以下命令检查整个集群的配置状态:

% istioctl proxy-status
NAME       CLUSTER       CDS       LDS       EDS        RDS       ECDS       ISTIOD              VERSION
curl.chapter11
Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED        NOT SENT     istiod-58c6454c57-9nt4r     1.16.0
envoydummy.chapter11
Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED        NOT SENT     istiod-58c6454c57-9nt4r     1.16.0
istio-egressgateway-5bdd756dfd-bjqrg.istio-system      Kubernetes     SYNCED     SYNCED     SYNCED     NOT SENT     NOT SENT     istiod-58c6454c57-9nt4r     1.16.0
istio-ingressgateway-67f7b5f88d-xx5fb.istio-system
Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED        NOT SENT     istiod-58c6454c57-9nt4r     1.16.0

以下是同步状态的可能值:

  • SYNCED:Envoy 已经拥有最新的配置。

  • NOT SENT:istiod 尚未向 Envoy 发送任何配置;在大多数情况下,原因是 istiod 没有配置需要发送。在此示例中,Istio Egress Gateway 的状态为 NOT SENT,因为没有需要同步的路由信息。

  • STALE:Envoy 没有最新的配置,表示 Envoy 和 istiod 之间存在网络问题。

你可以使用以下命令检查工作负载的状态:

% istioctl proxy-status envoydummy.chapter11
Clusters Match
Listeners Match
Routes Match (RDS last loaded at Sat, 17 Dec 2022 11:53:31 AEDT)Access Logs

如果被调查的 Pod 支持 curl,那么你可以通过以下命令从 Envoy 的管理界面(暴露在端口 15000 上)抓取配置,来执行 istio-proxy 配置的转储:

% kubectl exec curl -c curl -n chapter11 -- curl localhost:15000/config_dump > config_dump.json

你可以通过使用以下 istioctl proxy-config 命令,选择性地转储监听器、集群、路由和端点的配置:

% istioctl proxy-config endpoints envoydummy.chapter11 -o json > endpoints-envoydummy.json
% istioctl proxy-config routes envoydummy.chapter11 -o json > routes-envoydummy.json
% istioctl proxy-config listener envoydummy.chapter11 -o json > listener-envoydummy.json
% istioctl proxy-config cluster envoydummy.chapter11 -o json > cluster-envoydummy.json

你可以从 istiod 的角度获取 Envoy 配置,并将其与从 Envoy 管理界面获取的配置进行比较:

% kubectl exec istiod-58c6454c57-gj6cw -n istio-system -- curl 'localhost:8080/debug/config_dump?proxyID=curl.chapter11' | jq . > Chapter11/config-from-istiod-for-curl.json

你可以通过以下命令,从 Web 浏览器中检查 Pod 中的 istio-proxy 配置:

$ kubectl port-forward envoydummy -n chapter11 15000

现在,你可以通过浏览器访问 Envoy 仪表盘,如下图所示:

图 11.2 – Envoy 仪表盘

图 11.2 – Envoy 仪表盘

Envoy 仪表盘是查看值的一个好选择,但在更改配置参数时需要谨慎,因为通过此仪表盘,你将更改数据平面配置,而这超出了 istiod 的控制范围。

在写这本书时,istioctl describe是一个实验性功能。它用于描述一个 Pod,如果该 Pod 满足成为服务网格一部分的所有要求,那么此命令(对 Pod 运行时)还可以告诉你该 Pod 中的 istio-proxy 是否已启动,或者该 Pod 是否属于服务网格的一部分。它会输出任何警告和建议,以便更好地将 Pod 集成到服务网格中。

以下是对 envoydummy Pod 运行 istioctl describe 命令的示例:

% istioctl x describe pod envoydummy -n chapter11
Pod: envoydummy
   Pod Revision: default
   Pod Ports: 10000 (envoyproxy), 15090 (istio-proxy)
Suggestion: add 'app' label to pod for Istio telemetry.
--------------------
Service: envoydummy
   Port:  80/auto-detect targets pod port 10000
--------------------
Effective PeerAuthentication:
   Workload mTLS mode: PERMISSIVE

在输出中,你可以看到它建议将 app 标签应用到该 Pod 上以启用 Istio 测量。Istio 推荐明确地将 appversion 标签添加到服务网格中的工作负载上。这些标签为 Istio 收集的指标和遥测数据添加了上下文信息。输出还描述了其他一些重要信息,比如服务暴露在端口 80 上,端点在端口 10000 上,istio-proxy Pod 暴露在端口 15090 上,以及 mTLS 模式 是宽松的。它还描述并警告了有关目标规则和虚拟服务的任何问题。

以下命令是另一个示例,展示了当错误的虚拟服务配置应用到 envoydummy 时的情况。正确的配置可以在 Chapter11/04-istio-gateway-chaos.yaml 中找到:

% kubectl apply -f Chapter11/04-istio-gateway-chaos.yaml
gateway.networking.istio.io/chapter11-gateway configured
virtualservice.networking.istio.io/mockshop configured
destinationrule.networking.istio.io/envoydummy configured
% istioctl x describe pod envoydummy -n chapter11
Pod: envoydummy
   Pod Revision: default
   Pod Ports: 10000 (envoyproxy), 15090 (istio-proxy)
--------------------
Service: envoydummy
   Port:  80/auto-detect targets pod port 10000
DestinationRule: envoydummy for "envoydummy"
   Matching subsets: v1
      (Non-matching subsets v2)
   No Traffic Policy
--------------------
Effective PeerAuthentication:
   Workload mTLS mode: PERMISSIVE
Exposed on Ingress Gateway http://192.168.49.2
VirtualService: mockshop
   WARNING: No destinations match pod subsets (checked 1 HTTP routes)
      Warning: Route to UNKNOWN subset v3; check DestinationRule envoydummy

前面片段底部的警告清楚地描述了虚拟服务将流量路由到 UNKNOWN subset v3。要解决这个问题,你需要配置虚拟服务以纠正目标规则中定义的子集,或者在目标规则中添加 v3 子集。正确的配置可以在 Chapter11/04-istio-gateway.yaml 中找到。

另一个用于检查和检测服务网格中任何配置错误的诊断工具是 istioctl analyze。它可以对整个集群运行,也可以在将配置应用到服务网格之前对任何配置运行:

% istioctl analyze Chapter11/04-istio-gateway-chaos.yaml -n chapter11
Error [IST0101] (VirtualService chapter11/mockshop Chapter11/04-istio-gateway-chaos.yaml:35) Referenced host+subset in destinationrule not found: "envoydummy+v3"
Info [IST0118] (Service chapter11/envoydummy) Port name  (port: 80, targetPort: 10000) doesn't follow the naming convention of Istio port.
Error: Analyzers found issues when analyzing namespace: chapter11.
See https://istio.io/v1.16/docs/reference/config/analysis for more information about causes and resolutions.

在前面的示例中,istioctl analyze通过分析配置文件指出了错误。这对于在将配置应用到服务网格之前验证任何错误配置非常有用。在接下来的部分,我们将学习如何使用 Envoy 访问日志来排除错误。

使用访问日志排查错误

istio-proxy 容器。访问日志是流量访问 Envoy 的记录,可以与 Ingress 和 Egress 网关的访问记录以及网格中所有其他上游和下游工作负载的访问记录交织在一起,以跟踪请求的生命周期。

默认情况下,访问日志是关闭的,除非你使用演示配置文件安装了 Istio。你可以通过检查 Istio 配置映射来查看是否启用了访问日志:

% kubectl get cm/istio -n istio-system -o json | jq .data.mesh
"accessLogFile: \"\"\ndefaultConfig:\n  discoveryAddress: istiod.istio-system.svc:15012\n  proxyMetadata: {}\n  tracing:\n    zipkin:\n      address: zipkin.istio-system:9411\nenablePrometheusMerge: true\nextensionProviders:\n- envoyOtelAls:\n    port: 4317\n    service: opentelemetry-collector.istio-system.svc.cluster.local\n  name: otel\nrootNamespace: istio-system\ntrustDomain: cluster.local"

可以通过以下命令启用访问日志:

% istioctl install --set profile=demo --set meshConfig.accessLogFile="/dev/stdout"

根据你的需求和系统性能要求,你可以决定是否启用访问日志。如果启用了访问日志且想要禁用它,可以通过将 accessLogFile 参数设置为空值来实现:

% istioctl install --set profile=demo --set meshConfig.accessLogFile=""

访问日志也可以在工作负载或命名空间级别启用。假设你已全局关闭访问日志,我们可以使用 Telemetry 资源有选择性地为 envoydummy 工作负载打开访问日志:

apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: envoy-dummy-accesslog-overwrite
  namespace: chapter11
spec:
  selector:
    matchLabels:
      service.istio.io/canonical-name: envoydummy
  accessLogging:
  - providers:
    - name: envoy
  - disabled: false

该配置将为 envoydummy Pod 打开访问日志。配置文件可在 GitHub 上的 Chapter11/03-telemetry-01.yaml 中找到:

  1. 使用以下命令应用配置:

    $ kubectl apply -f Chapter11/03-telemetry-01.yaml
    telemetry.telemetry.istio.io/envoy-dummy-accesslog-overwrite configured
    
  2. 现在,你可以从 curl Pod 向 envoydummy 发起一个 curl 请求,命令如下:

    % kubectl exec -it curl -n chapter11 -c curl -- curl envoydummy.chapter11
    Bootstrap Service Mesh Implementation with Istio
    
  3. 接下来,如果你检查 curlenvoydummy Pods 中的 istio-proxy 访问日志,你会发现 curl Pod 中没有访问日志,而 envoydummy Pod 中有访问日志:

    [2022-12-15T00:48:54.972Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 48 0 0 "-" "curl/7.86.0-DEV" "2d4eec8a-5c17-9e2c-8699-27a341c21b8b" "envoydummy.chapter11" "172.17.0.9:10000" inbound|10000|| 127.0.0.6:49977 172.17.0.9:10000 172.17.0.8:56294 outbound_.80_._.envoydummy.chapter11.svc.cluster.local default
    

curl Pod 中没有访问日志的原因是我们全局关闭了访问日志,但通过 Telemetry 资源有选择性地为 envoydummy Pod 打开了访问日志。

你可以阅读更多关于如何使用 Telemetry 配置 accessLogging 的信息,网址是 istio.io/latest/docs/reference/config/telemetry/#AccessLogging

访问日志的默认编码是按照以下规范格式化的字符串:

[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS% %CONNECTION_TERMINATION_DETAILS% \"%UPSTREAM_TRANSPORT_FAILURE_REASON%\" %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\" \"%REQ(USER-AGENT)%\" \"%REQ(X-REQUEST-ID)%\" \"%REQ(:AUTHORITY)%\" \"%UPSTREAM_HOST%\" %UPSTREAM_CLUSTER% %UPSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_REMOTE_ADDRESS% %REQUESTED_SERVER_NAME% %ROUTE_NAME%\n

每个字段的详细定义可以在 www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage 上找到。

访问日志也可以配置为以 JSON 格式显示,可以通过将 accessLogEncoding 设置为 JSON 来实现:

% istioctl install --set profile=demo --set meshConfig.accessLogFile="" --set meshConfig.accessLogFormat="" --set meshConfig.accessLogEncoding="JSON"

设置后,访问日志将以 JSON 格式显示,我已经简化了它以便于阅读:

{
   "duration":0,
   "start_time":"2022-12-15T01:03:02.725Z",
   "bytes_received":0,
   "authority":"envoydummy.chapter11",
   "upstream_transport_failure_reason":null,
   "upstream_cluster":"inbound|10000||",
   "x_forwarded_for":null,
   "response_code_details":"via_upstream",
   "upstream_host":"172.17.0.9:10000",
   "user_agent":"curl/7.86.0-DEV",
   "request_id":"a56200f2-da0c-9396-a168-8dfddf8b623f",
   "response_code":200,
   "route_name":"default",
   "method":"GET",
   "downstream_remote_address":"172.17.0.8:45378",
   "upstream_service_time":"0",
   "requested_server_name":"outbound_.80_._.envoydummy.chapter11.svc.cluster.local",
   "protocol":"HTTP/1.1",
   "path":"/",
   "bytes_sent":48,
   "downstream_local_address":"172.17.0.9:10000",
   "connection_termination_details":null,
   "response_flags":"-",
   "upstream_local_address":"127.0.0.6:42313"
}

在访问日志中,有一个字段叫做 response_flags(如前面的代码片段所示),这是在通过访问日志排查故障时非常有用的信息。

接下来,我们将通过在 envoydummy Pod 中注入一些错误来了解响应标志:

  1. 首先,通过以下命令为 curl Pod 打开访问日志:

    % kubectl apply -f Chapter11/03-telemetry-02.yaml
    telemetry.telemetry.istio.io/curl-accesslog-overwrite created
    
  2. 然后,删除 envoydummy Pod,但保留 envoydummy 服务:

    % kubectl delete po envoydummy -n chapter11
    pod "envoydummy" deleted
    
  3. 现在,服务已经损坏,你将无法使用 curl

    % kubectl exec -it curl -n chapter11 -c curl -- curl envoydummy.chapter11
    no healthy upstream
    
  4. 检查 curl Pod 的访问日志:

    % kubectl logs -f curl -n chapter11 -c istio-proxy | grep response_flag
    {"path":"/","response_code":503,"method":"GET","upstream_cluster":"outbound|80||envoydummy.chapter11.svc.cluster.local","user_agent":"curl/7.86.0-DEV","connection_termination_details":null,"authority":"envoydummy.chapter11","x_forwarded_for":null,"upstream_transport_failure_reason":null,"downstream_local_address":"10.98.203.175:80","bytes_received":0,"requested_server_name":null,"response_code_details":"no_healthy_upstream","upstream_service_time":null,"request_id":"4b39f4ca-ffe3-9c6a-a202-0650b0eea8ef","route_name":"default","upstream_local_address":null,"response_flags":"UH","protocol":"HTTP/1.1","start_time":"2022-12-15T03:49:38.504Z","duration":0,"upstream_host":null,"downstream_remote_address":"172.17.0.8:52180","bytes_sent":19}
    

response_flags 的值为 UH,这意味着上游集群中没有健康的上游主机。response_flag 的可能值显示在以下表格中,并参考了 www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage

Name 描述
UH 上游集群中没有健康的上游主机,此外还返回了 503 响应码
UF 上游连接失败,此外还返回了 503 响应码
UO 上游溢出(断路器)发生,此外还返回了 503 响应码
NR 没有为给定请求配置路由,此外还返回了 404 响应码,或下游连接没有匹配的过滤链
URX 请求被拒绝,因为达到了上游重试限制(HTTP)或最大连接尝试次数(TCP)
NC 未找到上游集群
DT 请求或连接超出了 max_connection_durationmax_downstream_connection_duration

表格 11.1 – HTTP 和 TCP 连接的响应标志值

以下表格描述了 HTTP 连接的响应标志值:

Name 描述
DC 下游连接终止
LH 本地服务健康检查失败请求,此外还返回了 503 响应码
UT 上游请求超时,此外还返回了 504 响应码
LR 连接本地重置,此外还返回了 503 响应码
UR 上游远程重置,此外还返回了 503 响应码
UC 上游连接终止,此外还返回了 503 响应码
DI 请求处理被延迟,延迟时间由故障注入指定
FI 请求被中止,并返回了通过故障注入指定的响应码
RL 请求被 HTTP 速率限制过滤器在本地限速,此外还返回了 429 响应码
UAEX 请求被外部授权服务拒绝
RLSE 请求被拒绝,因为速率限制服务发生错误
IH 请求被拒绝,因为它为严格检查的头部设置了无效的值,此外还返回了 400 响应码
SI 流空闲超时,此外还返回了 408 或 504 响应码
DPE 下游请求发生 HTTP 协议错误
UPE 上游响应发生 HTTP 协议错误
UMSDR 上游请求达到了最大流持续时间
OM 超载管理器终止了请求
DF 请求由于 DNS 解析失败而终止

表格 11.2 – HTTP 连接的响应标志值

响应标志在故障排除访问日志时非常有用,是指示上游系统可能出错的好指标。了解这些信息后,我们可以将焦点转向 Istio 调试日志如何用于故障排除。

使用调试日志排查错误

Istio 组件支持灵活的日志方案,用于调试日志。调试日志级别可以从较高的级别更改为非常冗长的级别,以便获取 Istio 控制平面和数据平面中发生的详细信息。接下来的两节将描述如何更改 Istio 数据平面和控制平面的日志级别。让我们直接开始吧!

更改 Istio 数据平面的调试日志

以下是 Istio 数据平面的各种日志级别——即 Envoy 侧车:

  • trace:最高冗长的日志消息

  • debug:非常冗长的日志消息

  • info:关于 Envoy 执行状态的通知性消息

  • warning/warn:表明可能导致错误事件的问题的事件

  • error:重要的错误事件,可能会损害 Envoy 的某些功能,但不会让 Envoy 完全无法运行

  • critical:严重的错误事件,可能导致 Envoy 停止运行

  • off:不产生任何日志

日志级别可以使用以下命令格式进行更改:

istioctl proxy-config log [<type>/]<name>[.<namespace>] [flags]

这样的命令示例如下:

% istioctl proxy-config log envoydummy.chapter11 -n chapter11 --level debug

这种改变日志级别的方法不需要重启 Pods。如本章前面探索 istiod 端口一节所述,也可以使用 ControlZ 接口来更改日志级别。

更改 Istio 控制平面的日志级别

Istio 控制平面支持以下日志级别:

  • none:不产生任何日志

  • error:只产生错误信息

  • warn:产生警告消息

  • info:在正常情况下产生详细信息

  • debug:产生最多的日志消息

istiod 内部的每个组件根据所记录消息的类型对日志进行分类。这些分类包括 adsadscallanalysisauthnauthorizationcacacheclidefaultinstallerklogmcpmodelpatchprocessingresourcesourcespiffetpathtranslatorutilvalidationvalidationControllerwle

以下是更改不同范围的日志级别时使用的命令示例:

% istioctl analyze --log_output_level validation:debug,validationController:info,ads:debug

在这个示例中,我们将 validation 范围的日志级别更改为 debug,将 validationController 范围更改为 info,将 ads 更改为 debug

% istioctl admin log | grep -E 'ads|validation'
ads                    ads debugging                                debug
adsc                   adsc debugging                               info
validation             CRD validation debugging                     debug
validationController   validation webhook controller                info
validationServer       validation webhook server                    info

你可以使用 istioctl admin log 来获取所有 Istio 组件的日志级别:

% istioctl admin log
ACTIVE    SCOPE      DESCRIPTION               LOG LEVEL
ads                    ads debugging                                debug
adsc                   adsc debugging                               info
analysis               Scope for configuration analysis runtime     info
authn                  authn debugging                              info
authorization          Istio Authorization Policy                   info
ca                     ca client                                    info
controllers            common controller logic                      info
default                Unscoped logging messages.                   info
delta                  delta xds debugging                          info
file                   File client messages                         info
gateway                gateway-api controller                       info
grpcgen                xDS Generator for Proxyless gRPC             info
……

我们刚刚看到了 Envoy,它位于请求路径的request上,但还有另一个关键组件,它不在request路径上,但对 Envoy 的平稳操作非常重要。在接下来的章节中,我们将了解如何调试 Istio 代理中的问题。

调试 Istio 代理

在本节中,我们将学习如何排查由 Istio 代理配置错误导致的数据平面问题。Istio 代理可能由于多种原因未按预期工作;本节将讨论调试和排查此类问题的多种方法。

以下命令可用于检查 Envoy 用来启动并与 istiod 连接的初始引导配置文件:

$ istioctl proxy-config bootstrap envoydummy -n chapter11 -o json >bootstrap-envoydummy.json

引导配置由 Istiod 控制器在通过验证和 Sidecar 注入 Webhook 注入 Sidecar 时提供的信息组成。

我们可以使用以下命令检查由istio-agent为 Envoy 配置的证书和secret

% istioctl proxy-config secret envoydummy -n chapter11
RESOURCE NAME     TYPE           STATUS     VALID CERT     SERIAL NUMBER                               NOT AFTER                NOT BEFORE
default           Cert 
Chain      ACTIVE      true     151990293406794295074718429679 77531899      20XX-12-26T01:02:53Z     20XX-12-25T01:00:53Z
ROOTCA        CA        ACTIVE        true 177195801324177165655021729164749485784     20XX-12-11T05:19:23Z     20XX-12-14T05:19:23Z

你还可以通过在命令中添加-o json来以 JSON 格式显示详细信息。ROOTCA是根证书,default是工作负载证书。在进行多集群配置时,ROOTCA 在不同集群之间必须匹配。

你可以使用以下命令检查证书的值:

% istioctl proxy-config secret envoydummy -n chapter11 -o json | jq '.dynamicActiveSecrets[0].secret.tlsCertificate.certificateChain.inlineBytes' -r | base64 -d | openssl x509 -noout -text

根据配置的网关和目标规则,可能还会有其他 Secrets。在日志中,如果发现 Envoy 处于警告状态,这意味着正确的 Secret 未加载到 Envoy 中。与default证书和ROOTCA相关的问题通常是由于 istio-proxy 与 istiod 之间的连接问题引起的。

要检查 Envoy 是否已成功启动,可以使用以下命令登录到istio-proxy容器:

% kubectl exec -it envoydummy -n chapter11 -c istio-proxy --  pilot-agent wait
2022-12-25T05:29:44.310696Z info Waiting for Envoy proxy to be ready (timeout: 60 seconds)...
2022-12-25T05:29:44.818220Z info Envoy is ready!

在 Sidecar 代理的启动过程中,istio-agent通过 ping http://localhost:15021/healthz/ready来检查 Envoy 的就绪状态。它还在 Pod 的生命周期内使用相同的端点来确定 Envoy 的就绪状态。HTTP 状态码 200 表示 Envoy 已就绪,istio-proxy容器被标记为已初始化。如果istio-proxy容器处于待处理状态并未初始化,这意味着 Envoy 未从 istiod 接收到配置,可能是由于与 istiod 的连接问题或 Envoy 拒绝配置。

以下错误信息表明istio-proxy日志中存在连接问题,可能是由于网络问题或 istiod 不可用:

20XX-12-25T05:58:02.225208Z     warning envoy config    StreamAggregatedResources gRPC config stream to xds-grpc closed since 49s ago: 14, connection error: desc = "transport: Error while dialing dial tcp 10.107.188.192:15012: connect: connection refused"

如果 Envoy 能够连接到 istiod,那么你会在日志文件中找到类似以下的消息:

20XX-12-25T06:00:08.082523Z     info    xdsproxy        connected to upstream XDS server: istiod.istio-system.svc:15012

在本节中,我们深入分析了 Istio 调试日志,并查看了如何使用 Envoy、istio-agent和 Istio 控制平面的调试日志进行故障排除。在下一节中,我们将阅读有关 Istio 最佳实践的内容,了解如何安全地管理和高效地操作 Istio。

了解 Istio 的最佳实践

在操作 Service Mesh 时,建议假设安全威胁不仅会来自组织安全边界之外,还可能来自安全边界内部。你应该始终假设网络不是坚不可摧的,并创建能够保护资产的安全控制,即使网络边界被突破。在本节中,我们将讨论在实施 Service Mesh 时需要关注的各种攻击向量。

检查控制平面的攻击向量

以下列表显示了针对控制平面发起攻击的常见策略:

  • 故意造成配置问题,使控制平面发生故障,从而使服务网格无法正常工作,进而影响网格管理的业务关键应用。这也可能是针对 Ingress 或其他应用程序的攻击的前奏。

  • 获取特权访问权限,能够执行控制平面和数据平面的攻击。通过获得特权访问,攻击者可以修改安全策略,从而利用资产进行攻击。

  • 垃圾数据窃取:窃听控制平面数据,或篡改并伪造数据平面与控制平面之间的通信。

检查数据平面的攻击路径。

以下是对数据平面发起攻击的常见策略:

  • 窃听服务间通信以窃取敏感数据,并将其发送给攻击者。

  • 假冒为网格中的受信任服务;攻击者可以执行服务间的中间人攻击。通过使用中间人攻击,攻击者可以窃取敏感数据或篡改服务之间的通信,以便为攻击者创造有利结果。

  • 操控应用程序进行僵尸网络攻击。

另一个容易受到攻击的组件是托管 istio 的基础设施,可以是 Kubernetes 集群、虚拟机或任何托管 istio 的底层堆栈组件。我们不打算深入讨论如何保护 Kubernetes,因为有各种关于如何安全配置 Kubernetes 集群的书籍;其中一本是 Kaizhe Huang 和 Pranjal Jumde 所著的Learn Kubernetes Security,由 Packt 出版。其他最佳实践包括保护服务网格,我们将在下一节中讨论。

保护服务网格

以下是保护服务网格的一些最佳实践:

  • 部署Web 应用防火墙WAF)来保护入口流量。WAF 实施安全控制措施,包括开放 Web 应用安全项目OWASP)识别的威胁;你可以在owasp.org/阅读有关 OWASP 的信息。大多数云提供商提供 WAF 作为其云服务的一部分;例如 AWS 的AWS WAF、Google Cloud 的Cloud Armor和 Azure 的Azure Web 应用防火墙。还有其他供应商,如 Cloudflare、Akamai、Imperva 和 AppTrana,提供 SaaS 形式的 WAF,而像 Fortinet 和 Citrix 这样的供应商也提供自托管 WAF 服务。WAF 是你防御的第一道防线,它会处理许多指向网格入口的攻击路径。

  • 定义策略来控制从外部网格到网格内部服务的访问。入口访问控制策略对于禁止未授权访问服务非常重要。每个入口应明确定义,并且应具有关联的身份验证和授权策略,以验证外部请求是否被授权访问通过入口网关暴露的服务。然而,所有的入口访问应该通过入口网关进行,并且每个入口应通过虚拟服务及其关联的目标规则进行路由。

  • 所有出口系统应为已知并定义,且不应允许向未知的出口点发送流量。安全策略应强制出口流量的 TLS 起源,并且所有出口流量应通过出口网关进行。授权策略应用于控制哪些工作负载被允许发送出口流量,并且如果允许,所有出口端点应由安全管理员知晓并批准。出口安全策略还帮助防止数据泄露;通过出口策略,您可以仅控制流量发送到已知的出口点,从而阻止已渗透系统的攻击者将数据发送到攻击者的系统。这还可以防止网格中的应用程序参与任何僵尸网络攻击。

  • 网格中的所有服务应通过 mTLS 进行通信,并应具有相关的身份验证和授权策略。默认情况下,所有服务间通信应被拒绝,除非通过授权策略授权,并且任何服务间通信应通过明确定义的服务身份进行显式启用。

  • 当服务间通信代表终端用户或系统进行时,除 mTLS 外,所有此类通信应使用 JWT。JWT 作为凭证,证明服务请求是明确代表终端用户发出的;调用服务需要提供一个 JWT 作为凭证,以识别终端用户,并结合身份验证和授权策略来确定可以访问的服务以及授予的访问级别。这有助于防止任何受损的应用程序进行数据泄露或服务利用。

  • 如果使用任何外部长期有效的身份验证令牌来验证任何主体,无论是终端用户还是系统,那么此类令牌应被短期令牌替换。令牌替换应在入口处进行,然后在整个网格中使用短期令牌。这样做有助于防止攻击者窃取令牌并利用其进行未授权访问。同时,外部长期攻击可能会附带许多更广泛的作用域,受损的应用程序可能会滥用这些作用域,而短期令牌具有受限作用域,有助于避免令牌滥用。

  • 在应用网格或安全规则的例外时,应谨慎定义例外策略。例如,如果您希望启用工作负载 A 在网格中允许来自另一个工作负载 B 的 HTTP 流量,那么您应明确规定允许来自工作负载 B 的 HTTP 流量的例外,而不是明确允许所有 HTTP 流量,且所有其他流量应仅限于 HTTPS。

  • 必须限制并控制对 istiod 的访问。防火墙规则应限制对控制平面的访问,仅允许已知来源。该规则应同时适应人工操作员和数据平面对控制平面的访问,无论是在单集群还是多集群配置中。

  • 所有由服务网格管理的工作负载应仅通过基于角色的访问控制RBAC)策略在 Kubernetes 环境中进行管理,对于非 Kubernetes 工作负载,则通过用户组进行管理。Kubernetes 管理员应仔细定义应用程序用户和网格管理员的 RBAC 策略,并仅允许后者对网格进行任何更改。网格操作员应根据其被授权执行的操作进一步分类。例如,有权限在命名空间中部署应用程序的网格用户不应有权访问其他命名空间。

  • 限制用户可以访问的仓库,以便从中拉取镜像进行部署。

在本节中,我们了解了 istio 的最佳实践;除此之外,您还应该访问 istio 官网,阅读有关最佳实践的内容,网址为istio.io/latest/docs/ops/best-practices/。该网站会根据 istio 社区的反馈定期更新。

即便在所有控制措施到位的情况下,网格操作员也可能会错误地配置服务网格,导致意外结果甚至安全漏洞。一个解决方案是强制执行严格的审查和治理流程来进行更改,但手动执行这一过程既昂贵又耗时,且容易出错,通常也很麻烦。在接下来的部分,我们将了解OPA Gatekeeper以及如何使用它来自动化最佳实践政策。

使用 OPA Gatekeeper 自动化最佳实践

为了避免人为错误,你可以将最佳实践和约束定义为策略的形式,并在集群中创建、删除或更新资源时自动强制执行这些策略。自动化的政策执行确保了一致性,并遵循最佳实践,同时不影响敏捷性和部署速度。一种这样的软件是Open Policy AgentOPA)Gatekeeper,它是一个基于自定义资源定义CRD)的准入控制器,执行由 OPA 执行的策略。OPA Gatekeeper 使得强制执行保护措施成为可能;任何不符合保护措施的 istio 配置都会被自动拒绝。它还允许集群管理员审核不符合最佳实践的资源。通过以下步骤,我们将设置 OPA Gatekeeper,并配置一些用于 istio 的最佳实践强制执行。让我们开始吧!

  1. 使用以下命令安装 Gatekeeper:

    % kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml
    
  2. 配置 Gatekeeper 同步命名空间、Pods、Services、istio CRD 网关、虚拟服务、目标规则、策略和服务角色绑定到其缓存中。我们已经在以下文件中定义了这一点,文件路径为Chapter11/05-GatekeeperConfig.yaml

    apiVersion: config.gatekeeper.sh/v1alpha1
    kind: Config
    metadata:
      name: config
      namespace: gatekeeper-system
    spec:
      sync:
        syncOnly:
          - group: ""
            version: "v1"
            kind: "Namespace"
          - group: ""
            version: "v1"
            kind: "Pod"
          - group: ""
            version: "v1"
            kind: "Service"
          - group: "networking.istio.io"
            version: "v1alpha3"
            kind: "Gateway"
          - group: "networking.istio.io"
            version: "v1alpha3"
            kind: "VirtualService"
          - group: "networking.istio.io"
            version: "v1alpha3"
            kind: "DestinationRule"
          - group: "authentication.istio.io"
            version: "v1alpha1"
            kind: "Policy"
          - group: "rbac.istio.io"
            version: "v1alpha1"
            kind: "ServiceRoleBinding"
    
  3. 现在,使用以下命令应用配置:

    % kubectl apply -f Chapter11/05-GatekeeperConfig.yaml
    config.config.gatekeeper.sh/config created
    

这完成了 Gatekeeper 的安装;接下来,我们将配置约束。

我们将从确保 Pod 命名约定符合 istio 最佳实践的简单策略开始。正如在检查和分析 istio 配置部分中讨论的,istio 建议向每个 Pod 部署添加显式的appversion标签。appversion标签为 istio 收集的指标和遥测数据添加了上下文信息。

执行以下步骤来强制执行这一治理规则,以便任何不遵守该规则的部署都会被自动拒绝:

  1. 首先,我们必须定义ConstraintTemplate。在约束模板中,我们执行以下操作:

    • 描述将用于强制执行约束的策略

    • 描述约束的模式

重要说明

Gatekeeper 约束使用一种专门的、高层次的声明式语言Rego定义。Rego 特别用于编写 OPA 策略;你可以在 https://www.openpolicyagent.org/docs/latest/#rego 查看更多关于 Rego 的信息。

以下步骤定义了约束模板和模式:

  1. 我们将使用 OPA CRD 声明一个约束模板:
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: istiorequiredlabels
  annotations:
    description: Requires all resources to contain a specified label with a value
      matching a provided regular expression.
  1. 接下来,我们将定义模式:
spec:
  crd:
    spec:
      names:
        kind: istiorequiredlabels
      validation:
        # Schema for the `parameters` field
        openAPIV3Schema:
          properties:
            message:
              type: string
            labels:
              type: array
              items:
                type: object
                properties:
                  key:
                    type: string
  1. 最后,我们将定义rego以检查标签并检测任何违规:
 targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package istiorequiredlabels
        get_message(parameters, _default) = msg {
          not parameters.message
          msg := _default
        }
        get_message(parameters, _default) = msg {
          msg := parameters.message
        }
        violation[{"msg": msg, "details": {"missing_labels": missing}}] {
          provided := {label | input.review.object.metadata.labels[label]}
          required := {label | label := input.parameters.labels[_].key}
          missing := required - provided
          count(missing) > 0
          def_msg := sprintf("you must provide labels: %v", [missing])
          msg := get_message(input.parameters, def_msg)
        }

配置文件位于Chapter11/gatekeeper/01-istiopodlabelconstraint_template.yaml。使用以下命令应用配置:

% kubectl apply -f Chapter11/gatekeeper/01-istiopodlabelconstraint_template.yaml
constrainttemplate.templates.gatekeeper.sh/istiorequiredlabels created
  1. 接下来,我们将定义用于通知 Gatekeeper 我们希望强制执行名为istiorequiredlabels的约束模板的constraints,该模板根据在mesh-pods-must-have-app-and-version约束中定义的配置进行定义。示例文件可在Chapter11/gatekeeper/01-istiopodlabelconstraint.yaml中找到:

    apiVersion: constraints.gatekeeper.sh/v1beta1
    kind: istiorequiredlabels
    metadata:
      name: mesh-pods-must-have-app-and-version
    spec:
      enforcementAction: deny
      match:
        kinds:
          - apiGroups: [""]
            kinds: ["Pod"]
        namespaceSelector:
          matchExpressions:
            - key: istio-injection
              operator: In
              values: ["enabled"]
      parameters:
        message: "All pods must have an `app and version` label"
        labels:
          - key: app
          - key: version
    

对于约束配置,我们定义了以下字段:

  • enforcementAction:此字段定义处理约束违规的操作。该字段设置为deny,这是默认行为;任何违反此约束的资源创建或更新将根据强制执行的操作进行处理。其他支持的enforcementAction值包括dryrunwarn。在向运行中的集群推出新约束时,dryrun功能可以帮助在不强制执行的情况下测试它们。warn强制执行操作提供与dryrun相同的好处,例如在不强制执行的情况下测试约束。此外,它还提供了即时反馈,说明为何该约束会被拒绝。

  • match:此字段定义了选择标准,用于识别约束将应用于哪些对象。在配置中,我们定义了约束应应用于在具有istio-injection标签并且值为enabled的命名空间中部署的pod资源。通过这样做,我们可以选择性地将约束应用于属于数据平面网格的命名空间。

  1. 最后,我们定义了当约束被违反时显示的消息。应用以下约束:

    % kubectl apply -f Chapter11/gatekeeper/01-istiopodlabelconstraint.yaml
    istiorequiredlabels.constraints.gatekeeper.sh/mesh-pods-must-have-app-and-version created
    
  2. 作为测试,我们将部署缺少标签的envoydummy Pod:

    % kubectl apply -f Chapter11/06-envoy-proxy-chaos.yaml -n chapter11
    service/envoydummy created
    Error from server (Forbidden): error when creating "Chapter11/01-envoy-proxy.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [all-must-have-owner] All pods must have an `app` label
    

Gatekeeper 已经验证了该部署并拒绝了它,因为它违反了约束,缺少appversion标签。请修复标签并重新部署,以检查是否可以使用正确的标签成功部署。

我希望这能给你一些关于如何使用 Gatekeeper 自动化一些最佳实践的想法。我们将进行另一个示例练习,以帮助你进一步了解如何使用 Gatekeeper。一开始,定义约束可能看起来是一个艰巨的任务,但一旦你积累了使用rego的经验,你会发现它简单易用。

作为另一个示例,让我们编写另一个约束,以强制执行端口命名约定。根据在istio.io/v1.0/docs/setup/kubernetes/spec-requirements/中描述的 Istio 最佳实践,Service端口必须命名。端口名称必须采用<protocol>[-<suffix>]的形式,其中httphttp2grpcmongoredis<protocol>。如果你希望利用 Istio 的路由功能,这一点非常重要。例如,name: http2-envoyname: http是有效的端口名称,但name: http2envoy是无效的名称。

约束模板中的rego如下所示:

package istio.allowedistioserviceportname
        get_message(parameters, _default) = msg {
          not parameters.message
          msg := _default
        }
        get_message(parameters, _default) = msg {
          msg := parameters.message
        }
        violation[{"msg": msg, "details": {"missing_prefixes": prefixes}}] {
          service := input.review.object
          port := service.spec.ports[_]
          prefixes := input.parameters.prefixes
          not is_prefixed(port, prefixes)
          def_msg := sprintf("service %v.%v port name missing prefix",
            [service.metadata.name, service.metadata.namespace])
          msg := get_message(input.parameters, def_msg)
        }
        is_prefixed(port, prefixes) {
          prefix := prefixes[_]
          startswith(port.name, prefix)
        }

rego中,我们定义了端口名称应该以前缀开始,如果缺少前缀,则应视为违规。

该约束定义如下:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: AllowedIstioServicePortName
metadata:
  name: port-name-constraint
spec:
  enforcementAction: deny
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Service"]
    namespaceSelector:
      matchExpressions:
        - key: istio-injection
          operator: In
          values: ["enabled"]
  parameters:
    message: "All services declaration must have port name will one of following  prefix http-, http2-, grpc-, mongo-,redis-"
    prefixes: ["http-", "http2-","grpc-","mongo-","redis-"]

在约束中,我们定义了允许的各种前缀,并且当约束被违反时,显示相应的错误信息。

现在,让我们应用约束模板和配置,如下所示:

% kubectl apply -f Chapter11/gatekeeper/02-istioportconstraints_template.yaml
constrainttemplate.templates.gatekeeper.sh/allowedistioserviceportname created
% kubectl apply -f Chapter11/gatekeeper/02-istioportconstraints.yaml
allowedistioserviceportname.constraints.gatekeeper.sh/port-name-constraint configured

应用配置后,我们部署一个名称不正确的envoydummy服务端口。我们将创建一个没有指定任何端口名称的envoydummy服务,具体方法如下代码片段所示:

spec:
  ports:
  - port: 80
    targetPort: 10000
  selector:
    name: envoydummy

文件位于Chapter11/07-envoy-proxy-chaos.yaml。使用以下代码应用配置,并观察错误信息,查看 OPA Gatekeeper 的实际操作:

% kubectl apply -f Chapter11/07-envoy-proxy-chaos.yaml
pod/envoydummy created
Error from server (Forbidden): error when creating "Chapter11/07-envoy-proxy-chaos.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [port-name-constraint] All services declaration must have port name with one of following prefix http-, http2-, grpc-, mongo-, redis-

在响应中,你可以看到由于服务配置中的端口命名错误而导致的错误信息。通过如下方式给端口声明添加名称,可以解决该问题:

spec:
  ports:
  - port: 80
    targetPort: 10000
    name: http-envoy
  selector:
    name: envoydummy

OPA Gatekeeper 是一个强大的工具,用于自动执行服务网格的最佳实践。它弥补了人为操作导致的任何配置错误,减少了保持网格与最佳实践对齐所需的时间和成本。你可以在open-policy-agent.github.io/gatekeeper/website/docs/阅读更多关于 OPA Gatekeeper 的内容,并且在github.com/crcsmnky/gatekeeper-istio中有一些 Gatekeeper 的优秀示例。

卸载 OPA Gatekeeper

要卸载 OPA Gatekeeper,可以使用以下命令:

% kubectl delete -``f raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml

概述

在这一章中,我们学习了各种故障排除技巧以及配置和操作 istio 的最佳实践。到现在为止,你应该已经很好地理解了 istio 暴露的各种端口以及它们如何帮助诊断网格中的任何错误。你还学习了 Envoy 和 istiod 生成的调试信息和访问日志,以及它们如何帮助你找出错误的根本原因。istio 在其诊断工具包中提供了多种非常有用的工具,帮助故障排除和分析服务网格中的问题和错误。

安全性在运行服务网格时至关重要,这也是我们讨论控制平面和数据平面的各种攻击向量的原因。你现在应该已经对可以采取的安全控制措施有了很好的理解。最后,我们阅读了如何使用 OPA Gatekeeper 自动化最佳实践,以捕捉大多数(如果不是全部的话)不合规的配置。你学习了如何设置 OPA Gatekeeper,如何使用 Rego 和约束模板定义约束,以及如何使用它们来捕捉不良配置。我希望这一章能让你有信心去排查问题、操作 Istio,并使用自动化工具如 OPA Gatekeeper 来强制执行配置卫生,确保你的 Istio 服务网格实现符合最佳实践。

在下一章,我们将通过在网格上部署一个应用程序,将本书的学习内容付诸实践。

第十二章:总结我们学到的内容及下一步

在全书中,你学习并实践了服务网格的各种概念,并了解如何使用 Istio 应用它们。强烈建议你在每一章中练习动手示例。不要仅限于本书中呈现的场景,而是要探索、调整、扩展这些示例,并将它们应用到你在组织中面临的实际问题上。

在本章中,我们将通过为在线精品应用程序实现 Istio,回顾本书中讨论的概念。建议在查看代码示例之前,先查看本章中呈现的场景并尝试自己实现。我希望阅读本章能给你使用 Istio 带来更多信心。我们将在本章中讲解以下主题:

  • 使用 OPA Gatekeeper 执行最佳实践

  • 将本书中的学习应用到示例的在线精品应用程序

  • Istio 的路线图、愿景和文档,以及如何与社区互动

  • 认证、学习资源以及各种学习路径

  • 扩展伯克利数据包过滤器

技术要求

本章中的技术要求类似于第四章。我们将使用 AWS EKS 部署一个在线精品商店的网站,这是一个可通过 Apache 许可证 2.0 获得的开源应用程序,地址是github.com/GoogleCloudPlatform/microservices-demo

请检查第四章技术要求部分,使用 Terraform 在 AWS 中设置基础设施,设置 kubectl,并安装包括可观察性附加组件在内的 Istio。要部署在线精品商店应用程序,请使用 GitHub 上Chapter12/online-boutique-orig文件中的部署工件。

你可以使用以下命令部署在线精品商店应用程序:

$ kubectl apply -f Chapter12/online-boutique-orig/00-online-boutique-shop-ns.yaml
namespace/online-boutique created
$ kubectl apply -f Chapter12/online-boutique-orig

最后一条命令应该部署在线精品应用程序。过一段时间后,你应该能够看到所有的 Pods 正在运行:

$ kubectl get po -n online-boutique
NAME                        READY   STATUS    RESTARTS   AGE
adservice-8587b48c5f-v7nzq               1/1     Running   0          48s
cartservice-5c65c67f5d-ghpq2             1/1     Running   0          60s
checkoutservice-54c9f7f49f-9qgv5         1/1     Running   0          73s
currencyservice-5877b8dbcc-jtgcg         1/1     Running   0          57s
emailservice-5c5448b7bc-kpgsh            1/1     Running   0          76s
frontend-67f6fdc769-r8c5n                1/1     Running   0          68s
paymentservice-7bc7f76c67-r7njd          1/1     Running   0          65s
productcatalogservice-67fff7c687-jrwcp   1/1     Running   0          62s
recommendationservice-b49f757f-9b78s     1/1     Running   0          70s
redis-cart-58648d854-jc2nv               1/1     Running   0          51s
shippingservice-76b9bc7465-qwnvz         1/1     Running   0          55s

工作负载的名称也反映了它们在在线精品应用程序中的角色,但你可以在github.com/GoogleCloudPlatform/microservices-demo上找到更多关于这个免费开源应用程序的信息。

现在,你可以通过以下命令访问应用程序:

$ kubectl port-forward svc/frontend 8080:80 -n online-boutique
Forwarding from 127.0.0.1:8080 -> 8079
Forwarding from [::1]:8080 -> 8079

然后,你可以通过http://localhost:8080在浏览器中打开它。你应该能看到类似以下内容:

图 12.1 – Google 的在线精品应用程序

图 12.1 – Google 的在线精品应用程序

这完成了本章中代码示例所需的技术设置。现在,让我们进入本章的主要内容。我们将从设置 OPA Gatekeeper 开始,以执行 Istio 部署最佳实践。

使用 OPA Gatekeeper 执行工作负载部署最佳实践

在本节中,我们将使用第十一章的知识部署 OPA Gatekeeper。然后,我们将配置 OPA 策略,强制每个部署都有 appversion 作为标签,并且所有端口名称以协议名称作为前缀:

  1. 安装 OPA Gatekeeper。按照第十一章使用 OPA 自动化最佳实践Gatekeeper部分的说明进行部署:

    % kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml
    
  2. 部署 OPA Gatekeeper 后,您需要配置它来同步命名空间、Pod、服务以及 Istio CRD 网关、虚拟服务、目标规则和策略与服务角色绑定到其缓存中。我们将使用在第十一章中创建的配置文件:

    $ kubectl apply -f Chapter11/05-GatekeeperConfig.yaml
    config.config.gatekeeper.sh/config created
    
  3. 配置 OPA Gatekeeper 以应用约束。在第十一章中,我们配置了约束,强制 Pod 应有 appversion 作为标签(定义在 Chapter11/gatekeeper/01-istiopodlabelconstraint_template.yamlChapter11/gatekeeper/01-istiopodlabelconstraint.yaml 中),并且所有端口名称应该以协议名称作为前缀(定义在 Chapter11/gatekeeper/02-istioportconstraints_template.yamlChapter11/gatekeeper/02-istioportconstraints.yaml 中)。使用以下命令应用约束:

    $ kubectl apply -f Chapter11/gatekeeper/01-istiopodlabelconstraint_template.yaml
    constrainttemplate.templates.gatekeeper.sh/istiorequiredlabels created
    $ kubectl apply -f Chapter11/gatekeeper/01-istiopodlabelconstraint.yaml
    istiorequiredlabels.constraints.gatekeeper.sh/mesh-pods-must-have-app-and-version created
    $ kubectl apply -f Chapter11/gatekeeper/02-istioportconstraints_template.yaml
    constrainttemplate.templates.gatekeeper.sh/allowedistioserviceportname created
    $ kubectl apply -f Chapter11/gatekeeper/02-istioportconstraints.yaml
    allowedistioserviceportname.constraints.gatekeeper.sh/port-name-constraint created
    

这完成了 OPA Gatekeeper 的部署和配置。您应该根据需要扩展约束,确保工作负载的部署描述符保持良好的规范。

在下一部分,我们将重新部署在线精品应用,并启用 istio sidecar 注入,然后发现违反 OPA 约束的配置,并逐一解决它们。

将我们的学习应用到示例应用中

在本节中,我们将把书中的学习成果——特别是第四章第六章的知识——应用到我们的在线精品应用中。让我们直接开始吧!

为示例应用启用服务网格

现在,OPA Gatekeeper 已经配置好并强制执行我们希望它在部署中应用的所有约束,是时候部署示例应用了。我们将首先从撤销 online-boutique 应用的部署开始,并在命名空间级别启用 istio-injection 后重新部署。

通过删除 online-boutique 命名空间来撤销在线精品应用的部署:

% kubectl delete ns online-boutique
namespace " online-boutique " deleted

一旦撤销部署,让我们修改命名空间并添加一个 istio-injection:enabled 标签,然后重新部署应用。更新后的命名空间配置如下:

apiVersion: v1
kind: Namespace
metadata:
  name: online-boutique
  labels:
    istio-injection: enabled

示例文件可在 GitHub 上的 Chapter12/OPAGatekeeper/automaticsidecarinjection/00-online-boutique-shop-ns.yaml 中找到。

启用自动 sidecar 注入后,让我们尝试使用以下命令部署应用:

$ kubectl apply -f Chapter12/OPAGatekeeper/automaticsidecarinjection
namespace/online-boutiquecreated
$ kubectl apply -f Chapter12/OPAGatekeeper/automaticsidecarinjection
Error from server (Forbidden): error when creating "Chapter12/OPAGatekeeper/automaticsidecarinjection/02-carts-svc.yml": admission webhook "validation.gatekeeper.sh" denied the request: [port-name-constraint] All services declaration must have port name with one of following  prefix http-, http2-,https-,grpc-,grpc-web-,mongo-,redis-,mysql-,tcp-,tls-

由于 OPA Gatekeeper 强制的约束违反,将会发生错误。前面的示例输出已被截断以避免重复,但从您的终端输出中,您必须注意到所有部署都违反了约束,因此没有任何资源部署到 online-boutique 命名空间。

尝试通过应用正确的标签并按照 Istio 最佳实践正确命名端口来修复约束违规问题。

你需要为所有部署应用appversion标签。以下是一个frontend部署的示例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: online-boutique
spec:
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
        version: v1

同样,你需要为服务声明中的所有端口定义添加name。以下是一个carts服务的示例:

apiVersion: v1
kind: Service
metadata:
  name: frontend
  namespace: online-boutique
spec:
  type: ClusterIP
  selector:
    app: frontend
  ports:
  - name: http-frontend
    port: 80
    targetPort: 8080

为了方便起见,更新后的文件可在Chapter12/OPAGatekeeper/automaticsidecarinjection中找到。使用以下命令部署 Online Boutique 应用:

% kubectl apply -f Chapter12/OPAGatekeeper/automaticsidecarinjection

通过这个,我们已经练习了在 Service Mesh 中部署 Online Boutique 应用。你应该在集群中部署了 Online Boutique 应用及其自动侧车注入功能。Online Boutique 应用是 Service Mesh 的一部分,但尚未完全准备好。在下一节中,我们将应用第五章关于管理应用流量的学习内容。

配置 Istio 以管理应用流量

在本节中,基于第四章的学习内容,我们将配置 Service Mesh 来管理 Online Boutique 应用的流量。我们将首先从配置 Istio Ingress 网关开始,允许流量进入服务网格。

配置 Istio Ingress 网关

第四章中,我们了解到网关就像是服务网格边缘的负载均衡器,它接收传入的流量,然后将流量路由到底层的工作负载。

在以下源代码块中,我们已经定义了网关配置:

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: online-boutique-ingress-gateway
  namespace: online-boutique
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "onlineboutique.com"

该文件也可以在 GitHub 上的Chapter12/trafficmanagement/01-gateway.yaml找到。使用以下命令应用配置:

$ kubectl apply -f Chapter12/trafficmanagement/01-gateway.yaml
gateway.networking.istio.io/online-boutique-ingress-gateway created

接下来,我们需要配置VirtualService,将onlineboutique.com主机的流量路由到相应的frontend服务。

配置 VirtualService

VirtualService用于定义网关配置中指定的每个主机的路由规则。VirtualService与网关相关联,主机名由该网关管理。在VirtualService中,你可以定义流量/路由的匹配规则,并在匹配时指定流量应该被路由到哪里。

以下源代码块定义了VirtualService,它匹配由online-boutique-ingress-gateway处理的任何流量,并且主机名为onlineboutique.com。如果匹配,流量将被路由到名为frontend的目标服务的v1子集:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: onlineboutique-frontend-vs
  namespace: online-boutique
spec:
  hosts:
  - "onlineboutique.com"
  gateways:
  - online-boutique-ingress-gateway
  http:
  - route:
    - destination:
        host: frontend
        subset: v1

配置文件可以在 GitHub 上的Chapter12/trafficmanagement/02-virtualservice-frontend.yaml找到。

接下来,我们将配置DestinationRule,它定义了请求将如何由目标处理。

配置 DestinationRule

虽然它们看起来可能不必要,但当您的工作负载有多个版本时,DestinationRule 用于定义流量策略,例如负载均衡策略、连接池策略、异常检测策略等。以下代码块为 frontend 服务配置了 DestinationRule

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: frontend
  namespace: online-boutique
spec:
  host: frontend
  subsets:
  - name: v1
    labels:
      app: frontend

配置已与 VirtualService 配置一起提供,在 GitHub 上的 Chapter12/trafficmanagement/02-virtualservice-frontend.yaml 中。

接下来,我们使用以下命令创建 VirtualServiceDestinationRule

$ kubectl apply -f Chapter12/trafficmanagement/02-virtualservice-frontend.yaml
virtualservice.networking.istio.io/onlineboutique-frontend-vs created
destinationrule.networking.istio.io/frontend created

现在,您应该能够通过 Web 浏览器访问在线精品店网站。您需要找到暴露 Ingress 网关服务的 AWS 负载均衡器的公共 IP – 别忘了按照第四章中的讨论,并通过 ModHeader 扩展在 Chrome 中添加 Host 头部,正如以下截图所示:

图 12.2 – 带 Host 头部的 ModHeader 扩展

图 12.2 – 带 Host 头部的 ModHeader 扩展

一旦添加了正确的 Host 头部,您就可以通过 AWS 负载均衡器的公共 DNS 使用 Chrome 访问在线精品店:

图 12.3 – 在线精品店登录页面

图 12.3 – 在线精品店登录页面

到目前为止,我们只创建了一个虚拟服务,将流量从 Ingress 网关路由到网格中的 frontend 服务。默认情况下,Istio 会将流量发送到网格中的所有相关微服务,但正如我们在前一章讨论的那样,最佳实践是通过 VirtualService 定义路由,并通过目标规则定义请求如何路由。遵循最佳实践,我们需要为剩余的微服务定义 VirtualServiceDestinationRule。拥有 VirtualServiceDestinationRule 有助于在底层工作负载有多个版本时管理流量。

为了方便起见,VirtualServiceDestinationRule 已在 GitHub 上的 Chapter12/trafficmanagement/03-virtualservicesanddr-otherservices.yaml 文件中定义。您可以使用以下命令应用配置:

$ kubectl apply -f Chapter12/trafficmanagement/03-virtualservicesanddr-otherservices.yaml

在应用配置并生成一些流量后,查看 Kiali 仪表板:

图 12.4 – 在线精品店的版本化应用图

图 12.4 – 在线精品店的版本化应用图

在 Kiali 仪表板中,您可以观察到 Ingress 网关、所有虚拟服务以及底层工作负载。

配置访问外部服务

接下来,我们快速回顾一下将流量路由到网格外目标的概念。在第四章中,我们学习了 ServiceEntry,它使我们能够将额外的条目添加到 Istio 的内部服务注册表中,以便网格中的服务可以将流量路由到这些不属于 Istio 服务注册表的端点。以下是将 xyz.com 添加到 Istio 服务注册表的 ServiceRegistry 示例:

apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: allow-egress-to-xyv.com
spec:
  hosts:
  - "xyz.com"
  ports:
  - number: 80
    protocol: HTTP
    name: http
  - number: 443
    protocol: HTTPS
    name: https

本节内容讲解了如何管理应用程序流量,我们通过 Istio Ingress Gateway 暴露了onlineboutique.com,并为流量路由和处理定义了VirtualServiceDestinationRule

配置 Istio 以管理应用程序的韧性

Istio 提供了多种功能来管理应用程序的韧性,我们在第五章中详细讨论了这些功能。我们将应用该章节中的一些概念到在线商店应用程序中。

让我们从超时和重试开始!

配置超时和重试

假设电子邮件服务存在间歇性故障,如果在 5 秒内没有收到电子邮件服务的响应,那么最好设置超时,并在失败后重试发送邮件几次,而不是直接放弃。我们将配置电子邮件服务的重试和超时,以修改应用程序的韧性概念。

Istio 提供了配置超时的功能,即 Istio 代理边车应该等待来自给定服务的回复的时间。在以下配置中,我们为电子邮件服务应用了 5 秒的超时:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  namespace: online-boutique
  name: emailvirtualservice
spec:
  hosts:
  - emailservice
  http:
  - timeout: 5s
    route:
    - destination:
        host: emailservice
        subset: v1

Istio 还提供了自动重试的功能,这一功能作为VirtualService配置的一部分实现。在以下源代码块中,我们已将 Istio 配置为重试两次发送请求给电子邮件服务,每次重试的超时时间为 2 秒,且仅在下游返回5xx,gateway-error,reset,connect-failure,refused-stream,retriable-4xx错误时才会重试:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  namespace: online-boutique
  name: emailvirtualservice
spec:
  hosts:
  - emailservice
  http:
  - timeout: 5s
    route:
    - destination:
        host: emailservice
        subset: v1
    retries:
      attempts: 2
      perTryTimeout: 2s
      retryOn: 5xx,gateway-error,reset,connect-failure,refused-stream,retriable-4xx

我们已经通过VirtualService配置设置了timeoutretries。假设电子邮件服务较为脆弱,并且会出现临时故障,我们将尝试通过缓解流量激增或尖峰可能导致的问题来解决这一问题。

配置速率限制

Istio 提供了控制来自消费者的流量激增的功能,并能够根据消费者的处理能力来控制流量。

在以下目标规则中,我们定义了电子邮件服务的速率限制控制。我们定义了电子邮件服务的活跃请求数为1(根据http2MaxRequests),每个连接只有 1 个请求(在maxRequestsPerConnection中定义),而在等待连接池连接时,排队请求数为 0(根据http1MaxPendingRequests定义):

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  namespace: online-boutique
  name: emaildr
spec:
  host: emailservice
  trafficPolicy:
      connectionPool:
        http:
          http2MaxRequests: 1
          maxRequestsPerConnection: 1
          http1MaxPendingRequests: 0
  subsets:
  - name: v1
    labels:
      version: v1
      app: emailservice

让我们做更多假设,假设电子邮件服务有两个版本,其中v1v2更不稳定。在这种情况下,我们需要应用异常检测策略来执行熔断器。Istio 提供了良好的异常检测控制。以下代码块描述了您需要在电子邮件服务的目标规则中的trafficPolicy中添加的配置:

      outlierDetection:
        baseEjectionTime: 5m
        consecutive5xxErrors: 1
        interval: 90s
        maxEjectionPercent: 50

在异常值检测中,我们定义了baseEjectionTime,其值为5分钟,这是每次驱逐的最短持续时间。然后,这个值会乘以电子邮件服务被发现不健康的次数。例如,如果v1电子邮件服务被发现异常 5 次,那么它将从连接池中被驱逐baseEjectionTime*5分钟。接下来,我们定义了consecutive5xxErrors,其值为1,即需要发生的5x错误次数,才能将上游服务标记为异常值。然后,我们定义了interval,其值为90s,即 Istio 扫描上游服务健康状态的检查间隔时间。最后,我们定义了maxEjectionPercent,其值为50%,即连接池中可以被驱逐的最大主机数。

这样,我们就对在线精品应用程序的应用韧性管理进行了修订和应用了各种控制。Istio 提供了各种控制,用于管理应用韧性,而无需修改或构建任何特定于应用程序的内容。在下一部分,我们将把第六章的学习应用到我们的在线精品应用程序中。

配置 Istio 来管理应用安全性

现在,我们已经通过 Istio Gateway 创建了 Ingress,通过 Istio VirtualServiceDestinationRules配置了路由规则,以处理流量如何被路由到最终目的地,我们可以进入下一步——在网格中保护流量。以下策略强制要求所有流量在网格中严格通过 mTLS 进行:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: strictmtls-online-boutique
  namespace: online-boutique
spec:
  mtls:
    mode: STRICT

配置文件可在 GitHub 上的Chapter12/security/strictMTLS.yaml文件中找到。如果没有此配置,网格中的所有流量都将处于PERMISSIVE模式,这意味着流量可以通过 mTLS 或明文传输进行。你可以通过部署curl Pod 并向网格中的任何微服务发起 HTTP 请求来验证这一点。但一旦应用此策略,Istio 将强制执行STRICT模式,这意味着所有流量将严格使用 mTLS 进行。使用以下命令应用配置:

$ kubectl apply -f Chapter12/security/strictMTLS.yaml
peerauthentication.security.istio.io/strictmtls-online-boutique created

你可以在 Kiali 中检查网格中的所有流量是否都通过 mTLS 进行:

图 12.5 – 显示服务之间 mTLS 通信的应用图

图 12.5 – 显示服务之间 mTLS 通信的应用图

接下来,我们将使用https来保护 Ingress 流量。这个步骤很重要,需要修订,但它的结果会导致无法访问应用程序的问题,因此我们将执行修订步骤后再将其恢复,以便继续访问应用程序。

我们将使用第四章通过 HTTPS 暴露入口部分的内容进行学习。如果你拥有一个onlineboutique.com域名,步骤会简单得多:

  1. 创建一个 CA。这里,我们正在使用onlineboutique.inc来创建 CA:

    $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=Online Boutique./CN=onlineboutique.inc' -keyout onlineboutique.inc.key -out onlineboutique.inc.crt
    Generating a 2048 bit RSA private key
    writing new private key to 'onlineboutique.inc.key'
    
  2. 生成一个onlineboutique.com,同时生成一个私钥:

    $ openssl req -out onlineboutique.com.csr -newkey rsa:2048 -nodes -keyout onlineboutique.com.key -subj "/CN=onlineboutique.com/O=onlineboutique.inc"
    Generating a 2048 bit RSA private key
    ...........................................................................+++
    .........+++
    writing new private key to 'onlineboutique.com.key'
    
  3. 使用以下命令通过 CA 签署 CSR:

    $ openssl x509 -req -sha256 -days 365 -CA onlineboutique.inc.crt -CAkey onlineboutique.inc.key -set_serial 0 -in onlineboutique.com.csr -out onlineboutique.com.crt
    Signature ok
    subject=/CN= onlineboutique.com/O= onlineboutique.inc
    
  4. 将证书和私钥作为 Kubernetes Secret 加载:

    $ kubectl create -n istio-system secret tls onlineboutique-credential --key=onlineboutique.com.key --cert=onlineboutique.com.crt
    secret/onlineboutique-credential created
    

我们已经创建了证书并将其存储为 Kubernetes Secret。在接下来的步骤中,我们将修改 Istio Gateway 配置,通过 HTTPS 暴露流量,并使用这些证书。

  1. 按照以下命令创建 Gateway 配置:

    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: online-boutique-ingress-gateway
      namespace: online-boutique
    spec:
      selector:
        istio: ingressgateway
      servers:
      - port:
          number: 443
          name: https
          protocol: HTTPS
        tls:
          mode: SIMPLE
          credentialName: onlineboutique-credential
        hosts:
        - "onlineboutique.com"
    

应用以下配置:

$ kubectl apply -f Chapter12/security/01-istio-gateway.yaml

你可以使用以下命令访问并检查证书。请注意,输出已被截断,仅突出显示相关部分:

$ curl -v -HHost:onlineboutique.com --connect-to "onlineboutique.com:443:aced3fea1ffaa468fa0f2ea6fbd3f612-390497785.us-east-1.elb.amazonaws.com" --cacert onlineboutique.inc.crt --head  https://onlineboutique.com:443/
..
* Connected to aced3fea1ffaa468fa0f2ea6fbd3f612-390497785.us-east-1.elb.amazonaws.com (52.207.198.166) port 443 (#0)
--
* Server certificate:
*  subject: CN=onlineboutique.com; O=onlineboutique.inc
*  start date: Feb 14 23:21:40 2023 GMT
*  expire date: Feb 14 23:21:40 2024 GMT
*  common name: onlineboutique.com (matched)
*  issuer: O=Online Boutique.; CN=onlineboutique.inc
*  SSL certificate verify ok.
..

该配置将保护 online-boutique 商店的 Ingress 流量,但这也意味着你将无法通过浏览器访问它,因为浏览器中使用的 FQDN 与证书中配置的 CN 不匹配。你可以选择在 AWS 负载均衡器上注册 DNS 名称,但现在,你可能会发现删除 HTTPS 配置并恢复使用 GitHub 上的 Chapter12/trafficmanagement/01-gateway.yaml 文件会更容易。

让我们深入探讨安全性,并为 Online Boutique 商店执行 RequestAuthentication 和授权。在 第六章 中,我们详细讲解了使用 Auth0 构建认证和授权的过程。按照相同的思路,我们将为 frontend 服务构建认证和授权策略,但这一次,我们将使用 Istio 附带的虚拟 JWKS 端点。

我们将从创建一个 RequestAuthentication 策略开始,用来定义 frontend 服务支持的认证方法:

apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
 name: frontend
 namespace: online-boutique
spec:
  selector:
    matchLabels:
      app: frontend
  jwtRules:
  - issuer: "testing@secure.istio.io"
    jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.17/security/tools/jwt/samples/jwks.json"

我们正在使用 Istio 附带的虚拟 jwksUri 进行测试。使用以下命令应用 RequestAuthentication 策略:

$ kubectl apply -f Chapter12/security/requestAuthentication.yaml
requestauthentication.security.istio.io/frontend created

应用 RequestAuthentication 策略后,你可以通过向 frontend 服务提供虚拟令牌来进行测试:

  1. 获取虚拟令牌并将其设置为环境变量,稍后在请求中使用:

    TOKEN=$(curl -k https://raw.githubusercontent.com/istio/istio/release-1.17/security/tools/jwt/samples/demo.jwt -s); echo $TOKEN
    eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyen BBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ2ODU5ODk3MDAsImZvbyI6ImJhciIsImlhdCI6MTUzMjM4OTcwMCwiaXNzIjoidGVzdGluZ0BzZWN1cmUuaXN0aW8uaW8iLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9\. CfNnxWP2tcnR9q0vxyxweaF3ovQYHYZl82hAUsn21bwQd9zP7c-LS9qd_vpdLG4Tn1A15NxfCjp5f7QNBUo-KC9PJqYpgGbaXhaGx7bEdFWjcwv3nZzvc7M__ZpaCERdwU7igUmJqYGBYQ51vr2njU9ZimyKkfDe3axcyiBZde7G6dabliUosJvvKOPcKIWPccCgefSj_GNfwIip3-SsFdlR7BtbVUcqR-yv-XOxJ3Uc1MI0tz3uMiiZcyPV7sNCU4KRnemRIMHVOfuvHsU60_GhGbiSFzgPTAa9WTltbnarTbxudb_YEOx12JiwYToeX0DCPb43W1tzIBxgm8NxUg
    
  2. 使用 curl 进行测试:

    $ curl -HHost:onlineboutique.com http://aced3fea1ffaa468fa0f2ea6fbd3f612-390497785.us-east-1.elb.amazonaws.com/ -o /dev/null --header "Authorization: Bearer $TOKEN" -s -w '%{http_code}\n'
    200
    

注意到你收到了一个 200 响应。

  1. 现在尝试使用无效令牌进行测试:

    $ curl -HHost:onlineboutique.com http://aced3fea1ffaa468fa0f2ea6fbd3f612-390497785.us-east-1.elb.amazonaws.com/ -o /dev/null --header "Authorization: Bearer BLABLAHTOKEN" -s -w '%{http_code}\n'
    401
    

RequestAuthentication 策略已生效并拒绝了请求。

  1. 测试没有令牌的情况:

    % curl -HHost:onlineboutique.com http://aced3fea1ffaa468fa0f2ea6fbd3f612-390497785.us-east-1.elb.amazonaws.com/ -o /dev/null  -s -w '%{http_code}\n'
    200
    

请求的结果不是我们期望的,但这是预期的,因为 RequestAuthentication 策略仅负责在传递令牌时验证该令牌。如果请求中没有 Authorization 头部,RequestAuthentication 策略将不会被调用。我们可以通过使用 AuthorizationPolicy 来解决这个问题,后者为服务网格中的工作负载强制执行访问控制策略。

让我们构建 AuthorizationPolicy,强制要求请求中必须存在一个主体:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: require-jwt
  namespace: online-boutique
spec:
  selector:
    matchLabels:
      app: frontend
  action: ALLOW
  rules:
  - from:
    - source:
       requestPrincipals: ["testing@secure.istio.io/testing@secure.istio.io"]

配置文件可以在 GitHub 上的 Chapter12/security/requestAuthorizationPolicy.yaml 文件中找到。使用以下命令应用配置:

$ kubectl apply -f Chapter12/security/requestAuthorizationPolicy.yaml
authorizationpolicy.security.istio.io/frontend created

在应用了RequestAuthentication策略后,按照步骤 1步骤 4执行配置测试时,您会发现所有步骤都按预期工作,但在步骤 4中,我们遇到了以下问题:

$ curl -HHost:onlineboutique.com http://aced3fea1ffaa468fa0f2ea6fbd3f612-390497785.us-east-1.elb.amazonaws.com/ -o /dev/null  -s -w '%{http_code}\n'
403

这是因为授权策略强制要求 JWT 的存在,且该 JWT 具有["``testing@secure.istio.io/testing@secure.istio.io"]主体。

这就是我们为在线商店应用程序配置安全性设置的总结。在下一节中,我们将阅读有关各种资源的内容,这些资源将帮助您成为使用和操作 Istio 的专家,并获得认证。

Istio 的认证和学习资源

学习 Istio 的主要资源是 Istio 官方网站(istio.io/latest/)。网站上有关于执行从基础到多集群设置的详细文档。资源涵盖了初学者和高级用户,并提供了各种关于流量管理、安全性、可观察性、可扩展性和策略执行的练习。除了 Istio 文档之外,提供大量 Istio 支持内容的另一个组织是 Tetrate(tetrate.io/),它还提供实验室和认证课程。Tetrate Academy 提供的一项认证是认证 Istio 管理员。有关课程和考试的详细信息,请访问academy.tetrate.io/courses/certified-istio-administrator。Tetrate Academy 还提供了一门免费的 Istio 基础课程。您可以在academy.tetrate.io/courses/istio-fundamentals找到课程的详细信息。同样,Solo.io 也提供了一门名为开始使用 Istio的课程,您可以在academy.solo.io/get-started-with-istio查看课程的详细信息。来自 Linux 基金会的另一门好课程叫做Istio 简介,您可以在training.linuxfoundation.org/training/introduction-to-istio-lfs144x/找到该课程的详细信息。

我个人喜欢在istiobyexample.dev/上使用的学习资源;该网站详细解释了 Istio 的各种使用场景(例如金丝雀发布、管理 Ingress、管理 gRPC 流量等),并提供了配置示例。如有任何技术问题,您可以随时访问 StackOverflow,stackoverflow.com/questions/tagged/istio。这里有一个充满活力和热情的 Istio 用户与开发者社区,他们在discuss.istio.io/讨论各种与 Istio 相关的话题;欢迎注册讨论论坛。

Tetrate Academy 还提供了一个免费的 Envoy 基础课程;该课程非常有助于理解 Envoy 的基础知识,进而理解 Istio 数据平面。你可以在 academy.tetrate.io/courses/envoy-fundamentals 找到该课程的详细信息。课程中包含了许多实用的实验和小测验,非常有助于你掌握 Envoy 技能。

Istio 网站已经整理了一份有用的资源清单,帮助你了解 Istio 的最新动态并与 Istio 社区互动;你可以在 istio.io/latest/get-involved/ 找到这份清单。该清单还提供了如何报告 bugs 和问题的详细信息。

总结来说,除了少数几本书和网站外,相关资源并不多,但你大多数问题的答案可以在 istio.io/latest/docs/ 找到。关注 IstioCon 也是一个不错的主意,IstioCon 是 Istio 社区的年会,每年举办一次。你可以在 events.istio.io/istiocon-2022/sessions/ 找到 2022 年的会议内容,在 events.istio.io/istiocon-2021/sessions/ 找到 2021 年的会议内容。

理解 eBPF

在本书接近尾声时,了解与服务网格相关的其他技术也非常重要。其中一个技术就是 扩展伯克利数据包过滤器eBPF)。在这一部分,我们将了解 eBPF 及其在服务网格演变中的作用。

eBPF 是一个框架,它允许用户在操作系统内核中运行自定义程序,而无需修改内核源代码或加载内核模块。自定义程序被称为 eBPF 程序,用于在运行时向操作系统添加额外的功能。eBPF 程序既安全又高效,像内核模块一样,它们是轻量级的沙盒虚拟机,由操作系统在特权上下文中运行。

eBPF 程序是基于内核级事件触发的,这通过将它们与钩子点关联来实现。钩子是预定义的内核级事件,包括系统调用、网络事件、函数入口和退出等。在没有合适钩子的场景下,用户可以使用内核探针,也叫做 kprobes。kprobes 被插入到内核例程中;eBPF 程序被定义为 kprobes 的处理程序,并且每当在内核中触发特定断点时,eBPF 程序就会执行。像钩子和 kprobes 一样,eBPF 程序也可以附加到 uprobes 上,uprobes 是用户空间级别的探针,绑定到用户应用程序级别的事件,因此 eBPF 程序可以在从内核到用户应用程序的任何级别执行。执行内核级程序时,最大的关注点是程序的安全性。在 eBPF 中,这是通过 BPF 库来保证的。BPF 库处理加载 eBPF 程序的系统调用,分为两个步骤。第一个步骤是验证步骤,在此过程中验证 eBPF 程序,确保它能够完成执行并且不会锁死内核,加载 eBPF 程序的进程具有正确的权限,并且 eBPF 程序不会对内核造成任何危害。第二个步骤是即时编译JIT)步骤,它将程序的通用字节码转换为特定机器的指令,并对其进行优化,以获得程序的最大执行速度。这使得 eBPF 程序的运行效率与本地编译的内核代码相当,就像它作为内核模块加载一样。一旦这两个步骤完成,eBPF 程序就会被加载并编译进内核,等待钩子或 kprobes 触发执行。

BPF 已经广泛作为内核的附加组件使用。大多数应用程序都集中在网络层,主要用于可观察性领域。eBPF 被用来提供对数据包和套接字级别的系统调用的可视化,进而用于构建可以从内核低级上下文中操作的安全解决方案系统。eBPF 程序还用于对用户应用程序以及运行该应用程序的内核部分进行自省,这提供了一个统一的视角,用于排查应用程序性能问题。你可能会想知道为什么我们在服务网格的上下文中讨论 eBPF。eBPF 的可编程性和插件模型在网络领域特别有用。eBPF 可以用于执行 IP 路由、数据包过滤、监控等功能,且速度与内核模块的本地速度相同。Istio 架构的一个缺点是它为每个工作负载都部署一个边车(sidecar),正如我们在第二章中讨论的那样——边车的基本工作原理是拦截网络流量,利用 iptables 配置内核的 netfilter 数据包过滤功能。这种方法的缺点是性能不太理想,因为为服务流量创建的数据路径比没有边车流量拦截时的路径要长得多。通过 eBPF 的套接字相关程序类型,你可以过滤套接字数据、重定向套接字数据,并监控套接字事件。这些程序有可能替代基于 iptables 的流量拦截;使用 eBPF,有多种方式可以拦截和管理网络流量,而不会对数据路径性能产生负面影响。

Isovalent(访问 isovalent.com/)就是这样一个正在革新 API 网关和服务网格架构的组织。Cilium是 Isovalent 的一个产品,它提供多种功能,包括 API 网关功能、服务网格、可观察性和网络功能。Cilium 是以 eBPF 为核心技术构建的,它在 Linux 内核的多个位置注入 eBPF 程序,从而实现应用程序网络、安全和可观察性功能。Cilium 正在被 Kubernetes 网络中采用,以解决由于数据包需要在主机和 Pod 之间多次穿越相同的网络栈而导致的性能下降问题。Cilium 通过绕过 iptables 的网络堆栈,避免了 iptables 带来的网络过滤器和其他开销,从而大幅提高了网络性能。你可以在isovalent.com/blog/post/cilium-release-113/阅读更多关于 Cilium 产品栈的信息;你将会惊讶地发现,eBPF 正在如何革新应用程序网络领域。

Istio 还创建了一个名为 Merbridge 的开源项目,它用 eBPF 程序替代了 iptables,允许数据直接在边车容器和应用容器的进出套接字之间传输,从而缩短整体数据路径。Merbridge 虽然还处于初期阶段,但已经取得了一些有前景的成果;你可以在github.com/merbridge/merbridge找到这个开源项目。

随着 eBPF 和像 Cilium 这样的产品的出现,未来网络代理产品的设计和操作方式可能会有显著的进展。eBPF 正在被包括 Istio 在内的各种服务网格技术积极探索,研究如何利用它来克服缺点并提升使用 Istio 的整体性能和体验。eBPF 是一项非常有前景的技术,已经在像 Cilium 和 Calico 这样的产品中得到了广泛应用,做出了很多令人惊叹的成果。

总结

我希望本书能为你提供对 Istio 的深入了解。第一章第三章为你设定了服务网格为何需要以及 Istio 的控制平面和数据平面如何运作的背景。这三章中的信息对理解 Istio 及其架构非常重要。接着,第四章第六章提供了如何使用 Istio 构建我们在前几章中讨论的应用网络的详细信息。

然后,在第七章中,你学习了可观察性以及 Istio 如何与各种观察工具进行集成,接下来的步骤是你应该探索与其他可观察性和监控工具的集成,例如 Datadog。接着,第八章展示了如何在多个 Kubernetes 集群中部署 Istio 的实践,这应该让你对如何在生产环境中安装 Istio 充满信心。第九章接着提供了如何使用 WebAssembly 扩展 Istio 及其应用的详细信息,而第十章讨论了 Istio 如何帮助将旧有的虚拟机世界与新兴的 Kubernetes 世界连接,通过讨论如何扩展服务网格以包含部署在虚拟机上的工作负载。最后,第十一章涵盖了操作 Istio 的最佳实践,以及如何使用 OPA Gatekeeper 等工具自动化一些最佳实践。

在本章中,我们通过部署和配置另一个开源示范应用,复习了第四章第六章的概念,这应该让你充满信心并获得经验,将书中的学习应用到实际应用中,并利用 Istio 提供的应用网络和安全功能。

你还会了解 eBPF 以及它作为一种颠覆性技术的重要性,它使得在不需要理解或经历内核恐怖的情况下,也可以在内核级别编写代码。eBPF 可能会给服务网格、API 网关和一般的网络解决方案带来许多变化。在本书的附录中,你将找到关于其他服务网格技术的信息:Consul Connect、Kuma Mesh、Gloo Mesh 和 Linkerd。附录提供了这些技术的概述,并帮助你理解它们的优势和局限性。

希望你喜欢学习 Istio。为了巩固你对 Istio 的知识,你还可以探索参加 Tetrate 提供的认证 Istio 管理员考试。你还可以探索本章中提供的其他学习途径。希望你阅读本书的过程能帮助你提升职业生涯,并在使用 Istio 构建可扩展、弹性和安全的应用程序方面获得更多经验。

祝你好运!

附录 – 其他服务网格技术

在本附录中,我们将学习以下服务网格实现:

  • Consul Connect

  • Gloo Mesh

  • Kuma

  • Linkerd

这些服务网格技术很受欢迎,并且正在被越来越多的组织认可和采用。本 附录 中提供的关于这些服务网格技术的信息并不详尽;这里的目标是让你熟悉并了解 Istio 的替代方案。我希望阅读本 附录 能为你提供这些替代技术的基本认知,并帮助你理解这些技术与 Istio 相比的表现。让我们开始深入了解吧!

Consul Connect

Consul Connect 是由 HashiCorp 提供的服务网格解决方案,也称为 Consul 服务网格。在 HashiCorp 的官网上,你会发现 Consul Connect 和 Consul 服务网格这两个术语是可以互换使用的。它是基于 Consul 构建的,而 Consul 是一个服务发现解决方案和键值存储。Consul 是一个非常流行且历史悠久的服务发现解决方案;它为各种工作负载提供和管理服务身份,然后这些身份被服务网格用来管理 Kubernetes 中服务之间的流量。它还支持使用 ACL 来实现零信任网络,并提供对网格中流量流动的精细控制。

Consul 使用 Envoy 作为其数据平面,并将其注入到工作负载 Pod 中作为 sidecar。注入可以基于注解和全局配置来自动将 sidecar 代理注入到指定命名空间中的所有工作负载。我们将从在你的工作站上安装 Consul 服务网格开始,然后进行一些练习,帮助你掌握使用 Consul 服务网格的基础知识。

让我们从安装 Consul 开始:

  1. 克隆 Consul 仓库:

    % git clone https://github.com/hashicorp-education/learn-consul-get-started-kubernetes.git
    …..
    Resolving deltas: 100% (313/313), done.
    
  2. 安装 Consul CLI:

    • 对于 MacOS,请按照以下步骤操作:

      1. 安装 HashiCorp tap:
      % brew tap hashicorp/tap
      
    1. 安装 Consul Kubernetes CLI:
    % brew install hashicorp/tap/consul-k8s
    Running `brew update --auto-update`...
    ==> Auto-updated Homebrew!
    Updated 1 tap (homebrew/core).
    You have 4 outdated formulae installed.
    You can upgrade them with brew upgrade
    or list them with brew outdated.
    ==> Fetching hashicorp/tap/consul-k8s
    ==> Downloading https://releases.hashicorp.com/consul-k8s/1.0.2/consul-k8s_1.0.2_darwin_arm64.zip
    ######################################################################## 100.0%
    ==> Installing consul-k8s from hashicorp/tap
    consul-k8s on the Consul CLI:
    
% consul-k8s version
    consul-k8s v1.0.2
  • 对于 Linux Ubuntu/Debian,请按照以下步骤操作:
  1. 添加 HashiCorp GPG 密钥:
% curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add –
  1. 添加 HashiCorp 的 apt 仓库:
apt-get install to install the consul-k8s CLI:

% sudo apt-get update && sudo apt-get install consul-k8s


*   For CentOS/RHEL, follow these steps:1.  Install yum-config-manager to manage your repositories:

% sudo yum install -y yum-utils


1.  Use yum-config-manager to add the official HashiCorp Linux repository:

consul-k8s CLI:

% sudo yum -y install consul-k8s
            启动 minikube:
% minikube start --profile dc1 --memory 4096 --kubernetes-version=v1.24.0
            使用 Consul CLI 在 minikube 上安装 Consul。

        在 learn-consul-get-started-kubernetes/local 中运行以下命令:
% consul-k8s install -config-file=helm/values-v1.yaml -set global.image=hashicorp/consul:1.14.0
==> Checking if Consul can be installed
✓ No existing Consul installations found.
✓ No existing Consul persistent volume claims found
✓ No existing Consul secrets found.
==> Consul Installation Summary
    Name: consul
    Namespace: consul
….
--> Starting delete for "consul-server-acl-init-cleanup" Job
✓ Consul installed in namespace "consul".
            1.  检查命名空间中的 Consul Pods:

```
% kubectl get po -n consul
NAME                    READY   STATUS    RESTARTS    AGE
consul-connect-injector-57dcdd54b7-hhxl4       1/1     Running   1 (21h ago)   21h
consul-server-0       1/1     Running   0             21h
consul-webhook-cert-manager-76bbf7d768-2kfhx   1/1     Running   0             21h
```

            1.  配置 Consul CLI 以便能够与 Consul 通信。

        我们将设置环境变量,以便 Consul CLI 能够与您的 Consul 集群通信。

        从`secrets/consul-bootstrap-acl-token`设置`CONSUL_HTTP_TOKEN`并将其设置为环境变量:
CONSUL_HTTP_TOKENsecrets/consul-bootstrap-acl-token --template={{.data.token}} | base64 -d)
        设置 Consul 目标地址。默认情况下,Consul 在端口 8500 上运行 HTTP,在端口 8501 上运行 HTTPS:
% export CONSUL_HTTP_ADDR=https://127.0.0.1:8501
        删除 SSL 验证检查,以简化与您的 Consul 集群的通信:
% export CONSUL_HTTP_SSL_VERIFY=false
            1.  使用以下命令访问 Consul 仪表盘:

```
% kubectl port-forward pods/consul-server-0 8501:8501 --namespace consul
```

        在浏览器中打开`localhost:8501`以访问 Consul 仪表盘,如下图所示:

        ![图 A.1 – Consul 仪表盘](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/bts-svc-mesh-impl-istio/img/Figure_13.01_B17989.jpg)

        图 A.1 – Consul 仪表盘

        现在我们已在 minikube 上安装了 Consul 服务网格,让我们部署一个示例应用程序,并了解 Consul 服务网格的基本原理。

        部署示例应用程序

        在这一节中,我们将部署 envoydummy 及一个 curl 应用程序。`sampleconfiguration`文件位于`AppendixA/envoy-proxy-01.yaml`。

        在配置文件中,您将看到以下注释:
      annotations:
        consul.hashicorp.com/connect-inject: "true"
        该注释允许 Consul 自动为每个服务注入一个代理。代理创建了一个数据平面,根据 Consul 的配置处理服务之间的请求。

        应用配置以创建`envoydummy`和`curl` Pods:
% kubectl create -f AppendixA/Consul/envoy-proxy-01.yaml -n appendix-consul
configmap/envoy-dummy created
service/envoydummy created
deployment.apps/envoydummy created
servicedefaults.consul.hashicorp.com/envoydummy created
serviceaccount/envoydummy created
servicedefaults.consul.hashicorp.com/curl created
serviceaccount/curl created
pod/curl created
service/curl created
        几秒钟后,您将看到 Consul 自动将 sidecar 注入到 Pods 中:
% % kubectl get po -n appendix-consul
NAME                          READY   STATUS    RESTARTS   AGE
curl                          2/2     Running   0          16s
envoydummy-77dfb5d494-2dx5w   2/2     Running   0          17s
        要了解更多关于 sidecar 的信息,请使用以下命令检查`envoydummy` Pod:
% kubectl get po/envoydummy-77dfb5d494-pcqs7 -n appendix-consul -o json | jq '.spec.containers[].image'
"envoyproxy/envoy:v1.22.2"
"hashicorp/consul-dataplane:1.0.0"
% kubectl get po/envoydummy-77dfb5d494-pcqs7 -n appendix-consul -o json | jq '.spec.containers[].name'
"envoyproxy"
"consul-dataplane"
        在输出中,您会看到一个名为`consul-dataplane`的容器,它是从名为`hashicorp/consul-dataplane:1.0.0`的镜像创建的。您可以在[`hub.docker.com/layers/hashicorp/consul-dataplane/1.0.0-beta1/images/sha256-f933183f235d12cc526099ce90933cdf43c7281298b3cd34a4ab7d4ebeeabf84?context=explore`](https://hub.docker.com/layers/hashicorp/consul-dataplane/1.0.0-beta1/images/sha256-f933183f235d12cc526099ce90933cdf43c7281298b3cd34a4ab7d4ebeeabf84?context=explore)查看该镜像,您会发现它是由 envoy 代理组成的。

        让我们尝试从`curl` Pod 访问`envoydummy`:
% kubectl exec -it pod/curl -n appendix-consul -- curl http://envoydummy:80
curl: (52) Empty reply from server
command terminated with exit code 52
        到目前为止,我们已成功部署`envoydummy` Pod,并将`consul-dataplane`作为 sidecar 一同部署。我们通过观察`curl` Pod 发现,尽管它与`envoydummy` Pod 部署在同一个命名空间,但无法访问该 Pod,从而验证了 Consul 服务网格安全的实际效果。在下一节中,我们将了解这一行为,并学习如何配置 Consul 以实现零信任网络。

        零信任网络

        Consul 通过名为意图的 Consul 构造来管理服务间授权。使用 Consul CRD,你需要定义意图,规定哪些服务可以互相通信。意图是 Consul 中零信任网络的基石。

        意图由边车代理在入站连接上执行。边车代理通过其 TLS 客户端证书识别入站服务。在识别入站服务后,边车代理检查是否存在允许客户端与目标服务通信的意图。

        在下面的代码块中,我们定义了一个意图,允许`curl`服务到`envoydummy`服务的流量:
apiVersion: consul.hashicorp.com/v1alpha1
kind: ServiceIntentions
metadata:
  name: curl-to-envoydummy-api
  namespace: appendix-consul
spec:
  destination:
    name: envoydummy
  sources:
    - name: curl
      action: allow
        在配置中,我们指定了目标服务和源服务的名称。在`action`中,我们指定了`allow`,允许源到目标的流量。`action`的另一个可能值是`deny`,即拒绝源到目标的流量。如果你不想指定服务名称,你需要使用`*`。例如,如果`sources`中的服务名称是`*`,则允许所有服务到`envoydummy`的流量。

        让我们使用以下命令应用意图:
% kubectl create -f AppendixA/Consul/curl-to-envoy-dummy-intentions.yaml
serviceintentions.consul.hashicorp.com/curl-to-envoydummy-api created
        你可以在 Consul 仪表板中验证已创建的意图:

        ![图 A.2 – Consul 意图](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/bts-svc-mesh-impl-istio/img/Figure_13.02_B17989.jpg)

        图 A.2 – Consul 意图

        鉴于我们已经创建了允许`curl`服务到`envoydummy`服务的流量意图,接下来让我们测试`curl` Pod 是否能够通过以下命令与`envoydummy` Pod 进行通信:
% kubectl exec -it pod/curl -n appendix-consul -- curl http://envoydummy
V1----------Bootstrap Service Mesh Implementation with Istio----------V1%
        通过使用意图,我们能够定义控制服务间流量的规则,而无需配置防火墙或进行集群中的任何更改。意图是 Consul 构建零信任网络的关键组成部分。

        流量管理与路由

        Consul 提供了全面的服务发现和流量管理功能。服务发现包括三个阶段:路由、拆分和解析。这三个阶段也被称为服务发现链,它可以用于基于 HTTP 头、路径、查询字符串和工作负载版本实现流量控制。

        让我们逐步分析服务发现链的每个阶段。

        路由

        这是服务发现链的第一个阶段,用于使用 Layer 7 构造(如 HTTP 头和路径)拦截流量。这是通过`service-router`配置条目实现的,你可以使用各种标准控制流量路由。例如,对于`envoydummy`,假设我们希望强制任何发送到`envoydummy`版本 v1 并且 URI 中包含`/latest`的请求应该路由到`envoydummy`版本 v2,而任何发送到`envoydummy`应用版本 v2 但路径中包含`/old`的请求应该路由到`envoydummy`应用版本 v1。这可以通过以下`ServiceRouter`配置实现:
apiVersion: consul.hashicorp.com/v1alpha1
kind: ServiceRouter
metadata:
  name: envoydummy
spec:
  routes:
    - match:
        http:
          pathPrefix: '/latest'
      destination:
        service: 'envoydummy2'
        在配置中,我们指定了任何目标为 `envoydummy` 服务的请求,如果 `pathPrefix` 设置为 `'/latest'`,将会路由到 `envoydummy2`。而在接下来的配置中,我们指定了任何目标为 `envoydummy2` 服务的请求,如果 `pathPrefix` 设置为 `'/old'`,将会路由到 `envoydummy`:
apiVersion: consul.hashicorp.com/v1alpha1
kind: ServiceRouter
metadata:
  name: envoydummy2
spec:
  routes:
    - match:
        http:
          pathPrefix: '/old'
      destination:
        service: 'envoydummy'
        两个 `ServiceRouter` 配置保存在 `AppendixA/Consul/routing-to-envoy-dummy.yaml` 中。`envoydummy` 版本 v2 的部署描述符以及允许从 `curl` Pod 进行流量的意图也可以在 GitHub 上的 `AppendixA/Consul/envoy-proxy-02.yaml` 找到。

        使用以下命令部署 `envoydummy` 版本 v2 以及 `ServiceRouter` 配置:
% kubectl apply -f AppendixA/Consul/envoy-proxy-02.yaml
% kubectl apply -f AppendixA/Consul/routing-to-envoy-dummy.yaml -n appendix-consul
servicerouter.consul.hashicorp.com/envoydummy configured
servicerouter.consul.hashicorp.com/envoydummy2 configured
        你可以使用 Consul 仪表板检查配置。以下两张截图展示了我们应用的两个 `ServiceRouter` 配置:

            +   `ServiceRouter` 配置,将带有 `/latest` 前缀的流量发送到 `envoydummy2`:

        ![图 A.3](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/bts-svc-mesh-impl-istio/img/Figure_13.03_B17989.jpg)

        图 A.3

            +   `ServiceRouter` 配置,将带有 `/old` 前缀的流量发送到 `envoydummy`:

        ![图 A.4](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/bts-svc-mesh-impl-istio/img/Figure_13.04_B17989.jpg)

        图 A.4

        现在我们已经配置了服务路由,让我们来测试一下路由行为:

            1.  向 `envoydummy` 版本 v1 发起请求,URI 不是 `/latest`:

```
% kubectl exec -it pod/curl -n appendix-consul -- curl http://envoydummy/new
V1----------Bootstrap Service Mesh Implementation with Istio----------V1%
```

        输出符合预期:请求应该被路由到 `envoydummy` 版本 v1。

            1.  向 `envoydummy` 版本 v1 发起请求,URI 为 `/latest`:

```
% kubectl exec -it pod/curl -n appendix-consul -- curl http://envoydummy/latest
V2----------Bootstrap Service Mesh Implementation with Istio----------V2%
```

        输出符合预期:请求虽然是发送给 `envoydummy` 版本 v1,但实际上被路由到了 `envoydummy` 版本 v2。

            1.  向 `envoydummy` 版本 v2 发起请求,URI 不是 `/old`:

```
% kubectl exec -it pod/curl -n appendix-consul -- curl http://envoydummy2/new
V2----------Bootstrap Service Mesh Implementation with Istio----------V2%
```

        输出符合预期:请求应该被路由到 `envoydummy` 版本 v2。

            1.  向 `envoydummy` 版本 v2 发起请求,URI 为 `/old`:

```
% kubectl exec -it pod/curl -n appendix-consul -- curl http://envoydummy2/old
V1----------Bootstrap Service Mesh Implementation with Istio----------V1%
```

        输出符合预期:请求虽然是发送给 `envoydummy` 版本 v2,但实际上被路由到了 `envoydummy` 版本 v1。

        在这些示例中,我们利用路径前缀作为路由标准。其他选项还包括查询参数和 HTTP 头。`ServiceRouter` 还支持重试逻辑,这可以添加到目标配置中。以下是添加到 `ServiceRouter` 配置中的重试逻辑示例:
apiVersion: consul.hashicorp.com/v1alpha1
kind: ServiceRouter
metadata:
  name: envoydummy2
spec:
  routes:
    - match:
        http:
          pathPrefix: '/old'
      destination:
        service: 'envoydummy'
        requestTimeout = "20s"
        numRetries = 3
        retryOnConnectFailure = true
        你可以在 HashiCorp 网站上阅读更多关于 `ServiceRouter` 配置的内容:[`developer.hashicorp.com/consul/docs/connect/config-entries/service-router`](https://developer.hashicorp.com/consul/docs/connect/config-entries/service-router)。

        接下来是服务发现链中的拆分,我们将在以下部分学习相关内容。

        拆分

        服务分流是 Consul 服务发现链中的第二个阶段,通过`ServiceSplitter`配置进行配置。`ServiceSplitter`允许你将请求分流到多个子集工作负载。通过此配置,你还可以进行金丝雀发布。以下是一个示例,展示了`envoydummy`服务的流量以 20:80 的比例路由到`envoydummy`应用的 v1 和 v2 版本:
apiVersion: consul.hashicorp.com/v1alpha1
kind: ServiceSplitter
metadata:
  name: envoydummy
spec:
  splits:
    - weight: 20
      service: envoydummy
    - weight: 80
      service: envoydummy2
        在`ServiceSplitter`配置中,我们将 80%的流量路由到`envoydummy2`服务,剩下的 20%流量路由到`envoydummy`服务。该配置文件位于`AppendixA/Consul/splitter.yaml`中。你可以使用以下命令应用该配置:
% kubectl apply -f AppendixA/Consul/splitter.yaml -n appendix-consul
servicesplitter.consul.hashicorp.com/envoydummy created
        应用配置后,你可以在 Consul 仪表盘上查看路由配置。在下面的截图中,我们可以看到所有流量都被路由到`envoydummy`和`envoydummy2`。截图中没有显示百分比,但你可以将鼠标悬停在连接分流器和解析器的箭头上,应该能看到百分比:

        ![图 A.5 – envoydummy2 服务的流量分配](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/bts-svc-mesh-impl-istio/img/Figure_13.05_B17989.jpg)

        图 A.5 – envoydummy2 服务的流量分配

        以下截图显示了`envoydummy`的流量分配:

        ![图 A.6 – envoydummy 服务的流量分配](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/bts-svc-mesh-impl-istio/img/Figure_13.06_B17989.jpg)

        图 A.6 – envoydummy 服务的流量分配

        现在`ServiceSplitter`配置已经到位,测试我们服务的流量是否按照配置文件中指定的比例进行路由:
% for ((i=0;i<10;i++)); do kubectl exec -it pod/curl -n appendix-consul -- curl http://envoydummy/new ;done
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
V1----------Bootstrap Service Mesh Implementation with Istio----------V1
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
V1----------Bootstrap Service Mesh Implementation with Istio----------V1
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
        你将观察到,流量在两个服务之间按 20:80 的比例进行路由。`ServiceSplitter`是一个强大的功能,可以用于 A/B 测试、金丝雀发布以及蓝绿部署。通过使用`ServiceSplitter`,你还可以在同一服务的不同子集之间进行基于权重的路由。它还允许你在路由服务时添加 HTTP 头。你可以在[`developer.hashicorp.com/consul/docs/connect/config-entries/service-splitter`](https://developer.hashicorp.com/consul/docs/connect/config-entries/service-splitter)了解更多关于`ServiceSplitter`的信息。

        我们已经了解了 Consul 服务发现链中的三个步骤中的两个,最后一个步骤是解析,我们将在下一节中讲解。

        解析

        Consul 还有另一种配置类型叫做`ServiceResolver`,用于定义哪些服务实例映射到客户端请求的服务名称。它们控制服务发现,并决定请求最终路由到哪里。通过使用`ServiceResolver`,你可以通过将请求路由到健康的上游来控制系统的弹性。`ServiceResolver`可以在服务跨多个数据中心时分发负载,并在服务出现故障时提供故障转移。关于`ServiceResolver`的更多细节可以在[`developer.hashicorp.com/consul/docs/connect/config-entries/service-resolver`](https://developer.hashicorp.com/consul/docs/connect/config-entries/service-resolver)找到。

        Consul 服务网格还为网关提供了管理来自网格外部流量的功能。它支持三种类型的网关:

            +   **网格网关**用于启用并保护数据中心之间的通信。它充当代理,提供对服务网格的入口,同时使用 mTLS 来保护流量。网格网关用于在不同数据中心和/或 Kubernetes 集群中部署的 Consul 服务网格实例之间进行通信。关于网格网关的一个很好的实践练习可以在[`developer.hashicorp.com/consul/tutorials/kubernetes/kubernetes-mesh-gateways`](https://developer.hashicorp.com/consul/tutorials/kubernetes/kubernetes-mesh-gateways)找到。

            +   **入口网关**用于为网格内的服务提供对外部客户端的访问。客户端可以位于同一个 Kubernetes 集群内,也可以完全位于集群外部,但在组织的网络边界内或边界外。你可以在[`developer.hashicorp.com/consul/docs/k8s/connect/ingress-gateways`](https://developer.hashicorp.com/consul/docs/k8s/connect/ingress-gateways)了解更多关于入口网关的内容。

            +   `ServiceDefault`。这里定义了外部服务的详细信息,终结网关会引用这些信息。你可以在[`developer.hashicorp.com/consul/docs/k8s/connect/terminating-gateways`](https://developer.hashicorp.com/consul/docs/k8s/connect/terminating-gateways)了解更多关于终结网关的内容。

        最后,Consul 服务网格还提供了对网格的全面可观察性。Sidecar 代理收集并暴露通过网格流量的数据。Sidecar 代理暴露的度量数据随后由 Prometheus 抓取。数据包括第七层度量,如 HTTP 状态码、请求延迟和吞吐量。Consul 控制平面还提供一些度量数据,如配置同步状态、异常和错误,类似于 Istio 控制平面。可观察性技术栈也类似于 Istio;像 Istio 一样,Consul 也支持与各种其他可观察性工具的集成,如 Datadog,以便深入了解 Consul 服务网格的健康状况和性能。你可以在 [`developer.hashicorp.com/consul/tutorials/kubernetes/kubernetes-layer7-observability`](https://developer.hashicorp.com/consul/tutorials/kubernetes/kubernetes-layer7-observability) 上了解更多关于 Consul 服务网格可观察性的内容。

        我希望这一部分简要但信息丰富地介绍了 Consul 服务网格是如何工作的,Consul 服务网格中的各种构件是什么,以及它们是如何操作的。我相信你一定注意到 Consul 服务网格和 Istio 之间的相似之处:它们都使用 Envoy 作为 sidecar 代理,Consul 服务发现链与 Istio 的虚拟服务和目标规则非常相似;Consul 服务网格网关与 Istio 网关非常相似。主要区别在于控制平面的实现方式以及每个集群节点上使用代理的方式。Consul 服务网格可以在虚拟机上运行,为传统工作负载提供服务网格的优势。Consul 服务网格由 HashiCorp 提供支持,并与 HashiCorp 的其他产品(包括 HashiCorp Vault)紧密集成。它也作为一种免费增值产品提供。对于需要企业支持的组织,还有一个企业版本,并且有一个名为 HCP Consul 的 SaaS 提供服务,为希望一键式网格部署的客户提供完全托管的云服务。

        卸载 Consul 服务网格

        你可以使用 consul-k8s 卸载 Consul 服务网格,使用以下命令:

        `% consul-k8s uninstall -auto-approve=true -wipe-data=true`

        `..`

        `删除安装数据:`

        `名称:consul`

        `命名空间 consul`

        `✓` `已删除 PVC => data-consul-consul-server-0`

        `✓` `PVCs 已删除。`

        `✓` `已删除密钥 => consul-bootstrap-acl-token`

        `✓` `Consul 密钥已删除。`

        `✓` `已删除服务帐户 => consul-tls-init`

        `✓` `Consul 服务帐户已删除。`

        你可以通过 macOS 上的 Brew 卸载 consul-k8s CLI:

        `% brew` `uninstall consul-k8s`

        Gloo Mesh

        Gloo Mesh 是 Solo.io 提供的服务网格产品。它有一个开源版本,叫做 Gloo Mesh,还有一个企业版,叫做 Gloo Mesh Enterprise。两者都基于 Istio 服务网格,并声称在开源 Istio 的基础上提供了更好的控制平面和附加功能。Solo.io 在其官网提供了一个功能对比,概述了 Gloo Mesh Enterprise、Gloo Mesh Open Source 和 Istio 之间的差异,您可以在 [`www.solo.io/products/gloo-mesh/`](https://www.solo.io/products/gloo-mesh/) 访问该对比内容。Gloo Mesh 主要专注于提供一个 Kubernetes 原生管理平面,用户可以通过它配置和操作多个集群中的多个异构服务网格实例。它提供一个 API,抽象了管理和操作多个网格的复杂性,用户无需了解多个服务网格下的复杂实现。您可以在 [`docs.solo.io/gloo-mesh-open-source/latest/getting_started/`](https://docs.solo.io/gloo-mesh-open-source/latest/getting_started/) 获取有关 Gloo Mesh 的详细信息,这是一个关于如何安装和试用 Gloo Mesh 的全面资源。Solo.io 还提供另一款产品,叫做 Gloo Edge,它既是一个 Kubernetes Ingress 控制器,又是一个 API 网关。Gloo Mesh Enterprise 与 Gloo Edge 一起部署,后者提供了许多全面的 API 管理和 Ingress 功能。Gateway Gloo Mesh Enterprise 增加了使用 OIDC、OAuth、API 密钥、LDAP 和 OPA 的外部身份验证支持。这些策略通过一个自定义 CRD,称为 ExtAuthPolicy 来实现,当路由和目标匹配某些条件时,可以应用这些身份验证。

        Gloo Mesh Enterprise 提供 WAF 策略,用于监控、过滤和阻止任何有害的 HTTP 流量。它还通过对 Envoy 记录的响应体和内容执行一系列正则替换,提供数据丢失防护(DLP)支持。这是一个从安全角度来看非常重要的功能,可以防止敏感数据被记录到日志文件中。DLP 过滤器可以在监听器、虚拟服务和路由上进行配置。Gloo Mesh 还提供支持,通过 SOAP 消息格式连接到传统应用程序。提供了构建数据转换策略的选项,应用 XSLT 转换来现代化 SOAP/XML 端点。这些数据转换策略可以用于转换请求或响应负载。它还支持通过 Inja 模板进行特殊转换。使用 Inja,你可以编写循环、条件逻辑以及其他函数来转换请求和响应。

        还广泛支持 WASM 过滤器。Solo.io 提供了定制的工具,加速 WebAssembly 的开发和部署。为了存储 WASM 文件,Solo.io 提供了 WebAssembly Hub,网址是[`webassemblyhub.io/`](https://webassemblyhub.io/),还有一个名为 wasme 的开源 CLI 工具。你可以在[`docs.solo.io/web-assembly-hub/latest/tutorial_code/getting_started/`](https://docs.solo.io/web-assembly-hub/latest/tutorial_code/getting_started/)了解更多关于如何使用 WebAssembly Hub 和 wasme CLI 的信息。

        由于 Gloo Mesh 和 Solo.io 的其他产品与企业服务网格(Enterprise Service Mesh)紧密集成,您可以获得许多其他功能,其中之一就是全球 API 门户。API 门户是一个自我发现门户,用于发布、共享和监控 API 的使用情况,支持内部和外部的盈利化。在使用多异构网格时,用户无需担心为每个网格管理可观察性工具;相反,Gloo Mesh Enterprise 通过每个网格提供汇总的度量数据,提供了管理和观察多个网格的无缝体验。

        在企业环境中,多个团队和用户能够访问和部署网格中的服务,而不会互相干扰是非常重要的。用户需要知道可以消费哪些服务以及他们已经发布了哪些服务。用户应该能够自信地执行网格操作,而不影响其他团队的服务。Gloo Mesh 使用工作空间的概念,工作空间是为团队设定的逻辑边界,限制团队在工作空间内执行服务网格操作,这样多个团队可以同时使用网格。工作空间提供了每个团队发布的配置之间的安全隔离。通过工作空间,Gloo Mesh 解决了企业环境中多租户的复杂性,使多个团队可以在彼此配置隔离的情况下,安全地使用网格,并实现严格的访问控制。

        Gloo Mesh 还与基于不同架构的另一个服务网格(与 Istio 不同)进行了集成。这个网格叫做 Istio Ambient Mesh,它不是为每个工作负载添加侧车代理,而是在每个节点级别添加代理。Istio Ambient Mesh 与 Gloo Mesh 集成,用户可以同时运行基于侧车代理的网格和每个节点代理的 Istio Ambient Mesh。

        Gloo Enterprise Mesh 与 Solo.io 产品(如 Gloo Edge)的集成,使其成为服务网格解决方案中的强有力竞争者。它支持多集群和多网格部署,通过工作空间实现多租户,强大的身份验证支持,零信任网络,以及通过 Gloo Edge 成熟的 Ingress 管理,使其成为一个全面的服务网格解决方案。

        Kuma

        Kuma 是一个开源的 CNCF 沙箱项目,由 Kong Inc.捐赠给 CNCF。像 Istio 一样,Kuma 也使用 Envoy 作为数据平面。它支持多集群和多网格部署,提供一个全球控制平面来管理所有部署。在撰写本书时,Kuma 是一个用 GoLang 编写的单一可执行文件。它可以在 Kubernetes 上部署,也可以在虚拟机上部署。当在非 Kubernetes 环境中部署时,它需要一个 PostgreSQL 数据库来存储其配置。

        让我们先下载并安装 Kuma,然后进行相关的动手操作:

            1.  下载适用于你操作系统的 Kuma:

```
% curl -L https://kuma.io/installer.sh | VERSION=2.0.2 sh -
INFO Welcome to the Kuma automated download!
INFO Kuma version: 2.0.2
INFO Kuma architecture: arm64
INFO Operating system: Darwin
INFO Downloading Kuma from: https://download.konghq.com/mesh-alpine/kuma-2.0.2-darwin-arm64.tar.gz
```

            1.  在 minikube 上安装 Kuma。解压下载的文件,在解压文件夹的`bin`目录中运行以下命令,将 Kuma 安装到 Kubernetes 中:

```
% kumactl install control-plane | kubectl apply -f -
```

        这将创建一个名为`kuma-system`的命名空间,并在该命名空间中安装 Kuma 控制平面,同时配置各种 CRD 和准入控制器。

            1.  此时,我们可以使用以下命令访问 Kuma 的图形用户界面(GUI):

```
% kubectl port-forward svc/kuma-control-plane -n kuma-system 5681:5681
```

        在浏览器中打开`localhost:5681/gui`,你将看到以下仪表板:

        ![图 A.7 – Kuma 仪表板](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/bts-svc-mesh-impl-istio/img/Figure_13.07_B17989.jpg)

        图 A.7 – Kuma 仪表板

        Kuma GUI 提供了关于网格的全面详细信息。我们将使用它来检查配置,当我们构建策略并将应用程序添加到网格中时。在 GUI 的主页上,你会注意到显示了一个名为**default**的网格。Kuma 中的网格是一个与其他 Kuma 网格逻辑隔离的服务网格。你可以在一个 Kubernetes 集群中安装 Kuma,然后管理多个服务网格,也许为每个团队或部门部署其应用程序。这个概念非常重要,并且是 Kuma 与其他服务网格技术的一个关键区分点。

        在 Kuma 网格中部署 envoydemo 和 curl

        部署文件可以在`AppendixA/Kuma/envoy-proxy-01.yaml`找到。与 Istio 的部署文件相比,明显的区别是添加了以下标签,它指示 Kuma 将其 sidecar 代理注入到`envoydummy`中:
        kuma.io/sidecar-injection: enabled
        以下命令将部署`envoydummy`和`curl`应用程序:
% kubectl create ns appendix-kuma
namespace/appendix-kuma created
% kubectl apply -f AppendixA/Kuma/envoy-proxy-01.yaml
configmap/envoy-dummy created
service/envoydummy created
deployment.apps/envoydummy created
serviceaccount/envoydummy created
        几秒钟后,使用以下命令检查 Pods 是否已部署并且 sidecar 已注入:
% kubectl get po -n appendix-kuma
NAME                          READY   STATUS    RESTARTS   AGE
curl                          2/2     Running   0          71s
envoydummy-767dbd95fd-tp6hr   2/2     Running   0          71s
serviceaccount/curl created
pod/curl created
        这些 sidecar 也叫做**数据平面代理**(**DPPs**),它们与网格中的每个工作负载一起运行。DPP 由定义 DPP 配置的数据平面实体和一个 kuma-dp 二进制文件组成。在启动过程中,kuma-dp 从 Kuma 控制平面(kuma-cp)检索 Envoy 的启动配置,并使用该配置启动 Envoy 进程。一旦 Envoy 启动,它将通过 XDS 连接到 kuma-cp。kuma-dp 在启动时还会启动一个 core-dns 进程。

        值得注意的是,安装 Kuma 并部署应用程序非常简单。它非常直观,甚至对于初学者来说也很容易上手。

        使用 GUI,让我们检查网格的整体状态。

        从**MESH** | **概览**中,你可以看到新添加的 DPP:

        ![图 A.8 – Kuma GUI 中的网格概览](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/bts-svc-mesh-impl-istio/img/Figure_13.08_B17989.jpg)

        图 A.8 – Kuma GUI 中的网格概览

        从**MESH** | **数据平面代理**,你可以找到关于工作负载的详细信息:

        ![图 A.9 – 数据平面代理](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/bts-svc-mesh-impl-istio/img/Figure_13.09_B17989.jpg)

        图 A.9 – 数据平面代理

        现在我们已经安装了应用程序,我们将进行一些关于 Kuma 策略的动手操作,来体验 Kuma 的使用。

        我们将首先从`curl` Pod 访问`envoydummy`服务:
% kubectl exec -it pod/curl -n appendix-kuma -- curl http://envoydummy:80
V1----------Bootstrap Service Mesh Implementation with Istio----------V1%
        输出结果符合预期。默认情况下,Kuma 允许网格内外的流量。默认情况下,所有流量在网格中都是未加密的。我们将启用 mTLS 并拒绝网格内的所有流量,以建立零信任网络。首先,我们将删除允许网格内所有流量的策略,使用以下命令:
% kubectl delete trafficpermissions/allow-all-traffic
        `allow-all-traffic`是一个允许网格内所有流量的流量权限策略。前面的命令删除了该策略,从而限制了网格内的所有流量。

        接下来,我们将在网格内启用 mTLS 以启用安全通信,并让`kong-dp`通过比较服务身份与 DPP 证书来正确识别服务。如果没有启用 mTLS,Kuma 无法强制执行流量权限。以下策略启用默认网格中的 mTLS。它使用内建的 CA,但如果你希望使用外部 CA,也可以提供外部生成的根 CA 和密钥。Kuma 会为每个工作负载自动生成证书,并以 SPIFEE 格式的 SAN 进行配置。
apiVersion: kuma.io/v1alpha1
kind: Mesh
metadata:
  name: default
spec:
  mtls:
    enabledBackend: ca-1
    backends:
    - name: ca-1
      type: builtin
        在配置文件中,我们定义了该策略适用于`default`网格。我们声明了一个名为`ca-1`的 CA,它是`builtin`类型,并将其配置为作为 mTLS 的根 CA,定义`enabledBackend`。配置文件位于`AppendixA/Kuma/enablemutualTLS.yaml`。你可以使用以下命令应用配置:
% kubectl apply -f AppendixA/Kuma/enablemutualTLS.yaml
        启用 mTLS 后,我们来尝试从`curl`访问`envoydummy`:
% kubectl exec -it pod/curl -n appendix-kuma -- curl http://envoydummy:80
curl: (52) Empty reply from server
command terminated with exit code 52
        输出结果符合预期,因为启用了 mTLS,且没有`TrafficPermission`策略允许`curl`与`envoydummy`之间的流量。

        为了允许流量,我们需要创建以下`TrafficPermission`策略:
apiVersion: kuma.io/v1alpha1
kind: TrafficPermission
mesh: default
metadata:
  name: allow-all-traffic-from-curl-to-envoyv1
spec:
  sources:
    - match:
        kuma.io/service: 'curl_appendix-kuma_svc'
  destinations:
    - match:
        kuma.io/service: 'envoydummy_appendix-kuma_svc_80'
        请注意,`kuma.io/service`字段包含对应标签的值。标签是一组键值对,包含 DPP 所属服务的详细信息和该服务的元数据。以下是应用于`envoydummy`的 DPP 标签:
% kubectl get dataplane/envoydummy-767dbd95fd-tp6hr -n appendix-kuma -o json | jq '.spec.networking.inbound[].tags'
{
  "k8s.kuma.io/namespace": "appendix-kuma",
  "k8s.kuma.io/service-name": "envoydummy",
  "k8s.kuma.io/service-port": "80",
  "kuma.io/protocol": "http",
  "kuma.io/service": "envoydummy_appendix-kuma_svc_80",
  "name": "envoydummy",
  "pod-template-hash": "767dbd95fd",
  "version": "v1"
}
        类似地,你可以获取`curl` DPP 的值。配置文件位于`AppendixA/Kuma/allow-traffic-curl-to-envoyv1.yaml`。使用以下命令应用配置:
% kubectl apply -f AppendixA/Kuma/allow-traffic-curl-to-envoyv1.yaml
trafficpermission.kuma.io/allow-all-traffic-from-curl-to-envoyv1 created
        应用配置后,测试你是否可以从`curl`访问`envoydummy`:
% kubectl exec -it pod/curl -n appendix-kuma -- curl http://envoydummy:80
V1----------Bootstrap Service Mesh Implementation with Istio----------V1%
        我们刚刚体验了如何在网格中控制工作负载之间的流量。你会发现这与 Consul 服务网格中的`ServiceIntentions`非常相似。

        流量管理与路由

        现在我们将探讨 Kuma 中的流量路由。我们将部署`envoydummy`服务的 v2 版本,并在版本 v1 和 v2 之间路由特定的请求。

        第一步是部署`envoydummy`的 v2 版本,然后定义流量权限以允许`curl` Pod 与`envoydummy` v2 Pod 之间的流量。文件位于`AppendixA/Kuma/envoy-proxy-02.yaml`和`AppendixA/Kuma/allow-traffic-curl-to-envoyv2.yaml`。应用配置后,测试`curl`是否能够访问`envoydummy`的 v1 和 v2 Pod:
% for ((i=0;i<2;i++)); do kubectl exec -it pod/curl -n appendix-kuma -- curl http://envoydummy ;done
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
V1----------Bootstrap Service Mesh Implementation with Istio----------V1
        接下来,我们将通过使用名为`TrafficRoute`的 Kuma 策略来配置路由。此策略允许我们为网格中的流量路由配置规则。

        该策略可以分为四个部分,便于理解:

            1.  在第一部分,我们声明了`TrafficRoute`策略。该策略的基本用法已在[`kuma.io/docs/2.0.x/policies/traffic-route/`](https://kuma.io/docs/2.0.x/policies/traffic-route/)文档中记录。在此,我们声明该策略适用于默认网格,以及来自`curl_appendix-kuma_svc`并以`envoydummy_appendix-kuma_svc_80`为目标的任何请求:

```
apiVersion: kuma.io/v1alpha1
kind: TrafficRoute
mesh: default
metadata:
  name: trafficroutingforlatest
spec:
  sources:
    - match:
        kuma.io/service: curl_appendix-kuma_svc
  destinations:
    - match:
        kuma.io/service: envoydummy_appendix-kuma_svc_80
```

            1.  接下来,我们将配置任何带有`'/latest'`前缀的请求,按照`destination`下标出的标签路由到 DPP:

```
 conf:
    http:
    - match:
        path:
          prefix: "/latest"
      destination:
        kuma.io/service: envoydummy_appendix-kuma_svc_80
        version: 'v2'
```

            1.  然后,我们将配置带有`'/old'`前缀的请求,按照`destination`下标出的标签路由到数据平面:

```
    - match:
        path:
          prefix: "/old"
      destination:
        kuma.io/service: envoydummy_appendix-kuma_svc_80
        version: 'v1'
```

            1.  最后,我们声明了未匹配前面配置部分中任何路径的请求的默认目标。默认目标将是带有以下代码中高亮标签的 DPP:

```
    destination:
      kuma.io/service: envoydummy_appendix-kuma_svc_80
```

        配置文件位于`AppendixA/Kuma/trafficRouting01.yaml`。应用配置后,测试以下场景:

            +   所有带有`'/latest'`前缀的请求应该路由到版本 v2:

```
% for ((i=0;i<4;i++)); do kubectl exec -it pod/curl -n appendix-kuma -- curl http://envoydummy/latest ;done
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
```

            +   所有带有`'/old'`前缀的请求应路由到版本 v1:

```
% for ((i=0;i<4;i++)); do kubectl exec -it pod/curl -n appendix-kuma -- curl http://envoydummy/old ;done
V1----------Bootstrap Service Mesh Implementation with Istio----------V1
V1----------Bootstrap Service Mesh Implementation with Istio----------V1
V1----------Bootstrap Service Mesh Implementation with Istio----------V1
V1----------Bootstrap Service Mesh Implementation with Istio----------V1
```

            +   所有其他请求应遵循默认行为:

```
% for ((i=0;i<4;i++)); do kubectl exec -it pod/curl -n appendix-kuma -- curl http://envoydummy/xyz ;done
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
V2----------Bootstrap Service Mesh Implementation with Istio----------V2
V1----------Bootstrap Service Mesh Implementation with Istio----------V1
V1----------Bootstrap Service Mesh Implementation with Istio----------V1
```

        请求路由按预期工作,类似于您使用 Istio 配置相同行为的方式。现在,让我们看看 Kuma Mesh 的负载均衡特性。我们将构建另一个流量路由策略,在`envoydummy`的 v1 和 v2 版本之间进行加权路由。以下是`AppendixA/Kuma/trafficRouting02.yaml`配置的一个片段:
  conf:
    split:
      - weight: 10
        destination:
          kuma.io/service: envoydummy_appendix-kuma_svc_80
          version: 'v1'
      - weight: 90
        destination:
          kuma.io/service: envoydummy_appendix-kuma_svc_80
          version: 'v2'
        应用配置后,您可以使用以下命令测试流量分配:
% for ((i=0;i<10;i++)); do kubectl exec -it pod/curl -n appendix-kuma -- curl http://envoydummy/xyz ;done
        流量应以大约 1:9 的比例在两个版本之间分配。您可以使用`TrafficRoute`策略进行流量路由、流量修改、流量拆分、负载均衡、金丝雀发布和感知区域的负载均衡。要了解更多关于`TrafficRoute`的信息,请查阅此处的完整文档:https://kuma.io/docs/2.0.x/policies/traffic-route。

        Kuma 还提供了用于断路器、故障注入、超时、速率限制等的策略。Kuma 策略的完整列表可以在这里查看:[`kuma.io/docs/2.0.x/policies/introduction/`](https://kuma.io/docs/2.0.x/policies/introduction/)。这些开箱即用的策略使得 Kuma 非常易于使用,学习曲线也非常浅。

        在到目前为止的实践示例中,我们一直在默认网格中部署所有工作负载。我们之前讨论过,Kuma 允许你创建不同的隔离网格,使得团队可以在同一个 Kuma 集群中拥有隔离的网格环境。你可以使用以下配置创建一个新网格:
apiVersion: kuma.io/v1alpha1
kind: Mesh
metadata:
  name: team-digital
        配置可以在 `AppendixA/Kuma/team-digital-mesh.yaml` 中找到。使用以下命令应用该配置:
% kubectl apply -f AppendixA/Kuma/team-digital-mesh.yaml
mesh.kuma.io/team-digital created
        一旦你创建了网格,你可以通过在工作负载部署配置中添加以下注释来创建网格中的所有资源:
kuma.io/mesh: team-digital
        并将以下内容添加到 Kuma 策略中:
mesh: team-digital
        创建网格的能力是企业环境中非常有用的功能,也是 Kuma 与 Istio 区别的一个关键点。

        Kuma 还提供了内置的 Ingress 能力来处理南北向流量以及东西向流量。Ingress 被管理为一个名为网关的 Kuma 资源,网关本身是 kuma-dp 的一个实例。你可以灵活地部署任意数量的 Kuma 网关,但理想情况下,建议每个网格部署一个网关。Kuma 还支持与非 Kuma 网关集成,也称为委托网关。现在,我们将讨论内置的 Kuma 网关,稍后会简要讨论委托网关。

        要创建内置网关,首先需要定义 `MeshGatewayInstance` 和匹配的 `MeshGateway`。`MeshGatewayInstance` 提供了网关实例如何实例化的详细信息。以下是 `MeshGatewayInstance` 的示例配置,该配置也可以在 `AppendixA/Kuma/envoydummyGatewayInstance01.yaml` 中找到:
apiVersion: kuma.io/v1alpha1
kind: MeshGatewayInstance
metadata:
  name: envoydummy-gateway-instance
  namespace: appendix-kuma
spec:
  replicas: 1
  serviceType: LoadBalancer
  tags:
    kuma.io/service: envoydummy-edge-gateway
        在配置中,我们设置了将有 `1 个副本` 和 `LoadBalancer` 类型的服务,并且我们应用了一个标签 `kuma.io/service: envoydummy-edge-gateway`,该标签将用于与 `MeshGateway` 构建关联。

        在以下配置中,我们正在创建一个名为 `envoydummy-edge-gateway` 的 `MeshGateway`。该配置可以在 `AppendixA/Kuma/envoydummyGateway01.yaml` 中找到:
apiVersion: kuma.io/v1alpha1
kind: MeshGateway
mesh: default
metadata:
  name: envoydummy-edge-gateway
  namespace: appendix-kuma
spec:
  selectors:
  - match:
      kuma.io/service: envoydummy-edge-gateway
  conf:
    listeners:
      - port: 80
        protocol: HTTP
        hostname: mockshop.com
        tags:
          port: http/80
        `MeshGateway` 资源指定了监听器,它们是接收网络流量的端点。在配置中,你需要指定端口、协议和一个可选的主机名。在 `selectors` 下,我们还指定了与 `MeshGateway` 配置相关联的 `MeshGatewayInstance` 标签。请注意,我们指定的是在 `MeshGatewayInstance` 配置中定义的相同标签。

        接下来,我们将定义`MeshGatewayRoute`,它描述了请求如何从`MeshGatewayInstance`路由到工作负载服务。配置示例可在`AppendixA/Kuma/envoydummyGatewayRoute01.yaml`中找到。以下是文件中的一些片段:

            +   在`selectors`下,我们指定了此路由应附加到的网关和监听器的详细信息。通过提供相应网关和监听器的标签来指定详细信息:

```
spec:
  selectors:
    - match:
        kuma.io/service: envoydummy-edge-gateway
        port: http/80
```

            +   在`conf`部分,我们提供了请求的第 7 层匹配标准,例如路径和 HTTP 头信息,以及目标详细信息:

```
  conf:
    http:
      rules:
        - matches:
            - path:
                match: PREFIX
                value: /
          backends:
            - destination:
                kuma.io/service: envoydummy_appendix-kuma_svc_80
```

            +   最后但同样重要的是,我们通过配置`TrafficPermission`,允许边缘网关与 envoy 假服务之间的流量,如以下代码片段所述。你可以在`AppendixA/Kuma/allow-traffic-edgegateway-to-envoy.yaml`中找到该配置:

```
kind: TrafficPermission
mesh: default
metadata:
  name: allow-all-traffic-from-curl-to-envoyv1
spec:
  sources:
    - match:
        kuma.io/service: 'envoydummy-edge-gateway'
  destinations:
    - match:
        kuma.io/service: 'envoydummy_appendix-kuma_svc_80'
```

        在配置了流量权限之后,我们现在准备通过以下一组命令应用配置:

            1.  创建`MeshGatewayInstance`:

```
% kubectl apply -f AppendixA/Kuma/envoydummyGatewayInstance01.yaml
meshgatewayinstance.kuma.io/envoydummy-gateway-instance created
```

            1.  创建`MeshGateway`:

```
% kubectl apply -f AppendixA/Kuma/envoydummyGateway01.yaml
meshgateway.kuma.io/envoydummy-edge-gateway created
```

            1.  创建`MeshGatewayRoute`:

```
% kubectl apply -f AppendixA/Kuma/envoydummyGatewayRoute01.yaml
meshgatewayroute.kuma.io/envoydummy-edge-gateway-route created
```

            1.  创建`TrafficPermissions`:

```
$ kubectl apply -f AppendixA/Kuma/allow-traffic-edgegateway-to-envoy.yaml
trafficpermission.kuma.io/allow-all-traffic-from-curl-to-envoyv1 configured
```

        你可以通过以下命令验证 Kuma 是否已创建网关实例:
% kubectl get po -n appendix-kuma
NAME                         READY   STATUS    RESTARTS   AGE
curl                         2/2     Running   0          22h
envoydummy-767dbd95fd-br2m6                    2/2     Running   0          22h
envoydummy-gateway-instance-75f87bd9cc-z2rx6   1/1     Running   0          93m
envoydummy2-694cbc4f7d-hrvkd                   2/2     Running   0          22h
        你也可以使用以下命令检查相应的服务:
% kubectl get svc -n appendix-kuma
NAME                  TYPE   CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
envoydummy       ClusterIP      10.102.50.112   <none>        80/TCP         22h
envoydummy-gateway-instance   LoadBalancer   10.101.49.118   <pending>     80:32664/TCP   96m
        现在我们已经准备好使用内置的 Kuma 网关访问`envoydummy`。但首先,我们需要找到一个 IP 地址,通过该地址我们可以访问 minikube 上的 Ingress 网关服务。使用以下命令来查找 IP 地址:
% minikube service envoydummy-gateway-instance --url -n appendix-kuma
http://127.0.0.1:52346
        现在,通过使用 http://127.0.0.1:52346,你可以通过在终端执行`curl`访问`envoydummy`服务:
% curl -H "host:mockshop.com" http://127.0.0.1:52346/latest
V1----------Bootstrap Service Mesh Implementation with Istio----------V1
        你已经学习了如何创建一个`MeshGatewayInstance`,它随后与`MeshGateway`关联。关联后,kuma-cp 创建了一个内置 Kuma 网关的网关实例。接着,我们创建了一个`MeshGatewayRoute`,指定了如何将请求从网关路由到工作负载服务。随后,我们创建了一个`TrafficPermission`资源,允许从`MeshGateway`到`EnvoyDummy`工作负载的流量。

        Kuma 还提供了将外部网关用作 Ingress 的选项,也叫做委托网关。在委托网关中,Kuma 支持与各种 API 网关的集成,但 Kong Gateway 是首选且文档最完善的选项。你可以在[`kuma.io/docs/2.0.x/explore/gateway/#delegated`](https://kuma.io/docs/2.0.x/explore/gateway/#delegated)阅读有关委托网关的更多内容。

        与 Istio 类似,Kuma 也原生支持 Kubernetes 和基于虚拟机的工作负载。Kuma 为在多个 Kubernetes 集群、数据中心和云提供商之间运行复杂配置的服务网格提供了广泛的支持。Kuma 有一个区域的概念,区域是可以相互通信的 DPP(数据平面代理)的逻辑聚合。Kuma 支持在多个区域中运行服务网格,并支持多区域部署中控制平面的分离。每个区域都会分配一个水平可扩展的控制平面,从而实现区域之间的完全隔离。所有区域还由一个集中式的全球控制平面进行管理,后者负责管理应用于 DPP 的策略的创建和更改,并将特定区域的策略和配置传输到各自区域的控制平面。全球控制平面提供了一个单一视窗,展示所有区域中的所有 DPP 的清单。

        如前所述,Kuma 是一个开源项目,由 Kong 捐赠给 CNCF。Kong 还提供了 Kong Mesh,这是一个基于 Kuma 的企业版,扩展了 Kuma,包含了运行企业级工作负载所需的功能。Kong Mesh 提供了一个即插即用的服务网格解决方案,具有与 OPA 集成、FIPS 140-2 合规性和基于角色的访问控制等功能。结合 Kong Gateway 作为入口网关,基于 Kuma 的服务网格、额外的企业级插件以及可靠的企业支持,使得 Kong Mesh 成为一项即插即用的服务网格技术。

        卸载 Kuma

        你可以使用以下命令卸载 Kuma Mesh:

        `% kumactl install control-plane | kubectl delete -``f -`

        Linkerd

        Linkerd 是一个 CNCF 毕业项目,采用 Apache v2 许可证。Buoyant ([`buoyant.io/`](https://buoyant.io/)) 是 Linkerd 的主要贡献者。在所有服务网格技术中,Linkerd 可能是最早的之一,甚至可能是最老的。它最初由 Buoyant 在 2017 年公开发布。虽然它一开始取得了一定的成功,但后来因为资源消耗过大而受到批评。Linkerd 中使用的代理是用 Scala 和 Java 网络生态系统编写的,运行时使用 **Java 虚拟机**(**JVM**),导致显著的资源消耗。2018 年,Buoyant 发布了 Linkerd 的新版本,名为 Conduit。Conduit 后来被重新命名为 Linkerd v2。Linkerd v2 数据平面由 Linkerd2-proxy 组成,它是用 Rust 编写的,资源消耗较小。Linkerd2-proxy 专门为 Kubernetes Pods 中的 sidecar 代理而构建。虽然 Linkerd2-proxy 是用 Rust 编写的,但 Linkerd 控制平面是用 Golang 开发的。

        与本文 *附录* 中讨论的其他开源服务网格技术一样,我们将通过实际操作 Linkerd,观察它与 Istio 的相似与不同之处。让我们从在 minikube 上安装 Linkerd 开始:

            1.  使用以下命令在 minikube 上安装 Linkerd:

```
% curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install | sh
Downloading linkerd2-cli-stable-2.12.3-darwin...
Linkerd stable-2.12.3 was successfully installed
Add the linkerd CLI to your path with:
  export PATH=$PATH:/Users/arai/.linkerd2/bin
```

            1.  按照建议将 linkerd2 添加到你的路径中:

```
export PATH=$PATH:/Users/arai/.linkerd2/bin
```

            1.  Linkerd 提供了一项检查和验证 Kubernetes 集群是否满足安装 Linkerd 所需所有先决条件的选项:

```
% linkerd check --pre
```

            1.  如果输出包含以下内容,那么安装就成功了:

```
Status check results are √
```

        如果没有,您需要通过以下链接中的建议解决问题:[`linkerd.io/2.12/tasks/troubleshooting/#pre-k8s-cluster-k8s%20for%20hints`](https://linkerd.io/2.12/tasks/troubleshooting/#pre-k8s-cluster-k8s%20for%20hints)。

            1.  接下来,我们将分两步安装 Linkerd:

1.  首先,我们安装 CRDs:

```
linkerd namespace:
```
% linkerd install --set proxyInit.runAsRoot=true | kubectl apply -f -
            安装控制平面后,请使用以下命令检查 Linkerd 是否完全安装:
% linkerd check
        如果 Linkerd 安装成功,你应该会看到以下消息:
Status check results are √
        这完成了 Linkerd 的安装!现在让我们分析一下已安装的内容:
% kubectl get pods,services -n linkerd
NAME                    READY   STATUS    RESTARTS   AGE
pod/linkerd-destination-86d68bb57-447j6       4/4     Running   0          49m
pod/linkerd-identity-5fbdcccbd5-lzfkj         2/2     Running   0          49m
pod/linkerd-proxy-injector-685cd5988b-5lmxq   2/2     Running   0          49m
NAME   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/linkerd-dst           ClusterIP    10.102.201.182   <none>        8086/TCP   49m
service/linkerd-dst-headless        ClusterIP   None             <none>        8086/TCP   49m
service/linkerd-identity        ClusterIP   10.98.112.229    <none>       8080/TCP   49m
service/linkerd-identity-headless       ClusterIP   None             <none>        8090/TCP   49m
service/linkerd-policy           ClusterIP    None             <none>        8090/TCP   49m
service/linkerd-policy-validator    ClusterIP   10.102.142.68    <none>        443/TCP    49m
service/linkerd-proxy-injector      ClusterIP   10.101.176.198   <none>        443/TCP    49m
service/linkerd-sp-validator        ClusterIP    10.97.160.235    <none>        443/TCP    49m
        这里需要注意的是,控制平面由多个 Pod 和服务组成。`linkerd-identity` 服务是用于生成 Linkerd 代理签名证书的 CA。`linkerd-proxy-injector` 是负责修改 Kubernetes Pod 规格以添加 linkerd-proxy 和 proxy-init 容器的 Kubernetes 审核控制器。`destination` 服务是 Linkerd 控制平面的“大脑”,它维护服务发现和关于服务的身份信息,以及管理和安全保障网格中流量的策略。

        在 Linkerd 中部署 envoydemo 和 curl

        现在让我们部署 envoydummy 和 curl 应用,并检查 Linkerd 如何执行服务网格功能。按照以下步骤安装应用:

            1.  和大多数服务网格解决方案一样,我们需要在部署描述符中添加以下注解:

```
      annotations:
        linkerd.io/inject: enabled
```

        `envoydummy` 和 `curl` 应用的配置文件和注解位于 `AppendixA/Linkerd/envoy-proxy-01.yaml`。

            1.  准备好部署描述符后,你可以应用配置:

```
% kubectl create ns appendix-linkerd
% kubectl apply -f AppendixA/Linkerd/envoy-proxy-01.yaml
```

            1.  这应该会部署 Pod。一旦 Pod 部署完成,你可以通过以下命令检查 Pod 中注入了什么内容:

```
% kubectl get po/curl -n appendix-linkerd -o json | jq '.spec.initContainers[].image, .spec.initContainers[].name'
"cr.l5d.io/linkerd/proxy-init:v2.0.0"
"linkerd-init"
% kubectl get po/curl -n appendix-linkerd -o json | jq '.spec.containers[].image, .spec.containers[].name'
"cr.l5d.io/linkerd/proxy:stable-2.12.3"
"curlimages/curl"
"linkerd-proxy"
"curl"
```

        从前面的输出中,可以观察到 Pod 初始化是由名为 `linkerd-init` 的容器执行的,容器类型为 `cr.l5d.io/linkerd/proxy-init:v2.0.0`,并且 Pod 中有两个正在运行的容器:`curl` 和 `linkerd-proxy`,它们的类型是 `cr.l5d.io/linkerd/proxy:stable-2.12.3`。`linkerd-init` 容器在 Pod 初始化阶段运行,并修改 iptables 规则,将所有网络流量从 `curl` 路由到 `linkerd-proxy`。如你所知,在 Istio 中,我们有 `istio-init` 和 `istio-proxy` 容器,它们与 Linkerd 容器类似。与 Envoy 相比,`linkerd-proxy` 极其轻量且速度极快。它使用 Rust 编写,这使其性能可预测,且不需要垃圾回收,垃圾回收通常会导致高延迟。Rust 在内存安全性方面比 C++ 和 C 更加优越,这使其不易受到内存安全漏洞的影响。你可以在 [`linkerd.io/2020/12/03/why-linkerd-doesnt-use-envoy/`](https://linkerd.io/2020/12/03/why-linkerd-doesnt-use-envoy/) 了解更多关于为什么 `linkerd-proxy` 比 envoy 更好的原因。

        验证 `curl` 是否能够与 `envoydummy` Pod 通信,如下所示:
% kubectl exec -it pod/curl -c curl -n appendix-linkerd -- curl http://envoydummy:80
V1----------Bootstrap Service Mesh Implementation with Istio----------V1%
        现在我们已经安装了 `curl` 和 `envoydummy` Pods,让我们来探索 Linkerd 服务网格的功能。首先,我们将探索如何使用 Linkerd 限制网格内的流量。

        零信任网络

        Linkerd 提供了全面的策略来限制网格中的流量。Linkerd 提供了一组 CRD,通过这些 CRD 可以定义策略来控制网格中的流量。让我们通过实施策略来控制访问 `envoydummy` Pod 的流量,来探索这些策略:

            1.  我们将首先使用以下命令限制集群中的所有流量:

```
% linkerd upgrade --default-inbound-policy deny --set proxyInit.runAsRoot=true | kubectl apply -f -
```

        我们使用 `linkerd upgrade` 命令应用了一个 `default-inbound-policy`,其值为 `deny`,这会禁止所有流量访问网格中工作负载暴露的端口,除非该端口附有服务器资源。

        应用策略后,所有访问 `envoydummy` 服务的请求将被拒绝:
% kubectl exec -it pod/curl -c curl -n appendix-linkerd -- curl --head http://envoydummy:80
HTTP/1.1 403 Forbidden
content-length: 0
l5d-proxy-error: unauthorized request on route
            1.  接下来,我们创建一个服务器资源来描述 `envoydummy` 端口。服务器资源是一种指示 Linkerd 仅授权的客户端才能访问该资源的方式。我们通过声明以下 Linkerd 策略来实现这一点:

```
apiVersion: policy.linkerd.io/v1beta1
kind: Server
metadata:
  namespace: appendix-linkerd
  name: envoydummy
  labels:
    name: envoydummy
spec:
  podSelector:
    matchLabels:
      name: envoydummy
  port: envoydummy-http
  proxyProtocol: HTTP/1
```

        配置文件位于 `AppendixA/Linkerd/envoydummy-server.yaml`。服务器资源在与工作负载相同的命名空间中定义。在配置文件中,我们还定义了以下内容:

            +   `podSelector`:选择工作负载的标准。

            +   `port`:声明此服务器配置的端口名称或端口号。

            +   `proxyProtocol`:配置入站连接的协议发现,必须是以下之一:unknown、`HTTP/1`、`HTTP/2`、`gRPC`、`opaque` 或 `TLS`。

        使用以下命令应用服务器资源:
% kubectl apply -f AppendixA/Linkerd/envoydummy-server.yaml
server.policy.linkerd.io/envoydummy created
        尽管我们已经应用了服务器资源,但 `curl` Pod 仍然无法访问 `envoydummy` 服务,除非我们授权它。

            1.  在这一步,我们将创建一个授权策略,授权`curl`访问`envoydummy`。授权策略是通过提供目标目的地的服务器详细信息和用于运行源服务的服务账户详细信息来配置的。我们在前一步中创建了一个名为`envoydummy`的服务器资源,并且根据`AppendixA/Linkerd/envoy-proxy-01.yaml`,我们使用名为`curl`的服务账户来运行`curl` Pod。该策略定义如下,并且也可以在`AppendixA/Linkerd/authorize-curl-access-to-envoydummy.yaml`中找到:

```
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
  name: authorise-curl
  namespace: appendix-linkerd
spec:
  targetRef:
    group: policy.linkerd.io
    kind: Server
    name: envoydummy
  requiredAuthenticationRefs:
    - name: curl
      kind: ServiceAccount
```

            1.  按如下方式应用配置:

```
% kubectl apply -f AppendixA/Linkerd/authorize-curl-access-to-envoydummy.yaml
authorizationpolicy.policy.linkerd.io/authorise-curl created
```

        一旦`AuthorizationPolicy`到位,它将授权所有来自任何使用`curl`服务账户运行的工作负载的流量访问 Envoy 服务器。

            1.  你可以使用以下命令验证`curl`和`envoydummy` Pods 之间的访问:

```
% kubectl exec -it pod/curl -c curl -n appendix-linkerd – curl  http://envoydummy:80
V1----------Bootstrap Service Mesh Implementation with Istio----------V1
```

        使用`AuthorizationPolicy`,我们控制了来自网格中其他客户端的端口访问。通过另一个名为`s`的策略,可以管理细粒度的访问控制,如控制对 HTTP 资源的访问。

        我们可以通过一个例子更好地理解这个概念,因此我们做一个要求:只有 URI 以`/dummy`开头的请求才能从`curl`访问;对任何其他 URI 的请求必须被拒绝。让我们开始吧:

            1.  我们首先需要定义一个`HTTPRoute`策略,如以下代码片段所示:

```
apiVersion: policy.linkerd.io/v1beta1
kind: HTTPRoute
metadata:
  name: envoydummy-dummy-route
  namespace: appendix-linkerd
spec:
  parentRefs:
    - name: envoydummy
      kind: Server
      group: policy.linkerd.io
      namespace: appendix-linkerd
  rules:
    - matches:
      - path:
          value: "/dummy/"
          type: "PathPrefix"
        method: GET
```

        配置也可以在`AppendixA/Linkerd/HTTPRoute.yaml`中找到。此配置将创建一个 HTTP 路由,目标是`envoydummy`服务器资源。在`rules`部分,我们定义了用于识别请求的标准,这些标准将用于识别该路由的 HTTP 请求。在这里,我们定义了一个规则来匹配任何带有`dummy`前缀和`GET`方法的请求。`HTTPRoute`还支持使用头部和查询参数进行路由匹配。你还可以在`HTTPRoute`中应用其他过滤器,以指定请求在请求或响应周期中应如何处理;例如,你可以修改传入请求头、重定向请求、修改请求路径等。

            1.  一旦定义了`HTTPRoute`,我们可以修改`AuthorizationPolicy`,使其关联到`HTTPRoute`而不是服务器,如以下代码片段所示,并且也可以在`AppendixA/Linkerd/HttpRouteAuthorization.yaml`中查看:

```
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
  name: authorise-curl
  namespace: appendix-linkerd
spec:
  targetRef:
    group: policy.linkerd.io
HTTPRoute
envoydummy-dummy-route
  requiredAuthenticationRefs:
    - name: curl
      kind: ServiceAccount
```

        配置更新了`AuthorizationPolicy`,并且不再将目标引用为服务器(在`AppendixA/Linkerd/authorize-curl-access-to-envoydummy.yaml`中配置的`envoydummy`),而是将策略现在引用为`HTTPRoute`(命名为`envoydummy-dummy-route`)。

        应用两个配置并测试,你是否能够对 URI 中以`/dummy`为前缀的请求进行操作。任何其他请求都将被 Linkerd 拒绝。

        到目前为止,在`AuthorizationPolicy`中我们使用了`ServiceAccount`认证。`AuthorizationPolicy`还支持`MeshTLSAuthentication`和`NetworkAuthentication`。以下是这些认证类型的简要概述:

            +   `MeshTLSAuthentication` 用于基于其网格身份标识客户端。例如,`curl` Pod 将表示为 `curl.appendix-linkerd.serviceaccount.identity.linkerd.local`。

            +   `NetworkAuthentication` 用于根据其网络位置使用 **无类域间路由** (**CIDR**) 块标识客户端。

        Linkerd 还提供重试和超时功能,以在系统承受压力或遭受部分故障时提供应用程序弹性。除了支持常规重试策略外,还提供了指定重试预算的选项,以确保重试不会加剧弹性问题。Linkerd 使用 **指数加权移动平均** (**EWMA**) 算法自动负载均衡到所有目标端点的请求。Linkerd 支持基于权重的流量分割,对于进行金丝雀部署和蓝/绿部署非常有用。Linkerd 的流量分割使用 **服务网格接口** (**SMI**) Traffic Split API,允许用户在蓝色和绿色服务之间逐步转移流量。您可以在 [`github.com/servicemeshinterface/smi-spec/blob/main/apis/traffic-split/v1alpha4/traffic-split.md`](https://github.com/servicemeshinterface/smi-spec/blob/main/apis/traffic-split/v1alpha4/traffic-split.md) 上了解 Traffic Split API,以及在 [`smi-spec.io`](https://smi-spec.io) 上了解 SMI。Linkerd 与 Flagger 的集成有明确定义和文档化,用于在进行金丝雀部署和蓝/绿部署时执行自动流量转移。

        还有很多关于 Linkerd 的内容需要学习和消化。您可以在 [`linkerd.io/2.12`](https://linkerd.io/2.12) 上阅读相关信息。Linkerd 由于其使用 Rust 构建的超轻量级服务代理而具有超高性能。它被精心设计用来解决应用程序网络问题。超轻量级代理执行大多数服务网格功能,但在诸如断路器和速率限制等功能方面欠缺。希望 Linkerd 的创建者能够弥补与 Envoy 的差距。

        希望您现在已经熟悉了 Istio 的各种替代方案以及它们如何实现服务网格。Consul、Linkerd、Kuma 和 Gloo Mesh 具有许多相似之处,它们都非常强大,但 Istio 是拥有最大社区支持和多个知名组织支持的服务网格之一。此外,有各种组织为 Istio 提供企业支持,在将 Istio 部署到生产环境时这是非常重要的考虑因素。

第十三章:索引

由于此电子书版本没有固定的页码,下面的页码仅供参考,基于本书的印刷版。

A

访问日志 293

用于故障排除错误 293,-298

激活 67

适配器 242

聚合发现服务(ADS) 85

警报

与 Grafana 196-200

应用程序二进制接口(ABI) 248

应用程序网络 16

应用程序弹性 127

带故障注入 128

带负载均衡 141

带重试 138-141

带超时 135-137

应用网格 31

认证

与网格外客户端 162-164

带互联网 TLS(mTLS) 157

AWS 故障注入模拟器

参考链接 128

AWS WAF 304

Azure Web 应用程序防火墙 304

B

蓝/绿部署 25-28

引导配置 77

Buoyant 372

URL 372

C

金丝雀部署 25-28

资本支出(CAPEX) 7

证书颁发机构(CA) 113, 213, 330

证书签名请求(CSR) 113, 154, 163, 331

认证课程

参考文献 336

认证 Istio 管理员 336

证书管理器 216

混沌工程 128

参考链接 128

混沌猴

参考链接 128

Cilium 338

断路器 148

参考链接 76

断路器 24

带异常检测 148-150

无类域间路由(CIDR)块 213,379

Cloud Armor 304

云计算 3-5

优势 6-8

基础设施即服务(IaaS)5

平台即服务(PaaS)5

软件即服务(SaaS)5

云原生计算基金会(CNCF)30

云服务

社区云 6

混合云 6

私有云 6

公有云 5

集群 70

集群管理员 306

集群发现服务(CDS)84

CNAME

HTTPS,启用 119-121

商用现成产品(COTS)269

通用名称(CN)330

社区云 6

Conduit 372

配置生成器 68

Consul 31

Consul Connect 341

Consul 服务网格 341

示例应用程序,部署 345,346

Ingress 网关 354

安装 341-345

网格网关 354

服务发现链 348

终止网关 354

流量管理 348

卸载 355

零信任网络 346-348

容器 13

控制平面 58

控制理论 179

ControlZ 接口 287,288

计数器 188

自定义资源(CR)65

自定义资源定义(CRDs)64,244,306

D

数据平面 58

自定义,使用 Envoy 过滤器 244-247

数据平面代理(DPPs)359

调试日志 298

更改,针对 Istio 数据平面 299

用于故障排除错误 298

延迟 129

委托网关 371

参考链接 371

Delta 聚合发现服务(DxDS) 85

部署模型,针对 Istio 控制平面 70

主集群,带单一控制平面 71, 72

远程集群,带单一控制平面 71, 72

单集群,带外部控制平面 72, 73

单集群,带本地控制平面 71

目标网络地址转换(DNAT) 20

维度

添加,至 Istio 指标 189-191

分布式追踪 200

启用,使用 Jaeger 201-208

实现 200

Docker

参考链接 77

Docker Hub 259

动态配置

通过 xDS APIs 83

E

eBPF 程序 337

出站流量

管理,使用 Istio 121-125

EKS 集群

创建 90, 91

弹性容器服务(ECS) 31

弹性 Kubernetes 服务(EKS) 5, 90

端点发现服务(EDS) 84

企业资源规划(ERP) 5

环境

设置 90

Envoy 74, 189, 271

架构 74, 75

自动重试 75

电路断开 76

配置 75, 76

通过 xDS APIs 的动态配置 83-85

探索 73

可扩展性 85, 86

全局速率限制 76

HTTP 过滤器 76-82

监听器过滤器 82, 83

异常值检测 76

参考链接 74

请求对冲 76

流量镜像 76

Envoy 属性

参考链接 192

Envoy 虚拟应用

部署 226-228

用于部署 235

用于测试 236

Envoy,扩展以支持自定义用例

参考链接 242

EnvoyFilter 配置

参考链接 247

Envoy 过滤器

用于定制数据平面 244-247

编写,选项 85, 86

Envoy HTTP Lua 过滤器

参考链接 243

Envoy 端口

探索 288

端口 15000 288

端口 15001 288

端口 15004 288

端口 15006 288

端口 15020 288

端口 15021 288

端口 15053 288

端口 15090 288

Envoy, 线程

文件刷新线程 74

主线程 74

工作线程 74

错误 181

故障排除,使用访问日志 293-298

故障排除,使用调试日志 298

EWMA 算法 379

扩展伯克利数据包过滤器 (eBPF) 337

可扩展性

需求 242, 243

扩展配置发现服务 (ECDS) 84, 262

参考链接 262

F

F5 NGINX 服务网格 31, 32

故障注入 128

使用,用于应用程序弹性 128

故障注入,Istio

HTTP 中止 132-134

HTTP 延迟 129-132

联邦信息处理标准 (FIPS) 73

文件刷新线程 74

过滤链 74

fortio 工具

参考链接 146

完全限定域名 (FQDN) 115

G

仪表 188

全球 API 门户 356

Gloo Mesh 356

参考链接 356

Google Cloud

基础设施,设置 265-268

Google Kubernetes Engine 服务

使用 212

Grafana 56, 57, 194

报警功能 196-200

Istio 控制平面仪表盘 195

Istio Mesh 仪表盘 196

Istio 性能仪表盘 196

Istio 服务和工作负载仪表盘 196

遥测,可视化 194

gRPC 75

URL 83

H

HashiCorp Vault 271

六边形架构 9

直方图 189

Homebrew

用于 minikube 安装 32-35

HTTP 中止 132

注入 132-134

HTTP 延迟

注入 129-132

HTTP 过滤器 76-80

参考链接 82

HTTPInspector 监听器过滤器

参考链接 82

HTTP Lua 过滤器 243

HTTP 重定向

启用,至 HTTPS 116, 117

HTTPS

启用,针对 CNAME 119-121

启用,针对多个主机 117-119

启用,针对通配符记录 119-121

HTTP 重定向,启用至 116, 117

Ingress,暴露至 113-116

混合架构

检查 268-270

服务网格,设置 270

混合云 6

HyperKit 35

I

基础设施即服务(IaaS) 4, 5

Ingress

暴露,通过 HTTPS 113-116

Ingress 网关 354

参考链接 354

Ingress 流量

管理,与 Kubernetes Ingress 资源 93-100

Ingress,使用 Istio 网关

创建 100, 101

管理 100

虚拟服务,创建 101-104

指令集架构(ISA) 247

IP 虚拟服务器(IPVS) 23

Isovalent

URL 338

Istio 30

架构 57, 58

认证资源 336

下载链接 35

启用,针对示例应用 38-40

故障注入 128

安装 35-38

学习资源 336

Sidecar 注入 40-45

URL 336

用于管理 Egress 流量 121-125

Istio 代理 69

调试 301, 302

Istio,替代方案

App Mesh 31

Consul 31

F5 NGINX 服务网格 31, 32

Kuma 30

Linkerd 30

Red Hat OpenShift 服务网格 31

Istio 证书授权(CA) 64

Istio 社区

参考链接 336

Istio 组件

交互 286

IstioCon

参考链接 336

IstioCon 2022

参考链接 336

Istio 配置

分析 288-293

检查 288-293

Istio 配置,用于管理应用弹性

流量控制,配置 328,329

超时和重试,配置 326,327

Istio 配置,用于管理应用流量

访问外部服务,配置 325,326

DestinationRule,配置 323-325

Istio Ingress 网关,配置 321,322

VirtualService,配置 322,323

Istio 控制平面

通信,建立 72

组件,探索 61,62

部署模型 70

最终用户上下文传播 65

身份目录和注册表 65

Istio 节点代理 64

sidecar 注入 64

Istio 控制平面仪表盘 195

istioctl 65-68

istiod 62,63

API 验证 64

配置监视 64

Istio 证书授权 64

Istio 数据平面

调试日志,变更 299

Envoy,探索 73

扩展,使用 Wasm 251-263

日志级别,变更 299-301

istiod 端口

探索 286

端口 9876 287

端口 15010 287

端口 15012 287

端口 15014 286

端口 15017 286

Istio Ingress 网关

使用 46-51

Istio 安装

工作站,准备 32

Istio 指标

创建 192-194

自定义 187-189

维度 188

维度,添加到 189-191

名称 188

值 188

Istio 操作符 65-68

IstioOperator 65

卸载 68

IstioOperatorSpec 65

Istio 性能仪表盘 196

Istio 的最佳实践 303

攻击向量,检查控制平面 303

攻击向量,检查数据平面 303, 304

自动化,使用 OPA Gatekeeper 306-314

参考链接 305

服务网格,保障安全 304, 305

Istio 安全架构 153-156

Istio 服务和工作负载仪表盘 196

Istio 工作负载组 271

J

Jaeger 54, 55, 201

分布式追踪,启用 201-208

特性 201

安装 201

Java 虚拟机 (JVM) 372

即时编译 (JIT) 步骤 337

K

Kiali 52-54

Kong Inc. 271

kubeconfig

设置 92

kubectl 17, 33

设置 92

kube-proxy 17

Kubernetes 入驻控制器 42

Kubernetes 集群

额外的 Google Cloud 步骤 213, 214

设置 212

Kubernetes 命令行工具

安装 32-35

Kubernetes 凭证 271

Kubernetes CSR API 216

Kubernetes Ingress 资源

用于管理 Ingress 流量 93-100

Kubernetes (K8S) 12-15, 30

优势 14

Kuma 30,357

curl,部署 359-362

仪表盘 358

envoydemo,部署 359-362

安装 357,358

参考链接,针对策略 366

TrafficRoute 策略 363

流量路由 363-371

卸载 371

L

延迟 181

学习资源

参考 336

LEAST_REQUEST 负载均衡策略 143,144

Linkerd 30,372

curl,部署 374,375

envoydemo,部署 374,375

安装 372-374

零信任网络 375-380

Linkerd2-proxy 31,372

linkerd-proxy

参考链接 375

Linkerd v2 372

监听器发现服务(LDS) 84

监听器过滤器 82,83

监听 74

Litmus

参考链接 128

负载均衡 141

用于构建应用程序弹性 141

负载均衡策略,Istio

LEAST_REQUEST 143,144

RANDOM 142

轮询 141,142

日志级别

更改,针对 Istio 数据平面 299-301

日志 180

Lua 过滤器 85,242

M

主线程 74

Mesh 配置协议(MCP) 64

MeshGatewayInstance 367,368

MeshGatewayRoute 368,369

网格网关 354

参考链接 354

度量维度 185

度量 180

度量抓取

与 Prometheus 一起使用 181

微服务架构 8-12

中间件即服务(MWaaS) 15

minikube 17, 32

安装,使用 Homebrew 32-35

镜像服务 109

Mixer 242

参考链接 242

模型-视图-控制器(MVC) 9

ModHeader 扩展 323

监控

黄金信号 181

单体架构 8

多集群部署

CA 外部,至 Istio 216

相互信任,建立 215

插件 CA 证书 215

多个负载均衡规则

定义 144, 145

多主设置

在不同网络上 232-235

在同一网络上 236-239

修改 Webhook 64

相互 TLS(mTLS) 157

宽容模式 157

严格模式 157

使用,用于身份验证 157

使用,设置服务到服务的通信 158-162

N

原生 C++ 过滤器 85

O

OAuth 2.0 授权框架

参考链接 164

可观察性 179

在控制理论中 179

在线书店应用程序

核心模块 9

在线精品应用示例

Istio 配置,用于管理应用程序弹性 326

Istio 配置,用于管理应用程序安全性 329-335

Istio 配置,用于管理流量 321

服务网格,使能 319-321

Open Policy Agent (OPA) Gatekeeper 306

最佳实践,自动化使用 306-314

设置 306

卸载 314

工作负载部署最佳实践,通过 317, 318 强制执行

开源软件 (OSS) 26

OpenSSL

设置 213

操作费用 (OPEX) 7

操作字段

参数 176

异常检测 148

OWASP

URL 304

P

pilot-agent 69

平台即服务 (PaaS) 4, 5

Pod 13

Pod 连接性 272

主集群 71

单一控制平面 71, 72

主-远程配置

在多网络上 217

在同一网络上 228-232

信任,在集群之间建立 217-226

私有云 6

Prometheus 55, 181

安装 182

示例应用,部署 183-187

用于指标抓取 181

PromQL 181

协议缓冲区 83

参考链接 83

Proxy-Wasm 251

Proxy-Wasm ABI 251-253

Proxy-Wasm SDK 254-258

公有云 5

R

随机负载均衡策略 142

流量限制 145

示例 146-148

Red Hat OpenShift 服务网格 31

Rego 307

参考链接 307

远程集群 71

单一控制平面 71, 72

渲染器 68

RequestAuthentication 策略

配置 164-176

请求对冲

参考链接 76

资源管理器 68

重试 135,138

用于应用程序弹性 138-141

微服务中的重试模式

参考链接 75

基于角色的访问控制(RBAC)策略 305

根跨度 204

循环轮询 141,142

路由发现服务(RDS)84

运行时发现服务(RTDS)85

S

饱和度 181

范围路由发现服务(SRDS)84

范围 299

抓取 181

SDS 客户端 69

SDS 服务器 69

秘密发现服务(SDS)69,84

安全命名 155

为每个人提供的安全生产身份框架(SPIFFE)154

服务器名称指示(SNI)114

服务账户 155

参考链接 155

服务发现链,Consul

解析 354,355

路由 348-351

分割 351-354

服务网格 3,15-24

蓝绿部署 25-28

金丝雀部署 25-28

电路断路 24

截止日期 24

重试机制 24

超时 24

服务网格接口(SMI)379

服务网格设置,用于混合架构

演示应用程序,在虚拟机上设置 273

Istio,在集群中设置 274,275

Istio,在虚拟机上设置 277-279

Kubernetes 集群,配置 275-277

概述 271,272

虚拟机工作负载,集成到网格中 280-282

ServiceRouter 配置 349, 350

服务到服务认证 157

服务到服务通信

设置,使用 mTLS 158-162

侧车注入 40-45

单一集群

使用外部控制平面 72, 73

使用本地控制平面 71

网站可靠性工程师 (SREs) 180

Sockshop 应用

部署 92

软件即服务 (SaaS) 4, 5

软件开发工具包 (SDKs) 251

Solo.io 271

源配置

字段 173, 174

跨度 204

Spifee 可验证身份文档 (SVID) 64

StackOverflow

参考链接 336

步骤 CLI

安装链接 154

主体备用名称 (SAN) 154

T

遥测

数据 180

可视化,使用 Grafana 194

终止网关 354

参考链接 354

Tetrate

URL 336

Tetrate Academy 336

超时 134-138

应用程序弹性 135-137

TinyGo,用于主机操作系统

安装链接 256

跟踪 180, 204

流量 181

基于金丝雀发布 104-109

路由 104-109

路由到集群外部的服务 111-113

流量镜像 109-111

参考链接 76

TrafficRoute 策略 363

参考链接 366

流量影像 109

翻译器 68

U

Unix 域套接字(UDS)69

V

验证 Webhook 64

验证器 67

虚拟主机发现服务(VHDS)84

虚拟服务

创建 101-104

W

Wasm 二进制工具包(WABT)258

Wasm 过滤器 86

Wasm 用于代理 251

WasmPlugin API 259

Wasm (原型)

参考链接 253

Web 应用防火墙(WAF)304

WebAssembly 核心规范

参考链接 251

WebAssembly Hub

参考链接 356

WebAssembly(Wasm)85,241

基础知识 247

概览 248

示例应用程序,用于将小写字母转换为大写 248-250

选择,构建扩展的理由 247,248

用于扩展 Istio 数据平面 251-263

WebAssembly 工作组

URL 251

通配符记录

启用 HTTPS 119-121

工作线程 74

工作负载部署最佳实践

强制执行,使用 OPA Gatekeeper 317,318

WorkloadEntry 279

配置 279

WorkloadGroup 277

配置 277

工作站,供 Istio 安装使用

Kubernetes 命令行工具,安装 32-35

minikube,使用 Homebrew 安装 32-35

准备工作 32

系统规格 32

万维网联盟(W3C)251

X

xDS API

实现 75, 83

Logo 描述自动生成

Packtpub.com

订阅我们的在线数字图书馆,全面访问超过 7,000 本书籍和视频,以及行业领先的工具,帮助您规划个人发展并推动职业生涯发展。欲了解更多信息,请访问我们的网站。

为什么订阅?

  • 利用来自超过 4000 名行业专业人士的实用电子书和视频,减少学习时间,多花时间编程

  • 提升你的学习效果,使用特别为你打造的技能计划

  • 每月获取一本免费电子书或视频

  • 完全可搜索,方便获取重要信息

  • 复制粘贴、打印和书签内容

你知道吗,Packt 提供每本书的电子书版本,PDF 和 ePub 文件都可以获取?你可以在 packtpub.com 升级为电子书版本,作为印刷书籍的购买者,你还可以享受电子书副本的折扣。详情请联系我们:customercare@packtpub.com

www.packtpub.com,你还可以阅读一系列免费的技术文章,注册各种免费的新闻通讯,并享受 Packt 书籍和电子书的独家折扣与优惠。

你可能会喜欢的其他书籍

如果你喜欢本书,可能会对 Packt 出版的其他书籍感兴趣:

Kubernetes 运维工具框架书

迈克尔·戴姆

ISBN: 978-1-80323-285-0

  • 深入了解 Operator Framework 和运维工具的好处

  • 实施设计运维工具的标准方法

  • 使用 Operator SDK 以逐步方式开发运维工具

  • 使用 OperatorHub.io 等分发选项发布运维工具

  • 使用不同的 Operator 生命周期管理选项部署运维工具

  • 发现 Kubernetes 开发标准如何与运维工具相关

  • 运用从真实世界运维工具案例研究中学到的知识

学习 DevOps - 第二版

米卡埃尔·克里夫

ISBN: 978-1-80181-896-4

  • 理解基础的基础设施即代码模式与实践

  • 了解 Git 命令和 Git 流的概览

  • 安装并编写 Packer、Terraform 和 Ansible 代码,以根据 Azure 示例进行云基础设施的配置和预配

  • 使用 Vagrant 创建本地开发环境

  • 使用 Docker 和 Kubernetes 容器化应用程序

  • 应用 DevSecOps 进行合规性测试和保护 DevOps 基础设施

  • 使用 Jenkins、Azure Pipelines 和 GitLab CI 构建 DevOps CI/CD 管道

  • 探索蓝绿部署和 DevOps 实践,适用于开源项目

Packt 正在寻找像你这样的作者

如果你有兴趣成为 Packt 的作者,请访问 authors.packtpub.com 并立即申请。我们与成千上万的开发者和技术专家合作,帮助他们与全球技术社区分享他们的见解。你可以进行一般申请,申请我们正在招募作者的特定热门话题,或提交你自己的创意。

分享你的想法

现在您已经完成了《用 Istio 构建服务网格》,我们非常希望听到您的想法!如果您是从亚马逊购买的这本书,请点击这里直接跳转到该书的亚马逊评价页面并分享您的反馈,或者在您购买的站点留下评论。

您的评论对我们和技术社区非常重要,它将帮助我们确保提供优质的内容。

下载本书的免费 PDF 版本

感谢您购买本书!

您喜欢随时随地阅读,但无法将纸质书籍随身携带吗?

您购买的电子书与您选择的设备不兼容吗?

不用担心,现在购买每本 Packt 书籍时,您都会免费获得该书的无 DRM 版 PDF。

在任何地方、任何设备上阅读。直接将您最喜欢的技术书籍中的代码搜索、复制并粘贴到您的应用程序中。

福利不仅仅是这些,您还可以获得独家的折扣、新闻通讯以及每天发送到您邮箱的精彩免费内容。

请按照以下简单步骤获取福利:

  1. 扫描二维码或访问以下链接

packt.link/free-ebook/9781803246819

  1. 提交您的购买证明

  2. 就这些!我们会将您的免费 PDF 和其他福利直接发送到您的电子邮箱。

posted @ 2025-06-30 19:27  绝不原创的飞龙  阅读(6)  评论(0)    收藏  举报