클라우드 네이티브 Prometheus를 사용하여 Kubernetes 스케줄러를 확장하는 방법에 대한 심층 분석

1. 쿠버네티스 스케줄링 구성

① 스케줄러 구성

  • kube-scheduler는 구성 파일에 대한 리소스를 kube-scheduler용 구성 파일로 제공하며 파일은 시작 시 --config=에 의해 지정됩니다. 현재 각 kubernetes 버전에서 사용되는 KubeSchedulerConfiguration은 다음과 같습니다.
    • 1.21 이전 버전은 v1beta1을 사용합니다.
    • 버전 1.22는 v1beta2를 사용하지만 v1beta1은 유지합니다.
    • 버전 1.23, 1.24 및 1.25는 v1beta3을 사용하지만 v1beta2는 유지하고 v1beta1은 삭제합니다.
  • 아래와 같이 kubeSchedulerConfiguration의 간단한 예인데, 여기서 kubeconfig는 시작 매개변수 --kubeconfig와 동일한 기능을 가지며, kubeSchedulerConfiguration은 서비스로 시작된 구성 파일인 kubeletConfiguration과 같이 다른 구성 요소의 구성 파일과 유사합니다.
apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: /etc/srv/kubernetes/kube-scheduler/kubeconfig
  • –kubeconfig와 --config는 동시에 지정할 수 없으며 --config를 지정하면 다른 매개변수는 당연히 실패합니다.

② kubeSchedulerConfiguration 사용

  • 구성 파일을 통해 사용자는 여러 스케줄러를 사용자 정의하고 각 단계의 확장 지점을 구성할 수 있으며 플러그인은 이러한 확장 지점을 통해 전체 스케줄링 컨텍스트에서 스케줄링 동작을 제공합니다.
  • 아래에 표시된 구성은 확장 지점을 구성하는 예입니다(name="*"인 경우 확장 지점에 해당하는 모든 플러그인이 비활성화/활성화됨).
apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration
profiles:
  - plugins:
      score:
        disabled:
        - name: PodTopologySpread
        enabled:
        - name: MyCustomPluginA
          weight: 2
        - name: MyCustomPluginB
          weight: 1
  • 쿠버네티스는 다중 스케줄러를 제공하기 때문에 환경설정 파일에 대해 다중 환경설정 파일을 자연스럽게 지원한다 프로파일도 리스트 형식이다 다중 환경설정 목록만 지정하면 된다 다음은 다중 환경설정 파일의 예이다 확장자가 여러 개인 경우 points 및 여러 확장 지점을 각 스케줄러에 대해 구성할 수도 있습니다.
apiVersion: kubescheduler.config.k8s.io/v1beta2
kind: KubeSchedulerConfiguration
profiles:
  - schedulerName: default-scheduler
   plugins:
      preScore:
        disabled:
        - name: '*'
      score:
        disabled:
        - name: '*'
  - schedulerName: no-scoring-scheduler
    plugins:
      preScore:
        disabled:
        - name: '*'
      score:
        disabled:
        - name: '*'

③ 스케줄러 스케줄링 플러그인

  • kube-scheduler는 기본적으로 스케줄링 방법으로 많은 플러그인을 제공하며, 이러한 플러그인은 다음과 같이 기본적으로 구성되지 않은 경우 활성화됩니다.
    • ImageLocality: 예약은 컨테이너 이미지가 있는 노드에 더 편향됩니다. 확장점: 점수;
    • TaintToleration: taint 및 tolerance, 확장 지점의 기능 실현: 필터, preScore, 점수;
    • NodeName: 스케줄링 전략에서 가장 간단한 스케줄링 방법 NodeName 구현, 확장 지점: 필터;
    • NodePorts: 스케줄링은 노드 포트가 점유되었는지 여부를 확인합니다. 확장 지점: preFilter, filter;
    • NodeAffinity: 노드 선호도 관련 기능, 확장점 제공: 필터, 점수;
    • PodTopologySpread: 포드 토폴로지 도메인 기능 실현, 확장점: preFilter, 필터, preScore, 점수;
    • NodeResourcesFit: 이 플러그인은 다음 세 가지 전략 중 하나를 사용하여 포드에서 요청한 모든 리소스가 노드에 있는지 확인합니다.
    • VolumeBinding: 노드가 요청된 볼륨을 가지고 있거나 바인딩할 수 있는지 확인합니다. 확장 포인트: preFilter, filter, reserve, preBind, score;
    • VolumeRestrictions: 노드에 설치된 볼륨이 볼륨 공급자별 제한, 확장 지점: 필터를 충족하는지 확인합니다.
    • VolumeZone: 요청된 볼륨이 가질 수 있는 영역 요구 사항을 충족하는지 확인합니다. 확장 지점: 필터;
    • InterPodAffinity: 포드 간의 친화성 및 반친화성 기능 실현, 확장 지점: preFilter, 필터, preScore, 점수;
    • PrioritySort: 기본 우선순위, 확장점: queueSort를 기반으로 정렬을 제공합니다.

2. kube-scheduler를 확장하는 방법은 무엇입니까?

  • 처음 스케줄러 작성을 생각하면 보통 kube-scheduler를 확장하는 것이 매우 어려운 일이라고 생각합니다. 버전 1.15 프레임워크는 스케줄러를 보다 확장 가능하게 만드는 것을 목표로 합니다.
  • 프레임워크는 각각의 확장 포인트를 재정의하여 플러그인으로 사용하고 사용자가 kube-scheduler에 등록할 수 있도록 트리 외부 확장을 등록하도록 지원합니다.

① 항목 정의

  • 스케줄러는 사용자 정의를 허용하지만 해당 NewSchedulerCommand를 참조하고 플러그인 논리를 구현하기만 하면 됩니다.
import (
    scheduler "k8s.io/kubernetes/cmd/kube-scheduler/app"
)

func main() {
    
    
    command := scheduler.NewSchedulerCommand(
            scheduler.WithPlugin("example-plugin1", ExamplePlugin1),
            scheduler.WithPlugin("example-plugin2", ExamplePlugin2))
    if err := command.Execute(); err != nil {
    
    
        fmt.Fprintf(os.Stderr, "%v\n", err)
        os.Exit(1)
    }
}
  • NewSchedulerCommand는 외부 플러그인 주입, 즉 외부 커스텀 플러그인 주입을 허용하는데, 이 경우 스케줄러를 정의하기 위해 소스 코드를 수정할 필요 없이 직접 구현해야만 커스텀 스케줄러를 완성할 수 있다.
// WithPlugin 用于注入out of tree plugins 因此scheduler代码中没有其引用。
func WithPlugin(name string, factory runtime.PluginFactory) Option {
    
    
 return func(registry runtime.Registry) error {
    
    
  return registry.Register(name, factory)
 }
}

② 플러그인 구현

  • 플러그인 구현은 해당 확장점 인터페이스만 구현하면 됩니다.내장 플러그인 NodeAffinity는 그 구조를 관찰하여 찾을 수 있습니다.플러그인 구현은 해당 확장점 추상 인터페이스를 구현하는 것입니다.

여기에 이미지 설명 삽입

  • 플러그인 구조 정의: framework.FrameworkHandle은 Kubernetes API와 스케줄러 간의 호출에 사용됩니다.lister, informer 등을 포함하는 구조에서 볼 수 있습니다. 이 매개변수도 구현해야 합니다.
type NodeAffinity struct {
    
    
 handle framework.FrameworkHandle
}
  • 해당 확장점을 구현합니다.
func (pl *NodeAffinity) 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 := nodeInfo.Node()
 if node == nil {
    
    
  return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))
 }

 affinity := pod.Spec.Affinity

 var count int64
 // A nil element of PreferredDuringSchedulingIgnoredDuringExecution matches no objects.
 // An element of PreferredDuringSchedulingIgnoredDuringExecution that refers to an
 // empty PreferredSchedulingTerm matches all objects.
 if affinity != nil && affinity.NodeAffinity != nil && affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil {
    
    
  // Match PreferredDuringSchedulingIgnoredDuringExecution term by term.
  for i := range affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
    
    
   preferredSchedulingTerm := &affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[i]
   if preferredSchedulingTerm.Weight == 0 {
    
    
    continue
   }

   // TODO: Avoid computing it for all nodes if this becomes a performance problem.
   nodeSelector, err := v1helper.NodeSelectorRequirementsAsSelector(preferredSchedulingTerm.Preference.MatchExpressions)
   if err != nil {
    
    
    return 0, framework.NewStatus(framework.Error, err.Error())
   }

   if nodeSelector.Matches(labels.Set(node.Labels)) {
    
    
    count += int64(preferredSchedulingTerm.Weight)
   }
  }
 }

 return count, nil
}
  • 마지막으로, main.go의 트리 외부 플러그인으로 스케줄러에 삽입할 수 있는 New 함수를 구현하여 이 확장을 등록하는 방법을 제공합니다.
// New initializes a new plugin and returns it.
func New(_ runtime.Object, h framework.FrameworkHandle) (framework.Plugin, error) {
    
    
 return &NodeAffinity{
    
    handle: h}, nil
}

3. 네트워크 트래픽 기반 스케줄링

  • 위의 스케줄러 플러그인 확장 방법에 대한 이해를 통해 트래픽 기반 스케줄링의 예를 아래에서 완성하겠습니다.일반적으로 네트워크에서 노드가 일정 기간 동안 사용하는 네트워크 트래픽은 생산 환경.
  • 예를 들어 균형 잡힌 구성의 여러 호스트 중에서 호스트 A는 서비스 주문 스크립트로 실행되고 호스트 B는 일반 서비스로 실행되는데, 주문을 위해서는 많은 양의 데이터를 다운로드해야 하지만 하드웨어 리소스를 거의 점유하지 않기 때문에 이때 , Pod가 이 노드에 예정되어 있으면 양 당사자의 비즈니스에 영향을 줄 수 있습니다. 네트워크 대역폭 점유로 인한 효율성 감소).

① 환경 구성

  • kubernetes 클러스터에는 최소 2개의 노드가 있어야 합니다.
  • 제공된 kubernetes 클러스터는 모두 클러스터 내부 또는 외부에 있을 수 있는 prometheus node_exporter를 설치해야 하며 여기서는 클러스터 외부에 있는 것을 사용합니다.
  • promQLclient_golang 에 대한 이해가 있어야 합니다 .
  • 예제는 대략 다음 단계로 나뉩니다.
    • 플러그인 API를 정의하고 플러그인의 이름은 NetworkTraffic입니다.
    • 확장점을 정의하고 점수 확장점이 여기에서 사용되며 채점 알고리즘이 정의됩니다.
    • 점수를 얻는 방법을 정의합니다(프로메테우스 표시기에서 해당 데이터 가져오기).
    • 사용자 지정 스케줄러에 대한 매개변수 입력을 정의합니다.
    • 프로젝트를 클러스터에 배포합니다(클러스터 내 배포 및 클러스터 외부 배포).
    • 결과 검증 예시.
  • 예제는 코드 작성을 완료하기 위해 내장된 플러그인 노드친화도를 따를 것 입니다 . -in은 동일한 효과를 가집니다.

② 오류 처리

  • 프로젝트 초기화, go mod tidy 및 기타 작업을 수행할 때 다음과 같은 오류가 많이 발생합니다.
go: github.com/GoogleCloudPlatform/spark-on-k8s-operator@v0.0.0-20210307184338-1947244ce5f4 requires
        k8s.io/apiextensions-apiserver@v0.0.0: reading k8s.io/apiextensions-apiserver/go.mod at revision v0.0.0: unknown revision v0.0.0
  • 이 문제는 kubernetes issue #79384 에 언급되어 있습니다. 얼핏 봐서는 왜 이런 문제가 발생했는지 설명이 되어 있지 않습니다. 맨 아래에 상사가 스크립트를 제공했습니다. 위의 문제가 해결되지 않을 때 스크립트를 직접 실행하면 됩니다. 정상:
#!/bin/sh
set -euo pipefail

VERSION=${
    
    1#"v"}
if [ -z "$VERSION" ]; then
    echo "Must specify version!"
    exit 1
fi
MODS=($(
    curl -sS https://raw.githubusercontent.com/kubernetes/kubernetes/v${
    
    VERSION}/go.mod |
    sed -n 's|.*k8s.io/\(.*\) => ./staging/src/k8s.io/.*|k8s.io/\1|p'
))
for MOD in "${MODS[@]}"; do
    V=$(
        go mod download -json "${MOD}@kubernetes-${VERSION}" |
        sed -n 's|.*"Version": "\(.*\)".*|\1|p'
    )
    go mod edit "-replace=${MOD}=${MOD}@${V}"
done
go get "k8s.io/kubernetes@v${VERSION}"

③ 플러그인 API 정의

  • 위의 내용 설명을 통해 우리는 플러그인 정의가 해당 확장 지점 추상 인터페이스를 구현하기만 하면 프로젝트 디렉토리 pkg/networtraffic/networktraffice.go를 초기화할 수 있음을 이해합니다.
  • 플러그인 이름과 변수를 정의합니다.
const Name = "NetworkTraffic"
var _ = framework.ScorePlugin(&NetworkTraffic{
    
    })
  • 플러그인의 구조를 정의합니다.
type NetworkTraffic struct {
    
    
 // 这个作为后面获取node网络流量使用
 prometheus *PrometheusHandle
 // FrameworkHandle 提供插件可以使用的数据和一些工具
 // 它在插件初始化时传递给 plugin 工厂类
 // plugin 必须存储和使用这个handle来调用framework函数
 handle framework.FrameworkHandle
}

④ 확장점 정의

  • Score 확장 포인트가 선택되었으므로 해당 추상화를 실현하려면 해당 메서드를 정의해야 합니다.
func (n *NetworkTraffic) Score(ctx context.Context, state *framework.CycleState, p *corev1.Pod, nodeName string) (int64, *framework.Status) {
    
    
    // 通过promethes拿到一段时间的node的网络使用情况
 nodeBandwidth, err := n.prometheus.GetGauge(nodeName)
 if err != nil {
    
    
  return 0, framework.NewStatus(framework.Error, fmt.Sprintf("error getting node bandwidth measure: %s", err))
 }
 bandWidth := int64(nodeBandwidth.Value)
 klog.Infof("[NetworkTraffic] node '%s' bandwidth: %s", nodeName, bandWidth)
 return bandWidth, nil // 这里直接返回就行
}
  • 다음으로 결과를 정규화해야 합니다. 소스 코드에서 Score 확장 포인트가 이 단일 메서드 이상을 구현해야 한다는 것을 알 수 있습니다.
// Run NormalizeScore method for each ScorePlugin in parallel.
parallelize.Until(ctx, len(f.scorePlugins), func(index int) {
    
    
    pl := f.scorePlugins[index]
    nodeScoreList := pluginToNodeScores[pl.Name()]
    if pl.ScoreExtensions() == nil {
    
    
        return
    }
    status := f.runScoreExtension(ctx, pl, state, pod, nodeScoreList)
    if !status.IsSuccess() {
    
    
        err := fmt.Errorf("normalize score plugin %q failed with error %v", pl.Name(), status.Message())
        errCh.SendErrorWithCancel(err, cancel)
        return
    }
})
  • 위의 코드에서 Score를 구현하기 위해서는 ScoreExtensions를 구현해야 하고, 구현되지 않은 경우 바로 리턴한다는 것을 알 수 있습니다. nodeaffinity의 예제에 따르면 이 메서드는 확장점 개체 자체만 반환하고 특정 정규화는 NormalizeScore에서 실제 스코어링 작업임을 알 수 있습니다.
// NormalizeScore invoked after scoring all nodes.
func (pl *NodeAffinity) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status {
    
    
 return pluginhelper.DefaultNormalizeScore(framework.MaxNodeScore, false, scores)
}

// ScoreExtensions of the Score plugin.
func (pl *NodeAffinity) ScoreExtensions() framework.ScoreExtensions {
    
    
 return pl
}
  • 스케줄링 프레임워크에서 실제로 작업을 수행하는 방법도 NormalizeScore()입니다.
func (f *frameworkImpl) runScoreExtension(ctx context.Context, pl framework.ScorePlugin, state *framework.CycleState, pod *v1.Pod, nodeScoreList framework.NodeScoreList) *framework.Status {
    
    
 if !state.ShouldRecordPluginMetrics() {
    
    
  return pl.ScoreExtensions().NormalizeScore(ctx, state, pod, nodeScoreList)
 }
 startTime := time.Now()
 status := pl.ScoreExtensions().NormalizeScore(ctx, state, pod, nodeScoreList)
 f.metricsRecorder.observePluginDurationAsync(scoreExtensionNormalize, pl.Name(), status, metrics.SinceInSeconds(startTime))
 return status
}
  • NormalizeScore에서 노드를 선택하는 특정 알고리즘을 구현해야 합니다.구현된 알고리즘 공식은 최고 점수, 최고 현재 대역폭 및 최고 대역폭입니다.이것은 더 큰 대역폭 점유를 가진 기계가 더 낮은 점수를 갖도록 보장합니다. 예를 들어 최고 대역폭이 200,000이고 현재 노드 대역폭이 140,000이면 노드 점수는 다음과 같습니다.
// 如果返回framework.ScoreExtensions 就需要实现framework.ScoreExtensions
func (n *NetworkTraffic) ScoreExtensions() framework.ScoreExtensions {
    
    
 return n
}

// NormalizeScore与ScoreExtensions是固定格式
func (n *NetworkTraffic) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *corev1.Pod, scores framework.NodeScoreList) *framework.Status {
    
    
 var higherScore int64
 for _, node := range scores {
    
    
  if higherScore < node.Score {
    
    
   higherScore = node.Score
  }
 }
 // 计算公式为,满分 - (当前带宽 / 最高最高带宽 * 100)
 // 公式的计算结果为,带宽占用越大的机器,分数越低
 for i, node := range scores {
    
    
  scores[i].Score = framework.MaxNodeScore - (node.Score * 100 / higherScore)
  klog.Infof("[NetworkTraffic] Nodes final score: %v", scores)
 }

 klog.Infof("[NetworkTraffic] Nodes final score: %v", scores)
 return nil
}
  • 쿠버네티스에서 최대 노드 수는 5000개를 지원하는데, 최대 점수를 얻을 때 루핑이 성능을 많이 잡아먹는다는 뜻이 아닐까요?사실 걱정할 필요가 없습니다. 스케줄러는 배포 주기 수를 결정하는 매개변수 백분율OfNodesToScore를 제공합니다.

⑤ 플러그인 이름 구성

  • 등록 시 플러그인을 사용하려면 다음과 같은 이름으로 구성해야 합니다.
// Name returns name of the plugin. It is used in logs, etc.
func (n *NetworkTraffic) Name() string {
    
    
 return Name
}

⑥ 전달할 파라미터 정의

  • 또한 네트워크 플러그인의 확장에는 prometheusHandle이 있는데, 이는 지표를 얻기 위해 prometheus-server를 작동시키는 작업입니다. 먼저 PrometheusHandle 구조를 정의해야 합니다.
type PrometheusHandle struct {
    
    
 deviceName string // 网络接口名称
 timeRange  time.Duration // 抓取的时间段
 ip         string // prometheus server的连接地址
 client     v1.API // 操作prometheus的客户端
}
  • 구조를 가지고 action과 Indicator를 쿼리해야 하는데, Indicator의 경우 여기서는 Node 네트워크 트래픽을 얻기 위한 계산 방법으로 node_network_receive_bytes_total을 사용합니다. 환경이 클러스터 외부에 배포되므로 노드 호스트 이름이 없으며 promQL을 통해 얻습니다.전체 문은 다음과 같습니다.
sum_over_time(node_network_receive_bytes_total{
    
    device="eth0"}[1s]) * on(instance) group_left(nodename) (node_uname_info{
    
    nodename="node01"})
整个 Prometheus 部分如下:

type PrometheusHandle struct {
    
    
 deviceName string
 timeRange  time.Duration
 ip         string
 client     v1.API
}

func NewProme(ip, deviceName string, timeRace time.Duration) *PrometheusHandle {
    
    
 client, err := api.NewClient(api.Config{
    
    Address: ip})
 if err != nil {
    
    
  klog.Fatalf("[NetworkTraffic] FatalError creating prometheus client: %s", err.Error())
 }
 return &PrometheusHandle{
    
    
  deviceName: deviceName,
  ip:         ip,
  timeRange:  timeRace,
  client:     v1.NewAPI(client),
 }
}

func (p *PrometheusHandle) GetGauge(node string) (*model.Sample, error) {
    
    
 value, err := p.query(fmt.Sprintf(nodeMeasureQueryTemplate, node, p.deviceName, p.timeRange))
 fmt.Println(fmt.Sprintf(nodeMeasureQueryTemplate, p.deviceName, p.timeRange, node))
 if err != nil {
    
    
  return nil, fmt.Errorf("[NetworkTraffic] Error querying prometheus: %w", err)
 }

 nodeMeasure := value.(model.Vector)
 if len(nodeMeasure) != 1 {
    
    
  return nil, fmt.Errorf("[NetworkTraffic] Invalid response, expected 1 value, got %d", len(nodeMeasure))
 }
 return nodeMeasure[0], nil
}

func (p *PrometheusHandle) query(promQL string) (model.Value, error) {
    
    
    // 通过promQL查询并返回结果
 results, warnings, err := p.client.Query(context.Background(), promQL, time.Now())
 if len(warnings) > 0 {
    
    
  klog.Warningf("[NetworkTraffic Plugin] Warnings: %v\n", warnings)
 }

 return results, err
}

⑦ 스케줄러의 매개변수를 구성합니다.

  • prometheus의 주소, 네트워크 카드의 이름, 획득한 데이터의 크기를 지정해야 하므로 전체 구조는 다음과 같으며, 매개변수 구조는 Args 형식의 이름을 따라야 합니다.
type NetworkTrafficArgs struct {
    
    
 IP         string `json:"ip"`
 DeviceName string `json:"deviceName"`
 TimeRange  int    `json:"timeRange"`
}
  • 이러한 유형의 데이터를 KubeSchedulerConfiguration에서 파싱할 수 있는 구조로 만들기 위해서는 APIServer를 확장할 때 해당 리소스 유형을 확장해야 하는 한 단계가 더 필요합니다.
    • 하나는 이전 버전에서 제공되는 framework.DecodeInto 함수가 이 작업을 수행할 수 있다는 것입니다.
func New(plArgs *runtime.Unknown, handle framework.FrameworkHandle) (framework.Plugin, error) {
    
    
 args := Args{
    
    }
 if err := framework.DecodeInto(plArgs, &args); err != nil {
    
    
  return nil, err
 }
 ...
}
    • 또 다른 방법은 NodeLabel에서와 같이 해당 딥 카피 방법을 구현하는 것입니다.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// NodeLabelArgs holds arguments used to configure the NodeLabel plugin.
type NodeLabelArgs struct {
    
    
 metav1.TypeMeta

 // PresentLabels should be present for the node to be considered a fit for hosting the pod
 PresentLabels []string
 // AbsentLabels should be absent for the node to be considered a fit for hosting the pod
 AbsentLabels []string
 // Nodes that have labels in the list will get a higher score.
 PresentLabelsPreference []string
 // Nodes that don't have labels in the list will get a higher score.
 AbsentLabelsPreference []string
}
  • 마지막으로 레지스터에 등록합니다. 전체 동작은 APIServer를 확장하는 것과 유사합니다.
// addKnownTypes registers known types to the given scheme
func addKnownTypes(scheme *runtime.Scheme) error {
    
    
 scheme.AddKnownTypes(SchemeGroupVersion,
  &KubeSchedulerConfiguration{
    
    },
  &Policy{
    
    },
  &InterPodAffinityArgs{
    
    },
  &NodeLabelArgs{
    
    },
  &NodeResourcesFitArgs{
    
    },
  &PodTopologySpreadArgs{
    
    },
  &RequestedToCapacityRatioArgs{
    
    },
  &ServiceAffinityArgs{
    
    },
  &VolumeBindingArgs{
    
    },
  &NodeResourcesLeastAllocatedArgs{
    
    },
  &NodeResourcesMostAllocatedArgs{
    
    },
 )
 scheme.AddKnownTypes(schema.GroupVersion{
    
    Group: "", Version: runtime.APIVersionInternal}, &Policy{
    
    })
 return nil
}
  • 딥 카피 기능 및 기타 파일을 생성하려면 kubernetes 코드 베이스의 kubernetes/hack/update-codegen.sh 스크립트를 사용할 수 있으며 편의를 위해 여기서는 framework.DecodeInto 메서드를 사용합니다.

⑧ 프로젝트 전개

  • 스케줄러의 프로필을 준비하면 커스텀 매개변수가 KubeSchedulerConfiguration의 리소스 유형으로 인식될 수 있음을 알 수 있습니다.
apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: /mnt/d/src/go_work/customScheduler/scheduler.conf
profiles:
- schedulerName: custom-scheduler
  plugins:
    score:
      enabled:
      - name: "NetworkTraffic"
      disabled:
      - name: "*"
  pluginConfig:
    - name: "NetworkTraffic"
      args:
        ip: "http://10.0.0.4:9090"
        deviceName: "eth0"
        timeRange: 60
  • 클러스터 내부에 배포해야 하는 경우 미러 이미지로 패키징할 수 있습니다.
FROM golang:alpine AS builder
MAINTAINER cylon
WORKDIR /scheduler
COPY ./ /scheduler
ENV GOPROXY https://goproxy.cn,direct
RUN \
    sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories && \
    apk add upx  && \
    GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-s -w" -o scheduler main.go && \
    upx -1 scheduler && \
    chmod +x scheduler

FROM alpine AS runner
WORKDIR /go/scheduler
COPY --from=builder /scheduler/scheduler .
COPY --from=builder /scheduler/scheduler.yaml /etc/
VOLUME ["./scheduler"]
  • 클러스터 내부 배포에 필요한 리소스 목록:
apiVersion: v1
kind: ServiceAccount
metadata:
  name: scheduler-sa
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: scheduler
subjects:
  - kind: ServiceAccount
    name: scheduler-sa
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: system:kube-scheduler
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: custom-scheduler
  namespace: kube-system
  labels:
    component: custom-scheduler
spec:
  selector:
    matchLabels:
      component: custom-scheduler
  template:
    metadata:
      labels:
        component: custom-scheduler
    spec:
      serviceAccountName: scheduler-sa
      priorityClassName: system-cluster-critical
      containers:
        - name: scheduler
          image: cylonchau/custom-scheduler:v0.0.1
          imagePullPolicy: IfNotPresent
          command:
            - ./scheduler
            - --config=/etc/scheduler.yaml
            - --v=3
          livenessProbe:
            httpGet:
              path: /healthz
              port: 10251
            initialDelaySeconds: 15
          readinessProbe:
            httpGet:
              path: /healthz
              port: 10251
  • 인증 파일로 kubeconfig가 필요하도록 단순 바이너리 모드에서 시작되는 사용자 지정 스케줄러를 시작합니다.
$ ./main --logtostderr=true \
 --address=127.0.0.1 \
 --v=3 \
 --config=`pwd`/scheduler.yaml \
 --kubeconfig=`pwd`/scheduler.conf
  • 시작 후 검증의 편의를 위해 원래 kube-scheduler 서비스가 닫힙니다. 원래 kube-scheduler 서비스는 HA에서 마스터로 사용되었기 때문에 사용자 지정 스케줄러가 Pod 보류를 유발하는 데 사용되지 않습니다.

⑨ 검증 결과

  • 사용할 스케줄러의 이름을 지정하여 배포해야 하는 포드를 준비합니다.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2 
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
      schedulerName: custom-scheduler
  • 여기서 실험 환경은 마스터가 node01보다 서비스가 더 많기 때문에 master와 node01이라는 두 개의 노드가 있는 kubernetes 클러스터입니다. 이 경우 스케줄링 결과는 항상 node01로 예약됩니다.
$ kubectl get pods -o wide
NAME                                READY   STATUS    RESTARTS   AGE   IP             NODE     NOMINATED NODE   READINESS GATES
nginx-deployment-69f76b454c-lpwbl   1/1     Running   0          43s   192.168.0.17   node01   <none>           <none>
nginx-deployment-69f76b454c-vsb7k   1/1     Running   0          43s   192.168.0.16   node01   <none>           <none>
  • 스케줄러의 로그는 다음과 같습니다.
I0808 01:56:31.098189   27131 networktraffic.go:83] [NetworkTraffic] node 'node01' bandwidth: %!s(int64=12541068340)
I0808 01:56:31.098461   27131 networktraffic.go:70] [NetworkTraffic] Nodes final score: [{
    
    master-machine 0} {
    
    node01 12541068340}]
I0808 01:56:31.098651   27131 networktraffic.go:70] [NetworkTraffic] Nodes final score: [{
    
    master-machine 0} {
    
    node01 71}]
I0808 01:56:31.098911   27131 networktraffic.go:73] [NetworkTraffic] Nodes final score: [{
    
    master-machine 0} {
    
    node01 71}]
I0808 01:56:31.099275   27131 default_binder.go:51] Attempting to bind default/nginx-deployment-69f76b454c-vsb7k to node01
I0808 01:56:31.101414   27131 eventhandlers.go:225] add event for scheduled pod default/nginx-deployment-69f76b454c-lpwbl
I0808 01:56:31.101414   27131 eventhandlers.go:205] delete event for unscheduled pod default/nginx-deployment-69f76b454c-lpwbl
I0808 01:56:31.103604   27131 scheduler.go:609] "Successfully bound pod to node" pod="default/nginx-deployment-69f76b454c-lpwbl" node="no
de01" evaluatedNodes=2 feasibleNodes=2
I0808 01:56:31.104540   27131 scheduler.go:609] "Successfully bound pod to node" pod="default/nginx-deployment-69f76b454c-vsb7k" node="no
de01" evaluatedNodes=2 feasibleNodes=2

추천

출처blog.csdn.net/Forever_wj/article/details/131287697