openssl小技巧—生成加密密码
由于定期会对用户密码进行更新,之前一直采用的方法是先在测试机器上创建用户及密码,然后提取shadow中加密的部分,和朋友交流这个问题的时候发现完全可以用openssl来实现,直接见测试用例:
[root@localhost ~]# useradd -s /sbin/nologin -M pengyao [root@localhost ~]# echo -n "123456" |passwd --stdin pengyao Changing password for user pengyao. passwd: all authentication tokens updated successfully. [root@localhost ~]# grep pengyao /etc/shadow pengyao:$1$Fx612wwB$Fw1YB1mTalqgvO3Sd77Ak/:15322:0:99999:7::: [root@localhost ~]# openssl passwd -1 -salt Fx612wwB 123456 $1$Fx612wwB$Fw1YB1mTalqgvO3Sd77Ak/
———————————————————————–
扩展一点小知识, 密码的组成有几个部分(以$做分割):
第一部分(声明加密算法):1 —> MD5 ; 2a —> Blowfish ; 5 —>SHA-256 ; 6—>SHA-512
第二部分(salt,没事撒点盐,增加下加密强度): string
第三部分(加密后的密码)
美中不足的是:目前openssl passwd只提供MD5加密算法,而RHEL6/CentOS6已经将默认密码加密算法调整为SHA-512
———————————————–
引申下,openssl生成htpasswd相关密码(方便nginx用户使用)
[root@localhost ~]# htpasswd -nb pengyao 123456 pengyao:CPSVEiksR8iLs [root@localhost ~]# openssl passwd -salt CP 123456 CPSVEiksR8iLs
———————————————————————
更多内容请参考:
(1) openssl passwd手册: http://www.openssl.org/docs/apps/passwd.html#
(2) man 3 crypt
(3) man htpasswd
rrdtool小技巧—绘制固定值
最近在接触关于rrdtool的一些需求,其中的一个需求是在绘制磁盘使用率的同时,增加一明显标识的阀值(固定值),对于“明显”当然可以采用比较容易识别的颜色(如红色),而对于“固定值”的这个需求在翻rrdtool手册的时候发现了HRULE这个选项,其具体用法如下:
HRULE:value#color[:legend][:dashes[=on_s[,off_s[,on_s,off_s]...]][:dash-offset=offset]]
本需求中相应的处理为:
(1)在程序开头定义一个变量alter给予对应的赋值,并定义alter_color=FF0000
(2)在原rrdtool graph代码中增加如下代码:
"HRULE:${alter}#${alter_color} "
然后生成图形发现图形中已经出现了红色的阀值线,这样就可以更直观的了解某个值是否超过了允许的最大值,使其更具有可比性。
美中不足的是,绘图时习惯采用LINE2来做,而HRULE绘制出现线相对于LINE2显得总是那么单薄……
python小技巧—MySQLdb查询返回字典
之前python中使用MySQLdb模块进行Mysql数据查询,返回的结果为元组,其对应查询方法如下:
#!/usr/bin/env python
import MySQLdb
mysql_conn = MySQLdb.connect(host = "127.0.0.1",port = 3306,user = "pengyao",password = "",db = "information_schema")
mysql_cur = mysql_conn.cursor()
mysql_cur.execute("select version()")
mysql_ver = mysql_cur.fetchall()[0][0]
print mysql_ver
而对于某些复杂的SQL语句,由于不同的Mysqld版本,对应的列也不相同(如”show slave status”),导致常常在程序中需要指定对应的列的列数,然后通过元组[]取值,兼容性比较差。
碰巧在一web.py群里边聊天,一个网友提到了这个问题,另一个网友提到了MySQLdb支持MySQLdb.cursors.DictCursor通过字典方式返回对应的结果,特拿出来分享下,同时也直接应用到haproxy系列—定制mysql健康检查程序 中去,具体代码如下:
#!/usr/bin/env python
import MySQLdb
mysql_conn = MySQLdb.connect(host = "127.0.0.1",port = 3306,user = "pengyao",password = "",db = "information_schema")
mysql_cur = mysql_conn.cursor(MySQLdb.cursors.DictCursor)
mysql_cur.execute("select version()")
mysql_ver = mysql_cur.fetchall()[0][""version()""]
print mysql_ver
需要注意的是,据说通过字典方式的话性能会比较低(据说,没进行大数据测试),而本次应用为少量的数据查询,不需要特别关注性能,所以该方法非常不错!在此谢谢那位分享的朋友!
haproxy系列—定制mysql健康检查程序
随着生产环境用户访问的与日俱增,数据库访问压力也随之增大,近期准备对生产环境中的单一数据库进行扩容,增加多台slave数据库,进行读写分离操作,经过若干测试,最终选择使用Haproxy方案,在haproxy版本选型时,选择了1.4的版本,因为在1.4.9开始,haproxy增加了option mysql-check 健康检查功能,其工作原理是建立对应的Mysql连接,然后断开来判断数据库当前的健康状况。而实际生产环境中对数据一致性要求较高,不仅需要对mysql进行健康检查,也需要的slave节点的repliation状态(Slave_IO_Running,Slave_SQL_Running,Seconds_Behind_Master)进行检查,故现阶段的haproxy健康检查功能无法满足需求,需自己进行定制.
其检查流程为:
(1) 检查时指定需要检查的主机,端口,用户名,密码(可为空),同时需要指定检查的数据库类型(对于master类型数据库,则只需连接进去然后通过”select version()”来检查健康状态,对于slave类型的数据库,则需要增加”show slave status” replication状态检查)
(2)结果采用HTTP方式返回,指定不同的状态码对应不同的检查状态,如下所示:
· 200 为服务器端健康检查OK(健康)
———————————————————————-
·401 为检查段MySQLdb python模块没有安装(此程序采用python开发)
·405 为当前检查程序不支持该版本的mysqld (其主要原因是因为在不同的mysqld版本中,其”show slave status”的输出格式不同)
·400 为其他检查端未知异常
———————————————————————-
·501 为服务端(被检查端)连接失败 (其原理是捕捉错误码为2003的mysql报错)
·502 为服务端(被检查端)认证失败(其原理是捕捉错误码为1045的mysql报错)
·503 为服务端(被检查端)连接成功,但是执行查询失败 (如 无权限等情况)
·504 为服务端(被检查端)连接成功,且该服务端为slave类型,此时replication状态无法查询(如没有启动slave)
·505 为服务端(被检查端)连接成功,且该服务端为slave类型,此时检查replication状态,显示”Slave_IO_Running”不等于”Yes”
·506 为服务端(被检查端)连接成功,且该服务端为slave类型,此时检查replication状态,显示”Slave_SQL_Running”不等于”Yes”
·507 为服务端(被检查端)连接成功,且该服务端为slave类型,此时检查replication状态,显示”Seconds_Behind_Master”超过了生产环境中可以承受的replication最大延迟时间
具体代码如下:
#!/usr/bin/env python
#coding = utf8
#Author: pengyao
import sys,getopt
def _opts(args):
'''get option'''
mysql_opts={}
opts,args = getopt.getopt(args,"P:b:h:u:p:t:")
for opt, value in opts:
###mysql server host
if opt == "-h":
mysql_opts["host"] = value
###mysql server port
elif opt == "-P":
mysql_opts["port"] = value
###mysql server user
elif opt == "-u":
mysql_opts["user"] = value
###mysql server password
elif opt == "-p":
mysql_opts["password"] = value
###mysql server type
elif opt == "-t":
mysql_opts["type"] = value
###mysql slave alter behind_master
elif opt == "-b":
mysql_opts["alter_behind"] = value
return mysql_opts
def _response(resp_code):
'''response header'''
state_lists = {}
state_lists["200"] = ["OK","Check OK"]
state_lists["400"] = ["Client failed", "Client Unknow Failed"]
state_lists["401"] = ["MySQLdb module failed", "MySQLdb module is not install"]
state_lists["405"] = ["Mysql version failed", "Mysql Server version is not Allow"]
state_lists["500"] = ["Server failed", "Server Unknow Failed"]
state_lists["501"] = ["Connect failed", "Mysql server connect failed"]
state_lists["502"] = ["Auth failed", "Mysql server auth failed"]
state_lists["503"] = ["Query failed", "Mysql server query falied"]
state_lists["504"] = ["Slave failed", "Mysql slave is not running"]
state_lists["505"] = ["Slave IO failed", "Mysql slave server IO thread is not running"]
state_lists["506"] = ["Slave SQL failded", "Mysql slave server SQL thread is not running"]
state_lists["507"] = ["Slave Behind_master failed", "Mysql slave server is behind master very long time"]
print "HTTP/1.1 %s %s\r" %(resp_code,state_lists[resp_code][0])
print "Content-Type: text/plain\r"
print "\r"
print "%s!\r" %(state_lists[resp_code][1])
print "\r"
sys.exit(0)
def _mysql_check(host, port, user, password, type, alter_behind):
'''check mysql'''
try:
import MySQLdb
except ImportError:
_response("401")
except:
_response("400")
else:
try:
mysql_conn = MySQLdb.connect(host = host, port = int(port), user = user, passwd = password, db = "information_schema")
except MySQLdb.Error,e:
if e.args[0] == 2003:
_response("501")
elif e.args[0] == 1045:
_response("502")
else:
_response("500")
else:
###check mysql health
try:
mysql_cur = mysql_conn.cursor(MySQLdb.cursors.DictCursor)
mysql_cur.execute("select version()")
mysql_ver=mysql_cur.fetchall()[0]["version()"]
except:
_response("503")
else:
if type == "master":
mysql_conn.close()
_response("200")
else:
###slave check
try:
mysql_cur.execute("show slave status")
slave_result = mysql_cur.fetchall()[0]
except:
mysql_conn.close()
_response("503")
else:
mysql_conn.close()
if slave_result == ():
_response("504")
else:
IO_state = slave_result["Slave_IO_Running"]
SQL_state = slave_result["Slave_SQL_Running"]
Behind_state = slave_result["Seconds_Behind_Master"]
if IO_state != "Yes":
_response("505")
else:
if SQL_state != "Yes":
_response("506")
else:
if Behind_state >= int(alter_behind):
_response("507")
else:
_response("200")
def _main():
'''main'''
args = sys.argv[1:]
###Init Mysql Server
mysql_server = {}
mysql_server["host"] = "127.0.0.1"
mysql_server["port"] = "3306"
mysql_server["user"] = "db_check"
mysql_server["password"] = ""
mysql_server["type"] = "master"
mysql_server["alter_behind"] = 1
###mysql args
mysql_opts = _opts(args)
for each_opt in mysql_opts.keys():
mysql_server[each_opt] = mysql_opts[each_opt]
_mysql_check(mysql_server["host"],mysql_server["port"],mysql_server["user"],mysql_server["password"],mysql_server["type"],mysql_server["alter_behind"])
if __name__ == "__main__":
_main()
测试方法:
mysql_check.py -h 127.0.0.1 -P 3306 -u db_check -p db_check -t slave -b 1
对应程序也可直接下载: http://books.pengyao.org/haproxy/mysql_check.py
—————————————————————————————————
更改历史:
2011-09-02 调整MySQLdb查询方式(由原来返回结果为数组变更为字典),使其更具有兼容性
ganglia系列—定制ganglia
由于ganglia默认的自带的数据插件有限,生产环境中往往需要自己根据需求定制自己所需的插件,然后通过Web将相关数据通过绘图展现出来,而ganglia支持C/C++、python来定制自己想要的插件,刚好自己写了一些python的插件,特意将对应的经验分享出来。
(1) gmond
本例需求为获取集群(cluster)各个节点(node)的当前TCP 服务监听的数目(LISTEN),对应服务连接进来的SYN数目(SYN_RECV),对应服务当前的ESTABLIASHED数目(ESTABLIASHED)以及对应服务当前的TIME_WAIT数目(TIME_WAIT)
一般使用linux自带的工具netstat -ant 来获取到对应信息,然后对第六列进行过滤,为LISTEN的则将TCP服务监听数目自加1,依次类推
对于gmond python模块的对应规则,可以参考: http://sourceforge.net/apps/trac/ganglia/wiki/ganglia_gmond_python_modules
lib/ganglia/python_modules/tcp_stats.py代码如下:
#!/usr/bin/env python
#coding = utf8
import commands
port = ""
def tcp_stats():
'''get the special tcp port connections'''
global port
tcp_command = "netstat -ant"
tcp_state={}
tcp_state["tcp_listen"]=["LISTEN",0]
tcp_state["tcp_syn"]=["SYN_RECV",0]
tcp_state["tcp_established"]=["ESTABLISHED",0]
tcp_state["tcp_timewait"]=["TIME_WAIT",0]
result_code,result = commands.getstatusoutput(tcp_command)
if result_code != 0:
pass
else:
for each_result in result.split("\n"):
if len(each_result.split()) == 6 and each_result.startswith("tcp"):
current_port = each_result.split()[3].split(":")[-1]
current_state = each_result.split()[5]
if current_port == port or port == "":
for each_state in tcp_state.keys():
if current_state == tcp_state[each_state][0]:
tcp_state[each_state][1]+=1
return tcp_state
def tcp_filter(name):
'''filter the tcp connections'''
tcp_state = tcp_stats()
count = 0
if name in tcp_state.keys():
count = tcp_state[name][1]
return count
def metric_init(params):
'''mtric init'''
global port
if params:
if params.has_key("port"):
port = params["port"]
else:
port = ""
d_listen = {"name" : "tcp_listen",
"call_back" : tcp_filter,
"value_type" : "uint",
"units" : "Sockets",
"slope" : "both",
"format" : "%d",
"description" : "tcp LISTEN connections stats",
"groups" : "tcp_stats",
}
d_syn = {"name" : "tcp_syn",
"call_back" : tcp_filter,
"value_type" : "uint",
"units" : "Sockets",
"slope" : "both",
"format" : "%d",
"description" : "tcp SYN_RECV connections stats",
"groups" : "tcp_stats",
}
d_established = {"name" : "tcp_established",
"call_back" : tcp_filter,
"value_type" : "uint",
"units" : "Sockets",
"slope" : "both",
"format" : "%d",
"description" : "tcp ESTABLISHED connections stats",
"groups" : "tcp_stats",
}
d_timewait = {"name" : "tcp_timewait",
"call_back" : tcp_filter,
"value_type" : "uint",
"units" : "Sockets",
"slope" : "both",
"format" : "%d",
"description" : "tcp TIME_WAIT connections stats",
"groups" : "tcp_stats",
}
descriptors=[d_listen,d_syn,d_established,d_timewait]
return descriptors
def metric_cleanup():
pass
'''debug code'''
if __name__ == "__main__":
descriptors = metric_init(None)
for each_d in descriptors:
print "value for %s is %d"%(each_d['name'],each_d['call_back'](each_d['name']))
此时还需要在etc/conf.d/增加名为tcp_stats.pyconf文件,具体内容如下:
modules {
module {
name = "tcp_stats"
language = "python"
}
}
collection_group {
collect_every = 10
time_threshold = 30
metric {
name = "tcp_listen"
title = "TCP LISTEN Connections Stats"
value_threshold = 1
}
metric {
name = "tcp_syn"
title = "TCP SYN_RECV Connections Stats"
value_threshold = 1
}
metric {
name = "tcp_established"
title = "TCP ESTABLISHED Connections Stats"
value_threshold = 1
}
metric {
name = "tcp_timewait"
title = "TCP TIME_WAIT Connections Stats"
value_threshold = 1
}
}
此时重启gmond,将会在Web中对应的节点增加上关于TCP的相关统计,但是由于该图形只是在对应节点下,同时各个图形相对独立,需求需要在对应的节点下对该节点的TCP进行整合绘制在一张图上,同时对于集群(cluster)也全部累加到一张图形上,所以还需要的Web方面进行定制
(2) Web
对于ganglia 如何来定制自定义图形,请参考: http://sourceforge.net/apps/trac/ganglia/wiki/Custom_graphs ,由于ganglia Web前端是通过php进行编写的,也需要对PHP基础语法进行一定的了解
首先对config.php增加如下内容:
$optional_graphs = array('tcp_stats');
然后在graph.d目录下建立tcp_stats_report.php的文件,对应代码如下:
此时,会在cluster界面增加了一个真个集群(cluster)的各个节点的TCP相关统计的总和图,但是各个节点(node)界面也想增加一个本节点(node)的对应图形,则需要修改模板文件templates/default/host_view.tpl,在对应的位置上增加如下列:
此时刷新界面单个节点对应的图形也已经添加完毕,完全满足需求
————————————————————————————————–
由于ganglia使用rrdtool进行绘图,所以在添加对应图形的时候,也需要对rrdtool进行一定的了解!
同时为了方便大家进行相关测试,本例中出现的对应文件如下:
gmond — tcp_stats.py : http://books.pengyao.org/ganglia/modules/tcp_stats.py
gmond — tcp_stats.pyconf : http://books.pengyao.org/ganglia/modules/tcp_stats.pyconf
web — tcp_stats_report.php : http://books.pengyao.org/ganglia/modules/tcp_stats_report.php
ganglia系列—ganglia安全配置
第一小节《ganglia系列—初识ganglia》初步介绍了ganglia的工作流程以及安装过程中的注意事项,本小节主要对ganglia的安全方面进行相关介绍。
(1) gmond
gmond作为集群(cluster)内部数据收集的工具,其对应配置文件为etc/gmond.conf,其相关说明请参考 http://sourceforge.net/apps/trac/ganglia/wiki/Gmond%203.1.x%20General%20Configuration ,其内部数据传输可以通过udp多播和udp单波进行数据传输,多播着时方便,但是生产环境中往往采取最小化原则,所以建议在生产环境中可以选择集群(cluster)内部一两个节点作为集群内部中心节点,然后通过udp单波的方式将其他节点的数据发送至该节点,以保证只有需要的才需要。具体配置如下:
此例中假设只发送数据的节点IP: 192.168.1.33 ,集群(cluster)内部中心节点IP地址为: 192.168.1.10 , gmetad IP地址为: 192.168.1.1
@对于只是发送数据的节点
global部分
deaf = yes
udp_send_channel部分
udp_send_channel { host = 192.168.1.10 port = 8649 }如果需要发送到多台机器,多次书写udp_send_channel部分即可,其他部分默认即可@内部中心节点服务器udp_recv_channel部分udp_recv_channel {bind = 192.168.1.10port = 8649acl {default = "deny"access {ip = 192.168.1.33mask = 32action = "allow"}}}如果该服务器需要接受多台节点,则需要多次书写acl部分tcp_accept_channel 部分tcp_accept_channel { bind = 192.168.1.10 port = 8649 acl { default = "deny" access { ip = 192.168.1.1 mask = 32 action = "allow" } } }其他部分默认即可!(2) gmetadgmetad作为网格(grid)的核心,会连接各个集群(cluster)内的中心节点(gmond)来获取整个网格(grid)的数据,然后将利用rrdtool将对应的数据存放到本地的文件系统中,同时Web需要连接gmetad端口,由于通常情况下Web端和gmetad在同一台服务器上,所以建议在生产环境中监听的地址为127.0.0.1gmetad对应的配置文件为 etc/gmetad.conf,对于安全部分需要修改的trusted_hosts 127.0.0.1(3) Web由于ganglia Web前端本身无用户等概念,所以需要依赖于webserver的安全机制来做管理,可以通过Apache的 access相关模块进行安全加固.同时,针对生产环境,也可以通过iptables来进行相关安全加固--------------------------------------------------------------------如果你想对gmond acl 内容了解更多,可以访问: http://sourceforge.net/apps/trac/ganglia/wiki/Gmond%203.1.x%20General%20Configuration#access_control如果你想对Apache的access相关模块了解更多,可以访问: http://books.pengyao.org/Apache-2.2-Menu/mod/index.html如果你想对iptables了解更多,可以通过man iptables 获得更多内容
ganglia系列—初识ganglia
最近由于需要在现有的Web集群系统中建立对应的统计程序,采集现有集群环境的使用状况,以便对下一阶段的集群扩展提供数据支撑,评估几个对应的开源平台后,绝对选择ganglia作为本次统计平台的基础框架。
首先对ganglia的概念进行下了解:
Ganglia is a scalable distributed monitoring system for high-performance computing systems such as clusters and Grids. It is based on a hierarchical design targeted at federations of clusters. It leverages widely used technologies such as XML for data representation, XDR for compact, portable data transport, and RRDtool for data storage and visualization. It uses carefully engineered data structures and algorithms to achieve very low per-node overheads and high concurrency. The implementation is robust, has been ported to an extensive set of operating systems and processor architectures, and is currently in use on thousands of clusters around the world. It has been used to link clusters across university campuses and around the world and can scale to handle clusters with 2000 nodes.
了解相关手册发现ganglia的工作流程如下:
ganglia由三部分构成
(1)gmond : 集群(cluster)内部进行数据收集工具,在数据传输中可以通过UDP多包 或者UDP单包进行数据发送,接受时可以通过IP地址或者多播地址进行UDP数据接收,用于保证中心服务器可以存储本集群系统中所有节点的数据,同时对于需要gmetad连接的gmond服务器,需要开放TCP的accept端口
(2)gmetad:网格(grid)中心进程,通过TCP接口连接集群(clusters)节点进行获取所有的datasource,然后将对应的信息通过rrdtool存放在本地
(3)web:ganglia web前端,将网格(grid)信息中集群(clusters)以及对应的节点(nodes)信息通过web方式展现出来
具体安装请参考: http://sourceforge.net/apps/trac/ganglia/wiki/Ganglia%203.1.x%20Installation%20and%20Configuration
需要注意的是,如果你喜欢使用python来写gmond metric modules的话,请先安装python-devel包,然后再编译gmond,同时从ganglia工作流程中可以看出,因为web前端需要连接gmetad,同时也需要进行rrd文件的读取,而gmetad一般通过本地的rrdtool进行rrd文件的更新,所以建议将gmetad以及web前端部署在同一台机器上,之前测试的时候发现了这一问题,当时最后只好将rrd文件通过nfs共享过来,有点费周折.
————————————————————————————————————
如果想对ganglia了解更多,请访问ganglia网站: http://ganglia.info
因为ganglia通过rrdtool进行绘图,如果想对rrdtool了解更多,请访问rrdtool网站: http://www.mrtg.org/rrdtool/
同时推荐一本flickr工程运营经理写的一本高质量的书《web容量规划的艺术》,此书详细阐述了在http://flickr.com在运营过程中如果通过ganglia获取数据的经验,更多相关内容可以访问douban读书: http://book.douban.com/subject/4200645/
resin access-log有关rollover-size小说明(后续)
之前cowan将resin access-log关于rollover-size的问题提交给了caucho的buglist
0004533: Make access-log rolloverCheckTime configurable
今天在查找相关信息的时候发现已经在4.0.19中解决了该问题
•access-log: add rollover-check-time configuration (#4533, rep by peng yao)
更多resin 4.0的change log可参考: http://www.caucho.com/resin-4.0/changes/changes.xtp
resin access-log有关rollover-size小说明
最近有一点小需求,要求resin产生的access-log的size最大为2M,翻阅resin手册,发现resin access-log中提供了rollover-size的介绍,手册对其描述如下:
rollover-size is a size based specification of a size in bytes (50000), kb (128kb), or megabytes (10mb). When the log reaches the specified size the rollover occurs.
当时认为如果access-log大小超过rollover-size设定的大小,日志将自动rollover。但是在实际测试中确碰到了问题.
测试环境:resin 3.1.9 rollover-size=”10kb” auto-flush=”true”
然后进行40000次请求,发现实际情况和当时的理解不符,实际产生的日志大小最大为1.5MB,但是发现一个现象,就是多个日志rollover的时间间隔为120s~121s之间.
后发maillist求救(General Discussion for the Resin application server),此处就不贴出来邮件内容了(E文太烂了),今天早上接到一封回复,邮件来源于caucho.com域,姑且认为是官方的回复,回复结果如下:
Resin does not roll the access log exactly at rollover-size. rollover-size is a minimum size. It checks the size every 2 minutes.
很恰当的解释了为什么会产生与rollover-size设置不符的问题,因为实际中rollover-size设置的是一个最小的日志尺寸大小,然后每2分钟检查一次,如果检查时acess-log的日志达到了rollover-size设置的大小,将进行日志rollover,如果没有达到,继续向当前access-log文件中写,下次检查的时间是2分钟后,而在2分钟内有可能超过rollover-size的。
在路上
读万卷书,行万里路,我在路上!
新浪围脖直播: http://t.sina.com.cn/xweiyao
