在 Kubernetes 上部署第一个应用程序时的五个失误

在 Kubernetes 上部署第一个应用程序时的五个失误Aris Dreamer 的失败

许多人认为将应用程序转移到 Kubernetes 上(使用 Helm 或手动)就足够了——而且会有快乐。 但并非一切都那么简单。

团队 Mail.ru 云解决方案 翻译了 DevOps 工程师 Julian Gindy 的一篇文章。 他讲述了他的公司在迁移过程中遇到的陷阱,以免您重蹈覆辙。

第一步:设置 Pod 请求和限制

让我们从设置一个干净的环境开始,我们的 pod 将在其中运行。 Kubernetes 擅长 Pod 调度和故障转移。 但事实证明,如果很难估计它需要多少资源才能成功工作,调度程序有时无法放置 Pod。 这是弹出资源和限制请求的地方。 关于设置请求和限制的最佳方法存在很多争论。 有时这似乎更像是一门艺术而不是一门科学。 这是我们的方法。

Pod 请求 是调度程序用来优化放置 pod 的主要值。

Kubernetes 文档:过滤器步骤定义了一组可以调度 Pod 的节点。 例如,PodFitsResources 过滤器检查节点是否有足够的资源来满足来自 pod 的特定资源请求。

我们以可以估计有多少资源的方式使用应用程序请求 其实 应用程序需要它才能正常运行。 这样调度器就可以真实地放置节点。 最初我们想超调度请求,保证每个Pod有足够的资源,但是发现调度时间明显增加,有些Pod没有调度满,就好像没有资源请求一样。

在这种情况下,调度器通常会“挤掉”pod 而无法重新调度它们,因为控制平面不知道应用程序需要多少资源,而这是调度算法的关键组成部分。

Pod 限制 是对 pod 的更明确的限制。 它表示集群将分配给容器的最大资源量。

再次,从 官方文档:如果容器的内存限制为 4 GiB,那么 kubelet(和容器运行时)将强制执行。 运行时防止容器使用超过指定的资源限制。 例如,当容器中的进程尝试使用超过允许的内存量时,系统内核会终止进程并显示“内存不足”(OOM) 错误。

容器总是可以使用比资源请求指定的更多的资源,但它永远不能使用超过限制。 这个值很难正确设置,但却很重要。

理想情况下,我们希望一个 pod 的资源需求在一个进程的生命周期内发生变化,而不干扰系统中的其他进程——这就是设置限制的目的。

不幸的是,我无法具体说明要设置什么值,但我们自己遵守以下规则:

  1. 使用负载测试工具,我们模拟基本级别的流量并观察 pod 资源(内存和处理器)的使用情况。
  2. 将 pod 请求设置为任意低的值(资源限制约为请求值的 5 倍)并观察。 当请求级别太低时,进程无法启动,通常会导致神秘的 Go 运行时错误。

我注意到更高的资源限制使调度变得更加困难,因为 pod 需要一个具有足够可用资源的目标节点。

想象一下您有一个资源限制非常高的轻量级 Web 服务器的情况,例如 4 GB 内存。 这个过程可能需要横向扩展,每个新的 pod 都需要调度到至少有 4 GB 可用内存的节点上。 如果不存在这样的节点,集群必须引入一个新节点来处理这个pod,这可能需要一些时间。 重要的是要实现资源请求和限制之间的最小差异,以确保快速平稳地扩展。

第二步:设置 Liveness 和 Readiness 测试

这是 Kubernetes 社区中经常讨论的另一个微妙话题。 充分了解 Liveness 和 Readiness 测试非常重要,因为它们提供了一种机制来稳定软件运行并最大限度地减少停机时间。 但是,如果配置不正确,它们会严重影响应用程序的性能。 以下是两个示例的摘要。

活泼 显示容器是否正在运行。 如果失败,kubelet 会终止容器并为其启用重启策略。 如果容器未配备 Liveness Probe,则默认状态为成功 - 如中所述 Kubernetes 文档.

Liveness probes 应该很便宜,即不会消耗大量资源,因为它们经常运行并且应该通知 Kubernetes 应用程序正在运行。

如果您将选项设置为每秒运行一次,这将每秒增加 1 个请求,因此请注意处理此流量需要额外的资源。

在我们公司,Liveness 测试测试应用程序的核心组件,即使数据(例如,来自远程数据库或缓存)不完全可用。

我们在应用程序中设置了一个“健康”端点,它只返回一个 200 响应代码。这表明进程正在运行并且能够处理请求(但还不能处理流量)。

测试 准备就绪 指示容器是否准备好处理请求。 如果就绪探测失败,则端点控制器会从与该 pod 匹配的所有服务的端点中删除该 pod 的 IP 地址。 这在 Kubernetes 文档中也有说明。

就绪探测会消耗更多资源,因为它们必须以某种方式命中后端以表明应用程序已准备好接受请求。

关于是否直接访问数据库,社区中有很多争论。 考虑到开销(检查很频繁,但可以控制),我们决定对于某些应用程序,仅在检查从数据库返回记录后才计算服务流量的准备情况。 精心设计的就绪试验确保了更高级别的可用性并消除了部署期间的停机时间。

如果您决定查询数据库来测试您的应用程序是否准备就绪,请确保它尽可能便宜。 让我们接受这个查询:

SELECT small_item FROM table LIMIT 1

下面是我们如何在 Kubernetes 中配置这两个值的例子:

livenessProbe: 
 httpGet:   
   path: /api/liveness    
   port: http 
readinessProbe:  
 httpGet:    
   path: /api/readiness    
   port: http  periodSeconds: 2

您可以添加一些额外的配置选项:

  • initialDelaySeconds - 在容器启动和探测器启动之间经过多少秒。
  • periodSeconds — 样本运行之间的等待间隔。
  • timeoutSeconds — 秒数后 pod 被认为是紧急的。 正常超时。
  • failureThreshold 是在将重启信号发送到 pod 之前测试失败的次数。
  • successThreshold 是 pod 转换到就绪状态之前的成功试验次数(在 pod 启动或恢复失败后)。

第三步:设置 Pod 的默认网络策略

Kubernetes 具有“平坦”的网络拓扑结构,默认情况下所有 Pod 都直接相互通信。 在某些情况下,这是不可取的。

一个潜在的安全问题是攻击者可以使用单个易受攻击的应用程序将流量发送到网络上的所有 pod。 与许多安全领域一样,此处适用最小特权原则。 理想情况下,网络策略应明确说明允许和不允许 pod 之间的哪些连接。

例如,以下是拒绝特定命名空间的所有传入流量的简单策略:

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:  
 name: default-deny-ingress
spec:  
 podSelector: {}  
 policyTypes:  
   - Ingress

此配置的可视化:

在 Kubernetes 上部署第一个应用程序时的五个失误
(https://miro.medium.com/max/875/1*-eiVw43azgzYzyN1th7cZg.gif)
更多细节 这里.

第四步:使用钩子和初始化容器的自定义行为

我们的主要目标之一是在 Kubernetes 中为开发人员提供无需停机的部署。 这很难,因为关闭应用程序和释放其已用资源的选项有很多。

出现了特别的困难 Nginx的. 我们注意到,当按顺序部署这些 Pod 时,活动连接在成功完成之前被中断。

在互联网上进行了大量研究后发现,Kubernetes 在关闭 pod 之前不会等待 Nginx 连接耗尽。 在pre-stop hook的帮助下,我们实现了如下功能,彻底摆脱了宕机:

lifecycle: 
 preStop:
   exec:
     command: ["/usr/local/bin/nginx-killer.sh"]

nginx-killer.sh:

#!/bin/bash
sleep 3
PID=$(cat /run/nginx.pid)
nginx -s quit
while [ -d /proc/$PID ]; do
   echo "Waiting while shutting down nginx..."
   sleep 10
done

另一个非常有用的范例是使用 init 容器来处理特定应用程序的启动。 如果您有一个必须在应用程序启动之前运行的资源密集型数据库迁移过程,这将特别有用。 您还可以为此进程指定更高的资源限制,而无需为主应用程序设置此类限制。

另一种常见方案是访问 init 容器中的秘密,它向主模块提供这些凭据,从而防止从主应用程序模块本身未经授权访问秘密。

像往常一样,引用文档: init 容器安全地运行用户代码或实用程序,否则这些代码或实用程序会危及应用程序容器映像的安全性。 通过将不必要的工具分开,您可以限制应用程序容器映像的攻击面。

第五步:内核配置

最后,让我们谈谈更高级的技术。

Kubernetes 是一个极其灵活的平台,允许您以您认为合适的方式运行工作负载。 我们有许多非常耗费资源的高效应用程序。 在进行大量负载测试后,我们发现当默认 Kubernetes 设置生效时,其中一个应用程序很难跟上预期的流量负载。

但是,Kubernetes 允许您运行一个特权容器,该容器只更改特定 pod 的内核参数。 这是我们用来更改最大打开连接数的内容:

initContainers:
  - name: sysctl
     image: alpine:3.10
     securityContext:
         privileged: true
      command: ['sh', '-c', "sysctl -w net.core.somaxconn=32768"]

这是一种通常不需要的更高级的技术。 但是,如果您的应用程序正在努力应对繁重的负载,您可以尝试调整其中的一些设置。 有关此过程和设置不同值的更多信息 - 一如既往 在官方文档中.

总之

虽然 Kubernetes 看起来像是一个开箱即用的解决方案,但必须采取几个关键步骤来保持应用程序平稳运行。

在迁移到 Kubernetes 的整个过程中,遵循“负载测试周期”非常重要:运行应用程序,在负载下测试它,观察指标和扩展行为,根据这些数据调整配置,然后再次重复这个周期。

对预期流量要切合实际,并尝试超越它以查看哪些组件首先损坏。 使用这种迭代方法,仅列出的一些建议可能就足以取得成功。 或者可能需要更深入的定制。

总是问自己这些问题:

  1. 应用程序消耗了多少资源,这个数量将如何变化?
  2. 真正的缩放要求是什么? 该应用程序平均处理多少流量? 高峰流量怎么办?
  3. 服务需要多长时间扩展一次? 新 pod 需要多快启动并运行才能接收流量?
  4. Pod 如何优雅地关闭? 有必要吗? 是否可以在不停机的情况下实现部署?
  5. 如何最大限度地降低安全风险并限制任何受感染的 pod 造成的损害? 是否有任何服务拥有不需要的权限或访问权限?

Kubernetes 提供了一个令人难以置信的平台,允许您使用最佳实践在集群中部署数千个服务。 但是,所有应用程序都是不同的。 有时实施需要更多的工作。

幸运的是,Kubernetes 提供了实现所有技术目标所需的设置。 通过结合使用资源请求和限制、Liveness 和 Readiness 探测器、init 容器、网络策略和自定义内核调整,您可以实现高性能以及容错和快速可扩展性。

还有什么要读的:

  1. 在生产环境中运行容器和 Kubernetes 的最佳实践和最佳实践.
  2. 90 多种 Kubernetes 有用工具:部署、管理、监控、安全等.
  3. 我们在 Telegram 中围绕 Kubernetes 的频道.

来源: habr.com

添加评论