Elastalert规则运行原理详解与源码分析

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()
  1. 创建一个ElastAlerter类的对象,并在未设置silence模式下调用start()函数

  2. 类进行初始化操作,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))
    

    对每个规则进行初始化。

  3. 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.问题:测试删除指定的规则

  1. 测试细节:配置俩规则,一个标题带info,一个带error,然后手动在同步规则函数的删除规则地方将这条规则的job移除:self.scheduler.remove_job(job_id='test-alert-error'),果然就只剩下一个规则了。
    在这里插入图片描述
    结论是可以删除的,但是因为到同步函数load_rule_changes这里,删除的规则对应的规则文件已经被删除了,没法取到规则对应的id,也就没法删除。
  • 在怀疑是不是配置原因导致的,因为跑起来的系统有不断报错的地方;因为开源代码中调度器都写好了,开发人员是不可能在这种重要地方犯错的。这个是规则同步功能,比较重要的核心业务了。
  • 如果不是,则需要找到增删规则yaml文件的地方,牺牲一点耦合度,在running的规则同步后对文件再进行修改,保证同步性。但是根据下面这张图的日志我没找到文件修改的位置,如何是好??

INFO日志--文件操作完后报已经处理完
令人欣喜的是,当我去github源码翻看issue时候,发现三天前有人提出了同样的问题,看来确实是源码的问题:

  1. 问题描述:
    elastalert项目运行中,删除或修改规则(id修改情况下),对应警告不会停止/显示规则id对应的job未找到,重启项目才有效
    重启这是令人难以接受的,项目的可用性大大下降。

  2. 问题原因:
    在进入load_rule_changes进行规则同步时,规则对应的yaml配置文件已经被修改,而对running中的规则与rules_folder中的对应的规则文件同步时,删除情况下,那个规则文件已经不在了,没法获得id去remove_job,修改了规则name情况下,也没法找到对应的name进行modify_job,从而会报错

  3. 为什么重启就好了:
    重启时候会依据rules_folder文件夹中的规则一个一个初始化并add_job,不会遇到与name找不到相关的冲突

  4. 依据

    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.具体修改

  1. 首先重新确认项目操作配置文件的地方,然后可以采取稍稍牺牲耦合度,在同步规则的地方作为文件操作的终点。
    发现对规则文件的操作是前端发请求做的,所以服务端这里添加一个删除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尚多,可是功能也蛮多,没使用过具体的功能,仅管中窥豹。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值