需求
旧平台中有一个业务模块叫做服务器巡检作业,需要迁移,但我想将原本服务器巡检的作业用代码来代替,服务器巡检的内容包括内存负载、CPU负载、IP地址、磁盘负载,PC巡检机器合起来大概300-500台左右,要求能一键拉取这些机器的监控项,时间尽量短一些。
选型
旧平台中,服务器巡检作业是每天登到机器上查询相关参数,再填上去的,最多多做了一个复制新增的功能。
新平台中,延续了之前旧平台当中的新建一条记录和复制新增的功能。虽然一条巡检记录有多个选项和参数,但是有的数据变化大,并且更重要,有的数据,例如应用系统备份、系统备份是几乎每天都一样的,所以假如自动生成,我着重生成3个参数,分别是CPU负载(百分比)、IP地址、内存负载(百分比),并且由于当初旧平台上的数据和巡检系统里头的数据不统一,导致同一个IP地址对应的系统名不一样,因此想尽量和新系统的数据进行结合。
查询条件里头,希望能包括 时间区段、巡检人、IP地址查询三种。
公司有两种监控平台,都是目前还算主流的监控平台,一个是ZStack,可以看我的这篇文章https://blog.csdn.net/qq_34093082/article/details/106096743,另一个是Zabbix。两种平台都监控了当前的几乎所有的机器,我看了一下zstack的文档,好的那没事了,再看了一下zabbix的api接口,然后找到了这个github上的神奇API。
来琢磨一下这个项目的文档,哇,使用过程就五个步骤
request部分,哇,筛选条件放request再call就行耶
filter.put("host", new String[] { host });
Request getRequest = RequestBuilder.newBuilder()
.method("host.get").paramEntry("filter", filter)
.build();
JSONObject getResponse = zabbixApi.call(getRequest);
那就决定是这个了
实现
1、引入dependency
<dependency>
<groupId>io.github.hengyunabc</groupId>
<artifactId>zabbix-api</artifactId>
<version>0.0.2</version>
</dependency>
2、先查得到数据
这部分代码肯定要写在工具类中,我们先写一个ZabbixUtil的工具类,定义全局静态变量分别放url,userName,password,连接到zabbix的内置API操作对应的php。
编写静态代码块,把连接的代码写到静态代码块中。
ZabbixUtil
private static DefaultZabbixApi zabbixApi;
private static String zabbixUserName = "Admin";
private static String zabbixPassword = "*******";
private static String zabbixURL = "http://172.18.196.38/zabbix/api_jsonrpc.php";
static {
zabbixApi = new DefaultZabbixApi(zabbixURL);
zabbixApi.init();
try {
zabbixApi.login(zabbixUserName, zabbixPassword);
}catch (Exception e){
e.printStackTrace();
}
}
登录失败会有提示消息,记得用户名和密码就写你zabbix的用户名和密码就行。
然后我们来查一段数据,先查所有的数据。
Request request = RequestBuilder.newBuilder().method("item.get")
.build();
JSONObject resJson = zabbixApi.call(request);
得到的是一个JsonObject,你需要经历JsonObject--》JsonArray--》List<Map>,才可以将它转化为能够处理的数据,实际上就是key-value,item是范例给的方法名,得到的结果看得出来是key(监控选项)对应的值,因此item这个方法名对应的就是zabbix里头的监控项,挺好,我们一开始就找到了痛点。
但是仅仅知道这个是不够的,由于zabbix的数据库是mysql,我想弄清其中的过程,就直接去找了后台的数据库。
hosts:主机表,存放了主机的信息,主键为hostid
items:监控选项表,存放了监控项对应的信息,关联hostid好知道是哪台主机的监控项,主键为itemid
history:监控值表,关联itemid,一个监控项对应一个值,带有时间戳作为不同时期的监控值
host_groups:分组表,关联hostid,主键为groupid,详情见下图
所以他应该就是主机-监控项-监控记录,如果要查询某个组底下的所有机器,查询对应的分组id就行。正好我们这次要查的是PC巡检组底下的,并且zabbix上已经做好了分组。
3、查询某个组底下所有的Host
上面提到了method为item.get,综合表名,我们试试hostgroup.get,看能不能得到某个分组底下所有的机器(提前通过ip地址知道了我们要查的组id为48)
try {
JSONObject json2 = new JSONObject();
json2.put("groupid", new String[]{groupid});
Request request = RequestBuilder.newBuilder().method("hostgroup.get")
.paramEntry("selectHosts",new String[]{"hostid","host"})
.paramEntry("filter",json2)
.build();
JSONObject resJson = zabbixApi.call(request);
String error = String.valueOf(resJson.get("error"));
if (!StringUtils.isEmpty(error) && error != "null") {
System.out.println("调用zabbix接口出错");
} else {
JSONArray jsonArray = resJson.getJSONArray("result");
resultHost = (List) jsonArray;
System.out.println("查询成功");
}
} catch (Exception e) {
e.printStackTrace();
}
这个resultHost就是我们查询到的对应主机列表,由于过滤条件可以放多个分组,但我这只放了一个,因此,取到这个值之后,我们需要先get(0),然后再getHost,得到hostid的列表。如果你想要同时获得对应hostid的ip地址是哪个,你可以在request里同时输出host,也就是它的ip地址,然后分两个list存放就OK了
Map<String,Object> groupMap = resultHost.get(0);
List list1 = (List) groupMap.get("hosts");
for (Object hostObjects : list1){
Map<String,Object> hostMaps = JSONObject.parseObject(JSON.toJSONString(hostObjects));
idResultList.add(hostMaps.get("hostid"));
hostResultList.add(hostMaps.get("host"));
}
4、根据hostid获得监控项
值得一提的是,虽然我们前面说history是存放监控值的表,但实际上,我们在用items.get的方法的时候,就已经获得的是最新的检测值了,它是key-value的形式。于是我们用 Request request = RequestBuilder.newBuilder().method("item.get"),获取监控项。
/***
* 根据某组当中所有的主机的id获取对应的主机信息
* Host name of Zabbix agent running ip地址
* Available memory / Total memory 剩余内存百分比
* Processor load (1 min average per core) cpu负载
*/
public List<Map<String, Object>> getAllItems(String[] hostStringArgs) throws ParseException {
List<Map<String, Object>> resultItem = null;
try {
JSONObject json1 = new JSONObject();
// json1.put("name", new String[]{"Host name of Zabbix agent running","Processor load (1 min average per core)",
// "Available memory","Total memory","Free disk space on / (percentage)","Free disk space on C: (percentage)"});
Request request1 = RequestBuilder.newBuilder().method("item.get")
.paramEntry("output", new String[]{"hostid","name","lastvalue"})
.paramEntry("hostids", hostStringArgs)
.paramEntry("filter",json1).build();
JSONObject resJson1 = zabbixApi.call(request1);
String error = String.valueOf(resJson1.get("error"));
if (!StringUtils.isEmpty(error) && error != "null") {
System.out.println("调用zabbix接口出错");
} else {
JSONArray jsonArray = resJson1.getJSONArray("result");
resultItem = (List) jsonArray;
System.out.println("查询成功");
}
}catch (Exception e){
e.printStackTrace();
}
return resultItem;
}
来分析一下这段json。
itemid自增的一个不管,lastValue是值,name是监控项,hostid是主机的id。的确是一个萝卜一个坑,一个监控指标一个值。
我们怎么去获取cpu负载、内存负载和ip地址这三个数据呢?当然是通过name来获取了。
这边和zabbix的管理员对比过了,name是什么样子,取决于你zabbix的模板里头叫啥名字,zabbix本身给你配了两个通用的模板,一个是Linux的,一个是Windows的,它们对于同一个监控项的name是不同的,例如说cpu负载,Linux的模板叫CPU load per core(1 min),Windows 叫做 CPU load (1 min) ,所以你一定要先在zabbix 客户端中,把模板内这个监控项的名字改成相同的。这样子你才能一段代码,同时获得两种机器的参数。
然后我写了以下这个复杂度超高的代码。
//遍历查询到的主机参数表将他们放在pc_check的表格当中
public String createZabbixData(String groupID) throws Exception {
ZabbixUtil zabbixUtil = new ZabbixUtil();
List<Map<String, Object>> allItems = new ArrayList<>();
ControllerUtil controller = new ControllerUtil();
String nowDateTime = DateTools.getNowDateTime();
List<String> ipAddressList = new ArrayList<>();
try {
String[] allGroupHostID = (String[]) (zabbixUtil.getAllGroupHostByGruopId(groupID).get(0));
String[] allGroupHostIpAddress = (String[]) (zabbixUtil.getAllGroupHostByGruopId(groupID).get(1));
if(allGroupHostID != null){
//1、搜索hostid,查找匹配hostid的数据
//2、写if else语句将对应的字段相应的数据库字段当中
allItems = zabbixUtil.getAllItems(allGroupHostID);
String cpuLoad = null;
String memoryLoad = null;
String host = null;
int insertRes = 0 ;
String diskResource = null;
for (int hostIdIndex = 0; hostIdIndex < allGroupHostID.length; hostIdIndex ++) {
cpuLoad = null;
memoryLoad = null;
totalMemory = null;
availableMemory = null;
host = null;
diskResource = "" ;
String hostid = allGroupHostID[hostIdIndex];
String ipAddress = allGroupHostIpAddress[hostIdIndex];
for (int itemIndex = 0; itemIndex < allItems.size(); itemIndex++) {
if(!String.valueOf(allItems.get(itemIndex).get("hostid")).equals(hostid)){
continue;
}else if(String.valueOf(allItems.get(itemIndex).get("name")).equals("Host name of Zabbix agent running")){
host = ipAddress;
}else if(String.valueOf(allItems.get(itemIndex).get("name")).equals("Processor load (1 min average per core)")){
cpuLoad = String.valueOf(allItems.get(itemIndex).get("lastvalue"));
}else if(String.valueOf(allItems.get(itemIndex).get("name")).equals("内存使用率")){
memoryLoad = String.valueOf(allItems.get(itemIndex).get("lastvalue"))+"%";
}else if (String.valueOf(allItems.get(itemIndex).get("name")).contains("Free disk space on (percentage)")){
diskResource += String.valueOf(allItems.get(itemIndex).get("name")).replace("Free disk space on (percentage) ", "")+"的剩余容量是"+ String.valueOf(allItems.get(itemIndex).get("lastvalue")+"%"+"\n");
}
}
//加入查询到的zabbix数据
insertRes = yfPcCheckDao.insertZabbixData(cpuLoad, memoryLoad,host, nowDateTime,diskResource);
}
}else {
return "error";
}
} catch (ParseException e) {
e.printStackTrace();
}
return "success";
}
咱就是找到这个分组底下的所有host,然后它们的ip地址连着监控参数和当前日期,一起插到了数据库里头。
里头怎么会有个内存使用率的监控指标呢,还是中文,因为内存负载这个指标,原生的模板是不提供的,你只能查到Total memory 和 available memory,想要负载就自己除咯,我之前做了除了,但是管理员那边说他可以在那把除法 的工作做好给我,你到时候获取内存使用率就行。因此我这边的这几个name,都是在 zabbix客户端修改过模板值的,你们那的name,一定不是叫这个名字。由此,我们就顺利的把zabbix的监控项插入进去了。
5、磁盘负载怎么查询
来看看这两张图。
注意到了吗,磁盘负载那一栏,是我们每次巡检的重点对象,但是每台机器的盘的数量不同,盘名字也各有所别,之前我们的参数都是固定名字的,现在名字不同了,怎么办?
这就涉及到了string 模糊匹配了,前面说到模板的name是可以修改的,我们先保证Windows 和 Linux对应监控项的name是一样的,这边改成 Free disk space on (percentage) ,然后用模糊匹配,后面是什么盘 我们不管,就把一个hostid底下的同个前缀的参数对应的值拼接成一个字符串就完事了。
if (String.valueOf(allItems.get(itemIndex).get("name")).contains("Free disk space on (percentage)")){
diskResource += String.valueOf(allItems.get(itemIndex).get("name")).replace("Free disk space on (percentage) ", "")+"的剩余容量是"+ String.valueOf(allItems.get(itemIndex).get("lastvalue")+"%"+"\n");
}
6、速度问题
做到这里,zabbix查询耗费1秒多,就已经能够拉取所有机器的数据插入到数据库中了。
紫色代表拉取下来并插入到数据库的数据。现在我想将系统中原本的该IP地址对应的系统名和巡检人一起放进去怎么办,我这边选择都插入完之后做update操作,为啥不在insert的时候就查到一起插入呢,因为这样子 查询的操作会乘以循环的倍数,这个循环是所以所有item循环一次。我把ip地址查到,再做update,那就少了很多次循环,这是第一次速度考虑。
感受到查询速度慢,我给数据表的主机资料表和巡检表的对应字段做了单值索引,速度直接提升2倍多,这是第二次速度考虑。
之前工具类静态代码块中写连接zabbix,减少初始化的时间,这是第三次速度考虑。
7、每天只能拉取一次数据
这里是纯前端的一个知识点,这边就直接放代码了。
<button class="add" id="pullZabbixBtn" onclick="createZabbixData()">拉取巡检数据</button>
<input type="hidden" id="findZabbixDataIfExist" value="${findZabbixDataIfExist}"/>
//监听是否当日已经拉取过zabbix上的数据
$(function () {
var findZabbixDataIfExist = $("#findZabbixDataIfExist").val()
if(findZabbixDataIfExist == 1){
$("#pullZabbixBtn").attr("disabled",true);
$("#pullZabbixBtn").addClass("end");
$("#pullZabbixBtn").text('今日数据已拉取');
}
})
以上,就是如何使用ZabbixAPI实现自动化运维中拉取数据这个功能的我的做法。