0x01 前言

使用 vCloud Director(VCD)时,如果有人在 vCenter(VCSA)里直接删掉了某台 VM,而不是在 VCD 里操作,会遇到一种很麻烦的情况:VCD 里还残留着这台 VM 的对象记录,但 VCD 在 vCenter 里已经找不到对应的 VM,于是这台 VM 在 VCD UI 里显示为 Unknown 状态,所有电源/管理/删除等都无法操作。

更麻烦的是,这个 Unknown VM 会卡住它所在组织 VDC(Org VDC)的删除动作。本文记录如何通过 vCD REST API 定位这样的 Unknown VM、对它的隐藏 vApp执行 undeploy,从而顺利删除该 OVDC。全程只动用 API 和 UI,不直接改数据库,适配 PRD 环境的严格要求。

0x02 问题背景与核心知识点

要删除组织 VDC OVDC-name,但里面有一台 VM vm-name 处于 Unknown 状态,导致 OVDC 删除被卡住。经确认,这台 VM 是被人直接通过 VCSA(vCenter)删除的。VCD 里留着对象,vCenter 里却没有对应实体。

请留意文档:Error: “The requested operation could not be executed on vApp <vapp_name>. Stop the vApp and try again” when deleting standalone VMs in Cloud Director

在处理之前要先理解 VCD 的一个内部机制:standalone VM 在 VCD 里实际有一个自动生成的”隐藏 vApp,这个 vApp 在 UI 里不可见。

这个隐藏 vApp 的命名规则是 <VM名>-<UUID>。当 standalone VM 进入 powered-off 状态时,隐藏 vApp 应该也会自动 powered off。但在某些异常场景,比如本次这种 Unknown 状态,隐藏 vApp 可能仍保持 deployed=true。相当于还挂着虚拟网络、电源状态,这就阻塞了删除操作,报错 Stop the vApp and try again。

官方给出的处理路径:
1. 优先在 UI 尝试 Convert to vApp,把 standalone VM 转成 vApp 后再尝试删除
2. 若 VM 是 Unknown,Convert 按钮通常不可用,此时需用 Cloud Director API 先对隐藏 vApp 执行 undeploy,再删除

本文走的就是第 2 条路径。

0x03 用 API 确认对象结构

用系统管理员 token,走 vCD REST API 一步步定位。

0x03.1 获取 Token

先通过 GET /api/versions 确认本实例支持的最高正式 API 版本。本文实例是 36.2。37.0 在 10.3 里不存在,直接写会报 NOT_ACCEPTABLE。所有请求的 version=36.2 都要与实例一致。

# 建立 API 会话,获取 Access Token,在响应头 X-VMWARE-VCLOUD-ACCESS-TOKEN 中
curl -ks -X POST 'https://vcd-ip/cloudapi/1.0.0/sessions/provider' \
     -H 'accept: application/json;version=36.2' \
     -u 'administrator@system:<密码>'

# 响应头: X-VMWARE-VCLOUD-ACCESS-TOKEN: <VCD_ACCESS_TOKEN>

 

0x03.2 定位组织 VDC

系统管理员要用 adminOrgVdc 查询类型,用 orgVdc 会返回空:

curl -ks -X GET 'https://vcd-ip/api/query?type=adminOrgVdc&format=records&pageSize=50' \
     -H 'accept: application/*+xml;version=36.2' \
     -H 'Authorization: Bearer <VCD_ACCESS_TOKEN>'

 

找到我们的目标:

name=OVDC-name  status=READY
href=https://vcd-ip/api/admin/vdc/034585d9-ba6b-472a-a0af-8f4cff7bb2c1

 

得到 OVDC ID = 034585d9-ba6b-472a-a0af-8f4cff7bb2c1。

0x03.3 查询所有 vApp

curl -ks -X GET 'https://vcd-ip/api/query?type=adminVApp&filter=vdc==034585d9-ba6b-472a-a0af-8f4cff7bb2c1&format=records&pageSize=100' \
     -H 'accept: application/*+xml;version=36.2' \
     -H 'Authorization: Bearer <VCD_ACCESS_TOKEN>'

 

找到我们的目标:

name=vm-name-1eaa7b3b-79e9-4731-9513-d7ed01883644  status=UNKNOWN
href=https://vcd-ip/api/vApp/vapp-9e3037c2-c762-409f-988e-d59a6e9d9ee0

 

得到 vApp ID = vapp-9e3037c2-c762-409f-988e-d59a6e9d9ee0,名称 vm-name-1eaa7b3b-79e9-4731-9513-d7ed01883644。注意这个命名正好是 <VM名>-<UUID>,印证它就是 vm-name 的隐藏 vApp。

0x03.4 查询 vApp 详情

curl -ks -X GET 'https://vcd-ip/api/vApp/vapp-9e3037c2-c762-409f-988e-d59a6e9d9ee0' \
     -H 'accept: application/*+xml;version=36.2' \
     -H 'Authorization: Bearer <VCD_ACCESS_TOKEN>'

 

vApp 详情的关键属性:

<VApp xmlns="http://www.vmware.com/vcloud/v1.5"
      ovfDescriptorUploaded="true" deployed="true" status="10"
      name="vm-name-1eaa7b3b-79e9-4731-9513-d7ed01883644"
      id="urn:vcloud:vapp:9e3037c2-c762-409f-988e-d59a6e9d9ee0"
      href="https://vcd-ip/api/vApp/vapp-9e3037c2-c762-409f-988e-d59a6e9d9ee0"
      type="application/vnd.vmware.vcloud.vApp+xml">
  <Vm needsCustomization="false" deployed="false" status="6"
      name="vm-name"
      id="urn:vcloud:vm:4b449f56-d654-44ea-8f4b-698ab6ab122b"
      href="https://vcd-ip/api/vApp/vm-4b449f56-d654-44ea-8f4b-698ab6ab122b"
      type="application/vnd.vmware.vcloud.vm+xml"/>
</VApp>

 

这个 vm-name-1eaa7b3b-79e9-4731-9513-d7ed01883644 就是 VCD 给 standalone VM 自动生成的隐藏 vApp,里面藏着 vm-name。

vApp  vm-name-1eaa7b3b-79e9-4731-9513-d7ed01883644
        deployed="true"  status=10   ← 仍处于已部署状态,被堵住
        id=urn:vcloud:vapp:9e3037c2-c762-409f-988e-d59a6e9d9ee0
        network=segment-dev-common, owner=david.chanzp
 └─ VM vm-name
        status=6 (unknown),deployed=false
        id=urn:vcloud:vm:4b449f56-d654-44ea-8f4b-698ab6ab122b

 

从 vApp 的 Link 列表能看到可用的 rel=undeploy,说明 vApp 当前处于可 undeploy 的部署状态:

<Link rel="undeploy"
      href="https://vcd-ip/api/vApp/vapp-9e3037c2-c762-409f-988e-d59a6e9d9ee0/action/undeploy"
      type="application/vnd.vmware.vcloud.undeployVAppParams+xml"/>

0x04 对隐藏 vApp 执行 undeploy

确认结构无误后,对 vApp 发起 undeploy 操作,即 undeployPowerAction=powerOff:

curl -ks -i -X POST 'https://vcd-ip/api/vApp/vapp-9e3037c2-c762-409f-988e-d59a6e9d9ee0/action/undeploy' \
     -H 'accept: application/*+xml;version=36.2' \
     -H 'content-type: application/vnd.vmware.vcloud.undeployVAppParams+xml;version=36.2' \
     -H 'Authorization: Bearer <VCD_ACCESS_TOKEN>' \
     --data '<?xml version="1.0" encoding="UTF-8"?>
<UndeployVAppParams xmlns="http://www.vmware.com/vcloud/v1.5">
  <UndeployPowerAction>powerOff</UndeployPowerAction>
</UndeployVAppParams>'

 

注意 undeployPowerAction 必须是 <UndeployPowerAction> 的子元素,不是根元素的属性。写成 undeployPowerAction=”powerOff” 属性会直接返回 400:

cvc-complex-type.3.2.2: Attribute 'undeployPowerAction' is not allowed to appear in element 'UndeployVAppParams'.

 

成功返回 202 Accepted,并生成一个异步 Task:

HTTP/1.1 202 Accepted
Location: https://vcd-ip/api/task/990ab22d-f0bf-46d0-a39f-7c00b8ff5c4c
Content-Type: application/vnd.vmware.vcloud.task+xml;version=36.2

<Task xmlns="http://www.vmware.com/vcloud/v1.5" status="queued" operation="Stopping Virtual Application vm-name-1eaa7b3b-79e9-4731-9513-d7ed01883644(9e3037c2-c762-409f-988e-d59a6e9d9ee0)" operationName="vappUndeployPowerOff" serviceNamespace="com.vmware.vcloud" startTime="2026-08-19T09:32:34.598Z" expiryTime="2026-11-17T09:32:34.598Z" cancelRequested="false" name="task" id="urn:vcloud:task:990ab22d-f0bf-46d0-a39f-7c00b8ff5c4c" href="https://vcd-ip/api/task/990ab22d-f0bf-46d0-a39f-7c00b8ff5c4c" type="application/vnd.vmware.vcloud.task+xml">

 

轮询该 Task:GET /api/task/990ab22d-f0bf-46d0-a39f-7c00b8ff5c4c,直到 status=”success”:

curl -ks -X GET 'https://vcd-ip/api/task/990ab22d-f0bf-46d0-a39f-7c00b8ff5c4c' \
     -H 'accept: application/*+xml;version=36.2' \
     -H 'Authorization: Bearer <VCD_ACCESS_TOKEN>'
# 返回: status="success", operationName="vappUndeployPowerOff"

 

随后 vApp 的 deployed 状态会变化:

| 属性 | undeploy 前 | undeploy 后 |
| --- | --- | --- |
| deployed | true | false |
| status | 10 | 0 |

 

0x05 删除与清理

vApp 已经脱离部署状态后,目标 VM / vApp 就可以删除。本案例中最后通过 VCD GUI 删除 VM,随后 OVDC-name 成功删除。

由于该 VM 对应的 vCenter 对象已经不存在,某些情况下 VCD 删除时仍会报 Invalid virtual machine state(Broadcom KB 371530,标题 “Unable to manage UNKNOWN VM in VMware Cloud Director”)。如果出现这种数据库里的滞留记录连 API 也清不掉的情况,建议需要联系 Broadcom 技术支持,也可以考虑自行修改 VCD 数据库。

如需走 API 删除,对应的删除 API 如下:

  • 从 vApp 中移除 VM:DELETE /api/vApp/vm-4b449f56-d654-44ea-8f4b-698ab6ab122b
  • 删除 vApp 实体:DELETE /api/vApp/vapp-9e3037c2-c762-409f-988e-d59a6e9d9ee0

0x06 结语

这类 Unknown VM 大多源于绕过 VCD 直接在 vCenter 里删 VM,会留下一个 VCD 管不到的滞留对象,还常常伴随一个处于 deployed=true 的隐藏 vApp。归纳几个要点:

  1. 如果没有把握,别直接去数据库删记录。正确做法是走 vCD API / UI
  2. 再三确认 standalone VM 的隐藏 vApp,若删除被挡时,先查 vApp 详情里的 rel=undeploy 链接,对它执行 undeploy 解除部署状态,再尝试删
  3. API 版本要对齐实例。10.3 用 36.2,如写得过高,会返回 NOT_ACCEPTABLE;版本号要放在 Accept 头里,如 application/*+xml;version=36.2,不是单独的头