存储与数据管理架构
平台上的每个服务都会产生数据:LMS 中的课程材料、云存储中的文件、邮箱中的邮件、协作文档和配置状态。这些数据是机构最有价值的资产,如何存储、保护和管理它们决定了平台的可靠性。本文记录了存储架构:PersistentVolumes 如何提供持久存储,数据库后端如何为有状态应用服务,备份如何防止数据丢失,以及数据生命周期——从创建到归档——如何管理。
有关将数据传送给用户的网络路径,请参阅网络和流量架构。有关完整的平台概览,请参阅系统架构概览。
持久存储
PersistentVolumes 和存储类
Kubernetes 将计算(Pod,短暂的)与存储(PersistentVolumes,持久的)分离。当 Pod 重启时,其本地数据会丢失。PersistentVolumes(PV)在 Pod 重启、节点故障和重新调度后仍然存在。
平台使用 PersistentVolumeClaims(PVC)来请求存储。PVC 指定:
- 访问模式:卷如何挂载(读写一次、只读多次、读写多次)
- 存储大小:需要多少容量
- 存储类:使用什么类型的后端存储
存储类决定了物理存储后端。平台中常见的存储类包括:
- 本地持久存储:节点上直接附加的存储。快速但绑定到特定节点。适用于受益于低延迟的数据库。
- 网络附加存储(NFS):可从多个节点访问的共享文件系统。适用于需要从任何节点进行读写访问的文件存储服务(Nextcloud、OpenCloud)。
- 软件定义存储(Ceph):通过复制提供弹性的分布式存储。数据写入多个节点,因此单个节点故障不会导致数据丢失。适用于所有服务类型。
- 对象存储(S3 兼容):用于备份目标和大型非结构化数据。不用于应用 PV,但被 restic 用于备份存储。
每个服务通过其 Helm chart 中的 PVC 声明其存储需求。平台的存储类确保自动配置正确类型的存储。
访问模式
| 访问模式 | 缩写 | 描述 | 典型用途 |
|---|---|---|---|
| ReadWriteOnce | RWO | 一个节点读写挂载 | 数据库(MariaDB、PostgreSQL) |
| ReadOnlyMany | ROX | 多个节点只读挂载 | 配置、静态资产 |
| ReadWriteMany | RWX | 多个节点读写挂载 | 文件存储(Nextcloud、OpenCloud) |
大多数数据库使用 RWO,因为它们作为单个实例运行,不需要来自多个节点的并发写入。文件存储服务使用 RWX,因为任何节点都可能处理文件请求。
容量规划
存储容量是最关键的运维关注点之一。每个服务有不同的存储需求:
- 文件存储(Nextcloud、OpenCloud):最大的消费者。用户文件随时间累积。规划增长——拥有活跃用户的平台可能在几个月内需要数 TB 的文件存储。
- 电子邮件(Grommunio):邮箱稳步增长。每个用户的邮箱可能从几百 MB 到几 GB 不等。
- 数据库(MariaDB、PostgreSQL):相对于文件存储较小,但关键。数据库存储应在快速存储(SSD 或 NVMe)上以保证性能。
- 视频录制(BigBlueButton):录制可能很大(每会话几百 MB 到几 GB)。规划保留策略(录制保留多长时间)。
- 配置和状态(Keycloak、Nubus):小但关键。Keycloak 数据库的丢失意味着所有用户账户和联邦配置的丢失。
平台不规定具体的容量数字——每个机构的需求不同。但平台提供监控(Prometheus + Grafana)来跟踪存储使用情况并在容量不足时发出警报。
数据库后端
平台使用三种类型的数据库后端,每种适用于不同的工作负载:
MariaDB
MariaDB(MySQL 的一个分支)是需要 MySQL 兼容性的服务的主要关系型数据库。它用于:
- Grommunio(电子邮件):邮箱元数据、用户配置
- ILIAS(LMS):课程数据、用户进度、评估
- Moodle(LMS):课程数据、用户进度、作业
- XWiki(wiki):Wiki 页面、附件、元数据
MariaDB 作为 StatefulSet 在 Kubernetes 中运行,使用 PersistentVolume 存储数据。每个服务有自己的 MariaDB 实例(或共享实例中的数据库),通过命名空间隔离。
高可用性
对于生产部署,MariaDB 可以配置主从复制。主节点处理写入;从节点处理读取并提供故障转移。如果主节点故障,从节点被提升为主节点。这是配置,不是代码——Helm chart 支持单实例和复制设置。
PostgreSQL
PostgreSQL 用于偏好或需要 PostgreSQL 特定功能(JSONB、全文搜索、高级索引)的服务:
- Nextcloud(文件存储):元数据、文件索引、共享
- OpenProject(项目管理):项目、任务、时间跟踪
- Keycloak(身份):Realm 配置、用户账户、联邦元数据
- Zammad(帮助台):工单、文章、用户数据
PostgreSQL 也作为 StatefulSet 运行,使用 PersistentVolume。与 MariaDB 一样,它可以配置主从复制以实现高可用性。
连接池
MariaDB 和 PostgreSQL 都支持连接池(MariaDB 通过 ProxySQL,PostgreSQL 通过 PgBouncer)。连接池通过维护可重用连接池来减少建立新数据库连接的开销。当服务有许多短暂的数据库查询时,这很重要。
Redis
Redis 是一个内存键值存储,用于:
- 缓存:会话数据、频繁访问的对象、渲染页面
- 速率限制:跟踪 API 请求计数
- 消息队列:后台任务的轻量级作业队列
- 会话存储:对于将会话存储在 Redis 而非数据库中的服务
Redis 作为 StatefulSet 运行,使用 PersistentVolume 进行持久化(以便缓存数据在重启后仍然存在)。它配置了最大内存限制和驱逐策略(通常是 allkeys-lru——当内存满时驱逐最近最少使用的键)。
数据库连接
服务通过 Kubernetes DNS 名称连接到其数据库。例如,服务连接到 mariadb.database-namespace.svc.cluster.local:3306 而不是 IP 地址。这种抽象意味着数据库可以移动、重启或重新配置,而无需更改应用配置。
每个数据库有自己的凭证,存储为 Kubernetes Secrets。应用从环境变量或挂载的 secret 文件中读取凭证。没有数据库密码以明文形式存储在 Helm 配置中——它们在部署期间生成并存储在 Secrets 中。
备份集成
k8up 备份操作器
平台使用 k8up,一个 Kubernetes 原生备份操作器,来管理自动备份。k8up 在集群内运行并协调所有服务的备份计划。
k8up 使用 restic 作为备份后端。Restic 是一个快速、安全、高效的备份工具,支持:
- 增量备份:只传输更改的数据,减少备份时间和存储使用
- 去重:相同的数据块只存储一次,减少存储成本
- 加密:所有备份数据使用可配置密钥静态加密
- 多种存储后端:本地目录、NFS、S3 兼容对象存储、SFTP 服务器
备份计划
平台的备份计划是可配置的。典型设置:
- 数据库备份:每日,通过数据库转储(例如
mariadb-dump或pg_dump)。这些是逻辑备份,捕获数据库在某个时间点的状态。 - 持久卷快照:每周完整快照所有 PersistentVolume。这些是卷级备份,捕获整个 PV,包括数据库、文件和配置。
- 配置备份:配置存储在 Git 中(通过 ArgoCD),因此 Git 历史作为配置备份。不需要单独备份。
备份内容
所有服务的所有持久数据都包含在备份中:
- LMS 课程内容和用户提交(ILIAS、Moodle)
- BigBlueButton 录制文件
- Nextcloud 和 OpenCloud 用户文件
- Grommunio 邮箱(通过 MariaDB 转储)
- Collabora 文档缓存
- Keycloak 和 Nubus 配置状态
- 数据库内容(MariaDB、PostgreSQL)
- Redis 持久化数据
非持久数据被排除:容器镜像、临时缓存和可以重新生成的临时文件。
备份存储目标
Restic 支持多种存储后端。机构可以将备份导向:
- 本地 NFS/S3 兼容存储:机构控制的本地存储
- 异地对象存储:基于云的 S3 兼容存储,用于灾难恢复
- SFTP 服务器:远程服务器,用于异地备份存储
- 任何 restic 支持的后端:Restic 灵活的后端支持意味着机构可以选择适合其基础设施和合规要求的存储
备份目标在 k8up 的计划定义中配置。可以同时使用多个目标(例如,本地用于快速恢复,异地用于灾难恢复)。
恢复过程
从备份恢复涉及:
- 识别恢复点:哪个备份快照包含所需状态
- 停止受影响的服务:避免恢复期间的数据冲突
- 运行 restic 恢复:k8up 启动恢复作业,将数据从备份目标复制回 PersistentVolume
- 重启服务:恢复完成后,服务使用恢复的数据重启
对于数据库恢复,过程类似但使用数据库转储:转储文件恢复到数据库,重放 SQL 语句以重建数据库状态。
备份监控
k8up 与 Prometheus 集成以公开备份指标:
- 上次成功备份时间戳
- 备份持续时间
- 备份大小
- 仓库中的快照数量
- 备份失败(通过 Alertmanager 告警)
运维人员应监控这些指标并在备份失败时发出警报——静默的备份失败比没有备份更糟糕,因为它创造了虚假的安全感。
数据生命周期
创建
当用户与平台交互时,数据由服务创建。每个服务管理自己的数据格式和存储位置。平台不强制统一数据模型——每个服务使用其原生存储(Nextcloud 中的文件、MariaDB 中的记录、Collabora 中的文档)。
增长
随着平台的使用,数据增长。平台提供监控(Prometheus + Grafana)来跟踪:
- PersistentVolume 使用情况(每个 PV 有多满)
- 数据库大小(行数、消耗的存储)
- 备份大小和增长率
- 剩余容量
当存储接近容量时,运维人员可以:
- 扩展 PersistentVolume:大多数存储类支持卷扩展。PVC 更新为更大的大小,PV 自动增长(RWO 卷无停机;RWX 卷短暂重新挂载)。
- 添加节点:对于分布式存储(Ceph),添加节点同时增加计算和存储容量。
- 归档旧数据:将不常访问的数据移动到更便宜的存储或根据保留策略删除。
保留和归档
每个服务有自己的数据保留要求:
- 电子邮件:邮箱在用户账户存在期间保留。删除的邮件可能在可配置期间内可恢复。
- LMS 数据:课程数据根据机构策略保留。一些机构在学期结束后归档课程;其他机构无限期保留。
- 视频录制:BigBlueButton 录制可保留可配置的时间,然后自动删除或归档。
- 文件存储:用户文件保留直到用户删除或账户被移除。
平台不强制保留策略——每个机构根据自己的法律和运营要求配置保留。平台提供工具(备份计划、监控、存储扩展)来实施机构选择的任何保留策略。
删除
数据删除是永久的。当用户账户被移除时,平台删除:
- 用户的文件(Nextcloud、OpenCloud)
- 用户的邮箱(Grommunio)
- 用户的课程数据和提交(ILIAS、Moodle)
- 用户在 Keycloak 和 Nubus 中的配置
删除由服务自己的删除逻辑执行,而不是由中央平台范围的脚本。这确保每个服务的删除过程尊重自己的数据模型和引用完整性。
数据库迁移
当服务更新时,其数据库可能需要架构迁移。平台通过 Helm chart 钩子处理:
- 预升级钩子:在新版本启动前运行数据库迁移脚本
- 新版本启动:服务使用更新的架构启动
- 回滚(如果需要):如果迁移可逆,Helm chart 可以回滚到以前的版本
迁移是特定于服务的。每个服务的 Helm chart 包含其数据库的迁移逻辑。平台不强制统一迁移框架——它委托给每个服务的原生迁移工具。
故障模式和故障排除
PersistentVolume 满
症状:服务报告"磁盘已满"或"设备上没有剩余空间"错误。 原因:PersistentVolume 已达到其容量。 解决方案:扩展 PVC(如果存储类支持扩展)或清理不必要的数据。监控 PV 使用情况以及早发现问题。
数据库连接失败
症状:服务报告"连接被拒绝"或"无法连接到数据库"错误。
原因:数据库 Pod 宕机、网络策略阻止流量、或数据库凭证错误。
解决方案:检查数据库 Pod 状态(kubectl get pods),验证网络策略允许服务到达数据库,检查 Secret 中的凭证是否正确。
备份失败
症状:k8up 报告备份失败,或上次成功备份已过时。 原因:备份目标不可达、restic 仓库被锁定、或备份加密密钥已更改。 解决方案:检查备份目标连接性,验证 restic 仓库未被其他进程锁定,确保备份加密密钥未更改。
存储类配置错误
症状:PVC 卡在"Pending"状态。
原因:存储类不可用、存储类不支持请求的访问模式、或存储不足。
解决方案:检查存储类(kubectl get storageclass),验证访问模式受支持,检查可用容量。
延伸阅读
- 系统架构概览 — 完整的平台架构
- 概览中的存储与数据管理 — 系统架构中的备份概览
- 网络和流量架构 — 流量如何到达服务
- 安全架构 — 数据在静态和传输中如何保护
- 主权云:SCS vs Proxmox + K3s — 包括存储的基础设施平台比较
数据是机构最有价值的资产。存储架构不仅仅是关于数据存放在哪里——它是关于确保数据在平台的整个生命周期中是持久的、可恢复的和可扩展的。