
简介基于CMDB的资产配置管理系统是一套面向IT运维与系统管理人员的综合平台源码用于解决企业IT资产信息分散、配置变更难追踪等问题。系统覆盖资产自动发现、配置记录、变更审查、资产调配与审计报告等核心环节并强调访问控制与敏感数据加密等安全实践。资源包共613个文件以Vue与JavaScript前端组件为主辅以CSS样式、SVG图标、HTML页面以及少量Python脚本和配置文件整体约7.33MB目录结构清晰适合学习CMDB功能设计与前后端集成。目前已有40人学习浏览通过源码可了解从资产扫描到配置展示、变更管理的完整实现路径并可结合配置示例学习部署要点。这套源码对于希望快速搭建CMDB原型或研究IT资产管理系统的开发者是一份具体可参考的工程样例。1. 基于CMDB的资产配置管理系统IT资产管理的第一份准确台账把资产台账从 Excel 搬进 CMDB很多团队做到一半就停了因为发现这不过是一个能多人编辑的在线表格。基于 CMDB 的资产配置管理系统真正解决的不是录入而是三类问题配置项之间的关系说不清、变更之后底账没人更新、审计时拿不出一份可信的历史快照。这套系统的核心是一套配置项模型和自动校验链路界面反而最不重要。如果你手里拿到的是 j类系统的源码包不管是自己团队写的还是开源项目再封装的解压之后先别急着跑起来先看懂它的 CI 模型、采集器和校验任务。新手能照着它把服务器、中间件、数据库实例的台账建起来熟手能拿它做变更影响分析和配置漂移巡检。下面按我落地的顺序来写先把 CMDB 的模型和数据流讲清楚再给表结构和最小实现最后把最容易翻车的几个坑列出来。2. 先建模型再谈代码CI、关系与资产配置的三条数据流2.1 配置项CI不是资产编号定义字段才是第一步在这类资产配置管理系统里CMDB 的管理对象叫配置项Configuration Item简称 CI。一台物理服务器、一个虚拟机、一个 MySQL 实例、一套 Nginx 配置都可以是一个 CI。很多刚接触的人以为 CI 就是给资产编个号、填个 IP这是第一个误区编号只是主键真正决定系统价值的是 CI 的类型、属性和来源。我一般会在建模阶段先回答四个问题这个 CI 类型的唯一标识是什么例如物理机用序列号 资产编号虚拟机用实例 UUID 主机名。有哪些属性是“配置事实”哪些是“运维状态”IP、CPU、内存、操作系统是事实运行状态、维护窗口、负责人是状态。这个 CI 的数据源是哪个系统云平台 API、监控系统、Excel 盘点、手工录入。谁对这个 CI 的变更负责变更后是否要触发下游通知。用一张表把常见 CI 类型和字段先列出来再给它们定义必填项比直接开表快得多。CI 类型典型唯一键必填字段常见来源物理服务器资产编号序列号、机房、机架位置、带外 IP资产管理 / 手工盘点虚拟机实例 UUID宿主机、规格、镜像云平台 API / 虚拟化平台数据库实例实例名 端口版本、所属应用、连接串模板DBA 登记 / 自动发现中间件集群名 节点版本、端口、配置目录发布系统 / 配置中心业务应用应用编码负责人、代码仓库、部署单元研发效能平台字段用文本写得很简陋真实系统里每个 CI 类型还要配一个属性模板schema比如“物理服务器”的“内存”如果是可枚举的最好下拉选择而不是手写“64G / 64GB / 64”。这套属性模板就是后面自动采集比对的基准。2.2 三条数据流采集进来、消费出去、变更回写CMDB 资产配置管理系统本质上是一个数据中台要保住数据准确必须在设计之初就把数据流定死。我习惯把它拆成三条。采集流采集器定时从云平台、监控系统、配置中心或 CSV 文件拉取原始数据写入快照表与当前 CI 属性做比对。出现差异就生成一条配置漂移记录不直接改 CI。这里有一个关键点先有快照再有 diff最后才决定是否更新 CI绝不能在采集端直接 UPDATE 线上 CI 表。消费流下游系统发布平台、监控、成本分析通过 API 或数据库视图消费 CMDB 的 CI 属性与关系。消费流要求 CMDB 对外提供的字段语义稳定别今天叫 private_ip、明天叫 internal_ip否则下游跟着返工。我见过不止一个项目因为字段改名导致监控系统的告警关联全部失效。回写流运维人员变更 IP、扩缩容、上下架这些操作发生在真实环境里需要回写到 CMDB。回写要带上操作人、来源工单、时间戳并触发关联视图刷新。三条流对应三个模块采集适配器、消费 API、变更工单入口。一套资产配置管理系统如果只做了采集和展示没有回写通道三个月后必然会重新变成一张过期的表。2.3 模块边界解压源码后先把这五个模块找出来无论是拿到开源的 zip 包还是自己从零写我建议先按五个模块拆解系统避免把逻辑揉在一个“资产列表”页面里。模型管理定义 CI 类型、属性模板、唯一键、必填项和关系约束。采集与导入支持按时拉取poller和按需导入importer输出统一格式的快照数据。校验引擎消费快照跑配置比对、缺失检测和关系完整性校验。消费 API对外提供查询、订阅、变更通知的能力让监控和发布系统读得到数据。审计与视图记录每一次状态变更自动生成拓扑视图和配置看板。拆完模块再去看代码你会发现大部分 CMDB 类项目都逃不出这个骨架。之后照着 2.1 的模型表和 2.2 的数据流设计表结构就能把核心技术点吃透而不是被前端页面带偏。3. 表结构与字段约定把资产配置管理系统的主干落在数据库里3.1 ci_type 表用类型驱动一切表单和校验规则在落地 CMDB 时我倾向于先建 ci_type配置项类型表几乎所有界面和校验逻辑都从这里派生。别为每个 CI 类型单独建表否则加一个中间件类型就要改表结构、改 API、改前端非常累。-- 配置项类型表一张表承载所有CI类型的元数据 CREATE TABLE ci_type ( id BIGINT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(64) NOT NULL COMMENT 类型编码如 server、vm、db_instance, name VARCHAR(128) NOT NULL COMMENT 类型显示名, schema_json JSON NULL COMMENT 属性模板定义字段名、类型、必填、枚举, unique_key VARCHAR(256) NOT NULL COMMENT 唯一键表达式如 instance_uuidhostname, need_relation TINYINT NOT NULL DEFAULT 1 COMMENT 是否需要关系管理, is_active TINYINT NOT NULL DEFAULT 1, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_ci_type_code (code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT配置项类型定义;schema_json 就是一个 JSON 字段避免将属性列拆到几十张表。在 MySQL 8.0 里用 JSON 类型可以直接走函数索引查询配置属性字段时用 JSON_EXTRACT 也够用。max key 的字段 unique_key 说明如何查重物理机用“序列号 资产编号”虚拟机用“实例 UUID 主机名”。需要提醒一点唯一键涉及的字段在录入时不能为空。MySQL 的唯一索引对 NULL 不去重如果 allow 了 instance_uuid 为空的记录同一台机器可能因为没有 UUID 而注册出多条。3.2 ci_instance 与 ci_relation资产数据和关系数据分开接下来是 CI 实例主表。我会把基础字段和属性字段分开基础字段是每个类型都一样的通用列属性字段统一放 JSON这样后端展示时可以根据 ci_type.schema_json 渲染表单。CREATE TABLE ci_instance ( id BIGINT PRIMARY KEY AUTO_INCREMENT, ci_type_code VARCHAR(64) NOT NULL, instance_uuid CHAR(36) NOT NULL COMMENT 系统内生成的实例唯一UUID, attr_json JSON NULL COMMENT 该CI的属性快照, status VARCHAR(32) NOT NULL DEFAULT active COMMENT active/inactive/maintenance/retired, source VARCHAR(64) NOT NULL DEFAULT manual COMMENT cloud_api/monitor/excel/manual, owner VARCHAR(128) NULL, last_seen_at DATETIME NULL COMMENT 最近一次被采集或确认的时间, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_ci_type_uuid (ci_type_code, instance_uuid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT配置项实例主表; CREATE TABLE ci_relation ( id BIGINT PRIMARY KEY AUTO_INCREMENT, source_id BIGINT NOT NULL COMMENT 源CI实例ID, target_id BIGINT NOT NULL COMMENT 目标CI实例ID, rel_type VARCHAR(32) NOT NULL COMMENT depend_on/run_on/include/connect_to, direction TINYINT NOT NULL DEFAULT 1 COMMENT 1顺向-1逆向, extra_json JSON NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_relation (source_id, target_id, rel_type) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT配置项关系表;direction 用数字表示方向可以在关联查询时直接 JOIN 两次即可得到上游和下游无需把关系存两遍。extra_json 可以放依赖的端口、协议等。状态字段里的 retired 是重点资产退役不能用删除否则拓扑图和历史审计会断链。3.3 ci_snapshot 与 ci_change_log给资产配置装上后悔药和黑匣子CMDB 是资产唯一台账所以它必须回答“那一刻配置是什么”。我用两张表来实现ci_snapshot 存每次采集快照ci_change_log 存人工变更记录。CREATE TABLE ci_snapshot ( id BIGINT PRIMARY KEY AUTO_INCREMENT, ci_instance_id BIGINT NOT NULL, snapshot_hash VARCHAR(64) NOT NULL COMMENT 属性JSON的SHA256用于快速判断是否变化, attr_json JSON NOT NULL, collected_at DATETIME NOT NULL, source VARCHAR(64) NOT NULL, UNIQUE KEY uk_snapshot_instance_time (ci_instance_id, collected_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT配置采集快照表; CREATE TABLE ci_change_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, ci_instance_id BIGINT NOT NULL, change_type VARCHAR(32) NOT NULL COMMENT create/update/status_change/soft_delete, before_json JSON NULL, after_json JSON NULL, operator VARCHAR(128) NULL, source_ticket VARCHAR(128) NULL COMMENT 关联变更工单号, changed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT配置变更审计表;snapshot_hash 的意义采集器同一时间点如果抓回来的数据没变化hash 一致直接跳过写快照能省不少空间。采集快照不是越多越好通常一个 CI 保留最近 30 次快照就足够回溯问题平常做按周归档到冷表。ci_change_log 是人工回写的唯一入口。所有更新操作都通过一个 change_service 调用先把 before 和 after 记录下来再更新 ci_instance。这样就算有人误改配置也能通过 change_log 找回旧值避免资产配置系统变成一次性黑匣子。4. 用 Python 把最小校验链路跑通注册、比对、定时调度4.1 配置项注册与唯一键查重先写一个最小的注册函数用 SQLite 跑通逻辑后续换 MySQL 也不需要改代码结构。import hashlib import json import sqlite3 from uuid import uuid4 def register_ci(conn, ci_type: str, attrs: dict, unique_expr: str): # 按照唯一键表达式生成查重用的散列值 parts [] for field in unique_expr.split(): field field.strip() if field not in attrs: raise ValueError(fmissing unique field: {field}) parts.append(f{field}{attrs[field]}) unique_key hashlib.sha256(.join(parts).encode()).hexdigest() cur conn.execute( SELECT id FROM ci_instance WHERE ci_type_code? AND unique_hash?, (ci_type, unique_key), ) row cur.fetchone() if row: # 已存在则做更新保持幂等避免同一条资产被注册两次 conn.execute( UPDATE ci_instance SET attr_json?, updated_atCURRENT_TIMESTAMP WHERE id?, (json.dumps(attrs, ensure_asciiFalse), row[0]), ) return row[0], False cur conn.execute( INSERT INTO ci_instance(ci_type_code, instance_uuid, attr_json, status, source) VALUES(?,?,?,?,?), (ci_type, str(uuid4()), json.dumps(attrs, ensure_asciiFalse), active, api), ) return cur.lastrowid, True这里 unique_expr 从 ci_type.unique_key 读出用“”拼接多个字段再算哈希。第一次调用是插入第二次就变成更新。这个幂等设计很关键采集任务跑重复了或者两个采集器同时上报同一台服务器都不会产生脏数据。补充一个参数细节unique_expr 里字段的顺序要和 ci_type 表里定义保持一致否则“hostnameinstance_uuid”和“instance_uuidhostname”会生成两个不同的哈希查重就失效了。4.2 配置漂移比对采集快照与现网事实对拍采集器从云平台或 CSV 拿回的数据不能直接覆盖 CMDB先做一次差异检测。def detect_drift(conn, ci_type: str, collected: list[dict]) - list[dict]: collected 是从外部源拉回来的原始数据列表 返回的每条记录表示一个配置漂移 drifts [] for item in collected: cur conn.execute( SELECT id, attr_json FROM ci_instance WHERE ci_type_code? AND instance_uuid?, (ci_type, item[instance_uuid]), ) row cur.fetchone() if not row: drifts.append({uuid: item[instance_uuid], type: missing_in_cmdb}) continue current json.loads(row[1]) changed [] for k, v in item.items(): if str(current.get(k)) ! str(v): changed.append({field: k, cmdb: current.get(k), actual: v}) if changed: drifts.append({uuid: item[instance_uuid], type: field_diff, fields: changed}) return drifts比对时统一用 str() 再比较因为云平台返回的 CPU 核数是 intCMDB 里存的可能是字符串“8”。这类类型不一致非常常见。函数只输出差异结果不改库是否自动订正应该由策略决定默认不要自动改。这个逻辑里最容易被忽略的是 missing_in_cmdb 的处理。外部源有、CMDB 没有不一定是要新增资产也可能是采集源的临时数据或已下架机器未清理。所以我把这类差异也当作一种 drift让运维去人工确认而不是自动注册。4.3 定时调度把采集任务注册成系统服务比较稳的调度方式是用 systemd timer 或 crontab。以 Linux 为例我会创建一个 service 单元防止采集进程被杀掉。[Unit] DescriptionCMDB Collector Afternetwork-online.target [Service] Typesimple WorkingDirectory/opt/cmdb_collector ExecStart/usr/bin/python3 /opt/cmdb_collector/collector.py --inventory /etc/cmdb/inventory.csv Restarton-failure RestartSec30 [Install] WantedBymulti-user.target然后写 crontab# 每小时第 5 分钟跑一次采集错开整点高峰 5 * * * * cd /opt/cmdb_collector /usr/bin/python3 collector.py --inventory /etc/cmdb/inventory.csv /var/log/cmdb_collector.log 21参数说明采集间隔要大于单次执行时长。如果一次采集要跑 5 分钟就不要每 5 分钟调度一次否则任务会叠加。RestartSec30 是给被采集方一个喘息空间避免对云平台 API 触发限流。如果跑在 Windows 上把采集程序 zip 包设置成本地服务和这里其实是同一套思路用 NSSM 或 sc.exe 注册成系统服务即可。依赖库如果选择 MySQL 8.0 的 zip 免安装版部署在 Windows记得先执行 mysqld --initialize 初始化数据目录再注册服务我第一次直接注册服务就遇到 MySQL 无法启动报错信息里却没有一点提示。5. 避坑与排查CMDB 资产配置管理最容易翻车的五个现场5.1 zip 解压翻车报找不到 EOCD或解压让你输密码现象从团队网盘或聊天工具拿到的源码包解压到一半提示 invalid zip archive: could not find eocd或者明明打包时没设密码但一打开就要输密码。原因找不到 EOCDEnd of Central Directory通常是 zip 文件不完整下载中断、文件被网关改写或者对方发的其实是自解压文件改了个 zip 后缀。至于不设密码却要求输密码的情况大概率是 zip 伪加密文件头的加密标志位被置位但数据区根本没有真实加密常见于某些内部工具导出或恶意样本伪装。解决先用 7-Zip 打开判断。如果 7-Zip 能直接浏览文件且不弹密码框那就是伪加密直接“另存为”重新打包一次就能消除标志位。EOCD 异常则用 7-Zip 的修复功能或 zip -FF 尝试重建目录结构修复失败就找对方重新打包不要在一个损坏包上耗时间。解压后先做校验不要直接覆盖本地已改过的源码目录。5.2 重复资产同一台虚拟机出现两条记录现象按主机名查有两台机器字段差不多但 UUID 不同监控系统把两台机器的数据都上报CMDB 里的统计翻倍。原因唯一键定义不严谨。只用 hostname 做唯一虚拟机克隆、跨环境迁移、主机名回收都会造成冲突只用 IP 做唯一DHCP 变化或迁移后 IP 更换就会产生新纪录。解决在 ci_type 表里把唯一键调整为“instance_uuid hostname”。云上虚拟机直接取云平台实例 ID物理机用序列号加资产编号。同时把注册接口的查重冲突详情暴露出来不能静默跳过要让运维看到是哪两批数据撞了。5.3 软删除与级联删除退役一台物理机把业务全删了现象运维把一台即将下线的物理机从 CMDB 删除结果这台机器上所有虚拟机、业务应用、数据库实例在拓扑图里全部消失消费 API 也查不到了。原因关系表的外键配置了 ON DELETE CASCADE或者删除 CI 实例时用 SQL 把 source_id 或 target_id 指向它的关系一并 delete。一个 CI 可能被几十个对象引用硬删除会把整条关系链带走。解决资产配置系统一律软删除。status 置成 retired关系仍然保留查询和拓扑默认过滤 retired。真正清理数据的任务放到夜间定期执行并且要过审批。如果现有数据库已经硬删除成习惯至少先把外键的 ON DELETE 改成 RESTRICT强行阻止误删。5.4 时间口径不一致UTC 与本地时间混存导致误报现象采集器 10:00 跑快照的 collected_at 却写成 18:00漂移比对引擎按“最近 10 分钟快照”判断时发现没有新快照于是把所有资产都标成漂移。原因开发时有的模块用时间戳有的用字符串云平台返回 UTCPython 本地时间用了 Asia/Shanghai快照和 CI 的 last_seen_at 口径没统一。这是我踩过最隐蔽的坑因为表面看每个模块都正常只有比对引擎在抽风。解决所有时间字段统一存 DATETIME 且转成 UTC展示层再做本地化。快照比对不依赖“当前时间减去采集时间”的窗口改为按快照 ID 取最新一条。如果采集没跑到宁可报“未采集”也不要误报成“配置漂移”。5.5 自动订正配置漂移别把灰度环境的有意改动当成故障修掉现象一批服务器的内核参数被监控发现与 CMDB 基线不一致校验引擎自动“修复”把它们拉回基线值。结果这些机器是灰度集群新参数是有人故意上调的自动修复把灰度配置干掉了。原因把“比对”和“订正”两个动作合在了一个流程里且没有给基线加环境标签。解决自动订正默认关闭漂移记录进入待确认列表。同一机柜rack ops 视角内如果超过 20% 的机器发生了相同差异基本可以判断是有人在做有计划的变更需要人工确认而不是自动纠偏。基线按环境分生产、预发、灰度、测试各自一套不允许跨环境套用。6. 进阶与验证把台账变成变更影响分析引擎6.1 变更影响分析顺着 CI 关系推导受影响业务配置项关系表建好之后最有价值的用法是变更影响分析。某个数据库实例着火了它影响了哪些业务顺着 depend_on 关系向上遍历即可。def impact(conn, ci_id, rel_typedepend_on): affected set() queue [ci_id] while queue: curr queue.pop(0) if curr in affected: continue affected.add(curr) rows conn.execute( SELECT source_id FROM ci_relation WHERE target_id? AND rel_type?, (curr, rel_type), ).fetchall() queue.extend(r[0] for r in rows) return affected这个函数返回一个集合包含所有直接或间接依赖该 CI 的上游资产。把它和发布系统联动就能在变更单上自动展示“影响范围”省掉每次人工翻拓扑的功夫。6.2 上线前最值得跑的三类验证我会在一个新环境部署完成后跑三件事第一CI 总数和源端系统清单对一遍差异超过 1% 就要查采集链路第二随机抽 20 个 CI 逐个字段对拍验证唯一键和属性映射没写错第三跑一次关系完整性校验找出指向不存在 CI 的脏关系。这三步能在一小时内做完但可以过滤掉上线后 80% 的脏数据问题。我现在的习惯是每周一早上把这三步跑一遍已经成了固定动作希望帮到你。本文还有配套的精品资源点击获取