AR / Agent Runtime Lab
DEVICE × E2B × FOUNDRY × SESSIONS × SANDBOXES

四种云端方案,
每个任务选择一条。

我用独立 Device VM 模拟端侧,把文档工具、多轮模型对话、临时代码执行和可恢复工作区放在四个并列纵向框中。四条接口独立选择,不串联,也不同时扇出执行。ACA Sandboxes 使用真实原生 SDK,演示可信命令、文件校验、Disk 停止/恢复及快照恢复;不把第三条 Session Pool 换名当作第四条。

路径一 / E2B SDK

E2B on Azure VM

自己运营开源 Runtime 与 Firecracker;控制命令、文件、环境和回收。

路径二 / 模型 Agent

Foundry Hosted Agent

托管 Agent 容器、模型与平台对话历史;重点是 Agent,而非通用执行器。

路径三 / 原生 REST

ACA Dynamic Sessions

池化临时执行;本 Demo 使用 PythonLTS,状态在当前会话内复用。

路径四 / 原生 Sandbox SDK

ACA Sandboxes

独立 SandboxGroups,真实工作区、文件、Disk 生命周期与快照恢复;不是第三条池的别名。

01 / INTEGRATION MAP

端云边界,跟着部署形态一起看

━━ E2B 控制/文件━━ Foundry Responses━━ Dynamic Sessions REST━━ ACA Sandboxes SDK虚线:所选方案的结果回传

这里只播放协议步骤,不创建任务、不冒充运行日志。真实执行请回到 Device 页面。

点击节点可以查看责任边界。
POSITIONING / FOUR DIFFERENT CONTRACTS

不是四个等价的代码执行按钮

维度E2B on VMFoundry Hosted AgentDynamic SessionsACA Sandboxes
核心定位自运营的通用隔离执行基础设施托管 Agent 应用、推理与编排按 identifier 路由的临时执行会话可编程、可恢复的隔离工作区
当前实现真实开源 Embed;base 模板真实 planning-agent 容器;GPT-4.1-miniPythonLTS 内置解释器;非自定义容器eastus2 原生 SandboxGroups;SDK 0.1.0b4;可信工具、文件 SHA-256、Disk 停止/恢复、快照/restore 与回收
SDK / 使用入口官方 e2b;create → commands/files → killAIProjectClient + Responses;conversation → 多轮回复原生 REST;executions / files / session;池自动分配azure-containerapps-sandbox;begin_create_sandbox → exec/files → suspend/resume/delete
控制资源 / 数据面自己的 VM / upstream API 和 client-proxyFoundry account/project/agent version / Agent ResponsesMicrosoft.App/sessionPools / dynamicsessions.ioMicrosoft.App/sandboxGroups / management.azuredevcompute.io;不调用旧 sessionPools 路径
状态边界本 Demo 完成后 Kill;Blob 保留结果,端侧保存状态。上游其他能力另测conversation 是历史;compute session 是容器与文件系统。默认空闲 15 分钟,文档支持保存至 30 天;不承诺任意 Python 内存全量恢复当前 identifier 中保留 Python globals/文件;删除或 cooldown 后销毁,无原生持久卷本 Demo 验证 Disk 恢复与快照 restore 后文件保留;不宣称已验证内存/进程恢复。官方 memory 快照与卷能力需另外验证
运行环境 / 自定义Linux 模板、命令/文件;KVM/Firecracker 运维归自己。base 不保证 Jupyter自己的 Agent OCI 容器与协议;模型、工具、治理交给平台内置运行时免镜像;另有自定义容器池,但它采用 Dedicated/E16 计费,不能套用本 Demo 的解释器价格OCI 转磁盘镜像,快照克隆;XS 至 3XL:0.25–16 vCPU、0.5–32 GiB、20–320 GiB 磁盘
网络与安全责任API ACL、密钥、补丁、模板、宿主机容量与灾备由自己负责Entra 与 Agent 身份;工具访问还需逐项授权。托管 Agent 不是任意不可信代码执行 APIHyper-V 隔离;本池禁止出站。自定义容器可暴露协议,不代表任意任务天然安全组级角色、端口管理、域名/CIDR egress 策略和 VNet;最小权限与外部状态保护仍由应用负责
适用与不适用要 E2B 生态、模板控制、命令观测与基础设施掌控;不适合只想零运维、低利用率的临时脚本多轮业务 Agent 与模型编排;不宜拿模型首字速度代表 sandbox CPU 性能一次性 Python 分析/工具调用、快速上手;不是跨天保存工作区的方案跨任务工作区、Agent 开发环境、可暂停任务;若只是一次短 Python,生命周期/镜像管理可能过重

我先按“业务 Agent”与“执行工具”拆开选型。本演示每个任务四选一,不串联四个云方案。若另行设计组合工作流,Foundry 也可调用 E2B、Sessions 或 Sandboxes 做工具执行,但会累加两边成本,不能把 Hosted Agent 计算会话等同于独立的 ACA SandboxGroups。

第四条如何接入 Device

真实链路:Device → 同源 HTTPS BFF → Python Sandbox SDK → ACA Sandboxes 数据面。使用本机 Azure CLI 或独立 Device VM 托管身份;服务端保管原生 ID,仅向浏览器返回设备绑定句柄。任务 API 为 /api/sandboxes/tasks,负责可信执行、文件校验、Disk 生命周期、快照恢复、回传和回收,不复用第三条的 identifier 状态机。

环境由独立 Terraform root 部署 Microsoft.App/sandboxGroups 与组范围 Container Apps SandboxGroup Data Owner。代码位于 device-app/aca_sandbox_connector.py,使用 azure-containerapps-sandbox==0.1.0b4。组是预创建的管理边界;每个任务的工作区与自建快照在演示后删除,端侧文件与便签另行保存。

PERFORMANCE / CLAIMS AND MEASUREMENTS

启动速度、完整任务时延和吞吐分开看

路线官方能力 / 性能因素本 Demo 的容量与边界应该测什么
E2B VMFirecracker 与模板缓存能降低启动开销,但不引用 E2B SaaS 的宣传数字来保证这台 Azure VMD8s_v5:8 vCPU / 32 GiB;业务 worker 为 4。控制平面、缓存和 microVM 共享宿主机;不是无限并发create、envd readiness、命令、文件和完整回传;缓存命中/未命中;KVM、磁盘、CPU 排队
Foundry按会话启动/恢复托管 Agent;端到端还包含模型推理、Token 数、模型配额、工具和 pollingAgent 1 vCPU / 2 GiB;模型 GlobalStandard capacity 10 不是 10 个并发的保证冷会话、热会话、resume、模型 TTFT、总完成时间、Token/s;流式与非流式分别测
Dynamic Sessions文档称预热池分配为毫秒/亚秒;这是池就绪条件下的分配,不是整个 Python 请求的 SLA池 maxConcurrentSessions=5,Device worker=3。网络、Entra、解释器、文件上传下载仍有开销新 identifier 首次执行、同 identifier 再执行、文件、DELETE;池耗尽时排队/429与重试
ACA Sandboxes文档称预热分配与恢复亚秒;自定义 OCI 转换、快照大小、卷和网络路径会改变实测已进行原生 SDK 的单次文件/生命周期观测;未进行容量压测,不以文档“数千”当作订阅保障镜像冷/热、create、exec、Disk stop/resume、快照 restore;memory 与卷一致性另外测试

三条已有路线的小样本观测

我从同一操作端串行发起请求,每条取三个独立分配样本;E2B 与 Sessions 都执行 sum(range(100000)),Foundry 做一次短模型回复。首次 = 分配/会话创建 + 第一次执行;热请求 = 同一运行会话内再次调用。它不是跨产品同等工作负载排名。

读取时间与测量边界。

路线样本数首次中位数热请求中位数首次最小–最大

第四条:原生 Sandbox 生命周期观测

单次 SDK 流程记录:分配 2.11 秒,首次执行 0.38 秒,再次执行 0.35 秒,Disk 停止 7.49 秒,恢复 2.46 秒,创建快照 2.47 秒,从快照恢复新工作区 0.71 秒。这里的工作量是文件与生命周期验证,不是前三条相同的小算术;不从一个样本推导中位数、p95、吞吐或 SLA,也不宣称内存/进程已恢复。

这些是客户端墙钟时间,包含 DNS/TLS、SDK 身份解析和 Foundry 500ms 轮询;不包含 Device 业务队列、完整文档导出和 IndexedDB 回传,也没有强制清空平台缓存。原始记录可查看 测量 JSON。三次样本只报告中位数和范围,不推导 p95/p99、吞吐或稳定性。

性能容量模型与下一轮验收

T端到端 = 端侧调度 + 认证/网络 + 排队 + 分配/恢复 + 执行/模型 + 文件回传 + 本地提交。稳态所需并发约为 到达率 × 平均驻留时间,但尾延迟和突发需额外余量。E2B 的有效并发取 worker、可用 CPU/内存、磁盘吞吐和网络中的最小约束;Sessions 本应用至多三个 worker,不能因平台支持数千就声称 Demo 支持数千。

正式比较时统一数据、文件大小与计算量,至少各 100 个样本,分冷/热/恢复,按 1→3→5 并发逐级测,记录 p50/p95、成功率、429、吞吐与每成功任务成本;Foundry 单独固定模型、输入/输出 Token 与工具数。建议本 Demo 的验收目标是“接入与状态可见、文件一致、明确回收”,而非在未压测前承诺亚秒业务完成。

COST / EDITABLE WORKLOAD MODEL

成本先看计费粒度,再看单价

载入官方价格快照。

内置 Sessions 按每个已分配会话向上计入小时。 新建 20,000 个各运行一分钟的会话,仍是 20,000 会话小时;若每个会话连续做 100 个一分钟任务,则每会话约 100 分钟,按两小时计费。显式 DELETE 能限制尾时长,但不能把第一小时按一分钟折算。默认 cooldown=300 秒会延长分配时长,跨过小时边界时会增加一个计费小时。

Sandbox 在 running 期间按分配的 CPU/内存秒数计费,stopped 不收 CPU/内存费用,但镜像、快照、卷、网络和日志并不因此免费。Foundry Hosted Agent 的官方零售 meter 为 $0.0994/vCPU-hour、$0.0118/GiB-hour,另加模型 Token;这里使用真实 Hosted meter,不借用 ACA 费率,也不把 Foundry-native prompt/workflow 的“无额外费”套给 Hosted。实际活跃容器小时未填时,计算器明确保留待确认项。

单价 / USD:可修改以评估区域、折扣或价格变化

公式、边界与利用率

例如同样 20,000 次、每次运行 60 秒,Hosted 1 CPU/2 GiB 的计算若无尾时长为约 $41,加本例 Token $28,共 $69 已知组件;若每次新建计算会话并一直等到默认 900 秒空闲回收,则按活跃时长估算计算约 $656,加 Token 共 $684。它们是两种复用/回收假设,不是当前账单;Hosted 的小时单价本身不代表已证明像 Sessions 一样每次向上取整小时。

怎么选,怎么压低成本

稀疏短任务

只要内置 Python,Sessions 最省集成工作;但大量“每任务新 identifier”应仔细算小时最小粒度。Sandbox 可暂停的秒级计算更有吸引力,但有镜像、状态和接入成本。低利用率不宜只为了一个脚本全天开 D8s VM。

高复用 / 持续负载

同一授权会话连续做多个工具步骤能摊薄 Sessions 首小时;跨任务保留完整工作区更适合 Sandbox。E2B 的固定机器在高利用率才可能摊薄成本,还需计入运维与单机故障风险,不只比较 CPU 单价。

业务 Agent

选择 Foundry 是为了模型编排、身份、托管和治理;优化累计历史 Token、工具次数、模型和 idle timeout。不能用更便宜的 Python 执行费用证明它可以替代模型 Agent。

我会按资源组和 meter 每日对账,分开记录 Device 共享底座、路由计算、Token、存储与网络,配置预算提醒和显式回收。历史保留的 Azure 资源也会计费,但不计入这里的目标架构组件小计;本页面不代表整个订阅账单。

SOURCES / REPRODUCIBLE PRICING

价格证据与官方接口

快照不是实时价格。原始选择条件、meter ID、地区、单位与生效日在 价格与资源证据 JSON,可用 Azure Retail Prices API 复核。模型选择普通 GlobalStandard 输入/输出,不混入 Batch、cached input、微调托管或 Windows 365 Agent 单价。

组件meter原始计价单位USD/单位单价生效日
MODULAR FOUNDATIONS / OFFLINE DELIVERY

云模块独立部署,Device 保持独立项目

我把服务基础环境与演示应用分开:各云模块有自己的 Terraform root 和 state,可以单独 init / plan / apply,也能由统一入口按选择顺序部署。基础环境部署不要求先运行 Device,更不会把 Foundry 绑定到一台必须存在的 E2B VM。

模块代码 / Terraform部署职责边界
E2B VMcloud-service / infra/e2b-vm开源 Runtime、真实 KVM、控制平面、存储与网关--base-only 不探测 Device 网页;端侧可另部署
Foundryfoundry-agent / infra/foundry账户、项目、模型、ACR、身份;容器 Agent 可单独发布VM consumer 身份可选;基础环境不强制查找 E2B VM
ACA Dynamic Sessionsinfra/aca-sessions / infra/modules/aca-sessions独立会话池基础环境、操作员角色不需要 Device、Foundry 或 E2B;新 root 可独立部署
ACA Sandboxesinfra/aca-sandboxes / device-app/aca_sandbox_connector.py独立 SandboxGroups、组角色与原生 SDK 任务--base-only 只创建组;Device 任务按需创建工作区,完成演示后清理
Devicedevice-app / infra/agent-lab独立 VM、ACR 镜像与 BFF;消费已部署云端配置现有环境保留兼容池;新环境可使用外部 Sessions,避免重复创建
自有域名infra/dns在现有 stat200.net 中管理选定 A 记录不接管 zone、不改变授权或端口;必须明确审批,不随 all 自动启用

按模块部署,或者一次部署四个云基础环境

# 只部署一个云基础环境,示例:Foundry
.venv/bin/python scripts/deploy_environment.py --modules foundry --base-only
# Sessions / Sandboxes / E2B 各自可选
.venv/bin/python scripts/deploy_environment.py --modules aca-sessions --base-only
.venv/bin/python scripts/deploy_environment.py --modules aca-sandboxes --base-only
.venv/bin/python scripts/deploy_environment.py --modules e2b --base-only
# 统一基础环境:四个云 root,不发布 Device 或 Hosted Agent 容器
.venv/bin/python scripts/deploy_environment.py --modules all --base-only
# 只生成并保存计划,不 apply
.venv/bin/python scripts/deploy_environment.py --modules all --base-only --plan-only

去掉 --base-only 可发布选中模块的应用;Device 托管网页仍需已部署的云端身份校验与 Foundry 配置,不把“云基础环境独立”误说成当前 BFF 已支持任意单服务身份模式。Terraform state、密钥与计划留在私有位置,公开 environment 清单只包含非秘密配置。

域名、TLS 与端侧状态

已批准的自有域名配置为 device.stat200.net 与 runtime.stat200.net。DNS 指向原 VM IP;Caddy 管理受信任 TLS 证书;Runtime 业务接口为 HTTPS 7444,SDK/代理/原生管理仍受原操作员 CIDR 限制。域名变更不覆盖组织访问策略,也不关闭证书验证。

# 审批后管理现有 DNS zone 的两个应用记录
.venv/bin/python scripts/deploy_environment.py --modules dns --organization-approved
# 准备并应用 Runtime TLS/CORS 与 Device URL 配置
.venv/bin/python scripts/configure_domain.py --approved --apply

切换域名意味着浏览器 origin 改变:旧域名的 localStorage / IndexedDB 不会自动复制到新域名。原来的设备数据仍属于原 origin;不能因旧地址被组织限制就代理读取它。新域名使用自己的演示状态,服务器授权凭据和归属校验保持原规则。

报告可以完全离线打开

.venv/bin/python scripts/build_offline_report.py
# 输出 docs/agent-runtime-lab-offline.html

离线版将 CSS、SVG/JavaScript、价格与性能 JSON 全部内嵌到一个文件。双击即可筛选四方案、播放流程、修改成本参数和查看证据,不依赖网页域名、CDN、本地服务器或 Azure 登录。它展示保存的技术资料与测量快照,不读取实时部署状态。

02 / SDK PLACEMENT

SDK 不在同一个地方

E2B SDK

cloud-service/sandbox.py

位于 Azure VM 的业务控制平面,使用官方 e2b SDK 调用本机上游 API 和 client-proxy。浏览器不拿 Team Key,也不直接执行沙箱进程。

  1. Sandbox.create("base")
  2. commands.run / files.write/read/list
  3. set_timeout / kill

Foundry 调用 SDK

device-app/foundry_connector.py

位于 Device 的 Python 连接器。真正本机运行时,它使用 Azure CLI 身份直连 Foundry;网页托管到 VM 时,相同连接器在 VM 的 Device BFF 中运行,改用托管身份。

  1. AIProjectClient.get_openai_client(agent_name)
  2. conversations.create
  3. responses.create / retrieve / cancel

Foundry 容器内模型 SDK

foundry-agent/main.py

位于真实 Hosted Agent 容器。Responses 协议库接收请求与历史,项目 SDK 使用平台注入的 Agent 身份调用模型。它不集成 E2B SDK。

  1. ResponsesAgentServerHost
  2. context.get_input_text / get_history
  3. model Responses → TextResponse
NATIVE SANDBOX / DEVICE LOOP

第四条:在真实工作区执行,再把状态带回设备

SDK 留在 Device 连接器

device-app/aca_sandbox_connector.py 集成 azure-containerapps-sandbox==0.1.0b4。本机运行使用 Azure CLI,托管 Device VM 使用托管身份。浏览器只持设备访问凭据与任务句柄,不拿 Azure Token 或原生工作区 ID。

任务与生命周期

  1. 新建原生隔离工作区,执行固定可信工具,不把用户指令拼成任意 shell。
  2. 写入文件并读回,校验 SHA-256。
  3. 以 Disk 模式停止/恢复,检查文件仍在。
  4. 创建快照并恢复到新工作区,再次检查文件。
  5. 删除此次创建的工作区与快照;文件回传后保存到设备 IndexedDB 与便签。

实际验证的边界

这里验证磁盘状态和文件,不宣称 Python 内存变量、进程、GPU 或网络连接原样恢复。原生数据面使用 2026-02-01-preview,ARM 组部署使用 2026-07-01,两个版本不能混称。入口为 /api/sandboxes/tasks 的提交、查询、取消与 artifact;错误明确呈现,不回退到其他云方案。

03 / PROTOCOL AND AUTH

每一跳使用什么协议、凭什么访问

链路协议认证秘密留在哪里
浏览器 → E2B 业务控制平面HTTPS 7444;JSON / multipart;轮询设备访问码换取 24 小时 HMAC-SHA256 Bearer;不是 JWT浏览器只持业务令牌;不能取得 E2B Team Key
VM 控制平面 → E2B API官方 SDK;loopback HTTP 3000X-API-Key:本部署种子 Team KeyVM 的私有控制平面配置
VM 控制平面 → envd官方 SDK / Connect RPC;client-proxy 3002SDK 的逐沙箱访问令牌和路由头控制平面 SDK;不下发浏览器
外部操作员 → E2B SDK / 原生管理HTTPS 7443 / 8443 / 9443Team Key / 沙箱令牌 / Secure Cookie;精确操作员 CIDR操作员环境;不是 Device 访问码
浏览器 → 本机 Foundry 连接器同源 loopback HTTP 8081;JSON进程 nonce + 设备 ID;来源检查Azure 令牌不进浏览器;只由本机 SDK 获取
浏览器 → 托管 Device BFF同源 HTTPS;JSON设备 Bearer,BFF 向控制平面验证角色和归属不共享控制平面的 E2B Key 或 HMAC 签名密钥
Device 连接器 → FoundryHTTPS Responses;后台请求、轮询与取消Entra OAuth;本机 AzureCliCredential 或 VM ManagedIdentityCredential连接器内存中的 SDK;无浏览器 API Key
Hosted Agent → 模型项目 OpenAI-compatible Responses API平台创建的 Agent 身份Foundry 容器/平台;不是项目拉镜像身份
Device VM → ACA 会话池原生 HTTPS REST:executions / files / sessionEntra audience https://dynamicsessions.io;池范围 Session Executor托管身份令牌与私有 identifier 仅在连接器内;浏览器只拿设备绑定句柄
Terraform → ACA SandboxGroupsARM HTTPS management.azure.com;创建组与角色部署身份需要组创建和角色分配权限;应用身份只授予组范围 SandboxGroup Data OwnerTerraform 私有 state/配置;不向浏览器分发部署身份
Device SDK → ACA SandboxesHTTPS management.azuredevcompute.io;SDK 按区域解析数据端点原生 SDK 使用 Entra;本机 CLI 或 VM 托管身份;组范围 Data Owner。不得借用 Sessions identifier 或接口令牌留在连接器;设备句柄、原生工作区与快照归属分开保管
VM 控制平面 → Azure BlobHTTPS;私有终结点与 DNS容器范围托管身份授权不使用存储账户密钥或匿名 Blob

图中的业务委派不等于实现 A2A 标准。当前 Device 契约是明确的 HTTP JSON / multipart;Foundry 容器使用 Responses 2.0.0,不把两条链路混称为同一个 SDK 或协议。

04 / STATE AND BUSINESS FLOW

状态留在哪里

E2B:任务型隔离执行

  1. 设备规则选择文档 Skill,向 VM 提交文件。
  2. 控制平面创建业务 Session 与独立 microVM。
  3. envd 解析/导出;结果写 Blob,计算 SHA-256。
  4. Kill 沙箱;设备接回文件并写入 IndexedDB/便签。
  5. 设备 ACK 后才把业务会话标为 completed。

沙箱是任务级临时环境;业务审计和文件副本在 Blob,最终使用状态在设备。

Foundry:模型型持续对话

  1. 设备选择 Foundry,连接器使用自己的 Azure 身份。
  2. 创建 conversation,再提交 background Responses。
  3. 平台为 conversation 关联托管计算 session,容器读取历史。
  4. 模型结果经 Responses 返回,设备保存便签。
  5. 下一轮复用 conversation;可按真实 response_id 取消。

conversation 是历史标识,不是计算 session ID;服务未返回 session ID 时不会编造。平台默认闲置计算回收不等于删除历史,也不是 E2B 的 Kill。

切换菜单不会删除已有提醒、便签或文件。Foundry 路线当前演示文本规划/多轮记忆,不伪造 Word 下载;E2B 未配置模型时明确使用文档摘录/模板,不把沙箱真实运行说成模型推理。

05 / DEPLOY AND OBSERVE

部署顺序和当前连接

1 / E2B VM

.venv/bin/python scripts/deploy_vm.py --test

Terraform 管理单机 VM、上游开源 Runtime、私有 Blob、Caddy 和独立应用。生产多节点/持久队列不在 Demo 范围。

2 / 新 Foundry 项目

.venv/bin/python scripts/deploy_foundry.py

Terraform 创建独立账户/项目、模型、ACR 和项目范围角色;发布脚本远程构建 amd64 镜像并注册真实 Hosted Agent 版本,等待 active,不改成 Prompt Agent。

3 / 本机 Device

az login
.venv/bin/python scripts/run_device.py

打开 localhost:8081,在菜单选择方案。本机连接器直连 Foundry;E2B 仍走 VM 控制平面。托管网页需要更新 VM 应用包后使用托管身份。

从当前 Device 服务读取公开连接配置;离线打开时仍可浏览完整架构。

Device 页面显示实时业务事件与请求状态;E2B 原生 Dashboard 管理 Sandbox/Template,业务后台管理 Device/Session;Foundry 项目通过 Azure Foundry 门户管理 Agent 版本、模型与追踪。

06 / STANDALONE DEVICE AND ACA

Device VM 与 Runtime VM 分开

Device VM 只是端侧逻辑的模拟机器,物理上仍在 Azure,不能称为真正离线手机。它只运行独立的 device-app 容器与 HTTPS 网关,不持 E2B Team Key,也不运行 Firecracker。E2B SDK 留在 Runtime VM;Foundry SDK 和 ACA REST 连接器位于 Device VM。

ACA 连接器代码:device-app/aca_connector.py;共享身份边界:device-app/runtime_identity.py。不把原生 REST 错称为 E2B SDK。Device VM 与本机两种运行方式使用同一份应用容器/源码。

ACA 的执行与回收

  1. 服务端生成私有会话 identifier,绑定设备任务句柄。
  2. 上传 CSV 到 /mnt/data,原生 executions API 统计 value 列。
  3. 同一 identifier 再次执行,验证 Python 变量保留。
  4. 导出、列出并下载 result.json,再调用 DELETE session。
  5. 设备校验 SHA-256,保存文件与便签。

池禁止出站网络;300 秒空闲回收仅作删除失败时的兜底。它不是普通 Container App,也不是 Foundry Hosted Agent。

Terraform 与应用发布分离

参考环境的分文件结构:provider、variables、network、virtual-machine、session-pool、iam、outputs,以及 00-init / 01-plan / 02-apply。基础设施在同一 Terraform root 创建。

.venv/bin/python scripts/deploy_lab.py

应用源码仍在独立 device-app;ACR 构建 amd64 镜像并固定摘要,Terraform VM 扩展只部署镜像和配置。不复制安全豁免标签,也不通过 shell 跟踪输出秘密。