kube-scheduler 磁盘调度源码分析

7834a30ef8e9b686f153e9eaf901f54f.gif

作者 | leadersnowy

来源 | CSDN博客

kube-scheduler介绍

首先我们知道,kube-scheduler的根本工作任务是根据各种调度算法将Pod调度到最合适的工作节点上

一、整个调度流程分为两个阶段:

1、预选(Predicates):

输入是所有节点,输出是满足预选条件的节点。kube-scheduler根据预选策略过滤掉不满足策略的Nodes。例如,如果某节点的资源不足或者不满足预选策略的条件如“Node的label必须与Pod的Selector一致”时则无法通过预选。

2、优选(Priorities):

输入是预选阶段筛选出的节点,优选会根据优先策略为通过预选的Nodes进行打分排名,选择得分最高的Node。例如,资源越富裕、负载越小的Node可能具有越高的排名。

而整个调度过程是有很多因素影响的,包括节点状态(cpu,内存,磁盘),节点与pod的亲和性,节点标签等,而我们本次只做与磁盘相关的源码分析。

源码分析

2.1、磁盘预选(Predicates)

这一部分核心代码在pkg/scheduler/framework/plugins/nodevolumelimits/这个目录下,分两个文件

csi.go,non_csi.go 两个文件的核心思想基本一致,都是通过Filter来限制一个节点上的最大盘的数量,这个最大数量是默认配置在k8s源码中的。csi的就是限制一个节点上最多能挂几块csi的volume,而non_csi的相对比较复杂,因为牵扯到in-tree插件跟flexvolume插件,而且对每一种in-tree插件都进行了分别的可挂载盘的数量的配置,下面我们对于non-csi的进行具体的源码分析。

话不多说,直接上代码:

2.1.1 nonCSILimits

type nonCSILimits struct {name           stringfilter         VolumeFiltervolumeLimitKey v1.ResourceNamemaxVolumeFunc  func(node *v1.Node) intcsiNodeLister  storagelisters.CSINodeListerpvLister       corelisters.PersistentVolumeListerpvcLister      corelisters.PersistentVolumeClaimListerscLister       storagelisters.StorageClassLister// The string below is generated randomly during the struct's initialization.// It is used to prefix volumeID generated inside the predicate() method to// avoid conflicts with any real volume.randomVolumeIDPrefix string
}

最核心的结构体nonCSILimits ,提供了几个成员

  1. name 顾名思义,每一个nonCSILimits结构体变量的名称

  2. filter VolumeFilter类型的变量,具体包括几个方法,FilterVolume(),FilterPersistentVolume(),MatchProvisioner()以及IsMigrated()具体调用我们后面再讲

  3. volumeLimitKey 其实就是一个string类型,指定了几种类型的key

  4. maxVolumeFunc() 一个获取该Limits的最大数量的方法

  5. csiNodeLister csinode的监听对象

  6. pvLister pv的监听对象

  7. pvcLister pvc的监听对象

  8. scLister sc的监听对象

  9. randomVolumeIDPrefix 一个string类型,用于生成唯一的pvID

2.1.2 nonCSILimits初始化

func newNonCSILimits(filterName string,csiNodeLister storagelisters.CSINodeLister,scLister storagelisters.StorageClassLister,pvLister corelisters.PersistentVolumeLister,pvcLister corelisters.PersistentVolumeClaimLister,
) framework.Plugin {var filter VolumeFiltervar volumeLimitKey v1.ResourceNamevar name stringswitch filterName {case ebsVolumeFilterType:name = EBSNamefilter = ebsVolumeFiltervolumeLimitKey = v1.ResourceName(volumeutil.EBSVolumeLimitKey)case gcePDVolumeFilterType:name = GCEPDNamefilter = gcePDVolumeFiltervolumeLimitKey = v1.ResourceName(volumeutil.GCEVolumeLimitKey)case azureDiskVolumeFilterType:name = AzureDiskNamefilter = azureDiskVolumeFiltervolumeLimitKey = v1.ResourceName(volumeutil.AzureVolumeLimitKey)case indiskVolumeFilterType:name = IndiskNamefilter = indiskVolumeFiltervolumeLimitKey = v1.ResourceName(volumeutil.IndiskVolumeLimitKey)default:klog.Fatalf("Wrong filterName, Only Support %v %v %v %v %v", ebsVolumeFilterType,gcePDVolumeFilterType, azureDiskVolumeFilterType, cinderVolumeFilterType, indiskVolumeFilterType)return nil}pl := &nonCSILimits{name:                 name,filter:               filter,volumeLimitKey:       volumeLimitKey,maxVolumeFunc:        getMaxVolumeFunc(filterName),csiNodeLister:        csiNodeLister,pvLister:             pvLister,pvcLister:            pvcLister,scLister:             scLister,randomVolumeIDPrefix: rand.String(32),}return pl
}

初始化时引用了大量的常量

const (// defaultMaxGCEPDVolumes defines the maximum number of PD Volumes for GCE.// GCE instances can have up to 16 PD volumes attached.defaultMaxGCEPDVolumes = 16// defaultMaxAzureDiskVolumes defines the maximum number of PD Volumes for Azure.// Larger Azure VMs can actually have much more disks attached.// TODO We should determine the max based on VM sizedefaultMaxAzureDiskVolumes = 16// ebsVolumeFilterType defines the filter name for ebsVolumeFilter.ebsVolumeFilterType = "EBS"// gcePDVolumeFilterType defines the filter name for gcePDVolumeFilter.gcePDVolumeFilterType = "GCE"// azureDiskVolumeFilterType defines the filter name for azureDiskVolumeFilter.azureDiskVolumeFilterType = "AzureDisk"// cinderVolumeFilterType defines the filter name for cinderVolumeFilter.indiskVolumeFilterType = "Indisk"// ErrReasonMaxVolumeCountExceeded is used for MaxVolumeCount predicate error.ErrReasonMaxVolumeCountExceeded = "node(s) exceed max volume count"// KubeMaxPDVols defines the maximum number of PD Volumes per kubelet.KubeMaxPDVols = "KUBE_MAX_PD_VOLS"// MaxIndiskVolumes defines the maximum number of Indisk Volumes per node.MaxIndiskVolumes = "MAX_INDISK_VOLUMES"// DefaultIndiskVolumes defines the default number of Indisk Volumes per node.DefaultIndiskVolumes = "DEFAULT_INDISK_VOLUMES"
)

这些常量包括各intree存储类型的名字,以及默认的最大volume数量等信息,在nonCSILimits初始化时进行赋值。

除了这些常量之外,最关键的就是filter方法的初始化,看一下cinder的例子,是怎么区分这个volume是cinder的volume的。

var cinderVolumeFilter = VolumeFilter{FilterVolume: func(vol *v1.Volume) (string, bool) {if vol.Cinder != nil {return vol.Cinder.VolumeID, true}return "", false},FilterPersistentVolume: func(pv *v1.PersistentVolume) (string, bool) {if pv.Spec.Cinder != nil {return pv.Spec.Cinder.VolumeID, true}return "", false},MatchProvisioner: func(sc *storage.StorageClass) (relevant bool) {if sc.Provisioner == csilibplugins.CinderInTreePluginName {return true}return false},IsMigrated: func(csiNode *storage.CSINode) bool {return isCSIMigrationOn(csiNode, csilibplugins.CinderInTreePluginName)},
}

可以看到,filer其实是通过对元数据的字段(vol.Cinder,pv.Spec.Cinder,sc.Provisioner)等进行判断来分辨是不是本类型的volume的。

如果不是intree的,可以通过其它的字段来进行过滤

Flexvolume的我们可以用这些字段:

vol.FlexVolume.Driver,pv.Spec.FlexVolume.Driver

CSI的我们可以用这些字段:

vol.CSI.Driver,pv.Spec.CSI.Driver

2.1.3 核心方法Filter

// Filter invoked at the filter extension point.
func (pl *nonCSILimits) Filter(ctx context.Context, _ *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {// If a pod doesn't have any volume attached to it, the predicate will always be true.// Thus we make a fast path for it, to avoid unnecessary computations in this case.if len(pod.Spec.Volumes) == 0 {return nil}newVolumes := make(map[string]bool)if err := pl.filterVolumes(pod.Spec.Volumes, pod.Namespace, newVolumes); err != nil {return framework.NewStatus(framework.Error, err.Error())}// quick returnif len(newVolumes) == 0 {return nil}node := nodeInfo.Node()if node == nil {return framework.NewStatus(framework.Error, fmt.Sprintf("node not found"))}var csiNode *storage.CSINodevar err errorif pl.csiNodeLister != nil {csiNode, err = pl.csiNodeLister.Get(node.Name)if err != nil {// we don't fail here because the CSINode object is only necessary// for determining whether the migration is enabled or notklog.V(5).Infof("Could not get a CSINode object for the node: %v", err)}}// if a plugin has been migrated to a CSI driver, defer to the CSI predicateif pl.filter.IsMigrated(csiNode) {return nil}// count unique volumesexistingVolumes := make(map[string]bool)for _, existingPod := range nodeInfo.Pods {if err := pl.filterVolumes(existingPod.Pod.Spec.Volumes, existingPod.Pod.Namespace, existingVolumes); err != nil {return framework.NewStatus(framework.Error, err.Error())}}numExistingVolumes := len(existingVolumes)// filter out already-mounted volumesfor k := range existingVolumes {delete(newVolumes, k)}numNewVolumes := len(newVolumes)maxAttachLimit := pl.maxVolumeFunc(node)volumeLimits := volumeLimits(nodeInfo)if maxAttachLimitFromAllocatable, ok := volumeLimits[pl.volumeLimitKey]; ok {maxAttachLimit = int(maxAttachLimitFromAllocatable)}if numExistingVolumes+numNewVolumes > maxAttachLimit {return framework.NewStatus(framework.Unschedulable, ErrReasonMaxVolumeCountExceeded)}if nodeInfo != nil && nodeInfo.TransientInfo != nil && utilfeature.DefaultFeatureGate.Enabled(features.BalanceAttachedNodeVolumes) {nodeInfo.TransientInfo.TransientLock.Lock()defer nodeInfo.TransientInfo.TransientLock.Unlock()nodeInfo.TransientInfo.TransNodeInfo.AllocatableVolumesCount = maxAttachLimit - numExistingVolumesnodeInfo.TransientInfo.TransNodeInfo.RequestedVolumes = numNewVolumes}return nil
}

接下来看一下核心代码的逻辑

最开始是一些校验,首先校验这个pod有没有使用volume,再校验这个pod使用的volume是什么类型的volume,然后由对应类型的volume的filter进行处理。

看一下最核心的处理流程

numExistingVolumes := len(existingVolumes)// filter out already-mounted volumesfor k := range existingVolumes {delete(newVolumes, k)}numNewVolumes := len(newVolumes)maxAttachLimit := pl.maxVolumeFunc(node)volumeLimits := volumeLimits(nodeInfo)if maxAttachLimitFromAllocatable, ok := volumeLimits[pl.volumeLimitKey]; ok {maxAttachLimit = int(maxAttachLimitFromAllocatable)}if numExistingVolumes+numNewVolumes > maxAttachLimit {return framework.NewStatus(framework.Unschedulable, ErrReasonMaxVolumeCountExceeded)}if nodeInfo != nil && nodeInfo.TransientInfo != nil && utilfeature.DefaultFeatureGate.Enabled(features.BalanceAttachedNodeVolumes) {nodeInfo.TransientInfo.TransientLock.Lock()defer nodeInfo.TransientInfo.TransientLock.Unlock()nodeInfo.TransientInfo.TransNodeInfo.AllocatableVolumesCount = maxAttachLimit - numExistingVolumesnodeInfo.TransientInfo.TransNodeInfo.RequestedVolumes = numNewVolumes}return nil

逻辑非常清楚

  1. numExistingVolumes 表示该节点上已经有的volume数

  2. numNewVolumes是该pod需要创建的volume数

  3. maxAttachLimit 是该类型的volume在节点上所能创建的最大volume数

  4. 如果 numExistingVolumes+numNewVolumes > maxAttachLimit 则该节点不可调度

  5. 如果可以调度,则return nil,然后释放资源

到此,预选阶段的工作做完。

这里还需要注意一个地方

nodeInfo.TransientInfo.TransNodeInfo.AllocatableVolumesCount = maxAttachLimit - numExistingVolumesnodeInfo.TransientInfo.TransNodeInfo.RequestedVolumes = numNewVolumes

AllocatableVolumesCount,RequestedVolumes这两个变量的赋值是给后面进行优选打分的时候用的

2.2 磁盘优选(Priorities)

优选打分的条件也很多,这里我们只处理跟存储相关的,具体源码在

pkg\scheduler\framework\plugins\noderesources\resource_allocation.go

pkg\scheduler\framework\plugins\noderesources\least_allocated.go

2.2.1 resource_allocation

resource_allocation 字体意思就可以理解,就是资源分配,而资源分配有多种方,比如least_allocated,most_allocated,requested_to_capacity_ratio都是资源分配的方法,而resource_allocation只是提供一个score方法,代码如下:

func (r *resourceAllocationScorer) score(pod *v1.Pod,nodeInfo *framework.NodeInfo) (int64, *framework.Status) {node := nodeInfo.Node()if node == nil {return 0, framework.NewStatus(framework.Error, "node not found")}if r.resourceToWeightMap == nil {return 0, framework.NewStatus(framework.Error, "resources not found")}requested := make(resourceToValueMap, len(r.resourceToWeightMap))allocatable := make(resourceToValueMap, len(r.resourceToWeightMap))for resource := range r.resourceToWeightMap {allocatable[resource], requested[resource] = calculateResourceAllocatableRequest(nodeInfo, pod, resource)}var score int64// Check if the pod has volumes and this could be added to scorer function for balanced resource allocation.if len(pod.Spec.Volumes) > 0 && utilfeature.DefaultFeatureGate.Enabled(features.BalanceAttachedNodeVolumes) && nodeInfo.TransientInfo != nil {score = r.scorer(requested, allocatable, true, nodeInfo.TransientInfo.TransNodeInfo.RequestedVolumes, nodeInfo.TransientInfo.TransNodeInfo.AllocatableVolumesCount)} else {score = r.scorer(requested, allocatable, false, 0, 0)}if klog.V(10).Enabled() {if len(pod.Spec.Volumes) > 0 && utilfeature.DefaultFeatureGate.Enabled(features.BalanceAttachedNodeVolumes) && nodeInfo.TransientInfo != nil {klog.Infof("%v -> %v: %v, map of allocatable resources %v, map of requested resources %v , allocatable volumes %d, requested volumes %d, score %d",pod.Name, node.Name, r.Name,allocatable, requested, nodeInfo.TransientInfo.TransNodeInfo.AllocatableVolumesCount,nodeInfo.TransientInfo.TransNodeInfo.RequestedVolumes,score,)} else {klog.Infof("%v -> %v: %v, map of allocatable resources %v, map of requested resources %v ,score %d,",pod.Name, node.Name, r.Name,allocatable, requested, score,)}}return score, nil
}

核心代码就一句

if len(pod.Spec.Volumes) > 0 && utilfeature.DefaultFeatureGate.Enabled(features.BalanceAttachedNodeVolumes) && nodeInfo.TransientInfo != nil {score = r.scorer(requested, allocatable, true, nodeInfo.TransientInfo.TransNodeInfo.RequestedVolumes, nodeInfo.TransientInfo.TransNodeInfo.AllocatableVolumesCount)} else {score = r.scorer(requested, allocatable, false, 0, 0)}

如果pod有volume并且BalanceAttachedNodeVolumes这个feature打开了,并且节点有TransientInfo

那么就走存储相关的打分,否则就不走。

我们看一下r.scorer的参数的后两个,就是我们在预选阶段最后赋值的两个参数

AllocatableVolumesCount 表示还可以创建的volume数量

RequestedVolumes 表示该pod需要的volume数量

2.2.2 least_allocated

least_allocated代码最少资源优先,也就是节点上资源越少,分越高

直接看源码

type resourceAllocationScorer struct {Name                stringscorer              func(requested, allocable resourceToValueMap, includeVolumes bool, requestedVolumes int, allocatableVolumes int) int64resourceToWeightMap resourceToWeightMap
}func leastResourceScorer(resToWeightMap resourceToWeightMap) func(resourceToValueMap, resourceToValueMap, bool, int, int) int64 {return func(requested, allocable resourceToValueMap, includeVolumes bool, requestedVolumes int, allocatableVolumes int) int64 {var nodeScore, weightSum int64for resource, weight := range resToWeightMap {resourceScore := leastRequestedScore(requested[resource], allocable[resource])nodeScore += resourceScore * weightweightSum += weight}return nodeScore / weightSum}
}// The unused capacity is calculated on a scale of 0-MaxNodeScore
// 0 being the lowest priority and `MaxNodeScore` being the highest.
// The more unused resources the higher the score is.
func leastRequestedScore(requested, capacity int64) int64 {if capacity == 0 {return 0}if requested > capacity {return 0}return ((capacity - requested) * int64(framework.MaxNodeScore)) / capacity
}

我们来看核心算法

capacity表示还可以剩余资源数量

requested表示该pod需求资源数量

比如:capacity=10,requested=3,framework.MaxNodeScorel默认是100

那么得分就是 (10-3)*100/10=70

但是我们看到 leastResourceScorer中并没有引用存储的部分,所以我们可以手动添加上

if includeVolumes && allocatableVolumes - requestedVolumes > 0 && allocatableVolumes > 0 {nodeScore += int64(((allocatableVolumes - requestedVolumes) * int(framework.MaxNodeScore)) / allocatableVolumes)weightSum += 1}

至此,kube-scheduler存储部分代码解读。

de210a8931c18e04f208df5db854e3fc.gif

往期推荐

从 40% 跌至 4%,“糊”了的 Firefox 还能重回巅峰吗?

Gartner 发布 2022 年汽车行业五大技术趋势

使用这个库,让你的服务操作 Redis 速度飞起

漫画:什么是“低代码”开发平台?

93e10f643d016898bb5a5ea7fbc0e368.gif

点分享

6ff97e204eb88f622c16da32ba1dec9b.gif

点收藏

49dc2af0c94789a7b63165920db2d93c.gif

点点赞

a1199d1a2ebc652569ebc0be89c97f98.gif

点在看

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/512253.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开放搜索查询分析服务架构解读

简介: 搜索行为在后端都会有大量的数据计算和处理才会召回符合用户需求的搜索结果,本次分享结合自建搜索业务中查询分析服务常见的问题及难点,介绍阿里云开放搜索查询分析具备的能力及解决方案,并深度解读阿里巴巴查询分析服务架构…

多任务多目标CTR预估技术

简介: 多目标(Multi Objective Learning)是MTL中的一种。在业务场景中,经常面临既要又要的多目标问题。而多个目标常常会有冲突。如何使多个目标同时得到提升,是多任务多目标在真实业务场景中存在的意义。 作者 | 志阳…

Veeam 发布 2022 年数据保护趋势报告,开发者需关注哪些点?

如今数据作为重要的生产要素,成为数字经济高速发展的关键驱动力之一。越来越多开发者和企业认识到数据保护的重要性,关注数据保护发展趋势,以通过相关的技术解决方案来制定应对策略。 为帮助企业捋请思路,加快数字化转型步伐&…

blazeds调用java_Flex使用Blazeds与Java交互及自定义对象转换详解(转)

一、建立Flex与Java交互的工程。本文中讲到的交互是利用Blazeds的,因为这个是免费的,呵呵,我是穷人。首先就是去下载Blazeds的压缩包,这个可以从官网或者CSDN、JavaEye上下到。解压缩这个包,将里面的Blazeds.war解压&a…

从行业应用到智慧城市,升哲科技Alpha协议如何保障物理世界的数据传输

随着国家《“十四五”信息通信行业发展规划》和《物联网新型基础设施建设三年行动计划(2021-2023年)》的政策出台,物联网的产业发展迎来了新一波浪潮。在农业、制造业、生态环境、智慧消防等场景下,以数字化转型、智能化升级为动力…

Serverless 工程实践 | 零基础上手 Knative 应用

简介: Knative 是一款基于 Kubernetes 的 Serverless 框架。其目标是制定云原生、跨平台的 Serverless 编排标准。 Knative 介绍 Knative 通过整合容器构建(或者函数)、工作负载管理(动态扩缩)以及事件模型这三者实现…

DataWorks功能实践速览 05——循环与遍历

简介: DataWorks功能实践系列,帮助您解析业务实现过程中的痛点,提高业务功能使用效率!通过往期的介绍,您已经了解到在DataWorks上进行任务运行的最关键的几个知识点,其中上期参数透传中为您介绍了可以将上游…

阿里安全开源顶尖技术“猎豹” 计算更快数据更安全

两家公司想开展合作,发挥各自优势联合开发一款产品,如何以“隐私计算”的形式,在保护隐私的情况下,高效地实现两方联合计算,便成为解决这一问题的关键。 最近,阿里安全最新研发的Cheetah(猎豹&…

PaddlePaddle:在 Serverless 架构上十几行代码实现 OCR 能力

简介: 飞桨深度学习框架采用基于编程逻辑的组网范式,对于普通开发者而言更容易上手,同时支持声明式和命令式编程,兼具开发的灵活性和高性能。 飞桨 (PaddlePaddle) 以百度多年的深度学习技术研究和业务应用为基础,是中…

云原生体系下 Serverless 弹性探索与实践

简介: SAE 通过对弹性组件和应用全生命周期的不断优化以达到秒级弹性,并在弹性能力,场景丰富度,稳定性上具备核心竞争力,是传统应用 0 改造上 Serverless 的最佳选择。 作者:竞霄 Serverless 时代的来临 …

java jndi使用_Java项目中使用JNDI连接数据库

因为写的大作业经常用到数据库连接 所以自己写了个数据库连接的类 package DB_Link_info;/* * 数据库链接信息 */public class DB_link_Info {public static final String driverName "com.microsoft.sqlserver.jdbc.SQLServerDriver";public static开发环境为Java,…

Joint Consensus两阶段成员变更的单步实现

简介: Raft提出的两阶段成员变更Joint Consensus是业界主流的成员变更方法,极大的推动了成员变更的工程应用。但Joint Consensus成员变更采用两阶段,一次变更需要提议两条日志, 在一些系统中直接使用时有些不便。那么Joint Consen…

真香!8 行代码搞定最大子数组和问题

作者 | 码农的荒岛求生来源 | 码农的荒岛求生今天给大家带来一道极其经典的题目,叫做最大和子数组,给定一个数组,找到其中的一个连续子数组,其和最大。示例:输入: nums [-2,1,-3,4,-1,2,1,-5,4] 输出: 6 解释: 子数组…

深度干货|云原生分布式数据库 PolarDB-X 的技术演进

简介: 深入解读PolarDB-X的产品架构,以及分布式事务、透明分布式、水平扩展等技术内幕。 一、PolarDB-X是什么 PolarDB-X最早起源于阿里集团2009年提出用分布式架构替代传统商业数据库,阿里研发了TDDL分库分表中间件。2014年阿里集团开始全…

OpenStack 如何跨版本升级

作者 | 孙琦来源 | 万博智云OpenStack是中国私有云的事实标准根据三方统计报告,2020年,中国私有云市场规模达到951.8亿元,同比增长42.1%,私有云在国内IaaS市场占比约45%。私有云提供商有望在云计算市场持续高速发展进程中持续受益…

流计算引擎数据一致性的本质

简介: 本篇文章从流计算的本质出发,重点分析流计算领域中数据处理的一致性问题,同时对一致性问题进行简单的形式化定义,提供一个一窥当下流计算引擎发展脉络的视角,让大家对流计算引擎的认识更为深入,为可能…

java 的io流需要学吗_Java的IO流之字节流,必须要学得内容,你会嘛?

原标题:Java的IO流之字节流,必须要学得内容,你会嘛?伙伴们~端午节过的如何呀~有没有很开心呀~假期已过咱们继续开动了IO流先来认识一下IO流:IO流用来处理设备之间的数据传输,Java对数据的操作是通过流的方式…

为什么大家都在抵制用定时任务实现「关闭超时订单」功能?

作者 | 阿Q来源 | 阿Q说代码前几天领导突然宣布几年前停用的电商项目又重新启动了,让我把代码重构下进行升级。让我最深恶痛觉的就是里边竟然用定时任务实现了“关闭超时订单”的功能,现在想来,哭笑不得。我们先分析一波为什么大家都在抵制用…

面对疾风吧,如何搭建高协同的精准告警体系?

简介: 想要实现AiOps,智能告警少不了。Arms 告警运维中心让面向告警的组织协同更加便捷高效! 作者|九辩 世上没有一个系统是百分之百尽善尽美的。如果想要保证可用性,那么技术团队就得对服务的各种状态了如指掌&…

KubeMeet|聊聊新锐开源项目与云原生新的价值聚焦点

简介: 10 月 16 日上海,OAM/KubeVela、OpenKruise、OCM 三大开源项目的社区负责人、核心贡献者和企业用户将齐聚 KubeMeet,和现场 100 名开发者聊聊新的技术环境和企业需求下,有关“云原生应用管理”的那些事儿。 随着云原生关注…