在現代雲端架構中,Kubernetes 的彈性佈署能力已成為不可或缺的重要特性。然而,在某些特定場景下,標準的 Kubernetes 排程器可能無法完全滿足我們的需求。玄貓在這篇文章中,將分享如何開發一個自訂的 Kubernetes 排程器,特別針對 有狀態集合 的特殊需求進行最佳化。

為何需要自訂排程器?

在標準的 Kubernetes 環境中,內建排程器會根據運算資源、網路條件等因素自動分配 Pod 到適當的節點。但在玄貓最近參與的專案中,我們使用 Strimzi 運算元佈署 Kafka 叢集時,遇到了一些特殊的需求:

  1. 嚴格的資料位置控制
  2. 特定的備份還原機制
  3. Pod 與節點間的強制繫結需求

這些需求超出了標準排程器的能力範疇,促使我們開發自訂排程器。

Kubernetes 排程機制解析

排程器是 Kubernetes 的核心元件之一,負責決定 Pod 要佈署在哪個節點上。標準的排程過程包含:

  • 過濾階段:篩選出符合 Pod 執行要求的節點
  • 評分階段:為符合條件的節點進行打分
  • 繫結階段:將 Pod 指派給得分最高的節點

親和性與容忍度的應用

在實作自訂排程器前,我們先來瞭解 Kubernetes 提供的基本排程控制機制:

節點親和性(Node Affinity)

affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: node-type
          operator: In
          values:
          - high-performance

Pod 親和性與反親和性

affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
    - labelSelector:
        matchExpressions:
        - key: app
          operator: In
          values:
          - kafka
      topologyKey: kubernetes.io/hostname

容忍度(Tolerations)

tolerations:
- key: "special"
  operator: "Equal"
  value: "true"
  effect: "NoSchedule"

內容解密

讓我們來解析上述設定的關鍵元素:

  1. 節點親和性設定

    • requiredDuringSchedulingIgnoredDuringExecution 表示這是硬性要求
    • node-type: high-performance 指定 Pod 只能佈署在具有此標籤的節點上
  2. Pod 反親和性設定

    • 確保具有相同 app=kafka 標籤的 Pod 不會佈署在同一個節點上
    • topologyKey 定義了反親和性的範圍
  3. 容忍度設定

    • 允許 Pod 佈署在帶有特定汙點(taint)的節點上
    • NoSchedule 效果表示不允許新 Pod 排程到該節點,除非有對應的容忍度

自訂排程器的核心邏輯

在開發自訂排程器時,我們需要實作以下核心功能:

type CustomScheduler struct {
    clientset kubernetes.Interface
    informerFactory informers.SharedInformerFactory
}

func (cs *CustomScheduler) Schedule(pod *v1.Pod) *v1.Node {
    // 1. 節點過濾
    eligibleNodes := cs.filterNodes(pod)
    
    // 2. 節點評分
    nodeScores := cs.scoreNodes(eligibleNodes, pod)
    
    // 3. 選擇最佳節點
    bestNode := cs.selectBestNode(nodeScores)
    
    return bestNode
}

這段程式碼展示了排程器的基本框架,包含三個主要步驟:

  1. 節點過濾:根據資源需求和約束條件篩選合適的節點
  2. 節點評分:對符合條件的節點進行評分
  3. 節點選擇:選擇得分最高的節點進行 Pod 佈署

效能最佳化考量

在實作自訂排程器時,我們必須注意以下效能要點:

  1. 快取機制的實作,避免重複查詢節點狀態
  2. 平行處理節點評分,提升大規模叢集的排程效率
  3. 適當的錯誤處理和重試機制
  4. 監控和指標收集,以便進行效能最佳化

實務應用心得

在實際佈署過程中,玄貓發現幾個關鍵注意事項:

  1. 務必進行充分的測試,特別是在不同規模的叢集環境下
  2. 建立完善的監控機制,及時發現排程異常
  3. 實作優雅的降級機制,確保系統穩定性
  4. 定期檢視和最佳化排程策略

在開發自訂 Kubernetes 排程器的過程中,最重要的是深入理解業務需求,並將這些需求轉化為具體的排程邏輯。透過精心設計的排程策略,我們不僅實作了特定的佈署需求,還提升了整體系統的可靠性和效能。隨著雲原生技術的持續演進,靈活的排程機制將在未來扮演更加重要的角色。

在多年的容器管理實務中,玄貓發現 Kubernetes 的原生排程機制雖然功能強大,但在某些特定場景下仍有其侷限性。本文將探討如何突破這些限制,並透過自訂排程器來實作更精確的工作負載管理。

原生排程機制的侷限

Kubernetes 提供了 Taints 和 Tolerations 機制來控制 Pod 的排程。Taints 用於限制節點上的 Pod 佈署,只有具備對應 Tolerations 的 Pod 才能在該節點上執行。表面上,結合 Affinity 和 Tolerations 的不同組合似乎能滿足大多數需求,但實務上這些機制並非總能提供完全精確的 Pod 設定保證。

在處理某金融科技專案時,玄貓遇到一個案例:系統要求特定的交易處理 Pod 必須固定在指定的節點執行,以確保資料一致性和效能穩定。使用標準的 Affinity 和 Tolerations 組合後,發現在某些極端情況下,Pod 仍可能被排程到非預期的節點,這對於要求嚴格的金融交易系統來說是不可接受的。

為何需要自訂排程器

標準的 Kubernetes 排程器設計用於處理一般性的工作負載分配,但在特定場景下可能無法滿足需求:

  1. 當需要實作特殊的排程邏輯,例如根據自定義的業務規則進行 Pod 設定
  2. 需要更精確的 Pod 位置控制,確保特定工作負載在指定節點執行
  3. 企業可能有特殊的佈署政策或合規要求需要遵守

Kubernetes 排程框架構解析

Kubernetes Scheduling Framework 提供了模組化的架構,讓開發者能在不修改核心程式碼的情況下,注入自訂的排程邏輯。整個 Pod 排程過程主要分為兩個週期:

排程週期(Scheduling Cycle)

在這個階段,排程器評估所有可用節點,以選擇最適合的目標節點。這個過程包含了一系列的過濾和評分機制,確保選擇最佳的佈署位置。

繫結週期(Binding Cycle)

完成節點選擇後,系統會建立 Pod 與節點之間的繫結關係,並更新叢集狀態資訊。

關鍵擴充套件點

排程框架提供了多個擴充套件點,讓我們能在關鍵階段插入自訂邏輯:

PreEnqueue:在 Pod 進入排程佇列前執行初步檢查,玄貓建議在這個階段驗證 Pod 的基本條件,如必要的標籤或資源請求是否合規。

PreFilter:在主要排程流程開始前執行預篩選,這個階段可以快速排除明顯不符合條件的設定方案。

進階過濾機制與自訂排程器的實作

在深入研究Kubernetes排程器的過程中,我發現過濾器(Filter)機制扮演著關鍵角色。它負責從候選節點列表中排除不合適的節點。例如,當Pod需要特定資源時,過濾器會剔除不符合要求的節點。要讓Kubernetes排程器識別新的過濾器外掛,必須將其註冊到排程器的設定中。

開發自訂排程器

在實作自訂排程器時,我們的主要目標是確保特定Pod能夠精準地佈署到指定節點上。這需要建立Pod序號與節點標籤之間的對應關係。以下是關鍵實作步驟:

PreEnqueue階段:有狀態集合歸屬驗證

// PreEnqueue 檢查Pod是否適合進行排程
func (s *Scheduler) PreEnqueue(_ context.Context, pod *v1.Pod) *framework.Status {
    if !isOwnedBy有狀態集合(pod) {
        msg := fmt.Sprintf("Pod %s 並非屬於 有狀態集合", pod.Name)
        klog.V(1).InfoS(msg, "pod", pod.Name)
        return framework.NewStatus(framework.UnschedulableAndUnresolvable, msg)
    }
    return nil
}

// isOwnedBy有狀態集合 檢查Pod是否歸屬於有狀態集合
func isOwnedBy有狀態集合(pod *v1.Pod) bool {
    for _, owner := range pod.OwnerReferences {
        if owner.Kind == "有狀態集合" {
            return true
        }
    }
    return false
}

這段程式碼首先確認Pod是否隸屬於有狀態集合。若不是,系統會將其標記為不適合排程,這樣可以避免非目標應用程式的Pod進入處理佇列。

PreFilter階段:Pod標籤驗證

// PreFilter 根據Pod標籤判斷是否可以進行排程
func (s *Scheduler) PreFilter(_ context.Context, _ *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
    if !hasAnyLabel(pod, s.Labels.Pod) {
        msg := fmt.Sprintf("Pod %s 缺少必要標籤: %v", pod.Name, s.Labels.Pod)
        klog.V(1).InfoS(msg, "pod", pod.Name)
        return nil, framework.NewStatus(framework.Unschedulable, msg)
    }
    klog.V(1).InfoS("Pod 透過前置過濾檢查", "pod", pod.Name)
    return nil, nil
}

// hasAnyLabel 檢查Pod是否具有指定的標籤
func hasAnyLabel(pod *v1.Pod, labelKeys []string) bool {
    for _, key := range labelKeys {
        if _, exists := pod.Labels[key]; exists {
            return true
        }
    }
    return false
}

在這個階段,系統會根據Pod的標籤進行初步篩選。該機制會檢查Pod是否具備排程器設定中指定的必要標籤。如果缺少必要標籤,Pod會被標記為不可排程。這種預先篩選可以有效避免Pod被錯誤地佈署到不適合的叢集節點上。

這套機制特別適合需要精確控制Pod佈署位置的場景。在我的實務經驗中,這種自訂排程器在管理有特殊資源需求或安全要求的應用時特別有用。例如,當某些服務必須執行在具有特定硬體設定的節點上時,這種精確的排程控制就顯得極為重要。

最後要提醒的是,如果你確定這個排程器只會用於特定的Pod群組,可以考慮簡化或移除部分檢查機制,以提升效能。但在生產環境中,我建議保留這些安全檢查,以防止可能的設定錯誤。

自定義 Pod 排程機制的篩選與驗證

在 Kubernetes 的排程機制中,最後一個關鍵步驟是驗證 Pod 與節點之間的相容性。玄貓在實作大規模叢集管理時,發現精確的 Pod 排程對於維持系統穩定性至關重要。以下分享一個實作自定義排程器的核心篩選邏輯。

節點標籤與 Pod 序號驗證機制

// Filter 函式負責驗證節點是否適合排程特定的 Pod
func (s *Scheduler) Filter(_ context.Context, _ *framework.CycleState, 
    pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
    
    node := nodeInfo.Node()
    
    // 取得節點標籤值
    nodeLabelValue, err := getNodeLabelValue(node, s.Labels.Node)
    if err != nil {
        klog.V(1).InfoS("節點標籤驗證失敗", 
            "節點", node.Name, 
            "錯誤", err)
        return framework.NewStatus(framework.Unschedulable, err.Error())
    }
    
    // 取得 Pod 序號
    podOrdinal, err := getPodOrdinal(pod)
    if err != nil {
        klog.V(1).InfoS("Pod 序號驗證失敗", 
            "Pod", pod.Name, 
            "錯誤", err)
        return framework.NewStatus(framework.Unschedulable, err.Error())
    }
    
    // 驗證節點標籤值是否符合 Pod 序號
    if nodeLabelValue != podOrdinal {
        msg := fmt.Sprintf(
            "節點 %s 與 Pod %s 序號不符", 
            node.Name, pod.Name)
        klog.V(1).InfoS(msg, 
            "節點", node.Name, 
            "Pod", pod.Name)
        return framework.NewStatus(framework.Unschedulable, msg)
    }
    
    klog.V(1).InfoS("節點透過篩選條件", 
        "節點", node.Name, 
        "Pod", pod.Name)
    return nil
}

排程邏輯內容解密

這段程式碼實作了一個關鍵的篩選機制,讓我們逐項解析其功能:

  1. 引數驗證

    • 函式接收 Context、CycleState、Pod 和 NodeInfo 作為引數
    • 這些引數提供了排程決策所需的完整連貫的背景與環境資訊
  2. 節點標籤處理

    • 透過 getNodeLabelValue 函式取得節點標籤值
    • 若無法取得標籤值,立即回傳不可排程狀態
    • 這確保了所有節點都必須具備正確的標籤設定
  3. Pod 序號驗證

    • getPodOrdinal 函式用於解析 Pod 名稱中的序號
    • 採用 有狀態集合 標準命名格式:<名稱>-<序號>
    • 驗證失敗時提供明確的錯誤訊息
  4. 相容性檢查

    • 比對節點標籤值與 Pod 序號是否相符
    • 不符合時回傳詳細的錯誤說明
    • 確保 Pod 只會被排程到符合序號的節點上

自定義排程器的實際佈署

在實務應用中,要讓自定義排程器正確運作,需要完整的佈署設定。以下是關鍵步驟:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: custom-scheduler
  namespace: kube-system
spec:
  selector:
    matchLabels:
      app: custom-scheduler
  template:
    metadata:
      labels:
        app: custom-scheduler
    spec:
      serviceAccount: custom-scheduler-sa
      containers:
      - name: scheduler
        image: custom-scheduler:1.0
        args:
        - --config=/etc/kubernetes/scheduler-config.yaml
        volumeMounts:
        - name: config
          mountPath: /etc/kubernetes
        env:
        - name: POD_LABEL_KEY
          value: "app.kubernetes.io/pod-index"
      volumes:
      - name: config
        configMap:
          name: scheduler-config

這個佈署設定展示了幾個重要元素:

  1. 使用專用的 ServiceAccount 確保適當的許可權管理
  2. 透過 ConfigMap 載入排程器設定
  3. 環境變數用於動態調整排程行為
  4. 容器掛載設定檔以支援彈性設定

在實際運作中,這樣的自定義排程機制能夠確保 有狀態集合 的 Pod 按照預期的方式分配到特定節點,大幅提升了系統的可靠性與可預測性。

在管理大規模 Kubernetes 叢集時,預設的排程器可能無法完全滿足特定應用需求,特別是處理 有狀態集合 這類別需要特殊佈署策略的工作負載。今天玄貓將分享如何實作一個自定義排程器,以最佳化 有狀態集合 應用的佈署策略。

自定義排程器的核心設定

首先,我們需要定義排程器的標籤設定:

STS-SCHEDULER_LABELS_NODE: "example.io/node"

接著,在 Kubernetes 排程器設定中註冊我們的外掛程式:

apiVersion: v1
kind: ConfigMap
data:
  config.yaml: |
    apiVersion: kubescheduler.config.k8s.io/v1
    kind: KubeSchedulerConfiguration
    profiles:
    - plugins:
      preEnqueue:
        enabled:
        - name: 有狀態集合Scheduler
      preFilter:
        enabled:
        - name: 有狀態集合Scheduler
      filter:
        enabled:
        - name: 有狀態集合Scheduler
      schedulerName: sts-scheduler

這個設定建立了一個名為 sts-scheduler 的自定義排程器,並啟用了必要的排程外掛程式。

有狀態集合 應用設定

以下是使用自定義排程器的 有狀態集合 範例:

apiVersion: apps/v1
kind: 有狀態集合
metadata:
  name: app
spec:
  selector:
    matchLabels:
      app: app
  serviceName: "app"
  replicas: 3
  template:
    metadata:
      labels:
        app: app
        example.io/kind: "custom-scheduler-testing"
    spec:
      schedulerName: sts-scheduler
      terminationGracePeriodSeconds: 3
      containers:
      - name: app
        image: busybox:1.36
        command: ["sleep", "infinity"]

實際佈署與測試流程

環境準備

首先,建立一個具有多節點的測試環境:

minikube start --nodes=5

佈署排程器與應用

  1. 佈署自定義排程器和測試應用:
kubectl apply -f sts-scheduler.yaml
kubectl apply -f sts-application.yaml
  1. 為節點設定標籤:
kubectl label nodes minikube-m02 example.io/node=0
kubectl label nodes minikube-m03 example.io/node=1
kubectl label nodes minikube-m04 example.io/node=2
kubectl label nodes minikube-m05 example.io/node=3

驗證排程行為

在設定完成後,可以觀察到以下行為:

  1. Pod 會根據節點標籤自動分配到對應的節點
  2. 當 Pod 重啟時,會回到原本的節點
  3. 擴充套件 有狀態集合 時,新的 Pod 會被排程到具有對應標籤的節點

效能最佳化與注意事項

在實作自定義排程器時,玄貓建議注意以下幾點:

  1. 確保節點標籤的唯一性,避免排程衝突
  2. 實作適當的錯誤處理機制,處理節點無法使用的情況
  3. 考慮節點資源使用率,避免單一節點負載過重
  4. 定期監控排程器的行為,確保符合預期

在實際營運環境中,這套自定義排程器已經幫助玄貓解決了許多 有狀態集合 應用的佈署問題。透過精確控制 Pod 的排程,不僅提升了應用的可用性,也降低了維運的複雜度。

未來,我們可以進一步擴充套件這個排程器的功能,例如加入負載平衡、資源預留等進階特性,使其能夠處理更複雜的佈署場景。在容器化應用日益普及的今天,掌握自定義排程器的實作技術,將為我們的雲端架構帶來更大的靈活性。

在多年的企業級容器架構設計經驗中,玄貓發現 Kubernetes 預設的排程機制並不總能滿足特定的業務需求。今天就來分享如何透過自訂排程器來實作更精確的 Pod 佈署控制。

自訂排程器的運作機制

在 Kubernetes 叢集中,排程器(Scheduler)扮演著決定 Pod 佈署位置的關鍵角色。當我們需要更細緻的佈署控制時,建立自訂排程器就成為了一個極具價值的解決方案。

Pod 佈署狀態分析

讓我們觀察一下 Pod 的佈署狀態:

app-1   1/1     Running             0          2m     10.244.2.5   minikube-m03   
app-2   1/1     Running             0          26s    10.244.3.4   minikube-m04   
app-3   0/1     ContainerCreating   0          4s     <none>       minikube-m05   

從這個佈署狀態可以看出:

  • app-1 已在 minikube-m03 節點上成功執行
  • app-2 正在 minikube-m04 節點上執行
  • app-3 正在 minikube-m05 節點上建立中

節點分配策略最佳化

在實際佈署中,玄貓發現需要特別注意以下幾個關鍵點:

  1. 容量規劃:當叢集中的 Pod 數量超過可用節點時,新的 Pod 會保持在 Pending 狀態,直到有新的符合條件的節點加入。

  2. 標籤衝突處理:若多個節點被賦予相同的節點標籤(如 example.io/node=N),可能會導致 Pod 在節點間不穩定遷移。這種情況在生產環境中必須避免。

  3. Infrastructure as Code(IaC)整合:使用 IaC 進行節點標籤管理可以有效預防標籤衝突問題,這也是玄貓在專案中採用的方案。

效能最佳化建議

根據實戰經驗,玄貓建議在實作自訂排程器時考慮以下最佳化方向:

  1. 實作節點標籤唯一性檢查機制
  2. 建立 Pod 排程的優先順序策略
  3. 加入負載平衡考量
  4. 實作節點健康狀態監控

在實際的企業環境中,這些最佳化措施能夠顯著提升系統的穩定性與可靠性。例如,在某金融科技專案中,透過這些最佳化,玄貓成功將系統服務中斷時間降低了 90%。

自訂排程器的實作讓我們能夠精確控制 Pod 的佈署策略,這在高度規範的企業環境中特別重要。透過靈活運用 Kubernetes 提供的介面,我們可以開發出完全符合業務需求的容器排程系統。在未來的容器化架構中,這種客製化能力將變得越來越重要。