
# Volcano异构资源碎片整理
碎片整理（Repack）是Volcano面向异构资源的运行时碎片整理能力。通过不改变工作负载资源请求，调整现有Pod的节点分布，将分散的空闲资源集中到部分节点，释放完整节点容量。本文介绍Repack的功能配置及操作示例。
#### 背景信息
Repack解决的是"集群仍有空闲卡，但资源布局无法满足目标工作负载"这一运行时容量问题。集群长期经历任务结束、扩缩容、滚动更新和故障恢复后，NPU/GPU空闲容量会分散到不同节点；即使设备总量充足，也可能无法满足工作负载所需的完整节点、单Pod卡数或拓扑组合。
#### 资源布局影响工作负载的可调度性
异构资源的容量不能仅由设备总数或空闲率衡量。训练与推理任务通常具有固定的单Pod卡数和节点规格要求。只有布局满足工作负载资源规格的空闲设备，才是能够直接分配的可调度容量。
以一个训练、推理混部的千卡资源池为例。监控系统显示仍有192张设备空闲，账面空闲率接近20%。此时，一个分布式训练任务申请32张设备，由 4 个各占用整机8卡的Worker组成，并要求放置在同一调度拓扑域内。
从设备总量看，剩余容量足以运行6个此类任务；但从实际布局看，空闲设备分散在多个节点和拓扑域中，部分原本满足条件的整机节点又被单卡推理副本或小规模开发任务占用。在任一候选拓扑域内，均无法提供4个完整且满足约束的节点。最终结果是：集群仍有192张空闲设备，但对这一任务规格而言，有效可调度容量为0。
这类现象揭示了AI集群容量管理中的关键差异：设备利用率衡量资源是否被占用，可调度容量则衡量当前资源布局能否满足目标任务的资源规格。容量管理因此不能只回答"还剩多少张卡"，还要回答"这些卡能否满足工作负载的资源请求"。
#### 碎片是集群持续运行后的常态
资源碎片通常不是一次错误调度造成的，而是集群长期运行的结果。训练任务的设备规模和运行时长差异较大；推理服务持续扩缩容、滚动升级和故障重建；任务完成、重试和优先级调整又会不断释放或重新占用局部资源。即使初始布局接近最优，随着时间推移也会逐步偏离。
同一集群还可能同时承载单卡推理、小规模开发任务和多节点训练。小任务具有更高的放置灵活性，但如果持续占用大规格任务所需的完整节点或拓扑域，就会把可用容量切分为无法组合的局部余量。当集群负载较高时，碎片通常先表现为大规格任务排队，而不是设备利用率立即下降。
因此，仅依赖新Pod调度时的装箱策略无法完全解决问题。调度器可以优化当前放置，却无法预知后续任务的资源规格，也不会主动重排已经运行的工作负载。运行时碎片整理需要作为常规调度的补充，对现有资源布局进行受控调整。
#### 训练与推理工作负载具有更高的迁移成本
传统微服务通常可以逐副本重建，而AI工作负载中的Pod往往属于同一个训练任务、推理服务或PodGroup。驱逐一个Pod可能改变整个任务的运行状态，甚至触发工作负载控制器重建整个工作负载：
- 分布式训练通常声明最小可用成员数；剩余成员低于该阈值时，整个任务无法继续运行；
- 大规格任务需要完整节点或连续拓扑域，分散空闲设备无法通过简单累加满足资源诉求；
- 推理Pod重建涉及模型加载、缓存预热和流量恢复，迁移会直接占用服务冗余；
- 节点上的资源余量只是必要条件，接收节点还必须满足工作负载原有的调度约束。
因此，AI资源整理不能等同于普通Pod重调度。判断一个计划是否合理，既要评估资源收益，也要尽量减少受影响的工作负载数量，并优先迁移不会使PodGroup低于MinAvailable的 Pod。
#### 运行时整理的难点在于形成可执行方案
发现空闲卡分散，只能说明集群可能存在整理空间，不能直接推导出应该迁移哪些Pod。真正执行整理时，需要连续解决以下问题：
1. **整理后是否能形成实际收益**：将Pod从一个节点迁移到另一个节点，可能只是改变了碎片位置。只有释放完整的目标资源节点，并使整体布局更紧凑，迁移成本才换来了可使用的容量改善。
2. **腾空哪个节点对工作负载影响更小**：目标资源占用最少的节点不一定最适合腾空。该节点上的Pod可能分属多个工作负载，迁移后会扩大影响范围；也可能使某个训练任务低于最小可用成员数。候选选择需要从完整迁移方案评估影响，而不能只比较节点上的卡数。
3. **被迁移的Pod是否有可行接收节点**：接收节点有空闲卡不代表一定能够承载目标Pod。规划需要继承工作负载原有的调度要求，并累计前序迁移已经消耗的容量，避免多个单独可行的放置组合后相互冲突。
4. **如何避免整理产生新的碎片**：接收节点选择不当，会把原节点上的零散占用重新摊到更多节点。迁移需要优先填充确定会继续使用的节点，在释放腾空节点的同时保持剩余布局紧凑。
5. **如何控制一次整理的影响范围**：运行中的训练和推理工作负载不能被无限制迁移。平台需要明确允许驱逐的工作负载和可腾空节点，并限制单次影响的工作负载数量与目标资源迁移量。
6. **如何应对规划后集群状态变化**：从方案生成到Pod完成重建期间，节点资源、调度队列和驱逐条件都可能变化。计划需要能够审核，执行时需要重新校验，并记录计划节点、实际调度节点和最终收益。
因此，运行时碎片整理不是简单的Pod迁移，而是一次具有收益目标、驱逐范围、执行预算和结果验证的容量变更。Repack围绕这一过程提供规划与执行能力，用于周期性碎片整理、大规格任务提交前的容量准备，以及节点维护前的目标资源腾空。
#### 能力概述
Repack是Volcano面向异构资源的运行时碎片整理能力。它在不改变工作负载资源请求的前提下调整现有Pod的节点分布，将分散的空闲资源集中到部分节点，释放完整节点容量。规划以节点腾空为目标，以PodGroup识别受影响的工作负载并评估中断影响；执行阶段逐Pod记录驱逐、重建Pod识别和调度结果。
用户通过集群级CRD RepackRun发起一次整理。每个Run面向一种扩展资源，例如昇腾NPU资源 huawei.com/ascend-1980；GPU资源的使用方式相同，并支持两种执行模式：
- DryRun：基于当前集群状态计算方案，不驱逐Pod，用于判断预计释放的完整节点、受影响工作负载和迁移路径。
- Execute：读取集群实时状态并生成可执行方案，通过Kubernetes Eviction API发起迁移，并跟踪重建后的Pod或PodGroup的调度结果。
这两种模式使碎片整理具备明确的目标、驱逐范围、执行预算和结果记录。
![](https://support.huaweicloud.com/usermanual-cce/zh-cn_image_0000002666617422.png "点击放大")
**能力一览**
您可以通过本节判断Repack是否适合当前场景，后续章节再按需查看具体配置和示例：
| 能力         | 说明                                                                             |
|:---|:---|
| 目标与收益      | 指定一种NPU/GPU扩展资源，以碎片率下降和完整节点释放衡量容量改善。                                           |
| 工作负载驱逐范围   | 按标签或名称选择允许被驱逐的工作负载，并限定可腾空节点。                                                   |
| 调度可行性      | 使用Volcano Scheduler的调度规则验证接收节点，避免仅按资源数量生成迁移方案。                                 |
| Gang中断影响评估 | 识别VCJob、ModelServing等工作负载的PodGroup，减少受影响工作负载数量，并评估迁移后PodGroup是否仍满足MinAvailable |
| 节点选择与装箱    | 动态选择腾空节点，并按best-fit等策略填充接收节点，抑制二次碎片。                                           |
| 变更影响范围控制   | 限制单次影响的工作负载数量和目标资源迁移量。                                                         |
| 候选节点建议     | 通过nominatedNodeName提高重建Pod调度到建议节点的概率，不进行强制绑定或资源预留。                             |
| 结果记录与验证    | 记录受影响的工作负载、逐Pod驱逐与调度过程，以及最终节点腾空结果。                                             |
   
Repack当前适合由平台主动发起的周期性碎片整理、大规格训练任务提交前容量准备，以及节点维护前的目标资源腾空。它不会持续监听Pending作业并自动触发，也不会为后续任务预留已释放容量。
![](https://support.huaweicloud.com/usermanual-cce/zh-cn_image_0000002696416075.png "点击放大")
#### 前提条件
- 已创建v1.30及以上版本的集群，详情请参见[购买Standard/Turbo集群](https://support.huaweicloud.com/usermanual-cce/cce_10_0028.html)。
- 已安装Volcano插件，且插件版本在1.22.36及以上，版本详情请参见[Volcano调度器](https://support.huaweicloud.com/usermanual-cce/cce_10_0193.html)。
- 已选择Volcano为默认调度器，详情请参见[调度配置](https://support.huaweicloud.com/usermanual-cce/cce_10_0785.html)。
 
#### 约束与限制
- Repack仅支持NPU/GPU等异构资源的碎片整理，不支持CPU、Memory等通用资源。通用资源的碎片整理请参见重调度功能。
- 仅支持节点级碎片整理，不支持超节点维度的碎片整理。
- 为兼顾大规模集群下的规划时延与计算开销，Repack采用启发式搜索算法，不承诺全局最优解，仅推荐达到收益门槛的调度方案。
- Repack不锁定资源，也不暂停常规调度。在并发调度或任务删除等动态场景下，实际整理结果可能与计划产生偏差，属于预期行为。
- Repack不会提前规划PDB（Pod Disruption Budget）规避策略。当PDB不允许驱逐时，对应的迁移将失败。
- Repack不感知上层控制器的级联重建成本。当VCJob配置了PodEvicted生命周期策略或ModelServing配置了ServingGroupRecreate时，单个Pod的驱逐可能触发多个Pod的重建，实际中断范围可能大于计划迁移的Pod数量。
- 当前仅支持昇腾A2系列、310P常规机型；暂不支持A3系列及310P高密超节点机型。
- 使用Repack碎片整理能力时，建议开启"启用装箱策略 (Binpack)"开关，并配置对应GPU/NPU扩展资源类型的装箱权重，可达成更好的碎片整理效果。
 
#### 开启方式
1. 登录[CCE控制台](https://console.huaweicloud.com/cce2.0/?#/cce/cluster/list)，单击集群名称进入集群。
2. 单击左侧导航栏的"配置中心"，切换至"调度配置"页面，选择Volcano调度器找到对应的"专家模式"，单击"开始使用"。 
   ![](https://support.huaweicloud.com/usermanual-cce/zh-cn_image_0000002519785283.png "点击放大")
   
   
   
   
   
3. 将配置项repack_enable的值修改为true，单击"保存"，以开启Repack重调度能力。 
   ![](https://support.huaweicloud.com/usermanual-cce/zh-cn_image_0000002696306027.png)
   
   
4. 单击左侧导航栏的"插件中心"，在右侧找到**Volcano调度器**，单击"插件详情"，在"实例列表"页签，待volcano-repack-engine"状态"为"运行中"，即可开始碎片整理功能使用。
5. （可选）在"配置中心 \> 调度配置"页签的"资源利用率优化调度"区域，打开"启用装箱策略 (binpack)"的开关，单击"添加自定义资源类型"，根据集群的异构资源类型添加自定义资源装箱声明，比如：huawei.com/ascend-1980或者nvidia.com/gpu。装箱调度更多信息，请参见[装箱调度（Binpack）](https://support.huaweicloud.com/usermanual-cce/cce_10_0773.html)。
   
   ![](https://support.huaweicloud.com/usermanual-cce/zh-cn_image_0000002707202389.png "点击放大")
   
   
   
 
#### 功能详解
Repack会基于当前集群资源快照，在用户授权的工作负载范围和单次迁移预算内，快速寻找一个能够释放完整加速卡节点且中断影响较低的可执行方案。规划采用启发式搜索，因此不承诺枚举所有迁移组合或获得数学意义上的全局最优。系统优先保证调度可行性、影响范围可控和结果可审核；实际执行仍受实时资源竞争、PDB和控制器行为影响，应以Execute的最终结果为准。
#### 以完整节点释放衡量整理收益
对于具有固定单Pod卡数或整节点需求的工作负载，空闲卡能否用于调度，取决于单个节点的剩余容量是否满足其资源请求。Repack保持现有资源请求总量不变，通过减少目标资源的占用节点数即腾空目标资源的整节点来衡量碎片改善。
**碎片率计算**
对于目标资源R：
```
当前占用节点数 - 理论最少占用节点数
碎片率(R) =  ------------------------------------------------------- × 100%
                      提供资源R的节点数
```
- 提供资源的节点数：Allocatable\[R\] \> 0 的节点数。
- 当前占用节点数：正在使用资源R的节点数。
- 理论最少占用节点数：保持当前资源请求不变，紧凑装箱所需的最少节点数。
例如，20个NPU节点中有15个正在使用，理论上最少只需占用12个节点。本次计划再释放2个节点：
- 整理前：(15 - 12) / 20 × 100% = 15%
- 整理后：(13 - 12) / 20 × 100% = 5%
- 改善幅度：10个百分点
minFragImprovementPercent: 10表示碎片率至少下降10个百分点。碎片率按全集群统计，scope.nodes只限制可腾空节点，不改变统计范围。该指标衡量布局紧凑程度，不代表某个未来任务一定可以调度；计划中的每个迁移仍需通过Scheduler可行性验证。
Repack使用goals指定目标资源和最低改善幅度。方案必须至少释放一个目标资源节点，并达到配置的改善阈值。
**配置示例**
```
goals:
  - resource: huawei.com/ascend-1980
    # 碎片率至少下降 10 个百分点，本次整理才具有执行价值。
    minFragImprovementPercent: 10
```
布局已足够紧凑时，Run以NoFragmentation结束；存在碎片但无法达到收益门槛时，以InsufficientImprovement结束。两者都属于正常评估结果。
![](https://support.huaweicloud.com/usermanual-cce/zh-cn_image_0000002666448220.png "点击放大")
#### 使用Scope限定驱逐范围
碎片整理会驱逐运行中的工作负载。scope从两个维度限定本次操作范围：
- scope.podGroups：定义哪些工作负载的目标资源Pod允许被驱逐。
- scope.nodes：定义哪些节点允许作为腾空目标。
例如，训练、推理工作负载部署在同一集群时，可以允许低优先级训练和离线推理参与整理，同时排除关键在线推理；节点侧还可以只选择某个节点池作为本次腾空范围。
**配置示例**
```
scope:
  podGroups:
    include:
      selector:
        matchLabels:
          repack.volcano.sh/eligible: "true"
    exclude:
      selector:
        matchLabels:
          repack.volcano.sh/protected: "true"
  nodes:
    include:
      selector:
        matchLabels:
          accelerator-pool: ascend-npu
    exclude:
      names: [ascend-maintenance-01]
```
VCJob controller会把Job标签复制到其PodGroup，Kthena会把ModelServing标签复制到各ServingGroup对应的PodGroup；通用pg-controller也会继承Deployment、StatefulSet等工作负载Pod模板中的稳定标签。用户只需在工作负载上维护标签，再通过标签选择器配置驱逐范围，无需识别和维护自动生成的PodGroup名称。
同一include或exclude中，名称列表与标签选择器取并集，且exclude的优先级更高。PodGroup名称采用namespace/name，节点直接使用节点名。
![](https://support.huaweicloud.com/usermanual-cce/public_sys-resources/note_3.0-zh-cn.png)
scope.nodes限定的是"哪些节点允许被腾空"，并不限定Pod的接收节点。如果工作负载不能离开指定资源池，应通过节点亲和性、污点/容忍或其他原生调度约束表达；Repack会在规划时继承这些约束。
![](https://support.huaweicloud.com/usermanual-cce/zh-cn_image_0000002696402189.png "点击放大")
#### 基于完整调度语义验证迁移可行性
Pod请求的设备量小于节点空闲量，只能说明计划在资源总量上成立，并不能证明Pod可以实际调度。Repack在生成计划时复用Volcano Scheduler的调度策略进行节点过滤，对每个待迁移Pod模拟重新调度，验证其原有调度约束和节点剩余资源是否允许迁移。
一个节点能否作为腾空候选，也通过统一的可迁移性判断完成：
- 不申请本次目标资源的DaemonSet、CNI、kube-proxy和普通Pod不参与迁移，也不会阻止目标资源腾空。
- 申请目标资源的Pod必须由Volcano调度、关联可识别的PodGroup，并属于scope.podGroups允许驱逐的工作负载。
- 任一目标资源Pod不可迁移，或者任一待迁移Pod没有可行接收节点，该候选即被淘汰。
模拟过程会累计已经规划的迁移，避免多个局部可行方案组合后超过接收节点容量。它验证的是当前快照下是否存在完整迁移路径，不保证未来集群状态。模拟选出的接收节点不会触发资源预留；Execute会重新规划，并通过nominatedNodeName提供候选节点建议，最终绑定结果仍由Scheduler根据实时状态决定。
#### 以Gang语义评估工作负载中断影响
在分布式训练和模型推理中，整理方案不应将迁移分散到大量工作负载。即使迁移Pod总数相同，从一个工作负载迁移多个Pod通常也比从多个工作负载各迁移一个Pod具有更小的影响范围。Repack因此以PodGroup作为中断影响统计单元，对每个可行腾空方案计算多维成本。
**是否低于Gang最小可用成员数**
对于一个PodGroup，Repack从Scheduler快照读取Running成员数和MinAvailable，计算不影响Gang最小可用条件的最大迁移Pod数：
```
可安全迁移的 Pod 数 = max(Running - MinAvailable, 0)
```
- 计划迁移数不超过该值：PodGroup的可用副本数仍满足MinAvailable，受影响目标资源量按实际迁移量计算。
- 计划迁移数超过该值：PodGroup将低于MinAvailable，低于最小可用成员数的PodGroup数量增加；受影响目标资源量按整个PodGroup的目标资源占用量计算。
例如，一个训练任务当前有8个Running Worker，minAvailable: 6：
- 迁移1～2个Worker 后仍满足minAvailable；
- 迁移第 3个Worker后低于minAvailable，Repack将整个PodGroup的NPU占用计入受影响目标资源量。
**多维中断影响评分**
每个可行候选都按完整计划计算五个维度，而不是只评价当前节点上的局部Pod：
- 受影响工作负载数，默认权重1.0：尽量减少同时受到迁移影响的训练或推理工作负载。
- 低于 MinAvailable 的 PodGroup 数，默认权重0.8。
- 受影响目标资源量，默认权重0.6：PodGroup仍满足MinAvailable时计算实际迁移卡数，否则计算整个PodGroup的卡数。
- 迁移目标资源量，默认权重0.3：在工作负载影响接近时减少迁移的NPU/GPU数量。
- 迁移Pod数，默认权重0.1：进一步减少驱逐与重建对象数量。
每个维度都在当前一轮候选之间做 Min-Max 归一化，再乘以默认权重求和，总分越低，方案越优先。权重体现的是综合偏好，不是严格的逐级排序；分数也只用于同一规划轮次内的相对选择，不能跨集群或跨Run比较。
例如，三个方案都能释放一个节点并迁移3张卡：方案 A 从同一工作负载迁移3个Pod，且迁移后仍满足MinAvailable；方案B从3个工作负载各迁移1个Pod；方案C只影响1个工作负载，但迁移后低于 MinAvailable。综合评分通常优先方案A；方案B的工作负载影响范围更大，方案C在低于MinAvailable的PodGroup数和受影响目标资源量两个维度上的成本更高。
scope和maxPerRun是硬性限制，中断影响评分只用于在满足限制的候选方案中排序。评分倾向于减少受影响工作负载，并避免使 PodGroup低于MinAvailable，但不会将其作为禁止条件。不可中断的工作负载应通过scope.podGroups.exclude明确排除。
![](https://support.huaweicloud.com/usermanual-cce/zh-cn_image_0000002666454524.png "点击放大")
#### 动态选择腾空目标，并抑制二次碎片
Repack的规划不是预先排好一次节点顺序后逐个执行。规划器采用增量方式工作：每选定一个腾空方案，都会把相应迁移计入模拟状态，再重新评估剩余候选。这样可以处理接收容量被前序迁移消耗、PodGroup 影响范围扩大，以及后续节点腾空代价发生变化等情况。
**腾空候选的选择**
每一轮首先排除不满足可迁移性、执行预算、接收容量或Scheduler可行性的候选，再按中断影响总分从低到高选择。该总分同时考虑受影响工作负载数、低于MinAvailable的PodGroup数、受影响目标资源量、迁移资源量和迁移Pod数。分数相同时，继续比较腾空收益和候选名称，以获得稳定结果。
因此，Repack选择的不是"当前空闲卡最多的节点"，而是"能够腾空、具备完整迁移路径，并且工作负载中断影响相对较低的节点"。
![](https://support.huaweicloud.com/usermanual-cce/zh-cn_image_0000002696414323.png "点击放大")
**接收节点的填充**
把Pod从源节点移走并不等于碎片得到改善。如果接收节点选择不当，迁移可能只是把碎片从一个位置转移到另一个位置。Repack按以下原则组织接收节点：
1. 排除正在腾空的节点，并避免重新占用目标资源完全空闲的节点；
2. 优先填充因为工作负载不允许被驱逐、范围限制或既有迁移而确定会保持占用的节点；
3. 在仍可能被后续整理的节点中，优先使用未来腾空代价更高的节点，保留更容易释放的节点；
4. 对同类候选节点，优先选择迁入后目标资源余量更小的节点。
该策略在实现当前节点腾空目标的同时，尽量为后续任务和下一轮整理保留紧凑的资源布局。节点排序只决定模拟顺序，最终候选仍需通过Scheduler的完整调度过滤。
![](https://support.huaweicloud.com/usermanual-cce/zh-cn_image_0000002696454595.png "点击放大")
#### 通过执行预算限制单次影响范围
maxPerRun从工作负载数量和目标资源数量两个维度限制单次Run的变更范围：
- podGroups：最多影响多少个工作负载；系统内部按不同PodGroup计数。
- resources.\<resource-name\>：最多迁移多少单位目标资源。
podGroups是API字段名，用户不需要据此维护 PodGroup。VCJob通常对应一个PodGroup；ModelServing的每个ServingGroup分别对应一个PodGroup，因此应按实际可能受影响的ServingGroup数量设置预算。
**配置示例**
```
maxPerRun:
  # 本次最多影响两个训练或推理工作负载。
  podGroups: 2
  resources:
    # 即使工作负载规模更大，本次最多迁移8张卡。
    huawei.com/ascend-1980: 8
```
任何超过上限的候选都会在规划阶段被淘汰。首次上线建议从一个工作负载和较小的设备数量开始，结合DryRun、历史恢复时长和运维窗口逐步扩大预算。
Execute在集群内串行运行，并在一次执行完成后进入冷却时间，避免多轮整理相互影响。Pod驱逐通过Kubernetes Eviction API完成，API Server会根据实时状态检查PDB；DryRun不保证Execute时PDB仍允许驱逐。
![](https://support.huaweicloud.com/usermanual-cce/zh-cn_image_0000002696414761.png "点击放大")
#### 通过nominatedNodeName提供候选节点建议
规划阶段给出的接收节点基于当时的集群快照。进入Execute 后，资源占用和调度队列仍可能变化，因此Repack不会将计划节点作为强制绑定结果。
原Pod被驱逐后，工作负载控制器创建新的Pod。Volcano Controller Manager内置的Repack controller根据持久化的relocation记录识别重建后的Pod，在确认候选节点仍然可行后，将节点名写入pod.status.nominatedNodeName。Volcano Scheduler优先评估该节点，同时继续应用完整的调度规则。
- 建议节点仍可用时，重建后的Pod优先调度到该节点，提高计划完成概率。
- 建议节点不可用时，Scheduler可以选择其他可行节点，避免过期计划阻塞工作负载恢复。
- Repack不通过cordon、污点或资源预留限制节点使用，调度队列中的其他工作负载仍可参与调度。
- status.relocations\[\].placement记录规划节点、执行时选择的节点和实际绑定节点，用于识别替代调度及超时。
nominatedNodeName是调度建议，不是节点绑定或资源预留。
![](https://support.huaweicloud.com/usermanual-cce/zh-cn_image_0000002696290151.png "点击放大")
#### 从方案评估到结果验证形成闭环
DryRun和Execute使用同一套规划逻辑，但承担不同职责。DryRun用于确认收益门槛、受影响工作负载和迁移路径，相关信息保存在status.plan.summary、status.plan.moves和status.plan.freedNodes中。
审核通过后，应创建一个新的Execute Run，基于实时状态重新规划。Execute将逐Pod的驱逐和调度过程写入status.relocations，将实际腾空节点和最终碎片改善写入status.result。
计划值和实际值不同并不一定表示故障。差异可能来自PDB状态变化、重建后的Pod调度到其他节点，或者执行期间出现新的资源竞争。用户可以同时查看计划、执行过程和实际结果，判断本次整理是否达到容量目标。
 #### 工作负载驱逐与重建机制
**VCJob的驱逐恢复**
VCJob支持用户定义负载级的policy能力，比如VCJob额外配置了event: PodEvicted 与 action: RestartJob、RestartTask等生命周期策略，一次Repack驱逐可能触发整个工作负载或同一任务角色下的全部Pod重启，使实际中断范围远大于计划迁移的Pod。准备纳入Repack的VCJob不建议配置这类策略；确有整体重启语义时，应按所有受影响Pod评估恢复成本，并使用更严格的Scope和执行预算。
**ModelServing的驱逐恢复**
ModelServing的 spec.replicas 表示创建多个相互独立的服务分组（ServingGroup），而不是只创建对应数量的Pod。每个服务分组包含一个entry Pod和一个worker Pod，并共用一个PodGroup。Repack的 maxPerRun.podGroups 按这些服务分组对应的PodGroup计数。
示例使用 recoveryPolicy: ServingGroupRecreate。在该模式下，一个Pod被Repack驱逐后，Kthena不只补回这个Pod，而会删除同一服务分组内的全部Pod和旧PodGroup，再创建新的PodGroup和全部Pod。因此，最终效果可能是"Repack只驱逐一个Pod，但控制器重建整个ServingGroup"。
- restartGracePeriodSeconds 控制异常Pod触发ServingGroup恢复前的等待时间；它不同于 spec.eviction.gracePeriodSeconds，后者控制Repack Eviction请求的Pod优雅终止时间。
- Repack保留原PodGroup作为计划审计身份，通过相同ModelServing owner识别新一代PodGroup；名称变化时在 relocations\[\].replacementPodGroupName 中记录新名称，复用原名时该字段可以为空。
- 新PodGroup中的重建Pod经过placement lease和SchedulerGate协调后，由nominatedNodeName获得候选节点建议，最终绑定仍由Volcano Scheduler决定。
![](https://support.huaweicloud.com/usermanual-cce/public_sys-resources/notice_3.0-zh-cn.png)
这种恢复语义会扩大工作负载中断范围------maxPerRun限制的是Repack计划内的PodGroup和目标资源迁移量，不能替代ModelServing自身的可用性设计。生产推理服务应至少保留其他可用ServingGroup、确保流量摘除和模型预热能够完成，并将一个完整ServingGroup作为中断影响评估单元。不能接受ServingGroup重建的ModelServing应标记为 repack.volcano.sh/protected=true，由Scope明确排除。
#### 操作示例
以下示例以昇腾NPU资源huawei.com/ascend-1980为例。不同设备插件或驱动版本上报的资源名可能不同，请以节点status.allocatable中的实际资源名为准。GPU集群的使用流程相同，只需将目标资源替换为nvidia.com/gpu。
![](https://support.huaweicloud.com/usermanual-cce/public_sys-resources/note_3.0-zh-cn.png)
示例中涉及的节点名、PodGroup 名和资源容量需按实际集群调整。所有字段均为按需添加------不需要的能力不必写入 CR。
#### 步骤一：执行DryRun评估
首次使用只需指定mode和目标资源。以下CR会在提供huawei.com/ascend-1980的节点上评估碎片和迁移可行性，不会驱逐任何Pod：
```
apiVersion: repack.volcano.sh/v1alpha1
kind: RepackRun
metadata:
  name: ascend-npu-dryrun
spec:
  mode: DryRun
  goals:
    - resource: huawei.com/ascend-1980
```
执行提交命令并观察。
```
kubectl apply -f ascend-npu-dryrun.yaml
kubectl get repackrun ascend-npu-dryrun -w
kubectl get repackrun ascend-npu-dryrun -o yaml
```
重点关注status.conditions和status.plan。DryRun下status.phase=Succeeded仅表示评估正常完成，不代表已执行迁移。condition含义：
- RepackRecommended：存在具有正收益的可行计划，可以继续增加安全控制并审核。
- NoFragmentation：当前目标资源无需整理。
- InsufficientImprovement：存在碎片，但当前没有达到要求的可行计划。
未填scope表示不限制工作负载与腾空节点；未填maxPerRun表示不限制单次影响面。此最小配置适合首次评估。进入Execute前，应按需补齐scope与maxPerRun。
从这份基线CR扩展时，只需按实际需求增加对应字段：
- **限定允许驱逐的工作负载或节点池**：配置scope字段。
- **过滤收益过低的计划**：配置goals\[\].minFragImprovementPercent字段。
- **限制单次影响的工作负载数和卡数**：配置maxPerRun字段。
- **执行迁移**：新建RepackRun并将mode改为Execute。
- **调整Pod优雅终止时间或CR保留时间**：配置eviction或ttlSecondsAfterFinished字段。
 
#### 步骤二：（可选）准备工作负载与节点标签
如果你的VCJob、ModelServing已经配置了可供RepackRun scope筛选的稳定标签，可跳过本节，直接进入[步骤三：按需增加Scope]。
本节仅用于演示如何为示例环境准备标签和示例工作负载。
1. 为节点打标签。
   ```
   kubectl label node ascend-node-01 accelerator-pool=ascend-npu
   kubectl label node ascend-node-02 accelerator-pool=ascend-npu
   kubectl label node ascend-node-03 accelerator-pool=ascend-npu
   ```
   确认节点已正确上报NPU容量。
   ```
   kubectl describe node ascend-node-01
   ```
   
2. 创建示例工作负载。 以下示例创建一个VCJob训练任务和一个Kthena ModelServing推理服务。两类工作负载均显式标记允许参与整理，生产环境中不允许迁移的在线服务应设置repack.volcano.sh/protected=true（此处仅为示例，您可自定义lable），并在后续Scope中排除。
   ```
   apiVersion: v1
   kind: Namespace
   metadata:
     name: repack-demo
   ---
   apiVersion: batch.volcano.sh/v1alpha1
   kind: Job
   metadata:
     name: ascend-batch-training
     namespace: repack-demo
     labels:
       workload-type: training
       repack.volcano.sh/eligible: "true"
       repack.volcano.sh/protected: "false"
   spec:
     schedulerName: volcano
     queue: default
     minAvailable: 2
     tasks:
       - name: worker
         replicas: 2
         template:
           spec:
             nodeSelector:
               accelerator-pool: ascend-npu
             terminationGracePeriodSeconds: 30
             containers:
               - name: trainer
                 image: alpine:3.20
                 command: ["sh", "-c", "tail -f /dev/null"]
                 resources:
                   requests:
                     huawei.com/ascend-1980: 1
                   limits:
                     huawei.com/ascend-1980: 1
             restartPolicy: Never
   ---
   apiVersion: workload.serving.volcano.sh/v1alpha1
   kind: ModelServing
   metadata:
     name: ascend-online-serving
     namespace: repack-demo
     labels:
       workload-type: inference
       repack.volcano.sh/eligible: "true"
       repack.volcano.sh/protected: "false"
   spec:
     schedulerName: volcano
     replicas: 2
     recoveryPolicy: ServingGroupRecreate
     template:
       restartGracePeriodSeconds: 30
       gangPolicy:
         minRoleReplicas:
           inference: 1
       roles:
         - name: inference
           replicas: 1
           workerReplicas: 1
           entryTemplate:
             spec:
               nodeSelector:
                 accelerator-pool: ascend-npu
               containers:
                 - name: leader
                   image: alpine:3.20
                   command: ["sh", "-c", "tail -f /dev/null"]
                   resources:
                     requests:
                       huawei.com/ascend-1980: 1
                     limits:
                       huawei.com/ascend-1980: 1
               restartPolicy: Always
           workerTemplate:
             spec:
               nodeSelector:
                 accelerator-pool: ascend-npu
               containers:
                 - name: worker
                   image: alpine:3.20
                   command: ["sh", "-c", "tail -f /dev/null"]
                   resources:
                     requests:
                       huawei.com/ascend-1980: 1
                     limits:
                       huawei.com/ascend-1980: 1
               restartPolicy: Always
   ```
   
3. 执行以下命令。
   ```
   kubectl apply -f ascend-workloads.yaml
   kubectl get vcjob -n repack-demo
   kubectl get modelserving -n repack-demo
   kubectl get pod -n repack-demo -o wide
   kubectl get podgroup -n repack-demo --show-labels
   ```
   VCJob controller为一个Job创建一个PodGroup，并把VCJob metadata.labels复制到PodGroup；Kthena为每个ServingGroup创建一个PodGroup，并把ModelServing metadata.labels复制到这些PodGroup。用户只需在工作负载CR上维护标签，后续即可通过scope.podGroups.selector同时选择训练和推理工作负载。
   ![](https://support.huaweicloud.com/usermanual-cce/public_sys-resources/note_3.0-zh-cn.png)
   驱逐一个Pod可能触发VCJob整体重启或ModelServing整个ServingGroup重建，实际中断范围可能大于计划迁移量。使用Execute前请务必阅读[工作负载驱逐与重建机制]。
   
 
 #### 步骤三：按需增加Scope
最小DryRun会在全局范围评估。如果只允许带repack.volcano.sh/eligible=true标签的工作负载参与整理，并且只允许从accelerator-pool=ascend-npu节点池选择腾空目标，请复制最小CR、使用新的名称，并在spec中增加：
```
metadata:
  name: ascend-scoped-dryrun
spec:
  # 保留基线中的 mode 和 goals，在同级增加 scope。
  scope:
    podGroups:
      include:
        selector:
          matchLabels:
            repack.volcano.sh/eligible: "true"
    nodes:
      include:
        selector:
          matchLabels:
            accelerator-pool: ascend-npu
```
如果还需要排除关键在线推理等不可中断工作负载，只需在现有scope.podGroups下增加exclude；需要排除维护中的节点时，在scope.nodes下增加exclude：
```
spec:
  scope:
    podGroups:
      # 保留已有 include。
      exclude:
        selector:
          matchLabels:
            repack.volcano.sh/protected: "true"
    nodes:
      # 保留已有 include。
      exclude:
        selector:
          matchLabels:
            maintenance-state: draining
```
以上代码块是需要合并到基线CR的字段片段，不是两个独立的RepackRun。
```
kubectl apply -f ascend-scoped-dryrun.yaml
kubectl get repackrun ascend-scoped-dryrun -w
# 确认 selector 实际解析出的 PodGroup 和节点数量。
kubectl get repackrun ascend-scoped-dryrun \
  -o jsonpath='podGroups={.status.plan.summary.resolvedScope.podGroupCount}, nodes={.status.plan.summary.resolvedScope.nodeCount}{"\n"}'
# 查看受影响的工作负载标识、计划迁移的 Pod 及源节点/接收节点。
kubectl get repackrun ascend-scoped-dryrun \
  -o jsonpath='{range .status.plan.moves[*]}{.owner.kind}{"/"}{.owner.name}{"\t"}{.namespace}{"/"}{.podGroupName}{"\t"}{.cards}{" cards\n"}{range .pods[*]}{"  "}{.name}{": "}{.fromNode}{" -> "}{.toNode}{"\n"}{end}{end}'
kubectl get repackrun ascend-scoped-dryrun \
  -o jsonpath='{.status.plan.freedNodes}{"\n"}'
```
检查结果时应确认：
- resolvedScope的数量符合预期。
- plan.moves\[\].owner只包含Scope允许且未受保护的VCJob或ModelServing。
- plan.freedNodes只包含accelerator-pool=ascend-npu的节点。
 
#### 步骤四：加入收益门槛和执行预算
在生产Execute前，建议继续按需增加以下字段。
- 需要避免执行收益过低的计划时，在已有goals条目中增加最低碎片改善幅度：
- 需要限制单次影响范围时，增加maxPerRun。两个上限同时生效：
- 需要自动清理已完成的DryRun时，再增加TTL：
以上字段均为可选扩展。将目标资源、Scope、收益门槛和执行预算合并后，推荐的生产DryRun如下：
```
apiVersion: repack.volcano.sh/v1alpha1
kind: RepackRun
metadata:
  name: ascend-guarded-dryrun
spec:
  mode: DryRun
  scope:
    podGroups:
      include:
        selector:
          matchLabels:
            repack.volcano.sh/eligible: "true"
      exclude:
        selector:
          matchLabels:
            repack.volcano.sh/protected: "true"
    nodes:
      include:
        selector:
          matchLabels:
            accelerator-pool: ascend-npu
  goals:
    - resource: huawei.com/ascend-1980
      minFragImprovementPercent: 10
  maxPerRun:
    podGroups: 1
    resources:
      huawei.com/ascend-1980: 4
  ttlSecondsAfterFinished: 86400
```
执行以下命令：
```
kubectl apply -f ascend-guarded-dryrun.yaml
kubectl get repackrun ascend-guarded-dryrun -w
# 一次核对预期收益、腾空节点数和迁移卡数。
kubectl get repackrun ascend-guarded-dryrun \
  -o jsonpath='fragmentation={.status.plan.summary.fragBeforePercent}% -> {.status.plan.summary.fragAfterPercent}%, freedNodes={.status.plan.summary.freedNodeCount}, movedCards={.status.plan.summary.movedCardCount}{"\n"}'
```
假设DryRun输出如下，表示迁移批量训练PodGroup的2张卡，预计释放ascend-node-01，碎片率从33%降至0%：
```
status:
  phase: Succeeded
  plan:
    summary:
      fragBeforePercent: 33
      fragAfterPercent: 0
      freedNodeCount: 1
      movedCardCount: 2
      resolvedScope:
        podGroupCount: 1
        nodeCount: 3
    moves:
      - namespace: repack-demo
        podGroupName: ascend-batch-training-2f6c840d-8b14-4e45-a4cf-b43c249acbfd
        owner:
          apiVersion: batch.volcano.sh/v1alpha1
          kind: Job
          name: ascend-batch-training
        cards: 2
        pods:
          - name: ascend-batch-training-worker-0
            fromNode: ascend-node-01
            toNode: ascend-node-02
            cards: 1
          - name: ascend-batch-training-worker-1
            fromNode: ascend-node-01
            toNode: ascend-node-03
            cards: 1
    freedNodes:
      - ascend-node-01
  conditions:
    - type: Complete
      status: "True"
      reason: RepackRecommended
```
#### 步骤五：创建独立Execute Run
DryRun和Execute是两种独立执行模式。Execute不会执行旧DryRun中保存的计划，而会基于实时状态重新规划。审核生产DryRun后，复制其完整YAML，保留已经确认的Scope、目标和执行预算，只修改以下字段：
```
metadata:
  # 必须使用新名称创建新的 RepackRun。
  name: ascend-execute-001
  labels:
    change-ticket: change-20260730
spec:
  # 将 DryRun 改为 Execute。
  mode: Execute
  # 可选：覆盖 Eviction API 使用的优雅终止时间。
  eviction:
    gracePeriodSeconds: 30
  # 可选：Execute 结果保留 7 天用于审计。
  ttlSecondsAfterFinished: 604800
```
该代码块是相对于生产DryRun的修改片段。创建Execute文件时，仍需保留原CR中的scope、goals和maxPerRun。
```
kubectl apply -f ascend-execute-001.yaml
kubectl get repackrun ascend-execute-001 -w
# 持续观察被驱逐 Pod、重建 Pod 和节点变化。
kubectl get pod -n repack-demo -o wide -w
```
Execute使用Kubernetes Eviction API，PDB仍然生效。若另一个Execute正在运行或仍处于执行冷却时间，新Run会保持Pending，可通过Conditions中的 AnotherRunActive或ExecuteCooldownActive识别。
#### 步骤六：验证Execute是否生效
1. 检查Conditions和最终收益。
   ```
   kubectl get repackrun ascend-execute-001 -o wide
   kubectl get repackrun ascend-execute-001 \
     -o jsonpath='{range .status.conditions[*]}{.type}{"\t"}{.status}{"\t"}{.reason}{"\t"}{.message}{"\n"}{end}'
   # 对比计划值和实际值。
   kubectl get repackrun ascend-execute-001 \
     -o jsonpath='plan: frag {.status.plan.summary.fragBeforePercent}% -> {.status.plan.summary.fragAfterPercent}%, freed {.status.plan.summary.freedNodeCount}, moved {.status.plan.summary.movedCardCount}{"\n"}result: frag {.status.result.fragAfterPercent}%, freed {.status.result.freedNodeCount}, moved {.status.result.movedCardCount}, verified {.status.result.metricsVerified}{"\n"}'
   ```
   
2. 检查每个被驱逐Pod对应的重建Pod和实际调度节点。
   ```
   kubectl get repackrun ascend-execute-001 \
     -o jsonpath='{range .status.relocations[*]}{.namespace}{"/"}{.victimPodName}{"\teviction="}{.eviction.phase}{"\tplacement="}{.placement.phase}{"\tplanned="}{.plannedNodeName}{"\tselected="}{.placement.selectedNodeName}{"\tactual="}{.placement.actualNodeName}{"\treplacement="}{.placement.replacementPodName}{"\n"}{end}'
   # 确认 VCJob、ModelServing和Pod恢复正常，并检查计划腾空节点。
   kubectl get vcjob -n repack-demo
   kubectl get modelserving -n repack-demo
   kubectl get podgroup -n repack-demo
   kubectl get pod -n repack-demo -o wide
   kubectl get pod -A --field-selector spec.nodeName=ascend-node-01 -o wide
   kubectl describe node ascend-node-01
   ```
   涉及字段如下：
   
   | 字段               | 说明                                                                   |
   |:---|:---|
   | plannedNodeName  | Execute阶段计算出的目标节点，作为不可变计划记录。                                         |
   | selectedNodeName | Repack根据实时调度快照，为重建Pod写入nominatedNodeName的节点。                         |
   | actualNodeName   | Volcano Scheduler最终绑定的节点。由Repack Controller观察重建Pod的spec.nodeName后写入。 |
      
   nominatedNodeName不会强制绑定或预留资源，因此actualNodeName可能与前两个字段不同。若调度到其他节点后仍达到腾空目标，Run可以以 ExecutionCompletedWithAlternativePlacement成功结束。
   
3. 特殊场景：ModelServing验证
 
#### 步骤七：在整理后的节点上提交大型训练任务
Repack仅负责整理节点资源，不会为后续任务强制预留容量。因此，在完成 Execute 并确认节点释放后，应尽快下发大型训练任务，并通过工作负载的节点约束，将任务调度至整理好的昇腾资源池。
1. 任务提交示例。该Volcano Job需要2个Worker同时准入，每个Worker占用4张NPU卡，合计8卡------恰好可由一次整理后释放出的整节点承载。
   ```
   apiVersion: batch.volcano.sh/v1alpha1
   kind: Job
   metadata:
     name: ascend-large-training
     namespace: repack-demo
     labels:
       workload-type: training
   spec:
     schedulerName: volcano
     queue: default
     minAvailable: 2
     tasks:
       - name: worker
         replicas: 2
         template:
           spec:
             nodeSelector:
               accelerator-pool: ascend-npu
             containers:
               - name: trainer
                 image: alpine:3.20
                 command: ["sh", "-c", "tail -f /dev/null"]
                 resources:
                   requests:
                     huawei.com/ascend-1980: 4
                   limits:
                     huawei.com/ascend-1980: 4
             restartPolicy: Never
   ```
   
2. 执行并验证任务。
   ```
   kubectl apply -f ascend-large-training.yaml
   kubectl get job.batch.volcano.sh ascend-large-training -n repack-demo
   kubectl get pod -n repack-demo -l volcano.sh/job-name=ascend-large-training -o wide
   ```
   请根据节点实际卡数和训练并行策略调整replicas、minAvailable与单Pod NPU申请量。本验证关注的是整节点释放容量对任务的承载性，而非空闲卡的累计数量。
   
 
#### 完整RepackRun CR示例
下面的实例覆盖当前全部用户可配置的spec字段。RepackRun是集群级资源，不填写metadata.namespace。status由Repack组件维护，不应写入用户YAML。
#### YAML示例
```
apiVersion: repack.volcano.sh/v1alpha1
kind: RepackRun
metadata:
  # RepackRun 是集群级资源，不设置 namespace。
  name: ascend-complete-execute-001
  labels:
    # metadata.labels 仅用于检索和审计，不决定整理范围。
    accelerator-pool: ascend-npu
    change-ticket: change-20260730
spec:
  # DryRun 只生成计划；Execute 会重新规划并通过 Eviction API 驱逐 Pod。
  mode: Execute
  # 限定允许驱逐的工作负载和允许作为腾空目标的节点。
  # scope 为空时，这两个维度均不限制范围。
  scope:
    podGroups:
      # 工作负载驱逐范围。selector 匹配继承到 PodGroup 的工作负载标签。
      include:
        # 同一 include 内，selector 与 names 按并集计算。
        selector:
          matchLabels:
            repack.volcano.sh/eligible: "true"
        # PodGroup 名称格式为 namespace/name；自动生成的 PodGroup 推荐使用 selector。
        names:
          - repack-demo/manually-managed-training-pg
      # exclude 优先级高于 include，命中的工作负载不会被驱逐。
      exclude:
        selector:
          matchExpressions:
            - key: repack.volcano.sh/protected
              operator: In
              values:
                - "true"
        names:
          - repack-demo/protected-training-pg
    nodes:
      # 只限定可作为腾空目标的节点，不限制待迁移 Pod 的接收节点。
      include:
        # selector 与 names 按并集计算。
        selector:
          matchLabels:
            accelerator-pool: ascend-npu
        names:
          - ascend-node-03
      # 从腾空目标中排除维护中或本次不允许整理的节点。
      exclude:
        selector:
          matchLabels:
            maintenance-state: draining
        names:
          - ascend-node-08
  # 指定本次整理的目标扩展资源；当前每个 Run 最多配置一种资源。
  goals:
    - resource: huawei.com/ascend-1980
      # 要求碎片率至少下降 10 个百分点，未达到时不推荐执行。
      minFragImprovementPercent: 10
  # 限制单次 Execute 的影响范围；任一上限被突破时，候选方案会被过滤。
  maxPerRun:
    # 最多影响 2 个工作负载，系统内部按不同 PodGroup 计数。
    podGroups: 2
    resources:
      # 本次最多迁移 8 张昇腾 NPU 卡。
      huawei.com/ascend-1980: 8
  eviction:
    # 覆盖 Eviction 请求的优雅终止时间；省略时使用 Pod 自身配置。
    gracePeriodSeconds: 30
  # Run 进入终态 7 天后自动删除；删除 CR 不会回滚已完成的迁移。
  ttlSecondsAfterFinished: 604800
```
同一include或exclude内的selector与names按并集计算，exclude的优先级高于include。PodGroup名采用namespace/name，节点名直接填写Kubernetes Node名。自动生成的PodGroup 名不稳定，生产配置优先使用从工作负载继承的标签选择。
**核心status示例（只读）**
以下展示一个Execute成功后的核心状态。status由Repack Engine和Volcano Controller Manager内置的Repack controller协同更新，用户不应手工填写。DryRun同样包含生命周期字段、Conditions和plan，但不会生成result和relocations。
```
status:
  # 简化生命周期：Pending、Running、Succeeded 或 Failed。
  # Conditions 是权威状态，phase 由 Conditions 派生。
  phase: Succeeded
  # 面向操作者的当前结论摘要。
  message: "Repack succeeded for huawei.com/ascend-1980: all 1 replacement Pod was scheduled and all 1 planned node was verified free [ascend-node-01]; cluster fragmentation changed from 33% to 0%."
  # 首次进入 Running 和首次进入终态的时间。
  # completionTime 也是 ttlSecondsAfterFinished 的计时起点。
  startTime: "2026-07-30T10:00:00Z"
  completionTime: "2026-07-30T10:03:20Z"
  # DryRun 和 Execute 都会保留的规划快照。
  # Execute 不会用实际结果覆盖 plan。
  plan:
    summary:
      # 计划前与完整计划成功后的预测碎片率，单位为百分比。
      fragBeforePercent: 33
      fragAfterPercent: 0
      # 预计释放的目标资源节点数。
      freedNodeCount: 1
      # 预计迁移的目标加速卡总数。
      movedCardCount: 1
      resolvedScope:
        # selector 解析后、当前使用目标资源的候选 PodGroup 数。
        # 这不表示所有 PodGroup 最终都会被迁移。
        podGroupCount: 1
        # Scope 内提供目标资源、可作为腾空目标的节点数。
        nodeCount: 3
    # 按 PodGroup 汇总的计划迁移明细。
    moves:
      - namespace: repack-demo
        podGroupName: ascend-batch-training-2f6c840d
        # PodGroup ownerReference 中记录的直接工作负载控制器。
        owner:
          apiVersion: batch.volcano.sh/v1alpha1
          kind: Job
          name: ascend-batch-training
        # 该 PodGroup 本次计划迁移的目标加速卡总数。
        cards: 1
        pods:
          - name: ascend-batch-training-worker-0
            # Pod 的计划源节点和建议接收节点。
            fromNode: ascend-node-01
            toNode: ascend-node-02
            cards: 1
    # 完整计划成功后应释放目标资源的节点。
    freedNodes:
      - ascend-node-01
  # Execute 的实际观测结果；DryRun 中不存在。
  result:
    # 重建 Pod 完成调度后观测到的碎片率。
    fragAfterPercent: 0
    # 终态快照中实际验证已释放目标资源的节点。
    freedNodeCount: 1
    freedNodes:
      - ascend-node-01
    # Eviction API 已接受驱逐的 Pod 所对应的目标加速卡总数。
    movedCardCount: 1
    # true 表示以上收益指标来自重建 Pod 绑定后的一致 Scheduler 快照。
    # false 时不能将收益指标视为已经完成最终验证。
    metricsVerified: true
  # Execute 的逐 Pod 驱逐和重新调度记录；每个计划迁移的 Pod 对应一条。
  relocations:
    - namespace: repack-demo
      # 原始 PodGroup 名称，是不可变的计划身份。
      podGroupName: ascend-batch-training-2f6c840d
      # replacementPodGroupName 仅在重建 PodGroup 名称变化时出现，本例省略。
      victimPodName: ascend-batch-training-worker-0
      # UID 精确标识被驱逐的原 Pod，避免同名重建 Pod 被重复驱逐。
      victimPodUID: "11111111-1111-1111-1111-111111111111"
      # 规划阶段选择的建议接收节点，不会在执行过程中改写。
      plannedNodeName: ascend-node-02
      eviction:
        # Pending、InProgress、Accepted、IndirectlyRemoved 或 Rejected。
        phase: Accepted
      placement:
        # WaitingForReplacement、WaitingForNodeSelection、Nominated、
        # Placed 或 TimedOut。
        phase: Placed
        # 执行阶段基于实时快照选择并写入 nominatedNodeName 的节点。
        selectedNodeName: ascend-node-02
        # Repack 识别到的重建 Pod。
        replacementPodName: ascend-batch-training-worker-0
        replacementPodUID: "22222222-2222-2222-2222-222222222222"
        # Volcano Scheduler 最终绑定的节点，可能与 selectedNodeName 不同。
        actualNodeName: ascend-node-02
        # 本条 relocation 完成重建 Pod 调度协调的截止时间。
        expirationTime: "2026-07-30T10:10:30Z"
  # Job 风格的权威状态。终态成功时 Progressing=False、Complete=True。
  conditions:
    - type: Progressing
      status: "False"
      observedGeneration: 1
      lastTransitionTime: "2026-07-30T10:03:20Z"
      reason: ExecutionCompleted
      message: "Repack succeeded for huawei.com/ascend-1980: all 1 replacement Pod was scheduled and all 1 planned node was verified free [ascend-node-01]; cluster fragmentation changed from 33% to 0%."
    - type: Complete
      status: "True"
      observedGeneration: 1
      lastTransitionTime: "2026-07-30T10:03:20Z"
      # reason 给出本次 Run 的具体终态结论。
      reason: ExecutionCompleted
      message: "Repack succeeded for huawei.com/ascend-1980: all 1 replacement Pod was scheduled and all 1 planned node was verified free [ascend-node-01]; cluster fragmentation changed from 33% to 0%."
```
建议的生产操作顺序是：创建小范围DryRun，核对resolvedScope、计划收益和迁移明细；随后创建独立的小预算Execute；最后检查Conditions、status.result、relocation、工作负载状态和节点目标资源占用情况。
#### 状态说明
status.conditions是权威状态，status.phase是简化生命周期。常见状态如下：
- Pending：AnotherRunActive表示存在运行中的 Execute；ExecuteCooldownActive 表示仍处于冷却时间。
- Running：Planning、Evicting、ReconcilingPlacements分别表示规划、驱逐和重建Pod协调阶段。
- DryRun Succeeded：RepackRecommended表示存在可审核计划；NoFragmentation或InsufficientImprovement表示正常完成但无需执行。
- Execute Succeeded：ExecutionCompleted表示计划完成；ExecutionCompletedWithAlternativePlacement表示部分Pod调度到其他节点，但收益已验证。
- Failed时优先阅读status.message，再结合Conditions、相关 Pod/PDB 事件和引擎日志定位问题。
 
#### 常见问题
#### 为什么需要开启装箱策略 (binpack)？
碎片整理仅负责牵引其在整理计划（Plan）中主动驱逐的Pod，不会处理因ServingGroupRecreate、RoleRecreate、RestartJob、RestartPartition等策略被级联删除后重建的Pod。启用装箱策略后，调度器会优先将这类Pod调度到NPU/GPU利用率较高的节点，减少其落回待腾空节点的概率，从而保障碎片整理顺利进行。若不开启装箱策略，上述级联重建的Pod可能再次调度到正在腾空的节点，导致碎片整理无法完成资源回收，甚至出现新的资源碎片。
#### 节点上的DaemonSet会阻止整理吗？
不会，只要DaemonSet不申请本次 goals\[\].resource。Repack的腾空语义是释放目标扩展资源，不要求节点上不存在CPU-only Pod。
#### 节点上存在kube-scheduler调度的GPU/NPU Pod会怎样？
这类Pod通常没有可识别的Volcano PodGroup，因此**不可迁移** 。只要它请求了目标资源，所在节点**不能作为腾空目标**。
需要参与碎片整理的工作负载应由Volcano调度并关联PodGroup，否则应通过scope.nodes将其所在节点排除在腾空范围之外。
#### 只设置scope.nodes，不设置scope.podGroups，哪些工作负载可能被驱逐？
所有可识别PodGroup默认进入候选范围；scope.nodes仅限定腾空目标节点，不限定可驱逐的工作负载。生产环境应显式配置 scope.podGroups，避免扩大工作负载影响范围。
#### 需要单独给 PodGroup 添加标签吗？
取决于工作负载类型：
| 工作负载类型                        | 是否需要手动添加标签 | 说明                                                             |
|:---|:---|:---|
| VCJob                         | 不需要        | Job controller会自动复制VCJob的metadata.labels到PodGroup。             |
| ModelServing（兼容版本Kthena）      | 不需要        | 会自动复制ModelServing的metadata.labels到ServingGroup对应的PodGroup。     |
| Deployment/StatefulSet等通用工作负载 | 不需要        | Volcano-controller会为k8s原生负载自动创建PodGroup，并根据Pod中携带的label信息进行继承。 |
| 其他自建PodGroup的控制器              | 需要         | 用户自建PodGroup的场景下，需主动为PodGroup添加对应label信息。                      |
   
#### 为什么指定了节点范围，Pod 仍可能被调度到范围外的节点？
scope.nodes只限定腾空目标，不限定接收节点。若工作负载必须保留在指定节点范围，应使用节点亲和性、节点选择器或污点/容忍等调度约束限制其候选节点。
#### 为什么DryRun是 Succeeded，但没有移动计划？
Succeeded表示Run执行过程正常完成，不代表一定存在迁移计划。具体原因需查看Complete Condition：
- NoFragmentation：当前集群无碎片，无需整理。
- InsufficientImprovement：在给定范围、预算、可行性和收益阈值下，未找到满足条件的推荐方案。
 
#### 为什么Execute的最终腾空节点数少于DryRun？
DryRun是计划快照，Execute会基于实时状态重新规划。最终腾空节点数受实时资源变化、PDB、驱逐结果和重建 Pod 实际调度节点影响。因此应以Execute的status.result为准。
#### nominatedNodeName是否会将重建Pod强制绑定到指定节点？
不会。nominatedNodeName仅为调度器提供候选节点建议。Scheduler仍会根据实时资源和调度约束完成最终绑定。实际调度节点记录在status.relocations\[\].placement.actualNodeName中。
#### 如何停止或回滚？
Repack不支持自动回滚。删除RepackRun或关闭组件不会回滚已经完成的驱逐和重新调度。
- 尚未开始的Run：直接删除RepackRun即可。
- Execute已启动：停止后续Run，根据status.relocations和工作负载状态手动处理异常。
- 关闭功能：必须确认不存在运行中的Execute后再关闭组件/删除RepackRun。
 
#### 如何收集排障信息？
在进行故障排查时，请收集以下信息以便快速定位问题：
```
# RepackRun概览
kubectl get repackrun -o wide
# 查看详细事件和状态
kubectl describe repackrun <run-name>
# 导出完整YAML（含status）
kubectl get repackrun <run-name> -o yaml
# 查看集群事件（按时间排序）
kubectl get events -A --sort-by=.lastTimestamp
```
排障反馈时，请一并提供RepackRun YAML、目标节点的Pod列表、相关PodGroup和PDB等信息。
