SaltStack源码解析 — salt-master启动过程

环境介绍

 

我们使用如下环境来分析SaltStack源码:

  • 系统:CentOS-7
  • python版本:2.7.5
  • saltstack版本:2015.5.2 (Lithium)

我们使用如下方法来安装salt-master:

  1. rpm -Uvh http://mirrors.opencas.cn/epel/7/x86_64/e/epel-release-7-5.noarch.rpm
  2. yum install salt-master

 

场景描述

 

本节分析salt-master启动过程,我们使用如下命令启动salt-master:

  1. /usr/bin/salt-master -d

 

前置阅读

 

 

总体分析

 

salt-master的启动过程可简单分为两块,一是解析配置文件和命令行参数,二是启动所需进程。
对于解析命令行参数,主要使用到python标准模块optparse,解析master配置文件使用yaml模块。
主要过程为:
1、导入salt.syspaths作为命令行参数默认值,导入salt.config作为master配置文件默认值;
2、ConfigDirMixIn,LogLevelMixIn,RunUserMixin等几个类使用optparse的add_option注册命令行选项;
3、调用parse_args函数,解析命令行参数,解析master配置文件;
4、调用self.master.start(),注册sigusr1和sigusr2信号,分别用来输出堆栈和profile信息;
5、接着启动所需进程,如maintenance进程,publisher进程,master event进程等。

 

详细分析

 

函数调用流程图

 

源码解析

 

核心类功能介绍

 

类关系图

图片源码解析

MasterOptionParser类

此类主要用来解析命令行参数和yaml配置文件。继承了python标准optparse.OptionParser类,以前这六个类ConfigDirMixIn,LogLevelMixIn,RunUserMixin,DaemonMixIn,PidfileMixin,SaltfileMixIn分别用来注册配置文件路径,日志等级,守护进程,pid文件路径,saltfile路径等命令行选项。重写了optparse.OptionPaser的parse_args方法,添加对master yaml配置文件的解析。

 

Maintenance进程

Maintenance进程是用来做master的常规维护工作,如维护定时任务.

 

Publisher Server

绑定了默认的4505 zmq pub端口和publish_pull.ipc pull类型ipc,只要向publish_pull.ipc push数据,凡是subcribe 4505 publisher的client都将收到数据,具体此server什么作用还没有看.

 

EventPublisher Server

绑定了master_event_pub.ipc pub和master_event_pull.ipc pull,只要向master_event_pull.ipc push数据,订阅master_event_pub.ipc的client将收到消息.

 

ReqServer Server

启动了多个Mworker进程,每个进程连接workers.ipc REP,再启用了zmq_device,绑定tcp port 4506 ROUTER和workers.ipc DEALER,这样只要向4506 发送REQ数据,将会负载均衡到Mworker进程,Mworker进程收到请求后执行_handle_payload函数,也就是执行相应的模块.

使用zabbix根据时间监控多行格式的日志

我们目前想使用zabbix每五分钟监控一个错误日志文件,如果监控到有错误产生,就发邮件告警。像标准的访问日志,如nginx的access log,一行表示一条日志,解析起来比较容易,但当日志不是一行一条时,如tomcat,glassfish的日志,如下:
[2015-07-17T14:24:04.552+0800] [glassfish 4.0] [SEVERE] [AS-WEB-CORE-00037] [javax.enterprise.web.core] [tid: _ThreadID=26 _ThreadName=http-listener-1(3)] [timeMillis: 1437114244552] [levelValue: 1000] [[
An exception or error occurred in the container during the request processing
java.lang.IllegalArgumentException
at org.glassfish.grizzly.http.util.CookieParserUtils.parseClientCookies(CookieParserUtils.java:353)
at org.glassfish.grizzly.http.util.CookieParserUtils.parseClientCookies(CookieParserUtils.java:336)
at org.glassfish.grizzly.http.Cookies.processClientCookies(Cookies.java:220)
at org.glassfish.grizzly.http.Cookies.get(Cookies.java:131)
at org.glassfish.grizzly.http.server.Request.parseCookies(Request.java:1911)
at org.glassfish.grizzly.http.server.Request.getCookies(Request.java:1505)
at org.apache.catalina.connector.Request.parseSessionCookiesId(Request.java:4077)
at org.apache.catalina.connector.CoyoteAdapter.postParseRequest(CoyoteAdapter.java:649)
at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:297)
]]

这个时候解析起来就相对复杂,我们可以使用如下脚本来取得最近五分钟的日志:

  1. #!/bin/bash
  2.  
  3. # 取得前5分钟时间
  4. LAST_MINUTE=$(date -d ‘-5 minute’ +%H%M%S)
  5. # 初始化日志条数
  6. LOG_NUM=0
  7. # 最大获取日志条数
  8. MAX_LOG=3
  9. # 初始化最终匹配日志
  10. LOG_CONTENT=""
  11. # 初始化包含时间行的匹配值
  12. LOG_DATE_MATCH=false
  13. # 设置日志路径
  14. LOG_PATH="/data/log/glassfish/domain1/server.log"
  15.  
  16. while read line;do
  17.  
  18.      # 匹配包含时间的行
  19.      if echo "$line" | grep -q ‘^[20’;then
  20.           # 根据包含时间行获取出特定时间格式,如181320
  21.           date_time=$(echo $line | grep -E -o "[0-9]{2}:[0-9]{2}:[0-9]{2}" | tr -d ‘:’)
  22.  
  23.           date_time=$(echo $date_time | sed ‘s/^0//’)
  24.           LAST_MINUTES=$(echo $LAST_MINUTES | sed ‘s/^0//’)
  25.           # 当前行的时间是否大于5分钟前的时间         
  26.           if [[ "$date_time" -gt "$LAST_MINUTE" ]];then
  27.                LOG_CONTENT="$LOG_CONTENTn$log_entry"
  28.                ((LOG_NUM++))
  29.                LOG_DATE_MATCH=true
  30.                log_entry="$linen"
  31.           else
  32.                LOG_DATE_MATCH=false
  33.                continue
  34.           fi
  35.  
  36.      else
  37.           # 只当前面日志时间满足条件时才设置log_entry值
  38.           if $LOG_DATE_MATCH;then
  39.                log_entry="$log_entryn$line"
  40.           fi   
  41.      fi
  42.  
  43.      # 限制最大获取行数
  44.      if [[ "$LOG_NUM" -gt "$MAX_LOG" ]];then
  45.           break
  46.      fi
  47.  
  48. done < $LOG_PATH
  49.  
  50. # 输出全部日志
  51. echo -n -e "$LOG_CONTENT"

前面的脚本按顺序读取的,但当日志文件比较大时,获取日志的效率就非常低了,所以推荐下面倒序读取日志的方法,更高效。

  1. #!/bin/bash
  2.  
  3. # 取得前5分钟时间
  4. LAST_MINUTE=$(date -d ‘-5 minute’ +%H%M%S)
  5. # 初始化日志条数
  6. LOG_NUM=0
  7. # 最大获取日志条数
  8. MAX_LOG=3
  9. # 初始化最终匹配日志
  10. LOG_CONTENT=""
  11. # 设置日志路径
  12. LOG_PATH="/data/log/glassfish/domain1/server.log"
  13.  
  14. while read line;do
  15.  
  16.      # 匹配包含时间的行
  17.      if echo "$line" | grep -q ‘^[20’;then
  18.           # 根据包含时间行获取出特定时间格式,如181320
  19.           date_time=$(echo $line | grep -E -o "[0-9]{2}:[0-9]{2}:[0-9]{2}" | tr -d ‘:’)
  20.         
  21.           # 当前行的时间是否大于5分钟前的时间   
  22.           if [[ "$date_time" > "$LAST_MINUTE" ]];then
  23.                ((LOG_NUM++))
  24.                log_entry="$linen$log_entry"
  25.                LOG_CONTENT="$LOG_CONTENTn$log_entry"
  26.           else
  27.                break
  28.           fi
  29.  
  30.           log_entry=""
  31.  
  32.      else
  33.           log_entry="$linen$log_entry"
  34.      fi
  35.  
  36.      # 限制最大获取行数
  37.      if [[ "$LOG_NUM" > "$MAX_LOG" ]];then
  38.           break
  39.      fi
  40.  
  41. done < <(tac $LOG_PATH)
  42.  
  43. # 输出全部日志
  44. echo -n -e "$LOG_CONTENT"

之后就可以在zabbix添加一个监控项用来获取日志内容,触发器就使用{itemName.strlen(0)}#0表达式来检测获取到的日志内容是否不为空。itemName为监控项名称。

python optparse模块源码解析

功能介绍

 

python的optparse模块用来解析命令行参数。支持多种参数数据类型,如字符串,布尔,常数等;能自动生成帮助与usage等信息。

 

环境介绍

 

我们在如下环境来分析optparse模块源码:
系统:CentOS-7
Python版本:2.7.5

 

场景描述

 

使用官方使用的例子来进行分析:

  1. from optparse import OptionParser
  2. parser = OptionParser()
  3. parser.add_option("-f", "–file", dest="filename",
  4.                   help="write report to FILE", metavar="FILE")
  5. parser.add_option("-q", "–quiet",
  6.                   action="store_false", dest="verbose", default=True,
  7.                   help="don’t print status messages to stdout")
  8.  
  9. (options, args) = parser.parse_args()

 

总体分析

 

optparse模块总体设计比较清晰,首先实例化OptionParser,parser = OptionParser(),实例化中主要是初始化相关选项的实例属性,如短选项self._short_opt,长选项self._long_opt;接着通过parser实例的add_option方法来注册将要获取的命令行选项,主要涉及选项名称,默认值,参数数据类型,方便下一步的解析操作对命令行参数进行必要的检测;最后是调用parser的parse_args方法通过上一步add_option注册的选项来检测命令行参数是否合法,并且返回选项名及其值的字典,这样就可以通过选项名来获取值。

 

详细分析

 

函数调用流程图

 

源码解析

 

核心类功能介绍

 

类关系图

源码解析

OptionContainer类

作用
存储注册选项的数据,如短选项,长选项,关键词(定义选项数据类型等)。
主要属性:
option_list: 存储所有选项定义数据;
_short_opt: 存储短选项定义数据;
_long_opt: 存储长选项定义数据;
defaults: 各选项默认值。
主要方法:
add_option(self, *args, **kwargs): 注册选项,调用Option类存储选项相关数据,并存储在OptionContainer的相关属性中。

 

Option类

作用
解析并记录选项所有数据,如长选项,短选项,数据类型,默认值等。
主要属性
_short_opts: 短选项,如-f;
_long_opts: 长选项,如–file;
action: 对选项值作的操作,如store_true,存储为布尔true;
type: 数据类型;
dest: 设置选项值存储到的变量;
default: 设置选项默认值。
主要方法
主要是_check_*对关键词检查的方法,如_check_type是检查type的值是否合法,并返回正确的类型。

 

OptionParser类

作用
解析命令行参数,此类继承了OptionContainer类,所以可以通过OptionContainer中定义的选项数据来对传入的命令行参数作合法性检查,并返回正确的数据。解析完成之后返回选项名与值的字典。
主要属性
values: 就是解析完成后返回的字典。
主要方法
parse_args(self, args=None, values=None): 解析命令行参数,并返回选项名与值的字典。