# 标签系统

> ZStack 中的标签不仅帮助用户聚集资源，也帮助控制软件行为。ZStack 有一套完整的规范，用以定义标签的类别、形式和用法，供用户、插件和第三方软件使用。本文介绍了 ZStack 的标签系统。

- 发布时间: 2015-04-04
- 更新时间: 2015-04-04
- 作者: [张鑫](https://github.com/zxwing)
- 分类: 技术分享
- 标签: 核心架构, 标签系统, 用户标签, 系统标签, 元编程, 资源标签, 元数据, ZStack, 查询 API, 第三方集成, 数据库表结构

> *本文原发布于 [ZStack Blog](http://zstack.org/blog/tag.html)。
> ZSvirt 构建于 ZStack 之上，继承了以下描述的全部架构特性。
> 理解这些基础设计，有助于更深入地认识 ZSvirt 的性能优势与技术原理。*

*ZStack 中的标签不仅帮助用户聚集资源，也帮助控制软件行为。ZStack 有一套完整的规范，用以定义标签的类别、形式和用法。除了用户外，插件也可以创建自己的标签，以记录元数据和扩展已有的资源属性；通过这些手段，标签可以帮助插件引入新的特性，而不需要改变 ZStack 的数据库结构，消除了软件升级时对数据库迁移的需求。*

## 动机

随着云中资源的不断增长，用户可能会想要有一种方式，使用人类可读的标签去分组相似的资源。举个例子，所有 Web 服务器的虚拟机都可以有一个标签 `web-tier-vm`，这样就能从 UI 和 CLI 把它们作为一个组来获取。对于 IaaS 本身，预先定义的业务逻辑也许从来都不能满足用户的需求。以创建虚拟机为例，默认的选择目标主机的算法是从主机池中随机选择一个，但用户可能需要各种各样的算法来适应它们的使用场景，比如说选择内存超过 8G 的主机、选择拥有 [SR-IOV](http://en.wikipedia.org/wiki/X86_virtualization#PCI-SIG_Single_Root_I.2FO_Virtualization_.28SR-IOV.29) 硬件的主机，或者选择一个已经运行着当前用户的虚拟机的主机。IaaS 软件几乎不可能为所有无止境的、不可预知的需求提供单独的 API，必须有一种机制允许基础 API（如 APICreateVmInstanceMsg）携带额外信息。

根据各自的业务逻辑，插件可以选择是否创建数据库表。比如，Open vSwitch L2 Network 插件由于需要创建一种新类型的资源，可能需要添加一张新表；然而，一个允许主机保留内存的插件可能不需要添加新表，而仅需在主机上附加一点数据。如果 IaaS 软件没有为插件提供附加数据的方式，它们将开始创建新的、琐碎的表，或通过添加列来修改现有的表，导致软件升级时数据库迁移的难处理局面。

最后，对于建立在 ZStack 之上的第三方软件，允许它们将信息存储到 ZStack 的数据库可以避免数据完整性问题，并使它们能够使用 ZStack 完整的查询 API。

## 问题

大多数 IaaS 软件都有标签的概念。然而，它们并不是都为不同场景定义了一个详尽的标签规范。例如，一些 IaaS 使用标签是为了让用户聚合资源，一些 IaaS 是为了内部业务逻辑。ZStack 则为不同场景下标签的每一个层面都精心设计了规范。

## 标签系统

在 ZStack 中，标签本质上是携带了少量资源相关信息的字符串。一个标签通常由以下几个字段组成：

| 字段 | 描述 |
|------|------|
| uuid | 标签的 UUID |
| resourceUuid | 标签所关联的资源的 UUID |
| resourceType | 标签所关联的资源的类型 |
| tag | 一个包含了有意义信息的字符串 |
| type | 标签类型：System（系统）或者 User（用户） |

在标签方面，ZStack 和其他 IaaS 软件的本质区别是：ZStack 将标签分为两类——**用户（User）**和**系统（System）**。

### 1. 用户标签

用户标签，顾名思义，是用户为资源分组而创建的标签。例如，通过标签 `apache2-http` 将安装了 Apache2 HTTP 服务器的虚拟机分组，这样用户就可以通过查询 API（使用标签 `apache2-http` 作为查询条件）获取这些虚拟机：

```
QueryVmInstance __userTag__=apache2-http
```

> **备注**：详见[查询 API](https://www.zstack.io/help/dev_manual/dev_guide/v4/1.9.html)。

这是最常见的标签使用方法。一个资源可以和多个标签相关联，并被划分到不同的逻辑组。

[图片可在规范 HTML 页面中查看: 用户标签](https://zsvirt.io/zh/blog/tag-system/)

用户标签也可以和系统标签一起使用来控制 ZStack 的行为。例如，如果在一个主存储上创建了用户标签 `SSD`，那么一个系统标签可以指导 ZStack 只在带用户标签 `SSD` 的主存储上创建 VM 的根云盘。在这种情况下，用户标签更像是用户输入的资源元数据。我们很快就会看到，插件也可以使用系统标签创建资源的元数据。

### 2. 系统标签

不像用户标签那样可以由用户在任意时间以任意值创建，系统标签有固定的值格式，并且由 ZStack 的编排服务和插件预先定义，可以在以下场景被使用：

#### 2.1 元数据

插件可以使用系统标签来记录资源的元数据。例如，主机的数据库表中没有列去记录诸如 hypervisor 版本、hypervisor SDK 版本这样的元数据；然而，衍生的主机插件（例如 KVM 主机插件）可能需要这些元数据来确定当前的虚拟机管理程序是否支持某些特性；例如，KVM 是否支持在线快照是由 libvirt 和 QEMU 版本决定的。在 ZStack 中，当连接到后端主机时，KVM 主机插件将 OS 版本、libvirt 版本、QEMU 版本和 qemu-img 工具的版本作为系统标签保存。

```
QuerySystemTag fields=tag resourceUuid=d07066c4de02404a948772e131139eb4


{
  "inventories": [
      {
          "tag": "capability:liveSnapshot"
      },
      {
          "tag": "qemu-img::version::2.0.0"
      },
      {
          "tag": "os::version::14.04"
      },
      {
          "tag": "libvirt::version::1.2.2"
      },
      {
          "tag": "os::release::trusty"
      },
      {
          "tag": "os::distribution::Ubuntu"
      }
  ],
  "success": true
}
```

#### 2.2 资源属性

插件也可以使用系统标签将新属性添加到资源中。例如，虚拟机的数据库表结构中没有列来记录在分配虚拟机网卡时该使用什么 IP 分配算法。这种额外的属性可以用系统标签实现。插件可以创建的系统标签的数量没有限制，附属插件可以利用这一点，避免去动数据库表结构。

> **数据库表结构与系统标签**：因为数据库表结构和系统标签都可以定义资源属性，有时会难以决定一个属性应该是数据库表结构中的一列，还是单独一张表中的系统标签。修改现有的数据库表结构以添加新列，通常需要进行数据库迁移，这是 IaaS 软件升级的一个主要痛点，所以开发者可能更倾向于用系统标签来代表新属性。然而，滥用系统标签是一种错误的编程方式。按照 ZStack 的约定，只应该使用系统标签的形式来引入非固有的资源属性；系统标签并不是用来拯救设计得很糟糕的数据库表结构的。例如，如果 VM 的数据库表结构缺失集群 UUID（虽然不会发生），即使需要进行数据库迁移也必须把它补充回来；但由用户为私人用途创建的插件所引入的部门 ID 则应该作为一个系统标签实现。这种权衡有时候并不容易，我们会严格审视任何数据库表结构的变化。

#### 2.3 元编程

系统标签也可以标注资源以影响 ZStack 的执行流，这在某种程度上类似于[元编程（Metaprogramming）](http://en.wikipedia.org/wiki/Metaprogramming)。例如，管理员可以在一个 KVM 主机上创建系统标签 `reservedMemory::1G`，提示 ZStack 的主机分配器从该主机的可用内存中保留 1G 内存；如果管理员改变主意，他可以通过删除该标签来回收这 1G 内存。有很多类似的系统标签。

例如，在*用户标签*一节中，我们提到了同时使用用户标签 `SSD` 和系统标签来为 VM 的根云盘指定主存储。那个系统标签叫 `primaryStorage::allocator::userTag::{tag}::required`；如果在某个实例规格上创建了 `primaryStorage::allocator::userTag::SSD::required`，那么从该实例规格创建的 VM 的根云盘，将只会被分配到带用户标签 `SSD` 的主存储上。有许多所谓的"解释点（interpreting points）"会在执行过程中寻找特定的系统标签，从而改变代码的默认行为。

#### 2.4 第三方软件集成

建立在 ZStack 之上的第三方软件可以使用系统标签，在 ZStack 的数据库中存储与资源关联的信息，这能有效避免第三方软件数据库和 ZStack 数据库之间的数据不一致性。例如，一个私有软件可能需要记录虚拟机的部门 ID，以审计每个部门 IT 资源的使用情况。这个功能通常由一个私有数据库完成，并迫使私有软件跟踪虚拟机的生命周期，因为它需要在 VM 被创建或销毁时去更新自己的数据库，否则数据将无法正确反映真实情况。

有了系统标签的帮助，私有软件可以使用系统标签（例如 `audit::departmentId::{id}`）将信息存储在 ZStack 的数据库中，把管理部门 ID 生命周期的责任转移给 ZStack。当一个 VM 被销毁时，它的部门 ID（例如 `audit::departmentId::1`）将在删除该 VM 记录的同一个数据库事务中被自动删除。此外，私有软件可以用常规查询 API 按部门 ID 检索虚拟机：

```
QueryVmInstance fields=uuid __sysTag__=audit::departmentId::1
```

> **备注**：在当前 ZStack 版本（0.6）中，我们还没有开放允许定义任意系统标签的接口，所有的系统标签都是预先定义的。我们计划在下一个版本中开放这个接口，用户定义的系统标签可以在创建的时候使用一些允许的前缀，例如 `3rd::`。

## 与其他组件的关系

标签系统是 ZStack 的核心组件之一；它不仅拥有独立的 API 和服务，还与其他核心组件无缝集成。用户可以在资源创建时或在资源创建后创建标签。ZStack 所有创建型 API 支持两个固有参数：`userTags` 和 `systemTags`，通过它们传递的标签将随着资源一起创建。例如：

```
CreateVmInstance name=testTag systemTags=hostname::web-server-1 l3NetworkUuids=6572ce44c3f6422d8063b0fb262cbc62
instanceOfferingUuid=04b5419ca3134885be90a48e372d3895 imageUuid=f1205825ec405cd3f2d259730d47d1d8
```

如果资源已经存在，用户可以使用标签 API 来创建或删除标签：

```
CreateUserTag resourceType=VmInstanceVO resourceUuid=613af3fe005914c1643a15c36fd578c6 tag=web

DeleteTag uuid=596070a6276746edbf0f54ef721f654e
```

当资源被删除时，与资源相关联的标签将被自动删除。

资源可以通过两个特殊的查询条件进行查询：`__userTag__` 和 `__systemTag__`：

```
QueryVmInstance __userTag__=web zoneUuid=04b5419ca3134885be90a48e372d3895

QueryHost __systemTag__=capability:liveSnapshot
```

也有专门用于分类的查询 API：

```
QueryUserTag resourceUuid=0cd1ef8c9b9e0ba82e0cc9cc17226a26 tag~=web-server-%

QuerySystemTag resourceUuid=50fcc61947f7494db69436ebbbefda34
```

## 总结

在这篇文章中，我们展示了 ZStack 的标签系统。通过这个系统，用户、插件和第三方软件可以用各种各样的方式使用标签，而不需要改变代码和数据库表结构。这是又一个基石，使得 ZStack 在快速进化为一个成熟的、完整的云计算解决方案的同时，又能保持核心编排的强壮稳定。

---

## ZStack 核心架构系列

- [第一部分：异步架构](https://zsvirt.io/zh/blog/asynchronous-architecture/)
- [第二部分：无状态服务](https://zsvirt.io/zh/blog/stateless-clustering/)
- [第三部分：无锁架构](https://zsvirt.io/zh/blog/lock-free-architecture/)
- [工作流引擎](https://zsvirt.io/zh/blog/workflow-engine/)
- [进程内微服务架构](https://zsvirt.io/zh/blog/in-process-microservices/)
- [通用插件系统](https://zsvirt.io/zh/blog/plugin-system/)
- [标签系统](https://zsvirt.io/zh/blog/tag-system/)

## 官方链接


- [规范 HTML 页面](https://zsvirt.io/zh/blog/tag-system/)
- [在 GitHub 讨论](https://github.com/ZSvirt/zsvirt/discussions)
