跳转至
首页 解决方案 下载 文档
演示环境

资产采集插件开发

资产采集是【管控平台】用于资产采集的插件管理系统,维护完毕的插件在【资源平台】进行资产采集任务的管理。

资产采集插件的开发需要符合以下条件即可:

  • 资产采集的对象必须在【资源平台】提前定义好模型。
  • 资产采集输出的字段需要和【资源平台】定义的模型属性(字段)进行对应。
  • 以JSON格式输出。

MySQL资产采集案例

下面以一个MySQL资产采集插件为案例,介绍如何开发一个资产采集插件:

  1. CMDB新建资源模型
  2. 管控平台新建资产采集插件
  3. CMDB新建采集任务
  4. 查看采集详情

CMDB新建资源模型

在CMDB的资源模型下新建资产模型,记下模型分组和模型名称,新建采集脚本会选择

CMDB新建资产模型

新建模型后,在模型内新建要采集的字段,除名称和唯一标识外其余字段都将写入采集脚本内(CMDB_FIELDS),作为脚本输出字段, 记下要用的属性的属性编码,采集脚本会用。

CMDB新建采集字段

管控平台新建资产采集插件

在管控平台新建采集插件,指定分类(模型分组)采集对象(CMDB模型)脚本内容等,记下插件名称

新建采集插件-脚本内容

新建采集插件-调试

脚本内容

  1. CMDB_FIELDS需将采集到的字段与MySQL字段对应
  2. UNIQUE_FIELDS为采集更新依据字段,新建数据时将以此字段做完仓库数据的唯一标识,注意不能使用名称和唯一标识这两个内置的属性。
  3. clean_data()函数将采集来的数据输出成对应CMDB模型新建的字段,并格式化输出
  4. main()函数为入口函数
  5. 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新建采集任务

  1. CMDB新建采集任务选择采集插件以及指定的脚本版本
  2. 选择要执行的主机,请选择纳管状态正常的主机
  3. 采集时段可以选择服务器闲时执行
  4. 采集频率为脚本执行采集数据频率
  5. 上报间隔为采集数据入库频率

新建资产采集任务-1

新建资产采集任务-2

查看采集详情

可按照主机查看采集脚本执行情况,并查看上报详情

查看资产采集任务详情

查看资产采集任务详情2

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'"

新建采集插件

打开【管控平台】-【资产采集】-【新增】创建一个主机资产采集插件。将测试通过的脚本添加进去,并设置版本。

Document