原文:
zh.annas-archive.org/md5/be21eeebe72acd4358284a68e1289aea
译者:飞龙
第二十一章:多集群应用部署
本书共有二十章,应该明白 Kubernetes 可以是一个复杂的主题,当然,希望如果您已经读到这里,它比起初要清晰些。考虑到在单个 Kubernetes 集群中构建和运行应用程序的复杂性,为什么要增加设计和部署应用程序到多个集群的复杂性呢?
事实上,现实世界的需求意味着大多数应用程序需要进行多集群应用部署。这有许多原因,很可能您的应用程序至少符合其中一个要求。
第一个要求是冗余和弹性的需求。无论是在云端还是本地,单个数据中心通常是一个单一故障域。无论是猎人使用光纤电缆作为靶子的练习,还是冰风暴导致的停电,或者仅仅是软件发布的失败,部署到单个位置的任何应用程序都可能完全失败,使用户无法获得任何补救措施。在许多情况下,单个 Kubernetes 集群都与单个位置绑定,因此是一个单一故障域。
在某些情况下,特别是在云环境中,Kubernetes 集群被设计为区域性。区域性集群跨越多个独立区域,因此对于之前描述的基础设施问题具有弹性。因此很容易认为这样的区域性集群足以保证弹性,除了 Kubernetes 本身可能成为单点故障的事实。任何单个 Kubernetes 集群都与特定版本的 Kubernetes 绑定(例如,1.21.3),升级集群可能会导致应用程序出现问题。有时 Kubernetes 会弃用 API 或更改这些 API 的行为。这些变更不频繁,Kubernetes 社区会提前进行沟通确保变更顺利进行。此外,尽管经过大量测试,但偶尔仍会在发布版本中引入错误。尽管任何一个问题影响您的应用程序的可能性不大,但在大多数应用程序的生命周期(几年)内,您的应用程序很可能会受到某种程度的影响。对于大多数应用程序来说,这是不可接受的风险。
除了弹性要求外,多集群部署的另一个强大驱动因素是对区域关联的业务或应用需求。例如,游戏服务器需要靠近玩家以减少网络延迟并提高游戏体验。其他应用可能受到法律或监管要求的限制,要求数据位于特定的地理区域内。由于任何 Kubernetes 集群都与特定位置绑定,这些应用部署到特定地理位置的需求意味着应用必须跨越多个集群。
最后,尽管在单个集群中有多种隔离用户的方法(例如命名空间、RBAC、节点池——为不同能力或工作负载组织的 Kubernetes 节点集合),但 Kubernetes 集群仍然基本上是一个单一的合作空间。对于一些团队和产品来说,不同团队甚至会因为意外影响他们的应用的风险不值得,他们宁愿承担管理多个集群的复杂性。
到了这一点,你可以看到,无论你的应用程序如何,很可能现在或在不久的将来,你的应用程序都需要跨多个集群。本章的其余部分将帮助你理解如何实现这一点。
在你开始之前
在考虑迁移到多集群部署之前,确保单个集群部署具备正确的基础架构至关重要。每个人对他们的设置都有一份必须做的清单,但是这些捷径和问题在多集群部署中被放大。同样地,在拥有 10 个集群时,修复基础设施中的问题会变得十分困难。此外,如果增加额外的集群需要付出显著的额外工作,你可能会抗拒增加额外的集群,尽管(因为前面提到的种种原因)这对你的应用来说是正确的做法。
当我们说“基础架构”时,我们指的是什么?正确的自动化是最重要的部分。重要的是,这不仅包括部署应用程序的自动化,还包括创建和管理集群本身的自动化。当你只有一个集群时,它从定义上来说是一致的。然而,当你增加集群时,你增加了集群中各个组件版本不一致的可能性。你可能会拥有不同版本的 Kubernetes、不同版本的监控和日志代理,甚至是容器运行时的基本差异。所有这些变化都会使你的生活更加困难。基础设施的差异使得你的系统变得“更加奇怪”。在一个集群中获得的知识并不能转移到其他集群,有时因为这种差异性,问题似乎会在某些地方随机发生。保持稳定基础的一个最重要的部分就是确保所有集群的一致性。
实现这种一致性的唯一途径是自动化。你可能会想,“我总是以这种方式创建集群”,但经验告诉我们,这并不正确。下一章将详细讨论基础设施即代码对于管理你的应用程序的价值,但同样适用于管理你的集群。不要使用 GUI 或 CLI 工具来创建你的集群。起初通过源代码控制和 CI/CD 推送所有更改可能看起来很麻烦,但稳定的基础会带来显著的回报。
部署到您的集群中的基础组件也是如此。这些组件包括监控、日志记录和安全扫描器,在部署任何应用程序之前都必须存在。这些工具还需要使用 Helm 等基础设施即代码工具进行管理,并使用自动化进行部署。
超越集群形状的内容,还有其他必要的一致性方面。首先是为所有集群使用单一身份系统。虽然 Kubernetes 支持简单的基于证书的身份验证,但我们强烈建议使用与全球身份提供者(如 Azure Active Directory 或任何其他支持 OpenID Connect 的身份提供者)集成。确保每个人访问所有集群时都使用相同的身份是维护安全最佳实践并避免危险行为(如共享证书)的关键部分。此外,大多数这些身份提供者还提供额外的安全控制,如双因素身份验证,可以增强集群的安全性。
就像身份验证一样,确保集群的一致访问控制也非常关键。在大多数云平台中,这意味着使用基于云的 RBAC,在这种情况下,RBAC 角色和绑定存储在中央云位置,而不是在集群本身。在单一位置定义 RBAC 可以防止诸如在某个集群中留下权限或未能向某个单一集群添加权限等错误。不幸的是,如果要为本地集群定义 RBAC,则情况比身份验证要复杂得多。有一些解决方案(例如,用于 Kubernetes 的 Azure Arc)可以为本地集群提供 RBAC,但如果您的环境中没有此类服务,则在源代码控制中定义 RBAC 并使用基础设施即代码将规则应用于所有集群可以确保跨整个部署中应用一致的特权。
同样地,当您考虑为集群定义策略时,定义这些策略并在单一位置上具有用于查看所有集群符合状态的单一仪表板非常关键。与 RBAC 一样,此类全局服务通常通过您的云提供商提供,但对于本地部署,选项有限。同样可以使用基础设施即代码工具来帮助填补此差距,并确保您可以在单一位置定义您的策略。
就像设置正确的单元测试和构建基础设施对应用程序开发至关重要一样,为管理多个 Kubernetes 集群设置正确的基础设施为在广泛的基础设施群中稳定部署应用程序奠定了基础。在接下来的部分中,我们将讨论如何构建您的应用程序以在多集群环境中成功运行。
从顶部开始使用负载平衡方法
一旦开始考虑将应用部署到多个位置,就变得必要考虑用户如何访问它。通常通过域名来实现这一点(例如,my.company.com)。虽然我们将花费大量时间讨论如何构建你的应用程序以在多个位置运行,但更重要的起点是如何实施访问。这是因为显然使人们能够使用你的应用程序是至关重要的,但也因为设计人们如何访问你的应用程序可以提高你在意外负载或故障情况下快速响应和重新路由流量的能力。
访问你的应用程序始于一个域名。这意味着你的多集群负载均衡策略的开始是一个 DNS 查询。这个 DNS 查询是你负载均衡策略的第一个选择。在许多传统的负载均衡方法中,这个 DNS 查询被用于将流量路由到特定的位置。这通常被称为“GeoDNS”。在 GeoDNS 中,DNS 查询返回的 IP 地址与客户端的物理位置相关联。IP 地址通常是离客户端最近的区域集群。
虽然 GeoDNS 在许多应用中仍然流行,并且对于本地应用可能是唯一可行的方法,但它有一些缺点。第一个缺点是 DNS 在互联网的各个地方都有缓存,虽然你可以设置 DNS 查询的生存时间(TTL),但在追求更高性能时,许多地方会忽略这个 TTL。在稳定状态下运行时,这种缓存并不是什么大问题,因为 DNS 通常是相当稳定的,无论 TTL 是多少。然而,当你需要将流量从一个集群移动到另一个集群时,比如响应特定数据中心的故障时,这就成为一个非常大的问题。在这种紧急情况下,DNS 查询被缓存的事实可能会显著延长停机的持续时间和影响。此外,由于 GeoDNS 根据客户端的 IP 地址猜测你的物理位置,当许多不同的客户端从同一防火墙 IP 地址出口其流量时,尽管它们位于许多不同的地理位置,GeoDNS 经常会感到困惑并猜测错误的位置。
用于选择你的集群的另一种替代方法是一种称为任播的负载均衡技术。使用任播网络,单个静态 IP 地址通过核心路由协议从互联网的多个位置进行广告。传统上我们认为 IP 地址映射到单个机器,但是使用任播网络,IP 地址实际上是一个虚拟 IP 地址,根据你的网络位置被路由到不同的位置。你的流量根据网络性能的距离而不是地理距离被路由到“最近”的位置。任播网络通常能产生更好的结果,但并不总是在所有环境中都可用。
在设计负载平衡时的最后一个考虑因素是负载平衡是在 TCP 层还是 HTTP 层进行的。到目前为止,我们只讨论了 TCP 级别的负载平衡,但对于基于 Web 的应用程序来说,在 HTTP 层进行负载平衡有显著的好处。如果您正在编写基于 HTTP 的应用程序(大多数应用程序都是这样的),那么使用全局 HTTP 感知负载均衡器使您能够了解更多客户端通信的细节。例如,您可以根据浏览器中设置的 cookie 做出负载平衡决策。此外,了解协议的负载均衡器可以做出更智能的路由决策,因为它看到每个 HTTP 请求,而不仅仅是通过 TCP 连接的字节流。
无论您选择哪种方法,最终您的服务位置都将从全局 DNS 端点映射到代表服务入口点的一组区域 IP 地址。这些 IP 地址通常是您在本书前几章中了解到的 Kubernetes 服务或入口资源的 IP 地址。一旦用户流量到达该端点,它将根据您的应用程序设计流经您的集群。
构建多集群应用程序
一旦您解决了负载平衡问题,设计多集群应用程序的下一个挑战是考虑状态。理想情况下,您的应用程序不需要状态,或者所有状态都是只读的。在这种情况下,您几乎不需要做任何事情来支持多个集群部署。您的应用程序可以单独部署到每个集群中,顶部添加一个负载均衡器,您的多集群部署就完成了。不幸的是,对于大多数应用程序来说,存在必须以一致方式在应用程序副本之间管理的状态。如果您没有正确处理状态,您的用户将得到一个令人困惑且有缺陷的体验。
要理解复制状态如何影响用户体验,让我们以一个简单的零售商店为例。很明显,如果您只在多个集群中的一个中存储客户订单,当他们的请求移到不同的区域时,客户可能会无法看到他们的订单,无论是因为负载平衡还是因为他们实际上移动了地理位置。因此,用户的状态需要在各个区域之间复制。复制方法也可能影响客户体验,尽管这可能不那么明显。复制数据和客户体验的挑战被这个问题简洁地概括为:“我能读取我自己的写入吗?”看起来答案显而易见应该是“是的”,但实现这一点比看起来更难。例如,考虑一个在他们的电脑上下订单,然后立即在他们的手机上查看订单的客户。他们可能从完全不同的网络访问您的应用程序,因此可能会登陆到完全不同的集群上。用户关于他们能否查看刚刚下的订单的期望是一致性的一个例子。
一致性决定了您如何考虑复制数据。我们假设我们希望我们的数据是一致的;也就是说,无论我们从哪里读取数据,我们都能读取到相同的数据。但是,时间是一个复杂的因素:我们的数据必须多快才能保持一致?当数据不一致时,我们会得到任何错误指示吗?一致性有两种基本模型:强一致性保证了写操作直到成功复制之前都不会成功,而最终一致性则是写操作总是立即成功,并且只有在以后的某个时间点才保证成功复制。一些系统还允许客户端根据请求选择它们的一致性需求。例如,Azure Cosmos DB 实现了有界一致性,在这种最终一致性系统中对过期数据的一些保证。Google Cloud Spanner 使客户端能够指定他们愿意容忍旧数据读取,以换取更好的性能。
看起来每个人都会选择强一致性,因为它显然更容易理解,因为数据在任何地方都是相同的。但是强一致性是有代价的。在写入时保证复制需要更多的工作,当无法复制时,许多写入将失败。强一致性更昂贵,相对于最终一致性可以支持更少的并发事务。最终一致性更便宜,并且可以支持更高的写入负载,但对于应用程序开发者来说更为复杂,可能会向最终用户暴露一些边缘条件。许多存储系统仅支持单一并发模型。那些支持多个并发模型的存储系统要求在创建存储系统时指定。您选择的并发模型对应用程序的设计有重大影响,并且难以更改。因此,在为多个环境设计应用程序之前,选择一致性模型是一个重要的第一步。
注意
部署和管理复制的有状态存储是一项复杂的任务,需要一个专门的团队具有领域专业知识来设置、维护和监控。您应该强烈考虑使用基于云的存储来进行复制数据存储,这样负担将由云提供商庞大的团队深度承担,而不是您自己的团队。在本地环境中,您还可以将存储的支持转移到专注于运行您选择的存储解决方案的公司。只有在大规模时,投资于构建自己的团队来管理存储才有意义。
一旦确定了存储层,下一步是构建应用程序设计。
复制的孤立体:最简单的跨区域模型
将您的应用程序简单复制到多个集群和多个区域的最简单方法只是将您的应用程序复制到每个区域。您的应用程序的每个实例都是一个完全相同的克隆,无论在哪个集群中运行,它看起来完全相同。由于顶部有一个负载均衡器分布客户请求,并且您已经在需要状态的地方实现了数据复制,您的应用程序不需要太多更改来支持这种模型。根据您选择的数据一致性模型,您需要处理的事实是数据在区域之间可能不会被快速复制,但是,特别是如果您选择强一致性,这不需要进行主要的应用程序重构。
当您以这种方式设计应用程序时,每个区域都是其自身的孤立体。它需要的所有数据都存在于该区域内,一旦请求进入该区域,它将完全由运行在那个集群中的容器提供服务。这在减少复杂性方面具有显著的好处,但像通常情况一样,这也是以效率为代价。
要理解分离式方法如何影响效率,请考虑一个分布在世界各地大量地理区域的应用程序,以便为其用户提供非常低的延迟。世界的现实是,某些地理区域有大量人口,而某些地区则人口较少。如果应用程序中每个簇的每个分离式都完全相同,那么每个分离式都必须大小适应最大的地理区域的需求。这样做的结果是,地区簇中应用程序的大多数副本都被大量超量配置,因此应用程序的成本效率较低。这种多余成本的明显解决方案是减少在较小地理区域使用的资源大小。虽然调整应用程序大小可能看起来很容易,但由于瓶颈或其他要求(例如,至少保持三个副本),这并不总是可行。
特别是将现有应用程序从单一集群转移到多集群时,复制的分离式设计是最简单的方法,但值得理解的是,它会带来成本,这些成本可能最初可以承受,但最终会要求重新设计您的应用程序。
分片:区域数据
当您的应用程序扩展时,您可能会遇到的一个痛点是,采用区域分离方法全球复制所有数据变得越来越昂贵,也越来越浪费。虽然为了可靠性复制数据是一件好事,但不太可能所有应用程序数据都需要在部署应用程序的每个集群中共同存在。大多数用户只会从少数几个地理区域访问您的应用程序。
此外,随着您的应用程序在全球范围内的增长,您可能会遇到关于数据本地性的法规和其他法律要求。根据用户的国籍或其他考虑因素,可能会存在关于存储用户数据位置的外部限制。这些要求的结合意味着最终您将需要考虑区域数据分片。在不同区域分片您的数据意味着您的应用程序在所有集群中并非所有数据都存在,这(显然)会影响您的应用程序设计。
以此作为例子,想象一下我们的应用程序部署到了六个区域集群(A、B、C、D、E、F)。我们将应用程序的数据集拆分为三个子集或片段(1、2、3)。
我们的数据片段部署可能如下所示:
A | B | C | D | E | F | |
---|---|---|---|---|---|---|
1 | ✓ | - | - | ✓ | - | - |
2 | - | ✓ | - | - | ✓ | - |
3 | - | - | ✓ | - | - | ✓ |
每个分片在两个区域中都有冗余性,但每个区域集群只能提供三分之一的数据。这意味着每当您需要访问数据时,您必须为服务添加额外的路由层。路由层负责确定请求是否需要发送到本地或跨区域数据分片。
尽管将这种数据路由作为链接到主应用程序的客户端库的一部分实现可能很诱人,但我们强烈建议将数据路由作为单独的微服务构建。引入新的微服务可能会增加复杂性,但实际上它引入了一个简化事物的抽象层。而不是您应用程序中的每个服务都担心数据路由,您只需一个服务封装这些关注点,而其他服务只需访问数据服务。将应用程序分解为独立的微服务提供了在多集群环境中显著灵活性。
更好的灵活性:微服务路由
当我们讨论多集群应用程序开发的区域隔离方法时,我们举了一个例子,说明它可能会降低部署的多集群应用程序的成本效率。但灵活性也会受到其他影响。在创建这种隔离时,您正在以更大规模创建容器和 Kubernetes 试图打破的单块体。此外,您正在强迫应用程序内的每个微服务同时扩展到相同数量的区域。
如果您的应用程序小而集中,这可能有道理,但随着服务变得越来越大,特别是当它们可能开始在多个应用程序之间共享时,单片式的多集群方法开始显著影响您的灵活性。如果集群是部署单位,并且所有的 CI/CD 都与该集群相关联,那么即使这种匹配不合适,您也会强迫每个团队遵循相同的部署流程和时间表。
以具体示例来说,假设您有一个部署到三十个集群的非常大的应用程序,以及正在开发中的一个小型新应用程序。强迫小团队立即达到大型应用程序的规模是不合理的,但如果在应用设计上过于死板,这可能正是会发生的事情。
更好的方法是将应用程序中的每个微服务在应用程序设计上视为一个面向公共的服务。虽然可能永远不会真正期望其成为公共服务,但它应该像前面部分所描述的那样拥有自己的全局负载均衡器,并且应该管理其自身的数据复制服务。在所有意图和目的上,不同的微服务应该彼此独立。当一个服务调用另一个服务时,其负载在与外部负载相同的方式下平衡。有了这种抽象,每个团队可以独立扩展和部署他们的多集群服务,就像他们在单个集群内做的那样。
当然,为应用程序中的每个微服务都这样做可能会给您的团队带来重大负担,并且可能会通过每个服务的负载均衡器维护以及可能的跨区域网络流量增加成本。像软件设计中的一切一样,复杂性与性能之间存在权衡,您需要确定适合您的应用程序在哪些地方增加服务边界的隔离,并且在何处将服务组合成复制的隔离空间是有意义的。就像单集群上下文中的微服务一样,这种设计很可能随着应用程序的变化和增长而变化和适应。期望(并设计)这种流动性将有助于确保您的应用程序可以适应而无需进行大规模的重构。
概要
尽管将您的应用程序部署到多个集群会增加复杂性,但现实世界中的需求和用户期望使这种复杂性对于大多数您构建的应用程序都是必要的。从头开始设计您的应用程序和基础设施以支持多集群应用程序部署将极大地增强您的应用程序的可靠性,并显著降低应用程序增长时重构的概率。多集群部署的最重要部分之一是管理应用程序的配置和部署到集群。无论您的应用程序是区域性的还是多集群的,下一章将帮助确保您能够快速和可靠地部署它。
第二十二章:组织您的应用程序
在本书中,我们描述了构建在 Kubernetes 之上的应用程序的各种组件。我们描述了如何将程序封装为容器,将这些容器放置在 Pod 中,使用 ReplicaSets 复制这些 Pod,并使用 Deployments 进行部署。我们甚至描述了如何部署有状态和真实世界的应用程序,将这些对象收集到一个单一的分布式系统中。但是,我们并没有讨论如何实际操作这样的应用程序。您如何布置、共享、管理和更新构成您应用程序的各种配置?这就是本章的主题。
引导我们的原则
在深入探讨如何构建您的应用程序结构的具体细节之前,考虑一下驱动这种结构的目标是值得的。显然,可靠性和敏捷性是在 Kubernetes 中开发云原生应用程序的一般目标,但这与您如何设计应用程序的维护和部署有什么关系?以下部分描述了可以指导您设计最适合这些目标的结构的三个原则。这些原则是:
-
将文件系统视为真实性的源头
-
进行代码审查以确保变更的质量
-
使用功能标志来分阶段部署和回滚
文件系统作为真相的源头
当您开始探索 Kubernetes 时,就像我们在本书的开头所做的那样,通常会以命令式的方式与其进行交互。您会运行诸如 kubectl run
或 kubectl edit
的命令来创建和修改运行在集群中的 Pod 或其他对象。即使在我们开始探索如何编写和使用 YAML 文件时,这也是以一种临时的方式呈现的,好像文件本身只是修改集群状态道路上的一个途径。实际上,在一个真正生产化的应用程序中,相反的情况应该是真实的。
与其将集群的状态——存储在 etcd
中的数据——视为真实性的源头,不如将 YAML 对象的文件系统视为应用程序的真实性的最佳源头。部署到您的 Kubernetes 集群中的 API 对象然后是文件系统中存储的真实性的反映。
这是正确观点的众多理由。首要的是,它在很大程度上使您能够将您的集群视为不可变基础设施。随着我们进入云原生架构,我们越来越习惯于将我们的应用程序及其容器视为不可变基础设施,但将集群视为这样的基础设施则较少见。然而,将我们的应用程序迁移到不可变基础设施的相同理由也适用于我们的集群。如果您的集群是通过随意应用从互联网下载的随机 YAML 文件制造的雪花,那么它与通过命令式 bash 脚本构建的虚拟机一样危险。
此外,通过文件系统管理集群状态使得与多个团队成员协作变得非常容易。源代码控制系统被广泛理解,并且可以轻松地使多人同时编辑集群状态,同时使冲突(以及解决这些冲突)对每个人都清晰可见。
注意
所有部署到 Kubernetes 的应用程序都应首先在文件系统中的文件中进行描述,这绝对是一个第一原则。然后,实际的 API 对象只是这个文件系统投射到特定集群的一部分。
代码审查的作用
不久以前,对应用程序源代码进行代码审查还是一个新颖的想法。但现在很明显,在将代码提交到应用程序之前,多人审查代码是生产高质量、可靠代码的最佳实践。
因此令人惊讶的是,对于用于部署这些应用程序的配置来说,同样的情况相对较少。代码审查的所有理由都直接适用于应用程序配置。但是当你考虑它时,审查这些配置对于可靠部署服务同样至关重要是显而易见的。根据我们的经验,大多数服务中断是自我造成的,由于意外后果、拼写错误或其他简单错误。确保至少有两个人查看任何配置更改,显著降低了此类错误发生的概率。
注意
我们应用程序布局的第二个原则是,它必须促进对合并到代表我们集群真实源的文件集的每个更改的审查。
功能门
一旦你的应用源代码和部署配置文件都在源代码控制中,其中一个最常见的问题是这些仓库如何相互关联。你应该将应用程序源代码和配置文件放在同一个仓库中吗?对于小项目来说这可能行得通,但在大项目中通常更合理地将它们分开。即使是同一组人负责构建和部署应用程序,构建者和部署者的视角有足够大的不同,以至于这种关注点的分离是有道理的。
如果是这种情况,那么如何在源代码控制中开发新功能并将这些功能部署到生产环境中呢?这就是功能门控的重要作用所在。
思想是,当开发某个新功能时,该开发完全在功能标志或门后进行。这个门看起来像这样:
if (featureFlags.myFlag) {
// Feature implementation goes here
}
这种方法有许多好处。首先,它允许团队在功能准备好发货之前长时间提交到生产分支。这使得功能开发能够与仓库的HEAD
更紧密地对齐,因此可以避免长期分支的可怕合并冲突。
在功能标志背后工作还意味着启用功能只需进行配置更改即可激活标志。这使得在生产环境中明确了哪些内容发生了变化,并且如果功能激活导致问题,回滚功能激活也非常简单。
使用功能标志既简化了调试,又确保禁用功能不需要二进制回滚到较旧版本的代码,从而删除所有由新版本带来的错误修复和其他改进。
注意
应用程序布局的第三个原则是代码默认存储在源代码控制中,通过功能标志关闭,并且只能通过代码审查的配置文件更改来激活。
在源代码控制中管理您的应用程序
现在我们已确定文件系统应该代表集群的真实来源,下一个重要问题是如何实际布置文件系统中的文件。显然,文件系统包含层次目录,并且源代码控制系统添加了标签和分支等概念,因此本节描述如何将它们结合在一起以表示和管理您的应用程序。
文件系统布局
本节描述如何为单个集群设置应用程序实例的布局。在后续章节中,我们将描述如何为多个实例参数化此布局。在开始时正确地组织这一点非常重要。就像修改源代码控制中包的布局一样,修改部署配置后进行的复杂且昂贵的重构可能永远都不会完成。
组织应用程序的第一个基数是语义组件或层(例如前端或批处理工作队列)。尽管在早期可能看起来有些过度,因为一个团队管理所有这些组件,但这为团队扩展奠定了基础——最终,不同的团队(或子团队)可能负责每个组件。
因此,对于一个使用两个服务的前端的应用程序,文件系统可能如下所示:
frontend/
service-1/
service-2/
在每个目录中,存储每个应用程序的配置。这些是直接表示集群当前状态的 YAML 文件。通常将服务名称和对象类型包含在同一个文件中非常有用。
注意
虽然 Kubernetes 允许你在同一个文件中创建多个对象的 YAML 文件,但这通常是一个反模式。将多个对象分组放在同一个文件中的唯一好理由是它们在概念上是相同的。在决定将什么内容放入单个 YAML 文件时,考虑类似于定义类或结构体的设计原则。如果将这些对象组合在一起不形成一个单一的概念,它们可能不应该在同一个文件中。
因此,延伸我们之前的例子,文件系统可能如下所示:
frontend/
frontend-deployment.yaml
frontend-service.yaml
frontend-ingress.yaml
service-1/
service-1-deployment.yaml
service-1-service.yaml
service-1-configmap.yaml
...
管理周期版本
那么如何管理版本?能够回顾和查看应用程序之前的部署情况非常有用。同样,能够在保持稳定发布配置的同时将配置向前迭代也非常有用。
因此,能够同时存储和维护配置的多个修订版本非常方便。我们在这里列出的文件和版本控制系统有两种不同的方法可以使用。第一种是使用标签、分支和源控制功能。这种方法很方便,因为它与人们在源代码控制中管理修订版本的方式相匹配,并且导致更简化的目录结构。另一种选择是在文件系统内克隆配置,并使用不同修订版本的目录。这样可以非常直观地同时查看配置。
这些方法在管理不同的发布版本方面具有相同的能力,因此最终是在两者之间的美学选择。我们将讨论这两种方法,让您或您的团队决定哪种更合适。
使用分支和标签进行版本控制
当您使用分支和标签管理配置修订版本时,目录结构不会与前一节中的示例不同。当您准备发布时,您会在配置源控制系统中放置一个源控制标签(例如git tag v1.0
)。该标签代表了该版本使用的配置,源控制的HEAD
继续向前迭代。
更新发布配置略微复杂,但方法模拟了源控制中的操作。首先,您将更改提交到仓库的HEAD
。然后,您在v1.0
标签处创建一个名为v1
的新分支。您将所需的更改挑选到发布分支上(git cherry-pick *<edit>*
),最后,您使用v1.1
标签标记此分支以指示一个新的点发布。此方法在图 22-1 中有所说明。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/k8s-uprn/img/kur3_2201.png
图 22-1. 挑选工作流程
注
在将修复内容挑选到发布分支时,一个常见的错误是只将更改挑选到最新的发布中。最好将其挑选到所有活动的发布中,以防需要回滚版本但仍需要此修复。
版本控制与目录
使用文件系统功能的另一种选择是使用源控制功能。在这种方法中,每个版本化的部署都存在于其自己的目录中。例如,您的应用程序文件系统可能如下所示:
frontend/
v1/
frontend-deployment.yaml
frontend-service.yaml
current/
frontend-deployment.yaml
frontend-service.yaml
service-1/
v1/
service-1-deployment.yaml
service-1-service.yaml
v2/
service-1-deployment.yaml
service-1-service.yaml
current/
service-1-deployment.yaml
service-1-service.yaml
...
因此,每个修订版本存在于与发布关联的目录内的并行目录结构中。所有部署都来自HEAD
,而不是特定的修订版本或标签。您将向当前目录中的文件添加新配置。
在创建新版本时,您可以复制当前目录以创建与新版本关联的新目录。
当您对发布进行错误修复时,您的拉取请求必须修改所有相关发布目录中的 YAML 文件。这比之前描述的挑选方法稍好,因为在单个更改请求中明确指出正在更新所有相关版本,而不是需要每个版本都进行挑选。
为开发、测试和部署结构化您的应用程序
除了为周期性发布节奏构建应用程序外,您还希望为敏捷开发、质量测试和安全部署构建应用程序。这使得开发人员能够快速地对分布式应用程序进行更改和测试,并安全地将这些更改推向客户。
目标
关于开发和测试,您的应用程序有两个目标。首先,每个开发人员都应能轻松地为应用程序开发新功能。在大多数情况下,开发人员仅在一个组件上工作,但该组件与集群中的所有其他微服务都是相互连接的。因此,为了促进开发,开发人员能够在自己的环境中使用所有服务是至关重要的。
另一个目标是为了在部署前轻松准确地为应用程序进行结构化测试。这对于快速推出功能同时保持高可靠性至关重要。
发布进程
要实现这两个目标,将开发阶段与前述的发布版本阶段相关联是非常重要的。发布的阶段包括:
HEAD
配置的最前沿;最新的更改。
开发
主要是稳定的,但尚未准备部署。适合开发人员用于构建功能。
预发布
测试的开始,除非发现问题,否则不太可能更改。
金丝雀发布
面向用户的第一个真正发布版本,用于测试真实流量中的问题,并让用户有机会测试即将推出的内容。
发布
当前的生产发布版本。
引入开发标签
无论您是使用文件系统还是版本控制来构建发布版本,建模开发阶段的正确方式是通过源代码控制标签。这是因为开发必须跟踪稳定性,仅略微滞后于HEAD
。
要引入开发阶段,您需要向源代码控制系统添加一个新的development
标签,并使用自动化流程将此标签向前推进。定期,您将通过自动化集成测试测试HEAD
。如果这些测试通过,则将development
标签向前推进到HEAD
。因此,开发人员可以在部署其自己的环境时跟踪最新的更改,但同时可以确保已部署的配置至少通过了有限的冒烟测试。这种方法在图 22-2 中有所体现。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/k8s-uprn/img/kur3_2202.png
图 22-2. 开发标签工作流程
将各个阶段映射到修订版
或许诱人的是为每个阶段引入一组新的配置,但实际上,每个版本和阶段的每种组合都会造成混乱,这将非常难以理解。相反,正确的做法是引入一个将修订版与阶段进行映射的方法。
无论您是使用文件系统还是源控制修订版来表示不同的配置版本,都可以轻松实现从阶段到修订版的映射。在文件系统情况下,可以使用符号链接将阶段名称映射到修订版:
frontend/
canary/ -> v2/
release/ -> v1/
v1/
frontend-deployment.yaml
...
对于版本控制而言,它只是与适当版本相同修订版的附加标签。
在任一情况下,版本控制都是使用先前描述的过程进行的,并且根据需要将阶段移动到新版本。实际上,这意味着存在两个同时进行的过程:第一个用于生成新的发布版本,第二个用于将发布版本合格化为应用程序生命周期中特定阶段的版本。
使用模板参数化您的应用程序
一旦您拥有环境和阶段的笛卡尔乘积,保持它们完全相同变得不切实际或不可能。然而,努力使环境尽可能相似是很重要的。在不同环境之间的变化和漂移会产生雪花和难以理解的系统。如果您的演示环境与发布环境不同,您真的能相信您在演示环境中运行的负载测试来验证发布吗?为了确保您的环境保持尽可能相似,使用参数化环境非常有用。参数化环境使用模板来处理大部分配置,但是混入一小部分参数以生成最终配置。这种方式,大部分配置包含在共享模板中,而参数化范围有限,并且在一个小的参数文件中维护,便于可视化不同环境之间的差异。
使用 Helm 和模板进行参数化
有各种不同的语言用于创建参数化配置。通常它们将文件分为模板文件,其中包含大部分配置,以及参数文件,可以与模板结合以生成完整的配置。除了参数外,大多数模板语言允许参数具有默认值,如果未指定值,则使用默认值。
以下示例展示了如何使用Helm,这是 Kubernetes 的包管理器,来参数化配置。尽管各种语言的信徒可能会说些不同,但所有参数化语言在很大程度上是相等的,与编程语言一样,你偏爱哪一种很大程度上是个人或团队风格的问题。因此,这里描述的 Helm 模式适用于您选择的任何模板语言。
Helm 模板语言使用“mustache”语法:
metadata:
name: {{ .Release.Name }}-deployment
这表明Release.Name
应该用部署的名称替换。
要为此值传递参数,您可以使用名为values.yaml的文件,其内容如下:
Release:
Name: my-release
参数替换后,结果如下:
metadata:
name: my-release-deployment
参数化的文件系统布局
现在你了解如何为你的配置参数化了,那么如何将其应用到文件系统布局中呢?不要将每个部署生命周期阶段都视为指向某个版本的指针,而是将每个部署生命周期阶段视为参数文件和指向特定版本的组合。例如,在基于目录的布局中,可能如下所示:
frontend/
staging/
templates -> ../v2
staging-parameters.yaml
production/
templates -> ../v1
production-parameters.yaml
v1/
frontend-deployment.yaml
frontend-service.yaml
v2/
frontend-deployment.yaml
frontend-service.yaml
...
使用版本控制执行此操作看起来类似,只是每个生命周期阶段的参数保留在配置目录树的根目录下:
frontend/
staging-parameters.yaml
templates/
frontend-deployment.YAML
...
在全球范围内部署您的应用程序
现在,您的应用程序有多个版本在多个部署阶段中运行,配置结构化的最后一步是在全球范围内部署您的应用程序。但不要认为这些方法仅适用于大型应用程序。您可以使用它们从两个不同的区域扩展到全球范围内的十个或数百个区域。在云中,整个区域可能会失败,因此部署到多个区域(以及管理该部署)是满足要求用户需求的唯一方法。
全球部署架构
通常情况下,每个 Kubernetes 集群旨在位于单个区域,并包含应用程序的单个完整部署。因此,应用程序的全球部署由多个不同的 Kubernetes 集群组成,每个集群都有其自己的应用程序配置。描述如何实际构建全球应用程序,特别是涉及数据复制等复杂主题,超出了本章的范围,但我们将描述如何在文件系统中安排应用程序配置。
特定区域的配置在概念上与部署生命周期中的阶段相同。因此,将多个区域添加到配置中等同于添加新的生命周期阶段。例如,不是:
-
开发
-
演练
-
金丝雀
-
生产
您可能会有:
-
开发
-
演练
-
金丝雀
-
东部美国
-
西部美国
-
欧洲
-
亚洲
配置文件系统中的建模看起来像:
frontend/
staging/
templates -> ../v3/
parameters.yaml
eastus/
templates -> ../v1/
parameters.yaml
westus/
templates -> ../v2/
parameters.yaml
...
如果你使用版本控制和标签,文件系统将如下所示:
frontend/
staging-parameters.yaml
eastus-parameters.yaml
westus-parameters.yaml
templates/
frontend-deployment.yaml
...
使用这种结构,您将为每个区域引入一个新标签,并使用该标签下的文件内容部署到该区域。
实施全球部署
现在您已经为世界各地的每个区域配置了配置,问题变成了如何更新这些不同的区域。使用多个区域的主要目标之一是确保非常高的可靠性和正常运行时间。虽然人们可能会认为云和数据中心的宕机是停机的主要原因,但事实上,宕机通常是由新版本的软件发布引起的。因此,构建高可用系统的关键是限制您可能进行的任何更改的影响,或者说“爆炸半径”。因此,在跨多个区域推出版本时,逐个区域谨慎移动、验证并确保信心,显得十分合理。
在全球范围内推出软件通常看起来更像是一个工作流程,而不是单一的声明性更新:您首先将版本更新到最新版本,并在所有区域中进行逐步推进,直到在所有地方都推出为止。但是,您应该如何结构化各个区域,以及在各区域之间进行验证之间应该等待多长时间呢?
注意
您可以使用诸如GitHub Actions之类的工具自动化部署工作流程。它们提供了一种声明性语法来定义您的工作流程,并且也存储在源代码控制中。
要确定在各个区域之间推出的时间间隔,考虑软件的“烟雾平均时间”。这是一个新版本在推出到一个区域后,平均需要多长时间才能发现问题(如果存在问题)。显然,每个问题都是独特的,可能需要不同的时间才能被发现,这就是为什么您要了解平均时间。在规模化管理软件时,这是一种概率而非确定性的业务,因此您希望等待一个使错误概率低到足够让您放心继续向下一个区域推进的时间。例如,两到三倍的平均烟雾时间可能是一个合理的起点,但这取决于您的应用程序,因此会有很大的变化。
要确定各地区的顺序,重要的是考虑各个地区的特点。例如,您可能会有高流量地区和低流量地区。根据您的应用程序,某些功能在一个地理区域比另一个地方更受欢迎。在制定发布时间表时应考虑所有这些特征。您可能希望首先在低流量地区进行推出。这样可以确保您发现的早期问题仅限于影响不大的地区。尽管这不是一个硬性规则,但早期问题通常最为严重,因为它们会在您首次推出的地区迅速显现出来。因此,减少此类问题对客户的影响是有意义的。接下来,推出到高流量地区。一旦您通过低流量地区成功验证了发布的正确性,就要验证其在大规模上的正确性。唯一的方法是将其推出到单个高流量地区。当您成功推出到低流量和高流量地区时,您可以相信您的应用程序可以安全地在所有地方推出。然而,如果存在区域性差异,您可能还想在更广泛地推出发布之前在各种地理区域逐渐测试。
制定发布时间表时,重要的是无论发布的大小如何,都要完全遵循它。许多停机事件是因为人们加速发布,要么是为了解决其他问题,要么是因为他们认为是“安全”的。
全球部署的仪表板和监控
当您在小规模开发时,这可能是一个奇怪的概念,但在中等或大规模时,您可能会遇到的一个重要问题是,不同版本的应用程序部署到不同的地区。这可能由于各种原因发生(例如,因为发布失败、被中止或在特定地区出现问题),如果您不仔细追踪,您可能会迅速遇到在全球各地部署不同版本的难以管理的问题。此外,随着客户询问他们正在经历的错误的修复情况,一个常见的问题将是:“它已经部署了吗?”
因此,开发仪表板是至关重要的,它可以让您一眼看出各个地区运行的哪个版本,以及警报功能,当您的应用程序部署了太多版本时将触发警报。最佳实践是将活跃版本数量限制在不超过三个:一个用于测试,一个正在推出,一个正在被推出的版本所替代。如果活跃版本超过这个数量,将会带来麻烦。
总结
本章提供了关于如何通过软件版本、部署阶段和全球各地区来管理 Kubernetes 应用程序的指导。它强调了组织应用程序基础的原则:依赖文件系统进行组织、使用代码审查来确保质量变更,并依赖于功能标志或门控,以便逐步添加和删除功能。
和其他所有内容一样,本章中的示例应视为灵感,而非绝对真理。阅读指南,找到最适合您应用程序特定情况的方法组合。但请记住,在部署应用程序时,您正在设定一个可能需要多年维护的过程。
附录. 搭建你自己的 Kubernetes 集群
Kubernetes 通常通过公共云计算的虚拟世界来体验,你只需在网页浏览器或终端中操作,距离你的集群非常近。但在裸机上物理搭建一个 Kubernetes 集群可能会带来非常丰富的体验。同样地,看到你随意断开某个节点的电源或网络,然后观察 Kubernetes 如何恢复你的应用程序,你会深信其实用性。
自己搭建集群可能看起来既具有挑战性又昂贵,但幸运的是实际上都不是。购买低成本的片上系统计算机板的能力,以及社区为了让 Kubernetes 更易安装而做出的大量工作,意味着你可以在几个小时内建立一个小型 Kubernetes 集群。
在接下来的说明中,我们专注于搭建一组 Raspberry Pi 机器的集群,但稍作调整,同样的说明也可以用于各种不同的单板机器或者你周围的任何其他计算机。
零件清单
搭建集群的第一步是准备好所有的零件。在这里的所有示例中,我们假设是一个四节点的集群。你也可以建立一个三节点或者甚至一百节点的集群,但四节点是一个相当好的选择。首先,你需要购买(或者搜刮到)搭建集群所需的各种零件。
这里是购物清单,列出了一些写作时的大致价格:
-
四台 Raspberry Pi 4 机器,每台至少配备 2 GB 内存— $180
-
四张至少 8 GB 的 SDHC 存储卡(务必购买高质量的!)— $30–50
-
四根 12 英寸 Cat. 6 以太网线— $10
-
四根 12 英寸 USB-A 到 USB-C 电缆— $10
-
一个 5 端口 10/100 快速以太网交换机— $10
-
一个 5 端口 USB 充电器— $25
-
一个能容纳四个 Pi 的 Raspberry Pi 堆叠盒子— $40(或者自己动手建造)
-
一个用于给以太网交换机供电的 USB 到插口插头(可选)— $5
整个集群的总成本约为$300,如果你建立一个三节点集群并且跳过盒子和交换机的 USB 电源电缆,你可以将成本降低到$200(尽管盒子和电缆确实能使整个集群更加整洁)。
关于存储卡的另一个注意事项:不要省钱在这里。低端存储卡表现不可预测,会使你的集群非常不稳定。如果你想节省一些钱,可以购买较小但高质量的存储卡。在线购买高质量的 8 GB 存储卡大约每张约$7。
一旦你准备好了所有零件,你就可以继续搭建集群了。
注意
这些说明还假设你有一个能够刷写 SDHC 卡的设备。如果没有,你需要购买一个 USB 存储卡读写器。
刷写镜像
默认的 Ubuntu 20.04 镜像支持 Raspberry Pi 4,也是许多 Kubernetes 集群常用的操作系统。安装它的最简单方法是使用由Raspberry Pi 项目提供的 Raspberry Pi Imager:
使用映像工具将 Ubuntu 20.04 映像写入每张存储卡。在映像工具中,Ubuntu 可能不是默认的映像选择,但您可以选择它作为选项。
首次启动
首先要做的是仅启动 API 服务器节点。组装您的集群,并决定哪一个将是 API 服务器节点。插入存储卡,将板子插入 HDMI 输出,并将键盘插入 USB 端口。
接下来,连接电源以启动板子。
在提示符下使用用户名**ubuntu
和密码ubuntu
**登录。
警告
您的 Raspberry Pi(或任何新设备)的第一件事是更改默认密码。每种安装的默认密码都是众所周知的,这使得那些有意恶作剧的人可以访问系统的默认登录。这会让互联网对所有人都不安全。请更改默认密码!
为您集群中的每个节点重复这些步骤。
设置网络
下一步是在 API 服务器上设置网络。为 Kubernetes 集群设置网络可能会很复杂。在以下示例中,我们正在设置一个网络,其中一台机器通过无线网络连接到互联网;这台机器还通过有线以太网连接到集群网络,并提供 DHCP 服务器以向集群中的其余节点提供网络地址。此网络的示例如下图所示:
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/k8s-uprn/img/kur3_aain01.png
决定哪个板子将托管 API 服务器和etcd
。通常最容易记住这一点的方法是将其设置为堆栈中的顶部或底部节点,但也可以使用某种标签。
要做到这一点,请编辑文件*/etc/netplan/50-cloud-init.yaml*。如果不存在此文件,您可以创建它。文件的内容应如下所示:
network:
version: 2
ethernets:
eth0:
dhcp4: false
dhcp6: false
addresses:
- '10.0.0.1/24'
optional: true
wifis:
wlan0:
access-points:
<your-ssid-here>:
password: '<your-password-here>'
dhcp4: true
optional: true
这将使主要以太网接口具有静态分配的地址 10.0.0.1,并设置 WiFi 接口以连接到您的本地 WiFi。然后运行sudo netplan apply
来应用这些新变更。
重新启动机器以获取 10.0.0.1 地址。您可以通过运行ip addr
并查看eth0
接口的地址来验证设置是否正确。还要验证与互联网的连接是否正常。
接下来,我们将在此 API 服务器上安装 DHCP,以便为工作节点分配地址。运行:
$ apt-get install isc-dhcp-server
然后按以下方式配置 DHCP 服务器(/etc/dhcp/dhcpd.conf):
# Set a domain name, can basically be anything
option domain-name "cluster.home";
# Use Google DNS by default, you can substitute ISP-supplied values here
option domain-name-servers 8.8.8.8, 8.8.4.4;
# We'll use 10.0.0.X for our subnet
subnet 10.0.0.0 netmask 255.255.255.0 {
range 10.0.0.1 10.0.0.10;
option subnet-mask 255.255.255.0;
option broadcast-address 10.0.0.255;
option routers 10.0.0.1;
}
default-lease-time 600;
max-lease-time 7200;
authoritative;
您可能还需要编辑*/etc/default/isc-dhcp-server*以将INTERFACES
环境变量设置为eth0
。使用sudo systemctl restart isc-dhcp-server
重新启动 DHCP 服务器。现在您的机器应该正在分配 IP 地址。您可以通过通过以太网连接第二台机器到交换机来测试这一点。第二台机器应该从 DHCP 服务器获得 10.0.0.2 地址。
记得编辑 /etc/hostname 文件,将此机器重命名为 node-1
。为了帮助 Kubernetes 进行网络设置,还需要设置 iptables
,使其能够看到桥接网络流量。在 /etc/modules-load.d/k8s.conf 创建一个文件,只包含 br_netfilter
。这将在您的内核中加载 br_netfilter
模块。
接下来,您需要为网络桥接和地址转换(NAT)启用一些 systemctl
设置,以使 Kubernetes 网络正常工作,并且您的节点能够访问公共互联网。创建名为 /etc/sysctl.d/k8s.conf 的文件,并添加以下内容:
net.ipv4.ip_forward=1
net.bridge.bridge-nf-call-ip6tables=1
net.bridge.bridge-nf-call-iptables=1
然后编辑 /etc/rc.local 文件(或等效文件),添加 iptables
规则以从 eth0
转发到 wlan0
(以及反向):
iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE
iptables -A FORWARD -i wlan0 -o eth0 -m state \
--state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i eth0 -o wlan0 -j ACCEPT
到这一步,基本的网络设置应该已经完成。插入并启动其余两个板(应该看到它们被分配了地址 10.0.0.3 和 10.0.0.4)。在每台机器上编辑 /etc/hostname 文件,分别命名为 node-2
和 node-3
。
首先查看 /var/lib/dhcp/dhcpd.leases 验证,然后 SSH 到节点(记得首先更改默认密码)。确认节点能够连接到外部互联网。
安装容器运行时
在安装 Kubernetes 之前,您需要先安装一个容器运行时。有几种可能的运行时可供选择,但最广泛采用的是来自 Docker 的 containerd
。containerd
由标准的 Ubuntu 软件包管理器提供,但其版本有点滞后。虽然需要多做一些工作,但我们建议从 Docker 项目自身安装它。
第一步是将 Docker 设置为系统上安装软件包的仓库:
# Add some prerequisites
sudo apt-get install ca-certificates curl gnupg lsb-release
# Install Docker's signing key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor \
-o /usr/share/keyrings/docker-archive-keyring.gpg
最后一步,创建文件 /etc/apt/sources.list.d/docker.list,内容如下:
deb [arch=arm64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \
https://download.docker.com/linux/ubuntu focal stable
现在您已经安装了 Docker 软件包仓库,可以通过运行以下命令安装 containerd.io
。重要的是要安装 containerd.io
,而不是 containerd
,以获取 Docker 软件包,而不是默认的 Ubuntu 软件包:
sudo apt-get update; sudo apt-get install containerd.io
到这一步,containerd
已经安装完毕,但您需要配置它,因为软件包提供的配置不能满足 Kubernetes 的需求:
containerd config default > config.toml
sudo mv config.toml /etc/containerd/config.toml
# Restart to pick up the config
sudo systemctl restart containerd
现在您已经安装了容器运行时,可以继续安装 Kubernetes 自身了。
安装 Kubernetes
此时,所有节点应该已经启动,并具有 IP 地址,并且能够访问互联网。现在是在所有节点上安装 Kubernetes 的时候了。使用 SSH,在所有节点上运行以下命令安装 kubelet
和 kubeadm
工具。
首先,添加软件包的加密密钥:
# curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg \
| sudo apt-key add -
然后将该仓库添加到您的仓库列表中:
# echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" \
| sudo tee /etc/apt/sources.list.d/kubernetes.list
最后,更新并安装 Kubernetes 工具。这也会更新系统上的所有软件包,以确保一切正常:
# sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install -y kubelet kubeadm kubectl kubernetes-cni
配置集群
在运行 DHCP 和连接到互联网的 API 服务器节点上运行:
$ sudo kubeadm init --pod-network-cidr 10.244.0.0/16 \
--apiserver-advertise-address 10.0.0.1 \
--apiserver-cert-extra-sans kubernetes.cluster.home
请注意,您正在广告您的内部 IP 地址,而不是外部地址。
最终,这将打印出一个命令,用于将节点加入到您的集群中。它看起来会像这样:
$ kubeadm join --token=*<token>* 10.0.0.1
SSH 到集群中每个工作节点,并运行该命令。
当所有这些都完成后,您应该能够运行此命令并查看您的工作集群:
$ kubectl get nodes
设置集群网络
您已经设置好了节点级网络,但仍然需要设置 Pod 到 Pod 的网络。由于集群中的所有节点都在同一物理以太网网络上运行,因此您可以简单地在主机内核中设置正确的路由规则。
管理此操作的最简单方法是使用由 CoreOS 创建并现在由 Flannel 项目 支持的 Flannel 工具。Flannel 支持多种不同的路由模式;我们将使用 host-gw
模式。您可以从 Flannel 项目页面 下载一个示例配置文件。
$ curl https://oreil.ly/kube-flannelyml \
> kube-flannel.yaml
Flannel 提供的默认配置使用 vxlan
模式。要修复此问题,请在您喜欢的编辑器中打开该配置文件;将 vxlan
替换为 host-gw
。
您还可以使用 sed
工具来完成此操作:
$ curl https://oreil.ly/kube-flannelyml \
| sed "s/vxlan/host-gw/g" \
> kube-flannel.yaml
一旦您更新了 kube-flannel.yaml 文件,您就可以使用以下命令创建 Flannel 网络设置:
$ kubectl apply -f kube-flannel.yaml
这将创建两个对象,一个用于配置 Flannel 的 ConfigMap 和一个运行实际 Flannel 守护程序的 DaemonSet。您可以使用以下命令检查它们:
$ kubectl describe --namespace=kube-system configmaps/kube-flannel-cfg
$ kubectl describe --namespace=kube-system daemonsets/kube-flannel-ds
摘要
此时,您应该已经在您的树莓派上运行一个工作的 Kubernetes 集群。这对于探索 Kubernetes 非常有用。安排一些任务,打开 UI,并尝试通过重新启动机器或断开网络来破坏您的集群。