确保古巴的资源

图片


群集资源管理始终是一个复杂的主题。 如何解释为将应用程序部署到集群的用户配置资源的需求? 也许自动化起来更容易?


问题描述


在Kubernetes集群管理中,资源管理是一项重要任务。 但是,如果Kubernetes为您完成所有艰苦的工作,那为什么很重要? 因为不是。 Kubernetes为您提供了解决许多问题的便捷工具...如果您使用这些工具。 对于群集中的每个Pod,可以指定其容器所需的资源。 Kubernetes将使用此信息在群集节点之间分布应用程序实例。


很少有人认真对待Kubernetes的资源管理。 对于带有几个静态应用程序的轻负载集群,这是正常的。 但是,如果您有一个非常动态的集群,该怎么办? 应用程序来往何处,始终在哪里创建和删除名称空间? 一个具有大量用户的集群,这些用户可以创建自己的名称空间并部署应用程序? 好吧,在这种情况下,您将在应用程序中甚至在Kubernetes本身的组件中发生一堆随机崩溃,而不是稳定且可预测的编排!


这是此类集群的示例:


图片


您会看到3个炉膛处于“终止”状态。 但这不是通常的炉膛移除-它们停留在此状态,因为其节点上的containerd守护进程受到了非常耗费资源的攻击。


可以通过适当地处理资源不足来解决此类问题,但这不是本文的主题(有一篇不错的文章 ),也不是解决所有资源问题的灵丹妙药。


出现此类问题的主要原因是群集中的错误或缺少资源管理。 而且,如果这种问题对部署来说不是灾难,因为它们很容易创建一个新的部署对象,那么对于DaemonSet之类的实体,甚至对于StatefulSet而言,这样的冻结将是致命的,需要手动干预。


您可以拥有一个拥有大量CPU和内存的巨大群集。 如果在没有正确的资源设置的情况下在其上运行许多应用程序,则所有资源密集型Pod都有机会放置在同一节点上。 即使集群中的其余节点实际上仍然空闲,它们也将争夺资源。


您还经常会看到一些应用程序受邻居影响较小的情况。 即使正确配置了这些“无辜”应用程序的资源,也可能在其中徘徊并杀死它们。 这种情况的一个示例:


  1. 您的应用程序请求4 GB的内存,但最初仅占用1 GB。
  2. 在没有资源配置的情况下,在其下徘徊的对象被分配给同一节点。
  3. 在下面漫游会消耗所有可用内存。
  4. 您的应用程序试图分配更多的内存,并且由于没有更多内存而崩溃。

另一个相当受欢迎的案例是重估汇率。 一些开发人员“以防万一”在清单中提出了巨大的要求,从不使用这些资源。 结果是浪费金钱。


决策理论


恐怖! 对不对
幸运的是,Kubernetes通过指定默认资源配置以及最小值和最大值,提供了对Pod施加一些限制的方法。 这是使用LimitRange对象实现的。 当名称空间数量有限或完全控制它们的创建过程时,LimitRange是一种非常方便的工具。 即使没有正确配置资源,您的应用程序也将受到限制。 “无辜”的,经过适当调整的炉膛将是安全的,并免受有害邻居的伤害。 如果有人部署没有资源配置的贪婪应用程序,则该应用程序将收到默认值,并且可能会崩溃。 仅此而已! 该应用程序将不再拖累任何人。


因此,我们有一个工具可以控制和强制配置炉膛资源,现在看来我们是安全的。 那呢 不完全是 事实是,正如我们之前所述,用户可以创建我们的名称空间,因此,LimitRange可能不会出现在此类名称空间中,因为必须在每个名称空间中分别创建它。 因此,我们不仅需要在名称空间级别上,而且在集群级别上也需要。 但是Kubernetes中还没有这样的功能。


这就是为什么我决定为这个问题写解决方案的原因。 让我介绍一下-极限算子。 这是一个基于Operator SDK框架创建的运算符 ,该框架使用ClusterLimit定制资源并有助于保护群集中所有“无害”应用程序的安全。 使用此运算符,您可以使用最少的配置量来控制所有名称空间的默认值和资源限制。 它还允许您使用namespaceSelector选择确切的位置来应用配置。


ClusterLimit示例
apiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: default-limit spec: namespaceSelector: matchLabels: limit: "limited" limitRange: limits: - type: Container max: cpu: "800m" memory: "1Gi" min: cpu: "100m" memory: "99Mi" default: cpu: "700m" memory: "900Mi" defaultRequest: cpu: "110m" memory: "111Mi" - type: Pod max: cpu: "2" memory: "2Gi" 

使用此配置,操作员将仅在带有标签limit: limited的名称空间中创建LimitRange。 这对于在一组特定的名称空间中提供更严格的限制很有用。 如果未指定namespaceSelector,则操作员将LimitRange应用于所有命名空间。 如果要为特定名称空间手动配置LimitRange,则可以使用批注"limit.myafq.com/unlimited": true这将告诉操作员跳过该名称空间而不自动创建LimitRange。


使用运算符的示例脚本:


  • 创建具有自由约束且没有namespaceSelector的默认ClusterLimit-将在所有地方应用。
  • 对于具有轻量级应用程序的一组命名空间,请使用namespaceNameor创建另一个更严格的ClusterLimit。 将标签相应地放在这些命名空间上。
  • 在具有大量资源密集型应用程序的命名空间上,添加注释“ limit.myafq.com/unlimited”:true,并以比默认ClusteLimit中指定的更宽的限制手动配置LimitRange。

重要的是要了解一个名称空间中的几个LimitRange:
在具有多个LimitRange的命名空间中创建子时,将采用最大的默认值来配置其资源。 但是最大值和最小值将根据LimitRange的最严格检查。

实际例子


操作员将跟踪所有名称空间,ClusterLimits,子LimitRanges中的所有更改,并将启动群集状态与受监视对象中任何更改的协调。 让我们看看它在实践中如何工作。


首先,在没有任何限制的情况下创建:


kubectl运行/获取输出
 ❯() kubectl run --generator=run-pod/v1 --image=bash bash pod/bash created ❯() kubectl get pod bash -o yaml apiVersion: v1 kind: Pod metadata: labels: run: bash name: bash namespace: default spec: containers: - image: bash name: bash resources: {} 

注意:为了简化示例,省略了部分命令输出。


如您所见,“资源”字段为空,这意味着该子程序可以在任何地方启动。
现在,我们将为整个集群创建具有相当自由值的默认ClusterLimit:


default-limit.yaml
 apiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: default-limit spec: limitRange: limits: - type: Container max: cpu: "4" memory: "5Gi" default: cpu: "700m" memory: "900Mi" defaultRequest: cpu: "500m" memory: "512Mi" 

对于命名空间的子集也更加严格:


限制性限制
 apiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: restrictive-limit spec: namespaceSelector: matchLabels: limit: "restrictive" limitRange: limits: - type: Container max: cpu: "800m" memory: "1Gi" default: cpu: "100m" memory: "128Mi" defaultRequest: cpu: "50m" memory: "64Mi" - type: Pod max: cpu: "2" memory: "2Gi" 

然后在其中创建名称空间和Pod,以查看其工作原理。
具有默认限制的普通名称空间:


 apiVersion: v1 kind: Namespace metadata: name: regular 

根据传说,对于轻量级应用程序,命名空间略为有限:


 apiVersion: v1 kind: Namespace metadata: labels: limit: "restrictive" name: lightweight 

如果在创建名称空间后立即查看操作员的日志,则可以在扰流器下找到类似的内容:


操作员日志
 {...,"msg":"Reconciling ClusterLimit","Triggered by":"/regular"} {...,"msg":"Creating new namespace LimitRange.","Namespace":"regular","LimitRange":"default-limit"} {...,"msg":"Updating namespace LimitRange.","Namespace":"regular","Name":"default-limit"} {...,"msg":"Reconciling ClusterLimit","Triggered by":"/lightweight"} {...,"msg":"Creating new namespace LimitRange.","Namespace":"lightweight","LimitRange":"default-limit"} {...,"msg":"Updating namespace LimitRange.","Namespace":"lightweight","Name":"default-limit"} {...,"msg":"Creating new namespace LimitRange.","Namespace":"lightweight","LimitRange":"restrictive-limit"} {...,"msg":"Updating namespace LimitRange.","Namespace":"lightweight","Name":"restrictive-limit"} 

日志中缺少的部分包含3个当前不相关的字段


如您所见,每个名称空间的创建都开始了新的LimitRange的创建。 一个更受限制的名称空间有两个LimitRange-默认和更严格。


现在,让我们尝试在这些命名空间中创建一对壁炉。


kubectl运行/获取输出
 ❯() kubectl run --generator=run-pod/v1 --image=bash bash -n regular pod/bash created ❯() kubectl get pod bash -o yaml -n regular apiVersion: v1 kind: Pod metadata: annotations: kubernetes.io/limit-ranger: 'LimitRanger plugin set: cpu, memory request for container bash; cpu, memory limit for container bash' labels: run: bash name: bash namespace: regular spec: containers: - image: bash name: bash resources: limits: cpu: 700m memory: 900Mi requests: cpu: 500m memory: 512Mi 

如您所见,尽管事实上我们没有更改pod的创建方式,但现在填充了资源字段。 您可能还会注意到LimitRanger自动创建的注释。


现在在一个轻量级的命名空间下创建:


kubectl运行/获取输出
 ❯() kubectl run --generator=run-pod/v1 --image=bash bash -n lightweight pod/bash created ❯() kubectl get pods -n lightweight bash -o yaml apiVersion: v1 kind: Pod metadata: annotations: kubernetes.io/limit-ranger: 'LimitRanger plugin set: cpu, memory request for container bash; cpu, memory limit for container bash' labels: run: bash name: bash namespace: lightweight spec: containers: - image: bash name: bash resources: limits: cpu: 700m memory: 900Mi requests: cpu: 500m memory: 512Mi 

请注意,炉膛中的资源与前面的示例相同。 这是因为在使用多个LimitRange的情况下,创建容器时将使用不太严格的默认值。 但是为什么我们需要一个更有限的LimitRange? 它将用于检查资源的最大值和最小值。 为了说明这一点,我们将使有限的ClusterLimit更加有限:


限制性限制
 apiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: restrictive-limit spec: namespaceSelector: matchLabels: limit: "restrictive" limitRange: limits: - type: Container max: cpu: "200m" memory: "250Mi" default: cpu: "100m" memory: "128Mi" defaultRequest: cpu: "50m" memory: "64Mi" - type: Pod max: cpu: "2" memory: "2Gi" 

请注意以下部分:


 - type: Container max: cpu: "200m" memory: "250Mi" 

现在,我们为炉膛中的容器设置了200m CPU和250Mi内存最大值。 现在再次尝试创建以下内容:


 ❯() kubectl run --generator=run-pod/v1 --image=bash bash -n lightweight Error from server (Forbidden): pods "bash" is forbidden: [maximum cpu usage per Container is 200m, but limit is 700m., maximum memory usage per Container is 250Mi, but limit is 900Mi.] 

我们的子项具有默认的LimitRange设置的较大值,并且由于未通过最大允许资源检查而无法启动。




这是使用限制运算符的示例。 自己尝试一下,然后在本地Kubernetes实例中使用ClusterLimit。


在GitHub Limit Operator存储库中,您可以找到用于部署该操作员的清单以及源代码。 如果您想扩展操作员的功能,欢迎使用拉式和功能式功能!


结论


Kubernetes的资源管理对于您的应用程序的稳定性和可靠性至关重要。 尽可能自定义您的壁炉资源。 并使用LimitRange来确保不可能发生的情况。 使用Limit Operator自动创建LimitRange。


遵循这些提示,您的集群将永远免受流浪者的无用混乱。

Source: https://habr.com/ru/post/zh-CN473908/


All Articles