最近在忙于搭建公司内部的应用日志分析系统,鉴于公司架构的要求,所有的服务都必须双活。对于应用日志分析系统,现在普遍采用的都是ELK stack的框架,前端部分的kibana是必选项。而如果需要将kibana做成双活,则两个kibana之间需要做一定的同步(因为共享一个elasticsearch集群作为数据存储,则两个kibana之间在执行alarm,report等动作时,需要一定的同步机制,避免同一个alarm或者report在两台服务器上发两遍)。当然,同步的方法有很多,你可以选择直接使用elasticsearch作为同步的服务器,不过这会涉及到更多的开发工作和数据的设计工作。我是一个懒人,毕竟日志分析系统上使用到了kafka和zookeeper,那还是用zookeeper来做分布式同步吧,毕竟这货就是拿来干这个的。
但kibana是一个nodejs开发的应用,弄起来还是不如java顺手,这里分享一下,其中踩过的一些坑。
需用同步的是kibana是的一个插件sentinl。这是一个类似于x-pack的watcher的插件,用于告警和报告服务。因为安装了两个kibana服务器,一旦订制了一条告警规则,规则会被存储于elasticsearch,两个服务器都会定时出发,造成同一个告警双发的问题。为了解决这个问题,需要在两台服务器间做同步。因为kibana是用node开发,因此需要使用到node上的node-zookeeper。这里需要注意的是,该模块是一个node native module,需要在对应的平台上编译,具体方法见之前的两篇博文:用native C++模块扩展Node.js和使用docker编译不同平台上的node native module。
先来看个简单的例子,该例子是node-zookeeper里自带的test.js:
var ZooKeeper = require ("zookeeper");
var zk = new ZooKeeper({
connect: "localhost:8888" // zk server的服务器地址和监听的端口号
,timeout: 200000 // 以毫秒为单位
,debug_level: ZooKeeper.ZOO_LOG_LEVEL_WARN
,host_order_deterministic: false
});
zk.connect(function (err) {
if(err) throw err;
console.log ("zk session established, id=%s", zk.client_id);
zk.a_create ("/node.js1", "some value", ZooKeeper.ZOO_SEQUENCE | ZooKeeper.ZOO_EPHEMERAL, function (rc, error, path) {
if (rc != 0) {
console.log ("zk node create result: %d, error: '%s', path=%s", rc, error, path);
} else {
console.log ("created zk node %s", path);
process.nextTick(function () {
zk.close ();
});
}
});
});
其中:
创建一个zookeeperHelper.js
'use strict'
const ZooKeeper = require('zookeeper');
const Promise = require('bluebird');
const _ = require('lodash');
'development';
let connect = 'localhost:2181';
let timeout = 20000; // client的超时时间,单位毫秒。server端在该时间内没有收到心跳会判断客户端掉线
let path = '/sentinl';
let debug_level = ZooKeeper.ZOO_LOG_LEVEL_WARN;
let host_order_deterministic = false;
let defaultInitOpt = {
connect,
timeout,
debug_level,
host_order_deterministic
};
class ZK {
constructor(opt) {
this.opt = opt;
this._initZk();
}
_initZook() {
this.zookeeper = new ZooKeeper(this.opt || defaultInitOpt);
}
_initZk() {
this.zookeeper = new ZooKeeper(this.opt || defaultInitOpt);
}
registZk() {
let self = this;
self.zookeeper.connect(function (err, client) {
if (err) throw err;
console.log('zk session established, id=%s', self.zookeeper.client_id);
self.client = client;
// create parent node
client.a_create(path, null, ZooKeeper.ZOO_PERSISTENT, function (rc, error, path) {
if (rc != 0) {
console.log("zk node create result: %d, error: '%s', path=%s", rc, error, path);
} else {
console.log("created zk node %s", path);
}
})
// create children node
client.a_create(path + '/' + 'alarm', null, ZooKeeper.ZOO_SEQUENCE | ZooKeeper.ZOO_EPHEMERAL, function (rc, error, path) {
if (rc != 0) {
console.log("zk node create result: %d, error: '%s', path=%s", rc, error, path);
} else {
let pathArr = path.split('/');
self.node = pathArr[pathArr.length - 1];
console.log("mynode is %s", self.node);
}
})
})
}
getLock() {
let self = this;
return new Promise((resolve, reject) => {
console.log('zk session established, id=%s', self.zookeeper.client_id);
self.client.a_get_children(path, true, function (rc, error, children) {
if (rc !== 0) {
console.log('zk node get result: %d, error: "%s", stat=%s, children=%s', rc, error, stat, children);
} else {
children = children.sort();
console.log('get zk children: ' + children);
if (children[0] === self.node) {
resolve(true);
}
else {
resolve(false);
}
}
})
});
}
}
module.exports = ZK;
将该模块拷贝到sentinl的server/lib/目录。同时更新sentinl的package.json文件。加入以下dependency
"zookeeper": "^3.4.9"
通过以下命令,下载zookeeper:
npm update
修改schedule.js文件:
import ZookeeperHelper from './zookeeperHelper';
/**
* Schedules and executes watchers in background
*/
export default function Scheduler(server) {
const config = getConfiguration(server);
let watcher;
let client;
const zkHelper = new ZookeeperHelper();
...
/* Run Watcher in interval */
server.sentinlStore.schedule[task._id].later = later.setInterval(() => {
zkHelper.getLock().then(function (result) {
if(result)
{
server.log(['status', 'info', 'Sentinl', 'zookeeper'],
'get the lock, trigger watcher ' + task._id);
watching(task);
}
else {
server.log(['status', 'info', 'Sentinl', 'zookeeper'],
'not get the lock');
}
}, function (error) {
server.log(['status', 'error', 'Sentinl', 'scheduler'],error);
})
}, interval);
这样,就可以通过一个简单的zookeeper锁,保证同一个时刻只有一个kibana服务器能够响应告警和报告服务