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과 같이 다른 구성 요소의 구성 파일과 유사합니다.
쿠버네티스는 다중 스케줄러를 제공하기 때문에 환경설정 파일에 대해 다중 환경설정 파일을 자연스럽게 지원한다 프로파일도 리스트 형식이다 다중 환경설정 목록만 지정하면 된다 다음은 다중 환경설정 파일의 예이다 확장자가 여러 개인 경우 points 및 여러 확장 지점을 각 스케줄러에 대해 구성할 수도 있습니다.
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 {
return0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))}
node := nodeInfo.Node()if node == nil {
return0, 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 {
return0, 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를 설치해야 하며 여기서는 클러스터 외부에 있는 것을 사용합니다.
이 문제는 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'
))forMODin"${MODS[@]}";doV=$(
go moddownload-json "${MOD}@kubernetes-${VERSION}"|
sed -n 's|.*"Version":"\(.*\)".*|\1|p'
)
go modedit"-replace=${MOD}=${MOD}@${V}"
done
go get "k8s.io/kubernetes@v${VERSION}"
③ 플러그인 API 정의
위의 내용 설명을 통해 우리는 플러그인 정의가 해당 확장 지점 추상 인터페이스를 구현하기만 하면 프로젝트 디렉토리 pkg/networtraffic/networktraffice.go를 초기화할 수 있음을 이해합니다.
플러그인 이름과 변수를 정의합니다.
constName="NetworkTraffic"
var _ = framework.ScorePlugin(&NetworkTraffic{
})
다음으로 결과를 정규화해야 합니다. 소스 코드에서 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.ScoreExtensionsfunc(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 {
returnName}
⑥ 전달할 파라미터 정의
또한 네트워크 플러그인의 확장에는 prometheusHandle이 있는데, 이는 지표를 얻기 위해 prometheus-server를 작동시키는 작업입니다. 먼저 PrometheusHandle 구조를 정의해야 합니다.
구조를 가지고 action과 Indicator를 쿼리해야 하는데, Indicator의 경우 여기서는 Node 네트워크 트래픽을 얻기 위한 계산 방법으로 node_network_receive_bytes_total을 사용합니다. 환경이 클러스터 외부에 배포되므로 노드 호스트 이름이 없으며 promQL을 통해 얻습니다.전체 문은 다음과 같습니다.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object// NodeLabelArgs holds arguments used to configure the NodeLabel plugin.typeNodeLabelArgsstruct{
metav1.TypeMeta// PresentLabels should be present for the node to be considered a fit for hosting the podPresentLabels[]string
// AbsentLabels should be absent for the node to be considered a fit for hosting the podAbsentLabels[]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의 리소스 유형으로 인식될 수 있음을 알 수 있습니다.