@HUST张友东 work@taobao zyd_com@126.com
分类: LINUX
2014-05-13 10:22:57
TFS负责运维的同学在工作过程中,积累了各种运维脚本,全部使用shell编写,用于完成TFS机器的上下线、坏盘下线、集群同步、迁移等功能,这些脚本构成现在的运维平台;但由于运维同学的不断更替,新同学在使用前辈们留下来的运维脚本过程中经常踩坑,严重影响运维工作效率。
以下列举了TFS运维过程中,出过的一些问题:
上面大部分问题有个共性,就是对一大批机器进行相同的操作,但由于运维操作失误或运维脚本bug,导致部分机器的操作没有应用成功。以版本升级为例,对一批机器升级后,会输出每台机器的执行结果。
node 192.168.1.30 update success node 192.168.1.31 update success node 192.168.1.32 update failed node 192.168.1.33 update success ......
因为运维脚本在编写的时候,集群规模还比较小,上述的执行结果,运维同学肉眼核对一下,能很快的发现问题;而当集群规模到数百台甚至上千台机器时,如果仍按这种方式简单的输出结果,稍有疏忽就会遗漏掉部分机器的操作。
去年年底,在被坑了数次之后,我们决定对现有的运维平台进行改造,希望运维同学在维护系统时,完全傻瓜式的操作,不需要对系统了解很全面就能上手。
新的运维平台,没有使用puppet等牛逼的开源系统,因为我们的需求相对比较简单,并且希望我们能完全handle住,出现问题时能花最少的时间搞定。由于缺乏"全栈工程师",新的平台没有华丽的操作界面,还是由一堆脚本组成,只不过语言换成了python。
如下图所示,运维平台的python代码部署到一个主控机上(tfsops console),所有的运维操作由主控节点发出,主控节点负责将操作应用到TFS集群(cluster)中的每台机器上,并反馈最终的操作结果。
虽然运维平台里很多工作使用shell实现起来更简单,但最终我们决定用python,主要基于以下原因:
1. shell脚本太太太太太难维护了
2. 稍微复杂点的需求用shell难以实现,尤其涉及全局控制、以及需要记录状态的操作
3. 最重要的原因,负责改造运维平台的都是TFS的开发同学,C++ developer写python比写shell更在行
主控机与被管理节点采用ssh协议通信,因为每个节点上部署ssh服务是标准配置(包含在装机模板里),不用再到每个节点上部署单独的proxy来与主控机通信(本身又会带来新的运维工作)。
主控机除了与被管理节点通信,还需要与mysql数据库打交道,TFS每台机器的信息都会记录在数据库里,主要包括机器类型、机器状态、机器的物理位置信息(所属机房、机架等)、部署服务类型、在线服务的磁盘号索引等。其中磁盘索引主要跟TFS管理磁盘的方式相关,TFS为了最大化文件存储服务的QPS,采用每个dataserver(DS)进程管理一块磁盘的方式。在DS运行过程中,可能会出现坏盘的情况,也可能会有新的磁盘加入,也就是说,一台物理机上面部署的DS进程数量是不确定的,磁盘索引就是为了解决这个问题。
每台机器加到数据库前,会通过python脚本来分析机器的初始配置情况,比如机器有5块磁盘,初始时每块盘都是正常的,磁盘索引会被初始化为1,2,3,4,5,在运行过程中,当某块磁盘坏掉时(目前主要靠脚本自动检测),会触发"下盘操作",下盘操作主要讲磁盘从集群里移除,并更新所在机器的磁盘索引,比如disk 2坏了,下盘操作结束后,磁盘索引就变成了1,3,4,5;经过一段时间后,disk 2可能修好或是被新的盘替代,这时会触发一个"上盘操作",上盘操作将磁盘加入到集群里服务,并更新所在机器的磁盘索引,此时磁盘索引又变成了1,2,3,4,5。
磁盘索引在TFS的运维中起着重要的作用,DS服务的监控以及启动都是根据磁盘索引,监控DS节点时,会根据磁盘索引来决定需要监控哪些端口,在服务启动时,也会现在从数据库里获取这台机器上需要启动哪些DS的信息。
除了机器及磁盘信息的维护外,运维平台还有版本升级、配置管理等功能,这些都涉及在一批机器上执行相同的操作,针对这个特殊需求,我们做了一个多线程任务执行的框架。
1. 根据指定的线程数(并发度),将机器分成多批。比如100台机器,10个线程,那么每批操作10台机器。
2. 对于每一批机器,主控节点同时向这批机器发送操作请求,并等待操作结束,并记录每个机器的执行结果。
3. 等所有机器操作结束后,打印全局的执行结果,如下所示。
node 192.168.1.30 update success
node 192.168.1.31 update success
node 192.168.1.32 update failed
node 192.168.1.33 update success
Summary ==> total:4 success:3 fail:1
除了打印每个机器的执行状态,运维操作执行完后,会输出总的执行结果,操作人员只需要看最后这行输出就能知道本次运维操作是否成功;如果不是所有的机器都成功,由于我们所有的操作都设计成“可重做的”(同一个操作,多次执行,得到的结果一样的),操作人员只需要重新执行即可。
以TFS版本升级为例,主要包含如下几个步骤:
1. 安装新版本的TFS
2. 重启所有的服务进程
在步骤1开始前,会先检查下对应版本是否已经安装好,如果已经是期望的版本,则这一步就直接跳过了;在步骤2结束后,也就是升级成功后,会将机器的当前版本记录到机器本地;升级前如果发现机器本地记录的版本已经是期望的版本,则1、2都可以跳过了。这么做的主要原因时,如果在第一次升级时,假设300台机器成功了290台,有10台失败了,那么在第二次重试时,已经成功的290台在经过检查后发现已经升级成功就可以直接忽略了,只用升级上次失败的10台机器。
在配置管理上,以DS为例,DS绝大多数配置项都是相同的,只有少数参数可能因集群、机器而异,于是我们采用模板的方式来管理配置。每种服务对应一个基线的配置模板,模板里包含通用的配置参数,在每个机器上线时,将机器特定的一些配置项应用到基线模板上,生成出机器最终的配置文件。为了防止出现配置项被误修改的情况,我们对每台机器的配置文件进行监控,周期性的比对机器当前配置文件与生成出来的配置是否相符,以检查出是否有配置项被修改过,如果有则及时告警。
TFS采用主备集群的方式来做机房容灾,写入主集群的文件数据会被自动同步到备集群,但在某些场景下,需要人工的在集群间同步文件数据,比如:
1. 主备集群间同步出现问题,比如网络异常,需要增量的同步异常期间写入主集群的文件。
2. 某个集群超过副本个数的磁盘或机器同时挂掉,导致数据块Block丢失,需要从对等的集群恢复。
3. 新搭建一个备份集群时,需要将主集群里现有的数据同步到备集群。
以上场景实际上都是将某个文件(或某个数据块Block里的所有文件)从一个集群同步到另外一个集群,tfs/bin/sync_by_file,sync_by_blk两个同步工具就是用来完成这个工作。因为同步的数据量可能很大,靠单个进程或是单机多进程来同步效率太低,我们用脚本对同步工具进行了一层封装,实现了简单的任务分发和执行结果收集的功能,利用分布式来提高同步到效率。
1. 根据同步到规模(文件或Block数量),从集群中选择一批load较低的机器。
2. 主控机将同步输入列表均分到这批机器上,并在所有机器上同时启动同步。
3. 每个机器上的同步进程会将执行情况记录到本地的日志里。
4. 主控机从每个机器收集任务执行结果。
5. 重做执行失败的同步任务,直到成功或超出重试次数。
新的运维平台已经上线使用3个多月,我们不断根据运维同学反馈的意见进行问题修复、使用改进,现在已经比较稳定了。目前,运维平台只接入了TFS的nameserver和dataserver,后续会将扩展服务如rcserver、kvmeta server等所有服务的运维管理都接入到新的运维平台,实现统一、简单的运维。