Análisis en profundidad de la nube nativa sobre cómo usar Prometheus para extender el programador de Kubernetes

1. configuración de programación de kubernetes

① Configuración del programador

  • kube-scheduler proporciona recursos para archivos de configuración, como un archivo de configuración para kube-scheduler, y el archivo se especifica mediante --config= al inicio. La KubeSchedulerConfiguration que se usa actualmente en cada versión de kubernetes es:
    • Las versiones anteriores a la 1.21 usan v1beta1;
    • La versión 1.22 usa v1beta2, pero conserva v1beta1;
    • Las versiones 1.23, 1.24 y 1.25 usan v1beta3, pero conservan v1beta2 y eliminan v1beta1;
  • Como se muestra a continuación, es un ejemplo simple de kubeSchedulerConfiguration, donde kubeconfig tiene la misma función que el parámetro de inicio --kubeconfig, y kubeSchedulerConfiguration es similar a los archivos de configuración de otros componentes, como kubeletConfiguration es un archivo de configuración que se inicia como un servicio:
apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: /etc/srv/kubernetes/kube-scheduler/kubeconfig
  • –kubeconfig y --config no se pueden especificar al mismo tiempo Si se especifica --config, otros parámetros fallarán naturalmente.

② kubeSchedulerConfiguration 使用

  • A través del archivo de configuración, los usuarios pueden personalizar varios programadores y configurar los puntos de extensión de cada etapa, y el complemento proporciona un comportamiento de programación en todo el contexto de programación a través de estos puntos de extensión.
  • La configuración que se muestra a continuación es un ejemplo para configurar el punto de extensión (si name="*", todos los complementos correspondientes al punto de extensión estarán deshabilitados/habilitados):
apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration
profiles:
  - plugins:
      score:
        disabled:
        - name: PodTopologySpread
        enabled:
        - name: MyCustomPluginA
          weight: 2
        - name: MyCustomPluginB
          weight: 1
  • Dado que kubernetes proporciona varios programadores, naturalmente admite varios archivos de configuración para los archivos de configuración. El perfil también tiene la forma de una lista. Solo necesita especificar varias listas de configuración. El siguiente es un ejemplo de varios archivos de configuración. Si hay varias extensiones También se pueden configurar varios puntos de extensión para cada programador:
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: '*'

③ complemento de programación del programador

  • kube-scheduler proporciona muchos complementos como métodos de programación de forma predeterminada, y estos complementos se habilitarán si no están configurados de forma predeterminada, como:
    • ImageLocality: la programación estará más sesgada hacia los nodos con imágenes de contenedores, punto de extensión: puntuación;
    • TaintToleration: realice la función de corrupción y tolerancia, puntos de extensión: filtro, preScore, puntuación;
    • NodeName: implemente el método de programación más simple NodeName en la estrategia de programación, punto de extensión: filtro;
    • NodePorts: la programación verificará si el puerto del nodo está ocupado, puntos de extensión: prefiltro, filtro;
    • NodeAffinity: proporciona funciones relacionadas con la afinidad de nodos, puntos de extensión: filtro, puntuación;
    • PodTopologySpread: realice la función del dominio de topología Pod, puntos de extensión: preFilter, filter, preScore, score;
    • NodeResourcesFit: este complemento verificará si el nodo tiene todos los recursos solicitados por el Pod, utilizando una de las siguientes tres estrategias: LeastAllocated (predeterminado) MostAllocated y RequestedToCapacityRatio, puntos de extensión: preFilter, filter, score;
    • VolumeBinding: Comprobar si el nodo tiene o puede enlazar el volumen solicitado, puntos de extensión: preFilter, filter, reserve, preBind, score;
    • Restricciones de volumen: compruebe si el volumen instalado en el nodo cumple con las restricciones específicas del proveedor de volumen, punto de extensión: filtro;
    • VolumeZone: comprueba si los volúmenes solicitados cumplen con los requisitos de zona que puedan tener, punto de extensión: filtro;
    • InterPodAffinity: realiza la función de afinidad y antiafinidad entre Pods, puntos de extensión: preFilter, filter, preScore, score;
    • PrioritySort: proporciona una clasificación basada en la prioridad predeterminada, punto de extensión: queueSort.

2. ¿Cómo extender kube-scheduler?

  • Cuando piensas en escribir un programador por primera vez, generalmente piensas que extender kube-scheduler es algo muy difícil. De hecho, el kubernetes oficial ya ha pensado en estas cosas. Por esta razón, kubernetes introdujo el concepto de marco en versión 1.15 El marco tiene como objetivo hacer que el programador sea más extensible.
  • El marco lo usa como complemento al redefinir cada punto de extensión y permite a los usuarios registrarse fuera del árbol de extensiones para que puedan registrarse en kube-scheduler.

① Definir entrada

  • El programador permite la personalización, pero solo necesita hacer referencia al NewSchedulerCommand correspondiente e implementar la lógica de los complementos:
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)
    }
}
  • El NewSchedulerCommand permite la inyección de complementos fuera del árbol, es decir, la inyección de complementos personalizados externos. En este caso, no es necesario modificar el código fuente para definir un programador, sino completar un programador personalizado solo implementándolo usted mismo:
// 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)
 }
}

② Implementación de complementos

  • La implementación del complemento solo necesita implementar la interfaz del punto de extensión correspondiente. El NodeAffinity del complemento incorporado se puede encontrar observando su estructura. La implementación del complemento es implementar la interfaz abstracta del punto de extensión correspondiente:

inserte la descripción de la imagen aquí

  • Defina la estructura del complemento: framework.FrameworkHandle se usa para llamar entre la API de Kubernetes y el planificador. Se puede ver en la estructura que incluye lister, informador, etc. Este parámetro también debe implementarse:
type NodeAffinity struct {
    
    
 handle framework.FrameworkHandle
}
  • Implementar el punto de extensión correspondiente:
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
}
  • Finalmente, proporcione un método para registrar esta extensión mediante la implementación de una nueva función, que se puede inyectar en el programador como complementos fuera del árbol en 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. Programación basada en el tráfico de red

  • A través de la comprensión anterior de cómo extender el complemento del programador, a continuación se completará un ejemplo de programación basada en el tráfico. Por lo general, el tráfico de red utilizado por un Nodo en la red durante un período de tiempo también es una situación muy común en el entorno de producción.
  • Por ejemplo, entre varios hosts con una configuración equilibrada, el host A se ejecuta como un script de pedido de servicio y el host B se ejecuta como un servicio normal. Debido a que el pedido requiere descargar una gran cantidad de datos, pero los recursos de hardware rara vez están ocupados, en este momento , si un pod está programado para este nodo, el negocio de ambas partes puede verse afectado (el agente front-end piensa que el nodo tiene una pequeña cantidad de conexiones y se programará en grandes cantidades, y el script de orden reducir la eficiencia debido a la ocupación del ancho de banda de la red).

① Configuración del entorno

  • Un clúster de kubernetes debe tener al menos dos nodos.
  • Todos los clústeres de kubernetes proporcionados deben instalar Prometheus node_exporter, que puede estar dentro o fuera del clúster, y aquí se usa el que está fuera del clúster.
  • Comprender promQL y client_golang .
  • El ejemplo se divide aproximadamente en los siguientes pasos:
    • Defina la API del complemento, y el complemento se llama NetworkTraffic;
    • Defina el punto de extensión, aquí se utiliza el punto de extensión Puntuación y se define el algoritmo de puntuación;
    • Definir la forma de obtener la puntuación (obtener los datos correspondientes del indicador Prometheus);
    • Defina la entrada de parámetros para el programador personalizado;
    • Implementar el proyecto en el clúster (implementación dentro del clúster e implementación fuera del clúster);
    • Ejemplo de verificación de resultados.
  • El ejemplo seguirá el complemento integrado nodeaffinity para completar la escritura del código. La razón para elegir este complemento es que este complemento es relativamente simple y tiene básicamente el mismo propósito que la necesidad. De hecho, otro complemento -ins tienen el mismo efecto.

② Manejo de errores

  • Al inicializar el proyecto, ir a mod tidy y otras operaciones, encontrará muchos de los siguientes errores:
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
  • Este problema se mencionó en el problema de Kubernetes n.° 79384. Después de un vistazo superficial, no explicó por qué ocurrió este problema. En la parte inferior, un jefe proporcionó una secuencia de comandos. Cuando el problema anterior no se pueda resolver, ejecute la secuencia de comandos directamente y será normal:
#!/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}"

③ Definir API de complemento

  • A través de la descripción del contenido anterior, entendemos que definir un complemento solo necesita implementar la interfaz abstracta del punto de extensión correspondiente, luego se puede inicializar el directorio del proyecto pkg/networtraffic/networktraffice.go.
  • Defina el nombre del complemento y las variables:
const Name = "NetworkTraffic"
var _ = framework.ScorePlugin(&NetworkTraffic{
    
    })
  • Defina la estructura del complemento:
type NetworkTraffic struct {
    
    
 // 这个作为后面获取node网络流量使用
 prometheus *PrometheusHandle
 // FrameworkHandle 提供插件可以使用的数据和一些工具
 // 它在插件初始化时传递给 plugin 工厂类
 // plugin 必须存储和使用这个handle来调用framework函数
 handle framework.FrameworkHandle
}

④ Definir puntos de extensión

  • Debido a que se selecciona el punto de extensión Score, se debe definir el método correspondiente para realizar la abstracción correspondiente:
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 // 这里直接返回就行
}
  • A continuación, los resultados deben normalizarse.Se puede ver en el código fuente que el punto de extensión Score necesita implementar más que solo este método único:
// 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
    }
})
  • Del código anterior, puede entender que para implementar Score, debe implementar ScoreExtensions y regresar directamente si no está implementado. De acuerdo con el ejemplo en nodeaffinity, se encuentra que este método solo devuelve el objeto de punto de extensión en sí mismo, y la normalización específica es la operación de puntuación real en 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
}
  • En el marco de programación, el método para realizar la operación también es 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
}
  • En NormalizeScore es necesario implementar un algoritmo específico para la selección de nodos, la fórmula del algoritmo implementado será la puntuación más alta, el ancho de banda actual más alto y el ancho de banda más alto, esto asegura que la máquina con una ocupación de mayor ancho de banda tenga una puntuación más baja. Por ejemplo, si el ancho de banda más alto es 200 000 y el ancho de banda actual del nodo es 140 000, la puntuación del nodo es:
// 如果返回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
}
  • En kubernetes el número máximo de nodos soporta 5000. ¿No significa que el looping consume mucho rendimiento a la hora de obtener la máxima puntuación?, de hecho no hay de qué preocuparse. El programador proporciona un parámetro percentOfNodesToScore, que determina la cantidad de ciclos de implementación.

⑤ Configure el nombre del complemento

  • Para que el complemento se use al registrarse, también debe configurarse con un nombre:
// Name returns name of the plugin. It is used in logs, etc.
func (n *NetworkTraffic) Name() string {
    
    
 return Name
}

⑥ Definir los parámetros a pasar en

  • También hay un prometheusHandle en la extensión del complemento de red, que es la acción de operar el servidor de prometheus para obtener los indicadores. Primero, debe definir una estructura PrometheusHandle:
type PrometheusHandle struct {
    
    
 deviceName string // 网络接口名称
 timeRange  time.Duration // 抓取的时间段
 ip         string // prometheus server的连接地址
 client     v1.API // 操作prometheus的客户端
}
  • Con la estructura, debe consultar acciones e indicadores. Para los indicadores, aquí se utiliza node_network_receive_bytes_total como método de cálculo para obtener el tráfico de red del nodo. Dado que el entorno se implementa fuera del clúster, no hay un nombre de host de nodo y se obtiene a través de promQL. La declaración completa es la siguiente:
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
}

⑦ Configurar los parámetros del programador

  • Debido a que es necesario especificar la dirección de Prometheus, el nombre de la tarjeta de red y el tamaño de los datos obtenidos, la estructura completa es la siguiente. Además, la estructura de parámetros debe seguir el nombre en el formato Args:
type NetworkTrafficArgs struct {
    
    
 IP         string `json:"ip"`
 DeviceName string `json:"deviceName"`
 TimeRange  int    `json:"timeRange"`
}
  • Para hacer de este tipo de datos una estructura que KubeSchedulerConfiguration pueda analizar, se requiere un paso más, que es expandir el tipo de recurso correspondiente al extender APIServer. Aquí, kubernetes proporciona dos métodos para extender el tipo de recurso de KubeSchedulerConfiguration:
    • Una es que la función framework.DecodeInto proporcionada en la versión anterior puede realizar esta operación:
func New(plArgs *runtime.Unknown, handle framework.FrameworkHandle) (framework.Plugin, error) {
    
    
 args := Args{
    
    }
 if err := framework.DecodeInto(plArgs, &args); err != nil {
    
    
  return nil, err
 }
 ...
}
    • Otra forma es implementar el método de copia profunda correspondiente, como en 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
}
  • Finalmente, regístrelo en el registro, todo el comportamiento es similar a extender 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
}
  • Para generar funciones de copia profunda y otros archivos, puede usar el script kubernetes/hack/update-codegen.sh en la base de código de kubernetes Para mayor comodidad, aquí se usa el método framework.DecodeInto.

⑧ Despliegue del proyecto

  • Prepare el perfil del programador, puede ver que los parámetros personalizados se pueden reconocer como el tipo de recurso de 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
  • Si necesita implementarse dentro del clúster, puede empaquetarse en una imagen espejo:
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"]
  • Lista de recursos necesarios para la implementación dentro del clúster:
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
  • Inicie el programador personalizado, que se inicia en un modo binario simple, por lo que se requiere un kubeconfig como archivo de autenticación:
$ ./main --logtostderr=true \
 --address=127.0.0.1 \
 --v=3 \
 --config=`pwd`/scheduler.yaml \
 --kubeconfig=`pwd`/scheduler.conf
  • Después del inicio, para facilitar la verificación, el servicio kube-scheduler original se cierra, porque el kube-scheduler original se usó como maestro en HA, por lo que el programador personalizado no se usará para causar que el pod esté pendiente.

⑨ Resultado de la verificación

  • Prepare un pod que debe implementarse, especificando el nombre del programador que se usará:
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
  • El entorno experimental aquí es un clúster de Kubernetes con dos nodos, maestro y nodo 01, porque el maestro tiene más servicios que el nodo 01. En este caso, pase lo que pase, el resultado de la programación siempre se programará para el nodo 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>
  • El registro del planificador es el siguiente:
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

Supongo que te gusta

Origin blog.csdn.net/Forever_wj/article/details/131287697
Recomendado
Clasificación