业务镜像替换
业务镜像替换用于处理主备集群镜像仓库不一致的问题。它只修改恢复到目标集群的 Kubernetes 资源中的镜像字段,不会复制镜像,也不会自动创建镜像拉取 Secret。
典型场景:
- 主集群使用
10.10.10.170:32070/e2e/...,备集群只能从10.10.10.171:32071/e2e/...拉取。 - 生产仓库是内网地址,灾备仓库是 Harbor、Registry 或离线仓库。
- 镜像 tag 经常变化,不希望每次版本发布后都重新维护完整镜像替换规则。
这里说的是业务应用镜像替换,不是添加集群时的 Velero 镜像源。Velero 镜像源只影响 Velero 组件安装,业务应用镜像应通过恢复策略中的批量修改或 rewriteImage 配置。
选择哪种方式
| 方式 | 动作 | 适用场景 | 是否推荐用于高频发版 |
|---|---|---|---|
| 完全替换 | replaceExactValue | 源镜像完整值固定,或者只想替换一个确定的镜像。 | 否 |
| 前缀重写 | rewriteImage | 主备仓库前缀不同,但镜像路径和 tag 需要跟随源端当前值。 | 是 |
| 手写自定义规则 | modifierRules | 需要精确控制某个资源、某个 JSON Patch 路径。 | 只适合少量固定路径 |
如果业务镜像 tag 变化频率高,优先使用 rewriteImage。它会在 ResourceSync 或 Drill 恢复构建阶段读取源集群当前 workload spec 中的镜像,再按前缀动态生成修改规则,不依赖提交实例时保存的旧 tag。
前置条件
镜像替换只改资源清单,不负责镜像分发。配置前需要确认:
- 目标仓库中已经存在替换后的镜像 tag 或 digest。
- 目标集群节点可以访问目标镜像仓库。
- 私有仓库需要认证时,目标命名空间中有可用的 imagePullSecret,或者应用资源中已经引用了正确的 Secret。
restorePolicy.useUnifiedDirectionResolver已开启。
完全替换
完全替换适合镜像值稳定的场景。例如只把一个固定镜像:
10.10.10.170:32070/e2e/disaster-web-test:v0.1
替换为:
10.10.10.171:32071/e2e/disaster-web-test:v0.1
在控制台的 资源批量修改/删除 输入框中填写数组:
[
{
"id": "replace-170-image-to-171-image",
"action": "replaceExactValue",
"enabled": true,
"applyTo": ["resourceSync", "drill"],
"sourceValue": "10.10.10.170:32070/e2e/disaster-web-test:v0.1",
"targetValue": "10.10.10.171:32071/e2e/disaster-web-test:v0.1",
"directionPolicy": "Auto"
}
]
如果直接编辑 DisasterInstance,完整片段如下:
spec:
restorePolicy:
useUnifiedDirectionResolver: true
bulkModifierActions:
- id: replace-170-image-to-171-image
action: replaceExactValue
enabled: true
applyTo:
- resourceSync
- drill
sourceValue: 10.10.10.170:32070/e2e/disaster-web-test:v0.1
targetValue: 10.10.10.171:32071/e2e/disaster-web-test:v0.1
directionPolicy: Auto
replaceExactValue 会扫描保护范围内资源的字符串叶子节点,命中完整 sourceValue 后生成可逆修改规则。它会跳过 /status/**、/metadata/finalizers/**、/metadata/ownerReferences/** 等禁止修改路径。
限制:
- 镜像 tag 一旦从
v0.1变成v0.2,这条完全替换规则就不再命中新镜像。 - 如果手写
modifierRules,不要把 patch path 指向/status/containerStatuses/*/image,该路径属于 Pod status,恢复时禁止修改。
前缀重写
前缀重写适合镜像版本频繁变化的场景。只维护稳定的源仓库前缀和目标仓库前缀,tag 跟随源端当前 workload。
例如源端当前镜像是:
10.11.11.1:5000/blueking/bcs-bkcmdb-synchronizer:v1.30.0
希望恢复到目标集群时变成:
registry-test.xxx.xxx.com:30088/dr_images/10_11_11_1_5000/blueking/bcs-bkcmdb-synchronizer:v1.30.0
在控制台的 资源批量修改/删除 输入框中填写数组:
[
{
"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"
}
}
]
170/171 真实环境中,源端镜像前缀类似 10.134.81.9:5000/,目标仓库需要保留源端路径并加上归档目录时,可以直接写:
[
{
"id": "rewrite-primary-registry",
"action": "rewriteImage",
"enabled": true,
"applyTo": ["resourceSync", "drill"],
"directionPolicy": "Auto",
"imageRewrite": {
"sourcePrefix": "10.134.81.9:5000/",
"targetPrefix": "registry-tke.szmacloud.csg:30088/dr_images/10.134.81.9_5000/",
"unmatchedPolicy": "Keep",
"digestPolicy": "Preserve"
}
}
]
例如源端 initContainer 镜像:
10.134.81.9:5000/groundnuty/k8s-wait-for:v1.5.1
恢复到目标集群时会生成:
registry-tke.szmacloud.csg:30088/dr_images/10.134.81.9_5000/groundnuty/k8s-wait-for:v1.5.1
如果源端后来升级为:
10.11.11.1:5000/blueking/bcs-bkcmdb-synchronizer:v1.31.0
下一次资源同步、演练或切换触发的最终同步会动态生成目标镜像:
registry-test.xxx.xxx.com:30088/dr_images/10_11_11_1_5000/blueking/bcs-bkcmdb-synchronizer:v1.31.0
完整 DisasterInstance 片段:
spec:
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
字段说明
| 字段 | 说明 |
|---|---|
sourcePrefix | 源端镜像前缀。建议以 / 结尾,系统会做前缀规范化。 |
targetPrefix | 目标端镜像前缀。替换时保留源镜像前缀之后的路径、tag 和 digest。 |
unmatchedPolicy | 未匹配前缀时的处理方式。Keep 表示保持原值,Fail 表示编译失败。默认 Keep。 |
digestPolicy | digest 处理方式。当前支持 Preserve,表示保留原镜像 digest。 |
directionPolicy | 建议使用 Auto,由运行时方向决定正向或反向替换。 |
applyTo | 支持 resourceSync 和 drill,不支持 dataSync。 |
多条前缀同时匹配时,最长前缀优先。如果存在长度相同但目标不同的冲突规则,编译会失败。
生效范围
rewriteImage 会读取源集群当前 workload spec 中的镜像字段,并在恢复构建阶段生成修改规则。覆盖的常见字段包括:
Deployment.spec.template.spec.containers[*].imageStatefulSet.spec.template.spec.containers[*].imageDaemonSet.spec.template.spec.containers[*].imageJob.spec.template.spec.containers[*].imageCronJob.spec.jobTemplate.spec.template.spec.containers[*].imagePod.spec.containers[*].image- 对应的
initContainers和ephemeralContainers
它不会修改:
Pod.status.containerStatuses[*].imagePod.status.containerStatuses[*].imageID- 任意
/status/**字段
Pod status 是运行时观测状态,不是期望状态。镜像替换是否成功,应看 Deployment、StatefulSet、Job 或 Pod 的 spec 镜像字段,而不是只看 status.containerStatuses[*].image。
和资源选择的关系
不要为了避开 Pod status 镜像而配置:
{
"resourceSelection": {
"excludedResources": ["pods"]
}
}
镜像替换逻辑本身会跳过 /status/**,不需要通过排除 Pod 来规避 status patch。是否排除 pods 应由容灾策略决定:如果业务只通过 Deployment/StatefulSet 管理 Pod,通常可以不单独恢复裸 Pod;如果保护范围中存在需要恢复的裸 Pod,排除 pods 会影响这些资源恢复。
验证方式
保存实例后,先确认恢复策略已经写入:
kubectl -n disaster-system get disasterinstance <instance-name> \
-o jsonpath='{.spec.restorePolicy.bulkModifierActions}{"\n"}'
触发资源同步、演练或切换后,在目标集群检查 workload spec:
kubectl --context <target-context> -n <namespace> get deploy <name> \
-o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
也可以检查 Pod spec:
kubectl --context <target-context> -n <namespace> get pod <pod-name> \
-o jsonpath='{.spec.containers[0].image}{"\n"}'
检查 initContainers:
kubectl --context <target-context> -n <namespace> get pod <pod-name> \
-o jsonpath='{range .spec.initContainers[*]}{.name}{"="}{.image}{"\n"}{end}'
如果 Pod status 中仍显示旧 tag,但 spec 已经是目标镜像,优先以 spec 为准。使用同一镜像内容重新打 tag 时,容器运行时可能在 status 中展示原始镜像名或旧 tag。
常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
ModifierRuleRejected ... /status/containerStatuses/0/image is forbidden | 手写规则或旧规则命中了 Pod status。 | 不要 patch /status/**;改用批量修改或 rewriteImage。 |
| initContainer 镜像没有变化 | 使用了旧镜像源映射入口、完整值规则未命中,或没有触发新的 ResourceSync/Drill 恢复构建。 | 使用 bulkModifierActions[].action=rewriteImage,确认 applyTo 包含 resourceSync 或 drill,然后检查 .spec.initContainers[*].image。 |
| tag 升级后仍恢复旧镜像 | 使用了完整值 replaceExactValue,规则只匹配旧 tag。 | 改用 rewriteImage,只维护源/目标前缀。 |
modifierRuleSnapshot 为空 | 仅配置 rewriteImage 时,规则在运行时动态生成。 | 这是预期行为,检查 ResourceSync/Drill 结果即可。 |
目标 Pod ImagePullBackOff | 目标镜像不存在、Secret 缺失或节点无法访问仓库。 | 先把镜像推到目标仓库,并确认 imagePullSecret 和网络。 |
conditions.groupResource is required | 把手写 modifierRules 当成批量修改写法使用。 | 批量修改入口填写 bulkModifierActions 数组;手写 modifierRules 必须包含 conditions.groupResource。 |
| 前缀没有生效 | sourcePrefix 与源端实际镜像不匹配。 | 用 kubectl get deploy -o jsonpath 查看源端 spec 镜像,按稳定前缀重新配置。 |
推荐实践
- 新配置优先使用
rewriteImage,不要再依赖旧的业务镜像源映射入口。 - 只把稳定仓库前缀写入 DSL,不要把频繁变化的 tag 写进
sourceValue。 - 每次切换前确认目标仓库已经有对应 tag。
- 使用
directionPolicy: Auto,让 Failover、Undo、Reprotect 按运行时方向自动选择替换方向。 - 验证时看 workload spec 镜像字段,不用 Pod status 判断规则是否生效。