动态插件是您可以为任何支持的传输节点(如 ESXi 主机)创建的自定义插件,用于检查节点的运行状况。

可以在运行时安装动态插件。它执行以下功能:

  • 为受影响的传输节点写入系统日志。
  • 通过 run_command() 函数执行命令或 CLI。
  • 读取现有衡量指标。
  • 将数据导出到 Wavefront(仅在 VMware Cloud 环境中)。

如果具有管理 NSX 方面的专业知识,您可以创建动态插件。也可以由 VMware 支持人员创建动态插件。创建的插件必须提交到 GIT 存储库以进行验证。在提交之前,必须对插件进行检查和测试。

安全管理

在插件检查和测试完成后,将提交插件、其测试结果和代码更改以在 GIT 存储库中进行验证。请使用以下 GIT 存储库详细信息:

  • GitLab:https://gitlab.eng.vmware.com/core-build/nsbu-sha-plugin
  • 产品:nsx-sha-plugins
  • GIT 检查支持:由 VMware 团队执行

在验证并批准插件后,插件将提交到 GIT 存储库。在创建新的内部版本时,将构建所有动态插件并对其进行签名。每个插件是单独打包和签名的。您可以从发布的内部版本文件中获取所需的插件。

在将签名的插件上载到管理平面后,管理平面使用公钥验证签名并确认该插件有效。在验证插件后,插件文件将通过管理平面和中央控制平面 (Central Control Plane, CCP) 之间以及 CCP 和主机之间的安全通道推送到目标主机。

如果重新启动系统运行状况代理 (System Health Agent, SHA) 实例,它将再次从管理平面中获取插件文件。由于所有文件是通过安全通道发布的,并且不使用临时文件,因此,避免了黑客可以修改脚本的风险。

另外,为了防止有害代码的风险,SHA 在执行脚本之前使用 RestrictedPython 检查插件 Python 脚本。

版本管理

在更高版本的 NSX 中可能不支持插件基于的命令或工具,因此,每个自定义插件必须在 manifest.yml 文件中定义支持的 NSX 版本。对于所有支持的版本,版本应为 REGEX 字符串。主机端的 SHA 检查自定义插件的版本,并仅运行与 REGEX 匹配的版本。

建议的版本管理策略是:
  • 定义主要版本的支持 NSX 版本。

    考虑到大多数命令和工具在同一主要版本的次要版本之间不会发生变化,建议使用以下方法为所有次要版本定义版本。

    例如,

    version: ^2\.5\.[0-9.]+ <== The custom plugin supporting all NSX 2.5 releases

  • 在发布新的主要版本时,应检查所有提交的动态插件。
  • 在相关的命令或工具发生变化时,插件编写者必须更新脚本。

安装动态插件

在其中安装插件的传输节点或 Edge 节点必须至少具有 30 MB 内存空间。另请注意,您最多只能安装 10 个插件。在插件数达到 10 个后,任何其他的插件安装将会失败。

要安装插件,请执行以下任务:
  1. 在 GIT 存储库中创建动态插件文件。有关插件文件的详细信息,请参见动态插件文件一节。
  2. 触发 GIT 存储库中的产品内部版本以生成动态插件文件的压缩包,然后下载该包。
  3. 将以下 API 与 POST 方法一起使用以创建动态插件。

    https://<manager_ip>/api/v1/systemhealth/plugins

  4. 将以下 API 与 POST 方法一起使用以将插件压缩包上载到管理平面中。管理平面提取上载的文件,并执行所需的验证。

    /systemhealth/plugins/<plugin-id>/files/<file-name>/data

    注: 插件压缩文件的最大大小为 500K。
  5. 将以下 API 与 POST 方法一起使用以创建节点组,并将所需的传输节点作为成员。

    /<manager_ip>/api/v1/ns-groups

  6. 使用以下 API 创建新的服务配置,以将插件配置文件应用于节点组。服务配置框架将插件内容发送到节点组。

    https://<manager_ip>/api/v1/service-configs

有关 API 的详细信息,请参见NSX API 指南》文档。

获取插件状态

在动态插件运行后,它通过现有的消息通道自动将状态上载到管理平面中。管理平面汇总插件状态信息,并将其存储到数据库中。要获取每个节点上的所有插件的状态,请将以下 API 与 GET 方法一起使用。

https://<manager_ip>/api/v1/systemhealth/plugins/status/<transport_node_id>

请求示例:

GET https://<manager_ip>/api/v1/systemhealth/plugins/status/a257b981-1a1c-4b95-b16c-8646

响应示例:
{
 "result_count":1,
 "results": [
 {
 "id": "72e1bd4b-6df6-42d0-9c59-a1c31312c9f1",
 "name": "health-check-compute",
 "status": "NORMAL",
 "detail": ""
 }
 ]
 }

卸载动态插件

要卸载插件,请使用以下 API 移除服务配置。

https://<manager_ip>/api/v1/service-configs/<service_config_id>

用于管理插件的其他 API

下表列出了用于管理动态插件的 API。有关 API 的详细信息,请参见NSX API 指南》文档。

任务 方法 API
删除插件 DELETE /systemhealth/plugins/<plugin-id>
创建系统运行状况配置文件 POST /systemhealth/profiles

监视插件状态

GET /systemhealth/plugins/status/<node-id>
启用插件 启用插件过程分为两个步骤,如下所示:
  1. 使用以下 API 将 enabled 属性设置为 truefalse

    https://<manager_ip>/api/v1/systemhealth/profiles/

  2. 使用以下 API 将 SHA 配置文件应用于 NS 组。

    https://<manager_ip>/api/v1/service-configs

更改插件间隔 POST

更改插件间隔过程为两个步骤,如下所示:

  1. 使用以下 API 设置 config 属性。

    https://<manager_ip>/api/v1/systemhealth/profiles/

  2. 使用以下 API 将 SHA 配置文件应用于 NS 组。

    https://<manager_ip>/api/v1/service-configs

动态插件文件

动态插件包含以下文件:

  • 安装规范文件
    安装规范文件 manifest.yml 包含系统运行状况代理的以下信息:
    • 插件结构
    • 限制(如果有)
    • 如何安装和使用插件
    • 运行状况检查脚本的安全限制。例如,脚本具有的权限以及脚本可以访问的文件。
    下表列出了 manifest.yml 文件中指定的字段。
    名称 描述 必需/可选 示例
    classes 指定插件脚本中所需的类。

    必须使用以下格式指定类:'<module_name>.<class_name>'

    可选 classes: ['datetime.datetime','datetime.date']
    modules 指定插件脚本中所需的模块。 可选 modules: ['random', 'math']
    plugin

    指定插件结构,如下所示:

    config: 配置文件名

    script: 脚本文件名

    必需

    plugin:

    config: plugin.cfg.yml

    script: plugin.py

    version 指定可以在其中安装该插件的 NSX 版本。 必需 version: '^3\.1\.[0-9.]+'
    node_type

    指定可以在其中安装该插件的 NSX 节点类型。可用的节点类型为:

    • nsx-esx
    • nsx-bms
    • nsx-edge
    必需 node_type: ['nsx-esx']
    metrics 指定可以在插件脚本中使用的衡量指标。 可选 metrics: ['nsx.host.host-metrics']
    precondition

    指定插件的前提条件。可用的前提条件是 Wavefront。

    注: 该字段仅适用于 VMware Cloud (VMC) 环境。
    可选 precondition: ['wavefront']
    不要使用以下内置模块:
    • os

    • subprocess

    • sys

    • multiprocessing

    • importlib

    下表列出了在代替相应模块的内置函数时必须使用的接口。这些接口是系统提供的。您可以直接使用它们,而无需在 manifest.yml 文件中指定其模块/类。
    模块 内置函数 替代接口
    datetime datetime.date.strftime(self, fmt)

    datetime_date_strftime(dt, fmt)

    :param dt: 日期实例

    :param fmt: 格式字符串

    datetime datetime.date.today() datetime_date_today()
    sys sys.getrecursionlimit() sys_getrecursionlimit()
    sys sys.getrefcount(object) sys_getrefcount(object)
    sys sys.getsizeof(object, default) sys_getsizeof(object, default)
    sys sys.maxsize sys_maxsize
    sys sys.path sys_path
    manifest.yml 文件示例。
    # specify classes needed in plugin script 
    classes: ['datetime.datetime','datetime.date']
    # specify modules needed in plugin script 
    modules: ['random', 'math']
    # plugin structure
    plugin:
     config: plugin.cfg.yml
     script: plugin.py
    # specify nsx versions on which this plugin can be installed
    version: '^3\.1\.[0-9.]+'
    # specify nsx node type where this plugin can be installed
    node_type: ['nsx-esx']
    # specify metrics which can be consumed in plugin script
    metrics: ['nsx.host.host-metrics']
    # specify precondition for plugin 
    precondition: ['wavefront']
  • 默认配置文件

    默认配置文件文件 plugin.cfg.yml 用于配置插件行为,例如运行状况检查脚本的执行频率。要更改默认配置,您可以为特定的动态插件创建 SHA 配置文件,并使用管理平面到 CCP 到 NestDB 通道通过 NS 组将其应用于传输节点。

    下表列出了 plugin.cfg.yml 文件中指定的字段。
    名称 描述 必需/可选 示例
    CHECK_INTERVAL 指定插件脚本执行的默认间隔(以秒为单位)。 必需 CHECK_INTERVAL: 20
    ENABLE 指定是否默认启用插件。 必需 ENABLE: true
    plugin.cfg.yml 文件示例。
    # Required field - default interval (unit: second) between plugin script executions.
    CHECK_INTERVAL: 20
    
    # Required field - whether plugin is enabled by default
    ENABLE: true
    
    # Plugin user can add other fields as below if needed to control plugin script logic.
    EXPORT_DATA: true
  • 运行状况检查脚本

    运行状况检查脚本文件 plugin.py 包含一个 Python 脚本,用于检查传输节点的运行状况。

    下表列出了可以在 plugin.py 文件中使用的系统定义的变量和函数以及可以读取的数据。
    变量/数据/函数 描述 类型 示例
    logger 将日志信息写入到 syslog 中。可以直接在插件脚本中使用现有的系统定义变量 logger。

    输出日志以插件名称和 ID 为前缀,如以下示例输出中所示。

    2020-10-28T10:47:43Z nsx-sha: NSX 2101378 - [nsx@6876 comp="nsx-esx" subcomp="nsx-sha" username="root" level="INFO"] [name:hl-esx-002-04][id:a3eb14f1-d185-4bc7-bfaa-6cf888bbeb22] dynamic plugin - not export data

    变量 logger.info("this is a demo log")
    data_store

    现有的系统定义字典,用于获取系统提供的数据。例如,profile、metric 和 host_id。

    变量 profile = data_store['profile']
    profile

    配置文件数据是通过从 data_store 中读取的默认配置文件 (plugin.cfg.yml) 或有效 SHA 配置文件(用户通过管理器 API 应用的)解析的字典。它具有以下格式:

    {'ENABLE': True, 'CHECK_INTERVAL': 20, 'EXPORT_DATA': True}

    数据 profile = data_store['profile']
    metric

    衡量指标是从 data_store 中读取的字典,它具有“value”和“timestamp”。它具有以下格式:

    data_store['metrics'][<metric_name>]

    其中,

    第一个键必须是“metrics”。

    第二个键是现有的衡量指标名称。

    数据

    metric = data_store['metrics']['nsx.host.host-metrics']

    metric is: {

    ‘value’:XXX, <== the collected data of the metric

    ‘timestamp’: XXX <== timestamp of the data collected

    }

    注意:第一次运行插件可能不会返回衡量指标,因为衡量指标当前是在插件运行时异步收集的,因此,在第一次运行插件时可能尚未收集衡量指标。

    host_id host_id 是从 data_store 中读取的 uuid.UUID 类实例。 数据 host_id = data_store['host_id']
    run_command 该函数使用列表格式运行命令。它具有以下格式:

    run_command(cmd, timeout=4)

    其中,

    • cmd:要执行的命令。必须采用列表格式,如示例中所示。
    • timeout:等待命令结果的超时时间。默认超时为 4 秒。设置的超时不应大于 20 秒。

    该函数返回命令执行结果。

    函数

    cmd = ['nsxdp-cli', 'ipfix', 'settings', 'granular', 'get', '--dvs-alias', 'nsxvswitch', '--dvport=dafa09ca-33ed-4e04-ae3d-1c53305d5fe6']

    res = run_command(cmd)

    Exportdata 该函数将数据导出到 Wavefront。当前,动态插件仅支持导出到 Wavefront。
    注: 该函数仅适用于 VMware Cloud (VMC) 环境。
    它具有以下格式:

    Exportdata: ExportData(data={}, exports=[], source=host_uuid)

    其中,

    data:要导出的数据;数据应采用字典格式,如示例中所示。

    exports:要导出的目标列表。在 HL 中,仅在目标中支持 Wavefront。它是必需的。

    source:要导出的源字符串。它仅用于 Wavefront 目标。它是可选的,默认值为 NSX host_uuid。

    该函数不返回任何值。

    函数 Exportdata(data={'esx.plugin.stats': {'stats': {'gc-esx-001': data}}}, exports=['wavefront'])
    plugin.py 文件示例。
    def report_test_data(edge_service_status):
        if edge_service_status == 'running':
            data = 2
        else:
            data = 3
        
        # examples on how to report data to wavefront.
        Exportdata(data={'esx.plugin.stats': {'stats': {'esx-dynamic-plugin-001': data}}}, exports=['wavefront'])
    
    
    def run():
        # examples on how to write log.
        logger.debug("this is a debug message!")
        logger.info("this is a demo message!")
        
        # examples on how to use specified module in manifest. Take 'random' as an example.
        s_res = random.randint(1,10)
        logger.warn("random.randint(1,10)=%s", s_res)
        
        # examples on how to use specified class in manifest. Take 'datetime' and 'date' as an example.
        logger.info('date.ctime(datetime.now()):{}'.format(date.ctime(datetime.now())))
        
        # examples on how to run cmd via interface run_command
        cmd = ['nsxdp-cli', 'ipfix', 'settings', 'granular', 'get', '--dvs-alias', 'nsxvswitch', '--dvport=dafa09ca-33ed-4e04-ae3d-1c53305d5fe6']
        c_res = run_command(cmd)
        logger.error("run_command(cmd) res:%s", c_res)
        
        # examples on how to read existing metrics from data_store
        m_res = data_store['metrics']['nsx.host.host-metrics']
        # examples on how to read effective profile from data_store
        profile = data_store['profile']
        logger.error("data_store['metrics']['nsx.host.host-metrics']:%s, profile:%s", m_res, profile)
        
        # examples on how to read host_id from data_store
        host_id = data_store['host_id']
        logger.info('host_id:{}'.format(host_id))
    
        if profile['EXPORT_DATA']:
            report_test_data('running')
            logger.info("dynamic plugin - exported data to wavefront")
        else:
            logger.info("dynamic plugin - not export data ")
        
        # examples on how to use substitute interfaces for sys. 
        logger.info("sys_path:{}".format(sys_path))
        logger.info("sys_getsizeof(1):{}".format(sys_getsizeof(1)))
        logger.info("sys_getrefcount(cmd):{}".format(sys_getrefcount(cmd)))
        logger.info("sys_maxsize:{}".format(sys_maxsize))
        logger.info("sys_getrecursionlimit():{}".format(sys_getrecursionlimit()))
        
        # examples on how to use substitute interfaces for datetime.
        today = datetime_date_today()
        logger.info("datetime today:{}".format(today))
        logger.info("datetime_date_strftime now:{}".format(datetime_date_strftime(datetime.now(), '%H:%M')))
        logger.info('date.ctime(today):{}'.format(date.ctime(today)))
    
    
    run()