容灾 Velero Hook
Velero Hook 用于在备份或恢复过程中让业务容器执行自定义命令。常见场景包括数据库冻结和解冻、缓存刷新、恢复后初始化、写入恢复标记、通知业务进程重新加载配置等。
Testudo 不实现自己的 Hook 执行器。平台接收、校验、保存并投影 Velero 原生 Hook 结构,真正执行仍由 Velero 完成。
Hook 会在业务 Pod 或恢复出的 Pod 中执行命令。生产环境启用前,先在测试命名空间验证命令的幂等性、超时、失败策略和数据一致性。
支持范围
| 场景 | 输入字段 | 投影目标 | 是否执行 Hook |
|---|---|---|---|
| 手工应用备份 | hooks | AppBackup.spec.template.hooks -> Velero Backup.spec.hooks 或 Schedule.spec.template.hooks | 是 |
| 手工应用恢复 | hooks | AppRestore.spec.template.hooks -> Velero Restore.spec.hooks | 是 |
| 容灾实例数据备份 | spec.veleroHooks.dataBackup | DataSync 创建的 AppBackup.spec.template.hooks | 是 |
| 容灾实例数据恢复 | spec.veleroHooks.dataRestore | DataSync 创建的 AppRestore.spec.template.hooks | 是,exec post hook 会做 Trafficless 兼容处理 |
| 容灾资源同步 | spec.veleroHooks 存在 | ResourceSync 创建的 AppBackup/AppRestore | 不投影,不承诺执行 |
| 容灾演练数据恢复 | DisasterDrill.spec.veleroHooks.dataRestore | Drill Data Restore 创建的 AppRestore | 是 |
| 容灾演练数据备份 | DisasterDrill.spec.veleroHooks.dataBackup | 不支持 | Server 拒绝 |
前置条件
使用前确认:
- Velero 已安装并且
BackupStorageLocation可用。 - 业务命名空间、标签选择器和资源范围能命中需要执行 Hook 的 Pod。
- Hook 命令所在的容器镜像中确实存在对应脚本或 shell。
- 如果命令写入 PVC,例如
/data/hook.log,对应容器已挂载该路径。 - 需要敏感值时,通过业务容器已有 Secret env、
envFrom.secretRef或挂载文件传入,不要把明文密码或 token 写进 Hook command。
控制台入口
应用备份 Hook
进入 备份恢复 / 应用备份,创建或编辑应用备份。在高级选项中启用备份钩子,填写 Hook 资源、标签选择器、前置钩子和后置钩子。

备份 Hook 支持:
pre:备份该 Pod 前执行。post:备份该 Pod 后执行。container:执行命令的业务容器名。command:Velero 原生 argv 数组。timeout:单次 exec 超时。onError:Fail或Continue。
应用恢复 Hook
进入 备份恢复 / 应用恢复,创建恢复时启用恢复钩子。恢复 Hook 主要用于恢复 Pod 创建后执行初始化命令,或通过 init hook 注入初始化容器。

恢复 Hook 支持:
postHooks[].exec:恢复 Pod 就绪前后执行命令。postHooks[].init:向恢复 Pod 注入 initContainer。waitForReady:是否等待容器 ready 后执行。waitTimeout:等待 ready 超时。execTimeout:命令执行超时。
容灾实例 Hook
进入 容灾管理 / 实例配置,创建或编辑容灾实例,在高级选项中启用备份钩子和恢复钩子。

容灾实例保存后,字段写入:
spec:
veleroHooks:
dataBackup:
resources: []
dataRestore:
resources: []
DataSync 会读取该字段:
dataBackup投影到数据同步备份使用的 AppBackup。dataRestore投影到数据同步恢复使用的 AppRestore。- 更新实例 Hook 后,后续 DataSync 会在触发新备份前对齐既有
ds-*AppBackup 的 desired template。
命令输入格式
Hook command 必须是 Velero 原生 argv 数组。控制台中每个蓝色标签就是数组中的一个元素。
推荐写法:
["/bin/sh", "-c", "echo pre-backup >> /data/hook.log"]
不要把整条命令提交成一个字符串:
/bin/sh -c echo pre-backup >> /data/hook.log
这不是 Velero 期望的结构。当前控制台使用分段标签输入,输入一段参数后按空格或回车确认,最终按数组提交。
如果命令本身需要 shell 展开变量,显式把 shell 放进 argv:
["/bin/sh", "-c", "dr-hook pre-backup --mode=${DR_MODE:-quiesce}"]
这里的 ${DR_MODE} 由业务容器内的 shell 解析,平台不会渲染或替换。
选择器和多个 init hook
Hook 的 labelSelector 使用 Kubernetes 原生 LabelSelector 语义。matchLabels 中多个键是 AND 关系,不能写出同一个键的 OR 条件:
labelSelector:
matchLabels:
app: abc
tier: backend
如果要表达同一个 key 的多个 value,例如 app=abc OR app=qwe,使用 matchExpressions 和 In:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- abc
- qwe
如果要表达不同 key 或不同条件组之间的 OR,例如 app=abc OR component=qwe,需要拆成多个 Hook resource。平台按 Velero 原生结构透传,不额外扩展 selector 表达式。
恢复 init hook 可以配置多个。Velero 会把匹配到的 init hook 注入恢复出的 Pod;同一个 Pod 最终得到的 initContainers[].name 必须唯一。平台不会自动重命名,也不会合并同名 initContainer。多个 init hook 需要使用不同名称,例如:
postHooks:
- init:
initContainers:
- name: restore-init-db
image: registry.example.com/dr-tools:1.0
- init:
initContainers:
- name: restore-init-cache
image: registry.example.com/dr-tools:1.0
手工 AppBackup 示例
通过 API 或 CRD 配置手工备份 Hook 时,hooks 写入 AppBackup.spec.template.hooks:
apiVersion: testudo.softcdata.com/v1
kind: AppBackup
metadata:
name: bookinfo-backup-hook
spec:
cluster: prod-a
schedule: ""
template:
storageLocation: minio-dr
includedNamespaces:
- demo-bookinfo
labelSelector:
matchLabels:
app: bookinfo
hooks:
resources:
- name: app-quiesce
includedNamespaces:
- demo-bookinfo
includedResources:
- pods
labelSelector:
matchLabels:
app: bookinfo
pre:
- exec:
container: app
command:
- /bin/sh
- -c
- echo pre-backup >> /data/hook.log
onError: Fail
timeout: 5m
post:
- exec:
container: app
command:
- /bin/sh
- -c
- echo post-backup >> /data/hook.log
onError: Continue
timeout: 5m
一次性备份会创建带 spec.hooks 的 Velero Backup;调度备份会创建带 spec.template.hooks 的 Velero Schedule。
手工 AppRestore 示例
恢复 Hook 写入 AppRestore.spec.template.hooks,并透传到 Velero Restore.spec.hooks:
apiVersion: testudo.softcdata.com/v1
kind: AppRestore
metadata:
name: bookinfo-restore-hook
spec:
cluster: prod-b
backupName: bookinfo-backup-hook
restorePVs: true
targetCluster: prod-b
template:
backupName: bak-bookinfo-backup-hook-xxxx
includedNamespaces:
- demo-bookinfo
namespaceMapping:
demo-bookinfo: demo-bookinfo-restore
hooks:
resources:
- name: app-after-restore
includedNamespaces:
- demo-bookinfo-restore
includedResources:
- pods
labelSelector:
matchLabels:
app: bookinfo
postHooks:
- exec:
container: app
command:
- /bin/sh
- -c
- echo post-restore >> /data/hook.log
onError: Continue
waitForReady: false
waitTimeout: 10m
execTimeout: 5m
如果使用 init hook,可以通过 Kubernetes initContainer 原生字段传参:
postHooks:
- init:
initContainers:
- name: restore-init
image: registry.example.com/dr-tools:1.0
command:
- /bin/sh
- -c
args:
- dr-restore-init --namespace=$POD_NAMESPACE
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
envFrom:
- secretRef:
name: restore-hook-secret
timeout: 10m
容灾实例示例
容灾实例 Hook 适用于自动 DataSync 链路:
apiVersion: testudo.softcdata.com/v1
kind: DisasterInstance
metadata:
name: bookinfo-dr
spec:
config: dc-prod
namespaces:
- demo-bookinfo
veleroHooks:
dataBackup:
resources:
- name: freeze-before-data-sync
includedResources:
- pods
labelSelector:
matchLabels:
app: bookinfo
pre:
- exec:
container: app
command:
- /bin/sh
- -c
- echo inst-pre-backup >> /data/hook.log
onError: Fail
timeout: 5m
post:
- exec:
container: app
command:
- /bin/sh
- -c
- echo inst-post-backup >> /data/hook.log
onError: Continue
timeout: 5m
dataRestore:
resources:
- name: init-after-data-restore
includedResources:
- pods
labelSelector:
matchLabels:
app: bookinfo
postHooks:
- exec:
container: app
command:
- /bin/sh
- -c
- echo inst-post-restore >> /tmp/data-restore-hook.log
onError: Continue
waitForReady: false
waitTimeout: 10m
execTimeout: 5m
DataSync dataRestore 的 Trafficless 兼容规则
DataSync 的数据恢复会创建临时 Trafficless Pod,以避免恢复出的 Pod 被 Service 导流或被 workload controller 接管。Trafficless 恢复会移除业务 labels,只保留平台隔离标签。
因此,用户输入的 dataRestore exec post hook 如果使用业务标签,例如:
labelSelector:
matchLabels:
app: bookinfo
平台会在创建数据恢复 AppRestore 时做兼容处理:
- 使用原始 selector 匹配备份对象中的 Pod。
- 在 Trafficless labels 覆盖之后,为恢复 Pod 增加系统 marker label,例如
testudo.softcdata.com/data-restore-hook-0=true。 - 将对应 exec post hook 的 selector 改写为 marker selector。
- 保留用户输入的
command、container、onError、waitForReady、waitTimeout和execTimeout。
init restore hook 不做 marker 改写,因为 init hook 在 Pod 创建前按备份对象原始 selector 匹配。
如果查看 DataSync 创建的 AppRestore,dataRestore exec hook 的 selector 可能不是你在实例里输入的业务标签。这是预期行为,用于兼容 Trafficless 恢复。判断是否成功应看 Velero status.hookStatus 和 Pod 内业务 marker,而不是只比较 Hook selector 是否原样相等。
容灾演练 Hook
演练只支持 dataRestore Hook:
- 演练未配置
veleroHooks.dataRestore:继承实例级 dataRestore Hook。 - 演练配置
veleroHooks.dataRestore:覆盖实例级 Hook,不合并。 - 演练显式提交
veleroHooks: {}:清空继承,本次演练恢复不使用实例 Hook。 - 演练提交
veleroHooks.dataBackup:Server 拒绝,因为演练不创建新的数据备份。
API 片段:
{
"name": "bookinfo-drill-hook",
"instanceName": "bookinfo-dr",
"veleroHooks": {
"dataRestore": {
"resources": [
{
"name": "drill-after-restore",
"includedResources": ["pods"],
"labelSelector": {
"matchLabels": {
"app": "bookinfo"
}
},
"postHooks": [
{
"exec": {
"container": "app",
"command": ["/bin/sh", "-c", "echo drill-post-restore >> /data/hook.log"],
"onError": "Continue",
"waitForReady": false,
"waitTimeout": "10m",
"execTimeout": "5m"
}
}
]
}
]
}
}
}
校验和安全限制
平台在 Server 入口做基础校验,避免明显错误配置进入 CRD:
| 规则 | 行为 |
|---|---|
Hook 目标资源显式不包含 pods | 拒绝 |
| command 为空 | 拒绝 |
onError 不是 Fail 或 Continue | 拒绝 |
command 包含平台占位符,例如 ${testudo.namespace} 或 {{ namespace }} | 拒绝 |
command、args、env value 中包含明显明文敏感参数,例如 password=plain、token=plain | 拒绝,错误码 VeleroHookSensitiveParameter |
Backup exec timeout > 10m | 拒绝 |
Restore exec execTimeout > 10m | 拒绝 |
Restore exec waitTimeout > 30m | 拒绝 |
Restore init timeout > 30m | 拒绝 |
敏感值建议通过 Secret 注入:
envFrom:
- secretRef:
name: app-hook-secret
或:
env:
- name: APP_TOKEN
valueFrom:
secretKeyRef:
name: app-hook-secret
key: token
状态查看
Velero 会在 Backup 或 Restore status 中写入 Hook 执行汇总:
status:
hookStatus:
hooksAttempted: 2
hooksFailed: 0
平台会回显:
AppBackup.status.history[].veleroStatus.hookStatusAppRestore.status.restoreStatus.hookStatus- DataSync 同步历史中的
backupHookStatus和restoreHookStatus
常用检查命令:
kubectl get appbackup <appbackup> -n disaster-system -o yaml
kubectl get apprestore <apprestore> -n disaster-system -o yaml
kubectl --context <source-cluster> -n velero get backup <velero-backup> -o yaml
kubectl --context <target-cluster> -n velero get restore <velero-restore> -o yaml
kubectl --context <cluster> -n <app-namespace> exec deploy/<workload> -- cat /data/hook.log
如果是容灾实例链路,还可以查看 DataSync 生成的 AppBackup/AppRestore:
kubectl get datasync -n disaster-system
kubectl get appbackup -n disaster-system | grep '^ds-'
kubectl get apprestore -n disaster-system | grep '^rec-ds-'
排障
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
hookStatus 为空 | selector 没有命中 Pod,或 Hook 不适用于该恢复阶段 | 检查 namespace、includedResources、labelSelector 和备份/恢复范围 |
hooksAttempted > 0 但 hooksFailed > 0 | 命令退出非 0,或容器/路径不存在 | 查看 Velero describe/logs 和业务容器日志 |
| Server 返回敏感参数错误 | command 或 args 中出现明文 password/token/secret | 改用 Secret env、Secret envFrom 或挂载文件 |
| Server 返回 timeout 错误 | 超过平台上限 | 调整 timeout,或把长任务拆到业务侧异步执行 |
| DataSync dataRestore AppRestore 中 selector 变成平台 marker | Trafficless 兼容改写 | 这是预期行为,验证 hookStatus 和业务 marker |
| ResourceSync 没有 Hook | ResourceSync 不投影 Hook | 如果需要恢复后执行命令,使用 DataSync dataRestore 或 Drill dataRestore |
| 恢复成功但恢复出的临时 Pod 消失 | DataSync 清理 Trafficless 临时 Pod | 查看 Hook 写入的持久化路径或 Velero hookStatus,不要只依赖临时 Pod 是否仍存在 |