Kubernetes Pod 亲和性和反亲和性
通过 Kubernetes 你可以将一个 pod 限制或倾向于在某些特定节点运行。有几种方式可以达到这个目的,它们都通过 label selectors 进行选择。通常情况下,这样的约束是不必要的,因为调度程序会自动进行合理的调度(如通过一系列的评分机制将 pods 合理分配到最优节点上,而不会将 pod 分配在没有足够资源的节点上等)。但是在某些情况下,可能需要更多的策略控制,例如,将 pod 调度到 SSD 的计算节点上,或者将两个通信比较频繁的不同服务 pod 调度到同一个可用域。
labels
在 K8s 中是一个很重要的概念,作为一个标识,Service、Deployments 和 Pods 之间的关联都是通过 label
来实现的。而每个节点也都拥有 label
,通过设置 label
相关的策略可以使得 pods 关联到对应 label
的节点上。
1. nodeSelector
nodeSelector
是最简单的约束方式。 nodeSelector
是 PodSpec 的一个字段。
通过 --show-labels
可以查看当前 nodes 的 labels
$ kubectl get nodes --show-labels NAME STATUS ROLES AGE VERSION LABELS minikube Ready <none> 1m v1.10.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/ hostname=minikube
如果没有额外添加 nodes labels
,那么看到的如上所示的默认标签。我们可以通过 kubectl label node
命令给指定 node 添加 labels
:
$ kubectl label node minikube disktype=ssd node/minikube labeled $ kubectl get nodes --show-labels NAME STATUS ROLES AGE VERSION LABELS minikube Ready <none> 5m v1.10.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,kubernetes.io/hostname=minikube
当然,你也可以通过
kubectl label node
删除指定的labels
(标签 key 接-
号即可)$ kubectl label node minikube disktype- node/minikube labeled $ kubectl get node --show-labels NAME STATUS ROLES AGE VERSION LABELS minikube Ready <none> 23m v1.10.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=minikube
创建测试 pod 并指定 nodeSelector
选项绑定节点:
$ cat nginx.yaml apiVersion: v1 kind: Pod metadata: name: nginx labels: env: test spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent nodeSelector: disktype: ssd $ kubectl create -f nginx.yaml pod/nginx created
查看 pod 调度的节点,即我们指定有 disktype=ssd
label 的 minikube 节点:
$ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE nginx 1/1 Running 0 1m 172.18.0.4 minikube
nodeSelector
可以很方便的解决以上比较简单的需求,但是它还不够灵活。比如我想以机架为单位,部署的服务可以很好的分散在不同机架的服务器上,此时 nodeSelector
就并不是那么管用了。因此,Kubernetes 引入了亲和性和反亲和性概念。
2. 亲和性和反亲和性(Affinity and anti-affinity)
affinity/anti-affinity 特性还处于 beta 状态,相比 nodeSelector
来说有几点优势:
- 提供更多的表示式(不仅仅是 and 匹配)
- 可以指定 "软" 规则限制而不仅仅是硬限制,因此即使调度器不能满足它的规则,也可以正常调度 pod
- 可以针对 node 上运行的 pods 指定
labels
,而不仅仅局限于 node 本身
affinity 特性拥有两种类型,一种是 node affinity,一种是 pod affinity/anti-affinity。node affinity 类似 nodeSelector
,但同时拥有上文提到的 1、2 两点优势,pod affinity/anti-affinity 针对 pods 指定 labels
,同时拥有以上三点优势。
2.1. Node affinity
K8s 在 1.2 的时候以 alpha 的特性引入 node affinity。node affinity 通过 node labels
约束 pod 调度节点。Node affinity 有两种类型:
requiredDuringSchedulingIgnoredDuringExecution
(硬限制,同nodeSelector
)preferredDuringSchedulingIgnoredDuringExecution
(软限制)
看以下例子:
apiVersion: v1 kind: Pod metadata: name: with-node-affinity spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/e2e-az-name operator: In values: - e2e-az1 - e2e-az2 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
以上规则表达的意思是,该 Pod 只能被调度到拥有 kubernetes.io/e2e-az-name=e2e-az1
或者 kubernetes.io/e2e-az-name=e2e-az2
标签的节点上,其中在满足之前标签条件的同时更倾向于调度在拥有 another-node-label-key=another-node-label-value
标签的节点上。
新的 node affinity 支持 In
、 NotIn
、 Exists
、 DoesNotExist
、 Gt
、 Lt
操作符。可以使用 NotIn
、 DoesNotExist
来实现反亲和性,也可以通过 node taints 来实现。
如果同时指定 nodeSelector
和 nodeAffinity
,两者同时满足才会被调度。如果 nodeAffinity
中指定了多个 nodeSelectorTerms
,只要满足其中一个 nodeSelectorTerms
匹配条件即可调度。如果 nodeSelectorTerms
中有多个 matchExpressions
,那么自由满足所有的条件才会被调度。
2.2. Pod affinity and anti-affinity
pod 亲和性和反亲和性在 K8s 1.4 版本引入,它基于运行在 node 上的 pod 标签来限制 pod 调度在哪个节点上,而不是节点的标签。
pod 亲和性和反亲和性需要大量的计算,会显著降低集群的调度速度,不建议在大于几百个节点的集群中使用。 pod 反亲和性要求集群中的所有节点必须具有
topologyKey
匹配的标签,否则可能会导致意外情况发生。
同 node affinity,pod 亲和性和反亲和性也有两种类型:
requiredDuringSchedulingIgnoredDuringExecution
(硬限制)preferredDuringSchedulingIgnoredDuringExecution
(软限制)
不同的是,pod 通过 podAntiAffinity
设置反亲和性,如下例子:
apiVersion: v1 kind: Pod metadata: name: with-pod-affinity spec: affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: security operator: In values: - S1 topologyKey: failure-domain.beta.kubernetes.io/zone podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: security operator: In values: - S2 topologyKey: kubernetes.io/hostname containers: - name: with-pod-affinity image: k8s.gcr.io/pause:2.0
以上示例表示,pod 必须调度在至少运行一个 security=S1
标签的 pod 的节点上(更准确的说,这个 pod 可以运行在节点 N 上,如果该节点有标签 key 为 failure-domain.beta.kubernetes.io/zone
,而且运行着标签为 security=S1
的实例)。另外,反亲和规则表明最好不要调度到运行有 security=S2
标签的 pod 的节点上(更准确的说,如果这个节点拥有标签 key 为 failure-domain.beta.kubernetes.io/zone
,但运行有 security=S2
标签的 pod,那么这个节点就不会被优先选择调度)。
podAffinity
和 podAntiAffinity
支持 In
、 NotIn
、 Exists
、 DoesNotExist
四种表达式。
原则上, topologyKey
可以为任何合法的键值对。但是因为性能和安全的原因,有以下限制:
- 针对
podAffinity
和podAntiAffinity
中的requiredDuringSchedulingIgnoredDuringExecution
,topologyKey
为空是不允许的 - 针对
podAntiAffinity
中的requiredDuringSchedulingIgnoredDuringExecution
,准入控制器选项LimitPodHardAntiAffinityTopology
可以把topologyKey
限制为kubernetes.io/hostname
,如果想自定义值,可以修改准入控制器或者直接禁用 - 针对
podAntiAffinity
中的preferredDuringSchedulingIgnoredDuringExecution
,topologyKey
为空,则代表所有拓扑(仅限于kubernetes.io/hostname
,failure-domain.beta.kubernetes.io/zone
andfailure-domain.beta.kubernetes.io/region
) - 除了以上情况,
topologyKey
可以为任意合法的键值对
除了 labelSelector
和 topologyKey
,还可以指定 namespace 的 labelSelector
作为匹配。 labelSelector
和 topologyKey
属于同一级别,如果未定义或设置为空值,那么默认为定义 pod affinity 和 anti-affinity 所在的空间。
3. 实践案例
3.1. 始终调度在同一个节点
在一个三个节点的集群中,一个 web 应用程序依赖内存存储,如 redis,我们想 web 程序尽可能的和缓存调度在同一个节点上。redis 的 Deployment 配置如下:
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
redis-cache 规则表达 pod 不被调度在同一个节点上。web-server 规则设置如下,表达 pod 不被调度在同一个节点上,并且必须调度在运行标签 app=store
pod 的节点上。
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.12-alpine
相关更多应用场景,建议阅读 抽象优雅的 Affinity,讲解的非常详细。