ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Docker双实例与Nginx平滑切换:Ubuntu下RagFlow不停机升级实践

Docker双实例与Nginx平滑切换:Ubuntu下RagFlow不停机升级实践 从“夜里升级翻车”到“白天也能安心切”Ubuntu下Docker双实例平滑升级RagFlow先说一个我踩过的坑某次给公司知识库升级RagFlow按官方最常规的流程操作——拉最新代码、改配置文件、docker compose up -d结果我这边命令刚执行完同事就在群里说知识库连不上了紧接着又有人反馈“正在解析的那份合同文档白搞了”。我当场意识到这种部署方式天生带有一个空窗期旧容器停止、新容器创建的间隙服务是断的而且RagFlow启动并不是秒级实际上要等好几分钟。后来我把升级方案改成“双实例加Nginx平滑切换”才真正做到了白天也能升级、用户无感知。这篇文章就是针对Ubuntu服务器上Docker部署的RagFlow完整记录我验证过的不停机升级方案。适合负责RagFlow运维、企业知识库/Agent应用部署的同学参考尤其是那些“知识库不能断、文档解析不能丢”的生产环境。1. 为什么RagFlow的常规升级一定会打断服务1.1 官方默认升级流程的“隐藏停机窗口”RagFlow官方文档里推荐的升级方式说白了就是两步先git pull拉最新代码再docker compose up -d让服务按新配置重建。听起来没什么问题但如果你深挖Docker Compose的执行逻辑就会发现up -d并不是“原地热替换”它的实际动作是检测到镜像或配置变化后先停止旧的容器再用新配置创建并启动一个新容器。这里的关键矛盾是端口RagFlow默认把宿主机的9380端口映射到容器内的HTTP服务上同一时间只能有一个进程监听这个端口所以新容器必须等旧容器完全停止并释放端口才有办法启动。这个“先停后起”的间隙就是停机窗口。窗口本身可能只有几秒但那只是“容器状态”层面的几秒。真正的不可用时间远比这个长因为Docker把容器标记为“Up”不意味着RagFlow应用已经就绪。我刚接手RagFlow部署的时候就因为只看了docker compose ps里状态变成 Up 就宣布升级完成结果前端一直转圈查了半天才发现应用内部还没初始化完。1.2 RagFlow应用启动链路比你想的慢得多RagFlow服务端启动不是一个简单的Python进程监听端口。它启动时要依次完成这些事连接MySQL并校验元数据、连接Redis、连接Elasticsearch并校验/初始化索引、连MinIO对象存储、加载配置的Embedding模型本地模型或在线模型最后才是HTTP服务开始监听。任何一个依赖出问题容器都会进入CrashLoopBackOff。我实测过一组数据在一台4C16G的Ubuntu服务器上RagFlow容器从创建到健康检查通过最快也要1分钟最慢的一次接近4分钟——那次是因为Embedding模型要从远端拉取网络抖动导致模型下载重试。也就是说官方默认升级方式的真实不可用时长不是命令执行的那几秒而是“建容器初始化依赖加载模型”的完整链路。对外提供的知识库一旦断这么久基本就是事故。1.3 端口独占让“原地重启”这条路走不通很多人会想那我不重建容器直接docker restart行不行不行。restart只是重启同一个容器的进程镜像和版本没变谈不上“升级”。想要换版本就必然要经历“旧容器退出-新容器绑定端口”的过程。除非你能接受换端口对外提供服务否则只要入口还指向9380就必须有一个瞬间是没人监听的。所以结论很清晰想做到“不停机”不是把重启动作变快而是要把版本切换动作从用户请求链路上摘除让请求始终有一个可用的后端在响应用户感知不到后端在换人。2. 不靠运气的不停机方案双实例加反向代理2.1 核心思路让新旧两个版本同时存在用网关切流量我的做法是在RagFlow前面加一层Nginx作为统一入口宿主机上同时跑两套RagFlow实例旧实例继续监听9380端口处理生产流量新实例监听9390端口处于“待命”状态。日常Nginx把请求全部转发给旧实例等新实例验证完毕我修改Nginx的upstream配置把流量平滑切到新实例。可能有人会问这不就是蓝绿部署吗对就是蓝绿部署只不过是在单机Docker Compose环境下的轻量实现。蓝绿部署看起来“土”但对RagFlow这种单体Web应用反而是最合适的。它不依赖Kubernetes的滚动更新能力也不需要额外搭建服务发现组件一台Ubuntu服务器加上Nginx就能完成。整个链路可以描述为用户请求 - Nginx(:80/443) - 旧实例 127.0.0.1:9380 - 新实例 127.0.0.1:9390Nginx的reload是平滑重载执行瞬间不中断现有TCP连接已经进入旧Worker的请求会在旧Worker上正常走完新请求则由新Worker按新配置处理。对RagFlow这种“网页HTTP API”的短连接形态来说平滑程度足够。2.2 数据层怎么处理共享还是隔离双实例方案里最核心的技术决策不是Nginx配置而是“新实例的数据层怎么接”。我在这上面纠结过很久实践下来可以给出一个决策表升级场景数据层策略理由小版本升级官方release notes未提及数据库结构变更共享现有MySQL/Redis/MinIO/ES成本最低数据实时一致切换后无数据同步问题大版本升级官方说明涉及数据库迁移或索引变更完全隔离新实例独立数据卷避免新版迁移逻辑污染生产数据切换失败可直接切回无法确认兼容性或生产环境数据极其重要优先隔离先导数据验证再切用资源换安全回滚容错率最高共享数据层的连接方式其实不复杂。RagFlow官方docker compose里的服务名是mysql、redis、minio、elasticsearch这些名字在Docker自定义网络里就是DNS主机名。要让新实例的server容器直接用这些名字连上生产依赖只需要把新实例的compose网络指向生产环境的默认网络网络名通常是项目目录名_default比如/opt/ragflow/docker这个目录启动的项目网络就叫ragflow_default。隔离数据层则是新起一套完整的compose用-p ragflow-green指定独立项目名这样数据卷、网络都和生产分开切换前做备份导入。缺点是要准备两套资源而且数据导入也需要时间我这里给一个真实的成本参考我迁移过约60GB的MinIO对象存储、几百MB的MySQL库大概用了20多分钟。这个时间窗口对于升级来说可以接受但不适合频繁切换。2.3 资源开销到底要多少单机跑两套RagFlow很多人第一反应是“服务器扛得住吗”。我梳理一下各组件的大致内存占用ragflow-server容器本身吃2-4GElasticsearch吃1-2GMySQL加Redis加MinIO加起来2G左右一套全栈下来大概6-8G。如果采用共享数据层多跑一个新server容器额外增加2-4G内存就够了如果采用隔离方案那就是两份全栈约12-16G起步。所以我的经验是16G内存的Ubuntu服务器是“共享数据层升级”的舒适线8G会有点紧张但也不是不能用只是新实例启动和文档解析并发时要注意观察。磁盘方面新实例的数据卷需要预留空间建议至少留出当前数据量1.5倍的空闲容量这样MinIO、ES快照备份和迁移都有地方放。坦诚说不停机升级不是免费的本质是用富余资源换取业务连续性。如果服务器内存只有8G我建议优先考虑隔离数据层方案里的“停旧起新”变体把停机时间压缩到分钟级而不是硬撑双实例导致整机OOM。3. 在Ubuntu上完整执行不停机升级的实操过程3.1 第零步升级前把备份做扎实无论采取共享还是隔离方案备份都是必经步骤。这不是求心安而是给回滚埋锚点。我在升级前固定执行三件事备份MySQL、备份关键数据卷、保存当前compose配置快照。MySQL备份用mysqldump注意不要影响线上写入加--single-transaction参数即可cd /opt/ragflow/docker docker compose exec -T mysql sh -c \ exec mysqldump -uroot -p$MYSQL_PASSWORD --single-transaction ragflow \ /backup/ragflow_mysql_$(date %F).sql数据卷备份需要先确认卷名。不同版本的RagFlow compose对数据卷的命名略有差异执行docker volume ls | grep ragflow看一下实际的卷名然后逐个打包docker run --rm -v ragflow_minio-data:/data -v /backup:/backup \ alpine tar czf /backup/minio-data_$(date %F).tgz -C /data . docker run --rm -v ragflow_esdata:/data -v /backup:/backup \ alpine tar czf /backup/esdata_$(date %F).tgz -C /data .MinIO里的原始文件和ES索引在备份时可能有少量写入这一点不用太纠结我们回滚真正依赖的是MySQL dump加MinIO文件ES索引如果真丢了最坏情况下重新解析一遍文档也能重建虽然代价不小但至少有退路。3.2 搭第二套实例共享数据层的最小化操作如果你决定采用共享数据层方案操作重点是把新版server独立拉起来同时避免和生产服务的端口、容器名冲突。先把官方docker目录复制一份cp -a /opt/ragflow/docker /opt/ragflow-green cd /opt/ragflow-green在生产目录里拉取新版本镜像。这里注意不要用latest这种浮动标签必须锁死版本号docker pull infiniflow/ragflow:v0.18.0以你实际要升级的release tag为准我这里只是举例。拉完镜像后编辑/opt/ragflow-green/docker-compose.yml做三处修改把ragflow-server的镜像tag改成目标版本比如v0.18.0把端口9380:80改为9390:80避免与生产冲突删除mysql、redis、minio、elasticsearch这些服务定义只保留ragflow-server因为我们共享生产的数据层。然后修改网络部分让新server加入生产网络services: ragflow-server: image: infiniflow/ragflow:v0.18.0 container_name: ragflow-server-green ports: - 9390:80 networks: - default networks: default: external: name: ragflow_default里面的ragflow_default要换成你生产项目实际的网络名不确定就执行docker network ls查。启动新实例docker compose -p ragflow-green up -d-p指定独立项目名即使你在/opt/ragflow-green目录下操作数据卷和容器名也会和生产的ragflow项目区分开。3.3 启动后等健康检查别看着Up就切新实例启动后不能马上切流量因为容器状态Up不代表RagFlow可用。正确做法是盯着日志看启动进度docker compose -p ragflow-green logs -f ragflow-server等日志里出现HTTP服务监听的标志通常是Running on或Uvicorn running一类再验证健康检查接口。RagFlow的健康检查路径是/v1/health如果拿不准直接看官方compose里healthcheck用的路径照抄过来curl -s http://127.0.0.1:9390/v1/health返回的内容里包含healthy类似字样才算通过。但通过健康检查只代表服务活着功能层面还需要进一步验证打开新实例的Web界面确认知识库列表、文档列表和你生产环境一致上传一个小文件触发解析跑一次对话测试。这一步本质是“用生产数据做冒烟测试”非常关键它能在切换前把大部分兼容性问题暴露出来。我个人习惯让新实例带压运行10到30分钟再切。这段时间里反复看docker compose -p ragflow-green logs有没有ES索引告警、Redis连接告警、MinIO权限告警。这些日志虽然不致命但往往是升级后被用户吐槽的隐患。3.4 Nginx上线与灰度切换Nginx这里我建议单独跑一个容器或者用宿主机Nginx关键是配置必须清晰。下面是我生产环境验证过的配置模板upstream ragflow_backend { server 127.0.0.1:9380 weight100; server 127.0.0.1:9390 weight0; } server { listen 80; server_name kb.example.com; client_max_body_size 200m; location / { proxy_pass http://ragflow_backend; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_buffering off; proxy_read_timeout 300s; } }有四个点值得展开说client_max_body_size默认只有1m知识库上传的PDF、Word文档随便就几十MB不调大必然上传失败proxy_buffering off是给RagFlow的流式对话响应准备的不关闭缓存的话用户看到的回答会一截一截地卡顿proxy_read_timeout要调长RAG场景下一步检索加生成往往要几十秒默认60秒很容易超时proxy_set_header里的Upgrade和Connection是给WebSocket能力预留的RagFlow有些交互需要用到。切换流程我分两步走。第一步先放一小部分流量到新实例试水upstream ragflow_backend { server 127.0.0.1:9380 weight99; server 127.0.0.1:9390 weight1; }保存配置后执行docker exec nginx nginx -s reloadNginx会平滑重载。在1%流量下观察新实例的日志和Nginx access log如果没有异常第二步再全量切换。全量切换时有个细节把旧实例标记为down而不是单纯把weight改成0这样已建立的连接会自然结束新连接全部走新实例upstream ragflow_backend { server 127.0.0.1:9380 down; server 127.0.0.1:9390; }再reload一次这时候对外入口已经完全在新版本上了。3.5 切换后不要急着“庆祝”先观察真实请求全量切换之后我一般会让流量跑5到10分钟再下结论。观察三个地方Nginx access log里9390的响应码有没有大量4xx/5xx新实例日志有没有异常堆栈MySQL和ES的慢查询/连接数有没有异常上升。如果这十分钟一切平稳升级才算完成一半接下来是回滚预案的保留和旧实例的善后。4. 验证、灰度与回滚最容易翻车的地方全在这4.1 别只看健康检查业务路径要逐个过切换后最容易犯的错就是“看到health接口正常就宣布成功”。RagFlow的业务链路长health只是门槛。我整理过一个验证清单每条基本就是点几下页面的事上传一个测试文档确认能触发解析并能在文档列表看到解析状态跑一次知识库对话确认能命中文档内容并流式返回答案打开包含图片/附件的文档记录确认MinIO的访问URL正常图片能预览如果你们用API对接外部系统调一次/v1/chats之类的接口确认鉴权和响应体格式没变化。特别是MinIO这条新实例的环境变量如果和生产不一致比如MINIO_ENDPOINT指向了错误的地址或者bucket权限配置有出入前端文档预览会出现图片裂开、附件无法下载的问题这种问题健康检查根本看不出来。4.2 回滚不是删旧重来而是切回去共享数据层方案最大的优势是回滚动作非常轻只要旧实例的容器和数据卷还在回滚就是改Nginx的upstream把流量从9390切回9380再reload一次。但这里有一个必须提前确认的前提新版RagFlow启动时会不会对MySQL schema做自动迁移。如果官方release notes明确说了数据库结构变更或者你启动新实例时发现日志里有DDL操作那回滚到旧版本就可能失败——旧代码连不上新schema报字段不存在或类型不匹配的错。这种情况就不能用共享数据层必须走隔离数据层方案在隔离环境里先把迁移脚本验证清楚再切。我在这上面吃过亏。有一次升级后续版本旧实例上还有定时任务在写数据新实例启动时迁移了表结构导致两边并发写同一张表最后表数据出了问题。后来恢复的方式是拿升级前的MySQL dump直接整库还原再手动切回旧实例这个过程中服务是真停了一段时间。所以请大家记住共享数据层的“优雅回滚”是有前提的前提不满足就不要硬上。4.3 灰度比例怎么定才合理有朋友问我Nginx按5%、10%这样渐进地切流量行不行。我的实际体验是对RagFlow这种应用“5%的流量试新版本”听起来稳妥实际意义没有想象中大因为5%的流量未必会打到你没验证过的功能路径上。用户可能5%的请求都集中在登录和聊天而你最担心的文档解析恰好没被覆盖到。所以我更推荐“先19%跑半小时再100%切换”的两段式而不是多次小比例切换。19%的流量足够触发一次真实的文档上传和对话请求再加上我在切换前已经用生产数据做过业务冒烟测试两个动作叠加覆盖度是够的。反复多次切换反而会增加状态不一致的风险尤其是在共享数据层下新旧两个server同时处理写请求对数据库的压力虽然不大但没必要去制造这种不必要的并发窗口。4.4 旧实例的清理时机与数据卷保留新实例稳定运行24小时之后可以清理旧容器了。我的建议是先停后删不要立刻删数据卷docker stop ragflow-server-old docker rm ragflow-server-old数据卷继续保留一周再决定是否释放。这一周里如果用户反馈“我上周传的文档打开有问题”“这个解析结果和之前不一样”你还来得及把旧实例重新拉起来对照。保留旧数据卷的成本只是磁盘占用相比数据丢失后的代价这点成本非常划算。最后分享一个实战细节这套流程跑顺之后我最大的体会是“不停机”不是靠某一条命令实现的而是靠流程设计。Nginx前置、双实例、灰度切换、回滚预案每一步都是提前设计好的升级执行反而变成了一件没有惊喜的例行操作。最后再分享一个我踩过的坑自动化脚本里千万不要把docker compose up -d和nginx -s reload写成同一行直接执行。中间必须留出健康检查的等待逻辑等/v1/health返回正常再执行reload。我之前图省事写过一个一键脚本结果新实例数据库初始化没完成Nginx已经切过去了用户那边白屏了好几分钟。从那以后我把升级拆成两段第一阶段“起新实例并验证”第二阶段“切流量”中间必须由人确认慢即是快。
返回列表