Fabric远程自动化使用说明

来源:互联网 发布:信号微机监测数据异常 编辑:程序博客网 时间:2024/05/29 08:47

关于Fabric的介绍,可以看官网说明。简单来说主要功能就是一个基于Python的服务器批量管理库/工具,Fabric 使用 ssh(通过 paramiko 库)在多个服务器上批量执行任务、上传、下载。在使用Fabric之前,都用Python的paramiko模块来实现需求,相比之后发现Fabric比paramiko模块强大很多。具体的使用方法和说明可以看官方文档介绍。下面写类一个paramiko(apt-get install python-paramiko)封装的远程操作类的模板: 

复制代码
#!/usr/bin/python# -*- encoding: utf-8 -*-import paramikoimport sysreload(sys)sys.setdefaultencoding('utf8')class Remote_Ops():    def __init__(self,hostname,ssh_port,username='',password=''):        self.hostname = hostname        self.ssh_port = ssh_port        self.username = username        self.password = password#密码登入的操作方法    def ssh_connect_exec(self,cmd):        try:            ssh_key = paramiko.SSHClient()            ssh_key.set_missing_host_key_policy(paramiko.AutoAddPolicy())            ssh_key.connect(hostname=self.hostname, port=self.ssh_port, username=self.username, password=self.password, timeout=10)#            paramiko.util.log_to_file('syslogin.log')        except Exception, e:            print('Connect Error:ssh %s@%s: %s' % (self.username, self.hostname, e))            exit()        stdin, stdout, stderr = ssh_key.exec_command(cmd,get_pty=True)#切换root        stdin.write(self.password+'\n')         stdin.flush()        err_list = stderr.readlines()        if len( err_list ) > 0:            print 'ERROR:' + err_list[0]            exit()#        print stdout.read()        for item in stdout.readlines()[2:]:            print item.strip()        ssh_key.close()#ssh登陆的操作方法    def ssh_connect_keyfile_exec(self,file_name,cmd):        try:            ssh_key = paramiko.SSHClient()            ssh_key.set_missing_host_key_policy(paramiko.AutoAddPolicy())            ssh_key.connect(hostname=self.hostname, port=self.ssh_port, key_filename=file_name, timeout=10)#            paramiko.util.log_to_file('syslogin.log')        except Exception, e:            print e            exit()        stdin, stdout, stderr = ssh_key.exec_command(cmd)        err_list = stderr.readlines()        if len( err_list ) > 0:            print 'ERROR:' + err_list[0]            exit()        for item in stdout.readlines():            print item.strip()        ssh_key.close()    if __name__ == '__main__':#密码登陆的操作方法:    test = Remote_Ops('10.211.55.11', 22, 'zjy', 'zhoujinyi')    test.ssh_connect_exec('sudo ls -lh /var/lib/mysql/')#ssh key登陆的操作方法:(需要到root下运行)    file_name = '/var/root/.ssh/id_rsa'    test1 = Remote_Ops('10.211.55.11', 22)    test1.ssh_connect_keyfile_exec(file_name,'apt-get update')    
复制代码

关于更多的paramiko信息可以看官方文档python运维之paramikopython远程连接paramiko 模块。本文将要介绍的是Fabric的使用方法。

说明:

1.安装

$ pip install fabricOR$ sudo apt-get install fabric

2.参数(fab -h)

复制代码
~$ fab -h                                                                                                                              Usage: fab [options] <command>[:arg1,arg2=val2,host=foo,hosts='h1;h2',...] ...   #命令行的使用方法Options:  -h, --help            show this help message and exit  -d NAME, --display=NAME                        print detailed info about command NAME  -F FORMAT, --list-format=FORMAT                        formats --list, choices: short, normal, nested  -I, --initial-password-prompt                        Force password prompt up-front  --initial-sudo-password-prompt                        Force sudo password prompt up-front  -l, --list            print list of possible commands and exit                  #显示出可以执行的命令函数名  --set=KEY=VALUE,...   comma separated KEY=VALUE pairs to set Fab env vars  --shortlist           alias for -F short --list  -V, --version         show program's version number and exit  -a, --no_agent        don't use the running SSH agent  -A, --forward-agent   forward local agent to remote end  --abort-on-prompts    abort instead of prompting (for password, host, etc)      #出现提示就中指,如密码、主机提示  -c PATH, --config=PATH                        specify location of config file to use  --colorize-errors     Color error output                                        #输出颜色错误  -D, --disable-known-hosts                        do not load user known_hosts file  -e, --eagerly-disconnect                        disconnect from hosts as soon as possible  -f PATH, --fabfile=PATH                                                          #指定fab执行的文件,默认是fabfile.py                        python module file to import, e.g. '../other.py'  -g HOST, --gateway=HOST                                                          #指定堡垒机(中转机)的地址                        gateway host to connect through  --gss-auth            Use GSS-API authentication  --gss-deleg           Delegate GSS-API client credentials or not  --gss-kex             Perform GSS-API Key Exchange and user authentication  --hide=LEVELS         comma-separated list of output levels to hide              #output隐藏的等级设置,多个等级逗号分隔  -H HOSTS, --hosts=HOSTS                                                          #指定操作的服务器,多个用逗号分隔                        comma-separated list of hosts to operate on  -i PATH               path to SSH private key file. May be repeated.             #指定私钥文件  -k, --no-keys         don't load private key files from ~/.ssh/  --keepalive=N         enables a keepalive every N seconds                        #  --linewise            print line-by-line instead of byte-by-byte  -n M, --connection-attempts=M                        make M attempts to connect before giving up  --no-pty              do not use pseudo-terminal in run/sudo  -p PASSWORD, --password=PASSWORD                                                #指定远程登陆的密码包括sudo                        password for use with authentication and/or sudo  -P, --parallel        default to parallel execution method                      #指定是否并行执行的  --port=PORT           SSH connection port                                       #指定ssh的默认端口  -r, --reject-unknown-hosts                                                      #指定拒绝的主机                        reject unknown hosts  --sudo-password=SUDO_PASSWORD                                                   #指定sudo密码                        password for use with sudo only  --system-known-hosts=SYSTEM_KNOWN_HOSTS                        load system known_hosts file before reading user                        known_hosts  -R ROLES, --roles=ROLES                                                         #指定role,以role来区分不同执行函数                         comma-separated list of roles to operate on  -s SHELL, --shell=SHELL                                                         #指定的shell的执行环境,                        specify a new shell, defaults to '/bin/bash -l -c'  --show=LEVELS         comma-separated list of output levels to show             #指定显示output的等级,多个用逗号分隔  --skip-bad-hosts      skip over hosts that can't be reached                     #指定跳过不能到达的主机  --skip-unknown-tasks  skip over unknown tasks                                   #指定跳过不识别的执行函数  --ssh-config-path=PATH                                                          #指定ssh配置文件的路径                        Path to SSH config file  -t N, --timeout=N     set connection timeout to N seconds                       #指定连接超时时间  -T N, --command-timeout=N                                                       #指定远程命令超时时间                        set remote command timeout to N seconds   -u USER, --user=USER  username to use when connecting to remote hosts           #指定远程登陆用户  -w, --warn-only       warn, instead of abort, when commands fail                #指定命令错误发出警告而不是中止  -x HOSTS, --exclude-hosts=HOSTS                        comma-separated list of hosts to exclude                  #指定排除的主机  -z INT, --pool-size=INT                        number of concurrent processes to use in parallel mode    #指定并发线程的数量
复制代码

使用

①:命令行接口

~$ fab -u zjy -p zhoujinyi -H 10.211.55.9,10.211.55.11 -- 'ls -lh /tmp/'  

效果:

复制代码
[10.211.55.9] Executing task '<remainder>'[10.211.55.9] run: ls -lh /tmp/[10.211.55.9] out: 总用量 16K[10.211.55.9] out: -rw-rw-r-- 1 zjy zjy  853 11月 10 18:42 change_pwd.py[10.211.55.9] out: [10.211.55.11] Executing task '<remainder>'[10.211.55.11] run: ls -lh /tmp/[10.211.55.11] out: 总用量 12K[10.211.55.11] out: -rw-rw-r-- 1 zjy     zjy     2.4K 11月 10 18:29 remote_ops.py[10.211.55.11] out: 
复制代码

不推荐使用命令行,最好都写到一个文件脚本里,方便也安全。

②:脚本

注:默认fabric使用一个名为fabfile.py文件里,如:

#!/usr/bin/pythonfrom fabric.api import local, lcddef lsfab():    with lcd('/tmp/'):        local('ls')

效果:

[localhost] local: lscom.apple.launchd.ESurbfatee    com.apple.launchd.ykbSkcZdfZ    mykey.txt            parallels_crash_dumpsDone.

如果写到其他文件则需要通过-f来指定执行:

~$ mv fabfile.py ttt.py
~$ fab -f ttt.py lsfab

③:参数

定义的执行函数里带参数:

#!/usr/bin/pythonfrom fabric.api import *def hello(name,age):    print "hello,%s,%s" %(name,age)

带参数的执行函数执行:

 ~$ fab hello:zhoujy,123hello,zhoujy,123Done.

④:模块说明

复制代码
1:from fabric.api import *local    #执行本地命令,如local('uname -s')lcd      #切换本地目录,如lcd('/home')cd       #切换远程目录,如cd('/var/logs')run      #执行远程命令,如run('free -m')sudo     #sudo方式执行远程命令,如sudo('/etc/init.d/httpd start')put      #上次本地文件导远程主机,如put('/home/user.info','/data/user.info')get      #从远程主机下载文件到本地,如:get('/data/user.info','/home/user.info')prompt   #获得用户输入信息,如:prompt('please input user password:')confirm  #获得提示信息确认,如:confirm('Test failed,Continue[Y/N]?')reboot   #重启远程主机,如:reboot()@task    #函数修饰符,标识的函数为fab可调用的,非标记对fab不可见,纯业务逻辑@runs_once   #函数修饰符,标识的函数只会执行一次,不受多台主机影响@roles() #运行指定的角色组里,通过env.roledefs里的定义
2:from fabric.colors import *print blue(text)print cyan(text)print green(text)print magenta(text)print red(text)print white(text)print yellow(text)3:
...
用到再补充...
复制代码

基础实例说明

实例1: 本地操作

复制代码
from fabric.api import *def lsfab():    with lcd('/tmp/'):  #本地切换目录        local('ls')     #本地执行命令def host_name():    local('uname -s')   #本地执行命令
复制代码

-l查看可以执行的命令函数:

~$ fab -f fab_ops.py -lAvailable commands:    host_name    lsfab

可以执行上面2个执行函数,执行:

~$ fab -f fab_ops.py host_name[localhost] local: uname -sDarwinDone.

上面2个函数合并,并且对外只显示一个执行入口函数(@task):

复制代码
from fabric.api import *def lsfab():    with lcd('/tmp/'):        local('ls')def host_name():    local('uname -s')@taskdef go():    lsfab()    host_name()
复制代码

执行:

复制代码
~$ fab -f fab_ops.py go[localhost] local: lscom.apple.launchd.ESurbfatee    com.apple.launchd.ykbSkcZdfZ    parallels_crash_dumps[localhost] local: uname -sDarwinDone.
复制代码

实例2:远程操作,env变量

复制代码
from fabric.api import * #配置远程服务器env.hosts = ['10.211.55.9','10.211.55.11']#端口env.port = '22'#用户env.user = 'zjy'#密码,远程服务器密码都一样env.password = 'zhoujinyi'def lsfab():    with cd('/tmp/'):   #远程切换目录        run('ls')       #远程命令运行def host_name():    run('uname -s')@taskdef go():    lsfab()    host_name()
复制代码

执行看到的信息:执行的函数,命令和命令的输出结果。

复制代码
~$ fab -f fab_ops.py go              [10.211.55.9] Executing task 'go'[10.211.55.9] run: ls[10.211.55.9] out: 1  2  3[10.211.55.9] out: [10.211.55.9] run: uname -s[10.211.55.9] out: Linux[10.211.55.9] out: [10.211.55.11] Executing task 'go'[10.211.55.11] run: ls[10.211.55.11] out: a  b  c  mongodb-27017.sock[10.211.55.11] out: [10.211.55.11] run: uname -s[10.211.55.11] out: Linux[10.211.55.11] out: Done.Disconnecting from 10.211.55.11... done.Disconnecting from 10.211.55.9... done.
复制代码

远程机器的密码不一致,怎么配置?这时可以用env.passwords来替换env.password:注意格式:user@ip:pwd

env.passwords = {    'zjy@10.211.55.9:22' : 'zjy',    'zjy@10.211.55.11:22': 'zhoujinyi',}

实例3:如何让不同服务器组执行不同的操作?如DB和WEB服务器各自执行自己的操作。这里需要用env.roledefs来定义角色组,根据不同的roles来使用execute进行不同的操作。

复制代码
#!/usr/bin/python# -*- encoding: utf-8 -*-from fabric.api import * #定义角色,操作一致的服务器可以放在一组。因为服务器的用户端口不一样,需要在role里指定用户、IP和端口env.roledefs = {    'dbserver':['zjy@10.211.55.9:22','zjy@10.211.55.11:22'],    'webserver':['zhoujy@192.168.200.25:221'],}#密码,远程服务器密码不一致时使用,格式user@host:port:pwdenv.passwords = {    'zjy@10.211.55.9:22' : 'zjy',    'zjy@10.211.55.11:22': 'zhoujinyi',    'zhoujy@192.168.200.25:221':'123456',}@task                             #入口@roles('dbserver')                #角色修饰符def get_memory():    run('free -m')@task@roles('webserver')def mkfile_task():    with cd('/home/zhoujy/'):        run('touch xxxx.log')
复制代码

执行效果:执行get_memory函数,在dbserver中的主机上执行,mkfile_touch函数则在webserver中的主机上执行。

通过env.roledefsenv.passwords指定好了用户名、端口和密码,这时上面2个函数合并,并且对外只显示一个执行函数(@task),还要注意的是因为各自的执行函数处于不同的roles下执行的,要放到一个函数里执行,需要添加:execute(),这样可以在一个执行函数里操作多个远程的机器。 

复制代码
......@taskdef go():    execute(get_memory)    execute(mkfile_task)
复制代码

效果:

 View Code

实例4:多台服务器并行执行,@parallel

复制代码
#!/usr/bin/python# -*- encoding: utf-8 -*-from fabric.api import * 
#要是各个服务器的端口、用户名不一样,就不能配置env.port、env.user了,需要在env.hosts中设置端口:用户@IP:端口env.hosts
= ['zjy@10.211.55.9:22','zjy@10.211.55.11:22','zhoujy@192.168.200.25:221']#密码,远程服务器密码不一致时使用,格式user@host:port:pwdenv.passwords = { 'zjy@10.211.55.9:22' : 'zjy', 'zjy@10.211.55.11:22': 'zhoujinyi', 'zhoujy@192.168.200.25:221':'123456',}@task #入口@paralleldef get_memory(): run('free -m')
复制代码

执行效果:执行函数同时在多个主机上运行,加快执行效率

 View Code

实例5:输出信息等级设置:with settings(hide(...)):

复制代码
#!/usr/bin/python# -*- encoding: utf-8 -*-from fabric.api import * from fabric.colors import *env.hosts = ['zjy@10.211.55.9:22','zjy@10.211.55.11:22','zhoujy@192.168.200.25:221']#密码,远程服务器密码不一致时使用,格式user@host:port:pwdenv.passwords = {    'zjy@10.211.55.9:22' : 'zjy',    'zjy@10.211.55.11:22': 'zhoujinyi',    'zhoujy@192.168.200.25:221':'123456',}@runs_once      #只执行一次,避免每个主机处理都输入一次def put_file():
#获取输入信息
return prompt("输入上传的文件名(绝对路径),默认当前目录文件:",default ="")@taskdef put_task(): local_path = put_file() remote_path = "/tmp/" put(local_path,remote_path) #上传 #输出等级设置,隐藏指定的类型 with settings(hide('running', 'stdout', 'stderr','warnings','everything')): result = put(local_path,remote_path) print yellow("%s上传成功...") %env.host
复制代码

隐藏:

 View Code

没有隐藏:

 View Code

实例6:异常处理、捕获:

异常处理:settings(warn_only=True)遇到错误继续执行还是退出settings(warn_only=False),False是默认方式。

复制代码
from fabric.api import *def lsfab():    with lcd('/tmp/'):        local('lsa')                #执行不存在的命令,报错退出    print "xxxxxxx" def lsfab1():    with settings(warn_only=True):  #执行不存在的命令,报错,继续执行后面的        with lcd('/tmp/'):            local('lsa')    print "xxxxxx"
复制代码

效果:

 View Code

异常捕获:if result.failed:abord()捕获到异常之后直接退出;if result.failed and not confirm("")捕获到异常之后确认退出还是继续 

复制代码
#!/usr/bin/python# -*- encoding: utf-8 -*-from fabric.colors import *from fabric.contrib.console import confirmfrom fabric.api import *def lsfab():    with settings(warn_only=True):        with lcd('/Users/jinyizhou/uuii/'):            result = local('lsn',capture=True) #通过local来执行任务,需要通过capture=True来得到值            if result.failed:                  #即使warn_only设置成True,捕捉到错误之后,还是退出                abort(red("错误..."))            print 'xxxxx'def lsfab1():    with settings(warn_only=True):        with lcd('/Users/jinyizhou/uuii/'):            result = local('lsn',capture=True)            if result.failed and not confirm("failed. Continue?"):  #即使warn_only设置成True,捕捉到错误之后,还是要确认是否退出或则继续                abort(red("错误..."))            print 'xxxxx'
复制代码

效果:

 View Code

实例7:通过中转(路由)机连接远程机执行,env.gateway 

复制代码
#!/usr/bin/python# -*- encoding: utf-8 -*-from fabric.api import * from fabric.colors import *#中转机env.gateway = 'zjy@10.211.55.9:22'        #中转机的地址,注意中转机的端口、密码和其他远程机器不一样,需要设置成user@ip:port的格式。#操作服务器env.hosts = ['zjy@10.211.55.11:22','zhoujy@192.168.200.25:221']env.passwords = {    'zjy@10.211.55.9:22' : 'zjy',    'zjy@10.211.55.11:22': 'zhoujinyi',    'zhoujy@192.168.200.25:221':'123456',}#远程连接超时时间env.timeout = 30#命令超时时间env.command_timeout = 30@runs_once      #只执行一次,避免每个主机处理都输入一次def put_file():    return prompt("输入上传的文件名(绝对路径),默认当前目录文件:",default ="")@taskdef put_task():    local_path  = put_file()    remote_path = "/tmp/"    put(local_path,remote_path)    #输出等级设置,隐藏指定的类型    with settings(hide('running','stdout', 'stderr','everything'),warn_only=True):        result = put(local_path,remote_path)        print yellow("%s上传成功...") %env.host    if result.failed and not confirm("put file failed,Continue[Y/N]?"):        abort("Aborting file put task!")
复制代码

效果:本地执行脚本通过中转机来向远程机器上传文件,本机和远程机不需要有关联

复制代码
~$ fab -f zz.py put_task[zjy@10.211.55.11:22] Executing task 'put_task'输入上传的文件名(绝对路径),默认当前目录文件: /Users/jinyizhou/fabric_script/gateway.txt[zjy@10.211.55.11:22] put: /Users/jinyizhou/fabric_script/gateway.txt -> /tmp/gateway.txt10.211.55.11上传成功...192.168.200.25上传成功...Done.Disconnecting from zhoujy@192.168.200.25:221... done.Disconnecting from zjy@10.211.55.11... done.Disconnecting from zjy@10.211.55.9... done.  #最后断开中转机 
复制代码

实例8:通过ssh key(密钥)登陆

复制代码
#!/usr/bin/python# -*- encoding: utf-8 -*-from fabric.api import *from fabric.colors import *#中转机env.gateway = 'zjy@10.211.55.9:22'        #中转机的地址,注意中转机的端口、密码和其他远程机器不一样,需要设置成user@ip:port的格式。#操作服务器env.hosts = ['zhoujy@192.168.200.25:221','zhoujy@192.168.200.102:222']env.passwords = {    'zhoujy@192.168.200.25:221':'123456',    'zhoujy@192.168.200.102:222':'123456',    }#env.use_ssh_config = True#密钥登陆中转服务器env.key_filename = ['~/.ssh/id_rsa']#远程连接超时时间env.timeout = 30#命令超时时间env.command_timeout = 30@runs_once      #只执行一次,避免每个主机处理都输入一次def put_file():    return prompt("输入上传的文件名(绝对路径),默认当前目录文件:",default ="/home/zjy/ttxx.py")@taskdef put_task():    local_path  = put_file()    remote_path = "/tmp/"    put(local_path,remote_path)    #输出等级设置,隐藏指定的类型    with settings(hide('running','stdout', 'stderr','everything'),warn_only=True):        result = put(local_path,remote_path)        print yellow("%s上传成功...") %env.host    if result.failed and not confirm("put file failed,Continue[Y/N]?"):        abort("Aborting file put task!")
复制代码

效果:执行脚本的机器通过密钥登陆GATEWAY机器,再在GATEWAY机器上通过账号密码登陆要操作的机器(不确定这里能否也通过SSH密钥登陆?)。

复制代码
zhoujy@192.168.200.25:221] Executing task 'put_task'输入上传的文件名(绝对路径),默认当前目录文件: [/home/zjy/ttxx.py] [zhoujy@192.168.200.25:221] put: /home/zjy/ttxx.py -> /tmp/ttxx.py192.168.200.25上传成功...192.168.200.102上传成功...Done.Disconnecting from zhoujy@192.168.200.25:221... done. Disconnecting from zhoujy@192.168.200.102:222... done.Disconnecting from 10.211.55.9... done.  #最后退出GATEWAY
复制代码

如何建立SSH的密钥连接,在MySQL MHA 搭建&测试里有说明,可以了解一下。 

应用说明

应用1:带参数批量修改服务器密码

复制代码
#!/usr/bin/python# -*- coding: utf-8 -*-from fabric.api import *import socketimport paramikofrom fabric.colors import *env.user = 'zjy'env.password = 'zhoujinyi'env.hosts = ['10.211.55.11','10.211.55.9']def isup(host):    print 'connecting host: %s' % host    timeout = socket.getdefaulttimeout()    socket.setdefaulttimeout(1)    up = True    try:        paramiko.Transport((host, 22))    except Exception, e:        up = False        print '%s down, %s' % (host, e)    finally:        socket.setdefaulttimeout(timeout)        return up@task@paralleldef passwd(user,password):    with settings(hide('running', 'stdout', 'stderr','everything'), warn_only=True):        if isup(env.host):            sudo("echo -e '%s\n%s' | passwd %s" % (password, password, user))            print yellow("%s 更新密码...") %env.host
复制代码

执行效果:

复制代码
~$ fab -f change_pwd.py passwd:zjy,zjy                                                                                                 1 ↵[10.211.55.11] Executing task 'passwd'[10.211.55.9] Executing task 'passwd'connecting host: 10.211.55.9connecting host: 10.211.55.1110.211.55.9 更新密码...10.211.55.11 更新密码...Done.
复制代码

应用2:远程开、关、升级MySQL,并且自动完成MHA相关的切换开启工作。

 View Code 

该脚本执行在Ubuntu上,并且用于Percona5.6、5.7的相关操作,以及mha的切换开启工作。需要注意的是,mysql无密码登陆用了mysql_config_editor和远程开启mha后台运行程序时用到的进程监控(deamon运行)Supervisor程序。

关于Fabric的介绍,可以看官网说明。简单来说主要功能就是一个基于Python的服务器批量管理库/工具,Fabric 使用 ssh(通过 paramiko 库)在多个服务器上批量执行任务、上传、下载。在使用Fabric之前,都用Python的paramiko模块来实现需求,相比之后发现Fabric比paramiko模块强大很多。具体的使用方法和说明可以看官方文档介绍。下面写类一个paramiko(apt-get install python-paramiko)封装的远程操作类的模板: 

复制代码
#!/usr/bin/python# -*- encoding: utf-8 -*-import paramikoimport sysreload(sys)sys.setdefaultencoding('utf8')class Remote_Ops():    def __init__(self,hostname,ssh_port,username='',password=''):        self.hostname = hostname        self.ssh_port = ssh_port        self.username = username        self.password = password#密码登入的操作方法    def ssh_connect_exec(self,cmd):        try:            ssh_key = paramiko.SSHClient()            ssh_key.set_missing_host_key_policy(paramiko.AutoAddPolicy())            ssh_key.connect(hostname=self.hostname, port=self.ssh_port, username=self.username, password=self.password, timeout=10)#            paramiko.util.log_to_file('syslogin.log')        except Exception, e:            print('Connect Error:ssh %s@%s: %s' % (self.username, self.hostname, e))            exit()        stdin, stdout, stderr = ssh_key.exec_command(cmd,get_pty=True)#切换root        stdin.write(self.password+'\n')         stdin.flush()        err_list = stderr.readlines()        if len( err_list ) > 0:            print 'ERROR:' + err_list[0]            exit()#        print stdout.read()        for item in stdout.readlines()[2:]:            print item.strip()        ssh_key.close()#ssh登陆的操作方法    def ssh_connect_keyfile_exec(self,file_name,cmd):        try:            ssh_key = paramiko.SSHClient()            ssh_key.set_missing_host_key_policy(paramiko.AutoAddPolicy())            ssh_key.connect(hostname=self.hostname, port=self.ssh_port, key_filename=file_name, timeout=10)#            paramiko.util.log_to_file('syslogin.log')        except Exception, e:            print e            exit()        stdin, stdout, stderr = ssh_key.exec_command(cmd)        err_list = stderr.readlines()        if len( err_list ) > 0:            print 'ERROR:' + err_list[0]            exit()        for item in stdout.readlines():            print item.strip()        ssh_key.close()    if __name__ == '__main__':#密码登陆的操作方法:    test = Remote_Ops('10.211.55.11', 22, 'zjy', 'zhoujinyi')    test.ssh_connect_exec('sudo ls -lh /var/lib/mysql/')#ssh key登陆的操作方法:(需要到root下运行)    file_name = '/var/root/.ssh/id_rsa'    test1 = Remote_Ops('10.211.55.11', 22)    test1.ssh_connect_keyfile_exec(file_name,'apt-get update')    
复制代码

关于更多的paramiko信息可以看官方文档python运维之paramikopython远程连接paramiko 模块。本文将要介绍的是Fabric的使用方法。

说明:

1.安装

$ pip install fabricOR$ sudo apt-get install fabric

2.参数(fab -h)

复制代码
~$ fab -h                                                                                                                              Usage: fab [options] <command>[:arg1,arg2=val2,host=foo,hosts='h1;h2',...] ...   #命令行的使用方法Options:  -h, --help            show this help message and exit  -d NAME, --display=NAME                        print detailed info about command NAME  -F FORMAT, --list-format=FORMAT                        formats --list, choices: short, normal, nested  -I, --initial-password-prompt                        Force password prompt up-front  --initial-sudo-password-prompt                        Force sudo password prompt up-front  -l, --list            print list of possible commands and exit                  #显示出可以执行的命令函数名  --set=KEY=VALUE,...   comma separated KEY=VALUE pairs to set Fab env vars  --shortlist           alias for -F short --list  -V, --version         show program's version number and exit  -a, --no_agent        don't use the running SSH agent  -A, --forward-agent   forward local agent to remote end  --abort-on-prompts    abort instead of prompting (for password, host, etc)      #出现提示就中指,如密码、主机提示  -c PATH, --config=PATH                        specify location of config file to use  --colorize-errors     Color error output                                        #输出颜色错误  -D, --disable-known-hosts                        do not load user known_hosts file  -e, --eagerly-disconnect                        disconnect from hosts as soon as possible  -f PATH, --fabfile=PATH                                                          #指定fab执行的文件,默认是fabfile.py                        python module file to import, e.g. '../other.py'  -g HOST, --gateway=HOST                                                          #指定堡垒机(中转机)的地址                        gateway host to connect through  --gss-auth            Use GSS-API authentication  --gss-deleg           Delegate GSS-API client credentials or not  --gss-kex             Perform GSS-API Key Exchange and user authentication  --hide=LEVELS         comma-separated list of output levels to hide              #output隐藏的等级设置,多个等级逗号分隔  -H HOSTS, --hosts=HOSTS                                                          #指定操作的服务器,多个用逗号分隔                        comma-separated list of hosts to operate on  -i PATH               path to SSH private key file. May be repeated.             #指定私钥文件  -k, --no-keys         don't load private key files from ~/.ssh/  --keepalive=N         enables a keepalive every N seconds                        #  --linewise            print line-by-line instead of byte-by-byte  -n M, --connection-attempts=M                        make M attempts to connect before giving up  --no-pty              do not use pseudo-terminal in run/sudo  -p PASSWORD, --password=PASSWORD                                                #指定远程登陆的密码包括sudo                        password for use with authentication and/or sudo  -P, --parallel        default to parallel execution method                      #指定是否并行执行的  --port=PORT           SSH connection port                                       #指定ssh的默认端口  -r, --reject-unknown-hosts                                                      #指定拒绝的主机                        reject unknown hosts  --sudo-password=SUDO_PASSWORD                                                   #指定sudo密码                        password for use with sudo only  --system-known-hosts=SYSTEM_KNOWN_HOSTS                        load system known_hosts file before reading user                        known_hosts  -R ROLES, --roles=ROLES                                                         #指定role,以role来区分不同执行函数                         comma-separated list of roles to operate on  -s SHELL, --shell=SHELL                                                         #指定的shell的执行环境,                        specify a new shell, defaults to '/bin/bash -l -c'  --show=LEVELS         comma-separated list of output levels to show             #指定显示output的等级,多个用逗号分隔  --skip-bad-hosts      skip over hosts that can't be reached                     #指定跳过不能到达的主机  --skip-unknown-tasks  skip over unknown tasks                                   #指定跳过不识别的执行函数  --ssh-config-path=PATH                                                          #指定ssh配置文件的路径                        Path to SSH config file  -t N, --timeout=N     set connection timeout to N seconds                       #指定连接超时时间  -T N, --command-timeout=N                                                       #指定远程命令超时时间                        set remote command timeout to N seconds   -u USER, --user=USER  username to use when connecting to remote hosts           #指定远程登陆用户  -w, --warn-only       warn, instead of abort, when commands fail                #指定命令错误发出警告而不是中止  -x HOSTS, --exclude-hosts=HOSTS                        comma-separated list of hosts to exclude                  #指定排除的主机  -z INT, --pool-size=INT                        number of concurrent processes to use in parallel mode    #指定并发线程的数量
复制代码

使用

①:命令行接口

~$ fab -u zjy -p zhoujinyi -H 10.211.55.9,10.211.55.11 -- 'ls -lh /tmp/'  

效果:

复制代码
[10.211.55.9] Executing task '<remainder>'[10.211.55.9] run: ls -lh /tmp/[10.211.55.9] out: 总用量 16K[10.211.55.9] out: -rw-rw-r-- 1 zjy zjy  853 11月 10 18:42 change_pwd.py[10.211.55.9] out: [10.211.55.11] Executing task '<remainder>'[10.211.55.11] run: ls -lh /tmp/[10.211.55.11] out: 总用量 12K[10.211.55.11] out: -rw-rw-r-- 1 zjy     zjy     2.4K 11月 10 18:29 remote_ops.py[10.211.55.11] out: 
复制代码

不推荐使用命令行,最好都写到一个文件脚本里,方便也安全。

②:脚本

注:默认fabric使用一个名为fabfile.py文件里,如:

#!/usr/bin/pythonfrom fabric.api import local, lcddef lsfab():    with lcd('/tmp/'):        local('ls')

效果:

[localhost] local: lscom.apple.launchd.ESurbfatee    com.apple.launchd.ykbSkcZdfZ    mykey.txt            parallels_crash_dumpsDone.

如果写到其他文件则需要通过-f来指定执行:

~$ mv fabfile.py ttt.py
~$ fab -f ttt.py lsfab

③:参数

定义的执行函数里带参数:

#!/usr/bin/pythonfrom fabric.api import *def hello(name,age):    print "hello,%s,%s" %(name,age)

带参数的执行函数执行:

 ~$ fab hello:zhoujy,123hello,zhoujy,123Done.

④:模块说明

复制代码
1:from fabric.api import *local    #执行本地命令,如local('uname -s')lcd      #切换本地目录,如lcd('/home')cd       #切换远程目录,如cd('/var/logs')run      #执行远程命令,如run('free -m')sudo     #sudo方式执行远程命令,如sudo('/etc/init.d/httpd start')put      #上次本地文件导远程主机,如put('/home/user.info','/data/user.info')get      #从远程主机下载文件到本地,如:get('/data/user.info','/home/user.info')prompt   #获得用户输入信息,如:prompt('please input user password:')confirm  #获得提示信息确认,如:confirm('Test failed,Continue[Y/N]?')reboot   #重启远程主机,如:reboot()@task    #函数修饰符,标识的函数为fab可调用的,非标记对fab不可见,纯业务逻辑@runs_once   #函数修饰符,标识的函数只会执行一次,不受多台主机影响@roles() #运行指定的角色组里,通过env.roledefs里的定义
2:from fabric.colors import *print blue(text)print cyan(text)print green(text)print magenta(text)print red(text)print white(text)print yellow(text)3:
...
用到再补充...
复制代码

基础实例说明

实例1: 本地操作

复制代码
from fabric.api import *def lsfab():    with lcd('/tmp/'):  #本地切换目录        local('ls')     #本地执行命令def host_name():    local('uname -s')   #本地执行命令
复制代码

-l查看可以执行的命令函数:

~$ fab -f fab_ops.py -lAvailable commands:    host_name    lsfab

可以执行上面2个执行函数,执行:

~$ fab -f fab_ops.py host_name[localhost] local: uname -sDarwinDone.

上面2个函数合并,并且对外只显示一个执行入口函数(@task):

复制代码
from fabric.api import *def lsfab():    with lcd('/tmp/'):        local('ls')def host_name():    local('uname -s')@taskdef go():    lsfab()    host_name()
复制代码

执行:

复制代码
~$ fab -f fab_ops.py go[localhost] local: lscom.apple.launchd.ESurbfatee    com.apple.launchd.ykbSkcZdfZ    parallels_crash_dumps[localhost] local: uname -sDarwinDone.
复制代码

实例2:远程操作,env变量

复制代码
from fabric.api import * #配置远程服务器env.hosts = ['10.211.55.9','10.211.55.11']#端口env.port = '22'#用户env.user = 'zjy'#密码,远程服务器密码都一样env.password = 'zhoujinyi'def lsfab():    with cd('/tmp/'):   #远程切换目录        run('ls')       #远程命令运行def host_name():    run('uname -s')@taskdef go():    lsfab()    host_name()
复制代码

执行看到的信息:执行的函数,命令和命令的输出结果。

复制代码
~$ fab -f fab_ops.py go              [10.211.55.9] Executing task 'go'[10.211.55.9] run: ls[10.211.55.9] out: 1  2  3[10.211.55.9] out: [10.211.55.9] run: uname -s[10.211.55.9] out: Linux[10.211.55.9] out: [10.211.55.11] Executing task 'go'[10.211.55.11] run: ls[10.211.55.11] out: a  b  c  mongodb-27017.sock[10.211.55.11] out: [10.211.55.11] run: uname -s[10.211.55.11] out: Linux[10.211.55.11] out: Done.Disconnecting from 10.211.55.11... done.Disconnecting from 10.211.55.9... done.
复制代码

远程机器的密码不一致,怎么配置?这时可以用env.passwords来替换env.password:注意格式:user@ip:pwd

env.passwords = {    'zjy@10.211.55.9:22' : 'zjy',    'zjy@10.211.55.11:22': 'zhoujinyi',}

实例3:如何让不同服务器组执行不同的操作?如DB和WEB服务器各自执行自己的操作。这里需要用env.roledefs来定义角色组,根据不同的roles来使用execute进行不同的操作。

复制代码
#!/usr/bin/python# -*- encoding: utf-8 -*-from fabric.api import * #定义角色,操作一致的服务器可以放在一组。因为服务器的用户端口不一样,需要在role里指定用户、IP和端口env.roledefs = {    'dbserver':['zjy@10.211.55.9:22','zjy@10.211.55.11:22'],    'webserver':['zhoujy@192.168.200.25:221'],}#密码,远程服务器密码不一致时使用,格式user@host:port:pwdenv.passwords = {    'zjy@10.211.55.9:22' : 'zjy',    'zjy@10.211.55.11:22': 'zhoujinyi',    'zhoujy@192.168.200.25:221':'123456',}@task                             #入口@roles('dbserver')                #角色修饰符def get_memory():    run('free -m')@task@roles('webserver')def mkfile_task():    with cd('/home/zhoujy/'):        run('touch xxxx.log')
复制代码

执行效果:执行get_memory函数,在dbserver中的主机上执行,mkfile_touch函数则在webserver中的主机上执行。

通过env.roledefsenv.passwords指定好了用户名、端口和密码,这时上面2个函数合并,并且对外只显示一个执行函数(@task),还要注意的是因为各自的执行函数处于不同的roles下执行的,要放到一个函数里执行,需要添加:execute(),这样可以在一个执行函数里操作多个远程的机器。 

复制代码
......@taskdef go():    execute(get_memory)    execute(mkfile_task)
复制代码

效果:

 View Code

实例4:多台服务器并行执行,@parallel

复制代码
#!/usr/bin/python# -*- encoding: utf-8 -*-from fabric.api import * 
#要是各个服务器的端口、用户名不一样,就不能配置env.port、env.user了,需要在env.hosts中设置端口:用户@IP:端口env.hosts
= ['zjy@10.211.55.9:22','zjy@10.211.55.11:22','zhoujy@192.168.200.25:221']#密码,远程服务器密码不一致时使用,格式user@host:port:pwdenv.passwords = { 'zjy@10.211.55.9:22' : 'zjy', 'zjy@10.211.55.11:22': 'zhoujinyi', 'zhoujy@192.168.200.25:221':'123456',}@task #入口@paralleldef get_memory(): run('free -m')
复制代码

执行效果:执行函数同时在多个主机上运行,加快执行效率

 View Code

实例5:输出信息等级设置:with settings(hide(...)):

复制代码
#!/usr/bin/python# -*- encoding: utf-8 -*-from fabric.api import * from fabric.colors import *env.hosts = ['zjy@10.211.55.9:22','zjy@10.211.55.11:22','zhoujy@192.168.200.25:221']#密码,远程服务器密码不一致时使用,格式user@host:port:pwdenv.passwords = {    'zjy@10.211.55.9:22' : 'zjy',    'zjy@10.211.55.11:22': 'zhoujinyi',    'zhoujy@192.168.200.25:221':'123456',}@runs_once      #只执行一次,避免每个主机处理都输入一次def put_file():
#获取输入信息
return prompt("输入上传的文件名(绝对路径),默认当前目录文件:",default ="")@taskdef put_task(): local_path = put_file() remote_path = "/tmp/" put(local_path,remote_path) #上传 #输出等级设置,隐藏指定的类型 with settings(hide('running', 'stdout', 'stderr','warnings','everything')): result = put(local_path,remote_path) print yellow("%s上传成功...") %env.host
复制代码

隐藏:

 View Code

没有隐藏:

 View Code

实例6:异常处理、捕获:

异常处理:settings(warn_only=True)遇到错误继续执行还是退出settings(warn_only=False),False是默认方式。

复制代码
from fabric.api import *def lsfab():    with lcd('/tmp/'):        local('lsa')                #执行不存在的命令,报错退出    print "xxxxxxx" def lsfab1():    with settings(warn_only=True):  #执行不存在的命令,报错,继续执行后面的        with lcd('/tmp/'):            local('lsa')    print "xxxxxx"
复制代码

效果:

 View Code

异常捕获:if result.failed:abord()捕获到异常之后直接退出;if result.failed and not confirm("")捕获到异常之后确认退出还是继续 

复制代码
#!/usr/bin/python# -*- encoding: utf-8 -*-from fabric.colors import *from fabric.contrib.console import confirmfrom fabric.api import *def lsfab():    with settings(warn_only=True):        with lcd('/Users/jinyizhou/uuii/'):            result = local('lsn',capture=True) #通过local来执行任务,需要通过capture=True来得到值            if result.failed:                  #即使warn_only设置成True,捕捉到错误之后,还是退出                abort(red("错误..."))            print 'xxxxx'def lsfab1():    with settings(warn_only=True):        with lcd('/Users/jinyizhou/uuii/'):            result = local('lsn',capture=True)            if result.failed and not confirm("failed. Continue?"):  #即使warn_only设置成True,捕捉到错误之后,还是要确认是否退出或则继续                abort(red("错误..."))            print 'xxxxx'
复制代码

效果:

 View Code

实例7:通过中转(路由)机连接远程机执行,env.gateway 

复制代码
#!/usr/bin/python# -*- encoding: utf-8 -*-from fabric.api import * from fabric.colors import *#中转机env.gateway = 'zjy@10.211.55.9:22'        #中转机的地址,注意中转机的端口、密码和其他远程机器不一样,需要设置成user@ip:port的格式。#操作服务器env.hosts = ['zjy@10.211.55.11:22','zhoujy@192.168.200.25:221']env.passwords = {    'zjy@10.211.55.9:22' : 'zjy',    'zjy@10.211.55.11:22': 'zhoujinyi',    'zhoujy@192.168.200.25:221':'123456',}#远程连接超时时间env.timeout = 30#命令超时时间env.command_timeout = 30@runs_once      #只执行一次,避免每个主机处理都输入一次def put_file():    return prompt("输入上传的文件名(绝对路径),默认当前目录文件:",default ="")@taskdef put_task():    local_path  = put_file()    remote_path = "/tmp/"    put(local_path,remote_path)    #输出等级设置,隐藏指定的类型    with settings(hide('running','stdout', 'stderr','everything'),warn_only=True):        result = put(local_path,remote_path)        print yellow("%s上传成功...") %env.host    if result.failed and not confirm("put file failed,Continue[Y/N]?"):        abort("Aborting file put task!")
复制代码

效果:本地执行脚本通过中转机来向远程机器上传文件,本机和远程机不需要有关联

复制代码
~$ fab -f zz.py put_task[zjy@10.211.55.11:22] Executing task 'put_task'输入上传的文件名(绝对路径),默认当前目录文件: /Users/jinyizhou/fabric_script/gateway.txt[zjy@10.211.55.11:22] put: /Users/jinyizhou/fabric_script/gateway.txt -> /tmp/gateway.txt10.211.55.11上传成功...192.168.200.25上传成功...Done.Disconnecting from zhoujy@192.168.200.25:221... done.Disconnecting from zjy@10.211.55.11... done.Disconnecting from zjy@10.211.55.9... done.  #最后断开中转机 
复制代码

实例8:通过ssh key(密钥)登陆

复制代码
#!/usr/bin/python# -*- encoding: utf-8 -*-from fabric.api import *from fabric.colors import *#中转机env.gateway = 'zjy@10.211.55.9:22'        #中转机的地址,注意中转机的端口、密码和其他远程机器不一样,需要设置成user@ip:port的格式。#操作服务器env.hosts = ['zhoujy@192.168.200.25:221','zhoujy@192.168.200.102:222']env.passwords = {    'zhoujy@192.168.200.25:221':'123456',    'zhoujy@192.168.200.102:222':'123456',    }#env.use_ssh_config = True#密钥登陆中转服务器env.key_filename = ['~/.ssh/id_rsa']#远程连接超时时间env.timeout = 30#命令超时时间env.command_timeout = 30@runs_once      #只执行一次,避免每个主机处理都输入一次def put_file():    return prompt("输入上传的文件名(绝对路径),默认当前目录文件:",default ="/home/zjy/ttxx.py")@taskdef put_task():    local_path  = put_file()    remote_path = "/tmp/"    put(local_path,remote_path)    #输出等级设置,隐藏指定的类型    with settings(hide('running','stdout', 'stderr','everything'),warn_only=True):        result = put(local_path,remote_path)        print yellow("%s上传成功...") %env.host    if result.failed and not confirm("put file failed,Continue[Y/N]?"):        abort("Aborting file put task!")
复制代码

效果:执行脚本的机器通过密钥登陆GATEWAY机器,再在GATEWAY机器上通过账号密码登陆要操作的机器(不确定这里能否也通过SSH密钥登陆?)。

复制代码
zhoujy@192.168.200.25:221] Executing task 'put_task'输入上传的文件名(绝对路径),默认当前目录文件: [/home/zjy/ttxx.py] [zhoujy@192.168.200.25:221] put: /home/zjy/ttxx.py -> /tmp/ttxx.py192.168.200.25上传成功...192.168.200.102上传成功...Done.Disconnecting from zhoujy@192.168.200.25:221... done. Disconnecting from zhoujy@192.168.200.102:222... done.Disconnecting from 10.211.55.9... done.  #最后退出GATEWAY
复制代码

如何建立SSH的密钥连接,在MySQL MHA 搭建&测试里有说明,可以了解一下。 

应用说明

应用1:带参数批量修改服务器密码

复制代码
#!/usr/bin/python# -*- coding: utf-8 -*-from fabric.api import *import socketimport paramikofrom fabric.colors import *env.user = 'zjy'env.password = 'zhoujinyi'env.hosts = ['10.211.55.11','10.211.55.9']def isup(host):    print 'connecting host: %s' % host    timeout = socket.getdefaulttimeout()    socket.setdefaulttimeout(1)    up = True    try:        paramiko.Transport((host, 22))    except Exception, e:        up = False        print '%s down, %s' % (host, e)    finally:        socket.setdefaulttimeout(timeout)        return up@task@paralleldef passwd(user,password):    with settings(hide('running', 'stdout', 'stderr','everything'), warn_only=True):        if isup(env.host):            sudo("echo -e '%s\n%s' | passwd %s" % (password, password, user))            print yellow("%s 更新密码...") %env.host
复制代码

执行效果:

复制代码
~$ fab -f change_pwd.py passwd:zjy,zjy                                                                                                 1 ↵[10.211.55.11] Executing task 'passwd'[10.211.55.9] Executing task 'passwd'connecting host: 10.211.55.9connecting host: 10.211.55.1110.211.55.9 更新密码...10.211.55.11 更新密码...Done.
复制代码

应用2:远程开、关、升级MySQL,并且自动完成MHA相关的切换开启工作。

 View Code 

该脚本执行在Ubuntu上,并且用于Percona5.6、5.7的相关操作,以及mha的切换开启工作。需要注意的是,mysql无密码登陆用了mysql_config_editor和远程开启mha后台运行程序时用到的进程监控(deamon运行)Supervisor程序。