预热池自动扩缩容

更新时间:
复制 MD 格式

PoolAutoscaler 根据可用 Sandbox 数量与 Cron 定时计划自动调整预热池大小,不足时补池、空闲时缩池,兼顾领取延迟与预热成本。

背景信息

PoolAutoscaler 用于自动调整预热池的大小。它以未被领取的可用 Sandbox 数量和配置的 Cron 计划为依据,修改 SandboxSet 的 spec.replicas:可用 Sandbox 不足时补池,空闲 Sandbox 过多时缩池,也可以在已知高峰前提前预热。

相比基于 CPU、QPS 指标的自动伸缩,PoolAutoscaler 不采集业务指标,也不预测流量,仅管理预热池容量;Sandbox 被领取后的业务生命周期仍由 Agent Sandbox 服务处理。预热池的基础概念与创建方式,请参见创建Agent Sandbox。

应用场景

该能力适合以下场景:

  • 请求量随时变化:例如通过 E2B API(开源沙箱服务接口)创建、使用并释放 Sandbox,希望始终保留一定数量的可用 Sandbox,以降低领取等待时间。

  • 高峰时间可预期:例如工作日上班前、定时任务开始前或活动开始前,可在指定时间提前创建预热池。

  • 两类流量并存:日常通过容量策略补池,并在已知高峰时由 Cron 策略指定预热规模。

适用范围

  • 已安装 ack-agent-sandbox-controller 组件且版本不低于 v0.6.6-release.5:PoolAutoscaler 自该版本起提供并默认启用,无需额外配置;更低版本不支持该能力,需先升级组件。若组件版本已满足仍无法创建 PoolAutoscaler,可通过启动参数 --feature-gates=PoolAutoscaler=true 显式开启。版本与变更记录、安装方式,请参见ack-agent-sandbox-controller 使用说明。

  • 已创建待管理的 SandboxSet,且 SandboxSet 控制器正常运行。具体操作,请参见创建Agent Sandbox。

  • 同一命名空间(Namespace)内,一个 SandboxSet 最多只能由一个 PoolAutoscaler 管理。

  • PoolAutoscaler 至少配置一种策略:capacityPolicy 或 cronPolicies。

使用限制

  • PoolAutoscaler 仅修改目标 SandboxSet 的 spec.replicas,不直接创建或删除 Pod。请勿让 HPA(Horizontal Pod Autoscaler,Pod 水平自动伸缩)、AHPA(Advanced Horizontal Pod Autoscaler,高级预测弹性伸缩)、脚本或其他控制器同时修改同一个字段,否则扩缩结果会互相覆盖。

  • PoolAutoscaler 与 SandboxSet 必须位于同一命名空间。

场景一:按可用容量自动补池

此场景适用于请求量难以准确预估、但希望稳定获得低领取延迟的业务。例如业务通过 E2B API 不断创建 Sandbox、执行代码或命令、完成后释放 Sandbox。每次领取都会消耗预热池中的可用 Sandbox;容量策略据此补充未被领取的 Sandbox。

步骤一:通过 SandboxSet 创建预热池

若已创建预热池,可直接复用现有 SandboxSet。以下为最小示例,各字段含义见注释;请将标注“必须替换”的字段改为当前集群已验证的值。

  1. 将以下内容保存为 sandboxset.yaml。

    apiVersion: agents.kruise.io/v1alpha1
    kind: SandboxSet
    metadata:
      name: sandbox-pool        # SandboxSet 名称,步骤二的 PoolAutoscaler 通过此名称引用;可按需修改
      namespace: default        # 部署命名空间;PoolAutoscaler 必须与它同 namespace
    spec:
      replicas: 2               # 初始预热池大小;应用 PoolAutoscaler 后由其接管,可按成本设小一些
      scaleStrategy:
        maxUnavailable: "30%"   # 建议配置:兼作启动预算,启动失败/超时数达到该值时暂停继续扩容
      runtimes:
        - name: agent-runtime   # 注入 envd 兼容运行时,步骤三的 E2B 代码执行/文件/命令能力依赖它
      template:
        spec:
          containers:
            - name: sandbox
              image: <YOUR-SANDBOX-IMAGE>   # 必须替换:集群内可拉取的 Agent Sandbox 运行时镜像
              resources:
                requests:         # 调度申请资源,影响预热池单实例成本和可放置的节点范围
                  cpu: "1"
                  memory: 1Gi
                limits:           # 资源上限;按实际运行时需要调整,过大浪费预热成本,过小影响启动
                  cpu: "1"
                  memory: 1Gi
  2. 创建预热池并查看状态。

    kubectl apply -f sandboxset.yaml
    kubectl get sandboxset sandbox-pool -n default
    runtimes: agent-runtime 是步骤三 E2B 验证的必要条件(run_code、文件读写、命令执行均依赖注入的 envd 运行时)。运行时注入机制及其对 sandbox-injection-config ConfigMap 的依赖,请参见为 Agent Sandbox 配置 Sidecar 容器自动注入。

步骤二:配置 PoolAutoscaler

以下策略按比例维护预热池:目标可用比例为 50%,使用默认容差 10%,近期平均可用比例低于 40% 时扩容,高于 60% 时缩容,两者之间不调整。百分比水位随池规模弹性伸缩,适合负载波动的业务。

  1. 将以下内容保存为 pool-autoscaler.yaml。

    apiVersion: agents.kruise.io/v1alpha1
    kind: PoolAutoscaler
    metadata:
      name: sandbox-pool-autoscaler   # PoolAutoscaler 名称,仅用于标识和后续 kubectl 查询,可按需修改
      namespace: default              # 必须与目标 SandboxSet 同 namespace
    spec:
      scaleTargetRef:
        apiVersion: agents.kruise.io/v1alpha1
        kind: SandboxSet              # 仅支持 SandboxSet,不可修改
        name: sandbox-pool            # 步骤一中创建的 SandboxSet 名称
      minReplicas: 2                  # 预热池下限;百分比目标需满足缩容可达性,见下方说明
      maxReplicas: 50                 # 预热池上限;按业务峰值并发量与集群容量设置
      capacityPolicy:
        targetAvailable: "50%"        # 目标可用(未被领取的)Sandbox 占副本数的比例,随池规模弹性伸缩
        # tolerance 未设置时默认为 10%,与目标合并后形成 40%~60% 的不调整区间
        scaleUp:
          stabilizationWindowSeconds: 60    # 扩容稳定窗口,即连续两次扩容的最小间隔;设小可加快补池,但过小会导致扩容过于频繁
        scaleDown:
          stabilizationWindowSeconds: 300   # 缩容稳定窗口,即连续两次缩容的最小间隔;拉长可避免夜间流量波动频繁回收预热池
    
  2. 创建 PoolAutoscaler 并查看状态。

    kubectl apply -f pool-autoscaler.yaml
    kubectl get poolautoscaler sandbox-pool-autoscaler -n default

minReplicas 和 maxReplicas 始终生效,所有策略计算出的副本数都会被限制在这个范围内。targetAvailable、tolerance 和 minReplicas 的取值共同决定空闲预热池能否收缩到 minReplicas,配置规则参见本文容量策略调优说明。

稳定窗口的取值建议保持缩容长于扩容(默认 60 秒对 300 秒,上述示例也保持了这一关系):快速扩容可及时补齐预热池、保障领取延迟,缓慢缩容可避免瞬时流量回落时回收预热容量造成容量抖动。

步骤三:使用 E2B 业务流量验证

以下单文件脚本模拟真实业务流量:每个并发循环从预热池领取一个 Sandbox,执行代码、读写文件、执行命令,短暂持有后释放。预热池的扩缩容状态可通过步骤四的命令另行观察。

准备工作:

  1. 本地已安装 Python 3.9 及以上版本,并已配置目标集群的 kubeconfig(用于 kubectl 观察状态)。

  2. 安装 E2B SDK(版本须与 ack-sandbox-manager 组件兼容):pip install e2b-code-interpreter。

  3. 准备可访问的 E2B 服务域名、API Key;集群使用自签证书时,还需准备 CA 证书。

将脚本保存为 poolautoscaler-verify.py。

#!/usr/bin/env python3
"""Generate E2B business traffic against a PoolAutoscaler-managed pool.

Usage:
  E2B_API_KEY='<YOUR-E2B-API-KEY>' \
  E2B_DOMAIN='<YOUR-E2B-DOMAIN>' \
  SSL_CERT_FILE='<YOUR-CA-FILE>' \
  python poolautoscaler-verify.py --template sandbox-pool
"""
import argparse, os, sys, threading, time
from concurrent.futures import ThreadPoolExecutor

stop = threading.Event()

def business_loop(loop_id, template, hold, deadline):
    """One worker: claim a sandbox, run business calls, hold, then release."""
    from e2b_code_interpreter import Sandbox
    seq = 0
    while time.monotonic() < deadline and not stop.is_set():
        seq += 1
        sandbox = None
        try:
            # no timeout: the SDK timeout is the sandbox lifetime, not a create wait
            sandbox = Sandbox.create(template=template)
            sandbox.run_code(f"print('loop={loop_id} seq={seq}')")
            path = f"verify-{loop_id}-{seq}.txt"
            content = f"loop={loop_id} seq={seq}\n"
            sandbox.files.write(path, content)
            assert sandbox.files.read(path) == content, "file content mismatch"
            assert sandbox.commands.run(f"cat {path}").stdout == content
            stop.wait(hold)          # simulate the business holding the sandbox
            sandbox.kill()
        except Exception as exc:
            if sandbox is not None:
                try:
                    sandbox.kill()   # release the claimed sandbox before backing off
                except Exception:
                    pass
            print(f"loop {loop_id} seq {seq} failed: "
                  f"{type(exc).__name__}: {exc}", file=sys.stderr, flush=True)
            stop.wait(5)             # back off before the next attempt
 
 
def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--template", default="sandbox-pool",
                        help="SandboxSet name, i.e. the E2B template")
    parser.add_argument("--concurrency", type=int, default=10,
                        help="simultaneous business sandboxes; each consumes one pool slot")
    parser.add_argument("--duration-seconds", type=int, default=300)
    parser.add_argument("--hold-seconds", type=int, default=30,
                        help="how long each loop holds its sandbox before releasing")
    args = parser.parse_args()
    if not os.getenv("E2B_API_KEY"):
        parser.error("E2B_API_KEY must be set")

    deadline = time.monotonic() + args.duration_seconds
    print(f"load: {args.concurrency} loops x {args.duration_seconds}s, "
          f"hold={args.hold_seconds}s (Ctrl+C stops early)", flush=True)
    try:
        with ThreadPoolExecutor(max_workers=args.concurrency) as pool:
            futures = [pool.submit(business_loop, i, args.template,
                                   args.hold_seconds, deadline)
                       for i in range(args.concurrency)]
            for future in futures:
                future.result()
    except KeyboardInterrupt:
        print("interrupted; waiting for in-flight sandboxes to settle...", flush=True)
    finally:
        stop.set()
    return 0


if __name__ == "__main__":
    main()

运行脚本(另开一个终端执行步骤四的观察命令,可实时看到扩容):

E2B_API_KEY='<YOUR-E2B-API-KEY>' \
E2B_DOMAIN='<YOUR-E2B-DOMAIN>' \
SSL_CERT_FILE='<YOUR-CA-FILE>' \
python poolautoscaler-verify.py \
  --template sandbox-pool \
  --concurrency 10 \
  --duration-seconds 300 \
  --hold-seconds 30

参数说明:

  • --concurrency:并发领取数。建议不超过池内稳态可用数(由 minReplicas 与 targetAvailable 共同决定):超过会击穿预热池,首次数据面调用可能返回 503(属预期现象,稍后重试即可),正常补池约需 20~30 秒。

  • --hold-seconds:每个 Sandbox 的业务持有时长;越长则单位时间领取次数越少。

  • --duration-seconds:压测总时长,建议不小于扩容稳定窗口(默认 60 秒)的 3 倍,以观察至少两轮扩容。

预期现象:压测期间使用步骤四的命令观察,currentCapacity.available 下降、desiredReplicas 逐步抬升,说明容量策略检测到领取消耗并补充预热池。池被击穿时 create 虽返回 running,但数据面可能尚未就绪,首次调用返回 503 属预期现象,稍后重试即可。

验证缩容(可选):流量结束后,手动调低 targetAvailable 并观察预热池收敛。

kubectl patch poolautoscaler sandbox-pool-autoscaler -n default \
  --type merge -p '{"spec":{"capacityPolicy":{"targetAvailable":"10%"}}}'
kubectl get sandboxset sandbox-pool -n default -w

预热池应逐步缩至 minReplicas(示例为 2)。验证完成后,将 targetAvailable 改回原值即可。

步骤四:观测扩缩容节奏与启动保护

kubectl get poolautoscaler sandbox-pool-autoscaler -n default -o yaml
kubectl get sandboxset sandbox-pool -n default -o yaml
kubectl describe poolautoscaler sandbox-pool-autoscaler -n default

重点查看 status.currentReplicas、status.desiredReplicas、status.currentCapacity.available 和 status.conditions。这些是最近一次调谐(Reconcile,控制器周期性将实际状态对齐期望状态的过程)的观测值,可能与 SandboxSet 的实时状态存在短暂偏差;可用 Sandbox 数为 0 时,status.currentCapacity.available 字段不显示。扩缩容动作与异常原因会以事件形式记录在 PoolAutoscaler 对象上,常见事件参见本文常见事件。

容量策略基于近期一段观察时间内的平均可用数量做决策,以平滑瞬时波动;首次扩缩容可以立即执行,后续按稳定窗口节奏执行:扩容稳定窗口默认 60 秒,缩容稳定窗口默认 300 秒,可通过 stabilizationWindowSeconds 调整(参见本文参数配置约束说明)。扩容稳定窗口的默认值已包含 Pending 超时的安全余量;配置更大的值时以配置值为准。

当 SandboxSet 当前 generation 的 ScalingLimited=True 时,PoolAutoscaler 暂停继续扩容,避免在启动失败或 Pending 超时已耗尽预算时继续提高目标;缩容不受此限制。正在创建的 Sandbox 的创建并发由 SandboxSet 控制,PoolAutoscaler 不直接管理 Pod。限流的触发条件、恢复方式与排查步骤参见本文故障排查:扩容限流的触发与恢复。

调参建议:从小范围配置起步——较小的 minReplicas、maxReplicas 和较长的缩容稳定窗口——结合启动成功率、领取延迟及预热成本逐步调整。若需临时观察或人工接管,可暂停自动扩缩容(恢复时设为 false 或删除该字段):

kubectl patch poolautoscaler sandbox-pool-autoscaler -n default \
  --type merge -p '{"spec":{"suspend":true}}'

步骤五(可选):删除 PoolAutoscaler

不再需要自动扩缩容时(例如业务下线或改为固定规模),可删除 PoolAutoscaler。删除后,SandboxSet 和现有 Sandbox 均会保留,SandboxSet 副本数维持在删除前的值,不会自动缩容;如需回收预热池,需再手动调整 spec.replicas 或删除 SandboxSet。

警告

删除 SandboxSet 会级联删除该预热池内的全部 Sandbox,包括已被业务领取、仍在运行的 Sandbox,业务会话会随之中断,且删除后不可恢复。建议先按下述顺序回收预热池:先将 spec.replicas 调小或置为 0,确认池内 Sandbox 均已被业务释放后,再删除 SandboxSet。

kubectl delete poolautoscaler sandbox-pool-autoscaler -n default

场景二:按时间提前预热

此场景适用于高峰时间明确的业务,例如每个工作日 09:00 前会有大量用户创建开发环境。Cron 策略会在高峰开始前直接设定预热池目标,避免等到可用 Sandbox 耗尽后再扩容。

以下示例在工作日 08:30 将预热池设为 30 个副本,20:00 调整为 5 个副本。

  1. 将以下内容保存为 pool-autoscaler-cron.yaml。

    apiVersion: agents.kruise.io/v1alpha1
    kind: PoolAutoscaler
    metadata:
      name: sandbox-pool-schedule   # 仅用于标识,可按需修改
      namespace: default            # 必须与目标 SandboxSet 同 namespace
    spec:
      scaleTargetRef:
        apiVersion: agents.kruise.io/v1alpha1
        kind: SandboxSet            # 仅支持 SandboxSet
        name: sandbox-pool          # 必须替换:已创建的 SandboxSet 名称
      minReplicas: 2                # 平台下限;Cron 目标也会被限制在该范围内
      maxReplicas: 50               # 平台上限;需覆盖所有 Cron 策略中最大的 targetReplicas
      cronPolicies:
        - name: weekday-peak        # 策略名,同一 PoolAutoscaler 内必须唯一
          timeZone: Asia/Shanghai   # 不设置时默认使用 controller 所在时区,建议显式指定
          schedule: "30 8 * * 1-5"  # 五段 Cron:分 时 日 月 星期;此处为工作日 08:30
          targetReplicas: 30        # 触发时直接把预热池设为 30,需不超过 maxReplicas
        - name: weekday-offpeak
          timeZone: Asia/Shanghai
          schedule: "0 20 * * 1-5"  # 工作日 20:00 缩回夜间规模
          targetReplicas: 5
  2. 创建 PoolAutoscaler 并查看状态。

    kubectl apply -f pool-autoscaler-cron.yaml
    kubectl get poolautoscaler sandbox-pool-schedule -n default

cronPolicies 使用五段 Cron 表达式:分、时、日、月、星期。未设置 timeZone 时使用 controller manager 进程的时区。当 Cron 计划时间点触发时,会立即将副本数调整为该策略的 targetReplicas,且不受容量策略稳定窗口限制;最终副本数仍受 minReplicas 和 maxReplicas 限制,扩容同样受 SandboxSet 启动保护限制(参见本文故障排查:扩容限流的触发与恢复)。

若同时配置容量策略,则未触发 Cron 时按容量策略补池,两次 Cron 触发之间,副本数由容量策略按可用容量水位持续调整,可能将副本数调离 Cron 目标(包括向下),Cron 目标并非持续生效的容量下限。Cron 策略直接设定目标副本数,不受容量策略水位和缩容可达性规则约束,但仍会遵守扩容限流:Cron 抬升目标时若 SandboxSet 启动预算耗尽,同样会被阻塞并等待预算恢复。需要 Cron 时段内的持续保底容量时,用 minReplicas 表达(始终生效);避免将 Cron 目标设置在容量策略期望水位之下,否则副本数会在两个目标之间反复调整。

故障排查:扩容限流的触发与恢复

扩容限流是 SandboxSet 的启动保护机制:当同一时间存在过多启动失败或长期 Pending 的 Sandbox 时,暂停进一步提高副本数,避免在异常状态下制造更多无法启动的 Sandbox。PoolAutoscaler 读取该信号并暂停扩容。

触发条件

SandboxSet 的 spec.scaleStrategy.maxUnavailable 同时作为启动预算;未设置时默认为当前副本数(相当于 100%,即需全部实例启动异常才触发限流)。注意它与 updateStrategy.maxUnavailable(滚动更新用,默认 20%)是不同字段。处于创建阶段的 Sandbox 按以下两类计数占用预算:

  • Failed:Ready condition 为 False 且 reason 为 StartContainerFailed、PodCreateFailed 或 Unschedulable,即明确的启动失败(容器启动失败、镜像或配置错误、创建 API 失败、调度失败如节点资源不足或底层调度错误等)。

  • TimedOut:长期停留在 Creating/ResourcePending 状态,且创建时间超过 Pending 超时阈值(默认 50 秒)仍未 Ready。该计数在 SandboxSet 的 status message 与事件中显示为 Timeout。

说明

镜像拉取失败 ImagePullBackOff不计入 Failed,而是超过 Pending 阈值(默认 50 秒)后计入 Timeout,此时 Pod 的 Ready condition reason 仍为 PodReady。

当 Failed + TimedOut >= 启动预算 时,SandboxSet 写入如下状态,同时发出 Warning 级 ScalingLimited 事件。

apiVersion: agents.kruise.io/v1alpha1
kind: SandboxSet
metadata:
  name: sandbox-pool
  namespace: default
status:
  conditions:
    - type: ScalingLimited
      status: "True"
      reason: StartupBudgetExhausted
      message: '2 of 2 startup slots are blocked: Timeout=1, Failed=1'

PoolAutoscaler 检测到该 condition 后:

  • 暂停扩容:status.desiredReplicas 不再抬升,即使可用数量已低于下水位;等待期间周期性发出 Normal 级 ScaleBlocked 事件。

  • 缩容不受影响:上水位触发的缩容照常执行。

  • 容错处理:仅当 SandboxSet 报告的是当前 generation 的显式 True 才阻塞;condition 缺失、陈旧或为 Unknown 时按无信号处理,扩容照常,避免升级或首次调谐期间误阻塞。

重要

注意区分两个同名 condition:SandboxSet status 中的 ScalingLimited 表示启动预算耗尽;PoolAutoscaler 自身的 ScalingLimited(参见本文CRD 字段说明)表示期望副本数触及 minReplicas/maxReplicas 边界,两者含义不同。

恢复方式

恢复不需要人工干预,满足任一条件后预算自动释放:

  • 失败的 Sandbox 被删除(手动删除或业务侧清理),或其 Pod 恢复 Running 且 Ready 变为 True;

  • Pending 超时的 Sandbox 完成启动进入 Running,或被删除。

当 Failed + TimedOut 降回预算以下,SandboxSet 下一次调谐会将 ScalingLimited 改回 False,PoolAutoscaler 随即恢复扩容并继续补池。

排查与应对

# 查看限流状态与 Timeout/Failed 计数
kubectl get sandboxset sandbox-pool -n default -o jsonpath='{.status.conditions[?(@.type=="ScalingLimited")]}'
kubectl describe sandboxset sandbox-pool -n default

# 查看限流与阻塞事件
kubectl get events -n default --field-selector involvedObject.name=sandbox-pool

# 列出预热池中的 Sandbox 及其状态
kubectl get sandbox -n default -l agents.kruise.io/sandbox-pool=sandbox-pool

# 查看某个失败实例的 Ready condition 及具体原因
kubectl get sandbox <YOUR-SANDBOX-NAME> -n default -o jsonpath='{.status.conditions[?(@.type=="Ready")]}'

根据 message 中的计数定位主要问题:

  • Failed 为主:多为镜像拉取失败、资源配置错误或资源配额不足。查看失败 Sandbox 的 Ready condition message 确认具体原因,修正 SandboxSet 配置后删除失败 Sandbox。

    kubectl delete sandbox <YOUR-SANDBOX-NAME> -n default

    SandboxSet 会自动重建新 Sandbox;若配置未修复,新 Sandbox 仍会失败并再次触发限流。

  • TimedOut 为主:底层创建速度跟不上扩容节奏。可显式配置较小的 SandboxSet scaleStrategy.maxUnavailable,让限流更早触发(暂停继续抬升副本数),或联系集群管理员评估底层供给能力。

重要

镜像拉取失败通常表现为 Timeout 计数偏高而 Failed 为 0(message 形如「Timeout=N, Failed=0」)。应结合 ImagePullBackOff 的 kubectl describe pod 事件确认是否为镜像地址/凭证问题,不要只依据 Failed 计数排查。

常见事件

PoolAutoscaler 在每次调谐时将扩缩容动作与异常原因记录为自身对象的事件,常用查看方式:

# 查看 PoolAutoscaler 的事件列表
kubectl describe poolautoscaler sandbox-pool-autoscaler -n default

# 按对象筛选事件
kubectl get events -n default --field-selector involvedObject.name=sandbox-pool-autoscaler

事件(Reason)

级别

含义与应对

ScaledUp

Normal

副本数已上调,message 附带调整前后的数值与决策原因,例如可用数量低于下水位(available below lower watermark)或 Cron 策略触发。

ScaledDown

Normal

副本数已下调,message 附带调整前后的数值与决策原因,例如可用数量高于上水位,或缩容被 minReplicas 下限限制。

ScaleBlocked

Normal

一次扩缩容被抑制:目标仍在对应方向的稳定窗口冷却期内(message 形如 blocked by stabilization window (cooldown remaining: 45s),长窗口显示为 3m55s 形式,冷却期间每次调谐重复发出并聚合计数),或 SandboxSet 启动预算耗尽导致扩容被抑制(message 形如 scaling blocked by SandboxSet ScalingLimited condition (reason=StartupBudgetExhausted),在阻塞开始或对应 condition 变化时发出)。

FailedGetScale

Warning

无法读取目标 SandboxSet。检查 scaleTargetRef.name 是否拼写正确、SandboxSet 是否与 PoolAutoscaler 位于同一命名空间。

FailedComputeReplicas

Warning

期望副本数计算失败,通常由策略配置非法导致,对照本文参数配置约束说明核对 capacityPolicy、cronPolicies 取值。

FailedScale

Warning

向 SandboxSet 写入新副本数失败,例如 API 冲突或 Webhook 拒绝。控制器会自动重试,持续出现时需排查。

SandboxSet 自身在启动预算耗尽时会在其对象上发出 Warning 级 ScalingLimited 事件,与 PoolAutoscaler 侧的 ScaleBlocked 事件成对出现,详见本文故障排查:扩容限流的触发与恢复。

容量策略调优说明

容量策略根据“目标值 + 容差”计算上下水位:

  • targetAvailable 为绝对值(如 10)时:下水位 = 目标值 - 容差,上水位 = 目标值 + 容差。

  • targetAvailable 为百分比(如 "60%")时:以近期平均副本数为基准,先将目标与容差的百分比合并,再向上取整,即下水位 = ceil(平均副本数 × (目标 - 容差)%),上水位 = ceil(平均副本数 × (目标 + 容差)%)。

  • tolerance 未设置时默认为 10%。目标是百分比时按百分比合并;目标是绝对值时,默认容差为该目标值的 10% 向上取整(例如目标为 10 时容差为 1,目标为 15 时容差为 2)。

平均可用数量低于下水位时扩容,并补足到目标值附近;高于上水位时缩容;位于两者之间时不调整。

警告

避免将 targetAvailable 配置为 "100%":可用数量最多等于副本数,永远不会严格高于上水位,因此无论容差如何配置,空闲预热池都永不缩容、只增不减。若确实希望池规模只增不减,再考虑使用 100% 目标。

确保空闲预热池能收缩到 minReplicas

容量策略应保证空闲预热池能够收缩到 minReplicas,否则预热池会停留在 minReplicas 之上,空闲资源无法回收到预期下限。判定方法是检查最后一步缩容:当预热池副本数为 minReplicas + 1 时,上水位必须不大于 minReplicas,这一步缩容才能触发。

  • 绝对值目标 T 与解析后的容差 D(含默认容差):需满足 T + D <= minReplicas。满足时,空闲预热池的稳定规模就是 minReplicas。

  • 百分比目标 p% 与百分比容差 q%(含默认容差):需满足 p + q <= 100 × minReplicas / (minReplicas + 1)。百分比水位随预热池缩小而降低,空闲时会逐步收敛到 minReplicas。

  • 百分比目标配绝对容差 D:在 minReplicas + 1 个副本处评估,需满足 ceil((minReplicas + 1) × p%) + D <= minReplicas。

  • 绝对目标 T 配百分比容差 q%:容差按 ceil(T × q%) 解析,需满足 T + ceil(T × q%) <= minReplicas。

常见配置的效果如下:

minReplicas

targetAvailable

tolerance

在 minReplicas+1 个副本处的上水位

效果

1

"40%"

默认("10%")

1

可从 2 个副本缩到 1 个

1

"50%"

默认("10%")

2

上水位为 2,停留在 2 个副本

1

1

0

1

固定保底 1 个可用 Sandbox

1

1

默认("10%")

2

默认容差将上水位抬高到 2,停留在 2 个副本

2

"50%"

默认("10%")

2

可从 3 个副本缩到 2 个(即场景一示例)

1

"100%"

任意(含 0)

>= 副本数

可用数最多等于副本数,永不高于上水位,空闲池永不缩容

4

"70%"

"10%"

4

可从 5 个副本缩到 4 个

场景一演示了百分比目标的配置方式:水位随池规模弹性伸缩,适合负载波动的业务。需要固定规模的预热池时,可改用绝对值目标并设置 tolerance: 0,同时让 minReplicas 不小于目标值。无论哪种方式,都请按上述规则核对 minReplicas。

如果预热池长期停留在高于 minReplicas 的规模且 status.conditions 无异常,通常是上述规则未满足:此时可调低 targetAvailable 或 tolerance,或调高 minReplicas,然后观察 status.desiredReplicas 与 status.currentCapacity.available 是否逐步收敛。

参数配置约束说明

创建或更新 PoolAutoscaler 时,Webhook 会校验以下规则;违反时请求被拒绝(HTTP 422)并返回具体原因。

通用约束

字段

约束

spec.scaleTargetRef.kind

仅支持 SandboxSet。

spec.scaleTargetRef.name

必填,指向同命名空间内已存在的 SandboxSet;当 SandboxSet 不存在时,创建不会拦截,仅产生 FailedGetScale Warning 事件,可用 kubectl describe poolautoscaler <name> 查看事件定位(事件汇总参见本文常见事件)。

spec.maxReplicas

必填且大于 0。

spec.minReplicas

大于等于 0,且不能大于 maxReplicas。

spec.capacityPolicy / spec.cronPolicies

至少配置一项,否则没有任何策略驱动扩缩容。

同命名空间内唯一性

一个 SandboxSet 只能由一个 PoolAutoscaler 管理。

容量策略约束

字段

约束

targetAvailable

绝对值必须 >= 0 且 <= maxReplicas(超出则目标永远无法达到);百分比格式为 "<number>%",数值范围 0 到 100。

targetAvailable 为百分比时

必须同时设置 minReplicas >= 1,否则空池时水位全部归 0,预热池无法自举。

tolerance

格式约束同 targetAvailable;且必须小于 targetAvailable(同为百分比或同为绝对值时可静态判定),否则下水位被钳制为 0,扩容永不触发。绝对目标配百分比容差时,容差百分比必须小于 100%。

scaleUp.stabilizationWindowSeconds

不设置时默认 60 秒;显式设置必须在 60 到 3600 秒之间。

scaleDown.stabilizationWindowSeconds

不设置时默认 300 秒;显式设置必须在 60 到 3600 秒之间。建议将缩容稳定窗口设置得长于扩容稳定窗口(默认已保持 300 秒对 60 秒的关系):快速扩容可及时补齐预热池、保障领取延迟,缓慢缩容可避免流量瞬时回落时频繁回收预热池,保证集群整体容量稳定。

缩容可达性虽然不是当前版本的硬性校验,但配置不当会导致预热池无法缩至 minReplicas,建议按本文确保空闲预热池能收缩到 minReplicas 的规则核对。

cronPolicies 的校验规则(最多 20 条、name 唯一、五段合法 Cron 表达式、合法时区、targetReplicas >= 0)已在本文CRD 字段说明中标注,此处不再重复。

CRD 字段说明

PoolAutoscaler 是 Agent Sandbox 提供的自定义资源定义(Custom Resource Definition,CRD),本节说明其字段的含义与约束。

字段

说明

spec.scaleTargetRef

待管理的 SandboxSet 引用;kind 必须为 SandboxSet,目标与 PoolAutoscaler 必须位于同一命名空间。

spec.minReplicas / spec.maxReplicas

预热池副本下限和上限。maxReplicas 必须大于 0,minReplicas 不能大于 maxReplicas。容量策略需能将空闲预热池收缩到 minReplicas,取值约束参见本文容量策略调优说明。

spec.capacityPolicy.targetAvailable

目标可用 Sandbox 数,可填绝对值或百分比,例如 10、"60%"。百分比以近期平均副本数为基准;使用百分比时必须设置 minReplicas: 1 或更大。取值需与 tolerance、minReplicas 共同满足缩容可达性规则,详见本文容量策略调优说明。

spec.capacityPolicy.tolerance

目标允许偏差,可填绝对值或百分比;未设置时默认为 10%。例如目标为 10、偏差为 2 时,低于 8 扩容、高于 12 缩容。解析方式与缩容可达性约束参见本文容量策略调优说明。

spec.capacityPolicy.scaleUp.stabilizationWindowSeconds

扩容稳定窗口,即连续两次扩容之间的最小间隔。显式设置范围为 60 到 3600 秒,默认 60 秒。

spec.capacityPolicy.scaleDown.stabilizationWindowSeconds

缩容稳定窗口,即连续两次缩容之间的最小间隔。显式设置范围为 60 到 3600 秒,默认 300 秒。建议长于扩容稳定窗口,避免快速缩容导致预热池容量抖动。

spec.cronPolicies

定时策略列表,最多 20 条,Webhook 校验:name 必填且同一列表内唯一;schedule 必填且必须是合法的五段 Cron 表达式(分 时 日 月 星期);timeZone 可选,设置时必须是合法时区(如 Asia/Shanghai);targetReplicas 必须大于等于 0,超过 maxReplicas 时会被限制在 maxReplicas。

spec.suspend

设为 true 后暂停后续自动扩缩容,不会删除已有 Sandbox。恢复时设为 false 或删除该字段。

status.currentReplicas

目标 SandboxSet 最近观测到的当前副本数。

status.desiredReplicas

PoolAutoscaler 最近计算出的期望副本数。

status.currentCapacity.available

最近观测到的可用 Sandbox 数量。

status.conditions

ScalingActive 表示控制器是否可用,AbleToScale 表示是否可计算和写入目标,ScalingLimited 表示是否受副本边界限制。

status.appliedCronPolicies

每条 Cron 策略最近一次成功执行的时间。