Application Image Rewrite
Application image rewrite handles different business image registries between primary and standby clusters. It changes image fields in restored Kubernetes resources. It does not copy images and does not create image pull Secrets automatically.
Typical cases:
- The primary cluster uses
10.10.10.170:32070/e2e/..., while the standby cluster can only pull from10.10.10.171:32071/e2e/.... - Production images live in an internal registry, while the DR site uses Harbor, Registry, or an offline registry.
- Image tags change often, and you do not want to update exact image replacement rules after every release.
This page covers business application images. It is different from the Velero image registry configured when adding a cluster. The Velero image registry only affects Velero component installation.
Choose A Method
| Method | Action | Use case | Recommended for frequent releases |
|---|---|---|---|
| Exact replacement | replaceExactValue | A specific full image value is stable. | No |
| Prefix rewrite | rewriteImage | Registry prefixes differ, while image path and tag should follow the current source workload. | Yes |
| Hand-written modifier rules | modifierRules | You need exact control over a resource and JSON Patch path. | Only for a few fixed paths |
For high-churn tags, prefer rewriteImage. During ResourceSync or Drill restore construction, the operator reads current source workload spec images and generates runtime modifier rules from stable source and target prefixes.
Preconditions
Image rewrite changes manifests only. Before enabling it, verify that:
- The rewritten image tag or digest already exists in the target registry.
- Target cluster nodes can access the target registry.
- Private registries have usable image pull Secrets in target namespaces, or the application resources already reference the correct Secret.
restorePolicy.useUnifiedDirectionResolveris enabled.
Exact Replacement
Exact replacement is useful when the full image value is stable. For example, replace:
10.10.10.170:32070/e2e/disaster-web-test:v0.1
with:
10.10.10.171:32071/e2e/disaster-web-test:v0.1
In the Bulk modifications/deletions field, enter this array:
[
{
"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"
}
]
When editing DisasterInstance directly:
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 scans string leaf nodes inside the protected resource scope and creates reversible rules for values equal to sourceValue. It skips forbidden paths such as /status/**, /metadata/finalizers/**, and /metadata/ownerReferences/**.
Limitations:
- If the image tag changes from
v0.1tov0.2, this exact rule no longer matches. - If you write
modifierRulesmanually, do not patch/status/containerStatuses/*/image. Pod status is not writable during restore.
Prefix Rewrite
Prefix rewrite is the recommended method for frequent image releases. Configure only the stable source and target registry prefixes; the tag follows the current source workload.
For example, source image:
10.11.11.1:5000/blueking/bcs-bkcmdb-synchronizer:v1.30.0
should become:
registry-test.xxx.xxx.com:30088/dr_images/10_11_11_1_5000/blueking/bcs-bkcmdb-synchronizer:v1.30.0
In the Bulk modifications/deletions field, enter:
[
{
"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"
}
}
]
In the 170/171 environment, if the source image prefix is 10.134.81.9:5000/ and the target registry keeps the source path under an archive prefix, configure:
[
{
"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"
}
}
]
For example, this source initContainer image:
10.134.81.9:5000/groundnuty/k8s-wait-for:v1.5.1
is restored as:
registry-tke.szmacloud.csg:30088/dr_images/10.134.81.9_5000/groundnuty/k8s-wait-for:v1.5.1
If the source workload later upgrades to:
10.11.11.1:5000/blueking/bcs-bkcmdb-synchronizer:v1.31.0
the next ResourceSync, Drill, or failover final sync dynamically generates:
registry-test.xxx.xxx.com:30088/dr_images/10_11_11_1_5000/blueking/bcs-bkcmdb-synchronizer:v1.31.0
Full DisasterInstance snippet:
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
Fields
| Field | Description |
|---|---|
sourcePrefix | Source image prefix. Use a trailing /; the platform normalizes prefixes. |
targetPrefix | Target image prefix. The path, tag, and digest after the source prefix are preserved. |
unmatchedPolicy | Keep leaves unmatched images unchanged. Fail fails compilation. Default is Keep. |
digestPolicy | Currently supports Preserve, which keeps the original digest. |
directionPolicy | Use Auto so runtime direction controls forward or reverse replacement. |
applyTo | Supports resourceSync and drill, not dataSync. |
If multiple prefixes match, the longest prefix wins. Equal-length conflicting prefixes fail compilation.
Scope
rewriteImage reads current source workload spec image fields and generates restore-time modifier rules. Common covered fields include:
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- Matching
initContainersandephemeralContainers
It does not modify:
Pod.status.containerStatuses[*].imagePod.status.containerStatuses[*].imageID- Any
/status/**field
Pod status is observed runtime state, not desired state. Validate rewrite behavior through workload or Pod spec image fields, not only through status.containerStatuses[*].image.
Resource Selection
Do not exclude Pods only to avoid Pod status image fields:
{
"resourceSelection": {
"excludedResources": ["pods"]
}
}
Image rewrite already skips /status/**. Whether to exclude pods should be a DR policy decision. If applications are managed by Deployments or StatefulSets, bare Pods usually do not need to be restored separately. If the protected scope contains bare Pods that must be restored, excluding pods affects those resources.
Verification
After saving an instance, confirm the policy:
kubectl -n disaster-system get disasterinstance <instance-name> \
-o jsonpath='{.spec.restorePolicy.bulkModifierActions}{"\n"}'
After ResourceSync, Drill, or failover, check the target workload spec:
kubectl --context <target-context> -n <namespace> get deploy <name> \
-o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
You can also check Pod spec:
kubectl --context <target-context> -n <namespace> get pod <pod-name> \
-o jsonpath='{.spec.containers[0].image}{"\n"}'
Check initContainers:
kubectl --context <target-context> -n <namespace> get pod <pod-name> \
-o jsonpath='{range .spec.initContainers[*]}{.name}{"="}{.image}{"\n"}{end}'
If Pod status still shows an old tag while spec shows the rewritten target image, trust spec first. When the same image content is retagged, container runtimes may show the original image name or old tag in status.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
ModifierRuleRejected ... /status/containerStatuses/0/image is forbidden | A manual or legacy rule patched Pod status. | Do not patch /status/**; use bulk actions or rewriteImage. |
| initContainer images are unchanged | The legacy image registry mapping path was used, an exact-value rule did not match, or no new ResourceSync/Drill restore build has run. | Use bulkModifierActions[].action=rewriteImage, make sure applyTo includes resourceSync or drill, and inspect .spec.initContainers[*].image. |
| A new tag still restores as the old image | Exact replaceExactValue only matched the old tag. | Use rewriteImage and configure prefixes only. |
modifierRuleSnapshot is empty | rewriteImage is compiled dynamically at runtime. | This is expected; verify ResourceSync or Drill output. |
Target Pod is ImagePullBackOff | Target image is missing, Secret is missing, or the node cannot reach the registry. | Push the image to the target registry and verify imagePullSecret/network access. |
conditions.groupResource is required | A hand-written modifierRules object was used where a bulk action array was expected. | Use bulkModifierActions for bulk changes; hand-written modifierRules must include conditions.groupResource. |
| Prefix rewrite has no effect | sourcePrefix does not match the actual source spec image. | Inspect the source workload spec image and configure the stable prefix. |
Recommendations
- Prefer
rewriteImagefor new business image registry rewrite configurations. - Keep frequently changing tags out of
sourceValue. - Before failover, make sure the target registry contains the corresponding image tag.
- Use
directionPolicy: Autoso failover, undo, and reprotect can choose the correct direction at runtime. - Verify workload spec images instead of Pod status images.