CMDB 建设中一个比较重要的方面是保证数据的及时更新以及准确性,靠人工肯定是很难做到的,必须通过技术手段用自动化的方式去做。本文记录一种通过 Zabbix inventory 来审计和更新 iTop CMDB 中服务器基础信息的方案。
概述
大致流程如下:
- 服务器基础信息的采集,通过 Zabbix 的自定义 Key 功能来实现。
- 服务器基础信息信息存储,在 Zabbix 添加监控项时将其设置为某个 inventory 项目,这样做的优点是调用 API 时更方便。
- 服务器基础信息的利用,通过 Zabbix API 和 CMDB API 来审计和更新数据。

数据采集
配置 UserParameter。
1 |
UserParameter=lld_asset[*],/usr/local/etc/script/assetinfo.sh $1 |
然后在脚本中实现采集项目。大致需要采集的项目包括 序列号,CPU 核数,内存大小,硬盘数量大小,RAID 级别,品牌型号,操作系统版本,内核版本,IP 地址等。下面记录一些项目的采集方法。
序列号
物理机直接使用 SN,虚拟机使用 UUID。
1 2 3 4 |
sn=`sudo dmidecode -s system-serial-number |grep -v "^#"` uuid=`sudo dmidecode -s system-uuid |grep -v "^#" |tr '[a-z]' '[A-Z]'` # 物理机 SN 一般没有空格或者 - echo $sn |grep -E " |-" &>/dev/null && assettag=$uuid || assettag=$sn |
品牌型号
1 2 |
manufacturer=`sudo dmidecode -s system-manufacturer|grep -v "^#"` product=`sudo dmidecode -s system-product-name|grep -v "^#" |sed 's/-\[.*\]-//g'` |
操作系统和内核
1 2 |
os=`cat /etc/redhat-release |sed -r 's/\(.*\)|Linux|release//g'` kernel=`uname -r |sed -r 's/(-[0-9]+)\..*/\1/g'` |
磁盘数量大小和RAID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
if [ "$manufacturer"x == "HP"x ];then pd=`sudo /opt/hp/hpssacli/bld/hpssacli ctrl all show config 2>/dev/null|grep "physicaldrive" |cut -f3 -d',' |awk '{sum+=$1}END{print NR,int(sum/1024)}'` raid=`hp_raid` else pd=`sudo /opt/MegaRAID/MegaCli/MegaCli64 -PDList -aALL 2>/dev/null|grep "Raw Size:" |awk '{if($4=="GB") sum+=$3/1024; else sum+=$3}END{print NR,int(sum)}'` raid=`dell_raid` fi case $1 in "pdnum") echo "$pd" |awk '{print $1}';; "pdsize") echo "$pd" |awk '{print $2}';; "raid") echo $raid |sed 's/*1+/+/g' |sed 's/*1$//g';; *) exit;; esac |
惠普服务器和支持 MegaCli 的服务器判断 RAID 级别的方法如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function hp_raid() { sudo /opt/hp/hpssacli/bld/hpssacli ctrl all show config 2>/dev/null|grep "RAID" |cut -f2 -d',' |awk '{print $2}' |grep -v "^$" |uniq -c |awk '{print $2"*"$1}' |tr '\n' '+' |sed 's/+$//g' } function dell_raid() { raid=`sudo /opt/MegaRAID/MegaCli/MegaCli64 -LDInfo -Lall -aALL 2>/dev/null|grep "RAID Level"|awk -F": " '{print $2}'|tr ' ' '#'|tr -d ','` r="" for id in $raid;do case "$id" in "Primary-1#Secondary-0#RAID#Level#Qualifier-0") r="$r+1";; "Primary-0#Secondary-0#RAID#Level#Qualifier-0") r="$r+0";; "Primary-5#Secondary-0#RAID#Level#Qualifier-3") r="$r+5";; "Primary-1#Secondary-3#RAID#Level#Qualifier-0") r="$r+10";; "*") r="$r+N";; esac done [ "$r"x == ""x ] && r="N" echo $r |tr '+' '\n' |grep -v "^$" |uniq -c |awk '{print $2"*"$1}' | tr '\n' '+' |sed 's/+$//g' } |
IP地址
采集服务器的所有 IP ,包括 内网 IP,公网 IP,VIP,管理卡 IP。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function all_ip() { ips="" for item in `ip add |grep "inet " |grep -E "host lo|global eth|global bond" |grep -v "127.0.0.1" |awk '{print $2}'`;do ip=`echo $item |cut -f1 -d'/'` mask=`echo $item |cut -f2 -d'/'` first=`echo $ip |cut -f1 -d'.'` if [ $mask -eq 32 ];then ips="$ips,vip-$ip" elif [ $first -eq 10 ];then ips="$ips,int-$ip" else ips="$ips,ext-$ip" fi done oob=`timeout 3 sudo ipmitool lan print 2>/dev/null |grep "^IP Address" |grep -v "Source" |awk '{print $NF}'` if [ "$oob"x != ""x ];then ips="$ips,oob-$oob" fi echo $ips |sed 's/^,//g' } |
数据存储
服务器监控模板新增自定义监控项,格式为 lld_asset[param]
。并设置该项为 inventory 项。
CMDB审计和更新
本文使用的 CMDB 是 iTop, 和 Zabbix Web 界面一样都是使用 PHP 开发的,有现成的 PHP SDK,因此选用了 PHP 写定时任务脚本。
1 2 3 4 5 6 |
{ "require": { "ec-europa/itopapi": "^0.5.2", "confirm-it-solutions/php-zabbix-api": "2.4.2" } } |
iTop API 调用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
// iTop 中查询所有服务器列表,仅输出需要审计的字段 function getAllServer() { global $iTopAPI; $oql = "SELECT Server WHERE status != 'obsolete'"; $output_fields = "status,name,hostname,osfamily_name,osversion_name,pdnum,pdsize,kernel,raid,brand_name,model_name,cpu,ram,ip_list,vip_list,organization_name,purchase_date"; $data = $iTopAPI->coreGet("Server", $oql, $output_fields); $data = json_decode($data, true); return $data['objects']; } // update cmdb function updateAssetInfo($cmdbdata, $zbxdata) { global $iTopAPI; $os = explode(" ", $zbxdata['inventory']['os']); $cmdbServer = array( 'name' => $cmdbdata['fields']['name'], 'hostname' => $cmdbdata['fields']['hostname'], 'brand_name' => $cmdbdata['fields']['brand_name'], 'model_name' => $cmdbdata['fields']['model_name'], 'cpu' => $cmdbdata['fields']['cpu'], 'ram' => $cmdbdata['fields']['ram'], 'osfamily_name' => $cmdbdata['fields']['osfamily_name'], 'osversion_name' => $cmdbdata['fields']['osversion_name'], 'pdnum' => $cmdbdata['fields']['pdnum'], 'pdsize' => $cmdbdata['fields']['pdsize'], 'raid' => $cmdbdata['fields']['raid'], 'kernel' => $cmdbdata['fields']['kernel'], 'purchase_date' => $cmdbdata['fields']['purchase_date'], ); $zbxServer = array( 'name' => $zbxdata['inventory']['asset_tag'], 'hostname' => $zbxdata['host'], 'brand_name' => $zbxdata['inventory']['vendor'], 'model_name' => $zbxdata['inventory']['model'], 'cpu' => $zbxdata['inventory']['tag'], 'ram' => $zbxdata['inventory']['notes'], 'osfamily_name' => reset($os), 'osversion_name' => end($os), 'pdnum' => $zbxdata['inventory']['url_a'], 'pdsize' => $zbxdata['inventory']['url_b'], 'raid' => $zbxdata['inventory']['url_c'], 'kernel' => $zbxdata['inventory']['type'], 'purchase_date' => $zbxdata['inventory']['date_hw_purchase'], ); $key = array("name" => $cmdbServer['name']); if(array_diff_assoc($cmdbServer, $zbxServer)) { $zbxServer['brand_id'] = array("name" => $zbxServer['brand_name']); $zbxServer['model_id'] = array("name" => $zbxServer['model_name'], "brand_name" => $zbxServer['brand_name']); $zbxServer['osfamily_id'] = array("name" => $zbxServer['osfamily_name']); $zbxServer['osversion_id'] = array("name" => $zbxServer['osversion_name'], "osfamily_name" => $zbxServer['osfamily_name']); // 只读变量在iTop 2.5以后会报错,需删除(Error: model_name: Attempting to set the value on the read-only attribute Server::model_name) unset($zbxServer['brand_name']); unset($zbxServer['model_name']); unset($zbxServer['osfamily_name']); unset($zbxServer['osversion_name']); $ret = $iTopAPI->coreUpdate("Server", $key, $zbxServer); $ret = json_decode($ret, true)['message']; if($zbxServer['hostname'] != $cmdbServer['hostname']) { $ret = $ret . "hostname changed: " . $cmdbServer['hostname'] . " -> " . $zbxServer['hostname']; } return($ret); } return(null); } |
Zabbix API 调用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// zabbix查询host接口 function zabbixHostGet($name) { global $zbxAPI; $param = array( "output" => array("host","inventory"), "selectInventory" => array("asset_tag", "vendor", "model", "tag", "notes", "os", "type", "url_a", "url_b", "url_c", "host_networks", "date_hw_purchase"), "searchInventory" => array("asset_tag" => $name) ); $data = $zbxAPI->hostGet($param); return($data); } // zabbix获取所有有asset_tag的服务器 function zabbixAllHostGet() { return(json_decode(json_encode(zabbixHostGet("")), true)); } |
监控审计,找出没有添加监控的服务器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
// 监控审计 function audit_monitor($data, $zbxServers) { $audit_ret = array( "monitor" => array(), "updatecmdb" => array() ); if(!$data) { return; } $zbxAll = array(); foreach($zbxServers as $server) { $sn = $server['inventory']['asset_tag']; $zbxAll[$sn] = $server; } $exclude = excludeFilter(); foreach($data as $key => $server) { if($server['fields']['status'] == "obsolete") { continue; } // 从命令行排除一些机器 foreach($exclude as $key => $val) { if(array_key_exists($key, $server['fields'])) { if(in_array($server['fields'][$key], $val)) continue 2; } } $sn = $server['fields']['name']; if(!array_key_exists($sn, $zbxAll)) { $ips = $server['fields']['ip_list']; $intip = ""; foreach($ips as $ip) { if($ip['type'] == "int") { $intip = $ip['ipaddress']; } } $audit_ret['monitor'][$sn] = $intip; }else // 更新cmdb中的资产信息(以zabbix数据为准) { $updateinfo = updateAssetInfo($server, $zbxAll[$sn]); if($updateinfo) { $audit_ret['updatecmdb'][$sn] = $updateinfo; } } } return $audit_ret; } |
CMDB 审计和更新,找出没有录入 CMDB 的服务器,包括两种情况:已监控但是 CMDB 未录入, 已加监控但 CMDB 中机器状态是下线。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
// cmdb服务器缺失情况审计(已监控但是cmdb未录入, 已加监控但是cmdb中机器状态是下线) function audit_cmdb($cmdbdata, $zbxServers) { global $iTopAPI; $ret = array("missing"=>array(), "obsolete"=>array()); // 降维处理,方便下面的循环体直接用in_array,减少循环次数 $cmdbServers = array(); $obsoleteServers = array(); foreach($cmdbdata as $server) { array_push($cmdbServers, $server['fields']['name']); if($server['fields']['status'] == 'obsolete') { array_push($obsoleteServers, $server['fields']['name']); } } $i = 1; foreach($zbxServers as $server) { $sn = $server['inventory']['asset_tag']; if($sn == "") { $sn = "blank" . $i; $i++; } $hostname = $server['host']; if(!in_array($sn, $cmdbServers)) { $ret["missing"][$sn] = $hostname; } if(in_array($sn, $obsoleteServers)) { $ret["obsolete"][$sn] = $hostname; } } return $ret; } |
审计主体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
function main() { $cmdbServer = getAllServer(); $zbxServers = zabbixAllHostGet(); $ret = audit_monitor($cmdbServer, $zbxServers); $ipret = audit_ip($cmdbServer, $zbxServers); $csvHelper = new CSV(); $csv_monitor = $csvHelper->arrayToCSV($ret['monitor']); $sum = count($ret['monitor']); $csv_updatecmdb = $csvHelper->arrayToCSV($ret['updatecmdb']); // ip审计结果 $surplus_ip = implode("\n",$ipret['surplus_ip']); $surplus_vip = implode("\n",$ipret['surplus_vip']); $lack_ip = implode("\n", $ipret['lack_ip']); $lack_vip = implode("\n",$ipret['lack_vip']); $lack_ip = importIP($lack_ip); $lack_vip = importIP($lack_vip); $ret_cmdb = audit_cmdb($cmdbServer, $zbxServers); $csv_auditcmdb = $csvHelper->arrayToCSV($ret_cmdb['missing']); $csv_auditcmdb_obsolete = $csvHelper->arrayToCSV($ret_cmdb['obsolete']); $content = "说明:\n1. 服务器唯一标识为SN(虚拟机使用UUID做为SN)\n"; $content = $content . "2. 未加监控服务器: 以CMDB为基准,找出SN在zabbix中不存在的服务器"; $content = $content . "\n3. CMDB信息更新情况: 以zabbix inventory信息为准,更新CMDB中服务器的主机名,CPU,型号等信息. 只显示更新失败以及主机名发生变化的服务器。需要人工关注\n"; $content = $content . "4. 未录入CMDB服务器: 以zabbix inventory为基准,找出SN在zabbix中存在但是CMDB中不存在的服务器,需要人工录入CMDB"; $content = $content . "\n\nCMDB信息更新情况:\n\n" . $csv_updatecmdb; $content = $content . "\n\n未录入CMDB服务器:\n\n" . $csv_auditcmdb; $content = $content . "\n\n未清监控的已下线服务器: \n\nSN, 内网IP\n" . $csv_auditcmdb_obsolete; $content = $content . "\n\n未加监控服务器总数: $sum \n\nSN, 内网IP\n" . $csv_monitor; $content = $content . "\n\nCMDB多余的IP:\n" . $surplus_ip; $content = $content . "\n\nCMDB缺失的IP:\n" . $lack_ip; $content = $content . "\n\nCMDB多余的VIP:\n" . $surplus_vip; $content = $content . "\n\nCMDB缺失的VIP:\n" . $lack_vip; print_r($content); $dt = date("Y-m-d", time()); $subject = "CMDB-Zabbix双向审计报告-$dt"; //$headers = "From: ". MAILFROM; //$headers = "MIME-Version: 1.0" . "\r\n"; //$headers .= "Content-type:text/html;charset=iso-8859-1" . "\r\n"; sendmail($subject,$content); //die(json_encode($ret)); // 更新all_ip updateIP($cmdbServer); } |
后记
由于有 IDC 部门提供基础资源服务,交换机等没有考虑在内。其实服务器的上联交换机及其端口信息,以及机架位信息也很重要,而且也需要自动化维护。对于交换机信息,如果交换机支持 LLDP 或 CDP 协议,并启用了该协议。那我们就可以用过tcpdump来抓取物理连接信息。
1 2 3 4 |
# LLDP 协议号 0x88cc tcpdump -i eth0 ether proto 0x88cc -A -s0 -t -c 1 # CDP 协议,一般是 Cisco,协议号 0x2000 tcpdump -i eth0 ether proto 0x2000 -A -s0 -t -c 1 |
关于机架位,可能需要智能机架?有这种东西吗?我暂时没有这方面经验。
(全文完)
能提供更完整的配置方法吗