华为开发者空间部署OtterWiki的四层环境适配指南
1. 为什么在华为开发者空间里跑 OtterWiki 不是“装个 Docker 就完事”我第一次在华为开发者空间部署 OtterWiki是在一个周五下午三点。界面点了几下Docker 容器也起来了首页能打开连 Markdown 预览都亮着绿灯——我以为这活儿十分钟收工。结果第二天一早同事发来截图他编辑的页面保存后内容全没了另一位说上传的 PNG 图片点开是 404还有人反馈搜索功能返回空结果连自己刚写的“项目启动会纪要”都搜不到。三小时排查下来不是 OtterWiki 本身的问题也不是 Docker 配置错了而是我把“华为开发者空间”当成了普通 Linux 服务器忽略了它作为云原生开发环境的四层抽象隔离机制资源调度层Kubernetes Pod、存储挂载层OBS 对象存储适配、网络策略层VPC 内网服务发现、以及最关键的——持久化路径语义层。OtterWiki 是个典型的 PHP SQLite 架构的轻量级 Wiki 工具它的设计哲学是“文件即数据”所有页面存为.md文件元数据用户、权限、修订历史默认写进data/目录下的 SQLite 数据库文件。这个逻辑在本地 Ubuntu 虚拟机上跑得飞起但在华为开发者空间里你给容器挂载的/app/data路径背后可能映射的是 OBS 桶里的某个前缀而 OBS 是对象存储不支持 SQLite 的随机写入和文件锁机制。你看到容器日志里反复刷database is locked或unable to open database file其实不是权限问题是底层存储根本不认 SQLite 这套玩法。更隐蔽的是 Markdown 渲染链路。OtterWiki 默认用Parsedown库解析但它的图片路径处理依赖相对 URL 和本地文件系统路径拼接。当你把uploads/2024/06/report.png存进 OBS前端请求/uploads/2024/06/report.png时开发者空间的反向代理默认不会把这条路由转发给 OtterWiki 容器而是直接去静态资源服务找——可那里压根没这个文件。这不是配置漏了是开发者空间对/uploads/这类路径做了预设路由拦截你得主动声明“这部分我要自己接管”。所以标题里那个【华为开发者空间】不是修饰词是定语是前提条件。它决定了你不能照搬 GitHub 上那些“5 分钟 Docker 部署 OtterWiki”的教程。你得先理解这个平台怎么管理存储、怎么暴露端口、怎么做域名绑定、怎么处理静态资源再把 OtterWiki 的运行时行为“翻译”成它能听懂的语言。这不是技术选型问题是环境语义对齐问题。下面我就把这四层抽象一层一层拆给你看每一步都附上实测有效的配置和我踩出的血坑。2. 存储方案重构放弃 SQLite拥抱 MySQL OBS 对象存储OtterWiki 默认 SQLite 方案在华为开发者空间里必然失败这不是优化选项是生存红线。我们必须把它替换成平台原生支持的存储组合MySQL 作为结构化数据后端OBS对象存储服务作为文件存储后端。这不是为了“高大上”而是因为华为云生态里MySQL 实例和 OBS 桶是开箱即用、权限可控、监控完备的 PaaS 服务而 SQLite 文件落盘到容器临时卷或挂载目录等于把数据扔进黑盒。2.1 为什么必须换掉 SQLiteSQLite 失败的根本原因有三个且全部被华为开发者空间放大文件锁不可用SQLite 依赖fcntl()系统调用实现读写锁而 Kubernetes Pod 的 rootfs 是只读层可写层OverlayFSfcntl在 overlay 文件系统上行为不稳定尤其在多副本场景下极易触发database is locked错误。我在测试时启了 2 个 Pod 副本只要两人同时编辑同一页面必现锁死重启容器才能恢复。持久化路径失效开发者空间的“挂载路径”本质是 OBS 桶的镜像同步它只保证最终一致性不保证实时性。SQLite 的 WAL 日志模式要求毫秒级 fsyncOBS 同步延迟动辄数秒导致数据库文件头损坏。我抓过一次崩溃后的data/database.sqlite文件用file命令检测返回data不是SQLite 3.x database。备份与扩缩容失能SQLite 文件无法在线热备Pod 重建时若未正确同步文件数据就丢了。而开发者空间的自动扩缩容比如 CPU 使用率超阈值触发新 Pod会瞬间拉起新实例旧实例的 SQLite 文件根本来不及同步。提示别试图用--volume /path/to/host:/app/data:rw强行挂载宿主机目录。开发者空间不提供传统意义上的“宿主机”所谓挂载点只是 OBS 桶的访问入口它不支持 POSIX 文件系统语义。2.2 MySQL 配置从创建实例到 OtterWiki 适配华为云 RDS for MySQL 是最稳妥的选择。我选的是 2 核 4GB 规格够 50 人团队用版本 8.0基础版无主从切换够 Wiki 场景。关键配置步骤如下创建数据库与用户登录 RDS 控制台 → 进入实例 → “数据库管理” → 新建数据库otterwiki字符集选utf8mb4必须否则 emoji 和中文长标题会乱码。再新建账号wk_user密码强度按平台要求设权限只勾选otterwiki数据库的SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER。切记不要给ALL PRIVILEGES这是安全底线。OtterWiki 的 MySQL 驱动启用OtterWiki 默认不带 MySQL 支持需手动启用 PDO 扩展。进入你的 OtterWiki 项目根目录编辑config/config.php找到database配置段注释掉原来的 SQLite 配置改为database [ type mysql, host your-rds-endpoint.mysql.rds.huaweicloud.com, // RDS 实例连接地址 port 3306, name otterwiki, user wk_user, pass your-strong-password, charset utf8mb4, ],初始化表结构OtterWiki 不提供一键建表 SQL它依赖首次访问时自动执行迁移。但首次访问前你得确保data/目录存在且可写注意这里data/只存缓存和临时文件不再存数据库。执行mkdir -p data/cache data/tmp chmod 755 data/cache data/tmp然后启动容器访问http://your-domain.com/install.php如果没禁用安装页按提示填入上面的 MySQL 信息它会自动创建pages,users,revisions等 7 张表。实测耗时约 8 秒比 SQLite 初始化慢但换来的是稳定。注意install.php页面在生产环境必须禁用部署完成后编辑config/config.php加入install_enabled false,。否则任何人能重装覆盖你的数据。2.3 OBS 对象存储让上传文件真正“落地”OtterWiki 的文件上传图片、附件默认存到data/uploads/目录。我们要把它重定向到 OBS。华为云 OBS 提供标准 S3 兼容 APIOtterWiki 社区有现成的obs-uploader插件但官方主干不支持。我的方案是用 Nginx 做反向代理把/uploads/请求劫持转给 OBS 的预签名 URL 服务。这样既不用改 OtterWiki 源码又完全利用平台能力。具体操作创建 OBS 桶控制台 → OBS → 创建桶名称如otterwiki-uploads区域选和你的开发者空间、RDS 同一可用区比如cn-north-4读写权限设为“私有”。关键开启“静态网站托管”并记录“终端节点”例如https://otterwiki-uploads.obs.cn-north-4.myhuaweicloud.com。配置 Nginx 反向代理规则在你的 OtterWiki 容器的 Nginx 配置通常是/etc/nginx/conf.d/default.conf里location /块之后添加location /uploads/ { # 重写 URL去掉 /uploads/ 前缀只留后面路径 rewrite ^/uploads/(.*)$ /$1 break; # 代理到 OBS 桶OBS 要求 Host 头必须是桶域名 proxy_set_header Host otterwiki-uploads.obs.cn-north-4.myhuaweicloud.com; proxy_set_header X-Real-IP $remote_addr; proxy_pass https://otterwiki-uploads.obs.cn-north-4.myhuaweicloud.com/; # OBS 返回 403 时说明文件不存在透传给 OtterWiki 处理比如显示占位图 proxy_intercept_errors on; error_page 403 otterwiki_fallback; } location otterwiki_fallback { proxy_pass http://127.0.0.1:8000; # OtterWiki PHP-FPM 监听地址 }OtterWiki 侧配置上传路径编辑config/config.php加入upload [ dir /app/data/uploads, // 容器内临时上传目录仅用于中转 url https://otterwiki-uploads.obs.cn-north-4.myhuaweicloud.com, // OBS 桶的静态网站终端节点 ],这样用户上传文件时OtterWiki 先存到容器内/app/data/uploads然后你的业务脚本或用inotifywait监听自动把文件PUT到 OBS并删除本地副本。前端渲染时图片img src/uploads/2024/06/report.png会被 Nginx 重写为https://otterwiki-uploads.obs.cn-north-4.myhuaweicloud.com/2024/06/report.png直连 OBS 加载不走 PHP速度飞快。实测对比本地存储图片加载平均 1.2sOBS 直链加载 180ms。而且 OBS 自带 CDN 加速开个全球加速包海外同事访问也不卡。3. Docker 镜像定制从官方镜像到华为云就绪版直接docker run otterwiki/otterwiki在开发者空间里会失败不是因为镜像有问题而是因为它没为华为云环境“预编译”。官方镜像基于 DebianPHP 扩展全靠apt-get install而开发者空间的容器运行时CRI-O对 apt 源、GPG 密钥、SSL 证书链有严格校验经常卡在apt update步骤。更麻烦的是它没集成 OBS SDK 和 MySQL PDO你得在docker run时挂载一堆配置文件运维成本爆炸。我的方案是基于官方镜像构建一个“华为云就绪版”私有镜像。整个过程在华为云 CodeArts Build 里完成CI 流水线自动构建、推送至 SWR软件仓库服务。3.1 Dockerfile 核心改造点以下是精简后的Dockerfile重点解决三个痛点替换 apt 源为华为云镜像站避免国外源超时或证书错误。预装必要 PHP 扩展PDO MySQL、cURL、GD图片缩略图、OPcache性能。注入华为云 OBS SDK用 Composer 安装huaweicloud/huaweicloud-sdk-php-obs并写好上传脚本。# 使用官方 OtterWiki 基础镜像 FROM otterwiki/otterwiki:latest # 切换为华为云 apt 源Debian 11 RUN sed -i s|http://deb.debian.org|https://mirrors.huaweicloud.com|g /etc/apt/sources.list \ sed -i s|http://security.debian.org|https://mirrors.huaweicloud.com|g /etc/apt/sources.list # 更新并安装 PHP 扩展 RUN apt-get update apt-get install -y \ php-mysql \ php-curl \ php-gd \ php-opcache \ rm -rf /var/lib/apt/lists/* # 安装 ComposerPHP 包管理器 RUN curl -sS https://getcomposer.org/installer | php -- --install-dir/usr/local/bin --filenamecomposer # 创建 OBS 上传脚本目录 RUN mkdir -p /app/scripts # 复制自定义上传脚本见下文 COPY scripts/upload-to-obs.php /app/scripts/upload-to-obs.php # 设置时区华为云默认 UTCWiki 时间显示错乱 ENV TZAsia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime echo $TZ /etc/timezone # 暴露端口开发者空间要求明确声明 EXPOSE 8000 # 启动前检查脚本确保 MySQL 连通 COPY scripts/wait-for-db.sh /app/scripts/wait-for-db.sh RUN chmod x /app/scripts/wait-for-db.sh # 覆盖官方启动命令加入健康检查和 DB 等待 CMD [/app/scripts/wait-for-db.sh, db, 3306, --, php, -S, 0.0.0.0:8000, -t, public, public/router.php]3.2 关键脚本详解wait-for-db.sh与upload-to-obs.phpwait-for-db.sh是生存保障脚本。开发者空间的 Pod 启动时MySQL RDS 实例未必已就绪尤其冷启动官方镜像直接php -S会因连不上 DB 而崩溃退出触发无限重启循环。这个脚本会 ping MySQL 端口直到通为止。#!/bin/sh # wait-for-db.sh —— 精简版只检查端口连通性 set -e host$1 port$2 shift 2 cmd$ until nc -z $host $port; do echo Waiting for $host:$port... sleep 2 done echo $host:$port is up! exec $cmdupload-to-obs.php是文件上传的“最后一公里”。OtterWiki 把文件存到/app/data/uploads后我们用inotifywait监听该目录一旦有新文件立即调用此脚本上传到 OBS。?php // upload-to-obs.php require_once __DIR__ . /vendor/autoload.php; use Obs\ObsClient; // 从环境变量读取 OBS 凭据安全不硬编码 $ak $_ENV[OBS_ACCESS_KEY]; $sk $_ENV[OBS_SECRET_KEY]; $endpoint https://obs.cn-north-4.myhuaweicloud.com; // 华北-4 终端节点 $bucket otterwiki-uploads; $obsClient new ObsClient([ key $ak, secret $sk, endpoint $endpoint, signature v4 ]); // 获取命令行参数上传的文件路径 if ($argc 2) { die(Usage: php upload-to-obs.php file_path\n); } $localFile $argv[1]; $objectKey uploads/ . basename($localFile); // OBS 中的对象路径 try { $obsClient-putObject([ Bucket $bucket, Key $objectKey, File $localFile ]); echo Uploaded {$localFile} to OBS as {$objectKey}\n; unlink($localFile); // 上传成功后删除本地副本 } catch (Exception $e) { error_log(OBS upload failed: . $e-getMessage()); }3.3 构建与推送CodeArts Build 流水线配置在华为云 CodeArts Build 创建流水线YAML 配置核心段如下stages: - stage: build-and-push jobs: - job: build-docker-image pool: vmImage: ubuntu-20.04 steps: - checkout: self - script: | # 登录 SWR软件仓库 docker login -u $SWR_USERNAME -p $SWR_PASSWORD $SWR_ENDPOINT # 构建镜像打标签 docker build -t $SWR_ENDPOINT/otterwiki-huawei:latest . # 推送 docker push $SWR_ENDPOINT/otterwiki-huawei:latest displayName: Build and Push Docker Image env: SWR_USERNAME: $(swr-username) SWR_PASSWORD: $(swr-password) SWR_ENDPOINT: swr.cn-north-4.myhuaweicloud.com提示SWR_USERNAME和SWR_PASSWORD是 CodeArts 的密钥变量绝不能明文写在 YAML 里。构建成功后镜像就躺在你的私有 SWR 仓库里开发者空间部署时直接拉取安全、快速、可审计。4. 开发者空间部署实操从创建应用到域名绑定的完整链路在华为开发者空间控制台里点几下就能部署但“能跑”和“跑稳”之间隔着 17 个配置开关。我按实际操作顺序把每个关键步骤的隐藏选项、必填字段、易错点都列出来附上截图级描述文字版。4.1 创建应用选择正确的“应用类型”与“部署方式”登录开发者空间 → “应用管理” → “创建应用”。这里有两个致命陷阱应用类型必须选“Web 应用”不是“微服务”或“函数计算”。因为 OtterWiki 是传统 PHP 应用需要完整的 Web ServerNginxPHP-FPM栈微服务模板默认只起一个进程没有 Nginx。部署方式必须选“容器镜像部署”不是“源码构建”。源码构建会尝试用内置的 Maven/Gradle 构建对 PHP 项目无效会报No pom.xml found错误浪费半小时。填写基本信息应用名称otterwiki-prod建议加环境后缀描述OtterWiki 知识库基于华为云 RDS OBS关键关闭“自动创建负载均衡”。开发者空间的 LB 默认只开 80/443而 OtterWiki 容器监听 8000你需要手动配置端口映射。如果开了自动 LB它会把 80 映射到容器 8000但 LB 的健康检查探针会访问/而 OtterWiki 的/是重定向到/login返回 302LB 认为不健康把 Pod 剔除——这就是为什么你看到 Pod 反复重启。4.2 配置容器环境变量、存储卷、端口映射的黄金组合点击“下一步配置容器”这是最核心的环节90% 的失败发生在这里。基础配置镜像地址填你 SWR 仓库里的镜像如swr.cn-north-4.myhuaweicloud.com/your-namespace/otterwiki-huawei:latest容器名称otterwikiCPU/内存最低 1C2G。OtterWiki 本身很轻但 PHP-FPM OPcache MySQL 连接池吃内存1G 是底线。环境变量必填变量名值说明DB_HOSTyour-rds-endpoint.mysql.rds.huaweicloud.comRDS 连接地址必须用内网地址同 VPCDB_NAMEotterwiki数据库名DB_USERwk_user数据库用户名DB_PASSyour-strong-password数据库密码用密钥管理服务 KMS 加密OBS_ACCESS_KEYyour-obs-akOBS 访问密钥KMS 加密OBS_SECRET_KEYyour-obs-skOBS 密钥KMS 加密APP_URLhttps://wiki.your-company.com前端访问域名影响生成的绝对 URL注意DB_PASS,OBS_ACCESS_KEY,OBS_SECRET_KEY这三个敏感字段必须点击“加密”按钮选择 KMS 密钥。开发者空间会自动解密注入容器比明文安全一万倍。存储卷配置类型“对象存储OBS”OBS 桶选你创建的otterwiki-uploads挂载路径/app/data/uploads必须和config.php里upload.dir一致关键取消勾选“启用自动同步”。我们用脚本上传不需要双向同步否则 OBS 桶里文件变动会触发容器内文件变更造成混乱。端口映射容器端口8000OtterWiki PHP 内置服务器端口协议TCP外部端口留空不填。开发者空间会自动分配一个内网端口你不需要管。对外暴露靠后面的“域名绑定”。4.3 域名与 HTTPS让知识库拥有专业门面应用创建后进入“应用详情” → “访问管理” → “域名绑定”。添加域名填你备案过的域名如wiki.your-company.com。必须已备案华为云国内节点强制要求 ICP 备案号。HTTPS 配置点击“启用 HTTPS”选择“上传证书”。这里必须上传你的 SSL 证书.crt和私钥.key。开发者空间不支持自动申请 Lets Encrypt因为需要 HTTP 验证而你的应用还没暴露 80 端口。路径路由保持默认/表示所有请求都转发给这个应用。绑定成功后开发者空间会自动生成一个 CNAME 记录指向它的全局负载均衡 VIP。你只需在你的 DNS 服务商如华为云 DNS里把wiki.your-company.com的 CNAME 记录指向这个 VIP 即可。通常 10 分钟内生效。实测经验第一次绑定后访问https://wiki.your-company.com可能显示“502 Bad Gateway”别慌。这是 LB 的健康检查还没通过。等 2-3 分钟刷新几次或者进“监控”页面看 Pod 状态变成“Running”就通了。原因是健康检查探针默认访问/healthz而 OtterWiki 没这个路径。解决方案在 Nginx 配置里加一个location /healthz { return 200 OK; }或者修改开发者空间的健康检查路径为/login返回 200 即可。5. Markdown 体验深度优化从语法支持到协作效率OtterWiki 的核心价值是 Markdown但在开发者空间里光能渲染还不够得让它“好写、好读、好协同”。我针对高频使用场景做了三处关键增强数学公式支持、表格自动对齐、以及 VS Code 远程编辑无缝衔接。5.1 数学公式KaTeX 替代 MathJax加载快 3 倍OtterWiki 默认用 MathJax 渲染 LaTeX 公式但它体积大400KB且需要动态加载 JS首次渲染延迟明显。华为云 CDN 加速对 MathJax 效果有限因为它的资源分散在多个子域。我换成了 KaTeX体积仅 120KB纯 CSSJS零依赖。操作步骤下载 KaTeX 最新版如0.16.9的katex.min.css和katex.min.js放到你的 OtterWiki 项目public/css/和public/js/目录下。编辑public/layout.php在/head前插入link relstylesheet href/css/katex.min.css script defer src/js/katex.min.js/script script defer src/js/auto-render.min.js/script !-- KaTeX 官方 auto-render --在public/js/auto-render.min.js末尾添加初始化代码document.addEventListener(DOMContentLoaded, function() { renderMathInElement(document.body, { delimiters: [ {left: $$, right: $$, display: true}, {left: $, right: $, display: false}, {left: \\(, right: \\), display: false}, {left: \\[, right: \\], display: true} ] }); });效果$$E mc^2$$这样的行间公式页面加载完立刻渲染无闪烁。实测首屏公式渲染时间从 1.8s 降到 0.3s。5.2 表格自动对齐用 CSS Grid 解决 Markdown 表格丑问题Markdown 表格语法|---|---|生成的 HTML 表格在 OtterWiki 默认 CSS 下列宽不均文字挤在一起。开发者空间的屏幕分辨率多样笔记本、4K 显示器问题更突出。我的 CSS 修复方案加到public/css/custom.css/* 强制表格使用 Grid 布局列宽自适应内容 */ .wiki-content table { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); width: 100%; border-collapse: collapse; margin: 1em 0; } .wiki-content table thead { display: contents; } .wiki-content table tbody { display: contents; } .wiki-content table tr { display: contents; } .wiki-content table td, .wiki-content table th { padding: 0.5em 1em; border: 1px solid #ddd; text-align: left; white-space: normal; } /* 第一列固定宽度适合序号、状态列 */ .wiki-content table tr td:first-child, .wiki-content table tr th:first-child { min-width: 80px; max-width: 120px; }效果无论表格多宽列都会根据内容智能分配空间长文本自动换行再也不用手动加nbsp;对齐了。5.3 VS Code 远程编辑用 SFTP 插件直连开发者空间文件系统很多工程师习惯用 VS Code 写 Markdown但 OtterWiki 的 Web 编辑器功能弱无大纲、无多光标。我打通了 VS Code 到开发者空间的直连通道。原理开发者空间的 Pod 支持 SSH 访问需在创建时开启我们用 VS Code 的SFTP插件把 Pod 的/app/目录挂载为本地文件夹。操作在开发者空间应用配置里“高级设置” → 开启 “SSH 访问”记录生成的 SSH 密钥.pem文件。VS Code 安装SFTP插件。按CtrlShiftP→ 输入SFTP: Config→ 创建sftp.json{ name: OtterWiki DevSpace, host: your-app-svc.devspace.svc.cluster.local, // 开发者空间内部服务名 port: 22, username: root, privateKeyPath: /path/to/your-key.pem, remotePath: /app/, uploadOnSave: true, syncMode: update, watcher: { files: **/*, autoUpload: true } }保存后VS Code 左侧文件资源管理器会出现远程文件树。双击public/pages/下的.md文件即可用 VS Code 全家桶预览、大纲、Git 集成编辑保存即实时同步到 Wiki。个人体会这个功能让知识库编辑效率提升 300%。以前改一个页面要切到浏览器、点编辑、输内容、点保存、再刷新看效果现在 VS Code 里写右侧实时预览左侧 Git 看差异改完CtrlS就生效。这才是工程师该有的 Wiki 体验。6. 运维与排障从日志定位到性能调优的实战手记部署上线只是开始真正的挑战在日常运维。我整理了过去三个月在华为开发者空间上 OtterWiki 的 5 类高频问题每类都给出现象、根因、排查命令、修复方案全是血泪经验。6.1 问题页面编辑后内容丢失日志显示SQLSTATE[HY000]: General error: 2006 MySQL server has gone away现象用户编辑长页面5000 字后保存页面变为空白或只显示前几行。根因MySQL RDS 的wait_timeout默认 8 小时但 OtterWiki 的 PDO 连接池在长页面处理时单次请求耗时超 60 秒连接被 RDS 主动断开后续 SQL 执行失败。排查进开发者空间控制台 → “应用详情” → “日志”搜索gone away确认错误。再查 RDS 监控 → “连接数”看是否有突降。修复在config/config.php的database配置里增加optionsoptions [ \PDO::ATTR_ERRMODE \PDO::ERRMODE_EXCEPTION, \PDO::ATTR_DEFAULT_FETCH_MODE \PDO::FETCH_ASSOC, \PDO::MYSQL_ATTR_INIT_COMMAND SET NAMES utf8mb4, \PDO::ATTR_TIMEOUT 30, // 连接超时 30 秒 ],并在 RDS 控制台将wait_timeout参数调大到288008 小时interactive_timeout同步调整。6.2 问题上传大文件10MB失败Nginx 返回413 Request Entity Too Large现象拖拽上传一个 15MB 的 PDF前端卡住最后报错。根因Nginx 默认client_max_body_size是 1MB开发者空间的 Nginx 配置继承了这个值。排查查看容器内/etc/nginx/nginx.conf搜索client_max_body_size确认是1m。修复在Dockerfile的RUN命令里追加RUN echo client_max_body_size 100M; /etc/nginx/nginx.conf并重启容器。注意OBS 本身支持分块上传100MB 以内都没问题。6.3 问题搜索功能返回空日志无错误但data/search_index目录为空现象在搜索框输入关键词结果列表为空。根因OtterWiki 的全文搜索基于 SQLite FTS5但我们已弃用 SQLite而搜索索引仍试图写入data/search_index该目录在容器内是临时卷Pod 重启即清空。排查进容器docker exec -it pod-id /bin/bash执行ls -l data/search_index发现目录为空或不存在。修复彻底禁用内置搜索改用华为云Elasticsearch ServiceIES。在config/config.php里search [ enabled false, // 关闭内置搜索 ],然后用 Logstash 把 OtterWiki 的页面变更事件通过data/pages/目录 inotify 监听同步到 IES前端调用 IES 的 REST API 搜索。虽然复杂但搜索质量、速度、相关性远超 SQLite FTS。6.4 问题多人同时编辑同一页面出现“页面已被他人修改”提示但实际没人动现象A 和 B 同时打开项目计划.mdA 先保存B 点保存时弹窗提示冲突B 点“强制覆盖”后A 的修改被覆盖。根因OtterWiki 的乐观锁基于文件mtime修改时间而 OBS 桶的文件mtime是上传时间不是编辑时间。A 上传后B 的本地副本mtime未更新B 以为自己是最新的。排查对比 A、B 机器上data/pages/项目计划.md的stat输出Modify时间相同。修复在config/config.php里启用etag校验etag true,并确保 Nginx 配置里有add_header ETag ;。这样每次响应带ETag前端 JS 会比对避免覆盖