Python自动化运维与远程部署:fabric

0X00 安装fabric

使用pip可以轻松地安装fabric

1
pip install fabric

0X01 初次调用

在当前目录下创建一个名为fabfile.py的文件,填写文件内容如下:

1
2
3
4
5
6
# coding=utf-8
import fabric
def test():
print 'hello,world'

然后在当前目录下执行命令fab test就可以看到一条hello,world输出了。

0X02 浅显的道理

根据上面简单的例子可以看出来fab命令执行的时候会默认找到当前目录下的fabfile.py文件,找到后会用fab命令的参数去匹配fabfile.py中的函数名,执行相应的功能。

实际上当前目录可以没有fabfile.py,如果当前目录的上级目录中有fabfile.py是会采用上级目录中的fabfile.py的。而且文件名也不一定用fabfile.py,假设取了一个名为asdf.py的文件,那么只需要执行fab -f asdf.py就可以采用这个fabfile了。

0X03 执行本地命令

作为一个可以用来自动化运维和远程部署的库,运行本地命令是一个必不可少的功能。

1
2
3
4
5
6
7
# coding=utf-8
from fabric.api import local
def list_home_dir():
local('ls ~/.')

这里的local方法就是执行本地命令,并将输出打印出来。然后运行fab list_home_dir就可以看到自己~目录下的文件们了。当然,也可以带参数的。

1
2
def cp_file(file_a, file_b):
local('cp %s %s' % (file_a, file_b))

调用这个方法的时候可以通过fab cp_file:"file_a=~/hello.py,file_b=~/hello_b.py"这个命令将~/hello.py复制到hello_b.py

0X04 执行远程命令

fabric用处最大的一点就是远程执行命令了。使用下面这段代码,运行fab list_homefabric会使用你提供的登录信息通过SSH登录到远程机器上执行ls ~/.的命令。其中run()就是在远程机器上执行命令。

1
2
3
4
5
6
7
8
9
10
# coding=utf-8
from fabric.api import run, env
env.hosts = ['qcloud.just666.cn', ]
env.user = 'root'
env.password = '5L2g5b2T5oiR5YK75ZWK'
def list_home():
run('ls ~/.')

0X05 多台机器执行相同的命令

有的时候我们有多台机器,需要执行相同的命令,这种时候就可以用env.passwords来解决问题。通过hosts指定用户名和主机,用passwords指定密码,就可以同时登录到多台机器上了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from fabric.api import env, run
# 指定主机
env.hosts = [
'root@qcloud.just666.cn',
'root@aliyun.just666.cn',
'root@aws.just666.cn',
]
# 指定密码
env.passwords = {
'root@qcloud.just666.cn': '5L2g5b2T5oiR5YK75ZWK',
'root@aliyun.just666.cn': '5L2g5b2T5oiR5YK75ZWK',
'root@aws.just666.cn': '5L2g5b2T5oiR5YK75ZWK',
}
def hello():
run('ls ~/.')

其实我也不是很懂,既然env.passwords都已经制定了用户名,主机和密码为什么还要用hosts再指定一次呢?不是很懂,注释过hosts,就不能用了。

0X06 多台机器执行不同命令

不过通常情况下我们是有不止一台远程机器的,要不然也不会需要什么自动化了。那么假设我们有三台机器,分别是mysql/apache/nginx这三个服务,那么我们可以这么写脚本。这样我们可以通过fab start_firewalld启动三台机器的防火墙,使用fab start_mysql/start_httpd/start_nginx分别启动在这三台机器上的三个服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from fabric.api import env, run, roles
env.hosts = [
'root@mysql.just666.cn',
'root@apache.just666.cn',
'root@nginx.just666.cn',
]
env.passwords = {
'root@mysql.just666.cn': 'zhangHAO8',
'root@apache.just666.cn': '5L2g5b2T5oiR5YK75ZWK',
'root@nginx.just666.cn': '5L2g5b2T5oiR5YK75ZWK',
}
env.roledefs = {
'all': [
'root@mysql.just666.cn',
'root@apache.just666.cn',
'root@nginx.just666.cn',
],
'mysql': ['root@mysql.just666.cn', ],
'apache': ['root@apache.just666.cn', ],
'nginx': ['root@nginx.just666.cn', ],
}
@roles('all')
def start_firewalld():
run('systemctl start firewalld')
@roles('mysql')
def start_mysql():
run('systemctl start mysql')
@roles('apache')
def start_httpd():
run('systemctl start httpd')
@roles('nginx')
def start_nginx():
run('systemctl start nginx')

0X07 并发任务

通常情况下fabric是穿行执行任务的,假设有100台机器要执行相同的命令,虽然我们批量化了,但是他们依旧是串行的,导致效率比较低。这种时候我们可以采用@parallel装饰器来使方法变成并行的。比如有一个方法def test_speed用来测试机器的网速,需要持续一分钟才行,并且有很多台机器,那么这个@parallel就可以发挥作用了。

1
2
3
4
5
from fabric.api import parallel
@parallel
def test_speed():
run('test_speed')

此时这些任务就是并行的了,会快很多。不过如果真的有非常多的机器要并行,那么fabric可能扛不住,可以给并行数量设置一个上限@parallel(pool_size=10),这样就是最高10个并行任务了。

0X08 传文件

文件传输用的是scp的原理,分成两个方法,分别是get/put,用法也非常简单就像普通的cp一样。get('remote_file', 'local_file')put('local_file', 'remote_file')

1
2
3
4
5
6
7
8
9
10
11
from fabric.api import env, get, put
env.hosts = ['qcloud.just666.cn', ]
env.user = 'root'
env.password = '5L2g5b2T5oiR5YK75ZWK'
def get_vimrc():
get('~/.vimrc', '~/.vimrc')
def put_vimrc():
put('~/.vimrc', '~/.vimrc')

0X09 切目录

fabric中切目录要配合Python的with语法使用。共有cd/lcd这两种切目录方法,对应的是远程目录和本地目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from fabric.api import env, cd, lcd
env.hosts = ['qcloud.just666.cn', ]
env.user = 'root'
env.password = '5L2g5b2T5oiR5YK75ZWK'
def list_local_dir():
with lcd('/'): # 切到本地根目录
local('ls') # 在目录下执行命令
with lcd('~'): # 切到本地主目录
local('ls') # 在目录下执行命令
def list_remote_dir():
with cd('/'): # 切到远程根目录
run('ls') # 在目录下执行命令
with cd('~'): # 切到远程主目录
run('ls') # 在目录下执行命令

0X0A PATH

有的时候我们需要临时添加PATH,可以通过这种方法。

1
2
3
4
5
6
7
8
9
10
from fabric.api import env, run, path
env.hosts = ['qcloud.just666.cn', ]
env.user = 'root'
env.password = '5L2g5b2T5oiR5YK75ZWK'
def hello():
with path('/home/shawn/.envs/study_django/bin/'): # 添加目录到PATH中
run('echo $PATH') # 查看新的PATH
run('echo $PATH') # 退出去后PATH也被删除了

0X0B 环境变量

如果要临时修改环境变量的话可以这样子:

1
2
3
4
5
6
7
8
9
10
from fabric.api import env, run, local, shell_env
env.hosts = ['qcloud.just666.cn', ]
env.user = 'root'
env.password = '5L2g5b2T5oiR5YK75ZWK'
def hello():
with shell_env(JAVA_HOME='/opt/oracle/java'):
run('echo $JAVA_HOME') # 远程机器的环境变量被修改了
local('echo $JAVA_HOME') # 本地的环境变量也被临时修改了。

修改只是暂时的,退出with之后就会恢复原状的。

0X0C 带颜色输出

有的时候为了便与查看输出结果,我们可能会需要用不同颜色的字标示不同的内容。比如用红色标示失败,黄色标示警告,绿色标示成功等。fabric也可以轻松实现这个功能的。

1
2
3
4
5
6
from fabric.colors import *
def test_color():
print green("OK.")
print yellow("Warning")
print red("Error")