九个 Kubernetes 性能技巧

九个 Kubernetes 性能技巧

大家好! 我叫 Oleg Sidorenkov,在 DomClick 担任基础设施团队负责人。 我们在生产中使用 Kubik 已经三年多了,在这段时间里我们经历了许多不同的有趣时刻。 今天,我将告诉您如何通过正确的方法,为您的集群从普通 Kubernetes 中获得更多性能。 准备就绪,稳扎稳打!

大家都非常清楚,Kubernetes 是一个可扩展的容器编排开源系统; 好吧,或者 5 个通过管理服务器环境中微服务的生命周期发挥神奇作用的二进制文件。 此外,它是一个相当灵活的工具,可以像乐高一样组装,以最大程度地定制不同的任务。

一切似乎都很好:将服务器扔进集群就像柴火扔进火箱一样,你不会有任何悲伤。 但如果你是为了环境,你会想:“我怎样才能让火继续燃烧,不伤害森林呢?” 换句话说,如何找到改善基础设施和降低成本的方法。

1. 监控团队和应用程序资源

九个 Kubernetes 性能技巧

最常见但有效的方法之一是引入请求/限制。 按命名空间划分应用程序,按开发团队划分命名空间。 在部署之前,设置应用程序的处理器时间、内存和临时存储消耗值。

resources:
   requests:
     memory: 2Gi
     cpu: 250m
   limits:
     memory: 4Gi
     cpu: 500m

根据经验,我们得出的结论是:您不应将限制的请求增加两倍以上。 集群的容量是根据请求计算的,如果你给应用程序提供不同的资源,例如5-10倍,那么想象一下当你的节点装满了pod并突然收到负载时会发生什么。 没什么好的。 最低限度是节流,最高限度是,您将告别工作线程,并在 Pod 开始移动后在剩余节点上获得循环负载。

此外,在帮助下 limitranges 一开始,您可以设置容器的资源值 - 最小值、最大值和默认值:

➜  ~ kubectl describe limitranges --namespace ops
Name:       limit-range
Namespace:  ops
Type        Resource           Min   Max   Default Request  Default Limit  Max Limit/Request Ratio
----        --------           ---   ---   ---------------  -------------  -----------------------
Container   cpu                50m   10    100m             100m           2
Container   ephemeral-storage  12Mi  8Gi   128Mi            4Gi            -
Container   memory             64Mi  40Gi  128Mi            128Mi          2

不要忘记限制命名空间资源,以免一个团队无法接管集群的所有资源:

➜  ~ kubectl describe resourcequotas --namespace ops
Name:                   resource-quota
Namespace:              ops
Resource                Used          Hard
--------                ----          ----
limits.cpu              77250m        80
limits.memory           124814367488  150Gi
pods                    31            45
requests.cpu            53850m        80
requests.memory         75613234944   150Gi
services                26            50
services.loadbalancers  0             0
services.nodeports      0             0

从描述中可以看出 resourcequotas,如果运维团队想要部署将消耗另外 10 个 cpu 的 pod,调度程序将不允许这样做并会抛出错误:

Error creating: pods "nginx-proxy-9967d8d78-nh4fs" is forbidden: exceeded quota: resource-quota, requested: limits.cpu=5,requests.cpu=5, used: limits.cpu=77250m,requests.cpu=53850m, limited: limits.cpu=10,requests.cpu=10

为了解决这样的问题,你可以写一个工具,例如, ,能够存储和提交命令资源的状态。

2. 选择最佳的文件存储方式

九个 Kubernetes 性能技巧

这里我想谈谈持久卷和 Kubernetes Worker 节点的磁盘子系统。 我希望没有人在生产中使用 HDD 上的“Cube”,但有时普通 SSD 已经不够了。 我们遇到了由于I/O操作导致日志杀盘的问题,解决方案并不多:

  • 使用高性能 SSD 或切换到 NVMe(如果您管理自己的硬件)。

  • 降低日志记录级别。

  • 对占用磁盘的 pod 进行“智能”平衡(podAntiAffinity).

上面的屏幕显示了启用 access_logs 日志记录(约 12 条日志/秒)时,在 nginx-ingress-controller 下到磁盘发生的情况。 当然,这种情况可能会导致该节点上的所有应用程序性能下降。

至于PV,唉,我还没有尝试过一切 类型 持久卷。 使用最适合您的选项。 从历史上看,我国曾经发生过一小部分服务需要RWX卷的情况,很早以前他们就开始使用NFS存储来完成这项任务。 便宜而且...足够了。 当然,他和我吃了屎——上帝保佑你,但我们学会了不去管它,我的头不再疼了。 如果可能的话,迁移到 S3 对象存储。

3. 收集优化图像

九个 Kubernetes 性能技巧

最好使用容器优化的镜像,以便 Kubernetes 可以更快地获取它们并更有效地执行它们。 

优化意味着图像:

  • 仅包含一个应用程序或仅执行一项功能;

  • 尺寸小,因为大图像在网络上传输效果较差;

  • 拥有健康和就绪端点,允许 Kubernetes 在停机时采取行动;

  • 使用容器友好的操作系统(如 Alpine 或 CoreOS),它们更能抵抗配置错误;

  • 使用多阶段构建,以便您只能部署已编译的应用程序,而不能部署随附的源代码。

有许多工具和服务可让您即时检查和优化图像。 始终保持最新状态并进行安全测试非常重要。 结果你得到:

  1. 减少了整个集群的网络负载。

  2. 减少容器启动时间。

  3. 整个 Docker 注册表的大小更小。

4.使用DNS缓存

九个 Kubernetes 性能技巧

如果我们谈论高负载,那么如果不调整集群的 DNS 系统,生活会非常糟糕。 曾几何时,Kubernetes 开发人员支持他们的 kube-dns 解决方案。 这里也实现了,但是这个软件没有经过特别的调整,没有产生所需的性能,尽管它看起来是一个简单的任务。 然后 coredns 出现了,我们切换到了它并没有悲伤;它后来成为 K8s 中的默认 DNS 服务。 在某个时候,我们的 DNS 系统的 rps 增长到了 40,这个解决方案也变得不够用了。 但是,幸运的是,Nodelocaldns 出现了,又名节点本地缓存,又名 节点本地 DNS 缓存.

我们为什么用这个? Linux 内核中存在一个错误,当通过 UDP 上的 conntrack NAT 进行多次调用时,会导致 conntrack 表中的条目出现竞争条件,并且通过 NAT 的部分流量会丢失(每次通过服务的行程都是 NAT)。 Nodelocaldns 通过摆脱 NAT 并升级到上游 DNS 的 TCP 连接以及本地缓存上游 DNS 查询(包括短暂的 5 秒负缓存)来解决此问题。

5. 自动水平和垂直缩放 Pod

九个 Kubernetes 性能技巧

您能否自信地说您的所有微服务都已准备好将负载增加两到三倍? 如何为您的应用程序正确分配资源? 让几个 Pod 超出工作负载运行可能是多余的,但让它们背靠背运行可能会面临因服务流量突然增加而导致停机的风险。 服务如 水平 Pod 自动缩放器 и 垂直 Pod 自动缩放器.

VPA 允许您根据实际使用情况自动提高 Pod 中容器的请求/限制。 怎么可能有用呢? 如果您的 Pod 由于某种原因无法水平扩展(这并不完全可靠),那么您可以尝试将对其资源的更改委托给 VPA。 它的功能是基于来自度量服务器的历史和当前数据的推荐系统,因此如果您不想自动更改请求/限制,您可以简单地监视容器的推荐资源并优化设置以节省 CPU 和集群中的内存。

九个 Kubernetes 性能技巧图片取自https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizo​​ntal-pod-autoscaler-and-vertical-pod-2a441d9ad231

Kubernetes 中的调度程序始终基于请求。 无论您在那里输入什么值,调度程序都会根据它搜索合适的节点。 Cubelet 需要限制值来了解何时限制或终止 pod。 由于唯一重要的参数是请求值,VPA 将使用它。 每当您垂直扩展应用程序时,您都可以定义请求的内容。 那么极限会发生什么? 该参数也将按比例缩放。

例如,以下是常用的 pod 设置:

resources:
   requests:
     memory: 250Mi
     cpu: 200m
   limits:
     memory: 500Mi
     cpu: 350m

推荐引擎确定您的应用程序需要 300m CPU 和 500Mi 才能正常运行。 您将获得以下设置:

resources:
   requests:
     memory: 500Mi
     cpu: 300m
   limits:
     memory: 1000Mi
     cpu: 525m

如上所述,这是基于清单中的请求/限制比率的比例缩放:

  • CPU:200m→300m:比例1:1.75;

  • 内存:250Mi→500Mi:比例1:2。

至于 HPA,那么运行机制就更加透明。 CPU 和内存等指标设有阈值,如果所有副本的平均值超过阈值,则应用程序将按 +1 sub 缩放,直到该值低于阈值或达到最大副本数。

九个 Kubernetes 性能技巧图片取自https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizo​​ntal-pod-autoscaler-and-vertical-pod-2a441d9ad231

除了 CPU 和内存等常用指标之外,您还可以在 Prometheus 中设置自定义指标的阈值,如果您认为这是何时扩展应用程序的最准确指示,则可以使用它们。 一旦应用程序稳定在指定的指标阈值以下,HPA 将开始将 Pod 缩减到最小副本数或直到负载达到指定的阈值。

6. 不要忘记 Node Affinity 和 Pod Affinity

九个 Kubernetes 性能技巧

并非所有节点都在相同的硬件上运行,也并非所有 Pod 都需要运行计算密集型应用程序。 Kubernetes 允许您使用以下命令设置节点和 Pod 的专业化 节点亲和力 и Pod 亲和力.

如果您有适合计算密集型操作的节点,那么为了获得最大效率,最好将应用程序绑定到相应的节点。 要执行此操作,请使用 nodeSelector 带有节点标签。

假设您有两个节点:其中一个带有 CPUType=HIGHFREQ 和大量快速核心,另一个具有 MemoryType=HIGHMEMORY 更多内存和更快的性能。 最简单的方法是将部署分配给节点 HIGHFREQ通过添加到该部分 spec 这个选择器:

…
nodeSelector:
	CPUType: HIGHFREQ

一种更昂贵且更具体的方法是使用 nodeAffinity 在现场 affinity 部分 spec。 有两种选择:

  • requiredDuringSchedulingIgnoredDuringExecution:硬设置(调度程序将仅在特定节点(而不是其他节点)部署 Pod);

  • preferredDuringSchedulingIgnoredDuringExecution:软设置(调度程序将尝试部署到特定节点,如果失败,它将尝试部署到下一个可用节点)。

您可以指定管理节点标签的特定语法,例如 In, NotIn, Exists, DoesNotExist, Gt или Lt。 但是,请记住,长标签列表中的复杂方法会减慢关键情况下的决策速度。 换句话说,保持简单。

如上所述,Kubernetes 允许您设置当前 pod 的亲和性。 也就是说,您可以确保某些 Pod 与同一可用区(与云相关)或节点中的其他 Pod 协同工作。

В podAffinity 领域 affinity 部分 spec 与以下情况相同的字段可用 nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution и preferredDuringSchedulingIgnoredDuringExecution。 唯一的区别是 matchExpressions 会将 Pod 绑定到已经运行具有该标签的 Pod 的节点。

Kubernetes还提供了一个领域 podAntiAffinity,相反,它不会将 pod 绑定到具有特定 pod 的节点。

关于表达式 nodeAffinity 可以给出相同的建议:尝试保持规则简单且符合逻辑,不要尝试使用一组复杂的规则来超载 pod 规范。 创建与集群条件不匹配的规则非常容易,从而对调度程序造成不必要的负载并降低整体性能。

7. 污点和宽容

还有另一种方法来管理调度程序。 如果您有一个包含数百个节点和数千个微服务的大型集群,那么很难不允许某些 Pod 托管在某些节点上。

污点机制(禁止规则)对此有所帮助。 例如,在某些场景下,您可以禁止某些节点运行 Pod。 要将污点应用到特定节点,您需要使用该选项 taint 在 kubectl. 指定键和值,然后像这样进行污点 NoSchedule или NoExecute:

$ kubectl taint nodes node10 node-role.kubernetes.io/ingress=true:NoSchedule

还值得注意的是,污点机制支持三种主要效果: NoSchedule, NoExecute и PreferNoSchedule.

  • NoSchedule 意味着现在 pod 规范中不会有相应的条目 tolerations,它将无法部署在节点上(在本例中 node10).

  • PreferNoSchedule - 简化版本 NoSchedule。 在这种情况下,调度程序将尝试不分配没有匹配条目的 Pod tolerations 每个节点,但这不是一个硬限制。 如果集群中没有资源,则 Pod 将开始在此节点上部署。

  • NoExecute - 此效果会触发没有相应条目的吊舱立即撤离 tolerations.

有趣的是,可以使用容忍机制来取消这种行为。 当存在“禁止”节点并且您只需要在其上放置基础设施服务时,这很方便。 怎么做? 仅允许具有适当容差的 pod。

Pod 规格如下:

spec:
   tolerations:
     - key: "node-role.kubernetes.io/ingress"
        operator: "Equal"
        value: "true"
        effect: "NoSchedule"

这并不意味着下一次重新部署将落在该特定节点上,这不是节点亲和性机制,并且 nodeSelector。 但通过组合多种功能,您可以实现非常灵活的调度程序设置。

8. 设置 Pod 部署优先级

仅仅因为将 Pod 分配给节点并不意味着必须以相同的优先级对待所有 Pod。 例如,您可能希望在其他 pod 之前部署一些 pod。

Kubernetes 提供了不同的方法来配置 Pod 优先级和抢占。 设置由几个部分组成: 对象 PriorityClass 和字段描述 priorityClassName 在 pod 规范中。 让我们看一个例子:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 99999
globalDefault: false
description: "This priority class should be used for very important pods only"

我们创造 PriorityClass,为其指定名称、描述和值。 更高 value,优先级越高。 该值可以是小于或等于 32 的任何 1 位整数。较高的值是为通常无法抢占的关键任务系统 Pod 保留的。 只有当高优先级的 pod 无处掉头时才会发生位移,然后某个节点的部分 pod 会被疏散。 如果这个机制对你来说太僵化,你可以添加选项 preemptionPolicy: Never,然后就不会发生抢占,pod会站在队列的第一位,等待调度器为它找到空闲资源。

接下来,我们创建一个 pod,在其中指定名称 priorityClassName:

apiVersion: v1
kind: Pod
metadata:
  name: static-web
  labels:
    role: myrole
 spec:
  containers:
    - name: web
      image: nginx
      ports:
        - name: web
          containerPort: 80
          protocol: TCP
  priorityClassName: high-priority
          

您可以根据需要创建任意多个优先级类别,但建议不要为此而得意忘形(例如,将自己限制为低、中和高优先级)。

因此,如有必要,您可以提高部署关键服务(例如 nginx-ingress-controller、coredns 等)的效率。

9.优化ETCD集群

九个 Kubernetes 性能技巧

ETCD堪称整个集群的大脑。 将该数据库的操作保持在高水平非常重要,因为 Cube 中的操作速度取决于它。 一个相当标准且同时良好的解决方案是将 ETCD 集群保留在主节点上,以便将 kube-apiserver 的延迟降至最低。 如果您做不到这一点,请将 ETCD 放置得尽可能近,并在参与者之间提供良好的带宽。 还要注意 ETCD 中有多少节点可以掉下来而不会对集群造成损害

九个 Kubernetes 性能技巧

请记住,过度增加集群中的成员数量可能会以牺牲性能为代价来提高容错能力,一切都应该适度。

如果我们谈论设置服务,有一些建议:

  1. 根据集群的大小拥有良好的硬件(您可以阅读 这里).

  2. 如果您在一对 DC 之间分布了集群,或者您的网络和磁盘还有很多不足之处,请调整一些参数(您可以阅读 这里).

结论

本文描述了我们团队试图遵守的要点。 这不是操作的分步描述,而是可能对优化集群开销有用的选项。 显然,每个集群都有其独特的方式,并且配置解决方案可能差异很大,因此获得有关如何监控 Kubernetes 集群以及如何提高其性能的反馈将会很有趣。 在评论中分享您的经验,了解会很有趣。

来源: habr.com