docker swarmkit 源码阅读: Orchestrator 部分

在 swarmkit 中 Orchestrator 负责 service 的运行管理,官方文档这么描述:

The Orchestrator ensures that services have the appropriate set of tasks running in the cluster according to the service configuration and polices.

 

swarmkit 支持两种 service 类型,一种是 global service,一种是 replicated service,对于 global service 它的 task 会运行在所有的 node 中,除非 node 被 drain 掉;对于 replicated service,需要一个 num,即运行 task 的数量,比如 task 数量是 10,那么 service 的 Slot 从 1 到 10。

 

先看看 global service。

7b635512-79a5-4ff2-ab2c-2d82ff5fa2f4

它的 struct 如上图。

1. 从 store 监控 event;

2. 从 store 获取 cluster,放入 cluster;

3.  从 store 中获取所有 node,如果不是 drain 状态,放入 nodes 中;

4. 从 store 获取所有 service,如果是 global service,放入 globalServices,并且 reconcile  此 service。

5. reconcile 的过程是:从 store 中拿到 service 的所有 task,放入以 node 为 key 的 map 中,如果某一个 node 中的一个 task 是 Completed 状态,则把此 node 的其他 task 都 delete 掉;如果一个 node 中没有 task,则在此 node 中增加 task;对于其他情形的 task,则 Update;

6. 然后进行循环,等待 event,分别对下面的 event 类型做处理:

1). state.EventUpdateCluster

更新 cluster

2). state.EventCreateService 和 state.EventUpdateService

增加或修改 service 信息到 globalServices,并 reconcile 此 service

3). state.EventDeleteService

删除 service 的所有 task,在 globalServices 删除此 service,清除 restart 中的 ServiceHistory

4). state.EventCreateNode

reconcile 此 node,如果此 node 是 drain,从 nodes 删除 node 信息和上面的 task,否则保存 node 信息到 nodes,然后对每一个 service reconcileServiceOneNode,方式类似第 5 点所说。

5). state.EventUpdateNode

如果 node 状态是 NodeStatus_DOWN,删除 node 中的 task;

如果状态是 NodeStatus_READY,reconcileOneNode。

6). state.EventDeleteNode

删除 node 中的 task,从 nodes 中删除 node

7). state.EventUpdateTask

如果 task 的 service 不在 globalServices 中,不处理;

否则重启 task

8). state.EventDeleteTask

如果 task 的 service 不在 globalServices 中,不处理;

否则 reconcileServiceOneNode。

 

 

然后是 replicated service。

17ffa76a-028d-4c5c-99e3-f7c34d862186

它的 struct 如上图。

1.  从 store 监控 event;

2. 执行 initTasks,这个函数从 store 里拿到所有 task,对于有绑定 node 的「状态」小于等于 TaskStateRunning 的「目标状态」小于等于 TaskStateRunning 的 task,放入 restartTasks,这些是需要监控重启的 task;然后删除没有绑定 service 的 task;对于状态是 TaskStateReady 的 task,进行启动或延迟启动。

3. 执行 initServices,把是 replicated service 的 service 加入 reconcileServices;

4. 执行 initCluster,从 store 中获取 cluster 放入 cluster;

5. 先进行一次 tick,包括 tickTasks 和 tickServices,即重启 restartTasks 的 task 和 reconcile reconcileServices 中的 service。对于 reconcile service,如果指定的 Slot 数量大于当前 Slot 数量,会进行扩容,如果小于,则会进行缩减,缩减的优先级是已经停止的 Slot 和较多运行在某一个 node 上的 Slot。不动的 Slot 会进行 Update;

6. 接下来会处理 event,如果是以下 event:

EventDeleteNode
EventCreateNode
EventUpdateNode
EventDeleteTask
EventUpdateTask
EventCreateTask
EventDeleteService
EventCreateService
EventUpdateService

这些 event 最终都会反馈到 restartTasks 和 reconcileServices,最终被 tick 掉。

7. 如果是 EventCommit,则进行 tick。

8. 如果是 EventUpdateCluster,则更新 cluster。

 

 

上面提到了对 task 的 Restart 和 Update,Restart 就是重启,Update 则是检查 task 的配置( Spec )是否有变更,比如 cpu、mem 等,task 的 Spec 其实就是 service 的 Spec,当 service 有变化时,必须要 Update 以保证 task 运行在正确的配置下。

另外要注意一点,当 service Update 时,如果 Update 失败,有三种处理的选择:UpdateConfig_PAUSE、UpdateConfig_CONTINUE 和 UpdateConfig_ROLLBACK,第一种是暂停,不能继续 Update 了,第二种可以继续,第三种也可以,但是会回滚 service 配置。而且,如果 Update 失败了,由于 Update 失败会产生 event ( task 启动失败,是 EventCommit 还是 EventUpdateTask ?),这个 event 会被 replicated service 的 tick 捕获,从而进行不断重启(此点没有经过实践验证)。

 

 

总结一下,Orchestrator 负责从「数据库」拿到 service 的变更并执行,至于资源分配和调度就不是它的工作了。

 

参考:

https://github.com/docker/swarmkit/blob/master/design/nomenclature.md

 

Puppet file 指令删除目标目录中的不在源目录中的文件

file {'/home/op/open-falcon-agent/plugin':
    path => '/home/op/open-falcon-agent/plugin/',
    source => 'puppet:///modules/sre-scripts/',
    recurse => true,
    ignore => '\.git',
    group => root,
    owner => root,
    mode => 755,
    purge => true,
    force => true,
    require => Package['open-faclon-agent']
}

这段配置,把 puppet:///modules/sre-scripts/ 中的目录和文件同步到 /home/op/open-falcon-agent/plugin,如果想把 /home/op/open-falcon-agent/plugin 中不在 puppet:///modules/sre-scripts/ 中的文件和目录删除掉,可以使用:

purge => true,
force => true,

purge => 默认会删除文件,不会删除目录,再配合使用 force => true,可以把不存在的目录删掉。

2F7828D7-1698-4746-A268-0D7C3965B027

 

参考:

https://docs.puppet.com/puppet/latest/reference/type.html

 

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 监控、我需要的集群监控和趋势监控、还有减少报警数量的报警合并功能,很符合互联网公司的需求,赞。

 

使用 MegaCli 遇到的两个问题

总结一下使用 MegaCli 遇到的问题。

1. 系统中有 3T 11 块硬盘,fdisk -l 看不到盘,此时可以使用:

MegaCli -PDMakeJBOD -PhysDrv[32:1,32:2,32:3,32:4,32:5,32:6,32:7,32:8,32:9,32:10,32:11] -a0

解决此问题。

2. 执行上面的命令之后,如果想对其中若干块盘做 RAID,会报错,比如:

# MegaCli -CfgSpanAdd -R10 -Array0[32:1,32:2] -Array1[32:3,32:4] -Array2[32:5,32:6] -Array3[32:7,32:8] -Array4[32:9,32:10] WB Direct -a0

The specified physical disk does not have the appropriate attributes to complete
the requested command.

Exit Code: 0x26

此时,把硬盘的 NonRaid 关掉就 OK 了,命令:

MegaCli -AdpSetProp -EnableNonRaid -0 -a0