当前位置: 首页 > 科技观察

第一次部署Kubernetes应用时总是忽略这些东西

时间:2023-03-19 17:40:12 科技观察

根据我的个人经验,大多数人似乎更喜欢通过Helm或手动将应用dump到Kubernetes,然后坐以待毙轻松的日常通话。但是,在GumGum的实践中,我们经历了一系列Kubernetes应用的“陷阱”,希望将这些陷阱分享给大家,为大家的Kubernetes探索之旅带来一些启发。1.配置Pod请求和限制我们首先配置一个可以运行Pod的简单环境。Kubernetes非常擅长处理pod调度和故障状态,但我们也意识到,如果Kubernetes调度程序无法准确测量pod成功运行需要多少资源,部署有时会充满挑战。而这个挑战也是资源请求和限制机制设计的根源。目前,关于设置应用程序请求和限制的最佳实践仍有很多争论。事实上,这项工作与其说是一门纯科学,不如说是一门艺术。接下来谈谈GumGum内部对这个问题的思考:PodRequests:这是调度器用来衡量部署Pod的最佳方式的主要指标。以下是Kubernetes文档中的相关内容:过滤步骤会在可行的情况下找到一组Pod。例如,PodFitsResources过滤器检查候选节点是否有足够的可用资源来满足来自Pod的特定资源请求。在内部,我们使用应用程序请求的方式是,通过设置,我们可以估算应用程序正常运行真实工作负载时的资源需求。基于此,调度器可以更合理地放置节点。最初,我们希望将请求设置得更高,以确保每个Pod都有足够的资源。但是我们很快发现这种方式会大大增加调度时间,导致部分Pod无法被完全调度。结果实际上类似于我们根本不指定资源请求时看到的结果:在后一种情况下,由于控制平面不知道应用程序需要多少资源,因此调度程序通常会在没有的情况下“驱逐”Pod然后重新安排。正是这个调度算法的关键组成部分阻碍了我们得到预期的调度效果。Podlimit:是对Pod的直接限制,代表集群允许每个容器使用的最大资源量。另请看官方文档中的描述:如果您为容器设置了4GiB的内存限制,则kubelet(带有容器运行时)将强制执行此限制。运行时将阻止容器使用超出配置上限的资源容量。例如,当容器中的进程消耗的内存超过允许的内存时,系统内核会终止该资源分配尝试并出现内存不足(OOM)错误。容器实际使用的资源量可以高于其请求,但绝不会高于配置的上限。显然,正确设置限价指标是相当困难的,但也非常重要。理想情况下,我们希望允许Pod的资源需求在整个进程生命周期中发生变化,而不会干扰系统上的其他进程——这就是节流机制的用途。遗憾的是,我们无法明确给出最合适的设置,但可以按照以下过程进行调优:使用负载测试工具,我们可以模拟一个基线流量水平,观察Pod的资源使用情况(包括内存和CPU)。我们将pod请求设置在非常低的水平,同时将pod资源限制保持在请求值的5倍左右,并观察其行为。当请求太低时,进程将无法启动,不时抛出神秘的Go运行时错误。这里需要强调的是,资源约束越严格,Pod调度就越困难。这是因为Pod调度需要目标节点有足够的资源。例如,如果您的资源非常有限(只有4GB内存),即使是轻量级的Web服务器进程也很难运行。在这种情况下,您需要横向扩展,并且每个新容器还应该在同样具有至少4GB可用内存的节点上运行。如果不存在这样的节点,则需要在集群中引入一个新的节点来处理pod,这无疑会增加启动时间。简而言之,一定要找到资源请求和限制之间的最小“边界”,以确保快速和平衡的扩展。2.配置Liveness和ReadinessProbesKubernetes社区中经常讨论的另一个有趣的话题是如何配置Liveness和ReadinessProbes。正确使用这两个探测器为我们提供了一种以最少的停机时间运行容错软件的机制。但如果配置不正确,它们也会对您的应用程序产生严重的性能影响。下面我们来看一下这两个探针的基本情况以及如何使用:Liveness探针:“用于指示容器是否正在运行。如果Liveness探测失败,kubelet将关闭容器并且容器将开始执行重启策略。如果容器不提供Liveness探测,则其默认状态被认为是成功的。”—Kubernetes文档Liveness探针的资源要求必须很低,因为它们需要经常运行,并且需要在应用程序运行时向Kubernetes发出通知。请注意,如果将其设置为每秒运行一次,系统将需要引发额外的请求每秒处理1个。因此,重要的是要仔细考虑如何处理这些额外的请求和相应的资源。在GumGum,我们将Liveness探针设置为在应用程序的主要组件运行时响应,而不管数据是它完全可用(例如来自远程数据库或缓存的数据)。例如,我们将在应用程序中有一个特定的“健康”端点,它专门负责返回200响应代码。只要响应仍在返回,表示进程已经准备好处理请求(但还没有正式产生流量).Readinessprobe:"表示容器是否准备好h处理请求。如果就绪探测失败,端点控制器将从与该pod匹配的所有服务端点中删除该pod的IP地址。“Readiness探测的运行成本要高得多,因为它的作用是不断通知后端整个应用程序已启动并准备好接收请求。社区中有很多关于这个探测是否应该命中数据库的争论。考虑由于由于Readiness探针(需要频繁运行,但频率可以灵活调整)带来的开销,我们决定在某些应用中,我们只在从数据库返回记录后才开始“提供流量”。通过精心设计通过Readiness探针,我们已经能够实现更高级别的可用性以及零停机部署。但是如果确实需要通过应用的Readiness探针随时查看数据库请求的就绪状态,请尽量控制查询操作的资源占用,例如...下面的SELECTsmall_itemFROMtableLIMIT1就是我们指定的对于这两个探测器在Kubernetes配置值:livenessProbe:httpGet:path:/api/livenessport:httpreadinessProbe:httpGet:path:/api/readinessport:httpperiodSeconds:2你还可以添加一些其他配置选项:initialDelaySeconds-多少秒后容器启动后,探测器开始实际运行periodSeconds-两次探测器之间的等待间隔timeoutSeconds-在确定Pod处于故障状态之前应该经过多少秒。相当于传统的超时指示器failureThreshold-多少次探测失败后才向Pod发送重启信号successThreshold-多少次探测成功后才能确定Pod准备就绪。设置默认Pod网络策略Kubernetes使用“扁平”网络拓扑;默认情况下,所有pod都可以直接相互通信。但结合实际用例,这种通信能力往往是不必要的,甚至是不可接受的。其中一个潜在的安全隐患是,如果一个易受攻击的应用程序被利用,攻击者可以获得完全访问权限,将流量发送到网络上的所有pod。因此,也需要在Pod网络中应用最少访问原则,理想情况下通过网络策略明确规定允许哪些容器建立相互连接。以下面的简单策略为例,你可以看到它会拒绝特定命名空间中的所有入口流量:---apiVersion:networking.k8s.io/v1kind:NetworkPolicymetadata:name:default-deny-ingressspec:podSelector:{}policyTypes:-Ingress4。通过Hooks和Init容器的自定义行为我们希望在Kubernetes系统中实现的核心目标之一是尝试为现有开发人员提供几乎零停机时间的部署支持。但不同的应用程序往往有不同的关机方式和资源清理过程,因此零停机的总体目标很难实现。首先摆在我们面前的就是Nginx的难度。我们注意到,当开始滚动部署pod时,活动连接在成功终止之前被丢弃。经过广泛的在线研究,事实证明Kubernetes在终止Pod之前并没有等待Nginx耗尽其连接资源。使用预停止挂钩,我们能够注入此功能,从而实现零停机时间。生命周期:preStop:exec:command:["/usr/local/bin/nginx-killer.sh"]isnginx-killer.sh:#!/bin/bashsleep3PID=$(cat/run/nginx.pid)nginx-squitwhile[-d/过程/$PID];doecho"Waitingwhileshuttingdownnginx..."sleep10done另一个实际例子是通过Init容器来处理具体应用的启动任务。一些流行的Kubernetes项目也使用Istio等初始化容器将Envoy处理代码注入到Pod中。如果您需要在应用程序启动之前完成繁重的数据库迁移工作,则Init容器特别有用。您还可以为此进程设置更高的资源上限,使其独立于主应用程序设置的限制。另一种常见的模式是向init-conatiner提供对机密的访问,并且容器将这些凭据发布到主Pod,防止从主应用程序Pod本体中授权访问机密。另请参阅文档中的声明:Init容器可以安全地运行实用程序或自定义代码,防止其破坏应用程序容器映像的安全性。通过剥离这些不必要的工具,您可以限制应用程序容器映像的攻击面。5.内核调优最后,让我们谈谈一项最先进的技术。Kubernetes本身是一个高度灵活的平台,可帮助您以最适合自己的方式运行工作负载。在GumGum,我们有几个对运行资源要求极其苛刻的高性能应用程序。经过广泛的负载测试后,我们发现一个应用程序难以使用Kubernetes默认设置处理必要的流量负载。但是Kubernetes允许我们通过修改它来配置适合特定Pod的内核运行参数来运行高权限容器。通过下面的示例代码,我们修改Pod的最大打开连接数:initContainers:-name:sysctlimage:alpine:3.10securityContext:privileged:truecommand:['sh','-c',"sysctl-wnet.core.somaxconn=32768"]这是一种高级技术,使用频率较低。如果您的应用程序在高负载场景下难以健康运行,您可能需要调整其中一些参数。建议参考官方文档中参数调优和可选值的相关细节。6.结论虽然Kubernetes已经被认为是一个几乎“开箱即用”的解决方案,但仍然有一系列关键步骤需要大家采取,以确保应用程序的平衡运行。在将应用程序迁移到Kubernetes的整个过程中,关注负载测试“循环”非常重要——运行应用程序,对其进行负载测试,观察指标和扩展行为,根据结果调整配置,然后重复。尝试尽可能客观地设置预期流量,并尝试将流量增加到超限水平以查看哪些组件会首先崩溃。使用这种迭代方法,您可能只需要执行本文中描述的一些步骤就可以让您的应用程序按预期工作。总之,始终关注以下核心问题:我的应用程序占用的资源是多少?入住率将如何变化?服务的实际扩展要求是什么?预计要处理的平均流量是多少?什么是峰值流量水平?服务需要多久扩展一次?新的pod需要多长时间才能正式开始接收流量?我们的Pod终止过程是否优雅且易于管理?需要这种优雅和控制吗?我们能否实现零停机部署?您如何最大限度地降低安全风险并限制Pod入侵情况的“爆炸半径”(影响范围)?服务中是否存在一些不必要的权限或访问能力?Kubernetes是一个令人印象深刻的强大平台,您可以在其中使用最佳实践在集群中部署数千个服务。但是不同的软件之间总是存在差异的,有时你的应用程序可能需要进一步调整。幸运的是,Kubernetes为我们提供了很多调整“旋钮”,让用户尽可能轻松地达到符合预期的技术目标。结合资源请求和限制、许可和就绪检查、初始化容器、网络策略和自定义内核调优方法,相信您可以在Kubernetes平台上实现更好的基线性能、弹性和快速扩展能力。