在自托管环境中,OnlyOffice DocumentServer 往往只是一个“应用执行层”:真正需要长期保存的文档通常位于外部云盘或文件服务中,而 DocumentServer 负责文档解析、协同编辑和格式转换。

这类应用看似可以直接“拉取新镜像然后重建”,但实际升级中仍有几个很容易被忽略的问题:JWT 密钥是否稳定、容器内部数据库如何迁移、哪些目录需要持久化,以及为什么一次普通的容器重建可能突然造成明显的磁盘 I/O 压力。

下面以一次实际的 Docker 升级过程为例,整理这些问题背后的原理。

一、先区分“应用数据”和“用户数据”

OnlyOffice DocumentServer 本身并不一定保存最终用户文档。

典型部署结构是:

云盘 / 文件服务
        │
        │ 文档内容
        ▼
OnlyOffice DocumentServer
        │
        ├─ 文档转换
        ├─ 在线编辑
        ├─ 临时缓存
        ├─ 字体与转换资源
        └─ 内部数据库/消息组件

真正重要的数据通常仍由云盘系统负责保存。

因此,对 DocumentServer 的升级策略不一定需要达到数据库服务器那种“任何字节都不能丢”的灾难恢复级别。

真正需要重点保护的是:

  • Docker Compose 配置;
  • 环境变量;
  • JWT 密钥;
  • 必要的持久化目录;
  • 与外部应用之间的认证关系。

这也是判断备份范围时非常重要的原则:

不要因为应用内部存在数据库,就自动把它当作业务数据数据库。


二、JWT 密钥为什么应该固定

OnlyOffice 与外部云盘集成时,常使用 JWT 对请求进行认证。

一个常见问题是:

JWT secret 只存在于容器内部配置
        ↓
删除并重新创建容器
        ↓
新容器重新生成 secret
        ↓
云盘仍保存旧 secret
        ↓
集成失效

这种部署在日常运行时可能完全正常,因为普通的:

restart

不会删除容器 writable layer。

但只要执行:

recreate

问题就暴露出来了。

更合理的做法,是把 JWT 参数放入宿主机持久化配置,例如:

JWT enabled
JWT secret
JWT header

再由 Compose 注入容器。

这样就形成:

宿主机配置
   │
   └── 固定 JWT secret
             │
             ▼
       新旧容器保持一致

以后即使:

  • 升级镜像;
  • 删除容器;
  • 重建 Compose project;
  • 迁移到新服务器;

JWT 都不会因为容器生命周期变化而改变。

对于应用容器而言,这种配置持久化往往比备份整个 writable layer 更有价值。


三、restart 和 recreate 是两件完全不同的事情

很多 Docker 问题都来自这一点。

restart

执行 restart 时:

Container
 ├─ Image layer
 └─ Writable layer

两个部分都还存在。

容器以前初始化生成的:

  • 缓存;
  • 字体索引;
  • 临时配置;
  • 运行时生成文件;

往往仍然存在。

因此重新启动可能非常快。

recreate

而 Compose recreate 的过程更接近:

旧 container 删除
        ↓
旧 writable layer 消失
        ↓
从 image 创建新 container
        ↓
重新执行初始化

即使镜像版本完全没有变化,也可能重新触发:

  • 字体扫描;
  • 字体索引生成;
  • 数据目录初始化;
  • 内部数据库检查;
  • RabbitMQ 初始化;
  • Web 服务生成配置;
  • 各类缓存重建。

这意味着:

“镜像没变”并不等于“启动过程没变”。


四、为什么一次重建可能造成非常高的 I/O

在一次实际重建过程中,可以观察到字体生成程序长时间运行,同时整台服务器出现明显的 I/O wait。

这类情况有一个很容易误判的地方。

看到:

font generator

处于不可中断睡眠状态,并不能直接证明它就是唯一的 I/O 制造者。

更准确的解释是:

多个服务初始化
      +
大量小文件访问
      +
数据库写入
      +
缓存重建
      +
单块底层存储
      ↓
随机 I/O 队列积压
      ↓
大量进程进入 D state

此时经常会出现一种现象:

磁盘利用率接近饱和
但实际吞吐只有几 MB/s

这并不矛盾。

磁盘性能不仅取决于 MB/s,还取决于 IOPS 和延迟。

大量:

  • 小文件读取;
  • metadata 更新;
  • journal 写入;
  • 数据库随机访问;

即使吞吐量很低,也能把机械硬盘或较慢存储设备完全打满。

因此判断磁盘压力时,不能只看:

MB/s

还应该看:

await
queue depth
utilization
iowait
blocked processes

五、持久化 PostgreSQL 时要注意跨版本残留

OnlyOffice 的一体化镜像内部可能自带 PostgreSQL。

如果数据库目录被 bind mount 持久化,升级后可能看到类似:

old-major/
new-major/

两个数据库集群目录同时存在。

这时不能简单认为:

旧目录存在 = 仍然被使用

正确方法是检查当前 PostgreSQL cluster 状态。

如果当前只有新 major cluster:

online

而旧 cluster 已不再注册、不再运行,那么旧目录通常只是升级遗留。

这种清理应遵循:

确认当前运行 cluster
        ↓
确认旧目录未使用
        ↓
再删除旧目录

而不是直接根据目录名称猜测。


六、升级验证不应该只看“容器是 Up”

Docker 显示:

Up

只能证明 PID 还活着。

完整验证至少应该包括几个层次。

第一层:容器状态

检查:

running
restart count
OOM killed
image

第二层:应用内部服务

DocumentServer 一体化容器中通常还有:

Web server
Document service
Converter
Database
Message queue
Supervisor

必须确认核心服务实际处于运行状态。

第三层:健康接口

应该分别验证:

localhost health endpoint
reverse proxy public endpoint

这样可以同时确认:

应用
Docker publish
宿主网络
反向代理
TLS

整个链路。

第四层:认证

JWT 不应该只检查“变量存在”。

更可靠的方法是:

宿主机 secret
       │
       ├── hash
       │
容器 secret
       │
       └── hash
       
只比较是否相同

既能确认一致,又不会把 secret 打到终端或日志中。

第五层:真实业务测试

最终仍然需要真实应用验证:

打开文档
→ 编辑
→ 保存
→ 关闭
→ 再次打开

只有这一层成功,才能证明整个:

云盘
↔ JWT
↔ DocumentServer
↔ 保存回写

链路真正正常。


七、旧镜像应该精确删除,而不是 prune

升级成功以后,经常需要释放旧镜像占用空间。

更安全的方式是:

确定旧 image ID
        ↓
确认没有 container 引用
        ↓
删除该 image

而不是:

docker image prune
docker system prune

在运行大量自托管应用的服务器上,后两者可能清理掉本来希望保留的:

  • 停止容器;
  • 未运行但准备回退的镜像;
  • 临时 network;
  • 构建缓存。

精确删除始终比全局清理更适合作为生产服务器维护方式。


八、备份也需要控制范围

升级前进行备份当然是好习惯,但备份并非越多越好。

对于这类可重新部署的应用,真正高价值的备份通常只是:

docker-compose.yml
.env
关键认证配置

而不是每次升级都打包整个应用目录。

尤其当:

  • 用户文档并不存储在该应用;
  • 镜像可以重新下载;
  • 内部缓存可以重建;
  • 数据目录已经单独持久化;

那么创建数 GB 的全量归档可能只是增加:

  • I/O;
  • 操作时间;
  • 清理工作;
  • 判断复杂度。

好的备份策略应该围绕:

“哪些东西无法轻易重新生成?”

而不是:

“哪些东西能复制就全部复制。”


九、这次升级最重要的几个经验

OnlyOffice 这类 Docker 应用升级,可以归纳成一个相对简单的流程:

固定关键 secret
        ↓
备份少量核心配置
        ↓
拉取目标镜像
        ↓
正常关闭旧应用
        ↓
Compose recreate
        ↓
等待首次初始化完成
        ↓
验证内部服务
        ↓
验证 health endpoint
        ↓
验证 JWT
        ↓
进行真实文档测试
        ↓
精确清理旧镜像和旧数据残留

其中最值得注意的不是某个具体版本,而是几个长期有效的原则:

容器 restart 与 recreate 不等价。

真正重要的是外部持久化配置,而不是容器 writable layer。

低吞吐并不意味着磁盘没有饱和。

认证 secret 应该脱离容器生命周期。

生产服务器应优先精确清理,而不是全局 prune。

只要把这些边界处理清楚,OnlyOffice 这样的应用即使跨较大版本升级,也可以保持部署结构简单、可维护,而且不需要把升级过程做成一次复杂的数据迁移工程。

Leave a Reply

Your email address will not be published. Required fields are marked *