机器环境管理

对于机器的环境管理,已经痛苦很长时间了,每次机器创建之后,需要先部署基础环境,比如 Java、tomcat 等,然后再发布应用程序。对于这个问题,我之前一直想用 Docker 解决,但是事实上 Docker 也不能解决所有的问题,比如 Hadoop 并不适合 Docker,对于 Hadoop 这种适合部署在机器上的组件还需要手动维护基础环境。

作为一个有追求的不断提高效率的人,我现在终于决定要解决这个问题了,也就是机器的环境管理问题。

事实上,机器分为 物理机 和 虚拟机,对于大部分类 Web 程序我们使用虚拟机,对于 Hadoop、Mysql 等资源消耗型程序我们使用物理机,而且物理机和虚拟机基本没有「混布」,这是前提,如果混布,管理起来会麻烦很多(需要分目录来管理,不能共用)。

 

因为一个服务的环境是一样的,所以我们基于服务树来统一管理服务的环境,思路很简单:

1. 把 服务节点 和 Puppet class 做一个关联,即表示服务节点下的所有机器都使用此 Puppet class 来配置基础环境。

2. Puppet 有多个 class,class 之间的继承等关联关系通过 Puppet 配置文件来维护;

3. Puppet class 需要暴露出来一个 class 的列表来供做关联的时候做选择,这个列表可以放在 Puppet 配置中的一个文件里,文件对外提供 HTTP 下载(然后再封装成 API)。

那么,当机器来请求 Puppet 获取配置的时候,怎么返回呢?

参考 这篇文章,我们使用 Puppet 的 ENC 功能,写一个自定义脚本,传入机器名,返回包含 class 的 yaml 格式文本。脚本的流程:

1. 根据机器名获取机器属于哪个(些)服务节点(基于服务树 API);

2. 根据服务节点获取 Puppet class;

3. 拼成 yaml 格式文本并打印。

如果属于多个服务节点,而且服务节点的 Puppet class 不一样,此时可以使用 default class (只配置系统基础环境,不配置应用基础环境)。

 

但是,还有一个问题,如何在机器创建之后立即配置环境?

现状是:

1. 机器安装好之后会立即执行 Puppet;

2. Puppet 执行之后会自动安装资产系统 agent,agent 把机器上报到资产系统,表示此机器安装完成;

3. 然后调用 API 把机器关联到安装机器时指定的服务节点。

像上面讲的,Puppet 配置(应用)基础环境需要知道是哪个服务节点,而第 1 步的时候不知道是哪个服务节点。

这里抛出一个修改 ENC 脚本的方法:

1. ENC 调用服务树 API,获取机器绑定的服务节点;

2. 如果找不到节点,那么继续调用装机系统 API,获取服务节点。

这种方法只有「正在装」的机器才会调用装机系统 API,增加的调用不多,所以能接受。

 

如何自动化的生成 Nginx 配置文件

本文讨论如何自动化的生成 Nginx 配置文件,Nginx 配置文件指的是 server 的配置,这里有一个很重要的概念,就是:服务树,我们希望基于服务树来维护 server 配置需要的信息。

 

服务树的每一个节点就是一个服务,那么,我们对每个节点定义必要的信息,包括:

node_id – 节点 id,代表服务;

domain_uris – 代表服务绑定的域名和路径,支持多个,因为一个服务可能同时需要绑定外网域名和内网域名,而且有绑定多个路径的需求,它的格式如下:

[{‘domain_id’: domain_id, ‘uri’: uri }, …] ;

upstream node name – 这个服务的 upstream 名称;

servers – node_id 下绑定的机器列表(每台机器可以绑定不同的权重,以实现流量控制);

port – 这个服务监听的端口;

ip_hash – 是否对 upstream 启用 ip hash。

 

上面的 domain_id 表示域名信息,事实上域名信息包括:

domain – 域名;

ports – 端口,可以包含多个端口,并且支持是否开启 ssl;

access_log_path – 正常日志路径,默认为 ${domain}_access.log;

error_log_path – 错误日志路径,默认为 ${domain}_error.log;

ssl_cert_path – 证书文件路径,如果 ports 中没有开启 ssl,此处可以留空;

ssl_cert_key_path – 私钥文件路径,如果 ports 中没有开启 ssl,此处可以留空;

要注意的一点,如果 ssl 文件配置在 server 中,如果客户端不支持 SNI,ssl 连接可能会失败,这里我们不讨论多个域名证书不放在 server 中的配置问题。

生成配置文件基于模板,很简单,不过要注意的是,location $uri 有顺序要求,只要能保证按照 uri 的长度从高到低就没问题了。

 

自动生成配置文件的好处:

1. 在页面上点点就能增加、修改或删除服务的 Nginx 配置,比较方面,而且彻底解决了使用 git 管理配置带来的各种 rewrite 、if 等冗长的配置问题;

2. 可以基于服务做可用率的报警,根据服务可以查到 domain 和 path,然后过滤 Nginx 日志来计算可用率。而且当服务出问题的时候可以查看到服务的每台机器的可用率状况,然后做出自动剔除机器的动作。

Codis 运维应该做到的几点

我司目前使用 Codis,突然对如何 Codis 运维有点思考,记录一下。

 

1、 扩容和缩容对开发无感

这一点 Codis 可以做到。

 

2、 迁移对开发无感

如果使用 Codis,迁移分为三个部分:

1). 迁移 Codis Server,和上一点相同,Codis 可以做到;

2). 迁移 Codis Proxy,先新建 Proxy,然后启动新建的 Proxy,启动之后会向 zk 注册,然后停止旧的 Proxy,zk 中就只有新的 Proxy 了,程序会自动获取新的 Proxy 并连接;

3). 迁移 zk。由于 Codis Proxy 和 程序都依赖 zk,迁移 zk 看起来麻烦点。

迁移 zk 机器,可以对 zk 封装一个代理,比如 LVS,通过 LVS 转发到多个 zk,这样 zk 机器的迁移可以实现无感;

用了 LVS 就会涉及到 LVS 的迁移,我们的做法是把 LVS 的 IP 封装成域名,Codis Proxy 和程序中都写这个域名,当需要迁移 LVS 时,可以在新的 LVS 上建立新 IP 转发到 zk 机器,然后修改域名的 DNS 指向新 LVS IP。注:这里可能涉及到 DNS 缓存的问题。

 

3、申请和部署尽量简单

申请和部署和用不用 Codis 都没有关系,我的思路是:

1). 对于申请,开发人员首先通过提交配置,包括 内存、是否需要 Slave 等,提交方式可以通过网页提交单子或者通过 git,然后运维审核配置,通过后调用「部署」的 API 「自动」创建。

2). 对于部署,Redis (Codis) 的集群资源需要一个管理工具来统一管理,需要有 API 来新建、删除和修改,也需要有很好的调度功能(什么样的申请在什么机器创建)。

 

open-falcon 在豌豆荚的应用

现有监控系统的问题

目前我们使用的监控系统是豌豆荚自己开发的,有几个问题:

1. 报警功能不够稳定,程序会出现僵死的情况,而且 HTTP 报警偶尔报不出来。

2. 报警添加比较麻烦,没有模板和继承的概念。

3. 对于需要和机器关联的监控,是通过指定机器名的方式,如果机器改名或迁移,需要手动修改监控,非常麻烦,这种情况应该使用 主机组( hostGroup )。

4. 没有完整的报警组的功能,报警组信息只是简单的放在 redis 里,增加和删除只能通过调用 API 修改。

5. 现有报警系统没有解决离职员工还会收到短信的问题,这个问题可以在现有报警中优化掉,但是既然现有监控系统要废弃掉,那么就准备在新监控中解决这个问题。

总结一下,现有监控系统是一个缺乏好的顶层设计的系统,而且不是一个完善的系统。

 

为什么使用 open-falcon

open-falcon 是小米开源的监控系统,经历过大规模机器的考验,我相信在稳定性方面表现出色,而且在设计上也更为先进,我相信能够解决大部分的监控需求。

还有一点,open-falcon 在代码阅读和二次开发上会有一些便利。

 

改造了哪些东西

1. 最大的一点改进是基于 hostGroup,使用 hostGroup 好处多多,只要 hostGroup 和模板绑定,hostGroup 中的每台机器都会去使用模板,而只要维护好机器的增加和删除就好了,非常方便。

对于这一点,需要改的东西有:

1). open-falcon 自己维护了一个 host 表,hbs 会把 agent 上报的主机名和 IP 信息写入 host 表,由于我们有自己的资产系统,所以把这个功能删除。

2). 写一个同步任务从资产系统同步所有机器名到 host 表,host 表的 IP 字段可以不使用,这个功能可以在 task 中增加。

3). open-falcon 中 grp 表的 id 是自增的,由于我们有自己独立的服务树,我们决定使用 服务数节点的 id 作为 grp id,那么 id 就不能是自增的了,所以我们去掉了自增属性。

4). 由于 grp id 完全基于服务树节点 id,我们需要写一个同步任务把服务树倒数第二级节点的 id 同步到 grp 表中,grp name 我们定为节点的全路径(类似 /wandoujia/xxx/yyy/service_node)。

这里为什么是倒数第二级节点,因为我们的服务树的设计倒数第二级节点是 服务节点,最后一级是 机房节点(会绑定机器),比如:

154.pic

这个图,puppet 是倒数第二层,是服务节点,db 和 hlg 是倒数第一层机房节点。

同步倒数第二级节点后,我们可以对这些 grp 做一些通用监控(包括系统监控和 nodata 监控,而且可以对这些 grp 绑定加上 负责人的报警,后面有说)。

5). 上一个同步任务建立了 grp,那么 grp 对应的 hostname 也要定期更新,所以也要写一个任务把 grp 的 hostname 更新到 grp_host 表中(grp 的 hostname 列表基于 grp id 从资产系统中获取)。

6). 上面也提到了 grp 的 name 是基于节点的 full path,由于节点可以改名,所以要写一个同步任务更新 grp name。

7). 由于服务树节点可能被删除,所以要写一个同步任务来更新 grp_tpl、grp_host 和 grp  表。

2. 添加监控和服务树整合,使添加监控更加方便。

CAF1DB92-F724-41AC-A7D8-23B89A25D0AA

界面如上图,我们使用模板继承的概念,右面列表的模板被继承到左边时,会先创建一个新的模板 (i_nodeid_tplname 格式),这个模板的父模板是后边选中的模板,然后为新模板创建一个 action,新模板的 action uic 初始化为父模板的 action uic(初始的 uic 一般是运维部门 team,以保证运维人员可以收到报警)。

1). 为什么使用继承,因为好维护,只要修改父模板的策略,子模板就可以继承。

2). 为什么要为新模板创建一个 action,因为我们会把服务节点的负责人加到对应 grp 绑定的模板的 action.uic,新创建 action 不会污染父模板的 action.uic。

3. 报警联系人和服务树整合,使得增加报警联系人非常方便。

像上面所说的,模板继承时会把服务节点的负责人加到 grp 绑定的模板的 action.uic,那么只要维护好每个节点的负责人就好了。

两个点:

1). 需要写一个同步任务,如果节点负责人不在 action.uic 里,则添加。

2). 由于 uic 字段内容只能是用户组,那么我们写一个同步任务,对每一个公司员工创建一个名称为「用户名」的组。

4. Fe 模块的改造

1). 改造 Fe 的登陆验证,登陆方式使用公司的 SSO,去掉 ldap 验证,去掉注册功能。

2). 登陆 fe 后会设置 .DOMAIN.com cookie ( sig ),使得登陆 portal.DOMAIN.com 、dashboard.DOMAIN.com 和 alarm.DOMAIN.com 无需再验证。

这里, portal、dashboard 和 alarm 要基于 cookie 去 fe 验证(如果需要验证),fe 要实现验证的功能( fe 设置 cookie 后也会把 cookie 放入 redis)。

3). 用户从公司的员工管理系统同步,写入 user 表。

4). 由于用户的手机号可能会修改,也要写一个任务把最新手机号写入 user 表。

5). 需要判断员工是否离职,如果离职,从 rel_team_user、user 和 group 表中删除。

5. 自动添加系统监控和 nodata 监控

可以看到在第二张图中,已经继承的模板有 sa.sys.common 和 sa.agent.nodata,我们是写一个同步任务把所有倒数第二级节点都绑定这两个模板。

第一个模板是系统监控模板,监控内容包括 cpu、mem、disk、file num 等。

第二个是 nodata 模板,用于监控 agent 存活,模板的策略是当 agent.alive 为 -1 时报警。

agent 在存活的时候 agent.alive 不可能为 -1,open-falcon 提供了一个当 agent 死了没数据的情况下补偿数据的功能( Nodata 功能 ),我们对每台机器设置了当 agent 没数据补偿 agent.alive 为 -1,所以就可以根据 agent.alive 来添加 nodata 模板。

Nodata 功能可以根据 grp id 或者 hostname 来补偿数据,由于使用 grp id 程序内部还会做转换,所以直接使用 hostname 来补偿。所有 hostname 的补偿我们是写了一个同步任务,基于 host 表。

6. 关于集群监控和趋势(波动)监控

上面说的系统监控只是针对一台机器的,我们有对集群某个指标的监控和对集群某个指标趋势监控的需求。

对于集群某个指标的监控,比如平均 cpu.idle,需要在用到 aggregator 这个模块,aggregator 会根据定义的 分子、分母等信息去抓取集群的数据,计算之后再 push 到 transfer(我们同样是写了一个任务把服务树倒数第二级节点对应的 grp 加上 aggregator 配置)。然后再根据新定义的 metric 增加相应的策略。

C955CBC2-11D2-4A21-BA0B-82C1D0525E11

D097F47D-09F6-4A36-8C3F-E959669B0133

另外,对于趋势监控,只要把策略的 all(#3) 改成 diff(#3) 或者 pdiff(#3) 就可以了。

 

遇到的问题

1. 对于 url 的检测( url.check.health ),由于我们的机器访问不了外网,所以对于监控外网 url 的需求,我们采用通过 http/https 代理的方式,要改一下 agent 代码。

2. 由于我们使用 Cobar 作为 Mysql 中间件,由于 Cobar 不支持 prepare 功能,所以我们遇到了大量的 Prepare unsupported 的报错。

解决办法:对于要执行的 sql 先用 fmt.Sprintf 格式化,再整体执行一条 sql。

3. open-falcon 的 url.check.health 监控只能监控简单的 GET 请求,也不能设置 header 或者对 response content 做判断,而我们有这样的需求。和 open-faclon 作者 laiwei 聊了一下,他们基本不用 url 监控,主要用端口监控和「业务自动上报 metrics」,这是一个很好的思路,可是我们目前没搞业务自动上报。

考虑到修改 open-falcon 支持复杂的 url 监控比较麻烦,我们决定使用「插件」。

 

总结

open-falcon 是一个功能很全的系统,很好的 nodata 监控、我需要的集群监控和趋势监控、还有减少报警数量的报警合并功能,最主要的是它实现了 hostGroup 的报警方式,非常有用。

 

open-falcon 源码解析(二)

Judge 部分

1. Transfer 会把 MetaData 格式的数据改成 JudgeItem 格式之后再发送 Judge,  JudgeItem 格式是:

type JudgeItem struct {
Endpoint string `json:”endpoint”`
Metric string `json:”metric”`
Value float64 `json:”value”`
Timestamp int64 `json:”timestamp”`
JudgeType string `json:”judgeType”`
Tags map[string]string `json:”tags”`
}

JudgeType 是 MetaData  的 CounterType 值。

2. Judge 收到数据之后,放在一个大 Map 里,初始化如下:

8D316166-2447-467B-A9D7-8366DBFF4475

JudgeItemMap 结构如下:
type JudgeItemMap struct {
sync.RWMutex
M map[string]*SafeLinkedList
}

M 的 key 是 Endpoint、Metric 和 Tags 的 md5,SafeLinkedList 中的元素则是 JudgeItem。

3. Judge 收到数据之后,以 md5 的前两位为 key 存在 HistoryBigMap 中,然后开始判断是否可以报警。在配置文件中有一个参数叫做 remain,用来定义一条报警数据(以 md5 区分)最多的条数,如果超过删除多余的旧数据。

4. 用于判断报警的 Strategy 结构如下:

type Strategy struct {
Id int `json:”id”`
Metric string `json:”metric”`
Tags map[string]string `json:”tags”`
Func string `json:”func”` // e.g. max(#3) all(#3)
Operator string `json:”operator”` // e.g. < !=
RightValue float64 `json:”rightValue”` // critical value
MaxStep int `json:”maxStep”`
Priority int `json:”priority”`
Note string `json:”note”`
Tpl *Template `json:”tpl”`
}
Func 类似 all(#3)、sum(#3)、avg(#10)、diff(#10),第一部分是说数据如何计算,数字是 limit 值,limit 是指用于判定的监控数据条数,少于则不判定;
MaxStep 表示最多报多少次警。

strategy 也保存在 map 里,以 Endpoint_Metric 为 key,判断是否报警的时候也要判断JudgeItem 和 Strategy 的 Tags 是否匹配,否则不处理。

判断报警主要根据 Func、Operator、RightValue,比如 Func 是 avg(#10),如果一个 key 的数据少于 10 就放弃,大于 10 则算出平均值,这个值叫做 leftValue,然后用 leftValue、Operator 和 rightValue 比较,如果成功则表示触发。

5. 然后会创建一个 event 来判断是否发送 event 到 alarm 的 redis,event 的格式是:

type Event struct {
Id string `json:”id”`
Strategy *Strategy `json:”strategy”`
Expression *Expression `json:”expression”`
Status string `json:”status”` // OK or PROBLEM
Endpoint string `json:”endpoint”`
LeftValue float64 `json:”leftValue”`
CurrentStep int `json:”currentStep”`
EventTime int64 `json:”eventTime”`
PushedTags map[string]string `json:”pushedTags”`
}

Id 用 s_strategy.Id_md5 标识。

如果此次 event 触发报警,而上一次 event 是 OK 的,则发送此次 event,CurrentStep 设置为1;如果报警次数已经大于等于 MaxStep,不发送 event;如果报警太频繁,间隔时间小于 MinInterval,不发送;否则发送,CurrentStep 加1。

如果此次 event 是 OK 的,而上次 event 是 PROBLEM,也发送。

发送 event 到 alarm redis,是使用 LPUSH 指令,默认 key 是event:p{Priority},Priority 是优先级( 取自 Event.Strategy.Priority 或者 Event.Expression.Priority)。

6. 除了上面的 Strategy 之外,还会同时执行 Expression 判断。

type Expression struct {
Id int `json:”id”`
Metric string `json:”metric”`
Tags map[string]string `json:”tags”`
Func string `json:”func”` // e.g. max(#3) all(#3)
Operator string `json:”operator”` // e.g. < !=
RightValue float64 `json:”rightValue”` // critical value
MaxStep int `json:”maxStep”`
Priority int `json:”priority”`
Note string `json:”note”`
ActionId int `json:”actionId”`
}

方式和 Strategy 基本类似,Strategy 倾向于基于机器和组的报警,Expression 则是自定义 tag 报警,更加灵活。

7. 会起一个 goroutine 不断从 Hbs 同步 Strategy 和 Expression。

8. 还会起一个 goroutine 清理 JudgeItem 数据,防止占用太多内存,默认清理 7 天前的数据。

 

alarm 部分

1. alarm 在配置文件中定义了 highQueues 和 lowQueues 两个 redis 队列,前者是高优先级,后者是低优先级。对于 highQueues 和 lowQueues 都会起一个 goroutine,读取这些队列中的 event,使用的指令如下:

BRPOP  highQueue1 highQueue2 highQueue3 … 0

如果前面的队列有数据则返回,如果没有则按照顺序检查下一个队列,0 表示无限的等待下去( 如果是正数表示等待超时时间 )。

一个 BRPOP 指令会读取一个 event,然后去消费。

2. 消费的过程,先取出 event 的 actionId,actionId 为 Event.Expression.ActionId 或者 Event.Strategy.Tpl.ActionId,然后根据 actionId 去 Portal 服务取出 action,action 的格式为:

type Action struct {
Id int `json:”id”`
Uic string `json:”uic”`
Url string `json:”url”`
Callback int `json:”callback”`
BeforeCallbackSms int `json:”before_callback_sms”`
BeforeCallbackMail int `json:”before_callback_mail”`
AfterCallbackSms int `json:”after_callback_sms”`
AfterCallbackMail int `json:”after_callback_mail”`
}

如果 action.Callback 为 1,则先去调用 callback,Action 定义了是否在 callback 之前或之后发送短信或邮件,有四个变量表示:BeforeCallbackSms、BeforeCallbackMail、AfterCallbackSms、AfterCallbackMail,Action.Url 就是要 callback 的地址。

如果在 callback 之前或者之后发送短信或邮件,根据 Action.Uic (teams) 去 Uic 服务请求手机和邮箱,然后写入( LPUSH )短信或者邮件的 redis 队列,短信和邮箱的格式分别是:
type Sms struct {
Tos string `json:”tos”`
Content string `json:”content”`
}

type Mail struct {
Tos string `json:”tos”`
Subject string `json:”subject”`
Content string `json:”content”`
}

3. 处理完 callback 的逻辑之后,才开始真正的消费,对于高优先级队列,直接写入短信和邮件发送队列(对于短信代码里写了当 Priority < 3 才发送),对于低优先级队列,做合并处理(对于短信当 Priority < 3 才合并)。

合并之前先把 event 转换成 SmsDto 和 MailDto 格式,然后分别扔进 UserSmsQueue 和 UserMailQueue 队列(还是通过 LPUSH )。

type SmsDto struct {
Priority int `json:”priority”`
Metric string `json:”metric”`
Content string `json:”content”`
Phone string `json:”phone”`
Status string `json:”status”`
}

type MailDto struct {
Priority int `json:”priority”`
Metric string `json:”metric”`
Subject string `json:”subject”`
Content string `json:”content”`
Email string `json:”email”`
Status string `json:”status”`
}

4. 然后起两个 goroutine,分别合并 Sms 和 Mail,合并间隔是 1 分钟。

UserSmsQueue 和 UserMailQueue 的合并规则类似,以 UserSmsQueue 举例:

1). 先 pop 出 UserSmsQueue 中的所有数据;

2). 以 Priority、Status、Phone、Metric 为 key,data 为 []*SmsDto。

如果 data 长度为 1,写入短信发送队列;如果 data > 1,则把数据发送到 Links 服务,以便提供一个可以查看的 url,然后把 Priority、Status、Metric、url、 data 长度等信息写入短信发送队列。

 

sender 部分

sender 非常简单,它从 alarm 写入的短信和邮件发送队列读取数据,发送短信和邮件。

配置文件中要指定短信和邮件的发送  api,sender 使用  post 方法来调用 api,短信 api  接受的参数需要是:tos 和 content,邮件 api 接受的参数需要是:tos、subject 和 content。

sender 使用 带缓存的 channel 来控制发送并发量。

 

Heartbeat Server 部分

也就是 hbs – 心跳服务器,它其实是个缓存服务器,从 Mysql 中同步数据放在内存中,供其他组件读取。

1. 从 Mysql 同步的数据分布放在下面的结构中:

GroupPlugins
GroupTemplates
HostGroupsMap
HostMap
TemplateCache
Strategies
HostTemplateIds
ExpressionCache
MonitoredHosts

2. Agent 会通过 rpc 上报机器信息给 hbs,hbs 会把机器信息存入 Mysql host 表中,同时打上时间戳放入 Agents 内存变量中。额外的,hbs 起一个 goroutine 每天清理一次数据,一天之内没上报的机器信息会被删除,考虑一下机器改名的情况,由于 Agents 以 hostname 为 key,新 hostname 会被上报,而老 hostname 的数据在一天之后被删除;

3. hbs 还给 Agent 提供 插件、信任 IP 列表 和 BuiltinMetrics( 包括 net.port.listen、proc.num、du.bs、url.check.health 四项);

4. hbs 还负责给 Judge 提供 Strategy 和 Expressions。

Strategy 的逻辑还挺复杂的,得说说:

1). 首先拿到 host 和 Template id 对应关系:hidTids;

2). 拿到 host  列表:hosts,包括 hostId 和 hostname;

3). 拿到 Template 信息:tpls;

4). 拿到 Strategy 信息:strategies;

5). 计算出 Template 对应的 Strategy:tpl2Strategies,一个 Template 可以被很多个 Strategy 使用;

6). 然后对 hidTids 中的每一个 hostId 获取它的 Strategy,hostId 对应一个 tplIds( []int{} ),也就是一台机器可以对应多个 Template;

7). 开始处理 tplIds,先获取 tplIds 中每一个 tplId 的 Parent 以及 Parent 的 Parent (包括 tplId 自己,递归处理),tplId 和 Parent 们会排序,从根 Parent 开始,到 tplId 结束,放在 tpl_buckets 中( [][]int{} );

8). 处理 tpl_buckets,有继承关系的放在同一个 bucket 中(就是有继承的话取最长的),比如

11123ou23213

会变成

23432423oj1

放入 uniq_tpl_buckets 中( [][]int{} )。

9). 对 uniq_tpl_buckets 中的每一个 bucket 做处理:bucket 中的 每个 tid 都是从父模板到子模板顺序,取得每个 tid 的 strategies,每个 Strategy 以 metric:{metric}/tags:{tags} 为 key 存储,然后根据 key 可以使子模板覆盖父模板,覆盖之后拿到 Strategy 列表,列表中的 Strategy 的 Strategy.Tpl 再设置成这个 bucket 的最新( 最后 ) tid 模板。

把每个 bucket 合起来就是一个 hostId 的 Strategy 了,所有 hostId 合起来就是所有的 Strategy 了。

 

Portal 部分

Portal 用来配置报警策略,它的建库 SQL 是整个 open-falcon 的核心,文件是:scripts/schema.sql, Heartbeat Server 从 Portal 数据库读取数据,如果不看 Portal 只看 Heartbeat Server 可能不好理解。

schema.sql 包括的表有:
host
grp
grp_host
tpl
strategy
expression
grp_tpl
plugin_dir
action
cluster
mockcfg

它们的关系:机器可以放入机器组,机器组和模板绑定,模板可以继承;策略和模板对应,一个模板可以对应多个策略,事实上基于模板来查询策略(子模块会覆盖父模板的策略);模板会绑定 action_id,action 中包含 用户手机号和邮箱、callback 等信息;额外的,机器组也会绑定插件目录;对于 Expression,直接和 action 关联。

 

 

总结一下,open-falcon 是一套挺复杂的监控系统,其中我觉得 Graph 和 Judge 最复杂。如果我用起来,可以优化的点:

1. Portal 中的 host 表可以不使用,机器信息通过 机器管理系统 获取;

2. 每个组件配置文件中的机器列表,最好通过 zookeeper 来动态获取;

3. 目前使用了 rrd 存储,是否可以尝试下 opentsdb 或者 influxdb。