使用Zabbix采集服务器基础信息

CMDB 建设中一个比较重要的方面是保证数据的及时更新以及准确性,靠人工肯定是很难做到的,必须通过技术手段用自动化的方式去做。本文记录一种通过 Zabbix inventory 来审计和更新 iTop CMDB 中服务器基础信息的方案。

概述

大致流程如下:

  • 服务器基础信息的采集,通过 Zabbix 的自定义 Key 功能来实现。
  • 服务器基础信息信息存储,在 Zabbix 添加监控项时将其设置为某个 inventory 项目,这样做的优点是调用 API 时更方便。
  • 服务器基础信息的利用,通过 Zabbix API 和 CMDB API 来审计和更新数据。
基于 Zabbix 审计 CMDB 数据基本流程
基于 Zabbix 审计 CMDB 数据基本流程

数据采集

配置 UserParameter。

UserParameter=lld_asset[*],/usr/local/etc/script/assetinfo.sh $1

然后在脚本中实现采集项目。大致需要采集的项目包括 序列号,CPU 核数,内存大小,硬盘数量大小,RAID 级别,品牌型号,操作系统版本,内核版本,IP 地址等。下面记录一些项目的采集方法。

序列号

物理机直接使用 SN,虚拟机使用 UUID。

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

品牌型号

manufacturer=`sudo dmidecode -s system-manufacturer|grep -v "^#"`
product=`sudo dmidecode -s system-product-name|grep -v "^#" |sed 's/-\[.*\]-//g'`

操作系统和内核

os=`cat /etc/redhat-release |sed -r 's/\(.*\)|Linux|release//g'`
kernel=`uname -r |sed -r 's/(-[0-9]+)\..*/\1/g'`

磁盘数量大小和RAID

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 级别的方法如下。

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。

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 写定时任务脚本。

{
    "require": {
        "ec-europa/itopapi": "^0.5.2",
        "confirm-it-solutions/php-zabbix-api": "2.4.2"
    }
}

iTop API 调用示例:

// 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 调用示例:

// 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));
}

监控审计,找出没有添加监控的服务器。

// 监控审计
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 中机器状态是下线。

// 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;
}

审计主体。

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来抓取物理连接信息。

# 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

关于机架位,可能需要智能机架?有这种东西吗?我暂时没有这方面经验。

(全文完)

One thought on “使用Zabbix采集服务器基础信息

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注