python自动化运维——系统基础信息模块详解

系统基础信息采集模块作为监控模块的重要组成部分,能够帮助运维人员了解当前系统健康程度,同时也是衡量业务的服务质量的依据。比如系统资源吃紧,会直接影响业务的服务质量及用户体验,另外获取设备的流量信息,也可以让运维人员更好地评估带宽、设备资源是否应该扩容。

本章通过运用Python第三方系统基础模块,可以轻松获取服务器关键监控指标数据:包括Linux基本性能、块设备、网卡接口、系统信息、网络地址库等信息。在采集到这些数据后,就可以全方位了解系统服务的状态,再结合告警机制,可以在第一时间响应,将异常出现在苗头时就得以处理。

 

一、 系统性能信息模块 psutil

psutil是一个跨平台库,能够轻松实现获取系统运行的进程和系统利用率(包括CPU、内存、磁盘、网络等)信息。主要应用于系统监控,分析和限制系统资源及进程的管理,它实现了同等命令行工具提供的功能,如ps、top、lsof、netstat、ifconfig、who、df、kill、free、nice、ionice、iostat、iotop、uptime、pidof、tty、taskset、pmap等。

目前支持32位和64位的Linux、Windows、OS X、FreeBSD和Sun Solaris等操作系统。通常我们获取操作系统信息往往采用编写shell来实现,如获取当前物理内存总大小及已使用大小,shell命令如下:

#物理内存total值:
free -m | grepMem | awk '{print $2}'
#物理内存used值:
free -m | grepMem | awk '{print $3}'

相比较而言,使用psutil库实现则更加简单明了,psutil大小单位一般采用字节,如下:

>>> import psutil
>>>mem = psutil.virtual_memory()
>>>mem.total,mem.used
(506277888L, 500367360L)

psutil源码安装步骤如下:

wget https://pypi.python.org/packages/source/p/psutil/psutil-2.0.0.tar.gz --no-check-certificate
tar -xzvf psutil-2.0.0.tar.gz
cd psutil-2.0.0
python setup.py install

 

1. 获取系统性能信息

采集系统的基本性能信息包括CPU、内存、磁盘、网络等,可以完整描述当前系统的运行状态及质量。psutil模块已经封装了这些方法,用户可以根据自身的应用场景,调用相应的方法来满足需求,非常简单实用。

 

(1)CPU信息

Linux CPU利用率有以下几个部分:

  • User Time:执行用户进程的时间百分比;
  • System Time:执行内核进程和中断的时间百分比;
  • Wait IO:由于IO等待而使CPU处于idle(空闲)状态的时间百分比;
  • Idle:CPU处于idle状态的时间百分比。

使用psutil.cpu_times()方法可以非常简单地得到这些信息,也可以获取CPU的硬件相关信息,比如CPU的物理与逻辑个数

>>> import psutil

#使用cpu_times方法获取CPU完整信息,指定方法变量percpu=True可显示所有逻辑CPU信息,即psutil.cpu_times(percpu=True)
>>>psutil.cpu_times() 
scputimes(user=38.039999999999999, nice=0.01, system=110.88, idle=177062.59, iowait=53.399999999999999, irq=2.9100000000000001, softirq=79.579999999999998, steal=0.0, guest=0.0)

#获取单项数据信息,如用户user的CPU时间比
>>>psutil.cpu_times().user
38.0

#获取CPU的逻辑个数,默认logical=True
>>>psutil.cpu_count()
4

#获取CPU的物理个数
>>>psutil.cpu_count(logical=False)    
2

 

(2)内存信息

psutil.virtual_memory()与psutil.swap_memory()方法基本对应Linux free命令:

>>> import psutil

#使用psutil.virtual_memory方法获取内存完整信息
>>>mem = psutil.virtual_memory()
>>>mem
svmem(total=506277888L, available=204951552L, percent=59.5, used=499867648L, free=6410240L, active=245858304, inactive=163733504, buffers=117035008L, cached=81506304)

#获取内存总数
>>>mem.total    
506277888L

#获取空闲内存数
>>>mem.free
6410240L

#获取SWAP分区信息
>>>psutil.swap_memory()    
sswap(total=1073733632L, used=0L, free=1073733632L, percent=0.0, sin=0, sout=0)

 

(3)磁盘信息

在系统的所有磁盘信息中,我们更加关注磁盘的利用率及IO信息,其中磁盘利用率使用psutil.disk_usage方法获取。

磁盘IO信息包括read_count(读IO数)、write_count(写IO数)、read_bytes(IO读字节数)、write_bytes(IO写字节数)、read_time(磁盘读时间)、write_time(磁盘写时间)等,这些IO信息可以使用psutil.disk_io_counters()获取。

#使用psutil.disk_partitions方法获取磁盘完整信息
>>>psutil.disk_partitions()
[sdiskpart(device='/dev/sda1', mountpoint='/', fstype='ext4', opts='rw'),sdiskpart(device='/dev/sda3', mountpoint='/data', fstype='ext4', opts='rw')]

#使用psutil.disk_usage方法获取分区(参数)的使用情况
>>>psutil.disk_usage('/')
sdiskusage(total=15481577472,used=4008087552,free=10687057920,percent=25.899999999999999)

#使用psutil.disk_io_counters获取硬盘总的IO个数、读写信息
>>>psutil.disk_io_counters()
sdiskio(read_count=9424, write_count=35824, read_bytes=128006144, write_bytes=204312576, read_time=72266, write_time=182485)

#perdisk=True参数获取单个分区IO个数、读写信息
>>>psutil.disk_io_counters(perdisk=True)
{'sda2': sdiskio(read_count=322, write_count=0, read_bytes=1445888, write_bytes=0, read_time=445, write_time=0), 'sda3': sdiskio(read_count=618, write_count=3, read_bytes=2855936, write_bytes=12288, read_time=871, write_time=155), 'sda1': sdiskio(read_count=8484, write_count=35821, read_bytes=123704320, write_bytes=204300288, read_time=70950, write_time=182330)}

 

(4)网络信息

系统的网络信息与磁盘IO类似,涉及几个关键点,包括bytes_sent(发送字节数)、bytes_recv=28220119(接收字节数)、packets_sent=200978(发送数据包数)、packets_recv=212672(接收数据包数)等。这些网络信息使用psutil.net_io_counters()方法获取。

#使用psutil.net_io_counters获取网络总的IO信息,默认pernic=False
>>>psutil.net_io_counters()
snetio(bytes_sent=27098178, bytes_recv=28220119, packets_sent=200978, packets_recv=212672, errin=0, errout=0, dropin=0, dropout=0)

#pernic=True输出每个网络接口的IO信息
>>>psutil.net_io_counters(pernic=True)  
{'lo': snetio(bytes_sent=26406824, bytes_recv=26406824, packets_sent=198526, packets_recv=198526, errin=0, errout=0, dropin=0, dropout=0), 'eth0': snetio(bytes_sent=694750, bytes_recv=1816743, packets_sent=2478, packets_recv=14175, errin=0, errout=0, dropin=0, dropout=0)}

 

(5)其他系统信息

psutil模块还支持获取用户登录、开机时间等信息:

#使用psutil.users方法返回当前登录系统的用户信息
>>>psutil.users()
[suser(name='root', terminal='pts/0', host='192.168.1.103', started=1394638720.0), suser(name='root', terminal='pts/1', host='192.168.1.103', started=1394723840.0)]

>>> import psutil, datetime

#使用psutil.boot_time方法获取开机时间,以Linux时间戳格式返回
>>>psutil.boot_time()
1389563460.0

#转换成自然时间格式
>>>datetime.datetime.fromtimestamp(psutil.boot_time()).strftime("%Y-%m-%d %H:%M:%S")
'2014-01-12 22:51:00'

 

二、 系统进程管理方法 psutil

获得当前系统的进程信息,可以让运维人员得知应用程序的运行状态,包括进程的启动时间、查看或设置CPU亲和度、内存使用率、IO信息、socket连接、线程数等,这些信息可以呈现出指定进程是否存活、资源利用情况,为开发人员的代码优化、问题定位提供很好的数据参考。

 

(1)进程信息

psutil模块在获取进程信息方面也提供了很好的支持,包括使用psutil.pids()获取所有进程PID,使用psutil.Process()获取单个进程的名称、路径、状态、系统资源利用率等信息:

>>> import psutil

#列出所有进程PID
>>>psutil.pids()
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19……]

#实例化一个Process对象,参数为一进程PID
>>> p = psutil.Process(2424)

#进程名
>>> p.name()
'java'

#进程bin路径
>>> p.exe()
'/usr/java/jdk1.6.0_45/bin/java'

#进程工作目录绝对路径
>>>p.cwd()
'/usr/local/hadoop-1.2.1'

#进程状态
>>>p.status()
'sleeping'

#进程创建时间,时间戳格式
>>>p.create_time()
1394852592.6900001

#进程uid信息
>>>p.uids()
puids(real=0, effective=0, saved=0)

#进程gid信息
>>>p.gids()
pgids(real=0, effective=0, saved=0)

#进程CPU时间信息,包括user、system
>>>p.cpu_times()
pcputimes(user=9.0500000000000007, system=20.25)

#get进程CPU亲和度,如要设置进程CPU亲和度,将CPU号作为参数即可
>>>p.cpu_affinity()
[0, 1]

#进程内存利用率
>>>p.memory_percent()
14.147714861289776

#进程内存rss、vms信息
>>>p.memory_info()
pmem(rss=71626752, vms=1575665664)

#进程IO信息,包括读写IO数及字节数
>>>p.io_counters()
pio(read_count=41133, write_count=16811, read_bytes=37023744, write_bytes=4722688)

#返回打开进程socket的namedutples列表,包括fs、family、laddr等信息
>>>p.connections()
[pconn(fd=65, family=10, type=1, laddr=('::ffff:192.168.1.20', 9000), raddr=(),……]

#进程开启的线程数
>>>p.num_threads()
33

 

(2)popen类的使用

psutil提供的popen类可获取用户启动的应用程序进程信息,以便跟踪程序进程的运行状态。

>>> import psutil
>>>from subprocess import PIPE

#通过psutil的Popen方法启动的应用程序,可以跟踪该程序运行的所有相关信息
>>> p = psutil.Popen(["/usr/bin/python", "-c", "print('hello')"], stdout=PIPE)
>>>p.name()
'python'

>>>p.username()
'root'

>>>p.communicate()
('hello\n', None)

#得到进程运行的CPU时间
>>>p.cpu_times()
pcputimes(user=0.01, system=0.040000000000000001)

参考

https://github.com/giampaolo/psutil。

http://psutil.readthedocs.org/en/latest/。

 

二、 实用的IP地址处理模块IPy

IP地址规划是网络设计中非常重要的一个环节,规划的好坏会直接影响路由协议算法的效率,包括网络性能、可扩展性等方面,在这个过程中,免不了要计算大量的IP地址,包括网段、网络掩码、广播地址、子网数、IP类型等。

Python提供了一个强大的第三方模块IPy,可以很好地辅助我们高效完成IP的规划工作。

以下是IPy模块的安装方式:

wget https://pypi.python.org/packages/source/I/IPy/IPy-0.81.tar.gz --no-check-certificate
tar -zxvf IPy-0.81.tar.gz
cd IPy-0.81
python setup.py install

 

1. IP地址、网段基本处理

IPy模块包含IP类,可以方便处理绝大部分格式为IPv6及IPv4的网络和地址。

比如通过version方法就可以区分出IPv4与IPv6:

>>>IP('10.0.0.0/8').version()
4    #4代表IPv4类型

>>>IP('::1').version()
6    #6代表IPv6类型

通过指定的网段输出该网段的IP个数及所有IP地址清单:

from IPy import IP
ip = IP('192.168.0.0/16')

#输出192.168.0.0/16网段的IP个数
print ip.len()

#输出192.168.0.0/16网段的所有IP清单
for x in ip:
    print(x)
#执行结果如下:
65536
192.168.0.0
192.168.0.1
192.168.0.2
……

下面介绍IP类几个常见的方法,包括反向解析名称、IP类型、IP转换等。

>>>from IPy import IP
>>>ip = IP('192.168.1.20')

>>>ip.reverseNames()          #反向解析地址格式
['20.1.168.192.in-addr.arpa.']

>>>ip.iptype()    #192.168.1.20为私网类型
'PRIVATE'

>>> IP('8.8.8.8').iptype()    #8.8.8.8为公网类型
'PUBLIC'

>>> IP("8.8.8.8").int()       #转换成整型格式
134744072

>>> IP('8.8.8.8').strHex()    #转换成十六进制格式
'0x8080808'

>>> IP('8.8.8.8').strBin()    #转换成二进制格式
'00001000000010000000100000001000'

>>> print(IP(0x8080808))      #十六进制转成IP格式
8.8.8.8

IP方法也支持网络地址的转换,例如根据IP与掩码生产网段格式,如下:

>>>from IPy import IP

>>>print(IP('192.168.1.0').make_net('255.255.255.0'))
192.168.1.0/24

>>>print(IP('192.168.1.0/255.255.255.0', make_net=True))                   
192.168.1.0/24

>>>print(IP('192.168.1.0-192.168.1.255', make_net=True))  
192.168.1.0/24

#也可以通过strNormal方法指定不同wantprefixlen参数值以定制不同输出类型的网段。

>>>IP('192.168.1.0/24').strNormal(0)
'192.168.1.0'

>>>IP('192.168.1.0/24').strNormal(1)
'192.168.1.0/24'

>>>IP('192.168.1.0/24').strNormal(2)
'192.168.1.0/255.255.255.0'

>>>IP('192.168.1.0/24').strNormal(3)
'192.168.1.0-192.168.1.255'

wantprefixlen的取值及含义:

  • wantprefixlen = 0,无返回,如192.168.1.0
  • wantprefixlen = 1,prefix格式,如192.168.1.0/24
  • wantprefixlen = 2,decimalnetmask格式,如192.168.1.0/255.255.255.0
  • wantprefixlen = 3,lastIP格式,如192.168.1.0-192.168.1.255

 

2 多网络计算方法详解

有时候我们想比较两个网段是否存在包含、重叠等关系,比如同网络但不同prefixlen会认为是不相等的网段,如10.0.0.0/16<>10.0.0.0/24;如果同prefixlen但处于不同网络,同样也不相等,如10.0.0.0/16<>192.0.0.0/16。

# IPy支持类似于数值型数据的比较:
>>>IP('10.0.0.0/24') < IP('12.0.0.0/24')
True

# 判断IP地址和网段是否包含于另一个网段中,如下:
>>> '192.168.1.100' in IP('192.168.1.0/24') 
True

>>>IP('192.168.1.0/24') in IP('192.168.0.0/16')
True

# 判断两个网段是否存在重叠,采用IPy提供的overlaps方法,如:
>>>IP('192.168.0.0/23').overlaps('192.168.1.0/24')
1    #返回1代表存在重叠

>>>IP('192.168.1.0/24').overlaps('192.168.2.0')
0    #返回0代表不存在重叠

根据输入的IP或子网返回网络、掩码、广播、反向解析、子网数、IP类型等信息。

#!/usr/bin/env python
from IPy import IP

ip_s = input('Please input an IP or net-range: ') #接收用户输入,参数为IP地址或网段地址
ips = IP(ip_s)
if len(ips) > 1:  #为一个网络地址
  print('net: %s' % ips.net())  #输出网络地址
  print('netmask: %s' % ips.netmask())  #输出网络掩码地址
  print('broadcast: %s' % ips.broadcast())  #输出网络广播地址
  print('reverse address: %s' % ips.reverseNames()[0])  #输出地址反向解析
  print('subnet: %s' % len(ips))  #输出网络子网数
else:  #为单个IP地址
  print('reverse address: %s' % ips.reverseNames()[0])  #输出IP反向解析

print('hexadecimal: %s' % ips.strHex())  #输出十六进制地址
print('binary ip: %s' % ips.strBin())  #输出二进制地址
print('iptype: %s' % ips.iptype())  #输出地址类型,如PRIVATE、PUBLIC、LOOPBACK等

分别输入网段、IP地址的运行返回结果如下:

python simple1.py 

Please input an IP or net-range: 192.168.1.0/24
# 运行结果如下
net: 192.168.1.0
netmask: 255.255.255.0
broadcast: 192.168.1.255
reverse address: 1.168.192.in-addr.arpa.
subnet: 256
hexadecimal: 0xc0a80100
binaryip: 11000000101010000000000100000000
iptype: PRIVATE
python simple1.py     

Please input an IP or net-range: 192.168.1.20
#输出信息如下
reverse address: 20.1.168.192.in-addr.arpa.
hexadecimal: 0xc0a80114
binaryip: 11000000101010000000000100010100
iptype: PRIVATE

参考

https://github.com/haypo/python-ipy/。

https://blog.philippklaus.de/2012/12/ip-address-analysis-using-python

https://www.sourcecodebrowser.com/ipy/0.62/class_i_py_1_1_i_pint.html

 

三、 DNS处理模块 dnspython

dnspython是Python实现的一个DNS工具包,它支持几乎所有的记录类型,可以用于查询、传输并动态更新ZONE信息,同时支持TSIG(事务签名)验证消息和EDNS0(扩展DNS)。

 

安装方法:

#下载 http://www.dnspython.org/kits/1.9.4/dnspython-1.9.4.tar.gz

tar -zxvf dnspython-1.9.4.tar.gz
cd dnspython-1.9.4
python setup.py install

 

1. 模块域名解析方法详解

dnspython提供了一个DNS解析器类—resolver,使用它的query方法来实现域名的查询功能。

query方法的定义如下:

query(self,qname,rdtype=1,rdclass=1,tcp=False,source=None,raise_on_no_answer=True,source_port=0)
  • qname:为查询的域名
  • rdtype:用来指定RR资源的类型,常用的有以下几种:
A记录:将主机名转换成IP地址;
MX记录:邮件交换记录,定义邮件服务器的域名;
CNAME记录:指别名记录,实现域名间的映射;
NS记录:标记区域的域名服务器及授权子域;
PTR记录:反向解析,与A记录相反,将IP转换成主机名;
SOA记录:SOA标记,一个起始授权区的定义。
  • rdclass:用于指定网络类型,可选的值有IN(默认)、CH与HS。
  • tcp:用于指定查询是否启用TCP协议,默认为False(不启用)。
  • source与source_port:作为指定查询源地址与端口,默认值为查询设备IP地址和0。
  • raise_on_no_answer:用于指定当查询无应答时是否触发异常,默认为True。

 

2 常见解析类型示例说明

常见的DNS解析类型包括A、MX、NS、CNAME等,利用dnspython的dns.resolver. query方法可以简单实现这些DNS类型的查询,为后面要实现的功能提供数据来源,比如对一个使用DNS轮循业务的域名进行可用性监控,需要得到当前的解析结果。下面一一进行介绍。

(1)A记录

import dns.resolver

domain = input('Please input an domain: ')  #输入域名地址
A = dns.resolver.query(domain, 'A')  #指定查询类型为A记录
for i in A.response.answer:  #通过response.answer方法获取查询回应信息
  for j in i.items:  #遍历回应信息
print (j.address)

运行代码,这里以www.google.com域名为例:

python simple1.py 

Please input an domain: www.google.com
#输出
173.194.127.180
173.194.127.178
173.194.127.176
173.194.127.179
173.194.127.177

 

(2)MX记录

import dns.resolver

domain = input('Please input an domain: ')
MX = dns.resolver.query(domain, 'MX')  #指定查询类型为MX记录
for i in MX:  #遍历回应结果,输出MX记录的preference及exchanger信息
 print ('MX preference ='+i.preference+'mail exchanger ='+i.exchange)

运行代码查看结果,这里以163.com域名为例:

python simple2.py 

Please input an domain: 163.com
#输出
MX preference = 10 mail exchanger = 163mx03.mxmail.netease.com.
MX preference = 50 mail exchanger = 163mx00.mxmail.netease.com.
MX preference = 10 mail exchanger = 163mx01.mxmail.netease.com.
MX preference = 10 mail exchanger = 163mx02.mxmail.netease.com.

 

(3)NS记录

import dns.resolver

domain = input('Please input an domain: ')
ns = dns.resolver.query(domain, 'NS')  #指定查询类型为NS记录
for i in ns.response.answer:
 for j in i.items:
   print (j.to_text())

只限输入一级域名,如baidu.com。如果输入二级或多级域名,如www.baidu.com,则是错误的。

python simple3.py 

Please input an domain: baidu.com
#输出
ns4.baidu.com.
dns.baidu.com.
ns2.baidu.com.
ns7.baidu.com.
ns3.baidu.com.

 

(4)CNAME记录

import dns.resolver

domain = input('Please input an domain: ')
cname = dns.resolver.query(domain, 'CNAME')  #指定查询类型为CNAME记录
for i in cname.response.answer:  #结果将回应cname后的目标域名
 for j in i.items:
   print (j.to_text())

结果将返回cname后的目标域名。

 

3. 实践:DNS域名轮循业务监控

大部分的DNS解析都是一个域名对应一个IP地址,但是通过DNS轮循技术可以做到一个域名对应多个IP,从而实现最简单且高效的负载平衡。不过此方案最大的弊端是目标主机不可用时无法被自动剔除,因此做好业务主机的服务可用监控至关重要。

本示例通过分析当前域名的解析IP,再结合服务端口探测来实现自动监控,在域名解析中添加、删除IP时,无须对监控脚本进行更改。实现架构图如图1-1所示。

python自动化运维——系统基础信息模块详解_第1张图片

步骤

  • 实现域名的解析,获取域名所有的A记录解析IP列表 -- 通过dns.resolver.query()方法获取业务域名A记录信息,查询出所有IP地址列表
  • 对IP列表进行HTTP级别的探测 -- 使用httplib模块的request()方法以GET方式请求监控页面,监控业务所有服务的IP是否正常
import dns.resolver
import httplib

iplist=[]  #定义域名IP列表变量
appdomain="www.google.com.hk"  #定义业务域名

def get_iplist(domain=""):  #域名解析函数,解析成功IP将被追加到iplist
    try:
        A = dns.resolver.query(domain, 'A')  #解析A记录类型
    except Exception as e:
        print ("dns resolver error:"+str(e))
        return
    
    for i in A.response.answer:
        for j in i.items:
            iplist.append(j.address)  #追加到iplist
    return True

def checkip(ip):
    checkurl=ip+":80"
    getcontent=""
    httplib.socket.setdefaulttimeout(5)  #定义http连接超时时间(5秒)
    conn=httplib.HTTPConnection(checkurl)  #创建http连接对象
    
    try:
        conn.request("GET", "/",headers = {"Host": appdomain}) #发起URL请求,添加host主机头
        r=conn.getresponse()
        getcontent =r.read(15)  #获取URL页面前15个字符,以便做可用性校验
    finally:
        if getcontent=="": #监控URL页的内容一般是事先定义好的,比如“HTTP200”等
            print (ip+" [OK]")
        else:
            print (ip+" [Error]")  #此处可放告警程序,可以是邮件、短信通知

if __name__=="__main__":
    if get_iplist(appdomain) and len(iplist)>0:  #条件:域名解析正确且至少返回一个IP
        for ip in iplist:
            checkip(ip)
    else:
        print ("dns resolver error.")

可以将此脚本放到crontab中定时运行,再结合告警程序,这样一个基于域名轮循的业务监控已完成。

运行程序,显示结果如下:

python simple5.py 
#输出
74.125.31.94 [OK]
74.125.128.199 [OK]
173.194.72.94 [OK]

从结果可以看出,域名www.google.com.hk解析出3个IP地址,并且服务都是正常的。

 

参考

https://book.2cto.com/201411/48236.html

 

你可能感兴趣的:(Python)