Longhorn 异步拉取远程备份目标:BackupTarget/BackupVolume/Backup CRD 与控制器架构详解

原创2026-09-27 23:59:561,210 阅读
文章标签:云原生存储高可用容器编排

Longhorn 异步拉取远程备份目标:BackupTarget/BackupVolume/Backup CRD 与控制器架构详解

本指南基于 Longhorn 仓库中的增强设计文档 enhancements/20210525-async-pull-backups.md,讲解 Longhorn 如何将"与远程备份目标(S3/NFS)的同步阻塞式通信"重构为"异步拉取 + 集群 CRD 缓存"的最终一致模型。你将掌握三个核心 CRD(BackupTarget、BackupVolume、Backup)的字段语义、backup 命令行(list / inspect-volume / head)的职责拆分、四个控制器(setting / backup_target / backup_volume / backup)的协作循环,以及相关 HTTP API 的前后行为变化,可用于排查或二次开发 Longhorn 备份链路。

背景与问题:阻塞式通信带来的备份列表超时

在 Longhorn 引入异步拉取机制之前,Longhorn manager 与远程备份目标(S3/NFS)之间的通信是阻塞式的:列出备份卷、列出备份等操作需要实时访问远端备份目标并读取其配置文件(volume.cfg、backup_backup_<backup-hash>.cfg)。这种设计在以下场景会产生明显的可靠性问题:

  • 远程备份目标中积累了大量备份卷或备份;
  • Longhorn 集群与远程备份目标之间的网络延迟较高(例如跨地域 S3);
  • 备份目标操作引发的级联故障(如 NFS 短暂不可用)会影响依赖远程备份目标的功能。

其中最直接的痛点是:当用户在 Longhorn GUI 上点击 Backup 页面时,列表请求的默认超时时间为 1 分钟。若备份目标数据量大或延迟高,用户会直接遭遇列表超时。设计文档特别说明,之所以不提供"增大列表超时时间"的设置项,是因为浏览器本身也有超时限制(例如 Google Chrome 不允许用户修改默认超时值)——即使 Longhorn manager 侧放宽超时,浏览器侧依然会中断请求,因此从架构上消除同步阻塞才是正解。

该设计对应的原始问题包括 issue #1761、#1955、#2536、#2543,最终在 Longhorn 1.4 及后续版本中落地(CHANGELOG 中亦有相关后续修复记录,例如 CHANGELOG/CHANGELOG-1.4.0.md 中关于"无法从远程备份目标拉取由另一 Longhorn 系统创建的备份(#4637)"的修复,即属于该机制落地后的边界问题)。

目标与边界(Goals / Non-goals)

目标:降低"列出备份卷"或"列出备份"时的查询延迟,覆盖三种典型场景——备份卷数量庞大、备份数量庞大、集群到远程备份目标的网络延迟高。

非目标(明确不做的事):

总体方案:异步拉取并把结果持久化为集群 CR

核心思路是"同步阻塞查询 → 后台轮询同步 + CRD 缓存":

  1. 后台按轮询间隔异步查询远程备份目标,将备份卷、备份的元数据以 Kubernetes 自定义资源(CR)的形式持久化保存到集群内;
  2. 用户的列表请求不再直连远程备份目标,而是读取集群内的 CR;
  3. 集群内 CR 与远程备份目标之间通过 spec.syncRequestAt / status.lastSyncedAt 的时间戳比较驱动增量同步,实现最终一致;
  4. 删除操作同样异步化:HTTP 端点只负责打标/删除 CR,由控制器在后台真正删除远端备份数据。

方案的五个组成部分:

  • 修改 longhorn/backupstore 的 list 命令行为,新增 inspect-volume 与 head 命令(把"列名"与"读配置"分离);
  • 新增 BackupTarget CRD(保存备份目标 URL、凭证 Secret、轮询间隔);
  • 新增 BackupVolume CRD(保存备份卷配置);
  • 新增 Backup CRD(保存单个备份配置);
  • 改造既有 setting_controller 并新增 backup_target_controller、backup_volume_controller、backup_controller 三个控制器;
  • 备份卷/备份相关的 HTTP 端点改为与 CR 交互,不再直连远端。

backupstore 命令行改造:把 list、read、head 彻底分离

原设计中 backup ls 在列出的同时会读取配置,这正是一次列出大量备份时延迟高的根源。改造后三条命令职责分明:

改造前(阻塞式 + 读取配置)

backup ls --volume-only:列出所有备份卷并读取其配置(volume.cfg):

$ backup ls s3://backupbucket@minio/ --volume-only
{
  "pvc-004d8edb-3a8c-4596-a659-3d00122d3f07": {
    "Name": "pvc-004d8edb-3a8c-4596-a659-3d00122d3f07",
    "Size": "2147483648",
    "Labels": {},
    "Created": "2021-05-12T00:52:01Z",
    "LastBackupName": "backup-c5f548b7e86b4b56",
    "LastBackupAt": "2021-05-17T05:31:01Z",
    "DataStored": "121634816",
    "Messages": {}
  },
  "pvc-7a8ded68-862d-4abb-a08c-8cf9664dab10": {
    "Name": "pvc-7a8ded68-862d-4abb-a08c-8cf9664dab10",
    "Size": "10737418240",
    "Labels": {},
    "Created": "2021-05-10T02:43:02Z",
    "LastBackupName": "backup-432f4d6afa31481f",
    "LastBackupAt": "2021-05-10T06:04:02Z",
    "DataStored": "140509184",
    "Messages": {}
  }
}

backup ls --volume <volume-name>:列出指定卷下所有备份并读取每个备份的配置:

$ backup ls s3://backupbucket@minio/ --volume pvc-004d8edb-3a8c-4596-a659-3d00122d3f07
{
  "pvc-004d8edb-3a8c-4596-a659-3d00122d3f07": {
    "Name": "pvc-004d8edb-3a8c-4596-a659-3d00122d3f07",
    "Size": "2147483648",
    "Labels": {},
    "Created": "2021-05-12T00:52:01Z",
    "LastBackupName": "backup-c5f548b7e86b4b56",
    "LastBackupAt": "2021-05-17T05:31:01Z",
    "DataStored": "121634816",
    "Messages": {},
    "Backups": {
      "s3://backupbucket@minio/?backup=backup-02224cb26b794e73&volume=pvc-004d8edb-3a8c-4596-a659-3d00122d3f07": {
        "Name": "backup-02224cb26b794e73",
        "URL": "s3://backupbucket@minio/?backup=backup-02224cb26b794e73&volume=pvc-004d8edb-3a8c-4596-a659-3d00122d3f07",
        "SnapshotName": "backup-23c4fd9a",
        "SnapshotCreated": "2021-05-17T05:23:01Z",
        "Created": "2021-05-17T05:23:04Z",
        "Size": "115343360",
        "Labels": {},
        "IsIncremental": true,
        "Messages": null
      },
      ...
      "s3://backupbucket@minio/?backup=backup-fa78d89827664840&volume=pvc-004d8edb-3a8c-4596-a659-3d00122d3f07": {
        "Name": "backup-fa78d89827664840",
        "URL": "s3://backupbucket@minio/?backup=backup-fa78d89827664840&volume=pvc-004d8edb-3a8c-4596-a659-3d00122d3f07",
        "SnapshotName": "backup-ac364071",
        "SnapshotCreated": "2021-05-17T04:42:01Z",
        "Created": "2021-05-17T04:42:03Z",
        "Size": "115343360",
        "Labels": {},
        "IsIncremental": true,
        "Messages": null
      }
    }
  }
}

backup inspect <backup>:读取单个备份配置(backup_backup_<backup-hash>.cfg):

$ backup inspect "s3://backupbucket@minio/?backup=backup-fa78d89827664840&volume=pvc-004d8edb-3a8c-4596-a659-3d00122d3f07"
{
  "Name": "backup-fa78d89827664840",
  "URL": "s3://backupbucket@minio/?backup=backup-fa78d89827664840&volume=pvc-004d8edb-3a8c-4596-a659-3d00122d3f07",
  "SnapshotName": "backup-ac364071",
  "SnapshotCreated": "2021-05-17T04:42:01Z",
  "Created": "2021-05-17T04:42:03Z",
  "Size": "115343360",
  "Labels": {},
  "IsIncremental": true,
  "VolumeName": "pvc-004d8edb-3a8c-4596-a659-3d00122d3f07",
  "VolumeSize": "2147483648",
  "VolumeCreated": "2021-05-12T00:52:01Z",
  "Messages": null
}

改造后(只列名,配置单独读取)

backup ls --volume-only:仅列出备份卷名:

$ backup ls s3://backupbucket@minio/ --volume-only
{
  "pvc-004d8edb-3a8c-4596-a659-3d00122d3f07": {},
  "pvc-7a8ded68-862d-4abb-a08c-8cf9664dab10": {}
}

backup ls --volume <volume-name>:仅列出备份名:

$ backup ls s3://backupbucket@minio/ --volume pvc-004d8edb-3a8c-4596-a659-3d00122d3f07
{
  "pvc-004d8edb-3a8c-4596-a659-3d00122d3f07": {
    "Backups": {
      "backup-02224cb26b794e73": {},
      ...
      "backup-fa78d89827664840": {}
    }
  }
}

backup inspect-volume <volume>:读取单个备份卷配置(volume.cfg):

$ backup inspect-volume "s3://backupbucket@minio/?volume=pvc-004d8edb-3a8c-4596-a659-3d00122d3f07"
{
  "Name": "pvc-004d8edb-3a8c-4596-a659-3d00122d3f07",
  "Size": "2147483648",
  "Labels": {},
  "Created": "2021-05-12T00:52:01Z",
  "LastBackupName": "backup-c5f548b7e86b4b56",
  "LastBackupAt": "2021-05-17T05:31:01Z",
  "DataStored": "121634816",
  "Messages": {}
}

backup inspect <backup>:语义不变,仍读取单个备份配置:

$ backup inspect "s3://backupbucket@minio/?backup=backup-fa78d89827664840&volume=pvc-004d8edb-3a8c-4596-a659-3d00122d3f07"
{
  "Name": "backup-fa78d89827664840",
  "URL": "s3://backupbucket@minio/?backup=backup-fa78d89827664840&volume=pvc-004d8edb-3a8c-4596-a659-3d00122d3f07",
  "SnapshotName": "backup-ac364071",
  "SnapshotCreated": "2021-05-17T04:42:01Z",
  "Created": "2021-05-17T04:42:03Z",
  "Size": "115343360",
  "Labels": {},
  "IsIncremental": true,
  "VolumeName": "pvc-004d8edb-3a8c-4596-a659-3d00122d3f07",
  "VolumeSize": "2147483648",
  "VolumeCreated": "2021-05-12T00:52:01Z",
  "Messages": null
}

新增 backup head <config>:只获取配置元数据(修改时间),用于判断配置是否变化:

{
  "ModificationTime": "2021-05-17T04:42:03Z"
}

三个核心 CRD:把远端状态缓存进集群

设计文档规划了三个 CRD,当前仓库的 Helm Chart 中已将它们落地为 longhorn.io 组、v1beta2 版本的正式 CRD(见 chart/templates/crds.yaml),并提供了 CLI 短名:lhbt(BackupTarget)、lhbv(BackupVolume)、lhb(Backup)。

BackupTarget CRD(backuptargets.longhorn.io,短名 lhbt)

保存备份目标的连接信息与轮询状态,对应 CRD 定义位于 chart/templates/crds.yaml,当前 CRD 中 backupTargetName 已被移除,说明该 CRD 落地时已随多备份目标演进调整。设计文档中的核心结构:

metadata:
  name: "default"          # 备份目标名,最初仅支持单一备份目标
spec:
  backupTargetURL: ""      # 备份目标 URL(string)
  credentialSecret: ""     # 备份目标凭证 Secret(string)
  pollInterval: 0s         # 备份目标轮询间隔(metav1.Duration)
  syncRequestAt: null      # 请求同步远端备份目标的时间(*metav1.Time)
status:
  ownerID: ""              # 负责运行备份目标控制器操作的节点 ID
  available: false         # 远端备份目标是否可用
  lastSyncedAt: null       # 备份目标最近一次执行 reconcile 的时间

当前 CRD 中保留了 spec.backupTargetURL、spec.credentialSecret、spec.pollInterval、spec.syncRequestedAt,状态中除 available、lastSyncedAt、ownerID 外还扩展了 conditions(记录备份目标不可用的原因),并在 additionalPrinterColumns 中暴露 URL、Credential、Available、LastSyncedAt 等列,便于 kubectl get lhbt 直接观察。

BackupVolume CRD(backupvolumes.longhorn.io,短名 lhbv)

缓存单个备份卷的配置,对应 CRD 定义位于 chart/templates/crds.yaml。设计文档结构:

metadata:
  name: "<backup-volume-name>"   # 备份卷名称
spec:
  syncRequestAt: null            # 请求同步远端备份卷的时间(*metav1.Time)
  fileCleanupRequired: false     # 是否删除远端备份卷配置(bool)
status:
  ownerID: ""                    # 负责运行备份卷控制器操作的节点 ID
  lastModificationTime: null     # 备份卷配置的最后修改时间(Time)
  size: ""                       # 备份卷大小(string)
  labels: {}                     # 备份卷标签(map[string]string)
  createAt: ""                   # 备份卷创建时间(string)
  lastBackupName: ""             # 最新备份名(string)
  lastBackupAt: ""               # 最新备份时间(string)
  dataStored: ""                 # 备份卷已存块数(string)
  messages: {}                   # 调用 longhorn engine 列/查备份卷时的错误信息(map[string]string)
  lastSyncedAt: null             # 备份卷最近一次同步进集群的时间(*metav1.Time)

当前 CRD 的状态字段基本沿用此设计(createdAt、dataStored、lastBackupAt、lastBackupName、lastModificationTime、lastSyncedAt、messages、ownerID、size、labels),并随功能演进补充了 backingImageName、backingImageChecksum、storageClassName、linkedCloneSourceVolume、linkedCloneSourceSnapshot 等字段(后者用于快速克隆场景)。kubectl get lhbv 可看到 BackupTarget、CreatedAt、LastBackupName、LastBackupAt、LastSyncedAt 等列。

Backup CRD(backups.longhorn.io,短名 lhb)

缓存单个备份的配置,并通过标签关联所属备份卷,对应 CRD 定义位于 chart/templates/crds.yaml。设计文档结构:

metadata:
  name: "<backup-name>"
  labels:
    longhornvolume: "<backup-volume-name>"   # 标记该备份所属的备份卷
spec:
  fileCleanupRequired: false                 # 是否删除远端备份配置及关联块文件(bool)
  snapshotName: ""                           # 快照名(string)
  labels: {}                                 # 快照备份标签(map[string]string)
  backingImage: ""                           # 备份映像(string)
  backingImageURL: ""                        # 备份映像 URL(string)
status:
  ownerID: ""                                # 负责运行备份控制器操作的节点 ID
  backupCreationIsStart: false               # 快照备份创建是否已开始(bool)
  url: ""                                    # 快照备份 URL(string)
  snapshotName: ""                           # 快照名(string)
  snapshotCreateAt: ""                       # 快照创建时间(string)
  backupCreateAt: ""                         # 快照备份创建时间(string)
  size: ""                                   # 快照大小(string)
  labels: {}                                 # 快照备份标签(map[string]string)
  messages: {}                               # 调用 longhorn engine 列/查备份时的错误信息(map[string]string)
  lastSyncedAt: null                         # 备份最近一次同步进集群的时间(*metav1.Time)

当前 CRD 中 spec 保留了 snapshotName、labels、backupMode(full/incremental)、backupBlockSize(0 表示沿用默认 2MiB,-1 表示无效,2097152/16777216 为可选块大小)、syncRequestedAt;status 保留了 backupCreatedAt、labels、backupTargetName、compressionMethod、error、lastSyncedAt 等,并在 additionalPrinterColumns 中暴露 SnapshotName、SnapshotSize、SnapshotCreatedAt、BackupTarget、State、LastSyncedAt 列。

控制器架构:四级协调实现最终一致

1. setting_controller(既有控制器改造)

监听 Setting CR settings.longhorn.io 的 backup-target、backup-target-credential-secret、backupstore-poll-interval 三个字段,负责创建/更新默认的 BackupTarget CR;同时按 backupstore-poll-interval 启动一个定时器 goroutine,定时把 BackupTarget CR 的 spec.syncRequestAt 更新为 time.Now()。若轮询间隔为 0,则不更新 spec.syncRequestAt(即关闭自动轮询)。

2. backup_target_controller(新增)

监听 BackupTarget CR 的变化,负责创建/更新/删除 BackupVolume CR 的 metadata 与 spec。Reconcile 步骤:

  1. 若当前节点 ID ≠ BackupTarget CR 的 spec.responsibleNodeID,跳过;
  2. 若 status.lastSyncedAt ≥ spec.syncRequestAt,跳过(无需同步);
  3. 调用 longhorn engine 执行 backup ls --volume-only 列出远端备份卷 backupStoreBackupVolumes;若远端不可用:
    • 置 status.available=false、status.lastSyncedAt=time.Now();
    • 跳过本次 reconcile;
  4. 列出集群内 BackupVolume CR clusterBackupVolumes;
  5. 计算差集 backupVolumesToPull = backupStoreBackupVolumes - clusterBackupVolumes,为缺失的备份卷创建 BackupVolume CR(metadata.name);
  6. 计算差集 backupVolumesToDelete = clusterBackupVolumes - backupStoreBackupVolumes,删除多余的 BackupVolume CR;
  7. 重新列出集群内 BackupVolume CR,更新其 spec.syncRequestAt = time.Now()(触发下一层同步);
  8. 更新 BackupTarget CR 状态:status.available=true、status.lastSyncedAt=time.Now()。

3. backup_volume_controller(新增)

监听 BackupVolume CR,负责删除场景(远端清理)、状态更新与 Backup CR 的创建/删除。Reconcile 步骤:

  1. 校验当前节点 ID(同前);
  2. 若收到删除 BackupVolume CR 事件:
    1. 若 BackupVolume CR spec.fileCleanupRequired=true,则把所有 Backup CR 的 spec.fileCleanupRequired 置为 true;
    2. 删除对应 Backup CR;
    3. 若 spec.fileCleanupRequired=true,执行 backup rm --volume <volume-name> <url> 删除远端备份卷;
    4. 移除 finalizer;
  3. 若 status.lastSyncedAt ≥ spec.syncRequestAt,跳过;
  4. 执行 backup ls --volume <volume-name> 列出远端备份 backupStoreBackups;
  5. 列出集群内 Backup CR clusterBackups;
  6. 差集 backupsToPull = backupStoreBackups - clusterBackups:创建 Backup CR(metadata.name + metadata.labels["longhornvolume"]=<backup-volume-name>);
  7. 差集 backupsToDelete = clusterBackups - backupStoreBackups:删除 Backup CR;
  8. 执行 backup head <volume-config> 获取备份卷配置的最后修改时间,与 status.lastModificationTime 比较;若未变化,仅更新 status.lastSyncedAt 并返回(避免无谓重读);
  9. 执行 backup inspect-volume <volume-name> 读取备份卷配置;
  10. 依据配置更新 BackupVolume CR 状态,并更新 status.lastModificationTime 与 status.lastSyncedAt;
  11. 同步更新 Volume CR 的 status.lastBackup 与 status.lastBackupAt。

4. backup_controller(新增)

监听 Backup CR,负责向远端备份目标创建/删除备份并更新状态。Reconcile 步骤:

  1. 校验当前节点 ID(同前);
  2. 若收到删除 Backup CR 事件:
    1. 若 spec.fileCleanupRequired=true,执行 backup rm <url> 删除远端备份;
    2. 更新对应 BackupVolume CR 的 spec.syncRequestAt=time.Now();
    3. 移除 finalizer;
  3. 若 spec.snapshotName != "" 且 status.backupCreationIsStart == false:
    1. 调用 longhorn engine/replica 执行备份创建;
    2. 置 status.backupCreationIsStart = true;
    3. 派生 goroutine 监控创建进度,当进度达到 100% 时:若 BackupVolume CR 存在则更新其 spec.syncRequestAt = time.Now(),否则创建该 BackupVolume CR(metadata.name);
  4. 若 status.lastSyncedAt != nil,说明备份配置已同步过,跳过;
  5. 执行 backup inspect <backup-url> 读取备份配置;
  6. 按配置更新 Backup CR 状态;
  7. 更新 status.lastSyncedAt。

从实现上看,"差集同步"是三个新控制器的共同模式:pull 差集(远端有、集群没有 → 创建 CR)、delete 差集(集群有、远端没有 → 删除 CR),从而保证集群内 CR 集合与远端备份目标最终一致。

HTTP API 前后行为对照

设计文档给出了完整的端点行为对照表,改造后所有备份卷/备份相关的读操作都变成读集群内 CR,写/删操作则转为打标 + 控制器异步执行:

HTTP Endpoint Before(直连远端) After(CR 驱动)
GET /v1/backupvolumes 从远端备份目标读取所有备份卷 读取所有 BackupVolume CR
GET /v1/backupvolumes/{volName} 从远端读取单个备份卷 按卷名读取 BackupVolume CR
DELETE /v1/backupvolumes/{volName} 从远端删除备份卷 删除 BackupVolume CR,由 backup_volume_controller 协调删除远端备份卷
POST /v1/volumes/{volName}?action=snapshotBackup 直接向远端创建备份 创建新 Backup CR,由 backup_controller 协调创建远端备份
GET /v1/backupvolumes/{volName}?action=backupList 从远端读取备份列表 按标签过滤 volume=<backup-volume-name> 读取 Backup CR 列表
GET /v1/backupvolumes/{volName}?action=backupGet 从远端读取单个备份 按备份名读取 Backup CR
DELETE /v1/backupvolumes/{volName}?action=backupDelete 从远端删除备份 删除 Backup CR,由 backup_controller 协调删除远端备份

两个删除端点的具体流程:

  • DELETE /v1/backupvolumes/{volName}:先将 BackupVolume CR 的 spec.fileCleanupRequired 置为 true,再删除该 CR,最终由控制器执行远端清理并移除 finalizer;
  • DELETE /v1/backupvolumes/{volName}?action=backupDelete:先将 Backup CR 的 spec.fileCleanupRequired 置为 true,再删除该 CR,最终由 backup_controller 执行 backup rm <url>。

POST /v1/volumes/{volName}?action=snapshotBackup 会先生成备份名,再创建如下 Backup CR:

metadata:
  name: <backup-name>
  labels:
    longhornvolume: <backup-volume-name>
spec:
  snapshotName: <snapshot-name>
  labels: <snapshot-backup-labels>
  backingImage: <backing-image>
  backingImageURL: <backing-image-URL>

用户故事与体验提升

设计文档给出了三个典型用户故事,用于验证改造效果:

  • Story 1:远端备份目标有大量备份卷且延迟高 → 用户仍能在 GUI 上列出全部备份卷;
  • Story 2:远端备份目标有大量备份且延迟高 → 用户仍能在 GUI 上列出全部备份;
  • Story 3:用户在 GUI 上创建备份 → 系统先创建 Backup CR,backup_controller 再协调 longhorn engine/replica 真正执行远端备份。

改造后,GUI 的列表请求不再受"远端查询耗时"的直接影响,超时问题从架构上消除。

配置与部署:轮询间隔与默认备份存储

异步拉取的轮询行为由设置 backupstore-poll-interval 控制。在 Helm Chart 中可通过 chart/values.yaml 的 defaultBackupStore 段配置默认值:

defaultBackupStore:
  # 默认备份存储端点(可选:"NFS"、"CIFS"、"AWS"、"GCP"、"AZURE")
  backupTarget: ~
  # 与默认备份目标关联的 Kubernetes Secret 名称
  backupTargetCredentialSecret: ~
  # Longhorn 等待多久再检查默认备份存储是否有新备份(秒)。
  # 默认值为 "300";值为 "0" 时禁用轮询。
  pollInterval: ~

这些配置经 chart/templates/default-resource.yaml 写入默认 Setting CR:

backup-target: {{ .Values.defaultBackupStore.backupTarget }}
backup-target-credential-secret: {{ .Values.defaultBackupStore.backupTargetCredentialSecret }}
backupstore-poll-interval: {{ .Values.defaultBackupStore.pollInterval }}

setting_controller 正是监听这三个 Setting 字段来创建/更新默认 BackupTarget CR,并按 backupstore-poll-interval(默认 300 秒,即 5 分钟;设为 0 则完全禁用自动轮询)触发 spec.syncRequestAt 更新。

设计文档还建议提供"立即同步"的能力:可在 Backup 页面提供按钮更新 BackupTarget CR 的 spec.syncRequestAt = time.Now(),或在 Backup → Backup Volume 页面提供按钮更新 BackupVolume CR 的 spec.syncRequestAt = time.Now(),让用户在配置变更后不必等待整个轮询周期。

测试计划与验证要点

设计文档给出的测试围绕高负载、高延迟场景(超过 1000 个备份卷、超过 1000 个备份,且从 longhorn manager 到远端备份目标每次操作延迟 700–800ms):

基础备份/恢复:配置备份目标与 5 分钟轮询 → 在 vol-A、vol-B 各创建两个备份 → GUI 可见对应 BackupVolume 与备份 → 删除单个备份后远端数据随之删除 → 删除 vol-A 备份卷后远端备份卷与其全部备份被删除 → 切换到另一备份目标后看不到 vol-B → 将 backupstore-poll-interval 改为 1 分钟并切回原目标,1 分钟后恢复可见 → 从 vol-B 备份创建卷。

DR 卷操作(双集群共享同一备份目标):集群 A 创建卷并周期性备份 → 集群 B 在轮询周期后能列出备份卷/备份 → 从备份卷创建 DR 卷 → 校验 DR 卷 status.LastBackup/status.LastBackupAt 周期性更新 → 集群 A 删除备份卷 → 集群 B 在轮询周期后该备份卷消失且 DR 卷状态不再更新。

备份目标 URL 清空:配置目标并创建备份 → 将备份目标设置置空 → 轮询触发后:默认 BackupTarget CR status.available=false、status.lastSyncedAt 更新、所有 BackupVolume/Backup CR 被删除、vol-A CR 的 status.lastBackup/status.lastBackupAt 被清理、GUI 显示目标不可用。

切换备份目标 URL:S3 备份 → 切到 NFS 备份 → 再切回 S3 → 轮询触发后 BackupVolume/Backup CR 与 vol-A 状态按 S3 数据重新同步。

凭证 Secret 变更:将备份目标凭证 Secret 置空 → 轮询触发后 status.available=false,GUI 显示目标不可用。

后续演进与现状对照

该设计落地后 Longhorn 持续演进,仓库中可看到与本主题直接相关的后续增强:

由此可见,异步拉取架构从 2021 年的增强提案起步,逐步成为 Longhorn 备份链路的基础设施,并围绕可靠性(误删防护)、多目标支持、备份模式等方向持续完善。

小结

本增强的核心价值可以概括为三点:列表与读配置分离(backupstore 命令职责拆分)、远端状态集群化(三个 CRD 缓存备份目标/备份卷/备份元数据)、控制器异步协调(差集同步 + 时间戳驱动 + finalizer 清理),最终让"备份列表超时"从架构层面消失,并为后续的多备份目标、备份块大小可配置等能力打下基础。若需深入源码,可重点研读 chart/templates/crds.yaml 中三个 CRD 的字段定义,以及 chart/values.yaml 的默认备份存储配置,结合本文的 reconcile 步骤理解数据流。

登录后查看全文
longhorn