关于Python自动化的话题,上一篇介绍了Invoke库,它是Fabric最重要的组件之一。Fabric也是一个被广泛使用的自动化工具库,是不得不提的自动化运维工具。因此,本文将在以后进行介绍。Fabric主要用于自动化应用程序部署和系统管理等任务。简单轻量,提供丰富的SSH扩展接口。在Fabric1.x版本中,它混合了本地和远程功能;但是从Fabric2.x版本开始,分离出了独立的Invoke库来处理本地的自动化任务,而Fabric则专注于网络层面的远程和远程任务。为了实现这一点,Fabric主要依赖另一个核心组件Paramiko,它是一个基于SSH协议的远程控制模块。Fabric在此基础上封装了更加友好的接口,可以远程执行Shell命令、传输文件、批量操作服务器、身份验证、各种配置和设置代理等等。1、FabricPython2版本的版本区分已经在今年元旦正式宣布“退役”,未来只会是Python3的阶段。为了适应Python的非兼容迁移版本,很多项目也必须推出自己的新版本(兼容或只支持Python3),包括本文的主角Fabric。Fabric本身有两个大版本:Fabric1和Fabric2,而在这个库的基础上,还有两个容易混淆的相关库:Fabric2和Fabric3(注意这里的数字是库名的一部分)。它们的区别如下:Fabric1.x:支持Python2.5-2.7,不支持Python3Fabric2.x:支持Python2.7和3.4+,但不兼容Fabric1.x的fabfileFabric2:相当于Fabric2。x,forMakedifferentversionscoexist(installanold1.xversion,andtheninstallitasanewversion)Fabric3:基于Fabric1.x的fork(非官方),兼容Python2&3,兼容Fabric1.xfabfile综上所述,我们可以看出,建议使用官方的Fabric2.x系列版本,但同时需要注意的是,一些过时的教程可能是基于更早的版本(或者非官方的Fabric3,这也是基于Fabric1.x),需要识别。比如在Fabric1.x系列中,import这样写:fromfabric.apiimportrun;新版本会报错:"ImportError:Nomodulenamedapi"(PS:可以根据是否有fabric.api来判断Fabric的版本,就像从print语句或print函数判断版本一样在Python中)。同时,由于新版本不支持旧版本的fabfile,使用时可能会报错:“Noideawhat'xxx'is!”Fabric2是一个不兼容的版本。与上一版本相比,其主要改进有:支持Python2.7和3.4+线程安全,取消多进程并发实现API围绕fabric.connection.Connection重新组织完全修改命令行解析器,允许常规GNU/POSIX风格taskbasisFlagsandoptions(fabmytask:weird=custom,arg=formatarenolongerrequired)可以声明pre-tasks和post-tasks...(官方列出了10多个[1],本文不一一列举byone)在Fabric2的开发过程中分离Invoke之前引入的,具体原因可以参考这个回答[2]。总之,在使用Fabric的时候,要注意版本差异的问题。二、Fabric的基本使用1、安装首先安装:pipintallfabric。安装完成后可以在命令行窗口查看版本信息:>>>fab-VFabric2.5.0Paramiko2.7.1Invoke1.4.0执行“fab-V”从上面的结果可以看出我安装的是Fabric2.5.0版本,同时可以看到它的两个核心依赖库Paramiko和Invoke的版本信息。2.一个简单的例子Fabric主要用于远程任务,即操作远程服务器,下面是一个简单的例子:#任意文件名都可以fromfabricimportConnectionhost_ip='47.xx.xx.xx'#服务器地址user_name='root'#服务器用户名password='****'#服务器密码cmd='date'#shell命令查询服务器时间con=Connection(host_ip,user_name,connect_kwargs={'password':password})result=con.run(cmd,hide=True)print(result)以上代码,通过账号+密码登录远程服务器,然后执行date命令查看服务器时间,执行结果:Commandexitedwithstatus0.===stdout===FriFeb1415:33:05CST2020(nostderr)现在打印的结果中,除了服务器时间,还有一些不相关的信息。这是因为它打印的“result”是一个“fabric.runners.Result”类,我们可以解析其中的信息:print(result.stdout)#FriFeb1415:33:05CST2020print(result.exited)#0print(result.ok)#Trueprint(result.failed)#Falseprint(result.command)#dateprint(result.connection.host)#47.xx.xx.xx以上代码使用了Connection类及其run()方法在连接的服务器上运行shell命令。如果需要使用管理员权限,需要换成sudo()方法。如果想在本地执行shell命令,需要将其替换为local()方法。此外,还有get()、put()等方法,详见下文介绍。3、命令行使用上面例子中的代码可以写在任何.py脚本中,然后运行脚本,也可以稍微封装一下,导入到其他脚本中使用。另外,Fabric也是一个命令行工具,可以通过fab命令来执行任务。我们稍微修改一下上面例子的代码:#filename:fabfile.pyfromfabricimportConnectionfromfabricimporttaskhost_ip='47.xx.xx.xx'#服务器地址user_name='root'#服务器用户名password='****'#服务器密码cmd='date'#用于查询服务器时间的shell命令@taskdeftest(c):"""从远程主机获取日期。"""con=Connection(host_ip,user_name,connect_kwargs={'password':password})result=con.run(cmd,hide=True)print(result.stdout)#只打印解释的时候,主要变化是:fabfile.py文件名:脚本名入口代码必须使用Thisname@taskdecorator:这个装饰器需要从fabric引入,它是invoke的@task装饰器的封装,实际用法和invoke一样(注:还需要一个context参数"c",但实际上它并没有在代码块中使用,而是使用了Connection类的一个实例)然后,在脚本同级目录的命令行窗口中,可以查看并执行相应的任务:>>>fab-lAvailabletasks:testGetdatefromremotehost.>>>fabtestFriFeb1416:10:24CST2020fab是Invoke的扩展实现,继承了很多原有的功能,所以执行“fab--help”与之前介绍的“inv--help”相比,你会发现它们的很多参数和解释是完全一样的。针对远程服务场景,fab增加了几个命令行选项(蓝色标注),其中:--prompt-for-login-password:让程序在命令行输入SSH登录密码(上面的例子在代码中指定connect_kwargs.password参数,如果使用该选项,执行时可以手动输入密码)--prompt-for-passphrase:让程序在命令行输入SSH私钥加密文件的路径-H或--hosts:指定要连接的主机名-i或--i??dentity:指定SSH连接使用的私钥文件-S或--ssh-config:指定运行时加载的SSH配置文件有关命令的更多信息Fabric文档[3]的行接口。4.交互操作如果远程服务器上有交互提示,要求输入密码或者“yes”等信息,这就需要Fabric能够监听和响应。下面是一个简单的例子。引入invoke的Responder,初始化内容为常规字符串和响应信息,最后赋值给watchers参数:frominvokeimportResponderfromfabricimportConnectionc=Connection('host')sudopass=Responder(pattern=r'\[sudo\]password:',response='mypassword\n')c.run('sudowhoami',pty=True,watchers=[sudopass])5.文件传输在本地和服务器之间传输文件是一种常见的用法。Fabric在这方面做了很好的封装。Connection类中有以下两个方法:get(args,*kwargs):将远程文件拉到本地文件系统或类文件对象put(args,*kwargs):将本地文件或类文件对象推送到建立连接后的远程文件系统,示例:#(省略)con.get('/opt/123.txt','123.txt')con.put('test.txt','/opt/test.txt')第一个参数是指要传输的源文件,第二个参数是要传输的目标,可以指定为文件名或文件夹(当为空或无时,使用defaultpath):#(省略)con.get('/opt/123.txt','')#为空时使用默认路径con.put('test.txt','/opt/')#指定路径/opt/get()方法默认存放路径为os.getcwd,put()方法默认存放路径为home目录。6、服务器批量操作对于服务器集群的批量操作,最简单的实现方式是使用for循环,然后建立连接,一个一个执行操作,类似这样:forhostin('web1','web2','mac1'):result=Connection(host).run('uname-s')但是有时候,这个方案会存在一个问题:如果有多组不同的服务器集群需要执行不同的操作,那么你需要写很多for循环。如果要将各组操作的结果进行聚合(如字典形式、key-host、value-result),则必须在for循环外加入额外的操作。for循环顺序同步执行,效率太低,缺乏异常处理机制(如果中间出现异常,会导致后续操作跳出)针对这些问题,Fabric提出了Group的概念,可以将一组主机定义为一个Group。它的API方法和Connection一样,就是一个Group可以简化为一个Connection。然后,开发者只需要简单的操作Group,最终得到一个结果集,减少了异常处理和执行顺序的工作量。Fabric提供了一个fabric.group.Group基类,并从中派生了两个子类。区别在于:SerialGroup(hosts,*kwargs):以串行方式执行操作ThreadingGroup(hosts,*kwargs):并发执行操作组的类型决定了主机集群的操作方式,我们只需要进行选择即可。然后,他们的执行结果是一个fabric.group.GroupResult类,它是dict的子类,里面存放着每一个主机连接与其执行结果的对应关系。>>>fromfabricimportSerialGroup>>>results=SerialGroup('web1','web2','mac1').run('uname-s')>>>print(results)
