介绍
设计和运行具有可扩展性、可移植性和稳健性的应用程序可能具有挑战性,尤其是随着系统复杂性的增加。 应用程序或系统的架构决定了它必须如何运行、它对环境的期望以及它与相关组件的耦合程度。 在设计阶段遵循某些模式并遵守某些操作实践可以帮助解决应用程序在高度分布式环境中运行时面临的一些最常见问题。
Docker 和 Kubernetes 等技术帮助团队打包软件,然后在分布式计算机平台上分发、部署和扩展。 学习如何最好地利用这些工具的强大功能可以帮助您以更大的灵活性、控制力和响应能力来管理应用程序。
在本指南中,我们将讨论您可能希望采用的一些原则和模式,以帮助您在 Kubernetes 上扩展和管理工作负载。 虽然 Kubernetes 可以运行多种类型的工作负载,但您的选择会影响操作的易用性和可用的可能性。
应用程序可扩展性设计
在生产软件时,许多需求会影响您选择采用的模式和架构。 对于 Kubernetes,最重要的因素之一是能够 水平扩展 ,增加并行运行的应用程序相同副本的数量,以分配负载并提高可用性。 这是垂直缩放的替代方案,通常是指增加单个应用程序堆栈的容量。
特别是, 微服务 是一种软件设计模式,适用于集群上的可扩展部署。 开发人员创建小型、可组合的应用程序,这些应用程序通过定义明确的 API 在网络上进行通信,而不是通过内部机制进行通信的大型复合程序。 将单体应用程序重构为离散的单一用途组件,可以独立扩展每个功能。 应用程序级别通常存在的大部分复杂性和开销都转移到了操作领域,可以由 Kubernetes 等平台进行管理。
除了特定的软件模式之外,云原生应用程序的设计还考虑了一些额外的考虑因素。 云原生应用程序是通常遵循微服务架构模式的程序,具有内置的弹性、可观察性和管理功能,可充分利用云平台。
例如,云原生应用程序使用健康报告指标构建,以使平台能够在实例变得不健康时管理生命周期事件。 它们生成(并可供导出)强大的遥测数据,以提醒操作员注意问题并让他们做出明智的决定。 应用程序旨在处理定期重启和故障、后端可用性变化和高负载,而不会损坏数据或变得无响应。
遵循12因子应用理念
Twelve-Factor App 哲学是一种流行的方法,可以帮助您专注于创建云就绪 Web 应用程序时最重要的特征。 最初是为了帮助开发人员和运营团队了解旨在在云中运行的 Web 服务所共有的核心品质,这些原则非常适用于将在 Kubernetes 等集群环境中运行的软件。 虽然单体应用程序可以从遵循这些建议中受益,但围绕这些原则设计的微服务架构效果特别好。
对十二个因素的快速总结是:
- 代码库: 管理版本控制系统(如 Git 或 Mercurial)中的所有代码。 代码库全面规定了部署的内容。
- Dependencies: 依赖项应该由代码库完全明确地管理,无论是供应商(与代码一起存储)还是以包管理器可以安装的格式固定的版本。
- Config: 将配置参数从应用程序中分离出来,并在部署环境中定义它们,而不是将它们烘焙到应用程序本身中。
- 支持服务: 本地和远程服务都被抽象为网络可访问资源,并在配置中设置连接详细信息。
- 构建、发布、运行: 应用程序的构建阶段应该与应用程序的发布和操作过程完全分开。 构建阶段从源代码创建部署工件,发布阶段结合工件和配置,运行阶段执行发布。
- Processes: 应用程序被实现为不应依赖于本地存储状态的进程。 应该将状态卸载到支持服务,如第四个因素中所述。
- 端口绑定: 应用程序应本机绑定到端口并侦听连接。 路由和请求转发应该在外部处理。
- 并发: 应用程序应该依赖于通过流程模型进行扩展。 同时运行应用程序的多个副本,可能跨多个服务器,允许在不调整应用程序代码的情况下进行扩展。
- 可处置性: 进程应该能够快速启动并优雅地停止而不会产生严重的副作用。
- Dev/prod parity: 您的测试、登台和生产环境应该紧密匹配并保持同步。 环境之间的差异是出现不兼容和未经测试的配置的机会。
- Logs: 应用程序应该将日志流式传输到标准输出,以便外部服务可以决定如何最好地处理它们。
- 管理进程: 一次性管理进程应针对特定版本运行,并与主进程代码一起提供。
通过遵守十二要素提供的指导方针,您可以创建和运行非常适合 Kubernetes 的应用程序。 十二要素鼓励开发人员专注于应用程序的主要用途,考虑组件之间的运行条件和接口,并使用输入、输出和标准流程管理功能在 Kubernetes 中以可预测的方式运行。
容器化应用程序组件
Kubernetes 使用容器在其集群节点上运行隔离的、打包的应用程序。 要在 Kubernetes 上运行,您的应用程序必须封装在一个或多个容器映像中,并使用 Docker 等容器运行时执行。 虽然容器化您的组件是 Kubernetes 的一项要求,但它也有助于强化上面讨论的十二因素应用程序方法中的许多原则,从而实现更好的扩展和管理。
例如,容器提供应用程序环境和外部主机系统之间的隔离。 它们支持应用程序间通信的网络方法,通常通过环境变量进行配置,并公开写入 stdout
和 stderr
的日志。 容器本身鼓励基于进程的并发性,并通过独立可扩展和捆绑进程的运行时环境来帮助维护开发/产品的奇偶校验。 这些特性使您可以打包您的应用程序,以便它们在 Kubernetes 上顺利运行。
容器优化指南
容器技术的灵活性允许封装应用程序的多种不同方式。 但是,在 Kubernetes 环境中,某些方法比其他方法效果更好。
将应用程序容器化的大多数最佳实践都与映像构建有关,您可以在其中定义如何在容器中设置和运行软件。 一般来说,保持图像尺寸小而简单会带来很多好处。 大小优化的镜像可以通过在镜像更新之间重用现有层来减少在集群上启动新容器所需的时间和资源,Docker 和其他容器框架旨在自动执行此操作。
创建容器映像的第一步是尽力将构建步骤与将在生产中运行的最终映像分开。 编译或捆绑软件通常需要额外的工具,花费额外的时间,并产生可能在容器之间不一致或对最终运行时环境不必要的工件(例如,跨平台依赖性)。 将构建过程与运行时环境完全分离的一种方法是使用 Docker 多阶段构建 。 多阶段构建配置允许您指定一个在构建过程中使用的基本映像,并定义另一个在运行时使用。 这使得可以使用安装了所有构建工具的映像来构建软件,并将生成的工件复制到一个精简的、流线型的映像中,以后每次都会使用该映像。
有了这种可用的功能,在最小的父映像之上构建生产映像通常是一个好主意。 如果你希望完全避免像 ubuntu:20.04
(包括完整的 Ubuntu 20.04 服务器环境)这样的“发行版”式父层中发现的臃肿,你可以使用 scratch
构建你的镜像——Docker 最最小基础镜像——作为父镜像。 然而,scratch
基础层不提供对许多核心工具的访问,并且经常会打破关于 Linux 环境的一些基线假设。 作为替代方案,Alpine Linux alpine
映像已成为一种稳固的、最小的基础环境,它提供了一个小巧但功能齐全的 Linux 发行版。
对于 Python 或 Ruby 等解释型语言,范式略有变化,因为没有编译阶段,并且解释器必须可用于在生产环境中运行代码。 然而,由于纤薄的镜像仍然是理想的,许多基于 Alpine Linux 构建的特定语言的优化镜像都可以在 Docker Hub 上使用。 为解释语言使用较小镜像的好处与编译语言类似:Kubernetes 将能够快速将所有必要的容器镜像拉到新节点上,以开始做有意义的工作。
确定容器和 Pod 的范围
虽然您的应用程序必须容器化才能在 Kubernetes 集群上运行,但 pods 是 Kubernetes 可以直接管理的最小抽象单元。 Pod 是由一个或多个紧密耦合的容器组成的 Kubernetes 对象。 pod 中的容器共享一个生命周期,并作为一个单元一起管理。 例如,容器总是调度(部署)在同一个节点(服务器)上,同时启动或停止,并共享文件系统和 IP 寻址等资源。
了解 Kubernetes 如何处理这些组件以及每个抽象层为您的系统提供了什么非常重要。 一些注意事项可以帮助您使用这些抽象中的每一个来确定应用程序的一些自然封装点。
确定容器有效范围的一种方法是寻找自然开发边界。 如果您的系统使用微服务架构运行,则通常会构建精心设计的容器来表示可在各种上下文中使用的离散功能单元。 这种抽象级别允许您的团队发布对容器映像的更改,然后将此新功能部署到使用这些映像的任何环境中。 应用程序可以通过组合单独的容器来构建,每个容器都实现给定的功能,但可能无法单独完成整个过程。
与上述相反,Pod 通常是通过考虑系统的哪些部分可能从独立的 管理 中受益最大来构建的。 由于 Kubernetes 使用 pod 作为其最小的面向用户的抽象,这些是 Kubernetes 工具和 API 可以直接与之交互和控制的最原始的单元。 您可以启动、停止和重新启动 pod,或者使用基于 pod 构建的更高级别的对象来引入复制和生命周期管理功能。 Kubernetes 不允许您独立管理 pod 中的容器,因此您不应该将容器组合在一起,这可能会受益于单独的管理。
由于 Kubernetes 的许多特性和抽象直接处理 pod,因此将应该在单个 pod 中一起扩展的项目捆绑在一起并将应该独立扩展的项目分开是有意义的。 例如,将 Web 服务器与不同 pod 中的应用程序服务器分开,您可以根据需要独立扩展每一层。 但是,如果适配器提供了 Web 服务器正常工作所需的基本功能,那么将 Web 服务器和数据库适配器捆绑到同一个 pod 中是有意义的。
通过捆绑支持容器增强 Pod 功能
考虑到这一点,应该将哪些类型的容器捆绑在一个 pod 中? 通常,主容器负责完成 pod 的核心功能,但可以定义附加容器来修改或扩展主容器或帮助其连接到独特的部署环境。
例如,在 Web 服务器 pod 中,Nginx 容器可能会侦听请求并提供内容,而关联的容器会在存储库更改时更新静态文件。 将这两个组件打包在一个容器中可能很诱人,但是将它们实现为单独的容器有很大的好处。 Web 服务器容器和存储库拉取程序都可以在不同的上下文中独立使用。 它们可以由不同的团队维护,并且每个都可以被开发以概括他们的行为以与不同的配套容器一起工作。
Brendan Burns 和 David Oppenheimer 在他们关于基于容器的分布式系统的 设计模式 的论文中确定了捆绑支持容器的三种主要模式。 这些代表了将容器打包到一个 pod 中的一些最常见的用例:
- Sidecar: 在这种模式中,辅助容器扩展并增强了主容器的核心功能。 此模式涉及在单独的容器中执行非标准或实用功能。 例如,转发日志或监视更新配置值的容器可以在不改变其主要关注点的情况下增强 pod 的功能。
- Ambassador: 大使模式使用补充容器为主容器抽象出远程资源。 主容器直接连接到大使容器,大使容器又连接并抽象出潜在复杂的外部资源池,例如分布式 Redis 集群。 主容器不必知道或关心连接到外部服务的实际部署环境。
- Adaptor: 适配器模式用于转换主容器的数据、协议或接口,以符合外部各方期望的标准。 适配器容器支持对集中式服务的统一访问,即使它们所服务的应用程序可能仅原生支持不兼容的接口。
将配置提取到 ConfigMap 和 Secret
虽然应用程序配置 可以 烘焙到容器映像中,但最好使您的组件在运行时可配置,以支持在多个上下文中部署并允许更灵活的管理。 为了管理运行时配置参数,Kubernetes 提供了两种不同类型的对象,称为 ConfigMaps 和 Secrets。
ConfigMaps 是一种用于存储数据的机制,这些数据可以在运行时暴露给 pod 和其他对象。 存储在 ConfigMaps 中的数据可以作为环境变量呈现,也可以作为文件挂载到 pod 中。 通过将应用程序设计为从这些位置读取,您可以在运行时使用 ConfigMaps 注入配置并修改组件的行为,而无需重建容器映像。
Secrets 是一种类似的 Kubernetes 对象类型,用于安全地存储敏感数据并根据需要选择性地允许 pod 和其他组件访问它。 Secrets 是一种将敏感材料传递给应用程序的便捷方式,无需将它们作为纯文本存储在正常配置中易于访问的位置。 从功能上讲,它们的工作方式与 ConfigMap 非常相似,因此应用程序可以使用相同的机制来使用来自 ConfigMap 和 Secrets 的数据。
ConfigMaps 和 Secrets 帮助您避免将配置参数直接放在 Kubernetes 对象定义中。 您可以映射配置键而不是值,从而允许您通过修改 ConfigMap 或 Secret 来动态更新配置。 这使您有机会在不修改资源的 Kubernetes 定义的情况下更改 pod 和其他 Kubernetes 对象的活动运行时行为。
实施就绪和活跃度探测
Kubernetes 包含许多开箱即用的功能,用于管理组件生命周期并确保您的应用程序始终健康且可用。 然而,要利用这些特性,Kubernetes 必须了解它应该如何监控和解释应用程序的健康状况。 为此,Kubernetes 允许您定义 liveness 和 readiness 探针。
Liveness 探针允许 Kubernetes 确定容器中的应用程序是否处于活动状态并正在运行。 Kubernetes 可以定期在容器内运行命令来检查基本应用程序行为,或者可以将 HTTP 或 TCP 网络请求发送到指定位置,以确定进程是否可用并能够按预期响应。 如果活性探测失败,Kubernetes 会重新启动容器以尝试在 pod 内重新建立功能。
就绪探针是一种类似的工具,用于确定 pod 是否已准备好为流量提供服务。 容器内的应用程序可能需要在准备好接受客户端请求之前执行初始化过程,或者它们可能需要在配置更改时重新加载。 当就绪探测失败时,Kubernetes 不会重新启动容器,而是暂时停止向 pod 发送请求。 这允许 pod 完成其初始化或维护例程,而不会影响整个组的健康。
通过结合 liveness 和 readiness 探针,您可以指示 Kubernetes 自动重新启动 pod 或将它们从后端组中删除。 配置您的基础架构以利用这些功能允许 Kubernetes 管理您的应用程序的可用性和运行状况,而无需额外的操作工作。
使用部署管理规模和可用性
早些时候,在讨论一些 pod 设计基础时,我们提到其他 Kubernetes 对象构建在这些原语之上以提供更高级的功能。 deployment,一个这样的复合对象,可能是最常定义和操作的 Kubernetes 对象。
部署是构建在其他 Kubernetes 原语上以添加额外功能的复合对象。 它们向称为 ReplicaSets 的中间对象添加生命周期管理功能,例如执行滚动更新、回滚到早期版本以及状态之间转换的能力。 这些 ReplicaSet 允许您定义 pod 模板以启动和管理单个 pod 设计的多个副本。 这有助于您轻松扩展基础架构、管理可用性要求并在发生故障时自动重启 Pod。
这些附加功能为基础 pod 层提供了管理框架和自我修复功能。 虽然 pod 是最终运行您定义的工作负载的单元,但它们并不是您通常应该配置和管理的单元。 相反,将 pod 视为一个构建块,当通过部署等更高级别的对象进行配置时,它可以稳健地运行应用程序。
创建服务和入口规则以管理对应用程序层的访问
部署允许您配置和管理一组可互换的 pod,以扩展您的应用程序并满足用户需求。 但是,将流量路由到配置的 pod 是一个单独的问题。 由于 pod 作为滚动更新的一部分被换出、重新启动或由于主机故障而移动,之前与运行组关联的网络地址将发生变化。 Kubernetes services 允许您通过维护动态 pod 池的路由信息和控制对基础架构各个层的访问来管理这种复杂性。
在 Kubernetes 中,服务是控制流量如何路由到一组 pod 的特定机制。 无论是转发来自外部客户端的流量还是管理多个内部组件之间的连接,服务都允许您控制流量的流动方式。 然后,即使环境发生变化和网络地址发生变化,Kubernetes 也会更新和维护将连接转发到相关 pod 所需的所有信息。
在内部访问服务
为了有效地使用服务,您首先必须确定每组 pod 的预期使用者。 如果您的服务仅被部署在您的 Kubernetes 集群中的其他应用程序使用,则 clusterIP 服务类型允许您使用只能从集群内路由的稳定 IP 地址连接到一组 pod。 部署在集群上的任何对象都可以通过将流量直接发送到服务的 IP 地址来与复制的 pod 组进行通信。 这是最直接的服务类型,适用于内部应用层。
一个可选的 DNS 插件使 Kubernetes 能够为服务提供 DNS 名称。 这允许 pod 和其他对象通过名称而不是 IP 地址与服务通信。 这种机制不会显着改变服务的使用,但基于名称的标识符可以更简单地连接组件或定义交互,而不必知道服务 IP 地址。
公开服务供大众消费
如果接口应该是可公开访问的,那么您最好的选择通常是 负载均衡器 服务类型。 这使用您的特定云提供商的 API 来配置负载均衡器,该负载均衡器通过公开的 IP 地址为服务 pod 提供流量。 这允许您将外部请求路由到服务中的 pod,从而为您的内部集群网络提供受控的网络通道。
由于负载均衡器服务类型为每个服务创建一个负载均衡器,因此使用这种方法公开公开 Kubernetes 服务可能会变得昂贵。 为了帮助缓解这种情况,Kubernetes ingress 对象可用于描述如何根据预定的规则集将不同类型的请求路由到不同的服务。 例如,对“example.com”的请求可能会发送到服务 A,而对“sammytheshark.com”的请求可能会路由到服务 B。 Ingress 对象提供了一种描述如何根据预定义模式将混合的请求流逻辑路由到其目标服务的方法。
入口规则必须由 入口控制器 解释 - 通常是某种负载平衡,如 Nginx - 作为 pod 部署在集群中,它实现入口规则并将流量相应地转发到 Kubernetes 服务。 Ingress 实现可用于最小化集群所有者需要运行的外部负载均衡器的数量。
使用声明式语法管理 Kubernetes 状态
Kubernetes 在定义和控制部署到集群的资源方面提供了很大的灵活性。 使用 kubectl
等工具,您可以强制定义临时对象以立即部署到您的集群。 虽然这对于在学习 Kubernetes 时快速部署资源很有用,但这种方法存在一些缺点,使其不适合长期的生产管理。
命令式管理的主要问题之一是它不会留下任何您已部署到集群的更改的记录。 这使得在发生故障时难以或不可能恢复或在将操作更改应用于您的系统时对其进行跟踪。
幸运的是,Kubernetes 提供了另一种声明性语法,允许您在文本文件中完全定义资源,然后使用 kubectl
应用配置或更改。 将这些配置文件存储在版本控制存储库中是监视更改并与用于组织其他部分的审核流程集成的好方法。 基于文件的管理还可以通过复制和编辑现有定义来使现有模式适应新资源。 将 Kubernetes 对象定义存储在版本化目录中,您可以在每个时间点维护所需集群状态的快照。 这在恢复操作、迁移或追踪引入系统的意外更改的根本原因时非常有用。
结论
管理将运行您的应用程序的基础架构并学习如何最好地利用现代编排环境提供的功能可能会令人生畏。 但是,当您的开发和运营实践与工具所围绕的概念保持一致时,Kubernetes 等系统和容器等技术提供的许多好处会变得更加清晰。 使用 Kubernetes 擅长的模式来构建您的系统,并了解某些功能如何缓解复杂部署的挑战,可以改善您在平台上运行的体验。
接下来,您可能想了解 对 Kubernetes 的现有应用程序进行现代化改造 。