简介
fabric 是一个 Python 的库,同时它也是一个命令行工具。使用 fabric 提供的命令行工具,可以很方便地执行应用部署和系统管理等操作。
fabric 依赖于 paramiko 进行 ssh 交互,fabric 的设计思路是通过几个 API 接口来完成所有的部署,因此 fabric 对系统管理操作进行了简单的封装,比如执行命令、上传文件、并行操作和异常处理等。
安装:
pip3 install fabric3
。
fabric 是一个 Python 库的同时它还是一个命令行工具,可通过 --help
来查看它的使用说明:
$ fab --help
Usage: fab [options] <command>[:arg1,arg2=val2,host=foo,hosts='h1;h2',...] ...
Options:
-h, --help show this help message and exit
-d NAME, --display=NAME
print detailed info about command NAME
-F FORMAT, --list-format=FORMAT
formats --list, choices: short, normal, nested
-I, --initial-password-prompt
Force password prompt up-front
--initial-sudo-password-prompt
Force sudo password prompt up-front
-l, --list print list of possible commands and exit
...
使用
快速入门
fabirc 对 paramiko 进行了高层次的封装,让我们能简单的调用函数实现对多台服务器的批量操作,下面就以在所有服务器中执行 echo hello fabric > /tmp/hello.txt
为例。
新建名为 test01.py
的 python 脚本文件,内容如下:
from fabric.api import *
# 主机清单
env.hosts = ['192.168.0.130', '192.168.0.109']
# SSH 端口
env.port = 22
# 用户名
env.user = 'zze'
# 密码
env.password = '123'
def hello():
run('echo hello fabric > /tmp/hello.txt')
if __name__ == '__main__':
execute(hello)
其执行后的输出如下:
[192.168.0.130] Executing task 'hello'
[192.168.0.130] run: echo hello fabric > /tmp/hello.txt
[192.168.0.109] Executing task 'hello'
[192.168.0.109] run: echo hello fabric > /tmp/hello.txt
从其输出的结果就可以看出,该脚本在 192.168.0.130
和 192.168.0.109
这两台主机上执行了 echo hello fabric > /tmp/hello.txt
,这里可以登入两台主机检查一下:
$ ssh zze@192.168.0.130 'cat /tmp/hello.txt'
hello fabric
$ ssh zze@192.168.0.109 'cat /tmp/hello.txt'
hello fabric
在上面的操作示例中,执行操作实际是由 fabirc 库中的 execute
函数触发的,在上面介绍内容中有提到过,fabirc 不仅仅是个 Python 库,它还提供了命令行工具,所以我们也可以通过它的命令行工具来操作 fabric 脚本,如下:
# 查看可用的命令,一个命令对应 python 脚本中的一个函数,每个函数在 fabric 也被称为 task
# -f:指定操作的 fabric 脚本;
# -l:查看指定脚本中可执行的命令(函数、task);
$ fab -f test01.py -l
Available commands:
hello
# 执行指定的函数,可同时执行多个函数,以空格隔开即可
$ fab -f test01.py hello
[192.168.0.130] Executing task 'hello'
[192.168.0.130] run: echo hello fabric > /tmp/hello.txt
[192.168.0.109] Executing task 'hello'
[192.168.0.109] run: echo hello fabric > /tmp/hello.txt
Done.
Disconnecting from 192.168.0.130... done.
Disconnecting from 192.168.0.109... done.
当然,既然是函数,它也可以接受传参,现在来添加一个批量查看远程主机 /tmp/hello.txt
文件内容的函数到 test01.py
中:
def cat_hello(file_path='/tmp/hello.txt'):
sudo('cat {}'.format(file_path))
执行:
# 如有多个参数,以 , 隔开即可
$ fab -f test01.py cat_hello:file_path='/tmp/hello.txt'
[192.168.0.130] Executing task 'cat_hello'
[192.168.0.130] sudo: cat /tmp/hello.txt
[192.168.0.130] out: hello fabric
[192.168.0.130] out:
[192.168.0.109] Executing task 'cat_hello'
[192.168.0.109] sudo: cat /tmp/hello.txt
[192.168.0.109] out: hello fabric
[192.168.0.109] out:
Done.
Disconnecting from 192.168.0.130... done.
Disconnecting from 192.168.0.109... done.
下面对上述使用到的知识点进行一下说明:
1、run 函数:用于远程执行命令;
2、sudo 函数:同样是用于远程执行命令,只是执行命令前使用 sudo 提权;
3、env 对象:保存连接相关的配置信息,比如登录用户名 env.user、密码 env.password、端口 env.port 等,如果没有指定用户名那么默认使用当前用户,端口使用 22;
命令行参数
下面再来详细了解一下 fabric 的命令行选项,其可以通过 fab --help
查看,这里列出常用选项的说明:
-l:查看 task 列表;
-f:指定 fab 的入口文件,默认是 fabfile.py;
-g:指定网关设备,比如堡垒机环境下,填写堡垒机的 IP;
-H:在命令行指定目标服务器,用逗号分隔多个服务器;
-P:以并行方式运行任务,默认为串行;
-R 指定roel(角色),以角色名区分不同业务组设备;
-t:连接超时的时间,以秒为单位;
-w:命令执行失败时的警告,默认是终止任务;
-T:设置远程主机命令执行超时时间(秒);
--user:指定登录到远程主机的用户名;
--password:指定登录到远程主机的用户的密码;
-- '<command>':要执行的命令;
直接看如下示例:
$ fab -H 192.168.0.130 --port 22 --user='zze' --password='123' -- 'cat /tmp/hello.txt'
[192.168.0.130] Executing task '<remainder>'
[192.168.0.130] run: cat /tmp/hello.txt
[192.168.0.130] out: hello fabric
[192.168.0.130] out:
Done.
Disconnecting from 192.168.0.130... done.
env 对象详解
env
本身是一个全局唯一的字典,保存了 fabric 所有的环境配置信息,在 fabric 的实现中,env
是一个 _AttributeDict()
对象,之所以封装成 _AttributeDict()
对象,是因为它覆盖了 __getattr__
和 __setattr__
,使我们可以使用 对象.属性=值
的方式,操作字典。
查看源码:
env = _AttributeDict({
'abort_exception': None,
'again_prompt': 'Sorry, try again.',
'all_hosts': [],
'combine_stderr': True,
'colorize_errors': False,
'command': None,
'command_prefixes': [],
'cwd': '', # Must be empty string, not None, for concatenation purposes
'dedupe_hosts': True,
'default_port': default_port,
'eagerly_disconnect': False,
'echo_stdin': True,
'effective_roles': [],
'exclude_hosts': [],
'gateway': None,
'gss_auth': None,
'gss_deleg': None,
'gss_kex': None,
'host': None,
'host_string': None,
'lcwd': '', # Must be empty string, not None, for concatenation purposes
'local_user': _get_system_username(),
'output_prefix': True,
'passwords': {},
'path': '',
'path_behavior': 'append',
'port': default_port,
'real_fabfile': None,
'remote_interrupt': None,
'roles': [],
'roledefs': {},
'shell_env': {},
'skip_bad_hosts': False,
'skip_unknown_tasks': False,
'ssh_config_path': default_ssh_config_path,
'sudo_passwords': {},
'ok_ret_codes': [0], # a list of return codes that indicate success
# -S so sudo accepts passwd via stdin, -p with our known-value prompt for
# later detection (thus %s -- gets filled with env.sudo_prompt at runtime)
'sudo_prefix': "sudo -S -p '%(sudo_prompt)s' ",
'sudo_prompt': 'sudo password:',
'sudo_user': None,
'tasks': [],
'prompts': {},
'use_exceptions_for': {'network': False},
'use_shell': True,
'use_ssh_config': False,
'user': None,
'version': get_version('short')
})
class _AttributeDict(dict):
def __getattr__(self, key):
try:
return self[key]
except KeyError:
# to conform with __getattr__ spec
raise AttributeError(key)
def __setattr__(self, key, value):
self[key] = value
def first(self, *names):
for name in names:
value = self.get(name)
if value:
return value
在运行时可通过序列化的方式打印该对象的各属性值:
import json
from fabric.api import env
print(json.dumps(env, indent=3))
常用的 env
属性有如下:
env.hosts:定义目标主机,可以用 IP 或主机名表示,以 Python 的列表形式定义,如 env.hosts=["192.168.88.2", "192.168.88.3"];
env.exclude_hosts:排除指定主机,如 env.exclude_hosts=['192.168.88.2'];
env.user:定义用户名,如 env.user='root';
env.port:定义端口,默认为 22,如 env.port='22';
env.password:定义密码,如 env.password='123.com';
env.passwords:定义多个密码,不同主机对应不同密码,如:env.passwords={'root@192.168.88.2:22':'123.com', 'root@192.168.88.2:22':'123456'};
env.gateway:定义网关(中转,堡垒机)ip,如 env.gateway='192.168.88.10';
env.roledefs:定义角色组,比如 web 组合 db 组主机区分开来 env_roledefs={'webserver':['192.168.88.2', '192.168.88.3'], 'dbserver':['192.168.88.4']};
env.reject_unkown_hosts:控制未知 host 的行为,默认 True,类似于 SSH 的 StrictHostKeyChecking 的选项设置为 no,不进行公钥确认;
env.parallel:控制 task 是否并行执行,默认为 False;
env.<var>:自定义全局变量,格式:env. + '变量名', 如 env.age, env.sex 等;
针对不同主机不同密码的情况,可以使用如下的方式:
env.hosts = [
'zze@192.168.10.201:22',
'zze@192.168.10.202:22',
'zze@192.168.10.203:22'
]
env.passwords = {
'zze@192.168.10.201:22':'12301',
'zze@192.168.10.202:22':'12302',
'zze@192.168.10.203:22':'12303'
常用函数
run()
run
函数用于在远程服务器上执行命令,其第一个参数是要执行的命令字符串,除此之外还有一个重要的参数 pty
,如果我们执行命令以后需要有一个常驻的守护进程,那么就需要设置 pty=False
,避免因为 fabric 退出导致进程的退出,比如:
run('./prometheus &',pty=False)
run
函数其实还有返回值,返回对象中保存了执行命令输出的信息,同时还可通过对象的 return_code
属性判定命令的执行是否成功(成功为 0
,否则为 非 0
)。
def test():
# 隐藏其它信息输出,后面会详述
with settings(hide('everything'), warn_only=True):
result = run('cat /tmp/hello.txt', pty=False)
print(result) # 输出结果
print(result.return_code) # 是否成功
该 task 的执行结果如下:
[192.168.0.130] Executing task 'start_nginx'
hello fabric
0
Done.
Disconnecting from 192.168.0.130... done.
sudo()
使用同 run
函数,只是它会使用 sudo
提权后再进行操作。
local()
用于在本地执行命令,local
是对 Python 的 subprocess 模块的封装,更复杂的功能可以直接使用 subprocess 模块,包含 capture
参数,默认为 False
,表示 subprocess 输出的信息进行显示,如果不想显示,那么指定 capture=True
即可。
def test():
result = local('hostname', capture=True)
print(result) # 命令的输出结果
print(result.failed) # 是否失败
print(result.succeeded) # 是否成功
该 task 的执行结果如下:
[192.168.0.130] Executing task 'test'
[localhost] local: hostname
zbook
False
True
get()
从远程服务器上获取文件,通过 remote_path
参数声明从何处下载(支持通配符),通过 local_path
表示下载到何处。
get(remote_path='/etc/passwd',local_path='./passwd')
put()
将本地的文件上传到远程服务器,参数与 get
相似,此外,还可以通过 mode
参数执行远程文件的权限配置。
put(remote_path='~', local_path='./test01.py', mode='644')
reboot()
重启远程服务器,可以通过 wait
参数设置等待几秒钟重启。
reboot(wait=30)
propmt()
用以在 fabric 执行任务的过程中与管理员进行交互,类似于 Python 的 input
函数,但它还提供了输入信息的校验功能。
input_num = prompt('input a number:', validate=int, default='')
confirm()
输出确认提示信息。
from fabric.contrib.console import confirm
result =confirm('确认?')
lcd()
本地切换工作目录。
# 切换目录,with 块内的操作会在 ~ 中执行
with lcd('~'):
local('echo 111 > testlcd.txt')
local('pwd')
# 到这一步会切换回之前的目录
local('pwd')
cd()
同 lcd
,唯一不同的是在远程主机上切换工作目录。
# 切换目录,with 块内的操作会在 ~ 中执行
with cd('~'):
local('echo 111 > testlcd.txt')
local('pwd')
# 到这一步会切换回之前的目录
local('pwd')
path()
配置远程服务器 PATH
环境变量,只对当前会话有效,不会影响远程服务器的其他操作。PATH
的修改支持如下几种模式:
append
:默认行为,将给定的路径添加到PATH
后面;prepend
:将给定的路径添加到PATH
的前面;replace
:替换当前环境的PATH
变量;
# 改变的 PATH 仅在 with 块内生效
with path('/tmp','prepend'):
run("echo $PATH")
run("echo $PATH")
prefix()
它接受一个命令作为参数,表示在其执行每一条命令之前,都要先执行 prefix
的命令参数。
with prefix('echo 123'):
# echo '123' && echo '456'
run('echo 456')
# echo '123' && echo '789'
run('echo 789')
shell_env()
设置临时的环境变量。
with shell_env(HTTP_PROXY='192.168.0.138:41091'):
# export HTTP_PROXY="192.168.0.138:41091" && echo $HTTP_PROXY"
run('echo $HTTP_PROXY')
run('echo $HTTP_PROXY')
settings()
通用配置,用于临时覆盖 env
变量。
with settings(user='zze'): # 临时修改用户名为 zze
run('whoami')
run('whoami')
hide()
用于隐藏指定类型的输出信息,hide
定义的可选类型有如下 7 种:
status
:状态信息,如服务器断开链接,用户使用 ctrl+C 等,如果 fabric 顺利执行,不会有状态信息;aborts
:终止信息,一般将 fabric 当作库使用的时候需要关闭;warnings
:警告信息,如grep
的字符串不在文件中;running
:fabric 运行过程中的数据;stdout
:执行 shell 命令的标准输出;stderr
:执行 shell 命令的错误输出;user
:用户输出,类似于 Python 中的print
函数;
为方便使用,fabric 对以上 7 种类型做了进一步的封装:
output
:包含stdout
、stderr
;everything
:包含stdout
、stderr
、warnings
、running
、user
;commands
:包含stdout
、running
;
with settings(hide('everything'), warn_only=True):
run('cat hello.txt')
show()
与 hide
相反,表示显示指定类型的输出。
with settings(show('commands'), warn_only=True):
run('echo hello')
quiet()
隐藏全部输出,仅在执行错误的时候发出告警信息,功能等同于 with settings(hide('everything'),warn_only=True)
。
with settings(quiet(),warn_only=True):
run("echo hello")
装饰器
fabric 提供的装饰器用来控制如何执行操作、在哪些服务器上执行这些操作,主要有如下几个装饰器:
hosts
:定制执行 task 的服务器列表;roles
:定义执行 task 的 role 列表;parallel
:并行执行 task;serial
:串行执行 task;task
:定义一个 task;runs_once
:该 task 只执行一次;
@task
task
装饰器用来标识一个函数为 task,表示该函数可以在远程主机上执行,如果使用了 task
装饰器,那么每个未被 task
装饰的函数就不是 task,如果没有使用 task
装饰器,则所有函数都可以作为 task 在远程主机执行。
from fabric.api import *
env.user = 'root'
env.password = 'root1234'
@task
def hello():
run('echo hello')
def world():
run('echo world')
使用 fab
命令查看可执行的 task 如下:
$ fab -f test02.py -l
Available commands:
hello
@host
fabric 提供了非常灵活的方式让我们可以指定对哪些远程服务器执行操作,根据我们前面的知识,我们已经知道有如下两种方式:
- 通过
env.hosts
来定义要执行 task 的主机列表,指定 host 时,可以同时指定用户名和端口号:username@hostname:port
; - 在使用
fab
命令的时候使用-H
参数,或在 task 名称后面定义hosts
变量,如fab mytask:hosts="host1;host2"
;
除了上述两种方式外,我们还可以使用 host
装饰器来指定某 task 仅能在某些主机上执行,如下:
from fabric.api import *
env.hosts = [
'root@192.168.0.130:22',
'root@192.168.0.109:22',
]
env.passwords = {
'root@192.168.0.130:22': '123',
'root@192.168.0.109:22': '123',
}
@hosts('root@192.168.0.130:22')
@task
def hello():
run('hostname')
@task
def world():
run('hostname')
此时执行 hello
task 则仅会在 192.168.0.130
主机上执行。
如果去掉上面的 @host
装饰器,此时也可以使用 fab
命令达到相同的效果,如下:
fab -f test03.py hello:hosts="root@192.168.0.130"
[root@192.168.0.130] Executing task 'hello'
[root@192.168.0.130] run: hostname
[root@192.168.0.130] out: w130
[root@192.168.0.130] out:
Done.
Disconnecting from root@192.168.0.130... done.
@role
role 是对服务器进行分类的手段,通过 role
装饰器可以定义服务器的角色,以便对具有相同功能的服务器批量执行不同的操作。
from fabric.api import *
env.hosts = [
'gyadmin@192.168.0.130:22',
'gyadmin@192.168.0.109:22',
]
env.passwords = {
'gyadmin@192.168.0.130:22': 'Speakin@2020#spk',
'gyadmin@192.168.0.109:22': 'Speakin@2020#spk',
}
# 定义 Role
env.roledefs = {
# 定义一个名称为 web 的 Role
'web': ['gyadmin@192.168.0.130:22',],
# 定义一个名称为 db 的 Role
'db': ['gyadmin@192.168.0.109:22', ]
}
# 只对 Role 为 web 的主机进行操作
@roles('web')
@task
def hello_web():
run('echo web')
# 只对 Role 为 db 的主机进行操作
@roles('db')
@task
def hello_db():
run('echo db')
@parallel
通知 fabric 并行执行 task,它接受一个 pool_size
作为参数(默认为 0
),表示可以有几个任务并行执行。
@parallel(pool_size=3)
@task
def hello():
run('echo hello')
@serial
强制被装饰的 task 串行执行,使用该装饰器时优先级最高,即便是指定了并发执行的参数。
@task
@serial
def hello():
run('echo hello')
@runs_once
让被装饰的 task 只执行一次,防止指定的 task 被多次调用。
@runs_once
def get_name():
name = prompt('please input a your name:')
return name
@task
def print_name():
name = get_name()
run('echo ' + name)
补充
utils 模块
fabric.utils
模块包含一些辅助用的功能函数,常用的函数如下:
abort
:终止函数执行,打印错误信息到 stderr,并且以退出码1
退出;warn
:输出警告信息,但是不会终止函数的执行;puts
:打印输出,类似于 Python 中的print
函数;
@task
@runs_once
def test_utils():
abort('----->abort') # 执行到这里时,直接退出
warn('----->warn') # 会发出提示信息,不会退出
puts('----->puts') # 会打印括号中的信息
带颜色的输出
fabric 为了让输出日志更具有可读性,对命令行终端的颜色输出进行了封装,使用 print
打印带有不同颜色的文本,这些颜色包含在 fabric.color
s中。像 warn
、puts
打印输出的内容也可以直接渲染颜色,常用函数有如下:
blue(text,blod=False)
:蓝色;cyan(text,blod=False)
:淡蓝色;green(text,blod=False)
:绿色;magenta(text,blod=False)
:紫色;red(text,blod=False)
:红色;white(text,blod=False)
:白色;yellow(text,blod=False)
:黄色;
from fabric.colors import *
def test_color():
print(blue('蓝色',bold=True))
print(cyan('淡蓝色',bold=False))
print(green('绿色',bold=True))
print(magenta('紫色',bold=True))
print(red('红色',bold=False))
print(white('白色',bold=True))
print(yellow('黄色',bold=False))
参考:
评论区