蛮荆

Kubernetes 应用最佳实践 - 亲和性和污点容忍度

2023-03-31

亲和性

亲和性用于指定某个 Pod 应该或尽可能在哪些 Node 上运行。

两种策略

1. requiredDuringSchedulingIgnoredDuringExecution

也称之为硬性亲和性 (暴力变量命名法),Pod 必须被调度到满足条件的节点上,如果没有满足的条件,就一直重试。

2. preferredDuringSchedulingIgnoredDuringExecution

也称之为软性亲和性,即使没有满足条件的节点,调度器依然会将 Pod 调度到某一个节点上。

在软性亲和性中,可以为每个匹配表达式设置权重,取值范围是 1 - 100。当调度器找到所有匹配 Pod 亲和性标签的节点时,会通过遍历匹配表达式对权重值求和,选择出总分,从而确定调度的优先级。

后缀

不论是软性亲和性还是硬性亲和性,都有两种通用的后缀:

  • IgnoredDuringExecution: 如果节点标签变化后不再满足 Pod 的匹配条件,节点上已经运行的 Pod 不会受到影响
  • requiredDuringSchedulingRequiredDuringExecution: 如果节点标签变化后不再满足 Pod 的匹配条件,节点上已经运行的 Pod 会被驱逐

常用操作符

操作符 作用
In 标签值存在于集合中
NotIn 标签值不存在于集合中
Exists 包含对应标签
DoesNotExist 不包含对应的标签

匹配表达式

  • nodeSelectorTerms: 如果存在多个条件,节点满足任何一个条件就可以
  • matchExpressions: 如果存在多个条件,节点必须满足所有的条件才可以

三种类型

大部分情况下,可以使用标签选择器来约束 Pod 的调度,保证将 Pod 调度到匹配标签的节点上。

但是相比于标签选择器,亲和性拥有更强的表达能力和调度控制能力,亲和度类型有下面三种:

  1. nodeAffinity: Node 亲和性,表示 Pod 倾向于调度到拥有指定标签的节点上
  2. podAffinity: Pod 亲和性,表示 Pod 倾向于和拥有同样标签的 Pod 调度到一起
  3. podAntiAffinity: Pod 反亲和性,表示 Pod 尽量避免和拥有同样标签的 Pod 调度到一起

注意: 如果同时指定了标签选择器和亲和性,则两者的条件必须同时满足,才能将 Pod 调度到节点上。


示例

通过亲和性规则,我们可以实现更强大的调度逻辑语义,下面来看几个小的示例。

1. 必须调度到 XX 节点

...
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-server
spec:
  # ...
  
  affinity:  # 亲和性
    nodeAffinity: # Node 亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬性亲和性
        nodeSelectorTerms:
          - matchExpressions:
              - key: memory-size # 内存标签名称
                operator: In
                values:
                  - large # 内存标签值
  containers:
    ...

通过上面的 yaml 声明,指定 Redis 必须被调度到拥有较大内存的节点上面。

2. 尽量调度到 XX 节点

...
apiVersion: apps/v1
kind: Stateful
metadata:
  name: mysql-server
spec:
  # ...
  
  affinity: # 亲和性
    nodeAffinity: # Node 亲和性
      preferredDuringSchedulingIgnoredDuringExecution: # 软性亲和性
        - weight: 100 # 权重设置为最高
          nodeSelectorTerms:
            - matchExpressions:
                - key: storage # 硬盘标签名称
                  operator: In
                  values:
                    - ssd # 硬盘标签值
  containers:
    ...

通过上面的 yaml 声明,指定 MySQL 尽可能被调度到拥有 SSD 的节点上面。

3. 避免单点故障

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 3 # 副本数量
  selector:
    matchLabels:
      app: nginx
  template:
    # ...
    
    spec:
      affinity:
        podAntiAffinity: # Pod 反亲和性
          requiredDuringSchedulingIgnoredDuringExecution: # 硬性亲和性
          - labelSelector:
              matchExpressions:
              - key: app # 标签名称
                operator: In
                values:
                - nginx # 标签值
            topologyKey: "kubernetes.io/hostname" # 拓扑域
      containers:
        ...

通过上面的 yaml 声明,指定拥有 3 个副本的 Nginx 必须被调度到 3 个不同节点上面,避免单个节点宕机时服务中断,提高可用性。

上面的 yaml 声明中,有一行代码:

topologyKey: "kubernetes.io/hostname"

这里简单进行说明: 这行代码用于指定反亲和性的拓扑域 key, 告诉调度器应该根据节点的主机名称来确定节点之间的拓扑域。因为我们使用的是反亲和性规则, 同时使用主机名称作为拓扑域 key, 两者结合,确保了同一个 Pod 不会被调度到具有相同主机名称的节点上。


污点和容忍度

污点的作用目标是 Node (节点), 容忍度的作用目标是 Pod。一个 Node 可以设置多个污点,一个 Pod 也可以设置多个容忍度。

和亲和性的差异

污点 (Taint) 和容忍度 (Toleration) 和亲和性机制类似,可以优化 Pod 在集群节点间的调度,只不过两者的工作方式正好相反。

  • 对于亲和性来说,可以将 Pod 调度到满足亲和性的节点上
  • 对于污点来说,只要给节点设置了污点,默认情况下所有 Pod 都无法调度到该节点,只有能容忍该节点污点的 Pod 才会被调度过来

关键字段

名称 描述
key
value
operator 污点操作符
effect 污点影响

常用操作符

操作符 作用
Equal 容忍度等于具体的值
Exists 容忍度包含具体的值

污点影响

NoSchedule

表示只有可以容忍节点污点的 Pod 才会被调度到这个节点上。

PreferNoSchedule

表示 NoSchedule 的宽松版本,如果 Pod 没有节点的污点容忍度,会尽可能阻止 Pod 被调度到这个节点上,但是如果没有其他节点可以调度,Pod 还是会被调度到这个节点上。

NoExecute

前面两种类型只影响到 调度期间的 Pod, NoExecute 类型同时还会影响当前正在节点 运行期间的 Pod。

如果一个节点添加了 NoExecute 污点,那么就会该节点上正在运行着的 Pod 进行检测, 如果 Pod 没有这个新增的 NoExecute 污点容忍度,将会被驱逐。


示例

$ kubectl taint nodes node-123 compute_type=GPU:NoSchedule

上面的命令为名称为 node-123 的节点添加了污点,其中:

  • 污点的 key 字段为 compute_type, 表示计算类型
  • 污点的 value 字段为 GPU
  • 污点的 effect 字段为 NoSchedule

通过这行命令设置,保证只有可以容忍污点的 Pod 才可以被调度到该节点上面,换句话说,这是一个 GPU 计算型节点。

1. 实现节点专用

利用污点和容忍度,可以实现 “非生产环境的 Redis 不能运行在生产环境的节点上” 这个功能。

$ kubectl taint nodes node-123 environment=production:NoSchedule

在生产环境的节点 node-123 上面添加一个污点 (environment=production),并将污点效果设置为 NoSchedule。

apiVersion: v1
kind: Deployment
metadata:
  name: redis-server
spec:
  tolerations: # 容忍度
    - key: environment # 污点 key 
      operator: Equal  # 污点操作符
      value: production # 污点 value
      effect: NoSchedule # 污点影响
  containers:
    ...

在 Pod 中定义了一个污点容忍度规则,确保只有生产环境中的 Pod 会被调度到该节点上面。


最佳实践

亲和性

1. 优化资源分配

通过亲和性将业务相关联的 Pod 调度到同一个节点或者同一组节点 (例如以机架进行分组),可以提高资源利用率并节省成本,尤其是在使用云服务商提供的按量付费资源时效果更明显。

例如在测试环境中,不需要保证高可用性,这时就可以把无状态服务和缓存服务调度到同一节点,通过更紧凑地编排 Pod 的分布来减少总节点的数量,这样多余的节点就不会产生任何费用了。

除了优化资源分配外,还可以实现协同部署,例如将一个前端 Pod 和一个后端 Pod 部署到靠近的节点上,可以降低延时,提升请求的响应时间。

2. 高可用和容错性

通过亲和性将 Pod 调度到不同的节点,扩大服务发生故障时的节点域,提高服务的可用性和容错性,避免单点故障。

污点和容忍度

1. 保证资源独享

通过污点将重要的节点标记为不可调度,然后为重要的服务 Pod 设置容忍度机制,保证只有重要的服务才会被调度到重要的节点上 (例如专门用于 CPU, GPU 密集计算型的节点), 实现 “将 Pod 调度给指定节点” 功能。

2. 避免服务中断

通过容忍性规则可以保证在节点维护或升级期间,将节点标记为故障节点,并触发已运行 Pod 的驱逐和调度,避免服务中断。

注意事项

Pod 间亲和性 (podAffinity) 和反亲和性 (podAntiAffinity) 都需要相当的计算量,因此在大规模集群中会显著降低调度性能,官方建议不要在数百个及以上节点的集群中使用这两类规则。

标签命名 (附录)

Kubernetes 推荐每个资源对象都应该充分使用标签,这样就可以指定任意维度对资源进行拆分组合,下面是官方的标签类型和名称建议。

描述
name 应用名称
instance 应用实例唯一名称
version 应用当前版本
component 应用位于架构中的组件类型
part-of 更高级别应用名称
managed-by 应用管理工具

下面是一个 wordpress 的无状态应用示例:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    name: wordpress
    instance: wordpress-abcxzy
    version: "4.9.4"
    managed-by: helm
    component: server
    part-of: wordpress
...

扩展阅读

转载申请

本作品采用 知识共享署名 4.0 国际许可协议 进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,商业转载请联系作者获得授权。