资产采集采集插件¶
资产采集是【管控平台】用于资产采集的插件管理系统,维护完毕的插件在【资源平台】进行资产采集任务的管理。
资产采集插件的开发需要符合以下条件即可:
- 资产采集的对象必须在【资源平台】提前定义好模型。
- 资产采集输出的字段需要和【资源平台】定义的模型属性(字段)进行对应。
- 以JSON格式输出。
MySQL资产采集案例¶
下面以一个MySQL资产采集插件为案例,介绍如何开发一个资产采集插件:
- CMDB新建资源模型
- 管控平台新建资产采集插件
- CMDB新建采集任务
- 查看采集详情
CMDB新建资源模型¶
在CMDB的资源模型下新建资产模型,记下模型分组和模型名称,新建采集脚本会选择
新建模型后,在模型内新建要采集的字段,除名称和唯一标识外其余字段都将写入采集脚本内(CMDB_FIELDS),作为脚本输出字段, 记下要用的属性的属性编码,采集脚本会用。
管控平台新建资产采集插件¶
在管控平台新建采集插件,指定分类(模型分组)采集对象(CMDB模型)脚本内容等,记下插件名称
脚本内容
- CMDB_FIELDS需将采集到的字段与MySQL字段对应
- UNIQUE_FIELDS为采集更新依据字段,新建数据时将以此字段做完仓库数据的唯一标识,注意不能使用名称和唯一标识这两个内置的属性。
- clean_data()函数将采集来的数据输出成对应CMDB模型新建的字段,并格式化输出
- main()函数为入口函数
- get_data_info()为自定义函数,可将采集步骤写入
#!/usr/bin/env python3
# -*- coding: utf8 -*-
"""
资产采集脚本
1. 新建资产采集脚本首先需要在CMDB新建要采集的模型,并创建指定的属性(字段)
2. 在脚本中采集的字段与创建的模型属性对应,格式如CMDB_FIELDS字典,脚本输出的字段与CMDB模型中的属性字段对应
3. 在脚本中指定更新依据字段,格式如UNIQUE_FIELDS列表,上报CMDB时会以此字段组合为唯一标识,可以指定多个字段
4. 脚本输出为指定格式
{
"data_info": [
{
"MySQL_PID": 7482,
"MySQL_SERVICE_NAME": "mysql",
"MySQL_STATUS": "sleeping",
"MySQL_DATA_DIR": "/var/lib/mysql",
"MySQL_SOCKET_FILE": "/var/lib/mysql/mysql.sock",
"MySQL_LISTEN_IP": "127.0.0.1",
"MySQL_LISTEN_PORT": 3306
}
],
"unique_fields": [
"MySQL_LISTEN_PORT",
"MySQL_LISTEN_IP"
]
}
"""
import sys
import json
import psutil
PY3 = sys.version_info[0] > 2
# CMDB字段对应
CMDB_FIELDS = {
"username": "MySQL_SERVICE_NAME",
"pid": "MySQL_PID",
"status": "MySQL_STATUS",
"datadir": "MySQL_DATA_DIR",
"socket": "MySQL_SOCKET_FILE",
"listen_ip": "MySQL_LISTEN_IP",
"listen_port": "MySQL_LISTEN_PORT",
}
# 更新依据字段
UNIQUE_FIELDS = [
"MySQL_LISTEN_PORT",
"MySQL_LISTEN_IP"
]
class CollectMySQL:
def __init__(self):
self.cmdb_fields = CMDB_FIELDS
self.unique_fields = UNIQUE_FIELDS
# 通过mysql进程关键字"mysqld"和"sock"来确定MySQL的Pid
def get_pid_of_mysql(self):
"""Return pid of MySQL Instance"""
mysql_pids = []
for p in psutil.process_iter(attrs=['name', 'pid']):
if 'mysqld' == p.info['name']:
mysql_pids.append(p.pid)
return mysql_pids
# 通过MySQL的Pid来获取mysql的进程详情和启动参数
def get_detail_by_pid(self, pid):
"""Return detail of every pid with 定义属性"""
try:
p = psutil.Process(pid)
except:
pass
return p.as_dict(attrs=['name', 'pid', 'username', 'status', 'connections', 'cmdline'])
def get_data_info(self):
"""input you code"""
pids = self.get_pid_of_mysql()
data_info = list()
if len(pids) > 0:
for pid in pids:
mysql_detail = {}
proc_detail = self.get_detail_by_pid(pid)
mysql_detail["pid"] = proc_detail['pid']
mysql_detail["username"] = proc_detail['username']
mysql_detail["status"] = proc_detail['status']
for name in proc_detail['cmdline']:
if "datadir" in name:
mysql_detail["datadir"] = name.split('=')[1]
if "sock" in name:
mysql_detail["socket"] = name.split('=')[1]
for name in proc_detail['connections']:
mysql_detail["listen_ip"] = name.laddr[0]
mysql_detail["listen_port"] = name.laddr[1]
data_info.append(mysql_detail)
return data_info
def clean_data(self, data_info):
"""
将初始字段与CMDB字段对应并整理成指定输出格式
data_info: [
{
"pid": 7482,
"username": "mysql",
"status": "sleeping",
"datadir": "/var/lib/mysql",
"socket": "/var/lib/mysql/mysql.sock",
"listen_ip": "127.0.0.1",
"listen_port": 3306
}
]
"""
new_data = []
for data in data_info:
dic = {self.cmdb_fields.get(k): v for k, v in data.items() if self.cmdb_fields.get(k)}
new_data.append(dic)
return {"data_info": new_data, "unique_fields": self.unique_fields}
# 将获取数据格式化为JSON
def main(self, args):
"""Trans All data format with JSON"""
data_info = self.get_data_info()
clean_data_info = self.clean_data(data_info)
return json.dumps(clean_data_info)
if __name__ == "__main__":
sys.stdin.close()
print(CollectMySQL().main(sys.argv))
#!/bin/bash
# mysql scanner
# count process of mysql
process_count=$(pgrep -f -a "mysqld" | wc -l )
container_count=$(pgrep -f "mysqld" -U 999 | wc -l )
if [ $process_count -ge 1 ];then
if [ $container_count -ge 1 ];then
mysql_instance="container"
else
mysql_instance="host"
fi
else
exit 1
fi
username=root
password="123456"
ipaddr=10.0.0.80
sql1='''show VARIABLES WHERE variable_name LIKE "character_set_database"
OR variable_name LIKE "slow_query_log"
OR variable_name LIKE "datadir"
or variable_name LIKE "basedir"
OR variable_name LIKE "version"
or variable_name LIKE "log_bin" '''
sql2='SHOW VARIABLES WHERE variable_name ="default_storage_engine"'
# 大小
sql3="select concat(round((sum(data_length)+sum(index_length))/1024/1024,2),'MB') as data from information_schema.tables"
# 数据库名
sql4="show databases"
base_info=$(mysql -u$username -h$ipaddr -p$password -s -e "${sql1}" 2>/dev/null)
# read or write
sql5='''SHOW VARIABLES WHERE variable_name= "read_only"'''
read_info=$(mysql -u$username -h$ipaddr -p$password -s -e "${sql5}" 2>/dev/null)
sql10='''SHOW VARIABLES WHERE variable_name LIKE "innodb_buffer_pool_size" OR variable_name LIKE "innodb_log_buffer_size" OR variable_name LIKE "innodb_flush_log_at_trx_commit" OR variable_name LIKE "thread_cache_size" OR variable_name LIKE "query_cache_size" OR variable_name LIKE "max_connections"'''
base_info2=$(mysql -u$username -h$ipaddr -p$password -s -e "${sql10}" 2>/dev/null)
charset=`echo $base_info|awk -F ' ' '{print $4}'`
db_version=`echo $base_info|awk -F ' ' '{print $12}'`
db_size=$(mysql -u$username -h$ipaddr -p$password -s -e "${sql3}" 2>/dev/null)
basedir=`echo $base_info|awk -F ' ' '{print $2}'`
datafile_path=`echo $base_info|awk -F ' ' '{print $6}'`
db_name=$(mysql -u$username -h$ipaddr -p$password -s -e "${sql4}" 2>/dev/null)
storage_engine=`echo $(mysql -u$username -h$ipaddr -p$password -s -e "${sql2}" 2>/dev/null)|awk -F ' ' '{print $2}'`
is_binlog=`echo $base_info|awk -F ' ' '{print $8}'`
is_slow_query_log=`echo $base_info|awk -F ' ' '{print $10}'`
innodb_buffer_pool_size=`echo $base_info2|awk -F ' ' '{print $2}'`
innodb_log_buffer_size=`echo $base_info2|awk -F ' ' '{print $6}'`
innodb_flush_log_at_trx_commit=`echo $base_info2|awk -F ' ' '{print $4}'`
thread_cache_size=`echo $base_info2|awk -F ' ' '{print $12}'`
query_cache_size=`echo $base_info2|awk -F ' ' '{print $10}'`
max_connections=`echo $base_info2|awk -F ' ' '{print $8}'`
is_read_only=`echo $read_info|awk -F ' ' '{print $2}'`
db_names=($db_name)
export db_name_list='"'${db_names[0]}'"'
for i in "${db_names[@]:1}";
do
db_name_list+=,'"'$i'"'
done
ret='''{"'"data_info"'":
[{
"'"MySQL_DEPLOY_ENV"'":"'"$mysql_instance"'",
"'"MySQL_CHART_SET"'":"'"$charset"'",
"'"MySQL_VERSION"'":"'"$db_version"'",
"'"MySQL_DB_SIZE"'":"'"$db_size"'",
"'"MySQL_INSTALL_PATH"'":"'"$basedir"'",
"'"MySQL_DBFILE_DIR"'":"'"$datafile_path"'",
"'"MySQL_DB_NAME"'":['"$db_name_list"'],
"'"MySQL_STORE_ANGINE"'":"'"$storage_engine"'",
"'"MySQL_ENABLE_BINLOG"'":"'"$is_binlog"'",
"'"MySQL_ENABLE_SLOWLOG"'":"'"$is_slow_query_log"'",
"'"MySQL_THREAD_CACHE_SIZE"'":'"$thread_cache_size"',
"'"MySQL_QUERY_CACHE_SIZE"'":'"$query_cache_size"',
"'"MySQL_MAX_CONNECTIONS"'":'"$max_connections"',
"'"MySQL_IS_READONLY"'":"'"$is_read_only"'",
"'"MySQL_PORT"'":3306,
"'"MySQL_IP_ADDR"'":"'"$ipaddr"'"
}],
"'"unique_fields"'":[
"'"MySQL_IP_ADDR"'",
"'"MySQL_PORT"'"]
}'''
echo "$ret"
CMDB新建采集任务¶
- CMDB新建采集任务选择采集插件以及指定的脚本版本
- 选择要执行的主机,请选择纳管状态正常的主机
- 采集时段可以选择服务器闲时执行
- 采集频率为脚本执行采集数据频率
- 上报间隔为采集数据入库频率
查看采集详情¶
可按照主机查看采集脚本执行情况,并查看上报详情
MongoDB资产采集案例¶
下面是一个经典的Shell版本的资产采集插件编写,其中有以下注意事项:
- 如果是容器化部署的应用,需要主机应用的PID和监听端口的PID是不同的。
- 如果部署了Prometheus的Exporter需要注意,过滤应用名称的时候,需要忽略掉Exporter,因为大部分名称是一致的。
- 如果需要自定义采集后的名词,需要返回:模型唯一标识_VISIBLE_NAME字段,例如下面的MONGODB_VISIBLE_NAME。
- Shell脚本中的declare -A是声明了一个变量的类型为数组。因为一台主机上可能有多个采集的实例,所以先把数据放到一个数组中,然后去循环采集。
采集脚本¶
#!/bin/bash
# 获取所有的MongoDB进程ID
mongodb_instances(){
# 首先判断本主机是否存在MongoDB,如果不存在,直接返回空,并退出脚本。
MONGODB_PIDS=$(ps aux | grep "mongod --auth" | grep -v grep | awk -F ' ' '{print $2}')
#if [ -z ${MONGODB_PIDS} ];then
# exit
#fi
}
# 编写资产采集函数
mongodb_cmdb(){
INSTALL_DIR=$(ps $MONGODB_PID | awk -F ' ' '{print $5}' | tail -1)
LISTEN_PORT=$(netstat -ntlp | grep ${MONGODB_PID}/ | awk -F ' ' '{print $4}' | awk -F ':' '{print $2}')
LISTEN_IP=$(netstat -ntlp | grep ${MONGODB_PID}/ | awk -F ' ' '{print $4}' | awk -F ':' '{print $1}')
VISIBLE_NAME=${HOSTNAME}_${INSTALL_DIR}
instance_ret='''
{
"'"MONGODB_INSTALL_DIR"'":"'"$INSTALL_DIR"'",
"'"MONGODB_LISTEN_PORT"'":"'"$LISTEN_PORT"'",
"'"MONGODB_LISTEN_IP"'":"'"$LISTEN_IP"'",
"'"MONGODB_VISIBLE_NAME"'":"'"$VISIBLE_NAME"'"
},
'''
echo $instance_ret
}
# 循环调用采集函数进行采集
main(){
declare -A MONGODB_INSTENCE_LIST
mongodb_instances;
for MONGODB_PID in ${MONGODB_PIDS};do
MONGODB_INSTENCE=$(mongodb_cmdb)
MONGODB_INSTENCE_LIST["$MONGODB_PID"]=${MONGODB_INSTENCE}
done
ret_dict_tmp=`echo ${MONGODB_INSTENCE_LIST[*]}`
ret_dict=$(echo ${ret_dict_tmp%?})
# 数据组装并输出
ret='''{"'"data_info"'":
['''${ret_dict}'''],
"'"unique_fields"'":[
"'"MONGODB_LISTEN_IP"'",
"'"MONGODB_LISTEN_PORT"'"]
}'''
echo $ret
}
main
主机采集案例¶
需求场景:默认内置的主机模型无法满足需求,你需要在资源平台(CMDB)中给云主机这个模型,增加字段(属性),然后你还想能够自动采集数据。
增加模型字段¶
打开【资源平台】-【资源模型】-【云主机】-(添加属性)
编写资产采集脚本¶
1.编写脚本,脚本需要输出一个JSON。
=== "Shell版本"
#!/bin/bash
#******************************************
# Author: Jason Zhao
# Email: zhaoshundong@opsany.com
# Organization: OpsAny https://www.opsany.com/
# Description: Host Asset collection plug-in
#******************************************
# 编写资产采集函数
cloud_host_cmdb(){
CPU_ARCH=$(uname -m)
MEMORY=$(free -m | grep 'Mem:' | awk -F ' ' '{print $2}')
KERNEL_RELEASE=$(uname -r)
OS=$(lsb_release -i | awk -F ' ' '{print $3}')
CPU_NUM=$(lscpu | grep 'CPU(s):' | awk -F ' ' '{print $2}')
INTERNAL_IP=$(ifconfig eth0 | grep inet | grep -v inet6 |awk '{print $2}'|tr -d "addr:")
instance_ret='''
{
"'"CLOUD_SERVER_CPU_ARCH"'":"'"$CPU_ARCH"'",
"'"CLOUD_SERVER_CPU_NUM"'":"'"$CPU_NUM"'",
"'"CLOUD_SERVER_OS"'":"'"$OS"'",
"'"CLOUD_SERVER_MEMORY"'":"'"$MEMORY"'",
"'"CLOUD_SERVER_INTERNAL_IP"'":"'"$INTERNAL_IP"'",
"'"CLOUD_SERVER_HOSTNAME"'":"'"$HOSTNAME"'",
"'"CLOUD_SERVER_KERNEL_RELEASE"'":"'"$KERNEL_RELEASE"'",
"'"CLOUD_SERVER_VISIBLE_NAME"'":"'"$HOSTNAME"'"
}
'''
echo $instance_ret
}
# 主函数
main(){
ret_dict=$(cloud_host_cmdb)
# 数据组装为JSON并输出
ret='''{"'"data_info"'":
['''${ret_dict}'''],
"'"unique_fields"'":[
"'"CLOUD_SERVER_INTERNAL_IP"'"]
}'''
echo $ret
}
main
2.执行脚本进行调试,查看输出是否正常。
[root@ops-node1 ~]# bash host.sh
3.使用Python来检测输出是否符合JSON规范。
[root@ops-node1 ~]# bash host.sh | python -c "import sys,json;json.loads(sys.stdin.read());print 'OK'"
新建采集插件¶
打开【管控平台】-【资产采集】-【新增】创建一个主机资产采集插件。将测试通过的脚本添加进去,并设置版本。