elastalert删除修改规则,规则仍然在作用,必须重启生效 源码分析——elastalert.py核心功能
1.前言
elastalert在kibana有个前端插件,在前台就可以对规则进行增删改,那么前台操作完之后,后端做了什么样的操作呢,这里根据个人俩天的熟悉与探究,先总结一个初版本,以进行反思与回顾。
- github源码:Yelp/elastalert
- 具体文件路径:elastalert/elastalert/elastalert.py
2.整体逻辑
来看elastalert.py的main()主函数干了什么:
def main(args=None):
signal.signal(signal.SIGINT, handle_signal)
if not args:
args = sys.argv[1:]
client = ElastAlerter(args)
if not client.args.silence:
client.start()
-
创建一个ElastAlerter类的对象,并在未设置silence模式下调用start()函数
-
类进行初始化操作,
self.rules = self.rules_loader.load(self.conf, self.args)
这句,调用了loaders.py文件的RulesLoader类中的load()函数,负责发现并加载conf和args中定义的所有规则,将config.yaml配置文件中所配置的rules_folder
文件夹下的所有yaml文件作为规则返回规则列表给全局变量self.rules。remove = [] for rule in self.rules: if not self.init_rule(rule): remove.append(rule) list(map(self.rules.remove, remove))
对每个规则进行初始化。
-
start()函数给每一个规则塞一个初始化时间,然后写一句info日志
Starting up
服务起来了,使用初始化步骤中创建的scheduler调度器增加任务handle_config_change和handle_pending_alerts,我先只关注一个handle_config_change,也就是当配置修改的时候,做了哪些操作。
3.规则同步,运行中的规则如何与rules_folder中的yaml规则文件保持同步
start()函数开启后进行一个间断性的轮询操作,以保持运行中的规则和rules_folder中的规则文件的同步性。handle_config_change负责处理配置改变,里面有load_rule_changes负责规则同步。来看具体逻辑:(将在代码中插入注释)
def load_rule_changes(self):
""" Using the modification times of rule config files, syncs the running rules
to match the files in rules_folder by removing, adding or reloading rules. """
# 使用配置文件中规则的修改时间,对运行中使用的规则与rules_folder 规则文件夹中的增删改的规则进行同步
# (也就是,规则文件的增删改在别处已经完成,其后在这里只将在运行中的规则进行同步即可)
new_rule_hashes = self.rules_loader.get_hashes(self.conf, self.args.rule)
# 检查当前修改了的规则
# Check each current rule for changes
for rule_file, hash_value in self.rule_hashes.items():
if rule_file not in new_rule_hashes:
# Rule file was deleted # 规则文件被删除的情况
elastalert_logger.info('Rule file %s not found, stopping rule execution' % (rule_file))
# self.rules列表中删掉这个规则
self.rules = [rule for rule in self.rules if rule['rule_file'] != rule_file]
continue
if hash_value != new_rule_hashes[rule_file]:
# Rule file was changed, reload rule # 规则文件被修改,重载规则
try:
new_rule = self.rules_loader.load_configuration(rule_file, self.conf)
if not new_rule:
logging.error('Invalid rule file skipped: %s' % rule_file)
continue
if 'is_enabled' in new_rule and not new_rule['is_enabled']:
elastalert_logger.info('Rule file %s is now disabled.' % (rule_file))
# Remove this rule if it's been disabled
self.rules = [rule for rule in self.rules if rule['rule_file'] != rule_file]
continue
except EAException as e:
message = 'Could not load rule %s: %s' % (rule_file, e)
self.handle_error(message)
# Want to send email to address specified in the rule. Try and load the YAML to find it.
try:
rule_yaml = self.rules_loader.load_yaml(rule_file)
except EAException:
self.send_notification_email(exception=e)
continue
self.send_notification_email(exception=e, rule=rule_yaml)
continue
elastalert_logger.info("Reloading configuration for rule %s" % (rule_file))
# Re-enable if rule had been disabled
for disabled_rule in self.disabled_rules:
if disabled_rule['name'] == new_rule['name']:
self.rules.append(disabled_rule)
self.disabled_rules.remove(disabled_rule)
break
# 根据修改的规则文件初始化规则
# Initialize the rule that matches rule_file
new_rule = self.init_rule(new_rule, False) # 初始化函数
# 将在运行中的规则从self.rules列表中删除
self.rules = [rule for rule in self.rules if rule['rule_file'] != rule_file]
if new_rule:
self.rules.append(new_rule) # self.rules中添加新的规则,至此完成修改规则的同步
# 载入新的规则
# Load new rules
if not self.args.rule:
for rule_file in set(new_rule_hashes.keys()) - set(self.rule_hashes.keys()):
try:
new_rule = self.rules_loader.load_configuration(rule_file, self.conf)
if not new_rule:
logging.error('Invalid rule file skipped: %s' % rule_file)
continue
if 'is_enabled' in new_rule and not new_rule['is_enabled']:
continue
if new_rule['name'] in [rule['name'] for rule in self.rules]:
raise EAException("A rule with the name %s already exists" % (new_rule['name']))
except EAException as e:
self.handle_error('Could not load rule %s: %s' % (rule_file, e))
self.send_notification_email(exception=e, rule_file=rule_file)
continue
# 前面判断完成这个规则文件,线上的规则没有对应的,因此在这里对规则初始化,再添加到self.rules里即可
if self.init_rule(new_rule):
elastalert_logger.info('Loaded new rule %s' % (rule_file))
if new_rule['name'] in self.es_clients:
self.es_clients.pop(new_rule['name'])
self.rules.append(new_rule)
self.rule_hashes = new_rule_hashes
4.调度器的使用
scheduler调度器可以有四种任务add_job、remove_job、modify_job、pause_job,分别是增删改暂停任务;
self.scheduler.pause_job(job_id=rule['name'])
elastalert_logger.info('Rule %s disabled', rule['name']) # 再写句日志,规则被disable了
self.scheduler.remove_job(job_id=new_rule['name'])
self.scheduler.modify_job(job_id=rule['name'], next_run_time=rule['next_starttime'])
elastalert_logger.info('Pausing %s until next run at %s' % (rule['name'], pretty_ts(rule['next_starttime']))) # 写句日志,提醒日志被暂停了,下次生效时间是多少
# 以下是开源代码中几个添加job的代码
job = self.scheduler.add_job(self.handle_rule_execution, 'interval',
args=[new_rule],
seconds=new_rule['run_every'].total_seconds(),
id=new_rule['name'],
max_instances=1,
jitter=5)
self.scheduler.add_job(self.handle_pending_alerts, 'interval',
seconds=self.run_every.total_seconds(), id='_internal_handle_pending_alerts')
self.scheduler.add_job(self.handle_config_change, 'interval',
seconds=self.run_every.total_seconds(), id='_internal_handle_config_change')
刚才我修改了一个创建好了的规则的name,然后看到服务器日志就开始报找不到这条规则,这个日志是在错误处理部分出现的,也就是运行中的规则没这条,应该暂停的name需要根据旧的去取,应该不会犯这种错误。
5.问题:测试删除指定的规则
- 测试细节:配置俩规则,一个标题带info,一个带error,然后手动在同步规则函数的删除规则地方将这条规则的job移除:
self.scheduler.remove_job(job_id='test-alert-error')
,果然就只剩下一个规则了。
结论是可以删除的,但是因为到同步函数load_rule_changes这里,删除的规则对应的规则文件已经被删除了,没法取到规则对应的id,也就没法删除。
- 在怀疑是不是配置原因导致的,因为跑起来的系统有不断报错的地方;因为开源代码中调度器都写好了,开发人员是不可能在这种重要地方犯错的。这个是规则同步功能,比较重要的核心业务了。
- 如果不是,则需要找到增删规则yaml文件的地方,牺牲一点耦合度,在running的规则同步后对文件再进行修改,保证同步性。但是根据下面这张图的日志我没找到文件修改的位置,如何是好??
令人欣喜的是,当我去github源码翻看issue时候,发现三天前有人提出了同样的问题,看来确实是源码的问题:
-
问题描述:
elastalert项目运行中,删除或修改规则(id修改情况下),对应警告不会停止/显示规则id对应的job未找到,重启项目才有效
重启这是令人难以接受的,项目的可用性大大下降。 -
问题原因:
在进入load_rule_changes进行规则同步时,规则对应的yaml配置文件已经被修改,而对running中的规则与rules_folder中的对应的规则文件同步时,删除情况下,那个规则文件已经不在了,没法获得id去remove_job,修改了规则name情况下,也没法找到对应的name进行modify_job,从而会报错 -
为什么重启就好了:
重启时候会依据rules_folder文件夹中的规则一个一个初始化并add_job,不会遇到与name找不到相关的冲突 -
依据
1.Deleting a rule does not stop querying or alerts when running elastalert as kubernetes deployment
2.Deleting a rule does not stop alerts #2043
6.具体修改
-
首先重新确认项目操作配置文件的地方,然后可以采取稍稍牺牲耦合度,在同步规则的地方作为文件操作的终点。
发现对规则文件的操作是前端发请求做的,所以服务端这里添加一个删除job的操作即可,在全局变量中保存了name,哭
我在同步功能中写的测试代码是,目的是测试能否删除规则定时job
self.scheduler.remove_job(job_id='test-alert-error')
而解决的issue添加的代码是
self.scheduler.remove_job(job_id=rule['name'])
经过github网友提醒,发现已经解决的bug,只不过没合并到稳定版本:
经本人测试没有太大问题
修改结果——规则文件被删除的情况作如下修改:
Stop job when it gets disabled #2484
/elastalert/elastalert/elastalert.py
load_rule_changes()
# Check each current rule for changes
for rule_file, hash_value in self.rule_hashes.items():
if rule_file not in new_rule_hashes: # 确定已经删掉的规则yaml文件rule_file
# Rule file was deleted
elastalert_logger.info('Rule file %s not found, stopping rule execution' % (rule_file))
for rule in self.rules: # 根据rule_file确定已经删除了规则yaml文件的规则rule
if rule['rule_file'] == rule_file:
break
else:
continue
self.scheduler.remove_job(job_id=rule['name']) # 从rule的name作为job_id创建移除规则的job即可
self.rules.remove(rule)
continue
我的问题是没有尝试出来在全局变量中取job_id,也就是rule[‘name’],再把self.rules循环一遍,符合删除了的rule,锁定后,根据name键名取得其id,然后创建一个remove_job即可,同理,有其他地方都可以这么去写。
7.其他问题
另外在前端插件中填规则时候,修改规则时候,还会有bug——No job by the id of test-alert-error was found
规则内容:
es_host: "192.168.XXX.XXX"
es_port: 9200
index: "XXXX"
name: test-alert-error
type: frequency
num_events: 1
timeframe:
seconds: 200
filter:
- term:
level.keyword: "ERROR"
alert: email
smtp_host: 192.168.XXX.XXX
smtp_port: 25
smtp_auth_file: /usr/local/elastalert/smtp.json
email:
- "XXX@XXX.com"
若在创建好规则的基础上,临时想再编辑rule的name,那么会报No job by the id of test-alert-error was found
错误,如果优化的话,应该在判断规则是修改了的情况下,添加规则的name被修改,job没法确定的情况。
elastalert源码值得一看,虽然Bug尚多,可是功能也蛮多,没使用过具体的功能,仅管中窥豹。