专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

腾讯开源云原生成本优化神器FinOpsCrane

  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进行了深入解析,相关资料及课件被收录进了《腾讯云云原生工具指南》里,除此以外,里面还涵盖了遨驰分布式云操作系统、微服务等多款热门产品的技术原理解读,帮助开发者用专业方法解决业务痛点,还有多个云原生实践标杆案例分享,讲述云原生如何实现价值,非常推荐给感兴趣的朋友下载看看。

一首史蒂文斯十三种看乌鸫的方式十三种看乌鸫的方式1hr二十座覆盖着雪的山岭之间唯一移动的是乌鸦的眼睛。2hr我有三颗心,就像一棵树上停着三只乌鸫。3hr乌鸫在秋风中盘旋,它是哑剧中不起眼的角色。4hr一个男人和曾经我的唇是你的吻郝有花(图片来自网络)时间靠左,记忆靠右你的转身在曾经的过往,在断笔里幻化出一首首断肠的情诗那些温暖,柔化了冻僵的心翅那些唯美的瞬间,一去不复返今天,我的心碎成一地,一片一片我从身星云大师遗言大彻大悟的7条语录一位功能无量的佛教大师一位年高寿长的智者,临终留下了一篇文字,总结了一生的经验教训。摘录7条最有共鸣最值得铭记的话1我一生,服膺于给的哲学,总是给人赞叹给人信心,给人欢喜,给人希望2023了,谁才是真正的社会底层?托尔斯泰曾经说过幸福的家庭都是相似的,不幸的家庭各有各的不幸。同样,不同人的人生也都各不相同,有些人功成名就,有些人则沦为了社会的底层。在中国历史上,社会底层人民常常被污名为贱民流女人,如果你不爱化妆,那么恭喜你这个世界,对男人和女人的标准不同,就连评价一对情侣时,都会用到郎才女貌这个词可见,女人的容貌,是她展示给世人的一张最直接的名片岁月不饶人,过去的时光会在女人脸上刻上痕迹,让她们不总管好自己的嘴,不要说这六句话!古人有云君子敏于行而讷于言。说的就是人行动要敏捷,说话要谨慎!有时候沉默是金,不是木讷寡言,恰恰体现的是,一个人的人品和智慧。聪明的人,不会信口开河,尤其是这六句话,不会随心所欲的喜欢网购的人注意了!这些短信一眼假菜乌裹裹?菜茑驿站?菜鸟菓菓这都是些啥?错别字?火星文?菜鸟的兄弟?这口从天而降的大锅菜鸟可不背近期不少诈骗短信傍上了菜鸟打着快递破损邮寄礼品等旗号忽悠消费者加微信打电话进而实施诈为什么唐太宗李世民不喜欢武则天,而唐高宗李治喜欢?武则天,两千余年封建时代时代中唯一一位女皇帝,后人对她的评价亦是毁誉参半。她的一生充满了传奇色彩,其母杨氏曾梦黑龙附体而孕。贞观五年(632年),当时善于替人看相的袁天罡曾为武则天大唐王朝皇室有多么奇葩?大唐王朝,是华夏文明的一个高峰,令四方臣服,万国来朝,天朝上国的思想也是从此而来。我也不得不承认,唐王朝初唐中唐前期时期,确实是如此,但是在唐中期后期安史之乱后,唐朝就光芒不再,对给孩子买书,是买孩子喜欢看的书,还是买家长认为好的书?家长百问百答妈妈,我不喜欢你给买的这些书,我不看,你也不问我就买了,我喜欢历史方面的书!好吧,妈妈下次买书前先问问你再确定买不买!过年前给大宝小宝买了一些书,当然每个月都会买几本。好吃到舔盘的6道家常下饭菜,食材简单开胃解馋,大人孩子都喜欢在日常生活中,你会不会每天回到家为吃什么,做什么菜而发愁呢?或者我们辛辛苦苦做了一桌子饭菜,当端起饭碗,又没了食欲呢?今天月月分享6道家常下饭菜,都是最日常普通的食材,做法也不难,
天津科技大学怎么样?天津科技大学怎么样,哪些专业比较容易就业?这个问题问得好,我查了很多资料,买了很多书,请教了很多人,终于可以回答你这个问题了,希望我的回答可以帮助到你。下面是我的答案,如果有不同意电商平台低价冲击市场,搞倒了实体门店,他们最后会垄断市场吗?电商吃饱,实体吃草。繁华落幕,一地鸡毛!网购的本质是某些人自己来做全国的中间商,造成大量的失业!年轻人大多都在送餐送快递,普通人很难再创业。财富更加集中到少数人手里,而网购的商家都现在滴滴好干吗?过了年准备跑滴滴,大家给点意见?特别好干,单子特别多,早高峰能接三四单,过了早高峰短短一小时就能接到一单,特别好,很轻松,一天流水高达一两百越来越好跑了,尤其是没有双证的非法营运黑网约车更好跑,月入两三万不是梦!为何感觉做网页的不多但是前端却比安卓ios的需求大?因为现在网页开发人员已经不再单纯的只开发网页在Web2。0时代前端开发人员都是往大前端方向发展HTMLCSSJS只是基本功还得需要会VueReactAngular三大框架小程序和W为什么三星沉寂了这么久,华为一衰落网络上就出现了铺天盖地的三星好?谁好,谁坏人民群众心里明白因为你能做的人家也能做,只是时机选择不同而已。你不能做的,人家还能做,这个就是技术掌握的问题了。你哪里看到铺天盖地的宣传三星的好了呢?在自媒体里和官媒里三2021年换什么手机好?推荐红米K30S至尊纪念版手机。推荐理由价格比iQOONeo3和苹果XXR更加具备性价比,同时也不比这两部手机差上多少。手机尺寸重量外观手机尺寸是165。1x76。4x9。33mm王者荣耀想练一个上单英雄,有没有什么推荐?大家好,我是清分很高兴能回答你这个问题。关于王者荣耀上单英雄厉害一点儿的。我所给出的推荐上单这个赛季其实还是算比较重要的吧,因为上单如果崩掉,那其他两路几乎都要凉的感觉。所以说我觉非专业人士想拍鸟,腾龙适马四款150600的镜头该怎样选?适马150600C版拍摄我用腾龙150600,一般手持可以,比适马的轻适马的金属感强点吧,重,价格比腾龙的贵些,我见过没用过。非专业人士想拍鸟,用什么镜头比较好呢?其实,这个问题,有多少人还在用三星手机?本人NOTE8一直在用感谢您的阅读!我之前使用过两部三星手机。第1部是一款全键盘的三星手机采用的是windows系统。第2部是一款被称为神机的三星Galaxy9100。这两部手机对荣耀magic3和小米13,该选哪个?小米13好像还没出捂脸,选荣耀吧,Magic影像,卓尔不凡,相机配置方面,荣耀Magic3Pro搭载了一颗5000万像素主摄(11。56英寸大底)一颗6400万像素黑白镜头一颗64英雄联盟中的缚地效果能有什么用,是不是真的很弱智?英雄联盟缚地是诸多控制技能中的一种,它可以让范围内的敌方英雄减速并且不可以适合位移技能,在控制技能中还是很强大的!缚地效果你看起来可能一般但当你走进里面之后你就会大呼恶心,因为在这
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网