资源定制化修改与批量修改删除
资源定制化修改用于处理源集群和目标集群之间的资源差异。常见场景包括:
- 源、目标集群的 IngressClass 不一致。
- 目标集群不允许继续使用源集群的
nodeSelector、affinity或特定注解。 - 业务配置中写死了源端地址,需要在目标端替换为目标端地址。
- 演练环境需要移除某些只适用于生产环境的标签或注解。
- 需要让 ResourceSync 恢复出来的 standby 资源按目标集群规则落地。
资源定制化修改在恢复过程中生效。它不会修改源集群原始资源,而是在 AppRestore 创建 Velero Restore 前生成 Resource Modifier 配置,让目标集群恢复出的资源被修改后再落地。
能力边界
当前实例恢复策略中有两类入口:
| 能力 | 字段 | 作用 |
|---|---|---|
| 资源定制化修改 | restorePolicy.modifierRules / restorePolicy.modifierRulesText | 精确指定匹配条件和 JSON Patch。 |
| 资源批量修改/删除 | restorePolicy.bulkModifierActions / restorePolicy.bulkModifierActionsText | 按实例保护范围扫描源集群资源,自动生成最终修改规则。 |
如果只关注业务镜像仓库不一致、镜像 tag 频繁变化或 rewriteImage 配置,请先阅读 业务镜像替换。
这里的“删除”指删除资源对象中的字段或键,例如删除某个 annotation、label、nodeSelector 键。它不是删除整个 Kubernetes 资源对象。如果需要不恢复某类资源,应使用 restorePolicy.resourceSelection.excludedResources 或 excludedNamespaceScopedResources。
使用 modifierRules 或 bulkModifierActions 时,建议同时开启:
{
"restorePolicy": {
"useUnifiedDirectionResolver": true
}
}
如果未开启统一方向解析器,新 DSL 规则可能无法被 operator 编译执行。
控制台入口
进入 容灾管理 / 实例配置,创建或编辑容灾实例,展开 高级选项:
- 资源定制化修改:适合输入明确的 JSON Patch 规则。
- 资源批量修改/删除:适合按值或键批量生成修改规则。
配置完成后保存实例。server 会在写入 DisasterInstance 前校验规则,并在需要时生成 modifierRuleSnapshot。
执行时机
资源定制化修改会被应用到实例恢复链路:
| 链路 | applyTo 值 | 说明 |
|---|---|---|
| 数据同步恢复 | dataSync | 用于 DataSync 触发的恢复。 |
| 资源同步恢复 | resourceSync | 用于 ResourceSync 恢复资源骨架。 |
| 容灾演练 | drill | 用于 Drill 恢复或演练覆盖配置。 |
modifierRules 可使用 dataSync、resourceSync、drill。bulkModifierActions 当前只支持 resourceSync 和 drill,不支持 dataSync。
如果 applyTo 为空:
modifierRules由 operator 的默认规则处理。bulkModifierActions默认等价于["resourceSync"]。
定制化修改规则
modifierRules 支持两种模式。
| 模式 | mode | 适用场景 |
|---|---|---|
| Velero 原生规则 | veleroNative | 直接下发 JSON Patch,适合单向修改或删除字段。 |
| 可逆规则 | reversible | 源、目标之间存在成对值,Failover、Reprotect、Drill 需要按方向自动选择值。 |
匹配条件
每条规则通过 conditions 匹配资源:
| 字段 | 说明 |
|---|---|
groupResource | 必填。资源复数名加 API group,例如 deployments.apps、statefulsets.apps、ingresses.networking.k8s.io、persistentvolumeclaims。 |
resourceNameRegex | 可选。按资源名称正则匹配。 |
namespaces | 可选。限制命名空间。不能超出实例保护的命名空间范围。 |
labelSelector | 可选。Kubernetes LabelSelector。 |
JSON Pointer 路径
path 使用 JSON Pointer 格式:
/spec/template/spec/containers/0/image
/metadata/annotations/example.com~1role
路径规则:
- 必须以
/开头。 /status、/metadata/finalizers、/metadata/ownerReferences禁止修改。- 路径中的
/要写成~1,~要写成~0。 - 数组下标从
0开始。 add到数组尾部可使用/-,但可逆规则不支持/-。
示例:修改 IngressClass
源集群使用 nginx,目标集群使用 traefik 时,可以配置 veleroNative 规则:
{
"restorePolicy": {
"useUnifiedDirectionResolver": true,
"modifierRules": [
{
"id": "ingress-class-to-traefik",
"mode": "veleroNative",
"enabled": true,
"applyTo": ["resourceSync", "drill"],
"conditions": {
"groupResource": "ingresses.networking.k8s.io",
"namespaces": ["demo"]
},
"veleroRule": {
"patches": [
{
"operation": "replace",
"path": "/spec/ingressClassName",
"value": "traefik"
}
]
},
"directionPolicy": "ForwardOnly",
"onConflict": "Fail"
}
]
}
}
如果源、目标会反向保护,建议使用可逆规则:
{
"restorePolicy": {
"useUnifiedDirectionResolver": true,
"modifierRules": [
{
"id": "ingress-class-reversible",
"mode": "reversible",
"enabled": true,
"applyTo": ["resourceSync", "drill"],
"conditions": {
"groupResource": "ingresses.networking.k8s.io",
"namespaces": ["demo"]
},
"pair": {
"path": "/spec/ingressClassName",
"sourceValue": "nginx",
"targetValue": "traefik"
},
"directionPolicy": "Auto",
"onConflict": "Fail"
}
]
}
}
directionPolicy=Auto 会根据当前恢复方向选择 sourceValue 或 targetValue。如果只允许源到目标方向生效,使用 ForwardOnly;如果只允许反向保护方向生效,使用 ReverseOnly。
示例:修改指定 PVC StorageClass
如果只需要改写少量 PVC 的 spec.storageClassName,可以用可逆规则精确匹配 PVC 名称。下面示例把 blueking 命名空间中的两个 PVC 在 DataSync 恢复时改到目标集群的 nfs-client:
{
"restorePolicy": {
"useUnifiedDirectionResolver": true,
"modifierRules": [
{
"id": "rewrite-skywalking-pvc-sc",
"mode": "reversible",
"enabled": true,
"applyTo": ["dataSync"],
"priority": 100,
"conditions": {
"groupResource": "persistentvolumeclaims",
"namespaces": ["blueking"],
"resourceNameRegex": "^bk-skywalking-agent-nfs-pvc$"
},
"pair": {
"path": "/spec/storageClassName",
"sourceValue": "local-storage",
"targetValue": "nfs-client"
},
"directionPolicy": "Auto",
"onConflict": "Fail"
},
{
"id": "rewrite-bkrepo-pvc-sc",
"mode": "reversible",
"enabled": true,
"applyTo": ["dataSync"],
"priority": 100,
"conditions": {
"groupResource": "persistentvolumeclaims",
"namespaces": ["blueking"],
"resourceNameRegex": "^bk-repo-bkrepo-nfs-pvc$"
},
"pair": {
"path": "/spec/storageClassName",
"sourceValue": "bkrepo-nfs",
"targetValue": "nfs-client"
},
"directionPolicy": "Auto",
"onConflict": "Fail"
}
]
}
}
如果是在控制台的 资源定制化修改 文本框中填写,通常只填写 modifierRules 数组本身,不需要外层 restorePolicy。JSON 必须是数组或对象中合法的数组字段,不能直接粘贴两个用逗号分隔的对象片段。
示例:删除 nodeSelector
目标集群节点标签和源集群不同,恢复工作负载时可以移除 nodeSelector:
{
"restorePolicy": {
"useUnifiedDirectionResolver": true,
"modifierRules": [
{
"id": "remove-node-selector",
"mode": "veleroNative",
"applyTo": ["resourceSync"],
"conditions": {
"groupResource": "deployments.apps",
"namespaces": ["demo"],
"resourceNameRegex": "^web-.*"
},
"veleroRule": {
"patches": [
{
"operation": "remove",
"path": "/spec/template/spec/nodeSelector"
}
]
},
"directionPolicy": "ForwardOnly",
"onConflict": "Fail"
}
]
}
}
如果部分 Deployment 没有 nodeSelector,提交期 live validation 可能返回路径不存在。此时应缩小 resourceNameRegex 或 labelSelector,只匹配确实存在该字段的资源。
示例:添加或替换注解
给恢复到目标集群的 Deployment 添加注解:
{
"restorePolicy": {
"useUnifiedDirectionResolver": true,
"modifierRules": [
{
"id": "mark-restored-by-testudo",
"mode": "veleroNative",
"applyTo": ["resourceSync"],
"conditions": {
"groupResource": "deployments.apps",
"namespaces": ["demo"]
},
"veleroRule": {
"patches": [
{
"operation": "add",
"path": "/metadata/annotations/testudo.softcdata.com~1restored-by",
"value": "resourcesync"
}
]
}
}
]
}
}
注意 testudo.softcdata.com/restored-by 中的 / 在 JSON Pointer 中写作 ~1。
批量修改
bulkModifierActions 用于把批量修改意图交给平台处理。提交期静态动作会按实例保护范围扫描源集群资源,并生成可执行的 modifierRuleSnapshot;运行时动态动作只保存稳定 DSL,由 operator 在恢复构建阶段读取源集群真实资源后动态生成规则。
支持的动作:
| 动作 | action | 说明 |
|---|---|---|
| 批量替换精确值 | replaceExactValue | 扫描所有字符串叶子节点,等于 sourceValue 的字段会生成可逆替换规则。 |
| 批量删除键 | removeKey | 扫描对象/map 中等于 key 的键,并生成 remove patch。 |
| 动态镜像重写 | rewriteImage | 按镜像前缀匹配源集群真实 workload 镜像,在 ResourceSync/Drill 运行时动态生成镜像替换规则。 |
批量扫描范围来自:
DisasterInstance.spec.namespacesDisasterInstance.spec.labelSelectorrestorePolicy.resourceSelection
默认只扫描命名空间资源。只有在 resourceSelection 明确包含集群级资源时,静态批量动作才会扫描集群级资源。静态扫描会跳过 /status/**、/metadata/finalizers/** 和 /metadata/ownerReferences/** 等禁止路径;不需要通过排除 pods 来规避 status.containerStatuses[*].image。
批量替换固定值
例如业务资源里多处写了源端服务地址 10.10.0.12,目标端需要改为 10.20.0.12:
{
"restorePolicy": {
"useUnifiedDirectionResolver": true,
"bulkModifierActions": [
{
"id": "replace-service-ip",
"action": "replaceExactValue",
"enabled": true,
"applyTo": ["resourceSync", "drill"],
"sourceValue": "10.10.0.12",
"targetValue": "10.20.0.12",
"directionPolicy": "Auto"
}
]
}
}
保存实例时,server 会扫描源集群匹配范围内的资源。例如发现:
Deployment demo/web /spec/template/spec/containers/0/env/0/value = 10.10.0.12
会生成类似快照规则:
{
"id": "bulk-replace-service-ip-0001",
"mode": "reversible",
"applyTo": ["resourceSync", "drill"],
"priority": -100,
"conditions": {
"groupResource": "deployments.apps",
"namespaces": ["demo"],
"resourceNameRegex": "^web$"
},
"pair": {
"path": "/spec/template/spec/containers/0/env/0/value",
"sourceValue": "10.10.0.12",
"targetValue": "10.20.0.12"
},
"directionPolicy": "Auto",
"onConflict": "Fail"
}
实际持久化在:
DisasterInstance.spec.restorePolicy.modifierRuleSnapshot
DisasterInstance.spec.restorePolicy.modifierRuleSnapshotHash
批量删除键
例如源端资源中存在只适用于主站点的 annotation:
metadata.annotations.site-role=primary
可以批量删除键 site-role:
{
"restorePolicy": {
"useUnifiedDirectionResolver": true,
"bulkModifierActions": [
{
"id": "drop-site-role",
"action": "removeKey",
"enabled": true,
"applyTo": ["resourceSync"],
"key": "site-role"
}
]
}
}
removeKey 默认使用 directionPolicy=ForwardOnly。它会删除所有对象/map 中名为 site-role 的键,例如:
/metadata/annotations/site-role
/metadata/labels/site-role
如果只想删除某类资源中的键,应配合实例命名空间、标签筛选和 resourceSelection 缩小范围。
动态重写镜像前缀
如果镜像 tag 或 digest 变化频率很高,不建议用 replaceExactValue 写完整镜像值。完整值一变化,已经保存的静态 modifierRuleSnapshot 就会失效。此时应使用 rewriteImage,只声明稳定的源/目标镜像前缀。
注意这里的 rewriteImage 是 restorePolicy.bulkModifierActions 下的动态动作,不是旧的实例级镜像源映射入口。新实例建议使用这个 DSL 表达业务镜像前缀重写:
{
"restorePolicy": {
"useUnifiedDirectionResolver": true,
"bulkModifierActions": [
{
"id": "rewrite-primary-registry",
"action": "rewriteImage",
"enabled": true,
"applyTo": ["resourceSync", "drill"],
"directionPolicy": "Auto",
"imageRewrite": {
"sourcePrefix": "10.11.11.1:5000/",
"targetPrefix": "registry-test.xxx.xxx.com:30088/dr_images/10_11_11_1_5000/",
"unmatchedPolicy": "Keep",
"digestPolicy": "Preserve"
}
}
]
}
}
恢复构建阶段,operator 会从源集群读取当前 workload spec 中的镜像。例如源端当前镜像是:
10.11.11.1:5000/blueking/bcs-bkcmdb-synchronizer:v1.32.0
运行时会生成等价的可逆修改规则,把目标端镜像改为:
registry-test.xxx.xxx.com:30088/dr_images/10_11_11_1_5000/blueking/bcs-bkcmdb-synchronizer:v1.32.0
rewriteImage 的要点:
sourcePrefix和targetPrefix必填,末尾/会被归一化处理。applyTo支持resourceSync和drill,不支持dataSync。unmatchedPolicy支持Keep和Fail,为空时默认为Keep。digestPolicy当前只支持Preserve,会保留原镜像 tag 或 digest 后缀。- 多个前缀同时命中时使用最长前缀;同长度命中多个不同结果会拒绝本次编译。
- 动态编译覆盖
Deployment、StatefulSet、DaemonSet、Job、CronJob和Pod的spec镜像字段,包括containers、initContainers、ephemeralContainers。 - 动态编译只处理 workload spec 中的镜像字段,不会修改
/status/containerStatuses/*/image。
replaceExactValue 和 rewriteImage 的选择原则:
| 场景 | 推荐动作 | 原因 |
|---|---|---|
| 固定 IP、固定域名、固定注解值散落在多个字段 | replaceExactValue | 提交时可扫描出确定路径,并生成长期 modifierRuleSnapshot。 |
| 镜像仓库前缀不同,但镜像 tag/digest 经常变化 | rewriteImage | 运行时按源集群真实镜像动态生成规则,不依赖旧 tag。 |
| 需要删除 annotation、label 或 map key | removeKey | 语义是删除键,不是替换值。 |
文本输入字段
控制台或 API 可以使用文本字段提交规则:
| 文本字段 | 解析结果 |
|---|---|
restorePolicy.modifierRulesText | JSON 数组字符串,解析到 modifierRules。 |
restorePolicy.bulkModifierActionsText | JSON 对象或 JSON 数组字符串,解析到 bulkModifierActions。 |
modifierRulesText 示例:
[
{
"id": "patch-ingress",
"mode": "veleroNative",
"applyTo": ["resourceSync"],
"conditions": {
"groupResource": "ingresses.networking.k8s.io",
"namespaces": ["demo"]
},
"veleroRule": {
"patches": [
{
"operation": "replace",
"path": "/spec/ingressClassName",
"value": "traefik"
}
]
}
}
]
bulkModifierActionsText 可以是单个对象:
{
"id": "replace-service-ip",
"action": "replaceExactValue",
"sourceValue": "10.10.0.12",
"targetValue": "10.20.0.12"
}
也可以是数组:
[
{
"id": "replace-service-ip",
"action": "replaceExactValue",
"sourceValue": "10.10.0.12",
"targetValue": "10.20.0.12"
},
{
"id": "drop-site-role",
"action": "removeKey",
"key": "site-role"
}
]
不要同时提交语义不一致的结构化字段和文本字段。例如 modifierRules 与 modifierRulesText 同时存在但内容不同,server 会返回:
ModifierRulesInputConflict
bulkModifierActions 与 bulkModifierActionsText 冲突时会返回:
BulkModifierActionsInputConflict
更新实例
可以通过实例更新接口修改恢复策略:
curl -X PUT "$TESTUDO_SERVER/apis/disasterinstances.testudo.softcdata.com/v1/instances/demo-dr?namespace=disaster-system" \
-H "Content-Type: application/json" \
-d '{
"restorePolicy": {
"useUnifiedDirectionResolver": true,
"modifierRules": [
{
"id": "remove-node-selector",
"mode": "veleroNative",
"applyTo": ["resourceSync"],
"conditions": {
"groupResource": "deployments.apps",
"namespaces": ["demo"]
},
"veleroRule": {
"patches": [
{
"operation": "remove",
"path": "/spec/template/spec/nodeSelector"
}
]
}
}
]
}
}'
更新后建议触发一次资源同步,让目标集群重新生成 standby 资源。
curl -X POST "$TESTUDO_SERVER/apis/disasterinstances.testudo.softcdata.com/v1/instances/demo-dr/actions?namespace=disaster-system" \
-H "Content-Type: application/json" \
-d '{
"action": "syncresource"
}'
也可以在控制台实例详情页执行 同步资源。
验证
查看实例策略
kubectl -n disaster-system get disasterinstance demo-dr -o yaml
重点查看:
spec:
restorePolicy:
useUnifiedDirectionResolver: true
modifierRules:
bulkModifierActions:
modifierRuleSnapshot:
modifierRuleSnapshotHash:
如果配置了 replaceExactValue 或 removeKey,但没有 modifierRuleSnapshot 或 modifierRuleSnapshotHash,说明提交期静态批量规则还没有成功生成快照。仅配置 rewriteImage 时,modifierRuleSnapshot 和 modifierRuleSnapshotHash 可以为空,这是预期行为,因为镜像规则会在 ResourceSync/Drill 恢复构建阶段动态生成。
查看 ResourceSync 和 AppRestore
kubectl -n disaster-system get resourcesync
kubectl -n disaster-system describe resourcesync <resourcesync-name>
kubectl -n disaster-system get apprestore
kubectl -n disaster-system describe apprestore <apprestore-name>
AppRestore.spec.resourceModifierRules 是 operator 最终传给 Velero 的可执行规则。
查看 Velero ResourceModifier ConfigMap
operator 会在目标集群的 velero 命名空间创建 Resource Modifier ConfigMap:
kubectl --kubeconfig=<target-kubeconfig> -n velero get configmap
kubectl --kubeconfig=<target-kubeconfig> -n velero get configmap <modifier-configmap-name> -o yaml
ConfigMap 中会包含:
version: v1
resourceModifierRules:
- conditions:
groupResource: deployments.apps
patches:
- operation: remove
path: /spec/template/spec/nodeSelector
查看目标资源
资源同步完成后,在目标集群检查实际资源:
kubectl --kubeconfig=<target-kubeconfig> -n demo get deploy web -o yaml
kubectl --kubeconfig=<target-kubeconfig> -n demo get ingress -o yaml
确认被修改字段已经符合预期。
常见错误
| 错误 | 常见原因 | 处理方式 |
|---|---|---|
ModifierRulesTextInvalid | modifierRulesText 不是合法 JSON 数组字符串。 | 校验 JSON 格式,确保最外层是数组。 |
BulkModifierActionsTextInvalid | bulkModifierActionsText 不是合法 JSON 对象或数组。 | 校验 JSON 格式。 |
ModifierRulesInputConflict | 同时提交了 modifierRules 和 modifierRulesText,且内容不一致。 | 只保留一个入口,或确保两者语义一致。 |
BulkModifierActionsInputConflict | 同时提交了 bulkModifierActions 和 bulkModifierActionsText,且内容不一致。 | 只保留一个入口,或确保两者语义一致。 |
matched zero resources | 规则或批量动作没有匹配到任何资源。 | 检查命名空间、资源类型、名称正则和标签选择器。 |
path segment not found | JSON Pointer 指向的字段不存在。 | 缩小匹配范围,或先确认源资源中该字段存在。 |
applyTo=dataSync is not supported | bulkModifierActions 配置了 dataSync。 | 批量动作只用于 resourceSync 或 drill。 |
imageRewrite.sourcePrefix is required | rewriteImage 没有配置源镜像前缀。 | 填写 imageRewrite.sourcePrefix。 |
imageRewrite.targetPrefix is required | rewriteImage 没有配置目标镜像前缀。 | 填写 imageRewrite.targetPrefix。 |
imageRewrite.digestPolicy must be Preserve | 配置了当前不支持的 digest 策略。 | 删除该字段或设置为 Preserve。 |
unified direction resolver is disabled | 使用新 DSL 但未开启 useUnifiedDirectionResolver。 | 设置 restorePolicy.useUnifiedDirectionResolver=true。 |
生产建议
- 先在演练实例或演练命名空间验证规则,不要直接在生产切换链路首次启用复杂规则。
- 优先使用
resourceNameRegex、namespaces、labelSelector缩小作用范围。 - 对需要反向保护的字段优先使用
reversible,不要写死单向replace。 - 删除类规则先从 annotation、label 这类低风险字段开始验证。
- 批量替换只匹配字符串叶子节点,不能替换数字、布尔、对象或数组。
- 静态批量动作匹配不到资源会失败,这是为了避免规则静默无效。
- 使用静态批量动作时,保存配置后检查
modifierRuleSnapshotHash,确认 server 已生成最终快照。 - 使用
rewriteImage时,不要把当前 tag 写进sourceValue/targetValue;只维护稳定前缀,并在资源同步或演练后检查目标 workload 镜像。 - 资源同步完成后必须到目标集群查看实际资源 YAML,而不是只看实例状态。