Crane是一个基于FinOps的云资源分析与成本优化平台,它的愿景是在保证客户应用运行质量的前提下实现极致的降本。Crane已经在腾讯内部自研业务实现了大规模落地,部署数百个K8s集群、管控CPU核数达百万,在降本增效方面取得了阶段性成果。以腾讯某部门集群优化为例,通过使用FinOpsCrane,该部门在保障业务稳定的情况下,资源利用率提升了3倍;腾讯另一自研业务落地Crane后,在一个月内实现了总CPU规模40万核的节省量,相当于成本节约超过1000万元月。 Crane会通过下面3个方面来开启成本优化之旅:成本展示:Kubernetes资源(Deployments,StatefulSets)的多维度聚合与展示。成本分析:周期性的分析集群资源的状态并提供优化建议。成本优化:通过丰富的优化工具更新配置达成降本的目标。 核心功能包括:成本可视化和优化评估;内置了多种推荐器资源推荐、副本推荐、闲置资源推荐;基于预测的水平弹性器;负载感知的调度器;基于QOS的混部。下面我们来详细了解下Crane的各项功能。安装 我们这里使用Helm的方式来进行安装,首先需要安装Prometheus和Grafana(如果您已经在环境中部署了Prometheus和Grafana,可以跳过该步骤)。 Crane使用Prometheus获取集群工作负载对资源的使用情况,可以使用如下所示命令安装Prometheus:helmrepoaddprometheuscommunityhttps:finopshelm。pkg。coding。netgocraneprometheuscommunityhelmupgradeinstallprometheusncranesystemsetpushgateway。enabledfalsesetalertmanager。enabledfalsesetserver。persistentVolume。enabledfalsefhttps:gitee。comfinopshelmchartsrawmainintegrationprometheusoverridevalues。yamlcreatenamespaceprometheuscommunityprometheus 由于Crane的Fadvisor会使用Grafana来展示成本预估,所以我们也需要安装Grafana:helmrepoaddgrafanahttps:finopshelm。pkg。coding。netgocranegrafanahelmupgradeinstallgrafanafhttps:gitee。comfinopshelmchartsrawmainintegrationgrafanaoverridevalues。yamlncranesystemcreatenamespacegrafanagrafana 上面我们指定的values文件中配置了Prometheus数据源以及一些相关的Dashboard,直接安装后即可使用。 然后接下来安装crane与fadvisor,同样直接使用HelmChart安装即可,如下命令所示:helmrepoaddcranehttps:finopshelm。pkg。coding。netgocranegocranehelmupgradeinstallcranencranesystemcreatenamespacecranecranehelmupgradeinstallfadvisorncranesystemcreatenamespacecranefadvisor 安装后可以查看Pod列表了解应用状态:kubectlgetpodsncranesystemNAMEREADYSTATUSRESTARTSAGEcraneagent8jrs501CrashLoopBackOff71(2m26sago)3h23mcraneagentt2rpz01CrashLoopBackOff71(65sago)3h23mcraned776c7b6c75gx8cp22Running03h28mfadvisor56fcc547b6zvf6r11Running0158mgrafana5cd57f9f6bd7nk511Running03h32mmetricadapter887f6548dqcbb811Running03h28mprometheuskubestatemetrics5f6f856ffb4lrrr11Running03h34mprometheusnodeexporter97vmz11Running03h27mprometheusnodeexporterm2gr911Running03h27mprometheusserver7744f66fb4lw2sz22Running03h34m 需要注意我们这里craneagent启动失败了,这是因为我的K8s集群使用的是containerd这种容器运行时,需要明确声明指定使用的运行时endpoint:kubectleditdscraneagentncranesystem。。。。。。spec:containers:args:v2runtimeendpointruncontainerdcontainerd。sock指定有containerd的sock文件command:craneagent。。。。。。 此外还需要更新craneagent的rbac权限:kubectleditclusterrolecraneagent。。。。。。apiGroups:ensurance。crane。ioresources:podqosensurancepoliciesnodeqoss增加nodeqoss和podqoss资源的权限podqoss。。。。。。 然后我们可以再创建一个Ingress对象来暴露crane的dashboard服务:apiVersion:networking。k8s。iov1kind:Ingressmetadata:name:ingresscranedashboardnamespace:cranesystemspec:ingressClassName:nginxrules:host:crane。k8s。localchangetoyourdomainhttp:paths:path:pathType:Prefixbackend:service:name:cranedport:number:9090 直接应用该ingress资源对象即可,当然前提是你已经安装了ingressnginx:kubectlgetpodsningressnginxNAMEREADYSTATUSRESTARTSAGEingressnginxcontroller7647c44fb96gcsf11Running8(44mago)21dingressnginxdefaultbackend7fc5bfd66cgqmmj11Running8(44mago)21dkubectlgetingressncranesystemNAMECLASSHOSTSADDRESSPORTSAGEingresscranedashboardnginxcrane。k8s。local192。168。0。528011s 将crane。k8s。local映射到192。168。0。52后就可以访问crane的dashboard了: 第一次访问dashboard的时候需要添加一个K8s集群,添加添加集群按钮开始添加,填入正确的CRNEEndpoint地址即可。 然后切换到集群总览可以查看到当前集群的一些成本相关数据,由于目前数据还不足,所以会有一些空的图表。 在成本分布页面可以按照维度成本、集群成本和利用率指标以及命名空间成本来展示成本的分布情况。智能推荐 在dasbhoard中开箱后就可以看到相关的成本数据,是因为在添加集群的时候我们安装了推荐的规则。 推荐框架会自动分析集群的各种资源的运行情况并给出优化建议。Crane的推荐模块会定期检测发现集群资源配置的问题,并给出优化建议。智能推荐提供了多种Recommender来实现面向不同资源的优化推荐。 在成本分析推荐规则页面可以看到我们安装的两个推荐规则。 这些推荐规则实际上是安装在K8s集群上的RecommendationRuleCRD对象:kubectlgetRecommendationRuleNAMERUNINTERVALAGEidlenodesrule24h16mworkloadsrule24h16m workloadsrule这个推荐规则的资源对象如下所示:apiVersion:analysis。crane。iov1alpha1kind:RecommendationRulemetadata:name:workloadsrulelabels:analysis。crane。iorecommendationrulepreinstall:truespec:resourceSelectors:kind:DeploymentapiVersion:appsv1kind:StatefulSetapiVersion:appsv1namespaceSelector:any:truerunInterval:24hrecommenders:name:Replicasname:Resource RecommendationRule是一个全部范围内的对象,该推荐规则会对所有命名空间中的Deployments和StatefulSets做资源推荐和副本数推荐。相关规范属性如下所示:每隔24小时运行一次分析推荐,runInterval格式为时间间隔,比如:1h,1m,设置为空表示只运行一次。待分析的资源通过配置resourceSelectors数组设置,每个resourceSelector通过kind、apiVersion、name选择K8s中的资源,当不指定name时表示在namespaceSelector基础上的所有资源。namespaceSelector定义了待分析资源的命名空间,any:true表示选择所有命名空间。recommenders定义了待分析的资源需要通过哪些Recommender进行分析。目前支持两种Recommender:资源推荐(Resource):通过VPA算法分析应用的真实用量推荐更合适的资源配置副本数推荐(Replicas):通过HPA算法分析应用的真实用量推荐更合适的副本数量 资源推荐 Kubernetes用户在创建应用资源时常常是基于经验值来设置request和limit,通过资源推荐的算法分析应用的真实用量推荐更合适的资源配置,你可以参考并采纳它提升集群的资源利用率。该推荐算法模型采用了VPA的滑动窗口(MovingWindow)算法进行推荐:通过监控数据,获取Workload过去一周(可配置)的CPU和内存的历史用量。算法考虑数据的时效性,较新的数据采样点会拥有更高的权重。CPU推荐值基于用户设置的目标百分位值计算,内存推荐值基于历史数据的最大值。 副本数推荐 Kubernetes用户在创建应用资源时常常是基于经验值来设置副本数。通过副本数推荐的算法分析应用的真实用量推荐更合适的副本配置,同样可以参考并采纳它提升集群的资源利用率。其实现的基本算法是基于工作负载历史CPU负载,找到过去七天内每小时负载最低的CPU用量,计算按50(可配置)利用率和工作负载CPURequest应配置的副本数。 当我们部署crane的时候会在同一个命名空间中创建一个名为recommendationconfiguration的ConfigMap对象,包含一个yaml格式的RecommendationConfiguration,该配置订阅了recommender的配置,如下所示:kubectlgetcmrecommendationconfigurationncranesystemoyamlapiVersion:v1data:config。yaml:apiVersion:analysis。crane。iov1alpha1kind:RecommendationConfigurationrecommenders:name:Replicas副本数推荐acceptedResources:kind:DeploymentapiVersion:appsv1kind:StatefulSetapiVersion:appsv1name:Resource资源推荐acceptedResources:kind:DeploymentapiVersion:appsv1kind:StatefulSetapiVersion:appsv1kind:ConfigMapmetadata:name:recommendationconfigurationnamespace:cranesystem 需要注意的是资源类型和recommenders需要可以匹配,比如Resource推荐默认只支持Deployments和StatefulSets。 同样的也可以再查看一次闲置节点推荐规则的资源对象,如下所示:kubectlgetrecommendationruleidlenodesruleoyamlapiVersion:analysis。crane。iov1alpha1kind:RecommendationRulemetadata:labels:analysis。crane。iorecommendationrulepreinstall:truename:idlenodesrulespec:namespaceSelector:any:truerecommenders:name:IdleNoderesourceSelectors:apiVersion:v1kind:NoderunInterval:24h 创建RecommendationRule配置后,RecommendationRule控制器会根据配置定期运行推荐任务,给出优化建议生成Recommendation对象,然后我们可以根据优化建议Recommendation调整资源配置。 比如我们这里集群中已经生成了多个优化建议Recommendation对象。kubectlgetrecommendationsNAMETYPETARGETKINDTARGETNAMESPACETARGETNAMESTRATEGYPERIODSECONDSADOPTIONTYPEAGEworkloadsruleresource8whzsResourceStatefulSetdefaultnacosOnceStatusAndAnnotation34mworkloadsruleresourcehx4cpResourceStatefulSetdefaultredisreplicasOnceStatusAndAnnotation34m。。。。。。 可以随便查看任意一个优化建议对象。kubectlgetrecommendworkloadsruleresourceg7nwpncranesystemoyamlapiVersion:analysis。crane。iov1alpha1kind:Recommendationmetadata:name:workloadsruleresourceg7nwpnamespace:cranesystemspec:adoptionType:StatusAndAnnotationcompletionStrategy:completionStrategyType:OncetargetRef:apiVersion:appsv1kind:Deploymentname:fadvisornamespace:cranesystemtype:Resourcestatus:action:Patchconditions:lastTransitionTime:20221020T07:43:49Zmessage:Recommendationisreadyreason:RecommendationReadystatus:Truetype:ReadycurrentInfo:{spec:{template:{spec:{containers:〔{name:fadvisor,resources:{requests:{cpu:0,memory:0}}}〕}}}}lastUpdateTime:20221020T07:43:49ZrecommendedInfo:{spec:{template:{spec:{containers:〔{name:fadvisor,resources:{requests:{cpu:114m,memory:120586239}}}〕}}}}recommendedValue:resourceRequest:containers:containerName:fadvisortarget:cpu:114mmemory:120586239targetRef:{} 在dashboard的资源推荐页面也能查看到优化建议列表。 在页面中可以看到当前资源(容器CPUMemory)与推荐的资源数据,点击采纳建议即可获取优化的执行命令。 执行命令即可完成优化,其实就是修改资源对象的resources资源数据。patchDatakubectlgetrecommendworkloadsruleresourceg7nwpncranesystemojsonpath{。status。recommendedInfo};kubectlpatchDeploymentfadvisorncranesystempatch{patchData} 对于闲置节点推荐,由于节点的下线在不同平台上的步骤不同,用户可以根据自身需求进行节点的下线或者缩容。 应用在监控系统(比如Prometheus)中的历史数据越久,推荐结果就越准确,建议生产上超过两周时间。对新建应用的预测往往不准。自定义推荐 RecommendationFramework提供了一套可扩展的Recommender框架并支持了内置的Recommender,用户可以实现一个自定义的Recommender,或者修改一个已有的Recommender。 和K8s调度框架类似,Recommender接口定义了一次推荐需要实现的四个阶段和八个扩展点,这些扩展点会在推荐过程中按顺序被调用。这些扩展点中的一些可以改变推荐决策,而另一些仅用来提供信息。 推荐框架架构 Recommender接口定义如下所示:typeRecommenderinterface{Name()stringframework。Filterframework。PrePrepareframework。Prepareframework。PostPrepareframework。PreRecommendframework。Recommendframework。PostRecommendframework。Observe}Phase:FiltertypeFilterinterface{Filter将过滤无法通过目标推荐器推荐的资源Filter(ctxRecommendationContext)error}Phase:PreparetypePrePrepareinterface{CheckDataProviders(ctxRecommendationContext)error}typePrepareinterface{CollectData(ctxRecommendationContext)error}typePostPrepareinterface{PostProcessing(ctxRecommendationContext)error}typePreRecommendinterface{PreRecommend(ctxRecommendationContext)error}Phase:RecommendtypeRecommendinterface{Recommend(ctxRecommendationContext)error}typePostRecommendinterface{Policy(ctxRecommendationContext)error}Phase:ObservetypeObserveinterface{Observe(ctxRecommendationContext)error} 整个推荐过程分成了四个阶段:Filter、Prepare、Recommend、Observe,阶段的输入是需要分析的Kubernetes资源,输出是推荐的优化建议。接口中的RecommendationContext保存了一次推荐过程中的上下文,包括推荐目标、RecommendationConfiguration等信息,我们可以根据自身需求增加更多的内容。 比如资源推荐就实现了Recommender接口,主要做了下面3个阶段的处理:Filter阶段:过滤没有Pod的工作负载Recommend推荐:采用VPA的滑动窗口算法分别计算每个容器的CPU和内存并给出对应的推荐值Observe推荐:将推荐资源配置记录到craneanalyticsreplicasrecommendation指标 除了核心的智能推荐功能之外,Crane还有很多高级特性,比如可以根据实际的节点利用率的动态调度器、基于流量预测的弹性HPA等等。智能调度器 Crane除了提供了智能推荐功能之外,还提供了一个调度器插件Cranescheduler可以实现智能调度和完成拓扑感知调度与资源分配的工作。动态调度器 K8s的原生调度器只能通过资源的requests值来调度pod,这很容易造成一系列负载不均的问题:对于某些节点,实际负载与资源请求相差不大,这会导致很大概率出现稳定性问题。对于其他节点来说,实际负载远小于资源请求,这将导致资源的巨大浪费。 为了解决这些问题,动态调度器根据实际的节点利用率构建了一个简单但高效的模型,并过滤掉那些负载高的节点来平衡集群。 动态调度器依赖于prometheus和nodeexporter收集汇总指标数据,它由两个组件组成:Nodeannotator定期从Prometheus拉取数据,并以annotations的形式在节点上用时间戳标记它们。Dynamicplugin直接从节点的annotations中读取负载数据,过滤并基于简单的算法对候选节点进行评分。 动态调度器提供了一个默认值调度策略,配置文件如下所示:policy。yamlapiVersion:scheduler。policy。crane。iov1alpha1kind:DynamicSchedulerPolicyspec:syncPolicy:cpuusagename:cpuusageavg5mperiod:3mname:cpuusagemaxavg1hperiod:15mname:cpuusagemaxavg1dperiod:3hmemoryusagename:memusageavg5mperiod:3mname:memusagemaxavg1hperiod:15mname:memusagemaxavg1dperiod:3hpredicate:cpuusagename:cpuusageavg5mmaxLimitPecent:0。65name:cpuusagemaxavg1hmaxLimitPecent:0。75memoryusagename:memusageavg5mmaxLimitPecent:0。65name:memusagemaxavg1hmaxLimitPecent:0。75priority:cpuusagename:cpuusageavg5mweight:0。2name:cpuusagemaxavg1hweight:0。3name:cpuusagemaxavg1dweight:0。5memoryusagename:memusageavg5mweight:0。2name:memusagemaxavg1hweight:0。3name:memusagemaxavg1dweight:0。5hotValue:timeRange:5mcount:5timeRange:1mcount:2 我们可以根据实际需求自定义该策略配置,默认策略依赖于以下指标:cpuusageavg5mcpuusagemaxavg1hcpuusagemaxavg1dmemusageavg5mmemusagemaxavg1hmemusagemaxavg1d 这几个指标我们这里是通过记录规则创建的,可以查看Prometheus的配置文件来了解详细信息:kubectlgetcmncranesystemprometheusserveroyamlapiVersion:v1data:alertingrules。yml:{}alerts:{}allowsnippetannotations:falseprometheus。yml:global:evaluationinterval:1mscrapeinterval:1mscrapetimeout:10srulefiles:etcconfigrecordingrules。ymletcconfigalertingrules。ymletcconfigrulesetcconfigalertsscrapeconfigs:jobname:prometheusstaticconfigs:targets:localhost:9090。。。。。。recordingrules。yml:groups:interval:3600sname:costs。rulesrules:。。。。。。interval:30sname:scheduler。rules。30srules:expr:100(avgby(instance)(irate(nodecpusecondstotal{modeidle}〔90s〕))100)record:cpuusageactiveexpr:100(1nodememoryMemAvailablebytesnodememoryMemTotalbytes)record:memusageactiveinterval:1mname:scheduler。rules。1mrules:expr:avgovertime(cpuusageactive〔5m〕)record:cpuusageavg5mexpr:avgovertime(memusageactive〔5m〕)record:memusageavg5minterval:5mname:scheduler。rules。5mrules:expr:maxovertime(cpuusageavg5m〔1h〕)record:cpuusagemaxavg1hexpr:maxovertime(cpuusageavg5m〔1d〕)record:cpuusagemaxavg1dexpr:maxovertime(memusageavg5m〔1h〕)record:memusagemaxavg1hexpr:maxovertime(memusageavg5m〔1d〕)record:memusagemaxavg1drules:{}kind:ConfigMapmetadata:name:prometheusservernamespace:cranesystem 在调度的Filter阶段,如果该节点的实际使用率大于上述任一指标的阈值,则该节点将被过滤。而在Score阶段,最终得分是这些指标值的加权和。 在生产集群中,可能会频繁出现调度热点,因为创建Pod后节点的负载不能立即增加。因此,我们定义了一个额外的指标,名为hotValue,表示节点最近几次的调度频率,并且节点的最终优先级是最终得分减去hotValue。 我们可以在K8s集群中安装Cranescheduler作为第二个调度器来进行验证:helmrepoaddcranehttps:finopshelm。pkg。coding。netgocranegocranehelmupgradeinstallschedulerncranesystemcreatenamespacesetglobal。prometheusAddrhttp:prometheusserver。cranesystem。svc。cluster。local:8080cranescheduler 安装后会创建一个名为schedulerconfig的ConfigMap对象,里面包含的就是调度器的配置文件,我们会在配置中启用Dynamic动态调度插件:kubectlgetcmncranesystemschedulerconfigoyamlapiVersion:v1data:schedulerconfig。yaml:apiVersion:kubescheduler。config。k8s。iov1beta2kind:KubeSchedulerConfigurationleaderElection:leaderElect:falseprofiles:schedulerName:craneschedulerplugins:filter:enabled:name:Dynamicscore:enabled:name:Dynamicweight:3pluginConfig:name:Dynamicargs:policyConfigPath:etckubernetespolicy。yamlkind:ConfigMapmetadata:name:schedulerconfignamespace:cranesystem 安装完成后我们可以任意创建一个Pod,并通过设置schedulerName:cranescheduler属性明确指定使用该调度器进行调度,如下所示:apiVersion:appsv1kind:Deploymentmetadata:name:cpustressspec:selector:matchLabels:app:cpustressreplicas:1template:metadata:labels:app:cpustressspec:schedulerName:craneschedulerhostNetwork:truetolerations:key:node。kubernetes。ionetworkunavailableoperator:Existseffect:NoSchedulecontainers:name:stressimage:docker。iogocranestress:latestcommand:〔stress,c,1〕resources:requests:memory:1Gicpu:1limits:memory:1Gicpu:1 直接创建上面的资源对象,正常创建的Pod就会通过CraneScheduler调度器进行调度了:Events:TypeReasonAgeFromMessageNormalScheduled22scraneschedulerSuccessfullyassigneddefaultcpustresscc8656b6chsqdgtonode2NormalPulling22skubeletPullingimagedocker。iogocranestress:latest 如果想默认使用该动态调度器,则可以使用该调度器去替换掉默认的调度器即可。拓扑感知调度 CraneScheduler和CraneAgent配合工作可以完成拓扑感知调度与资源分配的工作。CraneAgent从节点采集资源拓扑,包括NUMA、Socket、设备等信息,汇总到NodeResourceTopology这个自定义资源对象中。 CPU拓扑感知 CraneScheduler在调度时会参考节点的NodeResourceTopology对象获取到节点详细的资源拓扑结构,在调度到节点的同时还会为Pod分配拓扑资源,并将结果写到Pod的annotations中。CraneAgent在节点上Watch到Pod被调度后,从Pod的annotations中获取到拓扑分配结果,并按照用户给定的CPU绑定策略进行CPUSet的细粒度分配。 Crane中提供了四种CPU分配策略,分别如下:none:该策略不进行特别的CPUSet分配,Pod会使用节点CPU共享池。exclusive:该策略对应kubelet的static策略,Pod会独占CPU核心,其他任何Pod都无法使用。numa:该策略会指定NUMANode,Pod会使用该NUMANode上的CPU共享池。immovable:该策略会将Pod固定在某些CPU核心上,但这些核心属于共享池,其他Pod仍可使用。 首先需要在CraneAgent启动参数中添加featuregatesNodeResourceTopologytrue,CraneCPUManagertrue开启拓扑感知调度特性。 然后修改kubescheduler的配置文件(schedulerconfig。yaml)启用动态调度插件并配置插件参数:apiVersion:kubescheduler。config。k8s。iov1beta2kind:KubeSchedulerConfigurationleaderElection:leaderElect:trueclientConnection:kubeconfig:REPLACEMEWITHKUBECONFIGPATHprofiles:schedulerName:defaultscheduler可以改成自己的调度器名称plugins:preFilter:enabled:name:NodeResourceTopologyMatchfilter:enabled:name:NodeResourceTopologyMatchscore:enabled:name:NodeResourceTopologyMatchweight:2reserve:enabled:name:NodeResourceTopologyMatchpreBind:enabled:name:NodeResourceTopologyMatch 正确安装组件后,每个节点均会生成NodeResourceTopology对象。kubectlgetnrtNAMECRANECPUMANAGERPOLICYCRANETOPOLOGYMANAGERPOLICYAGEnode1StaticSingleNUMANodePodLevel35d 可以看出集群中节点node1已生成对应的NRT对象,此时Crane的CPUManagerPolicy为Static,节点默认的TopologyManagerPolicy为SingleNUMANodePodLevel,代表节点不允许跨NUMA分配资源。 使用以下实例进行调度测试:apiVersion:appsv1kind:Deploymentmetadata:name:nginxdeploymentlabels:app:nginxspec:selector:matchLabels:app:nginxtemplate:metadata:annotations:topology。crane。iotopologyawareness:true添加注解,表示Pod需要感知CPU拓扑,资源分配不允许跨NUMA。若不指定,则拓扑策略默认继承节点上的topology。crane。iotopologyawareness标签topology。crane。iocpupolicy:exclusive添加注解,表示Pod的CPU分配策略为exclusive策略。labels:app:nginxspec:containers:image:nginxname:nginxresources:limits:cpu:2需要limits。cpu值,如果要开启绑核,则该值必须等于requests。cpu。memory:2Gi 应用后可以从annotations中查看Pod的拓扑分配结果,发现Pod在NUMANode0上被分配了2个CPU核心。kubectlgetpodocustomcolumnsname:metadata。name,topologyresult:metadata。annotations。topology。crane。iotopologyresultnametopologyresultnginxdeployment754d99dcdfmtcdp〔{name:node0,type:Node,resources:{capacity:{cpu:2}}}〕实现基于流量预测的弹性 KubernetesHPA支持了丰富的弹性扩展能力,Kubernetes平台开发者部署服务实现自定义Metric的服务,Kubernetes用户配置多项内置的资源指标或者自定义Metric指标实现自定义水平弹性。 EffectiveHorizontalPodAutoscaler(简称EHPA)是Crane提供的弹性伸缩产品,它基于社区HPA做底层的弹性控制,支持更丰富的弹性触发策略(预测,观测,周期),让弹性更加高效,并保障了服务的质量。提前扩容,保证服务质量:通过算法预测未来的流量洪峰提前扩容,避免扩容不及时导致的雪崩和服务稳定性故障。减少无效缩容:通过预测未来可减少不必要的缩容,稳定工作负载的资源使用率,消除突刺误判。支持Cron配置:支持Cronbased弹性配置,应对大促等异常流量洪峰。兼容社区:使用社区HPA作为弹性控制的执行层,能力完全兼容社区。 EffectiveHPA兼容社区的KubernetesHPA的能力,提供了更智能的弹性策略,比如基于预测的弹性和基于Cron周期的弹性等。在了解如何使用EHPA之前,我们有必要来详细了解下K8s中的HPA对象。通过此伸缩组件,Kubernetes集群可以利用监控指标(CPU使用率等)自动扩容或者缩容服务中的Pod数量,当业务需求增加时,HPA将自动增加服务的Pod数量,提高系统稳定性,而当业务需求下降时,HPA将自动减少服务的Pod数量,减少对集群资源的请求量,甚至还可以配合ClusterAutoscaler实现集群规模的自动伸缩,节省IT成本。 不过目前默认的HPA对象只能支持根据CPU和内存的阈值检测扩缩容,但也可以通过custommetricapi来调用Prometheus实现自定义metric,这样就可以实现更加灵活的监控指标实现弹性伸缩了。 默认情况下,HPA会通过metrics。k8s。io这个接口服务来获取Pod的CPU、内存指标,CPU和内存这两者属于核心指标,metrics。k8s。io服务对应的后端服务一般是metricsserver,所以在使用HPA的时候需要安装该应用。 如果HPA要通过非CPU、内存的其他指标来伸缩容器,我们则需要部署一套监控系统如Prometheus,让Prometheus采集各种指标,但是Prometheus采集到的metrics指标并不能直接给K8s使用,因为两者数据格式是不兼容的,因此需要使用到另外一个组件prometheusadapter,该组件可以将Prometheus的metrics指标数据格式转换成K8sAPI接口能识别的格式,另外我们还需要在K8s注册一个服务(即custom。metrics。k8s。io),以便HPA能通过apis进行访问。 需要注意的是Crane提供了一个metricadapter组件,该组件和prometheusadapter都基于custommetricapiserver实现了CustomMetric和ExternalMetric的ApiService,在安装Crane时会将对应的ApiService安装为Crane的metricadapter,所以它会和prometheusadapter冲突,因为Prometheus是当下最流行的开源监控系统,所以我们更愿意使用它来获取用户的自定义指标,那么我们就需要去安装prometheusadapter,但是在安装之前需要删除Crane提供的ApiService。查看当前集群ApiServicekubectlgetapiservicegrepcranesystemv1beta1。custom。metrics。k8s。iocranesystemmetricadapterTrue3h51mv1beta1。external。metrics。k8s。iocranesystemmetricadapterTrue3h51m删除crane安装的ApiServicekubectldeleteapiservicev1beta1。custom。metrics。k8s。iokubectldeleteapiservicev1beta1。external。metrics。k8s。io 然后通过HelmChart来安装PrometheusAdapter:helmrepoaddprometheuscommunityhttps:prometheuscommunity。github。iohelmchartshelmrepoupdate指定有prometheus地址helmupgradeinstallprometheusadapterncranesystemprometheuscommunityprometheusadaptersetimage。repositorycnychprometheusadapter,prometheus。urlhttp:prometheusserver。cranesystem。svc,prometheus。port8080 当prometheusadapter安装成功后我们再将ApiService改回Crane的metricadapter,应用下面的资源清单即可:apiVersion:apiregistration。k8s。iov1kind:APIServicemetadata:name:v1beta1。custom。metrics。k8s。iospec:service:name:metricadapternamespace:cranesystemgroup:custom。metrics。k8s。ioversion:v1beta1insecureSkipTLSVerify:truegroupPriorityMinimum:100versionPriority:100apiVersion:apiregistration。k8s。iov1kind:APIServicemetadata:name:v1beta1。external。metrics。k8s。iospec:service:name:metricadapternamespace:cranesystemgroup:external。metrics。k8s。ioversion:v1beta1insecureSkipTLSVerify:truegroupPriorityMinimum:100versionPriority:100 应用了上面的对象后,ApiService改回了Crane的metricadapter,那么就不能使用prometheusadapter的自定义Metrics功能,我们可以通过Crane的metricadapter提供的RemoteAdapter功能将请求转发给prometheusadapter。 修改metricadapter的配置,将prometheusadapter的Service配置成CraneMetricAdapter的RemoteAdapter。kubectleditdeploymetricadapterncranesystemapiVersion:appsv1kind:Deploymentmetadata:name:metricadapternamespace:cranesystemspec:template:spec:containers:args:添加外部Adapter配置remoteadaptertrueremoteadapterservicenamespacecranesystemremoteadapterservicenameprometheusadapterremoteadapterserviceport443。。。。。。 这是因为Kubernetes限制一个ApiService只能配置一个后端服务,为了在一个集群内使用Crane提供的Metric和prometheusadapter提供的Metric,Crane支持了RemoteAdapter来解决该问题:CraneMetricAdapter支持配置一个KubernetesService作为一个远程AdapterCraneMetricAdapter处理请求时会先检查是否是Crane提供的LocalMetric,如果不是,则转发给远程Adapter 下面我们来部署一个示例应用,用来测试自定义指标的容器弹性伸缩。如下所示的应用暴露了Metric展示每秒收到的http请求数量。sampleapp。yamlapiVersion:appsv1kind:Deploymentmetadata:name:sampleappspec:selector:matchLabels:app:sampleapptemplate:metadata:labels:app:sampleappspec:containers:image:luxasautoscaledemo:v0。1。2name:metricsproviderresources:limits:cpu:500mrequests:cpu:200mports:containerPort:8080apiVersion:v1kind:Servicemetadata:name:sampleappspec:ports:name:httpport:80targetPort:8080selector:app:sampleapptype:NodePort 当应用部署完成后,我们可以通过命令检查httprequeststotal指标数据:curlhttp:(kubectlgetservicesampleappojsonpath{。spec。clusterIP})metricsHELPhttprequeststotalTheamountofrequestsservedbytheserverintotalTYPEhttprequeststotalcounterhttprequeststotal1 然后我们需要在Prometheus中配置抓取sampleapp的指标,我们这里使用如下所示命令添加抓取配置:kubectleditcmncranesystemprometheusserver添加抓取sampleapp配置jobname:sampleappkubernetessdconfigs:role:podrelabelconfigs:action:keepregex:default;sampleapp(。)sourcelabels:metakubernetesnamespacemetakubernetespodnameaction:labelmapregex:metakubernetespodlabel(。)action:replacesourcelabels:metakubernetesnamespacetargetlabel:namespacesourcelabels:〔metakubernetespodname〕action:replacetargetlabel:pod 配置生效后我们可以在PrometheusDashboard中查询对应的指标: 为了让HPA能够用到Prometheus采集到的指标,prometheusadapter通过使用promql语句来获取指标,然后修改数据格式,并把重新组装的指标和值通过自己的接口暴露。而HPA会通过apiscustom。metrics。k8s。io代理到prometheusadapter的service上来获取这些指标。 如果把Prometheus的所有指标到获取一遍并重新组装,那adapter的效率必然十分低下,因此adapter将需要读取的指标设计成可配置,让用户通过ConfigMap来决定读取Prometheus的哪些监控指标。 我们这里使用HelmChart方式安装的prometheusadapter,其默认的Rule配置如下所示:kubectlgetcmncranesystemprometheusadapteroyamlapiVersion:v1data:config。yaml:rules:seriesQuery:{namecontainer。,container!POD,namespace!,pod!}seriesFilters:〔〕resources:overrides:namespace:resource:namespacepod:resource:podname:matches:container(。)secondstotalas:metricsQuery:sum(rate(。Series{。LabelMatchers,container!POD}〔5m〕))by(。GroupBy)。。。。。。其他规则省略kind:ConfigMapmetadata:name:prometheusadapternamespace:cranesystem Prometheusadapter的配置文件格式如上所示,它分为两个部分,第一个是rules,用于custommetrics,另一个是resourceRules,用于metrics,如果你只用Prometheusadapter做HPA,那么resourceRules就可以省略。 我们可以看到rules规则下面有很多的查询语句,这些查询语句的作用就是尽可能多的获取指标,从而让这些指标都可以用于HPA。也就是说通过prometheusadapter可以将Prometheus中的任何一个指标都用于HPA,但是前提是你得通过查询语句将它拿到(包括指标名称和其对应的值)。也就是说,如果你只需要使用一个指标做HPA,那么你完全就可以只写一条查询,而不像上面使用了好多个查询。整体上每个规则大致可以分为4个部分:Discovery:它指定Adapter应该如何找到该规则的所有Prometheus指标Association:指定Adapter应该如何确定和特定的指标关联的Kubernetes资源Naming:指定Adapter应该如何在自定义指标API中暴露指标Querying:指定如何将对一个获多个Kubernetes对象上的特定指标的请求转换为对Prometheus的查询 我们这里使用的sampleapp应用的指标名叫httprequeststotal,通过上面的规则后会将httprequeststotal转换成Pods类型的CustomMetric,可以获得类似于podshttprequests这样的数据。 执行以下命令,通过CustomMetrics指标查询方式,查看HPA可用指标详情。kubectlgetrawapiscustom。metrics。k8s。iov1beta1namespacesdefaultpodshttprequestsjq。{kind:MetricValueList,apiVersion:custom。metrics。k8s。iov1beta1,metadata:{selfLink:apiscustom。metrics。k8s。iov1beta1namespacesdefaultpods2Ahttprequests},items:〔{describedObject:{kind:Pod,namespace:default,name:sampleapp6876d5585bwv8fl,apiVersion:v1},metricName:httprequests,timestamp:20221027T11:19:05Z,value:18m,selector:null}〕} 接下来我们就可以来测试下基于流量预测的容器弹性伸缩,这就需要用到Crane的EHPA对象了,我们可以使用上面的podshttprequests自定义指标来实现弹性功能。 许多业务在时间序列上天然存在周期性的,尤其是对于那些直接或间接为人服务的业务。这种周期性是由人们日常活动的规律性决定的。例如,人们习惯于中午和晚上点外卖;早晚总有交通高峰;即使是搜索等模式不那么明显的服务,夜间的请求量也远低于白天时间。对于这类业务相关的应用来说,从过去几天的历史数据中推断出次日的指标,或者从上周一的数据中推断出下周一的访问量是很自然的想法。通过预测未来24小时内的指标或流量模式,我们可以更好地管理我们的应用程序实例,稳定我们的系统,同时降低成本。EHPA对象可以使用DSP算法来预测应用未来的时间序列数据,DSP是一种预测时间序列的算法,它基于FFT(快速傅里叶变换),擅长预测一些具有季节性和周期的时间序列。 创建一个如下所示的EHPA资源对象,并开启预测功能:sampleappehpa。yamlapiVersion:autoscaling。crane。iov1alpha1kind:EffectiveHorizontalPodAutoscalermetadata:name:sampleappehpaannotations:metricquery。autoscaling。crane。io是固定的前缀,后面是前缀。Metric名字,需跟spec。metrics中的Metric。name相同,前缀支持pods、resource、externalmetricquery。autoscaling。crane。iopods。httprequests:sum(rate(httprequeststotal〔5m〕))by(pod)spec:ScaleTargetRef是对需要缩放的工作负载的引用scaleTargetRef:apiVersion:appsv1kind:Deploymentname:sampleappminReplicas是可以缩小到的缩放目标的最小副本数minReplicas:1maxReplicas是可以扩大到的缩放目标的最大副本数maxReplicas:10scaleStrategy表示缩放目标的策略,值可以是Auto或ManualscaleStrategy:Autometrics包含用于计算所需副本数的规范。metrics:在使用预测算法预测时,你可能会担心预测数据不准带来一定的风险,EHPA在计算副本数时,不仅会按预测数据计算,同时也会考虑实际监控数据来兜底,提升弹性的安全性,所以可以定义下面的Resource监控数据来兜底type:Resourcetype:Podspods:metric:name:httprequeststarget:type:AverageValueaverageValue:500m当出现了小数点,K8s又需要高精度时,会使用单位m或k。例如1001m1。001,1k1000。prediction定义了预测资源的配置,如果未指定,则默认不启用预测功能prediction:predictionWindowSeconds:3600PredictionWindowSeconds是预测未来指标的时间窗口predictionAlgorithm:algorithmType:dsp指定dsp为预测算法dsp:sampleInterval:60s监控数据的采样间隔为1分钟historyLength:7d拉取过去7天的监控指标作为预测的依据 在上面的资源对象中添加了一个metricquery。autoscaling。crane。iopods。httprequests:sum(rate(httprequeststotal〔5m〕))by(pod)的注解,这样就可以开启自定义指标的预测功能了。 相应的在规范中定义了spec。prediction属性,用来指定预测资源的配置,其中的predictionWindowSeconds属性用来指定预测未来指标的时间窗口,predictionAlgorithm属性用来指定预测的算法,比如我们这里配置的algorithmType:dsp表示使用DSP(DigitalSignalProcessing)算法进行预测,该算法使用在数字信号处理领域中常用的的离散傅里叶变换、自相关函数等手段来识别、预测周期性的时间序列,关于该算法的实现原理可以查看官方文档https:gocrane。iozhcndocstutorialstimeserieesforecastingbydsp的相关介绍,或者查看源码以了解背后原理,相关代码位于pkgpredictiondsp目录下。此外在prediction。predictionAlgorithm。dsp下面还可以配置dsp算法的相关参数,比如我们这里配置的sampleInterval:60s表示监控数据的采样间隔为1分钟,historyLength:7d表示拉取过去7天的监控指标作为预测的依据,此外还可以配置预测方式等。 然后核心的配置就是spec。metrics了,用来指定计算所需副本数的规范,我们这里指定了基于Pods指标的计算方式。type:Podspods:metric:name:httprequeststarget:type:AverageValueaverageValue:500m 上面的配置表示当podshttprequests的自定义指标平均值达到500m后就可以触发HPA缩放,这里有一个点需要注意自定义指标的pods。metric。name的值必须和annotations注解metricquery。autoscaling。crane。iopods。指标名保持一致。 EHPA对象水平弹性的执行流程如下所示:EffectiveHPAController创建HorizontalPodAutoscaler和TimeSeriesPrediction对象PredictionCore从Prometheus获取历史metric通过预测算法计算,将结果记录到TimeSeriesPredictionHPAController通过metricclient从KubeApiServer读取metric数据KubeApiServer将请求路由到Crane的MetricAdapter。HPAController计算所有的Metric返回的结果得到最终的弹性副本推荐。HPAController调用scaleAPI对目标应用扩缩容。 整体流程如下所示: 直接应用上面的EPHA对象即可:kubectlapplyfsampleappehpa。yamleffectivehorizontalpodautoscaler。autoscaling。crane。iosampleappehpacreatedkubectlgetehpaNAMESTRATEGYMINPODSMAXPODSSPECIFICPODSREPLICASAGEsampleappehpaAuto110117s 由于我们开启了自动预测功能,所以EPHA对象创建后会创建一个对应的TimeSeriesPrediction对象:kubectlgettspNAMETARGETREFNAMETARGETREFKINDPREDICTIONWINDOWSECONDSAGEehpasampleappehpasampleappDeployment36003m50skubectlgettspehpasampleappehpaoyamlapiVersion:prediction。crane。iov1alpha1kind:TimeSeriesPredictionmetadata:name:ehpasampleappehpanamespace:defaultspec:predictionMetrics:algorithm:algorithmType:dspdsp:estimators:{}historyLength:7dsampleInterval:60sexpressionQuery:expression:sum(httprequests{})resourceIdentifier:pods。httprequeststype:ExpressionQuerypredictionWindowSeconds:3600targetRef:apiVersion:appsv1kind:Deploymentname:sampleappnamespace:defaultstatus:conditions:lastTransitionTime:20221027T13:01:14Zmessage:notallmetricpredictedreason:PredictPartialstatus:Falsetype:ReadypredictionMetrics:ready:falseresourceIdentifier:pods。httprequests 在status中可以看到包含notallmetricpredicted这样的信息,这是因为应用运行时间较短,可能会出现无法预测的情况。同样也会自动创建一个对应的HPA对象:kubectlgethpaNAMEREFERENCETARGETSMINPODSMAXPODSREPLICASAGEehpasampleappehpaDeploymentsampleapp16m500m110169m 然后我们可以使用ab命令对sampleapp做一次压力测试,正常也可以触发该应用的弹性扩容。kubectlgetsvcsampleappNAMETYPECLUSTERIPEXTERNALIPPORT(S)AGEsampleappNodePort10。104。163。144none80:31941TCP3h59m对nodeport服务做压力测试abc50n2000http:192。168。0。106:31941kubectlgethpaNAMEREFERENCETARGETSMINPODSMAXPODSREPLICASAGEehpasampleappehpaDeploymentsampleapp7291m500m1101071mkubectldescribehpaehpasampleappehpaName:ehpasampleappehpa。。。。。。Metrics:(currenttarget)httprequestsonpods:8350m500mMinreplicas:1Maxreplicas:10Deploymentpods:10current10desiredConditions:TypeStatusReasonMessageAbleToScaleTrueReadyForNewScalerecommendedsizematchescurrentsizeScalingActiveTrueValidMetricFoundtheHPAwasabletosuccessfullycalculateareplicacountfrompodsmetrichttprequestsScalingLimitedTrueTooManyReplicasthedesiredreplicacountismorethanthemaximumreplicacountEvents:TypeReasonAgeFromMessageNormalSuccessfulRescale57shorizontalpodautoscalerNewsize:4;reason:podsmetrichttprequestsabovetargetNormalSuccessfulRescale42shorizontalpodautoscalerNewsize:8;reason:podsmetrichttprequestsabovetargetNormalSuccessfulRescale27shorizontalpodautoscalerNewsize:10;reason:podsmetrichttprequestsabovetarget 我们可以使用如下所示命令来查看EHPA自动生成的HPA对象的资源清单:kubectlgethpa。v2beta2。autoscalingehpasampleappehpaoyamlapiVersion:autoscalingv2beta2kind:HorizontalPodAutoscalermetadata:name:ehpasampleappehpanamespace:defaultspec:maxReplicas:10metrics:pods:metric:name:httprequeststarget:averageValue:500mtype:AverageValuetype:PodsminReplicas:1scaleTargetRef:apiVersion:appsv1kind:Deploymentname:sampleapp。。。。。。省略其他部分 可以观测到已经创建出基于自定义指标预测的Metric:httprequests,由于生产环境的复杂性,基于多指标的弹性(CPUMemory自定义指标)往往是生产应用的常见选择,因此EffectiveHPA通过预测算法覆盖了多指标的弹性,达到了帮助更多业务在生产环境落地水平弹性的成效。 除此之外EHPA对象还支持基于cron的自动缩放,除了基于监控指标,有时节假日和工作日的工作负载流量存在差异,简单的预测算法可能效果不佳。然后可以通过设置周末cron来支持更大数量的副本来弥补预测的不足。对于一些非web流量的应用,比如一些应用不需要在周末使用,可以把工作负载的副本数减少到1,也可以配置cron来降低你的服务成本。QOS增强与混部 除了上面介绍的主要功能之外,crane还具有很多QoS增强功能,QoS相关能力保证了运行在Kubernetes上的Pod的稳定性。crane具有干扰检测和主动回避能力,当较高优先级的Pod受到资源竞争的影响时,DisableSchedule、Throttle以及Evict将应用于低优先级的Pod,以保证节点整体的稳定,目前已经支持节点的cpu内存负载绝对值百分比作为水位线,在发生干扰进行驱逐或压制时,会进行精确计算,将负载降低到略低于水位线即停止操作,防止误伤和过渡操作。 同时,crane还支持自定义指标适配整个干扰检测框架,只需要完成排序定义等一些操作,即可复用包含精确操作在内的干扰检测和回避流程。 此外crane还具有预测算法增强的弹性资源超卖能力,将集群内的空闲资源复用起来,同时结合crane的预测能力,更好地复用闲置资源,当前已经支持cpu和内存的空闲资源回收。同时具有弹性资源限制功能,限制使用弹性资源的workload最大和最小资源使用量,避免对高优业务的影响和饥饿问题。 同时具备增强的旁路cpuset管理能力,在绑核的同时提升资源利用效率。总结 2022年,腾讯云原生FinOpsCrane项目组,结合行业及产业的发展趋势,联动中国产业互联网发展联盟、中国信通院、中国电子节能技术协会、FinOps基金会及中国内外众多生态合作伙伴,开展及推动技术标准、国内联盟、国际开源、双碳升级等多维度的成果落地,输出了系列白皮书和标准指南,旨在助力企业和生态更良性发展和应用先进技术,达成降本增效,节能减排目标方向。 Crane能力全景图 我们可以自己在K8s集群中安装crane来获取这些相关功能,此外这些能力也都会在腾讯云TKE的原生节点产品Housekeeper中提供,新推出的TKEHousekeeper是腾讯云推出的全新K8s运维范式,可以帮助企业像管理Workload一样声明式管理Node节点,高效解决节点维护、资源规划等各种各样的运维问题。 毫无疑问,Crane已经是K8s集群中用于云资源分析和经济的最佳FinOps平台了。目前,腾讯云Crane已进入CNCFLandScape,这意味着Crane已成为云原生领域的重要项目。面向未来,腾讯云还将持续反馈开源社区、共建开源生态,帮助更多企业通过云原生全面释放生产力,加速实现数字化和绿色化双转型。 最后,上周末举行的腾讯云TechoDay技术开放日活动也对Crane进行了深入解析,相关资料及课件被收录进了《腾讯云云原生工具指南》里,除此以外,里面还涵盖了遨驰分布式云操作系统、微服务等多款热门产品的技术原理解读,帮助开发者用专业方法解决业务痛点,还有多个云原生实践标杆案例分享,讲述云原生如何实现价值,非常推荐给感兴趣的朋友下载看看。