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选项。该选项值 

  • 相关阅读:
    atitit.ntfs ext 文件系统新特性对比
    Atitit.图片木马的原理与防范 attilax 总结
    Atitit.图片木马的原理与防范 attilax 总结
    Atitit.jdk java8的语法特性详解 attilax 总结
    Atitit.jdk java8的语法特性详解 attilax 总结
    Atitit.远程接口 监控与木马   常用的api 标准化v2 q216
    Atitit.远程接口 监控与木马   常用的api 标准化v2 q216
    Atitit..jdk java 各版本新特性 1.0 1.1 1.2 1.3 1.4 1.5(5.0) 1.6(6.0) 7.0 8.0 9.0 attilax 大总结
    Atitit..jdk java 各版本新特性 1.0 1.1 1.2 1.3 1.4 1.5(5.0) 1.6(6.0) 7.0 8.0 9.0 attilax 大总结
    Atitit.跨平台预定义函数 魔术方法 魔术函数 钩子函数 api兼容性草案 v2 q216  java c# php js.docx
  • 原文地址:https://www.cnblogs.com/qinghe123/p/8467326.html
Copyright © 2011-2022 走看看