入门

欢迎!本教程重点介绍Fabric的核心功能;有关更多详细信息,请参阅中的链接或文档索引,其中包含指向概念和API文档部分的链接。

关于 imports 的说明

Fabric组合了几个其他库,并在顶部提供自己的层;用户代码通常从 fabric 包,但有时直接从 invokeparamiko 也是:

  • Invoke 实现CLI解析、任务组织和shell命令执行(通用框架加上本地命令的特定实现)。

    • 任何不特定于远程系统的东西都倾向于活在invoke中,并且它经常被不需要任何远程功能的程序员单独使用。
    • Fabric用户经常导入Invoke对象,在这种情况下,Fabric本身不需要子类化或修改Invoke提供的内容。
  • Paramiko 实现低/中级别的ssh功能-ssh和sftp会话、密钥管理等。

    • Fabric 主要在引擎盖下使用;用户很少直接从帕拉米科进口。
  • Fabric 将其他库粘合在一起,并提供自己的高级对象,例如:

    • 对invoke的上下文和命令运行程序类进行子类化,将它们包装在paramiko级别的原语中;
    • 使用paramiko扩展invoke的配置系统 ssh_config 解析机制;
    • 实现新的高级原语,如端口转发上下文管理器。(这些可能会及时向下迁移到帕拉米科。)

通过连接和 run

Fabric最基本的用途是通过ssh在远程系统上执行shell命令,然后(可选)询问结果。默认情况下,远程程序的输出直接打印到终端, and 捕获。一个基本示例:

>>> from fabric import Connection
>>> c = Connection('web1')
>>> result = c.run('uname -s')
Linux
>>> result.stdout.strip() == 'Linux'
True
>>> result.exited
0
>>> result.ok
True
>>> result.command
'uname -s'
>>> result.connection
<Connection host=web1>
>>> result.connection.host
'web1'

相遇 Connection 它表示一个ssh连接并提供fabric的api核心,例如 run . Connection 对象至少需要一个主机名才能成功创建,并且可以通过用户名和/或端口号进一步参数化。您可以通过args/kwargs明确地给出这些:

Connection(host='web1', user='deploy', port=2202)

或通过填充 [user@]host[:port] 字符串输入 host 论点(尽管这纯粹是为了方便;每当出现歧义时总是使用Kwarg!)::

Connection('deploy@web1:2202')

Connection 对象的方法(如 run )通常返回 invoke.runners.Result (或其子类)公开上面看到的各种细节:请求的内容、远程操作发生时发生的内容以及最终结果。

注解

通过使用 connect_kwargs argument .

通过自动响应的超级用户权限

需要作为远程系统的超级用户运行吗?你可以调用 sudo 程序通过 run ,并且(如果远程系统没有配置无密码sudo)手动响应密码提示,如下所示。(注意我们需要如何请求远程伪终端;大多数 sudo 否则,在密码提示时,实现会变得很坏。)

>>> from fabric import Connection
>>> c = Connection('db1')
>>> c.run('sudo useradd mydbuser', pty=True)
[sudo] password:
<Result cmd='sudo useradd mydbuser' exited=0>
>>> c.run('id -u mydbuser')
1001
<Result cmd='id -u mydbuser' exited=0>

每次使用密码都会变老;谢天谢地,invoke强大的命令执行功能包括 auto-respond 用预先定义的输入对输出进行编程。我们可以用这个 sudo

>>> from invoke import Responder
>>> from fabric import Connection
>>> c = Connection('host')
>>> sudopass = Responder(
...     pattern=r'\[sudo\] password:',
...     response='mypassword\n',
... )
>>> c.run('sudo whoami', pty=True, watchers=[sudopass])
[sudo] password:
root
<Result cmd='sudo whoami' exited=0>

很难在代码片段中显示,但当执行上述操作时,用户不需要键入任何内容; mypassword 已自动发送到远程程序。容易多了!

这个 sudo 帮手

使用观察者/响应者在这里工作得很好,但是每次都要设置很多样板文件-特别是在现实世界中,需要更多的工作来检测失败/不正确的密码。

为了帮助实现这一点,invoke提供了 Context.sudo 为您处理大多数样板文件的方法(如 Connection 子类 Context ,免费获取此方法。) sudo 不会做任何用户自己不能做的事情——但和往常一样,常见问题最好通过共享解决方案来解决。

用户需要做的就是确保 sudo.password configuration value 填写(通过配置文件、环境变量或 --prompt-for-sudo-passwordConnection.sudo 处理其余部分。为了清晰起见,下面是一个示例,库/shell用户执行自己的 getpass -基于密码提示:

>>> import getpass
>>> from fabric import Connection, Config
>>> sudo_pass = getpass.getpass("What's your sudo password?")
What's your sudo password?
>>> config = Config(overrides={'sudo': {'password': sudo_pass}})
>>> c = Connection('db1', config=config)
>>> c.sudo('whoami', hide='stderr')
root
<Result cmd="...whoami" exited=0>
>>> c.sudo('useradd mydbuser')
<Result cmd="...useradd mydbuser" exited=0>
>>> c.run('id -u mydbuser')
1001
<Result cmd='id -u mydbuser' exited=0>

在本例中,我们在运行时预先填写了sudo密码;在实际情况下,您也可以通过配置系统(可能使用环境变量,以避免污染配置文件)提供该密码,或者理想情况下使用机密管理系统。

传输文件

除了shell命令执行之外,ssh连接的另一个常见用法是文件传输; Connection.putConnection.get 存在以满足这一需求。例如,假设您有一个要上载的存档文件:

>>> from fabric import Connection
>>> result = Connection('web1').put('myfiles.tgz', remote='/opt/mydata/')
>>> print("Uploaded {0.local} to {0.remote}".format(result))
Uploaded /local/myfiles.tgz to /opt/mydata/

这些方法通常遵循 cp and scp/sftp 在参数评估方面——例如,在上面的代码片段中,我们省略了远程路径参数的文件名部分。

多个动作

一行程序是很好的例子,但并不总是实际的用例——通常需要多个步骤来完成任何有趣的事情。在最基本的层面上,您可以通过调用 Connection 方法多次:

from fabric import Connection
c = Connection('web1')
c.put('myfiles.tgz', '/opt/mydata')
c.run('tar -C /opt/mydata -xzvf /opt/mydata/myfiles.tgz')

您可以(但不必)将这些代码块转换为函数,并用 Connection 来自调用方的对象,以鼓励重用:

def upload_and_unpack(c):
    c.put('myfiles.tgz', '/opt/mydata')
    c.run('tar -C /opt/mydata -xzvf /opt/mydata/myfiles.tgz')

正如您将在下面看到的,这些函数可以被传递给其他API方法,以支持更复杂的用例。

多个服务器

大多数实际用例涉及在多个服务器上执行操作。简单的方法可以是遍历 Connection 争论(或) Connection 对象本身,也许通过 map ):

>>> from fabric import Connection
>>> for host in ('web1', 'web2', 'mac1'):
>>>     result = Connection(host).run('uname -s')
...     print("{}: {}".format(host, result.stdout.strip()))
...
...
web1: Linux
web2: Linux
mac1: Darwin

这种方法是可行的,但是随着用例变得越来越复杂,将主机集合视为单个对象可能很有用。进入 Group 一个类包装一个或多个 Connection 对象并提供类似的API;具体来说,您需要使用其具体的子类之一,如 SerialGroupThreadingGroup .

上一个示例,使用 Group (SerialGroup 具体来说),如下所示:

>>> from fabric import SerialGroup as Group
>>> results = Group('web1', 'web2', 'mac1').run('uname -s')
>>> print(results)
<GroupResult: {
    <Connection 'web1'>: <CommandResult 'uname -s'>,
    <Connection 'web2'>: <CommandResult 'uname -s'>,
    <Connection 'mac1'>: <CommandResult 'uname -s'>,
}>
>>> for connection, result in results.items():
...     print("{0.host}: {1.stdout}".format(connection, result))
...
...
web1: Linux
web2: Linux
mac1: Darwin

Connection 方法返回单个 Result 对象(例如) fabric.runners.ResultGroup 方法返回 GroupResult - dict -类似于提供对每个连接结果以及整个运行的元数据的访问的对象。

Group 遇到错误, GroupResult 轻轻地包裹在 GroupException ,它被提升。因此,聚合行为类似于个体的聚合行为。 Connection 方法,返回成功时的值或在失败时引发异常。

把所有的东西放在一起

最后,我们得出了最现实的用例:您有大量的命令和/或文件传输,您希望将其应用到多个服务器上。你 能够 使用倍数 Group 要执行此操作的方法调用::

from fabric import SerialGroup as Group
pool = Group('web1', 'web2', 'web3')
pool.put('myfiles.tgz', '/opt/mydata')
pool.run('tar -C /opt/mydata -xzvf /opt/mydata/myfiles.tgz')

一旦逻辑变得必要,这种方法就不适用了——例如,如果您只想在以下情况下执行上面的复制和取消标记 /opt/mydata 是空的。执行这种检查需要对每台服务器执行。

你可以通过使用 Connection 对象(尽管这放弃了使用 Groups ):

from fabric import Connection
for host in ('web1', 'web2', 'web3'):
    c = Connection(host)
    if c.run('test -f /opt/mydata/myfile', warn=True).failed:
        c.put('myfiles.tgz', '/opt/mydata')
        c.run('tar -C /opt/mydata -xzvf /opt/mydata/myfiles.tgz')

或者,还记得我们如何在前面的例子中使用函数吗?你可以改走那条路:

from fabric import SerialGroup as Group

def upload_and_unpack(c):
    if c.run('test -f /opt/mydata/myfile', warn=True).failed:
        c.put('myfiles.tgz', '/opt/mydata')
        c.run('tar -C /opt/mydata -xzvf /opt/mydata/myfiles.tgz')

for connection in Group('web1', 'web2', 'web3'):
    upload_and_unpack(connection)

最后一种方法所缺少的唯一便利就是 Group.run -如果你想追踪所有 upload_and_unpack 作为一个集合,你必须自己去做。希望将来的功能版本能在这个空间中获得更多的信息!

附录: fab 命令行工具

从shell运行结构代码通常很有用,例如部署应用程序或在任意服务器上运行sysadmin作业。你可以用普通的 Invoke tasks 其中包含结构库代码,但另一种选择是结构自己的“面向网络”工具, fab .

fab 使用主机选择等功能包装Invoke的CLI机制,使您可以在各种服务器上快速运行任务,而无需定义 host 对你所有的任务或类似的事情都不感兴趣。

注解

这种模式是Fabric1.x的主要API;从2.0开始,它只是一种方便。每当您的用例不在这些快捷方式的范围内时,应该可以很容易地直接恢复到库API(无论是否包含了Invoke不那么固执己见的CLI任务)。

对于最后一个代码示例,让我们将前面的示例调整为 fab 任务模块已调用 fabfile.py ::

from invoke import task

@task
def upload_and_unpack(c):
    if c.run('test -f /opt/mydata/myfile', warn=True).failed:
        c.put('myfiles.tgz', '/opt/mydata')
        c.run('tar -C /opt/mydata -xzvf /opt/mydata/myfiles.tgz')

不难——我们所做的只是将临时任务功能复制到一个文件中,然后在文件上贴上一个装饰。 task 通知CLI机器在命令行上公开任务::

$ fab --list
Available tasks:

  upload_and_unpack

然后,什么时候 fab 实际上,它调用一个任务,它知道如何将控制目标服务器的参数缝合在一起,并为每台服务器运行一次该任务。要在单个服务器上运行该任务,请执行以下操作:

$ fab -H web1 upload_and_unpack

当这种情况发生时, c 任务内部被有效地设置为 Connection("web1") -和前面的例子一样。类似地,可以给多个主机,这些主机多次运行任务,每次使用不同的主机 Connection 上缴实例:

$ fab -H web1,web2,web3 upload_and_unpack