当前位置: 首页 > article >正文

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践

在 Kubernetes 集群中,如何在保障应用高可用的同时有效地管理资源,一直是运维人员和开发者关注的重点。随着微服务架构的普及,集群内各个服务的负载波动日趋明显,传统的手动扩缩容方式已无法满足实时性和弹性需求。

Cluster Autoscaler(简称 CA) 作为 Kubernetes 官方提供的自动伸缩组件,通过监控调度器中未能调度的 Pod,并自动调整节点数量,为集群资源的动态调配提供了一种高效解决方案。

Kubernetes 的自动伸缩分为三个维度:

  • Pod 级别:Horizontal Pod Autoscaler (HPA) 根据 CPU/内存等指标调整 Pod 副本数;
  • 节点级别:Cluster Autoscaler (CA) 动态调整集群节点数量;
  • 资源粒度:Vertical Pod Autoscaler (VPA) 动态调整 Pod 的 Request/Limit。

本篇文章,就来详细介绍下 CA 的原理和实践。

Cluster Autoscaler 工作原理

CA 抽象出了一个 NodeGroup 的概念,与之对应的是云厂商的伸缩组服务。CA 通过 CloudProvider 提供的 NodeGroup 计算集群内节点资源,以此来进行伸缩。

CA 启动后,CA 会定期(默认 10s)检查未调度的 Pod 和 Node 的资源使用情况,并进行相应的 Scale UP 和 Scale Down 操作。

CA 由以下几个模块组成:

  • autoscaler: 核心模块,负责整体扩缩容功能;
  • estimator: 负责评估计算扩容节点;
  • simulator: 负责模拟调度,计算缩容节点;
  • CA cloud-provider: 与云交互进行节点的增删操作。社区目前仅支持AWS和GCE,其他云厂商需要自己实现CloudProvider和NodeGroup相关接口。

CA的架构如下:

接下来,我们来看下 CA 的扩缩容时的具体工作流程(原理)。

扩容原理(Scale UP)

当 Cluster Autoscaler 发现有 Pod 由于资源不足而无法调度时,就会通过调用 Scale UP 执行扩容操作。

CA 扩容时会根据扩容策略,选择合适的 NodeGroup。为了业务需要,集群中可能会有不同规格的 Node,我们可以创建多个 NodeGroup,在扩容时会根据 --expander 选项配置指定的策略,选择一个扩容的节点组,支持如下五种策略:

  • random: 随机选择一个 NodeGroup。如果未指定,则默认为此策略;
  • most-pods: 选择能够调度最多 Pod 的 NodeGroup,比如有的 Pod 未调度是因为 nodeSelector,此策略会优先选择能满足的 NodeGroup 来保证大多数的 Pod 可以被调度;
  • least-waste: 为避免浪费,此策略会优先选择能满足 Pod 需求资源的最小资源类型的 NodeGroup。
  • price: 根据 CloudProvider 提供的价格模型,选择最省钱的 NodeGroup;
  • priority: 通过配置优先级来进行选择,用起来比较麻烦,需要额外的配置,可以看Priority based expander for cluster-autoscaler。

如果有需要,也可以平衡相似 NodeGroup 中的 Node 数量,避免 NodeGroup 达到 MaxSize 而导致无法加入新 Node。通过 --balance-similar-node-groups 选项配置,默认为 false

再经过一系列的操作后,最终计算出要扩容的 Node 数量及 NodeGroup,使用 CloudProvider 执行 IncreaseSize 操作,增加云厂商的伸缩组大小,从而完成扩容操作。

CA 扩容流程

Cluster Autoscaler 的扩容核心在于对集群资源的实时监控和决策,其主要工作流程如下:

  1. 监控未调度的 Pod: 当 Kubernetes 调度器发现某个 Pod 因为资源不足而无法被调度到现有节点时,CA 会监测到因无法调度而 Pending 的 Pod,进而触发 CA 扩容操作。CA 扩容的触发条件如下:
    • Pod 因资源不足(CPU/Memory/GPU)无法调度;
    • Pod 因节点选择器(NodeSelector)、亲和性(Affinity)或污点容忍(Tolerations)不匹配无法调度;
    • 节点资源碎片化导致无法容纳 Pod(例如剩余资源分散在不同节点)。
  2. 节点模板选择: CA 根据每个节点池的节点模板进行调度判断,挑选合适的节点模板。若有多个模板合适,即有多个可扩的节点池备选,CA 会调用 expanders 从多个模板挑选最优模板并对对应节点池进行扩容。选择了 NodeGroup 之后,便会调用云平台的 API 创建新的节点,并加入到集群中。

  1. 节点加入与 Pod 调度: 新增节点加入后,调度器重新调度之前未能分配的 Pod,满足业务需求。
CA ScaleUp 源码剖析

CA 扩容时调用,ScaleUp 的源码剖析如下:

 
func ScaleUp(context *context.AutoscalingContext, processors *ca_processors.AutoscalingProcessors, clusterStateRegistry *clusterstate.ClusterStateRegistry, unschedulablePods []*apiv1.Pod, nodes []*apiv1.Node, daemonSets []*appsv1.DaemonSet, nodeInfos map[string]*schedulernodeinfo.NodeInfo, ignoredTaints taints.TaintKeySet) (*status.ScaleUpStatus, errors.AutoscalerError) {......// 验证当前集群中所有 ready node 是否来自于 nodeGroups,取得所有非组内的 nodenodesFromNotAutoscaledGroups, err := utils.FilterOutNodesFromNotAutoscaledGroups(nodes, context.CloudProvider)if err != nil {return &status.ScaleUpStatus{Result: status.ScaleUpError}, err.AddPrefix("failed to filter out nodes which are from not autoscaled groups: ")}nodeGroups := context.CloudProvider.NodeGroups()gpuLabel := context.CloudProvider.GPULabel()availableGPUTypes := context.CloudProvider.GetAvailableGPUTypes()// 资源限制对象,会在 build cloud provider 时传入// 如果有需要可在 CloudProvider 中自行更改,但不建议改动,会对用户造成迷惑resourceLimiter, errCP := context.CloudProvider.GetResourceLimiter()if errCP != nil {return &status.ScaleUpStatus{Result: status.ScaleUpError}, errors.ToAutoscalerError(errors.CloudProviderError,errCP)}// 计算资源限制// nodeInfos 是所有拥有节点组的节点与示例节点的映射// 示例节点会优先考虑真实节点的数据,如果 NodeGroup 中还没有真实节点的部署,则使用 Template 的节点数据scaleUpResourcesLeft, errLimits := computeScaleUpResourcesLeftLimits(context.CloudProvider, nodeGroups, nodeInfos, nodesFromNotAutoscaledGroups, resourceLimiter)if errLimits != nil {return &status.ScaleUpStatus{Result: status.ScaleUpError}, errLimits.AddPrefix("Could not compute total resources: ")}// 根据当前节点与 NodeGroups 中的节点来计算会有多少节点即将加入集群中// 由于云服务商的伸缩组 increase size 操作并不是同步加入 node,所以将其统计,以便于后面计算节点资源upcomingNodes := make([]*schedulernodeinfo.NodeInfo, 0)for nodeGroup, numberOfNodes := range clusterStateRegistry.GetUpcomingNodes() {......}klog.V(4).Infof("Upcoming %d nodes", len(upcomingNodes))// 最终会进入选择的节点组expansionOptions := make(map[string]expander.Option, 0)......// 出于某些限制或错误导致不能加入新节点的节点组,例如节点组已达到 MaxSizeskippedNodeGroups := map[string]status.Reasons{}// 综合各种情况,筛选出节点组for _, nodeGroup := range nodeGroups {......}if len(expansionOptions) == 0 {klog.V(1).Info("No expansion options")return &status.ScaleUpStatus{Result:                 status.ScaleUpNoOptionsAvailable,PodsRemainUnschedulable: getRemainingPods(podEquivalenceGroups, skippedNodeGroups),ConsideredNodeGroups:   nodeGroups,}, nil}......// 选择一个最佳的节点组进行扩容,expander 用于选择一个合适的节点组进行扩容,默认为 RandomExpander,flag: expander// random 随机选一个,适合只有一个节点组// most-pods 选择能够调度最多 pod 的节点组,比如有 noSchedulerPods 是有 nodeSelector 的,它会优先选择此类节点组以满足大多数 pod 的需求// least-waste 优先选择能满足 pod 需求资源的最小资源类型的节点组// price 根据价格模型,选择最省钱的// priority 根据优先级选择bestOption := context.ExpanderStrategy.BestOption(options, nodeInfos)if bestOption != nil && bestOption.NodeCount > 0 {......newNodes := bestOption.NodeCount// 考虑到 upcomingNodes, 重新计算本次新加入节点if context.MaxNodesTotal > 0 && len(nodes)+newNodes+len(upcomingNodes) > context.MaxNodesTotal {klog.V(1).Infof("Capping size to max cluster total size (%d)", context.MaxNodesTotal)newNodes = context.MaxNodesTotal - len(nodes) - len(upcomingNodes)if newNodes < 1 {return &status.ScaleUpStatus{Result: status.ScaleUpError}, errors.NewAutoscalerError(errors.TransientError,"max node total count already reached")}}createNodeGroupResults := make([]nodegroups.CreateNodeGroupResult, 0)// 如果节点组在云服务商端处不存在,会尝试创建根据现有信息重新创建一个云端节点组// 但是目前所有的 CloudProvider 实现都没有允许这种操作,这好像是个多余的方法// 云服务商不想,也不应该将云端节点组的创建权限交给 ClusterAutoscalerif !bestOption.NodeGroup.Exist() {oldId := bestOption.NodeGroup.Id()createNodeGroupResult, err := processors.NodeGroupManager.CreateNodeGroup(context, bestOption.NodeGroup)......}// 得到最佳节点组的示例节点nodeInfo, found := nodeInfos[bestOption.NodeGroup.Id()]if !found {// This should never happen, as we already should have retrieved// nodeInfo for any considered nodegroup.klog.Errorf("No node info for: %s", bestOption.NodeGroup.Id())return &status.ScaleUpStatus{Result: status.ScaleUpError, CreateNodeGroupResults: createNodeGroupResults}, errors.NewAutoscalerError(errors.CloudProviderError,"No node info for best expansion option!")}// 根据 CPU、Memory及可能存在的 GPU 资源(hack: we assume anything which is not cpu/memory to be a gpu.),计算出需要多少个 NodesnewNodes, err = applyScaleUpResourcesLimits(context.CloudProvider, newNodes, scaleUpResourcesLeft, nodeInfo, bestOption.NodeGroup, resourceLimiter)if err != nil {return &status.ScaleUpStatus{Result: status.ScaleUpError, CreateNodeGroupResults: createNodeGroupResults}, err}// 需要平衡的节点组targetNodeGroups := []cloudprovider.NodeGroup{bestOption.NodeGroup}// 如果需要平衡节点组,根据 balance-similar-node-groups flag 设置。// 检测相似的节点组,并平衡它们之间的节点数量if context.BalanceSimilarNodeGroups {......}// 具体平衡策略可以看 (b *BalancingNodeGroupSetProcessor) BalanceScaleUpBetweenGroups 方法scaleUpInfos, typedErr := processors.NodeGroupSetProcessor.BalanceScaleUpBetweenGroups(context, targetNodeGroups, newNodes)if typedErr != nil {return &status.ScaleUpStatus{Result: status.ScaleUpError, CreateNodeGroupResults: createNodeGroupResults}, typedErr}klog.V(1).Infof("Final scale-up plan: %v", scaleUpInfos)// 开始扩容,通过 IncreaseSize 扩容for _, info := range scaleUpInfos {typedErr := executeScaleUp(context, clusterStateRegistry, info, gpu.GetGpuTypeForMetrics(gpuLabel, availableGPUTypes, nodeInfo.Node(), nil), now)if typedErr != nil {return &status.ScaleUpStatus{Result: status.ScaleUpError, CreateNodeGroupResults: createNodeGroupResults}, typedErr}}......}......
}

缩容原理(Scale Down)

缩容是一个可选的功能,通过 --scale-down-enabled 选项配置,默认为 true。在 CA 监控 Node 资源时,如果发现有 Node 满足以下三个条件时,就会标记这个 Node 为 unneeded

  • Node 上运行的所有的 Pod 的 CPU 和内存之和小于该 Node 可分配容量的 50%。可通过 --scale-down-utilization-threshold 选项改变这个配置;
  • Node 上所有的 Pod 都可以被调度到其他节点;
  • Node 没有表示不可缩容的 annotaition

如果一个 Node 被标记为 unneeded 超过 10 分钟(可通过 --scale-down-unneeded-time 选项配置),则使用 CloudProvider 执行 DeleteNodes 操作将其删除。一次最多删除一个 unneeded Node,但空 Node 可以批量删除,每次最多删除 10 个(通过 --max-empty-bulk-delete 选项配置)。

实际上并不是只有这一个判定条件,还会有其他的条件来阻止删除这个 Node,比如 NodeGroup 已达到 MinSize,或在过去的 10 分钟内有过一次 Scale UP 操作(通过 --scale-down-delay-after-add 选项配置)等等,更详细可查看 How does scale-down work?。

在决定缩容前,CA 会通过调度器模拟 Pod 迁移过程,确保其他节点有足够资源接收被迁移的 Pod。若模拟失败(如资源不足或亲和性冲突),则放弃缩容。

CA 缩容流程
  1. CA 监测到分配率(即 Request 值,取 CPU 分配率和 MEM 分配率的最大值)低于设定的节点。计算分配率时,可以设置 Daemonset 类型不计入 Pod 占用资源;
  2. CA 判断集群的状态是否可以触发缩容,需要满足如下要求:
    • 节点利用率低于阈值(默认 50%);
    • 节点上所有 Pod 均能迁移到其他节点(包括容忍 PDB 约束);
    • 节点持续空闲时间超过 scale-down-unneeded-time(默认 10 分钟)。
  3. CA 判断该节点是否符合缩容条件。可以按需设置以下不缩容条件(满足条件的节点不会被 CA 缩容):
    • 节点上有 pod 被 PodDisruptionBudget 控制器限制;
    • 节点上有命名空间是 kube-system 的 pods;
    • 节点上的 pod 不是被控制器创建,例如不是被 deployment, replica set, job, statefulset 创建;
    • 节点上有 pod 使用了本地存储;
    • 节点上 pod 驱逐后无处可去,即没有其他node能调度这个 pod;
    • 节点有注解:"cluster-autoscaler.kubernetes.io/scale-down-disabled": "true"(在 CA 1.0.3 或更高版本中受支持)。
  4. CA 驱逐节点上的 Pod 后释放/关机节点。
    • 完全空闲节点可并发缩容(可设置最大并发缩容数);
    • 非完全空闲节点逐个缩容。

Cluster Autoscaler 在缩容时会检查 PodDisruptionBudget (PDB),确保驱逐 Pod 不会违反最小可用副本数约束。若 Pod 受 PDB 保护且驱逐可能导致违反约束,则该节点不会被缩容。

与云服务提供商的集成

Cluster Autoscaler 原生支持多个主流云平台,如 AWS、GCP、Azure 等。它通过调用云服务 API 来实现节点的创建和销毁。实践中需要注意:

  • 认证与权限: 确保 Cluster Autoscaler 拥有足够的权限调用云平台的相关 API,通常需要配置相应的 IAM 角色或 API 密钥。
  • 节点组配置: 集群内通常会预先划分多个节点组,每个节点组对应不同的资源规格和用途。在扩缩容决策时,Autoscaler 会根据 Pod 的资源需求选择最合适的节点组。
  • 多节点组配置示例(以 AWS 为例):
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
nodeGroups:- name: ng-spotinstanceType: m5.largespot: trueminSize: 0maxSize: 10labels: node-type: spot- name: ng-on-demandinstanceType: m5.xlargeminSize: 1maxSize: 5labels:node-type: on-demand

通过标签区分节点组,CA 可根据 Pod 的 nodeSelector 选择扩缩容目标组。

  • 混合云注意事项: 若集群跨公有云和本地数据中心,需确保 CA 仅管理云上节点组,避免误删物理节点。可通过注释排除本地节点组:
metadata:annotations:cluster-autoscaler.kubernetes.io/scale-down-disabled: "true"

如何实现 CloudProvider?

如果使用上述中已实现接入的云厂商,只需要通过 --cloud-provider 选项指定来自哪个云厂商就可以,如果想要对接自己的 IaaS 或有特定的业务逻辑,就需要自己实现 CloudProvider Interface 与 NodeGroup Interface。并将其注册到 builder 中,用于通过 --cloud-provider 参数指定。

builder 在 cloudprovider/builder 中的 builder_all.go 中注册,也可以在其中新建一个自己的 build,通过 go 文件的 +build 编译参数来指定使用的 CloudProvider。

CloudProvider 接口与 NodeGroup 接口在 cloud_provider.go 中定义,其中需要注意的是 Refresh 方法,它会在每一次循环(默认 10 秒)的开始时调用,可在此时请求接口并刷新 NodeGroup 状态,通常的做法是增加一个 manager 用于管理状态。有不理解的部分可参考其他 CloudProvider 的实现。

type CloudProvider interface {// Name returns name of the cloud provider.Name() string// NodeGroups returns all node groups configured for this cloud provider.// 会在一此循环中多次调用此方法,所以不适合每次都请求云厂商服务,可以在 Refresh 时存储状态NodeGroups() []NodeGroup// NodeGroupForNode returns the node group for the given node, nil if the node// should not be processed by cluster autoscaler, or non-nil error if such// occurred. Must be implemented.// 同上NodeGroupForNode(*apiv1.Node) (NodeGroup, error)// Pricing returns pricing model for this cloud provider or error if not available.// Implementation optional.// 如果不使用 price expander 就可以不实现此方法Pricing() (PricingModel, errors.AutoscalerError)// GetAvailableMachineTypes get all machine types that can be requested from the cloud provider.// Implementation optional.// 没用,不需要实现GetAvailableMachineTypes() ([]string, error)// NewNodeGroup builds a theoretical node group based on the node definition provided. The node group is not automatically// created on the cloud provider side. The node group is not returned by NodeGroups() until it is created.// Implementation optional.// 通常情况下,不需要实现此方法,但如果你需要 ClusterAutoscaler 创建一个默认的 NodeGroup 的话,也可以实现。// 但其实更好的做法是将默认 NodeGroup 写入云端的伸缩组NewNodeGroup(machineType string, labels map[string]string, systemLabels map[string]string,taints []apiv1.Taint, extraResources map[string]resource.Quantity) (NodeGroup, error)// GetResourceLimiter returns struct containing limits (max, min) for resources (cores, memory etc.).// 资源限制对象,会在 build 时传入,通常情况下不需要更改,除非在云端有显示的提示用户更改的地方,否则使用时会迷惑用户GetResourceLimiter() (*ResourceLimiter, error)// GPULabel returns the label added to nodes with GPU resource.// GPU 相关,如果集群中有使用 GPU 资源,需要返回对应内容。 hack: we assume anything which is not cpu/memory to be a gpu.GPULabel() string// GetAvailableGPUTypes return all available GPU types cloud provider supports.// 同上GetAvailableGPUTypes() map[string]struct{}// Cleanup cleans up open resources before the cloud provider is destroyed, i.e. go routines etc.// CloudProvider 只会在启动时被初始化一次,如果每次循环后有需要清除的内容,在这里处理Cleanup() error// Refresh is called before every main loop and can be used to dynamically update cloud provider state.// In particular the list of node groups returned by NodeGroups can change as a result of CloudProvider.Refresh().// 会在 StaticAutoscaler RunOnce 中被调用Refresh() error
}// NodeGroup contains configuration info and functions to control a set
// of nodes that have the same capacity and set of labels.
type NodeGroup interface {// MaxSize returns maximum size of the node group.MaxSize() int// MinSize returns minimum size of the node group.MinSize() int// TargetSize returns the current target size of the node group. It is possible that the// number of nodes in Kubernetes is different at the moment but should be equal// to Size() once everything stabilizes (new nodes finish startup and registration or// removed nodes are deleted completely). Implementation required.// 响应的是伸缩组的节点数,并不一定与 kubernetes 中的节点数保持一致TargetSize() (int, error)// IncreaseSize increases the size of the node group. To delete a node you need// to explicitly name it and use DeleteNode. This function should wait until// node group size is updated. Implementation required.// 扩容的方法,增加伸缩组的节点数IncreaseSize(delta int) error// DeleteNodes deletes nodes from this node group. Error is returned either on// failure or if the given node doesn't belong to this node group. This function// should wait until node group size is updated. Implementation required.// 删除的节点一定要在该节点组中DeleteNodes([]*apiv1.Node) error// DecreaseTargetSize decreases the target size of the node group. This function// doesn't permit to delete any existing node and can be used only to reduce the// request for new nodes that have not been yet fulfilled. Delta should be negative.// It is assumed that cloud provider will not delete the existing nodes when there// is an option to just decrease the target. Implementation required.// 当 ClusterAutoscaler 发现 kubernetes 节点数与伸缩组的节点数长时间不一致,会调用此方法来调整DecreaseTargetSize(delta int) error// Id returns an unique identifier of the node group.Id() string// Debug returns a string containing all information regarding this node group.Debug() string// Nodes returns a list of all nodes that belong to this node group.// It is required that Instance objects returned by this method have Id field set.// Other fields are optional.// This list should include also instances that might have not become a kubernetes node yet.// 返回伸缩组中的所有节点,哪怕它还没有成为 kubernetes 的节点Nodes() ([]Instance, error)// TemplateNodeInfo returns a schedulernodeinfo.NodeInfo structure of an empty// (as if just started) node. This will be used in scale-up simulations to// predict what would a new node look like if a node group was expanded. The returned// NodeInfo is expected to have a fully populated Node object, with all of the labels,// capacity and allocatable information as well as all pods that are started on// the node by default, using manifest (most likely only kube-proxy). Implementation optional.// ClusterAutoscaler 会将节点信息与节点组对应,来判断资源条件,如果是一个空的节点组,那么就会通过此方法来虚拟一个节点信息。TemplateNodeInfo() (*schedulernodeinfo.NodeInfo, error)// Exist checks if the node group really exists on the cloud provider side. Allows to tell the// theoretical node group from the real one. Implementation required.Exist() bool// Create creates the node group on the cloud provider side. Implementation optional.// 与 CloudProvider.NewNodeGroup 配合使用Create() (NodeGroup, error)// Delete deletes the node group on the cloud provider side.// This will be executed only for autoprovisioned node groups, once their size drops to 0.// Implementation optional.Delete() error// Autoprovisioned returns true if the node group is autoprovisioned. An autoprovisioned group// was created by CA and can be deleted when scaled to 0.Autoprovisioned() bool
}

实践中的常见问题与最佳实践

部署与配置

  • 安装方式: Cluster Autoscaler 可以通过 Helm Chart 或直接使用官方提供的 YAML 清单进行部署。安装完成后,建议结合日志和监控系统,对其运行状态进行持续观察;
  • 关键参数配置: 根据集群规模和业务需求,合理配置参数非常关键。例如:
    • --scale-down-delay-after-add:设定新增节点后多久开始进行缩容判断;
    • --max-node-provision-time:控制节点从请求到成功加入集群的最长时间。
  • 日志与监控: 建议将 Autoscaler 的日志与集群监控系统(如 Prometheus)集成,以便及时发现和解决问题;
  • 关键参数详解:
参数默认值说明
--scale-down-delay-after-add10m扩容后等待多久开始缩容判断
-scale-down-unneeded-time10m节点持续空闲多久后触发缩容
--expanderrandom扩容策略(支持 priority, most-pods, least-waste)
--skip-nodes-with-local-storagetrue跳过含本地存储的节点缩容
  • 资源请求(Request)的重要性: CA 完全依赖 Pod 的 resources.requests 计算节点资源需求。若未设置 Request 或设置过低,可能导致:
    • 扩容决策错误(节点资源不足);
    • 缩容激进(误判节点利用率低)。

建议结合 VPA 或人工审核确保 Request 合理。

常见问题

  • Pod 长时间处于等待状态: 可能是由于资源请求过高或节点配置不足,建议检查 Pod 定义和节点组资源规格是否匹配;
  • 节点频繁扩缩容: 这种情况可能导致集群不稳定。通过调整缩容延迟和扩容策略,可以避免频繁的节点创建和销毁;
  • 云平台 API 限额: 在大规模伸缩场景下,需注意云服务商对 API 调用的限额,合理配置重试和等待机制;
  • DaemonSet Pod 阻碍缩容: 若节点仅运行 DaemonSet Pod(如日志收集组件),默认情况下 CA 不会缩容该节点。可通过以下注解允许缩容:
kind: DaemonSet
metadata:annotations:cluster-autoscaler.kubernetes.io/daemonset-taint-eviction: "true"
  • 僵尸节点(Zombie Node)问题: 若云平台 API 返回节点已删除但 Kubernetes 未更新状态,CA 会持续尝试缩容。可通过 --node-deletion-retries(默认 3)控制重试次数。

最佳实践

  • 与 HPA 结合: 将 CA 与 HPA 联合使用,可以实现从 Pod 级别到节点级别的全方位自动扩缩,提升资源利用率和集群弹性。HPA 会根据当前 CPU 负载更改部署或副本集的副本数。如果负载增加,则 HPA 将创建新的副本,集群中可能有足够的空间,也可能没有足够的空间。如果没有足够的资源,CA 将尝试启动一些节点,以便 HPA 创建的 Pod 可以运行。如果负载减少,则 HPA 将停止某些副本。结果,某些节点可能变得利用率过低或完全为空,然后 CA 将终止这些不需要的节点;
  • 定期评估和调整配置: 根据实际业务负载和集群运行情况,定期回顾和优化 Autoscaler 的配置,确保扩缩容策略始终符合当前需求;
  • 充分测试: 在生产环境部署前,建议在测试环境中模拟高负载和低负载场景,对扩缩容逻辑进行充分验证,避免意外情况影响业务;
  • 成本优化策略:
    • 使用 Spot 实例节点组:通过多 AZ 和实例类型分散中断风险;
    • 设置 --expander=priority:为成本更低的节点组分配更高优先级;
    • 启用 --balance-similar-node-groups:均衡相似节点组的节点数量。
  • 稳定性保障:
    • 为关键组件(如 Ingress Controller)设置 Pod 反亲和性,避免单点故障;
    • 使用 podDisruptionBudget 防止缩容导致服务不可用:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:name: zk-pdb
spec:minAvailable: 2selector:matchLabels:app: zookeeper

案例分享

以某大型电商平台为例,该平台在促销期间流量激增,通过配置 Cluster Autoscaler,实现了在高峰期自动扩容,而在流量恢复正常后及时缩容。实践中,他们不仅调整了扩缩容相关的时间参数,还结合应用流量监控,提前预估负载变化,确保集群资源始终处于最优状态。通过这种自动化手段,既保证了业务的高可用性,也大幅降低了运维成本。

  • 案例补充: 某金融公司未配置 podDisruptionBudget,导致缩容时 Kafka Pod 同时被驱逐,引发消息堆积。解决方案:
    1. 为 Kafka 设置 minAvailable: 2 的 PDB;
    2. 调整 scale-down-delay-after-add 至 30 分钟,避免促销后立即缩容
  • 参数调优示例:
# 生产环境推荐配置(兼顾响应速度与稳定性)
command:- ./cluster-autoscaler- --v=4- --stderrthreshold=info- --cloud-provider=aws- --skip-nodes-with-local-storage=false- --expander=least-waste- --scale-down-delay-after-add=20m- --scale-down-unneeded-time=15m--balance-similar-node-groups=true

总结

Kubernetes Cluster Autoscaler 为集群的自动伸缩提供了一种高效、智能的解决方案。通过对未调度 Pod 的实时监控和云平台 API 的调用,Cluster Autoscaler 能够根据实际负载动态调整集群规模,实现资源的按需分配。结合实际生产环境中的部署经验和最佳实践,合理配置和调优 Autoscaler,不仅可以提升集群的弹性,还能有效降低运维成本。随着云原生生态系统的不断发展,Cluster Autoscaler 也在不断演进,未来将为更复杂的场景提供更加完善的支持。

未来演进方向:

  • 预测性伸缩: 基于历史负载预测资源需求;
  • GPU 弹性调度: 支持动态创建/释放 GPU 节点;
  • 多集群协同: 跨集群资源池化,实现全局弹性。

往期文章回顾

  • 带你从0到1部署一个功能完备、生产可用的Kubernetes集群
  • 如何开发一个企业级的 LLMOps(智能体) 平台?
  • 如何在业务开发中引入声明式 API 编程模式
  • 知识星球:云原生AI实战营。10+ 高质量体系课( Go、云原生、AI Infra)、15+ 实战项目,P8 技术专家助你提高技术天花板,入大厂拿高薪;
  • 公众号:令飞编程,分享 Go、云原生、AI Infra 相关技术。回复「资料」免费下载 Go、云原生、AI 等学习资料;
  • 哔哩哔哩:令飞编程 ,分享技术、职场、面经等,并有免费直播课「云原生AI高新就业课」,大厂级项目实战到大厂面试通关;

相关文章:

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践

在 Kubernetes 集群中&#xff0c;如何在保障应用高可用的同时有效地管理资源&#xff0c;一直是运维人员和开发者关注的重点。随着微服务架构的普及&#xff0c;集群内各个服务的负载波动日趋明显&#xff0c;传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...

【深度学习新浪潮】什么是credit assignment problem?

Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...

阿里云Ubuntu 22.04 64位搭建Flask流程(亲测)

cd /home 进入home盘 安装虚拟环境&#xff1a; 1、安装virtualenv pip install virtualenv 2.创建新的虚拟环境&#xff1a; virtualenv myenv 3、激活虚拟环境&#xff08;激活环境可以在当前环境下安装包&#xff09; source myenv/bin/activate 此时&#xff0c;终端…...

面试高频问题

文章目录 &#x1f680; 消息队列核心技术揭秘&#xff1a;从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"&#xff1f;性能背后的秘密1.1 顺序写入与零拷贝&#xff1a;性能的双引擎1.2 分区并行&#xff1a;数据的"八车道高速公路"1.3 页缓存与批量处理…...

xmind转换为markdown

文章目录 解锁思维导图新姿势&#xff1a;将XMind转为结构化Markdown 一、认识Xmind结构二、核心转换流程详解1.解压XMind文件&#xff08;ZIP处理&#xff09;2.解析JSON数据结构3&#xff1a;递归转换树形结构4&#xff1a;Markdown层级生成逻辑 三、完整代码 解锁思维导图新…...

【WebSocket】SpringBoot项目中使用WebSocket

1. 导入坐标 如果springboot父工程没有加入websocket的起步依赖&#xff0c;添加它的坐标的时候需要带上版本号。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dep…...

恶补电源:1.电桥

一、元器件的选择 搜索并选择电桥&#xff0c;再multisim中选择FWB&#xff0c;就有各种型号的电桥: 电桥是用来干嘛的呢&#xff1f; 它是一个由四个二极管搭成的“桥梁”形状的电路&#xff0c;用来把交流电&#xff08;AC&#xff09;变成直流电&#xff08;DC&#xff09;。…...

论文阅读:Matting by Generation

今天介绍一篇关于 matting 抠图的文章&#xff0c;抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法&#xff0c;已经有很多的工作和这个任务相关。这两年 diffusion 模型很火&#xff0c;大家又开始用 diffusion 模型做各种 CV 任务了&am…...

Neko虚拟浏览器远程协作方案:Docker+内网穿透技术部署实践

前言&#xff1a;本文将向开发者介绍一款创新性协作工具——Neko虚拟浏览器。在数字化协作场景中&#xff0c;跨地域的团队常需面对实时共享屏幕、协同编辑文档等需求。通过本指南&#xff0c;你将掌握在Ubuntu系统中使用容器化技术部署该工具的具体方案&#xff0c;并结合内网…...

实战设计模式之模板方法模式

概述 模板方法模式定义了一个操作中的算法骨架&#xff0c;并将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的前提下&#xff0c;重新定义算法中的某些步骤。简单来说&#xff0c;就是在一个方法中定义了要执行的步骤顺序或算法框架&#xff0c;但允许子类…...

【Linux】Linux安装并配置RabbitMQ

目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的&#xff0c;需要先安…...

Python训练营-Day26-函数专题1:函数定义与参数

题目1&#xff1a;计算圆的面积 任务&#xff1a; 编写一个名为 calculate_circle_area 的函数&#xff0c;该函数接收圆的半径 radius 作为参数&#xff0c;并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求&#xff1a;函数接收一个位置参数 radi…...

【Kafka】Kafka从入门到实战:构建高吞吐量分布式消息系统

Kafka从入门到实战:构建高吞吐量分布式消息系统 一、Kafka概述 Apache Kafka是一个分布式流处理平台,最初由LinkedIn开发,后成为Apache顶级项目。它被设计用于高吞吐量、低延迟的消息处理,能够处理来自多个生产者的海量数据,并将这些数据实时传递给消费者。 Kafka核心特…...

Android写一个捕获全局异常的工具类

项目开发和实际运行过程中难免会遇到异常发生&#xff0c;系统提供了一个可以捕获全局异常的工具Uncaughtexceptionhandler&#xff0c;它是Thread的子类&#xff08;就是package java.lang;里线程的Thread&#xff09;。本文将利用它将设备信息、报错信息以及错误的发生时间都…...

人工智能 - 在Dify、Coze、n8n、FastGPT和RAGFlow之间做出技术选型

在Dify、Coze、n8n、FastGPT和RAGFlow之间做出技术选型。这些平台各有侧重&#xff0c;适用场景差异显著。下面我将从核心功能定位、典型应用场景、真实体验痛点、选型决策关键点进行拆解&#xff0c;并提供具体场景下的推荐方案。 一、核心功能定位速览 平台核心定位技术栈亮…...

绕过 Xcode?使用 Appuploader和主流工具实现 iOS 上架自动化

iOS 应用的发布流程一直是开发链路中最“苹果味”的环节&#xff1a;强依赖 Xcode、必须使用 macOS、各种证书和描述文件配置……对很多跨平台开发者来说&#xff0c;这一套流程并不友好。 特别是当你的项目主要在 Windows 或 Linux 下开发&#xff08;例如 Flutter、React Na…...

C++_哈希表

本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、基础概念 1. 哈希核心思想&#xff1a; 哈希函数的作用&#xff1a;通过此函数建立一个Key与存储位置之间的映射关系。理想目标&#xff1a;实现…...

WEB3全栈开发——面试专业技能点P7前端与链上集成

一、Next.js技术栈 ✅ 概念介绍 Next.js 是一个基于 React 的 服务端渲染&#xff08;SSR&#xff09;与静态网站生成&#xff08;SSG&#xff09; 框架&#xff0c;由 Vercel 开发。它简化了构建生产级 React 应用的过程&#xff0c;并内置了很多特性&#xff1a; ✅ 文件系…...

若依登录用户名和密码加密

/*** 获取公钥&#xff1a;前端用来密码加密* return*/GetMapping("/getPublicKey")public RSAUtil.RSAKeyPair getPublicKey() {return RSAUtil.rsaKeyPair();}新建RSAUti.Java package com.ruoyi.common.utils;import org.apache.commons.codec.binary.Base64; im…...

Linux 下 DMA 内存映射浅析

序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存&#xff0c;但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程&#xff0c;可以参考这篇文章&#xff0c;我觉得写的非常…...

第八部分:阶段项目 6:构建 React 前端应用

现在&#xff0c;是时候将你学到的 React 基础知识付诸实践&#xff0c;构建一个简单的前端应用来模拟与后端 API 的交互了。在这个阶段&#xff0c;你可以先使用模拟数据&#xff0c;或者如果你的后端 API&#xff08;阶段项目 5&#xff09;已经搭建好&#xff0c;可以直接连…...

6️⃣Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙

Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙 一、前言:离区块链还有多远? 区块链听起来可能遥不可及,似乎是只有密码学专家和资深工程师才能涉足的领域。但事实上,构建一个区块链的核心并不复杂,尤其当你已经掌握了一门系统编程语言,比如 Go。 要真正理解区…...

macOS 终端智能代理检测

&#x1f9e0; 终端智能代理检测&#xff1a;自动判断是否需要设置代理访问 GitHub 在开发中&#xff0c;使用 GitHub 是非常常见的需求。但有时候我们会发现某些命令失败、插件无法更新&#xff0c;例如&#xff1a; fatal: unable to access https://github.com/ohmyzsh/oh…...

前端高频面试题2:浏览器/计算机网络

本专栏相关链接 前端高频面试题1&#xff1a;HTML/CSS 前端高频面试题2&#xff1a;浏览器/计算机网络 前端高频面试题3&#xff1a;JavaScript 1.什么是强缓存、协商缓存&#xff1f; 强缓存&#xff1a; 当浏览器请求资源时&#xff0c;首先检查本地缓存是否命中。如果命…...

智能职业发展系统:AI驱动的职业规划平台技术解析

智能职业发展系统&#xff1a;AI驱动的职业规划平台技术解析 引言&#xff1a;数字时代的职业革命 在当今瞬息万变的就业市场中&#xff0c;传统的职业规划方法已无法满足个人和企业的需求。据统计&#xff0c;全球每年有超过2亿人面临职业转型困境&#xff0c;而企业也因此遭…...

​​企业大模型服务合规指南:深度解析备案与登记制度​​

伴随AI技术的爆炸式发展&#xff0c;尤其是大模型&#xff08;LLM&#xff09;在各行各业的深度应用和整合&#xff0c;企业利用AI技术提升效率、创新服务的步伐不断加快。无论是像DeepSeek这样的前沿技术提供者&#xff0c;还是积极拥抱AI转型的传统企业&#xff0c;在面向公众…...

高防服务器价格高原因分析

高防服务器的价格较高&#xff0c;主要是由于其特殊的防御机制、硬件配置、运营维护等多方面的综合成本。以下从技术、资源和服务三个维度详细解析高防服务器昂贵的原因&#xff1a; 一、硬件与技术投入 大带宽需求 DDoS攻击通过占用大量带宽资源瘫痪目标服务器&#xff0c;因此…...

Unity VR/MR开发-VR开发与传统3D开发的差异

视频讲解链接&#xff1a;【XR马斯维】VR/MR开发与传统3D开发的差异【UnityVR/MR开发教程--入门】_哔哩哔哩_bilibili...

在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例

目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码&#xff1a;冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...

Pydantic + Function Calling的结合

1、Pydantic Pydantic 是一个 Python 库&#xff0c;用于数据验证和设置管理&#xff0c;通过 Python 类型注解强制执行数据类型。它广泛用于 API 开发&#xff08;如 FastAPI&#xff09;、配置管理和数据解析&#xff0c;核心功能包括&#xff1a; 数据验证&#xff1a;通过…...