将Pod指派给节点
你可以约束一个Pod只能在特定的节点上运行。有几种方法可以实现这点,推荐的方法都是用标签选择算符来进行选择。通常这样的约束不是必须的,因为调度器将自动进行合理的放置(比如,将Pod分散到节点上,而不是将Pod放置在可用资源不足的节点上等等)。但在某些情况下,你可能需要进一步控制Pod被部署到的节点。例如,确保Pod最终落在连接了SSD的机器上,或者将来自两个不同的服务且有大量通信的Pods被放置在同一个可用区。
你可以使用下列方法中的任何一种来选择Kubernetes对特定Pod的调度:
节点标签
与很多其他Kubernetes对象类似,节点也有标签。你可以手动地添加标签。Kubernetes也会为集群中所有节点添加一些标准的标签。参见常用的标签、注解和污点以了解常见的节点标签。
Note:
这些标签的取值是取决于云提供商的,并且是无法在可靠性上给出承诺的。例如,
kubernetes.io/hostname的取值在某些环境中可能与节点名称相同,而在其他环境中会取不同的值。
节点隔离/限制
通过为节点添加标签,你可以准备让Pod调度到特定节点或节点组上。你可以使用这个功能来确保特定的Pod只能运行在具有一定隔离性,安全性或监管属性的节点上。
如果使用标签来实现节点隔离,建议选择节点上的kubelet无法修改的标签键。这可以防止受感染的节点在自身上设置这些标签,进而影响调度器将工作负载调度到受感染的节点。
NodeRestriction准入插件防止kubelet使用node-restriction.kubernetes.io/前缀设置或修改标签。
要使用该标签前缀进行节点隔离:
确保你在使用节点鉴权机制并且已经启用了NodeRestriction准入插件。将带有node-restriction.kubernetes.io/前缀的标签添加到Node对象,然后在节点选择器中使用这些标签。例如,example.com.node-restriction.kubernetes.io/fips=true或example.com.node-restriction.kubernetes.io/pci-dss=true。nodeSelector
nodeSelector是节点选择约束的最简单推荐形式。你可以将nodeSelector字段添加到Pod的规约中设置你希望目标节点所具有的节点标签。Kubernetes只会将Pod调度到拥有你所指定的每个标签的节点上。
亲和性与反亲和性
nodeSelector提供了一种最简单的方法来将Pod约束到具有特定标签的节点上。亲和性和反亲和性扩展了你可以定义的约束类型。使用亲和性与反亲和性的一些好处有:
节点亲和性
节点亲和性概念上类似于nodeSelector,它使你可以根据节点上的标签来约束Pod可以调度到哪些节点上。节点亲和性有两种:
Note:
在上述类型中,
IgnoredDuringExecution意味着如果节点标签在Kubernetes调度Pod时发生了变更,Pod仍将继续运行。
你可以使用Pod规约中的.spec.affinity.nodeAffinity字段来设置节点亲和性。例如,考虑下面的Pod规约:
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: another-node-label-key
operator: In
values:
- another-node-label-value
containers:
- name: with-node-affinity
image: k8s.gcr.io/pause:2.0
在这一示例中,所应用的规则如下:
你可以使用operator字段来为Kubernetes设置在解释规则时要使用的逻辑操作符。你可以使用In、NotIn、Exists、DoesNotExist、Gt和Lt之一作为操作符。
NotIn和DoesNotExist可用来实现节点反亲和性行为。你也可以使用节点污点将Pod从特定节点上驱逐。
Note:
如果你同时指定了
nodeSelector和
nodeAffinity,两者必须都要满足,才能将Pod调度到候选节点上。
如果你指定了多个与
nodeAffinity类型关联的
nodeSelectorTerms,只要其中一个
nodeSelectorTerms满足的话,Pod就可以被调度到节点上。
如果你指定了多个与同一
nodeSelectorTerms关联的
matchExpressions,则只有当所有
matchExpressions都满足时Pod才可以被调度到节点上。
节点亲和性权重
你可以为preferredDuringSchedulingIgnoredDuringExecution亲和性类型的每个实例设置weight字段,其取值范围是1到100。当调度器找到能够满足Pod的其他调度请求的节点时,调度器会遍历节点满足的所有的偏好性规则,并将对应表达式的weight值加和。
最终的加和值会添加到该节点的其他优先级函数的评分之上。在调度器为Pod作出调度决定时,总分最高的节点的优先级也最高。
例如,考虑下面的Pod规约:
apiVersion: v1
kind: Pod
metadata:
name: with-affinity-anti-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: label-1
operator: In
values:
- key-1
- weight: 50
preference:
matchExpressions:
- key: label-2
operator: In
values:
- key-2
containers:
- name: with-node-affinity
image: k8s.gcr.io/pause:2.0
如果存在两个候选节点,都满足requiredDuringSchedulingIgnoredDuringExecution规则,其中一个节点具有标签label-1:key-1,另一个节点具有标签label-2:key-2,调度器会考察各个节点的weight取值,并将该权重值添加到节点的其他得分值之上。
Note:
如果你希望Kubernetes能够成功地调度此例中的Pod,你必须拥有打了
kubernetes.io/os=linux标签的节点。
逐个调度方案中设置节点亲和性
FEATURESTATE:Kubernetesv1.20[beta]
在配置多个调度方案时,你可以将某个方案与节点亲和性关联起来,如果某个调度方案仅适用于某组特殊的节点时,这样做是很有用的。要实现这点,可以在调度器配置中为NodeAffinity插件的args字段添加addedAffinity。例如:
apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
- schedulerName: foo-scheduler
pluginConfig:
- name: NodeAffinity
args:
addedAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: scheduler-profile
operator: In
values:
- foo
这里的addedAffinity除遵从Pod规约中设置的节点亲和性之外,还适用于将.spec.schedulerName设置为foo-scheduler。换言之,为了匹配Pod,节点需要满足addedAffinity和Pod的.spec.NodeAffinity。
由于addedAffinity对最终用户不可见,其行为可能对用户而言是出乎意料的。应该使用与调度方案名称有明确关联的节点标签。
Note:
DaemonSet控制器为DaemonSet创建Pods,但该控制器不理会调度方案。DaemonSet控制器创建Pod时,默认的Kubernetes调度器负责放置Pod,并遵从DaemonSet控制器中奢侈的
nodeAffinity规则。
pod间亲和性与反亲和性
Pod间亲和性与反亲和性使你可以基于已经在节点上运行的Pod的标签来约束Pod可以调度到的节点,而不是基于节点上的标签。
Pod间亲和性与反亲和性的规则格式为“如果X上已经运行了一个或多个满足规则Y的Pod,则这个Pod应该(或者在反亲和性的情况下不应该)运行在X上”。这里的X可以是节点、机架、云提供商可用区或地理区域或类似的拓扑域,Y则是Kubernetes尝试满足的规则。
你通过标签选择算符的形式来表达规则(Y),并可根据需要指定选关联的名字空间列表。Pod在Kubernetes中是名字空间作用域的对象,因此Pod的标签也隐式地具有名字空间属性。针对Pod标签的所有标签选择算符都要指定名字空间,Kubernetes会在指定的名字空间内寻找标签。
你会通过topologyKey来表达拓扑域(X)的概念,其取值是系统用来标示域的节点标签键。
Note:
Pod间亲和性和反亲和性都需要相当的计算量,因此会在大规模集群中显著降低调度速度。我们不建议在包含数百个节点的集群中使用这类设置。
Note:
Pod反亲和性需要节点上存在一致性的标签。换言之,集群中每个节点都必须拥有与
topologyKey匹配的标签。如果某些或者所有节点上不存在所指定的
topologyKey标签,调度行为可能与预期的不同。
Pod间亲和性与反亲和性的类型
与节点亲和性类似,Pod的亲和性与反亲和性也有两种类型:
例如,你可以使用requiredDuringSchedulingIgnoredDuringExecution亲和性来告诉调度器,将两个服务的Pod放到同一个云提供商可用区内,因为它们彼此之间通信非常频繁。类似地,你可以使用preferredDuringSchedulingIgnoredDuringExecution反亲和性来将同一服务的多个Pod分布到多个云提供商可用区中。
要使用Pod间亲和性,可以使用Pod规约中的.affinity.podAffinity字段。对于Pod间反亲和性,可以使用Pod规约中的.affinity.podAntiAffinity字段。
Pod亲和性示例
考虑下面的Pod规约:
apiVersion: v1
kind: Pod
metadata:
name: with-pod-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: topology.kubernetes.io/zone
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S2
topologyKey: topology.kubernetes.io/zone
containers:
- name: with-pod-affinity
image: k8s.gcr.io/pause:2.0
本示例定义了一条Pod亲和性规则和一条Pod反亲和性规则。Pod亲和性规则配置为requiredDuringSchedulingIgnoredDuringExecution,而Pod反亲和性配置为preferredDuringSchedulingIgnoredDuringExecution。
亲和性规则表示,仅当节点和至少一个已运行且有security=S1的标签的Pod处于同一区域时,才可以将该Pod调度到节点上。更确切的说,调度器必须将Pod调度到具有topology.kubernetes.io/zone=V标签的节点上,并且集群中至少有一个位于该可用区的节点上运行着带有security=S1标签的Pod。
反亲和性规则表示,如果节点处于Pod所在的同一可用区且至少一个Pod具有security=S2标签,则该Pod不应被调度到该节点上。更确切地说,如果同一可用区中存在其他运行着带有security=S2标签的Pod节点,并且节点具有标签topology.kubernetes.io/zone=R,Pod不能被调度到该节点上。
查阅设计文档以了解Pod亲和性与反亲和性的更多示例。
你可以针对Pod间亲和性与反亲和性为其operator字段使用In、NotIn、Exists、DoesNotExist等值。
原则上,topologyKey可以是任何合法的标签键。出于性能和安全原因,topologyKey有一些限制:
除了labelSelector和topologyKey,你也可以指定labelSelector要匹配的命名空间列表,方法是在labelSelector和topologyKey所在层同一层次上设置namespaces。如果namespaces被忽略或者为空,则默认为Pod亲和性/反亲和性的定义所在的命名空间。
名字空间选择算符
FEATURESTATE:Kubernetesv1.24[stable]
用户也可以使用namespaceSelector选择匹配的名字空间,namespaceSelector是对名字空间集合进行标签查询的机制。亲和性条件会应用到namespaceSelector所选择的名字空间和namespaces字段中所列举的名字空间之上。注意,空的namespaceSelector({})会匹配所有名字空间,而null或者空的namespaces列表以及null值namespaceSelector意味着“当前Pod的名字空间”。
更实际的用例
Pod间亲和性与反亲和性在与更高级别的集合(例如ReplicaSet、StatefulSet、Deployment等)一起使用时,它们可能更加有用。这些规则使得你可以配置一组工作负载,使其位于相同定义拓扑(例如,节点)中。
在下面的Redis缓存Deployment示例中,副本上设置了标签app=store。podAntiAffinity规则告诉调度器避免将多个带有app=store标签的副本部署到同一节点上。因此,每个独立节点上会创建一个缓存实例。
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-cache
spec:
selector:
matchLabels:
app: store
replicas: 3
template:
metadata:
labels:
app: store
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- store
topologyKey: "kubernetes.io/hostname"
containers:
- name: redis-server
image: redis:3.2-alpine
下面的Deployment用来提供Web服务器服务,会创建带有标签app=web-store的副本。Pod亲和性规则告诉调度器将副本放到运行有标签包含app=storePod的节点上。Pod反亲和性规则告诉调度器不要在同一节点上放置多个app=web-store的服务器。
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
spec:
selector:
matchLabels:
app: web-store
replicas: 3
template:
metadata:
labels:
app: web-store
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web-store
topologyKey: "kubernetes.io/hostname"
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- store
topologyKey: "kubernetes.io/hostname"
containers:
- name: web-app
image: nginx:1.16-alpine
创建前面两个Deployment会产生如下的集群布局,每个Web服务器与一个缓存实例并置,并分别运行在三个独立的节点上。
node-1node-2node-3
webserver-1
webserver-2
webserver-3
cache-1
cache-2
cache-3
nodeName
nodeName是比亲和性或者nodeSelector更为直接的形式。nodeName是Pod规约中的一个字段。如果nodeName字段不为空,调度器会忽略该Pod,而指定节点上的kubelet会尝试将Pod放到该节点上。使用nodeName规则的优先级会高于使用nodeSelector或亲和性与非亲和性的规则。
使用nodeName来选择节点的方式有一些局限性:
下面是一个使用nodeName字段的Pod规约示例:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
nodeName: kube-01
上面的Pod只能运行在节点kube-01之上。