尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Yuxi 显式存储域与 Kubernetes PVC 收敛:从广域共享挂载到双 PVC 契约的部署演进实录

Yuxi 显式存储域与 Kubernetes PVC 收敛:从广域共享挂载到双 PVC 契约的部署演进实录 Yuxi 显式存储域与 Kubernetes PVC 收敛从广域共享挂载到双 PVC 契约的部署演进实录【免费下载链接】Yuxi可私有部署的多租户知识智能体平台统一 RAG、知识图谱、多智能体、MCP/Skills、沙盒与权限管理。Self-hosted knowledge agent platform for RAG, knowledge graphs and multi-agent workflows.项目地址: https://gitcode.com/GitHub_Trending/yu/Yuxi本指南以 Yuxi 仓库中归档决策记录 2026-08-19-explicit-storage-domains-and-kubernetes-pvc.md 为主体完整还原该决策的动机、挂载契约、停机迁移协议与验证证据并结合 docker-compose.yml、docker/sandbox_provisioner/app.py 与 scripts/migrate-storage.sh 等仓库源码进行纵深印证。阅读本文后你将理解每个服务只挂载它明确拥有的存储域这一部署原则在 Yuxi 中的落地方式掌握 storage-migrator 停机迁移的正确操作顺序以及 Docker 与 Kubernetes 两套 provisioner 后端在存储边界上的具体实现差异。背景为什么需要显式存储域在引入显式存储域契约之前Yuxi 的部署边界存在两类问题广域共享挂载扩大了服务能力API、worker 与 provisioner 三个服务曾共同挂载/app/savesprovisioner 还需要从这个广域挂载反推宿主机路径。挂载本身就让每个服务获得了未声明的文件能力路径推导逻辑则把部署细节耦合进了业务代码。Kubernetes 端 PVC 语义混杂历史 thread PVC 同时承载 Project、User Data 与 Skill 投影三类数据。即使实时 Workdir 语义在运行时已经成立部署边界依然保留旧 thread/Skill 配置面且无法按数据类别声明存储能力。该记录给出的核心判断是即便业务语义正确部署边界上的隐式能力也必须被显式化。决策的落点是Compose 拥有 shipping 服务的显式存储挂载与迁移 gate动态 Sandbox 的 Docker host root 与 Kubernetes PVC/subPath 由 provisioner 拥有——即部署边界与运行时边界各有唯一 Owner。需要说明的是该决策已被 2026-08-19-workdir-in-user-workspace.md 取代并归档仅保留为部署设计演进的历史背景。下文在陈述当前仓库实际状态时均以后者与仓库代码为准在陈述该决策当时的设计时则忠实还原本归档记录。决策内容三个层面的存储收敛shipping 服务的显式挂载决策要求 shipping 服务API、worker、provisioner不再挂载/app/saves改为按服务职责声明明确的存储域API为用户的 Workspace UI 挂载显式User Data加上Skill source/projectionworker为 Agent 上下文读取挂载显式User Data加上Skill source/projectionprovisioner只挂载Project、User Data 与 Skill projection三个明确目录。这一契约在当前 docker-compose.yml 中可以看到完整落地APIL49-L63与 workerL111-L127均挂载threads - /app/user-data、skill-sources - /app/skill-sources、skill-projections - /app/skill-projections三组绑定而 provisionerL206-L215只挂载threads - /app/user-data与skill-projections - /app/skill-projections两组且没有/app/saves目标。仓库中的配置契约测试 test_docker_compose_service_boundaries.py 直接以FORBIDDEN_API_WORKER_TARGETS frozenset({/app/checkpoints, /app/models, /app/saves, /var/run/docker.sock})断言这些目标不得出现将显式存储域固化为可执行的工程契约。PostgreSQL 成为 checkpoint 的唯一 Owner决策明确PostgreSQL 是 LangGraph checkpoint 的唯一 OwnerAPI、worker 与 Agent 不读取后端选择环境变量也不挂载本地 checkpoint 目录。相应地LANGGRAPH_CHECKPOINTER_BACKEND、YUXI_CHECKPOINT_DIR等历史环境变量与/app/checkpoints目录从 shipping 配置面删除。仓库侧的对应验证在 test_docker_compose_checkpointer.pyL27-L28它断言 compose 的x-api-worker-env中不存在LANGGRAPH_CHECKPOINTER_BACKEND与YUXI_CHECKPOINT_DIR这两个键。这意味着 checkpoint 状态不再可能被写进进程本地文件多副本的暂停、恢复与审批语义因此可以在数据库层得到一致保证。旧变量与旧挂载的删除清单归档记录列出的删除项包括SAVE_DIRTHREAD_PVC运行时 fallback旧base.tomlshipping 读取历史 Skill workspace fixtureAPI/worker 启动期物化DOCKER_THREADS_HOST_PATH被DOCKER_USER_DATA_HOST_PATH取代。通过对仓库的全局检索可以确认这些旧能力在当前 shipping 代码中已不存在SAVE_DIR、THREAD_PVC、DOCKER_THREADS_HOST_PATH、LANGGRAPH_CHECKPOINTER_BACKEND、YUXI_CHECKPOINT_DIR、/app/saves、/app/checkpoints均不出现在 shipping 服务配置中仅在迁移测试与负向契约测试中作为禁止项出现。动态 Sandbox 的存储边界Docker 后端三个挂载分别解析 bind sourceDocker provisioner 从/app/projects、/app/user-data、/app/skill-projections三个挂载分别解析各自的 bind source不再推导共同父目录。在 docker/sandbox_provisioner/app.py 的LocalContainerProvisionerBackend中可以看到两个关键实现_resolve_host_paths()L736-L773从 provisioner 容器自身 inspect 出的 Mounts 中按/app/user-data与/app/skill-projections两个目标分别提取 Source任一缺失即抛错cannot infer explicit UserWorkspace/Skill host paths_normalize_host_bind_path()L602-L618统一处理 Docker Desktop 在 Windows 上报出的D:\...盘符路径转换为 Linux 容器内可见的/run/desktop/mnt/host/drive/...路径。此外_shared_workspace_host_path(uid)返回user-data/shared/uid/workspace_user_skills_host_path(uid)返回skill-projections/uidL662-L666配合_validate_directory_without_symlinks()L668-L690以O_NOFOLLOW逐层打开目录、拒绝任何 symlink 组件确保 mount 前的宿主路径是真实目录。容器内挂载语义单根 UserWorkspace 与只读 Skills虽然在归档决策当时的设计中 Sandbox 使用projects/workdir_id、user-data/shared/uid、skill-projections/uid三域挂载但后续实现2026-08-19-workdir-in-user-workspace.md已将 Sandbox 的持久文件挂载收敛为两个逻辑域当前用户 UserWorkspace - /home/gem/user-data rw 共享 Skill projection - /home/gem/skills roSandbox 的 cwd 设置为/home/gem/user-data/workdir_path其中workdir_path projects/opaque-workdir-id是 UserWorkspace 下的相对路径宿主机映射为user-data/shared/uid/workspace/workdir_path。每个 Thread 只改变 cwd不改变挂载配置因此不再需要独立的 Project bind mount、DOCKER_PROJECTS_HOST_PATH或PROJECT_DATA_PVC。app.py的create()L932-L1115忠实实现了该边界持久 Sandbox 挂载user_skills - /home/gem/skills (ro)与shared_workspace - /home/gem/user-data (rw)L1072-L1081working_dir指向/home/gem/user-data/{safe_workdir_path}L1083-L1085_is_expected_skills_mount()L692-L700与_has_expected_user_data_mounts()L702-L725在复用容器时校验挂载来源与读写模式任何不符合预期如 Skills 变可写、出现/home/gem/projects等额外持久挂载根都会触发重建。一次性ephemeralSandbox 则通过_has_no_persistent_file_mounts()L727-L734保证不获得任何持久卷。统一运行身份API、worker 与 Sandbox 数据面统一使用数值身份1000:1000访问同一 UserWorkspace新目录遵循 owner-only 权限旧数据由 root storage migrator 在运行时启动前一次性收敛所有权与权限。这一点在 Compose 中可见api/worker 以user: 1000:1000运行storage-migrator 以user: 0:0运行。Kubernetes 端则由kubernetes_storage_init_script()app.py L123-L200生成一次性 init 脚本在 PVC 子树内执行 no-follow 的 fchown/fchmod 归一化并写入.v072-runtime-identitymarker相关契约见 2026-08-20-unified-workspace-runtime-identity.md。Kubernetes 双 PVC 契约归档决策定义了两条 PVC 的职责划分PROJECT_DATA_PVC承载projects/workdir_id与user-data/shared/uidSKILLS_PVC承载skill-projections/uid。并明确两点硬性要求Sandbox 内 Skills mount始终只读Project Data PVC 的目标存储类必须支持 RWX才能跨节点实时共享同一 Project 的 POSIX 可见性。该决策同时限定双 PVC 契约只面向后续 Kubernetes 新部署历史THREAD_PVC不是运行时 fallback旧部署必须保留原卷、由 operator 离线导出校验后导入新布局不能把新变量直接指向旧 claim。在后续演进中随着 Project 存储域并入 UserWorkspacePVC 布局相应变为USER_DATA_PVC与SKILLS_PVC。当前 docker-compose.yml L252-L253 的默认值即为USER_DATA_PVC${USER_DATA_PVC:-yuxi-user-data}与SKILLS_PVC${SKILLS_PVC:-yuxi-skills}。在 docker/sandbox_provisioner/app.py 的KubernetesProvisionerBackend._build_pod_spec()L1274-L1411中Pod 使用user-datavolumeclaim 为USER_DATA_PVC容器挂载/home/gem/user-datasub_pathshared/uid/workspaceL1302-L1308skills-datavolumeclaim 为SKILLS_PVC容器挂载/home/gem/skillssub_pathskill-projections/uid且read_onlyTrueL1369-L1380home-dir使用emptyDirL1405-L1408。_pod_has_expected_mounts()L1438-L1499则对 Pod 的 volume claim 名称、mount path、subPath 与 Skills 只读性逐项校验作为 spec/unit 层面的契约护栏。需要强调的是归档记录本身也承认真实目标集群仍需用其实际 RWX CSI 做上线 smokespec/unit 不能替代该部署证据且该 smoke 当时标记为 Not run——这一点在下文验证章节会继续说明。停机迁移协议storage-migrator 与 quiescence proof这是本决策最具操作价值的部分。迁移被设计为停机专用的一次性流程普通docker compose up不得执行破坏性删除。迁移的职责边界storage-migrator是唯一可挂载历史广域目录的停机迁移 Owner。它在 API、worker 与 provisioner 启动前完成Project 物化旧 schema/对象清理共享 Skill 源迁移个人 Workspace Skill原地保留shipping 启动时只校验 active gate不再扫描历史宿主目录。在 docker-compose.yml 中storage-migratorL165-L199以user: 0:0运行挂载/app/legacy-saves对应${YUXI_STATE_DIR:-./docker/volumes}/yuxi、/app/user-data、/app/skill-sources与/app/skill-projections命令为python -m yuxi.storage_migrationapi 与 worker 均以depends_on: storage-migrator: condition: service_completed_successfully等待其成功退出从而保证迁移失败阻止 shipping 启动、不回退旧目录。迁移器的 fail-closed 语义在 backend/package/yuxi/storage_migration.py 中有清晰实现_legacy_skill_roots_exist()检查历史共享 Skill 目录是否仍存在、_legacy_system_config_exists()检查旧config/base.toml是否仍在广域目录中一旦判定旧业务 schema 已存在但切换未完成或仍存在旧 Skill 源_require_quiescence_proof()就会强制校验一次性 quiescence proof缺失或 token 不匹配HMAC 比较即抛错拒绝启动。quiescence proof 的建立流程一次性 proof 由宿主机脚本 scripts/migrate-storage.sh 创建其关键步骤docker compose $ stop api worker sandbox-provisioner—— 脚本接收并复用 Docker Compose 参数因此同一套命令可覆盖开发与生产配置docker compose $ up -d --no-deps --build --wait sandbox-provisioner—— 用--no-deps从已 stop/down 的部署启动受控 provisioner绕开其 storage-migrator 依赖调用 provisioner 的POST /api/sandboxes/quiesce?timeout_seconds180Bearer 认证校验 API/worker/provisioner 均已停止后用openssl rand -hex 32生成一次性 token 写入临时 proof 文件docker compose $ run --rm以-v proof:/app/legacy-saves/.storage-migration-quiesced:ro与YUXI_STORAGE_MIGRATION_QUIESCENCE_TOKEN运行 storage-migrator。provisioner 端的SandboxQuiescenceGateapp.py L417-L443与quiesce_sandboxes端点L2029-L2063保证了 quiesce 期间的严格语义quiesce 开始后拒绝新建 Sandboxacquire_create抛 503随后按 Docker 容器或 Kubernetes Pod 的权威枚举有界并行删除并等待归零_delete_sandbox_records_for_quiescenceL2066-L2103。脚本最后 echo 提示storage migration completed; restart Yuxi with the same Docker Compose options。首次安装与升级的区分决策要求首次安装与已 active 的部署由迁移前持久 schema 状态区分不依赖可竞态的瞬时文件/行数判空。迁移器的_converge_database_state()依据数据库中的持久 schema 状态决定是否收敛非终态 Runfail_nonterminal_runs仅在停机切换场景为 True而非扫描文件行数。此外迁移在 runtime quiescence 与目标校验后先提交数据库再清理旧源当前 Project schema 仍存在旧 thread 源时按同一目标重试未发布的ProjectWorkdir、FileStorageMaterialization与workdir_id中间 schema 明确拒绝不执行兼容导入。替代方案为什么拒绝这三条路归档记录明确列出了被拒绝的替代方案理解这些否决理由有助于把握契约边界替代方案否决理由保留一个共享 saves 靠代码自律挂载本身已扩大服务能力路径推导继续耦合部署User Workspace 改为对象存储/通用 FileStore产品要求实时 POSIXAPI 的专用 User Data 挂载是明确 Owner不应为形式统一增加同步协议用 RWO、hostPath 或 MinIO FUSE 宣称跨节点实时共享不能满足多个 Sandbox Pod 对同一 Project 的 POSIX 可见性契约这三条否决的共同逻辑是部署边界上的能力声明不能用代码自律或对象存储抽象来回避实时 POSIX 语义必须有物理挂载与存储类能力做支撑。后果清单升级者必须知道的边界决策的后果部分是对升级路径的直接影响逐条继承如下Project、User Data、共享 Skill source 与共享 Skill projection 有独立配置与挂载个人 Skill 属于 User Data历史 SQLite checkpoint 文件不再自动导入或删除升级后无法从这些文件继续暂停、审批或摘要状态升级必须先停止旧 execution runtime再运行一次性 migrator迁移失败会阻止 shipping 服务启动不会回退到旧目录动态 Kubernetes Pod spec 已使用两个 PVC 并校验 volume name、subPath 与 Skills read-only真实目标集群仍需用其实际 RWX CSI 做上线 smokespec/unit 不能替代该部署证据。归档记录的重新引入条件同样值得注意只有新的部署契约明确要求同一服务拥有多个存储域并提供权限、迁移、跨副本并发和目标环境证据时才可增加挂载不得恢复广域父目录或静默 fallback。重新引入 checkpoint 后端选择还必须先证明 API/worker 跨进程一致性、暂停恢复和升级迁移不得恢复进程本地 SQLite。验证证据契约测试与真实集成归档记录给出了多层次的验证矩阵这里按证据类型整理契约与单元层Compose contract恢复 API/worker/provisioner 的/app/saves或缺少显式域会失败对应 test_docker_compose_service_boundaries.py 与 test_docker_compose_checkpointer.pyDocker/Kubernetes provisioner unit显式 host root、双 PVC、Project/User subPath、Skill 只读与错误 volume 绑定均有负向案例对应 test_sandbox_provisioner_config.py 等一次性 migrator unitProject 先切换、旧 schema 待切换但缺少一次性停机证明时 fail-closed、个人 Skill 目录不会触发共享迁移且始终原地保留失败仍关闭数据库。集成与 E2E 层真实 PostgreSQL/MinIO/HTTP 与 Docker 集成迁移、作用域、撤权、Project/User/Skill producer-consumer18 passed真实主/子 Agent E2E2 passed远程 Skill 一次性 Sandbox 的真实 Docker 负控证明其不创建持久 User Data 或 Skill projection UID 目录。真实部署操作层实际 Compose 停机迁移在运行中和已停止状态各执行一次退出码均为 0API ready 的 Project Workdir、Sandbox、Skills 等必需组件均为ok容器 mount inspection 证明 API 的 User Data 为可写、worker 为只读provisioner 只有显式 Project/User/Skill projection 三个数据域且没有/app/saves。未完成项真实目标 Kubernetes RWX 双 Pod smokeNot run原因是当前开发环境没有目标 CSI/PVC。此外归档记录还报告了当时的全量回归数字backend non-slow unit1372 passed, 34 skipped宿主 Compose 配置 contract39 passed工程 contract48 passedWeb lint、43 passed unit、生产 build 与 docs build 通过Ruff check/format 与git diff --check通过。这些数字属于归档当期的验证快照反映的是该决策落地时的质量基线不构成对后续版本的持续承诺。总结从部署演进看设计原则这篇归档记录虽然已被 Workdir 归属 UserWorkspace 取代但它确立的几条原则仍然贯穿 Yuxi 当前的存储架构显式优于隐式每个服务只挂载它明确拥有的存储域禁止广域父目录与静默 fallback单一 OwnerPostgreSQL 拥有 checkpointUserWorkspace 拥有用户字节migrator 拥有历史布局provisioner 拥有动态运行时挂载破坏性操作必须停机并留证一次性 quiescence proof 把是否允许执行破坏性迁移变成可验证的部署 gate边界以契约测试固话Compose 边界、PVC subPath、Skill 只读性都有对应的负向测试兜底。对于要在 Kubernetes 上部署 Yuxi 的团队本记录的实操要点是准备 RWX 的目标 StorageClass、规划USER_DATA_PVC与SKILLS_PVC两块 PVC 及容量且旧部署必须走离线导出 → 校验 → 导入的迁移路径而不是把新变量指向旧 claim对于 Docker 单机部署则只需遵循scripts/migrate-storage.sh的停机迁移流程即可在不停留旧配置面的前提下完成升级。【免费下载链接】Yuxi可私有部署的多租户知识智能体平台统一 RAG、知识图谱、多智能体、MCP/Skills、沙盒与权限管理。Self-hosted knowledge agent platform for RAG, knowledge graphs and multi-agent workflows.项目地址: https://gitcode.com/GitHub_Trending/yu/Yuxi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表