刚接手一个规模不算小的运维团队时,我翻来覆去查不到上一批服务器到底跑了什么服务,配置和Excel台账对不上。那时候我就意识到,与其靠人肉维护资产清单,不如从零做一个CMDB自动化资产扫描系统。这个系列我会按项目实战的方式写,第一篇文章先聚焦“介绍与环境搭建”,把为什么要做、技术选型思路和基础环境准备一次讲透,方便你在自己环境里跟着复现。
这套方案的核心思路是在内网环境中,通过监听协议与指令通道主动采集服务器、网络设备的基础信息,自动写入CMDB资产库,实现硬件配置、操作系统、运行状态的定期更新。本文将覆盖前期需求分析、扫描技术路线选型、虚拟化准备、操作系统初始化、数据库与缓存服务搭建,以及目录结构和后续模块规划。运维工程师、DevOps转型人员、刚接触自动化运维的朋友都能从中拿到一套可直接落地的环境搭建清单。
1. 项目拆解:CMDB资产扫描到底在解决什么问题
1.1 人工资产台账的三大痛点
很多团队到现在还用Excel或者Wiki记录服务器资产,短时间看似乎能用,只要设备超过几十台,问题就一连串冒出来。第一个痛点是信息滞后,半年后看台账,IP变了不知道、内存扩容了没同步、业务下线了机器还在列表里“存活”。第二个痛点是口径不统一,有人填主机名用大写,有人用小写,操作系统版本有的写“CentOS 7.4”,有的只写“CentOS”,等汇总的时候完全没法对齐。第三个痛点是无法关联状态,资产清单只记录“有什么”,不记录“现在是否在线、CPU是否打满、磁盘还剩下多少”,这是运维排障时最要命的地方。
CMDB这个词听起来很专业,拆开来看其实就是配置管理数据库,专门存放IT基础架构中每个部件的配置信息与相互关系。自动化资产扫描是CMDB的数据入口,通过定期执行扫描任务,把服务器配置、网络联通性、运行状态回填到数据库,让资产数据从“人肉维护”变成“自动生长”。这个转变对运维团队带来的价值不仅是省人力,更重要的是让监控、变更、故障处理有了可依赖的单一数据源。
1.2 资产扫描的项目范围与目标
在做第1部分环境搭建之前,得先把项目范围圈清楚。我这次规划的扫描对象分三类:Linux服务器、Windows服务器、网络设备(交换机、防火墙)。采集信息也按层级拆成三类:基础身份信息包括IP地址、MAC地址、主机名、序列号;硬件配置信息包括CPU型号与核数、内存容量、磁盘分区与容量;运行状态信息包括操作系统版本、内核版本、开机时间、当前负载。这些字段基本覆盖了日常资产管理80%以上的需求。
项目第一期不追求大而全,明确做减法:不做软件资产清单,因为软件识别涉及包管理器差异性,工作量大且收益不高;不做IP段自动发现后的端口全扫描,只做已知IP池的定向采集;不做配置变更的自动推送,只做采集入库与变更告警。定好这个边界,后面环境搭建和代码实现就不会跑偏,也能在两周内出一个看得见效果的版本。
1.3 技术路线选型:Agentless优先
资产管理系统的采集方式,业内无非两条路:装Agent和不装Agent。Agent方式是在每台目标主机上部署客户端程序,定时采集信息回传中心。它的优点是数据采集粒度细,能拿到进程级信息,甚至能做到文件变更监控;缺点是部署工作量巨大,在老旧设备上还可能遇到兼容性问题。
我这次选择Agentless方案,也就是中心端主动发起采集,通过SSH协议登录Linux/Windows服务器执行查询命令,通过SNMP协议读取网络设备信息。选这条路的核心考量是:能在一周内覆盖几百台存量设备,不需要逐台安装客户端,遇到采集目标宕机也只是跳过,不会影响中心服务。后续如果需要对某些重点业务做深度监控,再单独给它们上Agent,形成“Agentless兜底+Agent补充”的混合模式。
2. 环境规划:用一套干净的基础设施支撑整套扫描服务
2.1 服务端与目标机器的网络拓扑设计
环境搭建第一步不是装软件,而是先把网络拓扑想清楚。整体上分成三个角色:扫描中心(CMDB服务端)、目标服务器池、数据库缓存层。我在实验环境里用一台物理服务器做虚拟化宿主机,在上面创建三台虚拟机:一台运行Ubuntu Server作为扫描中心兼数据库主机,两台CentOS Stream作为被扫描的目标主机,另加一台网络设备模拟器做SNMP采集验证。
宿主机创建虚拟网络时建议使用NAT或桥接模式,确保三台机器能互相通信。有个细节常被忽略:虚拟机安装系统时如果默认开启了DHCP,后面做SSH采集容易因为IP漂移导致连接失败,建议全部配置为静态IP。为了后续方便复现,我在VMware里分配了三个网段:管理网段用于扫描中心访问目标机器,业务网段留给后续对接监控系统,还有一个模拟外网的NAT网段用于软件源访问。
2.2 操作系统版本与Python环境选择
扫描中心我选了Ubuntu Server 22.04 LTS,理由很直接:Ubuntu的Python 3.10环境很干净,安装依赖库时不会像某些系统那样顺手把系统Python弄乱。软件源在国内环境建议直接替换为阿里云或清华源,否则安装依赖时超时会让人怀疑人生。
Python环境部分我不建议直接用系统Python跑代码,项目依赖一旦多起来,版本冲突是必然的。我用venv创建独立的虚拟环境,装在项目的backend目录下,之后用pip install安装的库全部落在虚拟环境里,和系统隔离。Python版本上,3.10够用,AsyncSSH、Netmiko、pysnmp这些库对3.10兼容都不错,没必要追新上3.12。
数据库选择上,资产数据有明显的关系结构:设备表、接口表、IP表、配置项变更记录表,天然适合MySQL。我在Ubuntu上用apt安装MySQL 8.0,存储引擎用InnoDB,字符集统一设置为utf8mb4。Redis安排一台单独的小虚拟机,存放扫描任务的执行状态和结果缓存,减轻MySQL的读写压力。
2.3 目录结构与代码仓库初始化
一个好的项目结构能让你后期少走很多弯路。我在扫描中心虚拟机里这样组织代码:
/opt/cmdb-scanner/ ├── backend/ # 主服务代码 │ ├── scanner/ # 采集模块(SSH/SNMP) │ ├── api/ # API服务模块 │ ├── models/ # 数据模型定义 │ └── utils/ # 公共工具函数 ├── frontend/ # 前端展示(后续版本) ├── scripts/ # 数据库初始化脚本 ├── logs/ # 日志输出目录 ├── config/ # 配置文件目录 └── requirements.txt # Python依赖清单这个结构是有讲究的。scanner目录独立成包,因为后面扩展采集协议只需要在这里加一个子模块;models放数据模型,是为了数据库表结构变更时可追溯;logs目录一定要预留,资产扫描跑起来后日志量会快速增加,到时候再改目录就麻烦了。项目仓库我用Git做版本管理,在扫描中心本地初始化仓库,每完成一个模块的功能就提交一次,方便回溯。
2.4 防火墙与端口规划清单
防火墙规不规范直接影响调试效率。我需要开放的端口包括:22端口用于SSH采集时从中心访问目标,但目标机器不需要回连中心,所以不需要在中心机放行22的入方向;161/162端口用于SNMP服务端与网络设备通信,需要在扫描中心放行UDP入方向;MySQL的3306端口只在内网访问,这个端口不要让扫描中心以外的机器访问;Redis的6379端口我对绑定地址做了限制,只允许本机和扫描中心连接。
这里我踩过一次坑:CentOS默认防火墙firewalld是开启的,目标机器上如果不放行SSH端口,扫描中心怎么连都连不上,排查半天还以为是密码错了。另外SNMP用的是UDP协议,没有连接状态,防火墙规则如果只放行了TCP就会莫名其妙地出现“部分设备能扫描,部分超时”的奇怪现象。建议环境搭建阶段把三台机器的防火墙规则一次性配置到位。
3. 实操记录:从创建虚拟机到服务全部跑通
3.1 虚拟机创建与操作系统安装
宿主机上我用VMware Workstation创建虚拟机,每台分配2核CPU和4GB内存,磁盘先给40GB。这个配置对实验环境完全够用,如果后面数据量变大,再加磁盘就行。安装Ubuntu时有个小技巧:在安装界面选择“最小安装”,不要装图形界面,装Server版省下来的资源可以留给扫描任务跑。安装过程中把openssh-server勾选上,因为后续所有远程操作都走SSH,如果没有提前装好,还得回头通过控制台补装。
两台CentOS Stream目标机的安装方式一样,唯一区别是安装包选择时我勾上了“开发工具”组,避免后续装一些依赖工具时缺编译器。虚拟机创建时的网络类型我统一选NAT模式,然后手动修改/etc/netplan或/etc/sysconfig/network-scripts里的配置,把IP改为静态地址。这三台机器信息我列个表:
| 角色 | 主机名 | IP地址 | 操作系统 | 关键服务 |
|---|---|---|---|---|
| 扫描中心 | cmdb-server | 192.168.10.10 | Ubuntu 22.04 | Python3.10, MySQL, Redis |
| 目标服务器1 | target-node1 | 192.168.10.21 | CentOS Stream 9 | SSH服务 |
| 目标服务器2 | target-node2 | 192.168.10.22 | CentOS Stream 9 | SSH服务 |
| 模拟网络设备 | switch-sim | 192.168.10.30 | Ubuntu + SNMP模拟 | SNMP服务 |
3.2 扫描中心基础环境配置
系统装好后的第一件事是更新软件源。我修改/etc/apt/sources.list,把Ubuntu官方源替换为国内镜像源,然后执行apt update && apt upgrade -y。接着安装编译工具链和系统依赖:
sudo apt install -y build-essential libssl-dev libffi-dev libmysqlclient-dev pkg-config这些库是后续安装Python依赖时的前置条件,比如cryptography库要编译C扩展,没有libssl-dev会直接报错。MySQL安装我用apt安装mysql-server,装完后通过mysql_secure_installation做初始化,设置root密码、移除匿名用户、禁止root远程登录(后面用的是专用账号连接)。Redis安装后需要修改/etc/redis/redis.conf,把bind 127.0.0.1改为bind 127.0.0.1 192.168.10.10,并设置requirepass,防止内网其他机器连上来清数据。
Python虚拟环境创建我用以下命令:
sudo apt install -y python3-venv mkdir -p /opt/cmdb-scanner/backend cd /opt/cmdb-scanner/backend python3 -m venv venv source venv/bin/activate激活后顺手把pip源换成国内镜像,因为后面requirements.txt里要装的paramiko、netmiko、pysnmp、fastapi等包总共有一百多个依赖项,默认源下载慢到你怀疑人生。
3.3 目标服务器的SSH与SNMP准备
要能被扫描中心采到数据,目标机器需要开启SSH服务并允许密码认证或密钥认证。实验阶段我用密码认证,这样代码里直连测试方便,但生产环境强烈建议换成密钥对免密登录。在两台CentOS上启动服务并设置开机自启:
sudo systemctl enable sshd sudo systemctl start sshd然后在扫描中心测试连通性和认证是否正常:
ssh root@192.168.10.21如果提示输入密码并能进入系统,说明通道没问题。这里要注意:生产环境不要直接用root账号扫资产,建议创建普通用户并加入sudo组,采集命令通过sudo执行,安全性高很多。
网络设备模拟器用的是Ubuntu上的snmpd服务配置。我在/etc/snmp/snmpd.conf中设置只读团体名,比如设置为cmdb_read,然后限制允许访问的网段:
rocommunity cmdb_read 192.168.10.0/24重启snmpd后,用snmpwalk验证能否读取系统信息:
snmpwalk -v2c -c cmdb_read 192.168.10.30 system能返回一堆以.1.3.6.1.2.1.1开头的OID,说明SNMP通道就绪了。
3.4 数据库初始化与连接测试
扫描中心的数据库既要存设备信息,也要存扫描历史,所以建表时要考虑数据版本。我不建议把所有字段堆在一张表里,而是拆成device_base(设备基础信息)、device_status(设备实时状态)、scan_record(扫描记录)、change_log(变更日志)四张核心表。数据库初始化脚本放在scripts目录,用以下命令创建:
mysql -u root -p < /opt/cmdb-scanner/scripts/01_create_database.sql建库时指定字符集:
CREATE DATABASE IF NOT EXISTS cmdb DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;创建专用账号并授权:
CREATE USER 'cmdb_user'@'192.168.10.10' IDENTIFIED BY 'YourStrongPassword'; GRANT ALL PRIVILEGES ON cmdb.* TO 'cmdb_user'@'192.168.10.10'; FLUSH PRIVILEGES;在扫描中心用Python测试数据库连接时,我写了一个最小脚本验证连通性:
import pymysql conn = pymysql.connect(host='127.0.0.1', user='cmdb_user', password='YourStrongPassword', database='cmdb') cursor = conn.cursor() cursor.execute('SELECT VERSION()') print(cursor.fetchone()) conn.close()能输出MySQL版本号,说明数据库连接链路没问题,接下来写采集模块的存储对象就不用反复调试连接了。
4. 核心采集逻辑的前置说明与数据设计
4.1 采集过程中“命令执行”与“数据解析”分离的设计思路
虽然这次标题是环境搭建,但我认为在搭建阶段就把采集逻辑的分层想好很重要,否则一进入第二篇代码实现,你很容易把代码糊成一团。我规划的采集模块分两层:连接层负责建立SSH/SNMP会话、处理超时重连、认证失败等异常;解析层负责执行采集命令后,把返回内容解析成结构化的字段。这样做的好处是连接层可以被复用到后续的批量巡检任务,而解析层可以针对不同操作系统做适配。
举个例子:要获取Linux机器内存信息,执行free -m后返回的文本在不同版本系统上有细微差异,CentOS 7可能有Mem:行,CentOS 9加了Swap:额外信息。如果把连接和解析混在一起,每加一种操作系统就要复制一遍连接代码。分离后,连接层只管执行命令把原始文本传给解析层,解析层只需要专心处理文本模式。
4.2 数据模型字段规划与变更记录设计
我在环境搭建阶段已经把数据表的字段规划好了,这部分直接决定后面代码写起来顺不顺。device_base表设计如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | INT PRIMARY KEY AUTO_INCREMENT | 自增主键 |
| ip_address | VARCHAR(45) | IP地址,兼容IPv6 |
| hostname | VARCHAR(128) | 主机名 |
| os_type | VARCHAR(64) | 操作系统类型 |
| os_version | VARCHAR(128) | 操作系统发行版本 |
| cpu_model | VARCHAR(255) | CPU型号 |
| cpu_cores | INT | CPU物理核数 |
| memory_total_mb | INT | 内存总大小(MB) |
| disk_total_gb | DECIMAL(10,2) | 磁盘总大小(GB) |
| serial_number | VARCHAR(128) | 服务器序列号 |
| last_scan_time | DATETIME | 最近一次扫描时间 |
| updated_at | DATETIME | 记录更新时间 |
change_log表我单独建,记录每次扫描后哪些字段发生了变化。这里的设计启发来自监控系统的事件模型:不能只存最终状态,还要存变化轨迹,否则出现配置回退时查不到原因。change_log字段包括device_id、field_name、old_value、new_value、scan_time。这套设计在第二篇实现时可让“变更告警”功能水到渠成,不用回头改表结构。
4.3 扫描任务的调度与执行状态管理
环境搭建阶段还需要考虑任务调度问题。后续我会用APScheduler来做定时调度,每隔4小时执行一轮全量扫描,考虑到扫描时间长,任务状态管理需要引入Redis缓存。Redis里可以存储每个扫描任务的执行进度,比如scan:task:001对应的value记录“已完成设备数/总设备数”,这样前端展示时能实时看到扫描进度。
Redis的键设计有一个参考模式:任务状态用Hash类型存储字段,分别是status(pending/running/finished/failed)、start_time、end_time、total_count、success_count、failed_count。这套数据模型虽然简短,但足够支撑一个简单的扫描任务列表页。这里就不展开全部代码,等写实现篇时再逐段贴。
5. 环境搭建阶段的高频问题与排错速查
5.1 虚拟机网络不通与SSH连接失败排查
实验环境最常遇到的故障就是“扫描中心SSH登录目标机连不上”或“ping得通但ssh超时”。排查时按这个顺序来:先确认目标机sshd服务状态,systemctl status sshd是否active;如果服务正常,再看防火墙规则,sudo firewall-cmd --list-all查22端口是否放行;然后确认目标机的IP没有和其他机器冲突,ip addr查看实际网卡地址,和规划表对比。如果这些问题都没有但依然连不上,检查目标机的/etc/hosts.deny文件,这地方经常被人忽略,其实配置了sshd限制规则就会直接拒绝连接。
扫描中心侧的问题主要是SSH认证方式问题,第一次连接时目标机的host key不在known_hosts中,ssh命令会询问是否继续连接,程序化调用时脚本就会卡住。解决办法是在paramiko连接参数里设置AutoAddPolicy,或者提前用ssh-keyscan把目标机的key导入到known_hosts:
ssh-keyscan -t rsa 192.168.10.21 >> ~/.ssh/known_hosts5.2 数据库连接报错与字符集问题
数据库这块踩过两个坑。第一个是MySQL默认使用auth_socket插件认证root,应用代码用密码连接报错“Access denied”,解决方式是ALTER USER改用caching_sha2_password并设置密码。第二个是字符集问题,Python端连接MySQL时连接串里如果没有指定charset,默认是latin1,存中文数据会变成乱码。连接参数务必加上:
pymysql.connect(..., charset='utf8mb4')还有一个怪坑:MySQL 8默认的默认认证插件是caching_sha2_password,与旧版pymysql可能有兼容问题,建议pymysql安装1.0.2以上版本,或者干脆连接时指定auth_plugin='mysql_native_password',但长远看还是升级客户端库更靠谱。
5.3 SNMP超时与返回数据缺失的应对策略
SNMP扫描网络设备时,超时问题首当其冲。默认的超时时间只有1秒,很多设备如果当前CPU繁忙响应不及时就会误报为离线。我在项目的采集配置里把超时时间调整到3秒,重试次数设为2次,同时开启SNMP的BulkWalk模式批量获取数据,减少交互次数。另外需要注意:部分老设备的MIB库不标准,某些OID可能返回空值,解析时要做空值兜底,不要因为个别字段缺失就让整条采集记录失败。
MIB缺失问题主要体现在设备的型号返回为空,解决办法是除了获取sysDescr外,再尝试获取sysObjectID和厂商自定义OID,比如华为设备在enterprises.2011目录下会有详细型号信息。这块内容等实现篇单独讲,环境搭建阶段先把snmpwalk能通即可。
5.4 资源不足与性能损耗优化建议
实验环境如果只有一台宿主机,跑了三台虚拟机再启动MySQL和Redis,扫描中心内存会吃紧。我在排障时发现,MySQL占用内存居高不下,主要是InnoDB缓冲池默认值128MB太小但内部线程多。对实验环境可以调低性能参数,在/etc/mysql/mysql.conf.d/mysqld.cnf中添加:
performance_schema=OFF innodb_buffer_pool_size=64M max_connections=50这样改完重启MySQL,内存占用能降至少200MB。另外扫描中心跑批量SSH采集时,如果使用单线程逐个连接,速度会非常慢,几百台设备可能要跑几小时。环境搭建阶段建议安装asyncssh库,为后续并发采集做准备:
pip install asyncssh6. 环境验证清单与后续规划
为了让读者确认自己的环境搭建没问题,我整理了一份验证清单:
- [ ] 扫描中心能ping通两台目标机和网络设备模拟器
- [ ] 从扫描中心SSH登录目标机能正常执行命令
- [ ] MySQL中已创建数据库cmdb,应用账号能正常连接
- [ ] Redis的ping返回PONG,带密码认证能访问
- [ ] snmpwalk能读取到设备system信息
- [ ] Python虚拟环境能导入paramiko、pysnmp、fastapi
这六项就是环境搭建阶段的核心验收标准。全部通过后,下一篇文章就可以直接进入扫描模块编码阶段。整个系列按计划会包含介绍与环境搭建、SSH采集模块实现、SNMP采集网络设备、数据入库与任务调度、API服务与前端展示、配置变更告警等六篇,如果你手头正好有设备资产梳理的需求,按照这篇先把环境搭好,后面的代码很快就能跑起来。
我在第一期项目实践中体会到,环境搭建是最容易“翻车”但也最容易一次做对的部分——把网络规划、账号权限、服务端口想清楚,后面所有自动化逻辑都建立在稳定的地基上。希望你照着这个清单搭完后,能感受到资产扫描带来的掌控感:那条天天让你头疼的资产清单,终于开始自动生长了。