自动化部署工具Fabric简介 Fabric就是一个帮助我们在上线时减少重复/繁琐操作的自动化部署利器,对于缺乏成熟运维平台的众多小公司的运维或开发人员来说,掌握这个工具是有必要的。 1. Fabric是什么Fabric官方文档的描述如下: 在系统运维和部署自动化领域,与fabric类似的工具还有很多(如Puppet, Chef),感兴趣的话,可以参考这篇文章48 Best Cloud Tools for Infrastructure Automation的介绍。 Fabric的安装非常方便,pip install fabric就可以搞定,这里不赘述。 2. Fabric支持的操作Fabric支持的常用命令列出如下: 此外,还有些不常用的命令(如:prompt, reboot, open_shell, require)这里没有列出,感兴趣的话,可以参考Fabric Operations文档。 需要特别注意的是,fabric通过run或sudo执行远程任务时,每次都会新建ssh连接,也即任务之间是不会耦合状态的,所以在实现需要多步操作的任务时,需要把多个命令放入同一行,命令间用逗号隔开。实例说明如下: 需要用下面的命令来实现: run('cd /home/work/tmp; mkdir test')1 run('cd /home/work/tmp && mkdir test')1当然,还可以借助fabric提供的context manager来实现: with cd('/home/work/tmp'): run('mkdir test')12上面介绍了fabric支持的元操作,那么如何基于这些操作实现复杂功能呢? 根据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的方式: 以classic方式定义的task示例下(摘自Fabric Overview and Tutorial): from fabric.api import localdef 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 在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的方式等价: @taskdef 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 根据的说明,fabric默认以串行方式运行tasks,具体而言: 假设在fabfile.py中定义了如下tasks: from fabric.api import run, env env.hosts = ['host1', 'host2'] def taskA(): run('ls') def taskB(): run('whoami')在终端运行fab –list时,我们会看到taskA和taskB两个任务,运行之: $ fab taskA taskB1结果示例如下: taskA executed on host1taskA executed on host2 taskB executed on host1taskB executed on host2通过上面的实例,大家应该可以明白fabric默认的串行执行策略是怎么回事。 Fabric还允许我们指定以并行方式(借助multiprocessing模块实现多个进程并行执行)在多台机器上并行地运行任务,甚至还可在同一个fabfile文件中指定某些task以并行方式运行,而某些task以默认的串行方式运行。具体地,可以借助@parallel或@serial指定任务的运行模式,还可以在命令行中通过-P参数指定任务是否要并性执行。示例如下: from fabric.api import * @parallel def runs_in_parallel(): pass def runs_serially(): pass当运行如下命令时: fab -H host1,host2,host3 runs_in_parallel runs_serially1执行结果示例如下: runs_in_parallel on host1, host2, and host3 runs_serially on host1 runs_serially on host2 runs_serially on host3此外,还可以通过对@parallel传入pool_size参数来控制并行进程数以防并行进程太多把机器拖垮。 2. 为task指定目标机器有多种方式可以指定任务的将要运行的目标机器,下面分别进行说明。 env.hosts的元素是fabric约定的”host strings”,每个host strings由username@hostname:port三部分构成,其中username和port部分可以缺省。本篇笔记前面的第1个代码实例其实已经说明了如何用env.hosts全局地指定task的目标机器列表,这里不再赘述。 env.roles则是在配置了env.roledefs的情况下才有用武之地。在很多时候,不同的机器有着不同的角色,如有些是接入层,有些是业务层,有些是数据存储层。env.roledefs可以用来组织这些机器列表以体现其角色,示例如下: from fabric.api import env env.roledefs = { 'web': { 'hosts': ['www1', 'www2', 'www3'], }, 'db': { 'hosts': ['db1', 'db2'], } } @roles('web') def mytask(): run('ls /var/www')上例通过env.roledefs配置了两个角色web和db,分别包含3台、2台机器,并借助@roles为mytask指定了目标机器列表。 2)通过命令行进行全局指定 $ fab -H host1,host2 mytask1需要注意的是,命令行通过-H参数指定的机器列表在fabfile脚本load前被解释,故如果fabfile中重新配置了env.hosts或env.roles,则命令行指定的机器列表会被覆盖。为了避免fabfile覆盖命令行参数,在fabfile中应该借助list.extend()指定env.hosts或env.roles,示例如下: from fabric.api import env, run env.hosts.extend(['host3', 'host4']) def mytask(): run('ls /var/www')123456此时,当我们运行”fab -H host1,host2 mytask”时,env.hosts包含来自命令行和fabfile的4台机器。 3)通过命令行为每个任务指定机器列表 $ fab mytask:hosts="host1;host2"1上述方式会覆盖全局指定的机器列表,确保mytask只会在host1, host2上执行。 4)借助装饰器@hosts为每个任务指定目标机器 from fabric.api import env, run env.hosts.extend(['host3', 'host4']) def mytask(): run('ls /var/www') ##or# my_hosts = ('host1', 'host2') @hosts(my_hosts) def mytask(): # ...每个任务的@hosts装饰器指定的机器列表会覆盖全局目标机器列表,但不会覆盖通过命令行为该任务单独指定的目标机器列表。 上述4种为task指定目标机器列表的方式之间的优先级规则总结如下: 截止目前,我们可以看到,fabric允许我们混合使用上面列出的几种目标机器指定方式,但是我们要明白混合的结果是否符合预期。 此外,fabric默认会对通过不同来源出现多次的同一个目标机器做去重,当然,可以通过设置env.dedupe_hosts为False来关闭默认的去重策略。甚至还可以指定任务需要跳过的机器列表。具体细节可以参考的说明,这里不赘述。 3. 任务执行时,目标机器的密码管理如果你亲自运行上面的示例代码,就会发现,每次在目标机器远程执行task时,fabric均会要求输入目标机器的登录名及密码。如果要在多台机器上执行task,那这些密码输入的过程可以自动化吗? 答案是肯定的。实现方式有两种,下面分别进行说明。 1)通过env.password或env.passwords配置目标机器的登录信息 但是,这种明文指定登录名/密码的方式存在安全性问题,所以,fabric还支持以ssh key认证的方式免密在远程机器执行任务。 在具体实现上,需要事先在目标机器上生成ssh public key并配置在~/.ssh/config文件中,然后在定义任务的fabfile中将env.use_ssh_config设置为True来启用基于ssh public key方式的身份认证,以便实现免密码远程执行任务。 (责任编辑:) |