文章

个人开发 Git Bare Repository + 群晖异地备份方案

个人开发 Git Bare Repository + 群晖异地备份方案

个人开发 Git Bare Repository + 群晖异地备份方案

1. 目标

当前开发环境采用:

1
2
3
4
5
开发机
   ↓ git push
主 NAS
   ↓
Bare Git Repository

计划增加第二台异地 NAS, 主要解决以下问题:

  1. 主 NAS 物理损坏.
  2. 主 NAS 硬盘或文件系统损坏.
  3. Git 仓库被误删.
  4. 数据发生损坏后较晚才被发现.
  5. 主 NAS 所在地点发生火灾、进水、雷击等整体事故.

当前阶段的首要目标是:

保证 Git 仓库数据可以可靠恢复.

不是:

主 NAS 故障后, 第二台 NAS 必须立即接管 Git 服务.

后者属于更进一步的灾难恢复需求, 后续再讨论.


2. 当前建议架构

采用群晖 Hyper Backup:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
开发机
   │
   │ git push
   ▼
NAS A
主 NAS
│
├── GitBareRepos/
│   ├── ProjectA.git
│   ├── ProjectB.git
│   ├── ShaderLibrary.git
│   └── ...
│
└── Hyper Backup
        │
        │ 定时增量备份
        ▼
NAS B
异地备份 NAS
│
└── Hyper Backup Vault
        │
        └── Hyper Backup Backup Repository

职责明确划分为:

1
2
3
4
5
6
7
8
开发机
    = 工作副本 + Local Git Repository

NAS A
    = 日常使用的中央 Bare Git Repository

NAS B
    = 异地历史备份

NAS B 不作为日常 Git Server 使用.


3. 为什么不直接做 NAS A → NAS B 实时同步

实时同步解决的是:

保持两个位置的数据状态一致.

备份解决的是:

即使当前状态已经错误, 仍然能够恢复过去的正确状态.

例如:

1
2
3
4
5
NAS A

ProjectA.git
ProjectB.git
ProjectC.git

如果误删:

1
ProjectB.git

普通同步可能变成:

1
2
3
4
5
NAS A 删除 ProjectB.git
        ↓
同步
        ↓
NAS B 同样删除 ProjectB.git

结果是:

1
两个 NAS 都正确地保存了错误状态.

对于备份系统来说, 这是不可接受的.


4. Hyper Backup 的核心价值

Hyper Backup 保存的不只是:

1
当前文件

而是:

1
2
3
当前文件状态
+
历史版本

例如:

1
2
3
4
5
6
7
8
9
10
11
Version 1
    周一

Version 2
    周二

Version 3
    周三

Version 4
    周四

假设周四发生误删除:

1
ProjectB.git

仍然可以选择:

1
Version 3

恢复删除之前的数据.

因此:

备份不是复制现在, 而是保存过去.


5. Hyper Backup 是否每次完整复制

不是.

可以从使用层面将其近似理解为:

1
2
3
4
5
6
7
8
9
第一次备份
    ↓
建立完整基底

后续备份
    ↓
保存新增或变化的数据
+
保存对应版本的状态信息

例如:

1
2
3
4
5
6
7
8
Version 1
100 GB

Version 2
新增 / 修改 2 GB

Version 3
新增 / 修改 1 GB

不会简单变成:

1
2
3
4
5
6
7
100 GB
+
100 GB
+
100 GB

= 300 GB

而更接近:

1
2
3
4
5
初始数据
+
后续变化数据
+
版本索引 / 元数据

因此 Hyper Backup 属于:

增量、多版本备份系统.


6. Hyper Backup 和 Git 的关系

可以使用一个便于理解的类比:

1
2
3
4
5
Git Repository
    = 保存代码的历史

Hyper Backup
    = 保存整个 Git Repository 文件状态的历史

因此从使用角度看, Hyper Backup 有一点类似:

Git 仓库外面再增加一层版本历史.

但二者并不相同.

Git 理解:

1
2
3
4
5
6
Commit
Branch
Tag
Tree
Blob
Merge

Hyper Backup 不理解这些 Git 语义.

对于 Hyper Backup 来说:

1
ProjectA.git/

只是普通文件和目录.

所以更准确的描述是:

1
2
3
4
5
Git
    = 源代码版本管理

Hyper Backup
    = 文件 / 数据备份与版本管理

7. 为什么 Hyper Backup 不让 NAS B 直接接管

这是有意的设计取舍.

两个系统解决的是不同问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Hyper Backup

目标:
数据保护优先

特点:
历史版本
增量备份
压缩
去重
完整性检查
独立备份库

代价:
恢复之前不能直接作为原始文件使用

另一类方案则是:

1
2
3
4
5
6
7
8
9
10
Replication

目标:
快速恢复服务优先

特点:
NAS B 保存接近可直接使用的数据状态

优势:
NAS A 故障后可以更快接管

可以概括为:

1
2
3
4
5
6
7
Backup-first
    ↓
Hyper Backup

Recovery-first
    ↓
Replication

8. NAS A 物理损坏后的恢复流程

假设原结构为:

1
2
3
4
5
6
7
8
开发机
   ↓
NAS A
GitBareRepos/
   ↓
Hyper Backup
NAS B
Backup Repository

NAS A 完全损坏后:

1
2
NAS A
  X

处理流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1. 准备新的 NAS.

2. 安装 DSM.

3. 安装 Hyper Backup.

4. 让新 NAS 能够访问 NAS B.

5. 连接 NAS B 上已有的 Hyper Backup Repository.

6. 选择 NAS A 损坏前的正常恢复点.

7. 恢复 GitBareRepos/.

8. 恢复 Git 使用的用户、权限、SSH 等配置.

9. 开发机重新连接新的 NAS.

10. 执行:
    git fetch
    git push

11. 确认仓库恢复正常.

最终结构:

1
2
3
4
5
6
7
8
9
10
NAS B
Backup Repository
      │
      │ Restore
      ▼
新的 NAS
GitBareRepos/
      │
      ▼
重新成为主 Git Server

9. 最近一次备份之后的 Commit 怎么办

假设:

1
2
3
4
5
6
7
8
9
10
11
08:00
Hyper Backup 完成备份

09:00
开发机产生 Commit A

09:01
git push → NAS A

09:30
NAS A 损坏

此时 NAS B 只有:

1
08:00

的版本.

但是 Commit A 通常仍然存在于:

1
开发机的 Local Git Repository

因此恢复过程可以是:

1
2
3
4
5
6
7
8
9
NAS B
   ↓
恢复到 08:00

开发机
   ↓
git push

补回 08:00 之后的 Commit

Git 本身因此也构成了额外的数据保护层.


10. 备份 NAS 是否必须远大于主 NAS

不是.

真正应该关注的是:

1
需要备份的数据量

而不是:

1
NAS A 总容量

例如:

1
2
3
4
5
NAS A 总容量:
8 TB

实际 Git Repository:
1 TB

如果 Hyper Backup 只备份 Git Repository:

1
需要规划的是这 1 TB 数据的备份空间.

而不是必须准备大于 8 TB 的空间.


11. 备份容量的基本估算

可以粗略理解为:

1
2
3
4
5
6
7
8
备份空间

≈ 当前有效数据
+ 被保留的历史变化
+ 未来数据增长
+ 元数据
- 压缩收益
- 去重收益

例如:

1
2
当前 Git Repository:
2 TB

不意味着:

1
2
3
100 个备份版本
=
200 TB

因为历史版本不会每次完整复制全部数据.

实际容量取决于数据变化量.


12. Git Repository 的特殊容量问题

Bare Git Repository 内部存在:

1
2
objects/
pack/

Git 可能执行:

1
2
git gc
git repack

例如原本:

1
pack-A.pack

重新打包以后可能变成:

1
pack-B.pack

虽然 Git 仓库的逻辑内容变化不大, 但对于普通备份系统来说可能表现为:

1
2
3
一个大文件被删除
+
一个新的大文件产生

因此:

Git 仓库的备份空间增长速度, 有时会高于代码本身的实际增长速度.

规划备份容量时需要保留额外余量.

初期可以考虑:

1
2
实际 Git 数据容量
× 约 2

作为一个方便扩展的容量起点.

这不是 Hyper Backup 的硬性要求, 只是容量规划参考.


13. 推荐的初始备份策略

个人开发可以从比较简单的配置开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
备份对象:
整个 GitBareRepos/

备份频率:
每几小时一次

版本策略:
启用多版本

完整性检查:
定期执行

备份位置:
物理异地 NAS

不需要追求分钟级实时备份.

因为数据实际上存在三层:

1
2
3
4
5
6
7
开发机 Local Git
        │
        ▼
NAS A Bare Git
        │
        ▼
NAS B Hyper Backup

14. 当前方案能够解决什么

可以解决

1
2
3
4
5
6
7
NAS A 硬件故障
硬盘损坏
误删除仓库
错误修改
较晚发现的数据问题
需要恢复历史状态
主 NAS 所在地点发生物理事故

不能直接解决

1
NAS A 故障以后立即切换到 NAS B

因为 NAS B 保存的是:

1
Backup Repository

而不是可以直接使用的:

1
2
3
ProjectA.git
ProjectB.git
ProjectC.git

需要先 Restore.


15. 后续可能的第二阶段

如果未来出现新的要求:

NAS A 故障以后, 希望 NAS B 可以迅速继续提供 Git 服务.

那么需要进一步研究:

1
2
3
4
5
Snapshot
+
Replication
+
Failover

可能形成:

1
2
3
4
5
6
7
8
9
                 ┌── Replica
                 │
                 │   快速接管
NAS A ───────────┤
                 │
                 └── Hyper Backup
                     
                     历史备份
                     最终数据保护

两层分别解决:

1
2
3
4
5
Replication
    = 尽快恢复工作

Hyper Backup
    = 数据不要丢

这部分暂不纳入当前第一阶段方案.


16. 当前结论

现阶段采用:

1
2
3
4
5
6
7
开发机
   ↓ Git
NAS A
主 Bare Repository
   ↓ Hyper Backup
NAS B
异地历史备份

核心原则:

1
2
3
4
5
6
7
8
Local Git
    = 开发

NAS A
    = 主仓库

NAS B
    = Backup

不将 NAS B 当作第二个日常 Git Server.

第一阶段优先解决:

数据可靠性.

后续再根据实际需求讨论:

  1. NAS A / NAS B 的具体型号.
  2. 硬盘容量规划.
  3. RAID / SHR 配置.
  4. 两地网络连接方式.
  5. Hyper Backup 的具体配置.
  6. 备份版本保留策略.
  7. 完整性检查策略.
  8. 是否增加 Snapshot Replication.
  9. NAS A 故障后的快速接管方案.
  10. 实际灾难恢复演练流程.
本文由作者按照 CC BY 4.0 进行授权