openstack告警方案

  openstack的告警服务以告警策略(或告警器,英文alarm)为中心,告警检测服务轮询所有的告警策略,获取数据判断每个告警策略是否触发告警,然后调用对应通知方式发送告警通知。其中获取数据、通知方式都在告警策略里定义。

一个告警策略

  告警策略的数据结构在ceilometer.api.controllers.v2.alarms.Alarm类里有定义,各个字段都有注释。这里拷弄一个告警策略例子出来看看长什么样子。

  先看一下告警策略里都有哪些字段:

  • _id: 告警策略存储在数据库里的id
  • alarm_id: 告警策略的id
  • alarm_actions: 数组结构,告警器为alarm状态时要执行的动作
  • ok_actions: 数组结构,告警器为ok状态时要执行的动作
  • severity: 严重级别,可以为critical,moderate,low
  • state: 告警器状态,可以为alarm,ok,insufficient_data
  • timestamp: 告警器被修改的时间
  • enabled: 告警器是否启用,true/false
  • state_timestamp: 告警器状态被更新的时间
  • rule: 告警检测规则,根据type字段不同这里的内容也不同,下面是type == threshold时rule的字段(定义在ceilometer.api.controllers.v2.alarm_rules.threshold.AlarmThresholdRule
    • meter_name: 检测指标的名字
    • query: 定义在ceilometer.api.controllers.v2.base.Query
      • field: 指定要查询的域
      • op: 查询时用的操作符
      • value: 与field比较的值
      • typevalue的数据类型
    • period: 查询的时间周期,单位秒
    • comparison_operator: 跟阈值比较时用的操作符
    • threshold: 阈值
    • statistic: 用检测数据哪个统计值来与阈值比较,比如平均值、最大值等
    • evaluation_periods: 检测几个周期的数据
    • exclude_outliers: 是否要去除异常的数据
  • name: 告警器名字
  • time_constraints: 规定告警器在哪些时间段进行检测,具体字段内容注释见ceilometer.api.controllers.v2.alarms.AlarmTimeConstraint
    • description: 描述
    • start: ‘min hour day month day_of_week’格式的开始时间点
    • duration: 告警器在开始时间点后多久时间内均可被检测,单位秒
    • timezone: 时区信息
  • insufficient_data_actions: 数组结构,告警器为insufficient_data状态时要执行的动作
  • repeat_actions: 告警器的状态持续时是否重复执行相应动作
  • user_id: 创建该告警器的用户ID
  • project_id: 拥有该告警器的租户ID
  • type: 告警器类型,K版本里有threshold、combination、gnocchi几种类型
  • description: 描述

  下面是一个告警器的例子,这个告警器是阈值类型告警器(type字段),告警检测仅在每天23:00开始检测,持续检测3小时(time_constraints字段),告警器检测id为2a4d689b-f0b8-49c1-9eef-87cae58d80db的这个虚拟机,以60s为一个周期('period': 60),检测两个周期('evaluation_periods': 2),如果两个周期内该虚拟机平均cpu使用率都大于50%(threshold_rule字段),就触发告警。当前已经触发了告警('state': "alarm"),并且会执行告警动作http://site:8000/alarm,如果在下一次检测内还检测到告警,那么依然会执行该告警动作('repeat_actions': True)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
'alarm_id': '4512a64b-f45e-5684-10cc-182a62ccab17',
'name': "Instance cpu alarm",
'description': "cpu util overload alarm",
'type': 'threshold',
'time_constraints': [{
'name': 'SampleConstraint',
'description': 'nightly build every night at 23h for 3 hours',
'start': '0 23 * * *',
'duration': 10800,
'timezone': 'Europe/Ljubljana'
}],
'user_id': "c96c887c216949acbdfbd8b494863567",
'project_id': "c96c887c216949acbdfbd8b494863567",
'enabled': True,
'timestamp': '2017-01-16T13:06:25.981Z',
'state': "alarm",
'severity': "moderate",
'state_timestamp': '2017-01-16T13:06:25.981Z',
'ok_actions': ["http://site:8000/ok"],
'alarm_actions': ["http://site:8000/alarm"],
'insufficient_data_actions': ["http://site:8000/nodata"],
'repeat_actions': True,
'threshold_rule': {
'meter_name': 'cpu_util',
'period': 60,
'evaluation_periods': 2,
'threshold': 50.0,
'statistic': 'avg',
'comparison_operator': 'gt',
'query': [{
'field': 'resource_id',
'value': '2a4d689b-f0b8-49c1-9eef-87cae58d80db',
'op': 'eq',
'type': 'string'
}]
}
}

告警轮询流程

  从服务启动脚本(ceilometer/cmd/alarm.py#evaluator)开始看,这里载入命名空间为ceilometer.alarm.evaluator_service下名为”cfg.CONF.alarm.evaluation_service”的插件,我们看到setup.cfg该命名空间下还有不少定义的插件,比如singletonpartitioned。我们可以通过阅读代码看一下这些插件的区别(代码很少),在部署了多个告警检测服务的环境下,partitioned会通过coordination这个套件在多个服务间分配告警器,比如节点1上的服务分配到A、B两个告警器,节点2上的服务分配到C、D两个告警器。singleton则是每个服务都获取所有的告警器进行检测,多实例环境下可能会有重复检测的问题。我们可以在配置文件里选择合适的插件工作,也可以自定义自己的插件。
  以singletonceilometer.alarm.service.SingletonAlarmService)为例,该类继承了ceilometer.alarm.service.AlarmService,在父类的初始化函数里加载了ceilometer.alarm.evaluator命名空间的插件,获取目前定义的告警器类型列表。
  在start函数里,增加了一个定时任务_evaluate_assigned_alarms,继续跟踪代码(_evaluate_assigned_alarms->_evaluate_alarm),在_evaluate_alarm里执行了告警器的evaluate函数:

1
2
3
4
try:
self.evaluators[alarm.type].obj.evaluate(alarm)
except Exception:
LOG.exception(_('Failed to evaluate alarm %s'), alarm.alarm_id)

  这个evaluate函数就是每种类型的告警器的不同之处所在了,我们可以根据需要添加自己的告警器类型,只需继承ceilometer.alarm.evaluator.Evaluator,实现evaluate函数即可。
  evaluate函数的实现就不再赘述了,这里简单说一下阈值告警器(threshold)的实现,它调用ceilometer的statistics接口,接口根据periodevaluation_periods等参数返回数据,举个例子可能容易理解点,以上面那个告警器为例,可能会返回以下数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[<Statistics{
u'count': 6,
u'duration_start': u'2017-03-20T00: 45: 22',
u'min': 40,
u'max': 90,
u'duration_end': u'2017-03-20T00: 45: 33',
u'period': 60,
u'groupby': {
u'resource_id': u'2a4d689b-f0b8-49c1-9eef-87cae58d80db'
},
u'period_end': u'2017-03-20T00: 46: 11',
u'duration': 11.0,
u'period_start': u'2017-03-20T00: 45: 11',
u'avg': 60,
u'sum': 360,
u'unit': u'%'
}>,
<Statistics{
u'count': 6,
u'duration_start': u'2017-03-20T00: 46: 21',
u'min': 50,
u'max': 90,
u'duration_end': u'2017-03-20T00: 46: 31',
u'period': 60,
u'groupby': {
u'resource_id': u'2a4d689b-f0b8-49c1-9eef-87cae58d80db'
},
u'period_end': u'2017-03-20T00: 47: 11',
u'duration': 10.0,
u'period_start': u'2017-03-20T00: 46: 11',
u'avg': 70,
u'sum': 420,
u'unit': u'%'
}>]

  可以看到在检测的两个周期内,该虚拟机的平均cpu使用率都超过了阈值50(一个是60,一个是70)。
需要注意的是调用statistics接口需要在ceilometer的采样服务里添加一个采样指标,不然我们将无法得到数据,在上面的例子中,我们需要在采样服务里添加一个cpu_util的采样指标才能得到返回数据。

怎么发送告警通知?

  假设我们现在已经判断出该告警器已经触发告警了,现在是怎么发送告警消息的问题了,我们跟踪代码,可以看到告警器状态发生变化时的流程(_transition->_refresh->self.notifier.notify),然后这里会构造参数通过rpc发送消息到openstack-ceilometer-alarm-notifier服务去,由这个服务来完成下一步的通知步骤。
  openstack-ceilometer-alarm-notifier服务收到消息后,取出消息的actions参数,这个根据当前告警器的状态不同有不同的值(取ok_actions,alarm_actions,insufficient_data_actions中的一个),然后加载对应的插件调用notify方法。
  ceilometer允许我们用插件方式实现自己的通知方式,只需我们继承ceilometer.alarm.notifier.AlarmNotifier并实现notify方法,并在setup.cfg中注册即可。

now

  在openstack新版本里已经把告警服务从ceilometer挪到aodh中实现了,但基本原理还是一样。在分离后ceilometer专注于数据采集,而aodh也只关心告警这一服务,更好的体现了职责单一,分工协作。

帮助贫困儿童,人人有责