作者:Wei Huan(IBM),Aldo Culquicondor(谷歌)
管理跨集群的Pod分发非常困难。著名的Kubernetes特性为Pod亲和(affinity)和反亲和(anti-affinity),允许在不同的拓扑中控制Pod的放置。但是,这些特性只解决部分Pod分布用例:要么将无限个Pod放置到单个拓扑中,要么不允许两个Pod在同一个拓扑中共存。在这两种极端情况之间,有一个共同的需求,就是在拓扑之间均匀地分布Pod,以便实现更好的集群利用率和应用程序的高可用性。
PodTopologySpread调度插件(最初被提议为EvenPodsSpread)就是为了填补这一空白而设计的。我们在1.18将其升级为beta版本。
API的变化
Pod的spec API中引入了一个新的topologySpreadConstraints字段:
spec:
topologySpreadConstraints:
- maxSkew: <integer>
topologyKey: <string>
whenUnsatisfiable: <string>
labelSelector: <object>
由于该API嵌入到Pod的规范中,因此可以在所有高级工作负载API中使用该特性,例如Deployment、DaemonSet、StatefulSet等。
让我们看一个集群的例子来理解这个API。
高级用法
正如特性名称“PodTopologySpread”所示,此特性的基本用法是以绝对均匀的方式(maxSkew=1)或相对均匀的方式(maxSkew>=2)运行工作负载。详情请参阅官方文件。
除了这一基本用法之外,还有一些高级用法示例可以使你的工作负载受益于高可用性和集群利用率。
与NodeSelector/NodeAffinity一起使用
你可能会发现,我们没有“topologyValues”字段来限制Pod要调度的拓扑。默认情况下,它将搜索所有节点并按“topologyKey”对它们进行分组。有时这可能不是理想的情况。例如,假设有一个集群,节点标记为“env=prod”、“env=staging”和“env=qa”,现在你想跨区域均匀地将pod放置到“qa”环境中,这可能吗?
答案是肯定的。你可以利用NodeSelector或NodeAffinity API规范。在底层,PodTopologySpread特性将遵循这一点,并计算满足选择器的节点之间的扩展约束。
如上所示,可以指定spec.affinity。nodeAffinity将“搜索范围”限制为“qa”环境,在此范围内,Pod将被调度到一个满足topologySpreadConstraints的区域。在本例中,它是“zone2”。
多个TopologySpreadConstraint
理解单个TopologySpreadConstraint如何工作是很直观的。多重TopologySpreadConstraint是什么情况?在内部,每个TopologySpreadConstraint是独立计算的,结果集将被合并以生成最终的结果集,也就是合适的节点。
在下面的例子中,我们想要在一个集群中安排一个Pod,同时满足两个需求:
对于第一个约束,在zone1中有3个Pod,在zone2中有2个Pod,因此传入的Pod只能放在zone2中,以满足“maxSkew=1”约束。换句话说,结果集是nodeX和nodeY。
第二个约束条件是,nodeB和nodeX中的Pod太多,所以传入的Pod只能放在nodeA和nodeY中。
现在,我们可以从集合{nodeX, nodeY}(来自第一个约束)和{nodeA, nodeY}(来自第二个约束)的交集推断出唯一的限定节点是nodeY。
多重TopologySpreadConstraint功能强大,但是一定要理解它与前面的“NodeSelector/NodeAffinity”例子的区别:一个是独立计算结果集,然后相互连接;另一种是根据节点约束的过滤结果来计算topologySpreadConstraints。
除了在所有topologySpreadConstraints中使用硬约束之外,你还可以结合使用硬约束和软约束来满足更多样化的集群情况。
注意:如果两个TopologySpreadConstraints被应用到同一个{topologyKey, when unsatis} tuple,Pod的创建将被阻止,返回一个验证错误。
PodTopologySpread默认设定
PodTopologySpread是一个Pod级别的API。因此,要使用该特性,工作负载作者需要了解集群的底层拓扑,然后在Pod规范中为每个工作负载指定适当的topologySpreadConstraints。尽管pod级别的API提供了最大的灵活性,但也可以指定集群级别的默认值。
默认的PodTopologySpread约束,允许你为集群中的所有工作负载指定扩展,并根据其拓扑进行调整。当启动kube-scheduler时,操作员/管理员可以在scheduling配置文件API中将约束指定为PodTopologySpread插件参数。
示例配置可能如下所示:
apiVersion: kubescheduler.config.k8s.io/v1alpha2
kind: KubeSchedulerConfiguration
profiles:
pluginConfig:
- name: PodTopologySpread
args:
defaultConstraints:
- maxSkew: 1
topologyKey: example.com/rack
whenUnsatisfiable: ScheduleAnyway
在配置默认约束时,标签选择器必须为空。kube-scheduler将把标签选择器从Pod的成员推断为Services、ReplicationControllers、ReplicaSets或StatefulSets。Pod总是可以通过PodSpec提供它们自己的约束来覆盖默认的约束。
注意:当使用默认的PodTopologySpread约束时,建议禁用旧的DefaultTopologySpread插件。
总结
PodTopologySpread允许你使用灵活且富有表现力的Pod级API为工作负载定义扩展约束。过去,工作负载作者使用Pod反亲和(AntiAffinity)规则强制或提示调度器在每个拓扑域中运行单个Pod。相反,新的PodTopologySpread约束允许Pods指定所需的(硬的)或期望的(软的)skew级别。该特性可以与节点选择器和节点关联配对,以限制扩展到特定的域。Pod扩展约束可以为不同的拓扑(如主机名、区域、区域、机架等)定义。
最后,集群操作者可以定义应用于所有Pod的默认约束。这样,Pod就不需要知道集群的底层拓扑结构。