Linkerd-启动指南-全-
Linkerd 启动指南(全)
原文:
zh.annas-archive.org/md5/3cf07d62269e380e2433fbdb65c963d9译者:飞龙
序言
服务网格需要一点声誉修复。
许多云原生从业者似乎认为网格是可怕的、复杂的东西,是应该避免的,直到作为拯救濒临崩溃的应用程序的最后手段进行检查。我们希望改变这种看法:服务网格是使开发和运营云原生应用程序变得比以往更容易的不可思议的工具。
当然,我们认为 Linkerd 是目前最好的服务网格,可以让事情变得更容易。
所以,如果你一直在试图通过日志理解一个行为异常的应用程序而感到抓狂,或者如果你花了几个月时间尝试让其他的服务网格运行起来,只是想让事情正常运转,或者如果你正在尝试向另一个开发人员解释为什么他们真的不需要担心在他们的微服务中编写重试和 mTLS,那么你来对地方了。我们很高兴你在这里。
谁应该阅读这本书
这本书旨在帮助任何认为在创建、运行或调试微服务应用程序时,通过 Linkerd 可以更轻松地完成任务的人。虽然我们认为这本书将使对 Linkerd 感兴趣的人受益,但 Linkerd —— 就像计算本身一样 —— 最终是一种手段,而不是目的。这本书体现了这一点。
除此之外,对于我们来说,无论你是应用程序开发人员、集群操作员、平台工程师还是其他任何角色;这本书都应该有助于帮助你充分利用 Linkerd。我们的目标是为你提供一切你需要的东西,让 Linkerd 运行起来,帮助你完成任务。
你需要一些关于 Kubernetes 的基础知识,关于在容器中运行事物的整体概念,以及 Unix 命令行,才能充分利用本书。对 Prometheus、Helm、Jaeger 等工具有一定了解也会有所帮助,但并不是必须的。
为什么我们写这本书
我们两个在云原生领域工作多年,在那之前在软件领域工作了更多年。在所有这段时间里,从未消失的挑战是教育;新潮的东西并不是很有用,直到人们真正理解它是什么以及如何使用它。
现在应该对服务网格有相当深入的了解了,但当然每个月都会有人需要解决网格中最新和最伟大的变化,每个月都会有更多的人迁移到对他们来说完全新的云原生世界。我们写这本书,并将继续更新它,以帮助所有这些人。
导读本书
第一章,“服务网格 101”,是关于服务网格的介绍:它们的作用,它们可以帮助解决的问题,以及为什么你可能想要使用它们。对于不熟悉服务网格的人来说,这是必读的。
第二章,“Linkerd 简介”,深入探讨了 Linkerd 的架构和历史。如果你已经熟悉 Linkerd,这可能大部分是回顾。
第三章,“部署 Linkerd”,以及第四章,“向网格添加工作负载”,讨论如何在集群中运行 Linkerd,并让你的应用与 Linkerd 协同工作。这两章涵盖了实际使用Linkerd 的基本要点。第五章,“入口和 Linkerd”,接着讨论入口问题,如何管理以及 Linkerd 如何与入口控制器交互。
第六章,“Linkerd CLI”,讨论了linkerd CLI,你可以使用它来控制和检查 Linkerd 部署。
第七章,“mTLS、Linkerd 和证书”,深入探讨了 Linkerd mTLS 以及它如何使用 X.509 证书。第八章,“Linkerd 策略:概述和基于服务器的策略”,和第九章,“Linkerd 基于路由的策略”,继续探讨 Linkerd 如何使用这些 mTLS 身份在您的集群中强制执行策略。
第十章,“使用 Linkerd 观察您的平台”,全面探讨了 Linkerd 的应用程序级可观察性机制。第十一章,“使用 Linkerd 确保可靠性”,反过来,涵盖了如何使用 Linkerd 提高应用程序的可靠性,而第十二章,“Linkerd 跨多个 Kubernetes 集群进行通信”,则讨论了如何在多个 Kubernetes 集群之间扩展 Linkerd 网格。
第十三章,“Linkerd CNI 与 Init 容器”,讨论了 Linkerd 如何与集群的低级网络配置交互的棘手话题。不幸的是,这可能是讨论的必要话题,因为你考虑将 Linkerd 投入生产,这是第十四章,“生产就绪的 Linkerd”的主题。
最后,第十五章,“调试 Linkerd”,讨论了如何调试 Linkerd 本身,如果发现问题(尽管我们希望你不会!)。
本书使用的约定
本书使用以下排版约定:
斜体
表示新术语、URL、电子邮件地址、文件名和文件扩展名。
固定宽度
用于程序清单,以及在段落内引用程序元素,如变量或函数名、数据库、数据类型、环境变量、语句和关键字。
固定宽度斜体
显示应该用用户提供的值或由上下文确定的值替换的文本。
注意
这个元素表示一般性说明。
警告
这个元素表示警告或注意事项。
使用代码示例
补充材料(代码示例、练习等)可在https://oreil.ly/linkerd-code下载。
如果您有技术问题或在使用代码示例时遇到问题,请发送电子邮件至support@oreilly.com。
本书旨在帮助您完成工作。一般来说,如果本书附有示例代码,则您可以在程序和文档中使用它。除非您复制了大量代码的重要部分,否则无需联系我们请求许可。例如,编写一个使用本书中几个代码块的程序不需要许可。销售或分发 O’Reilly 图书中的示例代码则需要许可。引用本书并引用示例代码回答问题不需要许可。将本书中大量示例代码整合到您产品的文档中则需要许可。
我们感激,但通常不需要署名。署名通常包括标题、作者、出版商和 ISBN。例如:“Linkerd: Up and Running by Jason Morgan and Flynn (O’Reilly). Copyright 2024 Jason Morgan and Kevin Hood, 978-1-098-14231-5.”
如果您觉得您使用的代码示例超出了公平使用范围或上述许可,请随时通过permissions@oreilly.com与我们联系。
O’Reilly 在线学习
注意
40 多年来,O’Reilly Media一直为企业提供技术和商业培训、知识和见解,帮助它们取得成功。
我们独特的专家和创新者网络通过书籍、文章以及我们的在线学习平台分享他们的知识和专业知识。O’Reilly 的在线学习平台为您提供按需访问实时培训课程、深入学习路径、交互式编码环境以及来自 O’Reilly 和其他 200 多家出版商的大量文本和视频。欲了解更多信息,请访问https://oreilly.com。
如何联系我们
请将有关此书的评论和问题寄给出版商:
-
O’Reilly Media, Inc.
-
1005 Gravenstein Highway North
-
Sebastopol, CA 95472
-
800-889-8969(美国或加拿大)
-
707-827-7019(国际或当地)
-
707-829-0104(传真)
-
support@oreilly.com
我们为这本书设有网页,列出勘误、示例和任何额外信息。您可以访问https://oreil.ly/linkerd-up-and-running。
有关我们的图书和课程的新闻和信息,请访问https://oreilly.com。
在 LinkedIn 上找到我们:https://linkedin.com/company/oreilly-media
在 YouTube 上观看我们:https://youtube.com/oreillymedia
致谢
非常感谢所有帮助我们开发这本书的朋友们,包括(但不限于!):
-
我们的编辑 Angela Rufino。
-
技术审阅员 Daniel Bryant、Ben Muschko 和 Swapnil Shevate,他们提供了令人惊叹的反馈,使这本书变得更加完善。
-
O'Reilly 的无名英雄们使一切都成为可出版的形式。
-
最后但同样重要的是,Linkerd 的维护者们以及创建我们正在撰写的内容的 Buoyant 公司的优秀人才。
Flynn 要向 SC 和 RAH 大声喊出感谢,在这一年的努力中,他们一直陪伴着他。非常感谢。
第一章:服务网格 101
Linkerd 是第一个服务网格,事实上,它是首个提出“服务网格”这个术语的项目。它由 Buoyant, Inc. 在 2015 年创建,正如我们将在 第二章 中进一步讨论的那样,至今一直致力于使得生产和运行真正优秀的云原生软件变得更加容易。
但究竟什么是服务网格呢?我们可以从 CNCF 术语表 中的定义开始。
在微服务世界中,应用程序被分解成多个较小的服务,它们通过网络进行通信。就像您的 wifi 网络一样,计算机网络本质上是不可靠的、可黑客的,并且通常速度较慢。服务网格通过管理服务之间的流量(即通信),并在所有服务中统一添加可靠性、可观察性和安全性功能,来应对这一新的一套挑战。
云原生世界涉及从开发中在您的笔记本电脑上运行的小集群到谷歌和亚马逊管理的大规模基础设施的计算。当应用程序采用微服务架构时效果最佳,但微服务架构本质上比单体架构更加脆弱。
从根本上说,服务网格是关于将应用程序开发者和应用程序本身隐藏起来的脆弱性。它们通过将几个关键的特性从应用程序移到基础设施中来实现这一点。这使得应用程序开发者能够专注于使他们的应用程序独特的东西,而不必花费所有时间来担心如何提供应该在所有应用程序中都相同的关键功能。
在本章中,我们将高层次地了解服务网格的作用、工作原理及其重要性。在此过程中,我们将为您提供本书其余部分关于 Linkerd 的更详细讨论所需的背景。
基本的网格功能
服务网格提供的关键功能可以大致分为三大类:安全性、可靠性 和 可观察性。在我们分析这三个类别时,我们将比较它们在典型单体应用和微服务应用中的表现。
当然,“单体”可以指代几种不同的东西。 图 1-1 显示了我们将考虑的“典型”单体应用程序的图表。

图 1-1. 单体应用程序
单体应用是操作系统中的一个单一进程,这意味着它可以利用操作系统提供的所有保护机制;其他进程无法看到单体应用内部的任何内容,也绝对不能修改其中任何内容。单体应用内不同部分之间的通信通常是单体应用单一内存空间内的函数调用,因此其他进程无法查看或更改这些通信。确实,单体应用的某个区域可以修改其他部分正在使用的内存—事实上,这是错误的一个重要来源!—但这些通常只是错误,而不是攻击。
多进程对比多机器
“但等等!”我们听到你们在呼喊。“任何名副其实的操作系统都可以提供跨越一个以上进程的保护!那么内存映射文件或者 System V 共享内存段怎么办?环回接口和 Unix 域套接字(稍作拓展)呢?”
你是对的:这些机制可以允许多个进程在仍然受操作系统保护的情况下合作和共享信息。然而,它们必须明确编码到应用程序中,并且它们仅在单台机器上运行。像 Kubernetes 这样的云原生编排系统的一部分优势在于,它们可以在集群中的任何机器上调度 Pod,而你事先不知道哪台机器。这是极其灵活的,但也意味着那些假设一切都在单一机器上的机制在云原生世界中根本行不通。
相比之下,图 1-2 显示了对应的微服务应用程序。

图 1-2. 微服务应用程序
在微服务中,情况有所不同。每个微服务都是一个独立的进程,微服务之间仅通过网络进行通信—但操作系统提供的保护机制仅在进程内部有效。在需要微服务之间通信的世界中,这些机制是不够的。
当开发微服务应用程序时,依赖不可靠且不安全的网络进行通信会引发很多问题。
安全性
让我们从网络本身不安全的事实开始。这引发了许多可能的问题,其中一些如图 1-3 所示。

图 1-3. 通信是一项风险重重的业务
一些最重要的安全问题包括窃听、篡改、身份盗窃和越权:
窃听
恶意用户可能会拦截两个微服务之间的通信,阅读并非面向他们的通信。根据恶意用户学到的具体内容,这可能只是轻微烦恼,也可能是重大灾难。
防止窃听的典型方法是加密,它会将数据加密,以便只有预期的接收者能够理解。
篡改
恶意者还可能能够修改网络传输中的数据。在其最简单的形式中,篡改攻击将简单地破坏传输中的数据;在其最微妙的形式中,它将修改数据以有利于攻击者。
极其重要的是要理解,仅仅靠加密是无法防止篡改的!正确的保护方法是使用像校验和这样的完整性检查;所有设计良好的加密系统都将完整性检查作为其协议的一部分。
身份盗窃
当你把信用卡详细信息交给支付微服务时,你怎么能确信你确实在与你的支付微服务交流?如果恶意者成功假装成你的某个微服务,那将为各种麻烦可能性打开大门。
强大的身份验证对于防范此类攻击至关重要。这是确保你正在交流的微服务确实是你所认为的那个的唯一方法。
过度扩展
与身份盗窃相反,恶意者可能会利用微服务允许执行本不应允许的操作的地方。例如,想象一下,一个恶意者发现支付微服务可以毫不犹豫地接受来自本应只列出待售物品的微服务的请求。
在这里,注意授权至关重要。在理想情况下,每个微服务将能够严格按需操作,而且不多不少(最小特权原则)。
可靠性
在单体架构世界中,可靠性通常指的是单体功能的良好运行:当单体的不同部分通过函数调用进行通信时,通常不必担心调用丢失或者其中一个函数突然无响应!但是,正如在图 1-4 中所示,与微服务相比,不可靠的通信实际上是常态。

图 1-4. 不可靠的通信是常态
微服务可能不可靠的方式有很多,包括:
请求失败
有时网络请求会失败。可能的原因有很多,从崩溃的微服务到网络过载或分区。无论是应用程序还是基础设施都需要采取措施来处理失败的请求。
在最简单的情况下,网格可以简单地为应用程序管理重试:如果调用失败是因为被调用服务崩溃或超时,则只需重新发送请求。当然,这种方法并不总是有效:并非所有请求都适合重试,也并非每次故障都是暂时性的。但在许多情况下,简单的重试逻辑可以取得很好的效果。
服务故障
在某些请求失败的特殊情况下,不仅一个微服务实例会崩溃,而是所有实例都可能崩溃。也许是部署了错误版本,或者整个集群崩溃了。在这些情况下,服务网格可以通过故障转移到备用集群或已知良好的服务实现来提供帮助。
同样地,如果没有应用程序的帮助,这并非总能实现(例如,有状态服务的故障转移可能非常复杂)。但是,微服务通常设计为在无状态情况下运行,这种情况下,服务网格的故障转移可以提供极大的帮助。
服务超载
另一种特殊情况:有时故障发生是因为太多请求堆积在同一个服务上。在这些情况下,熔断可以帮助避免级联故障:如果网格快速拒绝一些请求,防止其依赖服务介入并造成更多麻烦,就可以帮助限制损害。这是一个激进的技术,但这种强制性的负载控制可以极大地提高整体应用程序的可靠性。
可观测性
在任何计算应用中,要看清楚发生了什么是很困难的:即使是一台慢机器,如今的操作时间比我们人类生活的时间尺度快上十亿倍!在单体应用程序中,通常通过内部日志记录或仪表板来处理可观测性问题,这些仪表板从单体的多个不同区域收集全局指标。但在微服务架构中,这显然不太可行,正如 图 1-5 中所示——即使可行,也不能完全反映实际情况。

图 1-5. 在黑暗中工作是困难的
在微服务世界中,“可观测性”往往更加关注调用图和黄金指标:
调用图
当查看微服务应用时,通常首要的事情是了解哪些服务被其他哪些服务调用。这就是调用图,如 图 1-6 所示,而服务网格的一个关键功能是提供有关每条边的流量量、成功率、失败率等指标。

图 1-6. 应用程序的调用图
调用图是一个关键的起点,因为用户从集群外部看到的问题实际上可能是由于图中深埋的单个服务出现了问题。具备整个图的可见性非常重要,以便能够解决问题。
值得注意的是,在特定情况下,图中的特定路径将会相关,如 图 1-7 所示。例如,用户的不同请求可能会在图中使用不同的路径,从而激活工作负载的不同方面。

图 1-7. 通过调用图的不同路径
黄金指标
对于每个微服务,我们可以收集大量的指标。随着时间的推移,其中三个指标在各种情况下反复证明特别有用,以至于我们现在将它们称为“黄金指标”(如图 1-8 所示):
延迟
请求完成需要多长时间?通常报告为某个百分比请求完成所需的时间。例如,P95 延迟表示 95% 的请求完成所需的时间,因此,“5 ms P95”可以解释为 95% 的请求在 5 毫秒或更短的时间内完成。
流量
给定服务处理多少请求?通常报告为每秒请求次数,或 RPS。
成功率
有多少请求成功?(这也可以报告为其反向值,错误率。)通常报告为总请求数的百分比,其中“成功率”通常缩写为 SR。

图 1-8. 三个黄金指标
原始“黄金信号”
最初在 Google 的“监控分布式系统”文章中描述这些内容为四个“黄金信号”:延迟、请求速率、错误率和饱和度。我们更喜欢“黄金指标”,因为指标是您可以直接测量的东西;您从指标(如“饱和度”)中得出信号。
我们将在第十章中更详细地讨论这些内容,但现在值得注意的是,这些指标已被证明非常有用,许多服务网格都花费了大量精力来记录它们,而服务网格正是跟踪它们的理想场所。
网格实际上是如何工作的?
最后,让我们快速看看服务网格的实际功能。
从高层次来看,所有的网格基本上都在做同样的工作:它们插入到操作系统的网络堆栈中,接管应用程序正在使用的低级网络,并调解应用程序在网络上的所有操作。这是唯一的实际方法,可以使网格提供其设计的所有功能,而无需对应用程序本身进行更改。
大多数服务网格,包括 Linkerd,使用 sidecar 模型,在每个应用程序容器旁边注入一个代理容器(参见图 1-9)。^(1) 一旦运行,代理会重新配置主机的网络路由规则,以便所有进出应用程序容器的流量都通过代理。这允许代理控制网格功能所需的所有内容。

图 1-9. Linkerd 和 sidecar 模型
还有其他模型,但 sidecar 模型在操作简易性和安全性方面具有巨大优势:
-
从系统的其他基本一切的角度来看,sidecar 的作用就像它是应用程序的一部分。特别是这意味着操作系统为保证应用程序安全所做的所有事情也适用于 sidecar。这是一个非常非常重要的特征:将 sidecar 限制在完全一个安全上下文内大大减少了 sidecar 的攻击面,并且使得判断 sidecar 所做的事情是否安全变得更加容易。
-
以同样的方式,管理 sidecar 与管理任何其他应用程序或服务完全相同。例如,
kubectl rollout restart将像单元一样工作来重新启动应用程序 Pod 及其 sidecar。
当然,它也有一些缺点。最大的问题是每个应用 Pod 都需要一个 sidecar 容器——即使你的应用有成千上万个 Pod。另一个普遍关注的问题是延迟:sidecar 根据定义需要一些时间来处理网络流量。再次强调,我们稍后会详细讨论这个问题,但值得注意的是,Linkerd 致力于最大程度地减少 sidecar 的影响,在实践中,Linkerd 非常快速且非常轻量级。
那么我们为什么需要这个呢?
简单来说,网格提供的功能不是可选的。你永远不会听到工程团队说“哦,我们不需要安全性”或者“哦,可靠性不重要”(尽管你可能需要说服人们理解可观察性的必要性——希望本书能帮到你!)。
换句话说,选择不是是否拥有这三个特性,而是这些特性是由网格提供还是需要在应用程序中提供。
在应用程序中提供它们是昂贵的。你的开发人员可以手工编写它们,但这意味着在每个微服务中复制大量繁琐的应用程序代码,这很容易出错(特别是因为总是有诱惑让资深开发人员专注于与你的业务相关的核心逻辑,而不是那些沉闷、不太显眼但同样关键的重试工作)。你可能还会遇到应用程序各部分之间的不兼容性问题,尤其是当应用程序规模扩大时。
或者,你可以找到为你实现功能的库,这确实节省了开发时间。另一方面,你仍然需要每个开发人员学习如何使用这些库,你受限于可以找到这些库的语言和运行时,而不兼容性仍然是一个严重的问题(假设一个微服务在另一个微服务之前升级了库)。
随着时间的推移,我们非常清楚,将所有这些功能推入网格中,即使应用开发人员不一定需要知道其存在,也是提供它的明智方式。我们认为,Linkerd 是现有各种网格中最好的选择。如果到本书结束时我们还没有说服您,请联系我们,告诉我们哪里做得不够!
摘要
总之,服务网格是平台级基础设施,能够在整个应用程序上提供安全性、可靠性和可观察性,而无需对应用程序本身进行更改。Linkerd 是第一个服务网格,我们认为它仍然是权力、速度和操作简易性最佳平衡的选择。
^(1) 这个名字的灵感来自于在摩托车上安装边车的类比。
第二章:介绍 Linkerd
2015 年是云原生计算非常重要的一年:第一个 Kubernetes 版本发布、Cloud Native Computing Foundation(CNCF)的创建以及 Linkerd 的诞生。Linkerd 是最早捐赠给 CNCF 的五个项目之一,也是首个提出“服务网格”概念的项目之一。
在本章中,您将更多地了解 Linkerd 的起源、其特点以及其工作原理。我们将简要、有用且有趣地介绍其历史,但如果您想直接获取重要信息,可以随时跳过。
Linkerd 的起源
Linkerd 项目由前 Twitter 工程师 William Morgan 和 Oliver Gould 于 2015 年在 Buoyant, Inc.创建。Linkerd 的第一个公开发布是在 2016 年 2 月。您可以在图 2-1 中看到其历史的简要总结。

图 2-1 Linkerd 简要时间轴
Linkerd1
最初版本的 Linkerd,现在称为“Linkerd1”,主要使用 Scala 编写,主要基于 Twitter 创建的 Finagle RPC 库。它是一个多平台网格,支持多种容器调度器,并提供了许多强大的功能。然而,使用 Finagle 需要 Linkerd1 在 Java 虚拟机(JVM)上运行,而最终 JVM 的性能成本太高。
Linkerd1 已经正式停止支持。今后,当我们谈论“Linkerd”时,我们将指的是现代 Linkerd——Linkerd2。
Linkerd2
2018 年,Linkerd 项目基于从 Linkerd1 在实际应用中积累的经验,进行了从头重写,告别了 Scala 世界。该项目放弃了对其他容器编排引擎的支持,全面转向支持 Kubernetes,大部分代码采用 Go 语言编写。此外,开发者选择编写一个小巧、快速、专为目的设计的 Rust 代理(创意命名为linkerd2-proxy),用于管理应用程序通信,而不是采用 Envoy 代理。
Linkerd 与 Rust
当 Linkerd2 重写开始时,Rust 编程语言因其内存安全性而受到关注,这使开发者能够编写避免 C 和 C++中许多内存管理漏洞的代码,同时编译成本地代码以实现高性能。不过,Rust 在网络支持方面有时缺少 Linkerd2 所需的功能;在许多情况下,Linkerd2 开发者不得不向 Rust crate(如hyper和tokio)添加这些功能。
决定专注于 Kubernetes 并创建一个专为目的设计的代理的驱动力是“操作简易性”:即一个项目应能够在保持简单易学易用的同时提供功能和性能。这一概念对整个 Linkerd 项目产生了巨大影响,并继续是 Linkerd 开发的主要关注点。
Linkerd 代理
值得重申,linkerd2-proxy 并非通用代理;它是专门为 Linkerd 设计的。它非常快速轻巧,并且作为 Linkerd 用户,您几乎永远不需要直接与其交互——在正常使用中它几乎是不可见的,大多数 Linkerd 用户从不需要调整或调试linkerd2-proxy。(事实上,Linkerd 的维护人员喜欢开玩笑说,地球上唯一的linkerd2-proxy专家是...Linkerd 的维护人员。)
在下一节介绍的 Linkerd 控制平面将成为您与 Linkerd 互动的主要接口。
Linkerd 架构
由于 Linkerd 设计为符合 Kubernetes 本地化,其所有控制界面均通过 Kubernetes 对象公开。您将通过 Kubernetes API 管理、配置和排查 Linkerd。
与其他服务网格类似,Linkerd 被分为两个主要组件:数据平面,即直接处理应用程序数据的网格部分(主要由代理组成),和控制平面,负责管理数据平面。这种架构在 图 2-2 中展示。
Linkerd 利用 Kubernetes 侧车的概念工作,这允许每个应用程序容器都与一个专用代理配对,负责处理其所有网络流量。这些代理——网格的数据平面——实现了网格本身的高级功能,中介和测量所有通过它们的流量。

图 2-2. Linkerd 的内部架构
Kubernetes 侧车容器
直到 Kubernetes 1.28 采纳 KEP-753 之前,Kubernetes 没有正式的侧车容器类型。尽管如此,侧车的概念已经存在多年了。
在 Linkerd edge-23.11.4 版本起,Linkerd 确实支持 KEP-753 侧车容器,如果您运行的是 Kubernetes 1.28 或更高版本。
Linkerd 还支持扩展的概念,即作为控制平面的额外微服务,用于实现可选功能(无论是在集群中还是在 Linkerd CLI 中)。一些扩展(如 Viz 和 Multicluster 扩展)已经与官方的 Linkerd 构建捆绑在一起;尽管它们必须单独安装到集群中,但您无需额外的工具即可执行此操作。其他一些扩展(如 SMI 扩展)必须单独获取,然后才能安装;扩展的文档应告诉您如何执行此操作。
mTLS 和证书
Linkerd 在网络安全方面高度依赖传输层安全性(TLS),如 图 2-3 所示,几乎所有在 图 2-2 中展示的通信都受到 TLS 保护。

图 2-3. TLS 架构
TLS 技术在过去的四分之一世纪中支撑了互联网上的数据安全和隐私,通过允许在不安全网络上进行安全通信,即使通信双方以前从未这样做过。这是一个巨大的话题,值得单独一本书来讨论。我们将在第七章中更详细地讨论它,但在架构层面上,理解 Linkerd 使用 TLS 来加密集群内的通信,并作为网格内身份的基础(特别使用双向 TLS或mTLS)非常重要。
在 TLS 中,加密和身份验证都依赖于密钥对。密钥对由公钥和私钥组成,其中:
-
私钥必须只有标识密钥对的单个实体知道。
-
公钥必须为需要与该实体通信的所有人所知。
密钥对允许一个实体(比如 Linkerd 网格中的工作负载)使用私钥来证明其身份;其他实体可以使用公钥来验证这一声明。
关于密钥对的重要说明是,它们需要有限的生命周期,因此我们需要定期更换任何给定实体正在使用的密钥的方法。这被称为轮换密钥。
认证机构
由于始终单独跟踪公钥和私钥非常繁琐,TLS 使用捆绑在X.509 证书(通常只称为证书)中的密钥,这为我们提供了保存密钥的标准格式以及允许使用一个证书来证明另一个证书有效的标准方法。这称为签发证书或签署证书。支持签发证书过程的组织称为认证机构或CA。有些公司将成为 CA 视为其业务的核心部分(如 Let’s Encrypt、Venafi 和大多数云提供商),还有软件允许在我们自己的组织内建立 CA。
使用证书来签发其他证书自然地创建了一组证书的层次结构,形成了从单个根证书的信任链,如图 2-4 所示。

图 2-4. 证书信任层次结构
像使用 TLS 的所有其他东西一样,Linkerd 需要一个正确配置的证书层次结构才能正常工作。我们将在第三章和第七章中更详细地讨论这个问题。
Linkerd 控制平面
截至目前为止,Linkerd 核心控制平面由三个主要组件组成,如 图 2-5 所示:代理注入器、身份控制器 和 目标控制器。我们将在 第 15 章 中详细讨论这些组件。从根本上说,它们负责允许你将个别应用程序添加到服务网格中,并启用 Linkerd 提供的核心安全性、可靠性和可观察性功能。为了提供这些功能,这些组件直接与 Linkerd 的 TLS 证书交互。

图 2-5. Linkerd 控制平面
Linkerd 扩展
图 2-5 展示了一些在侧边运行的扩展部署。Linkerd 扩展没有特权;特别地,它们与控制平面或代理的交互方式仅限于公开的 API。这使得任何人都可以编写这些扩展。
Linkerd 维护人员维护了几个扩展,以提供许多用户需要的功能,但并非每个 Linkerd 安装都必须使用;其中包括 Linkerd Viz、Linkerd Multicluster、Linkerd Jaeger、Linkerd CNI 和 Linkerd SMI。
Linkerd Viz
Linkerd Viz 扩展提供了 Linkerd 仪表盘及其相关组件,如 图 2-6 所示。它还提供了一些额外的 CLI 选项,在集群中调试应用程序时非常有用。

图 2-6. Linkerd Viz 扩展
Viz 由以下部分组成,我们将在后续章节中描述这些部分。
Web
Linkerd Viz 的 Web 组件提供了许多 Linkerd 运营商使用的仪表盘 GUI。实际上,你并不需要 GUI —— 它显示的所有内容都可以通过命令行访问 —— 但它被广泛使用,并且非常有用。
Linkerd Viz 仪表盘不需要身份验证
Linkerd Viz 仪表盘不进行用户身份验证 —— 使用的身份验证系统太多,无法实现。如果选择将 Linkerd Viz 暴露给网络,你需要使用 API 网关或类似工具来根据自己的策略保护对 Linkerd Viz 的访问。仪表盘无法更改集群中的任何内容,但确实公开了大量信息。
你也可以选择让仪表盘在集群外部无法访问,只需使用 linkerd viz dashboard CLI 命令通过端口转发在 web 浏览器中打开仪表盘。
Tap
Tap 允许 Linkerd 显示流经应用程序之间的请求的元数据。Tap 数据在实时环境中调试应用程序问题时非常有用,因为它允许实时观察请求和响应数据。
Tap 不显示请求主体
Tap 只能显示 元数据:路径、标头等,无法显示请求 主体。当然,在许多情况下,元数据就足以理解应用程序中发生的情况。
要访问请求主体,您需要整合应用程序级别的请求日志记录。即使在这种情况下,Tap 在检查更详细的日志时也可以帮助缩小感兴趣的微服务和请求 ID 的范围。
Tap 注入器
要使 Linkerd Viz 显示有关请求的元数据,必须从系统中的各个代理中收集元数据。Tap 注入器修改了代理注入器,以便新代理允许此元数据收集。
请注意,代理注入器无法影响任何已经运行的代理!在安装扩展之前启动的工作负载需要重新启动,以便为 Linkerd Viz 提供 Tap 数据。
指标 API
指标 API 参与了为 Linkerd 仪表板收集指标的过程。它为 Linkerd 仪表板以及 Linkerd CLI 提供了底层的摘要数据。与所有仪表板组件一样,它不参与向 Linkerd 代理提供信息。
Prometheus 和 Grafana
Linkerd 的 Viz 扩展附带了一个 Prometheus 实例。如果选择安装 Grafana(如 Linkerd 文档 中所述),Linkerd 还发布了几个开源的 Grafana 仪表板。
实际上,您并不需要 Linkerd Viz 来使用 Prometheus 和 Grafana。Linkerd 代理原生支持 Prometheus,因此如果愿意,可以安装 Prometheus 并配置它直接抓取代理。不过,Linkerd Viz 更简单。
始终使用自己的 Prometheus
默认情况下,安装 Linkerd Viz 将安装一个内部的 Prometheus 实例。不要在生产环境中使用此 Prometheus,因为它没有配置持久存储;相反,请参阅 Linkerd 文档 和 示例 10-9 了解如何使用外部的 Prometheus 实例。
Linkerd 多集群
Linkerd 多集群扩展允许用户通过任何公共或私有网络连接集群,如 图 2-7 所示。多集群扩展通过一个特殊的网关连接集群,使所有流量看起来像是在本地集群中发起的。这使用户在连接集群时无需配置任何特殊的网络设置。我们将在 第十二章 深入探讨多集群连接。

图 2-7. Linkerd 多集群架构
Linkerd Jaeger
Linkerd Jaeger 扩展允许 Linkerd 参与分布式跟踪,如Jaeger 项目所体现的那样。具体而言,它允许 Linkerd 发出和转发分布式跟踪 span,以便您可以在分布式跟踪中查看代理活动。如图 2-8 所示,Linkerd Jaeger 提供了一个收集器,用于将 span 转发至 Jaeger 实例,并提供了一个注入器,用于修改代理注入器,以便新的代理将数据发送到收集器。与 Linkerd Viz 一样,您需要重新启动在安装 Linkerd Jaeger 之前运行的所有工作负载!
需要理解的是,虽然 Linkerd 可以通过提供有关代理如何对分布式应用程序的流量做出贡献的细节来帮助您的应用程序跟踪,但它不能为您的应用程序添加跟踪仪器。为了利用 Linkerd 进行分布式跟踪,您的应用程序必须首先配置为传播跟踪标头并创建和发出自己的 span。
始终使用您自己的 Jaeger 实例
默认情况下,Linkerd Jaeger 会安装一个内部的 Jaeger 实例。请勿在生产环境中使用此 Jaeger 实例,因为它不提供持久存储;而是参阅Linkerd 文档,了解如何使用外部 Jaeger 实例的信息。

图 2-8. Linkerd Jaeger 架构
Linkerd CNI
当 Linkerd 代理开始运行时,它需要重新配置内核的网络层,以便代理可以拦截和调节应用程序的网络通信。Linkerd 有两种可能的方式来实现这一点:Linkerd init 容器或 Linkerd 容器网络接口(CNI)插件。
我们将在第十三章中详细讨论这个问题,但值得注意的是,在某些环境中,使用 init 容器可能不可行或不合适,此时 CNI 插件与 Kubernetes CNI 协作以重新配置网络堆栈。如果您计划使用 CNI,则必须在安装任何其他 Linkerd 组件之前安装 Linkerd CNI 插件。这是唯一必须在 Linkerd 核心控制平面之前安装的扩展。
Linkerd SMI
服务网格接口(SMI)是 CNCF 推出的一个项目,旨在提供一个标准的、跨平台的 API 来控制服务网格的行为。Linkerd SMI 扩展允许 Linkerd 使用 SMI TrafficSplit 自定义资源定义(CRD)进行流量分割。^(1)
SMI 在整体上的采用情况有些参差不齐,截至 2023 年 10 月,SMI 项目已归档,其中许多概念和目标被用来指导网关 API 内的 GAMMA 倡议,Linkerd 从版本 2.14 开始支持该倡议。
摘要
Linkerd 在 2015 年开始,并在 2018 年基于 Rust 和 Go 发展成现代形式。它的运作简单的理念推动了其发展,这一点在 Linkerd 的架构中得到了体现,包括一个小型的、专门构建的 Rust 数据平面代理,一个专注于关键功能的 Go 控制平面,以及一组用于可选功能的扩展。
^(1) 除了 TrafficSplit 之外,还有其他 SMI CRD,但它们复制了 Linkerd 已经具有 API 的功能。
第三章:部署 Linkerd
现在您了解了 Linkerd 是什么以及它的工作原理的一些知识,是时候深入到您的环境中部署 Linkerd 中了。我们将在本章中深入探讨安装 Linkerd 的内容及其背后的原因和方式。您还可以查阅官方 Linkerd 文档,查看入门指南。
考虑因素
安装 Linkerd 通常可以快速、简便且无痛。不幸的是,这种易用性有时会掩盖真正的陷阱,你应该避免。我们将在进入安装部分时更详细地探讨具体内容——现在,简单来说,当你在实际非演示环境中安装 Linkerd 时,你需要计划生成和存储我们在 第二章 中简要描述的 TLS 证书。你还需要确保对应用程序使用的所有非 HTTP 端口有很好的理解,以便可以为它们正确配置协议发现(这在 第四章 中有更详细的介绍)。
Linkerd 版本控制
我们在 第二章 中提到,本书专注于 Linkerd2,即 Linkerd 的第二个主要版本,实质上是项目的重写。为此,Linkerd 使用一个看起来像 语义版本控制 的版本系统,但实际上是独特的。Linkerd 有两个主要发布通道:稳定 和 边缘。您可以在 官方 Linkerd 文档 中详细了解这种版本控制方案和发布模型。
稳定
稳定通道用于供应商发布,例如 Buoyant 为 Linkerd 提供的企业版(毫不奇怪,由 Buoyant 提供)。该通道使用修改后的语义版本方案:
stable-2.*`<major>`*.*`<minor>`*.*`<patch>`*
这意味着,例如,“Linkerd 2.12.3”,主版本号是 12,次版本号是 3。此版本没有补丁号。
语义版本控制规则是,主版本变更表示 Linkerd 引入了破坏性变更或重大新功能,而仅次版本变更表示新版本与上一版本完全兼容,并包括改进或错误修复。补丁发布很少见,表明已为给定次版本发布了安全修复。
边缘
边缘发布通道是您可以找到纯开源 Linkerd 发布的地方,构建于 Linkerd 最新可用更改的基础上。边缘发布通常每周提供一次,采用以下版本控制方案:
edge-*`<two digit year>`*.*`<month>`*.*`<number within the month>`*
例如,edge-24.1.1 将是 2024 年第一个月的第一个 edge 版本发布。
边缘版本不使用语义版本控制
显然,边缘版本通道不使用语义版本控制,但重申这一点是很重要的。您必须阅读安装的每个边缘版本的发布说明,并向 Linkerd 团队提供您的使用反馈。
工作负载、Pod 和服务
Linkerd 是围绕 Kubernetes 设计的服务网格。这意味着,与许多其他服务网格选项不同,您可以在不与任何自定义资源定义互动的情况下使用 Linkerd。Linkerd 使用 Kubernetes 构建如工作负载、Pod 和服务来管理大部分的路由和配置选项。因此,如果您今天在 Kubernetes 中运行某些东西,您可以将 Linkerd 添加到其中,并且它应该表现出相同的行为,只是具备了 Linkerd 的优势(参见 图 3-1)。当然,也有一些例外情况,我们将在 第四章 中详细介绍。

图 3-1. 添加 Linkerd 不应该破坏您的应用程序
TLS 证书
正如我们在 第二章 中提到的,Linkerd 依赖于 TLS 证书来在网格中提供身份验证。具体来说,Linkerd 需要一个信任锚证书,它签署一个身份发行者证书,后者签署工作负载证书(网格中每个工作负载一个)。这在 图 3-2 中有所体现。

图 3-2. Linkerd 信任层级
Linkerd 会为您管理工作负载证书,但您需要与认证机构合作管理信任锚和身份发行者证书。在本章中,我们将介绍 CLI 和 Helm 安装是如何工作的。
Linkerd Viz
我们在 第二章 中简要提到了 Linkerd Viz:它是 Linkerd 的开源仪表板组件,提供了一个易于使用的指标收集和展示系统。它可以收集有关所有网格化工作负载的有用指标,并在简单的 Web UI 中呈现。仪表板可以提供有关您的 Linkerd 环境的以下详细信息:
-
详细的应用程序指标,按以下内容细分:
-
命名空间
-
工作负载
-
Pod
-
服务
-
-
关于工作负载之间连接的信息,包括:
-
TLS 状态
-
网格状态
-
工作负载身份
-
使用的路径和头部(通过 Viz Tap)
-
基于路径的指标细分
-
我们将在 第十章 中详细讨论如何使用 Linkerd Viz,并在 第十四章 中讨论 Linkerd Viz 的生产环境问题。
Linkerd Viz 仪表板不需要身份验证
正如在 第二章 中讨论的那样,Linkerd Viz 仪表板不进行用户身份验证。如何向用户提供访问权限完全取决于您的设置。
Linkerd Viz 被认为是 Linkerd 核心的一部分,但必须单独安装,因为某些 Linkerd 安装完全替换了 Viz 与自定义构建系统。一般来说,我们强烈建议安装 Viz,除非有强烈的理由不这样做。在接下来的说明中,我们将包括安装 Viz。
始终使用您自己的 Prometheus
默认情况下,安装 Linkerd Viz 将安装一个内部的 Prometheus 实例。不要在生产环境中使用这个 Prometheus,因为它没有配置持久存储;相反,请参阅 Linkerd 文档 和 Example 10-9 了解如何使用外部 Prometheus 实例的信息。
部署 Linkerd
要部署 Linkerd,您需要一个可用的 Kubernetes 集群。本指南将使用本地部署的 k3s 集群,使用 k3d 工具。如果您已经熟悉安装和部署 Linkerd,请随意跳到 第四章。
必需工具
在本书的其余部分,我们假设您已经具备以下工具:
配置 Kubernetes 集群
首先创建一个 k3d 集群:
$ k3d cluster create linkerd
k3d 将为您的 Kubernetes 集群进行配置,并更新您的 KUBECONFIG。您可以通过运行以下命令测试与新集群的连接:
$ kubectl get nodes
您还应该验证集群是否正确配置,并且您是否具有安装所需权限,通过 Linkerd CLI 运行预安装检查:
$ linkerd check --pre
通过 CLI 安装 Linkerd
Linkerd CLI 可以轻松帮助您开始 Linkerd 安装。它将生成安装 Linkerd 所需的 Kubernetes 清单,并允许您轻松地将它们应用到您的集群中。
运行以下命令来通过 CLI 安装 Linkerd:
$ linkerd install --crds | kubectl apply -f -
这将在您的集群中安装 Linkerd CRD。从 Linkerd 2.12 开始,安装 Linkerd 的 CRD 使用单独的图表完成,并在运行安装时需要其自己的命令。安装完 CRD 后,您需要继续安装核心 Linkerd 控制平面:
$ linkerd install | kubectl apply -f -
完成这些步骤后,Linkerd 控制平面将开始在您的集群中设置自身。您很快将可以访问运行最小 Linkerd 服务网格所需的所有工具。您可以通过运行以下命令确认安装是否成功:
$ linkerd check
生产集群需要生产证书
再次强调,如果您没有明确说明,linkerd install 将悄悄为您创建证书。这对于演示是可以的,但不适用于生产。
安装完核心 Linkerd 控制平面后,您可以安装 Linkerd Viz:
$ linkerd viz install | kubectl apply -f -
与 Linkerd 本身一样,这将启动安装并立即返回。要等待安装完成并确认安装成功,运行:
$ linkerd check
始终使用您自己的 Prometheus
默认情况下,安装 Linkerd Viz 将会安装一个内部的 Prometheus 实例。不要在生产环境中使用此 Prometheus,因为它没有配置持久存储;相反,请查看Linkerd 文档和示例 10-9 了解如何使用外部的 Prometheus 实例。
通过 Helm 安装 Linkerd
Buoyant 公司,Linkerd 的开发者,在他们的生产运行手册中建议你使用 Helm 在生产环境中安装和管理 Linkerd。Helm 提供了一个经过充分测试、有文档支持的安装和升级路径(实际上,Linkerd CLI 在底层使用 Helm 模板生成其 Kubernetes 清单)。
使用基于 Helm 的安装也需要你提前考虑证书管理问题,这简化了稍后更新证书的流程。我们将在第七章详细介绍证书;现在,让我们通过手动生成证书来简单演示 Helm 安装过程。
生成 Linkerd 证书
使用 Helm 最简单的安装方式是手动生成 Linkerd 所需的两个证书:信任锚点和身份发行者。我们将使用 Smallstep CLI,step,来完成此操作,如 示例 3-1 所示。
证书与安全性
我们在这里生成证书时,并未真正考虑如何安全地管理私钥。这对于演示来说是可以接受的,但不能用于生产环境。我们将在第七章进一步探讨这个问题。
示例 3-1. 为 Linkerd 创建证书
# Start by creating your root certificate, which Linkerd refers to
# as the trust anchor certificate.
$ step certificate create root.linkerd.cluster.local ca.crt ca.key \
--profile root-ca --no-password --insecure
# Next, create the intermediary certificate. Linkerd refers to this
# as the identity issuer certificate.
$ step certificate create identity.linkerd.cluster.local issuer.crt issuer.key \
--profile intermediate-ca --not-after 8760h --no-password --insecure \
--ca ca.crt --ca-key ca.key
运行这些命令后,你将得到如 图 3-4 所示的信任层级。你的笔记本将保存信任锚点和身份发行者的公钥和私钥,而身份发行者的证书将由信任锚点签名。(尚未有工作负载证书:当 Linkerd 安装在集群中时,将会创建这些证书。)

图 3-4. 使用 step 命令创建的信任层级
保管好你的密钥!
记住,在实际场景中,保护私钥的安全性非常重要。即使在我们这里更加学术化的使用中,也要妥善保管——在我们讨论第七章中的证书轮换时会用到它。
Linkerd 文档详细介绍了如何创建证书的过程,请在遇到任何困难时参考最新版本的文档。
Helm 安装
生成证书后,可以使用 示例 3-2 中的命令使用 Helm 安装 Linkerd。再次强调,官方文档中包含最新的指令;但是,了解 示例 3-2 中的 --set-file 参数的作用非常重要:
-
--set-file identityTrustAnchorsPEM告诉 Helm 从中复制信任锚点的公钥的文件。这是我们需要的信任锚点的唯一密钥。 -
--set-file identity.issuers.tls.crtPEM和--set-file identity.issuers.tls.keyPEM告诉 Helm 从中复制身份颁发者的公钥和私钥文件,两者都是必需的。
示例 3-2. 使用 Helm 安装 Linkerd
# Add the Linkerd stable repo
$ helm repo add linkerd https://helm.linkerd.io/stable
# Update your Helm repositories
$ helm repo update
# Install the Linkerd CRDs
$ helm install linkerd-crds linkerd/linkerd-crds \
-n linkerd --create-namespace
# Install the Linkerd control plane
$ helm install linkerd-control-plane \
-n linkerd \
--set-file identityTrustAnchorsPEM=ca.crt \
--set-file identity.issuer.tls.crtPEM=issuer.crt \
--set-file identity.issuer.tls.keyPEM=issuer.key \
linkerd/linkerd-control-plane
# Ensure your install was successful
$ linkerd check
linkerd check 命令将告诉您集群中 Linkerd 的当前状态。用于确保您的安装成功完成。
一旦helm install完成,集群将拥有 Linkerd 运行所需的密钥副本,如图 3-5 所示。当然,这些密钥仍然存在于您的笔记本上,所以请小心处理它们!
权限很重要!
注意,信任锚点的私钥不在集群中,但身份颁发者的私钥在集群中。这对于 Linkerd 的运行是必需的。在现实世界中,您需要确保只有 Linkerd 本身能够看到该密钥。更详细的内容在第七章中有所涵盖。

图 3-5. helm install 后创建的信任层次结构
最后,我们可以使用其 Helm 图表安装 Linkerd Viz:
$ helm install linkerd-viz linkerd/linkerd-viz \
-n linkerd-viz --create-namespace
与之前一样,我们将监视安装以确保其成功:
$ linkerd check
始终使用您自己的 Prometheus
默认情况下,安装 Linkerd Viz 将安装一个内部的 Prometheus 实例。在生产环境中不要使用此 Prometheus,因为它没有配置持久存储;而是参见Linkerd 文档和示例 10-9 以获取关于使用外部 Prometheus 实例的信息。
配置 Linkerd
现在,您已完成 Linkerd 核心控制平面的安装,我们将暂停一下,并查看您在集群中配置 Linkerd 控制平面的选项。这必然会是一个控制平面常见配置点的摘要,而不是详尽的列表。
从 Linkerd 2.12 开始,控制平面通过linkerd-control-plane Helm 图表进行管理和配置。以下设置为 Linkerd 提供了重要的配置点。通过以下命令查看当前 Helm 图表值来找到特定的设置:
$ helm show values linkerd/linkerd-control-plane
我们将讨论一般设置,您需要将它们映射到您值文件中的适当位置。参见第十四章中 Linkerd Helm 值文件的一些示例。
集群网络
Linkerd 在安装时没有办法读取您的集群网络设置,因此linkerd-control-plane Helm 图表假设您的集群正在使用常见的网络范围之一作为其集群网络。如果您的集群 IP 地址不在以下范围内,您需要在安装时覆盖 IP 范围:
10.0.0.0/8, 100.64.0.0/10, 172.16.0.0/12, 192.168.0.0/16
Linkerd 控制平面资源
Linkerd 的默认安装不设置资源请求或限制。您应考虑为控制平面组件设置请求和限制,以帮助调度 Pod 并确保 Linkerd 具有所需的资源。请注意:从 Linkerd 2.12 开始,Linkerd 目标组件具有相对固定的内存占用量,随着集群中端点数量的增加而扩展。如果您设置的内存限制过低,可能会发现目标组件被“内存耗尽杀死”或 OOMKilled。
不透明端口和跳过端口
不透明端口和跳过端口是 Linkerd 对应的端口名称,应用特殊规则。您将希望查阅相关 Linkerd 文档获取有关该主题的最新信息。我们将在此处简要概述这些概念;您将在第四章中找到更多细节。
在 Linkerd 中,不透明 端口是指应将其视为通用 TCP 连接的端口。Linkerd 仍将在不透明流量上使用 mTLS,但不会执行协议检测或任何类型的协议特定逻辑,因此最终结果类似于简单的基于连接的网络负载均衡工作方式。如果您知道端口将成为网格的一部分并提供非 HTTP TCP 流量,则应在安装时将其标记为不透明。
请记住,在不透明端口上没有协议检测,因此也没有请求度量或每请求负载平衡。
本节旨在讨论全局、安装时 Linkerd 配置值。任何在安装时设置为不透明端口的端口将全局应用于所有工作负载。
默认不透明端口
以下端口是安装时配置的默认值:
-
SMTP:25 和 587
-
MySQL:3306 和,与 Galera 一起使用时,4444 端口。(4567 和 4568 端口也可能被 Galera 使用,但默认情况下不透明。)
-
PostgreSQL:5432
-
Redis:6379
-
Elasticsearch:9300
-
Memcached:11211
相比之下,跳过 端口是指您指示 Linkerd 完全忽略的端口。跳过的流量将完全绕过代理;网格将不会处理它。值得注意的是,Linkerd 无法加密跳过端口上的流量。
与不透明端口不同,您需要告知 Linkerd 跳过端口是指入站流量、出站流量还是两者兼有。
默认跳过端口
在标准 Linkerd 安装中,默认情况下会忽略 4567 和 4568 端口(Galera)。
总结
您现在应该对如何安装 Linkerd 有了良好的理解,以及对 Linkerd 的主要配置点有了理解。您还应该对安装 Linkerd 可能存在的摩擦点有了良好的掌握:具体而言,生成证书和处理非 HTTP 流量。您可以使用 Linkerd CLI 或 Linkerd Helm 图表安装 Linkerd,我们建议您默认使用 Helm。
第四章:向网格添加工作负载
在你的集群中运行 Linkerd 是一个很好的第一步。但是单独运行 Linkerd 是没有意义的:要从你的 Linkerd 集群中获得实际价值,你需要让工作负载在服务网格中运行。在本章中,我们将向你展示如何实现这一点。
工作负载与服务的区别
在本章中,我们将经常讨论“工作负载”——但有时我们也会讨论“服务”,以及“Services”。不幸的是,这三个概念在某些情况下有些微的不同含义:
服务
一个广泛用于控制 Kubernetes 为服务分配 DNS 名称和 IP 地址的 Kubernetes 资源(参见 图 4-1)。
工作负载
一个实际代表你执行工作的东西。工作负载通过网络接收请求并执行代码以执行操作。在 Kubernetes 中,它通常是一个或多个 Pod(提供计算),通常由 Deployment 或 DaemonSet 资源管理,再加上一个或多个服务(管理名称和 IP 地址),如 图 4-1 所示。
服务
一个不太正式的术语,根据上下文可能指代服务或工作负载。这种不精确性正是 Kubernetes 术语比我们希望的更加混乱的一个例子。

图 4-1. 工作负载与服务的区别
作为应用开发者,你通常可以只说“服务”,并相信人们能够接受这种含糊。不幸的是,在谈论服务网格时,我们经常需要更加精确,因此这里讨论的是 工作负载 而不是 服务。
向网格添加工作负载意味着什么?
“向网格添加工作负载”实际上意味着“向每个工作负载的 Pod 添加 Linkerd sidecar”,如 图 4-2 所示。
最终,这意味着更改 Pod 的定义以包含 sidecar 容器。虽然你可以通过手动编辑定义 Pod 的 YAML 来实现这一点,但让 Linkerd 来完成这些繁重的工作会更容易、更安全。
Linkerd 包含一个名为 linkerd-proxy-injector 的 Kubernetes 准入控制器。它的工作,不出所料,就是向工作负载的 Pod 注入 Linkerd 代理。当然,它不是盲目地执行此操作;相反,它会查找告诉它哪些 Pod 需要注入的 Kubernetes 注解,如 图 4-3 所示。

图 4-2. 向网格添加工作负载

图 4-3. 代理注入器
注入单个工作负载
处理注入的最常见方式是直接将 linkerd.io/inject: enabled 注解添加到 Pod 本身,通常通过向 Deployment、DaemonSet 等的 Pod 模板添加此注解来实现。每当 linkerd-proxy-injector 看到具有此注解的新 Pod 时,它将为你注入代理 sidecar 到该 Pod 中。
值得指出的是注解的值很重要:enabled 表示进行正常的边车注入。我们很快会看到其他值。
所有 Pod 都是平等的
不管使用何种资源创建 Pod,都无关紧要。部署、守护进程集、手工制作的复制集、Argo Rollouts 资源——它们都以完全相同的方式创建它们的 Pod。Linkerd 注入器注意到的是存在新 Pod,而不是造成其创建的原因。
在命名空间中注入所有工作负载
您可以将 linkerd.io/inject 注解添加到命名空间,而不是 Pod。一旦完成这个操作,该命名空间中创建的每个新 Pod 都将被注入(而且再次强调,导致新 Pod 创建的原因是无关紧要)。
这在自动化创建 Pod 的情况下非常有用,但在修改 Pod 自身的注解时可能会很困难或容易出错。例如,某些入口控制器每次更改资源时都会重新创建部署;与其费力地修改入口控制器使用的 Pod 模板(如果可能的话),不如只是注释将要创建部署的命名空间。
linkerd.io/inject 值
linkerd.io/inject 注解的值确实很重要——这不仅仅是有一个非空字符串的问题。有三个具体的值是有意义的:
linkerd.io/inject: enabled
最常见的情况:linkerd-proxy-injector 将在 Pod 中添加一个代理容器,并告知代理以“正常”模式运行。
linkerd.io/inject: ingress
linkerd-proxy-injector 将在 Pod 中添加一个代理容器,但代理将以“ingress”模式运行(我们将在第五章讨论)。
linkerd.io/inject: disabled
这明确告诉 linkerd-proxy-injector 即使有一个命名空间注解要求添加边车,也不添加代理边车。
我们将在第五章更详细地讨论入口模式:这是为只知道如何直接路由到工作负载端点的入口控制器而设置的一种解决方案。在大多数情况下,您应该使用 linkerd.io/inject: enabled 以获得“正常”模式。
为什么可能决定不将工作负载添加到网格中?
一般来说:
-
您总是希望将应用工作负载添加到网格中。
-
您永远不希望将集群基础设施添加到网格中。
因此,例如,kube-system 命名空间中的内容永远不会被注入。所有这些 Pod 都被设计为无论发生什么,都能保护自己,并且其中一些需要确保没有任何东西位于它们和网络层之间,因此不应该对它们进行注入。
同样,Kubernetes 转换 webhook(如在图 4-3 中的application-code命名空间中显示的)通常不应该位于网格中。webhook 机制本身已经对 TLS 提出了具体要求,而网格对此无济于事。这可能不会有害,但实际上没有意义。这里的另一个很好的例子是 CNI 实现:这些需要直接访问网络层,不应该被注入。
另一方面,运行在集群中的应用程序的工作负载应始终被注入到网格中。所有这些准则都显示在图 4-4 中。

图 4-4. 注入应用程序,而不是基础设施
其他代理配置选项
尽管基本的linkerd.io/inject注释是您必须提供的唯一代理配置选项,但实际上还有许多其他可以配置代理的内容。完整列表可在Linkerd 代理配置文档中找到,但从一开始就非常值得学习的两个领域是协议检测和Kubernetes 资源限制。
协议检测
正如我们在第一章中讨论的那样,Linkerd 非常注重操作简易性;在可能的情况下,Linkerd 尽量确保当您将应用程序引入网格时事情能够顺利进行。协议检测是这一点的关键部分,因为 Linkerd 必须了解连接上使用的协议,以正确管理连接,如图 4-5 所示。

图 4-5. 协议检测
Linkerd(或任何其他网格)需要了解在传输过程中使用的协议的几个原因。我们将仅触及其中的一些:
可观测性
如果 Linkerd 不理解协议流,就无法提供适当的指标。识别请求的开始和结束对于测量请求率和延迟至关重要。读取请求状态对于测量成功率至关重要。
可靠性
任何超出基本功能的可靠性特性都要求理解飞行中的协议。例如,考虑负载平衡:如果 Linkerd 不知道协议,它只能进行基于连接的负载平衡,其中传入的 TCP 连接分配给特定的工作负载 Pod。
然而,对于诸如 HTTP/2 和 gRPC 之类的协议,基于连接的负载平衡效果并不好。在这些协议中,单个长连接可以携带多个请求,并且多个请求可以同时活动。通过将单独的请求分配给工作负载 Pod,而不是将整个连接固定到一个 Pod,Linkerd 可以显著提高可靠性和性能。(有趣的是,Linkerd 会自动执行此操作,无需任何配置;只需安装 Linkerd,即可免费获取此功能。)
安全性
如果一个工作负载向另一个工作负载发起了 TLS 连接,Linkerd 不应尝试重新加密它。它也不应尝试在负载均衡中做任何复杂的事情,因为它无法看到连接内部的任何内容。(这意味着当工作负载之间连接时,最好让 Linkerd 为你执行 mTLS,而不是让它尝试处理 TLS 连接!)
当协议检测出现问题时
自动协议检测有一个主要限制:它只能用于那些连接建立和数据发送者是同一方的协议(客户端先发起协议)。对于数据接收者先发送数据的协议(服务器先发起协议),它将失败。
这种限制的原因是在 Linkerd 知道协议之前,它无法合理地决定如何进行负载均衡,因此无法确定连接哪个服务器,也无法知道服务器将会返回什么!每个代理都面临这种令人沮丧的循环问题。
在云原生世界中,许多——也许是大多数?——常见协议都是客户端先发起的协议;例如,HTTP、gRPC 和 TLS 本身都是客户端先发起的。不幸的是,还存在一些重要的服务器先发起的协议:SMTP、MySQL 和 Redis 就是例子。
如果 Linkerd 无法检测到协议,它将假定这是一个原始的 TCP 连接,因为这是始终可用的最低公共分母。问题在于对于服务器先发起通信的协议,Linkerd 将等待 10 秒钟,然后才假定无法检测到协议,而这显然不是你想要的。为了避免这种情况,你需要告诉 Linkerd 要么跳过这个连接,要么将其视为不透明。
不透明端口与跳过端口
当你告诉 Linkerd 跳过一个连接时,你实际上是告诉它完全不涉及该连接。事实上,Linkerd 代理根本不会触碰该连接:数据包直接从工作负载流向另一个工作负载。
这意味着 Linkerd 不能进行 mTLS、负载均衡、指标收集或者任何其他操作。连接实际上完全发生在网格之外。
另一方面,不透明连接会经过 Linkerd 代理,这意味着它会通过 mTLS 进行传输。它仍然被加密,并且 Linkerd 仍会执行已配置的任何策略,但你只会得到每个连接的指标和负载均衡(因为 Linkerd 知道它无法查看单个请求的内容)。
这一区别显示在图 4-6 中。

图 4-6. 不透明端口与跳过端口
这一切都意味着,如果您需要使用服务器先说话的协议,最好将它们标记为不透明,而不是完全跳过它们。仅在流量的目的地不是您的网格的一部分时才需要跳过。由于不透明连接仍然依赖于 Linkerd 代理执行 mTLS,如果没有代理接收连接,它们将无法工作!
配置协议检测
有两种方法可以告诉 Linkerd 有关协议的信息。您可以使用服务器资源,在我们讨论策略时会介绍(参见第八章),或者您可以使用以下注释来标记特定端口为不透明或跳过:
config.linkerd.io/opaque-ports
这些端口的连接始终将被视为不透明。
config.linkerd.io/skip-inbound-ports
在这些端口进入此工作负载的连接将始终被跳过。
config.linkerd.io/skip-outbound-ports
离开这个工作负载的连接在这些端口上将始终被跳过。
所有这些都接受逗号分隔的端口号或端口范围列表,因此以下所有内容都是合法的:
config.linkerd.io/opaque-ports: 25
这将只将端口 25 视为不透明。
config.linkerd.io/skip-inbound-ports: 3300,9900
这将跳过在端口 3300 或 9900 上进入的连接。
config.linkerd.io/skip-inbound-ports: 8000-9000
这将跳过任何端口在 8000 到 9000 之间(包括端口 8000 和 9000)收到的连接。
config.linkerd.io/skip-outbound-ports: 25,587,8000-9000
这将跳过在端口 25、端口 587 或 8000 到 9000 之间的任何端口上的连接。
还有一个config.linkerd.io/skip-subnets选项,它会跳过与任何列出的子网之间的任何连接。其参数是一个逗号分隔的无类别域间路由(CIDR)范围列表,例如config.linkerd.io/skip-subnets: 10.0.0.0/8,192.168.1.0/24。
默认不透明端口
从 Linkerd 2.12 开始,默认情况下,几个端口被标记为默认不透明(详见“默认不透明端口”)。
默认端口旨在允许各种服务器先说话的协议(如 MySQL 和 SMTP)与 Linkerd 无缝工作。如果您将这些端口用于客户端先说话的协议,您需要使用服务器资源来覆盖端口默认设置(或者更好的选择是为您的客户端先说话的协议选择一个不同的端口!)。
Kubernetes 资源限制
与协议检测相比,Kubernetes 资源限制要简单得多。有一组简单的注释设置,允许您指定资源请求和限制,如表 4-1 所示。
表 4-1. 资源请求和限制的 Linkerd 注释
| 注释 | 效果 |
|---|---|
config.linkerd.io/proxy-cpu-limit |
代理边车可以使用的最大 CPU 单位数量 |
config.linkerd.io/proxy-cpu-request |
代理边车请求的 CPU 单位数量 |
config.linkerd.io/proxy-ephemeral-storage-limit |
用于覆盖 limitEphemeralStorage 配置 |
config.linkerd.io/proxy-ephemeral-storage-request |
用于覆盖 requestEphemeralStorage 配置 |
config.linkerd.io/proxy-memory-limit |
代理侧车可以使用的最大内存量 |
config.linkerd.io/proxy-memory-request |
代理侧车请求的内存量 |
总结
所以,这就是:从头到尾指南,让您的工作负载成为 Linkerd 网格的有效组成部分。希望现在您已经很好地理解了如何使一切正常运作,以及沿途的要点(例如服务器优先协议)。接下来是让 Linkerd 和入口控制器良好协同工作。
第五章:入口和 Linkerd
每当您使用 Kubernetes 时,您总是需要找到一种方式,使得您集群外部的用户能够向运行在您集群内部的某些服务发出请求。这就是入口问题(参见图 5-1):集群希望保护内部的所有内容免受大可怕的互联网的侵害,但这却是您的合法用户所在之处。

图 5-1。入口问题
显然有一整类应用程序称为入口控制器,其唯一目的是解决入口问题。Linkerd 不包括入口控制器;相反,它允许您网状化任何喜欢的入口控制器,只要遵循某些规则。在本章中,您将学习如何使 Linkerd 与您选择的入口控制器相互配合。
有许多不同的入口控制器,它们以迷人的不同方式解决入口问题。然而,所有这些控制器都有一些共同的主题,如图 5-2 所示。

图 5-2。入口控制器高级架构
这些共同的主题包括:
-
它们都设计为位于集群边缘(通常是在
LoadBalancer类型的 Kubernetes 服务之后),直接暴露在互联网上,以便其客户端可以访问它们。安全性始终是入口控制器的主要关注点。 -
他们总是有一种方法来控制外部的哪些请求被路由到内部的哪些服务。这是另一个关键的安全问题:安装入口控制器不能意味着集群中的所有服务都向互联网开放。
所有流行的入口控制器在OSI 层 7支持复杂的路由控制,通常专注于 HTTP 和 gRPC。许多还支持对 OSI 层 4 连接进行更有限的路由控制:
-
在 OSI 层 7(应用层),入口控制器可能具有诸如“将主机名为
foo.example.com且路径以/bar/开头的 HTTP 请求路由到名为bar-service的服务”的能力。 -
在 OSI 层 4(传输层),其功能更可能是“将到达端口 1234 的 TCP 连接路由到名为
bar-service的服务”。
根据使用的入口控制器的不同,用户配置路由的实际方式可能会有很大差异。
-
-
入口控制器始终可以终止和发起 TLS 连接(主要是 HTTPS),以处理集群边缘的安全性。这并不扩展 Linkerd 的 mTLS 到入口控制器的客户端;相反,它创建了两个 TLS 操作的分离领域,并要求入口控制器在它们之间进行转换,如图 5-3 所示。
![luar 0503]()
图 5-3。入口控制器和 TLS
通常将两个 TLS 世界分开是有道理的,因为 Ingress 控制器需要向用户呈现与用户期望的证书匹配的证书,但当其代理与 Linkerd 交互时,需要提供一个正确配置的工作负载身份。这两者并不相同,不应混淆。允许 Ingress 控制器与其客户端管理 TLS,同时允许 Linkerd 在集群内部管理 mTLS,这是一个强大的组合。
-
最后,许多 Ingress 控制器提供诸如最终用户认证、断路器、速率限制等功能。这些 Ingress 控制器也可能被称为API 网关。如何处理最终用户认证的示例在 图 5-4 中展示。
![luar 0504]()
图 5-4. 提供最终用户认证的 API 网关
API 网关可以对用户请求发生的具体情况有很大的灵活性,确实可以实现非常复杂的功能——尽管这显然超出了本书的范围。
使用 Linkerd 的 Ingress 控制器
Linkerd 在使用哪种 Ingress 控制器方面没有太多限制;几乎任何一个都能正常工作,通常不会出现太多问题。从 Linkerd 的角度来看,Ingress 只是另一个网格工作负载,而从 Ingress 控制器的角度来看,Linkerd 大部分时候是不可见的。
其他网格中的 Ingress 控制器
在这里,一些网格采取了非常不同的方法:它们配备了一个与网格紧密集成的 Ingress 控制器。Linkerd 采用了非常与 Ingress 无关的方法,因为这往往增加了灵活性,减少了操作复杂性,并使得在不同时间采用 Ingress 控制器和服务网格更加容易。
Ingress 控制器只是另一个网格工作负载
从 Linkerd 的角度来看,Ingress 控制器大部分时间只是网格中的一个工作负载,如 图 5-5 所示。Linkerd 并不担心客户端在集群外部与 Ingress 控制器通信的事情:你仍然需要将一个 Sidecar 注入 Ingress 控制器,所有常规的 Linkerd 功能如 mTLS 和度量都能正常工作。

图 5-5. Ingress 控制器只是另一个工作负载
Ingress 控制器几乎总是需要特殊处理的一种方式是,你几乎总是希望告诉 Linkerd 跳过 Ingress 控制器的入站端口。这是因为 Ingress 控制器可能需要访问客户端的 IP 地址进行路由或授权,但如果 Linkerd 处理连接,那么 Ingress 控制器将看到的唯一 IP 地址是 Linkerd 代理的 IP 地址。参见 图 5-6。

图 5-6. 跳过 Ingress 控制器的入站流量
Ingress 控制器专为边缘设计
记住,入口控制器的工作之一是位于集群边缘,因此它已经被设计为安全处理直接来自互联网的连接。告诉 Linkerd 不处理入口控制器的传入连接在安全性上不应该存在任何问题。
您将使用我们在 第四章 中介绍的 config.linkerd.io/skip-inbound-ports 注释来跳过传入端口。请注意端口号 - 您需要跳过实际上入口控制器 Pod 在其上监听的端口(通常不是客户端使用的端口!)。例如,如果您将您的入口控制器与此类服务关联:
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
type: LoadBalancer
ports:
- name: http
port: 80
protocol: TCP
targetPort: 8080
您需要跳过入站端口 8080;尝试跳过入站端口 80 将毫无效果。因此,正确的注释应该是:
config.linkerd.io/skip-inbound-ports: 8080
Linkerd 几乎是不可见的
从入口控制器的角度来看,Linkerd 基本上是不可见的。这是设计上的:将 Linkerd 添加到运行中的应用程序意味着一切都能正常工作!但是为了确保一切尽可能顺利地工作,有两件事需要注意:入口控制器应在集群内使用明文,并且应路由到服务而不是端点。
在集群内使用明文
我们知道:这可能是多年来您第一次看到有人建议使用明文而不是 TLS。明确一下,我们 不 是在讨论从客户端到入口控制器的连接!(那绝对要使用 HTTPS。)这里,我们谈论的是从入口控制器到集群中的网格工作负载建立的连接,如 图 5-7 所示。

图 5-7. 让 Linkerd 在集群内处理 mTLS
对于这些连接,应使用明文。如果入口控制器向工作负载发起 TLS 连接,Linkerd 将无法执行更多操作,只能进行每连接代理;您将错过每请求负载均衡、正确的请求指标以及 Linkerd 提供的许多其他非常有用的功能。使用明文连接可以享受所有高级功能,并且仍然安全,因为 Linkerd 的 mTLS 将保护连接。
路由到服务,而不是端点
这是 Kubernetes 术语和概念特别具有挑战性的一个领域。Kubernetes 服务实际上有三个完全不同的部分,这三个部分对于这一点都是相关的:
-
服务会导致集群的 DNS 服务中出现一个名称。
-
该 DNS 名称与服务本身的单个 IP 地址相关联。
-
该服务还与一组 Pod 相关联,每个 Pod 的 IP 地址与其他任何 Pod 的 IP 地址以及服务的 IP 地址都不同。
Pod 的 IP 地址的集合称为服务的 终端节点(Endpoints)。(Kubernetes 也有称为 Endpoints 和 EndpointSlices 的资源,但我们目前只讨论 Pod IP 地址的集合。)
这些部分在 图 5-8 中显示。再次强调,在考虑服务网格路由时,这三个部分都是相关的。

图 5-8. Kubernetes 服务的三个独立部分
入口控制器使用哪个 IP 地址进行连接是很重要的,因为通常 Linkerd 只会对连接到服务 IP 地址的连接进行负载均衡,而不是 直接连接到端点 IP 地址的连接,如图 5-9 所示。

图 5-9. Linkerd 如何选择路由的方式
Linkerd 以这种方式处理路由,以最大化应用设计者的选择:通过将路由到服务 IP 的方式,让入口控制器简单地将所有负载均衡决策交给 Linkerd 是很容易的,同时仍然可以让入口控制器自行进行负载均衡(通过直接路由到端点 IP)。
在大多数常见情况下,让入口控制器路由到服务 IP 是充分利用 Linkerd 的最简单方式。
网关 API 和服务路由
Gateway API 对此建议引入了一个变数:它需要支持云网关控制器,这些控制器实际上并未在集群中运行,因此无法在其旁边运行 Linkerd 代理。与此同时,这些云网关控制器可能极度敏感于延迟,因此它们不太可能支持服务路由。
这是 GAMMA 计划和 Gateway API 整体中的一个活跃工作领域。
根据使用的入口控制器不同,您可能需要特别配置入口控制器来执行此操作——或者您可能会发现无法配置您的入口控制器将路由到服务 IP。对于这些入口控制器,您将需要使用 Linkerd 的 入口模式。
入口模式
当入口模式激活并且 Linkerd 收到一个带有 l5d-dst-override 头部设置为完全限定的服务 DNS 名称的端点 IP 的请求时,Linkerd 将会将该请求路由到由 l5d-dst-override 头部指定的服务 IP 地址,如图 5-10 所示。
例如,给定命名空间 my-ns 中的服务 my-service,如果您直接向 my-service 的其中一个端点 IP 发送请求,但设置其如下所示的 l5d-dst-override 头部,那么 Linkerd 将会将此连接视为已连接到 my-service 的服务 IP:
l5d-dst-override: my-service.my-ns.svc.cluster.local

图 5-10. Linkerd 入口模式路由
入口控制器必须注入 l5d-dst-override
要有效使用入口模式,入口控制器必须将 l5d-dst-override 头部注入每个请求中。不能注入此头部的入口控制器与 Linkerd 入口模式不兼容。Linkerd 无法自行创建 l5d-dst-override 头部,因为通常情况下,无法从其端点 IP 地址确定服务的名称。这是因为给定的 Pod 可能属于多个服务。
如果可能的话,通常最好配置入口控制器来路由到 Services,而不是使用入口模式。
要使用入口模式,请注入代理:
linkerd.io/inject: ingress
而不是:
linkerd.io/inject: enabled
具体的入口控制器示例
这里有一些具体的示例,展示了不同入口控制器与 Linkerd 配合使用的配置。这并不是一个详尽的列表,仅仅是一个方便的集合,展示了一些可能性。更多关于此主题的信息,请参阅Linkerd 入口文档。
对于我们这里的示例,我们将看看 Emissary-ingress、NGINX 和 Envoy Gateway。
Emissary-ingress
使者入口 是一个自 2017 年以来存在的开源、Kubernetes 原生的 API 网关。它构建在 Envoy 代理之上,专注于操作简易性和自助配置,并且自 2021 年以来一直是 CNCF 的孵化项目。它定义了自己的本地配置 CRD,但也可以使用 Ingress 资源或 Gateway API。(完整披露:Flynn 是 Emissary 的原始作者。)
就设置 Emissary 与 Linkerd 一起使用而言,实际上没有太多需要深入挖掘的地方;它基本上只是工作而已。Emissary 默认路由到 Services,因此在将 Emissary 添加到 Linkerd 网格时唯一需要考虑的是,如果需要 Emissary 知道客户端 IP 地址,则跳过 Emissary 的传入端口。并且您需要确保 Emissary 不会向工作负载发起 TLS。
NGINX
NGINX 是一个开源的 API 网关和 Web 服务器,早在 Kubernetes 出现之前就已存在。虽然它本身不是 CNCF 项目,但它作为 ingress-nginx Kubernetes 入口控制器 的核心服务,是最早使用 Ingress 资源的入口控制器之一,并且已经足够流行,以至于当人们谈论在 Kubernetes 中运行 NGINX 时,通常指的是 ingress-nginx。
默认情况下,ingress-nginx 将路由到端点 IP,而不是服务 IP。要告诉它改为路由到服务 IP,您需要在您的 Ingress 资源上包含一个 ingress-nginx 注解:
nginx.ingress.kubernetes.io/service-upstream: "true"
安装和网格化 ingress-nginx 之后,应该是无痛的。记得查看跳过传入端口的设置!
Envoy Gateway
截至目前,Envoy Gateway 最近已达到版本 1.0。它提供了一个有趣的机会,可以探索使用 Gateway API 来管理 Linkerd 集群中的入口和网格。
Gateway API 具有一个有趣的特性,即用户不直接安装处理数据的 Pod(数据平面),而是安装了一个理解如何监视 Gateway 资源的 Gateway API 控制平面。因此,当用户创建 Gateway 时,Gateway API 控制平面会创建数据平面的 Pods。
Envoy Gateway 作为 Gateway API 控制平面,解释了这种设计特性意味着每当它看到对其 Gateway 资源的更改时,实际上会删除并重新创建数据平面的 Pods。这使得管理将 Envoy Gateway 数据平面注入到网格中变得有些具有挑战性!处理这个问题的最有效方式是在 envoy-gateway-system 命名空间中应用 linkerd.io/inject 注释,这是数据平面部署将被创建的地方。
此外,Envoy Gateway 总是路由到版本 1.0 中的端点 IP 地址。在未来版本的 Envoy Gateway 中解决此问题之前,这限制了 Linkerd 在使用 Envoy Gateway 时进行高级路由的能力。(目前可以在入口模式中混入 Envoy Gateway,然后配置 HTTPRoutes 来注入 l5d-dst-override 头,但目前还有些手动操作。)
由于 Linkerd 总是负责管理安全性(包括加密和策略),Envoy Gateway 与 Linkerd 的结合仍然是一种实用且有趣的组合。只需注意入站端口,就像处理其他入口控制器一样!
总结
Linkerd 的一个优点是其能够与各种入口控制器协同工作。只要特定的入口控制器可以接受 Linkerd sidecar 并路由到 Services,它应该可以与 Linkerd 无缝配合。这让你可以自由选择适合团队和应用的任何入口控制器,并确信它能与 Linkerd 兼容。
第六章:Linkerd CLI
Linkerd 命令行界面(CLI)是与 Linkerd 控制平面进行交互的有用工具。CLI 可以帮助你检查 Linkerd 实例的健康状态,查看有关代理和证书的详细信息,排除异常行为并查看策略。这是直接与 Linkerd 交互的推荐方式。它处理你需要处理的所有主要任务,并为验证和检查 Linkerd 提供重要工具。
在本章中,我们将介绍 CLI 可以执行的一些最有用的操作,并说明如何最好地利用它。当然,随着新的 Linkerd 发布的推出,CLI 当前也在不断发展,因此随时关注官方文档是非常重要的。
安装 CLI
CLI 与 Linkerd 的其他部分一起进行版本管理,因此当你安装 CLI 时,需要选择使用哪个发布通道。
要从稳定通道安装,请参考供应商说明(例如Linkerd 的 Buoyant Enterprise)。
要从 edge 通道完全安装开源的 Linkerd,你可以参考Linkerd 快速入门。在撰写本文时,操作如下:
$ curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install-edge | sh
无论哪种情况,一旦安装了 CLI,你都需要将其以适合你的 shell 的方式添加到 PATH 中。例如,如果你使用 bash,你可以直接修改 PATH 变量:
$ export PATH=$HOME/.linkerd2/bin:$PATH
更新 CLI
要更新 CLI,只需重新运行安装命令。随着时间的推移,你会在本地存储多个版本,并可以在它们之间进行选择。
安装特定版本
通常,Linkerd CLI 安装程序(任何通道都是如此)将安装最新版本的 CLI。你可以通过设置 LINKERD2_VERSION 环境变量来强制安装特定版本,例如,在使用 edge 通道时:
$ curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install-edge \
| LINKERD2_VERSION="stable-2.13.12" sh
设置 LINKERD2_VERSION 环境变量来执行 sh 命令,而不是 curl。
注意在上述命令中设置 LINKERD2_VERSION 环境变量的位置:它需要设置为执行 curl 下载的脚本的 sh 命令,而不是 curl 命令本身。为 curl 命令设置环境变量不会起作用。
其他安装方式
如果你使用的是 Mac,Homebrew 是安装 CLI 的简单方法:只需 brew install linkerd。你也可以直接从Linkerd 发布页面下载 CLI。
使用 CLI
CLI 的工作方式与任何其他 Go CLI 类似,例如 kubectl:
$ linkerd *`command`* *`[``options``]`*
*command* 告诉 CLI 你希望做什么;*options* 是特定命令的可选参数。你可以随时使用 --help 选项获取帮助。例如,linkerd --help 将告诉你可用的命令:
$ linkerd --help
linkerd manages the Linkerd service mesh.
Usage:
linkerd [command]
Available Commands:
authz List authorizations for a resource
check Check the Linkerd installation for potential problems
completion Output shell completion code for the specified shell (bash, zsh
or fish)
diagnostics Commands used to diagnose Linkerd components
help Help about any command
identity Display the certificate(s) of one or more selected pod(s)
inject Add the Linkerd proxy to a Kubernetes config
install Output Kubernetes configs to install Linkerd
install-cni Output Kubernetes configs to install Linkerd CNI
jaeger jaeger manages the jaeger extension of Linkerd service mesh
multicluster Manages the multicluster setup for Linkerd
profile Output service profile config for Kubernetes
prune Output extraneous Kubernetes resources in the linkerd control
plane
uninject Remove the Linkerd proxy from a Kubernetes config
uninstall Output Kubernetes resources to uninstall Linkerd control plane
upgrade Output Kubernetes configs to upgrade an existing Linkerd control
plane
version Print the client and server version information
viz viz manages the linkerd-viz extension of Linkerd service mesh
Flags:
--api-addr string Override kubeconfig and communicate directly
with the control plane at host:port (mostly
for testing)
--as string Username to impersonate for Kubernetes
operations
--as-group stringArray Group to impersonate for Kubernetes
operations
--cni-namespace string Namespace in which the Linkerd CNI plugin is
installed (default "linkerd-cni")
--context string Name of the kubeconfig context to use
-h, --help help for linkerd
--kubeconfig string Path to the kubeconfig file to use for CLI
requests
-L, --linkerd-namespace string Namespace in which Linkerd is installed
($LINKERD_NAMESPACE) (default "linkerd")
--verbose Turn on debug logging
Use "linkerd [command] --help" for more information about a command.
正如这个输出所示,你还可以获取特定命令的帮助信息。例如,linkerd check --help 将获取 check 命令的帮助,如下所示:
$ linkerd check --help
Check the Linkerd installation for potential problems.
The check command will perform a series of checks to validate that the linkerd
CLI and control plane are configured correctly. If the command encounters a
failure it will print additional information about the failure and exit with a
non-zero exit code.
Usage:
linkerd check [flags]
Examples:
# Check that the Linkerd control plane is up and running
linkerd check
# Check that the Linkerd control plane can be installed in the "test"
# namespace
linkerd check --pre --linkerd-namespace test
# Check that the Linkerd data plane proxies in the "app" namespace are up and
# running
linkerd check --proxy --namespace app
Flags:
--cli-version-override string Used to override the version of the cli
(mostly for testing)
--crds Only run checks which determine if the
Linkerd CRDs have been installed
--expected-version string Overrides the version used when checking
if Linkerd is running the latest version
(mostly for testing)
-h, --help help for check
--linkerd-cni-enabled When running pre-installation checks
(--pre), assume the linkerd-cni plugin is
already installed, and a NET_ADMIN check
is not needed
-n, --namespace string Namespace to use for --proxy checks
(default: all namespaces)
-o, --output string Output format. One of: table, json, short
(default "table")
--pre Only run pre-installation checks, to
determine if the control plane can be
installed
--proxy Only run data-plane checks, to determine
if the data plane is healthy
--wait duration Maximum allowed time for all tests to pass
(default 5m0s)
Global Flags:
--api-addr string Override kubeconfig and communicate directly
with the control plane at host:port (mostly
for testing)
--as string Username to impersonate for Kubernetes
operations
--as-group stringArray Group to impersonate for Kubernetes
operations
--cni-namespace string Namespace in which the Linkerd CNI plugin is
installed (default "linkerd-cni")
--context string Name of the kubeconfig context to use
--kubeconfig string Path to the kubeconfig file to use for CLI
requests
-L, --linkerd-namespace string Namespace in which Linkerd is installed
($LINKERD_NAMESPACE) (default "linkerd")
--verbose Turn on debug logging
选定的命令
linkerd CLI 支持许多命令。正如始终如此的官方文档所述,这里我们将总结一些最广泛使用的命令。这些是您应该随时掌握的命令。
linkerd version
关于了解的第一个命令是 linkerd version,它简单地报告 linkerd CLI 的运行版本和(如果可能的话)Linkerd 控制平面的版本:
$ linkerd version
Client version: stable-2.14.6
Server version: stable-2.14.6
如果您的集群中没有运行 Linkerd,linkerd version 将显示服务器版本为 unavailable。
如果 linkerd version 无法与您的集群通信,它将将其视为错误。您可以使用 --client 选项仅检查 CLI 本身的版本,甚至不尝试与集群通信:
$ linkerd version --client
Client version: stable-2.14.6
CLI 版本与控制平面版本的对比
非常重要的是记住 CLI 版本与控制平面版本独立。一些 CLI 命令非常复杂,进行了许多微妙的操作,因此确保您的 CLI 版本与控制平面版本匹配至关重要。主要版本相差一个版本是可以的,但不支持超过一个版本的差异。
linkerd check
linkerd check 命令提供了一个快速查看集群中 Linkerd 健康状态的视图。它会检测许多已知的失败条件,并允许您运行特定于扩展的健康检查。这个看似简单的命令实际上提供了许多强大的工具,用于验证和检查当前网格的状态。
使用 linkerd check 的最简单也是最完整的方法是不带参数地运行它:
$ linkerd check
这将运行一组默认检查,既相当详尽,也在合理的时间内完成,包括(除了许多其他事项):
-
确保 Linkerd 在默认命名空间中正确安装。
-
检查 Linkerd 证书是否有效
-
运行所有已安装扩展的检查
-
双重检查必要的权限
运行此命令将为您提供有关集群中 Linkerd 当前状态的许多见解,事实上,如果您需要针对 Linkerd 提交 bug 报告,您将总是被要求包含 linkerd check 的输出。
linkerd check --pre
预检选项运行一组检查,确保您的 Kubernetes 环境准备好安装 Linkerd:
$ linkerd check --pre
这是唯一使用 linkerd check 的情况,不要求 Linkerd 已经安装。预检查确保您的集群符合运行 Linkerd 的最低技术要求,并且您有执行核心 Linkerd 安装所需的适当权限。这是准备在新集群上安装 Linkerd 的一个有用步骤。
预检和 CNI 插件
如果您计划安装带有 CNI 插件的 Linkerd,则需要运行 linkerd check --pre --linkerd-cni-enabled,以便 linkerd check 不尝试检查 NET_ADMIN 权限。
linkerd check --proxy
你还可以告诉 linkerd check 明确检查数据平面:
$ linkerd check --proxy
代理检查运行了基本 linkerd check 命令执行的许多检查,但它还运行了特定于数据平面的额外检查,例如验证 Linkerd 代理是否正在运行。
Linkerd 扩展检查
每个安装的 Linkerd 扩展都有自己特定的一组检查,它将在 linkerd check 运行期间运行。如果需要,你还可以仅针对特定扩展运行检查,例如,你可以仅运行 Linkerd Viz 扩展的检查:
$ linkerd viz check
为什么要限制检查?
请记住,linkerd check 不带任何参数将运行所有已安装扩展的检查。将检查限制到单个扩展主要有助于减少 linkerd check 运行的时间。
linkerd check 的附加选项
linkerd check 命令遵循所有全局 CLI 覆盖项,例如更改 Linkerd 安装的命名空间 (--namespace)、修改你的 KUBECONFIG (--kubeconfig) 或 Kubernetes 上下文 (--context)。此外:
-
--output允许你指定输出类型,如果想要覆盖默认的表格输出,这将非常有用。选项包括json、basic、short和table。如果打算以程序化方式消费检查数据,输出 JSON 尤其有帮助。 -
--wait可以覆盖检查等待的时间,在出现问题时。默认值为 5 分钟,在许多情况下可能会显得过长。
linkerd inject
linkerd inject 命令读取 Kubernetes 资源并输出已修改以适当添加 Linkerd 代理容器的新版本。linkerd inject 命令:
-
从其标准输入、本地文件或 HTTPS URL 读取资源
-
可以同时操作多个资源
-
知道只修改 Pods,并且不会触及其他类型的资源
-
允许你配置代理
-
将修改后的资源输出到标准输出,实际应用它们的任务留给了你
最后一点值得重申:linkerd inject 永远不会直接修改其来源之一。相反,它会输出修改后的 Kubernetes 资源,以便你自行应用它们,将它们包含在 Git 存储库中,或者根据环境进行其他适当操作。这种“输出而不覆盖”的习惯用语在整个 linkerd CLI 中很常见。
使用 linkerd inject 可以非常简单地进行如下操作:
$ linkerd inject https://url.to/yml | kubectl apply -f -
如常,你可以通过运行 linkerd inject --help 查找更多示例并查看完整文档。
你必须处理应用注入的资源
关于 linkerd inject 最重要的一点是,它本身并不会对你的集群做出任何更改。你始终需要自行将 linkerd CLI 的输出应用到你的集群中,可以通过简单地将输出传递给 kubectl apply,或者提交到 GitOps 进行处理,或者其他方式。
注入到入口模式
--ingress 标志为工作负载设置入口模式注解。在在您的入口上设置此标志或相应的注解之前,请验证是否需要。您可以查看入口文档以获取有关入口模式的更多详细信息。
手动注入
默认情况下,linkerd inject 只会向您的工作负载 Pod 添加 linkerd.io/inject 注解,信任代理注入器来完成重型工作。设置 --manual 标志会指示 CLI 直接向您的清单添加 sidecar 容器,绕过代理注入器。
--manual 标志为在需要控制一些常规配置机制不支持的代理配置时提供了一个有价值的工具。但是,请注意在直接修改代理配置时要小心,因为您可能会迅速使自己的代理配置脱离整体配置。
注入调试 sidecar
设置 --enable-debug-sidecar 将向您的工作负载添加一个注解,这会导致代理注入器向您的 Pod 添加一个额外的调试 sidecar。在尝试使用调试 sidecar 之前,务必阅读第十五章和调试容器文档。
linkerd identity
linkerd identity 命令为故障排除 Pod 证书提供了一个有用的工具。它允许您查看任何 Pod 或 Pods 的证书详细信息;例如,这里是如何获取属于 Linkerd 目标控制器的 Pod 的身份:
$ linkerd identity -n linkerd linkerd-destination-7447d467f8-f4n9w
POD linkerd-destination-7447d467f8-f4n9w (1 of 1)
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 3 (0x3)
Signature Algorithm: ECDSA-SHA256
Issuer: CN=identity.linkerd.cluster.local
Validity
Not Before: Apr 5 13:51:13 2023 UTC
Not After : Apr 6 13:51:53 2023 UTC
Subject: CN=linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local
Subject Public Key Info:
Public Key Algorithm: ECDSA
Public-Key: (256 bit)
X:
98:41:63:15:e1:0e:99:81:3c:ee:18:a5:55:fe:a5:
40:bd:cf:a2:cd:c2:e8:30:09:8c:8a:c6:8a:20:e7:
3c:cf
Y:
53:7e:3c:05:d4:86:de:f9:89:cb:73:e9:37:98:08:
8f:e5:ec:39:c3:6c:c7:42:47:f0:ea:0a:c7:66:fe:
8d:a5
Curve: P-256
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
X509v3 Authority Key Identifier:
keyid:37:C0:12:A1:AC:2D:A9:36:2D:35:83:6B:5C:99:9A:A2:5E:9C:E5:C5
X509v3 Subject Alternative Name:
DNS:linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local
Signature Algorithm: ECDSA-SHA256
30:45:02:20:4a:fb:02:db:17:e7:df:64:a4:7b:d2:08:a2:2e:
66:e5:a4:74:14:35:d5:1a:f7:fc:15:95:9b:73:60:dd:78:a4:
02:21:00:8c:12:fb:bf:80:7a:c4:25:91:0c:ac:03:37:ca:e0:
82:d5:9d:9b:54:f1:20:b0:f0:14:e0:ef:ae:a8:ba:70:00
您的 Pod 身份将不同
如果您尝试此命令,则您的 Pod ID 和具体的证书信息将不同。但是,linkerd identity 提供的所有信息都不是敏感信息;它只显示公共信息。运行它是完全安全的。
您可以使用此命令的输出检查给定 Pod 证书的有效性。它还会告诉您哪个权威签署了证书,因此您可以检查它是否由正确的中介和根 CA 签署。
linkerd diagnostics
linkerd diagnostics 命令是一个强大的工具,允许平台运营商直接从 Linkerd 中收集信息。它将允许您直接从各种 Linkerd 组件的指标端点中抓取详细信息。
该命令还允许您诊断诸如 Linkerd 的快速失败错误之类的难以识别的条件,方法是列出给定服务的端点。这里给出了一些示例;还可以查看 Linkerd 网站上的最新文档。
收集指标
linkerd diagnostics 命令可以直接从控制平面和数据平面的指标端点收集数据。要收集控制平面指标,请使用此命令:
$ linkerd diagnostics controller-metrics
#
# POD linkerd-destination-8498c6764f-96tqr (1 of 5)
# CONTAINER destination
#
# HELP cluster_store_size The number of linked clusters in the remote discove...
# TYPE cluster_store_size gauge
cluster_store_size 0
# HELP endpoint_profile_updates_queue_overflow A counter incremented whenever...
# TYPE endpoint_profile_updates_queue_overflow counter
endpoint_profile_updates_queue_overflow 0
# HELP endpoint_updates_queue_overflow A counter incremented whenever the end...
# TYPE endpoint_updates_queue_overflow counter
endpoint_updates_queue_overflow{service="kubernetes.default.svc.cluster.local...
# HELP endpoints_cache_size Number of items in the client-go endpoints cache
# TYPE endpoints_cache_size gauge
endpoints_cache_size{cluster="local"} 17
...
要收集给定代理或一组代理的指标数据,请使用以下命令:
$ linkerd diagnostics proxy-metrics -n emojivoto deploy/web
#
# POD web-5b97875957-xn269 (1 of 1)
#
# HELP inbound_http_authz_allow_total The total number of inbound HTTP reques...
# TYPE inbound_http_authz_allow_total counter
inbound_http_authz_allow_total{target_addr="0.0.0.0:4191",target_ip="0.0.0.0"...
inbound_http_authz_allow_total{target_addr="0.0.0.0:4191",target_ip="0.0.0.0"...
# HELP identity_cert_expiration_timestamp_seconds Time when the this proxy's ...
# TYPE identity_cert_expiration_timestamp_seconds gauge
identity_cert_expiration_timestamp_seconds 1705071458
# HELP identity_cert_refresh_count The total number of times this proxy's mTL...
# TYPE identity_cert_refresh_count counter
identity_cert_refresh_count 1
# HELP request_total Total count of HTTP requests.
# TYPE request_total counter
request_total{direction="inbound",target_addr="0.0.0.0:4191",target_ip="0.0.0...
request_total{direction="inbound",target_addr="0.0.0.0:4191",target_ip="0.0.0...
...
linkerd diagnostics生成原始的 Prometheus 指标,因此如果您使用这些命令,您需要已经知道您要查找的信息。此外,请注意由于空间原因,示例输出已被截断——这些命令生成的输出比这里展示的要多得多(通常是数百行或更多)。
检查端点
在 Linkerd 中最难调试的问题之一是当linkerd2-proxy发出表明其处于failfast状态的消息时。failfast状态在第十五章中有更详细的讨论,但落入failfast的一个非常常见的原因是某个服务没有任何有效的端点。您可以使用linkerd diagnostics endpoints检查这种情况。例如,在这里我们检查emojivoto 示例应用程序的emoji-svc服务的端点。
$ linkerd diagnostics endpoints emoji-svc.emojivoto.svc.cluster.local:8080
NAMESPACE IP PORT POD SERVICE
emojivoto 10.42.0.15 8080 emoji-5b97875957-xn269 emoji-svc.emojivoto
请注意,您必须提供服务的完全合格的 DNS 名称以及端口号。如果找不到有效的端点,linkerd diagnostics endpoints将报告未找到端点,并且重要的是,请求将进入failfast状态。
诊断策略
从 Linkerd 2.13 开始,有一个新的linkerd diagnostics policy命令,可以提供有关 Linkerd 高级路由策略引擎的洞察。例如,您可以查看应用于smiley服务在faces命名空间端口 80 上的策略(如果您运行的是Faces 演示应用程序):
$ linkerd diagnostics policy -n faces svc/smiley 80 > smiley-diag.json
linkerd diagnostics policy的输出是非常详细的 JSON,因此几乎总是将其重定向到文件(如我们在这里所做的)或less、bat或类似工具中是一个好主意。您将看到http1.1、http2等各个部分,并且每个部分都将有一个非常详细的——再次强调,详细到令人叹为观止——策略细分。
举例来说,您可能会看到类似于示例 6-1 中的输出,描述未应用高级策略时 HTTP/2 流量的情况。
示例 6-1. 没有高级策略的 HTTP/2 输出块
http2:
routes:
- metadata:
Kind:
Default: http
rules:
- backends:
Kind:
FirstAvailable:
backends:
- backend:
Kind:
Balancer:
Load:
PeakEwma:
decay:
seconds: 10
default_rtt:
nanos: 30000000
discovery:
Kind:
Dst:
path: smiley.faces.svc.cluster.local:80
metadata:
Kind:
Default: service
queue:
capacity: 100
failfast_timeout:
seconds: 3
matches:
- path:
Kind:
Prefix: /
或者,假设您将显示在示例 6-2 中的 HTTPRoute 资源应用于分割发送到smiley的流量,使一半的流量继续发送到smiley工作负载,另一半被重定向到smiley2。(HTTPRoutes 在第九章中有更详细的讨论。)
示例 6-2. HTTPRoute 流量分割
apiVersion: policy.linkerd.io/v1beta3
kind: HTTPRoute
metadata:
name: smiley-split
namespace: faces
spec:
parentRefs:
- name: smiley
kind: Service
group: core
port: 80
rules:
- backendRefs:
- name: smiley
port: 80
weight: 50
- name: smiley2
port: 80
weight: 50
在生效的 HTTPRoute 条件下,linkerd diagnostics policy可能生成一个http2块,如示例 6-3 所示,显示确实正在分割流量。
示例 6-3. 带有流量分割的 HTTP/2 输出块
http2:
routes:
- metadata:
Kind:
Resource:
group: policy.linkerd.io
kind: HTTPRoute
name: smiley-split
namespace: faces
rules:
- backends:
Kind:
RandomAvailable:
backends:
- backend:
backend:
Kind:
Balancer:
Load:
PeakEwma:
decay:
seconds: 10
default_rtt:
nanos: 30000000
discovery:
Kind:
Dst:
path: smiley.faces.svc.cluster.local:80
metadata:
Kind:
Resource:
group: core
kind: Service
name: smiley
namespace: faces
port: 80
queue:
capacity: 100
failfast_timeout:
seconds: 3
weight: 50
- backend:
backend:
Kind:
Balancer:
Load:
PeakEwma:
decay:
seconds: 10
default_rtt:
nanos: 30000000
discovery:
Kind:
Dst:
path: smiley2.faces.svc.cluster.local:80
metadata:
Kind:
Resource:
group: core
kind: Service
name: smiley2
namespace: faces
port: 80
queue:
capacity: 100
failfast_timeout:
seconds: 3
weight: 50
matches:
- path:
Kind:
Prefix: /
随着 Linkerd 的发展,这些输出会发生变化,因此要对这些示例持保留态度。linkerd diagnostics policy 的目的是提供足够的细节,使您能够理解 Linkerd 如何管理特定工作负载的流量,无论源代码如何更改。
概要
linkerd CLI 不仅提供安装 Linkerd 所需的工具,还提供关键的运维工具,简化了在您的集群中运行 Linkerd 的流程。虽然完全可以使用 Linkerd 而不运行 linkerd CLI,但 CLI 是处理许多现实场景的最直接、最有效的方式。
第七章:mTLS、Linkerd 和证书
从单体应用转向微服务应用,在安全方面使我们处于一个非常有趣的位置。单体应用在其进程边缘提供了一个自然的安全边界,而微服务应用则完全没有自然的安全边界。以前通过在进程内部的函数调用中传递的敏感信息现在必须通过网络发送,如图 7-1 所示。

图 7-1. 单体与微服务应用中的安全立场
此外,微服务通常运行在由外部团队、组织甚至公司提供的基础设施和网络资源上。如果不采取措施来抵御威胁,攻击者很容易在访问网络的情况下读取、拦截和修改微服务之间的通信。这显然是一个严重的问题。
最后,网络甚至没有提供任何安全的方式让特定的微服务知道是谁在调用它。被调用的微服务可以找出调用者的 IP 和 MAC 地址,但这些并不是真正安全的——例如,伪造发送者的 IP 地址非常容易。当应用运行在它无法控制的网络上时,情况只会变得更糟。
安全通信
为了使任何微服务应用程序正常工作,我们需要安全通信。真正安全通信有三个不同的要素:
真实性
我们必须确信我们与我们认为正在交谈的人在交谈。
保密性
我们必须确信没有人能读取发送到连接上的数据。
完整性
我们必须确信我们的消息在传输过程中没有被篡改。
这些并不是新问题,而且已经有许多不同的技术以各种方式演变来解决它们。Linkerd 依赖于其中一个最值得信赖的技术之一:双向 TLS,或者mTLS。
TLS 和 mTLS
TLS 由RFC 8446定义,是一个经过实战检验的行业标准安全通信机制,可以追溯到 1999 年。这是 Web 浏览器多年来与银行、购物网站等进行安全通信所使用的相同机制。现代互联网已经依赖 TLS 近 25 年了,而密码分析学家至少有这么长时间在尝试找到它的弱点。TLS 使用图 7-2 所示的架构提供真实性、保密性和完整性。

图 7-2. TLS 架构
(Linkerd 特别使用 TLS 版本 1.3,但所有 TLS 版本都使用相同的架构。)
TLS 通过在传输中加密数据和添加消息摘要(加密校验和),确保了机密性和完整性,以便接收方可以验证发送的数据未被修改。这解决了我们三个关注点中的两个。
真实性更加复杂。TLS 使用证书来加密验证发送方和接收方的身份。TLS 标准始终要求接收端通过发送证书来识别自己。在许多情况下,这已经足够了;例如,当您使用网页浏览器访问购物网站时,浏览器发送证书并不是非常有用,因为购物网站会要求您单独登录。
对于服务网格来说,我们需要验证连接的双端。这意味着我们需要双方发送证书以进行身份验证。当我们像这样使用 TLS(就像 Linkerd 那样),我们称之为双向 TLS 或 mTLS,以区别于只有接收端进行身份验证的情况。这在图 7-3 中有所展示。

图 7-3. TLS 与 mTLS 比较
在双向使用证书的情况下,mTLS 利用了 TLS 提供的保证:它不仅为客户端和服务器提供了经过加密验证的身份,同时还保持了传输中的加密。对于 Linkerd,这意味着 mTLS 可以确保您的工作负载知道它们在与谁通信,且没有第三方能够拦截或监听它们的通信。
mTLS 和证书
正如我们在第二章中首次讨论的那样,mTLS 依赖的证书是建立在公钥和私钥对上的。私钥(当然)需要保密:只有标识该密钥对的实体可以知道它。另一方面,公钥应该广泛分发:这就是验证持有私钥实体身份的依据,因此每个需要与该实体通信的人都需要这个公钥。
证书使我们能够将名称和其他元数据与密钥对关联起来,这非常有用,因为它使我们人类更容易处理证书。它们还使得一个证书可以证明另一个证书的有效性(签名或发布证书),这非常有用,因为它大大简化了我们是否信任证书的判断。
使用证书签署其他证书会创建一个信任层级,如图 7-4 所示。这种层级非常重要:mTLS 可以通过访问层级结构中更高的任何公钥来验证证书的有效性,而建立在 mTLS 之上的系统(包括 Linkerd)利用了这一特性。

图 7-4. 证书信任层级
最后,限制给定密钥对的使用寿命非常重要:密钥使用越长时间,如果被泄露,危险越大。因此,我们需要定期更换正在使用的证书密钥。这涉及创建新的密钥对,然后生成新的证书,并最终确保它得到正确签名。整个过程称为 轮换 证书;这是在处理证书时的主要操作复杂性来源。
Linkerd 和 mTLS
Linkerd 透明地为所有应用间通信添加了 mTLS。这意味着所有网格化的流量都受到拦截和篡改的保护。这还意味着您的工作负载可以确保它们始终知道正在与哪些工作负载通信。
仅当每个网格化工作负载都有与该工作负载相关联的有效 TLS 证书,并且所有这些 工作负载证书 都属于相同的信任层次结构时,才能正常工作。手动管理这些将非常困难,因此 Linkerd 帮助自动化了这一过程。
我们在 第四章 中讨论了如何将工作负载添加到网格中,但让我们稍微详细地重新讨论一下。当将工作负载添加到网格中时,会向其 Pod 添加一个 linkerd2-proxy 容器。该容器将被配置为拦截进出 Pod 的所有 TCP 流量,并且在与另一个 Pod 建立连接时始终尝试建立 mTLS 会话。如果另一个 Pod 也有 linkerd2-proxy —— 意味着它是网格的一部分!—— 那么连接将受到 mTLS 保护。
由于此 mTLS 连接是从代理到代理的,Pod 中的应用容器甚至不知道 mTLS 是如何进行的:从应用程序的角度来看,Pod 之间的所有通信看起来都像是使用明文。这意味着应用 Pod 看不到 Linkerd 使用的任何证书信息,进而意味着它们不需要任何特殊的代码来处理证书信息。
协议检测和代理
代理拦截 所有 与 Pod 之间的通信意味着,有时您可能需要向其提供有关协议的额外信息,或者确实不尝试使用 mTLS。在 第四章 中详细讨论了所有这些内容,但一个很好的经验法则是,只要客户端先发言,那么一切都会正常。如果服务器先发言,则需要进行一些配置。
当然,这仅适用于服务器在网格中的情况!如果您尝试从网格化 Pod 向非网格化 Pod 进行通信,您将始终需要告诉 Linkerd 跳过此流量:否则,它将无法在目标 Pod 不在网格中时执行 mTLS。
证书和 Linkerd
我们在 第三章 中讨论了 Linkerd 证书。在本节中,我们将详细介绍这些证书的具体用途、创建方法以及在需要旋转时的操作。我们将涵盖 Linkerd 使用的三个层次的证书:信任锚、身份签发者和工作负载证书。
从 Linkerd 的角度来看,信任始于其信任锚证书,如 图 7-5 所示。信任锚当然可以由其他更高级的证书签名——然而对于 Linkerd 来说,信任在信任锚止步。

图 7-5. Linkerd 信任层次结构
Linkerd 自动处理工作负载证书的生成、分发和旋转,同时依赖用户管理信任锚和身份签发者。这在 图 7-6 中有所展示。

图 7-6. Linkerd 证书管理
永远不要让您的证书过期。
因为 Linkerd 默认要求 Pod 之间进行 mTLS 连接,所以它使用的证书的健康和安全性对网格的正常运行以及您的平台至关重要。如果证书过期或无法为新的 Pod 生成,您将遭受停机。
这是生产 Linkerd 集群中停机的最常见原因。了解和监控您的 Linkerd 证书至关重要。
Linkerd 信任锚
Linkerd 信任锚 是您的集群中所有信任的基础证书。它仅用于两件事:
-
安装 Linkerd 时,您将使用信任锚来发布 Linkerd 身份签发者证书,我们将在下一节中讨论这一点。这需要在安装之前,在 Linkerd 之外访问信任锚的私钥和公钥。
-
每当工作负载与另一个工作负载进行 mTLS 连接时,两个工作负载都使用身份签发者和信任锚来验证对方的身份。这需要访问信任锚的公钥,并在网格运行时不断进行。
因为只有在集群内操作时需要公钥(上述列表中的第二个小点),我们建议您永远不要将信任锚的私钥存储在 Kubernetes 集群中。相反,将信任锚存储在 Kubernetes 之外的安全系统中,并仅将其公钥复制到集群中,正如我们将在本章中介绍的那样。
非常重要的一点是,信任锚点与集群没有固有的联系:它完全独立于网络拓扑或集群边界。这意味着如果你给多个集群相同的信任锚点,它们将能够在不同集群中的工作负载之间进行安全的 mTLS,如图 7-7 所示。这非常强大,因为它使多集群网格设置变得非常容易。

图 7-7. 多集群信任层次
相应地,两个不应该相互通信的集群不应该共享信任锚点!对于大多数组织来说,这意味着不应该跨环境层次共享信任锚点——也就是说,测试集群不应该与开发或生产集群共享相同的信任锚点。
Linkerd 身份颁发者
在 Linkerd 信任层次的第二级是身份颁发者证书,正如我们在第三章中简要提到的那样。身份颁发者由 Linkerd 控制平面的身份控制器用于颁发工作负载证书,如图 7-8 所示。由于身份颁发者用于签署证书,Linkerd 必须可以访问身份颁发者证书的私钥和公钥,这意味着它必须存储在集群中的一个秘密位置。

图 7-8. 颁发者证书和身份控制器
身份颁发者必须由信任锚点签署,并且由于它必须存储在集群中,每个集群必须有自己的身份颁发者。
Linkerd 无法警告您如果共享身份颁发者证书
如果你不小心在两个集群中使用相同的身份颁发者证书,Linkerd 没有任何良好的方法来警告你,实际上一切都会正常运行。不要这样做。如果你这样做了,两个集群将变得无法区分,恶意用户有可能利用一个集群创建一个绕过另一个集群授权策略的工作负载证书。
确保每个集群都有一个唯一的身份颁发者。
Linkerd 工作负载证书
最后,我们来到实际执行应用程序之间提供 mTLS 功能的证书。当一个工作负载被添加到 Linkerd 网格中时,与每个工作负载 Pod 关联的 Linkerd 代理会自动向 Linkerd 身份控制器请求一个工作负载证书。这个工作负载证书是工作负载身份的基础。它将由身份颁发者证书签署,由于每个其他工作负载都可以访问身份颁发者和信任锚点的公钥,可以验证工作负载证书的有效性直到信任锚点。
每个 Pod 的工作负载证书都与分配给 Pod 的 Kubernetes ServiceAccount 在密码学上相关联,其名称包括 ServiceAccount 和命名空间的名称。这使得在它们彼此通信时能够唯一标识您的 Pods。当我们建立策略时,这也为我们提供了稍后将需要的身份。身份名称的基本格式为:
$serviceAccountName.$namespace.serviceaccount.identity.linkerd.$clusterDomain
当 $clusterDomain 没有被覆盖时,它将是 cluster.local。(大多数单集群 Linkerd 安装不需要覆盖此设置。)
Linkerd 完全自动处理工作负载证书;您不应该担心管理它们。
证书的生命周期和旋转
正如我们之前提到的,使用特定密钥的时间越长,突破该密钥的价值就越大。因此,证书被赋予固定的寿命,并且在其到期之前必须替换。用新证书替换证书称为旋转证书。
如前所述,选择证书旋转的频率是一种权衡。频繁旋转最安全,但可能会干扰正常运作并需要不切实际的努力。很少或不旋转非常简单,但也非常不安全。
Linkerd 会自动为您旋转工作负载证书:默认情况下,工作负载证书每 24 小时过期一次。然而,身份发行者和信任锚点证书的旋转由您决定,因为通常您的组织政策会决定您需要多频繁地旋转。需要考虑的关键因素是:
每次你旋转信任锚点时,你必须同时旋转身份发行者。
这是因为信任锚点必须签署身份发行者。如果你刚刚生成了一个新的信任锚点,旧的身份发行者无法由新的信任锚点签署,因此你也需要一个新的身份发行者。
换句话说,这意味着你不能比身份发行者更频繁地旋转信任锚点。
每次你旋转身份发行者时,你可能也要旋转工作负载证书。
由于 Linkerd 自动处理工作负载证书的旋转,当你旋转身份发行者时,你可以选择等待 Linkerd 自动旋转工作负载证书。如果你希望立即确保它们被旋转,请重新启动工作负载。
旋转证书的方式取决于它是哪种证书:
旋转信任锚点
实际上,本书的内容不涵盖旋转信任锚点:实践中,如果你遵循集群本身应该是临时的原则,那么将信任锚点的生命周期与集群的生命周期保持一致可能更实际。你可以在 官方 Linkerd 文档 中找到更多关于旋转信任锚点的信息。
旋转身份发行者
在 Linkerd 中,轮换身份颁发者是一项基本的操作任务。理想情况下,您将使用诸如 Venafi 的cert-manager之类的工具自动化轮换身份颁发者,并且我们在本章中展示了如何操作。您还可以根据官方 Linkerd 文档中的过程手动轮换信任锚。
无论您是自动化身份颁发者轮换还是手动进行,重要的是在身份颁发者到期之前练习轮换身份颁发者。拥有一个从未测试过的机制可能比根本没有设置更糟糕。
轮换工作负载证书
工作负载证书由 Linkerd 控制平面自动轮换,因此您几乎不需要处理它们。(正如之前提到的,如果您确实希望轮换工作负载证书,只需重新启动工作负载。)
默认情况下,工作负载证书有效期为 24 小时,控制平面将在其有效生命周期的 70%后开始尝试轮换工作负载证书。
调整工作负载证书
如果需要,您可以通过在安装 Linkerd 时设置issuanceLifetime值来调整工作负载证书的生命周期,但要注意,如果您缩短此值,则存在两个重要的操作问题。
首先,您增加了 Pod 与身份控制器通信的频率,这会增加身份控制器的负载。
其次,您减少了解决更新问题的时间。代理将开始尝试在生命周期的 70%进行更新:对于 24 小时的生命周期,这意味着在证书过期前大约还有 7 小时开始尝试更新,如果出现任何问题,您将有大约 7 小时来解决。如果整个生命周期只有 2 小时,如果出现任何问题,您将只有大约半小时来解决。
正如您可能已经推测到的那样,我们只是浅显地介绍了证书的一般工作原理,特别是在 Linkerd 中,但是现在您应该已经有足够的信息来理解在 Linkerd 中如何使用证书。
在 Linkerd 中的证书管理
在这一点上,明显应该清楚证书管理是保护生产 Linkerd 安装的关键部分。正确管理证书是减少事件发生可能性的重要方式。它还可以帮助最小化恢复时间和如果发生故障的整体影响。
有了这些信息,我们建议所有使用 Linkerd 进行任何类型的生产使用的人:
将信任锚的生命周期与集群的生命周期耦合。
虽然可以轮换信任锚,但将整个集群视为临时的并定期轮换整个集群通常会使灾难恢复和提供者迁移变得更简单。
自动化轮换身份颁发者。
确实可以手动管理身份发行者,但我们强烈建议使用类似 cert-manager 的工具来定期轮换身份发行者证书,例如每隔几天一次。这种更短的身份发行者寿命可以大大限制任何事故的范围,而使用 cert-manager 几乎是不可察觉的。
使用 cert-manager 进行自动证书管理
Venafi 的 cert-manager 是一个 CNCF 项目,用于管理自动证书生成和轮换,如图 7-9 所示。我们不会详细介绍 cert-manager 的内部工作原理(这超出了本书的范围);相反,我们将重点放在理解如何与 Linkerd 一起使用 cert-manager 的概念上。

图 7-9. 使用 cert-manager 自动轮换发行者证书
安装 cert-manager
我们首先通过 Helm 安装 cert-manager 来管理我们的安装,如示例 7-1 所示。要跟进操作,您需要在环境中准备以下工具:
-
kubectl -
使用
k3d或其他工具获取本地 Kubernetes 集群 -
helm3
示例 7-1. 安装 cert-manager
# Start by creating the cluster
$ k3d cluster create luar
# Add the jetstack Helm repo if you don't already have it
$ helm repo add jetstack https://charts.jetstack.io
# Make sure your Helm repositories are up-to-date
$ helm repo update
# Install cert-manager
$ helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.12.0 \
--set installCRDs=true
# Check out your cert-manager Pods
$ kubectl get pods -n cert-manager
软件版本
请注意,我们的示例使用特定版本,以确保在撰写本文时工作正常。但是,在您阅读本文时可能已有更新版本,请确保使用适当的版本。
配置 Linkerd 的 cert-manager
尽管本书不涵盖 cert-manager 的深入介绍,但讨论其整体架构对我们的使用场景是非常值得的。cert-manager 配置了 Issuer 和 Certificate 资源:Issuer 资源告诉 cert-manager 在哪里找到其需要发行证书的密钥,而 Certificate 资源告诉 cert-manager 使用哪个 Issuer 为特定证书发行证书。
在我们的案例中,如图 7-9 所示,我们将创建一个持有信任锚点密钥的 Issuer,并且一个描述如何使用该 Issuer 获取 Linkerd 发行者证书的 Certificate。
我们在本章开头提到,永远不要将根 CA 的私钥添加到 Kubernetes 集群中。由于在本书范围内部署外部密钥存储是不切实际的,我们将在示例 7-2 中违反此规则,并使用 cert-manager 将信任锚点存储在 Kubernetes Secret 中。在任何真实的生产环境中,您都不应这样做,但总体设置将保持不变,一个 Issuer 和一个 Certificate:只需将 Issuer 定义更改为使用您的外部代理即可(cert-manager 支持许多不同的外部代理;参见文档)。
将信任锚点密钥保持在集群外!
再次强调,不要在生产中使用此设置。它可以更安全,但是您的信任锚点密钥存在集群中永远不会像仅存在于外部存储中那样安全。
示例 7-2. 为 Linkerd 生成证书
# Start by generating a trust anchor for the cluster.
$ step certificate create root.linkerd.cluster.local ca.crt ca.key \
--profile root-ca --no-password --insecure --not-after=87600h
# Create the linkerd namespace so that we have a place to install
# the trust anchor Secret.
$ kubectl create ns linkerd
# Save the trust anchor as a Secret in the linkerd namespace.
#
# During your real-world installs, you'd instead use an external
# cert-manager-compatible Secret store (like Vault) to store the
# trust anchor.
$ kubectl create secret tls linkerd-trust-anchor \
--cert=ca.crt \
--key=ca.key \
--namespace=linkerd
# Create a cert-manager Issuer that uses the trust anchor Secret
# to issue certificates. This Issuer must be in the same namespace
# as the trust anchor Secret.
#
# During your real-world installs, you'd instead change this
# Issuer to connect to your external Secret store.
$ kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: linkerd-trust-anchor
namespace: linkerd
spec:
ca:
secretName: linkerd-trust-anchor
EOF
# With the Issuer created, we will now use a Certificate to instruct
# cert-manager to create our identity issuer certificate. We will
# also instruct it to automatically rotate that certificate every 48
# hours. This Certificate must be in the same namespace as the Secret
# it is writing, which (again) is the linkerd namespace.
$ kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: linkerd-identity-issuer
namespace: linkerd
spec:
secretName: linkerd-identity-issuer
duration: 48h
issuerRef:
name: linkerd-trust-anchor
kind: Issuer
commonName: identity.linkerd.cluster.local
dnsNames:
- identity.linkerd.cluster.local
isCA: true
privateKey:
algorithm: ECDSA
usages:
- cert sign
- crl sign
- server auth
- client auth
EOF
让我们回顾一下刚才的步骤。我们首先将 cert-manager 安装到我们的集群中,这将自动化证书的发布和轮换过程。然后我们创建了一个信任锚点,并告诉 cert-manager 使用该证书来自动创建和轮换 Linkerd 的颁发者证书。颁发者证书是 Linkerd 将用来创建、分发和轮换各个工作负载证书的中间 CA。cert-manager 将按照我们在其证书对象中定义的每 48 小时轮换一次颁发者证书。
让我们快速浏览一下我们的集群,如示例 7-3 所示,看看 cert-manager 实际上如何处理这个设置。
示例 7-3. 四处看看
# First, let's validate that the trust anchor Secret exists and
# has some information in it.
$ kubectl get secret linkerd-trust-anchor -n linkerd
# Given that, we can use the step CLI to examine the public part
# of the certificate itself. The way this works is that the public
# part is stored, base-64 encoded, in the "tls.crt" key of the
# Secret, so we extract that, decode it, and hand it to step.
$ kubectl get secret linkerd-trust-anchor -n linkerd \
-o jsonpath='{.data.tls\.crt}' \
| base64 -d \
| step certificate inspect -
# Next, let's check to see if cert-manager was able to create
# our issuer certificate. We should see a Certificate named
# linkerd-identity-issuer with a "ready" status of True.
$ kubectl get certificate -n linkerd
# Following that, we'll check in on the identity issuer Secret.
# This is just like what we did for the trust anchor, with a
# different name for the Secret.
$ kubectl get secret linkerd-identity-issuer -n linkerd \
-o jsonpath='{.data.tls\.crt}' \
| base64 -d \
| step certificate inspect -
有了这些,现在我们可以安装 Linkerd 了。
使用 cert-manager 安装 Linkerd
一旦 cert-manager 设置好发布证书,我们需要安装 Linkerd,以便它知道使用 cert-manager 管理的证书,如图 7-9 所示。
你会记得,在第三章中,我们讨论了安装 Linkerd 的各种方法。我们将使用 Helm 进行安装,如示例 7-4 所示,因为我们建议希望在生产环境中运行 Linkerd 的人使用 Helm 来安装 Linkerd。
示例 7-4. 使用 cert-manager 安装 Linkerd
# Configure our Linkerd Helm repo.
$ helm repo add linkerd https://helm.linkerd.io/stable
# Update our repos.
$ helm repo update
# Install the Linkerd CRDs.
$ helm install linkerd-crds -n linkerd --version 1.6.1 linkerd/linkerd-crds
# Install Linkerd's control plane.
#
# Unlike in earlier chapters, this install will not have us specifying
# the issuer certificate. Instead, we instruct Linkerd to use the
# existing certificate by setting the identity.issuer.scheme to
# kubernetes.io/tls.
$ helm install linkerd-control-plane -n linkerd \
--set-file identityTrustAnchorsPEM=ca.crt \
--set identity.issuer.scheme=kubernetes.io/tls \
--version 1.12.4 \
linkerd/linkerd-control-plane
# Validate the Linkerd install.
$ linkerd check
# You'll see warnings letting you know your Linkerd issuer certificate isn't
# valid for more than 60 days. That's to be expected, as you are now actively
# rotating the issuer certificate with cert-manager.
有了这些,您现在拥有一个完全功能的 Linkerd 实例,并且具有主动和自动轮换颁发者证书。您已经为您的环境添加了大量安全性,并确保您的集群定期获得新的证书。积极监控 cert-manager 并检查您的证书是否定期轮换非常重要。过期的颁发者证书是 Linkerd 可以主动关闭您的应用程序的少数方式之一,其健康和安全对您的平台至关重要。
总结
我们在本章中涵盖了大量内容。即使 mTLS 和证书处理已经存在很长时间,但它们仍然是复杂的主题。挑战在于,为了正确保护云原生应用程序,您现在需要比您可能希望了解更多关于这些内容。
Linkerd 简化您强化环境流程的一种方式是使 mTLS 成为有效自动化,允许任何 Linkerd 用户依赖于 mTLS 的广受信任的身份验证和加密机制来进行安全通信。另一种方式是,Linkerd 让您控制关键的证书管理操作:Linkerd 能够为您的应用程序工作负载颁发证书,然后频繁且自动地轮换它们,为您减少安全事件的可能性和影响提供了一些强大的工具。
第八章:Linkerd 策略:概述和基于服务器的策略
正如我们在第七章中讨论的那样,微服务应用需要比传统的单体应用更高级别的网络安全性。mTLS 提供了你需要的安全通信和工作负载身份,但是正是 Linkerd 的策略机制提供了利用这些身份来控制环境中工作负载如何相互通信的能力。
Linkerd 支持两种策略机制:基于服务器和基于路由。由于策略是 Linkerd 最复杂的领域,我们将提供概述,并在本章节讨论基于服务器的策略,然后在第九章中介绍基于路由的策略。
Linkerd 策略概述
所有 Linkerd 策略机制都基于显式授权:Linkerd 最初假定不应允许任何事情,必须明确告知允许哪些请求。这与零信任模型完全一致,并且使得权限推理变得简单,因为策略资源始终在允许某些操作。
不过,不要惊慌;这并不意味着策略总是由数百个资源构成的一团乱麻。Linkerd 允许在集群、命名空间和 Pod 级别设置默认策略,并且更具体级别的策略设置会覆盖更一般级别的策略设置:Pod 覆盖命名空间,命名空间覆盖集群范围的设置,正如图 8-1 所示。

图 8-1. 不同的 Linkerd 默认策略设置
谈论默认覆盖默认可能看起来很奇怪。这里,“默认”是指 Linkerd 支持的另一种策略设置类型的对比:使用动态策略资源。默认策略就是在没有针对特定请求的动态资源存在时应用的策略。
策略始终沿 Pod 边界执行,因为 Pod 是由 Linkerd 管理的基本单元。例如,不可能编写一个会影响 Pod 中单个容器通信的策略。
Linkerd 默认策略
Linkerd 有以下默认策略选项:
all-unauthenticated
允许所有流量,无论是否经过身份验证。
cluster-unauthenticated
允许来自本集群的所有流量。
all-authenticated
允许来自所有网格化客户端的流量。
cluster-authenticated
允许来自本集群中的网格化客户端的流量。
deny
否认一切。
all 和 cluster 的区别仅在于当你使用多集群时才相关(正如我们在第十二章中讨论的那样)。在多集群设置中,all 包括来自其他集群的客户端,而 cluster 则不包括,如图 8-2 所示。如果不使用多集群,则两者是等效的。

图 8-2. all vs cluster
集群默认策略在安装时使用proxy.defaultInboundPolicy值设置,如示例 8-1 所示。如果未设置,集群默认策略将是all-unauthenticated:这允许绝对任何请求,反映了 Kubernetes 的默认宽松姿态。Linkerd 使用此默认值确保在安装 Linkerd 时不想或不需要使用策略(或者只是还没有加固集群到达该点的用户)不会受到负面影响。
为什么采用宽松的默认策略?
Linkerd 的默认all-unauthenticated显然对安全性不利,我们强烈建议您选择不同的集群默认值用于生产安装。
然而,实际上,几乎任何其他的基本默认都几乎可以确保在运行中的应用程序中安装 Linkerd 会导致问题。最终,all-unauthenticated作为基本默认是在首次引入应用程序时确保 Linkerd 不会造成任何伤害的唯一方法,这就是为什么它是基本默认。
示例 8-1. 设置集群默认策略
# We can set the cluster's default policy with Helm...
$ helm install linkerd-control-plane -n linkerd \
... \
--set proxy.defaultInboundPolicy=all-authenticated \
...
# ...or with the Linkerd CLI.
$ linkerd install \
...
--set proxy.defaultInboundPolicy=all-authenticated \
... \
| kubectl install -
要覆盖命名空间、工作负载或 Pod 的默认值,您将使用config.linkerd.io/default-inbound-policy注释,将其设置为前面列出的值之一,如下所示:
$ kubectl annotate namespace *`your-namespace`* \
config.linkerd.io/default-inbound-policy=all-authenticated
Linkerd 策略资源
要覆盖默认策略,您将使用策略资源,这些资源是 CRD,配置应该允许哪些请求:
Server
描述一个或多个 Pod 及其上的一个端口
HTTP 路由
描述给定 Server 的 HTTP 流量子集
MeshTLSAuthentication
描述一个或多个网格身份
网络认证
描述一个或多个 IP 地址
AuthorizationPolicy
将 Server 或 HTTPRoute 绑定到网格或网络认证
这些资源如图 8-3 所示共同工作;例如,一个 AuthorizationPolicy 可以将 Server 和 MeshTLSAuthentication 链接起来,以允许特定集合的网格身份访问 Server。

图 8-3. Linkerd 策略资源
让我们更详细地查看每个资源以及它们如何用于配置 Linkerd 策略。
Server
我们在第四章简要讨论了 Server 资源。Server 资源是 Linkerd 特有的;它们允许描述工作负载的单个特定端口。例如,示例 8-2 中的 Server 描述了foo工作负载的http端口,该工作负载是具有app: foo标签的 Pod 集合。该 Server 还指出该端口处理 HTTP/1.1 流量。
示例 8-2. 一个 Server 资源
apiVersion: policy.linkerd.io/v1beta1
kind: Server
metadata:
name: foo
namespace: foo-ns
spec:
podSelector:
matchLabels:
app: foo
port: http
proxyProtocol: HTTP/1
注意,Server 是一个命名空间资源,必须出现在与其需要匹配的 Pod 相同的命名空间中。
HTTP 路由
HTTPRoute 是一个 Gateway API 资源,描述特定的 HTTP 请求。我们将在第九章更详细地讨论 HTTPRoute。
MeshTLSAuthentication
MeshTLSAuthentication 描述了一组特定的网格标识。任何具有列出的标识之一的工作负载都将匹配 MeshTLSAuthentication。例如,示例 8-3 显示了用于单个标识 foo.foo-ns.serviceaccount.identity.linkerd.cluster.local 的 MeshTLSAuthentication。
示例 8-3. 一个 MeshTLSAuthentication 资源
apiVersion: policy.linkerd.io/v1alpha1
kind: MeshTLSAuthentication
metadata:
name: foo
namespace: foo-ns
spec:
identities:
- "foo.foo-ns.serviceaccount.identity.linkerd.cluster.local"
MeshTLSAuthentication 是一个命名空间资源。通常会放置在与其相关的工作负载相同的命名空间中,尽管这不是一个严格的要求。
NetworkAuthentication
NetworkAuthentication 资源描述一组 IP 地址 CIDR 范围。来自列出范围中任何一个的请求都将匹配 NetworkAuthentication。
考虑到 Linkerd 强调使用工作负载标识而不是网络标识,可能会觉得 NetworkAuthentication 资源存在有些奇怪;然而,在管理未网格化的客户端时,它在某些时候确实很有用。
NetworkAuthentication 是一个命名空间资源。通常会放置在与其相关的工作负载相同的命名空间中,尽管这不是一个严格的要求。
授权策略
Linkerd 授权策略资源 允许访问 目标以进行一些 必需的身份验证。目标目前必须是服务器或 HTTP 路由之一。所需的身份验证必须是一个或多个 MeshTLSAuthentication 或 NetworkAuthentication 资源。
授权策略是一个命名空间资源。通常会放置在与其相关的工作负载相同的命名空间中,尽管这不是一个严格的要求。
当我们开始实际使用策略来锁定我们的集群时,我们将深入研究各个对象。
基于服务器的策略与基于路由的策略的比较
基于服务器的策略得名于它依赖于 Linkerd 服务器资源。您会注意到,虽然服务器资源描述了工作负载和端口,但并没有 描述 请求的任何内容。这意味着基于服务器的策略无法区分发往给定服务器的单独请求,而是要求发往服务器的每个请求都遵循相同的策略。
另一方面,路由策略(我们将在第九章讨论)确实会考虑请求细节。这是一种更强大、也更复杂的机制。
基于 emojivoto 应用程序的基于服务器的策略
我们将使用 emojivoto 示例应用程序 来说明在 Linkerd 中使用策略。作为参考,图 8-4 显示了我们的最终目标:整个 emojivoto 应用程序将受到保护,防止未授权的访问。

图 8-4. emojivoto 策略概览
在本章中,我们将引导您通过几种不同的模式来设置您的集群,每种模式都将逐步限制应用程序及其通信能力。
配置默认策略
我们可以采取的第一步来锁定我们的集群之一也是影响力和广泛性最大的措施之一。Linkerd 提供了一个简单的机制来设置所有代理的默认入站策略。在本节中,我们将展示如何在集群和命名空间级别设置默认的入站策略。
集群默认策略
让我们从设置整个集群的默认策略开始。记住,当你安装 Linkerd 时,整个集群的默认策略是all-unauthenticated,允许绝对任何请求,这与 Kubernetes 的默认开放态度一致。
我们首先将集群的默认策略切换为all-authenticated。这将要求所有连接到网络内的 Pod 的连接都来自于网络内的其他 Pod。这对安全性有好处,但会增加一些运维开销,因为你需要为任何想要继续与网络内的 Pod 进行通信的非网络化应用程序划出例外。例如,想象一下,你有一个非网络化的监控工具:当你将默认设置为all-authenticated时,它将突然无法与你的网络内的 Pod 进行通信,你要么需要将监控工具加入到网络中,要么为它添加 Linkerd 策略的例外。
是拒绝还是不拒绝
在理想情况下,整个集群的默认策略应设置为deny。从安全的角度来看,这绝对是最佳实践,但如果你正在处理现有的应用程序,并且将 Linkerd 的默认设置为deny,很可能会导致一些问题,除非你已经知道所有需要允许的不同流量。实际上,如果你没有使用精细化安全工具来工作,这种情况是很少见的。
一个有效且实际的妥协可以是从all-unauthenticated开始,然后使用 Linkerd 的可观察性工具确定哪些流量应被允许,然后逐步通过all-authenticated或cluster-authenticated来加强安全性,最终达到deny。还要记住,你可以将特定的命名空间切换到deny,作为逐步使整个集群达到deny的步骤之一。
在非生产环境中,当然,简单地将所有东西都切换到deny并观察可能会发生什么问题是了解哪些通信正在进行而你还没有考虑到的一个很好的方法。但在生产环境中不要这样做!
因为集群范围的策略是一个全局设置,我们将使用 Helm 进行配置。在示例 8-4 中,我们将使用helm upgrade来修改 Linkerd 的设置,而不改变你已安装的 Linkerd 版本。这个示例假设你正在使用 Linkerd 2.13.4。
示例 8-4. 集群策略
# Use helm upgrade to set the global inbound policy to all-authenticated.
$ helm upgrade linkerd-control-plane -n linkerd \
--set proxy.defaultInboundPolicy=all-authenticated \
--version 1.12.4 \
--reuse-values \
linkerd/linkerd-control-plane
# Now we can install the emojivoto app in our cluster to validate that it
# can operate normally while meshed.
$ curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/emojivoto.yml \
| linkerd inject - | kubectl apply -f -
# Once your Pods are up and running, test the emojivoto application to see
# if it's still working.
$ kubectl -n emojivoto port-forward svc/web-svc 8080:80
# Now browse to localhost:8080 and look at the emojivoto app. You should
# see the normal voting page.
命名空间默认策略
更进一步,我们可以将emojivoto命名空间切换到deny,以进一步保护我们的应用程序,如示例 8-5 所示。一旦这样做,该命名空间内的所有流量都将被拒绝,除非我们明确授权。
示例 8-5. 命名空间策略
# Start by using kubectl to annotate the namespace. We're going to set it
# to deny all traffic that hasn't been explicitly authorized.
$ kubectl annotate namespace emojivoto config.linkerd.io/default-inbound-policy=deny
# With that done, the policy changes won't have any impact on the Pods that
# are already running. You will need to perform a rollout restart for the
# new default policy to take effect.
$ kubectl rollout restart deploy -n emojivoto
# Once your Pods are up and running, test the emojivoto application to see
# if it's still working.
$ kubectl -n emojivoto port-forward svc/web-svc 8080:80
# Now browse to localhost:8080 and look at the emojivoto app. You should now
# see the page load, but all the emojis are gone. That is because the web
# frontend can no longer talk to either of its backends, voting or emoji.
时间很重要
Linkerd 的默认入站策略在代理启动时由您的代理读取。它不是动态读取的。这对操作员来说很重要,因为这意味着您所做的任何更改只有在创建 Pod 时才会生效。如果您更改了集群或命名空间的默认入站策略,则这些更改仅在重新创建命名空间中的 Pod 后才会生效。Pod 级别的入站策略更改将在 Kubernetes API 重新启动修改的 Pod 时生效,因此它们将在您修改相关的 Deployments、StatefulSets 或 DaemonSets 后立即生效。
有了这些设置,我们成功地阻止了emojivoto命名空间中工作负载之间的通信……而一切都瘫痪了。为了使应用程序重新正常工作,我们需要开始再次允许必要的流量,采用动态策略。
配置动态策略
如示例所示,仅仅使用默认设置来阻止所有内容并不是很有用。现在是时候看看如何使用我们的动态策略资源来允许有用的、必要的流量流动了。我们将从一个相当简单的概念开始:许多组织将命名空间视为应用程序或团队之间的逻辑边界,因此通常允许同一命名空间中的工作负载相互通信是有意义的。这通常被称为命名空间隔离。
命名空间隔离
使用命名空间隔离,我们可以轻松地限制命名空间中的流量仅限于共享该命名空间的工作负载。在我们的示例中,我们将开始允许emojivoto命名空间内的流量,只要源和目标身份都在同一个命名空间中。对于 emojivoto 应用程序来说,这是有意义的,因为其命名空间中运行的唯一内容是 emojivoto 本身的部分:这是该应用程序仅包含在单个命名空间中的自然结果。
身份而非 IP 地址
请注意,我们说的是“源和目的地身份”。Linkerd 对策略的所有操作都基于工作负载身份,而不是工作负载的 IP 地址或网络的任何其他内容。只要身份匹配,工作负载甚至不需要在同一个集群中,也可以正常运行。
我们将在示例 8-6 中为emojivoto设置命名空间隔离。公平警告:这看起来很复杂。内联注释对完全理解此示例的所有内容非常重要。
示例 8-6. 对emojivoto进行命名空间隔离
# To start applying policies to the emojivoto workloads, we need to create
# Server objects. A Server object selects a single named port on one or more
# Pods.
#
# We'll start by setting up a Server that matches the Linkerd admin port,
# used for metrics, for every Pod in our namespace.
$ kubectl apply -f - <<EOF
apiVersion: policy.linkerd.io/v1beta1
kind: Server
metadata:
namespace: emojivoto
name: linkerd-admin
spec:
podSelector:
matchLabels: {}
port: linkerd-admin
proxyProtocol: HTTP/2
EOF
# This object, a Server called linkerd, will, due to our matchLabels selector,
# match every Pod in our namespace. On each Pod it will bind to a port named
# linkerd-admin and allow us to apply policy to it.
#
# Next, we will create a Server object for each part of our application,
# starting with the web service (which serves the GUI).
$ kubectl apply -f - <<EOF
apiVersion: policy.linkerd.io/v1beta1
kind: Server
metadata:
namespace: emojivoto
name: web-http
labels:
app.kubernetes.io/part-of: emojivoto
app.kubernetes.io/name: web
app.kubernetes.io/version: v11
spec:
podSelector:
matchLabels:
app: web-svc
port: http
proxyProtocol: HTTP/1
EOF
# The Server web-http matches the HTTP port for Pods that are part of the
# web service by selecting any Pods with the app=web-svc label. It also has
# the added benefit of allowing us to skip protocol detection on this port
# by specifying the protocol as HTTP/1.
#
# Now we'll create the Servers for emojivoto's backing services,
# voting and emoji.
$ kubectl apply -f - <<EOF
---
apiVersion: policy.linkerd.io/v1beta1
kind: Server
metadata:
namespace: emojivoto
name: emoji-grpc
labels:
app.kubernetes.io/part-of: emojivoto
app.kubernetes.io/name: emoji
app.kubernetes.io/version: v11
app: emoji-svc
emojivoto/api: internal-grpc
spec:
podSelector:
matchLabels:
app: emoji-svc
port: grpc
proxyProtocol: gRPC
---
apiVersion: policy.linkerd.io/v1beta1
kind: Server
metadata:
namespace: emojivoto
name: voting-grpc
labels:
app: voting-svc
emojivoto/api: internal-grpc
spec:
podSelector:
matchLabels:
app: voting-svc
port: grpc
proxyProtocol: gRPC
EOF
# These are basically the same idea as the web Server, just with different
# label selectors. Also, since emojivoto uses gRPC for these workloads, we
# set the protocol to gRPC.
#
# With that, all of our Servers have been created, and we are ready to begin
# to allow communication within our namespace. We'll next define a single
# MeshTLSAuthentication resource that matches all service accounts within
# the emojivoto namespace.
$ kubectl apply -f - <<EOF
apiVersion: policy.linkerd.io/v1alpha1
kind: MeshTLSAuthentication
metadata:
name: emojivoto-accounts
namespace: emojivoto
spec:
identities:
- "*.emojivoto.serviceaccount.identity.linkerd.cluster.local"
EOF
# Then, we will bind that MeshTLSAuthentication to our Servers. We could do it
# individually on a port-by-port basis, but in this case it's simpler to bind
# to every policy object in the namespace.
$ kubectl apply -f - <<EOF
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: emojivoto-only
namespace: emojivoto
labels:
app.kubernetes.io/part-of: emojivoto
project: emojivoto
spec:
targetRef:
kind: Namespace
name: emojivoto
requiredAuthenticationRefs:
- name: emojivoto-accounts
kind: MeshTLSAuthentication
group: policy.linkerd.io
EOF
# With that, we should see that the emojivoto workloads are able to communicate
# with each other once again. You can check this by using a port forward to
# look at the emojivoto app's GUI: start this forwarder, then open
# http://localhost:8080/ in your browser.
$ kubectl -n emojivoto port-forward svc/web-svc 8080:80
名字的重要性
Linkerd 服务器对象是允许我们对应用程序应用策略的核心构造。它们通过基于某些选择标准匹配 Pod,然后选择一个端口根据端口的名称。Kubernetes 允许您创建没有将名称添加到端口的 Pod,因此在使用 Linkerd 策略时,必须确保应用程序中的每个端口都为其名称设置了一个值。
我们知道这是大量的 YAML!策略定义是您在使用 Linkerd 时需要承担的最费力的任务。幸运的是,策略是一个可选择的功能,您可以在启用之前提前准备好。我们强烈建议您在将所有策略对象应用于生产环境之前,在非生产环境中彻底测试它们。
早期测试,经常测试
这点需要重申。策略很复杂,容易出错。我们强烈建议在将策略定义应用于生产环境之前,在非生产环境中测试您的策略定义。
允许 Linkerd Viz
目前,我们已经在集群中隔离了emojivoto命名空间:来自命名空间外部的任何内容都无法与命名空间内部的任何内容通信。不幸的是,这会导致监控应用程序和入口控制器等功能中断。这显然不是理想的状态:虽然我们已经做了很多工作来保护我们的emojivoto命名空间,但却导致了其他问题。例如,我们让任何潜在的操作人员几乎无法监控我们的 emojivoto 工作负载在做什么。
要解决这个问题,我们可以使用引用来自命名空间外部身份的动态策略资源。在示例 8-7 中,我们将指导您安装 Linkerd Viz 并允许其轮询您的应用程序,如图 8-5 所示。

图 8-5. emojivoto 策略允许 Linkerd Viz
示例 8-7. 让 Viz 来临吧!
# Let's install the Linkerd Viz extension. We'll continue our theme of
# installing things with Helm.
$ helm install linkerd-viz linkerd/linkerd-viz \
-n linkerd-viz \
--create-namespace \
--version 30.8.4
# This command will install the linkerd 2.13.4 version of Linkerd's Viz
# extension.
#
# Once that's done, wait for Viz to be ready.
$ linkerd check
# We now want to restart our emojivoto workloads so that they start
# collecting Tap data. This is critical for observability.
$ kubectl rollout restart deploy -n emojivoto
# With that complete, we can now move on to validating that the Linkerd
# Viz extension is unable to talk to our workloads.
$ linkerd viz stat deploy -n emojivoto
# You should see all your deployments with no statistics associated with
# them. That's because Linkerd's Prometheus instance is located in the
# linkerd-viz namespace, and it hasn't been given permission to talk to
# anything in the emojivoto namespace.
# Let's fix that now. First, we define a MeshTLSAuthentication resource
# that matches the identities used by Tap and Prometheus, which are the
# parts of Linkerd Viz that collect data.
$ kubectl apply -f - <<EOF
apiVersion: policy.linkerd.io/v1alpha1
kind: MeshTLSAuthentication
metadata:
name: linkerd-viz
namespace: emojivoto
spec:
identities:
- "tap.linkerd-viz.serviceaccount.identity.linkerd.cluster.local"
- "prometheus.linkerd-viz.serviceaccount.identity.linkerd.cluster.local"
EOF
# Next, we permit that MeshTLSAuthentication to talk to Pods in the
# emojivoto namespace, using an AuthorizationPolicy as before.
$ kubectl apply -f - <<EOF
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: allow-viz
namespace: emojivoto
spec:
targetRef:
kind: Namespace
name: emojivoto
requiredAuthenticationRefs:
- name: linkerd-viz
kind: MeshTLSAuthentication
group: policy.linkerd.io
EOF
# At this point, Tap and Prometheus should be happily collecting data.
# Give them a minute or so to get something substantive, then you should
# be able to see good results from a second "linkerd viz stat" command.
$ linkerd viz stat deploy -n emojivoto
完成这些步骤后,我们已经详细介绍了如何在命名空间内部隔离流量,但仍允许外部监控工具(如 Linkerd Viz 扩展)进入。您现在具备开始使用 Linkerd 的策略工具隔离自己工作负载所需的基本知识。接下来,我们将更详细地说明只允许特定服务账户访问我们的工作负载。
按端口和身份锁定
命名空间隔离在进一步加固我们环境方面发挥了重要作用,但我们可以做得更多。到目前为止,我们已经应用的隔离允许任何请求,只要调用工作负载和被调用工作负载都在emojivoto命名空间内部即可,但这可能比我们实际需要的更宽松。为了更进一步,我们可以明确指出哪些账户被允许与哪些工作负载通信——但这当然需要准确了解应用程序真正需要的通信情况。
通过检查代码来弄清楚这些需求是繁琐且容易出错的,但幸运的是,我们可以利用像 Linkerd Viz(或其商业版本 Buoyant 的衍生产品)这样的工具来帮助我们映射应用程序的通信并构建我们的策略对象。
在emojivoto命名空间中,我们只需使用一个 Linkerd Viz CLI 命令即可查看哪些工作负载正在相互通信。
$ linkerd viz edges deploy -n emojivoto
这将生成一个在此命名空间中相互通信的部署列表。在 SRC 列下列出的部署是请求的源(客户端);在 DST 列下列出的是目标(服务器)。
如果您更喜欢使用 Viz 仪表板而不是其 CLI 进行调查,您可以运行:
$ linkerd viz dashboard
Viz 仪表板不在本书的讨论范围内,但它相当直观,我们鼓励您在没有使用过的情况下去试试。
从输出中,我们可以看到我们的 emojivoto 工作负载之间的连接,如 图 8-6 所示。

图 8-6. emojivoto 工作负载间通信
emojivoto 是一个非常简单的应用程序,因此只有三个连接:
-
vote-bot与web部署进行通信。 -
web与voting进行通信。 -
web与emoji进行通信。
有了这些,我们可以开始构建策略。我们将从收集各自工作负载上的主体名称开始,如 示例 8-8 所示。我们需要收集 vote-bot 和 web 的名称。我们不需要允许 voting 或 emoji 与任何其他服务通信,因为它们都不作为任何其他服务的客户端。
示例 8-8. 收集主体名称
# Start by grabbing the name of the first vote-bot Pod (which should
# be the only vote-bot Pod).
#
# This kubectl command uses -l app=vote-bot to pick all Pods with the
# "app: vote-bot" label, then uses JSONPath to pick the metadata.name
# of the first Pod in the list.
$ VOTEBOTPOD=$(kubectl get pods -n emojivoto -l app=vote-bot \
-o jsonpath='{ .items[0].metadata.name }')
# Now use the Pod name for vote-bot to get the Subject name.
$ linkerd identity $VOTEBOTPOD -n emojivoto | grep Subject:
# This will print out the Subject name for the vote-bot Pod, which is
# the name of that Pod's identity. It will look like:
#
# Subject: CN=default.emojivoto.serviceaccount.identity.linkerd.cluster.local
#
# We only want the part after CN=, so
# default.emojivoto.serviceaccount.identity.linkerd.cluster.local.
# Repeat for the web Pod, which we can find using the "app: web-svc" label.
$ WEBPOD=$(kubectl get pods -n emojivoto -l app=web-svc \
-o jsonpath='{ .items[0].metadata.name }')
$ linkerd identity $WEBPOD -n emojivoto | grep Subject:
# It should output a name like:
# Subject: CN=web.emojivoto.serviceaccount.identity.linkerd.cluster.local
#
# and again, we'll want the part after CN=:
# web.emojivoto.serviceaccount.identity.linkerd.cluster.local
名字的含义?
为什么 vote-bot 工作负载得到一个名为“default”的身份,而 web 工作负载得到一个名为web的身份?如果您仔细查看 vote-bot 和 web 的部署,您会发现 web 指定了要使用的 ServiceAccount,但 vote-bot 没有……所以 vote-bot 得到了默认的。这不是最佳实践。在理想的情况下,每个工作负载都应该有自己的 ServiceAccount。
我们展示这个是因为尽管这不是理想的,但在试图为未设计为零信任的应用程序设置策略时,看到这种默认 ServiceAccount 的使用是非常常见的,并且您可能需要创建新的 ServiceAccount 以及创建策略资源!
有了这两个主体名称,我们可以更新我们的策略,更明确地规定在 emojivoto 命名空间中允许谁与谁通信。值得记住,在前一节中,我们创建了多个策略对象,允许 emojivoto 工作负载相互通信。在 示例 8-9 中,我们将重新使用一些并删除其他对象,以从更宽松的安全姿态转向更少宽松的姿态,如 图 8-7 所示。

图 8-7. emojivoto 较不宽松的模型
服务器端策略
在 Linkerd 中,基于工作负载的策略引擎在服务器端执行所有策略决策。当我们在环境中将 deny 配置为默认选项时,我们必须逐个检查每台服务器,确保其所有客户端都已明确允许。
示例 8-9. 限制应用间通信
# We'll start by creating two new MeshTLSAuthentication objects. The
# first allows only the default identity (currently used by the vote-bot);
# the second allows only the web identity.
$ kubectl apply -f - <<EOF
apiVersion: policy.linkerd.io/v1alpha1
kind: MeshTLSAuthentication
metadata:
name: default
namespace: emojivoto
spec:
identities:
- 'default.emojivoto.serviceaccount.identity.linkerd.cluster.local'
---
apiVersion: policy.linkerd.io/v1alpha1
kind: MeshTLSAuthentication
metadata:
name: web
namespace: emojivoto
spec:
identities:
- 'web.emojivoto.serviceaccount.identity.linkerd.cluster.local'
EOF
# Each object corresponds to either the vote-bot or web application. We
# inserted the names we gathered in Example 8-8 to populate these
# objects. It's a good practice to name them after the identity they
# represent, rather than the workload -- in particular, the "default"
# identity is probably used by more than just the vote-bot, so we don't
# want to name that MeshTLSAuthentication "vote-bot" as that might give
# the impression that we need only think about the vote-bot when using
# that!
# With that done, we can begin binding those authentications to our servers.
# We'll start with allowing vote-bot (using the default identity) to talk
# to web.
$ kubectl apply -f - <<EOF
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
labels:
app.kubernetes.io/part-of: emojivoto
project: emojivoto
name: allow-default-to-web
namespace: emojivoto
spec:
requiredAuthenticationRefs:
- group: policy.linkerd.io
kind: MeshTLSAuthentication
name: default
targetRef:
group: policy.linkerd.io
kind: Server
name: web-http
EOF
# This AuthorizationPolicy will allow any workload using the default
# identity to talk to the web workload, using the "web-http" Server we
# already created.
# Now we will give the web application access to emoji and voting. In
# order to accomplish this we will need to create two AuthorizationPolicy
# objects, one for each Server.
$ kubectl apply -f - <<EOF
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
labels:
app.kubernetes.io/part-of: emojivoto
project: emojivoto
name: allow-web-to-voting
namespace: emojivoto
spec:
requiredAuthenticationRefs:
- group: policy.linkerd.io
kind: MeshTLSAuthentication
name: web
targetRef:
group: policy.linkerd.io
kind: Server
name: voting-grpc
---
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
labels:
app.kubernetes.io/part-of: emojivoto
project: emojivoto
name: allow-web-to-emoji
namespace: emojivoto
spec:
requiredAuthenticationRefs:
- group: policy.linkerd.io
kind: MeshTLSAuthentication
name: web
targetRef:
group: policy.linkerd.io
kind: Server
name: emoji-grpc
EOF
# Here, the allow-web-to-voting AuthorizationPolicy allows any workload
# using the web identity to talk to the voting workload; allow-web-to-emoji
# does the same for the emoji workload. Again, we're using Servers we created
# earlier.
# Now that we have our new policies in place, we can delete the policies that
# allow all the apps in the emojivoto namespace to talk to one another.
$ kubectl delete authorizationpolicies.policy.linkerd.io emojivoto-only -n emojivoto
# Finally, we'll use a port forward to test the emojivoto app and be sure it
# still operates normally.
$ kubectl -n emojivoto port-forward svc/web-svc 8080:80
操作顺序
在我们从命名空间范围的权限转向更具体的权限时,我们在移除命名空间范围的权限之前创建了新的策略对象。如果我们颠倒了顺序,就可能会破坏 emojivoto 工作负载之间的通信。
您现在进一步限制了对 emojivoto 命名空间中应用程序的访问。现在,只有在您的平台团队明确授权的情况下,您的工作负载之间才会发生通信。Linkerd 代理会记录每次拒绝,并且您的安全团队可以使用这些日志来识别集群中的恶意行为。希望您能看到这种加固方式如何显著降低环境中入侵的风险,并且通过适当的监控和日志记录,显著增加捕捉可疑行为的可能性。
总结
Linkerd 的基于服务器的策略是其最古老的策略机制,但在许多情况下仍然非常有效。基于服务器的策略使您能够设置已知的、可信的默认值,同时也可以简单地调整应用程序的所有内容,Linkerd 的 Tap 功能使您能够快速了解需要解决的问题。
第九章:Linkerd 基于路由的策略
在 第八章 中,我们讨论了实施基于 Linkerd 服务器的策略以增强 emojivoto 应用程序的安全性。该策略确保 Linkerd 有效地保护应用程序的工作负载,防止未经授权的工作负载发出请求。然而,如果我们希望更进一步呢?例如,考虑一个敏感应用程序的情况。您需要确保只有特定的 ServiceAccounts 被允许进行更改,并且只有特定的其他人员可以访问读取。
这就是 Linkerd 路由策略机制的作用所在。在本章中,我们将更详细地探讨路由策略的功能和使用方法。
基于路由的策略概述
基于路由的策略为 Linkerd 提供了一种方式来表达策略,这不仅依赖于参与的工作负载,还依赖于正在进行的特定请求。这些特定的 HTTP 请求通过使用 Gateway API HTTPRoute 资源来指定对 HTTP 路径、方法、头部等的匹配来识别。几乎可以使用任何内容,除了请求体。请求仍然使用 mTLS 身份进行验证。
Gateway API HTTPRoute 资源的工作原理是将一个或多个父资源与一个或多个规则关联起来。当使用 Gateway API 进行入口控制时,HTTPRoute 的父资源将是 Gateway 资源;然而,当使用 Gateway API 配置服务网格时,这样做并没有太多意义。在使用 Linkerd 时,将父资源指定为服务,HTTPRoute 将仅适用于流量被定向到父 Service 并且 符合 HTTPRoute 指定的规则。
HTTPRoutes、Gateway API 和你
Linkerd 2.13 支持 HTTPRoute 对象,但实际上使用的是 policy.linkerd.io/v1 API 组中的副本,而不是官方的 gateway.networking.k8s.io/v1beta1 HTTPRoute。这样可以避开与 Gateway API 一致性相关的问题。
在 Linkerd 2.13 发布时,服务网格无法符合 Gateway API 标准。到了 Linkerd 2.14,Gateway API 定义了 Mesh 一致性配置文件,指定了服务网格符合 Gateway API 的含义。Linkerd 2.14 及更高版本符合 Mesh 配置文件,并支持 gateway.networking.k8s.io/v1beta1 HTTPRoute(以及 Linkerd 2.13 支持的旧副本)。最终结果是一些依赖于 HTTPRoute 对象的工具与 Linkerd 2.13 不完全兼容,但与 Linkerd 2.14 及更高版本兼容性更好。
(如果您想了解更多相关内容,请查看 Gateway API 介绍 并阅读关于 Gateway API 和 GAMMA 计划的信息。)
基于路由的策略是 Linkerd 中最详细和最精细的策略级别,使用它需要大量规划和大量的 YAML。当您准备以这种程度保护您的环境时,您需要意识到在工程时间和精力方面的成本。我们还 强烈 建议,在构建任何类型的策略时使用多个环境——至少一个用于构建和测试策略,另一个用于执行策略。理想情况下,您将把策略的创建、审计和推广整合到您的标准应用程序开发生命周期中。
booksapp 示例应用程序
我们将使用 booksapp 示例应用程序 来展示如何使用基于路由的策略来限制调用,不仅基于工作负载,还基于特定的端点访问。
如 图 9-1 所示,该应用程序与 emojivoto 非常相似。

图 9-1. booksapp 应用程序间连接
在 booksapp 中,我们的两个后端服务(books 和 authors)需要互相通信,但它们不应该有无限制的访问权限。例如,authors 工作负载应该能够从 books 工作负载读取,以便显示每个作者写过的书籍。authors 的用户界面还允许您为当前查看的作者添加新书籍,因此 authors 需要能够向 books POST 新书籍。但它不能修改或删除书籍。
最终,我们希望只允许以下请求,不允许其他请求:
-
基础设施:
-
kubelet需要能够运行所有工作负载的健康检查。 -
Linkerd Viz 需要能够从所有工作负载中提取指标。
-
-
核心应用功能:
-
webapp需要能够读取、创建、删除和更新authors和books。 -
authors需要能够读取和创建books。 -
books需要能够读取和创建authors。
-
-
流量生成器:
traffic需要完全访问webapp。
这可以在 图 9-2 中看到。

图 9-2. booksapp 应用程序策略概述
安装 booksapp
设置非常简单。我们将拉取最新版本的 booksapp 应用程序,添加 Linkerd 代理,并将其应用到我们的集群中,如示例 9-1 所示。
示例 9-1. 设置
# Install booksapp...
$ kubectl create ns booksapp && \
curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/booksapp.yml \
| linkerd inject - \
| kubectl -n booksapp apply -f -
# Wait for Pods to be running...
$ kubectl rollout status -n booksapp deploy
# ...then test using a port forward.
$ kubectl -n booksapp port-forward svc/webapp 8080:80
# Now browse to localhost:8080.
配置 booksapp 策略
目前为止,booksapp 没有任何限制:所有内容可以互相访问。这通常是开始使用策略时最简单的地方;一旦确认应用程序运行正常,就可以开始加强安全性。
我们将逐步处理我们的 booksapp 策略:
-
在开始阶段,我们将处理低级基础设施,切换到默认拒绝并仍然允许 Linkerd Viz 工作。我们将使用基于服务器的策略;它不需要路由策略的精细度,因此我们将避免复杂性。
-
接下来我们将配置最小的基于路由的策略,以允许对应用程序进行只读访问。
-
然后我们将允许对
authors工作负载和books工作负载进行写入。 -
最后,我们将允许流量生成器访问
webapp。
按此顺序操作的好处是,它应该能够快速使至少部分应用程序运行,并且我们可以进行增量测试。在进行复杂配置时,这通常是一个很好的想法,并且(正如我们之前所说)基于路由的策略非常复杂。
基础设施策略
第一步是基础设施策略。我们将使用基于服务器的策略将booksapp命名空间切换为默认拒绝。这将要求我们显式允许 Linkerd Viz 继续工作。所有这些都在示例 9-2 中显示。
示例 9-2. books-infra-policy.yaml
# Create books-infra-policy.yaml.
$ cat <<EOF > books-infra-policy.yaml
---
apiVersion: policy.linkerd.io/v1beta1
kind: Server
metadata:
namespace: booksapp
name: linkerd-admin
spec:
podSelector:
matchLabels: {}
port: linkerd-admin
proxyProtocol: HTTP/2
---
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: allow-viz
namespace: booksapp
spec:
targetRef:
kind: Namespace
name: booksapp
requiredAuthenticationRefs:
- name: linkerd-viz
kind: MeshTLSAuthentication
group: policy.linkerd.io
---
apiVersion: policy.linkerd.io/v1alpha1
kind: MeshTLSAuthentication
metadata:
name: linkerd-viz
namespace: booksapp
spec:
identities:
- "tap.linkerd-viz.serviceaccount.identity.linkerd.cluster.local"
- "prometheus.linkerd-viz.serviceaccount.identity.linkerd.cluster.local"
EOF
这类似于我们在第八章中允许 Linkerd Viz 所做的操作。让我们继续应用基础设施策略 YAML,然后将booksapp命名空间切换为默认拒绝,如示例 9-3 中所示。
示例 9-3. 设置基础设施策略
# Apply the YAML we just created...
$ kubectl apply -f books-infra-policy.yaml
# Switch `booksapp` to default deny...
$ kubectl annotate namespace booksapp config.linkerd.io/default-inbound-policy=deny
# ...and finally, restart the booksapp workloads.
$ kubectl rollout restart deployment -n booksapp
健康检查呢?
敏锐的观察者将注意到,尽管我们的 Pod 配置了就绪和活动探测,它们仍在启动并保持就绪,即使我们还没有为kubelet授权探测我们的 Pod。这是因为 Linkerd 默认会寻找您的应用程序的活动状态和就绪探测,并创建一个默认的 HTTPRoute 来允许该流量,但只要您没有自己创建 HTTPRoutes。
一旦开始为您的应用程序创建自己的 HTTPRoutes,Linkerd 将删除其默认路由,这意味着您需要确保为活动状态和就绪探测创建路由。
此时,由于将booksapp命名空间切换为默认拒绝,并且仅授权了 Viz,我们的应用程序将完全无法工作。让我们继续使我们的应用程序运行起来。
只读访问
接下来我们将使用基于路由的策略来允许对应用程序进行只读访问。我们可以使用 Web 浏览器查找书籍和作者,但无法更改任何内容。
从现在开始,我们所做的一切只是应用 YAML,因此我们将展示您需要应用的 YAML。我们将从内部向外做这些事情,所以我们的第一步是允许books工作负载从authors工作负载获取/authors.json和/authors/,这需要四个资源。
首先,我们需要在books命名空间中为authors工作负载定义一个服务器,如示例 9-4 所示。这将允许我们使用 HTTPRoute 来配置对发送到authors工作负载的特定请求的策略。
示例 9-4. authors 服务器
---
apiVersion: policy.linkerd.io/v1beta1
kind: Server
metadata:
namespace: booksapp
name: authors
labels:
app: authors
app.kubernetes.io/part-of: booksapp
project: booksapp
spec:
podSelector:
matchLabels:
app: authors
project: booksapp
port: service
proxyProtocol: HTTP/1
接下来,我们将创建一个 HTTPRoute,指定我们想要允许的两个请求,如 9-5 所示。
Example 9-5. authors HTTPRoute
---
apiVersion: policy.linkerd.io/v1beta1
kind: HTTPRoute
metadata:
name: authors-get-route
namespace: booksapp
labels:
app.kubernetes.io/part-of: booksapp
project: booksapp
spec:
parentRefs:
- name: authors
kind: Server
group: policy.linkerd.io
rules:
- matches:
- path:
value: "/authors.json"
method: GET
- path:
value: "/authors/"
type: "PathPrefix"
method: GET
哪个 HTTPRoute?
我们已经使用了 policy.linkerd.io HTTPRoutes 来适应使用较旧版本 Linkerd 的读者。但是,如果您使用的是 Linkerd 2.14 或更新版本,可以切换到 gateway.networking.k8s.io/v1beta1 HTTPRoutes!
最后,我们可以指定一个 AuthorizationPolicy/MeshTLSAuthentication 对,其中 AuthorizationPolicy 的 targetRef 是我们刚刚定义的 HTTPRoute,以定义哪些身份可以使用这个 HTTPRoute,如 9-6 所示。
Example 9-6. authors AuthorizationPolicy 和 MeshTLSAuthentication
---
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: allow-books-to-authors
namespace: booksapp
labels:
app.kubernetes.io/part-of: booksapp
project: booksapp
spec:
targetRef:
group: policy.linkerd.io
kind: HTTPRoute
name: authors-get-route
requiredAuthenticationRefs:
- name: books
kind: MeshTLSAuthentication
group: policy.linkerd.io
---
apiVersion: policy.linkerd.io/v1alpha1
kind: MeshTLSAuthentication
metadata:
name: books
namespace: booksapp
spec:
identities:
- "books.booksapp.serviceaccount.identity.linkerd.cluster.local"
一旦应用了这些资源,books 工作负载将能够与 authors 工作负载通信。然而,正如我们之前指出的那样,我们刚刚破坏了 authors 工作负载的健康检查。当我们将我们的 HTTPRoute 附加到 authors 服务器后,Linkerd 生成的探针路由就消失了。
为了允许这些探针请求,我们将使用一个单独的 HTTPRoute,这将允许我们使用 NetworkAuthorization 允许来自集群任何地方的未认证探针请求。我们绝对不希望允许任何其他请求使用该 NetworkAuthorization,所以我们确实需要一个单独的 HTTPRoute 用于探针!如 9-7 所示。
Example 9-7. 重新允许 authors 健康探针
---
apiVersion: policy.linkerd.io/v1beta1
kind: HTTPRoute
metadata:
name: books-probes
namespace: booksapp
spec:
parentRefs:
- name: authors
kind: Server
group: policy.linkerd.io
rules:
- matches:
- path:
value: "/ping"
method: GET
---
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: authors-probe
namespace: booksapp
spec:
targetRef:
group: policy.linkerd.io
kind: HTTPRoute
name: books-probes
requiredAuthenticationRefs:
- name: probe-authn
kind: NetworkAuthentication
group: policy.linkerd.io
---
apiVersion: policy.linkerd.io/v1alpha1
kind: NetworkAuthentication
metadata:
name: probe-authn
namespace: booksapp
spec:
networks:
- cidr: 0.0.0.0/0
- cidr: ::/0
CIDR 是什么?
probe-authn NetworkAuthorization 的范围过于广泛;它真的应该仅限于您集群的 Pod CIDR 范围。我们无法预测这一点,所以您可以随意将 probe-authn NetworkAuthentication 资源中的 CIDR 范围替换为您集群的适当值。
现在,books 工作负载应能够从 authors 工作负载读取,并且对 authors 工作负载的探针也应该工作正常。现在我们需要重复这一切,以允许 authors 工作负载与 books 通信,如示例 9-8 所示。
Example 9-8. 允许 authors 从 books 读取
---
apiVersion: policy.linkerd.io/v1beta1
kind: Server
metadata:
namespace: booksapp
name: books
labels:
app: books
app.kubernetes.io/part-of: booksapp
project: booksapp
spec:
podSelector:
matchLabels:
app: books
project: booksapp
port: service
proxyProtocol: HTTP/1
---
apiVersion: policy.linkerd.io/v1beta1
kind: HTTPRoute
metadata:
name: books-get-route
namespace: booksapp
labels:
app.kubernetes.io/part-of: booksapp
project: booksapp
spec:
parentRefs:
- name: books
kind: Server
group: policy.linkerd.io
rules:
- matches:
- path:
value: "/books.json"
method: GET
- path:
value: "/books/"
type: "PathPrefix"
method: GET
---
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: allow-authors-to-books
namespace: booksapp
labels:
app.kubernetes.io/part-of: booksapp
project: booksapp
spec:
targetRef:
group: policy.linkerd.io
kind: HTTPRoute
name: books-get-route
requiredAuthenticationRefs:
- name: authors
kind: MeshTLSAuthentication
group: policy.linkerd.io
---
apiVersion: policy.linkerd.io/v1alpha1
kind: MeshTLSAuthentication
metadata:
name: authors
namespace: booksapp
spec:
identities:
- "authors.booksapp.serviceaccount.identity.linkerd.cluster.local"
---
apiVersion: policy.linkerd.io/v1beta1
kind: HTTPRoute
metadata:
name: authors-probes
namespace: booksapp
spec:
parentRefs:
- name: authors
kind: Server
group: policy.linkerd.io
rules:
- matches:
- path:
value: "/ping"
method: GET
---
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: authors-probe
namespace: booksapp
spec:
targetRef:
group: policy.linkerd.io
kind: HTTPRoute
name: authors-probes
requiredAuthenticationRefs:
- name: probe-authn
kind: NetworkAuthentication
group: policy.linkerd.io
最后,我们需要允许 webapp 与 authors 和 books 进行通信。我们可以在这里使用我们现有的 HTTPRoutes,而且我们不需要另一个服务器。我们需要做的只是添加新的 AuthorizationPolicy 和 MeshTLSAuthentication 资源,如 9-9 所示。
身份包含什么?
我们也可以通过向现有的 authors 和 books MeshTLSAuthentications 添加另一个身份来实现这一点。但是,基于路由的策略提供的细粒度是其优势的一个主要点,使用单独的 AuthorizationPolicy 和 MeshTLSAuthentication 有助于保持这种优势。
Example 9-9. 允许 Web 访问
---
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: allow-webapp-to-books
namespace: booksapp
labels:
app.kubernetes.io/part-of: booksapp
project: booksapp
spec:
targetRef:
group: policy.linkerd.io
kind: HTTPRoute
name: authors-get-route
requiredAuthenticationRefs:
- name: webapp
kind: MeshTLSAuthentication
group: policy.linkerd.io
---
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: allow-webapp-to-authors
namespace: booksapp
labels:
app.kubernetes.io/part-of: booksapp
project: booksapp
spec:
targetRef:
group: policy.linkerd.io
kind: HTTPRoute
name: authors-get-route
requiredAuthenticationRefs:
- name: webapp
kind: MeshTLSAuthentication
group: policy.linkerd.io
---
apiVersion: policy.linkerd.io/v1alpha1
kind: MeshTLSAuthentication
metadata:
name: webapp
namespace: booksapp
spec:
identities:
- "webapp.booksapp.serviceaccount.identity.linkerd.cluster.local"
此时,我们应该能够使用 Web 浏览器查看 booksapp GUI,并且应该能够阅读一切,但不能修改任何内容。
启用写访问
booksapp 应用程序应允许更新书籍和作者,因此我们的下一个任务将是允许对 authors 工作负载进行写入。完成后,我们将能够对作者进行更改(包括更新、添加和删除),但仍然无法更改任何书籍。
booksapp 的构建方式是,webapp 和 books 都需要能够对 authors 进行写入。我们将从创建一个 HTTPRoute 开始,如 示例 9-10 中所示,描述我们要允许的修改请求类型。
示例 9-10. 对 authors 的修改请求
---
apiVersion: policy.linkerd.io/v1beta1
kind: HTTPRoute
metadata:
name: authors-modify-route
namespace: booksapp
spec:
parentRefs:
- name: authors
kind: Server
group: policy.linkerd.io
rules:
- matches:
- path:
value: "/authors/"
type: "PathPrefix"
method: DELETE
- path:
value: "/authors/"
type: "PathPrefix"
method: PUT
- path:
value: "/authors.json"
method: POST
- path:
value: "/"
---
这个 HTTPRoute 附加到我们现有的 authors 服务器,因为它描述了对 authors 工作负载发出的请求。鉴于这个 HTTPRoute,我们希望允许 books 和 webapp 都发出这些请求,如 示例 9-11 中所示。
示例 9-11. 允许修改
---
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: authors-modify-policy
namespace: booksapp
spec:
targetRef:
group: policy.linkerd.io
kind: HTTPRoute
name: authors-modify-route
requiredAuthenticationRefs:
- name: webapp-books
kind: MeshTLSAuthentication
group: policy.linkerd.io
---
apiVersion: policy.linkerd.io/v1alpha1
kind: MeshTLSAuthentication
metadata:
name: webapp-books
namespace: booksapp
spec:
identities:
- "webapp.booksapp.serviceaccount.identity.linkerd.cluster.local"
- "books.booksapp.serviceaccount.identity.linkerd.cluster.local"
在这里,我们使用了在同一个 MeshTLSAuthentication 中列出多个身份的技术,因为在这个示例中,webapp 和 books 需要完全相同的权限。
完成所有这些后,我们有了在 图 9-3 中显示的策略设置。

图 9-3. 设置了 authors 策略后的 books
允许对书籍进行写入
我们将通过允许对 books 工作负载进行写入来完成 booksapp 的功能,如 示例 9-12 中所示。这与允许对 authors 进行写入完全相同,最终将允许 booksapp 完全运行。
示例 9-12. 对 books 的修改请求
---
apiVersion: policy.linkerd.io/v1beta1
kind: HTTPRoute
metadata:
name: books-modify-route
namespace: booksapp
spec:
parentRefs:
- name: books
kind: Server
group: policy.linkerd.io
rules:
- matches:
- path:
value: "/books/"
type: "PathPrefix"
method: DELETE
- path:
value: "/books/"
type: "PathPrefix"
method: PUT
- path:
value: "/books.json"
method: POST
- path:
value: "/"
---
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: books-modify-policy
namespace: booksapp
spec:
targetRef:
group: policy.linkerd.io
kind: HTTPRoute
name: books-modify-route
requiredAuthenticationRefs:
- name: webapp-authors
kind: MeshTLSAuthentication
group: policy.linkerd.io
---
apiVersion: policy.linkerd.io/v1alpha1
kind: MeshTLSAuthentication
metadata:
name: webapp-authors
namespace: booksapp
spec:
identities:
- "webapp.booksapp.serviceaccount.identity.linkerd.cluster.local"
- "authors.booksapp.serviceaccount.identity.linkerd.cluster.local"
重新启用流量生成器
最后,我们将为 traffic 工作负载添加权限,该工作负载始终生成一些负载,以访问 webapp 工作负载。booksapp 应用程序实际上不需要流量生成器,但对于调试和演示非常有用!所以让我们再次运行它。
我们将从 webapp 的服务器开始(这是我们以前不需要的),以便我们可以编写允许请求的策略。这在 示例 9-13 中显示。
示例 9-13. 为 webapp 创建服务器
---
apiVersion: policy.linkerd.io/v1beta1
kind: Server
metadata:
namespace: booksapp
name: webapp-server
labels:
app: webapp
app.kubernetes.io/part-of: booksapp
project: booksapp
spec:
podSelector:
matchLabels:
app: webapp
project: booksapp
port: service
proxyProtocol: HTTP/1
有了这个服务器,允许 traffic 访问它就很简单了。我们将采取懒惰的方式,在这里编写基于服务器的策略,如 示例 9-14 中所示,因为我们确实希望 traffic 能够基本上做任何事情。
示例 9-14. 允许流量生成器
---
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: allow-traffic
namespace: booksapp
labels:
app.kubernetes.io/part-of: booksapp
project: booksapp
spec:
targetRef:
group: policy.linkerd.io
kind: Server
name: webapp
requiredAuthenticationRefs:
- name: traffic
kind: MeshTLSAuthentication
group: policy.linkerd.io
---
apiVersion: policy.linkerd.io/v1alpha1
kind: MeshTLSAuthentication
metadata:
name: traffic
namespace: booksapp
spec:
identities:
- "traffic.booksapp.serviceaccount.identity.linkerd.cluster.local"
摘要
Linkerd 的基于路由的策略机制是 Linkerd 中最复杂的部分,因此实际上有许多强大的工具,无论是开源还是商业,都可以用来创建和调试 Linkerd 中的策略。Linkerd Viz 的 Tap 组件在这里是最简单、最常用的工具;同样,我们在 第六章 中讨论过的 linkerd diagnostics 命令也有很多优点。在商业领域,如果不提及 Buoyant Enterprise 为 Linkerd 提供的策略工具,我们就不全面了。
总体而言,在 Linkerd 中,策略是管理集群中流量的一种强大且可扩展的工具,特别是基于路由的策略不仅是一个非常强大的机制,而且是一个非常专注的工具。这是进一步优化使用服务器端机制建立的策略的绝佳方式。
第十章:使用 Linkerd 观察您的平台
处理微服务应用程序的一个挑战是监控它们。即使在单一语言中,处理多个开发团队,理解哪些工作负载正在通信并从这些通信中提取有用的指标可能是一个巨大的挑战。每个开发人员、语言和框架都会优先考虑不同的细节,组织需要一种统一的方式来查看所有这些不同的服务。
可观察性指的是通过外部观察来理解系统的能力。一个应用程序可以更或少可观察,因此当我们谈论 Linkerd 中的可观察性时,我们指的是它如何影响您应用程序的可观察性。在本章中,我们将看看 Linkerd 通过为所有应用程序提供标准指标来增加可观察性,让您看到微服务之间的关系,并允许拦截和分析应用程序间的通信。
为什么我们需要这个?
与应用安全性类似,微服务对平台工程师提出了新的挑战。动态扩展组件的能力,按需创建和更新服务,以及动态配置基础设施,增加了理解应用程序健康状况的难度。当您的组织为应用程序开发人员构建平台时,重要的是让团队能够轻松做出正确的决策。
Linkerd 如何帮助?
Linkerd 有助于将可观察性融入您的平台。当您将工作负载添加到网格中时,它会自动显示有关该工作负载行为的重要信息。这意味着当我们将 Linkerd 添加到我们的平台时,我们可以使所有应用团队在可观察性方面做出正确的决策。如果允许您的应用程序加入网格,您可以自动显示有关您的应用程序性能、健康状况和关系数据的标准信息。如果进一步构建服务配置文件,您可以保存和共享有关应用程序内个别路由的关键信息。
当我们通过linkerd CLI 来探索如何使用 Linkerd 观察您的应用程序时,我们将覆盖的所有内容也可以通过 Linkerd Viz 仪表板显示。我们将在本章末尾介绍仪表板。
正如我们在第一章中提到的,有三个黄金指标已被反复证明对理解微服务应用的运行情况至关重要:流量、成功率和延迟(参见图 1-8)。
在微服务应用中,每个工作负载都能够提供这些指标非常关键:仅凭这些黄金指标,您应该能够了解特定工作负载的表现如何,以及系统中哪些领域需要特别关注或优化。
Linkerd 代理会自动从每个工作负载和请求中收集详细的指标,并通过 Prometheus 提供这些信息,以便您可以使用各种广泛可用的工具在您的组织内部显示这些信息。
Linkerd 中的可观察性
我们将使用 booksapp 和 emojivoto 应用程序演示 Linkerd 中的可观察性。这两个应用程序故意包含各种故障:我们将使用 Linkerd 的可观察性工具精确定位故障的具体位置。(解决这些问题将留给读者作为练习!)
设置您的集群
您需要一个已安装了 Linkerd 和 Linkerd Viz 的集群(如果需要有关设置此类集群的复习,请参阅第三章)。我们将从克隆booksapp 示例应用和emojivoto 示例应用仓库开始,如示例 10-1 所示,因为我们需要这些仓库来正确配置这些示例应用。
示例 10-1. 克隆仓库
# Clone the booksapp repo
$ git clone https://github.com/BuoyantIO/booksapp.git
# Clone the emojivoto repo
$ git clone https://github.com/BuoyantIO/emojivoto.git
接下来,我们可以在集群中启动和运行这些应用程序,如示例 10-2 所示。
示例 10-2. 设置我们的应用程序
# Install booksapp
$ kubectl create ns booksapp && \
curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/booksapp.yml \
| linkerd inject - | kubectl -n booksapp apply -f -
# Install emojivoto
$ curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/emojivoto.yml \
| linkerd inject - | kubectl apply -f -
# Check that booksapp is ready
$ linkerd check --proxy --namespace booksapp
# Check that emojivoto is ready
$ linkerd check --proxy --namespace emojivoto
一旦我们的检查结果返回正常,我们可以开始使用 linkerd viz 命令查看我们的应用程序,如示例 10-3 所示。请注意,Linkerd Viz 开始显示数据可能需要一分钟左右的时间,因为它需要收集足够的数据以生成统计信息。
示例 10-3. 收集应用程序指标
# View namespace metrics
$ linkerd viz stat ns
# View deployment metrics
$ linkerd viz stat deploy -n emojivoto
$ linkerd viz stat deploy -n booksapp
# View Pod metrics
$ linkerd viz stat pod -n emojivoto
$ linkerd viz stat pod -n booksapp
仅从这些基本查询中,您立即可以看到 emojivoto 和 booksapp 应用程序都存在可靠性问题。在接下来的章节中,我们将深入探讨我们的应用程序,以确定问题的根源。
Tap
Linkerd Viz Tap 允许授权用户收集应用程序之间流动的请求的元数据。它会显示有关请求头、URI、响应代码等的详细信息,以便您在调试时按需访问这些数据,如示例 10-4 所示。Tap 还提供了方便的工具,用于验证您的应用间连接的 TLS 状态。
示例 10-4. 查看 Tap 数据
# Tap the emojivoto web frontend
$ linkerd viz tap deploy/web -n emojivoto
linkerd viz tap 命令会持续运行,直到收到终止信号。它会显示代理传输的实时数据,这些数据将详细描述去往和来自 Web 部署的各个请求。每一行将显示源和目标详情、TLS 状态、任何状态信息以及其他可用的元数据。
安装 Tap
Tap 已集成到 Linkerd Viz 扩展中,因此它将在执行 linkerd viz install 命令时自动安装。但是,如果您安装 Viz 前已运行任何工作负载,您需要在 Tap 可用之前重新启动这些工作负载。
Tap 数据是一个强大的诊断工具,可以提供关于您的应用程序如何彼此通信的详细见解。如果在查看 Linkerd Viz 仪表板中的工作负载时启用了 Tap,它将自动显示请求的摘要。确保在本章后期查看 Viz 仪表板时尝试查看 emojivoto 工作负载的 Tap 数据。
服务配置文件
Linkerd 服务配置文件,由 ServiceProfile 资源体现,允许您为网格提供有关如何使用给定工作负载的详细信息。在其最基本的层面上,ServiceProfile 定义了工作负载允许哪些路由。一旦定义了路由,您可以配置每个路由的指标、超时和重试,以及哪些 HTTP 状态将被视为失败。
ServiceProfile 和 HTTPRoute
Linkerd 项目正在过渡到完全采用网关 API。随着项目朝着这个目标迈进,你会看到一些 Linkerd 自定义资源,包括 ServiceProfile,开始被弃用。
在 Linkerd 2.13 和 2.14 中,ServiceProfile 和 HTTPRoute 常常具有互斥的功能,这使得在开始在集群中使用这些资源之前,仔细审查 ServiceProfile 文档 尤为重要。
您可以通过多种方式构建 ServiceProfiles。最灵活的方法是手工编写它们,但是 Linkerd CLI 提供了几种不同的自动生成方法,正如您将在以下部分看到的那样。
配置 emojivoto 的路由
emojivoto 应用程序有三个工作负载:
-
emoji和voting工作负载使用 gRPC 进行通信,其 gRPC 消息在 protobuf 文件中定义。 -
web工作负载使用 HTTP 与 web 浏览器交互。
我们将从 emoji 和 voting 开始,因为它们有 protobuf 文件。Protobuf 文件作为我们 API 的指南,并且可以被 Linkerd CLI 消耗,以自动创建 ServiceProfiles,如 示例 10-5 所示。
示例 10-5. 从 protobuf 文件创建 ServiceProfiles
# Begin by checking for any existing routes.
$ linkerd viz routes -n emojivoto deploy
# The output will show every workload in the emojivoto
# namespace with a default route. We will now work to
# create application-specific routes for emoji and
# voting.
# Create a ServiceProfile object.
$ linkerd profile --proto emojivoto/proto/Emoji.proto emoji-svc -n emojivoto
# This creates, but doesn't apply, the ServiceProfile
# for the emoji service. Take a minute to review the
# profile object so you understand the basic structure.
# We'll be using these ServiceProfiles again in the
# next chapter.
# Create and apply ServiceProfiles for emoji and voting.
$ linkerd profile --proto emojivoto/proto/Emoji.proto emoji-svc -n emojivoto |
kubectl apply -f -
$ linkerd profile --proto emojivoto/proto/Voting.proto voting-svc -n emojivoto |
kubectl apply -f -
# Now you can view the updated route data in your environment to see
# your deployed applications. You may need to wait a minute
# for data to populate.
$ linkerd viz routes deploy/emoji -n emojivoto
$ linkerd viz routes deploy/voting -n emojivoto
# Each app will show and store details about which routes have
# been accessed.
存储 Linkerd Viz 指标
一旦为您的应用程序创建了 ServiceProfiles,Linkerd 的 Viz 扩展将在 Prometheus 中存储这些数据。将 Linkerd 投入生产的一个非常重要的部分是计划如何长期管理这些 Prometheus 数据。Linkerd Viz 随附的 Prometheus 组件不足以进行长期数据收集:它将数据存储在内存中,并且每次重新启动时都会丢失数据。
通过为 emoji 和 voting 创建路由,我们已经覆盖了应用程序的三分之二。剩下的是 web 组件。虽然我们知道它必须使用 HTTP,因为我们通过浏览器与它通信,但不幸的是,这个组件的作者实际上并没有编写任何关于 API 结构的文档。这让我们不得不试图在没有关于 API 结构的任何信息的情况下构建 ServiceProfile。
幸运的是,我们可以使用 Linkerd 的 Tap 功能来实现这一点,就像 示例 10-6 中展示的那样。
示例 10-6. 使用 Tap 创建 ServiceProfiles
# Create a new ServiceProfile with Tap.
$ linkerd viz profile -n emojivoto web-svc --tap deploy/web --tap-duration 10s |
kubectl apply -f -
# After you run that command, you should expect to see a
# 10-second pause as Linkerd watches live traffic to the
# service in question and builds a profile.
# View the new profile.
$ kubectl get serviceprofile -n emojivoto web-svc.emojivoto.svc.cluster.local -o yaml
# You will see the object created with two routes, list and vote.
# View the updated route data for web. You may need to allow a minute
# for data to populate.
$ linkerd viz routes deploy/web -n emojivoto
Linkerd 默认路由
Linkerd 的 ServiceProfile 对象旨在定义整个 API,但当我们犯错误或者 API 在没有更新 ServiceProfile 的情况下发生变化时会发生什么?这就是默认路由的作用:任何未在 ServiceProfile 中明确定义的路由都将视为默认路由。
默认路由受到关于重试和超时的默认策略的约束。有关默认路由的流量数据被聚合到总括的 [DEFAULT] 路由条目中。
为 booksapp 构建路由
现在我们已经完成了 emojivoto 应用程序,需要为 booksapp 应用程序进行设置。
而 emojivoto 包含一些 API 的 protobuf 文件,booksapp 则使用 OpenAPI 定义。与 protobuf 文件类似,OpenAPI 定义(通常称为“Swagger 定义”,源自较早版本的标准)用于定义如何使用 API,并且 Linkerd 能够读取这些定义以创建 ServiceProfiles。
使用 OpenAPI 或 Swagger 定义创建 ServiceProfile 几乎与使用 protobuf 文件创建 ServiceProfile 完全相同,如 示例 10-7 所示。请务必跟着进行操作,因为我们将在 第十一章 再次使用这些 ServiceProfiles!
示例 10-7. 使用 OpenAPI 定义创建 ServiceProfiles
# Create routes for booksapp.
$ linkerd profile --open-api booksapp/swagger/authors.swagger authors -n booksapp |
kubectl apply -f -
$ linkerd profile --open-api booksapp/swagger/webapp.swagger webapp -n booksapp |
kubectl apply -f -
$ linkerd profile --open-api booksapp/swagger/books.swagger books -n booksapp |
kubectl apply -f -
# With that, we've profiled our applications. We can now wait a minute
# and view the relevant route information.
# View route data for booksapp.
$ linkerd viz routes deploy -n booksapp
# You should see a number of routes with varying success rates.
# In Chapter 11 we'll use some of Linkerd's reliability
# features to help address the issues booksapp is having.
拓扑结构
通过路由、指标和 Tap 数据,我们有很多有用的方法来理解我们的应用程序在做什么,而无需开发人员在其应用程序中包含仪表。另一个常见的挑战是弄清楚这些可能的调用中实际发生了哪些,以及从哪个工作负载到哪个工作负载。Linkerd 也可以为您展示这些信息。
在 示例 10-8 中,我们将检查 booksapp 应用程序组件之间的关系。您可以自行尝试探索 emojivoto 应用程序。
示例 10-8. 在 Linkerd 中查看边缘
# Start by getting the deployments in the booksapp namespace.
$ kubectl get deploy -n booksapp
# You'll see four deployments: traffic, webapp, authors, and books.
# Now, dig into the relationship between these components with
# the linkerd viz edges command.
$ linkerd viz edges deploy -n booksapp
输出被分为五列:
SRC
流量的源头
DST
流量的目的地
SRC_NS
流量起源的命名空间
DST_NS
流量所去的命名空间
SECURED
流量是否通过 Linkerd 的 mTLS 进行加密
结果输出将为您展示书籍应用组件之间的关系概述。它显示 linkerd-viz 命名空间中的 Prometheus 实例与 booksapp 命名空间中的每个部署之间的通信。此外,我们可以看到 traffic 与 webapp 之间的通信,webapp 与 books 以及 authors 之间的通信,以及 books 和 authors 之间的互通。
linkerd viz edges 命令可用于 Kubernetes 中的 Pod 或任何其他工作负载类型。
Linkerd Viz
您可能已经注意到,本章中使用的许多命令都是 linkerd viz 命令。这是我们在第二章介绍的 Linkerd Viz 扩展。它与核心 Linkerd 系统一起发布,因为它通常非常有用,但是从 Linkerd 2.10 版本开始,它被分离为扩展,因此不是每个人都被强制运行它。
Viz 扩展提供了许多 CLI 工具,用于观察您的 Linkerd 安装,以及一个基于 Web 的仪表盘,提供图形界面,用于探索您的 Linkerd 环境。
保护 Viz 仪表盘取决于您自己
如第二章所述,Linkerd Viz 仪表盘中没有内置用户认证功能。如果您希望将 Linkerd Viz 暴露给网络,您需要使用 API 网关或类似的工具来处理,或者简单地通过端口转发,在集群外部访问时将仪表盘设为不可用,并使用 linkerd viz dashboard CLI 命令在 Web 浏览器中打开仪表盘。
使用以下命令打开 Viz 仪表盘:
$ linkerd viz dashboard
现在您可以自己探索。尝试查找按命名空间和工作负载的度量标准。还可以查看单个命名空间,例如 emojivoto,并探索其拓扑结构。
Linkerd 的 Viz 仪表盘包含 Prometheus,并可以轻松与 Grafana 配合使用,如示例 10-9 所示。正如我们之前多次提到的那样,重要的是要意识到,默认的 Linkerd Viz 安装将创建一个仅适用于演示目的的内存中 Prometheus 实例,绝不能依赖于生产使用。我们建议您使用单独的 Prometheus 实例来收集 Linkerd 的指标。
Linkerd 和 Grafana
在较早版本的 Linkerd 中,linkerd viz install 自动安装了 Grafana。从 Linkerd 2.12 开始,由于 Grafana 许可证变更,我们不再允许这样做。Grafana 仍然可以与 Linkerd Viz 配合良好,但对于 Linkerd 2.12 及更高版本,您需要手动安装并配置它,使其与 Linkerd Viz 使用相同的 Prometheus 通信。
示例 10-9. 生产就绪的 Linkerd Viz 安装
# The first step of a production-ready Viz dashboard install
# involves installing a standalone Prometheus instance.
# This guide assumes you've done that in the linkerd-viz
# namespace.
# With that done, you can install Grafana.
$ helm repo add grafana https://grafana.github.io/helm-charts
$ helm repo update
$ helm install grafana -n grafana --create-namespace grafana/grafana \
-f https://raw.githubusercontent.com/linkerd/linkerd2/main/grafana/values.yaml
# The example install uses a values file provided by the
# Linkerd team. It includes important configurations that
# allow the dashboard to properly use Grafana. You can
# read more in the official Linkerd docs:
# https://linkerd.io/2/tasks/grafana/
# After Grafana is installed, install Linkerd Viz and
# tell it to use your Grafana instance.
$ linkerd viz install --set grafana.url=grafana.grafana:3000 \
| kubectl apply -f -
审计轨迹和访问日志
强化我们的环境以防止入侵并不仅仅是减少事件风险和影响。拥有强大的安全姿态还意味着能够快速检测到异常事件的发生,并提供数据,使您的安全团队能够准确理解您已经采取的措施。对于 Linkerd,许多这些数据包含在控制平面容器的系统日志中,可通过kubectl log访问。确保您的安全团队能够访问和分析日志消息的策略绝对值得。
除了普通的日志消息和事件之外,一些用户需要详细的历史记录,记录所有通过代理传输的 HTTP 请求。这需要访问日志。
访问日志:好的、坏的和丑陋的
Linkerd 中的访问日志意味着代理将为它处理的每个 HTTP 请求编写一条日志消息。在一个环境中,您有多个服务彼此通信时,这可能很快变成大量的日志消息,因此在实施访问日志之前,务必查阅官方 Linkerd 文档。我们将讨论高级概念和实际步骤,但日志是一个在 Linkerd 版本之间可能发生变化的领域,因此在查阅文档后务必测试您的设置。
好的
访问日志将为您提供关于应用程序之间交互的极其详细的信息。它是可配置的;您可以以apache或json格式发出消息,以便更容易以编程方式消费。启用访问日志后,您的安全团队将拥有大量数据,帮助他们了解任何安全事件的影响和范围。
糟糕的
存储和处理这些日志非常昂贵,需要显著的工程开销,并且在您的集群中消耗大量资源。您的平台或安全团队将需要管理日志聚合工具和集群上的日志收集代理。启用访问日志将增加运行平台的成本。
不好的
在 Linkerd 中,默认情况下禁用 HTTP 访问日志,因为它对代理的 CPU 和延迟性能有影响。这意味着启用后,您的应用响应时间和计算成本将会增加。具体影响程度将大大取决于您的流量级别和类型。
启用访问日志
您可以在工作负载、命名空间或 Pod 级别设置访问日志配置。在任何情况下,您都需要设置以下注释:
config.linkerd.io/access-log: apache
或:
config.linkerd.io/access-log: json
设置完成后,您需要重新启动目标工作负载以开始收集日志。
我们建议您在将访问日志启用到生产环境之前,测试其对应用性能的影响。这将为您的组织提供决策所需的数据,以便对 Linkerd 中的访问日志做出明智的决策。
摘要
在 Linkerd 中,可观察性从简单的指标到访问日志都有涵盖。Linkerd 允许我们了解应用程序的行为、性能和特性,而无需应用程序开发人员进行任何修改。服务网格的强大之处在于允许平台团队将可观察性作为平台功能提供给应用团队。它还确保所有应用可以以统一的方式被理解和比较。
第十一章:通过 Linkerd 确保可靠性
正如从一开始讨论的那样,在第一章中,微服务应用程序对于其所有通信完全依赖于网络。网络比进程内通信更慢且不太可靠,这引入了新的故障模式,并向我们的应用程序提出了新的挑战。
对于服务网格用户,其中网格中介所有应用程序流量,可靠性的好处在于网格可以在发生故障时做出智能选择。在本章中,我们将讨论 Linkerd 提供的机制,以减轻网络不可靠性问题,帮助解决微服务应用程序固有的不稳定性。
负载均衡
负载均衡可能看起来像一个奇怪的可靠性特性,因为许多人认为 Kubernetes 已经处理了它。正如我们在第五章中首次讨论的那样,Kubernetes 服务在服务的 IP 地址和与服务关联的 Pod 的 IP 地址之间进行区分。当流量发送到 ClusterIP 时,它最终会被重定向到其中一个端点 IP。
然而,在 Kubernetes 中,内置的负载均衡仅限于整个连接。Linkerd 通过使用理解连接所涉及协议的代理来改进这一点,为每个请求选择端点,如图 11-1 所示。

图 11-1。Linkerd 中的服务发现
正如您可以从图 11-1 看到的那样,Linkerd 将使用给定请求的目标地址,并根据它所引用的对象类型调整其端点选择算法以选择目标。
请求级负载均衡
这种连接级负载均衡与请求级负载均衡之间的区别比起初看起来更为重要。在底层,Linkerd 实际上维护了您的工作负载之间的连接池,让它能够快速分派请求到它认为合适的工作负载,而无需连接开销,从而负载均衡各个请求,使负载均匀高效地分布。
您可以在Kubernetes 博客了解有关 Kubernetes 连接级负载均衡的更多信息。
Linkerd 控制平面中恰如其名的目标控制器使所有这些成为可能。对于网格中的每个服务,它维护服务当前端点列表以及它们的健康和相对性能。Linkerd 代理使用这些信息来智能决策何时以及如何发送给定的请求。
重试
有时,由于网络问题或间歇性应用程序故障,请求可能会失败。在这种情况下,Linkerd 代理可以为您重试请求,自动重复以给工作负载另一次处理成功的机会。当然,并非每个请求都可以安全地重试,因此只有在为特定路由显式配置了重试时,Linkerd 代理才会执行自动重试,并且只有在确定安全时才应配置重试。
不要盲目重试!
在为特定请求启用重试之前,请三思!并非所有请求都可以安全地重试 —— 比如一个从账户中取款的请求,在请求成功但响应丢失或提款服务在转移资金后崩溃之前无法发送响应的场景中,是不应该重试的。
重试预算
许多服务网格和 API 网关使用计数重试,您在此定义请求在返回给调用者之前可以重试的最大次数。相比之下,Linkerd 使用预算重试,重试持续进行直到重试与原始请求的比率超出预算为止。
默认情况下,预算为 20%,每秒额外增加 10 次“免费”重试,平均分布在 10 秒内。例如,如果您的工作负载每秒处理 100 个请求(RPS),那么 Linkerd 将允许每秒添加 30 次额外重试(100 的 20%为 20,再加上额外的 10 次)。
预算重试与计数重试
Linkerd 使用预算重试,因为它们通常能让您更直接地控制真正关心的事情:重试将为系统增加多少额外负载?通常情况下,选择特定数量的重试并不能真正帮助控制负载:如果每秒处理 10 个请求并允许 3 次重试,则负载达到 40 RPS;但如果每秒处理 100 个请求并允许 3 次重试,则可能达到400 RPS。预算重试可以更直接地控制额外负载,同时倾向于避免在高负载下可能发生的重试风暴(其中大量重试本身可能会导致 Pod 崩溃,进而引发更多的重试……)。
配置重试
用linkerd viz分析从books到authors的流量需要花一分钟:
$ linkerd viz -n booksapp routes deploy/books --to svc/authors
你会发现books工作负载仅向authors服务的一个单一路由发送请求:HEAD /authors/{id}.json。这些请求失败率达到一半,非常适合重试 — HEAD请求始终是幂等的(即可以重复执行而不会改变结果),因此我们可以安全地在该路由上启用重试。
在 Linkerd 中,我们通过 ServiceProfile 资源来控制重试行为。在本例中,我们将使用authors服务的 ServiceProfile,因为我们将在与authors工作负载交流时启用重试。
重试、ServiceProfiles、HTTPRoutes 和 Linkerd
正如前面提到的,Linkerd 项目正在全面采用Gateway API,这意味着很快你将看到一些 Linkerd 自定义资源,包括 ServiceProfile,开始被弃用。
在 Linkerd 2.13 和 2.14 中,ServiceProfile 和 HTTPRoute 经常具有互斥功能,因此在开始为您的应用程序构建重试时,审查 重试和超时文档 尤为重要。
从使用 kubectl get 查看现有的 ServiceProfile 开始:
$ kubectl get serviceprofile -n booksapp \
authors.booksapp.svc.cluster.local
此 ServiceProfile 应该与 Example 11-1 中的一个非常相似。
示例 11-1. authors ServiceProfile
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
name: authors.booksapp.svc.cluster.local
namespace: booksapp
spec:
routes:
- condition:
method: GET
pathRegex: /authors\.json
name: GET /authors.json
- condition:
method: POST
pathRegex: /authors\.json
name: POST /authors.json
- condition:
method: DELETE
pathRegex: /authors/[^/]*\.json
name: DELETE /authors/{id}.json
- condition:
method: GET
pathRegex: /authors/[^/]*\.json
name: GET /authors/{id}.json
- condition:
method: HEAD
pathRegex: /authors/[^/]*\.json
name: HEAD /authors/{id}.json
您可以看到 ServiceProfile 中列出了五条路由。我们将重点放在最后一条路由上,即 HEAD /authors/{id}.json。
我们可以通过在 ServiceProfile 条目中添加 isRetryable: true 属性,为每个路由单独配置重试。除此之外,每个 ServiceProfile 对象还可以为 ServiceProfile 中所有路由定义重试预算。
添加此属性的最简单方法是交互式地编辑 ServiceProfile:
$ kubectl edit serviceprofiles authors.booksapp.svc.cluster.local -n booksapp
使用编辑器修改 ServiceProfile,使 HEAD /authors/{id}.json 路由的 isRetryable 属性设置为 true,如 Example 11-2 所示。
示例 11-2. 带有重试的 authors ServiceProfile
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
name: authors.booksapp.svc.cluster.local
namespace: booksapp
spec:
routes:
- condition:
method: GET
pathRegex: /authors\.json
name: GET /authors.json
- condition:
method: POST
pathRegex: /authors\.json
name: POST /authors.json
- condition:
method: DELETE
pathRegex: /authors/[^/]*\.json
name: DELETE /authors/{id}.json
- condition:
method: GET
pathRegex: /authors/[^/]*\.json
name: GET /authors/{id}.json
- condition:
method: HEAD
pathRegex: /authors/[^/]*\.json
name: HEAD /authors/{id}.json
isRetryable: true
保存您对 authors ServiceProfile 的更改,并再次使用 linkerd viz routes 检查路由,如下所示:
$ linkerd viz -n booksapp routes deploy/books --to svc/authors -o wide
使用 -o wide 切换输出格式告诉 linkerd viz routes 命令显示有效成功率(重试后)以及实际成功率(不考虑重试之前)。如果在启用重试后重复运行此命令,您将看到有效成功率将随着总体延迟的增加而上升。随着时间的推移,有效成功率应该会达到 100%,尽管实际成功率保持约 50%:authors 工作负载仍然在大约一半的时间内失败,尽管重试可以掩盖来自调用者的失败。
watch 命令
如果您拥有 watch 命令,那么现在正是使用它的好时机。它会每两秒重新运行该命令,直到被中断,为您提供一个轻松的方式来查看变化情况。
$ watch linkerd viz -n booksapp \
routes deploy/books --to svc/authors -o wide
您还可以看到有效和实际 RPS 之间的差异。有效 RPS 约为 2.2,但实际 RPS 将接近其两倍——这是因为重试增加了对故障服务的负载,通过额外的请求来掩盖失败。
为什么我们看到两倍的因素?
我们经常引用默认的重试预算为 20%——那么在这种情况下,我们怎么可能看到两倍的流量呢?事实上,我们如何看到 Linkerd 在 50% 请求失败时掩盖所有故障?
这两个问题的答案在于默认预算中包含的每秒“免费”10 次请求。由于实际负载远低于 10 次每秒,“免费”的额外 10 次请求足以有效允许重试实际流量的 100%,使得 Linkerd 能够掩盖所有故障……代价是流量翻倍。
这些“免费”的 10 次每秒也意味着,即使在轻度使用的服务中,您也不必担心 Linkerd 的预算会让故障泄漏出来,即使在重度使用的服务上,预算也可以保护您免受重试风暴的影响。
配置预算
对许多应用程序来说,Linkerd 的默认预算实际上运作良好,但如果您需要更改它,则需要在您的 ServiceProfile 中编辑retryBudget部分,如示例 11-3 所示。
示例 11-3. 一个重试预算的示例
...
spec:
...
# This retryBudget stanza is AN EXAMPLE ONLY
retryBudget:
retryRatio: 0.3
minRetriesPerSecond: 50
ttl: 60s
...
在示例 11-3 中显示的retryBudget部分将允许在整个一分钟内对原始请求的 30%进行重试,再加上每秒50次的“免费”请求,平均计算。
不要盲目使用这个预算!
在示例 11-3 中显示的预算只是一个例子。请不要假设它对任何实际应用程序有帮助!
超时
超时是一种工具,允许我们在给定请求花费过长时间时强制失败。当与重试一起使用时效果尤为显著,因此,如果请求时间过长,它将被重试——但您不必将它们一起使用!有很多情况下,明智地设置超时可以帮助应用程序做出智能决策,提供更好的用户体验。
当配置了超时并且请求时间过长时,Linkerd 代理将返回 HTTP 504 状态码。对于 Linkerd 的可观察性功能来说,超时会被视为任何其他请求失败(包括触发重试,如果启用了重试),并计入给定路由的有效失败率。
配置超时
让我们从查看从webapp到books的请求开始,看看用户请求的平均延迟如何:
$ linkerd viz -n booksapp routes deploy/webapp --to svc/books
让我们关注PUT /books/{id}.json路由。延迟因环境而异,但我们将从示例的延迟 25 毫秒开始;这可能导致大多数环境中触发一些超时。您可以使用结果的成功率来调整集群中的超时时间。
就像重试一样,超时是通过 Linkerd 中的 ServiceProfiles 进行配置的。与重试一样,我们将首先查看现有的配置文件。我们可以使用以下命令获取books的 ServiceProfile:
$ kubectl get sp/books.booksapp.svc.cluster.local -n booksapp -o yaml
这个 ServiceProfile 应该与示例 11-4 中的那个非常相似。
示例 11-4. books的 ServiceProfile
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
name: books.booksapp.svc.cluster.local
namespace: booksapp
spec:
routes:
- condition:
method: GET
pathRegex: /books\.json
name: GET /books.json
- condition:
method: POST
pathRegex: /books\.json
name: POST /books.json
- condition:
method: DELETE
pathRegex: /books/[^/]*\.json
name: DELETE /books/{id}.json
- condition:
method: GET
pathRegex: /books/[^/]*\.json
name: GET /books/{id}.json
- condition:
method: PUT
pathRegex: /books/[^/]*\.json
name: PUT /books/{id}.json
我们通过将timeout属性添加到路由条目来配置超时,将其值设置为可以被 Go 的time.ParseDuration解析的时间规范。
超时,ServiceProfiles,HTTPRoutes 和 Linkerd
如前所述,Linkerd 项目正在全面采用Gateway API,因此一些 Linkerd 自定义资源,包括 ServiceProfile,很快将开始被弃用。
ServiceProfile 和 HTTPRoute 从 Gateway API 1.0.0 开始具有重叠的超时功能,但在撰写本文时尚未得到稳定的 Linkerd 版本的支持。在开始向应用程序添加重试时,重要的是特别审查重试和超时文档以验证 ServiceProfile 的当前状态。
特别注意的是,HTTPRoute 超时的语法,由GEP-2257指定,比 Go 的time.ParseDuration更为严格,后者用于 ServiceProfile 超时。为了将来最大的兼容性,您可能需要考虑更新您的 ServiceProfile 超时以符合 GEP-2257。
向PUT /books/{id}.json路由添加超时的最简单方法是通过交互式编辑 ServiceProfile 完成,您可以使用以下命令完成:
$ kubectl edit serviceprofiles.linkerd.io \
books.booksapp.svc.cluster.local -n booksapp
您需要将timeout元素添加到PUT /books/{id}.json路由中,并设置值为25ms。示例在 Example 11-5 中展示。
示例 11-5. 带有超时的books ServiceProfile
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
name: books.booksapp.svc.cluster.local
namespace: booksapp
spec:
routes:
- condition:
method: GET
pathRegex: /books\.json
name: GET /books.json
- condition:
method: POST
pathRegex: /books\.json
name: POST /books.json
- condition:
method: DELETE
pathRegex: /books/[^/]*\.json
name: DELETE /books/{id}.json
- condition:
method: GET
pathRegex: /books/[^/]*\.json
name: GET /books/{id}.json
- condition:
method: PUT
pathRegex: /books/[^/]*\.json
name: PUT /books/{id}.json
timeout: 25ms
设置了超时后,您将希望观察从webapp到books服务的流量,以查看超时对服务整体可用性的影响。再次强调,linkerd viz routes是最简单的方法之一:
$ linkerd viz -n booksapp routes deploy/webapp --to svc/books
(如果您愿意,可以使用-o wide—这不会直接帮助您观察延迟,但肯定也不会有害。)
超时为确保应用程序整体可用性提供了宝贵的工具。它们允许您控制延迟,并确保应用程序在等待下游服务响应时不会挂起。
流量转移
流量转移 指的是根据外部条件更改请求的目标。通常这是两个或多个目的地的加权分割(金丝雀),或者基于头部匹配、用户名等的分割(A/B 分割),尽管还有许多其他类型的分割是可能的。流量转移是渐进式交付的重要组成部分,通过仔细地将流量转移到新版本并验证功能,您可以滚动部署新的应用程序版本。但是,即使不进行渐进式交付,也可以从流量转移中受益。
流量转移,Gateway API 和 Linkerd SMI 扩展
从 Linkerd 2.13 开始,Linkerd 原生支持使用 Gateway API HTTPRoute 资源进行流量转移,因此流量转移是我们将使用 Gateway API 资源配置 Linkerd 的第一个领域。
HTTPRoutes 与 Linkerd SMI
在 Linkerd 2.13 之前的版本中,您仍然可以进行流量转移,但需要使用 Linkerd SMI 扩展(我们在第二章中提到过)。有关 SMI 扩展及其传统的 TrafficSplit 资源的信息,请查阅Linkerd 官方关于 SMI 的文档。我们建议使用 2.13 及更高版本中的 Gateway API。
当我们探索 Linkerd 中的流量转移时,我们将看到两种基本的方法:基于权重和基于标头。
配置您的环境
在这一节中,我们将演示使用一个完全不同的应用程序Podinfo来进行流量转移。为了跟随流量转移演示,请您开始一个新的集群;如果您需要任何帮助,请参考第三章中的材料。
一旦您有了新的集群,您可以跟着示例 11-6 开始使用 Podinfo 进行流量转移。
示例 11-6. 启动 Podinfo
# Start in a clean working directory, as we will be cloning the
# linkerd-book/luar Git repository.
$ git clone https://github.com/linkerd-book/luar.git
# First, we'll create our namespace, podinfo, with the
# linkerd.io/inject: enabled annotation set on it. This will
# ensure our Pods get Linkerd proxies attached to them.
$ kubectl apply -f luar/reliability/ns.yaml
# Next, we'll install the podinfo application using Helm.
$ helm repo add podinfo https://stefanprodan.github.io/podinfo
$ helm repo up
# Install 3 versions of podinfo:
# - podinfo is our "version 1" Pod.
# - podinfo-2 is our "version 2" Pod.
# - frontend is a frontend to the whole thing.
$ helm install podinfo \
--namespace podinfo \
--set ui.message="hello from v1" \
podinfo/podinfo
$ helm install podinfo-2 \
--namespace podinfo \
--set ui.message="hello from v2" \
podinfo/podinfo
$ helm install frontend \
--namespace podinfo \
--set backend=http://podinfo:9898/env \
podinfo/podinfo
# Create a traffic generator for podinfo.
$ kubectl apply -f luar/reliability/generator.yaml
# Check that the applications are ready.
$ linkerd check --proxy -n podinfo
# Verify that both versions of the podinfo workload are running.
$ kubectl get pods -n podinfo
# Verify that each version of podinfo has its own Service.
$ kubectl get svc -n podinfo
有了这个,我们的基础演示应用程序已经准备好进行流量分流了。我们应用程序的基本布局如图 11-2 所示。

图 11-2. Podinfo 应用程序架构
接下来,您可以观察流量如何在您的集群中移动。建议您在一个单独的窗口中运行,就像示例 11-7 中展示的那样,这样您就可以看到随着资源操作的变化。
示例 11-7. 观察 Podinfo 的流量
# If you have the watch command, it works well for this.
$ watch linkerd viz stat deploy -n podinfo
# If you don't have watch, it's simple enough to emulate.
$ while true; do
clear
date
linkerd viz stat deploy -n podinfo
sleep 2
done
这将展示您的集群中的流量路由情况。您应该看到两个 Podinfo 部署,podinfo 和 podinfo-v2。此刻 podinfo-v2 应该接收到的流量非常少,因为我们还没有将任何流量转移到它。
基于权重的路由(金丝雀发布)
基于权重的路由 是一种根据简单的百分比选择请求目标位置的流量转移方法:一定百分比的可用流量发送到一个目标,剩余的流量发送到另一个目标。基于权重的路由允许我们将一小部分流量转移到服务的新版本,以查看其行为。
在渐进式交付中,这被称为 金丝雀路由,得名于煤矿中的“金丝雀”,当空气变坏时会死去,从而提醒矿工。这里的想法是,您可以将一小部分流量转移以测试新版本的工作负载是否会失败或成功,然后再转移更多流量。成功的金丝雀发布会在所有流量都转移完成后结束,并且旧版本可以被废弃。
要启动金丝雀发布,我们需要创建一个 HTTP 路由,如示例 11-8 所示。
哪个 HTTP 路由?
我们将使用policy.linkerd.io HTTPRoutes 来适应使用旧版 Linkerd 的读者。然而,重要的是要注意,像 Flagger 和 Argo Rollouts 不支持policy.linkerd.io!如果您使用这些工具之一,您需要使用要求 Linkerd 2.14 或更高版本的gateway.networking.k8s.io HTTPRoutes。
Example 11-8. canary HTTPRoute
---
apiVersion: policy.linkerd.io/v1beta2
kind: HTTPRoute
metadata:
name: podinfo-route
namespace: podinfo
spec:
parentRefs:
- name: podinfo
namespace: podinfo
kind: Service
group: core
port: 9898
rules:
- backendRefs:
- name: podinfo
namespace: podinfo
port: 9898
weight: 5
- name: podinfo-v2
namespace: podinfo
port: 9898
weight: 5
这个 HTTPRoute 将把流量分配到podinfo和podinfo-v2服务之间。我们为两个服务设置权重为 5,这将导致 50%的流量转移到podinfo-v2,而将另外 50%保留在我们原来的podinfo。
比例才是重要的
权重的绝对值通常不重要 —— 它们不需要加起来等于任何特定的数字。重要的是权重的比例,因此使用权重为 5 和 5、100 和 100 或 1 和 1 都会得到 50/50 的分流。
另一方面,权重为 0 明确表示不向该后端发送任何流量,所以不要试图使用 0/0 来进行 50/50 的分流。
服务对服务:ClusterIPs、端点和 HTTPRoutes
机智的读者会注意到我们在parentRefs和backendRefs中都使用了podinfo两次。这不会导致路由循环吗?我们难道安排流量来到podinfo,然后再次定向到podinfo,并且永远这样重复,直到最终它最终被转移到podinfo-v2吗?
放心,那是不会发生的。如果我们回到图 11-3(#k8s-service-architecture-3)展示的 Kubernetes 服务架构,关键点在于:
-
当一个服务在
parentRefs中使用时,这意味着 HTTPRoute 将控制定向到该服务的流量。 -
当一个服务在
backendRefs中使用时,它允许 HTTPRoute 将流量定向到与该服务连接的 Pod。

图 11-3. Kubernetes 服务的三个独立部分
所以,我们用podinfo-route实际上表明 95%的流量将被定向到podinfo服务 IP 的podinfo端点,另外 5%将被定向到podinfo-v2端点,因此没有环路。这种行为在 GAMMA 倡议的GEP-1426中定义。
你不能将路由发送到另一个路由
GEP-1426 也阻止 HTTPRoutes 的“堆叠”。假设我们应用了像 Example 11-8 中展示的podinfo-route,然后再应用另一个 HTTPRoute(podinfo-v2-canary)来尝试将流量分流到podinfo-v2。在这种情况下:
-
直接发送到
podinfo-v2的流量将由podinfo-v2-canary进行分流。 -
发送到
podinfo的流量,然后由podinfo-route重定向到podinfo-v2的将不会分流。
这是因为podinfo-route将其流量直接发送到podinfo-v2的端点。由于该流量绕过了podinfo-v2服务 IP,podinfo-v2-canary永远没有机会处理它。
将 podinfo-route 应用于您的集群,并查看在观察流量的终端窗口中流量如何转移。您将看到每秒约 25 个请求转移到 v2 部署(请记住,linkerd viz 观察到的指标需要一些时间才能跟上)。
您可以修改权重并实时查看流量如何变动:只需像这样使用 kubectl edit:
$ kubectl edit httproute -n podinfo podinfo-route
一旦保存了编辑版本,新的权重应立即生效,更改您在观察流量的窗口中看到的内容。
完成后,请继续使用以下命令删除 podinfo-route 路由:
$ kubectl delete httproute -n podinfo podinfo-route
您应该看到所有流量都转移到 podinfo,为我们的基于标头的路由实验做准备。
基于标头的路由(A/B 测试)
基于标头的路由 允许您根据请求中包含的标头做出路由决策。这通常用于 A/B 测试。例如,如果您有两个版本的用户界面,您通常不希望每次用户加载页面时随机选择它们之一。相反,您可以使用某个标头来标识用户,以确定性方式选择 UI 的版本,这样给定用户将始终看到一致的 UI,但不同用户可能会看到不同的 UI。
我们将使用基于标头的路由来允许使用标头选择 podinfo 的版本。首先应用一个新的 podinfo-route HTTPRoute,如示例 11-9 所示。(再次使用 policy.linkerd.io HTTPRoutes;有关此选择的警告,请参阅 “哪个 HTTPRoute?”。)
示例 11-9. 基于标头的路由
apiVersion: policy.linkerd.io/v1beta2
kind: HTTPRoute
metadata:
name: podinfo-route
namespace: podinfo
spec:
parentRefs:
- name: podinfo
kind: Service
group: core
port: 9898
rules:
- matches:
- headers:
- name: "x-request-id"
value: "alternative"
backendRefs:
- name: "podinfo-v2"
port: 9898
- backendRefs:
- name: "podinfo"
port: 9898
(如果您刚刚按照权重路由的说明操作,那很好;如果您还没有删除,此 podinfo-route 将覆盖该部分的内容。)
此版本具有新的 matches 部分,用于标头匹配。我们还将 podinfo-v2 的引用从主 backendRefs 部分移动到 matches 下的新 backendRefs。效果是,只有当具有值为 alternative 的 x-request-id 标头时,流量才会转移到 podinfo-v2。
由于我们安装的流量生成器不发送任何带有正确标头的请求,因此当您应用此 HTTPRoute 时,您应立即看到所有流量从 podinfo-v2 中消失。我们可以使用 curl 发送带有正确标头的流量以路由到 podinfo-v2,如 示例 11-10 所示。
示例 11-10. 使用 curl 测试基于标头的路由
# Start by forwarding traffic to your frontend service.
$ kubectl port-forward svc/frontend-podinfo 9898:9898 &
# Now send a request to the service and see what message you get back.
# You should see "hello from v1" since this request didn't include the
# header.
$ curl -sX POST localhost:9898/echo \
| jq -r ".[]" | grep MESSAGE
# Now try again, setting the x-request-id header.
# You should see "hello from v2" since this request does include the
# header.
$ curl -H 'x-request-id: alternative' -sX POST localhost:9898/echo \
| jq -r ".[]" | grep MESSAGE
流量转移总结
现在你对如何使用 HTTPRoute 对象在集群中操作流量有了了解。虽然暂时还可以使用 Linkerd SMI 扩展,但我们强烈建议改用 Gateway API —— 如果你正在使用像 Flagger 或 Argo Rollouts 这样的渐进交付工具与 Linkerd 一起使用,使用 Gateway API 可以显著简化与该工具的接口(尽管如前所述,你可能需要使用支持官方 Gateway API 类型的 Linkerd 2.14)。
熔断
当你在规模上运行应用程序时,自动隔离和将流量从任何遇到问题的 Pod 中转移可能是有帮助的。Linkerd 倾向于自动路由到表现不佳的 Pod,通过使用指数加权移动平均的延迟来选择接收给定请求的 Pod。通过熔断,你可以明确地让 Linkerd 避免路由到任何遇到问题的 Pod。
启用熔断
当你在一个服务上启用熔断时,Linkerd 将有选择地隔离那些经历了多次连续失败的终端点。像所有高级特性一样,请确保在将其应用于你的环境之前阅读最新的 Linkerd 熔断文档。
我们将演示如何使用 Linkerd 的熔断通过安装一个故意不良的 Pod:
$ kubectl apply -f luar/reliability/podinfo-v3.yaml
在你监控流量的终端中,现在应该看到三个 podinfo 部署正在运行。流量应该大致均匀地分配在 podinfo 和 podinfo-v3 之间,因为 podinfo-v3 被精心设置为与 podinfo 同一服务的一部分。
看到 podinfo-v2 吗?
如果你看到任何流量进入 podinfo-v2,请确保没有任何 HTTPRoutes 仍在通过运行 kubectl get httproute -n podinfo 来分流流量。
你还应该注意 podinfo-v3 的成功率不到 100%。按照这里展示的方法为 podinfo 服务添加一个熔断器应该会改善情况:
$ kubectl annotate -n podinfo svc/podinfo \
balancer.linkerd.io/failure-accrual=consecutive
这告诉 Linkerd 在 podinfo 服务上应用熔断策略。它会寻找连续的失败并停止路由到任何有问题的 Pod。如果你回看你监控流量的窗口,很快你就会发现 podinfo-v3 不再接收太多的流量。
为什么使用注释?
Linkerd 的熔断器仍然相对较新,因此目前只能使用注释进行配置。随着开发的进行,请密切关注最新的 Linkerd 熔断文档以保持最新!
调整熔断
我们可以通过服务上的附加注释进一步调整熔断器:
balancer.linkerd.io/failure-accrual-consecutive-max-failures
设置需要在终端点被隔离之前看到的失败次数。默认为7。
balancer.linkerd.io/failure-accrual-consecutive-min-penalty
设置端点应该被隔离的最短时间。GEP-2257 持续时间,默认为一秒 (1s)。
balancer.linkerd.io/failure-accrual-consecutive-max-penalty
设置隔离期的上限(端点在网格再次测试之前被隔离的最长时间)。GEP-2257 持续时间,默认为一分钟 (1m)。
balancer.linkerd.io/failure-accrual-consecutive-jitter-ratio
添加一些随机性到隔离和测试时间框架中。默认为 0.5;很少需要调整。
查看流量时,您可能仍然会看到 podinfo-v3 显示过多的失败。通过像示例 11-11 所示,使断路器对失败更加敏感,将允许断路器更积极地将 podinfo-v3 从使用中移出,这应该有助于改善情况。
示例 11-11. 调整断路器
# First, we'll set the number of failures we need to see to quarantine
# the endpoints. In this case, we'll change it from the default of 7 to 3.
$ kubectl annotate -n podinfo svc/podinfo \
balancer.linkerd.io/failure-accrual-consecutive-max-failures=3
# Next, we'll change the minimum quarantine time to 30 seconds from 1 second.
$ kubectl annotate -n podinfo svc/podinfo \
balancer.linkerd.io/failure-accrual-consecutive-min-penalty=30s
# Finally, we change the max penalty time to 2 minutes.
$ kubectl annotate -n podinfo svc/podinfo \
balancer.linkerd.io/failure-accrual-consecutive-max-penalty=2m
有了这个设置,我们应该看到通过到 podinfo-v3 的错误大大减少。
断路器不会隐藏所有故障
当 Linkerd 检查一个给定的端点是否已恢复时,它通过允许一个实际用户请求通过来进行。如果此请求失败,该故障将一直传递回调用者(除非还启用了重试)。
在我们的示例中,这意味着一个潜在的失败请求每 30 秒就会到达 podinfo-v3,以便 Linkerd 检查断路器是否可以被重置。
摘要
有了这个设置,我们已经介绍了 Linkerd 如何帮助提升应用程序的可靠性。您可以在网络和 API 的瞬时故障发生时重试,添加请求超时以保持总体可用性,将流量分割到服务的不同版本中以执行更安全的发布,设置断路器来保护服务免受失败 Pod 的影响。有了这些,您就可以很好地运行一个可靠和弹性的平台。
第十二章:使用 Linkerd 进行多集群通信
每个 Kubernetes 集群都代表着一个单一的安全和操作故障域。当你考虑扩展你的平台以适应更多团队、更多客户和更多用例时,你将不可避免地遇到一个问题:你希望如何分布你的应用程序?你想要使用大型区域性集群将所有生产应用程序放在一个地方吗?还是想要为每个应用程序或每个团队使用专门构建的集群?大多数团队最终会在中间某个地方,一些集群是共享的,而另一些是为某些应用程序或应用程序类别专门构建的。
Linkerd 的目标是简化运行多个集群时围绕技术实施问题的解决方案。
多集群配置类型
Linkerd 支持两种多集群配置风格:基于网关的多集群 和 Pod-to-Pod 多集群。基于网关的多集群设置更容易部署;Pod-to-Pod 设置提供更高级的功能。你可以根据具体情况选择最合适的方式,并且如果需要的话,甚至可以在同一集群中同时使用两种方式。
基于网关的多集群
Linkerd 的基于网关的多集群设置通过 Linkerd 称为网关的特殊工作负载路由集群之间的通信,这些网关可通过 LoadBalancer 服务访问。这意味着基于网关的多集群连接不需要特别复杂的网络配置:基于网关的多集群通信所需的只是集群中的 Pod 能够连接到另一个集群网关的 LoadBalancer,不管连接方式如何。网络也不需要安全:Linkerd 将通过其通常的 mTLS 处理此问题。
图表 12-1 显示了基于网关的多集群架构。

图表 12-1. 网关多集群架构
图表中的数字显示了当 cluster 1 中的 vote-bot 与 cluster 2 中的 web 交流时所需的网络跳数,然后再回到 cluster 1 中的 vote。 (实际上在 emojivoto 中发生了这种情况。)沿着这条路径,你会看到总共六个跳跃。这有时可能会复杂化事物,因此产生了 Pod-to-Pod 多集群的发展。
Pod-to-Pod 多集群
相比之下,Pod-to-Pod 多集群配置要求你的 Pod 能够直接互相通信,甚至跨集群边界。这可能更具挑战性,因为你的集群供应商需要支持此功能。如果你正在创建自己的裸金属集群,这可能是完全可行的。如果你使用的是云提供商,则取决于提供商。
图表 12-2 显示了 Pod-to-Pod 多集群架构。

图表 12-2. Pod-to-Pod 多集群架构
沿着从vote-bot到web再到vote的路径,我们只看到两次跳转,与单个集群中看到的相同。
网关与 Pod 到 Pod
像计算中的许多事物一样,选择的模式取决于您的情况:
-
基于网关的多集群模式无疑比 Pod 到 Pod 模式更容易设置;Pod 到 Pod 需要更高级的网络支持。
-
Pod 到 Pod 比基于网关的方式稍快,因为没有经过网关的跳转。不过,使用 Linkerd 时这种差异往往可以忽略不计。
-
可能更重要的一个问题是,在基于网关的多集群设置中,来自另一个集群的任何调用都将显示为具有网关的身份,而不是实际的原始工作负载。
-
另一个重要的问题是,在 Pod 到 Pod 模式下,Linkerd 的
destination服务也需要能够连接到远程 Kubernetes API 服务器。
记住,只要有正确的 IP 连接性,两种模式可以在同一个集群中共存。在处理多集群通信时,您有极大的灵活性。
多集群证书
无论您决定使用哪种多集群设置,您的集群信任层级都需要 Linkerd 之间建立多集群连接的共同根。迄今为止,最简单的方法是让它们共享一个共同的信任锚点,如图 12-3 所示。这也意味着,如果您希望集群彼此隔离,它们绝对不能共享相同的信任锚点!

图 12-3. 多集群信任层级
通常,管理这个最简单的方法是每个环境级别(开发、UAT、生产、测试等)拥有一个单一的信任锚点,因此,例如,开发集群可以互相对等,但不能与生产集群对等。同样,每个级别设置 CA 通常也是最简单的方式之一,这样每个 CA 只需关注一种证书类型。
不共享身份发行者
即使在多集群设置中,您的集群也不应共享身份发行者证书。保持身份发行者分离对于确保工作负载身份的来源以及简化身份发行者轮换的操作方面都至关重要。
最终,您的环境需求将决定您如何设置证书和 CA。
跨集群服务发现
多集群难题的最后一部分是服务发现:一个集群中的工作负载如何知道如何找到其他集群中的工作负载?Linkerd 通过服务镜像解决了这个问题,它是 Multicluster 扩展提供的控制平面的一部分。
正如其名称所示,服务镜像安排一个集群中的服务在其他集群中出现。它如何设置镜像服务取决于您使用的多集群配置类型:
基于网关的多集群
连接到镜像服务将被重定向到原始服务前面的网关。然后,网关知道如何将请求传递给真实的 Pod,包括端点选择、策略执行等。
Pod 到 Pod 多集群
连接到镜像服务将直接通过网络转发到另一个 Pod。在与进行连接的工作负载旁边的 Linkerd 代理知道直接可用的端点,因此它处理负载均衡、策略执行等。
服务镜像不会盲目地镜像每个服务;它只会镜像那些带有 mirror.linkerd.io/exported 标签的服务。标签的值,再次强调,取决于多集群模式:
mirror.linkerd.io/exported: true
适用于基于网关的多集群配置。服务镜像将期望远程集群有一个网关,并将设置镜像服务来使用它。
mirror.linkerd.io/exported: remote-discovery
适用于 Pod 到 Pod 多集群配置。服务镜像将设置镜像服务直接转发到原始 Pod。
还值得注意的是,服务镜像需要权限与远程集群的 Kubernetes API 服务器通信。此权限由 Linkerd Link 资源处理,通过 linkerd multicluster link CLI 命令创建。
链接和 GitOps
链接资源实际上比它们应该的要更加紧急:运行 linkerd multicluster link 命令会创建一个凭据密钥、一个链接资源和服务镜像控制器。不幸的是,目前在没有实际运行该命令的情况下复制所有内容非常困难。
有了这些背景知识,我们可以开始设置一个示例多集群架构。
设置多集群环境
多集群 Linkerd 始终 要求您能够在集群之间路由 IP 流量。在某些情况下,它还要求所有集群具有不同且不重叠的集群和服务 CIDR 范围。
显然,确保满足这些要求有点超出了服务网格的范围!但是,在本章中演示多集群设置时,我们将创建两个 k3d 集群,并在执行时确保满足这些要求。我们将在进行 k3d 特定基础设施操作时进行说明。
首先,如 示例 12-1 所示,我们将创建两个连接到同一 Docker 网络的 k3d 集群,并给它们分配独立的集群和服务 CIDR,以便我们可以在基于网关的多集群或 Pod 到 Pod 多集群模式中使用此设置。
这整个区块显然是 k3d 特定的!
示例 12-1. 创建集群
# Create cluster1
$ k3d cluster create cluster1 \
--servers=1 \
--network=mc-network \
--k3s-arg '--disable=traefik@server:*' \
--k3s-arg '--cluster-cidr=10.23.0.0/16@server:*' \
--k3s-arg '--service-cidr=10.247.0.0/16@server:*' \
--wait
# Create cluster2
$ k3d cluster create cluster2 \
--servers=1 \
--network=mc-network \
--k3s-arg '--disable=traefik@server:*' \
--k3s-arg '--cluster-cidr=10.22.0.0/16@server:*' \
--k3s-arg '--service-cidr=10.246.0.0/16@server:*' \
--wait
注意,两个集群都被告知使用相同的 Docker 网络 (--network=mc-network),但它们具有独立且不重叠的 CIDR 范围。
我们将继续通过设置集群之间的 IP 路由来进行,如 示例 12-2 所示。这里的 docker exec 命令是特定于 k3d 的,但在节点上运行 ip route add 命令的思路实际上并非特定于 k3d。
示例 12-2. 设置 IP 路由
# For each cluster, we need its Node IP and Pod CIDR range.
$ cluster1_node_ip=$(kubectl --context k3d-cluster1 get node k3d-cluster1-server-0 \
-o jsonpath='{.status.addresses[?(.type=="InternalIP")].address}')
$ cluster1_pod_cidr=$(kubectl --context k3d-cluster1 get node k3d-cluster1-server-0 \
-o jsonpath='{.spec.podCIDR}')
$ cluster2_node_ip=$(kubectl --context k3d-cluster2 get node k3d-cluster2-server-0 \
-o jsonpath='{.status.addresses[?(.type=="InternalIP")].address}')
$ cluster2_pod_cidr=$(kubectl --context k3d-cluster2 get node k3d-cluster2-server-0 \
-o jsonpath='{.spec.podCIDR}')
# Once that's done, we'll run `ip route add` on each Node to set up IP
# routing. We only need to do this once per cluster because these are
# single-Node clusters.
$ docker exec -it k3d-cluster1-server-0 \
ip route add ${cluster2_pod_cidr} via ${cluster2_node_ip}
$ docker exec -it k3d-cluster2-server-0 \
ip route add ${cluster1_pod_cidr} via ${cluster1_node_ip}
一旦我们设置好路由,就可以使用 step 创建新的证书,如 示例 12-3 所示。如前所述,无论您使用什么类型的集群,您都需要使用相同的信任锚。
示例 12-3. 为多集群创建证书
# First, create a trust anchor. This will be shared across all clusters.
$ step certificate create root.linkerd.cluster.local ca.crt ca.key \
--profile root-ca --no-password --insecure
# Next, use the trust anchor to create identity issuer certificates
# (one for each cluster).
$ step certificate create identity.linkerd.cluster.local issuer1.crt issuer1.key \
--profile intermediate-ca --not-after 8760h --no-password --insecure \
--ca ca.crt --ca-key ca.key
$ step certificate create identity.linkerd.cluster.local issuer2.crt issuer2.key \
--profile intermediate-ca --not-after 8760h --no-password --insecure \
--ca ca.crt --ca-key ca.key
最后,考虑到我们的证书,我们可以安装 Linkerd 和 Viz 扩展!这在 示例 12-4 中显示。
要注意上下文!
在处理多集群设置时,我们个人犯了比愿意承认的更多错误。尴尬的是,大部分问题是因为我们对错误的集群运行了 kubectl 命令,所以请注意那些 --context 参数!
或者,为每个集群设置一个窗口并进行工作。如果您有单独的 Kubernetes 配置文件,并且可以根据需要设置 KUBECONFIG 变量,那么这种方法效果很好。
示例 12-4. 安装 Linkerd
# Install Linkerd in cluster1...
$ linkerd install --context k3d-cluster1 --crds \
| kubectl apply --context k3d-cluster1 -f -
$ linkerd install --context k3d-cluster1 \
--identity-trust-anchors-file ca.crt \
--identity-issuer-certificate-file issuer1.crt \
--identity-issuer-key-file issuer1.key \
| kubectl apply --context k3d-cluster1 -f -
$ linkerd viz install --context k3d-cluster1 |
kubectl apply --context k3d-cluster1 -f -
$ linkerd check --context k3d-cluster1
# ...then repeat for cluster2.
$ linkerd install --context k3d-cluster2 --crds \
| kubectl apply --context k3d-cluster2 -f -
$ linkerd install --context k3d-cluster2 \
--identity-trust-anchors-file ca.crt \
--identity-issuer-certificate-file issuer2.crt \
--identity-issuer-key-file issuer2.key \
| kubectl apply --context k3d-cluster2 -f -
$ linkerd viz install --context k3d-cluster2 |
kubectl apply --context k3d-cluster2 -f -
$ linkerd check --context k3d-cluster2
此时,我们必须决定是否使用基于网关或 Pod 到 Pod 的多集群架构,因为从这一点开始我们的做法会发生变化。
继续使用基于网关的设置
如果您想要一个 Pod 到 Pod 的设置,请跳到 “继续使用 Pod 到 Pod 的设置”。
要继续使用基于网关的多集群模式,请按照 示例 12-5 中所示安装 Linkerd Multicluster 扩展。此扩展还随着核心 Linkerd CLI 一起提供,因此您无需安装额外的命令来使用它。这是安装 Linkerd Multicluster 的默认方式,因为基于网关的多集群模式早于 Pod 到 Pod。
示例 12-5. 使用网关安装 Linkerd Multicluster
$ linkerd multicluster install --context k3d-cluster1 |
kubectl apply --context k3d-cluster1 -f -
$ linkerd multicluster check --context k3d-cluster1
$ linkerd multicluster install --context k3d-cluster2 |
kubectl apply --context k3d-cluster2 -f -
$ linkerd multicluster check --context k3d-cluster2
接下来,我们需要使用网关将我们的集群连接在一起,如 示例 12-6 所示。
k3d 和 --api-server-address
k3d 集群很奇怪:它们总是创建 Kubernetes 上下文,说 Kubernetes API 服务器在 localhost 上。在同一个 Docker 网络上进行我们的多集群设置时,我们的 cluster1 不能使用 localhost 与 cluster2 或反之间进行通信。
因此,对于 k3d,我们必须使用 --api-server-address 来覆盖地址,使用可路由的 IP 地址用于其他集群。这是特定于 k3d 的。
示例 12-6. 使用网关连接集群
# Link cluster1 to cluster2\. Again, --api-server-address is k3d-specific.
# PAY ATTENTION TO CONTEXTS! We run `linkerd multicluster link` in the
# cluster1 context, then apply it in the cluster2 context.
$ linkerd multicluster link --context k3d-cluster1 \
--api-server-address https://${cluster1_node_ip}:6443 \
--cluster-name k3d-cluster1 |
kubectl apply --context k3d-cluster2 -f -
# Link cluster2 to cluster1\. Again, --api-server-address is k3d-specific.
# PAY ATTENTION TO CONTEXTS! We run `linkerd multicluster link` in the
# cluster2 context, then apply it in the cluster1 context.
$ linkerd multicluster link --context k3d-cluster2 \
--api-server-address https://${cluster2_node_ip}:6443 \
--cluster-name k3d-cluster2 |
kubectl apply --context k3d-cluster1 -f -
# Ensure everything is healthy (note that this will fail for k3d, even
# though things are working).
$ linkerd multicluster check
# Check on the gateways.
$ linkerd multicluster gateways --context k3d-cluster1
$ linkerd multicluster gateways --context k3d-cluster2
此时,请跳到 “多集群注意事项”。
继续使用 Pod 到 Pod 的设置
如果你想要一个基于网关的设置,请回到 “继续使用基于网关的设置”。
要继续使用 Pod 对 Pod 多集群模式,我们按照示例 12-7 中显示的方法安装 Linkerd 多集群扩展,使用--gateway false标志。
示例 12-7. 安装 Linkerd 多集群 Pod 对 Pod
$ linkerd multicluster install --gateway false --context k3d-cluster1 |
kubectl apply --context k3d-cluster1 -f -
$ linkerd multicluster check --context k3d-cluster1
$ linkerd multicluster install --gateway false --context k3d-cluster2 |
kubectl apply --context k3d-cluster2 -f -
$ linkerd multicluster check --context k3d-cluster2
现在,我们需要链接我们的集群,如示例 12-8 所示。再次需要--gateway false标志(并且我们只需要--api-server-address用于 k3d)。
示例 12-8. Pod 对 Pod 链接集群
# Link cluster1 to cluster2\. Again, --api-server-address is k3d-specific.
# PAY ATTENTION TO CONTEXTS! We run `linkerd multicluster link` in the
# cluster1 context, then apply it in the cluster2 context.
$ linkerd multicluster link --gateway false --context k3d-cluster1 \
--api-server-address https://${cluster1_node_ip}:6443 \
--cluster-name k3d-cluster1 |
kubectl apply --context k3d-cluster2 -f -
# Link cluster2 to cluster1\. Again, --api-server-address is k3d-specific.
# PAY ATTENTION TO CONTEXTS! We run `linkerd multicluster link` in the
# cluster2 context, then apply it in the cluster1 context.
$ linkerd multicluster link --gateway false --context k3d-cluster2 \
--api-server-address https://${cluster2_node_ip}:6443 \
--cluster-name k3d-cluster2 |
kubectl apply --context k3d-cluster1 -f -
# Ensure everything is healthy (note that this will fail for k3d, even
# though things are working).
$ linkerd multicluster check
多集群问题
无论您是为基于网关的多集群还是 Pod 对 Pod 多集群设置,有两件事情始终非常重要需要记住:
方向、上下文和链接
每个linkerd multicluster link命令都创建一个单向链接。在cluster1上下文中运行link命令,并将其应用于cluster2上下文,是将权限和 DNS 信息提供给cluster2,以便与cluster1通信。基本上,在cluster1上下文中运行link命令收集关于cluster1的信息和凭据;在cluster2上下文中应用它则将所有内容提供给cluster2。
在我们的示例设置中(无论是基于网关还是 Pod 对 Pod),我们分别运行两个链接。在大多数双集群设置中,这是有道理的,但绝对不是必需的。
检查您的连接
我们帮助过很多人解决他们的多集群设置问题,最常见的问题是集群之间的连接问题。在调试多集群设置时,首先要检查的总是确保您的集群之间有适当的连接。
通常,您可以通过在一个集群中运行一个带有curl、dig等工具的 Pod,然后尝试向另一个集群发出简单的 HTTP 调用来非常有效地进行此操作。
部署和连接应用
此时,我们的集群已经连接,我们需要开始利用我们的链接。在示例 12-9 中,我们将在两个集群中部署emojivoto 示例应用程序。
示例 12-9. 部署多集群应用程序
# Pull down the luar repo if you don't already have it.
$ git clone https://github.com/linkerd-book/luar.git
# Create the emojivoto ns in each cluster.
$ kubectl apply --context k3d-cluster1 -f luar/multicluster/ns.yaml
$ kubectl apply --context k3d-cluster2 -f luar/multicluster/ns.yaml
# This will ensure that all new Pods come up with
# the Linkerd proxy.
# Start the backing services in cluster2.
$ kubectl apply --context k3d-cluster2 -f luar/multicluster/emoji.yaml
$ kubectl apply --context k3d-cluster2 -f luar/multicluster/voting.yaml
# Start the web frontend and traffic generator in
# cluster1.
$ kubectl apply --context k3d-cluster1 -f luar/multicluster/web.yaml
# Check on the Pods in each cluster.
$ kubectl get pods -n emojivoto --context k3d-cluster1
$ kubectl get pods -n emojivoto --context k3d-cluster2
此时,Pod 将在每个集群中运行,但它们没有关于如何相互通信的信息。您可以简单地查看每个集群中的服务来验证这一点,如示例 12-10 所示。
示例 12-10. 检查每个集群中的服务
$ kubectl get svc -n emojivoto --context k3d-cluster1
$ kubectl get svc -n emojivoto --context k3d-cluster2
要使 emojivoto 在我们的场景中工作,我们需要在集群之间镜像服务。对于每个要导出的服务,我们将为其添加mirror.linkerd.io/exported标签:
-
如果您正在使用基于网关的多集群模式,请使用
mirror.linkerd.io/exported: true,如示例 12-11 所示。 -
如果您正在使用 Pod 对 Pod 多集群模式,请使用
mirror.linkerd.io/exported: remote-discovery,如示例 12-12 所示。
示例 12-11. 使用网关导出服务
$ kubectl --context=k3d-cluster1 label svc web-svc \
-n emojivoto mirror.linkerd.io/exported=true
$ kubectl --context=k3d-cluster2 label svc emoji-svc \
-n emojivoto mirror.linkerd.io/exported=true
$ kubectl --context=k3d-cluster2 label svc voting-svc \
-n emojivoto mirror.linkerd.io/exported=true
示例 12-12. 导出 Pod-to-Pod 服务
$ kubectl --context=k3d-cluster1 label svc web-svc \
-n emojivoto mirror.linkerd.io/exported=remote-discovery
$ kubectl --context=k3d-cluster2 label svc emoji-svc \
-n emojivoto mirror.linkerd.io/exported=remote-discovery
$ kubectl --context=k3d-cluster2 label svc voting-svc \
-n emojivoto mirror.linkerd.io/exported=remote-discovery
无论哪种情况,如果您检查 emojivoto 命名空间中的服务,如 示例 12-13 所示,您将看到镜像服务。
示例 12-13. 检查镜像服务
$ kubectl get svc -n emojivoto --context k3d-cluster1
$ kubectl get svc -n emojivoto --context k3d-cluster2
如果使用 Pod-to-Pod 多集群模式,您还可以使用 linkerd diagnostics endpoints 来检查一切是否正常工作,如 示例 12-14 所示。
示例 12-14. 检查服务端点
# Any valid Service DNS name should work here.
$ linkerd diagnostics endpoints --context k3d-cluster1 \
emoji-svc-cluster2.linkerd-multicluster.svc.cluster.local
$ linkerd diagnostics endpoints --context k3d-cluster2 \
web-svc-cluster1.linkerd-multicluster.svc.cluster.local
如 示例 12-14 所示,镜像服务显示为 *serviceName-clusterName*;例如,从 cluster2 镜像到 cluster1 的 emoji-svc 将显示为 emoji-svc-cluster2。
这是一个罕见的情况,默认情况下,应用可能必须更改以与 Linkerd 协同工作。我们在 示例 12-9 中应用的清单已经过调整,以便 emojivoto 应用程序使用镜像服务名称,但您也可以使用 HTTPRoute 和占位符服务来重定向流量。
例如,假设我们希望将所有 emoji-svc 的流量重定向到 emoji-svc-cluster2。我们可以首先创建一个名为 emoji-svc 的服务,没有 selector,这样该服务就无法匹配任何 Pod。这在 示例 12-15 中展示。
示例 12-15. 占位符 emoji-svc 服务
---
apiVersion: v1
kind: Service
metadata:
name: emoji-svc
namespace: emojivoto
spec:
type: ClusterIP
ports:
- port: 80
targetPort: http
然后,我们可以将一个 HTTPRoute 与占位符服务关联起来,以重定向所有流量,如 示例 12-16 所示。
示例 12-16. 将所有流量重定向到占位符 emoji-svc 服务
---
apiVersion: policy.linkerd.io/v1beta3
kind: HTTPRoute
metadata:
name: emoji-svc-route
namespace: emojivoto
spec:
parentRefs:
- name: emoji-svc
kind: Service
group: ""
port: 80
rules:
- backendRefs:
- name: emoji-svc-cluster2
port: 80
weight: 100
timeouts:
request: 5s
检查流量
此时,流量应该在集群之间流动,并且 emojivoto 应用程序应该工作正常,您可以通过将 Web 浏览器指向 cluster1 中的 web-svc 服务来查看,如 示例 12-17 所示。
示例 12-17. 使用浏览器查看 emojivoto
$ kubectl --context k3d-cluster1 port-forward -n emojivoto web-svc 8080:80 &
# Open a browser to http://localhost:8000/ here
您还可以观察 Linkerd Viz 仪表板中的流量流动,通过运行 示例 12-18 中显示的任一命令,或者使用 示例 12-19 中的 CLI 命令。
示例 12-18. 多集群 Linkerd Viz 仪表板
$ linkerd --context k3d-cluster1 viz dashboard
$ linkerd --context k3d-cluster2 viz dashboard
示例 12-19. 多集群 Linkerd Viz CLI
$ linkerd viz stat service -n emojivoto --context k3d-cluster1
$ linkerd viz stat service -n emojivoto --context k3d-cluster2
无论如何查看,您都应该能够看到跨集群的流量流动。
多集群环境中的策略
在结束本章之前,还有一件事要讨论。Linkerd 策略不仅适用于集群内部,还适用于集群之间。然而,正如您在图 12-1 和 12-2 中看到的那样,这两种模式的工作方式是不同的。在使用基于网关的多集群模式时,您需要在网关本身应用任何跨集群策略。它将接受类似于网格中的任何其他工作负载的策略配置。
Pod-to-Pod 多集群模式的优势在于在进行多集群请求时保留了原始工作负载的身份。这意味着您可以直接在目标工作负载上设置策略,仅允许来自需要访问它的服务的请求。
摘要
本章介绍了 Linkerd 中多集群架构的工作原理,并展示了如何在本地环境中进行设置。Linkerd 的多集群功能稳健、强大,并且被全球一些最大的组织广泛使用。请考虑多集群设置如何影响您的环境,以及它是否对您的平台是一个良好的补充。
第十三章:Linkerd CNI 与初始化容器对比
在 第二章 中,我们多次提到了初始化容器,但从未详细讨论过它。初始化容器是 Linkerd 在 Kubernetes 中处理网格网络的两种机制之一,另一种是 Linkerd CNI 插件。要理解它们的作用以及为什么选择其中之一,您需要了解网格化 Pod 启动时发生的情况。
恰巧,这比你预期的要复杂得多。我们将首先看看原生 Kubernetes,没有 Linkerd。
Kubernetes 无 Linkerd
Kubernetes 的核心目标是管理用户工作负载,以便开发人员可以集中精力于 Pod 和服务,而无需过多关注底层硬件。这是一件容易描述,使用起来相对容易,但在实施上极其复杂的事情。Kubernetes 依赖于多种不同的开源技术来完成所有这些任务。请记住,此时我们谈论的是 没有 Linkerd 的 Kubernetes 标准功能。
节点、Pod 和更多内容
Kubernetes 首先需要管理的是在集群内部执行工作负载的编排。它对此依赖于操作系统级别的隔离机制。以下是需要记住的一些关键点:
-
集群由一个或多个 节点 组成,这些节点是物理或虚拟机器,运行着 Kubernetes 本身。我们在这里讨论 Linux 节点。
-
由于节点彼此完全不同,一个节点上的所有内容都与其他节点隔离开来。
-
Pod 由一个或多个 容器 组成,并且它们在同一个节点上通过 Linux
cgroups 和namespaces 进行隔离。 -
同一 Pod 中的容器可以使用 loopback 网络进行通信。不同 Pod 中的容器需要使用非 loopback 地址,因为 Pod 之间是相互隔离的。无论 Pod 是否在同一节点上,Pod 与 Pod 之间的通信方式都是相同的。
-
一个重要的观点是 Linux 本身是在节点级别运行的:Pod 和容器不必运行单独的操作系统实例。这就是它们之间隔离如此重要的原因。
这种分层方法如 图 13-1 所示,允许 Kubernetes 在集群内部编排工作负载的分布,同时关注资源的可用性和使用情况:工作负载容器映射到 Pod,Pod 被调度到节点上,而所有节点连接到单一平面网络。

图 13-1. 集群、节点、Pod 和容器
(那么部署、副本集、守护集等呢?它们都是在提示 Kubernetes 创建的 Pod 应该被调度到哪里;底层的调度机制是相同的。)
Kubernetes 中的网络
Kubernetes 管理的另一个主要领域是网络,从基本原则开始,即每个 Pod 必须看到一个平坦透明的网络。每个 Pod 应能够与任何节点上的所有其他 Pod 通信。这意味着每个 Pod 必须有自己的 IP 地址(Pod IP)。
容器还是 Pod?
实际要求是任何两个容器必须能够互相通信,但 IP 地址是在 Pod 级别分配的——一个 Pod 内的多个容器共享同一个 IP 地址。
虽然可能让工作负载直接使用 Pod IP 与其他工作负载通信,但由于 Pod IP 的动态性,这不是个好主意:随着 Pod 的循环,它们会变化。在大多数情况下,使用 Kubernetes 服务是一个更好的选择。
服务相当复杂,正如我们在第五章中简要讨论的:
-
创建服务会触发 DNS 条目的分配,因此工作负载可以通过名称引用服务。
-
创建服务还会触发为服务分配独特的 IP 地址,与集群中的任何其他 IP 地址都不同。我们称之为服务 IP。
-
服务包括一个选择器,定义了哪些 Pod 将与该服务关联。
-
最后,服务收集其所有匹配 Pod 的 Pod IP 地址,并将它们维护为其端点。
这一切都显示在图 13-2 中——不幸的是,这仍然是对服务的一个简化视图。

图 13-2. Kubernetes 服务与寻址
当工作负载尝试连接到服务时,默认情况下 Kubernetes 将选择服务的其中一个端点并将连接路由到那里。这使 Kubernetes 能够执行连接的基本负载平衡,如图 13-3 所示:
-
Pod 内部的连接发生在本地主机上,因此它们保持在 Pod 内部。
-
到同一节点上托管的其他工作负载的连接保持在节点内部。
-
到托管在其他节点上的工作负载的连接是唯一经过网络的连接。

图 13-3. Kubernetes 基本网络路由
要使所有这些工作正常运行,Kubernetes 依赖于内置在 Linux 内核核心中的网络机制。
包过滤器的角色
Linux 内核长期以来一直包含一个强大的数据包过滤器机制,用于检查网络数据包并决定如何处理每个数据包。数据包过滤系统可以采取的可能操作包括让数据包继续原样传输、修改数据包、重新路由数据包,甚至完全丢弃数据包。
Kubernetes 充分利用包过滤器来处理集群中不断变化的一组 Pods 之间流量路由的复杂性。例如,过滤器可以拦截发送到 Service 的包,并重写为发送到特定 Pod IP。它还可以区分与发送方在同一节点上的 Pod IP 和不同节点上的 Pod IP,并适当管理路由。如果我们稍微放大一下图 13-3,我们可以得到更详细的视图在图 13-4 中。

图 13-4。Kubernetes 和包过滤器
让我们跟随图 13-4 中显示的虚线连接,从 Pod B-1 一路到 Pod C-2:
-
Pod B-1 中的应用容器连接到 Service C 的服务 IP 地址。
-
包过滤器看到从本地容器到服务 IP 的连接,因此它将该连接重定向到 Pod C-1 或 Pod C-2 的 Pod IP。默认情况下,每个新连接的选择是随机的(尽管集群网络层的确切配置可以改变这一点)。
-
在这种情况下,Pod IP 在不同的节点上,因此网络硬件参与进来,与第二个节点的网络通信。
-
在第二个节点上,包过滤器看到来自网络的连接到达 Pod IP 地址,因此它直接将连接交给 Pod,选择一个基于端口号的容器。
对于图中 Pod B-1 和 Pod A-1 之间的虚线连接,过程是相同的,只是网络硬件没有角色,因为连接完全在一个节点内。在所有情况下,容器看到的是一个简单的平面网络,所有容器都在相同的 IP 地址范围内——这当然需要 Kubernetes 在 Pod 创建和删除时持续更新包过滤器规则。
字母汤:iptables、nftables 和 eBPF
随着时间的推移,包过滤器已经有了几种实现方式,当人们谈论这个话题时,你可能会听到他们使用特定实现的名称。就目前而言,最常见的是iptables,但一个更新的nftables实现正变得越来越流行。
你也许会发现这一切让你想起了被称为 eBPF 的过滤技术,这也很合理,因为 eBPF 特别擅长这类包的神奇操作。然而,许多实现早于 eBPF,并不依赖于它。
容器网络接口
由于网络配置是 Kubernetes 的一个比较低级的方面,具体细节往往取决于使用的 Kubernetes 实现方式。容器网络接口(CNI)是一个标准,旨在提供管理动态网络配置的一致接口;例如,CNI 提供了在特定范围内分配和释放 IP 地址的机制,Kubernetes 利用这些机制来管理与服务和 Pod 关联的 IP 地址。
CNI 并不直接提供管理数据包过滤功能的机制,但它确实允许使用 CNI 插件。包括 Linkerd 在内的服务网格可以使用这些插件来实现它们需要的数据包过滤配置。
CNI 与 CNI
CNI 有许多实现方式,对于给定的 Kubernetes 解决方案,通常可以在几种不同的 CNI 实现中进行选择(例如,k3d 默认使用 Flannel 作为其网络层,但可以轻松切换到 Calico)。
Kubernetes Pod 启动过程
最终,Kubernetes 需要做以下操作来启动一个 Pod:
-
找到一个节点来托管新的 Pod。
-
运行由节点定义的任何 CNI 插件,插件必须在新 Pod 的上下文中工作。如果任何插件无法工作,则该进程失败。
-
执行为新 Pod 定义的任何 init 容器,按照它们定义的顺序进行。同样地,如果任何一个 init 容器无法工作,则启动过程失败。
-
启动由 Pod 定义的所有容器。
在 Pod 容器初始化过程中,重要的是要注意容器会按照 Pod 的 spec 中规定的顺序启动。然而,Kubernetes 不会等待一个容器启动完成再启动下一个容器,除非一个容器定义了 postStartHook。在这种情况下,Kubernetes 将启动该容器,运行 postStartHook 直到完成,然后才会继续启动下一个容器。我们将在 “容器顺序” 中详细讨论这一点。
Kubernetes 和 Linkerd
任何服务网格在启动过程中都会引入复杂性,Linkerd 也不例外。首要问题是 Linkerd 必须将其代理注入到应用 Pod 中,并且代理必须拦截进出 Pod 的网络流量。注入由一个 mutating admission 控制器管理。拦截更为复杂,Linkerd 有两种管理方式:可以使用 init 容器或 CNI 插件。
init 容器方法
Linkerd 配置网络的最直接方式是通过一个 init 容器,如 图 13-5 所示。Kubernetes 确保所有 init 容器按照 Pod 的 spec 中指定的顺序完成运行,然后再启动其他容器。这使得 init 容器成为配置数据包过滤器的理想方式。

图 13-5. 使用 init 容器进行启动
这里的缺点是 init 容器需要 NET_ADMIN 权限来执行所需的配置。在许多 Kubernetes 运行时中,这种权限可能根本不可用,因此您需要使用 Linkerd CNI 插件。
此外,某些 Kubernetes 集群中使用的操作系统可能不支持 Linkerd 默认使用的较旧的 iptables 二进制文件(这通常发生在 Red Hat 家族中)。在这些情况下,您需要设置 proxyInit.iptablesMode=nft,以指示 Linkerd 使用 iptables-nft。这不是默认设置,因为 iptables-nft 尚未普遍支持。
Linkerd CNI 插件方法
相比之下,Linkerd CNI 插件只需要在安装 Linkerd 之前安装插件即可。它不需要任何特殊的功能,并且每次 Pod 启动时 CNI 插件都会起作用,根据需要配置数据包过滤器,如 图 13-6 所示。

图 13-6. 使用 CNI 插件启动
主要的复杂性在于 CNI 最初设计是为了在创建集群之后使用而不是在创建集群时使用。因此,CNI 假设 CNI 插件的顺序完全由 Kubernetes 环境之外的人员处理。这证明并不理想,因此大多数 CNI 插件(包括 Linkerd CNI 插件)现在都编写成尝试无论集群操作者做了什么都能正常工作的方式。
在启用 Linkerd CNI 插件的情况下,Linkerd 将安装一个 DaemonSet,设计用于始终在最后运行 Linkerd CNI 插件。这允许其他插件在 Linkerd 开始拦截流量之前配置它们所需的内容。
使用 CNI 插件时,Linkerd 仍会注入一个 init 容器。如果您使用的是 stable-2.13.0 版本之前的 Linkerd 版本,则这将是一个基本不执行操作的 init 容器,正如其名称所示。从 stable-2.13.0 版本开始,init 容器将验证数据包过滤器是否正确配置。如果配置不正确,容器将失败,促使 Kubernetes 重新启动 Pod。这有助于避免启动时的竞争条件(更多详细信息请参阅下一节)。
竞争和排序
如您所见,Kubernetes 中的启动过程可能很复杂,这意味着事情可能会以几种不同的方式失败。
容器排序
如前所述,容器按照 Pod 的 spec 中的顺序启动,但 Kubernetes 在启动下一个容器之前不会等待特定容器启动(除了 init 容器)。这可能在 Linkerd 启动过程中造成麻烦:如果应用容器开始运行并尝试在 Linkerd 代理容器正常运行之前使用网络,会怎么样?
从 Linkerd 2.12 开始,Linkerd 代理容器具有postStartHook来处理此问题。当容器具有postStartHook时,Kubernetes 会先启动容器,然后完整运行postStartHook,然后再启动下一个容器。这为容器提供了一种简单的方法来确保顺序。
Linkerd 代理的postStartHook在代理实际运行之前不会完成,这迫使 Kubernetes 等待代理正常运行后才启动应用容器。如有必要,可以通过设置注解config.linkerd.io/proxy-await=disabled来禁用此功能。但我们建议除非有充分理由,否则保持启用!
CNI 插件排序
CNI 插件排序可能会造成混淆的几种方式:
守护集与其他 Pods
Kubernetes 将守护集 Pods 与任何其他 Pods 一样对待,这意味着应用 Pod 可能会在 Linkerd CNI DaemonSet 安装 Linkerd CNI 插件之前被调度!这意味着 Linkerd CNI 插件将不会为应用 Pod 运行,进而意味着应用容器将没有正常运行的 Linkerd 代理。
在 Linkerd stable-2.13.0之前,没有办法捕获这一问题,应用容器可能根本不会出现在网格中。但是,从stable-2.13.0开始,初始化容器会检查数据包过滤器是否配置正确。如果未配置正确,初始化容器将退出,从 Kubernetes 的角度看,这会导致崩溃循环,从而明显地显示出失败。
多个 CNI 插件
在大多数情况下,给定的 Kubernetes 安装将使用多个 CNI 插件。虽然 Linkerd CNI DaemonSet 竭尽全力确保 Linkerd CNI 插件最后运行,并且不会干扰其他 CNI 插件,但并非完美无缺。如果出现问题,Pod 可能(再次)永远不会出现在网格中。
配置错误的 CNI
当您首次安装 Linkerd CNI 插件时,很可能会简单地配置错误。例如,在运行 k3d 时,需要向插件提供某些路径,如果这些路径错误,插件本身将无法工作。这可能导致应用 Pods 在静默失败启动,或者在代理日志中显示“损坏消息”错误。
{ "message": "Failed to connect", "error": "received corrupt message" }
CNI 问题的唯一真正救赎在于它们通常是非常明显、显眼的错误:您将看到linkerd check失败,或者 Pods 无法启动,或类似的问题。另一方面,解决这些故障可能会很棘手,并且在很大程度上取决于具体的 CNI 插件,因此一般情况下,我们建议尽可能坚持使用初始化容器机制。
总结
Kubernetes 启动过程非常复杂,特别是涉及 Linkerd 时,但也有一些简单的建议可以帮助一切顺利进行:
-
保持 Linkerd 更新!最新版本已经添加了一些对启动非常有帮助的功能。
-
使用
proxy-await,除非你有一个非常好的理由禁用它。这将确保你的应用程序在启动之前具有一个可用的网格。 -
如果可能的话,请坚持使用初始化容器。如果不能,只需使用 CNI 插件,但如果你的集群可以运行初始化容器,那么这可能是最简单的方式。
第十四章:Linkerd 准备生产环境
一旦您部署了 Linkerd,您的下一个任务是为生产使用适当地加固您的环境。在准备过程中,熟悉您可以使用的资源非常有价值。Linkerd 用户可以访问两组基本资源:
-
社区提供的资源是免费使用的,对于每个人来说都是一个很好的信息来源。
-
Buoyant 提供的商业资源,Linkerd 的创建者
为了本书的目的,我们将避免涉及付费资源。如果您想了解更多关于 Buoyant 的商业产品信息,请访问 Buoyant 网站。
Linkerd 社区资源
Linkerd 社区活跃在 GitHub,Slack 和 CNCF 邮件列表 上。此外,在线上还有许多有用的指南和资源。
如果您希望了解有关在生产环境中运行 Linkerd 的更多信息,请查看 Buoyant 生产操作手册,该手册在 Linkerd 版本更改时进行了更新,并包含大量重要信息。
获取帮助
Linkerd 的社区支持主要由志愿社区成员在 Linkerd Slack 或直接在 GitHub 上的维护者和贡献者社区提供。重要的是,试图从开源社区获取帮助的用户理解,您有责任仔细测试对 Linkerd 所做的所有更改。在寻求 Linkerd 帮助时,您应确保清楚地阐明您面临的问题,并在可能的情况下提供重现问题的明确步骤。对于维护者或志愿社区成员来说,最困难的任务始终是理解和测试出现的任何特定问题。
负责任的披露
如果您在使用 Linkerd 过程中遇到安全问题,请您发送私人邮件至 cncf-linkerd-maintainers@lists.cncf.io,项目维护者将会确认您的报告,并在调查披露内容时提供额外信息。您可以订阅 Linkerd 漏洞通知,请访问 cncf-linkerd-announce 邮件列表。
Kubernetes 兼容性
Linkerd 已测试通过所有当前活动的 Kubernetes 版本。每个版本的发布说明包含支持的最低 Kubernetes 版本。
使用 Linkerd 进入生产环境
现在一切都准备好了,我们可以深入探讨如何使用 Linkerd 进入生产环境。
稳定版还是边缘版?
对于生产使用,您最简单的方法将是从稳定渠道运行发布,例如 Buoyant Enterprise for Linkerd。虽然在生产中运行边缘渠道版本是完全可能的。
如果你决定在生产环境中运行边缘版本,非常重要 的是要仔细阅读你考虑的每个发布版本的发布说明,并向 Linkerd 维护人员反馈你的体验。最简单的方法是通过 社区 Slack 进行反馈。在 GitHub 上的讨论或问题也是与 Linkerd 团队联系的好方式。
准备你的环境
确保你的环境为 Linkerd 准备好的第一步始终是运行 CLI 的预检查:
$ linkerd check --pre
确保你的环境已经准备好运行 Linkerd,特别是要注意 Kubernetes 权限。
除了预检之外,你还需要确保理解你的环境特定的安全需求。例如:
-
如果你不能允许你的 Pods 具有
NET_ADMIN权限,那么你将需要使用 Linkerd CNI 插件。 -
如果你正在使用 Kubernetes 的污点机制,并且已经在将运行 Linkerd 控制平面的节点上应用了自定义的污点,那么你需要为 Linkerd 的部署添加容忍(tolerations)。
-
如果你使用网络策略来隔离流量,你需要确保你的策略允许 Linkerd 控制平面和其代理之间的通信。你可能还想考虑使用 Linkerd 的策略机制来进行应用程序感知的策略执行。
除了通信之外,你还需要考虑如何将你的应用程序添加到网格中,如在 第四章 中讨论的那样。例如,你会使用命名空间注入还是工作负载注入?这些决策可能不会非常复杂,但提前考虑它们仍然是个好主意。
总结一下,通过指示 Linkerd 代理注入器在 Pod 创建时为 Pod 添加代理来将工作负载添加到网格中。可以通过在命名空间、工作负载或 Pod 级别添加注释来传递该指令:
linkerd.io/inject: enabled
对于生产使用,我们建议你在命名空间或工作负载级别添加注释。这通常是管理网格化应用程序工作负载的最简单方法,因为它不需要修改单个 Pod 清单。(虽然在某些情况下,你可能需要直接向 Pod 添加代理,如在 第六章 中讨论的那样,但这些情况并不多见。)
无论哪种情况,你都需要配置你的部署工具,在部署过程中添加适当的注释,以确保你的工作负载都被适当地网格化。这也是添加任何集群范围配置的跳过和不透明端口的异常的时机,如在 第四章 中讨论的那样。
明确启用注入
请记住,如果你在命名空间级别添加了 Linkerd 代理注入的注解,你仍然可以通过将以下注解添加到 Deployment 中的单个工作负载来覆盖注入行为:
linkerd.io/inject: disabled
配置 Linkerd 实现高可用性
如果你在生产环境中部署 Linkerd,这意味着你决定为你的生产应用程序添加关键的安全性、可观察性和可靠性功能。做得好!不幸的是,所有这些新功能都伴随着一些非常真实的成本。Linkerd 现在处于你最关键工作负载的关键运行路径。如果 Linkerd 遭遇灾难性故障,你很可能会遭受非常真实的应用程序停机,或者至少会服务质量下降。
为了减少这些风险,Linkerd 项目定义并支持高可用性(HA)模式。HA 模式修改了部署 Linkerd 的方式,如图 14-1 所示。

图 14-1. Linkerd 高可用模式
无论何时在生产环境中都要运行 HA
我们强烈建议在任何生产环境中使用 Linkerd 的 HA 模式。如果你没有显式地安装 HA 模式,你的 Linkerd 安装将存在几个单点故障,可能会导致应用程序的停机时间。
HA 模式的作用是什么?
高可用性模式对你的 Linkerd 控制平面安装进行了一些重要的更改。你可以在最新文档中找到有关 HA 模式确切变更的所有详细信息,我们强烈建议你在升级时查阅此文档,以确保使用的是最新信息。
尽管基本配置一直比较一致,但在大体上,HA 模式:
运行三个副本
在 HA 模式下,每个控制平面组件运行三个副本,而不仅仅是一个,以防止单个副本故障导致整个网格崩溃。
设置反亲和性
此外,HA 模式创建反亲和规则,防止任何单个节点运行控制平面组件的多个副本。这样可以防止单个节点故障导致整个网格崩溃。
加强资源限制
HA 模式建立了比非 HA 模式更为激进的 CPU 和内存资源请求和限制,以防止任何失控进程对整个集群造成更广泛的问题。
验证请求和限制
这些由 HA 模式设置的更为激进的限制对许多组织都能很好地发挥作用,但你应该把它们视为一个起点:它们可能不是你的组织所需的。重要的是要积极监控 Linkerd 控制平面的实际资源使用情况,并根据需要调整请求和限制。
特别是,控制平面组件如果达到其内存限制将被 OOMKilled,然后重新启动。如果这种情况不经常发生,可能会被忽略,但如果频繁发生,你可能会遭受生产事故。
使proxy-injector成为强制项
在 HA 模式下,Linkerd 控制平面的proxy-injector组件在任何 Pod 允许被调度之前必须保持健康。这反映了 Linkerd 通常负责确保应用程序内安全通信的事实。与其允许未经 Linkerd 代理的应用程序 Pod 运行,不如失败启动它们。
但要意识到,此要求是使用集群范围的入场 Webhook 强制执行的,因此它影响每个集群中的 Pod,而不仅仅是应用工作负载。这意味着您必须豁免关键的集群基础设施命名空间,例如kube-system,以免强制执行策略。
要豁免一个命名空间,请将以下标签应用于命名空间:
config.linkerd.io/admission-webhooks: disabled
基础设施无入场 Webhook!
您必须豁免基础设施命名空间免受 HA 入场 Webhook 的影响。如果不这样做,您很容易陷入僵局状态,Linkerd 正在等待系统基础设施,而系统基础设施正在等待 Linkerd。
使用 Helm 进行高可用安装
我们建议在生产环境中使用 Helm 安装 Linkerd,包括高可用安装。一个复杂因素是,与其他安装方式相比,高可用安装更有可能需要定制的 Helm 值。为了让这一过程更加简化,Linkerd Helm 图表包含一个值文件,您可以将其用作高可用安装的基础。
我们建议您在使用 Helm 部署 Linkerd HA 时始终参考最新的高可用安装说明。在撰写本文时,过程的简要概述显示在示例 14-1 中。
示例 14-1. 使用 Helm 在 HA 模式下安装 Linkerd
# Add the Linkerd stable repo
$ helm repo add linkerd https://helm.linkerd.io/stable
# Update your Helm repositories
$ helm repo update
# Pull down the latest version of the chart
$ helm fetch --untar linkerd/linkerd-control-plane
# Examine linkerd-control-plane/values-ha.yaml and edit if needed. The
# edited file should be placed in version control and, as new charts are
# released, periodically compared with the new values-ha.yaml files.
# Install the Linkerd CRDs
$ helm install linkerd-crds linkerd/linkerd-crds \
-n linkerd --create-namespace
# Install the Linkerd control plane
# Note the added reference to the values-ha.yaml file
$ helm install linkerd-control-plane \
-n linkerd \
--set-file identityTrustAnchorsPEM=ca.crt \
--set-file identity.issuer.tls.crtPEM=issuer.crt \
--set-file identity.issuer.tls.keyPEM=issuer.key \
-f linkerd-control-plane/values-ha.yaml \
linkerd/linkerd-control-plane
# Ensure your install was successful
$ linkerd check
如评论中所述,您应将您的values-ha.yaml版本控制起来。这是重要的资源,用于重新安装和灾难恢复。
使用 CLI 进行高可用安装
虽然我们通常不建议在生产环境中使用基于 CLI 的安装,但您可以使用 Linkerd CLI 输出配置了所有 HA 选项的部署 YAML,并将此 YAML 作为实际安装过程的基础。
要实现这一点,请使用linkerd install命令和--ha标志,并将生成的 YAML 保存到文件中:
$ linkerd install --ha > linkerd-ha.yaml
您可以将linkerd-ha.yaml放入版本控制,并根据需要进行编辑。
Linkerd 监控的注意事项
有一些商业供应商会自动配置 Linkerd 的监控和警报。对于那些希望自行监控 Linkerd 的用户,我们建议您建立监控程序,以确保 Linkerd 在您的环境中保持高可用性。
证书健康和到期
Linkerd 中断的最常见原因是证书过期。信任锚点和身份签发者证书必须始终有效,以避免停机时间。因此,仔细监控您的证书确保在其到期之前始终更新至关重要。
当您的根证书或发行者证书将在 60 天内过期时,linkerd check命令将开始警告您。
绝不让您的证书过期
因为 Linkerd 默认需要 Pod 之间的 mTLS 连接,它使用的证书的健康和安全对网格的正常运行至关重要,从而影响您的平台。如果证书过期或无法为新 Pod 生成,您将遭受停机时间。
这是生产 Linkerd 集群停机时间最常见的原因。理解和监控您的 Linkerd 证书至关重要。
控制平面
Linkerd 的控制平面对于您平台的正常运行至关重要。您应收集和测量控制平面的 Linkerd 代理指标,如成功率、延迟和每秒请求。对异常行为发出警报,并调查成功率低于 100%的情况。
您还应密切监视控制平面的资源消耗,并确保其永远不会接近 CPU 或内存限制。
数据平面
Linkerd 代理通常非常简单和直接易操作。尽管如此,确保代理未消耗比其应有的更多资源仍然非常重要:如果消耗过多,可能表示存在过多流量或其他问题。监视代理的资源使用情况,并确保其资源请求和限制与其处理应用程序流量所需的匹配。
确保监控您环境中 Linkerd 代理的版本是明智的。代理将使用代理注入器在创建 Pod 时定义的版本部署。如果您的 Pods 不定期重启,代理版本可能会与控制平面不同步。您需要确保您的代理始终至少与控制平面的一个主要版本保持一致。
指标收集
任何 Linkerd 的生产安装还需要考虑如何处理 Linkerd 生成的指标数据。Linkerd 代理不断收集有关其 Pod 进出的流量的有用信息,所有这些信息都以一种方式提供,任何兼容 OpenTelemetry 的工具应该能够访问它。 (Linkerd 长期以来还提供了配置 Prometheus 从 Linkerd 中抓取指标的开源配置详细信息。)
Linkerd 本身并不是监控工具(尽管 Linkerd Viz 可以消耗度量标准并显示有关它们的许多有用信息);相反,它设计用于向您环境中已有的监控解决方案提供度量标准。无论那是什么,对于负责生产 Linkerd 安装的平台工程师来说,创建一个用于收集、存储和使用 Linkerd 生成的所有度量标准的计划是最重要的长期任务之一,因为有效的长期监控对于理解应用程序的行为和健康非常有价值。
用于生产的 Linkerd Viz
Linkerd Viz 扩展消耗由 Linkerd 提供的度量标准,利用它们为 Linkerd 启用一些强大的诊断功能,并提供一个基本的开源仪表板(在图 14-2 中显示),以便更轻松地可视化一些这些度量标准。从 Linkerd 2.12 开始,从 CLI 可用的核心可见性数据——从度量标准到多集群网关的状态——需要安装 Linkerd Viz 扩展。

图 14-2。Linkerd Viz 仪表板
虽然 Linkerd Viz 并非必需用于生产使用,但我们通常建议安装它。在生产环境中运行 Viz 需要特别关注三个领域:
Prometheus 和 Linkerd Viz
安装 Linkerd Viz 时,它可以为您安装一个 Prometheus 实例。此 Prometheus 实例不建议用于生产使用,因为它使用内存数据存储,随着保存更多的度量标准数据,它会填满可用内存并崩溃,丢失所有到目前为止保存的度量标准数据。在繁忙的生产系统中,这可能每天发生多次。
因此,要在生产环境中使用 Linkerd Viz,您需要使用带有持久化存储的不同 Prometheus 实例。有关外部化 Prometheus 的完整流程在示例 10-9 中展示。您还可以查阅关于外部化 Prometheus的官方文档。
始终使用自己的 Prometheus
我们再说一遍:不要在生产中使用 Linkerd Viz 安装的 Prometheus。它只将您的度量标准存储在内存中,当其重新启动时,您将丢失所有历史数据。
第十章详细介绍了处理度量标准的正确方法。
保护 Linkerd Viz 仪表板
开源的 Linkerd Viz 仪表板提供对集群重要信息的访问,包括度量标准、Linkerd Tap 等。为了方便实验,它不包含任何身份验证机制;因此,我们不建议将此配置用于生产环境。
如果您计划在生产环境中提供 Linkerd Viz 仪表板,我们强烈建议使用您的入口控制器以及 Linkerd 授权策略来限制对其的访问。您可以在第五章中了解更多关于入口控制器的信息,以及如何确切地保护 Linkerd Viz 仪表板的详细信息可在Linkerd 文档中找到。
保护 Linkerd Tap
Linkerd Tap 允许操作员查看环境中应用程序之间请求的元数据。尽管它永远不会访问未加密的消息正文,但仍然很重要确保安全访问 Linkerd Tap,因为许多组织在其 URL 或标头中包含可能敏感的信息,不应向所有访问集群的人员公开。可以通过linkerd-linkerd-tap-admin ClusterRole 提供对 Linkerd Tap 的访问权限。
您可以在Linkerd 文档中了解更多关于保护 Tap 流量的信息,但这里最基本的操作是授予 Kubernetes 帐户访问 Linkerd Tap 的权限。可以通过示例 14-2 中显示的角色绑定来完成。
示例 14-2. 访问 Linkerd Tap
$ export USER=<target_username>
$ kubectl create clusterrolebinding \
"${USER}"-tap-admin \
--clusterrole=linkerd-linkerd-viz-tap-admin \
--user="${USER}"
访问 Linkerd 日志
Linkerd 控制平面和 Linkerd 代理均会发出日志信息,可通过kubectl logs访问,这些信息对于排除任何活动事件或调查异常行为可能非常有价值。每个发出的日志消息都有相关的日志级别:
错误
指示必须解决以继续操作网格的 Linkerd 存在严重问题的消息
警告
指示应解决但不会阻止网格正常运行的问题的消息
信息
信息性消息
调试
仅用于调试的消息,通常需要了解 Linkerd 才能解释
默认情况下,Linkerd 组件配置为以INFO级别及更高级别发出消息。必要时,您可以覆盖此配置以便 Linkerd 也发出DEBUG消息。(不建议关闭INFO级别的消息。)切换日志级别需要重新启动控制平面,尽管 Linkerd 代理可以在运行时更改其日志级别。
您应仅在积极排除故障时将 Linkerd 切换为发出DEBUG级别的日志消息;发出DEBUG级别的日志对 Linkerd 本身有实际的性能影响,并且额外的日志量可能会快速压倒日志聚合器。
在此期间,当监视您的 Linkerd 环境时,监视 Linkerd 组件的日志级别是值得的,以确保它们没有错误地继续发出DEBUG日志。
升级 Linkerd
Linkerd 的设计旨在安全运行和升级。在同一个主要版本内进行升级通常是安全的,并且如果配置了高可用模式,则可以放心地执行升级而不会丢失任何功能。尽管如此,在所有情况下,都应该在非生产环境中测试您的升级和升级过程,然后再转移到生产环境。
当使用稳定通道的发布时,请记住,与次要版本升级不同,主要版本升级可能包含破坏性变更。例如,Linkerd 2.10、2.11 和 2.12 都包含了对 Linkerd 操作的显著更改,需要许多用户更改其部署策略或仔细测试其应用程序的行为。作为平台运营商,您有责任在部署之前仔细阅读新主要版本的发布说明,并测试您的升级过程,然后再转移到生产环境。
永远不要跳过主要版本
在进行升级时,绝对不要跳过主要版本;例如,从 2.12.5 升级到 2.14.3 不受支持。升级仅在单个主要版本内进行测试;尝试跳过可能会导致未知问题并且很可能会导致停机时间。
请务必始终在升级之前阅读特定版本的升级说明;例如,Linkerd 2.12 添加了一个新步骤到流程中。当使用边缘通道的发布时,这一点尤为重要!您可以在Linkerd 升级文档中找到最新的说明。
与 Linkerd 安装一样,该项目支持两种主要的升级路径。
通过 Helm 进行升级
使用 Helm 是生产安装和升级的推荐方法。
阅读说明书
在开始升级之前,请务必阅读Linkerd 升级说明!
下面是具体的过程:
-
确保控制平面本身健康,并且 Linkerd 正常运行:
$ linkerd check -
如果
linkerd check显示任何问题,请在继续之前解决它们。在控制平面运行不正确时尝试升级可能会导致重大问题。 -
一旦确认控制平面运行正常,请更新您的 Helm 仓库:
$ helm repo update -
接下来,更新 Linkerd Helm 图表。请注意,从 Linkerd 2.12 开始,有两个独立的 Helm 图表,您需要对两者都运行升级操作。
首先,升级 Linkerd CRD:
$ helm upgrade linkerd-crds -n linkerd linkerd/linkerd-crds完成后,请升级控制平面本身:
$ helm upgrade linkerd-control-plane \ -n linkerd linkerd/linkerd-control-plane -
再次确保控制平面健康:
$ linkerd check
通过 CLI 进行升级
Linkerd 的 CLI 提供了一个 upgrade 命令,将输出 YAML,可直接应用于您的 Kubernetes 集群以升级 Linkerd 控制平面。虽然我们通常建议使用 Helm 来升级 Linkerd,但 Linkerd CLI 可能更适合某些工作流程。
阅读说明书
在开始升级之前,请务必阅读Linkerd 升级说明!
通过 CLI 进行升级的基本过程是:
-
确保控制平面本身健康运行,确保 Linkerd 运行正常:
$ linkerd check -
如果
linkerd check显示任何问题,请在继续之前解决它们。当控制平面功能不正常时进行升级可能会导致严重问题。 -
通过安装最新版本的 Linkerd CLI 来启动升级过程本身。这使得 CLI 可以获取各种 Linkerd 安装资源的最新版本:
$ curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install | sh确认你正在运行最新版本:
$ linkerd version --client -
升级运行在集群中的控制平面:
$ linkerd upgrade \ | kubectl apply --prune -l linkerd.io/control-plane-ns=linkerd -f -使用
--prune标志确保从集群中删除不再需要的资源。认真,读取说明!
作为阅读说明书重要性的例证,升级到 Linkerd 2.12 需要将更复杂的修剪指令传递给
kubectl apply!升级说明可能会随每个主要版本而变化,这就是为什么你应该在升级之前始终阅读最新文档。
-
再次确保控制平面正常运行:
$ linkerd check
就绪检查清单
一个小型演示环境与一个重要的生产环境有很多差异,正如我们刚刚提到的!以下检查清单涵盖了在将 Linkerd 投入生产时需要考虑的一些最重要的事项:
-
我已经使用我的安装凭据运行了 Linkerd 的预检查。
-
我正在将 Linkerd 镜像镜像到我的内部注册表中。
-
我确信我的集群有能力以高可用模式运行 Linkerd 的控制平面。
-
我有计划以高可用模式运行 Linkerd。
-
我已为 Linkerd 创建了自己的证书。
-
我有计划安全地存储和轮换这些证书。
-
我已经创建了监控,以确保在证书过期之前我会收到通知。
-
-
我已确定我环境中使用的各种非 HTTP 工作负载。
- 我知道哪些工作负载已经加入了网格,哪些没有。
-
我已注释
kube-system命名空间,以确保它在没有代理注入器可用时能够正常运行。 -
我已确保
linkerd命名空间不会配置为自动注入。 -
我已确保
kube-system命名空间不会配置为自动注入。 -
我知道还有其他命名空间需要确保不被代理注入。
- 我已将它们从注入器故障策略中豁免。
-
我有计划为我的工作负载添加适当的注释。
-
我有一个收集和存储 Linkerd 指标的计划。
-
我已经设置了工具,以确保 Linkerd 健康,并且如果有问题我会收到通知。
如果你能够勾选大部分或全部这些项目,那么你就能够自信地在生产环境中运行 Linkerd。
总结
在本章中,我们涵盖了许多与在生产环境中运行 Linkerd 相关的核心任务和关注点。没有两个组织具有相同的操作限制和要求,因此,与技术的其他方面一样,您应该准备好根据您的实际情况调整这些建议。如果您有特定的操作上的顾虑,或者需要帮助运行 Linkerd,我们建议您与 Linkerd 社区联系,或者建立与提供 Linkerd 支持或管理的供应商之间的商业关系。
第十五章:调试 Linkerd
如果您已经走到这一步,您就了解了 Linkerd 作为一个平台提供的有价值工具。它保护您的应用程序,为这些应用程序提供强大的洞察力,并通过使您的连接更可靠来解决基础应用程序和网络问题。在本章中,我们将探讨在需要排除 Linkerd 自身问题时应该做什么。
在所有情况下,诊断 Linkerd 问题的第一步是使用 Linkerd CLI 的内置健康检查工具运行linkerd check,正如我们在第六章中详细讨论的那样。 linkerd check是一种快速定位 Linkerd 安装中许多常见问题的方法;例如,它可以立即诊断到期的证书,这是实际中导致 Linkerd 停机最常见的问题。
诊断数据平面问题
Linkerd 因不需要大量手动处理数据平面而颇有名气;然而,掌握一些基本的故障排除仍然很有用。许多代理问题最终涉及到相似的解决方案集。
“常见”的 Linkerd 数据平面故障
虽然 Linkerd 通常是一个容错性强的服务网格,但我们看到有几种情况比其他情况更常见。知道如何应对这些情况可能非常有帮助。
Pod 启动失败
如果遇到注入 Pod 无法启动的情况,第一步将是确定故障发生的确切位置。这就是kubectl describe pod和kubectl logs命令能提供大量有用信息的地方。
通常最有用的方法是从描述失败的 Pod 开始,了解 Kubernetes 认为发生了什么。例如,Linkerd 初始化容器失败了吗?Pod 是否因其探针未能报告为准备好而被杀死?这些信息可以帮助您决定需要从哪些容器中获取日志,如果需要的话。
如果失败的容器属于应用程序而不是 Linkerd,则最好查看非 Linkerd 特定的潜在原因,如共同依赖项失败,或者节点资源不足。不过,如果是 Linkerd 容器失败,第二步就是了解故障是否影响所有新的 Pod 或仅影响某些新的 Pod:
单个 Pod 失败
如果单个 Pod 失败,您需要查看该 Pod 的特征。哪些容器无法启动?是代理、初始化容器还是应用本身?如果其他 Pod 正常启动,则这不是一个系统性问题,您需要深入研究特定 Pod 的细节。
所有 Pod 都无法启动
如果所有新的 Pod 都无法启动,那么您可能遇到了更严重的系统性问题。正如前面提到的,导致所有 Pod 无法启动的最常见原因是证书问题。linkerd check命令将立即显示这类问题,因此我们建议首先运行它。
另一个可能的——尽管不常见的——问题是 Linkerd 代理注入器未运行或不健康。请注意,在高可用模式下运行 Linkerd 时,我们推荐用于生产环境(如第十四章所讨论的),Kubernetes 将拒绝启动任何新的应用程序 Pod,直到注入器健康为止。
一部分 Pod 未能启动
如果只有一些新的 Pod 未能启动,那么现在是开始隔离那些 Pod 上存在的共同因素的时候了:
-
查看
kubectl get pods -o wide的输出,看它们是否已安排在同一节点或多个节点上。 -
查看
kubectl describe pod的输出:初始化容器是否未能成功完成? -
如果您正在使用 Linkerd CNI 插件,您需要检查网络验证器的状态,并可能需要重新启动该节点上的 CNI 容器。
-
如果您没有使用 Linkerd CNI 插件,请查看 Linkerd 初始化容器的
kubectl logs输出。如果未能成功完成,请尝试查看其运行所在节点的唯一之处。
间歇性代理错误
间歇性代理错误可能是最难解决的问题之一。代理的任何间歇性问题都必须难以捕捉和解决。在生产环境中运行 Linkerd 时,建议构建监控以捕获包括以下错误:
权限被拒绝的事件
这些可能代表您平台中的危险配置错误或对环境的真正威胁。您需要收集并分析 Linkerd 代理的日志以便检测这些事件。
协议检测超时
讨论了协议检测,见第四章,这是 Linkerd 在自动识别两个 Pod 之间的流量之前执行的过程。这是一个重要的步骤,在代理可以开始发送和接收流量之前发生。偶尔情况下,协议检测可能会超时,然后回退到将连接视为标准的 TCP 连接。这意味着代理将引入不必要的 10 秒延迟,然后连接将无法从 Linkerd 的请求级路由等功能中受益。
协议检测超时事件通常表明应将某个端口标记为跳过或不透明(再次见第四章)。特别是,代理永远无法正确执行服务器优先协议的协议检测。如果您使用 Linkerd 的策略资源,您可以在给定端口上声明协议。这使您可以完全跳过协议检测,并将提高应用程序的总体可用性和安全性。
代理也可能因过载而无法处理协议检测。
内存不足事件
对于代理的内存不足事件代表着资源分配问题。重要的是要监控、测试和管理 Linkerd 代理的资源限制。它拦截和管理流入和流出应用程序的流量,管理其资源是平台团队的核心职责。
确保为代理提供处理流经其的流量所需的资源,否则你将会度过一个糟糕的一天。
HTTP 错误
一般来说,Linkerd 会重用 Pod 之间的持久 TCP 连接,并显示发生的任何应用程序级错误,因此如果您的应用程序有任何底层配置问题,应该能看到应用程序实际生成的错误。
但是,有时候 Linkerd 代理会直接对请求作出 502、503 或 504 响应,了解导致这些响应的原因非常重要:
502 错误
当新的服务网格用户开始将应用程序添加到服务网格时,看到 502 错误的频率上升并不罕见。这是因为每当 Linkerd 发现代理之间的连接错误时,它将显示为 502 错误。如果您在环境中看到大量的 502 错误,请参考Linkerd 文档,了解更多可以采取的故障排除步骤。
503 和 504
当 Linkerd 代理发现请求超出工作负载的能力时,会显示 503 和 504 错误。
在正常操作中,Linkerd 代理会维护一个请求调度队列。传入的请求几乎不会在队列中停留:它被排队,代理选择一个可用的端点来处理请求,然后立即调度它。
然而,假设有大量的传入请求涌入,超过工作负载的处理能力。当队列变得过长时,Linkerd 开始负载剪裁,任何新到达的请求会直接从代理服务器收到一个 503 错误。要停止负载剪裁,传入请求的速率需要足够慢,以便队列中的请求得以调度,从而允许队列收缩。
此外,端点池是动态的:例如,断路器可以将给定的端点从池中移除。如果池变为空(即没有可用的端点),则队列进入一种称为快速失败的状态。在快速失败中,队列中的所有请求立即收到 504 响应,然后 Linkerd 转而进行负载剪裁,因此新的请求再次收到 503 响应。
要摆脱快速失败,某个后端必须再次变得可用。如果由于断路器将所有后端标记为不可用而导致快速失败,那么您最有可能的方式是断路器将允许探测请求通过,它将成功,然后后端将再次标记为可用。在那时,Linkerd 可以将工作负载从快速失败状态中恢复,并且事情将重新开始正常处理。
503 不等同于 504!
请注意那里的不同响应! 504s 仅在负载均衡器进入快速失败时发生,而 503s 指示负载过重——这可能是由于快速失败,也可能是由于流量过大。
设置代理日志级别
在正常操作中,Linkerd 代理不会记录调试信息。如果需要,您可以更改 Linkerd 代理的日志级别,而无需重新启动代理。
调试日志可能代价高昂
如果设置过于详细的日志级别,代理将消耗更多资源,性能会降低。除非必要,否则不要修改代理的日志级别,并确保在不进行活动调试时重置日志级别。
当您需要开始调试时,可以按照示例 15-1 所示打开调试级别的日志记录。
示例 15-1。打开调试日志
# Be sure to replace $POD_NAME with the name of the Pod in question.
$ kubectl port-forward $POD_NAME linkerd-admin
$ curl -v --data 'linkerd=debug' -X PUT localhost:4191/proxy-log-level
调试结束后,请务必关闭调试级别的日志记录,如示例 15-2 所示。不要让代理长时间运行调试级别的日志记录:这将影响性能。
示例 15-2。关闭调试日志
# Be sure to replace $POD_NAME with the name of the Pod in question.
$ kubectl port-forward $POD_NAME linkerd-admin
$ curl -v --data 'warn,linkerd2_proxy=info' -X PUT localhost:4191/proxy-log-level
日志级别在“访问 Linkerd 日志”中有讨论;您可以在官方 Linkerd 文档中找到有关配置 Linkerd 代理日志级别的更多信息。
调试 Linkerd 控制平面
Linkerd 的控制平面分为核心控制平面及其扩展(如 Linkerd Viz 和 Linkerd Multicluster)。由于控制平面的每个组件都使用 Linkerd 代理进行通信,您可以利用 Linkerd 的可观察性来进行调试。
Linkerd 控制平面和可用性
控制平面是所有网格操作的一部分,因此其健康状况至关重要。正如本章开头提到的那样,获取控制平面健康状况的最快方式——除了支付托管服务之外——是运行linkerd check。
linkerd check将执行一系列详细测试,并验证是否存在已知的配置错误。如果存在问题,linkerd check将指导您查看有关如何解决问题的文档。
总是从 linkerd check 开始
很难过分强调linkerd check的实用性。每当您发现您的网格出现异常情况时,始终从linkerd check开始。
也要注意linkerd check在运行其测试时的顺序是经过深思熟虑的:它首先运行核心控制平面的所有测试,然后按顺序进行每个扩展。在每个部分内部,linkerd check通常会按顺序执行其测试,每个测试在能够运行之前都必须通过。如果测试失败,其所在的部分及其位置本身就可以为您提供关于从哪里开始调试的大量信息。
核心控制平面
核心控制平面控制 Pod 创建、证书管理以及 Pod 之间的流量路由。正如在第二章中讨论的那样,核心控制平面由三个主要组件组成:代理注入器、目的地控制器和身份控制器。现在我们将深入讨论这些组件的故障模式及其应对方法。
在生产中使用 HA 模式
在生产中运行的任何 Linkerd 实例都必须使用高可用性模式,绝不能例外!没有 HA 模式,控制平面中的单个故障可能会使整个 mesh 处于风险之中,这在生产中是不可接受的。您可以在第十四章中详细了解 HA 模式。
身份控制器
身份控制器的任何故障都将影响工作负载证书的发放和更新。如果 Pod 在启动时无法获取证书,代理将无法启动,并会记录相关消息。另一方面,如果代理的证书过期,它将开始记录未能更新的日志消息,并且将无法连接到任何新的 Pod。
截至目前,我们只熟悉身份控制器的两种已知故障模式:
过期证书
安装 Linkerd 时,您需要为控制平面提供信任锚证书和身份发行者证书,如第七章中详细讨论的那样。如果身份发行者证书过期,身份控制器将无法继续发出新证书,这将导致整个 mesh 停机,因为各个代理无法再建立安全连接。
永远不要让您的证书过期
过期证书将导致整个 mesh 停滞不前,并且它们是导致 Linkerd 生产中断的最常见原因。您必须定期监控证书的健康状态。
身份控制器超载
每次创建新的 meshed Pod 时都会访问 Linkerd 的身份控制器。因此,通过创建大量 Pod 可能(虽然困难)会使身份控制器不堪重负。这将表现为 Pod 创建的长时间延迟,以及身份控制器日志中关于证书创建的大量消息。
处理超载身份控制器的最简单方法是进行水平扩展:向linkerd命名空间中的linkerd-identity部署添加更多副本。您可能还希望考虑允许其请求更多 CPU 资源。
目的地控制器
Linkerd 的目的地控制器负责为各个代理提供其路由信息,并提供环境有效策略的详细信息。它对 mesh 的正常运行至关重要,任何退化都应立即视为需要紧急处理的关键问题。这里有两个主要注意事项:
内存
目标控制器的内存使用量随着集群中端点数量的线性增长而增加。对于大多数集群而言,这导致目标控制器随时间消耗的内存量相对稳定。这意味着如果目标控制器被 Kubernetes 的 OOMKilled,它非常可能会达到其内存限制,并在每次重新启动时再次被 OOMKilled。因此,积极监视和管理目标控制器上的内存限制非常重要。
代理缓存
Linkerd 代理会维护一个端点的缓存,以便在目标控制器不可用时由代理重复使用。默认情况下,代理将缓存其端点列表 5 秒,并在任何给定的 5 秒间隔内重复使用该缓存。您可以通过 linkerd-control-plane Helm 图表的 outboundDiscoveryCacheUnusedTimeout 属性来配置该超时时间。增加超时时间将增加您在目标控制器故障时的整体弹性,特别是对于流量较少的服务而言。
代理注入器
Linkerd 的代理注入器是对 Pod 创建事件做出响应的突变 Webhook。在核心控制平面的所有元素中,代理注入器最不可能出现问题,但是积极监视其健康状态是非常重要的:
-
在 HA 模式下,如果代理注入器崩溃,则新的 Pod 将不被允许启动,直到代理注入器恢复在线状态。
-
在非 HA 模式下,如果代理注入器崩溃,则新的 Pod 不会被注入到网格中,直到其恢复在线状态。
在生产环境中使用 HA 模式
从上述描述中很明显,为什么在生产环境中使用高可用模式是如此重要。您可以在 第十四章 中了解更多关于 HA 模式的信息。
Linkerd 扩展
没有 Linkerd 扩展程序位于网格操作的关键路径上,但许多 Linkerd 用户特别使用 Linkerd Viz 扩展程序来收集有关其服务操作的额外细节。如果您发现从 Linkerd Viz 看到异常行为,linkerd check 几乎总是能显示出故障是什么,通常最佳的修复方法是升级或重新安装扩展程序。
这种解决 Linkerd Viz 故障的“重启”方法的一个例外是,如果您正在使用其内置的 Prometheus 实例。正如我们在 第十四章 和其他地方讨论的那样,内置的 Prometheus 实例只在内存中存储指标数据。这意味着随着您添加更多指标数据,它将周期性地被 Kubernetes 的 OOMKilled,您将丢失它存储的任何数据。这是内置 Prometheus 的已知限制。
在生产环境中不要使用内置的 Prometheus
从前面的描述中清楚地可以看出,在生产环境中绝不能使用内置的 Prometheus 实例。您可以在 第十四章 中了解更多相关信息。
摘要
一般来说,Linkerd 是一个表现良好、容错性强的网格(mesh)——特别是在高可用模式下。然而,像所有软件一样,出现问题时需要注意。阅读本章后,你应该知道在出现问题时从何处开始查找。




浙公网安备 33010602011771号