mcpField
C
cmdb-patterns

cmdb-patterns

github.com/serac-labs云与 DevOps7768 次安装9 周前更新
代码仓库

关于 cmdb-patterns

面向 ServiceNow CMDB 管理的技能,介绍 CI 类别、关系、影响分析和数据质量检查。重点说明通过 IRE 识别并更新 CI,以减少重复记录并保留数据来源。

主要功能

  1. 01介绍 CI 类别与关键表
  2. 02创建 CI 并维护关系
  3. 03分析 CI 的上下游影响
  4. 04检测孤立与过期 CI
  5. 05通过 IRE 识别和协调 CI

适用场景

  1. 01提交发现的服务器及其应用关系
  2. 02评估 CI 故障影响的业务服务
  3. 03检查 CMDB 中孤立或过期的 CI

MCP 相关

文档列出用于搜索、创建和查询 CI、建立关系、分析影响及触发发现的 MCP 工具。它强调使用 snow_cmdb_identify_reconcile 通过 IRE 写入,避免重复并记录数据来源。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/serac-labs/serac --skill cmdb-patterns

安装后在 Claude Code 中输入/cmdb-patterns确认已加载。

详细介绍

ServiceNow 的 CMDB 模式

配置管理数据库(CMDB)是 ServiceNow ITSM 的基础,用于跟踪所有配置项(CI)及其关系。

CMDB 架构

CI 类层次结构
cmdb (Base)
└── cmdb_ci (Configuration Item)
    ├── cmdb_ci_computer
    │   ├── cmdb_ci_server
    │   │   ├── cmdb_ci_linux_server
    │   │   ├── cmdb_ci_win_server
    │   │   └── cmdb_ci_unix_server
    │   └── cmdb_ci_pc_hardware
    ├── cmdb_ci_service
    │   ├── cmdb_ci_service_business
    │   ├── cmdb_ci_service_technical
    │   └── cmdb_ci_service_auto
    │       └── cmdb_ci_service_discovered
    ├── cmdb_ci_appl
    │   ├── cmdb_ci_app_server
    │   └── cmdb_ci_db_instance
    └── cmdb_ci_network_gear
        ├── cmdb_ci_netgear
        └── cmdb_ci_lb
关键 CI 表
表用途关键字段
cmdb_ciCI 基础表name, sys_class_name, operational_status
cmdb_ci_server服务器ip_address, os, cpu_count, ram
cmdb_ci_service服务(基类 — 业务服务为 cmdb_ci_service_business)service_classification, busines_criticality
cmdb_ci_appl应用程序version, install_directory
cmdb_rel_ciCI 关系parent, child, type

创建配置项

创建基本 CI(ES5)
// Create a new server CI
var ci = new GlideRecord("cmdb_ci_server")
ci.initialize()
ci.setValue("name", "PROD-WEB-001")
ci.setValue("ip_address", "10.0.1.100")
ci.setValue("os", "Linux Red Hat")
ci.setValue("os_version", "8.5")
ci.setValue("cpu_count", 8)
ci.setValue("ram", 32768)
ci.setValue("operational_status", 1) // Operational
ci.setValue("install_status", 1) // Installed
ci.setValue("used_for", "Production")
ci.setValue("owned_by", "sys_id_of_owner")
ci.setValue("support_group", "sys_id_of_group")
var sysId = ci.insert()
带有发现来源的 CI
// CI from Discovery
var ci = new GlideRecord("cmdb_ci_linux_server")
ci.initialize()
ci.setValue("name", "discovered-server-001")
ci.setValue("discovery_source", "ServiceNow")
ci.setValue("first_discovered", new GlideDateTime())
ci.setValue("last_discovered", new GlideDateTime())
ci.setValue("ip_address", "10.0.2.50")

// Set classification
ci.setValue("classification", "Production")
ci.setValue("environment", "Production")

ci.insert()

CI 关系

关系类型
类型父项 → 子项示例
Runs on::Runs应用 → 服务器ERP 运行于 PROD-DB-01
Depends on::Used by服务 → 应用HR 服务依赖 SAP
Contains::Contained by集群 → 服务器集群包含 Node1
Hosted on::HostsVM → 虚拟机监控程序VM01 托管于 ESX01
Members::Member ofCI → 组服务器是池的成员
创建关系(ES5)
// Create relationship between CIs
function createCIRelationship(parentSysId, childSysId, relationType) {
  // Find relationship type
  var relType = new GlideRecord("cmdb_rel_type")
  relType.addQuery("name", relationType)
  relType.query()

  if (!relType.next()) {
    gs.error("Relationship type not found: " + relationType)
    return null
  }

  // Check if relationship already exists
  var existing = new GlideRecord("cmdb_rel_ci")
  existing.addQuery("parent", parentSysId)
  existing.addQuery("child", childSysId)
  existing.addQuery("type", relType.getUniqueValue())
  existing.query()

  if (existing.next()) {
    gs.info("Relationship already exists")
    return existing.getUniqueValue()
  }

  // Create new relationship
  var rel = new GlideRecord("cmdb_rel_ci")
  rel.initialize()
  rel.setValue("parent", parentSysId)
  rel.setValue("child", childSysId)
  rel.setValue("type", relType.getUniqueValue())
  return rel.insert()
}

// Usage
createCIRelationship(appSysId, serverSysId, "Runs on::Runs")
查询关系
// Find all servers an application runs on
function getAppServers(appSysId) {
  var servers = []

  var rel = new GlideRecord("cmdb_rel_ci")
  rel.addQuery("parent", appSysId)
  rel.addQuery("type.name", "Runs on::Runs")
  rel.query()

  while (rel.next()) {
    var server = rel.child.getRefRecord()
    servers.push({
      sys_id: server.getUniqueValue(),
      name: server.getValue("name"),
      ip_address: server.getValue("ip_address"),
    })
  }

  return servers
}

// Find all dependencies of a service
function getServiceDependencies(serviceSysId) {
  var deps = []

  var rel = new GlideRecord("cmdb_rel_ci")
  rel.addQuery("parent", serviceSysId)
  rel.addQuery("type.name", "Depends on::Used by")
  rel.query()

  while (rel.next()) {
    deps.push({
      sys_id: rel.child.getUniqueValue(),
      name: rel.child.getDisplayValue(),
      class: rel.child.sys_class_name.toString(),
    })
  }

  return deps
}

影响分析

上游/下游分析
// Get all CIs affected by a CI outage (downstream impact)
function getDownstreamImpact(ciSysId, depth) {
  if (typeof depth === "undefined") depth = 3

  var impacted = []
  var processed = {}

  function traverse(sysId, currentDepth) {
    if (currentDepth > depth || processed[sysId]) return
    processed[sysId] = true

    var rel = new GlideRecord("cmdb_rel_ci")
    rel.addQuery("child", sysId)
    rel.query()

    while (rel.next()) {
      var parentId = rel.parent.toString()
      if (!processed[parentId]) {
        impacted.push({
          sys_id: parentId,
          name: rel.parent.getDisplayValue(),
          depth: currentDepth,
        })
        traverse(parentId, currentDepth + 1)
      }
    }
  }

  traverse(ciSysId, 1)
  return impacted
}

// Get all CIs this CI depends on (upstream dependencies)
function getUpstreamDependencies(ciSysId, depth) {
  if (typeof depth === "undefined") depth = 3

  var dependencies = []
  var processed = {}

  function traverse(sysId, currentDepth) {
    if (currentDepth > depth || processed[sysId]) return
    processed[sysId] = true

    var rel = new GlideRecord("cmdb_rel_ci")
    rel.addQuery("parent", sysId)
    rel.query()

    while (rel.next()) {
      var childId = rel.child.toString()
      if (!processed[childId]) {
        dependencies.push({
          sys_id: childId,
          name: rel.child.getDisplayValue(),
          depth: currentDepth,
        })
        traverse(childId, currentDepth + 1)
      }
    }
  }

  traverse(ciSysId, 1)
  return dependencies
}
业务服务影响
// Find all business services impacted by a CI
function getImpactedServices(ciSysId) {
  var services = []
  var processed = {}

  function findServices(sysId) {
    if (processed[sysId]) return
    processed[sysId] = true

    // Check if this CI is a service
    var ci = new GlideRecord("cmdb_ci")
    if (ci.get(sysId)) {
      if (ci.sys_class_name.toString().indexOf("cmdb_ci_service") === 0) {
        services.push({
          sys_id: sysId,
          name: ci.getValue("name"),
          criticality: ci.getValue("busines_criticality"),
        })
      }
    }

    // Traverse upstream
    var rel = new GlideRecord("cmdb_rel_ci")
    rel.addQuery("child", sysId)
    rel.query()

    while (rel.next()) {
      findServices(rel.parent.toString())
    }
  }

  findServices(ciSysId)
  return services
}

CMDB 健康状况与数据质量

检测孤立 CI
// Find CIs without relationships
function findOrphanCIs(ciClass) {
  var orphans = []

  var ci = new GlideRecord(ciClass || "cmdb_ci")
  ci.addQuery("operational_status", 1) // Operational only
  ci.query()

  while (ci.next()) {
    var sysId = ci.getUniqueValue()

    // Check for any relationships
    var rel = new GlideRecord("cmdb_rel_ci")
    rel.addQuery("parent", sysId).addOrCondition("child", sysId)
    rel.setLimit(1)
    rel.query()

    if (!rel.hasNext()) {
      orphans.push({
        sys_id: sysId,
        name: ci.getValue("name"),
        class: ci.getValue("sys_class_name"),
      })
    }
  }

  return orphans
}
检测过期 CI
// Find CIs not updated by discovery
function findStaleCIs(daysOld) {
  if (typeof daysOld === "undefined") daysOld = 30

  var stale = []
  var cutoff = new GlideDateTime()
  cutoff.addDaysLocalTime(-daysOld)

  var ci = new GlideRecord("cmdb_ci")
  ci.addQuery("operational_status", 1)
  ci.addQuery("last_discovered", "<", cutoff)
  ci.addNotNullQuery("last_discovered")
  ci.query()

  while (ci.next()) {
    stale.push({
      sys_id: ci.getUniqueValue(),
      name: ci.getValue("name"),
      last_discovered: ci.getValue("last_discovered"),
    })
  }

  return stale
}

MCP 工具集成

可用的 CMDB 工具
工具用途
snow_cmdb_identify_reconcile通过 IRE 插入/更新 CI — 写入前先进行识别
snow_create_ci使用正确的类创建新 CI(直接写入类表)
snow_cmdb_search使用筛选条件搜索 CI
snow_create_ci_relationship创建 CI 关系
snow_impact_analysis分析 CI 影响
snow_get_ci_details获取完整的 CI 信息
snow_run_discovery触发发现
通过 IRE 写入 CI,不要先搜索

先搜索 CI,搜索不到再创建,这种模式会让 CMDB 充斥重复项:只要名称、IP 或序列号有任何差异,就无法匹配;而第二次写入也没有记录数据来源。标识与协调引擎正是为正确匹配而设计的。snow_cmdb_identify_reconcile 会将负载提交到 /api/now/identifyreconcile,因此每个类的标识规则都会针对负载运行,已有的 CI 会得到更新而非重复创建,协调规则会决定此数据源能否覆盖各个属性,并将写入记录到来源 [sys_object_source]。

snow_create_ci 和 snow_update_ci 会直接写入类表,跳过上述所有流程。仅当你确定 CMDB 中不存在该 CI,且无需注明其来源时,才使用它们。不要理会名称容易引起误解的 snow_reconcile_ci —— 它会将你的字段 PUT 到基础 cmdb_ci,并原样返回 reconciliation_rule 参数;不会运行任何规则。

一个负载还可以通过 relations 描述其中各项之间的关系,其中 type 是 CI Relationship Type [cmdb_rel_type] 中的名称字段值,parent/child 是 items 中的索引。这样一来,发现主机及其上运行的应用程序的数据源就可以在一次调用中同时提交两者。

dry_run 不会提交任何更改,但权限按工具而非参数控制,因此它仍被视为写入操作:在生产实例上,试运行和实际调用一样,都需要 __confirmProd。

示例工作流
// 1. Dry run first: IRE reports what it would do and commits nothing. On a
//    400-CI payload this is the only way to see the verdict before taking it.
await snow_cmdb_identify_reconcile({
  data_source: "ServiceNow", // must be a choice on cmdb_ci.discovery_source
  dry_run: true,
  items: [
    {
      className: "cmdb_ci_linux_server",
      values: { name: "PROD-WEB-002", ip_address: "10.0.1.101", serial_number: "VMW-42-8A" },
    },
  ],
})
// Read items[0].operation — INSERT, UPDATE, NO_CHANGE, UPDATE_WITH_UPGRADE,
// UPDATE_WITH_DOWNGRADE, UPDATE_WITH_SWITCH or DELETE — and
// items[0].identificationAttempts for the identifier rule that matched.

// 2. Same payload without dry_run commits it. IRE answers HTTP 200 even when
//    individual items fail, so check data.items[i].errors, which the tool
//    surfaces as a failure rather than a success.
const ire = await snow_cmdb_identify_reconcile({
  data_source: "ServiceNow",
  items: [
    {
      className: "cmdb_ci_linux_server",
      values: { name: "PROD-WEB-002", ip_address: "10.0.1.101", serial_number: "VMW-42-8A" },
    },
  ],
})
// sys_id of the CI IRE inserted or matched: ire.data.items[0].sysId

// 3. Create relationship (relationship_type is a cmdb_rel_type sys_id,
//    look it up via snow_query_table on cmdb_rel_type)
await snow_create_ci_relationship({
  parent_ci: appSysId,
  child_ci: serverSysId,
  relationship_type: runsOnRelTypeSysId, // sys_id of "Runs on::Runs"
})

// 4. Impact analysis (the CI argument is ci_id; there is no direction parameter).
//    include_services is left off on purpose: the business_services key it fills
//    is not impact — see the csdm-modeling skill.
await snow_impact_analysis({
  ci_id: serverSysId,
  depth: 3,
  include_services: false,
})

最佳实践

  1. 使用正确的 CI 类 - 始终使用最具体的类(cmdb_ci_linux_server,而不是 cmdb_ci)
  2. 维护关系 - 没有关联关系的 CI 价值有限
  3. 与发现模式保持一致 - 使手动创建的 CI 与发现模式保持一致
  4. 运维状态 - 保持状态最新(Operational、Retired 等)
  5. 唯一标识符 - 使用 serial_number、asset_tag 确保唯一性
  6. 服务映射 - 将 CI 关联到业务服务
  7. 定期清理 - 归档已退役的 CI,移除孤立项

评论

支持 Markdown