Custom Scheduler 测验(Part 3)
本测验测试你对在 Kubernetes 中实现和使用 Custom Scheduler 的高级理解。
测验问题
1. 在 Kubernetes 中同时运行多个 scheduler 时,以下哪一项不是可能出现的问题?
A. 资源竞争 B. 调度决策冲突 C. 网络带宽增加 D. Leader election 冲突
显示答案
答案:C. 网络带宽增加
解释: “网络带宽增加”不是在 Kubernetes 中同时运行多个 scheduler 时可能出现的问题。虽然 scheduler 会与 API server 通信,但网络带宽使用量通常很小,并不是一个需要担心的问题。
同时运行多个 scheduler 时可能出现的实际问题:
资源竞争:
- 当多个 scheduler 尝试将 Pod 调度到同一个 node pool 时,可能发生资源竞争。
- 由于每个 scheduler 都独立运行,不了解其他 scheduler 的决策,因此存在过度分配 Node 资源的风险。
- 示例:两个 scheduler 可能同时将 Pod 调度到同一个 Node,导致超过 Node 容量。
调度决策冲突:
- 当多个 scheduler 尝试调度同一个 Pod 时,可能发生冲突。
- 当 Pod 没有显式指定
schedulerName,或者多个 scheduler 使用相同名称时,可能会发生这种情况。 - 示例:当两个 scheduler 尝试将同一个 Pod 绑定到不同 Node 时,会发生竞争条件。
Leader election 冲突:
- 如果多个同名 scheduler 实例在启用 leader election 的情况下运行,leader election 机制中可能会发生冲突。
- 示例:多个同名 scheduler 实例竞争 leadership,可能导致不稳定的 leadership 转换。
运行多个 scheduler 时的最佳实践:
明确划分职责:
yaml# Pod for default scheduler apiVersion: v1 kind: Pod metadata: name: default-pod spec: # Uses default scheduler when schedulerName is not specified containers: - name: nginx image: nginx # Pod for custom scheduler apiVersion: v1 kind: Pod metadata: name: custom-pod spec: schedulerName: my-custom-scheduler # Specify custom scheduler containers: - name: nginx image: nginx使用唯一的 scheduler 名称:
yaml# Custom scheduler deployment apiVersion: apps/v1 kind: Deployment metadata: name: my-custom-scheduler namespace: kube-system spec: replicas: 1 selector: matchLabels: component: my-custom-scheduler template: metadata: labels: component: my-custom-scheduler spec: containers: - name: scheduler image: my-custom-scheduler:v1.0 args: - --scheduler-name=my-custom-scheduler # Use unique name - --leader-elect=true - --leader-elect-resource-name=my-custom-scheduler # Use unique resource name使用 Node label 和 taint 分离 node pool:
yaml# Apply node labels and taints kubectl label node node1 scheduler=default kubectl label node node2 scheduler=custom kubectl taint nodes node2 dedicated=custom-scheduler:NoSchedule # Custom scheduler configuration apiVersion: kubescheduler.config.k8s.io/v1 kind: KubeSchedulerConfiguration profiles: - schedulerName: my-custom-scheduler plugins: filter: enabled: - name: NodeSelector pluginConfig: - name: NodeSelector args: nodeSelector: scheduler: custom设置 resource quota:
yamlapiVersion: v1 kind: ResourceQuota metadata: name: default-scheduler-quota namespace: default-workloads spec: hard: pods: "10" cpu: "20" memory: 40Gi --- apiVersion: v1 kind: ResourceQuota metadata: name: custom-scheduler-quota namespace: custom-workloads spec: hard: pods: "10" cpu: "20" memory: 40Gi
监控多个 scheduler:
# Check scheduler pods
kubectl get pods -n kube-system -l component=kube-scheduler
kubectl get pods -n kube-system -l component=my-custom-scheduler
# Check scheduler logs
kubectl logs -n kube-system -l component=kube-scheduler
kubectl logs -n kube-system -l component=my-custom-scheduler
# Check scheduling events
kubectl get events | grep -i "Successfully assigned"其他选项的解释:
- A. 资源竞争:当多个 scheduler 将 Pod 调度到同一个 node pool 时,可能出现的实际问题。
- B. 调度决策冲突:当多个 scheduler 尝试调度同一个 Pod 时,可能出现的实际问题。
- D. Leader election 冲突:当多个同名 scheduler 实例竞争 leadership 时,可能出现的实际问题。
2. Kubernetes scheduler 中 “Permit” extension point 的作用是什么?
A. 将 Pod 绑定到 Node B. 允许、拒绝或延迟 Pod 调度 C. 排除 Pod 无法运行的 Node D. 为 Node 分配分数
显示答案
答案:B. 允许、拒绝或延迟 Pod 调度
解释: Kubernetes scheduling framework 中 “Permit” extension point 的作用是允许、拒绝或延迟 Pod 调度。Permit plugin 会在选定 Node 之后、binding phase 之前运行,为 Pod 调度决策提供最终批准或拒绝。
Permit extension point 的关键功能:
- 允许:允许 Pod 调度进入 binding phase。
- 拒绝:拒绝 Pod 调度,以便可以选择另一个 Node。
- 等待:临时延迟 Pod 调度,并等待直到满足特定条件。
Permit plugin interface:
type PermitPlugin interface {
Plugin
// Permit allows, denies, or delays pod scheduling.
// Return values:
// - Success: Allows pod scheduling.
// - Deny: Rejects pod scheduling.
// - Wait: Delays pod scheduling and waits until timeout or allowed.
Permit(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) (*Status, time.Duration)
}Permit 结果类型:
- Success:允许 Pod 调度。
- Deny:拒绝 Pod 调度。
- Wait:延迟 Pod 调度,并等待指定时间。
默认 Permit plugin: Kubernetes 提供以下默认 Permit plugin:
- TaintToleration:检查 Node taint 和 Pod toleration。
- PodTopologySpread:检查 Pod topology spread constraint。
Custom Permit plugin 示例:
// CustomPermit implements custom permit logic.
type CustomPermit struct {
handle framework.Handle
// Map to track waiting pods
waitingPods map[string]waitingPod
// Mutex to synchronize map access
mu sync.RWMutex
}
// waitingPod stores information about waiting pods.
type waitingPod struct {
pod *v1.Pod
nodeName string
status chan bool // true: allow, false: deny
}
// Name returns the plugin name.
func (pl *CustomPermit) Name() string {
return "CustomPermit"
}
// Permit allows, denies, or delays pod scheduling.
func (pl *CustomPermit) Permit(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (*framework.Status, time.Duration) {
// Example: Allow, deny, or delay pod scheduling based on specific conditions
if shouldWait(pod, nodeName) {
// Add pod to waiting list
key := fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)
pl.mu.Lock()
if pl.waitingPods == nil {
pl.waitingPods = make(map[string]waitingPod)
}
pl.waitingPods[key] = waitingPod{
pod: pod,
nodeName: nodeName,
status: make(chan bool),
}
pl.mu.Unlock()
// Wait for up to 10 minutes
return framework.NewStatus(framework.Wait, "waiting for condition"), 10 * time.Minute
}
if shouldDeny(pod, nodeName) {
return framework.NewStatus(framework.Unschedulable, "denied by custom permit plugin"), 0
}
// Allow pod scheduling
return nil, 0
}
// Allow waiting pod
func (pl *CustomPermit) Allow(pod *v1.Pod) {
key := fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)
pl.mu.RLock()
waitingPod, ok := pl.waitingPods[key]
pl.mu.RUnlock()
if ok {
// Allow pod
waitingPod.status <- true
pl.mu.Lock()
delete(pl.waitingPods, key)
pl.mu.Unlock()
}
}
// Reject waiting pod
func (pl *CustomPermit) Reject(pod *v1.Pod) {
key := fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)
pl.mu.RLock()
waitingPod, ok := pl.waitingPods[key]
pl.mu.RUnlock()
if ok {
// Reject pod
waitingPod.status <- false
pl.mu.Lock()
delete(pl.waitingPods, key)
pl.mu.Unlock()
}
}
// Function to check if pod should wait
func shouldWait(pod *v1.Pod, nodeName string) bool {
// Implement custom logic
return false
}
// Function to check if pod should be denied
func shouldDeny(pod *v1.Pod, nodeName string) bool {
// Implement custom logic
return false
}在 scheduler configuration 中启用 Permit plugin:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: custom-scheduler
plugins:
permit:
enabled:
- name: CustomPermit
disabled:
- name: TaintToleration # Disable default pluginPermit 使用场景:
- Gang scheduling:延迟调度一组 Pod,直到所有相关 Pod 都准备好被调度。
- Resource reservation:在 Pod 被调度之前预留外部资源。
- Policy validation:确保 Pod 调度符合组织策略。
- Approval workflow:请求外部批准 Pod 调度。
Gang scheduling 示例: Gang scheduling 是一种确保所有相关 Pod 一起被调度的技术。这对于分布式训练作业等工作负载很有用,因为所有组件都必须同时运行。
// GangPermit implements Gang scheduling.
type GangPermit struct {
handle framework.Handle
// Map to track waiting pods by group
waitingGroups map[string]gangGroup
mu sync.RWMutex
}
// gangGroup stores Gang information.
type gangGroup struct {
pods map[string]*v1.Pod
nodeName map[string]string
minCount int
readyPods int
}
// Permit allows, denies, or delays pod scheduling.
func (pl *GangPermit) Permit(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (*framework.Status, time.Duration) {
// Get Gang ID
gangID, ok := pod.Labels["gang-id"]
if !ok {
// Process as regular pod if no Gang ID
return nil, 0
}
pl.mu.Lock()
defer pl.mu.Unlock()
// Create group if not exists
if _, ok := pl.waitingGroups[gangID]; !ok {
minCount, _ := strconv.Atoi(pod.Labels["gang-min-count"])
if minCount <= 0 {
minCount = 1
}
pl.waitingGroups[gangID] = gangGroup{
pods: make(map[string]*v1.Pod),
nodeName: make(map[string]string),
minCount: minCount,
readyPods: 0,
}
}
// Add pod
group := pl.waitingGroups[gangID]
key := fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)
group.pods[key] = pod
group.nodeName[key] = nodeName
group.readyPods++
// Check if minimum count reached
if group.readyPods >= group.minCount {
// Allow all pods
for _, p := range group.pods {
pl.handle.PermitPlugin().Allow(p)
}
// Delete group
delete(pl.waitingGroups, gangID)
return nil, 0
}
// Wait until minimum count is reached
return framework.NewStatus(framework.Wait, "waiting for gang members"), 10 * time.Minute
}其他选项的问题:
- A. 将 Pod 绑定到 Node:这是 “Bind” extension point 的作用。
- C. 排除 Pod 无法运行的 Node:这是 “Filter” extension point 的作用。
- D. 为 Node 分配分数:这是 “Score” extension point 的作用。
A. 只将 Pod 放置在特定 Node 上 B. 确保所有相关 Pod 一起被调度 C. 将 Pod 均匀分布到各个 Node 上 D. 基于优先级调度 Pod
显示答案
答案:B. 确保所有相关 Pod 一起被调度
解释: Kubernetes 中 Gang Scheduling 的主要目的是确保所有相关 Pod 一起被调度。这对于分布式训练作业和分布式数据处理作业等工作负载很重要,因为所有组件都必须同时运行。
为什么需要 Gang scheduling:
- All-or-Nothing 要求:某些工作负载要求所有组件同时运行;如果只有部分组件运行,作业就无法推进。
- 防止资源浪费:如果只有部分 Pod 被调度而其他 Pod 仍在等待,已调度 Pod 使用的资源可能会被浪费。
- 防止 deadlock:当相互依赖的 Pod 在不同时间被调度时,可能发生 deadlock。
Gang scheduling 实现方法: Kubernetes 本身不原生支持 Gang scheduling,但可以通过以下方式实现:
- Custom scheduler:使用 Permit extension point 实现 Gang scheduling。
- 外部 controller:实现一个在 Kubernetes 外部管理 Gang scheduling 的 controller。
- 开源解决方案:使用 Volcano 或 Kube-batch 等开源 scheduler。
Gang scheduling 示例(Volcano):
# PodGroup definition for Gang scheduling
apiVersion: scheduling.volcano.sh/v1beta1
kind: PodGroup
metadata:
name: tf-training
namespace: default
spec:
minMember: 4 # At least 4 pods must be scheduled together
minResources:
cpu: 8
memory: 16Gi
queue: default
---
# Pod belonging to Gang
apiVersion: v1
kind: Pod
metadata:
name: tf-worker-0
namespace: default
labels:
app: tf-training
annotations:
scheduling.volcano.sh/pod-group: tf-training # Reference PodGroup
spec:
schedulerName: volcano # Use Volcano scheduler
containers:
- name: tensorflow
image: tensorflow/tensorflow:latest-gpu
resources:
requests:
cpu: 2
memory: 4Gi
nvidia.com/gpu: 1使用 custom Permit plugin 实现 Gang scheduling:
// GangSchedulingPlugin implements Gang scheduling.
type GangSchedulingPlugin struct {
handle framework.Handle
// Pod tracking per Gang
gangs map[string]*Gang
mu sync.RWMutex
}
// Gang represents a group of related pods.
type Gang struct {
MinRequired int
Scheduled map[string]string // pod name -> node name
Waiting map[string]*framework.WaitingPod
}
// Name returns the plugin name.
func (pl *GangSchedulingPlugin) Name() string {
return "GangSchedulingPlugin"
}
// PreFilter initializes Gang information.
func (pl *GangSchedulingPlugin) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod) *framework.Status {
gangID, ok := pod.Labels["gang-id"]
if !ok {
return nil // Process as regular pod if no Gang ID
}
pl.mu.Lock()
defer pl.mu.Unlock()
if _, ok := pl.gangs[gangID]; !ok {
minRequired, _ := strconv.Atoi(pod.Labels["gang-min-required"])
if minRequired <= 0 {
minRequired = 1
}
pl.gangs[gangID] = &Gang{
MinRequired: minRequired,
Scheduled: make(map[string]string),
Waiting: make(map[string]*framework.WaitingPod),
}
}
return nil
}
// Permit implements Gang scheduling logic.
func (pl *GangSchedulingPlugin) Permit(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (*framework.Status, time.Duration) {
gangID, ok := pod.Labels["gang-id"]
if !ok {
return nil, 0 // Process as regular pod if no Gang ID
}
pl.mu.Lock()
defer pl.mu.Unlock()
gang, ok := pl.gangs[gangID]
if !ok {
return framework.NewStatus(framework.Error, "gang not found"), 0
}
podKey := fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)
gang.Scheduled[podKey] = nodeName
// Check if enough pods are scheduled
if len(gang.Scheduled) >= gang.MinRequired {
// Allow all waiting pods
for _, waitingPod := range gang.Waiting {
waitingPod.Allow(pl.Name())
}
gang.Waiting = make(map[string]*framework.WaitingPod)
return nil, 0
}
// Wait until enough pods are scheduled
waitingPod := framework.NewWaitingPod(pod)
gang.Waiting[podKey] = waitingPod
return framework.NewStatus(framework.Wait, "waiting for gang members"), 10 * time.Minute
}Gang scheduling 的优缺点: 优点:
- 确保所有相关 Pod 一起被调度
- 防止资源浪费
- 防止 deadlock 和 starvation
缺点:
- 实现复杂度增加
- 可能出现调度延迟
- 可能降低 cluster 资源利用率
需要 Gang scheduling 的工作负载:
- 分布式训练作业:TensorFlow、PyTorch 等分布式训练框架
- 分布式数据处理:Spark、Flink 等分布式数据处理框架
- MPI 作业:高性能计算(HPC)工作负载
- Service mesh:多个组件必须协同工作的 Service mesh
其他选项的问题:
- A. 只将 Pod 放置在特定 Node 上:这是 node selector 或 node affinity 的作用。
- C. 将 Pod 均匀分布到各个 Node 上:这是 Pod topology spread constraint 的作用。
- D. 基于优先级调度 Pod:这是 Pod priority 和 preemption 的作用。
4. 在 Kubernetes 中实现 Scheduler Extender 时,以下哪一项不是必需的 API endpoint?
A. /filter B. /prioritize C. /bind D. /validate
显示答案
答案:D. /validate
解释: 在 Kubernetes 中实现 Scheduler Extender 时,“/validate” 不是必需的 API endpoint。Scheduler extender 通常实现 “/filter”、“/prioritize”、“/bind”、“/preempt” 等 endpoint,但 “/validate” 不是 scheduler extender 的标准 API。
Scheduler Extender API endpoint:
- filter:接收 Node 列表并返回经过过滤的 Node 列表。
- prioritize:接收 Node 列表并为每个 Node 分配分数。
- bind:将 Pod 绑定到 Node。
- preempt:返回用于 preemption 的 Node 和 Pod。
Scheduler Extender configuration 示例:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
extenders:
- urlPrefix: "http://extender-service:8080"
filterVerb: "filter"
prioritizeVerb: "prioritize"
bindVerb: "bind"
enableHTTPS: false
nodeCacheCapable: false
ignorable: true
managedResources:
- name: example.com/foo
ignoredByScheduler: trueScheduler Extender API request 和 response 格式:
filter API:
- Request:json
{ "pod": <pod>, "nodes": <nodes>, "nodenames": <node-names> } - Response:json
{ "nodes": <filtered-nodes>, "nodenames": <filtered-node-names>, "failedNodes": <failed-nodes>, "error": <error-message> }
- Request:
prioritize API:
- Request:json
{ "pod": <pod>, "nodes": <nodes>, "nodenames": <node-names> } - Response:json
{ "hostPriorities": [ { "host": <node-name>, "score": <score> }, ... ], "error": <error-message> }
- Request:
bind API:
- Request:json
{ "pod": <pod>, "node": <node-name> } - Response:json
{ "error": <error-message> }
- Request:
preempt API:
- Request:json
{ "pod": <pod>, "nodenames": <node-names>, "nodes": <nodes> } - Response:json
{ "nodenames": <node-names>, "nodes": <nodes>, "podsToPreempt": { <node-name>: [<pod>, ...], ... }, "error": <error-message> }
- Request:
Scheduler Extender 实现示例(Go):
package main
import (
"encoding/json"
"log"
"net/http"
v1 "k8s.io/api/core/v1"
extender "k8s.io/kube-scheduler/extender/v1"
)
func main() {
http.HandleFunc("/filter", filterHandler)
http.HandleFunc("/prioritize", prioritizeHandler)
http.HandleFunc("/bind", bindHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
// Filter handler
func filterHandler(w http.ResponseWriter, r *http.Request) {
var args extender.ExtenderArgs
var result extender.ExtenderFilterResult
// Decode request body
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Implement filtering logic
filteredNodes := make([]v1.Node, 0, len(args.Nodes.Items))
failedNodes := make(map[string]string)
for _, node := range args.Nodes.Items {
// Custom filtering logic
if customFilter(&args.Pod, &node) {
filteredNodes = append(filteredNodes, node)
} else {
failedNodes[node.Name] = "Node failed custom filter"
}
}
// Set result
result.Nodes = &v1.NodeList{Items: filteredNodes}
result.FailedNodes = failedNodes
// Send response
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(result); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// Prioritize handler
func prioritizeHandler(w http.ResponseWriter, r *http.Request) {
var args extender.ExtenderArgs
var result extender.HostPriorityList
// Decode request body
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Implement prioritization logic
result = make(extender.HostPriorityList, 0, len(args.Nodes.Items))
for _, node := range args.Nodes.Items {
// Custom score calculation
score := customScore(&args.Pod, &node)
result = append(result, extender.HostPriority{
Host: node.Name,
Score: score,
})
}
// Send response
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(result); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// Bind handler
func bindHandler(w http.ResponseWriter, r *http.Request) {
var args extender.ExtenderBindingArgs
// Decode request body
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Implement binding logic
err := customBind(&args.Pod, args.Node)
// Send response
w.Header().Set("Content-Type", "application/json")
if err != nil {
json.NewEncoder(w).Encode(extender.ExtenderBindingResult{
Error: err.Error(),
})
} else {
json.NewEncoder(w).Encode(extender.ExtenderBindingResult{})
}
}
// Custom filtering function
func customFilter(pod *v1.Pod, node *v1.Node) bool {
// Implement custom filtering logic
return true
}
// Custom score calculation function
func customScore(pod *v1.Pod, node *v1.Node) int64 {
// Implement custom score calculation logic
return 100
}
// Custom binding function
func customBind(pod *v1.Pod, nodeName string) error {
// Implement custom binding logic
return nil
}Scheduler Extender 的优缺点: 优点:
- 可以独立于 scheduler codebase 进行开发
- 可以用多种编程语言实现
- 受 scheduler 升级影响较小
缺点:
- 由于 HTTP 通信开销,性能会下降
- 只能扩展 scheduling cycle 的部分阶段
- scheduler 与 extender 之间可能发生通信失败
Scheduler Extender vs Scheduling Framework plugin:
- Scheduler Extender:作为外部进程通过 HTTP webhook 运行。
- Scheduling Framework plugin:直接集成在 scheduler codebase 中运行。
其他选项的解释:
- A. /filter:有效的 scheduler extender API endpoint,用于过滤 Node 列表。
- B. /prioritize:有效的 scheduler extender API endpoint,用于为 Node 分配分数。
- C. /bind:有效的 scheduler extender API endpoint,用于将 Pod 绑定到 Node。
A. 在过滤后为 Node 分配分数 B. 在过滤后将 Pod 绑定到 Node C. 在过滤失败时执行 preemption 逻辑 D. 在过滤后更新 Pod 状态
显示答案
答案:C. 在过滤失败时执行 preemption 逻辑
解释: Kubernetes scheduling framework 中 “PostFilter” extension point 的作用是在过滤失败时执行 preemption 逻辑。当所有 Node 在 filtering phase 中都被排除且 Pod 无法被调度时,PostFilter plugin 会通过 preemption 查找调度该 Pod 的方式。
PostFilter extension point 的关键功能:
- 识别 preemption 候选对象:识别可以被 preempt 的 Pod 和 Node。
- Preemption 模拟:模拟在 preemption 之后 Pod 是否可以被调度。
- Preemption 决策:确定最佳 preemption 策略。
PostFilter plugin interface:
type PostFilterPlugin interface {
Plugin
// PostFilter is called when filtering fails.
// Finds ways to schedule pods through preemption.
PostFilter(ctx context.Context, state *CycleState, pod *v1.Pod, filteredNodeStatusMap NodeToStatusMap) (*PostFilterResult, *Status)
}
// PostFilterResult represents the result of PostFilter operation.
type PostFilterResult struct {
// Node where pod will be scheduled after preemption
NominatedNodeName string
}默认 PostFilter plugin: Kubernetes 提供以下默认 PostFilter plugin:
- DefaultPreemption:实现默认 preemption 逻辑。
DefaultPreemption plugin 的运行方式:
- 识别可以通过 preempt 低优先级 Pod 来腾出空间的 Node。
- 确定每个 Node 上要 preempt 哪些 Pod。
- 验证 preemption 后 Pod 是否可以被调度。
- 选择最佳 preemption 策略。
- 将选定 Node 设置为 Pod 的 nominatedNodeName。
Custom PostFilter plugin 示例:
// CustomPostFilter implements custom preemption logic.
type CustomPostFilter struct {
handle framework.Handle
}
// Name returns the plugin name.
func (pl *CustomPostFilter) Name() string {
return "CustomPostFilter"
}
// PostFilter is called when filtering fails.
func (pl *CustomPostFilter) PostFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, filteredNodeStatusMap framework.NodeToStatusMap) (*framework.PostFilterResult, *framework.Status) {
// Identify preemptable nodes
preemptableNodes := identifyPreemptableNodes(pl.handle, pod, filteredNodeStatusMap)
if len(preemptableNodes) == 0 {
return nil, framework.NewStatus(framework.Unschedulable, "no preemptable nodes found")
}
// Determine pods to preempt on each node
nodeToVictims := map[string]*framework.Victims{}
for _, node := range preemptableNodes {
victims, err := selectVictimsOnNode(pl.handle, pod, node)
if err != nil {
continue
}
nodeToVictims[node.Name] = victims
}
// Select optimal preemption strategy
nominatedNode, victims := selectBestNodeForPreemption(nodeToVictims)
if nominatedNode == "" {
return nil, framework.NewStatus(framework.Unschedulable, "no node for preemption")
}
// Execute preemption
for _, victim := range victims.Pods {
if err := pl.handle.ClientSet().CoreV1().Pods(victim.Namespace).Delete(ctx, victim.Name, metav1.DeleteOptions{}); err != nil {
return nil, framework.NewStatus(framework.Error, err.Error())
}
}
return &framework.PostFilterResult{
NominatedNodeName: nominatedNode,
}, nil
}
// Identify preemptable nodes
func identifyPreemptableNodes(handle framework.Handle, pod *v1.Pod, filteredNodeStatusMap framework.NodeToStatusMap) []*v1.Node {
// Implementation omitted
return nil
}
// Select pods to preempt on node
func selectVictimsOnNode(handle framework.Handle, pod *v1.Pod, node *v1.Node) (*framework.Victims, error) {
// Implementation omitted
return nil, nil
}
// Select optimal preemption strategy
func selectBestNodeForPreemption(nodeToVictims map[string]*framework.Victims) (string, *framework.Victims) {
// Implementation omitted
return "", nil
}在 scheduler configuration 中启用 PostFilter plugin:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: custom-scheduler
plugins:
postFilter:
enabled:
- name: CustomPostFilter
disabled:
- name: DefaultPreemption # Disable default pluginPreemption 相关设置:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: custom-scheduler
pluginConfig:
- name: DefaultPreemption
args:
minCandidateNodesPercentage: 10 # Minimum percentage of preemption candidate nodes
minCandidateNodesAbsolute: 100 # Minimum number of preemption candidate nodesPreemption 流程:
- 当 Pod 在所有 Node 上都未通过 filtering phase 时,会调用 PostFilter phase。
- PostFilter plugin 识别 preemption candidate Node。
- 确定每个 Node 上要 preempt 哪些 Pod。
- 验证 preemption 后 Pod 是否可以被调度。
- 选择最佳 preemption 策略。
- 将选定 Node 设置为 Pod 的 nominatedNodeName。
- 被 preempt 的 Pod 会经历 graceful termination。
- 当被 preempt 的 Pod 终止后,更高优先级的 Pod 会被调度。
监控 preemption 相关指标:
# Check preemption-related metrics from scheduler metrics
kubectl get --raw /metrics | grep scheduler_preemption检查 preemption event:
# Check preemption events
kubectl get events | grep -i preempt其他选项的问题:
- A. 在过滤后为 Node 分配分数:这是 “Score” extension point 的作用。
- B. 在过滤后将 Pod 绑定到 Node:这是 “Bind” extension point 的作用。
- D. 在过滤后更新 Pod 状态:这不是 scheduler framework 中的 extension point。
6. Kubernetes scheduler 中 “NodeResourcesBalancedAllocation” plugin 的主要目的是什么?
A. 给 CPU 和内存使用均衡的 Node 更高分数 B. 给资源使用较低的 Node 更高分数 C. 给资源使用较高的 Node 更高分数 D. 在 Node 上设置 resource limit
显示答案
答案:A. 给 CPU 和内存使用均衡的 Node 更高分数
解释: Kubernetes scheduler 中 “NodeResourcesBalancedAllocation” plugin 的主要目的是给 CPU 和内存使用均衡的 Node 更高分数。该 plugin 偏好 CPU 和内存利用率差异较小的 Node,从而提升整个 cluster 中的资源使用均衡性。
NodeResourcesBalancedAllocation plugin 的运行方式:
- 计算每个 Node 的 CPU 利用率和内存利用率。
- 计算 CPU 利用率与内存利用率之间的差异。
- 给差异较小的 Node 更高分数。
分数计算方法:
score = 10 - variance(cpuFraction, memoryFraction) * 10其中:
- cpuFraction =(已请求 CPU + Pod 的 CPU request)/ 可分配 CPU
- memoryFraction =(已请求内存 + Pod 的内存 request)/ 可分配内存
- variance(a, b) = |a - b|
示例:
- Node A:CPU 利用率 80%,内存利用率 80% -> 差异:0% -> 分数:10
- Node B:CPU 利用率 90%,内存利用率 50% -> 差异:40% -> 分数:6
- Node C:CPU 利用率 30%,内存利用率 90% -> 差异:60% -> 分数:4
在这种情况下,Node A 获得最高分,最有可能被选中。
在 scheduler configuration 中启用 NodeResourcesBalancedAllocation plugin:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
score:
enabled:
- name: NodeResourcesBalancedAllocation
weight: 2 # Set weightPlugin configuration:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
pluginConfig:
- name: NodeResourcesBalancedAllocation
args:
resources:
- name: cpu
weight: 1
- name: memory
weight: 1NodeResourcesBalancedAllocation vs 其他 scoring plugin:
- NodeResourcesBalancedAllocation:偏好 CPU 和内存使用均衡的 Node。
- NodeResourcesFit:相比请求资源,偏好可用资源更多的 Node。
- NodeResourcesLeastAllocated:偏好资源使用较低的 Node。
- NodeResourcesMostAllocated:偏好资源使用较高的 Node。
使用场景:
- 资源均衡:改善整个 cluster 的 CPU 和内存使用均衡。
- 防止瓶颈:防止某一类资源(CPU 或内存)先于另一类资源耗尽。
- 可扩展性提升:资源使用均衡的 cluster 可以更高效地扩展。
Custom balanced allocation plugin 示例:
// CustomBalancedAllocation implements custom balanced allocation logic.
type CustomBalancedAllocation struct {
handle framework.Handle
// Resource weights
resourceWeights map[v1.ResourceName]int64
}
// Name returns the plugin name.
func (pl *CustomBalancedAllocation) Name() string {
return "CustomBalancedAllocation"
}
// Score assigns a score to nodes.
func (pl *CustomBalancedAllocation) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
if err != nil {
return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))
}
// Node's allocatable resources
allocatable := nodeInfo.Node().Status.Allocatable
// Resources already requested on node
requested := nodeInfo.RequestedResource()
// Pod's resource request
podRequest := calculatePodResourceRequest(pod)
// Calculate resource utilization
fractions := make(map[v1.ResourceName]float64)
for resource, weight := range pl.resourceWeights {
if weight == 0 {
continue
}
allocatableValue := allocatable[resource]
if allocatableValue.IsZero() {
continue
}
requestedValue := requested.ResourceList[resource]
podRequestValue := podRequest[resource]
fraction := float64(requestedValue.Value()+podRequestValue.Value()) / float64(allocatableValue.Value())
fractions[resource] = fraction
}
// Calculate difference between resource utilizations
var variance float64
for _, fraction := range fractions {
for _, otherFraction := range fractions {
diff := fraction - otherFraction
if diff > 0 {
variance += diff
} else {
variance -= diff
}
}
}
// Calculate score
score := int64(100 - variance*100)
if score < 0 {
score = 0
}
return score, nil
}
// ScoreExtensions returns interface for score normalization.
func (pl *CustomBalancedAllocation) ScoreExtensions() framework.ScoreExtensions {
return nil
}
// Calculate pod's resource request
func calculatePodResourceRequest(pod *v1.Pod) v1.ResourceList {
result := v1.ResourceList{}
for _, container := range pod.Spec.Containers {
for resource, value := range container.Resources.Requests {
if currentValue, ok := result[resource]; ok {
currentValue.Add(value)
result[resource] = currentValue
} else {
result[resource] = value.DeepCopy()
}
}
}
return result
}其他选项的问题:
- B. 给资源使用较低的 Node 更高分数:这是 “NodeResourcesLeastAllocated” plugin 的作用。
- C. 给资源使用较高的 Node 更高分数:这是 “NodeResourcesMostAllocated” plugin 的作用。
- D. 在 Node 上设置 resource limit:这不是 scheduler plugin 的作用;Node resource limit 是 Node 本身的属性。
A. 将 Pod 绑定到 Node B. 在 binding 前执行必要操作 C. 在 binding 后执行清理 D. 当 binding 失败时执行恢复操作
显示答案
答案:B. 在 binding 前执行必要操作
解释: Kubernetes scheduling framework 中 “PreBind” extension point 的作用是在将 Pod 绑定到 Node 之前执行必要操作。例如,可以执行 volume provisioning、网络设置和 resource reservation 等操作。
PreBind extension point 的关键功能:
- Volume provisioning:创建并准备必要的 volume。
- 网络设置:配置必要的网络资源。
- Resource reservation:预留必要资源。
- 预验证:最终验证 binding 是否可行。
PreBind plugin interface:
type PreBindPlugin interface {
Plugin
// PreBind is called before binding a pod to a node.
PreBind(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) *Status
}默认 PreBind plugin: Kubernetes 提供以下默认 PreBind plugin:
- VolumeBinding:执行 volume binding 操作。
- DefaultPreBind:执行基本的 pre-binding 操作。
Custom PreBind plugin 示例:
// CustomPreBind implements custom pre-binding logic.
type CustomPreBind struct {
handle framework.Handle
}
// Name returns the plugin name.
func (pl *CustomPreBind) Name() string {
return "CustomPreBind"
}
// PreBind is called before binding a pod to a node.
func (pl *CustomPreBind) PreBind(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) *framework.Status {
// 1. Volume provisioning
if err := pl.provisionVolumes(ctx, pod, nodeName); err != nil {
return framework.NewStatus(framework.Error, err.Error())
}
// 2. Network resource setup
if err := pl.setupNetworking(ctx, pod, nodeName); err != nil {
return framework.NewStatus(framework.Error, err.Error())
}
// 3. Resource reservation
if err := pl.reserveResources(ctx, pod, nodeName); err != nil {
return framework.NewStatus(framework.Error, err.Error())
}
// 4. Final validation
if err := pl.validateBinding(ctx, pod, nodeName); err != nil {
return framework.NewStatus(framework.Error, err.Error())
}
return nil
}
// Volume provisioning
func (pl *CustomPreBind) provisionVolumes(ctx context.Context, pod *v1.Pod, nodeName string) error {
// Identify necessary volumes
for _, volume := range pod.Spec.Volumes {
if volume.PersistentVolumeClaim != nil {
// Check PVC status
pvc, err := pl.handle.ClientSet().CoreV1().PersistentVolumeClaims(pod.Namespace).Get(ctx, volume.PersistentVolumeClaim.ClaimName, metav1.GetOptions{})
if err != nil {
return err
}
// If PVC is not bound
if pvc.Status.Phase != v1.ClaimBound {
return fmt.Errorf("PVC %s is not bound", pvc.Name)
}
}
}
return nil
}
// Network resource setup
func (pl *CustomPreBind) setupNetworking(ctx context.Context, pod *v1.Pod, nodeName string) error {
// Example: Network policy setup
if err := pl.setupNetworkPolicies(ctx, pod, nodeName); err != nil {
return err
}
// Example: Service endpoint setup
if err := pl.setupServiceEndpoints(ctx, pod, nodeName); err != nil {
return err
}
return nil
}
// Resource reservation
func (pl *CustomPreBind) reserveResources(ctx context.Context, pod *v1.Pod, nodeName string) error {
// Example: GPU resource reservation
if err := pl.reserveGPUs(ctx, pod, nodeName); err != nil {
return err
}
// Example: Special hardware resource reservation
if err := pl.reserveSpecialHardware(ctx, pod, nodeName); err != nil {
return err
}
return nil
}
// Binding validation
func (pl *CustomPreBind) validateBinding(ctx context.Context, pod *v1.Pod, nodeName string) error {
// Example: Re-verify node status
node, err := pl.handle.ClientSet().CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{})
if err != nil {
return err
}
// Example: Check node resource availability
if !hasEnoughResources(node, pod) {
return fmt.Errorf("node %s does not have enough resources", nodeName)
}
return nil
}在 scheduler configuration 中启用 PreBind plugin:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: custom-scheduler
plugins:
preBind:
enabled:
- name: CustomPreBind
disabled:
- name: VolumeBinding # Disable default pluginPreBind 使用场景:
Volume provisioning:
- PersistentVolume 创建和 binding
- Ephemeral volume 准备
- Storage class 参数验证
网络设置:
- Network policy 应用
- Service endpoint 设置
- Load balancer 配置
Resource reservation:
- GPU 资源预留
- FPGA 资源预留
- 特殊硬件资源预留
安全设置:
- Security policy 应用
- Certificate provisioning
- Secret mount 准备
PreBind 失败处理: 当 PreBind plugin 返回失败时:
- scheduling cycle 被中止。
- Pod 返回 scheduling queue。
- 已预留资源被释放。
- 记录失败 event。
监控 PreBind log 和 event:
# Check PreBind-related messages in scheduler logs
kubectl logs -n kube-system <scheduler-pod> | grep -i prebind
# Check pod events
kubectl describe pod <pod-name> | grep -i prebind其他选项的问题:
- A. 将 Pod 绑定到 Node:这是 “Bind” extension point 的作用。
- C. 在 binding 后执行清理:这是 “PostBind” extension point 的作用。
- D. 当 binding 失败时执行恢复操作:这不是 scheduler framework 中的 extension point。
8. Kubernetes scheduler 中 “NodeResourcesFit” plugin 的主要目的是什么?
A. 监控 Node 资源使用情况 B. 设置 Node resource limit C. 将 Node 资源容量与 Pod resource request 进行比较 D. 维持 Node 资源使用均衡
显示答案
答案:C. 将 Node 资源容量与 Pod resource request 进行比较
解释: Kubernetes scheduler 中 “NodeResourcesFit” plugin 的主要目的是将 Node 资源容量与 Pod resource request 进行比较,以验证 Pod 是否可以在 Node 上运行。该 plugin 会考虑 CPU、内存、ephemeral storage 和 extended resource(如 GPU)等多种资源类型。
NodeResourcesFit plugin 的关键功能:
- Resource request 验证:验证 Pod resource request 不会超过 Node 的可分配资源。
- Resource limit 验证:验证 Pod resource limit 不会超过 Node 容量。
- Extended resource 验证:验证 GPU 和 FPGA 等 extended resource request 在 Node 上可用。
NodeResourcesFit plugin configuration:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
filter:
enabled:
- name: NodeResourcesFit
score:
enabled:
- name: NodeResourcesFit
weight: 1
pluginConfig:
- name: NodeResourcesFit
args:
scoringStrategy:
type: LeastAllocated
resources:
- name: cpu
weight: 1
- name: memory
weight: 1Scoring strategy: NodeResourcesFit plugin 支持以下 scoring strategy:
LeastAllocated:给使用资源较少的 Node 更高分数。
score = (capacity - requested) / capacityMostAllocated:给使用资源较多的 Node 更高分数。
score = requested / capacityRequestedToCapacityRatio:使用自定义函数基于请求资源与容量的比例分配分数。
Custom NodeResourcesFit plugin 示例:
// CustomNodeResourcesFit implements custom resource fit logic.
type CustomNodeResourcesFit struct {
handle framework.Handle
// Resource weights
resourceWeights map[v1.ResourceName]int64
}
// Name returns the plugin name.
func (pl *CustomNodeResourcesFit) Name() string {
return "CustomNodeResourcesFit"
}
// Filter checks node resource fitness.
func (pl *CustomNodeResourcesFit) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
// Node's allocatable resources
allocatable := nodeInfo.Node().Status.Allocatable
// Resources already requested on node
requested := nodeInfo.RequestedResource()
// Pod's resource request
podRequest := calculatePodResourceRequest(pod)
// Check each resource type
for resourceName := range pl.resourceWeights {
allocatableValue := allocatable[resourceName]
if allocatableValue.IsZero() {
return framework.NewStatus(framework.Unschedulable, fmt.Sprintf("node does not have resource %s", resourceName))
}
requestedValue := requested.ResourceList[resourceName]
podRequestValue := podRequest[resourceName]
if requestedValue.Value()+podRequestValue.Value() > allocatableValue.Value() {
return framework.NewStatus(framework.Unschedulable, fmt.Sprintf("insufficient %s", resourceName))
}
}
return nil
}
// Score assigns scores to nodes.
func (pl *CustomNodeResourcesFit) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
if err != nil {
return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))
}
// Node's allocatable resources
allocatable := nodeInfo.Node().Status.Allocatable
// Resources already requested on node
requested := nodeInfo.RequestedResource()
// Pod's resource request
podRequest := calculatePodResourceRequest(pod)
// Calculate score
var score int64 = 0
for resourceName, weight := range pl.resourceWeights {
allocatableValue := allocatable[resourceName]
if allocatableValue.IsZero() {
continue
}
requestedValue := requested.ResourceList[resourceName]
podRequestValue := podRequest[resourceName]
// Use LeastAllocated strategy
resourceScore := (float64(allocatableValue.Value()) - float64(requestedValue.Value()+podRequestValue.Value())) / float64(allocatableValue.Value())
score += int64(resourceScore * float64(weight))
}
return score, nil
}
// ScoreExtensions returns interface for score normalization.
func (pl *CustomNodeResourcesFit) ScoreExtensions() framework.ScoreExtensions {
return pl
}
// NormalizeScore normalizes scores.
func (pl *CustomNodeResourcesFit) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status {
var highest int64 = 0
for _, nodeScore := range scores {
if nodeScore.Score > highest {
highest = nodeScore.Score
}
}
if highest == 0 {
return nil
}
for i := range scores {
scores[i].Score = scores[i].Score * framework.MaxNodeScore / highest
}
return nil
}Resource request 和 limit 示例:
apiVersion: v1
kind: Pod
metadata:
name: resource-demo
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
cpu: "500m"
memory: "256Mi"
limits:
cpu: "1"
memory: "512Mi"其他选项的问题:
- A. 监控 Node 资源使用情况:这是 metrics server 或 monitoring system 的作用。
- B. 设置 Node resource limit:这是 Node configuration 或 kubelet 的作用。
- D. 维持 Node 资源使用均衡:这是 “NodeResourcesBalancedAllocation” plugin 的作用。
A. 处理 Pod 与 Node 之间的 affinity rule B. 处理 Pod 之间的 affinity 和 anti-affinity rule C. 处理 Pod 与 volume 之间的 affinity rule D. 处理 Pod 与 Service 之间的 affinity rule
显示答案
答案:B. 处理 Pod 之间的 affinity 和 anti-affinity rule
解释: Kubernetes scheduler 中 “InterPodAffinity” plugin 的主要目的是处理 Pod 之间的 affinity 和 anti-affinity rule。该 plugin 控制 Pod 是否与其他 Pod(affinity)放置在同一个 topology domain(Node、zone、region 等)中,或放置在不同 domain(anti-affinity)中。
InterPodAffinity plugin 的关键功能:
- Pod affinity rule 处理:确保 Pod 与带有特定 label 的其他 Pod 放置在同一个 topology domain 中。
- Pod anti-affinity rule 处理:确保 Pod 与带有特定 label 的其他 Pod 放置在不同 topology domain 中。
- Topology domain 考量:考虑包括 Node、zone 和 region 在内的多级 topology domain。
Pod affinity 和 anti-affinity 类型:
- requiredDuringSchedulingIgnoredDuringExecution:Pod 必须满足才能被调度的规则(硬性要求)。
- preferredDuringSchedulingIgnoredDuringExecution:偏好但非必需的规则(软性要求)。
Pod affinity 和 anti-affinity 示例:
apiVersion: v1
kind: Pod
metadata:
name: web-server
labels:
app: web
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- cache
topologyKey: kubernetes.io/hostname
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginxInterPodAffinity plugin configuration:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
preFilter:
enabled:
- name: InterPodAffinity
filter:
enabled:
- name: InterPodAffinity
score:
enabled:
- name: InterPodAffinity
weight: 2 # Set weight
pluginConfig:
- name: InterPodAffinity
args:
hardPodAffinityWeight: 1 # Hard pod affinity weightCustom InterPodAffinity plugin 示例:
// CustomInterPodAffinity implements custom inter-pod affinity logic.
type CustomInterPodAffinity struct {
handle framework.Handle
// Hard pod affinity weight
hardPodAffinityWeight int64
}
// Name returns the plugin name.
func (pl *CustomInterPodAffinity) Name() string {
return "CustomInterPodAffinity"
}
// PreFilter initializes inter-pod affinity information.
func (pl *CustomInterPodAffinity) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod) *framework.Status {
// Initialize pod affinity information
if pod.Spec.Affinity == nil || (pod.Spec.Affinity.PodAffinity == nil && pod.Spec.Affinity.PodAntiAffinity == nil) {
return nil
}
// Store pod affinity information
affinity := pod.Spec.Affinity
state.Write(framework.StateKey("CustomInterPodAffinity"), affinity)
return nil
}
// PreFilterExtensions returns interface providing additional features.
func (pl *CustomInterPodAffinity) PreFilterExtensions() framework.PreFilterExtensions {
return nil
}
// Filter checks inter-pod affinity rules.
func (pl *CustomInterPodAffinity) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
// Get pod affinity information
obj, err := state.Read(framework.StateKey("CustomInterPodAffinity"))
if err != nil {
return nil
}
affinity, ok := obj.(*v1.Affinity)
if !ok || affinity == nil {
return nil
}
// Check required pod affinity rules
if affinity.PodAffinity != nil {
for _, term := range affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution {
if !satisfiesPodAffinityTerm(pod, term, nodeInfo, pl.handle) {
return framework.NewStatus(framework.Unschedulable, "node does not satisfy pod affinity rules")
}
}
}
// Check required pod anti-affinity rules
if affinity.PodAntiAffinity != nil {
for _, term := range affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution {
if satisfiesPodAffinityTerm(pod, term, nodeInfo, pl.handle) {
return framework.NewStatus(framework.Unschedulable, "node does not satisfy pod anti-affinity rules")
}
}
}
return nil
}
// Score assigns scores to nodes.
func (pl *CustomInterPodAffinity) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
// Get pod affinity information
obj, err := state.Read(framework.StateKey("CustomInterPodAffinity"))
if err != nil {
return 0, nil
}
affinity, ok := obj.(*v1.Affinity)
if !ok || affinity == nil {
return 0, nil
}
nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
if err != nil {
return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))
}
var score int64 = 0
// Calculate preferred pod affinity score
if affinity.PodAffinity != nil {
for _, term := range affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
if satisfiesPodAffinityTerm(pod, term.PodAffinityTerm, nodeInfo, pl.handle) {
score += term.Weight
}
}
}
// Calculate preferred pod anti-affinity score
if affinity.PodAntiAffinity != nil {
for _, term := range affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
if !satisfiesPodAffinityTerm(pod, term.PodAffinityTerm, nodeInfo, pl.handle) {
score += term.Weight
}
}
}
return score, nil
}
// ScoreExtensions returns interface for score normalization.
func (pl *CustomInterPodAffinity) ScoreExtensions() framework.ScoreExtensions {
return pl
}
// NormalizeScore normalizes scores.
func (pl *CustomInterPodAffinity) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status {
var highest int64 = 0
for _, nodeScore := range scores {
if nodeScore.Score > highest {
highest = nodeScore.Score
}
}
if highest == 0 {
return nil
}
for i := range scores {
scores[i].Score = scores[i].Score * framework.MaxNodeScore / highest
}
return nil
}
// Check if pod affinity term is satisfied
func satisfiesPodAffinityTerm(pod *v1.Pod, term v1.PodAffinityTerm, nodeInfo *framework.NodeInfo, handle framework.Handle) bool {
// Implementation omitted
return true
}Pod affinity 和 anti-affinity 使用场景:
- 高可用性:将同一应用的实例分布到不同 Node、zone 或 region
- 性能优化:将相互通信的 Pod 放置在同一 Node 上以最大限度降低延迟
- 资源隔离:将资源密集型 Pod 分布到不同 Node 上
- 许可证限制:将有许可证限制的应用集中在特定 Node 上
Pod affinity 和 anti-affinity 的性能影响: Pod affinity 和 anti-affinity 的计算成本可能很高,因为它们需要考虑所有 Node 和 Pod。在大型 cluster 中,这可能影响调度性能,因此请谨慎使用。
其他选项的问题:
- A. 处理 Pod 与 Node 之间的 affinity rule:这是 “NodeAffinity” plugin 的作用。
- C. 处理 Pod 与 volume 之间的 affinity rule:这是 “VolumeBinding” plugin 的作用。
- D. 处理 Pod 与 Service 之间的 affinity rule:这不是 Kubernetes scheduler plugin。
10. Kubernetes scheduler 中 “NodeName” plugin 的主要目的是什么?
A. 验证 Pod 的 spec.nodeName 字段是否与 Node 名称匹配 B. 为 Node 分配名称 C. 为 Pod 分配 Node 名称 D. 验证 Node 名称格式
显示答案
答案:A. 验证 Pod 的 spec.nodeName 字段是否与 Node 名称匹配
解释: Kubernetes scheduler 中 “NodeName” plugin 的主要目的是验证 Pod 的 spec.nodeName 字段是否与 Node 名称匹配。该 plugin 检查 Pod 是否已被直接分配到特定 Node,并且在 filtering phase 中只允许名称匹配的 Node 通过。
NodeName plugin 的关键功能:
- Node 名称验证:如果 Pod 的
spec.nodeName字段已设置,则只选择名称匹配的 Node。 - 直接调度支持:允许用户将 Pod 直接分配到特定 Node。
- Scheduler bypass:设置了
spec.nodeName的 Pod 会绕过正常调度逻辑,并被直接分配到指定 Node。
NodeName plugin 实现:
// NodeName plugin implementation example
type NodeName struct{}
// Name returns the plugin name.
func (pl *NodeName) Name() string {
return "NodeName"
}
// Filter verifies that the pod's spec.nodeName field matches the node name.
func (pl *NodeName) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
if pod.Spec.NodeName == "" {
return nil
}
if pod.Spec.NodeName != nodeInfo.Node().Name {
return framework.NewStatus(framework.UnschedulableAndUnresolvable, "node name does not match")
}
return nil
}指定 nodeName 的 Pod 示例:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
nodeName: worker-node-1 # Direct assignment to specific node
containers:
- name: nginx
image: nginx使用 nodeName 时的注意事项:
- Scheduler bypass:使用
nodeName会绕过 scheduler 的 filtering、scoring 和其他逻辑。 - Node 存在性检查:如果指定的 Node 不存在,Pod 会保持在
Pending状态。 - 无资源检查:不会检查 Node 资源可用性,这可能因资源不足导致失败。
- 忽略约束:taint、affinity 和其他约束会被忽略。
nodeName vs nodeSelector vs nodeAffinity:
- nodeName:直接分配到特定 Node。限制最强,灵活性最低。
- nodeSelector:基于 label 选择 Node。简单但表达能力有限。
- nodeAffinity:支持复杂的 Node 选择规则。最灵活且表达能力最强。
nodeName 使用场景:
- 调试:在特定 Node 上运行 Pod 以调试问题。
- 测试:在特定 Node 上运行测试。
- 特殊硬件:将 Pod 分配到具有特定硬件的 Node。
- Static Pod:用于由 kubelet 直接管理的 Static Pod。
使用 nodeName 时的注意事项:
- 无自动恢复:如果 Node 发生故障,Pod 不会自动移动到其他 Node。
- 可扩展性有限:Node 名称是硬编码的,限制了可扩展性。
- 维护困难:如果 Node 名称变化,需要更新 Pod 定义。
- 无负载均衡:无法利用 scheduler 的负载均衡功能。
替代方案和建议:
使用 nodeSelector:
yamlapiVersion: v1 kind: Pod metadata: name: nginx spec: nodeSelector: kubernetes.io/hostname: worker-node-1 containers: - name: nginx image: nginx使用 nodeAffinity:
yamlapiVersion: v1 kind: Pod metadata: name: nginx spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - worker-node-1 containers: - name: nginx image: nginx
其他选项的问题:
- B. 为 Node 分配名称:Node 名称在创建 Node 时分配,并不是 scheduler plugin 的作用。
- C. 为 Pod 分配 Node 名称:这是在 scheduler 的 binding phase 中执行的,而不是由 NodeName plugin 执行。
- D. 验证 Node 名称格式:这是由 API server validation logic 执行的,而不是由 scheduler plugin 执行。