自动化部署工具Fabric简介

在持续集成/灰度发布越来越流行的今天,模块在预览或生产环境的部署流程自动化显得越来越重要。本文要介绍的Fabric就是一个帮助我们在上线时减少重复/繁琐操作的自动化部署利器,对于缺乏成熟运维平台的众多小公司的运维或开发人员来说,掌握这个工具是有必要的。

1. Fabric是什么

Fabric官方文档的描述如下:
Fabric is a Python (2.5-2.7) library and command-line tool for streamlining the use of SSH for application deployment or systems administration tasks.
具体来说,Fabric是一个Python库,只要目标机器支持ssh访问,就可以借助fabric来进行远程操作(如在host1上对host2远程运行shell命令),显然,由于fabric是个Python package,故其它Python package都可以被import到fabric特有的fabfile.py脚本中,这使得fabric如虎添翼,在功能的丰富程度和运维脚本的可维护性上,远远超过用shell实现的自动化部署脚本,更不要说与纯手工敲命令的上线方式相比所体现出的巨大优势了。

在系统运维和部署自动化领域,与fabric类似的工具还有很多(如Puppet, Chef),感兴趣的话,可以参考这篇文章48 Best Cloud Tools for Infrastructure Automation的介绍。

Fabric的安装非常方便,pip install fabric就可以搞定,这里不赘述。

2. Fabric支持的操作

Fabric支持的常用命令列出如下:
1)local
Run a command on the local system.
它是对subprocess模块的封装(Shell=True),可以通过设置Capture = True/False来捕获其执行结果。
2)run
Run a shell command on a remote host.
该命令的返回值包含了远程命令是否执行成功以及远程命令的返回码等信息。通过run执行命令时,通常会要求输入目标机器密码,如果对多台机器进行部署,可以通过设置env.passwords来避免手动输入密码,具体的设置方法会在下篇笔记中介绍。
3)get
Download one or more files from a remote host.
4)put
Upload one or more files to a remote host.
5)sudo
Run a shell command on a remote host, with superuser privileges.
功能与run操作类似,它可以对当前用户临时提权来执行某些需要root权限的命令。

此外,还有些不常用的命令(如:prompt, reboot, open_shell, require)这里没有列出,感兴趣的话,可以参考Fabric Operations文档。

需要特别注意的是,fabric通过run或sudo执行远程任务时,每次都会新建ssh连接,也即任务之间是不会耦合状态的,所以在实现需要多步操作的任务时,需要把多个命令放入同一行,命令间用逗号隔开。实例说明如下:
假设要在远程机器上cd至/home/work/tmp目录后创建test目录,则下面的命令无法实现预期目的:

run('cd /home/work/tmp')
run('mkdir test')  ## 第2run会重新创建ssh连接,且不会记忆上次cd到的目录!!

需要用下面的命令来实现:

run('cd /home/work/tmp; mkdir test')

run('cd /home/work/tmp && mkdir test')

当然,还可以借助fabric提供的context manager来实现:

with cd('/home/work/tmp'):
    run('mkdir test')

关于fabric支持的context managers,还有很多强大且好玩的功能,请参考官方文档Fabric Context Managers,不会让你失望的。

上面介绍了fabric支持的元操作,那么如何基于这些操作实现复杂功能呢?
在fabric中,一组具有逻辑关系的操作通常被封装成一个task,fabric以task为粒度来执行命令,下面开始介绍如何定义task。

3. 在fabfile中定义tasks

3.1 fabfile是什么

根据fabric的约定,当运行例如”fab deploy”这样的命令时,fab会默认搜索名为fabfile.py的python文件或名为fabfile的package,故基于fabric的部署脚本通常以fabfile.py命名且应该位于当期工作目录下以便于fab进行搜索,在该文件中实现我们想要的任务即可。当然,如果要实现的部署任务比较复杂,这些任务也可以写在多个脚本中,统一置于fabric package下。关于fabfile的细节,可以参考官方文档Fabfile construction and use,这里不赘述。

3.2 定义task

在语法约定上,fabric有两种定义task的方式:
1)经典方式(classic method)
所有定义在fabfile中的可调用对象(如函数、类)均可被当作task被fab执行,这种方式不支持嵌套,也即:若fabfile.py中import了其它模块,则即使这些模块中定义了可调用对象,这些不是直接定义在fabfile中的可调用对象也不会被当作fab task。

以classic方式定义的task示例下(摘自Fabric Overview and Tutorial):

from fabric.api import local

def prepare_deploy():
    local("./manage.py test my_app")
    local("git add -p && git commit")
    local("git push")

上述示例代码在fabfile.py中定义了一个普通函数prepare_deploy,不难看出,其功能是在本地执行代码测试后,将本地的最新codebase更新到版本管理系统中以便后续以该codebase进行部署。

2)基于Task类的新风格task
从fabric 1.1开始,这种new-style的task定义方式被引入。该方式约定,所有的fab任务必须定义成Task类的实例或子类,其最大的优点是支持嵌套namespaces,也即,task可以定义在其它文件,fabfile.py通过import引入该文件后,定义在该文件的task也是可以被fab识别并支持的。

在new-style方式定义task的具体实现上,由2种方法:a. 定义一个继承自Task的子类并为其实现run()方法; b. 借助@task装饰器。示例分别如下:

class MyTask(Task):
    name = "deploy"  ## 指定task name,会在fab --list输出中显示
    def run(self, environment, domain="whatever.com"):
        run("git clone foo")
        sudo("service apache2 restart")

instance = MyTask()

上述示例与借助@task定义task的方式等价:

@task
def deploy(environment, domain="whatever.com"):
    run("git clone foo")
    sudo("service apache2 restart")

被@task装饰的函数默认继承自Task类,我们可以让函数继承自定义的类,具体的用法可以参考文档Defining tasks的”Using custom subclasses with @task”部分,这里只是抛砖引玉,不再赘述。

需要特别注意的是,这两种task的定义方式是互斥的!具体而言,如果fabric在fabfile或它import的文件中发现了基于Task类的new-style定义,那么,所有以classic方式定义的task(s)均会被fabric忽略。个人认为,如果要用fabric实现复杂系统的自动化部署,最好以new-style定义任务,因为这种方式支持嵌套namespace,可以用不同的脚本文件分层组织不同的任务,更方便维护。

备注:可以运行”fab –list”来查看fabric可以识别的任务。

完成task定义后,fabric是如何执行的?尤其是远程部署多台机器时,如何更好地管理这些机器(如角色、密码等)?

这些问题会在下篇笔记中进行说明。

参考资料

[1] 48 Best Cloud Tools for Infrastructure Automation
[2] Deployment Management Tools: Chef vs. Puppet vs. Ansible vs. SaltStack vs. Fabric
[3] Fabric Doc: Overview and Tutorial
[4] Fabric Doc: Operations
[5] Fabric Doc: Context Managers
[6] Fabric Doc: Defining tasks

你可能感兴趣的:(Python,Linux)