使用 Kubernetes 时的 10 个常见错误

笔记。 翻译。:本文作者是捷克一家小公司pipetail的工程师。 他们设法整理了一份精彩的清单,其中列出了与 Kubernetes 集群操作相关的[有时很平庸,但仍然]非常紧迫的问题和误解。

使用 Kubernetes 时的 10 个常见错误

在使用 Kubernetes 的这些年里,我们使用了大量的集群(包括托管和非托管 - 在 GCP、AWS 和 Azure 上)。 随着时间的推移,我们开始注意到一些错误不断重复。 然而,这并没有什么可耻的:其中大部分都是我们自己完成的!

本文包含最常见的错误,并提到如何纠正这些错误。

1. 资源:请求和限制

这个项目绝对值得最密切的关注并排在列表的第一位。

通常CPU请求 要么根本没有指定,要么具有非常低的值 (在每个节点上放置尽可能多的 Pod)。 因此,节点变得过载。 在高负载期间,节点的处理能力得到充分利用,特定工作负载仅接收其“请求”的内容 CPU节流。 这会导致应用程序延迟增加、超时和其他令人不快的后果。 (在我们最近的其他翻译中了解更多相关信息:“Kubernetes 中的 CPU 限制和主动限制“ - 大约。 译)

最大的努力 (极其 没有 受到推崇的):

resources: {}

极低的CPU请求(极 没有 受到推崇的):

   resources:
      Requests:
        cpu: "1m"

另一方面,CPU 限制的存在可能会导致 Pod 不合理地跳过时钟周期,即使节点处理器未满载也是如此。 同样,这可能会导致延误增加。 围绕参数的争议仍在继续 CPU CFS 配额 在 Linux 内核中,CPU 节流取决于设置的限制,以及禁用 CFS 配额……唉,CPU 限制可能会导致比其所能解决的问题更多的问题。 有关此内容的更多信息可以在下面的链接中找到。

选择过多 (过度投入) 记忆力问题可能会导致更大的问题。 达到 CPU 限制需要跳过时钟周期,而达到内存限制则需要杀死 pod。 你有没有观察过 OOMkill? 是的,这正是我们正在讨论的。

您想尽量减少这种情况发生的可能性吗? 不要过度分配内存,并通过将内存请求设置为限制来使用保证的 QoS(服务质量)(如下例所示)。 阅读有关此内容的更多信息 亨宁·雅各布斯演讲 (Zalando 首席工程师)。

爆裂 (被 OOMkilled 的几率更高):

   resources:
      requests:
        memory: "128Mi"
        cpu: "500m"
      limits:
        memory: "256Mi"
        cpu: 2

保证:

   resources:
      requests:
        memory: "128Mi"
        cpu: 2
      limits:
        memory: "128Mi"
        cpu: 2

设置资源时什么可能有帮助?

指标服务器 您可以查看 Pod(以及其中的容器)当前的 CPU 资源消耗和内存使用情况。 最有可能的是,您已经在使用它了。 只需运行以下命令:

kubectl top pods
kubectl top pods --containers
kubectl top nodes

但是,它们仅显示当前的使用情况。 它可以让您粗略地了解数量级,但最终您需要 指标随时间变化的历史记录 (回答诸如“CPU 峰值负载是多少?”、“昨天早上的负载是多少?”等问题)。 为此,您可以使用 普罗米修斯, DataDog 和其他工具。 他们只是从指标服务器获取指标并存储它们,用户可以查询它们并相应地绘制它们。

VerticalPodAutoscaler 它允许 自动化 这个流程。 它跟踪 CPU 和内存使用历史记录,并根据此信息设置新的请求和限制。

有效地利用计算能力并不是一件容易的事。 就像一直在玩俄罗斯方块一样。 如果您为平均消耗较低(例如约 10%)的计算能力支付过高费用,我们建议您考虑基于 AWS Fargate 或 Virtual Kubelet 的产品。 它们建立在无服务器/按使用付费的计费模型之上,在这种情况下可能会更便宜。

2. 活跃度和就绪度探测

默认情况下,Kubernetes 中未启用活动性和就绪性检查。 有时他们忘记打开它们......

但是,如果发生致命错误,您还能如何启动服务重新启动呢? 负载均衡器如何知道 Pod 已准备好接受流量? 或者它可以处理更多流量?

这些测试经常相互混淆:

  • 活泼 —“生存性”检查,如果失败则重新启动 pod;
  • 准备就绪 — 准备情况检查,如果失败,它会断开 pod 与 Kubernetes 服务的连接(可以使用以下命令进行检查) kubectl get endpoints)并且在下一次检查成功完成之前流量不会到达它。

这两项检查 在 Pod 的整个生命周期内执行。 这是非常重要的。

一个常见的误解是,就绪探针仅在启动时运行,以便平衡器可以知道 pod 已准备就绪(Ready)并可以开始处理流量。 然而,这只是它们的使用选项之一。

另一个是可能会发现 pod 上的流量过多并且 使其超载 (或者 Pod 执行资源密集型计算)。 在这种情况下,准备情况检查会有所帮助 减少吊舱的负载并“冷却”它。 成功完成未来的准备情况检查可以 再次增加 Pod 的负载。 在这种情况下(如果就绪测试失败),活性测试的失败将产生非常适得其反的效果。 为什么要重启一个健康且努力工作的 Pod?

因此,在某些情况下,根本不进行检查比使用错误配置的参数启用它们要好。 如上所述,如果 活性检查复制就绪检查,那么你就有大麻烦了。 可能的选项是配置 仅准备就绪测试危险的活力 撇开。

当公共依赖项失败时,这两种类型的检查都不应该失败,否则这将导致所有 pod 的级联(类似雪崩)失败。 换句话说, 不要伤害自己.

3.每个HTTP服务的LoadBalancer

最有可能的是,您的集群中有 HTTP 服务,您希望将其转发到外部世界。

如果您将服务打开为 type: LoadBalancer,它的控制器(取决于服务提供商)将提供并协商一个外部LoadBalancer(不一定运行在L7上,甚至可以运行在L4上),这可能会影响成本(外部静态IPv4地址、计算能力、每秒计费) )由于需要创建大量此类资源。

在这种情况下,使用一个外部负载均衡器更为合乎逻辑,将服务开放为 type: NodePort。 或者更好的是,扩展类似的东西 nginx 入口控制器特拉菲克),谁将是唯一的一个 节点端口 与外部负载均衡器关联的端点,并将使用以下方式在集群中路由流量 进入-Kubernetes 资源。

其他相互交互的集群内(微)服务可以使用诸如 集群IP 以及通过 DNS 的内置服务发现机制。 只是不要使用他们的公共 DNS/IP,因为这会影响延迟并增加云服务的成本。

4. 在不考虑集群特性的情况下自动扩展集群

在向集群添加节点或从集群中删除节点时,不应依赖一些基本指标,例如这些节点上的 CPU 使用率。 Pod 规划必须考虑很多因素 限制,例如 Pod/节点亲和性、污点和容忍度、资源请求、QoS 等。 使用不考虑这些细微差别的外部自动缩放器可能会导致问题。

想象一下,应该调度某个 pod,但所有可用的 CPU 功率都被请求/分解,并且该 pod 陷入某种状态 Pending。 外部自动缩放器看到平均当前 CPU 负载(不是请求的负载)并且不会启动扩展 (向外扩展) - 不添加另一个节点。 因此,该 Pod 将不会被调度。

在这种情况下,反向缩放 (缩小) — 从集群中删除节点总是更难实现。 想象一下,您有一个有状态的 Pod(连接了持久存储)。 持久卷 通常属于 特定可用区 并且不会在该地区复制。 因此,如果外部自动缩放程序删除了具有此 Pod 的节点,则调度程序将无法将该 Pod 调度到另一个节点上,因为这只能在持久存储所在的可用区中完成。 Pod 将停留在状态 Pending.

在 Kubernetes 社区中非常受欢迎 集群自动缩放器。 它在集群上运行,支持主要云提供商的 API,考虑到所有限制,并且可以在上述情况下进行扩展。 它还能够在保持所有设定限制的同时进行缩减,从而节省资金(否则这些资金将花在未使用的容量上)。

5.忽视IAM/RBAC能力

谨防使用具有持久密钥的 IAM 用户 机器和应用。 使用角色和服务帐户组织临时访问 (服务帐户).

我们经常遇到这样的情况:访问密钥(和机密)在应用程序配置中被硬编码,并且尽管可以访问 Cloud IAM,但仍忽略机密的轮换。 在适当的情况下使用 IAM 角色和服务账户而不是用户。

使用 Kubernetes 时的 10 个常见错误

忘记 kube2iam 并直接进入服务帐户的 IAM 角色(如中所述 同名注释 什捷潘·弗拉尼 (Štěpán Vraný):

apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-role
  name: my-serviceaccount
  namespace: default

一注。 没那么难吧?

另外,不要授予服务帐户和实例配置文件权限 admin и cluster-admin如果他们不需要它。 这实现起来有点困难,尤其是在 RBAC K8s 中,但绝对值得付出努力。

6. 不要依赖 pod 的自动反亲和力

想象一下,您在一个节点上拥有某个部署的三个副本。 该节点崩溃了,所有副本也随之崩溃。 情况不愉快吧? 但为什么所有副本都在同一个节点上? Kubernetes 不是应该提供高可用性(HA)吗?!

不幸的是,Kubernetes调度器,主动地不遵守单独存在的规则 (反亲和力) 对于豆荚。 必须明确说明:

// опущено для краткости
      labels:
        app: zk
// опущено для краткости
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: "app"
                    operator: In
                    values:
                    - zk
              topologyKey: "kubernetes.io/hostname"

就这样。 现在 Pod 将被调度到不同的节点上(仅在调度期间检查此条件,而不是在其操作期间检查 - 因此 requiredDuringSchedulingIgnoredDuringExecution).

这里我们谈论的是 podAntiAffinity 在不同节点上: topologyKey: "kubernetes.io/hostname", - 而不是关于不同的可用区域。 要实现成熟的 HA,您必须更深入地研究这个主题。

7. 忽略 PodDisruptionBudgets

假设您的 Kubernetes 集群上有生产负载。 节点和集群本身必须定期更新(或停用)。 PodDisruptionBudget (PDB) 类似于集群管理员和用户之间的服务保障协议。

PDB可以让您避免由于缺少节点而导致的服务中断:

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: zookeeper

在此示例中,您作为集群的用户,向管理员声明:“嘿,我有一个 Zookeeper 服务,无论您做什么,我都希望该服务的至少 2 个副本始终可用。”

您可以阅读更多相关内容 这里.

8. 公共集群中的多个用户或环境

Kubernetes 命名空间 (命名空间) 不提供强绝缘.

一种常见的误解是,如果您将非产品负载部署到一个名称空间中,并将产品负载部署到另一个名称空间中,那么它们 不会以任何方式互相影响...但是,可以使用资源请求/限制、设置配额和设置优先级来实现一定程度的隔离。 数据平面中的一些“物理”隔离是由亲和力、容忍度、污点(或节点选择器)提供的,但这种隔离是相当有效的。 实施。

那些需要将两种类型的工作负载组合在同一集群中的人将不得不应对复杂性。 如果没有这样的需要,并且你有能力拥有一个 又一簇 (例如,在公共云中),那么最好这样做。 这将实现更高水平的绝缘。

9. 外部流量策略:集群

我们经常看到集群内的所有流量都通过 NodePort 之类的服务,并为其设置了默认策略 externalTrafficPolicy: Cluster。 这意味着 节点端口 在集群中的每个节点上都是开放的,您可以使用其中任何一个节点与所需的服务(pod 集)进行交互。

使用 Kubernetes 时的 10 个常见错误

同时,与上述NodePort服务关联的真实Pod通常仅在某个特定的端口上可用。 这些节点的子集。 换句话说,如果我连接到一个没有所需 Pod 的节点,它会将流量转发到另一个节点, 添加一跳 增加延迟(如果节点位于不同的可用区/数据中心,延迟可能会相当高;此外,出口流量成本也会增加)。

另一方面,如果某个 Kubernetes 服务设置了策略 externalTrafficPolicy: Local,那么 NodePort 仅在所需 pod 实际运行的节点上打开。 使用检查状态的外部负载均衡器时 (健康检查) 端点(它是如何做的 AWS弹性负载均衡器), 他 只会将流量发送到必要的节点,这将对延迟、计算需求、出口费用产生有益的影响(常识也是如此)。

您很有可能已经在使用类似的东西 特拉菲克 или nginx 入口控制器 作为 NodePort 端点(或 LoadBalancer,也使用 NodePort)来路由 HTTP 入口流量,设置此选项可以显着减少此类请求的延迟。

В 本出版物 您可以详细了解externalTrafficPolicy及其优点和缺点。

10. 不要与集群绑定,不要滥用控制平面

以前,习惯上用专有名称来调用服务器: Anton、HAL9000 和 Colossus...如今它们已被随机生成的标识符所取代。 然而,这个习惯仍然存在,现在专有名称变成了集群。

一个典型的故事(基于真实事件):这一切都始于概念验证,因此该集群有一个自豪的名字 测试......很多年过去了,它仍然在生产中使用,每个人都不敢碰它。

簇变成宠物并没有什么乐趣,所以我们建议在练习时定期将它们移除 灾难恢复 (这将有助于 混沌工程 - 大约。 译)。 另外,在控制层工作也不会有什么坏处 (控制平面)。 害怕碰他可不是什么好兆头。 死的? 伙计们,你们真的有麻烦了!

另一方面,您不应该因操纵它而得意忘形。 随着时间的推移 控制层可能会变慢。 最有可能的是,这是由于在没有旋转的情况下创建了大量对象(在默认设置下使用 Helm 时的常见情况,这就是为什么它在 configmaps/secrets 中的状态没有更新 - 因此,数千个对象累积在控制层)或不断编辑 kube-api 对象(用于自动扩展、CI/CD、监控、事件日志、控制器等)。

此外,我们建议检查与托管 Kubernetes 提供商的 SLA/SLO 协议并注意保证。 卖家可以保证 控制层可用性 (或其子组件),但不是您发送给它的请求的 p99 延迟。 换句话说,您可以输入 kubectl get nodes,并在10分钟后收到答复,这不会违反服务协议的条款。

11. 奖励:使用最新标签

但这已经是经典了。 最近,我们很少遇到这种技术,因为许多人从痛苦的经历中吸取了教训,已经停止使用该标签 :latest 并开始固定版本。 万岁!

ECR 保持图像标签的不变性; 我们建议您熟悉这个显着的功能。

总结

不要指望一切都能在一夜之间奏效:Kubernetes 不是万能药。 糟糕的应用程序 即使在 Kubernetes 中也将保持这种方式 (而且情况可能会变得更糟)。 粗心大意会导致控制层过于复杂、缓慢且工作压力大。 此外,您还可能面临没有灾难恢复策略的风险。 不要指望 Kubernetes 能够提供开箱即用的隔离和高可用性。 花一些时间让您的应用程序真正实现云原生。

你可以熟悉各个团队不成功的经历 这本故事集 作者:亨宁·雅各布斯。

那些希望添加到本文中给出的错误列表的人可以在 Twitter 上联系我们(@马雷克巴蒂克, @MstrsObserver).

译者PS

另请阅读我们的博客:

来源: habr.com

添加评论