xworkspace-console/docs/operations/iac-ansible-deploy-verification.md
Haitao Pan 26a4794f2f docs(verify): record clean green IaC↔Ansible run + nodejs/resolver fixes
Both hosts reached RC=0 on a single on-host curl|bash bootstrap; console 17000=200,
api 8788 up, litellm 4000=200 "I'm alive!" (incl. ubuntu26 uv-Py3.13), caddy active;
FQDN hostnames set; VPS destroyed, instances=0. Adds fixes #12 (nodejs self-ref
recursion / omit-sentinel leak) and #13 (browser resolver skips disabled chromium stub).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 11:15:24 +08:00

9.2 KiB
Raw Permalink Blame History

IaC ↔ Ansible 动态 inventory 联动部署 —— 验证与问题修复记录

日期: 2026-06-24 · 目标机: Vultr vc2-4c-8gb × 2(debian13 trixie / ubuntu26.04, nrt)

1. 架构与数据流

config/resources/ai-workspace-hosts.yaml        (IaC 声明, 唯一人工入口)
  → scripts/generate.py render (Python+Jinja2, 无 for_each)
  → terraform apply  → 2 台 VPS + 公网IP
  → scripts/generate.py inventory  → cmdb.json + inventory.ini
  → playbooks/inventory/terraform_cmdb.py (Ansible 动态 inventory, 只读 cmdb.json)
  → 部署
  • IaC 模块: ai-workspace-infra/iac_modules/terraform-hcl-standard/vultr-vps
  • Ansible: ai-workspace-infra/playbooks(all-in-one + roles/vhosts/ai-workspace 等)

2. 验证结果(已通过)

环节 结果
IaC 起机(terraform) 2 台创建成功, plan 与 state 零漂移
动态 inventory 联动 terraform_cmdb.py 读 cmdb.json, ansible -m ping 两台 pong
离线包联动 install.svc.plus → 302 引导脚本; Release offline-ai-workspace-14 含 debian-13-amd64 / ubuntu-26.04-amd64 包(分片); on-host 安装实测使用了离线包(日志见 /var/tmp/ai-workspace-offline/extracted/...)
on-host 部署 curl|bash 在主机本地执行, openclaw / ttyd / caddy 起来

3. 关键架构结论

all-in-one playbook 设计为"在目标主机本地执行"(curl|bash → ansible-playbook -c local,此时 localhost = 主机)。 从远程 controlleransible-playbook -i <inventory> 时,roles/agent_skills 大量 delegate_to: localhost 并读写 /root/.agents/skills{{playbook_dir|dirname}}/xworkspace-core-skills/skillscontroller 本地路径, 会与"controller≠主机"错位(在 mac 上写 /root 只读 → 失败)。

推论: IaC↔Ansible 联动的正确形态是 IaC 负责"起机 + 出 inventory",实际部署用 inventory 在每台主机上跑 官方 curl|bash 引导(本地模型, 支持离线加速),而非从 runner 远程跑 all-in-one。 xworkspace-console/.github/workflows/deploy-ai-workspace-iac.yaml 的 deploy job 据此应改为 on-host 引导(见下"待办")。

4. 发现的问题与修复

均在 ai-workspace-infra/playbooks:

# 问题 现象 修复
1 Python 3.13 + apt + pipelining apt 任务 UNREACHABLE + maxsplit DeprecationWarning 部署侧 ANSIBLE_PIPELINING=False + PYTHONWARNINGS=ignore(deploy 流水线已内置)
2 xworkspace_console_user 写死 ubuntu root 连接时 home=/root 但 user=ubuntu, enable 服务 link /root 报 "src does not exist" setup-xworkspace-console.yaml: user 跟随 ansible_env.USER
3 xfce4 大装包拖断 SSH 会话 "Install runtime packages" UNREACHABLE(包其实装完) 该 apt 任务加 async/poll
4 ai_agent_runtime apt 抢 dpkg 锁 texlive/pandoc Could not get lock roles/ai_agent_runtime/tasks/{main,docs,fonts,browser}.ymllock_timeout
5 console API 二进制路径错 api/xworkspace-api 不存在 → 203/EXEC 崩溃重启 manifest apiBinary: bin/xworkspace-api,setup-xworkspace-console.yamlapi_dirbin/
6 console 伺服方式错 预编译只发 dashboard/dist(无 package.json),npm run preview ENOENT(254)崩溃重启 console 是 127.0.0.1:17000 上的本地静态后端(dashboard 为无路由单页),Linux console.service 与 macOS console.plist 统一用 python3 -m http.server --directory dist;起第二个 caddy(避免与系统 caddy 抢 :80
7 caddy 安装未受开关控制 console play 无条件 apt install caddy apt 列表里 caddy 由 caddy_enabled 门控(VPS 默认 true;关→不装;macOS 无 apt 本就不装)
8 module_defaults.apt.lock_timeout(模板值)经 ansible.builtin.package 间接派发到 apt 时不渲染 字面 {{ ai_workspace_apt_lock_timeout … }} 当 int 失败 → bootstrap 在 bridge/litellm 之前中止 Debian 上 xworkmate_bridge/litellm/common(fail2ban) 预装任务改用 ansible.builtin.apt(非 Debian 留 package,yum/dnf 不继承该默认)
9 acp_server_opencode ACP 端点校验超时 服务(重)启后 ~1s 即探测,adapter 已 accept TCP 但未应答;uri 默认 30s + retries/until 在连接超时上未真正循环,一次即败 改为 curl 重试循环(每次 5s、最多 ~30 次);adapter 就绪后 acp.capabilities ~4ms 回 200
10 litellm × Python 3.14(仅 Ubuntu 26.04) pinned litellm fork 要求 <3.14,Ubuntu 26.04 系统 py=3.14 且 apt 无 3.13/3.12 → pip install 报 "requires a different Python" 系统解释器 ≥3.14 时用 uv 装独立 Python 3.13 重建 venv;Debian 13(3.13)不受影响
11 inventory_hostname 硬编码短名/127.0.0.1 主机标识/hostname/caddy 站点名错位 generate.pyservice_domains 首个 FQDN 为 CMDB/inventory 键;.sh on-host 的 -i 用 FQDN;bridge 角色据此设 /etc/hostname 与 caddy 站点名
12 nodejs_version 自引用(Ansible 2.19 递归) xfce include nodejs 角色传 nodejs_version: "{{ ai_agent_runtime_nodejs_version | default(nodejs_version) }}",2.19+ 惰性模板判定 Recursive loop detectednodejs_version_major set_fact 失败 改显式回退 default('22.22.3', true):default(omit) 在 include_role vars 里不回退角色默认而是塞入 omit 占位符,渲染成 node_<<Omit>>.x 仓库地址致 apt update 失败
13 浏览器 resolver 选中 disabled stub ai_agent_runtime resolver 仅以 command -v/-x 判存在,选中 xfce 装的 /usr/local/bin/chromium 禁用 stub(退出 126)而非 google-chrome → Check chromium version rc=126 失败(再次运行/角色顺序触发) resolver 增加 <candidate> --version 实跑校验,跳过 stub,解析到 google-chrome

部署侧加固(长途控制连接稳定性): ANSIBLE_SSH_ARGSServerAliveInterval/ControlPersist, ANSIBLE_SSH_RETRIES

非空传递检查(默认要求非空,缺失即 fail-fast):generate.py 校验每台 ip/instance_id 非空;流水线各 job Validate required secrets 校验必需 Vault 输出;bridge 角色断言 xworkmate_bridge_domain 为合法 FQDN(caddy 暴露时)。

4b. 公网暴露 / caddy 架构

  • caddy 是统一反代前端(:80/:443),每个对外服务一份 /etc/caddy/conf.d/*.caddy(reverse_proxy 到其本地端口)。
  • Linux VPS(有公网 IP):默认仅 XWORKMATE_BRIDGE_PUBLIC_ACCESS 开(经 -e 传入)→ bridge 进 conf.d 对外;console(17000*_PUBLIC_ACCESS 默认 false → 本地only、无 conf.dcaddy_enabled 默认 true → 装 caddy。
  • macOS 本机:无需暴露任何公网服务、全内网、caddy_enabled=false不装 caddy;console 同样 python 本地伺服。
  • 注意 :80apt caddy 包自带的默认 /etc/caddy/Caddyfile(:80 {}) 占用;早期把 console 也做成第二个 caddy 会因 auto-HTTPS 预留 :80 而冲突——故 console 改为 python 静态后端、由系统 caddy 反代(本地only 时则不反代)。

5. 验证结果

已确认(全新主机,on-host 引导):

平台 hostname console api 17000
debian13(caddy_enabled=true active(python3 active 200 <title>XWorkspace Dashboard</title>
ubuntu26.04 xworkmate-bridge-ubuntu-26.svc.plus(FQDN ✓) active(python3 active 200
macOS 本机(python3 伺服 dist 200 <title>XWorkspace Dashboard</title>
  • console(python 伺服)+ api(bin 路径)在两台全新主机直接 active、17000=200(此前 console 崩溃重启)。
  • FQDN hostname 在 ubuntu 实测生效;agent_skills 重构、lock_timeout(bridge/fail2ban修复均已越过。

最终干净一轮(IaC 起机 → on-host curl|bash → 两台 RC=0)—— 全绿:

平台 hostname curl|bash 17000(console) 8788(api) 4000(litellm) caddy
debian13 xworkmate-bridge-debian-13.svc.plus RC=0 200 up(404 无根路由) 200 "I'm alive!" active
ubuntu26.04 xworkmate-bridge-ubuntu-26.svc.plus RC=0 200 up 200 "I'm alive!"(uv-Py3.13 #10 生效) active
  • 运行单元:caddy.service / litellm-proxy.service / xworkmate-bridge.service(console/api 在各自端口应答)。

  • #12 nodejs 递归 + #13 resolver stub 是本轮新定位并修复的两处(均在活跃部署路径,非脏机伪症);叠加 #1#11 后两台一次性 RC=0

  • 验证完即 terraform destroy(2 instance + ssh key,Vultr API 复核 instances=0,零计费残留)。

  • deploy 流水线: deploy-ai-workspace-iac.yaml 的 deploy job 已改为"ssh 到主机本地跑 curl|bash 引导"(契合本地执行模型 + 离线加速),provision job 保留为批量起机模式;密钥经 Vault OIDC 取。

6. 离线/在线回退确认

  • 默认 AI_WORKSPACE_OFFLINE_MODE=auto + AUTO_DOWNLOAD=true → GitHub Releases 取分片包(download_offline_split 重组)。
  • AI_WORKSPACE_OFFLINE_PACKAGE_BASE_URL 空 → 跳过 rsync 镜像。
  • 离线获取失败 → 回退在线: install_prerequisites 按系统选 apt/yum/brew(macOS), 再 git clone + 在线拉 runtime tar。