Kubernetes Pod调度原理与实战

前言

Kubernetes作为云原生时代的容器编排标准,其调度器(Scheduler)是整个系统的”大脑”。调度器负责决定Pod应该运行在哪个节点上,这个看似简单的决策背后,却蕴含着复杂的算法和精妙的设计。

本文将深入剖析Kubernetes Pod调度的底层原理,并提供丰富的实战配置示例。


一、Pod调度流程

1.1 调度流程概览

1
创建Pod → API Server → Scheduler → 预选(Filtering) → 优选(Scoring) → 绑定节点

详细步骤:

  1. 用户创建Pod:通过kubectl或API创建Pod
  2. 写入etcd:API Server将Pod信息写入etcd
  3. Scheduler监听:Scheduler watch到新的未调度Pod
  4. 预选阶段:过滤掉不符合条件的节点
  5. 优选阶段:对剩余节点打分排序
  6. 绑定节点:选择得分最高的节点,更新Pod的nodeName字段

1.2 Scheduler工作原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Scheduler核心逻辑(伪代码)
func Schedule(pod *Pod) string {
// 1. 获取所有节点
nodes := getAllNodes()

// 2. 预选阶段
feasibleNodes := runPredicates(pod, nodes)

// 3. 优选阶段
scores := runPriorities(pod, feasibleNodes)

// 4. 选择得分最高的节点
bestNode := selectBestNode(scores)

// 5. 绑定Pod到节点
bindPodToNode(pod, bestNode)

return bestNode
}

二、预选策略(Predicates)

预选阶段会执行一系列检查,任何一个检查失败,该节点就会被淘汰。

2.1 常见预选策略

策略 说明
PodFitsResources 节点资源是否满足Pod需求
PodFitsHostPorts 节点端口是否被占用
MatchNodeSelector 是否匹配节点选择器
NoVolumeZoneConflict 存储卷zone是否冲突
CheckNodeMemoryPressure 节点内存是否有压力
CheckNodeDiskPressure 节点磁盘是否有压力

2.2 资源请求示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: resource-demo
spec:
containers:
- name: app
image: nginx:1.21
resources:
requests:
memory: "256Mi" # 至少需要256Mi内存
cpu: "500m" # 至少需要0.5核CPU
limits:
memory: "512Mi" # 最多使用512Mi内存
cpu: "1000m" # 最多使用1核CPU

如果节点剩余资源不足,预选阶段会被淘汰:

1
2
3
4
5
6
7
# 查看节点资源
kubectl describe node node-1

# 输出示例
Allocated resources:
CPU Requests: 950m (95%) # CPU已使用95%,无法调度500m的Pod
Memory Requests: 3Gi (75%)

三、优选策略(Priorities)

通过预选的节点会进入优选阶段,每个节点会得到一个分数(0-10分)。

3.1 常见优选策略

策略 说明 权重
LeastRequestedPriority 资源剩余越多,分数越高 1
BalancedResourceAllocation CPU和内存使用率越均衡,分数越高 1
NodeAffinityPriority 满足节点亲和性,分数越高 1
TaintTolerationPriority 容忍的污点越多,分数越高 1

3.2 优选算法示例

1
2
3
4
5
6
7
8
9
10
11
12
// LeastRequestedPriority算法(伪代码)
func LeastRequestedPriority(pod *Pod, node *Node) int {
cpuScore := (node.AllocatableCPU - node.RequestedCPU) / node.AllocatableCPU * 10
memScore := (node.AllocatableMem - node.RequestedMem) / node.AllocatableMem * 10

return (cpuScore + memScore) / 2
}

// 示例计算
// Node1: CPU剩余50%, Mem剩余60% → Score = (5 + 6) / 2 = 5.5
// Node2: CPU剩余80%, Mem剩余70% → Score = (8 + 7) / 2 = 7.5
// Node2得分更高,优先调度到Node2

四、节点选择器(NodeSelector)

最简单的节点选择方式,通过标签匹配节点。

4.1 给节点打标签

1
2
3
4
5
6
7
# 查看节点标签
kubectl get nodes --show-labels

# 给节点添加标签
kubectl label nodes node-1 disk=ssd
kubectl label nodes node-2 disk=hdd
kubectl label nodes node-3 env=production

4.2 使用NodeSelector

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Pod
metadata:
name: nginx-ssd
spec:
nodeSelector:
disk: ssd # 只调度到disk=ssd的节点
env: production # 且env=production的节点
containers:
- name: nginx
image: nginx:1.21

特点:

  • 简单易用
  • 硬性要求(必须匹配,否则调度失败)
  • 无法表达复杂逻辑(如”或”关系)

五、节点亲和性(Node Affinity)

比NodeSelector更强大,支持更复杂的选择逻辑。

5.1 硬亲和性(Required)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: v1
kind: Pod
metadata:
name: nginx-affinity-required
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node-1
- node-2
- key: disk
operator: NotIn
values:
- hdd
containers:
- name: nginx
image: nginx:1.21

说明:

  • requiredDuringSchedulingIgnoredDuringExecution:调度时必须满足,运行时不检查
  • In:节点标签值必须在列表中
  • NotIn:节点标签值必须不在列表中

支持的操作符:In、NotIn、Exists、DoesNotExist、Gt、Lt

5.2 软亲和性(Preferred)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: v1
kind: Pod
metadata:
name: nginx-affinity-preferred
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80 # 权重(1-100)
preference:
matchExpressions:
- key: disk
operator: In
values:
- ssd
- weight: 20
preference:
matchExpressions:
- key: zone
operator: In
values:
- zone-a
containers:
- name: nginx
image: nginx:1.21

说明:

  • 尽量满足条件,但不是强制要求
  • 权重越高,优先级越高
  • 如果没有满足的节点,依然可以调度到其他节点

六、Pod亲和性与反亲和性

控制Pod之间的关系,决定Pod是否应该调度到同一节点。

6.1 Pod亲和性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-cache
spec:
replicas: 3
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web
topologyKey: kubernetes.io/hostname
containers:
- name: redis
image: redis:6.2

说明:

  • Redis Pod会调度到与web Pod相同的节点上
  • topologyKey:拓扑域,kubernetes.io/hostname表示节点级别

6.2 Pod反亲和性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web
topologyKey: kubernetes.io/hostname
containers:
- name: web
image: nginx:1.21

说明:

  • 同一个节点上不会运行多个web Pod
  • 实现高可用:Pod分散到不同节点

6.3 软反亲和性示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
replicas: 5
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- api
topologyKey: kubernetes.io/hostname
containers:
- name: api
image: api-server:v1.0

说明:

  • 尽量不在同一节点运行多个api Pod
  • 如果节点数量不足,允许多个Pod在同一节点

七、污点和容忍(Taints and Tolerations)

污点用于标记节点,容忍用于Pod,决定Pod是否可以调度到有污点的节点。

7.1 给节点添加污点

1
2
3
4
5
6
7
8
9
10
11
# 添加NoSchedule污点
kubectl taint nodes node-1 key=value:NoSchedule

# 添加PreferNoSchedule污点
kubectl taint nodes node-2 gpu=true:PreferNoSchedule

# 添加NoExecute污点(会驱逐已有Pod)
kubectl taint nodes node-3 maintenance=true:NoExecute

# 删除污点
kubectl taint nodes node-1 key:NoSchedule-

污点效果:

  • NoSchedule:不允许调度新Pod
  • PreferNoSchedule:尽量不调度
  • NoExecute:不允许调度,且驱逐已有Pod

7.2 Pod容忍污点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
tolerations:
- key: "gpu"
operator: "Equal"
value: "true"
effect: "NoSchedule"
- key: "maintenance"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 3600 # 容忍3600秒,之后被驱逐
containers:
- name: gpu-app
image: gpu-app:v1.0

操作符:

  • Equal:key和value都必须匹配
  • Exists:只要key存在就匹配

7.3 实战场景:专用GPU节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 1. 给GPU节点打污点
kubectl taint nodes gpu-node-1 nvidia.com/gpu=true:NoSchedule

# 2. 创建需要GPU的Pod
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: cuda-app
spec:
tolerations:
- key: "nvidia.com/gpu"
operator: "Equal"
value: "true"
effect: "NoSchedule"
nodeSelector:
gpu: "true"
containers:
- name: cuda
image: nvidia/cuda:11.0-base
resources:
limits:
nvidia.com/gpu: 1
EOF

八、自定义调度器

除了默认调度器,Kubernetes支持自定义调度器。

8.1 指定调度器

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Pod
metadata:
name: custom-scheduled-pod
spec:
schedulerName: my-custom-scheduler # 使用自定义调度器
containers:
- name: nginx
image: nginx:1.21

8.2 自定义调度器部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-scheduler
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: my-scheduler-as-kube-scheduler
subjects:
- kind: ServiceAccount
name: my-scheduler
namespace: kube-system
roleRef:
kind: ClusterRole
name: system:kube-scheduler
apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-scheduler
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
component: my-scheduler
template:
metadata:
labels:
component: my-scheduler
spec:
serviceAccountName: my-scheduler
containers:
- name: scheduler
image: my-custom-scheduler:v1.0
command:
- /usr/local/bin/my-scheduler
- --leader-elect=false

九、调度性能优化

9.1 调度器配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: v1
kind: ConfigMap
metadata:
name: scheduler-config
namespace: kube-system
data:
scheduler-config.yaml: |
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
score:
disabled:
- name: NodeResourcesLeastAllocated
enabled:
- name: NodeResourcesBalancedAllocation
weight: 2
pluginConfig:
- name: NodeResourcesBalancedAllocation
args:
resources:
- name: cpu
weight: 1
- name: memory
weight: 1

9.2 监控调度性能

1
2
3
4
5
6
7
8
# 查看调度延迟
kubectl get --raw /metrics | grep scheduler_scheduling_duration_seconds

# 查看待调度Pod数量
kubectl get pods --all-namespaces --field-selector=status.phase=Pending

# 查看调度失败原因
kubectl describe pod <pod-name> | grep -A 5 Events

十、最佳实践

10.1 资源配置建议

1
2
3
4
5
6
7
8
# 为Pod设置合理的资源请求和限制
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"

10.2 高可用部署

1
2
3
4
5
6
7
8
9
10
# 使用反亲和性确保Pod分散
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: myapp
topologyKey: kubernetes.io/hostname

10.3 节点维护

1
2
3
4
5
6
7
8
# 标记节点不可调度
kubectl cordon node-1

# 驱逐节点上的Pod
kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data

# 恢复节点调度
kubectl uncordon node-1

总结

Kubernetes Pod调度是一个复杂而精妙的系统:

  • 预选阶段:快速过滤不合格节点
  • 优选阶段:综合评分选择最佳节点
  • 亲和性:控制Pod与节点、Pod与Pod的关系
  • 污点容忍:实现节点隔离和专用场景

掌握这些机制,可以:

  • 优化资源利用率
  • 提高应用可用性
  • 实现复杂的部署策略
  • 满足特殊硬件需求

希望本文能帮助你更好地理解和应用Kubernetes调度机制!