Kanister 应用级备份工作流与失败清理
恢复窗口里,数据库 Pod 已经缩容,新的空 PVC 也准备好了,ActionSet 却停在 failed。值班人员删除失败的 ActionSet 后重试,发现对象存储里既有完整归档,也有同名半成品;更糟的是,负责缩容的 Phase 已经成功,负责恢复副本数的命令却没有执行,业务一直保持零副本。团队原以为“删掉运行对象就会撤销任务”,但上传到外部仓库的字节、临时 Pod 和已经修改的工作负载并不受一个 Kubernetes CR 的事务保护。
这正是理解 Kanister 的入口:它不是带保留策略和恢复点目录的完整备份平台,而是把应用数据操作写成可复用工作流。Blueprint 定义动作,ActionSet 发起并记录一次执行,动作里的 Phase 按顺序调用 Function,输出的 Artifact 只是外部数据位置等键值引用。恢复、删除和失败补偿都必须由工作流作者明确写出。
六个对象怎样组成一次运行
Blueprint 是 cr.kanister.io/v1alpha1 的可复用定义。它的 actions 中可以有 backup、restore、delete 等动作;每个动作包含顺序执行的 phases、可选的 deferPhase、输入 Secret/ConfigMap/Artifact 名称和输出 Artifact 模板。创建 Blueprint 本身不会备份任何数据。
ActionSet 才是立即执行请求。它在 spec.actions[] 中选择 Blueprint 和 action,绑定目标 Kubernetes 对象、Kanister Profile、Secret、ConfigMap、Options 与输入 Artifact。controller 渲染模板后逐个执行 Phase,并把状态、输出和错误写回 status。同一个 ActionSet 中的多个 action 也按顺序执行,因此前一个外部副作用已经发生时,后一个失败不会自动回滚前一个。
Phase 是动作内部的有序步骤,Function 是实际执行单元,例如在 Pod 中运行命令、创建临时 Pod,或调用对象存储/快照能力。Function 的参数可以引用目标对象、Profile 和前序 Phase 输出。Phase 成功只证明该函数按自己的合同返回成功,不证明归档能被数据库恢复。
Kanister Profile 描述 S3-compatible、GCS 或 Azure 等 Artifact 位置及访问参数。它不是 Kasten Profile,也不承担调度和保留。Artifact 更不是统一恢复点:它是 Blueprint 作者定义的键值,例如归档路径、校验值或快照标识。Kanister 不会持续确认该路径仍存在,也不会在删除 ActionSet 时推导并删除对应数据。
Blueprint.actions.backup
-> ActionSet.spec.actions[0]
-> Phase 1 / Function: prepare or quiesce
-> Phase 2 / Function: dump and upload
-> status.actions[].phases[]
-> outputArtifacts: { backupLocation, checksum, ... }
-> deferPhase: unlock / scale up / remove temporary resources
restore ActionSet --input-artifact--> explicit restore action
delete ActionSet --input-artifact--> explicit delete action固定安装基线,再授予应用权限
下面的实验合同以 Kanister 0.119.0 为基线。当前版本和变更应从 Kanister 文档与 0.119.0 release核对。CRD 仍为 v1alpha1,不能把正式 release 理解成 API 已稳定为 v1。
实验需要 Helm、kubectl、一个可销毁 namespace、一个测试 PVC/应用,以及隔离的对象存储前缀。安装者需要 CRD、webhook、Deployment、ClusterRole 等权限;运行时 controller 默认只访问自己的 namespace,跨 namespace 操作要在目标 namespace 精确授权。
helm repo add kanister https://charts.kanister.io/
helm repo update
helm -n kanister upgrade --install kanister \
--create-namespace kanister/kanister-operator \
--version 0.119.0
kubectl -n kanister get deploy,pod
kubectl get crd | grep kanister
kubectl get validatingwebhookconfiguration | grep kanister
helm -n kanister get values kanister -a > /tmp/kanister-values.yaml预期证据不是一行 deployed,而是 operator Pod Ready、Kanister CRD 可发现、Blueprint validating webhook 可用,并且 controller 与工具镜像都固定到计划版本。chart 默认可由 controller 更新 CRD;若改成 controller.updateCRDs=false 交给 Helm 或平台管理,升级流程就必须先比较和应用 CRD,再升级 controller。自定义 webhook PKI 时应使用 bpValidatingWebhook.tls.mode=custom 并提供受信 CA 与 TLS Secret,不能用跳过 TLS 校验代替证书治理。
为实验 namespace 授权时,先从 Blueprint 实际 Function 推导 verbs。只读目标对象的工作流不需要 edit 整个 namespace;需要 pods/exec、临时 Pod、PVC 或 Secret 时再逐项增加。
kubectl create namespace kanister-lab
kubectl -n kanister-lab create role kanister-runner \
--verb=get,list,watch,patch,update \
--resource=deployments.apps,statefulsets.apps,pods,persistentvolumeclaims
kubectl -n kanister-lab create rolebinding kanister-runner \
--role=kanister-runner \
--serviceaccount=kanister:kanister-kanister-operator
kubectl auth can-i patch statefulsets.apps \
-n kanister-lab \
--as=system:serviceaccount:kanister:kanister-kanister-operatorServiceAccount 名称应以实际 release 为准。若 Blueprint 创建 Pod 或执行 pods/exec,上述最小角色会故意失败;这是后面区分 RBAC 故障与应用故障的证据,不应直接换成 cluster-admin 掩盖缺失权限。
正向实验:让备份、恢复和删除形成闭环
先准备一个只有合成数据的应用和隔离仓库前缀。对象存储身份只允许该前缀的读写删除,仓库密钥使用短期凭证或工作负载身份;不要把 access key 写进 Blueprint、shell 参数或 ActionSet status。Kanister Profile 和 Secret 的精确 schema 以目标 release 的 模板与凭证文档为准。
Blueprint 至少应表达以下语义:backup 先让应用进入可备份状态,再导出并上传,输出位置与校验信息;restore 读取 input Artifact,恢复到明确目标并做应用校验;delete 只删除该 Artifact 指向的前缀;deferPhase 无论主 Phase 成败都恢复副本数、解锁并删除临时资源。
apiVersion: cr.kanister.io/v1alpha1
kind: Blueprint
metadata:
name: database-logical-backup
namespace: kanister-lab
actions:
backup:
kind: StatefulSet
phases:
- func: KubeTask
name: createLogicalArchive
args:
namespace: "{{ .Object.metadata.namespace }}"
image: <approved-tools-image>
command: ["/opt/workflow/backup.sh"]
deferPhase:
func: KubeTask
name: restoreApplicationState
args:
namespace: "{{ .Object.metadata.namespace }}"
image: <approved-tools-image>
command: ["/opt/workflow/cleanup.sh"]
restore:
kind: StatefulSet
inputArtifactNames: [backupArtifact]
phases:
- func: KubeTask
name: restoreAndVerify
args:
namespace: "{{ .Object.metadata.namespace }}"
image: <approved-tools-image>
command: ["/opt/workflow/restore.sh"]
delete:
kind: StatefulSet
inputArtifactNames: [backupArtifact]
phases:
- func: KubeTask
name: deleteExactArtifact
args:
namespace: "{{ .Object.metadata.namespace }}"
image: <approved-tools-image>
command: ["/opt/workflow/delete.sh"]这是接口骨架,不是可直接上传真实数据库的通用脚本。不同 Function 的字段和输出名必须从 0.119.0 函数参考核对。脚本镜像应固定 digest,并把备份水位、对象数量、归档校验值和应用校验结果作为非敏感输出;不要打印环境变量或 Secret。
应用 Blueprint 后,用 kanctl --dry-run 先检查即将创建的 ActionSet,再发起 backup。完成后同时看 CR、Phase、Pod 日志和对象存储:
kubectl -n kanister-lab apply -f blueprint.yaml
kanctl create actionset --action backup \
--namespace kanister-lab \
--blueprint database-logical-backup \
--statefulset kanister-lab/database \
--profile kanister-lab/artifact-store \
--dry-run -o yaml
kanctl create actionset --action backup \
--namespace kanister-lab \
--blueprint database-logical-backup \
--statefulset kanister-lab/database \
--profile kanister-lab/artifact-store
kubectl -n kanister-lab get actionsets.cr.kanister.io
kubectl -n kanister-lab get actionset <backup-actionset> -o yaml
kubectl -n kanister get pods可接受的预期证据包括:每个 Phase 有明确终态;outputArtifacts 指向本次唯一前缀并带可复核校验值;deferPhase 完成后副本数和锁状态回到实验前基线;工具 Pod 日志无凭证明文;仓库中对象可列举且大小非零。随后从 backup ActionSet 生成 restore/delete,使 output Artifact 显式成为输入,而不是手写“最新路径”:
kanctl create actionset --action restore --from <backup-actionset> \
--namespace kanister-lab --dry-run -o yaml
kanctl create actionset --action restore --from <backup-actionset> \
--namespace kanister-lab
kanctl create actionset --action delete --from <backup-actionset> \
--namespace kanister-lab --dry-run -o yaml恢复验收要继续到数据库启动、业务查询、记录数/校验和与备份水位一致,并完成一次新写入。删除验收则确认精确前缀消失、相邻备份仍在。删除 backup ActionSet 只会删除运行记录,不会删除 Artifact;真正回收外部数据要执行 Blueprint 的 delete action。
反向实验:稳定区分四类失败
第一轮故意移除应用 namespace RoleBinding。预期是 controller 或 Function 在 Kubernetes API 操作处得到 Forbidden,ActionSet status 和 operator 日志能看到被拒绝的 resource/verb。此时不要调对象存储,因为命令尚未到达上传步骤。
第二轮把 Blueprint 的 kind 与 ActionSet 目标 kind 设为不一致。kind 当前虽仍存在兼容空间,但官方已说明未来会强化匹配;新 Blueprint 应主动填写并通过 webhook/dry-run 检查,避免升级后才暴露生成器错误。
第三轮使用错误 CA 或撤销该前缀凭证。预期 Function 进入 TLS 信任、认证或授权错误,仓库不产生完整 Artifact;skipSSLVerify 默认 false,应修复 CA、endpoint、region 与权限,不能长期关闭校验。
第四轮让上传脚本在写入部分对象后退出。预期主 Phase 失败,deferPhase 仍被调度以恢复应用和清理临时资源,但它本身也可能失败。删除正在运行的 ActionSet 只能尝试停止 action,而且该能力仍是 alpha;已经执行的命令、云快照和对象上传不具备事务回滚保证。
故障后按所有权清理:先确认应用是否仍停写或缩容,再检查 ActionSet 的 Phase/deferPhase 状态;随后核对临时 Pod/PVC、锁与租约;最后按运行唯一前缀列举仓库半成品,并由受审的 delete 流程清除。不要先删 ActionSet 和日志,否则会丢掉定位外部副作用所需的输入。
调度、保留与容量由平台层接住
Kanister 不提供统一调度与保留目录。需要周期执行时,外部调度器创建 ActionSet,并确保幂等键、并发互斥和错峰;需要保留期时,团队必须为 Artifact 建立索引、引用关系、删除审批和仓库生命周期。对象存储版本化或不可变保留可能让 delete 成功但空间不立即下降,费用要按逻辑归档大小、版本、请求次数、跨区流量和恢复下载共同估算。
大数据库的瓶颈通常不是 controller,而是 dump 工具的临时空间、应用读 I/O、压缩 CPU、网络和对象存储吞吐。上线前用接近生产的数据分布测量备份和恢复曲线;并发只在应用、节点临时盘和仓库都留有余量时增加。告警至少关联 ActionSet 等待年龄、Phase 失败、deferPhase 失败、临时资源残留、Artifact 增长趋势和周期性恢复校验失败。
凭证与镜像决定工作流的安全边界
Secret 可进入 Go template 和 shell,任何 Blueprint 作者事实上都能决定它是否出现在命令、status 或日志。平台应把“可修改 Blueprint”和“可引用生产 Secret”分离,限制 controller 对应用 namespace 的访问,使用独立前缀与身份,并对工具镜像做签名、漏洞和 digest 门禁。air-gap 环境不仅要镜像 controller 与 kanister-tools,还要镜像每个 Blueprint 使用的数据库客户端和脚本镜像。
升级、回滚与退出必须保留恢复证据
0.119.0 包含 AWS SDK v2、Go 与 PostgreSQL tools 基础镜像变化。升级前先导出 CRD、Blueprint、ActionSet/Artifact 索引、chart values、镜像 digest 和凭证映射,在隔离 namespace 对每类 Blueprint 各跑一次 backup/restore/delete 及失败补偿。先升级 CRD 还是 controller 由 controller.updateCRDs 的所有权决定;回滚也必须确认旧 controller 能读取已升级 schema。长期退出 Kanister 时,先停止新 ActionSet,等待或人工收敛运行任务,导出 Artifact 清单并用独立工具完成一次恢复,迁移保留与删除责任,最后卸载 release。CRD 和运行记录只有在不再承担审计与恢复索引后才删除,外部仓库则按单独的数据保留决策处理。
什么时候选择 Kanister
当关键问题是“数据库该如何停写、导出、上传、恢复和校验”,而现成平台没有对应应用动作时,Kanister 很合适:它把步骤、输入、输出和补偿编排成 Kubernetes 对象。若需要 Kubernetes API 对象捕获、统一保护策略、可恢复点目录与保留治理,应另配完整备份平台;若只是异步复制 PVC 数据,VolSync 的 Source/Destination 模型更直接。无论怎样组合,Artifact 可读、应用可启动和业务不变量成立才是恢复证据,ActionSet 的 complete 只是其中一层。
