VolSync PVC 复制、接管与回切
故障集群已经隔离,目标集群的 ReplicationDestination 也显示拥有 latestImage。团队立即把应用切到恢复 PVC,数据库却报告日志序列落后;随后源集群短暂恢复,旧 Pod 又开始写入,形成两个写入者。复制本身没有报错,事故发生在两个误判:latestImage 只代表目标端最近成功生成的卷数据镜像,不代表它追到了故障前最后一次业务提交;VolSync 复制的是 PVC 数据,不会连同 StatefulSet、Service、Secret、RBAC、CRD 或 DNS 一起恢复。
因此 VolSync 灾备不能从“复制成功”直接跳到“应用接管”。先要知道 Source 最近捕获了哪个时点,Destination 最近物化了哪个镜像,应用自身的事务/日志水位是多少;接管时必须建立单写者,回切时还要把新主端产生的数据反向复制回去。
Source、Destination 和 mover 各自保存什么
ReplicationSource 与 ReplicationDestination 是 volsync.backube/v1alpha1 的 namespaced CR。Source 指定源 PVC、触发方式、copy method 与 mover;Destination 描述接收端容量、StorageClass、访问模式、copy method 与 mover。controller 据此创建 mover Pod、临时 PVC、Service 或 VolumeSnapshot,并把进度与最近镜像写入 status。
VolSync v0.16.0 的官方 mover 面向不同数据路径:
rsync-tls 是一对一直接复制,Source 连接 Destination Service,双方共享 TLS-PSK。新部署优先它;原始 rsync-over-SSH 仍存在,但官方建议迁移到所需权限更少的 TLS 路径。rclone 通过对象存储中转,适合一源多目的 Source push / Destination pull。每条关系要隔离 bucket prefix,源与各目的 schedule 要错开。
restic 是带版本与保留的备份仓库,不是普通实时同步。Destination 可选择最新、previous 或 restoreAsOf 快照恢复。syncthing 是长期运行的多向最终一致同步。删除与冲突会传播,它不是不可变时间点备份。
copyMethod: Snapshot 先从源 PVC 创建快照,再由 mover 读取,减少复制期间 live write 变化;Clone 依赖 CSI clone 能力;Direct 直接挂载活动 PVC,最容易读到跨时刻内容。数据库仍需停写、checkpoint 或应用感知快照,VolSync 不会自动制造事务一致性。
Destination 采用 Snapshot 时,.status.latestImage 指向最新 VolumeSnapshot。它是 PVC 数据镜像,不是整应用恢复点。Volume Populator 可以让新 PVC 的 dataSourceRef 指向 ReplicationDestination;在 latest image 尚未产生时,该 PVC 会保持 Pending,这是防止拿空数据启动应用的重要信号。
安装前先证明快照链成立
实验以 VolSync v0.16.0 为基线。固定版本与变化应从 stable 文档和 v0.16.0 release核对;Read the Docs 的 latest 指向开发分支,不能作为生产行为承诺。
两端集群都要有 snapshot controller。使用 Snapshot/Clone 时,还要求 CSI driver、StorageClass 和 VolumeSnapshotClass 支持对应能力,并且 SC 与 VSC 的 driver 匹配。先在可销毁集群记录事实:
kubectl api-resources | grep -E 'replicationsource|replicationdestination|volumesnapshot'
kubectl get crd | grep -E 'volsync|snapshot.storage.k8s.io'
kubectl get storageclass
kubectl get volumesnapshotclass
kubectl get csidrivers.storage.k8s.io再安装固定 chart/app 版本。chart 版本号应通过官方仓库索引确认与 v0.16.0 的映射,不要假设 chart 与 app 永远同号:
helm repo add backube https://backube.github.io/helm-charts/
helm repo update
helm search repo backube/volsync --versions | head
helm upgrade --install volsync backube/volsync \
--namespace volsync-system \
--create-namespace \
--version <chart-version-for-v0.16.0>
kubectl -n volsync-system get deploy,pod
helm -n volsync-system get values volsync -a > /tmp/volsync-values.yaml预期证据是 operator Ready、两个 CRD 可发现、controller 镜像固定,并且目标 CSI 能实际创建和恢复测试 VolumeSnapshot。仅看到 CRD 不等于后端 driver 支持快照。AWS EBS CSI 不支持 volume clone,应选 Snapshot;其他 driver 也必须实测,不能凭云厂商名称推断。
正向实验:用 rsync-tls 建立可观测水位
先在两端创建同名实验 namespace,源端 PVC 写入合成文件,并在每轮复制前写一个单调递增的 generation 文件和校验清单。应用数据库还应记录事务日志位置或 checkpoint ID;文件 generation 只验证复制顺序,不能代替数据库一致性。
rsync-tls 两端共享 PSK Secret。Secret 必须位于对应 namespace,按复制关系隔离并限制读取;下面只引用占位名称,不展示真实 PSK。Destination 先创建并暴露服务,Source 再连接它:
apiVersion: volsync.backube/v1alpha1
kind: ReplicationDestination
metadata:
name: app-data-dst
namespace: volsync-lab
spec:
trigger:
manual: restore-001
rsyncTLS:
serviceType: ClusterIP
copyMethod: Snapshot
capacity: 10Gi
storageClassName: <destination-storage-class>
volumeSnapshotClassName: <destination-snapshot-class>
accessModes: [ReadWriteOnce]
keySecret: app-data-psk
moverSecurityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
---
apiVersion: volsync.backube/v1alpha1
kind: ReplicationSource
metadata:
name: app-data-src
namespace: volsync-lab
spec:
sourcePVC: app-data
trigger:
manual: sync-001
rsyncTLS:
copyMethod: Snapshot
keySecret: app-data-psk
address: <destination-service-address>
volumeSnapshotClassName: <source-snapshot-class>
moverSecurityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000字段以 rsync-tls 固定版本文档和集群 CRD schema 为准,可先执行 kubectl apply --server-side --dry-run=server。跨集群 ClusterIP 只有在网络可路由时成立;改用 LoadBalancer 会增加公网/专网暴露、云配额和持续费用,必须配合来源限制、防火墙和 TLS 1.3 验证。
kubectl apply --server-side --dry-run=server -f destination.yaml
kubectl apply --server-side --dry-run=server -f source.yaml
kubectl apply -f destination.yaml
kubectl apply -f source.yaml
kubectl -n volsync-lab get replicationdestination,replicationsource -o wide
kubectl -n volsync-lab get pods,svc,pvc,volumesnapshot
kubectl -n volsync-lab get replicationdestination app-data-dst -o yaml
kubectl -n volsync-lab get replicationsource app-data-src -o yaml一次合格的证据链要把四个水位关联起来:Source 本轮触发标识与完成状态、源 PVC 的 generation/应用日志位置、Destination status 的最近成功时间与 latestImage、从 latestImage 新建 PVC 后读到的 generation/应用日志位置。连续触发时,目标 generation 应单调推进但允许落后;若状态完成而数据水位不变,优先检查选错 PVC、快照复用、权限或应用缓存,而不是只重启 controller。
从 latestImage 创建隔离恢复 PVC,挂载到校验 Pod,先验证容量、volumeMode、accessModes、StorageClass、文件 UID/GID、校验和和应用只读启动。验证完成前不要让正式 StatefulSet 挂载它。
反向实验:制造网络中断和 live volume 不一致
第一轮在复制中临时阻断 Source 到 Destination Service。预期 Source/Destination status 与 mover 日志出现连接或 TLS 错误,旧 latestImage 仍指向上一次成功镜像;它不会因为新一轮失败自动变成“无效”。因此接管判断必须比较水位,而不是只判断字段非空。
第二轮复制 PSK Secret 时故意使用错误值。v0.16.0 的 rsync-tls 最低使用 TLS 1.3,旧代理、TLS inspection 或 cipher policy 也可能表现为握手失败。应分别检查 Secret 版本、endpoint、网络与 TLS 日志,不要降级到明文或把 SSH key 当作 PSK。
第三轮把 copyMethod 改为 Direct,同时让合成写入器持续改写大文件和索引。恢复后可能出现索引 generation 高于数据文件 generation,稳定暴露 live scan 不是时间点副本。清理该轮数据后改回 Snapshot,并在快照前执行应用 checkpoint/停写,再比较恢复不变量。
第四轮在支持 dual permission model 的 namespace 中让 rsync-tls 以 UID 0 运行。无特权模式下即使 capabilities 已 drop 也不能这样工作。正确做法是用 moverSecurityContext 匹配非零应用 UID/GID;只有经过租户风险评审时,才可对整个 namespace 添加 volsync.backube/privileged-movers=true。该注解允许 namespace 用户借用 mover ServiceAccount 运行具有 UID 0、DAC_OVERRIDE 等能力的 Pod,是 namespace 级信任升级,实验后应立即撤销。
接管:先封住旧写入者,再使用目标水位
接管前先冻结或隔离源端写入:缩容旧工作负载、阻断流量、确认 leader/lease 不再续约,并保存源端最后可观察业务水位。若源集群失联,使用 fencing、云磁盘 detach、网络隔离或外部数据库租约证明它不能重新写;“看不到旧 Pod”不是 fencing。
然后选择不晚于 latestImage 且经过校验的目标镜像,创建新 PVC,恢复 Kubernetes API 对象,再以只读或隔离流量启动应用。Deployment、StatefulSet、Service、Secret、ConfigMap、CRD、RBAC、DNS 和外部依赖来自 GitOps、另一套备份或 runbook,VolSync 不会生成它们。确认应用日志回放完成、业务不变量成立并完成首个成功写入后,才切换 Service/DNS 与生产流量。
接管记录至少保存源水位、目标 latestImage UID、VolumeSnapshot handle、恢复 PVC UID、应用恢复水位和流量切换标识。这样才能计算实际数据丢失窗口,也能在随后回切时知道哪一端是权威源。
回切:把方向反过来,而不是重新打开旧 Pod
目标集群接管后已经产生新数据,旧源集群即使恢复也只能作为待重建的目的端。先修复旧集群基础设施和应用声明,清空或隔离旧 PVC;然后在当前主集群创建新的 ReplicationSource,在原集群创建新的 ReplicationDestination,使用新的关系名、PSK 和手动触发标识完成反向复制。
对反向恢复 PVC 重做文件校验、数据库启动和应用水位检查。计划回切窗口内停止当前主端写入,执行最后一次增量同步,确认目标追到同一业务水位,再 fencing 当前主端,启动原集群应用并切回流量。任何时刻只允许一个写入者。完成观察期后再决定是否恢复原复制方向;不要保留两个相互触发的 Source,否则会形成不受业务语义约束的双向覆盖。
Restic、Rclone 与 Syncthing 的选型代价
需要多个历史版本和对象存储故障域时,使用 Restic mover。Source 定义 repository、schedule、retention 与 prune,Destination 可按 latest、previous 或 restoreAsOf 恢复到空 PVC。enableFileDeletion 默认 false,目标中备份不存在的文件会保留;精确还原前必须决定是否允许删除。retention 的 forget 先移除引用,空间到 prune 才回收,prune 会带来显著 I/O、对象请求和锁竞争。
一源多目的分发适合 Rclone,但 Source push 与每个 Destination pull 要错峰,bucket prefix 和身份要按关系隔离。删 CR 不等于清空中转对象。Syncthing 适合在线共享而非灾备恢复点:冲突与删除会传播,勒索或误删也可能同步到所有 peer。
所有权、清理和长期成本
删除 ReplicationDestination 时,VolSync 会删除它动态创建的 destination PVC 与 cache PVC;通过 destinationPVC 引用的用户预建 PVC 不由它删除。cleanupTempPVC/cleanupCachePVC 只改变 controller 临时卷的清理。Snapshot 路径还要检查 VolumeSnapshotClass 的 deletionPolicy、VolumeSnapshotContent、后端快照和 finalizer;Rclone 中转对象、Restic repository、用户 Secret/PVC 与 LoadBalancer 都要单独核查。
运行成本来自源快照/克隆容量、目标保留快照、临时/cache PVC、mover CPU/内存、跨区流量、对象存储请求与 egress、LoadBalancer 和 Restic prune。容量规划要观察源数据变化率、一次全量/增量时长、同步滞后、目标快照增长和恢复吞吐;生产阈值由 RPO/RTO 与基线测量决定,不用固定分钟数替代实测。
VolSync 默认只有 cluster-admin 能管理 CR,租户应只获得自己 namespace 中 Source/Destination 的 CRUD 与 status read,并按需读取 VolumeSnapshot。mover Pod 能读写 PVC,PSK、rclone.conf、Restic repository/password 和后端凭证都应按关系隔离。自建 moverServiceAccount 可承载私有镜像 pull secret 或工作负载身份,但不会自动获得 PVC、Secret 或云权限。
v0.16.0 更新了 Restic 与 Rclone,并把 rsync-tls 最低版本提升为 TLS 1.3;还移除了 Syncthing 的 insecureAllowOldTLSVersions 字段。升级先导出 CRD、Source/Destination、Secret 名称映射、SC/VSC、镜像和水位记录,使用 server-side dry-run 找出旧字段,再在隔离关系执行全量、增量、网络失败、恢复和反向回切。CRD 仍为 v1alpha1,回滚前要确认旧 controller 可读取新 schema。
退出时先停掉 schedule/manual trigger,等待 mover 收敛并记录最后水位;从保留镜像或 repository 完成一次独立恢复;再删除 CR 并逐项核对 PVC、cache、VolumeSnapshot、VolumeSnapshotContent、LoadBalancer、对象前缀和 Secret。最后卸载 operator。只有当 Kubernetes API 对象由其他方案可重建、PVC 数据已有可验证的新归宿,VolSync 才算真正退出。
