第一章:ResourceQuotas(资源配额)

1.1 简介

  • 当多个用户或团队共享具有固定节点数目的集群时,人们会担心 有人使用超过其基于公平原则所分配到的资源量
    资源配额是帮助管理员解决这一问题的工具。
  • 资源配额,通过 ResourceQuota 对象来定义,对每个命名空间的资源消耗总量提供限制。 它可以 限制 命名空间中 某种类型的对象的总数目上限也可以限制命令空间中的 Pod 可以使用的计算资源的总上限
  • 资源配额的工作方式如下:
    • 不同的团队可以在不同的命名空间下工作,目前这是非约束性的,在未来的版本中可能会通过 ACL (Access Control List 访问控制列表) 来实现强制性约束。
    • 集群管理员可以为每个命名空间创建一个或多个 ResourceQuota 对象
    • 当用户在命名空间下创建资源(如 Pod、Service 等)时,Kubernetes 的配额系统会跟踪集群的资源使用情况,以确保使用的资源用量不超过 ResourceQuota 中定义的硬性资源限额
    • 如果资源创建或者更新请求违反了配额约束,那么该请求会报错(HTTP 403 FORBIDDEN), 并在消息中给出有可能违反的约束。
    • 如果命名空间下的计算资源 (如 cpumemory)的配额被启用,则用户必须为 这些资源设定请求值(request)和约束值(limit),否则配额系统将拒绝 Pod 的创建。 提示: 可使用 LimitRanger 准入控制器来为没有设置计算资源需求的 Pod 设置默认值。

1.2 计算资源配额

  • 用户可以对给定命名空间下的可被请求的 计算资源 总量进行限制。 | 资源名称 | 描述 | | —- | —- | | limits.cpu | 所有非终止状态的 Pod,其 CPU 限额总量不能超过该值。 | | limits.memory | 所有非终止状态的 Pod,其内存限额总量不能超过该值。 | | requests.cpu | 所有非终止状态的 Pod,其 CPU 需求总量不能超过该值。 | | requests.memory | 所有非终止状态的 Pod,其内存需求总量不能超过该值。 | | hugepages-<size> | 对于所有非终止状态的 Pod,针对指定尺寸的巨页请求总数不能超过此值。 | | cpu | 与 requests.cpu
    相同。 | | memory | 与 requests.memory
    相同。 |
  • 示例:
  1. apiVersion: v1
  2. kind: ResourceQuota
  3. metadata:
  4. name: mem-cpu
  5. namespace: default # 限制 default 名称空间
  6. spec:
  7. hard:
  8. requests.cpu: "1"
  9. requests.memory: 1Gi
  10. limits.cpu: "2"
  11. limits.memory: 2Gi

1.3 扩展资源的资源配额

  • 除上述资源外,在 Kubernetes 1.10 版本中,还添加了对 扩展资源 的支持。
  • 由于扩展资源不可超量分配,因此没有必要在配额中为同一扩展资源同时指定 requestslimits。 对于扩展资源而言,目前仅允许使用前缀为 requests. 的配额项。
  • 以 GPU 拓展资源为例,如果资源名称为 nvidia.com/gpu,并且要将命名空间中请求的 GPU 资源总数限制为 4,则可以如下定义配额:
  1. requests.nvidia.com/gpu: 4

1.4 存储资源配额

  • 用户可以对给定命名空间下的 存储资源 总量进行限制。
  • 此外,还可以根据相关的存储类(Storage Class)来限制存储资源的消耗。 | 资源名称 | 描述 | | —- | —- | | requests.storage | 所有 PVC,存储资源的需求总量不能超过该值。 | | persistentvolumeclaims | 在该命名空间中所允许的 PVC
    总量。 | | <storage-class-name>.storageclass.storage.k8s.io/requests.storage | 在所有与 <storage-class-name>
    相关的持久卷申领中,存储请求的总和不能超过该值。 | | <storage-class-name>.storageclass.storage.k8s.io/persistentvolumeclaims | 在与 storage-class-name 相关的所有持久卷申领中,命名空间中可以存在的持久卷申领
    总数。 |
  • 例如,如果一个操作人员针对 gold 存储类型与 bronze 存储类型设置配额, 操作人员可以定义如下配额:
    • gold.storageclass.storage.k8s.io/requests.storage: 500Gi
    • bronze.storageclass.storage.k8s.io/requests.storage: 100Gi
  • 在 Kubernetes 1.8 版本中,本地临时存储的配额支持已经是 Alpha 功能: | 资源名称 | 描述 | | —- | —- | | requests.ephemeral-storage | 在命名空间的所有 Pod 中,本地临时存储请求的总和不能超过此值。 | | limits.ephemeral-storage | 在命名空间的所有 Pod 中,本地临时存储限制值的总和不能超过此值。 | | ephemeral-storage | 与 requests.ephemeral-storage
    相同。 |

注意:如果所使用的是 CRI 容器运行时,容器日志会被计入临时存储配额。 这可能会导致存储配额耗尽的 Pods 被意外地驱逐出节点。 参考日志架构 了解详细信息。

1.5 对象数量配额

  • 你可以使用以下语法对所有标准的、命名空间域的资源类型进行配额设置:
    • count/<resource>.<group>:用于非核心(core)组的资源。
    • count/<resource>:用于核心组的资源。
  • 这是用户可能希望利用对象计数配额来管理的一组资源示例。
    • count/persistentvolumeclaims
    • count/services
    • count/secrets
    • count/configmaps
    • count/replicationcontrollers
    • count/deployments.apps
    • count/replicasets.apps
    • count/statefulsets.apps
    • count/jobs.batch
    • count/cronjobs.batch
  • 相同语法也可用于自定义资源。 例如,要对 example.com API 组中的自定义资源 widgets 设置配额,请使用 count/widgets.example.com
  • 当使用 count/* 资源配额时,如果对象存在于服务器存储中,则会根据配额管理资源。 这些类型的配额有助于防止存储资源耗尽。例如,用户可能想根据服务器的存储能力来对服务器中 Secret 的数量进行配额限制。 集群中存在过多的 Secret 实际上会导致服务器和控制器无法启动。 用户可以选择对 Job 进行配额管理,以防止配置不当的 CronJob 在某命名空间中创建太多 Job 而导致集群拒绝服务。
  • 对有限的一组资源上实施一般性的对象数量配额也是可能的。 此外,还可以进一步按资源的类型设置其配额。 | 资源名称 | 描述 | | —- | —- | | configmaps | 在该命名空间中允许存在的 ConfigMap 总数上限。 | | persistentvolumeclaims | 在该命名空间中允许存在的 PVC
    的总数上限。 | | pods | 在该命名空间中允许存在的非终止状态的 Pod 总数上限。Pod 终止状态等价于 Pod 的 .status.phase in (Failed, Succeeded)
    为真。 | | replicationcontrollers | 在该命名空间中允许存在的 ReplicationController 总数上限。 | | resourcequotas | 在该命名空间中允许存在的 ResourceQuota 总数上限。 | | services | 在该命名空间中允许存在的 Service 总数上限。 | | services.loadbalancers | 在该命名空间中允许存在的 LoadBalancer 类型的 Service 总数上限。 | | services.nodeports | 在该命名空间中允许存在的 NodePort 类型的 Service 总数上限。 | | secrets | 在该命名空间中允许存在的 Secret 总数上限。 |
  • 例如,pods 配额统计某个命名空间中所创建的、非终止状态的 Pod 个数并确保其不超过某上限值。 用户可能希望在某命名空间中设置 pods 配额,以避免有用户创建很多小的 Pod, 从而耗尽集群所能提供的 Pod IP 地址。

  • 示例:

  1. apiVersion: v1
  2. kind: ResourceQuota
  3. metadata:
  4. name: pods-rq
  5. namespace: default # 限制 default 名称空间
  6. spec:
  7. hard:
  8. pods: "3" # 限制 Pod 的数量

1.6 基于优先级类(PriorityClass)来设置资源配额

  • Pod 可以创建为特定的优先级。 通过使用配额规约中的 scopeSelector 字段,用户可以根据 Pod 的优先级控制其系统资源消耗。
  • 仅当配额规范中的 scopeSelector 字段选择到某 Pod 时,配额机制才会匹配和计量 Pod 的资源消耗。
  • 如果配额对象通过 scopeSelector 字段设置其作用域为优先级类,则配额对象只能 跟踪以下资源:

    • pods
    • cpu
    • memory
    • ephemeral-storage
    • limits.cpu
    • limits.memory
    • limits.ephemeral-storage
    • requests.cpu
    • requests.memory
    • requests.ephemeral-storage
  • 示例:创建一个配额对象,并将其与具有特定优先级的 Pod 进行匹配

  1. # 集群中的 Pod 可取三个优先级类之一,即 "low"、"medium"、"high"
  2. # 为每个优先级创建一个配额对象
  3. apiVersion: v1
  4. kind: List
  5. items:
  6. - apiVersion: v1
  7. kind: ResourceQuota
  8. metadata:
  9. name: pods-high
  10. spec:
  11. hard:
  12. cpu: "1000"
  13. memory: 200Gi
  14. pods: "10"
  15. scopeSelector:
  16. matchExpressions:
  17. - operator : In
  18. scopeName: PriorityClass
  19. values: ["high"]
  20. - apiVersion: v1
  21. kind: ResourceQuota
  22. metadata:
  23. name: pods-medium
  24. spec:
  25. hard:
  26. cpu: "10"
  27. memory: 20Gi
  28. pods: "10"
  29. scopeSelector:
  30. matchExpressions:
  31. - operator : In
  32. scopeName: PriorityClass
  33. values: ["medium"]
  34. - apiVersion: v1
  35. kind: ResourceQuota
  36. metadata:
  37. name: pods-low
  38. spec:
  39. hard:
  40. cpu: "5"
  41. memory: 10Gi
  42. pods: "10"
  43. scopeSelector:
  44. matchExpressions:
  45. - operator : In
  46. scopeName: PriorityClass
  47. values: ["low"]
  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: high-priority
  5. spec:
  6. containers:
  7. - name: high-priority
  8. image: ubuntu
  9. command: ["/bin/sh"]
  10. args: ["-c", "while true; do echo hello; sleep 10;done"]
  11. resources:
  12. requests:
  13. memory: "10Gi"
  14. cpu: "500m"
  15. limits:
  16. memory: "10Gi"
  17. cpu: "500m"
  18. priorityClassName: high # priorityClassName 指的是什么,就优先使用这个优先级的配额约束。

第二章:LimitRange(限制范围)

2.1 简介

  • 默认情况下, Kubernetes 集群上的容器运行使用的计算资源没有限制。
  • 使用资源配额,集群管理员可以以名字空间为单位,限制其资源的使用与创建。
  • 在命名空间中,一个 Pod 或 Container 最多能够使用命名空间的资源配额所定义的 CPU 和内存用量。
  • 有人担心,一个 Pod 或 Container 会垄断所有可用的资源。 LimitRange 是在命名空间内限制资源分配(给多个 Pod 或 Container)的策略对象,例如:资源额度 ResourceQuotas 设置为 requests.cpu 为 1 ,requests.memory 为 1Gi ,但是有人的 Pod 直接设置为 requests.cpu 为 1,requests.memory 为 1Gi,那么其他人就不能再创建 Pod 了,所以需要使用 LimitRange 对资源配额设置区间(最小和最大)。
  • 一个 LimitRange(限制范围) 对象提供的限制能够做到:
    • 在一个命名空间中实施对每个 Pod 或 Container 最小和最大的资源使用量的限制。
    • 在一个命名空间中实施对每个 PersistentVolumeClaim 能申请的最小和最大的存储空间大小的限制。
    • 在一个命名空间中实施对一种资源的申请值和限制值的比值的控制。
    • 设置一个命名空间中对计算资源的默认申请/限制值,并且自动的在运行时注入到多个 Container 中。

2.2 实战

  • 示例:为命名空间配置 CPU 最小和最大约束
  1. apiVersion: v1
  2. kind: LimitRange
  3. metadata:
  4. name: cpu-min-max-demo-lr
  5. namespace: default # 限制 default 命名空间
  6. spec:
  7. limits:
  8. - max:
  9. cpu: "800m"
  10. min:
  11. cpu: "200m" # Pod 中的容器的 cpu 的范围是 200m~800m
  12. type: Container
  • 示例:配置命名空间的最小和最大内存约束
  1. apiVersion: v1
  2. kind: LimitRange
  3. metadata:
  4. name: mem-min-max-demo-lr
  5. namespace: default # 限制 default 命名空间
  6. spec:
  7. limits:
  8. - max:
  9. memory: 1Gi
  10. min:
  11. memory: 500Mi # Pod 中的容器的 memory 的范围是 500Mi~1Gi
  12. type: Container
  • 示例:为命名空间配置默认的 CPU 请求和限制
  1. apiVersion: v1
  2. kind: LimitRange
  3. metadata:
  4. name: cpu-limit-range
  5. namespace: default # 限制 default 命名空间
  6. spec:
  7. limits:
  8. - default:
  9. cpu: 1
  10. defaultRequest:
  11. cpu: 500m # 声明了一个默认的 CPU 请求和一个默认的 CPU 限制
  12. type: Container
  • 示例:为命名空间配置默认的内存请求和限制
  1. apiVersion: v1
  2. kind: LimitRange
  3. metadata:
  4. name: mem-limit-range
  5. namespace: default # 限制 default 命名空间
  6. spec:
  7. limits:
  8. - default:
  9. memory: 512Mi
  10. defaultRequest:
  11. memory: 256Mi # 为命名空间配置默认的内存请求和限制
  12. type: Container
  • 示例:限制存储消耗
  1. apiVersion: v1
  2. kind: LimitRange
  3. metadata:
  4. name: storagelimits
  5. spec:
  6. limits:
  7. - type: PersistentVolumeClaim
  8. max:
  9. storage: 2Gi
  10. min:
  11. storage: 1Gi
  1. apiVersion: v1
  2. kind: ResourceQuota
  3. metadata:
  4. name: storagequota
  5. spec:
  6. hard:
  7. persistentvolumeclaims: "5" # 限制 PVC 数目和累计存储容量
  8. requests.storage: "5Gi"

第三章:调度原理

3.1 概述

  • 在默认情况下,一个 Pod 在哪个 Node 节点上运行,是由 Scheduler 组件采用相应的算法计算出来的,这个过程是不受人工控制的。但是在实际使用中,这并不满足需求,因为很多情况下,我们想控制某些 Pod 到达某些节点上,那么应该怎么做?这就要求了解 Kubernetes 对 Pod 的调度规则,Kubernetes 提供了四大类调度方式:
    • 自动调度(默认):运行在哪个 Node 节点上完全由 Scheduler 经过一系列的算法计算得出。
    • 定向调度:NodeName、NodeSelector。
    • 亲和性调度:NodeAffinity、PodAffinity、PodAntiAffinity。
    • 污点(容忍)调度:Taints、Toleration。

3.2 定向调度

3.2.1 概述

  • 定向调度,指的是利用在 Pod 上声明的 nodeName 或 nodeSelector ,以此将 Pod 调度到期望的 Node 节点上。

注意:这里的调度是强制的,这就意味着即使要调度的目标 Node 不存在,也会向上面进行调度,只不过 Pod 运行失败而已。

3.2.2 nodeName(不建议)

  • nodeName 是节点选择约束的最简单方法,但是由于其自身限制,通常不使用它。 nodeName 是 PodSpec 的一个字段。 如果它不为空,调度器将忽略 Pod,并且给定节点上运行的 kubelet 进程尝试执行该 Pod。 因此,如果 nodeName 在 Pod 的 Spec 中指定了,则它优先于 nodeSelector 。
  • 使用 nodeName 来选择节点的一些限制:

    • 如果指定的节点不存在,
    • 如果指定的节点没有资源来容纳 Pod,Pod 将会调度失败并且其原因将显示为, 比如 OutOfmemory 或 OutOfcpu。
    • 云环境中的节点名称并非总是可预测或稳定的。
  • 示例:

  1. vi k8s-node-name.yaml
  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: nginx
  5. namespace: default
  6. labels:
  7. app: nginx
  8. spec:
  9. nodeName: k8s-node1 # 指定调度到k8s-node1节点上
  10. containers:
  11. - name: nginx
  12. image: nginx:1.20.2
  13. resources:
  14. limits:
  15. cpu: 200m
  16. memory: 500Mi
  17. requests:
  18. cpu: 100m
  19. memory: 200Mi
  20. ports:
  21. - containerPort: 80
  22. name: http
  23. volumeMounts:
  24. - name: localtime
  25. mountPath: /etc/localtime
  26. volumes:
  27. - name: localtime
  28. hostPath:
  29. path: /usr/share/zoneinfo/Asia/Shanghai
  30. restartPolicy: Always
  1. kubectl apply -f k8s-node-name.yaml

1.gif

3.2.3 nodeSelector

  • nodeSelector 是节点选择约束的最简单推荐形式。nodeSelector 是 PodSpec 的一个字段。 它包含键值对的映射。为了使 pod 可以在某个节点上运行,该节点的标签中 必须包含这里的每个键值对(它也可以具有其他标签)。 最常见的用法的是一对键值对。
  • 除了自己 添加 的标签外,节点还预制了一组标准标签。 参见这些常用的标签,注解以及污点
    • [kubernetes.io/hostname](https://kubernetes.io/zh/docs/reference/kubernetes-api/labels-annotations-taints/#kubernetes-io-hostname)
    • [failure-domain.beta.kubernetes.io/zone](https://kubernetes.io/zh/docs/reference/kubernetes-api/labels-annotations-taints/#failure-domainbetakubernetesiozone)
    • [failure-domain.beta.kubernetes.io/region](https://kubernetes.io/zh/docs/reference/kubernetes-api/labels-annotations-taints/#failure-domainbetakubernetesioregion)
    • [topology.kubernetes.io/zone](https://kubernetes.io/zh/docs/reference/kubernetes-api/labels-annotations-taints/#topologykubernetesiozone)
    • [topology.kubernetes.io/region](https://kubernetes.io/zh/docs/reference/kubernetes-api/labels-annotations-taints/#topologykubernetesiozone)
    • [beta.kubernetes.io/instance-type](https://kubernetes.io/zh/docs/reference/kubernetes-api/labels-annotations-taints/#beta-kubernetes-io-instance-type)
    • [node.kubernetes.io/instance-type](https://kubernetes.io/zh/docs/reference/kubernetes-api/labels-annotations-taints/#nodekubernetesioinstance-type)
    • [kubernetes.io/os](https://kubernetes.io/zh/docs/reference/kubernetes-api/labels-annotations-taints/#kubernetes-io-os)
    • [kubernetes.io/arch](https://kubernetes.io/zh/docs/reference/kubernetes-api/labels-annotations-taints/#kubernetes-io-arch)

注意:这些标签的值是特定于云供应商的,因此不能保证可靠。 例如,kubernetes.io/hostname 的值在某些环境中可能与节点名称相同, 但在其他环境中可能是一个不同的值。

  • 示例:
  1. # 给 k8s-node2 打上标签
  2. kubectl label node k8s-node2 nodeevn=pro
  1. vi k8s-node-selector.yaml
  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: nginx
  5. namespace: default
  6. labels:
  7. app: nginx
  8. spec:
  9. nodeSelector:
  10. nodeevn: pro # 指定调度到 nodeevn = pro 标签的 Node 节点上
  11. containers:
  12. - name: nginx
  13. image: nginx:1.20.2
  14. resources:
  15. limits:
  16. cpu: 200m
  17. memory: 500Mi
  18. requests:
  19. cpu: 100m
  20. memory: 200Mi
  21. ports:
  22. - containerPort: 80
  23. name: http
  24. volumeMounts:
  25. - name: localtime
  26. mountPath: /etc/localtime
  27. volumes:
  28. - name: localtime
  29. hostPath:
  30. path: /usr/share/zoneinfo/Asia/Shanghai
  31. restartPolicy: Always
  1. kubectl apply -f k8s-node-selector.yaml

2.gif

3.3 亲和性调度

3.3.1 概述

  • 虽然定向调度的两种方式,使用起来非常方便,但是也有一定的问题,那就是如果没有满足条件的 Node,那么 Pod 将不会被运行,即使在集群中还有可用的 Node 列表也不行,这就限制了它的使用场景。
  • 基于上面的问题,Kubernetes 还提供了一种亲和性调度(Affinity)。它在 nodeSelector 的基础之上进行了扩展,可以通过配置的形式,实现优先选择满足条件的 Node 进行调度,如果没有,也可以调度到不满足条件的节点上,使得调度更加灵活。
  • Affinity 主要分为三类:
    • nodeAffinity(node亲和性):以 Node 为目标,解决 Pod可 以调度到那些 Node 的问题。
    • podAffinity(pod亲和性):以 Pod 为目标,解决 Pod 可以和那些已存在的 Pod 部署在同一个拓扑域中的问题。
    • podAntiAffinity(pod反亲和性):以 Pod 为目标,解决 Pod 不能和那些已经存在的 Pod 部署在同一拓扑域中的问题。

关于亲和性和反亲和性的使用场景的说明:

  • 亲和性:如果两个应用频繁交互,那么就有必要利用亲和性让两个应用尽可能的靠近,这样可以较少因网络通信而带来的性能损耗。
  • 反亲和性:当应用采用多副本部署的时候,那么就有必要利用反亲和性让各个应用实例打散分布在各个 Node 上,这样可以提高服务的高可用性。

3.3.2 nodeAffinity

  • nodeAffinity 概念上类似于 nodeSelector,它使你可以根据节点上的标签来约束 Pod 可以调度到哪些节点。
  • 和 nodeSelector 的区别:
    • 引入了运算符:In、NotIn、Exists、DoesNotExist、 Gt、 Lt。
    • 支持 硬性过滤软性评分
      • 硬件过滤支持指定多条件之间的逻辑或运算
      • 软件评分规则支持设置条件权重
  • nodeAffinity 的可选配置项:
  1. pod.spec.affinity.nodeAffinity
  2. requiredDuringSchedulingIgnoredDuringExecution # Node节点必须满足指定的所有规则才可以,硬性过滤
  3. nodeSelectorTerms # 节点选择列表
  4. matchFields # 按节点字段列出的节点选择器要求列表
  5. matchExpressions # 按节点标签列出的节点选择器要求列表(推荐)
  6. key # 键
  7. values # 值
  8. operator # 关系符 支持Exists, DoesNotExist, In, NotIn, Gt, Lt
  9. preferredDuringSchedulingIgnoredDuringExecution # 优先调度到满足指定的规则的Node,软性评分 (倾向)
  10. preference # 一个节点选择器项,与相应的权重相关联
  11. matchFields # 按节点字段列出的节点选择器要求列表
  12. matchExpressions # 按节点标签列出的节点选择器要求列表(推荐)
  13. key # 键
  14. values # 值
  15. operator # 关系符 支持In, NotIn, Exists, DoesNotExist, Gt, Lt
  16. weight # 倾向权重,在范围1-100。

注意:

  • 如果我们修改或删除 Pod 调度到节点的标签,Pod 不会被删除;换言之,亲和调度只是在 Pod 调度期间有效。
  • 如果同时定义了 nodeSelector 和 nodeAffinity ,那么必须两个条件都满足,Pod 才能运行在指定的 Node 上。
  • 如果 nodeAffinity 指定了多个 nodeSelectorTerms ,那么只需要其中一个能够匹配成功即可。
  • 如果一个 nodeSelectorTerms 中有多个 matchExpressions ,则一个节点必须满足所有的才能匹配成功。
  • 示例:硬性过滤
  1. # 给 k8s-node1 和 k8s-nodes 打上标签
  2. kubectl label node k8s-node1 disktype=ssd
  3. kubectl label node k8s-node2 disktype=hdd
  1. vi k8s-node-affinity-required.yaml
  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: nginx
  5. namespace: default
  6. labels:
  7. app: nginx
  8. spec:
  9. containers:
  10. - name: nginx
  11. image: nginx:1.20.2
  12. resources:
  13. limits:
  14. cpu: 200m
  15. memory: 500Mi
  16. requests:
  17. cpu: 100m
  18. memory: 200Mi
  19. ports:
  20. - containerPort: 80
  21. name: http
  22. volumeMounts:
  23. - name: localtime
  24. mountPath: /etc/localtime
  25. volumes:
  26. - name: localtime
  27. hostPath:
  28. path: /usr/share/zoneinfo/Asia/Shanghai
  29. affinity:
  30. nodeAffinity:
  31. # DuringScheduling(调度期间有效)IgnoredDuringExecution(执行期间忽略,执行期间:Pod 运行期间)
  32. requiredDuringSchedulingIgnoredDuringExecution: # 硬性过滤:Node 节点必须满足指定的所有规则才可以
  33. nodeSelectorTerms:
  34. - matchExpressions: # 所有 matchExpressions 满足条件才行
  35. - key: disktype
  36. operator: In # 支持 Exists, DoesNotExist, In, NotIn, Gt, Lt
  37. values:
  38. - ssd
  39. - hdd
  40. restartPolicy: Always
  1. kubectl apply -f k8s-node-affinity-required.yaml

3.gif

  • 示例:软性评分
  1. # 给 k8s-node1 和 k8s-nodes 打上标签
  2. kubectl label node k8s-node1 disk=50 gpu=1000
  3. kubectl label node k8s-node2 disk=30 gpu=5000
  1. vi k8s-node-affinity-preferre.yaml
  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: nginx
  5. namespace: default
  6. labels:
  7. app: nginx
  8. spec:
  9. containers:
  10. - name: nginx
  11. image: nginx:1.20.2
  12. resources:
  13. limits:
  14. cpu: 200m
  15. memory: 500Mi
  16. requests:
  17. cpu: 100m
  18. memory: 200Mi
  19. ports:
  20. - containerPort: 80
  21. name: http
  22. volumeMounts:
  23. - name: localtime
  24. mountPath: /etc/localtime
  25. volumes:
  26. - name: localtime
  27. hostPath:
  28. path: /usr/share/zoneinfo/Asia/Shanghai
  29. affinity:
  30. nodeAffinity:
  31. # DuringScheduling(调度期间有效)IgnoredDuringExecution(执行期间忽略,执行期间:Pod 运行期间)
  32. preferredDuringSchedulingIgnoredDuringExecution: # 软性评分:优先调度到满足指定的规则的 Node
  33. - weight: 90 # 权重
  34. preference: # 一个节点选择器项,与相应的权重相关联
  35. matchExpressions:
  36. - key: disk
  37. operator: Gt
  38. values:
  39. - "40"
  40. - weight: 10 # 权重
  41. preference: # 一个节点选择器项,与相应的权重相关联
  42. matchExpressions:
  43. - key: gpu
  44. operator: Gt
  45. values:
  46. - "4000"
  47. restartPolicy: Always
  1. kubectl apply -f k8s-node-affinity-preferre.yaml

4.gif

3.3.3 podAffinity 和 podAntiAffinity

  • podAffinity 主要实现以运行的 Pod 为参照,实现让新创建的 Pod 和参照的 Pod 在一个区域的功能。
  • podAntiAffinity 主要实现以运行的 Pod 为参照,让新创建的 Pod 和参照的 Pod 不在一个区域的功能。
  • PodAffinity 的可选配置项:
  1. pod.spec.affinity.podAffinity
  2. requiredDuringSchedulingIgnoredDuringExecution 硬限制
  3. namespaces 指定参照podnamespace
  4. topologyKey 指定调度作用域
  5. labelSelector 标签选择器
  6. matchExpressions 按节点标签列出的节点选择器要求列表(推荐)
  7. key
  8. values
  9. operator 关系符 支持In, NotIn, Exists, DoesNotExist.
  10. matchLabels 指多个matchExpressions映射的内容
  11. preferredDuringSchedulingIgnoredDuringExecution 软限制
  12. podAffinityTerm 选项
  13. namespaces
  14. topologyKey
  15. labelSelector
  16. matchExpressions
  17. key
  18. values
  19. operator
  20. matchLabels
  21. weight 倾向权重,在范围1-1

注意:topologyKey 用于指定调度的作用域,例如:

  • 如果指定为 kubernetes.io/hostname(可以通过 kubectl get node —show-labels 查看),那就是以 Node 节点为区分范围。
  • 如果指定为 kubernetes.io/os,则以 Node 节点的操作系统类型来区分。
  • 示例:在一个两节点的集群中,部署一个使用 redis 的 WEB 应用程序,并期望 web-server 尽可能和 redis 在同一个节点上。
  1. vi k8s-pod-affinity.yaml
  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. name: redis-deploy
  5. namespace: default
  6. labels:
  7. app: redis-deploy
  8. spec:
  9. selector:
  10. matchLabels:
  11. app: store
  12. replicas: 2
  13. template:
  14. metadata:
  15. labels:
  16. app: store
  17. spec:
  18. affinity: # 亲和性配置
  19. podAntiAffinity: # Pod 反亲和性,符合以下指定条件不会被调度过去
  20. requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
  21. - labelSelector:
  22. matchExpressions:
  23. - key: app
  24. operator: In
  25. values:
  26. - store
  27. topologyKey: kubernetes.io/hostname # 拓扑键,划分逻辑区域。
  28. # node 节点以 kubernetes.io/hostname 为拓扑网络,如果 kubernetes.io/hostname 相同,就认为是同一个东西。
  29. # 亲和就是都放在这个逻辑区域,反亲和就是必须避免放在同一个逻辑区域。
  30. containers:
  31. - name: redis-server
  32. image: redis:5.0.14-alpine
  33. resources:
  34. limits:
  35. memory: 500Mi
  36. cpu: 1
  37. requests:
  38. memory: 250Mi
  39. cpu: 500m
  40. ports:
  41. - containerPort: 2375
  42. name: redis
  43. volumeMounts:
  44. - name: localtime
  45. mountPath: /etc/localtime
  46. volumes:
  47. - name: localtime
  48. hostPath:
  49. path: /usr/share/zoneinfo/Asia/Shanghai
  50. restartPolicy: Always
  51. ---
  52. apiVersion: apps/v1
  53. kind: Deployment
  54. metadata:
  55. name: nginx-deploy
  56. namespace: default
  57. labels:
  58. app: nginx-deploy
  59. spec:
  60. selector:
  61. matchLabels:
  62. app: web-store
  63. replicas: 2
  64. template:
  65. metadata:
  66. labels:
  67. app: web-store
  68. spec:
  69. affinity: # 亲和性配置
  70. podAffinity: # Pod 亲和性,符合以下指定条件会被调度过去
  71. requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
  72. - labelSelector:
  73. matchExpressions:
  74. - key: app
  75. operator: In
  76. values:
  77. - store
  78. topologyKey: kubernetes.io/hostname # 拓扑键,划分逻辑区域。
  79. # node 节点以 kubernetes.io/hostname 为拓扑网络,如果 kubernetes.io/hostname 相同,就认为是同一个东西。
  80. # 亲和就是都放在这个逻辑区域,反亲和就是必须避免放在同一个逻辑区域。
  81. podAntiAffinity: # Pod 反亲和性,符合以下指定条件不会被调度过去
  82. requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
  83. - labelSelector:
  84. matchExpressions:
  85. - key: app
  86. operator: In
  87. values:
  88. - web-store
  89. topologyKey: kubernetes.io/hostname # 拓扑键,划分逻辑区域。
  90. # node 节点以 kubernetes.io/hostname 为拓扑网络,如果 kubernetes.io/hostname 相同,就认为是同一个东西。
  91. # 亲和就是都放在这个逻辑区域,反亲和就是必须避免放在同一个逻辑区域。
  92. containers:
  93. - name: nginx
  94. image: nginx:1.20.2
  95. resources:
  96. limits:
  97. cpu: 200m
  98. memory: 500Mi
  99. requests:
  100. cpu: 100m
  101. memory: 200Mi
  102. ports:
  103. - containerPort: 80
  104. name: nginx
  105. volumeMounts:
  106. - name: localtime
  107. mountPath: /etc/localtime
  108. volumes:
  109. - name: localtime
  110. hostPath:
  111. path: /usr/share/zoneinfo/Asia/Shanghai
  112. restartPolicy: Always
  1. kubectl apply -f k8s-pod-affinity.yaml

5.gif

3.4 污点和容忍调度

3.4.1 污点

  • 前面的调度方式都是站在 Pod 的角度上,通过在 Pod 上添加属性,来确定 Pod 是否要调度到指定的 Node 上,其实我们也可以站在 Node 的角度上,通过在 Node 上添加污点属性,来决定是否运行 Pod 调度过来。
  • Node 被设置了污点之后就和 Pod 之间存在了一种相斥的关系,进而拒绝 Pod 调度进来,甚至可以将已经存在的 Pod 驱逐出去。
  • 污点的格式为:
  1. key=value:effect
  2. # key 和 value 是污点的标签及对应的值
  3. # effect 描述污点的作用
  • effect 支持如下的三个选项:
    • PreferNoSchedule:Kubernetes 将尽量避免把 Pod 调度到具有该污点的 Node 上,除非没有其他节点可以调度;换言之,尽量不要来,除非没办法。
    • NoSchedule:Kubernets 将不会把 Pod 调度到具有该污点的 Node 上,但是不会影响当前 Node 上已经存在的 Pod ;换言之,新的不要来,在这的就不要动。
    • NoExecute:Kubernets 将不会将 Pod 调度到具有该污点的 Node 上,同时会将 Node 上已经存在的 Pod 驱逐;换言之,新的不要来,这这里的赶紧走。

注意:NoExecute 一般用于实际生产环境中的 Node 节点的上下线。

  • 语法:设置污点
  1. kubectl taint node xxx key=value:effect
  • 语法:去除污点
  1. kubectl taint node xxx key:effect-
  • 语法:去除所有污点
  1. kubectl taint node xxx key-
  • 语法:查看污点
  1. kubectl describe node xxx | grep -i taints
  • 证明:kubeadm 安装的集群上 k8s-master 有污点
  1. kubectl describe node k8s-master | grep -i taints

6.png

  • 示例:演示污点效果(为了演示效果更为明显,暂时停止 k8s-node2 节点)
  • ① 为 k8s-node1 设置污点tag=xudaxian:PreferNoSchedule,然后创建 Pod1 (Pod1 可以运行)
  1. kubectl taint node k8s-node1 tag=xudaxian:PreferNoSchedule
  1. kubectl run pod1 --image=nginx:1.20.2

7.gif

  • ② 修改 k8s-node1 节点的污点为 tag=xudaxian:NoSchedule,然后创建Pod2(Pod1 可以正常运行,Pod2 失败)。
  1. # 取消污点
  2. kubectl taint node k8s-node1 tag:PreferNoSchedule-
  3. # 设置污点
  4. kubectl taint node k8s-node1 tag=xudaxian:NoSchedule
  1. kubectl run pod2 --image=nginx:1.20.2

8.gif

  • ③ 修改 k8s-node1 节点的污点为 tag=xudaxian:NoExecute,然后创建 Pod3(Pod1、Pod2、Pod3失败)。
  1. # 取消污点
  2. kubectl taint node k8s-node1 tag:NoSchedule-
  3. # 设置污点
  4. kubectl taint node k8s-node1 tag=xudaxian:NoExecute
  1. kubectl run pod3 --image=nginx:1.20.2

9.gif

3.4.2 容忍

  • 上面介绍了污点的作用,我们可以在 Node上 添加污点用来拒绝 Pod 调度上来,但是如果就是想让一个 Pod 调度到一个有污点的 Node 上去,这时候应该怎么做?这就需要使用到容忍。

污点就是拒绝,容忍就是忽略,Node 通过污点拒绝 Pod 调度上去,Pod 通过容忍忽略拒绝。

  • 容忍的详细配置:
  1. kubectl explain pod.spec.tolerations
  2. ......
  3. FIELDS:
  4. key # 对应着要容忍的污点的键,空意味着匹配所有的键
  5. value # 对应着要容忍的污点的值
  6. operator # key-value的运算符,支持Equal和Exists(默认)
  7. effect # 对应污点的effect,空意味着匹配所有影响
  8. tolerationSeconds # 容忍时间, 当effect为NoExecute时生效,表示pod在Node上的停留时间
  • 污点和容忍的匹配:
    • 当满足如下条件的时候,Kubernetes 认为污点和容忍匹配:
      • 键(key)相同。
      • 效果(effect)相同。
      • 污点的 operator 为:
        • Exists ,此时污点中不应该指定 value 。
        • Equal,此时容忍的 value 应该和污点的 value 相同。
    • 如果不指定 operator ,默认为 Equal 。
  • 特殊情况:
    • 容忍中没有定义 key ,但是定义了 operator 为 Exists ,Kubernetes 则认为此容忍匹配所有的污点,如: ```yaml tolerations:
  • operator: Exists

    最终,所有有污点的机器我们都能容忍,Pod 都可以调度。

    1. - 容忍中没有定义 effect,但是定义了 keyKubernetes 认为此容忍匹配所有 effect ,如:
    2. ```yaml
    3. tolerations: # 容忍
    4. - key: "tag" # 要容忍的污点的key
    5. operator: Exists # 操作符
    6. # 最终,有这个污点的机器我们可以容忍,Pod 都可以调度。
  • 示例:
  1. # 设置污点
  2. kubectl taint node k8s-node1 tag=xudaxian:NoExecute
  1. vi k8s-tolerations.yaml
  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: pod-toleration
  5. spec:
  6. containers: # 容器配置
  7. - name: nginx
  8. image: nginx:1.20.2
  9. imagePullPolicy: IfNotPresent
  10. ports:
  11. - name: nginx-port
  12. containerPort: 80
  13. protocol: TCP
  14. tolerations: # 容忍
  15. - key: "tag" # 要容忍的污点的key
  16. operator: Equal # 操作符
  17. value: "xudaxian" # 要容忍的污点的value
  18. effect: NoExecute # 添加容忍的规则,这里必须和标记的污点规则相同
  1. kubectl apply -f k8s-tolerations.yaml

10.gif