一、问题的引出

在我们使用 Kubernetes 进行容器编排的时候,默认的节点调度机制有时候会让我们头疼不已。想象一下,你有一堆不同需求的容器要运行在 Kubernetes 集群里,默认的调度器可能会把资源需求大的容器和资源需求小的容器随意分配到各个节点上,这样就可能导致有的节点资源紧张,而有的节点却有大量资源闲置,造成资源分配不合理。

比如说,在一个电商系统中,有商品展示服务、订单处理服务和用户评论服务等不同的微服务以容器的形式运行在 Kubernetes 集群中。商品展示服务可能只需要少量的 CPU 和内存资源,而订单处理服务则需要大量的 CPU 和内存来处理高并发的订单请求。如果默认调度器把多个订单处理服务的容器都调度到了同一个节点上,而其他节点却只运行着商品展示服务的容器,那么这个节点可能会因为资源不足而频繁出现性能问题,影响整个电商系统的稳定性和响应速度。

二、Kubernetes 默认节点调度机制概述

2.1 调度流程

Kubernetes 的默认调度器在调度一个 Pod 时,会经历两个主要阶段:过滤和打分。

过滤阶段就像是一个筛子,它会根据一些规则把不符合条件的节点过滤掉。这些规则包括节点的资源是否足够、节点是否有特定的标签等。例如,一个 Pod 需要 2GB 的内存,如果某个节点的可用内存只有 1GB,那么这个节点就会在过滤阶段被筛掉。

打分阶段则是在过滤后的节点中,根据一些策略为每个节点打分,最后选择分数最高的节点来运行 Pod。比如,有的策略会考虑节点的资源利用率,资源利用率低的节点可能会得到更高的分数。

2.2 示例代码(使用 YAML 文件定义 Pod)

apiVersion: v1
kind: Pod
metadata:
  name: example-pod
spec:
  containers:
  - name: example-container
    image: nginx:1.14.2
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

注释:

  • apiVersionkind 定义了这个 YAML 文件描述的是一个 Pod 对象。
  • metadata 部分给这个 Pod 起了一个名字叫 example-pod
  • spec 部分是 Pod 的规格,其中 containers 定义了 Pod 中包含的容器。这里定义了一个名为 example-container 的容器,使用的镜像为 nginx:1.14.2
  • resources 部分指定了容器的资源请求和限制。requests 表示容器运行时至少需要的资源,这里需要 64Mi 的内存和 250m(即 0.25 个 CPU 核心)的 CPU。limits 表示容器最多可以使用的资源,这里是 128Mi 的内存和 500m 的 CPU。

三、常见的调度问题及影响

3.1 资源不均衡

就像前面提到的电商系统的例子,默认调度器可能会导致资源在节点间分配不均衡。有的节点负载过高,容易出现性能瓶颈,而有的节点则资源闲置,造成浪费。这不仅会影响应用的性能,还会增加运营成本,因为你可能需要为闲置的资源支付费用。

3.2 亲和性和反亲和性问题

在一些场景下,我们希望某些 Pod 能够运行在同一组节点上,或者避免某些 Pod 运行在同一节点上。例如,在一个分布式数据库系统中,我们希望主节点和从节点的 Pod 运行在不同的节点上,以提高系统的可用性和容错性。但默认调度器可能无法满足这种需求,导致系统出现单点故障的风险。

3.3 示例问题分析

假设我们有一个由三个节点组成的 Kubernetes 集群,分别是 node1、node2 和 node3。现在有两个 Pod,PodA 需要 1GB 的内存,PodB 需要 2GB 的内存。默认调度器可能会把 PodA 和 PodB 都调度到 node1 上,而 node2 和 node3 则闲置。这样 node1 可能会因为资源不足而出现性能问题,影响 PodA 和 PodB 的正常运行。

四、解决策略

4.1 节点选择器

节点选择器是一种简单的调度策略,它允许我们通过标签来选择节点。我们可以给节点添加标签,然后在 Pod 的定义中指定需要调度到具有特定标签的节点上。

示例代码

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-node-selector
spec:
  containers:
  - name: nginx-container
    image: nginx:1.14.2
  nodeSelector:
    disktype: ssd

注释:

  • nodeSelector 部分指定了 Pod 需要调度到具有 disktype: ssd 标签的节点上。这样,只有那些被打上了 disktype: ssd 标签的节点才会被考虑用来运行这个 Pod。

4.2 亲和性和反亲和性

4.2.1 节点亲和性

节点亲和性允许我们更灵活地控制 Pod 调度到哪些节点上。它可以分为硬亲和性和软亲和性。硬亲和性表示 Pod 必须调度到满足条件的节点上,否则就无法调度;软亲和性则表示尽量调度到满足条件的节点上,但如果没有满足条件的节点,也可以调度到其他节点上。

示例代码(软节点亲和性)

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-node-affinity
spec:
  containers:
  - name: nginx-container
    image: nginx:1.14.2
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        preference:
          matchExpressions:
          - key: zone
            operator: In
            values:
            - us-east-1

注释:

  • affinity 部分定义了亲和性规则。
  • nodeAffinity 表示节点亲和性。
  • preferredDuringSchedulingIgnoredDuringExecution 表示这是一个软亲和性规则。
  • weight 表示这个规则的权重,权重越高,越优先考虑。
  • matchExpressions 定义了匹配规则,这里表示希望 Pod 尽量调度到 zone 标签值为 us-east-1 的节点上。

4.2.2 反亲和性

反亲和性则是让 Pod 尽量避免调度到某些节点上。例如,我们可以让同一类型的 Pod 尽量分布在不同的节点上,以提高系统的可用性。

示例代码(Pod 反亲和性)

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-pod-anti-affinity
  labels:
    app: my-app
spec:
  containers:
  - name: nginx-container
    image: nginx:1.14.2
  affinity:
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - my-app
          topologyKey: kubernetes.io/hostname

注释:

  • podAntiAffinity 表示 Pod 反亲和性。
  • preferredDuringSchedulingIgnoredDuringExecution 是软反亲和性规则。
  • labelSelector 定义了匹配的 Pod 标签,这里表示要避免和具有 app: my-app 标签的 Pod 调度到同一个节点上。
  • topologyKey 指定了拓扑域,这里是 kubernetes.io/hostname,表示在节点层面进行反亲和性调度。

4.3 资源请求和限制的合理设置

合理设置 Pod 的资源请求和限制可以帮助调度器更好地进行调度。如果资源请求设置得过大,可能会导致很多节点无法满足需求,从而影响调度效率;如果设置得过小,可能会导致 Pod 运行时因为资源不足而出现性能问题。

示例代码

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-resource-settings
spec:
  containers:
  - name: mysql-container
    image: mysql:8.0
    resources:
      requests:
        memory: "512Mi"
        cpu: "500m"
      limits:
        memory: "1Gi"
        cpu: "1"

注释:

  • 这个 Pod 中的 mysql-container 容器请求 512Mi 的内存和 0.5 个 CPU 核心,最大可以使用 1Gi 的内存和 1 个 CPU 核心。这样调度器在调度这个 Pod 时,会根据这些资源需求来选择合适的节点。

五、优化资源分配的效果

5.1 提高资源利用率

通过使用上述的解决策略,我们可以让 Pod 更合理地分布在各个节点上,避免资源的过度集中和闲置。例如,在前面提到的电商系统中,通过使用节点亲和性和反亲和性规则,我们可以把订单处理服务的容器均匀地分布在多个节点上,同时把商品展示服务的容器调度到资源需求较低的节点上,从而提高整个集群的资源利用率。

5.2 增强系统稳定性

合理的资源分配可以减少节点因为资源不足而出现的性能问题,提高系统的稳定性。例如,在一个分布式系统中,通过设置 Pod 反亲和性,避免同一类型的 Pod 都集中在一个节点上,当某个节点出现故障时,不会影响整个系统的正常运行。

六、注意事项

6.1 标签管理

在使用节点选择器和亲和性、反亲和性规则时,需要合理管理节点和 Pod 的标签。标签的命名和使用应该有统一的规范,避免标签混乱导致调度规则无法正常生效。

6.2 资源估算

在设置 Pod 的资源请求和限制时,需要对应用的资源需求进行准确的估算。这可能需要进行一些性能测试和监控,以确保设置的资源既能满足应用的正常运行,又不会造成资源的浪费。

6.3 调度策略的复杂度

虽然使用亲和性和反亲和性等策略可以更灵活地控制调度,但这些策略也会增加调度的复杂度。在实际使用中,需要根据具体的业务场景和集群规模来选择合适的调度策略,避免过度复杂的策略导致调度效率低下。

七、文章总结

Kubernetes 的默认节点调度机制在很多情况下可能无法满足我们对资源分配的需求,会导致资源不均衡、系统稳定性差等问题。通过使用节点选择器、亲和性和反亲和性、合理设置资源请求和限制等解决策略,我们可以优化 Kubernetes 集群的资源分配,提高资源利用率和系统的稳定性。

在实际应用中,我们需要根据具体的业务场景和集群规模,综合运用这些策略,并注意标签管理、资源估算和调度策略的复杂度等问题。这样才能充分发挥 Kubernetes 的优势,让我们的容器化应用在集群中高效、稳定地运行。