为 Kubernetes 构建应用程序

简介

设计和运行考虑到可伸缩性、可移植性和健壮性的应用程序可能具有挑战性,尤其是在系统复杂性增加的情况下。应用程序或系统的体系结构规定了它必须如何运行、它希望从其环境中得到什么,以及它与相关组件的耦合程度。在设计阶段遵循特定的模式并遵循特定的操作实践可以帮助应对应用程序在高度分布式环境中运行时面临的一些最常见的问题。

Docker)和Kubernetes)这样的技术帮助团队打包软件,然后在分布式计算机的平台上分发、部署和扩展。了解如何最好地利用这些工具的功能可以帮助您以更高的灵活性、控制力和响应性来管理应用程序。

在本指南中,我们将讨论您可能想要采用的一些原则和模式,以帮助您扩展和管理Kubernetes上的工作负载。虽然Kubernetes可以运行许多类型的工作负载,但您的选择可能会影响操作的简易性和可用的可能性。

如果您正在寻找托管Kubernetes托管服务,查看我们为growth.构建的简单托管Kubernetes服务

为应用可伸缩性设计

在生产软件时,许多需求会影响您选择采用的模式和体系结构。对于Kubernetes,最重要的因素之一是能够横向扩展 ,增加并行运行的应用程序的相同副本数量,以分布负载和提高可用性。这是一种替代** 垂直伸缩** 的方法,通常指的是增加单个应用堆栈的容量。

特别是,微服务 是一种软件设计模式,可以很好地用于集群上的可扩展部署。开发人员创建小型、可组合的应用程序,这些应用程序通过定义明确的API在网络上通信,而不是通过内部机制通信的大型复合程序。通过将单一应用程序重构为离散的单一用途组件,可以独立扩展每个功能。通常存在于应用程序级别的大部分复杂性和开销被转移到操作领域,在那里可以由Kubernetes等平台进行管理。

除了特定的软件模式,云原生 应用程序在设计时还考虑了一些额外的考虑因素。云本地应用程序通常遵循微服务架构模式,具有内置的弹性、可观察性和管理功能,以最大限度地利用云平台。

例如,使用运行状况报告指标构建云本地应用程序,以便在实例变得不健康时使平台能够管理生命周期事件。它们产生(并可供出口)强大的遥测数据,以提醒操作员注意问题,并使他们能够做出明智的决定。应用程序旨在处理定期重启和故障、后端可用性更改以及高负载,而不会损坏数据或变得无响应。

遵循十二要素应用理念

一种流行的方法可以帮助您专注于创建云就绪Web应用程序时最重要的特征,这就是十二要素应用程序哲学。这些原则最初是为了帮助开发人员和运营团队理解为在云中运行而设计的Web服务所共有的核心品质,现在非常适用于将在Kubernetes这样的集群环境中运行的软件。虽然单片应用程序可以从遵循这些建议中受益,但基于这些原则设计的微服务体系结构工作得特别好。

以下是12个因素的简要总结:

1.代码库: 管理版本控制系统(如Git或Mercurial)中的所有代码。代码库全面规定了部署的内容。 2.依赖项: 依赖项应该完全显式地由代码库管理,可以是提供的(与代码一起存储的),也可以是以包管理器可以安装的格式固定的版本。 3.配置: 将配置参数从应用程序中分离出来,并在部署环境中定义,而不是将其烘焙到应用程序本身。 4.支持服务: 本地和远程服务都抽象为网络可访问资源,连接详情在配置中设置。 5.构建、发布、运行: 应用程序的构建阶段应与应用程序的发布和运营流程完全分开。构建阶段从源代码创建部署构件,发布阶段组合构件和配置,运行阶段执行发布。 6.进程: 应用程序作为进程实现,不应依赖于本地存储状态。应将状态卸载到支持服务,如第四个因素中所述。 7.端口绑定: 应用本机绑定端口,监听连接。路由和请求转发应在外部处理。 8.并发: 应用程序应依赖于通过进程模型进行伸缩。同时运行应用程序的多个副本,可能跨多个服务器,允许在不调整应用程序代码的情况下进行扩展。 9.可丢弃: 进程应该能够快速启动和优雅地停止,而不会产生严重的副作用。 10.开发/生产奇偶校验: 您的测试、试运行和生产环境应紧密匹配并保持同步。环境之间的差异可能会出现不兼容和未经测试的配置。 11.日志: 应用程序应该将日志流到标准输出,以便外部服务可以决定如何最好地处理它们。 12.管理进程: 一次性管理进程应针对特定版本运行,并与主进程代码一起提供。

通过遵循十二要素提供的指导方针,您可以创建和运行非常适合Kubernetes的应用程序。十二个因素鼓励开发人员将重点放在应用程序的主要用途上,考虑组件之间的操作条件和接口,并使用输入、输出和标准流程管理功能在Kubernetes中可预测地运行。

应用组件的集装化

Kubernetes使用容器在其集群节点上运行隔离的打包应用程序。要在Kubernetes上运行,您的应用程序必须封装在一个或多个容器镜像中,并使用Docker等容器运行时执行。尽管组件的容器化是Kubernetes的一项要求,但它也有助于强化上面讨论的十二要素应用程序方法中的许多原则,从而实现更好的伸缩性和管理。

例如,容器在应用程序环境和外部主机系统之间提供隔离。它们支持应用程序间通信的联网方式,通常通过环境变量进行配置,并公开写入stdoutstderr的日志。容器本身鼓励基于进程的并发性,并通过独立可伸缩和捆绑进程的运行时环境来帮助维护开发/生产平衡。这些特性使得打包您的应用程序成为可能,以便它们能够在Kubernetes上流畅地运行。

容器优化指南

容器技术的灵活性允许以多种不同的方式封装应用程序。然而,有些方法在Kubernetes环境中比其他方法工作得更好。

容器化应用程序的大多数最佳实践都与映像构建有关,在映像构建中,您定义了如何从容器中设置和运行软件。一般来说,保持图像大小小而简单提供了许多好处。大小优化的镜像可以通过在镜像更新之间重用现有层来减少在集群上启动新容器所需的时间和资源,Docker和其他容器框架旨在自动完成这一任务。

创建容器映像的第一步是尽最大努力将构建步骤与将在生产中运行的最终映像分开。编译或捆绑软件通常需要额外的工具,花费额外的时间,并产生可能在不同容器之间不一致或对最终运行时环境不必要的构件(例如,跨平台依赖)。将构建过程与运行时环境完全分开的一种方法是使用Docker多阶段builds.多阶段构建配置允许您指定一个基本映像以在构建过程中使用,并定义另一个以在运行时使用。这使得可以使用安装了所有构建工具的映像来构建软件,并将生成的构件复制到一个精简的映像中,该映像将在每次使用时使用。

有了这类可用的功能,在最小化的父映像之上构建生产映像通常是一个好主意。如果你想完全避免像ubuntu:20.04这样的)中的膨胀,你可以用scratch - Docker最小的基础镜像-作为父层来构建镜像。然而,)alpine镜像已经变得流行,因为它是一个坚实的、最小的基础环境,提供了一个小型但功能齐全的Linux发行版。

对于像Python或Ruby这样的解释型语言,范例稍有变化,因为没有编译阶段,而且解释器必须可用于在生产环境中运行代码。然而,由于纤薄的图像仍然是理想的,所以在Docker Hub.)上提供了许多构建在阿尔卑斯Linux之上的特定语言的、优化的图像对解释语言使用较小图像的好处与对编译语言使用较小图像的好处相似:Kubernetes将能够快速将所有必要的容器图像拖到新节点上,以开始进行有意义的工作。

确定容器和Pod的范围

虽然您的应用程序必须容器化才能在Kubernetes集群上运行,但Pod 是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的核心功能,但可以定义其他容器来修改或扩展主容器或帮助其连接到唯一的部署环境。

例如,在Web服务器Pod中,当存储库发生变化时,Nginx容器可能会监听请求并提供内容,而关联的容器则会更新静态文件。将这两个组件打包到一个容器中可能很诱人,但将它们实现为单独的容器有很大的好处。Web服务器容器和存储库拉出器都可以在不同的上下文中独立使用。它们可以由不同的团队维护,并且可以各自开发来概括它们的行为,以便与不同的配套容器一起工作。

Brendan Burns和David Oppenheimer在他们的论文Design Patterns for Container-Based Distributed systems.》中指出了捆绑支持容器的三种主要模式这些代表了将容器一起包装在Pod中的一些最常见的用例:

*Sidecar: 在此模式中,辅助容器扩展并增强了主容器的核心功能。此模式涉及在单独的容器中执行非标准或实用程序函数。例如,转发日志或监视更新的配置值的容器可以在不改变其主要焦点的情况下增强pod的功能。 *大使: 大使模式使用辅助容器为主容器抽象远程资源。主容器直接连接到大使容器,大使容器又连接到并抽象潜在复杂外部资源池,如分布式Redis集群。主容器不必知道或关心连接到外部服务的实际部署环境。 *适配器: 适配器模式用于转换主容器的数据、协议或接口,以符合外部各方期望的标准。适配器容器支持对集中式服务的统一访问,即使它们所服务的应用程序可能只支持不兼容的接口。

将配置提取到配置映射和密钥中

虽然应用程序配置可以被烘焙到容器镜像中,但最好使您的组件在运行时可配置,以支持在多个上下文中的部署,并允许更灵活的管理。为了管理运行时配置参数,Kubernetes提供了两种不同类型的对象:ConfigMaps 和** Secrets** 。

ConfigMap是一种用于存储数据的机制,这些数据可以在运行时公开给Pod和其他对象。存储在ConfigMaps中的数据可以作为环境变量呈现,也可以作为Pod中的文件装载。通过将应用程序设计为从这些位置读取,您可以在运行时使用ConfigMaps注入配置并修改组件的行为,而不必重新构建容器映像。

Secret是一种类似的Kubernetes对象类型,用于安全地存储敏感数据,并根据需要选择性地允许Pod和其他组件访问它。机密是一种将敏感材料传递给应用程序的便捷方式,而无需将它们以纯文本形式存储在正常配置中易于访问的位置。在功能上,它们的工作方式与ConfigMaps基本相同,因此应用程序可以使用相同的机制使用ConfigMaps和Secrets中的数据。

ConfigMaps和Secrets帮助您避免将配置参数直接放入Kubernetes对象定义中。您可以映射配置密钥而不是值,从而允许您通过修改ConfigMap或Secret来动态更新配置。这使您有机会更改Pod和其他Kubernetes对象的活动运行时行为,而无需修改资源的Kubernetes定义。

实现就绪和活跃度探测

Kubernetes包括大量开箱即用的功能,用于管理组件生命周期并确保您的应用程序始终健康且可用。然而,要利用这些功能,Kubernetes必须了解它应该如何监视和解释您的应用程序的运行状况。为此,Kubernetes允许您定义活跃度 和** 就绪** 探测。

Liveness探针允许Kubernetes确定容器中的应用程序是否处于活动状态并正在积极运行。Kubernetes可以定期在容器中运行命令以检查基本的应用程序行为,或者可以将HTTP或TCP网络请求发送到指定位置,以确定进程是否可用并能够按预期响应。如果liveness探测失败,Kubernetes会重新启动容器,尝试在pod中重新建立功能。

就绪探头是用于确定Pod是否已准备好为流量提供服务的类似工具。容器中的应用程序可能需要在它们准备好接受客户端请求之前执行初始化过程,或者它们可能需要在配置更改后重新加载。当就绪探测失败时,Kubernetes将暂时停止向实例发送请求,而不是重启容器。这允许吊舱完成其初始化或维护例程,而不会影响整个组的健康。

通过活跃度探测和就绪探测相结合,您可以指示Kubernetes自动重启实例或将其从后端组中移除。通过配置您的基础设施以利用这些功能,Kubernetes无需额外的操作工作即可管理应用程序的可用性和运行状况。

使用Deployment管理规模和可用性

早些时候,在讨论一些Pod设计基础时,我们提到了其他Kubernetes对象构建在这些原语之上,以提供更高级的功能。部署 ,一个这样的复合对象,可能是最常用的定义和操作的Kubernetes对象。

部署是构建在其他Kubernetes原语之上以添加附加功能的复合对象。它们为称为ReplicaSets 的中间对象添加了生命周期管理功能,例如执行滚动更新、回滚到较早版本以及在状态之间转换的功能。这些ReplicaSet允许您定义Pod模板,以启动和管理单个Pod设计的多个副本。这有助于您轻松扩展基础设施、管理可用性要求,并在出现故障时自动重启Pod。

这些附加功能为基础Pod层提供了管理框架和自我修复功能。虽然Pod是最终运行您定义的工作负载的单元,但它们不是您通常应该配置和管理的单元。相反,可以将Pod视为一个构建块,当通过部署等更高级别的对象进行配置时,它可以健壮地运行应用程序。

创建管理应用层访问的服务和入口规则

部署允许您调配和管理多组可互换Pod,以扩展您的应用程序并满足用户需求。但是,将流量路由到配置的Pod是另一个需要考虑的问题。当Pod作为滚动更新的一部分被换出、重新启动或由于主机故障而被移动时,以前与运行组相关联的网络地址将改变。Kubernetes服务 允许您通过维护动态实例池的路由信息并控制对基础设施各层的访问来管理这种复杂性。

在Kubernetes中,服务是控制如何将流量路由到Pod集的特定机制。无论是转发来自外部客户端的流量,还是管理几个内部组件之间的连接,服务都允许您控制流量的流动方式。然后,Kubernetes将更新和维护将连接转发到相关吊舱所需的所有信息,即使环境发生变化和网络寻址发生变化也是如此。

内部访问服务

要有效地使用服务,您必须首先确定每组Pod的目标消费者。如果您的服务仅供Kubernetes集群内部署的其他应用使用,clusterIP 服务类型允许您使用稳定的IP地址连接到一组实例,该IP地址仅在集群内可路由。部署在集群上的任何对象都可以通过将流量直接发送到服务的IP地址来与复制的POD组进行通信。这是最直接的服务类型,适用于内部应用程序层。

可选的dns插件使Kubernetes能够为服务提供dns名称。这允许Pod和其他对象通过名称而不是IP地址与服务通信。这种机制不会显著改变服务的使用情况,但基于名称的标识符可使挂钩组件或定义交互变得更简单,而不必知道服务IP地址。

公开公共消费服务

如果接口需要公开访问,您的最佳选择通常是负载均衡 服务类型。这使用您特定的云提供商的API来提供负载均衡器,该均衡器通过公开的IP地址向服务Pod提供流量。这允许您将外部请求路由到您的服务中的Pod,为您的内部集群网络提供受控的网络通道。

由于负载均衡器服务类型为每个服务创建负载均衡器,因此使用此方法公开Kubernetes服务可能会变得昂贵。为了帮助缓解这一问题,Kubernetes ingress 对象可以用来描述如何根据预定义的规则集将不同类型的请求路由到不同的服务。例如,对example.com的请求可能转到服务A,而对sammytheshark.com的请求可能被路由到服务B。Ingress对象提供了一种描述如何基于预定义模式将混合请求流逻辑路由到其目标服务的方法。

入口规则必须由入口控制器 解释,通常是部署在集群内的某种负载均衡,如Nginx,它实现入口规则并相应地将流量转发到Kubernetes服务。入口实现可用于最大限度地减少集群所有者需要运行的外部负载平衡器的数量。

使用声明性语法管理Kubernetes状态

Kubernetes在定义和控制部署到集群的资源方面提供了相当大的灵活性。使用kubectl这样的工具,您可以强制定义即席对象以立即部署到您的集群。虽然这对于在学习Kubernetes时快速部署资源很有用,但这种方法存在缺陷,不适合长期的生产管理。

命令式管理的主要问题之一是,它不会为您部署到集群的更改留下任何记录。这使得很难或不可能在发生故障时进行恢复,或者跟踪应用到您的系统中的操作更改。

幸运的是,Kubernetes提供了一种替代的声明性语法,允许您在文本文件中完整定义资源,然后使用kubectl应用配置或更改。将这些配置文件存储在版本控制存储库中是监视更改并与用于组织其他部分的审查过程集成的好方法。基于文件的管理还使通过复制和编辑现有定义使现有模式适应新资源成为可能。将Kubernetes对象定义存储在版本化目录中允许您在每个时间点维护所需集群状态的快照。在恢复操作、迁移期间,或者在跟踪引入系统的意外更改的根本原因时,这可能是无价的。

结论

管理将运行应用程序的基础设施以及学习如何最好地利用现代编排环境提供的功能可能是令人生畏的。然而,当您的开发和运营实践与工具构建的概念相一致时,Kubernetes等系统和容器等技术提供的许多好处变得更加清晰。使用Kubernetes擅长的模式构建系统,并了解某些功能如何减轻复杂部署的挑战,可以改善您在平台上的运行体验。

接下来,您可能需要阅读为Kubernetes.实现现有应用程序的现代化

Published At
Categories with 技术
comments powered by Disqus