CMDB 建设中一个比较重要的方面是保证数据的及时更新以及准确性,靠人工肯定是很难做到的,必须通过技术手段用自动化的方式去做。本文记录一种通过 Zabbix inventory 来审计和更新 iTop CMDB 中服务器基础信息的方案。
概述
大致流程如下:
- 服务器基础信息的采集,通过 Zabbix 的自定义 Key 功能来实现。
- 服务器基础信息信息存储,在 Zabbix 添加监控项时将其设置为某个 inventory 项目,这样做的优点是调用 API 时更方便。
- 服务器基础信息的利用,通过 Zabbix API 和 CMDB API 来审计和更新数据。
数据采集
配置 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
关于机架位,可能需要智能机架?有这种东西吗?我暂时没有这方面经验。
(全文完)
能提供更完整的配置方法吗