クラウド ネイティブの Prometheus を使用して Kubernetes スケジューラを拡張する方法の詳細な分析

1. Kubernetes スケジューリング構成

① スケジューラの設定

  • 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
  • kubernetes は複数のスケジューラを提供しているため、設定ファイルも当然複数の設定ファイルをサポートしています。プロファイルもリスト形式です。複数の設定リストを指定するだけで済みます。以下は複数の設定ファイルの例です。拡張子が複数ある場合は、ポイント 、およびスケジューラごとに複数の拡張ポイントを構成することもできます。
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: 汚染と耐性の機能を実現します。拡張ポイント: フィルター、preScore、スコア。
    • NodeName: スケジューリング戦略の最も単純なスケジューリング メソッド NodeName を実装します。拡張ポイント: フィルター。
    • NodePorts: スケジューリングにより、ノード ポートが占有されているかどうかがチェックされます。拡張ポイント: preFilter、filter。
    • NodeAffinity: ノード アフィニティ関連の機能、拡張ポイント: フィルター、スコアを提供します。
    • PodTopologySpread: Pod トポロジ ドメインの機能を実現します。拡張ポイント: preFilter、filter、preScore、score。
    • NodeResourcesFit: このプラグインは、次の 3 つの戦略のいずれかを使用して、Pod によって要求されたすべてのリソースがノードにあるかどうかを確認します。 LeastAllocated (デフォルト) MostAllocated および RequestedToCapacityRatio、拡張ポイント: preFilter、フィルター、スコア。
    • VolumeBinding: ノードが要求されたボリュームを持っているか、またはバインドできるかどうかを確認します。拡張ポイント: preFilter、filter、reserve、preBind、score。
    • VolumeRestrictions: ノードにインストールされているボリュームがボリューム プロバイダー固有の制限、拡張ポイント: フィルターを満たしているかどうかを確認します。
    • VolumeZone: 要求されたボリュームがゾーン要件を満たしているかどうかを確認します。拡張ポイント: フィルター。
    • InterPodAffinity: Pod 間のアフィニティとアンチアフィニティの機能を実現します。拡張ポイント: preFilter、filter、preScore、score。
    • PrioritySort: デフォルトの優先順位に基づいて並べ替えを提供します。拡張ポイント: queueSort。

2. kube-schedulerを拡張するにはどうすればよいですか?

  • 初めてスケジューラを書こうと思うと、kube-scheduler を拡張するのは非常に難しいことだと考えるのが一般的ですが、実は Kubernetes の公式ではこれらのことをすでに考えていました。このため、kubernetes ではフレームワークの概念が導入されました。バージョン 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 とスケジューラーの間の呼び出しに使用されます。構造から、リスター、インフォーマーなどが含まれていることがわかります。このパラメーターも実装する必要があります。
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
}
  • 最後に、New 関数を実装することで、この拡張機能を登録するメソッドを提供します。この関数は、main.go のツリー プラグインの外としてスケジューラーに挿入できます。
// 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 は通常のサービスとして実行されます。オーダーには大量のデータのダウンロードが必要ですが、現時点ではハードウェア リソースが占有されることはほとんどないためです。ポッドがこのノードにスケジュールされている場合、双方のビジネスが影響を受ける可能性があります (フロントエンド エージェントは、ノードの接続数が少なく、多数の接続がスケジュールされると考え、注文スクリプトはネットワーク帯域幅の占有により効率が低下します)。

①環境構成

  • Kubernetes クラスターには少なくとも 2 つのノードが必要です。
  • 提供されている kubernetes クラスターはすべて、prometheus node_exporter をインストールする必要があります。prometheus node_exporter はクラスターの内部または外部に配置できますが、ここではクラスター外部のものが使用されます。
  • promQLclient_golangについて理解していること
  • この例は大きく次の手順に分かれています。
    • プラグイン API を定義します。プラグインの名前は NetworkTraffic です。
    • 拡張ポイントを定義します。ここではスコア拡張ポイントが使用され、スコアリング アルゴリズムが定義されます。
    • スコアを取得する方法を定義します (プロメテウス インジケーターから対応するデータを取得します)。
    • カスタム スケジューラへのパラメータ入力を定義します。
    • プロジェクトをクラスターにデプロイします (クラスター内デプロイメントおよびクラスター外デプロイメント)。
    • 結果検証の例。
  • この例では、組み込みプラグインnodeaffinityに従ってコードの記述を完了します。このプラグインを選択した理由は、このプラグインが比較的シンプルであり、必要な目的と基本的に同じであるためです。実際には、他のプラグインも-ins も同じ効果があります。

②エラー処理

  • プロジェクトの初期化、go mod tinyなどの操作を行うと、次のようなエラーが多数発生します。
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 拡張ポイントにはこの 1 つのメソッド以上の実装が必要であることがわかります。
// 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
}
  • kubernetes では、最大ノード数が 5000 までサポートされていますが、最大スコアを取得する場合、ループによって多くのパフォーマンスが消費されることになりませんか? 実際、心配する必要はありません。スケジューラーは、デプロイメント・サイクルの数を決定するパラメータpercentageOfNodesToScoreを提供します。

⑤ プラグイン名の設定

  • 登録時にプラグインを使用するには、名前を設定する必要もあります。
// Name returns name of the plugin. It is used in logs, etc.
func (n *NetworkTraffic) Name() string {
    
    
 return Name
}

⑥ 渡すパラメータを定義する

  • ネットワーク プラグインの拡張機能には prometheusHandle もあります。これは、prometheus サーバーを操作してインジケーターを取得するアクションです。まず、PrometheusHandle 構造を定義する必要があります。
type PrometheusHandle struct {
    
    
 deviceName string // 网络接口名称
 timeRange  time.Duration // 抓取的时间段
 ip         string // prometheus server的连接地址
 client     v1.API // 操作prometheus的客户端
}
  • この構造では、アクションとインジケーターをクエリする必要がありますが、インジケーターについては、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
}

⑦ スケジューラのパラメータを設定する

  • プロメテウスのアドレス、ネットワークカード名、取得するデータのサイズを指定する必要があるため、全体の構造は以下のようになり、さらにパラメータの構造もArgs形式で名前に従う必要があります。
type NetworkTrafficArgs struct {
    
    
 IP         string `json:"ip"`
 DeviceName string `json:"deviceName"`
 TimeRange  int    `json:"timeRange"`
}
  • このタイプのデータを KubeSchedulerConfiguration で解析できる構造にするためには、APIServer を拡張するときに対応するリソース タイプを拡張するというもう 1 つの手順が必要です。ここで、kubernetes は、KubeSchedulerConfiguration のリソース タイプを拡張するための 2 つのメソッドを提供します。
    • 1 つは、古いバージョンで提供されていた 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
 }
 ...
}
    • もう 1 つの方法は、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
}
  • 最後に、それを register に登録します。全体の動作は 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 のマスターとして使用されているため、カスタム スケジューラーがポッドの保留を引き起こすために使用されることはありません。

⑨検証結果

  • 使用するスケジューラーの名前を指定して、デプロイする必要があるポッドを準備します。
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
  • ここでの実験環境は、マスターとノード 01 の 2 つのノードを持つ kubernetes クラスターです。マスターにはノード 01 より多くのサービスがあるためです。この場合、何があっても、スケジューリング結果は常にノード 01 にスケジュールされます。
$ 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