云原生之深入解析如何使用Prometheus扩展Kubernetes调度器

news/2025/3/19 14:36:26/

一、kubernetes 调度配置

① Scheduler Configuration

  • 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: PodTopologySpreadenabled:- name: MyCustomPluginAweight: 2- name: MyCustomPluginBweight: 1
  • 既然 kubernetes 提供了多调度器,那么对于配置文件来说自然支持多个配置文件,profile 也是列表形式,只要指定多个配置列表即可,如下是多配置文件示例,其中,如果存在多个扩展点,也可以为每个调度器配置多个扩展点:
apiVersion: kubescheduler.config.k8s.io/v1beta2
kind: KubeSchedulerConfiguration
profiles:- schedulerName: default-schedulerplugins:preScore:disabled:- name: '*'score:disabled:- name: '*'- schedulerName: no-scoring-schedulerplugins:preScore:disabled:- name: '*'score:disabled:- name: '*'

③ scheduler 调度插件

  • kube-scheduler 默认提供了很多插件作为调度方法,默认不配置的情况下会启用这些插件,如:
    • ImageLocality:调度将更偏向于 Node 存在容器镜像的节点,扩展点:score;
    • TaintToleration:实现污点与容忍度功能,扩展点:filter, preScore, score;
    • NodeName:实现调度策略中最简单的调度方法 NodeName 的实现,扩展点:filter;
    • NodePorts:调度将检查 Node 端口是否已占用,扩展点:preFilter, filter;
    • NodeAffinity:提供节点亲和性相关功能,扩展点:filter, score;
    • PodTopologySpread:实现 Pod 拓扑域的功能,扩展点:preFilter, filter, preScore, score;
    • NodeResourcesFit:该插件将检查节点是否拥有 Pod 请求的所有资源,使用以下三种策略之一:LeastAllocated (默认)MostAllocated 和 RequestedToCapacityRatio,扩展点:preFilter, filter, score;
    • VolumeBinding:检查节点是否有或是否可以绑定请求的卷,扩展点:preFilter, filter, reserve, preBind, score;
    • VolumeRestrictions:检查安装在节点中的卷是否满足特定于卷提供程序的限制,扩展点:filter;
    • VolumeZone:检查请求的卷是否满足它们可能具有的任何区域要求,扩展点:filter;
    • InterPodAffinity:实现 Pod 间的亲和性与反亲和性的功能,扩展点:preFilter, filter, preScore, score;
    • PrioritySort:提供基于默认优先级的排序,扩展点:queueSort。

二、如何扩展 kube-scheduler?

  • 当在第一次考虑编写调度程序时,通常会认为扩展 kube-scheduler 是一件非常困难的事情,其实这些事情 kubernetes 官方早就想到了,kubernetes 为此在 1.15 版本引入了 framework 的概念,framework 旨在使 scheduler 更具有扩展性。
  • framework 通过重新定义各扩展点,将其作为 plugins 来使用,并且支持用户注册 out of tree 的扩展,使其可以被注册到 kube-scheduler 中。

① 定义入口

  • scheduler 允许进行自定义,但是对于只需要引用对应的 NewSchedulerCommand,并且实现 plugins 的逻辑即可:
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 允许注入 out of tree plugins,也就是注入外部的自定义 plugins,这种情况下就无需通过修改源码方式去定义一个调度器,而仅仅通过自行实现即可完成一个自定义调度器:
// 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 , 通过观察他的结构可以发现,实现插件就是实现对应的扩展点抽象 interface 即可:

在这里插入图片描述

  • 定义插件结构体:其中 framework.FrameworkHandle 是提供了 Kubernetes API 与 scheduler 之间调用使用的,通过结构可以看出包含 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.Affinityvar 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 函数来提供注册这个扩展的方法,这个 New 函数可以在 main.go 中将其作为 out of tree plugins 注入到 scheduler 中即可:
// New initializes a new plugin and returns it.
func New(_ runtime.Object, h framework.FrameworkHandle) (framework.Plugin, error) {return &NodeAffinity{handle: h}, nil
}

三、基于网络流量的调度

  • 通过上面了解了如何扩展 scheduler 插件,下面将完成一个基于流量的调度的示例,通常情况下,网络一个 Node 在一段时间内使用的网络流量也是作为生产环境中很常见的情况。
  • 例如在配置均衡的多个主机中,主机 A 作为业务拉单脚本运行,主机 B 作为寻常服务运行,因为拉单需要下载大量数据,而硬件资源占用的却很少,此时,如果有 Pod 被调度到该节点上,那么可能双方业务都会收到影响(前端代理觉得这个节点连接数少会被大量调度,而拉单脚本因为网络带宽的占用降低了效能)。

① 环境配置

  • 一个 kubernetes 集群,至少保证有两个节点。
  • 提供的 kubernetes 集群都需要安装 prometheus node_exporter,可以是集群内部的,也可以是集群外部的,这里使用的是集群外部的。
  • 对 promQL 与 client_golang 有所了解。
  • 示例大致分为以下几个步骤:
    • 定义插件 API,插件命名为 NetworkTraffic;
    • 定义扩展点,这里使用了 Score 扩展点,并且定义评分的算法;
    • 定义分数获取途径(从 prometheus 指标中拿到对应的数据);
    • 定义对自定义调度器的参数传入;
    • 将项目部署到集群中(集群内部署与集群外部署);
    • 示例的结果验证。
  • 示例将仿照内置插件 nodeaffinity 完成代码编写,为什么选择这个插件,只是因为这个插件相对比较简单,并且与需要的目的基本相同,其实其它插件也是同样的效果。

② 错误处理

  • 在初始化项目时,go mod tidy 等操作时,会遇到大量下面的错误:
go: github.com/GoogleCloudPlatform/spark-on-k8s-operator@v0.0.0-20210307184338-1947244ce5f4 requiresk8s.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 pipefailVERSION=${1#"v"}
if [ -z "$VERSION" ]; thenecho "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[@]}"; doV=$(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

  • 通过上面内容描述了解到了定义插件只需要实现对应的扩展点抽象 interface ,那么可以初始化项目目录 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 中需要实现具体的选择 node 的算法,实现的算法公式将为最高分当前带宽最高最高带宽,这样就保证带宽占用越大的机器,分数越低。例如,最高带宽为 200000,而当前 Node 带宽为 140000,那么这个 Node 分数为:
// 如果返回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 int64for _, 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 中最大的 node 数支持 5000 个,岂不是在获取最大分数时循环就占用了大量的性能,其实不必担心。scheduler 提供了一个参数 percentageOfNodesToScore,这个参数决定了这个部署循环的数量。

⑤ 配置插件名称

  • 为了使插件注册时候使用,还需要为其配置一个名称:
// 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的客户端
}
  • 有了结构就需要查询的动作和指标,对于指标来说,这里使用了 node_network_receive_bytes_total 作为获取 Node 的网络流量的计算方式。由于环境是部署在集群之外的,没有 node 的主机名,通过 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 stringtimeRange  time.Durationip         stringclient     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 时扩展对应的资源类型,在这里 kubernetes 中提供两种方法来扩展 KubeSchedulerConfiguration 的资源类型:
    • 一种是旧版中提供了 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 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
}
  • 最后将其注册到 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 的方式。

⑧ 项目部署

  • 准备 scheduler 的 profile,可以看到,自定义的参数就可以被识别为 KubeSchedulerConfiguration 的资源类型:
apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration
clientConnection:kubeconfig: /mnt/d/src/go_work/customScheduler/scheduler.conf
profiles:
- schedulerName: custom-schedulerplugins: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 schedulerFROM 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-sanamespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:name: scheduler
subjects:- kind: ServiceAccountname: scheduler-sanamespace: kube-system
roleRef:kind: ClusterRolename: system:kube-schedulerapiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:name: custom-schedulernamespace: kube-systemlabels:component: custom-scheduler
spec:selector:matchLabels:component: custom-schedulertemplate:metadata:labels:component: custom-schedulerspec:serviceAccountName: scheduler-sapriorityClassName: system-cluster-criticalcontainers:- name: schedulerimage: cylonchau/custom-scheduler:v0.0.1imagePullPolicy: IfNotPresentcommand:- ./scheduler- --config=/etc/scheduler.yaml- --v=3livenessProbe:httpGet:path: /healthzport: 10251initialDelaySeconds: 15readinessProbe:httpGet:path: /healthzport: 10251
  • 启动自定义 scheduler,这里通过简单的二进制方式启动,所以需要一个 kubeconfig 做认证文件:
$ ./main --logtostderr=true \--address=127.0.0.1 \--v=3 \--config=`pwd`/scheduler.yaml \--kubeconfig=`pwd`/scheduler.conf
  • 启动后为了验证方便性,关闭了原来的 kube-scheduler 服务,因为原来的 kube-scheduler 已经作为 HA 中的 master,所以不会使用自定义的 scheduler 导致 pod pending。

⑨ 验证结果

  • 准备一个需要部署的 Pod,指定使用的调度器名称:
apiVersion: apps/v1
kind: Deployment
metadata:name: nginx-deployment
spec:selector:matchLabels:app: nginxreplicas: 2 template:metadata:labels:app: nginxspec:containers:- name: nginximage: nginx:1.14.2ports:- containerPort: 80schedulerName: custom-scheduler
  • 这里实验环境为 2 个节点的 kubernetes 集群,master 与 node01,因为 master 的服务比 node01 要多,这种情况下不管怎样,调度结果永远会被调度到 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

http://www.ppmy.cn/news/468143.html

相关文章

RK3588平台开发系列讲解(MIPI篇)MIPI DSI2驱动代码说明

平台内核版本安卓版本RK3588Linux 5.10Android12🚀返回专栏总目录 文章目录 一、uboot二、kernel三、设备树沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇主要对RK3588 MIPI DSI2 驱动代码说明。 一、uboot 驱动位置drivers/video/drm/d

GMII(Gigabit MII)

1、信号定义(Source Synchronous Clocking) 信号名称描述方向GTX_CLK125 MHz for 1000M,10M/100M时unusedMAC → PHYTX_CLK25MHz for 100M MII Compatible 2.5MHz for 10M MII Compatible PHY → MACTX_ER发送数据错误MAC → PHYTX_EN发送使能MAC → PHYTX_[7:0]发送数据8bi…

佳博标签打印机GP-3120TUB linux驱动

先说下我的系统:ubuntu2004lts 打印机型号&#xff1a;佳博GP-3120TUB 从tsc网站下载驱动: http://www.tsc-china.com/display/136739.html 备用下载地址(和上面一样): 链接: https://pan.baidu.com/s/1RxkE6BxUDQUL_pZd2QEsjA 密码: 5a4e 未经验证&#xff1a; https://www.…

13、RH850 F1 选项字节和看门狗

前言: 选项字OPTION配置是RH850的一项重点&#xff0c;用户手册对选项字的配置介绍很少&#xff0c;这篇文章将主要针对选项字和看门狗的配置进行讲解。 一、选项字特性 闪存的选项字节是一个扩展区域&#xff0c;并保存用户为各种目的指定的数据。由选项字节指定的外围模块等的…

华为HCIP第一天---------RSTP

一、介绍 1、以太网交换网络中为了进行链路备份&#xff0c;提高网络可靠性&#xff0c;通常会使用冗余链路&#xff0c;但是这也带来了网络环路的问题。网络环路会引发广播风暴和MAC地址表震荡等问题&#xff0c;导致用户通信质量差&#xff0c;甚至通信中断。为了解决交换网…

RK3588-MIPI屏幕调试笔记:RK3588-MIPI-DSI

一. 简介 本文是基于RK3588平台&#xff0c;MIPI屏调试总结。 二. MIPI屏幕调试 2.1 调试总览&#xff0c;调试步骤分析 步骤 ① 先将背光点亮步骤 ② 根据屏幕的规格书配置dsi1_panel节点步骤 ③ 打开对应的dsi节点&#xff0c;开机logo步骤 ④ 编译烧写&#xff0c;调试屏…

Axure教程—省市区三级联动(中继器)

本文将教大家如何用AXURE中中继器制作省市区三级联动 一、效果 预览地址&#xff1a;https://t6gmmh.axshare.com 二、功能 选择省份、出现相应的市区&#xff0c;选择市区出现相应的区或县 省市区三级联动效果 三、制作 1、省 拖入一个矩形&#xff0c;命名为省&#xff0c…

GRE over IPsec VPN配置

GRE over IPsec VPN配置 【实验目的】 理解GRE Tunnel的概念。理解GRE over IPsec VPN的概念。掌握GRE Tunnel的配置。掌握GRE over IPsec VPN的配置。验证配置。 【实验拓扑】 实验拓扑如下图所示。 实验拓扑 设备参数表如下表所示。 设备参数表 设备 接口 IP地址 子网…