Python自动化运维:技术与最佳实践
上QQ阅读APP看书,第一时间看更新

第一部分 基础篇

第1章 系统基础信息模块详解

系统基础信息采集模块作为监控模块的重要组成部分,能够帮助运维人员了解当前系统的健康程度,同时也是衡量业务的服务质量的依据,比如系统资源吃紧,会直接影响业务的服务质量及用户体验,另外获取设备的流量信息,也可以让运维人员更好地评估带宽、设备资源是否应该扩容。本章通过运用Python第三方系统基础模块,可以轻松获取服务关键运营指标数据,包括Linux基本性能、块设备、网卡接口、系统信息、网络地址库等信息。在采集到这些数据后,我们就可以全方位了解系统服务的状态,再结合告警机制,可以在第一时间响应,将异常出现在苗头时就得以处理。

本章通过具体的示例来帮助读者学习、理解并掌握。在本章接下来的内容当中,我们的示例将在一个连续的Python交互环境中进行。

进入Python终端,执行python命令进入交互式的Python环境,像这样:

        # python
        Python 2.6.6 (r266:84292, Nov 222013, 12:16:22)
        [GCC 4.4.720120313 (Red Hat 4.4.7-4)] on linux2
        Type "help", "copyright", "credits" or "license" for more information.
        >>>

1.1 系统性能信息模块psutil

psutil是一个跨平台库(http://code.google.com/p/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等操作系统,支持从2.4到3.4的Python版本,目前最新版本为2.0.0。通常我们获取操作系统信息往往采用编写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.1.1 获取系统性能信息

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

(1)CPU信息

Linux操作系统的CPU利用率有以下几个部分:

❑User Time,执行用户进程的时间百分比;

❑System Time,执行内核进程和中断的时间百分比;

❑Wait IO,由于IO等待而使CPU处于idle(空闲)状态的时间百分比;

❑Idle,CPU处于idle状态的时间百分比。

我们使用Python的psutil.cpu_times()方法可以非常简单地得到这些信息,同时也可以获取CPU的硬件相关信息,比如CPU的物理个数与逻辑个数,具体见下面的操作例子:

        >>> import psutil
        >>>psutil.cpu_times()#使用cpu_times方法获取CPU完整信息,需要显示所有逻辑CPU信息,
        >>>#指定方法变量percpu=True即可,如psutil.cpu_times(percpu=True)
        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)
        >>>psutil.cpu_times().user    #获取单项数据信息,如用户user的CPU时间比
        38.0
        >>>psutil.cpu_count()    #获取CPU的逻辑个数,默认logical=True4
        >>>psutil.cpu_count(logical=False)    #获取CPU的物理个数
        2
        >>>

(2)内存信息

Linux系统的内存利用率信息涉及total(内存总数)、used(已使用的内存数)、free(空闲内存数)、buffers(缓冲使用数)、cache(缓存使用数)、swap(交换分区使用数)等,分别使用psutil.virtual_memory()与psutil.swap_memory()方法获取这些信息,具体见下面的操作例子:

        >>> import psutil
        >>>mem = psutil.virtual_memory()   #使用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
        >>>psutil.swap_memory()      #获取SWAP分区信息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()    #使用psutil.disk_io_counters获取硬盘总的IO个数、
                                        #读写信息
        sdiskio(read_count=9424,  write_count=35824,  read_bytes=128006144,     write_
        bytes=204312576, read_time=72266, write_time=182485)
        >>>
        >>>psutil.disk_io_counters(perdisk=True)  #“perdisk=True”参数获取单个分区IO个数、
                                                  #读写信息
        {'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()    #使用psutil.net_io_counters获取网络总的IO信息,默
                                       #认pernic=False
        snetio(bytes_sent=27098178, bytes_recv=28220119, packets_sent=200978, packets_
        recv=212672, errin=0, errout=0, dropin=0, dropout=0)
        >>>psutil.net_io_counters(pernic=True)   #pernic=True输出每个网络接口的IO信息
        {'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()    #使用psutil.boot_time方法获取开机时间,以Linux时间戳格式返回
        1389563460.0
        >>>datetime.datetime.fromtimestamp(psutil.boot_time()).strftime("%Y-%m-%d
        %H:%M:%S")
        '2014-01-12 22:51:00'    #转换成自然时间格式

1.1.2 系统进程管理方法

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

(1)进程信息

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

        >>> import psutil
        >>>psutil.pids()   #列出所有进程PID
        [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19……]
        >>> p = psutil.Process(2424)  #实例化一个Process对象,参数为一进程PID
        >>> p.name()  #进程名
        'java'
        >>> p.exe()  #进程bin路径
        '/usr/java/jdk1.6.0_45/bin/java'
        >>>p.cwd()  #进程工作目录绝对路径
        '/usr/local/hadoop-1.2.1'
        >>>p.status()    #进程状态
        'sleeping'
        >>>p.create_time()    #进程创建时间,时间戳格式
        1394852592.6900001
        >>>p.uids()    #进程uid信息
        puids(real=0, effective=0, saved=0)
        >>>p.gids()    #进程gid信息
        pgids(real=0, effective=0, saved=0)
        >>>p.cpu_times()    #进程CPU时间信息,包括user、system两个CPU时间
        pcputimes(user=9.0500000000000007, system=20.25)
        >>>p.cpu_affinity()    #get进程CPU亲和度,如要设置进程CPU亲和度,将CPU号作为参数即可
        [0, 1]
        >>>p.memory_percent()    #进程内存利用率
        14.147714861289776
        >>>p.memory_info()    #进程内存rss、vms信息
        pmem(rss=71626752, vms=1575665664)
        >>>p.io_counters()    #进程IO信息,包括读写IO数及字节数
        pio(read_count=41133,  write_count=16811,  read_bytes=37023744,    write_
        bytes=4722688)
        >>>p.connections()    #返回打开进程socket的namedutples列表,包括fs、family、laddr
                              #等信息
        [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)
        >>>p.cpu_times()    #得到进程运行的CPU时间,更多方法见上一小节
        pcputimes(user=0.01, system=0.040000000000000001)

参考提示

❑ 1.1.1节示例参考https://github.com/giampaolo/psutil。

❑ 1.1.1节模块说明参考官网http://psutil.readthedocs.org/en/latest/。

1.2 实用的IP地址处理模块IPy

IP地址规划是网络设计中非常重要的一个环节,规划的好坏会直接影响路由协议算法的效率,包括网络性能、可扩展性等方面,在这个过程当中,免不了要计算大量的IP地址,包括网段、网络掩码、广播地址、子网数、IP类型等。Python提供了一个强大的第三方模块IPy(https://github.com/haypo/python-ipy/),最新版本为V0.81。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.2.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')
        print ip.len()    #输出192.168.0.0/16网段的IP个数
        for x in ip:   #输出192.168.0.0/16网段的所有IP清单
            print(x)

执行结果如下:

        65536
        192.168.0.0
        192.168.0.1
        192.168.0.2
        192.168.0.3
        192.168.0.4
        192.168.0.5
        192.168.0.6
        192.168.0.7
        192.168.0.8
        ……

下面介绍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。

1.2.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对象进行比较,如:

        >>>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')
        >>>IP('192.168.1.0/24') in IP('192.168.0.0/16')
        True

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 = raw_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

参考提示

❑ 1.2.1节官网文档与示例参考https://github.com/haypo/python-ipy/。

❑ 1.2.2节示例1参考http://blog.philippklaus.de/2012/12/ip-address-analysis-using-python/和http://www.sourcecodebrowser.com/ipy/0.62/class_i_py_1_1_i_pint.html等文章的IPy类说明。

1.3 DNS处理模块dnspython

dnspython(http://www.dnspython.org/)是Python实现的一个DNS工具包,它支持几乎所有的记录类型,可以用于查询、传输并动态更新ZONE信息,同时支持TSIG(事务签名)验证消息和EDNS0(扩展DNS)。在系统管理方面,我们可以利用其查询功能来实现DNS服务监控以及解析结果的校验,可以代替nslookup及dig等工具,轻松做到与现有平台的整合,下面进行详细介绍。

首先介绍dnspython模块的安装,这里采用源码的安装方式,最新版本为1.9.4,如下:

        # 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.3.1 模块域名解析方法详解

dnspython模块提供了大量的DNS处理方法,最常用的方法是域名查询。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,其中IN为默认,使用最广泛。tcp参数用于指定查询是否启用TCP协议,默认为False(不启用)。source与source_port参数作为指定查询源地址与端口,默认值为查询设备IP地址和0。raise_on_no_answer参数用于指定当查询无应答时是否触发异常,默认为True。

1.3.2 常见解析类型示例说明

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

(1)A记录

实现A记录查询方法源码。

【/home/test/dnspython/simple1.py】

        #!/usr/bin/env python
        import dns.resolver
        domain = raw_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:    #遍历回应信息
        printj.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记录

实现MX记录查询方法源码。

【/home/test/dnspython/ simple2.py】

        #!/usr/bin/env python
        import dns.resolver
        domain = raw_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记录

实现NS记录查询方法源码。

【/home/test/dnspython/ simple3.py】

        #!/usr/bin/env python
        import dns.resolver
        domain = raw_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记录

实现CNAME记录查询方法源码。

【/home/test/dnspython/ simple4.py】

        #!/usr/bin/env python
        import dns.resolver
        domain = raw_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后的目标域名。

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

大部分的DNS解析都是一个域名对应一个IP地址,但是通过DNS轮循技术可以做到一个域名对应多个IP,从而实现最简单且高效的负载平衡,不过此方案最大的弊端是目标主机不可用时无法被自动剔除,因此做好业务主机的服务可用监控至关重要。本示例通过分析当前域名的解析IP,再结合服务端口探测来实现自动监控,在域名解析中添加、删除IP时,无须对监控脚本进行更改。实现架构图如图1-1所示。

图1-1 DNS多域名业务服务监控架构图

1. 步骤

1)实现域名的解析,获取域名所有的A记录解析IP列表;

2)对IP列表进行HTTP级别的探测。

2. 代码解析

本示例第一步通过dns.resolver.query()方法获取业务域名A记录信息,查询出所有IP地址列表,再使用httplib模块的request()方法以GET方式请求监控页面,监控业务所有服务的IP是否服务正常。

【/home/test/dnspython/simple5.py】

        #!/usr/bin/python
        import dns.resolver
        import os
        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,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=="<!doctype html>":  #监控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地址,并且服务都是正常的。