<python模块>fabric
目录:
什么是fabric
fabric封装了paramiko,可以执行进行本地执行和远程执行,我们用来和saltstack组成互备方案,这样确保有批量工具。
安装fabric
pip install fabric
简单的例子
需要创建一个fabfile.py文件,fabric默认会找这个文件
[root@why 18:25:15 ~]#vi fabfile.py
def hello():
print "Hello Fabric!"
执行fab命令
[root@why 18:25:33 ~]#fab hello
Hello Fabric!
Done.
在fabfile.py文件中每一个方法都是一个任务,任务名就是函数名,可以通过-l
来参数来列出所有任务。
[root@why 18:26:21 ~]#fab -l
Available commands:
hello
包括对于输入错的命令也可以提示,具体原因未知。
[root@why 20:44:33 ~]#fab hoell
Warning: Command(s) not found:
hoell
Available commands:
hello
对于传递参数的函数可以直接在方法传入
[root@why 19:08:59 ~]#vi fabfile.py
def hello(name, value):
print "Hello Fabric!%s. %s" % (name, value)
[root@why 19:46:57 ~]#fab hello:why,study
Hello Fabric!why. study
Done.
如果不使用fabfile.py文件,可以通过-f参数指定脚本文件
fab -f script.py hello
执行本地命令
本机的命令通过fabric.api中的local()方法提供,用来执行shell命令
[root@why 20:44:33 ~]#vi fabfile.py
from fabric.api import local
def hello():
local('df -h')
[root@why 20:44:38 ~]#fab hello
[localhost] local: df -h
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 40G 29G 9.0G 77% /
devtmpfs 3.9G 0 3.9G 0% /dev
tmpfs 3.9G 0 3.9G 0% /dev/shm
tmpfs 3.9G 3.3M 3.9G 1% /run
tmpfs 3.9G 0 3.9G 0% /sys/fs/cgroup
tmpfs 783M 0 783M 0% /run/user/1001
Done.
默认输出会被打印到控制台,而local提供了参数来获取标准输出
[root@why 20:55:47 ~]#vi fabfile.py
from fabric.api import local
def hello():
output = local('df -h', capture=True)
[root@why 20:56:01 ~]#fab hello
[localhost] local: df -h
Done.
可以看到仍然还是有信息输出的
远程执行
from fabric.api import run, env
env.hosts = ['whysdomain']
env.user = 'wanghongyu'
env.password = '123456'
env.port = '23'
def hello():
run('whoami')
执行fab task
[root@why 18:56:22 ~]#fab hello
[whysdomain] Executing task 'hello'
[whysdomain] run: whoami
[whysdomain] out: wanghongyu
[whysdomain] out:
Done.
Disconnecting from whysdomain:23... done.
host可以直接写入到fabfile中,也可以通过-H
参数指定
[root@why 18:56:26 ~]#vi fabfile.py
[root@why 19:02:44 ~]#fab -H 47.52.207.229 hello
[whysdomain] Executing task 'hello'
[whysdomain] run: whoami
[whysdomain] out: mrwhy
[whysdomain] out:
Done.
Disconnecting from whysdomain:23... done.
多台主机用',
'隔开即可,这些主机之间执行是串行执行的。
对于不同主机执行不同任务,可以对服务器角色进行定义
from fabric.api import env, roles, run, execute, cd
env.roledefs = {
'staging': ['bjhee@example1.com','bjhee@example2.com'],
'build': ['build@example3.com']
}
env.passwords = {
'staging': '11111',
'build': '123456'
}
@roles('build')
def build():
with cd('/home/build/myapp/'):
run('git pull')
run('python setup.py')
@roles('staging')
def deploy():
run('tar xfz /tmp/myapp.tar.gz')
run('cp /tmp/myapp /home/bjhee/www/')
def task():
execute(build)
execute(deploy)
执行task
fab task
fab会先执行build,根据装饰器@roles('build')
找到env.roledefs中记录的主机,执行build方法的操作,然后执行deploy,根据装饰器@roles('staging')
找到env.roledefs中记录的主机['bjhee@example1.com','bjhee@example2.com'],执行deploy方法的操作。
如果任务没有指定角色,也可以通过-R参数来指定执行的主机列表,对于多个角色也要用',
'分割
fab -R build deploy
注意: 'staging'角色是装饰器默认的,不通过-R指定也执行。
对于上边的fabfile来说,fab会对于build角色和staging都执行了deploy操作。
SSH功能函数
Fabric还提供了其他丰富的功能函数来辅助执行命令,这里我们介绍几个常用的:
sudo: 以超级用户权限执行远程命令
功能类似于”run()”方法,区别是它相当于在Shell命令前加上了”sudo”,所以拥有超级用户的权限。使用此功能前,你需要将你的用户设为sudoer,而且无需输密码。
from fabric.api import env, sudo
env.hosts = ['bjhee@example1.com', 'bjhee@example2.com']
env.password = '111111'
def hello():
sudo('mkdir /var/www/myapp')
get(remote, local): 从远程机器上下载文件到本地
它的工作原理是基于scp命令,使用的方法如下:
from fabric.api import env, get
env.hosts = ['bjhee@example.com',]
env.password = '111111'
def hello():
get('/var/log/myapp.log', 'myapp-0301.log')
上述任务将远程机上”/var/log/myapp.log”文件下载到本地当前目录,并命名为”myapp-0301.log”。
put(local, remote): 从本地上传文件到远程机器上
同get一样,put方法也是基于scp命令,使用的方法如下:
from fabric.api import env, put
env.hosts = ['bjhee@example1.com', 'bjhee@example2.com']
env.password = '111111'
def hello():
put('/tmp/myapp-0301.tar.gz', '/var/www/myapp.tar.gz')
上述任务将本地”/tmp/myapp-0301.tar.gz”文件分别上传到两台远程机的”/var/www/”目录下,并命名为”myapp.tar.gz”。如果远程机上的目录需要超级用户权限才能放文件,可以在”put()”方法里加上”use_sudo”参数:
put('/tmp/myapp-0301.tar.gz', '/var/www/myapp.tar.gz', use_sudo=True)
prompt: 提示输入
该方法类似于Shell中的”read”命令,它会在终端显示一段文字来提示用户输入,并将用户的输入保存在变量里:
from fabric.api import env, get, prompt
env.hosts = ['bjhee@example.com',]
env.password = '111111'
def hello():
filename = prompt('Please input file name: ')
get('/var/log/myapp.log', '%s.log' % filename)
现在下载后的文件名将由用户的输入来决定。我们还可以对用户输入给出默认值及类型检查:
port = prompt('Please input port number: ', default=8080, validate=int)
执行任务后,终端会显示:
Please input port number: [8080]
如果你直接按回车,则port变量即为默认值8080;如果你输入字符串,终端会提醒你类型验证失败,让你重新输入,直到正确为止。
reboot: 重启服务器
有时候安装好环境后,需要重启服务器,这时就要用到”reboot()”方法,你可以用”wait”参数来控制其等待多少秒后重启,没有此参数则代表立即重启:
from fabric.api import env, reboot
env.hosts = ['bjhee@example.com',]
env.password = '111111'
def restart():
reboot(wait=60)
上面的restart任务将在一分钟后重启服务器。
上下文管理器
Fabric的上下文管理器是一系列与Python的”with”语句配合使用的方法,它可以在”with”语句块内设置当前工作环境的上下文。
cd: 设置远程机器的当前工作目录
cd()
方法在之前的范例中出现过,”with cd()”语句块可以用来设置远程机的工作目录:
from fabric.api import env, cd, put
env.hosts = ['bjhee@example1.com', ]
env.password = '111111'
def hello():
with cd('/var/www/'):
put('/tmp/myapp-0301.tar.gz', 'myapp.tar.gz')
上例中的文件会上传到远程机的/var/www/
目录下。出了”with cd()”语句块后,工作目录就回到初始的状态,也就是”bjhee”用户的根目录。
lcd: 设置本地工作目录
lcd()
就是”local cd”的意思,用法同”cd()”一样,区别是它设置的是本地的工作目录:
from fabric.api import env, cd, lcd, put
env.hosts = ['bjhee@example1.com', ]
env.password = '111111'
def hello():
with cd('/var/www/'):
with lcd('/tmp/'):
put('myapp-0301.tar.gz', 'myapp.tar.gz')
这个例子的执行效果跟上个例子一样。
path: 添加远程机的PATH路径
from fabric.api import env, run, path
env.hosts = ['bjhee@example1.com', ]
env.password = '111111'
def hello():
with path('/home/bjhee/tmp'):
run('echo $PATH')
run('echo $PATH')
假设我们的PATH环境变量默认是/sbin:/bin
,在上述with path()
语句块内PATH变量将变为/sbin:/bin:/home/bjhee/tmp
。出了with语句块后,PATH又回到原来的值。
settings: 设置Fabric环境变量参数
Fabric环境变量即是我们例子中一直出现的fabric.api.env
,它支持的参数可以从官方文档中查到。
from fabric.api import env, run, settings
env.hosts = ['bjhee@example1.com', ]
env.password = '111111'
def hello():
with settings(warn_only=True):
run('echo $USER')
环境参数”warn_only”暂时设为True,这样遇到错误时任务不会退出。
shell_env: 设置Shell环境变量
可以用来临时设置远程和本地机上Shell的环境变量。
from fabric.api import env, run, local, shell_env
env.hosts = ['bjhee@example1.com', ]
env.password = '111111'
def hello():
with shell_env(JAVA_HOME='/opt/java'):
run('echo $JAVA_HOME')
local('echo $JAVA_HOME')
prefix: 设置命令执行前缀
from fabric.api import env, run, local, prefix
env.hosts = ['bjhee@example1.com', ]
env.password = '111111'
def hello():
with prefix('echo Hi'):
run('pwd')
local('pwd')
在上述with prefix()
语句块内,所有的run()
或local()
方法的执行都会加上echo Hi &&
前缀,也就是效果等同于:
run('echo Hi && pwd')
local('echo Hi && pwd')
配合后一节我们会讲到的错误处理,它可以确保在”prefix()”方法上的命令执行成功后才会执行语句块内的命令。
错误处理
默认情况下,Fabric在任务遇到错误时就会退出,如果我们希望捕获这个错误而不是退出任务的话,就要开启warn_only
参数。在上面介绍settings()
上下文管理器时,我们已经看到了临时开启warn_only
的方法了,如果要全局开启,有两个办法:
- 在执行
fab
命令时加上-w
参数
fab -w hello
- 设置
env.warn_only
环境参数为True
from fabric.api import env
env.warn_only = True
现在遇到错误时,控制台会打出一个警告信息,然后继续执行后续任务。那我们怎么捕获错误并处理呢?像run()
,local()
,sudo()
,get()
,put()
等SSH功能函数都有返回值。当返回值的succeeded
属性为True时,说明执行成功,反之就是失败。你也可以检查返回值的failed
属性,为True时就表示执行失败,有错误发生。在开启warn_only
后,你可以通过failed
属性检查捕获错误,并执行相应的操作。
from fabric.api import env, cd, put
env.hosts = ['bjhee@example1.com', ]
env.password = '111111'
def hello():
with cd('/var/www/'):
upload = put('/tmp/myapp-0301.tar.gz', 'myapp.tar.gz')
if upload.failed:
sudo('rm myapp.tar.gz')
put('/tmp/myapp-0301.tar.gz', 'myapp.tar.gz', use_sudo=True)
并行执行
多台机器的任务默认情况下是串行执行的。Fabric支持并行任务,当服务器的任务之间没有依赖时,并行可以有效的加快执行速度。怎么开启并行执行呢?
办法也是两个:
- 在执行
fab
命令时加上-P
参数
fab -P hello
貌似还有-z
参数
- 设置
env.parallel
环境参数为True
from fabric.api import env
env.parallel = True
如果,我们只想对某一任务做并行的话,我们可以在任务函数上加上”@parallel”装饰器
from fabric.api import parallel
@parallel
def runs_in_parallel():
pass
def runs_serially():
pass
这样即便并行未开启,runs_in_parallel()
任务也会并行执行。
反过来,我们可以在任务函数上加上@serial
装饰器:
from fabric.api import serial
def runs_in_parallel():
pass
@serial
def runs_serially():
pass
这样即便并行已经开启,runs_serially()
任务也会串行执行。
补充
终端输出带颜色
我们习惯上认为绿色表示成功,黄色表示警告,而红色表示错误,Fabric支持带这些颜色的输出来提示相应类型的信息
from fabric.colors import *
def hello():
print green("Successful")
print yellow("Warning")
print red("Error")
限制任务只能被执行一次
通过execute()
方法,可以在一个fab
命令中多次调用同一任务,如果想避免这个发生,就要在任务函数上加上@runs_once
装饰器。
from fabric.api import execute, runs_once
@runs_once
def hello():
print "Hello Fabric!"
def test():
execute(hello)
execute(hello)
现在不管我们”execute”多少次hello任务,都只会输出一次”Hello Fabric!”字样