mcpField
C
cmdb-patterns

cmdb-patterns

github.com/serac-labsクラウド・DevOps7768 回インストール9 週間前に更新
リポジトリ

cmdb-patterns について

ServiceNow の CMDB 管理を扱うスキルです。CI のクラス、関係、影響分析、データ品質の確認を説明し、重複を避けてデータソースを記録できる IRE の利用を重視します。

主な機能

  1. 01CI クラスと主要テーブルの説明
  2. 02CI の作成と関係の管理
  3. 03上流・下流への影響分析
  4. 04孤立 CI と古い CI の検出
  5. 05IRE による CI の識別と照合

活用シーン

  1. 01検出したサーバーとアプリの関係を登録
  2. 02CI 障害が影響するビジネスサービスの評価
  3. 03CMDB 内の孤立 CI や古い CI の確認

MCP 対応

CI の検索・取得、作成、関係の設定、影響分析、Discovery の起動に使う MCP ツールを記載しています。重複を避け、データソースを記録するため、IRE を使う snow_cmdb_identify_reconcile を推奨しています。

インストール

コマンドをコピーしてターミナルで実行します。

$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_ci基本 CI テーブル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_reconcileIRE を通じて CI を挿入/更新します。書き込み前に CI を識別します
snow_create_ci適切なクラスで新しい CI を作成します(クラスのテーブルに直接書き込みます)
snow_cmdb_searchフィルターを使用して CI を検索します
snow_create_ci_relationshipCI の関係を作成します
snow_impact_analysisCI の影響を分析します
snow_get_ci_detailsCI の詳細情報を取得します
snow_run_discoveryディスカバリーを実行します
まず検索するのではなく、IRE を通じて CI を書き込む

CI を検索し、見つからなければ作成する方法は、CMDB を重複で埋め尽くす原因になります。名前、IP、シリアル番号のどれかに差異があれば一致せず、2 回目の書き込みにはデータの出所を示す記録も残りません。Identification and Reconciliation Engine は、適切に照合するための仕組みです。snow_cmdb_identify_reconcile はペイロードを /api/now/identifyreconcile に送信するため、各クラスの識別ルールが適用され、既存の CI が重複作成される代わりに更新されます。また、調整ルールによって、このデータソースが各属性を上書きできるかどうかが決まり、書き込みは Source [sys_object_source] に記録されます。

snow_create_ci と snow_update_ci はクラスのテーブルに直接書き込むため、こうした処理はすべて省略されます。CMDB に存在しないことが確実で、ソースに帰属させる必要もない CI に使用してください。名前に惑わされず、snow_reconcile_ci は使用しないでください。これはフィールドをベースの cmdb_ci に PUT し、reconciliation_rule 引数を変更せずに返すだけで、ルールは実行されません。

1 つのペイロードには、独自のアイテム間の relations も含められます。type は CI Relationship Type [cmdb_rel_type] の名前フィールド値で、parent/child は items 内のインデックスです。これにより、ホストとそのホスト上のアプリケーションを検出したソースが、両方を 1 回の呼び出しで送信できます。

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 ではなく cmdb_ci_linux_server)。
  2. リレーションシップを維持する - リレーションシップのない CI の価値は限られます。
  3. ディスカバリーとの整合性を保つ - 手動で作成した CI をディスカバリーのパターンに合わせます。
  4. 運用ステータスを管理する - ステータスを最新に保ちます(運用中、廃止など)。
  5. 一意の識別子を使用する - 一意性を保つために serial_number、asset_tag を使用します。
  6. サービスマッピングを行う - CI をビジネスサービスに接続します。
  7. 定期的にクリーンアップする - 廃止された CI をアーカイブし、孤立した CI を削除します。

コメント