K8s 中的亲和性与反亲和性调度策略

各位听众,各位看官,大家好!我是你们的老朋友——一位在 Kubernetes (K8s) 的世界里摸爬滚打多年的“老码农”。今天,咱们不谈高深的架构,不聊复杂的源码,就来聊聊 K8s 里一个听起来高大上,但实际上非常实用,而且能让你的应用跑得更稳、更舒服的玩意儿:亲和性与反亲和性调度策略

这名字听起来是不是有点像相亲节目?没错,K8s 的调度器就像一个媒婆,负责把你的应用(Pod)介绍给合适的“家庭”(Node)。而亲和性和反亲和性,就是媒婆在牵线搭桥时需要考虑的重要因素。

为什么要考虑亲和性和反亲和性?

想象一下,如果你有两个好兄弟,一个爱吃辣,一个爱吃甜。你请他们吃饭,总不能把他们安排在一家只有辣菜的餐馆吧?或者,如果你有两个死对头,让他们坐在一起,恐怕还没等菜上来,就要打起来了。

K8s 也是一样。有些 Pod 之间天生就应该“亲近”,比如,数据库的 Master 和 Slave,最好部署在不同的 Node 上,这样可以避免单点故障。而有些 Pod 之间则应该“疏远”,比如,同一个应用的多个实例,最好部署在不同的 Node 上,以提高应用的可用性。

所以,亲和性和反亲和性,就是为了让我们的应用在集群中找到最合适的“归宿”,保证应用的性能、可用性和稳定性。

一、亲和性:让 Pod 们“亲上加亲”

亲和性,顾名思义,就是让 Pod 们尽可能地部署在一起。这就像给 Pod 们找对象,条件是“门当户对”、“志趣相投”。

K8s 提供了两种类型的亲和性:

  • 节点亲和性 (Node Affinity):基于 Node 的标签进行匹配,指定 Pod 只能部署在满足特定标签的 Node 上。
  • Pod 间亲和性 (Pod Affinity):基于 Pod 的标签进行匹配,指定 Pod 只能部署在与特定 Pod 部署在同一个 Node 上。

咱们一个个来分析:

1. 节点亲和性 (Node Affinity)

想象一下,你有一台 GPU 服务器,专门用来跑深度学习任务。你肯定不希望你的深度学习 Pod 被调度到没有 GPU 的 Node 上吧?这时候,就可以使用节点亲和性。

首先,给你的 GPU 服务器打上一个标签,比如 gpu=true

kubectl label node <node-name> gpu=true

然后,在你的 Pod 定义文件中,指定节点亲和性:

apiVersion: v1
kind: Pod
metadata:
  name: gpu-pod
spec:
  containers:
  - name: gpu-container
    image: your-gpu-image
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: gpu
            operator: In
            values:
            - "true"

这段 YAML 的意思是:

  • requiredDuringSchedulingIgnoredDuringExecution:表示在调度时必须满足亲和性规则,但是在 Pod 运行期间,如果 Node 的标签发生变化,Pod 不会被驱逐。
  • nodeSelectorTerms:定义了 Node 选择的条件。
  • matchExpressions:定义了具体的匹配规则。
    • key: gpu:表示要匹配的标签的 key 是 gpu
    • operator: In:表示匹配的操作符是 In,也就是 Node 必须包含 gpu=true 标签。
    • values: ["true"]:表示匹配的值是 true

简单来说,这段 YAML 就是告诉 K8s:这个 Pod 只能调度到拥有 gpu=true 标签的 Node 上。

节点亲和性还有另外两种策略:

  • preferredDuringSchedulingIgnoredDuringExecution:表示在调度时尽量满足亲和性规则,但是如果找不到合适的 Node,也可以调度到不满足规则的 Node 上。
  • requiredDuringSchedulingRequiredDuringExecution:和 requiredDuringSchedulingIgnoredDuringExecution 类似,但是如果 Node 的标签发生变化,导致 Pod 不再满足亲和性规则,Pod 会被驱逐。
策略 描述
requiredDuringSchedulingIgnoredDuringExecution 必须满足,调度后忽略。Pod 必须调度到满足条件的 Node 上,调度后即使 Node 标签改变,Pod 也不会被驱逐。
preferredDuringSchedulingIgnoredDuringExecution 尽量满足,调度后忽略。Pod 尽量调度到满足条件的 Node 上,如果没有满足条件的 Node,也可以调度到其他 Node 上。调度后即使 Node 标签改变,Pod 也不会被驱逐。
requiredDuringSchedulingRequiredDuringExecution 必须满足,调度后也必须满足。Pod 必须调度到满足条件的 Node 上,调度后如果 Node 标签改变,导致 Pod 不再满足条件,Pod 会被驱逐。

2. Pod 间亲和性 (Pod Affinity)

有时候,我们需要让两个 Pod 部署在同一个 Node 上,比如,一个 Web 应用和一个数据库,它们之间需要频繁的通信,如果部署在不同的 Node 上,会增加网络延迟。

这时候,就可以使用 Pod 间亲和性。

假设你有一个 Web 应用 Pod,标签是 app=web,你想让数据库 Pod 也部署在同一个 Node 上。你可以这样定义数据库 Pod:

apiVersion: v1
kind: Pod
metadata:
  name: db-pod
  labels:
    app: db
spec:
  containers:
  - name: db-container
    image: your-db-image
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - "web"
        topologyKey: kubernetes.io/hostname

这段 YAML 的意思是:

  • podAffinity:表示使用 Pod 间亲和性。
  • requiredDuringSchedulingIgnoredDuringExecution:和节点亲和性中的含义一样。
  • labelSelector:定义了要匹配的 Pod 的标签。
    • matchExpressions:定义了具体的匹配规则。
      • key: app:表示要匹配的标签的 key 是 app
      • operator: In:表示匹配的操作符是 In,也就是要匹配的 Pod 必须包含 app=web 标签。
      • values: ["web"]:表示匹配的值是 web
  • topologyKey: kubernetes.io/hostname:表示亲和性的范围是 Node。也就是说,只有和 app=web 的 Pod 部署在同一个 Node 上的,才能满足亲和性规则。

简单来说,这段 YAML 就是告诉 K8s:这个数据库 Pod 只能调度到和 app=web 的 Pod 部署在同一个 Node 上。

二、反亲和性:让 Pod 们“保持距离”

反亲和性,和亲和性正好相反,就是让 Pod 们尽可能地不要部署在一起。这就像给 Pod 们找对象,条件是“八字不合”、“水火不容”。

K8s 也提供了两种类型的反亲和性:

  • 节点反亲和性 (Node Anti-Affinity):基于 Node 的标签进行匹配,指定 Pod 不能部署在满足特定标签的 Node 上。
  • Pod 间反亲和性 (Pod Anti-Affinity):基于 Pod 的标签进行匹配,指定 Pod 不能部署在与特定 Pod 部署在同一个 Node 上。

咱们也一个个来分析:

1. 节点反亲和性 (Node Anti-Affinity)

假设你有一些老旧的服务器,性能比较差,你想避免重要的 Pod 调度到这些服务器上。你可以给这些服务器打上一个标签,比如 legacy=true,然后使用节点反亲和性。

apiVersion: v1
kind: Pod
metadata:
  name: important-pod
spec:
  containers:
  - name: important-container
    image: your-important-image
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: legacy
            operator: DoesNotExist

这段 YAML 的意思是:这个 Pod 不能调度到拥有 legacy=true 标签的 Node 上。 DoesNotExist 是一个操作符,表示 Node 不能包含指定的标签。

2. Pod 间反亲和性 (Pod Anti-Affinity)

这可能是最常用的反亲和性策略了。为了提高应用的可用性,我们通常会运行多个相同的 Pod 实例。但是,如果这些 Pod 实例都部署在同一个 Node 上,一旦这个 Node 发生故障,整个应用就挂了。

所以,我们需要使用 Pod 间反亲和性,让这些 Pod 实例尽可能地部署在不同的 Node 上。

假设你有一个 Web 应用 Pod,标签是 app=web,你想让同一个应用的多个实例不要部署在同一个 Node 上。你可以这样定义 Web 应用 Pod:

apiVersion: v1
kind: Pod
metadata:
  name: web-pod
  labels:
    app: web
spec:
  containers:
  - name: web-container
    image: your-web-image
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - "web"
        topologyKey: kubernetes.io/hostname

这段 YAML 的意思是:

  • podAntiAffinity:表示使用 Pod 间反亲和性。
  • requiredDuringSchedulingIgnoredDuringExecution:和亲和性中的含义一样。
  • labelSelector:定义了要匹配的 Pod 的标签。
    • matchExpressions:定义了具体的匹配规则。
      • key: app:表示要匹配的标签的 key 是 app
      • operator: In:表示匹配的操作符是 In,也就是要匹配的 Pod 必须包含 app=web 标签。
      • values: ["web"]:表示匹配的值是 web
  • topologyKey: kubernetes.io/hostname:表示反亲和性的范围是 Node。也就是说,只有和 app=web 的 Pod 部署在同一个 Node 上的,才不满足反亲和性规则。

简单来说,这段 YAML 就是告诉 K8s:同一个 app=web 的 Pod 实例,不能部署在同一个 Node 上。

三、实战案例:打造高可用的 Web 应用

现在,让我们通过一个实战案例,来巩固一下我们今天学到的知识。

假设我们要部署一个高可用的 Web 应用,包含以下组件:

  • Web 应用 (web):提供用户界面,需要部署多个实例,保证高可用。
  • 数据库 (db):存储用户数据,需要部署 Master 和 Slave,保证数据安全。
  • 缓存 (cache):缓存热点数据,提高访问速度。

我们的目标是:

  1. Web 应用的多个实例尽可能部署在不同的 Node 上。
  2. 数据库的 Master 和 Slave 部署在不同的 Node 上。
  3. 缓存和 Web 应用部署在同一个 Node 上,减少网络延迟。

我们可以这样定义这些组件:

1. Web 应用 (web)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: web-container
        image: your-web-image
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - "web"
            topologyKey: kubernetes.io/hostname
        podAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - "cache"
              topologyKey: kubernetes.io/hostname

这里使用了 Pod 间反亲和性,保证同一个 Web 应用的多个实例不会部署在同一个 Node 上。同时,使用了 Pod 间亲和性(preferredDuringSchedulingIgnoredDuringExecution),尽量和缓存部署在同一个节点,提高了性能。weight: 100 表示优先级,值越大,优先级越高。

2. 数据库 Master (db-master)

apiVersion: v1
kind: Pod
metadata:
  name: db-master
  labels:
    app: db
    role: master
spec:
  containers:
  - name: db-container
    image: your-db-image
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
          - "db"
        topologyKey: kubernetes.io/hostname

这里使用了 Pod 间反亲和性,保证数据库的 Master 和 Slave 不会部署在同一个 Node 上。

3. 数据库 Slave (db-slave)

apiVersion: v1
kind: Pod
metadata:
  name: db-slave
  labels:
    app: db
    role: slave
spec:
  containers:
  - name: db-container
    image: your-db-image
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - "db"
        topologyKey: kubernetes.io/hostname

和 Master 一样,也使用了 Pod 间反亲和性。

4. 缓存 (cache)

apiVersion: v1
kind: Pod
metadata:
  name: cache
  labels:
    app: cache
spec:
  containers:
  - name: cache-container
    image: your-cache-image

缓存组件没有特别的亲和性或反亲和性配置,因为 Web 应用已经配置了亲和性,会尽量和缓存部署在同一个节点。

通过以上配置,我们就可以打造一个高可用的 Web 应用,保证应用的性能、可用性和稳定性。

四、总结与注意事项

今天,我们一起学习了 K8s 中的亲和性与反亲和性调度策略。希望通过今天的学习,大家能够更好地理解和运用这些策略,让你的应用在 K8s 集群中跑得更稳、更舒服。

最后,给大家几点注意事项:

  • 过度使用亲和性和反亲和性可能会导致调度失败。如果你的集群资源不足,或者亲和性规则过于严格,可能会导致 Pod 无法调度。
  • 亲和性和反亲和性只是尽力而为,不能保证 100% 的效果。K8s 的调度器会尽力满足你的要求,但是如果实在无法满足,可能会忽略你的规则。
  • 合理使用亲和性和反亲和性可以提高应用的性能、可用性和稳定性。但是,也需要根据实际情况进行调整,找到最适合你的应用的配置。

希望今天的分享对大家有所帮助!如果你还有任何问题,欢迎随时提问。咱们下期再见! 🎉

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注