Invoke与Fabric注意事项详解教程

说到远程部署利器,那就不得不讲讲Fabric,Fabric 是一个 Python 的库,同时它也是一个命令行工具。它提供了丰富的同 SSH 交互的接口,可以用来在本地或远程机器上自动化、流水化地执行 Shell 命令。使用 fabric 提供的命令行工具,可以很方便地执行应用部署和系统管理等操作。因此它非常适合用来做应用的远程部署及系统维护
刚实习那会,也遇到这种需求,但是不知道Fabric,自己在那实现好半天搞不定,sudo的问题也一直在,paramiko库也看得头晕,最终不了了之,直到发现了Fabric,才发现人家发明的轮子真的香!

1. Fabric前世今生

我从一开始接触的就是Python3,之后因为远程打包部署接触到了Fabric,当时官网Fabric还不支持Python3,所以我用了民间的Fabric3,十分好用,对新手很友好。后来,发现Fabric支持Python3了,但是看了文档,看的稀里糊涂,看了别人的教程才成功连接到远程主机上,我就又撒手转向了民间的Fabric3。
所以我们先区分下 Fabric1,Fabric2,Fabric3,它们的python官网发布的地址:

安装方式分别是:

1
2
3
4
5
6
7
8
# https://pypi.org/project/Fabric/
pip install Fabric

# https://pypi.org/project/fabric2/
pip install fabric2

# https://pypi.org/project/Fabric3/
pip install fabric3

Fabric1和Fabric2,在pypi中的页面,就是同一个东西:

  • 都是Fabric的最新版:Fabric 2.x,截至到20200227,安装出来的版本是:2.5
  • 而官网之所以弄出来个Fabric2是因为:Fabric2和Fabric1相比,完全重写了,接口和功能都有很大改动,官网也不建议你继续用Fabric1,建议升级到Fabric2,最新版也早就支持Python 3.4+,和之前的Python2.7

而Fabric3,是非官网的是当之前Fabric1还没有支持Python3时,别人去fork出来,加了Python 3的支持的现在好像基本上不维护了

总结就是:

尽量不要用之前旧的版本的Fabric1了,如果还在用,建议升级到最新的Fabric2
不需要操心、忽略掉,所谓的、非官网的,现在已没价值的:Fabric3

因为要同时远程部署七八个树莓派,又要用到Fabric,趁着这次疫情在家,啃了一个星期的文档,蛮多人也觉得新版的Fabric比较难用,原因在于完全重写了,其实我们可以把这个库分开学习,分为Inovke和Paramiko,为什么重写之后要将Invoke独立出来,可以看这里Why was Invoke split off from the Fabric project?,那接下去就分别讲讲!

2. Invoke使用

Invoke 实现CLI解析,任务组织和Shell命令执行(通用框架以及本地命令的特定实现),关于Shell命令的执行其实和subprocess库挺像的,当然Invoke不仅于此。

2.1 基础使用

关于基础教程我就不说了,看官网例子一目了然,我在这里记录一些重点与自己的理解。

2.2 配置文件

关于invoke的配置方面我们大体看一下配置继承
配置很多层,有内部默认,系统级别,用户级别,项目级别…甚至可以在命令行运行的时候我们再添加配置也是可行的。
我这里推荐使用用户级别的配置文件( ~/.invoke.yaml,也可以是json,py文件),刚开始我自己测试的时候用的项目级别的配置文件(/home/user/myproject/invoke.yaml),一个原因是我在windows下测试,没有所谓的 ~/.invoke.yaml,还有个就是我想万一这个项目到别的机器上运行,我可能把隐藏的配置文件忘了,想显示放在项目里,结果这样就跳入了一个火坑,琢磨了我好久,原因在于:

Project-level configuration file living next to your top level tasks.py. For example, if your run of Invoke loads /home/user/myproject/tasks.py (see our docs on the load process), this might be /home/user/myproject/invoke.yaml.

项目级别的配置文件只有在你识别到tasks文件时才会调用同文件夹里的配置,其他方式你会没法识别到,比如你想单独用python3运行这个invoke脚本(把invoke当成subprocess那样)!下面会讲到相关例子。
其实,invoke本身没有啥配置修改,所以我也建议在执行invoke ... task1 ...时通过一些命令行参数来传递配置,省了配置文件的存在。

2.3 导入集合

Invoke执行模型的核心涉及一个或多个Collection对象,就是集合的意思。
默认调用invoke命令我们会查找同目录中的tasks.py或者tasks包,tasks就算是一个集合,具体我们可以看Loading collections,所以有接下来一个例子我上面说的火坑。
假如我tasks名字被占用了…我想换个任务名字来让Invoke搜索到,比如叫mytasks,常见的设置方法有两种,一种是设置tasks.collection_name 配置选项,或使用 --collection。集合名应该是Python模块名称,而不是文件名(所以mytasks,不是mytasks.py或 mytasks/。)
--collection没问题,问题在于设置tasks.collection_name 配置选项,我一开始是用的项目级别配置,则报错Can't find any collection named 'tasks'!,我明明设置文件里设置了,为什么还是在查找tasks.py,哪里有问题!问题在于上一节引用的英文里,项目级别配置是在同目录的tasks.py文件被找到后才会引入该配置,问题是一开始我都没有tasks.py我怎么引入配置文件,更别说里面的tasks.collection_name 配置选项了

2.4 集合的命名空间

当任务很多,存在不同类别层级的时候我们就需要构建命名空间(看Importing modules as collections部分就行),单纯的tasks.py满足不了我们了,使用tasks包,包里有多个python文件代表个各种任务,然后在tasks/__init__.py中做导入模块作为集合操作就行,不展开。

2.5 上下文Context

上下文Context这个参数出现了,很神奇,就好像Flask中的Context一样神奇。

what exactly is this context arg anyway?

A common problem task runners face is transmission of “global” data - values loaded from configuration files or other configuration vectors, given via CLI flags, generated in ‘setup’ tasks, etc.
Some libraries (such as Fabric 1.x) implement this via module-level attributes, which makes testing difficult and error prone, limits concurrency, and increases implementation complexity.
Invoke encapsulates state in explicit Context objects, handed to tasks when they execute . The context is the primary API endpoint, offering methods which honor the current state (such as Context.run) as well as access to that state itself.

Context对象是在命令行解析过程中创建的(如果需要的话,可以手动创建),并用于与执行的任务共享解析器和配置状态,下面两种例子是一样的.
第一种通过命令行,过程中创建c,即为context,会自动导入文件配置:

1
2
3
4
5
6
7
8
from invoke import task
@task
def show(c):
print(config)
c.run("ls")

$ invoke show
...

第二种手动创建,如果有配置我们需要手动添加配置而没法用文件配置,比如:

1
2
3
4
5
6
7
8
9
from invoke import Config,Context
# 默认配置
config = Config()
# 添加一些配置
config['sudo'] = {'password':'mypassoword'}
print(config)
# 传入配置,如果没有额外配置可以省略不填
c = Context(config=config)
c.run('ls')

2.6 sudo运行以及自动响应

最后我们说运行sudo命令时如何免密码以及与一运行一些其他命令时的提示互动.
我们可以先配置好文件:

1
2
3
4
5
6
7
8
9
{
"tasks": {
"auto_dash_names": false
},
"sudo": {
"password":"mypassword"
"prompt": "[sudo] password:",
}
}

然后在脚本里c.sudo('rm test.txt')就行.
或者我们简单点:直接在在函数里面加入c.sudo('rm test.txt',user='baird',password='mypassword')
关于自动响应程序输出的,我们可以看Automatically responding to program output

3. Fabric使用

Fabric与Invoke基本上属于继承创新的关系,很多都是相同操作,所以这一部分就不重复讲了.
Fabric相对于Invoke来说增加了Paramiko库的功能,也就是说实现了低/中级别SSH功能-SSH和SFTP会话,密钥管理等.但作为用户我们很少会直接从Paramiko导入,而是Fabric把它融合进来了,我们在看教程前,先来看看SSH认证

3.1 认证

即使在原始 OpenSSH客户端中,对远程服务器的身份验证也涉及多个潜在的秘密和配置来源。Fabric不仅支持其中的大多数,而且还具有自己的功能,下面是认证方式以及对应的配置:

  • 私人秘钥文件,通过connect_kwargs.key_filename配置
  • 如果私钥文件通过密码保护,通过connect_kwargs.passphrase配置
  • 私钥对象,通过connect_kwargs.pkey配置
  • SSH代理
  • 密码,通过connect_kwargs.password配置或者--prompt-for-login-password手动提示输入
  • GSSAPI,不太懂,pass
    上面的认证方式基本就全了,当然,每种认证方式的配置选择有很多,不止一种,很灵活,具体看Authentication.
    其实我们不用管那么多啥认证方式,密码就完事了~,方便省略

3.2 连接

有了认证之后,就得到了一个连接(connection),用于SSH守护程序的连接,其中包含命令和文件传输的方法.这个connection其实跟context是一样的,一个远程一个本机,我们可以看下,在fabfile.py里代码如下:

1
2
3
4
5
from fabric import task

@task
def show(c):
c.run('ls')

当我们用fab -H host1 show时是连接的host1再运行ls命令,而fab show是直接在本机运行ls命令,也就是Invoke的作用,当然我么需要一个配置文件,我用的是密码:

1
2
3
4
5
6
{
"user": "pi",
"connect_kwargs": {
"password": "mypassword"
},
}

以上是使用命令行调用,或者我们可以使用connection函数:

1
2
3
4
from fabric import Connection

c = Connection('pi@host1',connect_kwargs={'password':'mypassword'})
c.run('ls')

更多例子我们可以看官方教程,其实我最早看这上面例子时,我有点彷徨,没密码怎么连上去跑命令的,后来发现密码都是认证配置,连接并不重要,重要的是给你展示用法。

从上面这个例子我们可以看出,可以用各种灵活的配置以及实现方式来创建一个远程连接。我的项目中有涉及远程部署多台机器, 我开始的想法是一把梭,创建所有机器的connection,然后上传上去,即我不需要-H来指定哪些机器,我已全部手动实例化,这就意味着用Fabric的Connection类实例化再加Facric中的Invoke功能。这是一种选择。后来发现,我应该采用一种更通用的方法,函数里我就执行相应动作,然后用-H指定主机,但是这样的话,比如我想同时部署多台机器,或者一半的机器,我还得在-H之后一个个输地址,这很耗时间,但好处就是一台台调试的时候更方便。所以我们还需外的任务,来根据条件快速输出-H后面的值做一个辅助工具,比如说输入1~7,那就自动把1~7七台机器的ip提示出来买,也算是曲线救国

3.3 配置

Fabric配置系统的核心依赖于Invoke功能(与Fabric的其余大部分一样),只是配置文件名从invoke(不包括后缀)改为fabfile,然后是多了一些关于远程主机认证的配置,最常用的就是connect_kwargs,上面例子也出现过了,就不过多强调了

4.总结

这篇文章总结下来,花了我蛮多时间的,也算是打开了2020的新篇章。以为过程中一切都很顺利,结果写着写着就卡住了,打了自己”自以为很懂”的脸,也算有了更多收获,接下来更多加油!