当前位置: 首页 > 科技观察

如何高效远程部署?自动化运维利器Fabric教程

时间:2023-03-21 15:48:09 科技观察

关于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,为了让不同版本共存(安装旧版本的1.x,然后安装为新版本)Fabric3:基于Fabric1.x的fork(非官方),兼容Python2&3,compatiblewithFabric1.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=format不再需要)可以声明pre-tasks和post-tasks...(官方列出了10多个[1],本文就不一一列举了他们一一介绍)在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'#serverusernamepassword='****'#serverpasswordcmd='date'#shell命令,查询服务器上的时间con=Connection(host_ip,user_name,connect_kwargs={'password':password})result=con.run(cmd,hide=True)print(result)以上代码,log通过账号+密码in到远程服务器,然后执行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命令来执行任务。我们稍微修改一下上面例子的代码:#文件名:fabfile.pyfromfabricimportConnectionfromfabricimporttaskhost_ip='47.xx.xx.xx'#服务器地址user_name='root'#服务器用户名密码='****'#服务器密码cmd='date'#shell命令,查询服务器上的时间@taskdeftest(c):"""Getdatefromremotehost."""con=Connection(host_ip,user_name,connect_kwargs={'password':password})result=con.run(cmd,hide=True)print(result.stdout)#只打印时间说明,主要变化是:fabfile.py文件名:入口代码的脚本名必须使用这个名字@task装饰器:需要从fabric中引入这个装饰器,它是对invoke的@task装饰器的封装,实际用法和invoke是一样的(注意:它还需要一个context参数"c",但实际上代码中并没有用到block,butwith然后,在与脚本同目录的命令行窗口中,可以查看并执行相应的任务:>>>fab-lAvailabletasks:testGetdatefromremotehost.>>>fabtestFriFeb1416:10:24CST2020fab是Invoke的扩展实现,继承了很多原有的功能,所以执行“fab--help”,对比之前介绍的“inv--help”,你会发现他们的很多参数和解释完全一样。fab为远程服务场景增加了几个命令行选项(蓝色标注),其中:--prompt-for-login-password:让程序在命令行输入SSH登录密码(上例代码中指定connect_kwargs.password参数,如果使用该选项,执行时可手动输入密码)--prompt-for-passphrase:使程序在命令行-H或--hosts中输入SSH私钥加密文件的路径:指定要连接的主机名-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):推送本地文件或文件-likeobjecttoaremotefilesystemwhenaconnectionhasbeenestablished,example:#(omitted)con.get('/opt/123.txt','123.txt')con.put('test.txt','/opt/test.txt')第一个参数是指要传输的源文件,第二个参数是要传输的目标,可以指定为文件名或文件夹(当为空或无时,使用默认path):#(略)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):串行执行操作并发方式Group的类型决定了主机集群如何运行,我们只需要做出选择即可。然后,他们的执行结果是一个fabric.group.GroupResult类,它是dict的子类,里面存放着每一个主机连接与其执行结果的对应关系。>>>fromfabricimportSerialGroup>>>results=SerialGroup('web1','web2','mac1').run('uname-s')>>>print(results):,:,:,}>另外GroupResult还提供failed在两个属性成功的情况下,您可以取出失败/成功的子集。这样,二次操作也可以方便的批量进行。三、Fabric的高级用法1、身份认证Fabric使用SSH协议建立远程会话,是一种相对安全的基于应用层的加密传输协议。基本上,它有两个级别的安全认证方式:基于密码的认证:使用帐号和密码登录远程主机,安全性较低,容易受到“中间人”攻击Key-based认证:使用密钥正确的方式(公钥放在服务器,私钥放在客户端),不会被“中间人”攻击,但需要时间长登录。在前面的例子中,我们使用了第一种方式,即通过指定connect_kwargs.password参数,使用密码登录。当然,Fabric也支持第二种方式。指定私钥文件的路径有3种方式。优先级如下:先搜索connect_kwargs.key_filename参数,找到则作为私钥;其次,找到命令行用法的--identify选项,最后在操作系统的ssh_config文件中默认使用IdentityFile的值。如果私钥文件本身是加密的,则需要使用connect_kwargs.passphrase参数。2、配置文件Fabric支持将部分参数项从业务代码中分离出来,即通过配置文件进行管理。例如上面提到的密码和私钥文件可以写在配置文件中,避免与代码耦合。Fabric基本沿用了Invoke的配置文件系统(官方文档列出9层),同时增加了一些SSH相关的配置项。支持的文件格式有.yaml、.yml、.json、.py(按优先顺序排列),推荐使用yaml格式(后缀可以简写为yml)。其中,比较常用的配置文件有:系统级配置文件:/etc/fabric.yml用户级配置文件:~/.fabric.yml(Windows在C:\Users\xxx下)项目级配置file:/myproject/fabric.yml之上的文件优先级递减。由于我的机器是Windows,为了方便,我在用户目录下创建了一个“.fabric.yml”文件,内容如下:#filename:.fabric.ymluser:rootconnect_kwargs:password:xxxx#如果使用key,如下#key_filename:#-your_key_file我们提取了用户名和密码,所以可以在fabfile中删除这些内容:#Filename:fabfile.pyfromfabricimportConnectionfromfabricimporttaskhost_ip='47.xx.xx.xx'#server地址cmd='date'#shell命令,查询服务器上的时间@taskdeftest(c):"""Getdatefromremotehost."""con=Connection(host_ip)result=con.run(cmd,hide=True)print(result.stdout)然后,在命令行中执行:>>>fabtestTueFeb1810:33:38CST2020配置文件也可以设置很多参数,具体请参考文档[4]。3.网络网关如果远程服务与网络隔离,不能直接访问(在不同的局域网),这时候就需要网关/代理/隧道。中间层的机器通常称为跳板机或堡垒机。Fabric中有两种网关方案,对应OpenSSH客户端的两种选择:ProxyJump:简单,低开销,可嵌套ProxyCommand:高开销,不可嵌套,更灵活在创建Fabric的Connection对象时,可以指定网关参数来应用这两种方案:ProxyJump方式是在一个Connection中嵌套一个Connection作为前者的网关,后者使用SSH协议的direct-tcpip为前者打开与实际远程主机的连接,后者可以继续嵌套使用自己的网关。fromfabricimportConnectionc=Connection('internalhost',gateway=Connection('gatewayhost'))ProxyCommand方法是客户端在本地使用ssh命令(类似于“ssh-W%h:%pgatewayhost”)创建子进程,该子进程要与服务器通信,它可以读取标准输入和输出。这部分的实现细节分别在paramiko.channel.Channel和paramiko.proxy.ProxyCommand中。除了在参数中指定外,还可以在Fabric支持的配置文件中定义。更多细节请参考文献[5]。4.总结Fabric版本不兼容造成了一定程度的社区分裂,这无疑与Python3的实现密不可分,但我们有理由相信新版本优于旧版本。网上很多关于Fabric的文章已经过时了。本文基于最新的官方文档,整理了较为全面的知识点,可以指导大家快速入门Fabric。