LongLong's Blog

分享IT技术,分享生活感悟,热爱摄影,热爱航天。

实现Zabbix的高可用

Zabbix作为一个分布式的监控系统,相比Nagios其较大的优点在于设置和数据保存在数据库中,便于维护和分析,同时自身或是通过grafana还可以实现监控项目曲线图的绘制。可以完成以前使用Cacti+Nagios来实现的系统监控任务,相关的监控主机配置只需要保存一份即可,带来了很大的便利。

Zabbix和Nagios的监控方式较为类似,其包括Zabbix-server和Zabbix-agent,对应Nagios中的Nagios和Nrpe,但Zabbix可以支持被监控的主机主动向Zabbix-server提交需要监控项目的相关数据,如此可以很大程度减少Zabbix-server所在的服务器在数据采集时的工作量,以之前的经验在Cacti+Nagios采集数据的时间点,监控服务器的负载会非常高,同时大量读写RRD文件也会成为系统之后的瓶颈,同时监控一两百台服务器就已经力不从心了。

1. Zabbix的高可用问题

对于一个监控系统都存在的一个问题就是如何来监控自己,如果自身发生故障会导致全部监控实效,并且也无法发送报警消息。这就需要实现Zabbix系统的高可用,至少在监控系统出现故障时能够及时发送报警消息。一个比较简单的想法就是通过其他的脚本来监控Zabbix的状态,但这并不是一个很好的做法,相当于又做了一套新的监控系统,而且同样存在如何自监控的问题。

2. Zabbix的系统组成

首先分析一下Zabbix的系统组成,其包括负责处理监控数据的zabbix-server进程,另外还包括一个用来做Zabbix相关配置的Web前端服务和一个用来保存配置和监控数据的MySQL数据库,在被监控的主机上还存在一个用来执行监控逻辑和发送监控信息的zabbix-agentd进程。

对于一个监控项目,监控的流程为zabbix-server向zabbix-agentd发送要获取的监控数据,如果为被动监控,则zabbix-server通过调用zabbix-agentd中提供的相关监控函数获取到需要的监控项目数据并保存到数据库,如果为主动监控,则zabbix-server通知zabbix-agentd需要的数据项目和提交的时间间隔,然后由zabbix-agentd按照要求主动提交监控数据。

3. Zabbix的高可用实现

其中需要做到高可用的几个过程分别为,zabbix-server读取数据库,zabbix-agentd向zabbix-server提交数据。这里使用的方法是让zabbix-server通过本机安装的Haproxy来连接数据库,同时zabbix-agentd则通过本机的Haproxy来向zabbix-server提交数据。整个系统的结构设计入图所示

使用两台服务器来安装Zabbix监控服务,如图中左右两个虚线方框中所示,其中包括配置使用的Web前端,Zabbix-server,Haproxy,MySQL,Haproxy中配置MySQL的后端分别指向两台服务器上的MySQL,两个MySQL服务通过主从复制保持数据的一致性,Haproxy中设置Zabbix-server的后端分别指向两台服务器上的Zabbix-server,另外需要监控的主机上安装Haproxy设置Zabbix-server后端为上述的两个Zabbix-server,其Zabbix-agent通过这个Haproxy来向Zabbix-server提交数据。

如此任何一个Zabbix-server服务器出现问题,都可以通过Haproxy切换到另外一个Zabbix-server服务器上,实现监控服务的高可用。同时如此配置后建议监控项目都使用主动模式,因为如果配置为Zabbix-server通过zabbix-agentd获取,则两个Zabbix-server会采集两次数据,写入时可能会产生一些冲突。

4. 相关配置

Haproxy的配置,在Haproxy的配置文件haproxy.cfg中增加以下配置

#两个MySQL后端,这里设置其中的从库为backup,防止两个库同时写入导致数据的冲突
listen mysql
	bind 127.0.0.1:33061
	mode tcp
	server z1 192.168.1.101:3306
	server z2 192.168.1.102:3306 backup

#Zabbix-server后端
listen zabbix
	bind 127.0.0.1:10061
	mode tcp
	server z1 192.168.1.101:10051
	server z2 192.168.1.102:10051 backup

Zabbix-agent的配置,在配置中将连接的Zabbix-server指向本机的Haproxy

#修改ServerActive
ServerActive=127.0.0.1:10061

另外为了方便Zabbix-agent配置的管理,建议将Hostname参数单独写到一个文件中(这个参数是Zabbix配置中配置的对应该主机的名字),如/etc/zabbix/zabbix_agentd.d/hostname.conf,这样除了这一个文件以外,全部需要监控的主机的Zabbix-agent配置都是相同的。

最后将Web前端的配置也进行修改如下,即使连接的过程都通过Haproxy来实现

<?php
// Zabbix GUI configuration file.
global $DB;

$DB['TYPE']     = 'MYSQL';
$DB['SERVER']   = '127.0.0.1';
$DB['PORT']     = '33061';
$DB['DATABASE'] = 'zabbix';
$DB['USER']     = 'zabbix';
$DB['PASSWORD'] = 'zabbix';

// Schema name. Used for IBM DB2 and PostgreSQL.
$DB['SCHEMA'] = '';

$ZBX_SERVER      = '127.0.0.1';
$ZBX_SERVER_PORT = '10061';
$ZBX_SERVER_NAME = '';

$IMAGE_FORMAT_DEFAULT = IMAGE_FORMAT_PNG;

使用Python调用C函数

脚本语言经常会作为一种“胶水语言”来使用,来完成对各种外来组建的整合控制功能,如此能够将外部的功能简单的进行导入成为了一种脚本语言是否好用的关键。之前一直使用Lua做类似的工作,其优点就是轻量级,可以直接使用直接编译的标准so,缺点主要是功能过于有限,不适合完成比较复杂的工作。而由于Raspberry PI主要使用Python作为核心编程语言,这里就尝试使用Python来完成整合任务,其中关键的步骤就是使用Python调用C的函数。以下的方法仅适用于Python2。

1. 直接调用C的库函数

直接使用ctypes可以直接引入C函数库,然后直接调用即可,代码如下

from ctypes import *;
libc = cdll.LoadLibrary('/usr/lib/libc.dylib');
libc.printf("Hello World!\n");

2. 编写可用Python调用的C函数

与编写Lua可调用的C函数类似,Python可调用的C函数也为特定的模式,并通过编译为so的方式提供给Python使用,以下为简单的一个例子

#include <Python.h>

PyObject* wrap_demo_func(PyObject* self, PyObject* args) {
    int a, b;
    if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
        return NULL;
    }
    return Py_BuildValue("i", a + b);
}

static PyMethodDef demoMethods[] = {
    {"demo_func", wrap_demo_func, METH_VARARGS, "demo func"},
    {NULL, NULL}
};

extern "C" void initdemo() {
    Py_InitModule("demo", demoMethods);
}

编译的命令如下

g++ -g -Wall -I /usr/include/python2.7/ -fpic --shared demo.cc -o demo.so

运行的Python脚本如下

import demo;
print demo.demo_func(1, 2);

运行后会输出3

3. 编写Python调用的C函数中的一些处理

调用一个C++类中的方法

#include <Python.h>
class Demo {
public:
    void func() {
        printf("Hello World!\n");
    }
};
static void del_demo(PyObject *obj) {
    delete (Demo*) PyCapsule_GetPointer(obj, "Demo");
}
PyObject* wrap_create_demo(PyObject* self, PyObject* args) {
    Demo* demo = new Demo();
    return PyCapsule_New(demo, "Demo", del_demo);
}

PyObject* wrap_demo_func(PyObject* self, PyObject* args) {
    Demo* demo;
    PyObject* p_demo;
    if (!PyArg_ParseTuple(args,"O", &p_demo)) {
        return NULL;
    }
    if (!(demo = (Demo*) PyCapsule_GetPointer(p_demo, "Demo"))) {
        return NULL;
    }
    demo->func();
    return Py_BuildValue("");
}

static PyMethodDef demoMethods[] = {
    {"create_demo", wrap_create_demo, METH_VARARGS, "create demo"},
    {"demo_func", wrap_demo_func, METH_VARARGS, "demo func"},
    {NULL, NULL}
};

extern "C" void initdemo() {
    Py_InitModule("demo", demoMethods);
}

运行的Python脚本如下

import demo;
d = demo.create_demo();
demo.demo_func(d);

运行后输出Hello World!