OpenStack监控组件---Ceilometer alarm属性代码分析
Ceilometer是OpenStack中的一个子项目,它像一个漏斗一样,能把OpenStack内部发生 的几乎所有的事件都收集起来,然后为计费和监控以及其它服务提供数据支撑。
Ceilometer监控通过在计算节点部署Compute服务,轮询其计算节点上的instance,获取各自的CPU、网络、磁盘等监控信息,发送到RabbitMQ,Collector服务负责接收信息进行持久化存储。Ceilometer项目创建时最初的目的是实现一个能为计费系统采集数据的框架。在G版的开发中,社区已经更新了他们的目标,新目标是希望Ceilometer成为OpenStack里数据采集(监控数据、计费数据)的唯一基础设施,采集到的数据提供给监控、计费、面板等项目使用。
Alarm模块的目录结构:
ceilometerclient的目录结构:
Alarm的属性:
参数 | 类型 | 解释 |
name | str | name是project唯一的 |
description | str | 描述 |
enabled | bool | alarm的一个开关,可以停止/启动该alarm,默认是True |
ok_actions | list | 当alarm状态变为ok状态时,采取的动作,默认是[] |
alarm_actions | list | 当alarm状态变为alarm状态时,采取的动作,默认是[] |
insufficient_data_actions | list | 当alarm状态变为insufficient data状态时,采取的动作,默认是[] |
repeat_actions | bool | 当alarm被触发时,是否重复执行对应的动作,默认是False |
type | str | alarm类型,目前有threshold和combination两种,必填 |
threshold_rule | AlarmThresholdRule | 当alarm类型为threshold时,制定的threshold规则 |
combination_rule | AlarmCombinationRule | 当alarm类型为combination时,制定的combination规则 |
time_constraints | list(AlarmTimeConstraint) | 约束该alarm在哪些时间段执行,默认是[] |
state | str | alarm的状态,默认是insufficient data |
user_id | str | user id,默认是context user id |
project_id | str | project id, 默认是context project id |
timestamp | datetime | alarm的定义最后一次被更新的时间 |
state_timestamp | datetime | alarm的状态最后一次更改的时间 |
说明:
name: name是project唯一的,在创建alarm的时候会检查
enabled: 这个功能比较人性化,可以暂停该alarm,是微创新之一
xxx_actions: 定义了在该alarm状态由其他的状态变为xxx状态时,执行的动作
repeat_actions: 这个参数指定了是否要重复执行action,比如第一次检查alarm已经超过阈值,执行了相应的action了,当下一次检查时如果该alarm还是超过阈值,那么这个参数决定了是否要重复执行相应的action,这也是微创新之一
threshold_rule: 当type为threshold时,定义的alarm被触发的规则,详细参数见下面AlarmThresholdRule对象属性
combination_rule: 当type为combination时,定义的alarm被触发的规则,详细参数见下面AlarmCombinationRule对象属性
time_constraints: 这也是一个很人性化的参数,可以指定该alarm被检查的时间的一个列表,比如说我只想让这个alarm在每天晚上的21点到23点被检查,以及每天中午的11点到13点被检查,其它时间不检查该alarm,这个参数就可以做这个限制,不过该参数设置稍微复杂一点,详细参数见下面AlarmTimeConstraint对象属性,默认是[],即不设限制,随着alarm进程的interval time进行检查。
state: alarm总共有3个状态:OK, INSUFICIENT DATA, ALARM,这三个状态分别对应到上面的xxx_actions
AlarmThresholdRule:
meter_name: 监控指标
query: 该参数一般用于找到监控指标下的某个资源,默认是[]
period: 这个参数其实有两个作用,一个是确定了获取该监控指标的监控数据的时间范围,和下面的evaluation_periods配合使用,另外一个作用就是它确定了两个点之间的时间间隔,默认是60s
threshold: 阈值
comparison_operator: 这个参数确定了怎么和阈值进行比较,有6个可选:lt, le, eq, ne, ge, gt,默认是eq
statistic: 这个参数确定了使用什么数据去和threshold比较,有5种可选:max, min, avg, sum, count,默认是avg
evaluation_periods: 和period参数相乘,可以确定获取监控数据的时间范围,默认是1
exclude_outliners: 这个参数有点意思,我们都知道“标准差”是指一组数据的波动大小,平均值相同,但是标准差小的波动小,这个参数就是指对得到的一组监控数据,是否要根据标准差去除那些波动比较大的数据,以降低误判率,默认是False
AlarmCombinationRule:
operator: 定义alarms之间的逻辑关系,有两个选项:or 和 and,默认是and,注意这里的逻辑关系ALARM要比OK状态优先级高,比如有2个alarm,一个状态是ALARM,一个状态是OK,他们之间的逻辑关系是or,那么这个combination alarm是啥状态呢?答案是ALARM.
alarms_id: alarm列表
AlarmTimeConstraint:
name: name
description: description
start: 该参数以cron的格式指定了alarm被检查的开始时间,在程序中,使用croniter这个库来实现cron,格式是:"min hour day month day_of_week",比如"2 4 mon,fri",意思是在每周一和周五的04:02开始被检查
duration: 被检查持续的时间,单位是秒
timezone: 可以为上面的检查时间指定时区,默认使用的是UTC时间
分析alarm的evaluate实现:
对于有threshold的alarm,evaluate是通过ThresholdEvaluator(ceilometer/alarm/evaluator/threshold.py)中的evaluate函数实现的:
其中:
query = self._bound_duration(
alarm,
alarm.rule['query']
)
参照alarm的属性,alarm.rule[‘query’]是resource_id == INSTANCE_ID;
@classmethod
def _bound_duration(cls, alarm, constraints):
"""Bound the duration of the statistics query."""
now = timeutils.utcnow()
# when exclusion of weak datapoints is enabled, we extend
# the look-back period so as to allow a clearer sample count
# trend to be established
look_back = (cls.look_back if not alarm.rule.get('exclude_outliers')
else alarm.rule['evaluation_periods'])
window = (alarm.rule['period'] *
(alarm.rule['evaluation_periods'] + look_back))
start = now - datetime.timedelta(seconds=window)
LOG.debug(_('query stats from %(start)s to '
'%(now)s') % {'start': start, 'now': now})
after = dict(field='timestamp', op='ge', value=start.isoformat())
before = dict(field='timestamp', op='le', value=now.isoformat())
constraints.extend([before, after])
return constraints
该函数计算evaluate操作的时间段,结束时间是当前时间now,开始时间start是now – period*evaluation_periods(这两个参数在alarm的属性中) + lookback(这个参数是reporting/ingestion的延迟时间,可以忽略的细节),然后在query的内容中添加时间段为[start,now]的条件,最后返回更改后的query。
statistics = self._sanitize(
alarm,
self._statistics(alarm, query)
)
def _statistics(self, alarm, query):
"""Retrieve statistics over the current window."""
LOG.debug(_('stats query %s') % query)
try:
return self._client.statistics.list(
meter_name=alarm.rule['meter_name'], q=query,
period=alarm.rule['period'])
except Exception:
LOG.exception(_('alarm stats retrieval failed'))
return []
@staticmethod
def _sanitize(alarm, statistics):
"""Sanitize statistics."""
LOG.debug(_('sanitize stats %s') % statistics)
if alarm.rule.get('exclude_outliers'):
key = operator.attrgetter('count')
mean = utils.mean(statistics, key)
stddev = utils.stddev(statistics, key, mean)
lower = mean - 2 * stddev
upper = mean + 2 * stddev
inliers, outliers = utils.anomalies(statistics, key, lower, upper)
if outliers:
LOG.debug(_('excluded weak datapoints with sample counts %s'),
[s.count for s in outliers])
statistics = inliers
else:
LOG.debug('no excluded weak datapoints')
# in practice statistics are always sorted by period start, not
# strictly required by the API though
statistics = statistics[-alarm.rule['evaluation_periods']:]
LOG.debug(_('pruned statistics to %d') % len(statistics))
return statistics