Skip to content

Custom Scheduler クイズ (Part 3)

このクイズでは、Kubernetes における Custom Scheduler の実装と使用に関する高度な理解を確認します。

クイズ問題

1. Kubernetes で複数の scheduler を同時に実行する際に発生しうる問題ではないものは、次のうちどれですか?

A. リソース競合 B. スケジューリング判断の競合 C. ネットワーク帯域幅の増加 D. Leader election の競合

答えを表示

答え: C. ネットワーク帯域幅の増加

解説: 「ネットワーク帯域幅の増加」は、Kubernetes で複数の scheduler を同時に実行する際に発生しうる問題ではありません。scheduler は API server と通信しますが、ネットワーク帯域幅の使用量は通常ごくわずかであり、懸念事項にはなりません。

複数の scheduler を実行する際に実際に発生しうる問題:

  1. リソース競合:

    • 複数の scheduler が同じ node pool に Pod をスケジュールしようとすると、リソース競合が発生する可能性があります。
    • 各 scheduler は他の scheduler の判断を認識せずに独立して動作するため、node リソースを過剰に割り当てるリスクがあります。
    • 例: 2 つの scheduler が同時に同じ node に Pod をスケジュールし、node の容量を超える可能性があります。
  2. スケジューリング判断の競合:

    • 複数の scheduler が同じ Pod をスケジュールしようとすると、競合が発生する可能性があります。
    • これは、Pod が schedulerName を明示的に指定していない場合や、複数の scheduler が同じ名前を使用している場合に発生することがあります。
    • 例: 2 つの scheduler が同じ Pod を異なる node に bind しようとすると、競合状態が発生します。
  3. Leader election の競合:

    • 同じ名前の scheduler instance が leader election を有効にして複数実行されている場合、leader election メカニズムで競合が発生する可能性があります。
    • 例: 同じ名前の複数の scheduler instance がリーダーシップを競い合うと、不安定なリーダーシップ遷移が発生する可能性があります。

複数の scheduler を実行する際のベストプラクティス:

  1. 責任範囲の明確な分離:

    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
  2. 一意の 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
  3. 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
  4. resource quota を設定する:

    yaml
    apiVersion: 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 の監視:

bash
# 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 が同じ node pool に Pod をスケジュールする際に発生しうる実際の問題です。
  • B. スケジューリング判断の競合: 複数の scheduler が同じ Pod をスケジュールしようとする際に発生しうる実際の問題です。
  • D. Leader election の競合: 同じ名前の複数の scheduler instance がリーダーシップを競い合う際に発生しうる実際の問題です。

2. Kubernetes scheduler における "Permit" extension point の役割は何ですか?

A. Pod を node に bind する B. Pod のスケジューリングを許可、拒否、または遅延する C. Pod を実行できない node を除外する D. node に score を割り当てる

答えを表示

答え: B. Pod のスケジューリングを許可、拒否、または遅延する

解説: Kubernetes scheduling framework における "Permit" extension point の役割は、Pod のスケジューリングを許可、拒否、または遅延することです。Permit plugin は node が選択された後、binding phase の前に実行され、Pod のスケジューリング判断に対する最終的な承認または拒否を提供します。

Permit extension point の主な機能:

  1. Allow: Pod のスケジューリングが binding phase に進むことを許可します。
  2. Deny: Pod のスケジューリングを拒否し、別の node を選択できるようにします。
  3. Wait: Pod のスケジューリングを一時的に遅延し、特定の条件が満たされるまで待機します。

Permit plugin interface:

go
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 の結果タイプ:

  1. Success: Pod のスケジューリングを許可します。
  2. Deny: Pod のスケジューリングを拒否します。
  3. Wait: Pod のスケジューリングを遅延し、指定された時間だけ待機します。

デフォルトの Permit plugin: Kubernetes は次のデフォルト Permit plugin を提供します。

  1. TaintToleration: node taint と Pod toleration を確認します。
  2. PodTopologySpread: Pod topology spread constraint を確認します。

Custom Permit plugin の例:

go
// 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 を有効化する:

yaml
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: custom-scheduler
  plugins:
    permit:
      enabled:
      - name: CustomPermit
      disabled:
      - name: TaintToleration  # Disable default plugin

Permit のユースケース:

  1. Gang scheduling: 関連するすべての Pod をスケジュールできる準備が整うまで、Pod group のスケジューリングを遅延します。
  2. リソース予約: Pod がスケジュールされる前に外部リソースを予約します。
  3. ポリシー検証: Pod のスケジューリングが組織のポリシーに準拠していることを確認します。
  4. 承認ワークフロー: Pod のスケジューリングに対して外部承認を要求します。

Gang scheduling の例: Gang scheduling は、関連するすべての Pod が一緒にスケジュールされることを保証する手法です。これは、すべてのコンポーネントが同時に実行される必要がある分散トレーニングジョブのような workload に役立ちます。

go
// 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 する: これは "Bind" extension point の役割です。
  • C. Pod を実行できない node を除外する: これは "Filter" extension point の役割です。
  • D. node に score を割り当てる: これは "Score" extension point の役割です。
### 3. Kubernetes における Gang Scheduling の主な目的は何ですか?

A. Pod を特定の node のみに配置する B. 関連するすべての Pod が一緒にスケジュールされるようにする C. Pod をさまざまな node に均等に分散する D. 優先度に基づいて Pod をスケジュールする

答えを表示

答え: B. 関連するすべての Pod が一緒にスケジュールされるようにする

解説: Kubernetes における Gang Scheduling の主な目的は、関連するすべての Pod が一緒にスケジュールされるようにすることです。これは、分散トレーニングジョブや分散データ処理ジョブのように、すべてのコンポーネントが同時に実行される必要がある workload で重要です。

Gang scheduling が必要な理由:

  1. All-or-Nothing 要件: 一部の workload はすべてのコンポーネントが同時に実行される必要があり、一部だけが実行されてもジョブは進行しません。
  2. リソース浪費の防止: 一部の Pod だけがスケジュールされ、他の Pod が待機している場合、すでにスケジュールされた Pod が使用するリソースが無駄になる可能性があります。
  3. デッドロックの防止: 相互依存する Pod が異なるタイミングでスケジュールされると、デッドロックが発生する可能性があります。

Gang scheduling の実装方法: Kubernetes は Gang scheduling をネイティブにはサポートしていませんが、次の方法で実装できます。

  1. Custom scheduler: Permit extension point を使用して Gang scheduling を実装します。
  2. External controller: Kubernetes の外部で Gang scheduling を管理する controller を実装します。
  3. オープンソースソリューション: Volcano や Kube-batch のようなオープンソース scheduler を使用します。

Gang scheduling の例 (Volcano):

yaml
# 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 の実装:

go
// 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 が一緒にスケジュールされることを保証します
  • リソースの浪費を防ぎます
  • デッドロックと starvation を防ぎます

短所:

  • 実装の複雑さが増します
  • スケジューリング遅延が発生する可能性があります
  • cluster リソース使用率が低下する可能性があります

Gang scheduling を必要とする workload:

  1. 分散トレーニングジョブ: TensorFlow、PyTorch のような分散トレーニング framework
  2. 分散データ処理: Spark、Flink のような分散データ処理 framework
  3. MPI ジョブ: High-performance computing (HPC) workload
  4. 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

解説: "/validate" は、Kubernetes で Scheduler Extender を実装する際に必須の API endpoint ではありません。Scheduler extender は通常、"/filter"、"/prioritize"、"/bind"、"/preempt" などの endpoint を実装しますが、"/validate" は scheduler extender の標準 API ではありません。

Scheduler Extender API endpoint:

  1. filter: node のリストを受け取り、フィルタリング済みの node リストを返します。
  2. prioritize: node のリストを受け取り、各 node に score を割り当てます。
  3. bind: Pod を node に bind します。
  4. preempt: preemption のための node と Pod を返します。

Scheduler Extender configuration の例:

yaml
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: true

Scheduler Extender API の request と response の形式:

  1. 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>
      }
  2. prioritize API:

    • Request:
      json
      {
        "pod": <pod>,
        "nodes": <nodes>,
        "nodenames": <node-names>
      }
    • Response:
      json
      {
        "hostPriorities": [
          {
            "host": <node-name>,
            "score": <score>
          },
          ...
        ],
        "error": <error-message>
      }
  3. bind API:

    • Request:
      json
      {
        "pod": <pod>,
        "node": <node-name>
      }
    • Response:
      json
      {
        "error": <error-message>
      }
  4. 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>
      }

Scheduler Extender implementation example (Go):

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 通信オーバーヘッドによる performance 低下
  • scheduling cycle の一部の stage しか拡張できません
  • scheduler と extender 間の通信障害の可能性

Scheduler Extender と Scheduling Framework plugin の比較:

  • Scheduler Extender: HTTP webhook 経由で外部プロセスとして実行されます。
  • Scheduling Framework plugins: scheduler codebase に直接統合されて実行されます。

他の選択肢の解説:

  • A. /filter: node リストをフィルタリングする有効な scheduler extender API endpoint です。
  • B. /prioritize: node に score を割り当てる有効な scheduler extender API endpoint です。
  • C. /bind: Pod を node に bind する有効な scheduler extender API endpoint です。
### 5. Kubernetes scheduler framework における "PostFilter" extension point の役割は何ですか?

A. filtering 後に node に score を割り当てる B. filtering 後に Pod を node に bind する C. filtering が失敗したときに preemption logic を実行する D. filtering 後に Pod status を更新する

答えを表示

答え: C. filtering が失敗したときに preemption logic を実行する

解説: Kubernetes scheduling framework における "PostFilter" extension point の役割は、filtering が失敗したときに preemption logic を実行することです。filtering phase ですべての node が除外され、Pod をスケジュールできない場合、PostFilter plugin は preemption を通じて Pod をスケジュールする方法を見つけます。

PostFilter extension point の主な機能:

  1. preemption 候補の特定: preempt 可能な Pod と node を特定します。
  2. preemption simulation: preemption 後に Pod をスケジュールできるかどうかをシミュレートします。
  3. preemption 判断: 最適な preemption 戦略を決定します。

PostFilter plugin interface:

go
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 を提供します。

  1. DefaultPreemption: デフォルトの preemption logic を実装します。

DefaultPreemption plugin の動作:

  1. 優先度の低い Pod を preempt することで空きを作れる node を特定します。
  2. 各 node で preempt する Pod を決定します。
  3. preemption 後に Pod をスケジュールできることを検証します。
  4. 最適な preemption 戦略を選択します。
  5. 選択した node を Pod の nominatedNodeName として設定します。

Custom PostFilter plugin の例:

go
// 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 を有効化する:

yaml
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: custom-scheduler
  plugins:
    postFilter:
      enabled:
      - name: CustomPostFilter
      disabled:
      - name: DefaultPreemption  # Disable default plugin

preemption 関連の設定:

yaml
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 nodes

preemption プロセス:

  1. Pod がすべての node で filtering phase に失敗したとき、PostFilter phase が呼び出されます。
  2. PostFilter plugin が preemption candidate node を特定します。
  3. 各 node でどの Pod を preempt するかを決定します。
  4. preemption 後に Pod をスケジュールできることを検証します。
  5. 最適な preemption 戦略を選択します。
  6. 選択した node を Pod の nominatedNodeName として設定します。
  7. preempt された Pod は graceful termination を行います。
  8. preempt された Pod が終了すると、より優先度の高い Pod がスケジュールされます。

preemption 関連 metrics の監視:

bash
# Check preemption-related metrics from scheduler metrics
kubectl get --raw /metrics | grep scheduler_preemption

preemption event の確認:

bash
# Check preemption events
kubectl get events | grep -i preempt

他の選択肢の問題点:

  • A. filtering 後に node に score を割り当てる: これは "Score" extension point の役割です。
  • B. filtering 後に Pod を node に bind する: これは "Bind" extension point の役割です。
  • D. filtering 後に Pod status を更新する: これは scheduler framework の extension point ではありません。

6. Kubernetes scheduler における "NodeResourcesBalancedAllocation" plugin の主な目的は何ですか?

A. CPU と memory の使用率がバランスしている node に高い score を付ける B. リソース使用率が低い node に高い score を付ける C. リソース使用率が高い node に高い score を付ける D. node にリソース制限を設定する

答えを表示

答え: A. CPU と memory の使用率がバランスしている node に高い score を付ける

解説: Kubernetes scheduler における "NodeResourcesBalancedAllocation" plugin の主な目的は、CPU と memory の使用率がバランスしている node に高い score を付けることです。この plugin は、CPU と memory の利用率の差が小さい node を優先し、cluster 全体のリソース使用バランスを改善します。

NodeResourcesBalancedAllocation plugin の動作:

  1. 各 node の CPU utilization と memory utilization を計算します。
  2. CPU utilization と memory utilization の差を計算します。
  3. 差が小さい node に高い score を付けます。

score 計算方法:

score = 10 - variance(cpuFraction, memoryFraction) * 10

Where:

  • cpuFraction = (requested CPU + pod's CPU request) / allocatable CPU
  • memoryFraction = (requested memory + pod's memory request) / allocatable memory
  • variance(a, b) = |a - b|

例:

  • Node A: CPU utilization 80%、memory utilization 80% -> 差: 0% -> score: 10
  • Node B: CPU utilization 90%、memory utilization 50% -> 差: 40% -> score: 6
  • Node C: CPU utilization 30%、memory utilization 90% -> 差: 60% -> score: 4

この場合、Node A が最も高い score を受け取り、選択される可能性が最も高くなります。

scheduler configuration で NodeResourcesBalancedAllocation plugin を有効化する:

yaml
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
  plugins:
    score:
      enabled:
      - name: NodeResourcesBalancedAllocation
        weight: 2  # Set weight

Plugin configuration:

yaml
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
  pluginConfig:
  - name: NodeResourcesBalancedAllocation
    args:
      resources:
      - name: cpu
        weight: 1
      - name: memory
        weight: 1

NodeResourcesBalancedAllocation と他の scoring plugin の比較:

  1. NodeResourcesBalancedAllocation: CPU と memory の使用率がバランスしている node を優先します。
  2. NodeResourcesFit: 要求リソースと比較して、より多くの利用可能リソースを持つ node を優先します。
  3. NodeResourcesLeastAllocated: リソース使用率が低い node を優先します。
  4. NodeResourcesMostAllocated: リソース使用率が高い node を優先します。

ユースケース:

  1. リソースバランス: cluster 全体の CPU と memory の使用バランスを改善します。
  2. ボトルネック防止: あるリソースタイプ(CPU または memory)が他方より先に枯渇することを防ぎます。
  3. スケーラビリティ向上: リソース使用率がバランスした cluster は、より効率的にスケールできます。

Custom balanced allocation plugin の例:

go
// 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 に高い score を付ける: これは "NodeResourcesLeastAllocated" plugin の役割です。
  • C. リソース使用率が高い node に高い score を付ける: これは "NodeResourcesMostAllocated" plugin の役割です。
  • D. node にリソース制限を設定する: これは scheduler plugin の役割ではありません。node リソース制限は node 自体のプロパティです。
### 7. Kubernetes scheduler における "PreBind" extension point の役割は何ですか?

A. Pod を node に bind する B. binding 前に必要な操作を実行する C. binding 後に cleanup を実行する D. binding が失敗したときに recovery 操作を実行する

答えを表示

答え: B. binding 前に必要な操作を実行する

解説: Kubernetes scheduling framework における "PreBind" extension point の役割は、Pod を node に bind する前に必要な操作を実行することです。たとえば、volume provisioning、network setup、resource reservation のような操作を実行できます。

PreBind extension point の主な機能:

  1. Volume provisioning: 必要な volume を作成し、準備します。
  2. Network setup: 必要な network resource を設定します。
  3. Resource reservation: 必要なリソースを予約します。
  4. Pre-validation: binding が可能であることを最終確認します。

PreBind plugin interface:

go
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 を提供します。

  1. VolumeBinding: volume binding 操作を実行します。
  2. DefaultPreBind: 基本的な pre-binding 操作を実行します。

Custom PreBind plugin の例:

go
// 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 を有効化する:

yaml
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: custom-scheduler
  plugins:
    preBind:
      enabled:
      - name: CustomPreBind
      disabled:
      - name: VolumeBinding  # Disable default plugin

PreBind のユースケース:

  1. Volume provisioning:

    • PersistentVolume の作成と binding
    • Ephemeral volume の準備
    • Storage class parameter の検証
  2. Network setup:

    • Network policy の適用
    • Service endpoint の設定
    • Load balancer configuration
  3. Resource reservation:

    • GPU resource reservation
    • FPGA resource reservation
    • Special hardware resource reservation
  4. Security setup:

    • Security policy の適用
    • Certificate provisioning
    • Secret mount の準備

PreBind failure handling: PreBind plugin が failure を返した場合:

  1. scheduling cycle が中止されます。
  2. Pod は scheduling queue に戻ります。
  3. 予約されたリソースが解放されます。
  4. failure event がログに記録されます。

PreBind log と event の監視:

bash
# 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 する: これは "Bind" extension point の役割です。
  • C. binding 後に cleanup を実行する: これは "PostBind" extension point の役割です。
  • D. binding が失敗したときに recovery 操作を実行する: これは scheduler framework の extension point ではありません。

8. Kubernetes scheduler における "NodeResourcesFit" plugin の主な目的は何ですか?

A. node のリソース使用率を監視する B. node のリソース制限を設定する C. node のリソース容量と Pod のリソース要求を比較する D. node のリソース使用バランスを維持する

答えを表示

答え: C. node のリソース容量と Pod のリソース要求を比較する

解説: Kubernetes scheduler における "NodeResourcesFit" plugin の主な目的は、node のリソース容量と Pod のリソース要求を比較し、Pod が node 上で実行できるかどうかを検証することです。この plugin は CPU、memory、ephemeral storage、extended resource(GPU など)を含むさまざまなリソースタイプを考慮します。

NodeResourcesFit plugin の主な機能:

  1. Resource request validation: Pod の resource request が node の allocatable resource を超えていないことを検証します。
  2. Resource limit validation: Pod の resource limit が node capacity を超えていないことを検証します。
  3. Extended resource validation: GPU や FPGA のような extended resource request が node 上で利用可能であることを検証します。

NodeResourcesFit plugin configuration:

yaml
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: 1

Scoring strategy: NodeResourcesFit plugin は次の scoring strategy をサポートします。

  1. LeastAllocated: 使用中のリソースが少ない node に高い score を付けます。

    score = (capacity - requested) / capacity
  2. MostAllocated: 使用中のリソースが多い node に高い score を付けます。

    score = requested / capacity
  3. RequestedToCapacityRatio: 要求リソースと容量の比率に基づき、custom function を使用して score を割り当てます。

Custom NodeResourcesFit plugin の例:

go
// 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 の例:

yaml
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 のリソース制限を設定する: これは node configuration または kubelet の役割です。
  • D. node のリソース使用バランスを維持する: これは "NodeResourcesBalancedAllocation" plugin の役割です。
### 9. Kubernetes scheduler における "InterPodAffinity" plugin の主な目的は何ですか?

A. Pod と node 間の affinity rule を処理する B. Pod 間の affinity rule と anti-affinity rule を処理する C. Pod と volume 間の affinity rule を処理する D. Pod と service 間の affinity rule を処理する

答えを表示

答え: B. Pod 間の affinity rule と anti-affinity rule を処理する

解説: Kubernetes scheduler における "InterPodAffinity" plugin の主な目的は、Pod 間の affinity rule と anti-affinity rule を処理することです。この plugin は、Pod が他の Pod と同じ topology domain(node、zone、region など)に配置されるか(affinity)、または異なる domain に配置されるか(anti-affinity)を制御します。

InterPodAffinity plugin の主な機能:

  1. Pod affinity rule processing: 特定の label を持つ他の Pod と同じ topology domain に Pod が配置されることを保証します。
  2. Pod anti-affinity rule processing: 特定の label を持つ他の Pod とは異なる topology domain に Pod が配置されることを保証します。
  3. Topology domain consideration: node、zone、region を含むさまざまなレベルの topology domain を考慮します。

Pod affinity と anti-affinity のタイプ:

  1. requiredDuringSchedulingIgnoredDuringExecution: Pod をスケジュールするために満たす必要がある rule(hard requirement)。
  2. preferredDuringSchedulingIgnoredDuringExecution: 必須ではないが優先される rule(soft requirement)。

Pod affinity と anti-affinity の例:

yaml
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: nginx

InterPodAffinity plugin configuration:

yaml
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 weight

Custom InterPodAffinity plugin の例:

go
// 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 のユースケース:

  1. High availability: 同じ application の instance を異なる node、zone、または region に分散します
  2. Performance optimization: 互いに通信する Pod を同じ node に配置して latency を最小化します
  3. Resource isolation: resource-intensive な Pod を異なる node に分散します
  4. License restrictions: license restriction がある application を特定の node に集約します

Pod affinity と anti-affinity の performance impact: Pod affinity と anti-affinity は、すべての node と Pod を考慮する必要があるため、計算コストが高くなる場合があります。大規模 cluster では scheduling performance に影響する可能性があるため、注意して使用してください。

他の選択肢の問題点:

  • 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 field が node 名と一致することを検証する B. node に名前を割り当てる C. Pod に node 名を割り当てる D. node 名の形式を検証する

答えを表示

答え: A. Pod の spec.nodeName field が node 名と一致することを検証する

解説: Kubernetes scheduler における "NodeName" plugin の主な目的は、Pod の spec.nodeName field が node 名と一致することを検証することです。この plugin は、Pod が特定の node に直接割り当てられているかどうかを確認し、filtering phase で名前が一致する node のみを通過させます。

NodeName plugin の主な機能:

  1. Node name verification: Pod の spec.nodeName field が設定されている場合、一致する名前の node のみが選択されます。
  2. Direct scheduling support: ユーザーが Pod を特定の node に直接割り当てることを可能にします。
  3. Scheduler bypass: spec.nodeName が設定された Pod は通常の scheduling logic を bypass し、指定された node に直接割り当てられます。

NodeName plugin implementation:

go
// 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 の例:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  nodeName: worker-node-1  # Direct assignment to specific node
  containers:
  - name: nginx
    image: nginx

nodeName を使用する際の考慮事項:

  1. Scheduler bypass: nodeName を使用すると、scheduler の filtering、scoring、およびその他の logic を bypass します。
  2. Node existence check: 指定された node が存在しない場合、Pod は Pending 状態のままになります。
  3. No resource check: node の resource availability は確認されないため、resource shortage による failure につながる可能性があります。
  4. Ignoring constraints: taint、affinity、およびその他の constraint は無視されます。

nodeName と nodeSelector と nodeAffinity の比較:

  1. nodeName: 特定の node に直接割り当てます。最も制約が強く、最も柔軟性が低いです。
  2. nodeSelector: label に基づいて node を選択します。単純ですが表現力は限定的です。
  3. nodeAffinity: 複雑な node selection rule をサポートします。最も柔軟で表現力があります。

nodeName のユースケース:

  1. Debugging: 問題をデバッグするために特定の node で Pod を実行します。
  2. Testing: 特定の node でテストを実行します。
  3. Special hardware: 特定の hardware を持つ node に Pod を割り当てます。
  4. Static pods: kubelet によって直接管理される static Pod に使用されます。

nodeName 使用時の注意点:

  1. No automatic recovery: node が failure しても、Pod は自動的に他の node に移動しません。
  2. Limited scalability: node 名が hardcoded されるため、scalability が制限されます。
  3. Maintenance difficulty: node 名が変更された場合、Pod 定義を更新する必要があります。
  4. No load balancing: scheduler の load balancing 機能を活用できません。

代替手段と推奨事項:

  1. nodeSelector の使用:

    yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: nginx
    spec:
      nodeSelector:
        kubernetes.io/hostname: worker-node-1
      containers:
      - name: nginx
        image: nginx
  2. nodeAffinity の使用:

    yaml
    apiVersion: 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 によるものではありません。