1. 安装Fabric
首先Python的版本必须是2.7以上,可以通过下面的命令查看当前Python的版本:
$ python -V
Fabric的官网是http://www.fabfile.org/,源码托管在Github上。你可以clone源码到本地,然后通过下面的命令来安装。
$ python setup.py develop
在执行源码安装前,你必须先将Fabric的依赖包Paramiko装上。所以,个人还是推荐使用pip安装,只需一条命令即可:
$ pip install fabric
2. 第一个例子
万事从”Hello World”开始,我们创建一个”fabfile.py”文件,然后写个hello函数:
def hello(): print "Hello Fabric!"
现在,让我们在”fabfile.py”的目录下执行命令:
$ fab hello
你可以在终端看到”Hello Fabric!“字样。
简单解释下,”fabfile.py”文件中每个函数就是一个任务,任务名即函数名,上例中是”hello”。”fab”命令就是用来执行”fabfile.py”中定义的任务,它必须显式地指定任务名。你可以使用参数”-l”来列出当前”fabfile.py”文件中定义了哪些任务:
$ fab -l
任务可以带参数,比如我们将hello函数改为:
def hello(name, value): print "Hello Fabric! %s=%s" % (name,value)
此时执行hello任务时,就要传入参数值:
$ fab hello:name=Year,value=2016
Fabric的脚本建议写在”fabfile.py”文件中,如果你想换文件名,那就要在”fab”命令中用”-f”指定。比如我们将脚本放在”script.py”中,就要执行:
$ fab -f script.py hello
3. 执行本地命令
“fabric.api”包里的local()
方法可以用来执行本地Shell命令,比如让我们列出本地”/home/bjhee”目录下的所有文件及目录:
from fabric.api import local def hello(): local('ls -l /home/bjhee/')
local()
方法有一个capture
参数用来捕获标准输出,比如:
def hello(): output = local('echo Hello', capture=True)
这样,Hello字样不会输出到屏幕上,而是保存在变量output里。capture
参数的默认值是False。
4. 执行远程命令
Fabric真正强大之处不是在执行本地命令,而是可以方便的执行远程机器上的Shell命令。它通过SSH实现,你需要的是在脚本中配置远程机器地址及登录信息:
from fabric.api import run, env env.hosts = ['example1.com', 'example2.com'] env.user = 'bjhee' env.password = '111111' def hello(): run('ls -l /home/bjhee/')
fabric.api
包里的run()
方法可以用来执行远程Shell命令。上面的任务会分别到两台服务器”example1.com”和”example2.com”上执行ls -l /home/bjhee/
命令。这里假设两台服务器的用户名都是”bjhee”,密码都是6个1。你也可以把用户直接写在hosts里,比如:
env.hosts = ['bjhee@example1.com', 'bjhee@example2.com']
如果你的env.hosts
里没有配置某个服务器,但是你又想在这个服务器上执行任务,你可以在命令行中通过-H
指定远程服务器地址,多个服务器地址用逗号分隔:
$ fab -H bjhee@example3.com,bjhee@example4.com hello
另外,多台机器的任务是串行执行的,关于并行任务的执行我们在之后会介绍。
如果对于不同的服务器,我们想执行不同的任务,上面的方法似乎做不到,那怎么办?我们要对服务器定义角色:
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)
现在让我们执行:
$ fab task
这时Fabric会先在一台”build”服务器上执行build
任务,然后在两台”staging”服务器上分别执行deploy
任务。@roles
装饰器指定了它所装饰的任务会被哪个角色的服务器执行。
如果某一任务上没有指定某个角色,但是你又想让这个角色的服务器也能运行该任务,你可以通过-R
来指定角色名,多个角色用逗号分隔:
$ fab -R build deploy
这样”build”和”staging”角色的服务器都会运行deploy
任务了。注:”staging”是装饰器默认的,因此不用通过-R
指定。
此外,上面的例子中,服务器的登录密码都是明文写在脚本里的。这样做不安全,推荐的方式是设置SSH自动登录,具体方法大家可以去网上搜搜。
5. SSH功能函数
到目前为止,我们介绍了local()
和run()
函数分别用来执行本地和远程Shell命令。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
任务将在一分钟后重启服务器。
6. 上下文管理器
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()
方法上的命令执行成功后才会执行语句块内的命令。
7. 错误处理
默认情况下,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)
8. 并行执行
我们在介绍执行远程命令时曾提到过多台机器的任务默认情况下是串行执行的。Fabric支持并行任务,当服务器的任务之间没有依赖时,并行可以有效的加快执行速度。怎么开启并行执行呢?办法也是两个:
-
在执行
fab
命令时加上-P
参数$ fab -P hello
-
设置
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()
任务也会串行执行。
9. 补充
这个部分用来补充Fabric的一些特别功能:
- 终端输出带颜色
我们习惯上认为绿色表示成功,黄色表示警告,而红色表示错误,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!“字样
更多内容请参阅Fabric的官方文档。
1. Fabric的任务运行规则 根据Fabric Execution model的说明,fabric默认以串行方式运行tasks,具体而言: 1)在fabfile及其import文件中定义的task对象依次被创建(只是创建对象,并未真正执行),任务之间保持其定义的先后顺序。 2)对于每个task,生成将要运行该task的目标机器列表。 3)fab执行tasks时,按任务被指定的顺序依次执行这些任务;针对每个任务,依次在其指定的目标机器运行且只运行一次。 4)未指定目标机器的task被当作本地任务运行,且只会被运行一次。 假设在fabfile.py中定义了如下tasks: from fabric.api import run, env env.hosts = ['host1', 'host2'] def taskA(): run('ls') def taskB(): run('whoami') 1 2 3 4 5 6 7 8 9 在终端运行fab –list时,我们会看到taskA和taskB两个任务,运行之: $ fab taskA taskB 1 结果示例如下: taskA executed on host1 taskA executed on host2 taskB executed on host1 taskB executed on host2 1 2 3 4 通过上面的实例,大家应该可以明白fabric默认的串行执行策略是怎么回事。 Fabric还允许我们指定以并行方式(借助multiprocessing模块实现多个进程并行执行)在多台机器上并行地运行任务,甚至还可在同一个fabfile文件中指定某些task以并行方式运行,而某些task以默认的串行方式运行。具体地,可以借助@parallel或@serial指定任务的运行模式,还可以在命令行中通过-P参数指定任务是否要并性执行。示例如下: from fabric.api import * @parallel def runs_in_parallel(): pass def runs_serially(): pass 1 2 3 4 5 6 7 8 当运行如下命令时: $ fab -H host1,host2,host3 runs_in_parallel runs_serially 1 执行结果示例如下: runs_in_parallel on host1, host2, and host3 runs_serially on host1 runs_serially on host2 runs_serially on host3 1 2 3 4 此外,还可以通过对@parallel传入pool_size参数来控制并行进程数以防并行进程太多把机器拖垮。 2. 为task指定目标机器 有多种方式可以指定任务的将要运行的目标机器,下面分别进行说明。 1)通过env.hosts或env.roles进行全局指定 Fabric的env模块中定义了一系列全局变量,可以将其理解为可以控制fabric行为的环境变量。其中env.hosts和env.roles可以用来全局指定task的目标机器列表,这两个“环境变量”的默认值都是空列表[]。 env.hosts的元素是fabric约定的”host strings”,每个host strings由username@hostname:port三部分构成,其中username和port部分可以缺省。本篇笔记前面的第1个代码实例其实已经说明了如何用env.hosts全局地指定task的目标机器列表,这里不再赘述。 env.roles则是在配置了env.roledefs的情况下才有用武之地。在很多时候,不同的机器有着不同的角色,如有些是接入层,有些是业务层,有些是数据存储层。env.roledefs可以用来组织这些机器列表以体现其角色,示例如下: from fabric.api import env env.roledefs = { 'web': { 'hosts': ['www1', 'www2', 'www3'], }, 'db': { 'hosts': ['db1', 'db2'], } } @roles('web') def mytask(): run('ls /var/www') 1 2 3 4 5 6 7 8 9 10 11 12 13 14 上例通过env.roledefs配置了两个角色web和db,分别包含3台、2台机器,并借助@roles为mytask指定了目标机器列表。 2)通过命令行进行全局指定 $ fab -H host1,host2 mytask 1 需要注意的是,命令行通过-H参数指定的机器列表在fabfile脚本load前被解释,故如果fabfile中重新配置了env.hosts或env.roles,则命令行指定的机器列表会被覆盖。为了避免fabfile覆盖命令行参数,在fabfile中应该借助list.extend()指定env.hosts或env.roles,示例如下: from fabric.api import env, run env.hosts.extend(['host3', 'host4']) def mytask(): run('ls /var/www') 1 2 3 4 5 6 此时,当我们运行”fab -H host1,host2 mytask”时,env.hosts包含来自命令行和fabfile的4台机器。 3)通过命令行为每个任务指定机器列表 $ fab mytask:hosts="host1;host2" 1 上述方式会覆盖全局指定的机器列表,确保mytask只会在host1, host2上执行。 4)借助装饰器@hosts为每个任务指定目标机器 from fabric.api import hosts, run @hosts('host1', 'host2') def mytask(): run('ls /var/www') 1 2 3 4 5 或者: my_hosts = ('host1', 'host2') @hosts(my_hosts) def mytask(): # ... 1 2 3 4 每个任务的@hosts装饰器指定的机器列表会覆盖全局目标机器列表,但不会覆盖通过命令行为该任务单独指定的目标机器列表。 上述4种为task指定目标机器列表的方式之间的优先级规则总结如下: 1) Per-task, command-line host lists (fab mytask:host=host1) override absolutely everything else. 2) Per-task, decorator-specified host lists (@hosts(‘host1’)) override the env variables. 3) Globally specified host lists set in the fabfile (env.hosts = [‘host1’]) can override such lists set on the command-line, but only if you’re not careful (or want them to.) 4) Globally specified host lists set on the command-line (–hosts=host1) will initialize the env variables, but that’s it. 截止目前,我们可以看到,fabric允许我们混合使用上面列出的几种目标机器指定方式,但是我们要明白混合的结果是否符合预期。 此外,fabric默认会对通过不同来源出现多次的同一个目标机器做去重,当然,可以通过设置env.dedupe_hosts为False来关闭默认的去重策略。甚至还可以指定任务需要跳过的机器列表。具体细节可以参考Fabric Execution model的说明,这里不赘述。 3. 任务执行时,目标机器的密码管理 如果你亲自运行上面的示例代码,就会发现,每次在目标机器远程执行task时,fabric均会要求输入目标机器的登录名及密码。如果要在多台机器上执行task,那这些密码输入的过程可以自动化吗? 答案是肯定的。实现方式有两种,下面分别进行说明。 1)通过env.password或env.passwords配置目标机器的登录信息 下面的示例说明了如何通过env.passwords配置多台机器的登录信息: #!/bin/env python #-*- encoding: utf-8 -*- from fabric.api import run, env, hosts ## 需要注意的是,这里的host strings必须由username@host:port三部分构成,缺一不可,否则运行时还是会要求输入密码 env.passwords = { 'slvher@10.123.11.209:22': 'xxx', 'work@10.123.11.210:23': 'yyy', } @hosts('10.123.11.209', '10.123.11.210') def host_os_type(): run('uname -a') 1 2 3 4 5 6 7 8 9 10 11 12 13 14 由于通过env.passwords配置了目标机器的登录用户名/密码,所以,当我们在终端运行fab host_os_type时,会发现不用手动输入密码了,大大方便了脚本远程自动执行。 但是,这种明文指定登录名/密码的方式存在安全性问题,所以,fabric还支持以ssh key认证的方式免密在远程机器执行任务。 在具体实现上,需要事先在目标机器上生成ssh public key并配置在~/.ssh/config文件中,然后在定义任务的fabfile中将env.use_ssh_config设置为True来启用基于ssh public key方式的身份认证,以便实现免密码远程执行任务。 具体细节可以参考Fabric Leveraging native SSH config files文档的说明,或者参考StackOverflow的这篇帖子Connecting to a host listed in ~/.ssh/config when using Fabric,限于篇幅,这里不再赘述。 最后,推荐DigitalOcean上一篇Fabric教程How To Use Fabric To Automate Administration Tasks And Deployments,浅显易懂,值得一读。Enjoy it. 参考资料 [1] Fabric Doc: Execution model [2] Fabric Doc: Parallel execution [3] Fabric Doc: The environment dictionary, env [4] Fabric Doc: Password management [5] Fabric Doc: Leveraging native SSH config files [6] StackOverflow: Connecting to a host listed in ~/.ssh/config when using Fabric [7] DigitalOcean: How To Use Fabric To Automate Administration Tasks And Deployments =================== EOF ==================
使用Fabric远程部署Flask应用 同样,你需要先了解如何使用Fabric来远程部署Python应用。然后,我们来编写”fabfile.py”文件: from fabric.api import * env.hosts = ['example1.com', 'example2.com'] env.user = 'bjhee' def package(): local('python setup.py sdist --formats=gztar', capture=False) def deploy(): dist = local('python setup.py --fullname', capture=True).strip() put('dist/%s.tar.gz' % dist, '/tmp/myapp.tar.gz') run('mkdir /tmp/myapp') with cd('/tmp/myapp'): run('tar xzf /tmp/myapp.tar.gz') run('/home/bjhee/virtualenv/bin/python setup.py install') run('rm -rf /tmp/myapp /tmp/myapp.tar.gz') run('touch /var/www/myapp.wsgi') 上例中,”package”任务是用来将应用程序打包,而”deploy”任务是用来将Python包安装到远程服务器的虚拟环境中,这里假设虚拟环境在”/home/bjhee/virtualenv”下。安装完后,我们将”/var/www/myapp.wsgi”文件的修改时间更新,以通知WSGI服务器(如Apache)重新加载它。对于非WSGI服务器,比如uWSGI,这条语句可以省去。 编写完后,运行部署脚本测试下: $ fab package deploy
使用中报错解决
错误:No module named 'fabric.api'
主要是版本太新,选择fabric2以下版本

错误:No module named 'fabric.api',原因---fabric(2.0新特性) fabric经常出现在自动化运维领域,批量处理一些运维工作。fabric是在paramiko之上又封装了一层,操作起来更加简单易用。 本来只是想写个博客记录一下,然后发现之前写的代码不能运行了,报以下错误:No module named 'fabric.api' Traceback (most recent call last): File "D:/PycharmProjects/TestTool/Publish/fabric_sample.py", line 3, in <module> from fabric.api import * ModuleNotFoundError: No module named 'fabric.api' 然后百度了下,说是2.0以上的fabric版本已经移除了fabric.api,解决方法是指定安装2.0以下的版本,比如pip install fabric==1.14.0 ~_~ ,但都并没有介绍新的特性。然后去官网看了下文档,使用方法如下: 1.连接服务器 通过fabric.Connection方法连接服务器: conn = fabric.Connection(host , user = 'root',port = 22, config = None, geteway = None, connect_kwargs={"password": "123456"}) Connection参数的含义: 复制代码 复制代码 def __init__( self, host, #主机ip user=None, #用户名 port=None, #ssh端口,默认是22 config=None, #登录配置文件 gateway=None, #连接网关 forward_agent=None, #是否开启agent forwarding connect_timeout=None, #设置超时 connect_kwargs=None, #设置 密码登录connect_kwargs={"password": "123456"}) 还是 密钥登录connect_kwargs={"key_filename": "/home/myuser/.ssh/private.key"} inline_ssh_env=None, ) 复制代码 复制代码 2.通过fabric安装软件 下面通过一段代码实现在Ubuntu上批量安装docker,顺序取走列表中的ip。 复制代码 复制代码 import fabric def docker_install(): for host in ['10.1.4.24', '10.1.15.154', '10.1.14.106']: conn = fabric.Connection(host , user = 'root', connect_kwargs={"password": "123456"}) print("%s:" % host) conn.run("echo Y|apt-get remove docker.io") docker_install() 复制代码 复制代码 是不是很简单,几行代码就解决了~ cnn获取Connetction对象后,有很多属性方法可以使用,介绍下几个常用的: 复制代码 复制代码 run: #执行远程命令,如:run('uname -a') cd: #切换远程目录,如:cd('/root'); with conn.cd('/root'):继承这个状态 put: #上传本地文件到远程主机,如:put('/root/test.py','/root/test.py') get: #获取服务器上文件,如:get('/root/project/test.log') sudo: #sudo方式执行远程命令,如:sudo('service docker start') 复制代码 复制代码 全部属性: 调用本地命令: import invoke invoke.run('uname -a')