文中部分内容整理自官方文档。如果文中出现错误,欢迎指出与交流。
Fabric 是一个 Python的库和命令行工具,用来提高基于 SSH 的应用部署和系统管理效率。可以实现与远程服务器的自动化交互。Fabric底层基于paramiko模块。其特点即为简单高效,并且支持多线程。在需要运维数十台至几百台机器(视情况而定)的情况下非常好用。但如果需要运维上千台并不推荐使用Fabric,可以尝试使用saltstack。
在搞清Fabric的作用后,我们可以尝试通过一些简单的程序,来更加深入的了解Fabric。
注:文中实验环境均为Centos7.3。使用的python版本为python2.7。
需要先安装paramiko模块(上文提到了,Fabric基于这个模块)。
#yum install gcc python-crypto python-devel python-paramiko -y
通过pip命令安装Fabric
#pip install fabric
如果没有pip命令,可以通过以下命令安装:
#yum install python-pip
至此Fabric模块就安装好了。
一个合格的教程,一定少不了hello world这个国际惯例:
#vim fabfile.py
注:fabric执行的时候会默认寻找当前路径下的fabfile.py文件。如果文件名为其它,需要在执行的时候通过
-f 文件名
的方式手动指定文件。
def hello():
print "hello world"
Fabric脚本执行并不使用python命令,而是使用fab命令(在安装模块时会一起安装)。所以这个脚本的执行方法为:
#fab hello #hello对应脚本中你想执行的函数名。
Hello world!
Done.
或
#fab -f 你的文件名 hello #此方法针对文件名不为fabfile时
至此我们便基于Fabric完成了一个非常简单的脚本,甚至连Fabric模块都不需要导入。但从中大家可以了解到Fabric脚本就是在文件中定义一个或多个函数,函数中是你要执行的任务。然后通过fab命令执行fabfile.py文件,执行你要执行的函数。
Fabric.api是Fabric中最常用也是最核心的模块。可以使用此模块来实现与远程服务器的交互。简单的使用这些API就可以完成大部分应用场景的需求。
常用api | 说明 | 例子 |
---|---|---|
local | 在本地执行命令 | local(‘username -r’) |
run | 在远程执行命令 | run(‘username -r’) |
lcd | 切换本机路径 | lcd(‘/usr’) |
cd | 切换远程服务器路径 | cd(‘/usr’) |
put | 上传文件到远程服务器 | put(‘本地文件’,’远程路径’) |
get | 从远程服务器下载文件 | get(‘远程文件’,’本地路径’) |
prompt | 获取用户输入(类似input) | prompt(‘input path’) |
confirm | 让用户确认是否继续 | confirm(‘continue?’) |
env | 定义全局信息,如主机、密码等 | env.hosts=’localhost’ |
注:Fabric基于paramiko模块,paramiko底层就是调用ssh命令来对远程主机进行管理。所以要使用Fabric来管理远程主机,要保证服务器端开启sshd服务,并且保证可以成功访问。
查看本地和远程主机的内核版本。
from fabric.api import * #导入fabric.api模块
env.hosts= ['192.168.122.101','192.168.122.102','192.168.122.103'] #指定远端服务器的ip地址。如果有dns解析的也可以写主机名。
env.password='indionce' #指定远端主机的密码,如果各个密码不相同可以使用一个字典指定,例如:env.password={“[email protected]”:"indionce"}
def local_uname(): #定义一个本地任务的函数
local('uname -r')
def remote_uname(): #定义一个远程任务的函数
run('uname -r')
def uname(): #定义一个函数,将本地与远端组合起来使用
local_uname()
remote_uname()
执行这个脚本会得到下面的结果(因为字数原因我只取了前两台主机的返回信息):
[root@Fabric ~]# fab uname
[192.168.122.101] Executing task 'uname'
[localhost] local: uname -r
3.10.0-514.el7.x86_64
[192.168.122.101] run: uname -r
[192.168.122.101] out: 3.10.0-514.el7.x86_64
[192.168.122.101] out:
[192.168.122.102] Executing task 'uname'
[localhost] local: uname -r
3.10.0-514.el7.x86_64
[192.168.122.102] run: uname -r
[192.168.122.102] out: 3.10.0-514.el7.x86_64
[192.168.122.102] out:
.....
尽管我只保留了前两台主机的返回值,但是也可以从中发现。我有多少台主机,脚本便执行了多少次本地的uname -r
。在实际应用中,有很多类似内核信息的本地命令,我只需要执行一次。甚至多次执行会出现错误,这时我就需要使用@runs_once
来对函数进行修饰,具体使用也非常简单,如下:
@runs_once #指定下一行的函数在运行时,只运行一次。
def local_uname():
local('uname -r')
新脚本执行之后就会发现本地的内核信息只输出了一次。
查看远程服务器的文件夹列表
from fabric.api import *
@runs_once #一定要指定这一条,否则会让你输入多次路径
def input():
return prompt("input path:") #prompt函数,让用户输入自己想要的路径,将输入的值返回到函数。
def ls_path(dirname): #在定义函数的时候指定形参。
run("ls -l "+dirname)
def go():
ls_path(input()) #使用input返回的值,用于ls_path()的参数
执行这个脚本
[root@name ~]# fab go
[192.168.122.101] Executing task 'go'
input path: /home #用户自己输入一个路径,然后到远端服务器查看这个路径下的文件
[192.168.122.101] run: ls -l /home
[192.168.122.101] out: total 4
[192.168.122.101] out: -rw-r--r--. 1 root root 541 Jun 8 23:54 fatab
[192.168.122.101] out:
[192.168.122.102] Executing task 'go'
[192.168.122.102] run: ls -l /home
[192.168.122.102] out: total 4
[192.168.122.102] out: -rw-r--r--. 1 root root 541 Jun 8 23:54 fatab
[192.168.122.102] out:
[192.168.122.103] Executing task 'go'
[192.168.122.103] run: ls -l /home
[192.168.122.103] out: total 4
[192.168.122.103] out: -rw-r--r--. 1 root root 541 Jun 8 23:54 fatab
[192.168.122.103] out:
在执行任务中,难免出现命令执行失败的情况。而在日常使用中,肯定不会像我们测试时只执行一两条命令,一般都是一系列命令来共同完成一件事。如果其中一条命令执行失败,便会影响后续的命令。这个时候我们要让脚本停下来,以免出现更大的错误。
Fabric 会检查被调用程序的返回值,如果这些程序没有干净地退出,Fabric 会终止操作,下面我们就来看看如果一个测试用例遇到错误时会发生什么:
[root@Fabric ~]# fab uname
[192.168.122.101] Executing task 'uname'
[localhost] local: uname -rzzz
uname:无效选项 -- z
Try 'uname --help' for more information.
Fatal error: local() encountered an error (return code 1) while executing 'uname -rzzz'
Aborting.
太好了!我们什么都不用做,Fabric 检测到了错误并终止,不会继续执行接下来的任务。
如果我们想要更加灵活,给用户另一种选择,一个名为warn_only的设置就排上用场了。可以把退出换为警告,以提供更灵活的错误处理。具体如下:
from fabric.api import *
from fabric.contrib.console import * #这个模块中包含confirm
def backup():
with settings(warn_only=True): #with命令表示执行这句后,执行下面的命令。使用settings命令来设置警告模式
state=local('mkdir /root/zz') #创建一个文件夹
if state.failed and not confirm("/root/zz is already exist,continue?"): #使用failed来判断state这条命令是否失败,失败了为真。confirm向用户确认是否继续,继续为真。如果命令失败了,并且用户希望停止,便通过if判断。
abort("退出任务") #abort是退出任务,有些类似python的exit。退出并且时返回给用户一串字符串
local('tar cavf /root/zz/etc.tar.gz /etc') #将etc的文件备份到/root/zz文件夹中
运行该文件得到下列结果,出现错误时向用户提示错误,用户可以视情况自行选择是否继续。
[root@Fabric ~]# fab mkdir
[192.168.122.101] Executing task 'mkdir'
[localhost] local: mkdir /root/zz
mkdir: 无法创建目录"/root/zz": 文件已存在
Warning: local() encountered an error (return code 1) while executing 'mkdir /root/zz'
/root/zz is already exist,continue? [Y/n] a
I didn't understand you. Please specify '(y)es' or '(n)o'.
/root/zz is already exist,continue? [Y/n]
实际使用中服务器的网关一帮的设有防火墙,我们无法直接ssh管理服务器。Fabric也提供给我们一种夸网关的使用方式。只需要在配置文件中定义好网关的ip地址即可,具体如下:
from fabric.api import *
from fabric.contrib.console import confirm
env.user = "root"
env.gateway = '10.0.0.1' #定义网关的地址
env.hosts = ["10.0.0.2","10.0.0.3"]
env.passwords = {
'[email protected]:22':'redhat', #需要定义网关的密码
'[email protected]:22':'redhat',
'[email protected]:22':'redhat'
}
开篇时便提到Fabric支持多线程,但之前运行结果中很明显可以看出先从101主机运行,运行结束后才在102主机上运行,以此类推。并没有并行运行
[root@name ~]# fab remote_uname
[192.168.122.101] Executing task 'remote_uname'
[192.168.122.101] run: uname -r
[192.168.122.101] out: 3.10.0-514.el7.x86_64
[192.168.122.101] out:
[192.168.122.102] Executing task 'remote_uname'
[192.168.122.102] run: uname -r
[192.168.122.102] out: 3.10.0-514.el7.x86_64
[192.168.122.102] out:
[192.168.122.103] Executing task 'remote_uname'
[192.168.122.103] run: uname -r
[192.168.122.103] out: 3.10.0-514.el7.x86_64
[192.168.122.103] out:
由于并行执行影响的最小单位是任务,所以功能的启用或禁用也是以任务为单位使用 parallel 或 serial 装饰器。如下:
@parallel #将下面的函数设为并行执行。
def runs_parallel():
run('uname -r')
@serial #将下面的函数设为顺序执行(默认即为顺序执行 )
def runs_serially():
pass
这样在执行时runs_parallel即为并行执行,serially为顺序执行。也可以在执行命令的时候指定:
#fab parallel -P #-P用来指定并行执行
[192.168.122.103] run: uname -r
[192.168.122.102] run: uname -r
[192.168.122.101] run: uname -r
[192.168.122.101] out: 3.10.0-514.el7.x86_64
[192.168.122.101] out:
[192.168.122.102] out: 3.10.0-514.el7.x86_64
[192.168.122.102] out:
[192.168.122.103] out: 3.10.0-514.el7.x86_64
[192.168.122.103] out:
可以从上列返回值中很明显的看出并发执行与顺序执行的区别。
如果你的主机列表很多的时候,并行执行可能会分出几百个线程。这样对主机的压力是非常大的。这个时候需要限制线程的个数。默认是不限制的,即所有主机都会并发执行。可以使用pool_size来限制线程数。如下:
@parallel(pool_size=5) #将下面的函数设为并行执行,并且限制最多5个线程。
def runs_parallel():
pass
Fabric默认定义了几个输出层级:
层级名称 | 层级说明 |
---|---|
status | 状态信息,包括提示 Fabric 已结束运行、用户是否使用键盘中止操作、或者服务器是否断开了连接。通常来说这些信息都不会很冗长,但是至关重要。 |
aborts | 终止信息。和状态信息一样,只有当 Fabric 做为库使用的时候才可能应该关闭,而且还并不一定。注意,即使该输出集被关闭了,并不能阻止程序退出——你只会得不到任何 Fabric 退出的原因。 |
warnings: | 警报信息。通常在预计指定操作失败时会将其关闭,比如说你可能使用 grep 来测试文件中是否有特定文字。如果设置 env.warn_only 为 True 会导致远程程序执行失败时完全没有警报信息。和 aborts 一样,这项设置本身并不控制警报行为,仅用于是否输出警报信息。 |
running | 执行信息,输出正在执行的命令或者正在传输的文件名称 |
stdout | 标准输出,本地或远程的 stdout。来自命令行的非错误输出 |
stderr | 错误输出,本地或远程的 stderr。比如命令中错误相关的输出 |
在使用中,Fabric默认尽可能多的将信息显示出来。也就是上列信息都会输出到屏幕上。但有些时候我们并不需要在意这么多信息,而且过多的信息会使我们很难抓住重点。这时候我们可以通过hide和show来进行输出控制:
def my_task():
with hide('running', 'stdout', 'stderr'): #hide表示隐藏,下面的命令隐藏running,stdout,stderr输出
run('ls /var/www')
因为Fabric默认是打开所有输出的,show命令并不常用。但可以通过show命令开启debug模式:
def remote_uname():
with show("debug"): #打开debug输出,默认只有这项是关闭的
run('uname -r')
执行脚本:
[192.168.122.103] run: /bin/bash -l -c "uname -r"
[192.168.122.102] run: /bin/bash -l -c "uname -r"
[192.168.122.101] run: /bin/bash -l -c "uname -r"
之前的输出:
[192.168.122.103] run: uname -r
[192.168.122.102] run: uname -r
[192.168.122.101] run: uname -r
相比于之前的输出,可以看出命令提示更加完整。
更多关于fabric的信息与使用,可以查看官方文档http://www.fabfile.org/