zoukankan      html  css  js  c++  java
  • python自动化运维笔记

    命令行工具

    1.click模块

      click模块和argparse功能相同,但更为易用,使用click分为两个步骤,1)使用@click.command()装饰一个函数,使之成为命令行接口;2)使用@click.option()等装饰函数,为其添加命令行选项等

    import click
    @click.command()
    @click.option('--count',default=1,help='Number of greetings.')
    @click.option('--name',prompt='Your name',help='The person to greet')
    def hellp(count,name):
        '''simple program thar greets NAME for total of COUNT times...'''
        for x in range(count):
            click.echo('Hello %s!!!' % name)
    if __name__ == '__main__':
        hellp()
    click实例

    实例中,函数hellp接受两个参数count和name,他们的取值从命令行中获取,使用了click中的command、option、echo,使用了prompt选项,因此在没有指定name参数时,click会提示我们在交互模式下输入

    通过nargs配置参数的个数,通过type设置参数类型

    import click
    @click.command()
    @click.option('--pos',nargs=2,type=float)
    def findme(pos):
        click.echo('%s / %s' % pos)
    View Code

    type=click.Choice(['md5','sha1'])只能选择两者之一

    如果需要从命令行输入密码,使用argparse只能像输入普通参数一样,在click中只需将prompt设为True,设置hide_input为True,就可以隐藏输入,设置confirmation_prompt为True,就可以两次验证

    2.prompt_toolkit模块

      prompt_toolkit是一个处理交互式场景的开源库,用来取代开源的redline和curses。特点包括:

    1)语法高亮

    2)支持多行编辑

    3)支持代码补全

    4)支持自动提示

    5)支持鼠标移动光标

    6)支持Emacs与Vi风格快捷键

    7)支持查询历史

    8)对Unicode支持友好

    简单示例

    #!/usr/bin/python
    from __future__ import unicode_literals
    from prompt_toolkit import prompt
    while True:
        user_input = prompt('>')
        print(user_input)
    View Code

    这样交互式输入的时候就能使用ctrl+a或者ctrl+e等快捷键了,注意:在python2中需要加入from __future__ import unicode_literals

    实现历史输入

    from prompt_toolkit import prompt
    from __future__ import unicode_literals
    from prompt_toolkit.history import FileHistory
    
    while True:
        user_input = prompt('>',
                            history=FileHistory('history.txt'),
                            )
        print(user_input)
    View Code

    还可以实现输入提示

    from __future__ import unicode_literals
    from prompt_toolkit import prompt
    from prompt_toolkit.history import FileHistory
    from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
    
    while True:
        user_input = prompt('>',
                            history=FileHistory('history.txt'),
                            auto_suggest=AutoSuggestFromHistory(),
                            )
        print(user_input)
    View Code

    最后实现自动输入自动补全

    from prompt_toolkit import prompt
    from __future__ import unicode_literals
    from prompt_toolkit.history import FileHistory
    from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
    from prompt_toolkit.contrib.completers import WordCompleter
    
    SQLCompleter = WordCompleter(['select','update','drop','insert','delete'],ignore_case=True)
    while True:
        user_input = prompt('>',
                            history=FileHistory('history.txt'),
                            auto_suggest=AutoSuggestFromHistory(),
                            completer=SQLCompleter,
                            )
        print(user_input)
    View Code

    3.系统管理相关脚本

    按条件查找目录下的文件

    #!/usr/bin/python
    import os
    import fnmatch
    
    def is_file_match(filenames,patterns):
        if fnmatch.fnmatch(filenames,patterns):
            return True
        return False
    def find_specific_files(dir,patterns=['*'],exclude_dirs=[]):
        for root,dirnames,filenames in os.walk(dir):
            for filename in filenames:
                for pattern in patterns:
                    if is_file_match(filename,pattern):
                        yield os.path.join(root,filename)
            for d in exclude_dirs:
                if d in dirnames:
                    dirnames.remove(d)
    
    if __name__ == '__main__':
        patterns=['*.jpg','*.png','*.jbg']
        exclude_dirs  =  ['2']
        for item in find_specific_files(r"D:python_test",patterns,exclude_dirs):
            print(item)
    View Code

    例如查找目录下除dir2以外其他目录下的所有图片

    patterns=['*.jpg','*.png','*.jbg']

    exclude_dirs  =  [’ dir2']

    for item in find_specific_files(".",patterns,exclude_dirs):

        print(item) 

    filecmp模块

    比较目录和文件

    最简单的比较两个文件使用:filecmp.cmp('a.txt','b.txt'), 如果相同返回为True,不同返回为False

    filecmp的cmpfiles函数,用来同时比较两个不同目录下的多个文件,并且返回一个三元组,分别包含相同的文件、不同的文件和无法比较的文件

    找出目录下所有的重复文件:

    import hashlib
    import sys
    import os
    import fnmatch
    
    CHUCK_SIZE = 8192
    
    def is_file_match(filenames,patterns):
        if fnmatch.fnmatch(filenames,patterns):
            return True
        return False
    
    def find_specific_files(dir,patterns=['*'],exclude_dirs=[]):
        for root,dirnames,filenames in os.walk(dir):
            for filename in filenames:
                for pattern in patterns:
                    if is_file_match(filename,pattern):
                        yield os.path.join(root,filename)
            for d in exclude_dirs:
                if d in dirnames:
                    dirnames.remove(d)
    def get_chunk(filename):
        with open(filename,encoding="utf-8") as f:
            while True:
                chunk = f.read(CHUCK_SIZE)
                if not chunk:
                    break
                else:
                    yield chunk
    def get_file_checksum(filename):
        h = hashlib.md5()
        for chunk in get_chunk(filename):
            h.update(chunk.encode("utf-8"))
            return h.hexdigest()
    def main():
        sys.argv.append("")
        directory = sys.argv[1]
        if not os.path.isdir(directory):
            raise SystemExit("{0} is not a directory".format(directory))
        record = {}
        for item in find_specific_files(directory):
            checksum = get_file_checksum(item)
            if checksum in record:
                print("找到相同文件: {0}   VS   {1}".format(record[checksum],item))
                print(checksum)
            else:
                record[checksum] = item
    if __name__ == '__main__':
        main()
    View Code

    4.tarfile模块

    这是一个压缩模块,首先介绍基本用法   

    读取tar包:

    import tarfile
    with tarfile.open('tarfile_add.tar') as f:
        for member_ino in f.getmembers():
            print(member_info.name)

    tarfile中常用函数:getnames获取tar包中的文件列表     extract提取单个文件    extractall提取所有文件

    创建tar包:

    import tarfile
    with tarfile.open('tarfile_add.tar',mode='w') as out:
        out.add('README.txt)

    读取一个用gzip算法压缩的tar包:with tarfile.open('tarfile_add.tar',mode='r:gz') as out:

    创建一个用bzip2算法的压缩tar包: with .tarfile.open('tarfile_add.tar',mode='w:bz2') as out:

    实例,备份指定文件到压缩包中:

    #-*- conding:utf-8 -*-
    import os
    import fnmatch
    import tarfile
    import datetime
    
    def find_specific_files(dir,patterns=['*'],exclude_dirs=[]):
        for root,dirnames,filenames in os.walk(dir):
            for filename in filenames:
                if is_file_match(filename,patterns):
                    yield os.path.join(root,filename)
        for d in exclude_dirs:
            if d in dirnames:
                dirnames.remove(d)
    def main():
        patterns = ['*.txt','*.py']
        now = datetime.datetime.now().strftime('%Y-%m-%d_%H_%M_%S')
        filename = "all_file_{0}".format(now)
        with tarfile.open(filename,mode='w:gz') as f:
            for item in find_specific_files(".",patterns):
                f.add(item)
    if __name__ == '__main__':
        main()
    View Code

    暴力破解zip压缩包密码:

    with open('password.txt') as f:
        for line in f:
            try:
                f.extractall(pwd=line.strip())
                print("password is {0}".format(line.strip())
            except:
                pass
    View Code

    使用python监控linux系统

    开源工具:

    dstat:集成了linux下vmstat、iostat、netstat和ifstat等命令

    使用:dstat -cdngy(-a)统计cpu、磁盘io、网络流量、换页活动、系统统计(终端和上下文切换)

    --fs:统计文件打开数和inodes数

    -t:显示当前系统时间

    -l:统计负载情况

    -p:统计进程信息

    --tcp:显示常用的TCP统计

    --top-mem、--top-io、--top-cpu查看当前占用内存、io、cpu高的进程信息

    --output:输出到csv文件中

    glances

    glances是一款优秀的可视化很好的监控工具

    配合Bottle这个web框架,可以通过浏览器访问  pip install Bottle     glances -w 

    使用python监控磁盘io信息:

    from collections import namedtuple
    
    Disk = namedtuple('Disk','major_number minor_number device_name'
                              ' read_count read_merged_count read_sections'
                              ' time_spent_reading write_count write_merged_count'
                              ' write_sections time_spent_write io_requests'
                              ' time_spent_doing_io weighted_time_spent_doing_io'
                      )
    
    def get_disk_info(device):
        """
        from /proc/diskstats read IO message
        :param device:
        :return:
        """
        with open("/proc/diskstats") as f:
            for line in f:
                if line.split()[2] == device:
                    return Disk(*(line.split()))
        raise RuntimeError("device ({0}) not found !".format(device))
    
    def main():
        disk_info = get_disk_info('sda1')
        print(disk_info)
        print("磁盘写次数:{0}".format(disk_info.write_count))
        print("磁盘写字节数:{0}".format(disk_info.write_sections))
        print("磁盘写延时:{0}".format(disk_info.time_spent_write))
        
    if __name__ == '__main__':
        main()
    View Code

    psutil

    使用psutil实现系统监控,psutil常见去用法Google一下,一下是实例:

    from __future__ import unicode_literals
    import os
    import jinja2
    import socket
    from datetime import datetime
    import yagmail
    import psutil
    
    EMAILL_USER = 'xxx'
    EMALL_PASSWORD = 'xxx'
    RECIPIENTS = ['me@xxx.com']
    
    def render(tpl_path,**kwargs):
        path,filename = os.path.split(tpl_path)
        return jinja2.Environment(
            loader=jinja2.FileSystemLoader(path or './')
        ).get_template(filename).render(**kwargs)
    
    def bytes2human(n):
        symbols = ('B','K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
        prefix = {'B': 1,'K': 1024, 'M': 1048576, 'G': 1073741824, 'T': 1099511627776, 'P': 1125899906842624, 'E': 1152921504606846976,
                  'Z': 1180591620717411303424, 'Y': 1208925819614629174706176}
        while True:
            for p in reversed(symbols):
                if data > prefix[p]:
                    value = float(data) / prefix[p]
                    return '%.1f%s' % (value, p)
                elif data >= 1 and data <1024:
                    return "%sB" % data
                else:
                    continue
        
    
    def get_cpu_info():
        cpu_count = psutil.cpu_count()
        cpu_percent = psutil.cpu_percent(interval=1)
        return dict(cpu_count=cpu_count,cpu_percent=cpu_percent)
    
    def get_memory_info():
        virtual_mem = psutil.virtual_memory()
        mem_total = bytes2human(virtual_mem.total)
        mem_percent = virtual_mem.percent
        mem_free = bytes2human(virtual_mem.free + virtual_mem.buffers + virtual_mem.cached)
        mem_used = bytes2human(virtual_mem.total * virtual_mem.percent)
    
        return dict(mem_total=mem_total,mem_percent=mem_percent,mem_free=mem_free,mem_used=mem_used)
    
    def get_disk_info():
        disk_usage = psutil.disk_usage('/')
        disk_total = bytes2human(disk_usage.total)
        disk_percent = disk_usage.percent
        disk_free = bytes2human(disk_usage.free)
        disk_used = bytes2human(disk_usage.used)
    
        return dict(disk_total=disk_total,disk_percent=disk_percent,disk_free=disk_free,disk_used=disk_used)
    
    def get_boot_info():
        boot_time = datetime.fromtimestamp(psutil.boot_time()).strftime("%Y-%m-%d %H:%M:%S")
        return dict(boot_time=boot_time)
    
    def collect_monitor_data():
        data = {}
        data.update(get_boot_info())
        data.update(get_cpu_info())
        data.update(get_memory_info())
        data.update(get_disk_info())
        return data
    
    def main():
        hostname = socket.gethostname()
        data = collect_monitor_data()
        data.update(dict(hostname=hostname))
        content = render('monitor.html',**data)
        # with yagmail.SMTP(user=EMAILL_USER,password=EMALL_PASSWORD,host='smtp.163.com',port=25) as yag:
        #     for recipient in RECIPIENTS:
        #         yag.send(recipient,"监控信息".encode('utf-8').content.encode('utf-8'))
        print(data)
    if __name__ == '__main__':
        main()
    View Code

    monitor.html模板内容:

    <html>
            <header><title>监控信息</title>
            <body>
                    <table border="1">
                            <tr><td>服务器名称</td><td>{{hostname}}</td></tr>
                            <tr><td>开机时间</td><td>{{boot_time}}</td></tr>
    
                            <tr><td>cpu个数</td><td>{{cpu_count}}</td></tr>
                            <tr><td>cpu利用率</td><td>{{cpu_percent}}</td></tr>
    
                            <tr><td>内存总量</td><td>{{mem_total}}</td></tr>
                            <tr><td>内存利用率</td><td>{{mem_percent}}</td></tr>
                            <tr><td>内存已用空间</td><td>{{mem_used}}</td></tr>
                            <tr><td>内存可用空间</td><td>{{mem_free}}</td></tr>
    
                            <tr><td>磁盘空间总量</td><td>{{disk_total}}</td></tr>
                            <tr><td>磁盘空间利用率</td><td>{{disk_percent}}</td></tr>
                            <tr><td>磁盘已用空间</td><td>{{disk_used}}</td></tr>
                            <tr><td>磁盘可用空间</td><td>{{disk_free}}</td></tr>
    
                    </table>
            </body>
    </html>
    View Code

    python文档与报告

    openpyxl模块:

    openpyxl把Excel抽象为了三个不同的类:Workbook、Worksheet、Cell。Workbook是对Excel工作簿的抽象,Worksheet是对表格的抽象,Cell是对单元格的抽象

    加载一个Excel表格:wb = openpyxl.load_workbook('1.xlsx')  然后就可以调用属性和方法:active 获取活跃的Worksheet    read_only 是否以read only打开Excel表格 encoding 文档的字符集编码  properties:文档的元数据如标题,创建者,创建日期    worksheets 以列表的形式返回所有的Worksheet

    Workbook对象的方法大都与Worksheet相关 常用方法:get_sheet_names 获取所有表格的名称  get_sheet_by_name 通过表格名称获取Worksheet对象  get_active_sheet 获取活跃的表格  remove_sheet 删除一个表格  

    create_sheet 创建一个空的表格  copy_worksheet 在Worksheet内拷贝表格

    常用的Worksheet属性如下:title 表格的标题  dimensions 表格的大小,这里的大小是指含有数据的表格大小  max_row 表格的最大行   min_row 表格的最小行  max_column 表格的最大列  min_column 表格的最小列

    rows 按行获取单元格(Cell对象)  columns 按列获取单元格(Cell对象)  freeze_panes 冻结窗格  values 按行获取表格的内容(数据)   iter_rows 按行获取所有单元格(Cell对象)  iter_columns 按列获取所有单元格  append 在表格末尾添加数据   merged_cells 合并多个单元格   unmerge_cells 移除合并的单元格

    获取表格数据:

    import openpyxl
    
    wb = openpyxl.load_workbook('1.xlsx')
    ws = wb.get_sheet_by_name('Sheet1')
    
    for row in ws.row:
        print(*[cell.value for cell in row])
    
    # 或者也可以写成
    for i in ws.values:
        print(*i)
    
    # 再或者
    for  row in ws.iter_rows:
        print(*[cell.value for cell in row])
    View Code

    python处理图片

    PIL模块

    使用方法: from PIL import Image  打开一个图片:im = Image.open('1.jpg')  print(im.format,im.size,im.mode)   旋转图片:rot = im.rotate(45)  rot.save('2.jpg')

    把一个目录下的图片都创建为缩略图:

    from PIL import Image
    import glob
    import os
    
    size = 128,128
    
    for infile in glob.glob("*.jpg"):
        file,ext = os.path.splitext(infile)
        im = Image.open(infile)
        im.thumbnail(size)
        im.save(file + ".thumbnail","JPEG")
    View Code

    获取照片的exif信息:

    import sys
    import os
    
    from PIL.ExifTags import TAGS
    from PIL.ExifTags import GPSTAGS
    
    def get_iamge_meta_info(filename):
        exif_data = {}
        with Image.open(filename) as img:
            data = img._getexif()
            for tag,value in data.iteritems():
                decode = TAGS.get(tag)
                exif_data[decode] = value
            
            if exif_data['GPSInfo']:
                gps_data = {}
                for tag,value in exif_data['GPSInfo'].iteritems():
                    decode = GPSTAGS.get(tag)
                    gps_data['GPSInfo'] = gps_data
        return exif_data
    
    def main():
        sys.argv.append("")
        filename = sys.argv[1]
        if not os.path.isfile(filename):
            raise SystemExit("{0} is not exists".format(filename))
        exif_data = get_iamge_meta_info(filename)
        for key,value in exif_data.items():
            print(key,value,sep=':')
            
    if __name__ == "__main__":
        main()
    View Code

    发送邮件

    步骤:1、连接到SMTP服务器;2、发送SMTP的'Hello'消息;3、登录到SMTP服务器;4、发送电子邮件;5、关闭SMTP服务器的连接

    1:smtp = smtplib.SMTP('smtp.163.com',25) 创建SMTP对象

    2:smtp.ehlo()  调用ehlo方法与SMTP服务"打招呼"

    3:smtp.starttls()  调用starttls方法将当前会话转换成一个加密的会话

    4:smtp.login('xxx@163.com','password')  将smtp会话加密了就可以传输敏感信息了,如果没有调用starttls方法就登录,则会抛出SMTPAuthenticationError异常

    5:smtp.sendmail(发件人邮件,收件人邮件,邮件内容)   登录上SMTP服务器之后就可以调用sendmail方法发送邮件了

    6:smtp.quit()  邮件发送完成之后,调用quit方法断开与SMTP服务器的连接

    为了构造一个完整的邮件,需要借助email模块,email模块用来构造邮件和解析邮件内容,构造一个邮件就是创建一个Message对象,如果构造一个MIMEText对象就表示构造一个纯文本的邮件,如果构造一个MIMEImage对象,就表示构造了一个作为附件的图片。如果把多个对象组合起来,就需要用到MIMEMultipart对象email模块下有多个类包括Message、MIMEBase、MIMEText、MIMEAudio、MIMEImage和MIMEMultipart。

     发送纯文本信息的邮件:

    import smtplib
    from email.mime.text import MIMEText
    SMTP_SERVER = "smtp.163.com"
    SMTP_PORT = 25
    
    def send_mail(user,pwd,to,subject,text):
        msg = MIMEText(text)
        msg['From'] = user
        msg['To'] = to
        msg['subject'] = subject
        
        smtp_server = smtplib.SMTP(SMTP_SERVER,SMTP_PORT)
        print('Connecting To Mail Server.')
        try:
            smtp_server.ehlo()
            print('Starting Encrypted Section.')
            
            smtp_server.starttls()
            smtp_server.ehlo()
            print('Logging Info Mail Server')
            smtp_server.login(user,pwd)
            print('Sending Mail')
            smtp_server.sendmail(user,to,msg.as_string())
        except Exception as err:
            print('Sending Mail faild:{0}'.format(err))
            
        finally:
            smtp_server.quit()
            
    def main():
        send_mail(发件人邮箱,密码,收件人邮箱,邮件主题,邮件内容)
    
    if __name__ == '__main__':
        main()
    View Code

    发送带各种附件的邮件:

    import urllib
    import smtplib
    from email.mime.multipart import MIMEMultipart
    from email.mime.text import MIMEText
    from email.mime.application import MIMEApplication
    
    username = 'xxx@163.com'
    password = 'xxx'
    sender = username
    receivers = ','.join(['xxx@qq.com'])
    
    # 如名字所示: Multipart就是多个部分
    msg = MIMEMultipart()
    msg['Subject'] = 'Python mail Test'
    msg['From'] = sender
    msg['To'] = receivers
    
    # 下面是文字部分,也就是纯文本
    puretext = MIMEText('我是纯文本部分,')
    msg.attach(puretext)
    
    # 下面是附件部分 ,这里分为了好几个类型
    
    # 首先是xlsx类型的附件
    xlsxpart = MIMEApplication(open(r'C:UsersqingheshDesktop1.xlsx', 'rb').read())
    xlsxpart.add_header('Content-Disposition', 'attachment', filename='绩1.xlsx')
    msg.attach(xlsxpart)
    
    # jpg类型的附件
    jpgpart = MIMEApplication(open(r'D:1.jpg', 'rb').read())
    jpgpart.add_header('Content-Disposition', 'attachment', filename='1.jpg')
    msg.attach(jpgpart)
    
    # mp3类型的附件
    mp3part = MIMEApplication(open(r'C:UsersPublicMusicSample MusicKalimba.mp3', 'rb').read())
    mp3part.add_header('Content-Disposition', 'attachment', filename='Kalimba.mp3')
    msg.attach(mp3part)
    
    ##  下面开始真正的发送邮件了
    try:
        client = smtplib.SMTP()
        client.connect('smtp.163.com')
        client.login(username, password)
        client.sendmail(sender, receivers, msg.as_string())
        client.quit()
        print('带有各种附件的邮件发送成功!')
    except smtplib.SMTPRecipientsRefused:
        print('Recipient refused')
    except smtplib.SMTPAuthenticationError:
        print('Auth error')
    except smtplib.SMTPSenderRefused:
        print('Sender refused')
    except smtplib.SMTPException as e:
        print(e)
    View Code

    yagmail模块:

    import yagmail
    
    with yagmail.SMTP(user=user_mail,password=password,host=smtp_host,port=port) as yag:
        yag.send(recipients,subject,content)
    View Code

    注意这里的recipients和content都可以等于一个列表,表示多个收件人和多个附件

    cmcli客户端项目:

     1 import os
     2 import sys
     3 try:
     4     import ConfigParser
     5 except ImportError:
     6     import configparser as ConfigParser
     7 import argparse
     8 import yagmail
     9 
    10 from storage import Storage
    11 from logger import get_logger
    12 
    13 logger = get_logger()
    14 
    15 
    16 def get_argparse():
    17     parser = argparse.ArgumentParser(description='A email client in terminal')
    18     parser.add_argument('-s', action='store', dest='subject', required=True, help='specify a subject (must be in quotes if it has spaces)')
    19     parser.add_argument('-a', action='store', nargs='*', dest='attaches', required=False, help='attach file(s) to the message')
    20     parser.add_argument('-f', action='store', dest='conf', required=False, help='specify an alternate .emcli.cnf file')
    21     parser.add_argument('-r', action='store', nargs='*', dest='recipients', required=True, help='recipient who you are sending the email to')
    22     parser.add_argument('-v', action='version', version='%(prog)s 0.2')
    23     return parser.parse_args()
    24 
    25 
    26 def get_config_file(config_file):
    27     if config_file is None:
    28         config_file = os.path.expanduser('~/.emcli.cnf')
    29     return config_file
    30 
    31 
    32 def get_meta_from_config(config_file):
    33     config = ConfigParser.SafeConfigParser()
    34 
    35     with open(config_file) as fp:
    36         config.readfp(fp)
    37 
    38     meta = Storage()
    39     for key in ['smtp_server', 'smtp_port', 'username', 'password']:
    40         try:
    41             val = config.get('DEFAULT', key)
    42         except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) as err:
    43             logger.error(err)
    44             raise SystemExit(err)
    45         else:
    46             meta[key] = val
    47 
    48     return meta
    49 
    50 
    51 def get_email_content():
    52     return sys.stdin.read()
    53 
    54 
    55 def send_email(meta):
    56     content = get_email_content()
    57     body = [content]
    58     if meta.attaches:
    59         body.extend(meta.attaches)
    60 
    61     with yagmail.SMTP(user=meta.username, password=meta.password,
    62                       host=meta.smtp_server, port=int(meta.smtp_port)) as yag:
    63         logger.info('ready to send email "{0}" to {1}'.format(meta.subject, meta.recipients))
    64         ret = yag.send(meta.recipients, meta.subject, body)
    65 
    66 
    67 def main():
    68     parser = get_argparse()
    69 
    70     config_file = get_config_file(parser.conf)
    71 
    72     if not os.path.exists(config_file):
    73         logger.error('{0} is not exists'.format(config_file))
    74         raise SystemExit()
    75     else:
    76         meta = get_meta_from_config(config_file)
    77 
    78     meta.subject = parser.subject
    79     meta.recipients = parser.recipients
    80     meta.attaches = parser.attaches
    81 
    82     for attach in meta.attaches:
    83         if not os.path.exists(attach):
    84             logger.error('{0} is not exists'.format(attach))
    85             raise SystemExit()
    86 
    87     send_email(meta)
    88 
    89 
    90 if __name__ == '__main__':
    91     main()
    emcli.py
     1 import logging
     2 
     3 
     4 def get_logger(log_level=logging.INFO):
     5     logger = logging.getLogger(__name__)
     6     logger.setLevel(log_level)
     7 
     8     formatter = logging.Formatter("%(asctime)s [emcli] [%(levelname)s] : %(message)s", "%Y-%m-%d %H:%M:%S")
     9 
    10     handler = logging.StreamHandler()
    11     handler.setFormatter(formatter)
    12 
    13     logger.handlers = [handler]
    14 
    15     return logger
    logger.py
     1 class Storage(dict):
     2     """
     3     A Storage object is like a dictionary except `obj.foo` can be used
     4     in addition to `obj['foo']`.
     5     """
     6     def __getattr__(self, key):
     7         try:
     8             return self[key]
     9         except KeyError as k:
    10             raise AttributeError(k)
    11 
    12     def __setattr__(self, key, value):
    13         self[key] = value
    14 
    15     def __delattr__(self, key):
    16         try:
    17             del self[key]
    18         except KeyError as k:
    19             raise AttributeError(k)
    20 
    21     def __repr__(self):
    22         return '<Storage ' + dict.__repr__(self) + '>'
    storage.py
    1 from emcli import main
    __init__.py
    emcli.cnf
    [DEFAULT]
    smtp_server = smtp.163.com
    smtp_port = 25
    username = xxx@163.com
    password = 123456

    检查主机是否存活,用ping:

    第一种方法:

    import subprocess
    import threading
    
    def is_reacheable(ip):
        if subprocess.call(["ping","-c","2",ip]):
            print("{0} is alived".format(ip))
        else:
            print("{0} is unreacheable".format(ip))
            
    def main():
        with open('ip.txt') as f:
            lines = f.readlines()
            threads = []
            for line in lines:
                thr = threading.Thread(target=is_reacheable,args=(line,))
                thr.start()
                threads.append(thr)
                
            for thr in threads:
                thr.join()
                
    if __name__ == '__main__':
        main()
    View Code

    这种方法我们是将所有ip先都读入内存当中,当如果需要扫描机器非常非常多的时候,我们可以用生产者消费者模型来写,如下:

    import subprocess
    import threading
    import queue
    
    def call_ping(ip):
        if subprocess.call(["ping","-c","1",ip])
            print("{0} is alive".format(ip))
        else:
            print("{0} is unreacheable".format(ip))
            
    def is_reacheable(q):
        try:
            while True:
                ip = q.get()
                call_ping(ip)
        except:
            pass
            
    def main():
        q = queue.Queue()
        with open('ip.txt') as f:
            for line in f:
                q.put(line)
        threads = []
        for i in range(10):
            thr = threading.Thread(target=is_reacheable,args=(q,))
            thr.start()
            threads.append(thr)
        for ths in threads:
            ths.join()
            
    if __name__ == '__main__':
        main()
    View Code

    nmap

    打印IP列表,不进行任何操作:nmap -sL 192.168.0.0/30

    指定多个主机:nmap -sL 192.168.1.1 192.168.1.18  也可以排除某个IP:nmap -sL 192.168.0.* --exclude 192.168.0.100  也可以将IP保存在文件中,同过-iL选项读取文件中的IP:nmap -iL ip.list

    使用nmap检查网络上所有在线主机:nmap -sP 10.166.224.*  或者:nmap -sn 10.166.224.*

    IPy

    处理IP地址的模块,它能够自动识别IP地址的版本、IP地址类型。使用IPy模块,可以方便的进行IP地址计算,用法去百度。几个重要的用法

    IP('192.168.1.1').int():将ip地址转换为整数,mysql也有相应的函数,select inet_aton('192.168.1.1')可以将ip转化为整数,select inet_ntoa('2887432721')解析为IP地址

    dnspython

    域名解析模块

    dns.resolver.query('域名名称',rdtype=1,rdclass=1,tcp=False,source=None,raise_on_no_answer=True,source_port=9)

    rdtype:指定RR资源  A:地址记录,返回域名指向IP地址   NS:域名服务器记录,返回保存下一级域名信息的服务器地址。该记录只能设置为域名,不能设置为IP地址    MX:邮件记录,返回接受邮件的服务器地址

      CNAME:规范名称目录,别名记录,实现域名间的映射   PTR:逆向查询记录,反向解析,与A记录相反,将IP地址转换为主机名

    rdclass:网络类型

    tcp:指定查询是否使用TCP协议

    source:查询源的地址

    source_port:查询源的端口

    raise_on_no_answer:指定查询无应答时是否触发异常,默认为True

    polysh

    polysh运维工具,直接用pip安装即可,是一个交互式命令,可以批量对服务器进行处理。也就是说可以同时登陆多台服务器,并在各服务器上同时执行相同的操作。

    polysh --ssh='exec ssh -p 22 -i ~/.ssh/id_rsa' --user=root --hosts-file=hosts.txt

    fabric

    fabric是对paramiko更好的封装,它既是一个python库,也是一个命令行工具:fab  fabric典型的使用方式是,创建一个python文件(默认使用fab命令不指定文件的是寻找fabfile.py),改文件包含一到多个函数,然后使用fab命令直接调用这些函数即可

    from fabric.api import run,sudo
    from fabric.api import env
    
    env.hosts = ["ip1","ip2"]
    env.port = 22
    env.user = 'root'
    env.password = 'password'
    
    def hostname():
        run('hostname')
    def ipaddr():
        run('ifconfig')
    fabfile

    然后使用:fab hostname(函数名称)或者ipaddr(函数名称)调用即可  可以使用fab --list查看有哪些方法

    run:执行远程命令的封装       sudo:以sudo权限执行     env:保存配置信息的字典

    run和sudo函数有一个比较重要的参数,即pty,用于设置伪终端,如果我们执行命令以后需要有一个常驻的服务进程,则需要设置pty=False,避免因为fabric退出导致进程退出  创建一个文件:run("mkdir /tmp/1.txt")

    result=run("whoami")如果失败:result.failed               sudo("mkdir /var/www/1.log")     sudo("service httpd restart",pty=False)      

    local:local用以执行本地命令,local是对python的subprocess进行封装,便于在本地执行shell命令,如需执行更复杂的功能,可以直接使用subprocess    local("pwd ")

    get从远程服务器获取文件,通过remote_path参数声明从何处下载,通过local_path参数声明下载到何处   get(remote_path =” / tmp/log _ extracts.t ar.gz ”, local _pa th =” / l ogs / new_ log.tar.gz ”)

    put将本地的文件上传到远程服务器,参数与get相似,此外还可以通过mode设置远程文件的权限  put (” / local/path/to/app.tar.gz ”,” /t mp /trunk / app.tar.gz”,mode=0755)

    reboot重启远程服务器,可以通过wait参数设置等待几秒开始重启   reboot(wait=30)

    prompt用以在fabric执行任务的过程中与管理员进行交互,作用类似于raw_input   prompt ( ’ Please  specify  process  nicelevel  :’, key =’ nice ’, validate =int)

    Fabric设置:

    fabric将所有配置保存在全局的env字典中,我们可以直接修改env字典来改变fabric的配置,但是,有的时候我们并不希望修改全局的参数配置,只希望临时修改部分配置。例如修改当前工作目录,修改日志的输出级别等   

    1)在shell中语法:cd /tmp && pwd    在fabric中可以:with cd('/var/log'):  run('ls')

    2)lcd与cd类似,只不过是切换本地目录

    3)path配置远程服务器PATH环境变量:只会对当前会话有效,path修改支持多种模式:append:默认行为,将给定的路径添加到path后面,prepend:将给定的路径添加到path前面  replace:替换当前的path环境变量

    4)prefix就是前缀的意思,实际效果是对于prefix,每个命令都执行一遍:

    with cd('/path/to/app/'):
        with prefix('workon myvenv'):
            run('./manage.py syncdb')
            run('./manage.py loaddata')
    View Code

    替换成shell相当于:

    cd /path/to/app && workon myvenv && ./manage.py syncdb
    cd /path/to/app && workon myvenv && ./manage.py loaddata
    View Code

    5)shell_env设置Shell脚本的环境变量

    with shell_env(ZMQ_DIR='/home/user/local'):
        run('pip install pyzmq')

    相当于shell代码:

    export ZMQ_DIR='/home/user/local' && pip install pyzmq

    6)settings通用的设置,用于临时覆盖env变量

    with settings(user='foo'):
        do something

    7)remote_tunnel通过ssh的端口转发建立转发通道

    with remote_tunnel(3306):
        run('mysql -u root -p password')

    输出相关

    hide隐藏指定类型的输出

    def my_task():
        with hide('running','stout','stderr'):
            run('ls /var/www')

    hide可选类型有7种:

    status  状态信息,如服务器断开了连接,用户使用Ctrl+C等,如果fabric正常运行不会有状态信息

    aborts  终止信息,一般将fabric当做库使用的时候需要关闭

    warnings  警告信息

    running  运行过程中的输出

    stdout  执行shell命令的标准输出

    stderr  执行shell命令的错误输出

    user  用户输出,类似于python代码中的print函数

    show与hide相反,显示指定类型的输出   output:stdout、stderr   everything:stdout、stderr、warnings、running、user     commands:stdout、running

    quiet隐藏全部输出,仅在执行错误时发出警告

    warn_only:默认情况下当命令执行失败时,fabric会停止执行后面的命令,当设置为True的时候,允许执行失败的时候继续执行

    fabric提供的装饰器

    fabric提供的装饰器既不是执行具体操作的,也不是修改参数的,二是控制如何执行这些操作的,在哪些服务器上执行这些操作的

    task:就是fabric需要在远程服务器执行的任务,task是一个抽象的事。如下使用方法:

    from fabric.api import env
    from fabric.api import task,run
    import json
    
    @task
    def hostname():
        run('hostname')
    
    def ipaddr():
        run('ifconfig')

    fibric中所有可调用对象都是一个task,但是如果用task装饰器定义的task,其他没有被装饰器装饰的函数将不会被认为是一个task,上面的代码中,只有hostname是可调用的task,可以使用fab --list

    通过装饰器指定对哪些hosts执行当前task:@hosts('host1','host2')   def hostname():run('hostname')

    通过env.reject_unknow_hosts控制未知host的行为,该选项默认为True,类似于SSH中的StricHostKeyChecking为no

    fabric中的role

    role是对服务器进行分类的手段,使用role可以定义服务器的角色,以便对不同的服务器执行不容的操作,role是逻辑上将服务器进行了分类,分类之后我们需要将指定某一类服务器时指定role名称即可,一个role可以包含一台或多台服务器。role的定义保存在env.roledef中:

    from fabric.api import env
    env.roledefs['webserver'] = ['www1','www2','www3']
    
    env.roledefs = {
        'web':['www1','www2','www3'],
        'db':['db01','db02','db03']
    }

    定义好role后,就可以通过role装饰器指定在哪些role上运行当前这个task:

    from fabric.api import env
    
    env.roledefs = {
        'web':['www1','www2','www3'],
        'db':['db01','db02','db03']
    }
    
    @roles('db')
    def mirget():
        pass
    
    @roles('web')
    def webt():
        pass

    fabric执行模型

    步骤:

    1.创建一个任务列表,这些任务就是通过fab命令参数指定的任务,fabric会保持这些任务的顺序

    2.对于每个任务,构造需要执行任务的服务器列表,服务器列表可以通过传参数指定,可以通过env,hosts指定,也可以通过hosts或者roles装饰器指定

    3.历遍任务列表,对于每一台服务器分别执行任务

    默认是串行执行:

    for task in tasks:
        for host in hosts:
            execute(task in host)

    并行执行方式

    1.通过命令行参数-P(--parallel)通知Fabric并行执行task

    2.通过env.parallel设置是否需要并行执行

    3.通过parallel装饰器通知fabric并行执行task

    runs_once装饰器:只执行一次,防止task被多次调用:

    from fabric.api import execute,env,runs_once,task
    
    @runs_once
    def hello():
        print("hello")
        
    @task
    def test():
        execute(hello)
        execute(hello)

    serial:强制当前task串行执行。使用该装饰器后,即使用户通过参数--parallel指定需要并行执行,当执行呗serial装饰器修改过的task时,依然会串行执行。这样就达到了部分task可以串行执行

    execute函数,用来对task进行封装。其好处是将一个大的任务拆分成多个小任务。每个小任务相互独立,互不干扰:

    from fabric.api import execute,env,runs_once,task
    
    env.roledefs = {
        'db':['db01','db02'],
        'wb':['wb01','wb02']
    }
    @roles('db')
    def migrate():
        pass
    @roles('web')
    def update():
        pass
    
    def deploy():
        execute(migrate)
        execute(update)
    View Code

     一源码的方式在远程服务器上安装redis

    1)在本地执行make test及redis单元测试,如果单元测试出现错误,提示用户是否需要继续部署。执行成功之后,删除redis的二进制文件,并将redis源码打成一个tar包,显然在执行单元测试的时候只需要执行一次,使用runs_once装饰器装饰函数

    2)接着就是将redis源码上传到远程服务器,并执行“make install”,安装完成后清理远程服务器的文件,先使用cd切换到相应目录,在执行删除操作

    3)清理本地文件

    Ansible

    ansible all -m file -a "dest=/tmp/data.txt mode=500 owner=root group=root" -become          这里的-become相当于sudo

    plooybook示例:

    ---
    - host: test
      become: yes
      become_method: sudo
      task:
      - name: copy file
        copy: src=/tmp/data.txt dest=/tmp/data.txt
        
      - name: change mode
        file: dest=/tmp/data.txt mode=500 owner=root group=root
        
      - name: ensure packages installed 
        apt: pkg={{ item }} state=present 
        with_items:
          - tmux
          - git
    View Code

    YMAL语法规则:

    文件的第一行为'---',表示这是一个YAML文件

    YAML中的字段大小写敏感

    YAML与python一样,使用缩进表示层级关系

    YAML的缩进不允许使用Tab键,只允许使用空格,且空格数目不重要,只要相同层级的元素左侧对齐即可

    ‘#’表示注释

    YAML支持三种格式的数据,分别是:

    对象:键值对的集合,又称为映射,类似于python中的字典

    数组:一组按次序排列的值,又称为序列,类似于python中的列表

    纯量:单个的、不可再分的值,如字符串、布尔值或数字

    从数据库动态取出inventor:

    import argparse
    import json
    from collections import defaultdict
    from contextlib import contextmanager
    import pymysql
    
    def to_json(in_dict):
        return json.dumps(in_dict,sort_keys=True,indent=2)
    
    @contextmanager
    def get_conn(**kwargs):
        conn = pymysql.connect(**kwargs)
        try:
            yield conn
        finally:
            conn.close()
     
    def parse_args():
        parser = argparse.ArgumentParser(description='Openstack Inventory Module')
        group = parser.add_mutually_exclusive_group(required=True)
        group.add_argument('--list',action='store_true',help='List active servers')
        group.add_argument('--host',help='List details about the specific host')
        
        return parser.parse_args()
    
    def list_all_hosts(conn):
        hosts = defaultdict(list)
        with conn as cur:
            cur.execute('select * from hosts')
            rows = cur.fetchall()
            # print(rows)
            for row in rows:
                no,host,group,user,port = row
                hosts[group].append(host)
                
            return hosts
        
    def get_host_detail(conn,host):
        details = {}
        with conn as cur:
            cur.execute("select * from hosts where host='{0}'".format((host)))
            rows = cur.fetchall()
            if rows:
                no,host,group,user,port = rows[0]
                details.update(ansible_user=user,ansible_port=port)
            return details
        
    def main():
        parser = parse_args()
        with get_conn(host='x.x.x.x',user='root',passwd='123456',db='db_ansible') as conn:
            if parser.list:
                hosts = list_all_hosts(conn)
                print(to_json(hosts))
            else:
                details = get_host_detail(conn,parser.host)
                print(to_json(details))
                
    if __name__ == '__main__':
        main()
    View Code

    表结构为:

    CREATE TABLE `hosts` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `host` varchar(15) DEFAULT NULL,
      `groupname` varchar(15) DEFAULT NULL,
      `username` varchar(15) DEFAULT NULL,
      `port` int(11) DEFAULT '22',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 |
    View Code

    #创建一个目录

    ansible test -m file -a 'path=/tmp/dd state=directory mode=0755'

    #修改文件的权限

    ansible test -m file -a 'path=/tmp/dd state=touch mode="u=rw,g=r,o=r"'

    #创建一个软连接

    ansible test -m file -a 'path=/tmp/dd dest=/tmp/ddl owner=lmx group=lmx state=link'

    #修改一个文件的所有者

    ansible test -m file -a 'path=/tmp/dd owner=root group=root mode=0644' -become

    #创建一个用户

    ansible test -m user -a 'name=johd comment="jobe done" uid=1321 group=root' -become

    #删除一个用户

    ansible test -m user -a 'name=john state=absent' -become

    #创建一个用户,并产生一对秘钥

    ansible test -m user -a 'name=joind comment="joned jjj" generate_ssh_key=yes ssh_key_bits=2048' -become

    #创建群组

    ansible test -m group -a 'name=ansible state=present git=1234' -become

    #删除群组

    ansible test -m group -a 'name=ansible state=absent' -become

    #下载文件到远程服务器

    ansible test -m get_url -a 'url=http://www.baidu.com dest=/tmp/baidu.txt'

    #下载文件到远程服务器并修改文件的权限

    ansible test -m get_url -a 'url=http://www.baidu.com dest=/tmp/baidu.txt mode=0777'

    #在本地创建一个名为data的目录,并在该目录穿件一些文件,然后再该目录上创建两个压缩文件,分别为data.tar.gz和data.tar.bz2

    #创建一个目录

    ansible test -m file -a 'path=/data state=directory'

    #解压本地文件

    ansible test -m unarchive -a 'src=data.tar.gz dest=/data list_files=yes'

    #将本地文件拷贝到远程

    ansible test -m unarchive -a 'src=data.tar.bz2 dest=/data'

    #解压远程文件

    ansible test -m unarchive -a 'src=/data/data.tar.bz2 dest=/tmp remote_src=yes'

    git

    #将requests克隆到/tmp/request目录下

    ansible test -m git -a 'repo=https://github.com/kennethreitz/requests.git dest=/tmp/request version=HEAD'

    #从源码安装requests

    ansible test -a 'python setup.py install chdir=/tmp/request' -become

    #验证requests是否安装成功

    ansible test -a 'python -c "import requests"'

    #获取文件的相关信息

    ansible test -m stat -a 'path=/etc/passwd'

    cron

    #增加一个cron任务ansible test -m cron -a ’ backup=yes name =” test cron ” minute=*/2 hour=* job =” ls /tmp >/dev/null " 

    service

    #安装httpd

    ansible test -m yum -a 'name=httpd state=prestent' -become

    #停止httpd

    ansible test -m service -a 'name=httpd state=stopped'

    #重启httpd

    ansible test -m service -a 'name=httpd state=restarted'

    sysctl

    #设置overcommit_memory参数值为1

    ansible test -m sysctl -a 'name=vm.overcommit_memory value=1' -become

    mount

    #挂载/dev/vda盘到/mnt/data目录

    ansible test -m mount -a 'name=/mnt/data src=/dev/vda fstype=ext4 state=mounted'

    ---
    - hosts: dbserver
      become: yes
      become_method: sudo
      tasks:
      - name: install mongodb
        apt: name=mongodb-server state=present 
        
    - hosts: webservers
      tasks:
      - name: copy file
        copy: src=/tmp/data.txt dest=/tmp/data.txt
        
      - name: change mode
        file: dest=/tmp/data.txt mode=655 owner=root group=root
        
    View Code
    ---
    - hosts: dbserver
      become: yes
      become_method: sudo
      tasks:
      - name: install mongodb
        apt: name=mongodb-server state=present state=present
    
    - hosts: webserver
      tasks:
      - name: copy file
        copy: src=/tmp/data.txt dest=/data.txt 
      - name: change mode
        file: dest=/data.txt mode=755 ower=lmx group=lmx
    View Code

    palybook可以包含多个play,但是为了playbook的可读性和可维护性,我们一般会在一个playbook写一个play,如果我们在first_playbook.yml这个拆分成两个playbook,分别为web.yml和db.yml,当我们可以先执行db.yml,再执行web.yml,可以写一个all.yml   

    ---
    -  include: db.yml
    -  include: web.yml

    include选项是ansible提供的,用于在一个playbook中导入其他playbook,被导入的playbook会按顺序依次执行

    ansible-playbook

    -T --timeout:建立SSH连接的超时时间

    --key-file --private-key:建立SSH连接的私钥文件

    -i --inventory-file:指定inventory文件,默认是/etc/ansible/hosts

    -f --foks:并发执行进程数,默认是5

    --list-hosts:匹配到服务器列表

    --list-tasks:列出任务列表

    --step:每执行一个任务后停止,等待用户确认

    --syntax-check:检查playbook语法

    -C --check:检查当前这个playbook是否会修改远程服务器,相当于预测playbook的执行结果

    playbook详细语法

    在定义play时,只有hosts和tasks是必选项,其他都是根据需要加的

    在ansible中,默认使用当前用户连接远程服务器执行操作,我们也可以在ansible.cfg文件中配置连接远程服务器的默认用户,此外,如果是不同的用户使用不同类型的远程服务器,那么也可以在playbook的play定义中指定连接远程服务器的用户。例如我们可以指定执行play的用户:

    ---
    - hosts: webserver
      remote_user: root

    用户也可以细分每一个task

    ---
    - hosts: test
      remote_user: root
      tasks:
        - name: test connection
          ping:
          remote_user: root

    与remote_user类似,我们也可以为单个任务使用管理员权限,

    ---
    - hosts: test
      remote_user: root
      tasks:
        - service: name=nginx state=restarted
        become: yes
        become_method: sudo

    通知

      在ansible中,模块是幂等的。例如,我们要在远程服务器上创建一个用户,如果用户已存在,那么ansible不会将该用户删除后重新创建,而是直接返回成功,并通过change字段表示是否对远程服务器进行了修改

    当我们通过ansible修改apache的配置文件,并重启apache的时候,如果我们修改的内容没变,我们就不需要重启。在ansible中,通过notify与handler机制来实现  

    ---
    - hosts: webserver
      remote_user: test
      become: yes
      become_method: sudo
      tasks:
      - name: ensure apache is at letest version
        yum: name=httpd state=latest
        
      - name: write the apache config file
        template: src=/srv/httpd.j2dest=/ect/httpd.conf
        notify:
        - restart apache
        
      - name: ensure apache is running
        service: name=httpd state=started
        
      handlers:
        - name: restart apache
          service: name=httpd state=restarted

    需要注意的是,handle只会在所有task执行完之后执行,并且即便一个handler被触发多次,它只会执行一次。handler不是在被触发时立即执行,而是按照play中定义的顺序执行。一般情况下handler都位于play最后,即在所有任务执行完以后再执行

    变量

      在inventory中,我们可以定义变量,对于简单的playbook,最直接的方式是将变量定义在playbook的vars选项中

    ---
    - hosts: webserver
      vars:
        mysql_port: 3307
        

    在playbook中定义的变量,可以在模板渲染时使用,

    [mysqld]
    datadir=/var/lib/mysql
    socket=/var/lib/mysql/mysql.sock
    user=mysql
    port={{ mysql_port }}

    当变量比较多的时候,可以将其保存在一个独立的文件中,并通过vars_files选项引用该文件

    ---
    - hosts: webserver
      vars:
        favcolor: blue
      vars_files:
        - /vars/external_vars.yml
        
      tasks:
      - name: this is just a placeholder
        command: /bin/echo foo
        

    保存变量的文件是一个简单的YAML格式的字典

    ---
    somervar: somevalue
    password: magic

    在shell中,通过上一条命令的返回码判断命令是否执行成功,在ansible中,我们将任务的执行结果保存在一个变量中,并在之后引用这个变量。这样的变量在ansible中用register获取,也成为注册变量

    如下,执行/usr/bin/foo命令,通过register选项获取命令执行结果,将结果保存在foo_result中,之后的task中引用这个变量的执行结果

    ---
    - hosts: webserver
      tasks:
        - shell: /usr/bin/foo
          register: foo_result
          ignore_errors: True
        - shell: /usr/bin/bar
          when: foo_result.rc == 5 

    ignore_errors表示忽略中的错误,后者是一个条件语句,只有条件为真时才执行当前task

    Facts变量

    在ansible中还有一些特殊的变量,这些变量时直接使用的。facts变量是ansible执行远程部署之前从远程服务器获得的系统信息

    在playbook中可以直接引用变量,如下

    ---
    - hosts: test
      tasks:
        - shell: echo {{ansible_os_family}}
          register: myecho
        - debug: var=myecho.stdout_lines
        - name: install git on Centos linux
          yum: name=git state=installed
          when: ansible_os_family == "Centos"  

    访问复杂变量的playbook

    ---
    - hosts: test
      gather_facts: yes
      tasks:
        - shell: echo {{ansible_eth0["ipv4"]["address"]}}
          register: myecho
        - debug: var=myecho.stdout_lines
        - shell: echo {{ansible_eth0.ipv4.address}}
          register: myecho1
        - debug: var=myecho1.stdout_lines  

    在ansible中,可以通过gather_facts选项控制是否手机远程服务器的信息,该选项默认为yes,如果确定不需要到远程服务器的信息,可以将该选项设置为no,以此提高ansible部署效率

    ---
    - hosts: test
      gather_facts: no
      tasks:
        - name: Install Mysql package
          yum: name={{ item }} state=installed
          with_items:
            - mysql-server
            - MySQL-python
            - libselinux-python
            - libsemanage-python

    在playbook中,可以通过when选项执行条件语句,when就类似于编程语言的if,示例

    tasks:
      - name:"shut down Debian flavored systems"
        command: /sbin/shutdown -t now
        when: ansible_os_family == "Debian"

    when也支持多个条件语句,

    tasks:
      - name:"shut down Centos 6 systems"
        command:/sbin/shutdown -t now
        when:
            - ansible_distribution == "CentOS"
            - ansible_distribution_major_version == "6"

     对于更加复杂的条件可以使用and、or与括号进行定义

    ---
    - hosts: test
      gather_facts: yes
      tasks:
        - name: "shut down CentOS 6 and Debian 7 systems"
          command: /sbin/shutdown -t now
          when: (ansible_distribution == "CentOS" and ansible_distribution_major_version == "6") or (ansible_distribution == "Debian" and ansible_distribution_major_version == "7")

    when选项中可以读取变量的取值,如

    vars:
      epic: true
    tasks:
      - shell: echo "This is epic"
        when: epic

    when选项可以与循环一起使用,以实现过滤的功能:

    tasks:
      - command: echo {{ item }}
        with_items: [0,2,4,6,8,10]
        when: item > 5

    任务执行策略

    在ansible中,playbook的执行方式是以task为单位进行的,ansible默认是5个进程对远程执行任务,在默认情况的执行策略中,ansible首先执行task1,等到所有服务器执行完task1以后在执行task2,从2.0开始,ansible支持名为free的任务执行策略,允许执行较快的远程服务器提前完成Play的部署,不用等待其他远程服务器一起执行task

    ---
    - hosts: test
      strategy: free
      tasks:
        ...

    使用playbook部署nginx

    ---
    - hosts: test
      become: yes
      become_method: sudo
      vars:
        worker_connections: 768
        worker_processes: auto
        max_open_files: 65535
    
      tasks:
        - name: install nginx server
          yum: name=nginx state=latest
        - name: copy nginx config file
          template: src=/etc/ansible/playbook/nginx.conf.j2 dest=/etc/nginx/nginx.conf
          notify: restart nginx server
    
        - name: copy index
          template:
            src=/etc/ansible/playbook/index.html.j2
            dest=/usr/share/nginx/html/index.html
            mode=0644
      handlers:
        - name: restart nginx server
          service: name=nginx state=restarted
    nginx.yaml
    # For more information on configuration, see:
    #   * Official English Documentation: http://nginx.org/en/docs/
    #   * Official Russian Documentation: http://nginx.org/ru/docs/
    
    user root;
    worker_processes {{ worker_processes }};
    work_rlimit_nofile {{ max_open_files }};
    error_log /var/log/nginx/error.log;
    pid /run/nginx.pid;
    
    # Load dynamic modules. See /usr/share/nginx/README.dynamic.
    include /usr/share/nginx/modules/*.conf;
    
    events {
        worker_connections {{ worker_connections }};
    }
    
    http {
        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';
    
        access_log  /var/log/nginx/access.log  main;
    
        sendfile            on;
        tcp_nopush          on;
        tcp_nodelay         on;
        keepalive_timeout   65;
        types_hash_max_size 2048;
    
        include             /etc/nginx/mime.types;
        default_type        application/octet-stream;
    
        # Load modular configuration files from the /etc/nginx/conf.d directory.
        # See http://nginx.org/en/docs/ngx_core_module.html#include
        # for more information.
        include /etc/nginx/conf.d/*.conf;
    
        server {
            listen       80 default_server;
            listen       [::]:80 default_server;
            server_name  172.26.186.194;
            root         /usr/share/nginx/html;
    
            # Load configuration files for the default server block.
            include /etc/nginx/default.d/*.conf;
    
            location / {
                    root /opt/piwik;
                    index index.php index.html;
            }
    
            location ~ .php$ {
                    include /etc/nginx/fastcgi_params;
                    fastcgi_pass 127.0.0.1:9000;
                    fastcgi_index index.php;
                    fastcgi_param SCRIPT_FILENAME /usr/share/nginx/html/html$fastcgi_script_name;
            }
    
            error_page 404 /404.html;
                location = /40x.html {
            }
    
            error_page 500 502 503 504 /50x.html;
                location = /50x.html {
            }
        }
    
    # Settings for a TLS enabled server.
    #
    #    server {
    #        listen       443 ssl http2 default_server;
    #        listen       [::]:443 ssl http2 default_server;
    #        server_name  _;
    #        root         /usr/share/nginx/html;
    #
    #        ssl_certificate "/etc/pki/nginx/server.crt";
    #        ssl_certificate_key "/etc/pki/nginx/private/server.key";
    #        ssl_session_cache shared:SSL:1m;
    #        ssl_session_timeout  10m;
    #        ssl_ciphers HIGH:!aNULL:!MD5;
    #        ssl_prefer_server_ciphers on;
    #
    #        # Load configuration files for the default server block.
    #        include /etc/nginx/default.d/*.conf;
    #
    #        location / {
    #        }
    #
    #        error_page 404 /404.html;
    #            location = /40x.html {
    #        }
    #
    #        error_page 500 502 503 504 /50x.html;
    #            location = /50x.html {
    #        }
    #    }
    
    }
    nginx.conf.j2
    <html>
      <head>
        <title>Wecome to ansible</title>
      </head>
      <body>
      <h1>nginx,configured by ansible</h1>
      <p>If you can see this,ansible successful installed nginx.</p>
      <p>{{ ansible_hostname }}</p>
      </body>
    </html>
    index.html.j2

    在nginx.conf.j2中引用了nginx.yaml里面定义的三个参数,在index.html.j2里面引用了系统变量

     使用playbook部署mongodb

    playbook中的高级语法

    高级语法包括:serial、delegate_to、local_action、run_once、with_*、tags、changed_when和faild_when

    线性更新服务器

    ansible为了提高部署的效率,默认使用并发的方式对远程服务器进行更新。我们可以使用--forks参数控制并发的进程数,默认并发数为5.我们更新线上服务的时候应该循序渐进更新,以此达到降低对线上服务影响的目的。如果服务器数量较少,可以逐台更新,如果服务器较多,则渐进式更新。如先更新一台服务器,没有问题再更新2台服务器。如果更新过程中存在不规范或者有问题,循序渐进的更新方式可以及时停止更新,尽可能降低对线上的影响

    为了实现线性更新,我们可以使用ansible playbook的serial选项。该选项值 

  • 相关阅读:
    文件上传
    使用servlet+jdbc+MD5实现用户加密登录
    JDBC入门
    springmvc(三)
    springmvc(二)
    springmvc(一)
    JavaScript总结(一)
    Spring的AOP面向切面编程
    Spring框架(三)
    Spring框架(二)
  • 原文地址:https://www.cnblogs.com/qinghe123/p/8467326.html
Copyright © 2011-2022 走看看