CNCF 沙箱项目 OCM Placement 多集群调度指南

简介:在这篇文章中,将介绍 Placement 如何选择到所需的集群,Placement 可以提供的调度功能,以及一些场景下的最佳实践,使用者可以参考示例来编写符合自己要求的 Placement。其他一些高级调度功能,如支持污点 (taints) 和容忍 (tolerations),以及拓扑选择 (spread),正在 OCM 社区讨论中。
作者:
邱见|红帽资深软件工程师,Open Cluster Management (OCM) 社区发起人,负责人
郝青|红帽高级软件工程师,Open Cluster Management (OCM) 社区维护者 Open
Cluster Management(OCM) 项目已经在 2021 年 11 月 9 日成为 CNCF 的沙箱项目。OCM 作为一个社区驱动的项目,专注于 Kubernetes 应用程序的多集群和多云场景。
最新 OCM 社区版本 0.6.0 已于 2022 年 1 月 21 日正式发布。详细内容可访问 Open Cluster Management 0.6 发布[1]。
在多集群环境中,不同角色的用户对多集群操作有着不同的需求。比如管理员等用户需要对目标集群进行一些配置, 应用程序开发人员可能希望将工作负载部署到特定集群,这些工作负载可以是 Kubernetes 的 Service、Deployment、ConfigMap 或不同 Kubernetes 对象的捆绑包。这些用户对目标集群会有一些要求,比如:

  • 我只想在 Amazon Web Services(AWS) 上配置集群。
  • 我只想将工作负载部署到标签为 group=dev 的集群上。
  • 我希望工作负载始终在具有最大可分配内存的 3 个集群上运行。
为了选择出目标集群,可以选择在部署管道 (deploy pipeline) 中对直接指定目标集群名称,或使用某种形式的标签选择器。对于对资源有要求的工作负载,需要一个细粒度的调度器来将工作负载分发到具有足够资源的群集。当群集属性更改时,调度结果应该保持动态更新。
在 OCM 中,前面描述的调度功能是通过 Placement 来实现的。在这篇文章中,将介绍 Placement 如何选择到所需的集群,Placement 可以提供的调度功能,以及一些场景下的最佳实践,使用者可以参考示例来编写符合自己要求的 Placement。其他一些高级调度功能,如支持污点 (taints) 和容忍 (tolerations),以及拓扑选择 (spread),正在 OCM 社区[2]讨论中。
在阅读本文前,可访问以下链接了解相关基本概念:
  • ManagedCluster 和 ManagedClusterSet[3]
  • Placement[4]
为什么我们需要ManagedClusterSet?
“ClusterSet”是在 Kubernetes SIG 多集群工作小组的“多集群服务 (MultiClusterService/MCS)”API 中已经实践许久的概念,它意指多个拥有相同属性/特征的“集群小组”的概念。在多集群网络的场景里我们需要根据底座基础设施的拓扑为集群分组,同样的在 OCM 多集群管理平台里我也可以根据集群的场景用途,工作特性具体分组。这也是 OCM 引入 ClusterSet 模型的最初缘由之一。
在此基础上,OCM 在引入多集群分组的语义的同时考虑到了不同分组之间的“软租户隔离性” — 尤其考虑到不同集群小组可能是由不同的角色/团队去维护的,同时这些团队之间应该相互自治不干扰。在 OCM 的世界中,我们会允许管理员为每一种角色/团队会分配一个命名空间/namespace,同时通过利用 Kubernetes 原生提供的命名空间之间的隔离性使不同角色区别开来(其中所谓的角色落进实际场景里可以是一个应用或者也可以是一个组织团队等等)。那么这些角色只要在被分配的命名空间里活动就可以充分编排所关联的多个集群上的资源。
总而言之,在一个通用的多集群中枢控制平面里,如何解决多个用户/角色分离的问题其实是最首要的问题之一,OCM 之所以引入了 ClusterSet 模型且额外提供了其到命名空间的映射,是为了希望 OCM 作为一个平台能解决“多集群场景”里帮助用户解决最琐碎同时又最操心的问题。至于如何消费所关联的集群列表请参考下面的 Placement 模型。
什么是 Placement?
Placement API 用于在一个或多个托管集群组(ManagedClusterSet)中选择一组托管群集(ManagedCluster),以便将工作负载部署到这些群集上。
如果定义了有效的 Placement,则 Placement 控制器 (controller) 将生成相应的调度决策 (PlacementDecision),并在状态 (Status) 中列出选定的托管群集 (ManagedCluster)。作为最终用户,你可以解析出选定的集群,然后对目标集群进行操作。你也可以将更高层级的工作负载编排器 (orchestrator) 与 PlacementDecision 集成,来扩展 Placement 的调度能力。
例如,ArgoCD 已经与 Placement 集成。ArgoCD 的使用者可以在 ApplicationSet 的 clusterDecisionResource 中指定一个关联了的 PlacementDecision 资源的 ConfigMap,就可以利用 Placement 的调度决策,将应用自动分配到一组目标集群。如下:
apiVersion: argoproj.io/v1alpha1 kind: ApplicationSet metadata: name: book-import spec: generators: - clusterDecisionResource: configMapRef: ocm-placement labelSelector: matchLabels: cluster.open-cluster-management.io/placement: local-cluster requeueAfterSeconds: 30 template: …apiVersion: v1 kind: ConfigMap metadata: name: ocm-placement data: apiVersion: cluster.open-cluster-management.io/v1alpha1 kind: placementdecisions statusListKey: decisions matchKey: clusterNameapiVersion: cluster.open-cluster-management.io/v1alpha1 kind: PlacementDecision metadata: labels: cluster.open-cluster-management.io/placement: local-cluster name: local-cluster-decision-1 status: decisions: - clusterName: cluster1 reason: "" - clusterName: cluster2 reason: ""

KubeVela 作为开放应用程序模型 OAM(Open Application Model) 的实现,也即将利用 Placement API 进行工作负载调度。
与原生 Kubernetes 调度模型的区别与联系? 与 Kubernetes 的静态调度不同,Placement 使用动态调度的机制。调度抉择会随着集群属性变化也随之改变,用户可以通过在 Placement 上调整调度的稳定值来减少调度决策的抖动。另外,Placement API 尝试将整个调度过程显示化,让用户可以通过 API 查询调度抉择的原委,方便用户更加容易的调试调度配置和参数。
同时在原生 Kubernetes 中的调度是一次性的,而在多集群场景里我们往往需要的是一个“声明式的调度” — 我们定义出调度策略的“硬条件”和“软条件”是什么,再根据实际的集群拓扑/实时状态决策最终匹配的集群,所以它更像是原生 Kubernetes 中的驱散调度/反调度 PodDisruptionBudget 的模型而非静态调度中的 Taint/Toleration 的模型。
OCM 在 Placement 模型中同时考虑到了大规模多集群调度时集群列表长度暴涨的问题,在 Placement 的匹配产物 PlacementDecision 中所有匹配结果都是分页展示的以避免突破 Kubernetes CRD 对模型的限制。
Placement 如何选择集群? 有了上述的初步介绍,让我们更深入地了解 Placement API,看看它是如何选择所需的集群以及它可以提供哪些调度功能。
如下是一个 Placement 例子:
apiVersion: cluster.open-cluster-management.io/v1alpha1 kind: Placement metadata: name: placement namespace: ns1 spec: numberOfClusters: 4 clusterSets: - clusterset1 - clusterset2 predicates: - requiredClusterSelector: labelSelector: matchLabels: vendor: OpenShift prioritizerPolicy: mode: Exact configurations: - scoreCoordinate: builtIn: ResourceAllocatableMemory - scoreCoordinate: builtIn: Steady weight: 3 - scoreCoordinate: type: AddOn addOn: resourceName: default scoreName: cpuratio

Spec 包含以下四个字段:
numberOfClusters: 表示要选择的符合要求的 ManagedClusters 数量。
clusterSets: 表示从中选择 ManagedCluster 的 ManagedClusterSet 名称。
predicates: 包括了一组预选策略。可以使用标签选择器 (labelSelector) 和声明选择器 (claimSelector) 来选择 ManagedCluster。每一个预选策略配置之间是或的关系。
prioritizerPolicy: 定义了优选策略。优选策略中通过 mode 设置是否使用默认的优选器 (prioritizer)。同时也可以在 configurations 中配置具体的优选器 (prioritizer)。目前 Placement 内置支持的优选器 (prioritizer) 包括平衡 (Balance),稳定 (Steady),最大可分配CPU资源 (ResourceAllocatableCPU) 和最大可分配内存资源 (ResourceAllocatableMemory)。Placement 同时也支持通过第三方提供的分数来选择集群。weight 权重是一个 -10 到 10 的整数,用以调整不同的优选器打分对总分的影响。
如果未定义 Spec 中各字段的值,则使用默认值。每个字段中默认值的详细信息在 PlacementSpec[5] 中定义。
如果 Spec 为空,所有绑定到 Placement 命名空间 (namespace) 的 ManagedClusterSet 中的所有 ManagedCluster 将作为可能的选项。
以上每个字段的定义都在调度中发挥着作用。如下是一个典型的调度过程:
调度框架首先从 clusterSets 中定义的 ManagedClusterSet 中选择出可用的 ManagedCluster。
过滤器插件 (filter plugin) 通过预选策略 predicates 中定义的标签 (label) 和声明 (claim) 选择器进一步筛选 ManagedCluster。
在优选策略 prioritizerPolicy 启用的优选器插件 (prioritizer plugin) 会为每个筛选后的 ManagedCluster 打一个分数,并且按总分从高到低确定优先级。
调度框架会选择前 k 个 ManagedCluster,并把这些集群列在 PlacementDecision 中。k 的值是在 numberOfClusters 定义的集群数量。
如果将以上步骤对应的上述的例子中,调度过程如下:
调度框架首先选择 ManagedClusterSet clusterset1 和 clusterset2 中的集群作为可用的 ManagedCluster。
过滤器插件 (filter plugin) 筛选出带有标签 (label)vendor=OpenShift 的 ManagedCluster。
优选器插件 (prioritizer plugin)ResourceAllocatableMemory 和 Steady 为每一个筛选的 ManagedCluster 打分。当配置了优选策略 AddOn,Placement 会尝试获得集群对应的第三方资源提供的分数 cpuratio。并用如下公式计算每个 ManagedCluster 的总分:
1(ResourceAllocatableMemory 的默认权重) ResourceAllocatableMemory 的打分 + 3(Steady 的权重) Steady 的打分 + 1(AddOn 的默认权重) * cpuratio(AddOn 的分数)
调度框架按照每个 ManagedCluster 的总分从高到低排列,并返回最高分数的 ManagedCluster 作为结果。
在第 3 步优选器插件工作时,实际上多个插件的组合。每个插件的算法和权重都会影响最终的调度结果。下一节中,会更详细的介绍每个插件,以便你更好的了解 Placement是如何选择 ManagedCluster 的。
优选器插件如何工作?
在撰写此文时,我们有如下四个默认的优选器:
平衡 (Balance): 平衡每个集群上的调度决策 (PlacementDecision) 数量。拥有 PlacementDecision 数量最多的集群将得到最低分 -100 分,如果没有 PlacementDecision 则被赋予最高分 100 分。其他的分数介于 -100 到 100 之间。
稳定 (Steady): 确保现有的 PlacementDecision 中已选集群的结果保持稳定。现有的 PlacementDecision 已经选中的集群将得到最高分 100 分,没有被选中的集群得到最低分 0 分。
最大可分配 CPU 资源 (ResourceAllocatableCPU) 和最大可分配内存资源 (ResourceAllocatableMemory) 基于集群的可分配 CPU 或者内存做决策。拥有最多可分配资源(CPU 或者内存)的集群将得到最高分 100 分,拥有最少资源的集群将得到最低分 -100分。其他的分数介于 -100 到 100 之间。
优选策略 AddOn 还支持通过第三方提供的分数选择集群。这部分也是 Placement 在 OCM v0.6.0 中的最新功能。OCM v0.6.0 中提供了新的 API AddOnPlacementScore 用以支持一种基于自定义分数的更具可扩展性的调度方式。
作为使用者,可以在 yaml 文件中 prioritizerPolicy 下配置 AddOn,来指定自定义分数以选择集群。
作为分数的提供者,第三方的控制器 (controller) 可以在中心 (Hub Cluster) 或托管群集 (Managed Cluster) 上运行,controller 需要维护 AddOnPlacementScore 的生命周期并将分数更新到其中。
关于可扩展调度的更多详细内容,可以参考社区文档[6]。
在做出调度决策时,ManagedCluster 按照最终的总分排序。总分是每个优选器的打分乘以权重的总和: 总分 = sum(prioritizer_x_weight * prioritizer_x_score),其中 prioritizer_x_weight 是优选器 (prioritizer)X 的权重,prioritizer_x_score是优选器 (prioritizer)X 为一个 ManagedCluster 打的分数。
可以通过调整优选器 (prioritizer) 的权重来影响最终的分数,比如:
通过给资源类型的优选器 ResourceAllocatableCPU 和 ResourceAllocatableMemory 设置权重,来基于可分配的资源做调度。
通过给资源类型的优选器设置一个更高的权重,使得调度结果对于资源的变化更加敏感。
通过给优选器 Steady 设置更高的权重,使得调度结果可以忽略资源的变化保持稳定。
如下是一些实际的例子,来说明多个优选器是如何共同工作以得到最终的调度结果。这些例子也可以视为是在一些场景下的最佳实践。
以下示例中假设有三个托管集群 (ManagedCluster) 绑定在命名空间 (namespace)ns1, 其中 cluster1,cluster2,cluster3 分别有 60MB,80MB 和 100MB 可分配内存。
示例 1:选择具有最大可分配内存的集群。 在此示例中,希望选择具有最大可分配内存的集群。为了按可分配内存对集群进行优先级排序,可以在优选策略 (prioritizerPolicy) 中配置ResourceAllocatableMemory。
apiVersion: cluster.open-cluster-management.io/v1alpha1 kind: Placement metadata: name: demo namespace: ns1 spec: numberOfClusters: 2 prioritizerPolicy: configurations: - scoreCoordinate: builtIn: ResourceAllocatableMemory

Placement创建之后,可以通过oc describe placement命令,查看events来了解优先级排序是如何选中集群的。
# oc describe placement demo -n ns1 Name:demo Namespace:ns1 Labels: Annotations: API Version:cluster.open-cluster-management.io/v1alpha1 Kind:Placement … Status: Conditions: Last Transition Time:2021-11-09T07:02:14Z Message:All cluster decisions scheduled Reason:AllDecisionsScheduled Status:True Type:PlacementSatisfied Number Of Selected Clusters:2 Events: TypeReasonAgeFromMessage ------------------------- NormalDecisionCreate10splacementControllerDecision demo-decision-1 is created with placement demo in namespace ns1 NormalDecisionUpdate10splacementControllerDecision demo-decision-1 is updated with placement demo in namespace ns1 NormalScoreUpdate10splacementControllercluster1:0 cluster2:100 cluster3:200

在这个例子中,在 Additive 模式下,优选器包括了默认权重为 1 的 Balance 和 Steady 以及显示配置了权重为 1 的 ResourceAllocatableMemory。一个集群的最终得分将由如下公式决定:
1 prioritizer_balance_score + 1 prioritizer_steady_score + 1 * prioritizer_resourceallocatablememory_score
从上面的 event 中可以看出来,cluster1 总分为 0,cluster2 总分为 100,cluster3 总分为 200。调度结果应该选择 cluster2 和 cluster3。
可以通过 oc describe placementdecision 命令来验证调度结果,如下:
# oc describe placementdecision demo-decision-1 -n ns1 Name:demo-decision-1 Namespace:ns1 Labels:cluster.open-cluster-management.io/placement=placement-jkd42 Annotations: API Version:cluster.open-cluster-management.io/v1alpha1 Kind:PlacementDecision ... Status: Decisions: Cluster Name:cluster2 Reason: Cluster Name:cluster3 Reason: Events:

可以看到,在 PlacementDecision 的 status 中,cluster2 和 cluster3 被列在其中。
让我们尝试添加一个新的集群,并且这个集群上有着比被选中集群高出一些的可分配内存。
Placement 调度器会监视 (watch) 托管集群。一旦有资源变化,将触发重新调度。现在,让我们试着添加一个有 100MB 可分配内存的新集群 cluster4,同时检查 Placement 中的事件 (event)。
# oc describe placement demo -n ns1 ... Events: TypeReasonAgeFromMessage ------------------------- NormalDecisionCreate100splacementControllerDecision demo-decision-1 is created with placement demo in namespace ns1 NormalDecisionUpdate100splacementControllerDecision demo-decision-1 is updated with placement demo in namespace ns1 NormalScoreUpdate100splacementControllercluster1:0 cluster2:100 cluster3:200

可以看到并没有事件更新,调度结果也没有发生变化。所以当我们添加一个仅仅比 cluster2 的内存高出 20MB 的 cluster4 时,调度结果并不会被影响。
让我们尝试添加一个新的集群,并且这个集群上有着比被选中集群高出很多的可分配内存。
现在,让我们试着添加一个有 150MB 可分配内存的新集群 cluster4,同时再次检查 Placement 中的事件 (event)。
# oc describe placement demo -n ns1 ... Events: TypeReasonAgeFromMessage ------------------------- NormalDecisionCreate2m10splacementControllerDecision demo-decision-1 is created with placement demo in namespace ns1 NormalDecisionUpdate2m10splacementControllerDecision demo-decision-1 is updated with placement demo in namespace ns1 NormalScoreUpdate2m10splacementControllercluster1:0 cluster2:100 cluster3:200 NormalDecisionUpdate3splacementControllerDecision demo-decision-1 is updated with placement demo in namespace ns1 NormalScoreUpdate3splacementControllercluster1:200 cluster2:145 cluster3:189 cluster4:200

这一次,调度结果更新了,Placement 被重新调度到了 cluster3 和 cluster4 上。
# oc describe placementdecision demo-decision-1 -n ns1 ... Status: Decisions: Cluster Name:cluster3 Reason: Cluster Name:cluster4 Reason:

在上面这个例子中,当资源只发生了少许变化时,调度结果并不会被影响。而当资源发生比较大的变化时,变化会立刻反应在调度结果中。这样便引发出如下 2 个挑战:
如果希望调度结果对资源变化保持敏感,应该怎么做?
如果希望调度结果保持稳定,忽略资源的变化,应该怎么做?
还记得我们在 prioritizerPolicy 中有 4 个优选器并且可以调整他们的权重吗?我们可以通过修改 prioritizerPolicy 的配置来解决上面两个问题。
示例 2:选择具有最大可分配内存的群集,并使 Placement 对资源变化保持敏感。 为了使调度结果对资源的变化敏感,这次我们显式设置了优选器 ResourceAllocatableMemory,权重为 3。
apiVersion: cluster.open-cluster-management.io/v1alpha1 kind: Placement metadata: name: placement7 namespace: ns1 spec: numberOfClusters: 2 prioritizerPolicy: configurations: - scoreCoordinate: builtIn: ResourceAllocatableMemory weight: 3

当 Placement 创建好之后,让我们通过 oc describe 命令来检查 Placement 和 PlacementDecision 的结果。
# oc describe placement demo -n ns1 ... Status: Conditions: Last Transition Time:2021-11-09T08:58:40Z Message:All cluster decisions scheduled Reason:AllDecisionsScheduled Status:True Type:PlacementSatisfied Number Of Selected Clusters:2 Events: TypeReasonAgeFromMessage ------------------------- NormalDecisionCreate35splacementControllerDecision demo-decision-1 is created with placement demo in namespace ns1 NormalDecisionUpdate35splacementControllerDecision demo-decision-1 is updated with placement demo in namespace ns1 NormalScoreUpdate35splacementControllercluster1:-200 cluster2:100 cluster3:400 # oc describe placementdecision demo-decision-1 -n ns1 ... Status: Decisions: Cluster Name:cluster2 Reason: Cluster Name:cluster3 Reason:

初始的调度结果为 cluster2 和 cluster3。现在,让我们试着再次加入一个有 100MB 可分配内存的集群,然后检查 Placement 事件。
# oc describe placement demo -n ns1 ... Events: TypeReasonAgeFromMessage ------------------------- NormalDecisionCreate3m1splacementControllerDecision demo-decision-1 is created with placement demo in namespace ns1 NormalDecisionUpdate3m1splacementControllerDecision demo-decision-1 is updated with placement demo in namespace ns1 NormalScoreUpdate3m1splacementControllercluster1:-200 cluster2:100 cluster3:400 NormalDecisionUpdate2splacementControllerDecision demo-decision-1 is updated with placement demo in namespace ns1 NormalScoreUpdate2splacementControllercluster1:-200 cluster2:200 cluster3:500 cluster4:400

这一次,PlacementDecision 更新了,并且结果重新调度到了 cluster3 和 cluster4。
# oc describe placementdecision demo-decision-1 -n ns1 ... Status: Decisions: Cluster Name:cluster3 Reason: Cluster Name:cluster4 Reason:

示例 3:选择具有最大可分配内存的集群并稳定调度结果。 为了使调度结果保持稳定,这次我们显式设置了优选器 Steady,并且设置权重为 3。
apiVersion: cluster.open-cluster-management.io/v1alpha1 kind: Placement metadata: name: demo namespace: ns1 spec: numberOfClusters: 2 prioritizerPolicy: configurations: - scoreCoordinate: builtIn: ResourceAllocatableMemory - scoreCoordinate: builtIn: Steady weight: 3

Placement 创建好之后,再次通过 oc describe 命令来检查 Placement 和 PlacementDecision 的结果。
# oc describe placement demo -n ns1 ... Status: Conditions: Last Transition Time:2021-11-09T09:05:36Z Message:All cluster decisions scheduled Reason:AllDecisionsScheduled Status:True Type:PlacementSatisfied Number Of Selected Clusters:2 Events: TypeReasonAgeFromMessage ------------------------- NormalDecisionCreate15splacementControllerDecision demo-decision-1 is created with placement demo in namespace ns1 NormalDecisionUpdate15splacementControllerDecision demo-decision-1 is updated with placement demo in namespace ns1 NormalScoreUpdate15splacementControllercluster1:0 cluster2:100 cluster3:200 # oc describe placementdecision demo-decision-1 -n ns1 ... Status: Decisions: Cluster Name:cluster2 Reason: Cluster Name:cluster3 Reason:

初始的调度结果为 cluster2 和 cluster3。
现在,让我们试着再次加入一个有 150MB 可分配内存的集群,然后检查 Placement 事件。这一次 event 并没有更新。
# oc describe placement demo -n ns1 ... Events: TypeReasonAgeFromMessage ------------------------- NormalDecisionCreate80splacementControllerDecision demo-decision-1 is created with placement demo in namespace ns1 NormalDecisionUpdate80splacementControllerDecision demo-decision-1 is updated with placement demo in namespace ns1 NormalScoreUpdate80splacementControllercluster1:0 cluster2:100 cluster3:200

再次检查 PlacementDecision,可以看到调度结果并没有变化,固定在了 cluster2 和 cluster3。
# oc describe placementdecision demo-decision-1 -n ns1 ... Status: Decisions: Cluster Name:cluster2 Reason: Cluster Name:cluster3 Reason:

在前面的三个示例中,我们展示了多个优选器是如何协同工作的,以及如何通过调整每个优选器的权重来影响最终决策。在使用中,你也可以按需求尝试调整权重或更改已启用的优选器。
总结
通过本文,你可以了解到如何在不同的应用场景下使用 Placement API。这篇文章解释了什么是 Placement 以及它如何和一些主流的开源项目配合使用。介绍了 Placement 如何选择集群,以及通过一些示例展示多个优选器是如何共同工作并做出调度决策的。在文章的最后,提供了一些示例来展示最佳实践。欢迎随时在 open-cluster-management-io GitHub 社区[7]中提出问题,或使用 Slack[8]与我们联系,同时加入我们的 Google Groups 以订阅我们的定期社区会议。
未来我们将在 OCM 里看到更多结合 OCM 高级调度能力的其他高级功能模块,比如多集群 Workload 调度/容灾等等。
相关链接
[1] Open Cluster Management 0.6 发布:
https://open-cluster-manageme...
[2] OCM 社区:
https://github.com/open-clust...
[3] ManagedCluster 和 ManagedClusterSet:
https://open-cluster-manageme...
[4] Placement:
https://open-cluster-manageme...
[5] PlacementSpec:
https://github.com/open-clust...
[6] 社区文档:
https://github.com/open-clust...
[7] open-cluster-management-io GitHub 社区:
https://github.com/open-clust...
[8] Slack:
https://kubernetes.slack.com/...
参考:
https://timewitch.net/post/20...
【CNCF 沙箱项目 OCM Placement 多集群调度指南】原文链接
本文为阿里云原创内容,未经允许不得转载。

    推荐阅读