From:http://python.jobbole.com/87241/
From:Python模块学习 - fabric(Python3):https://www.cnblogs.com/xiao-apple36/p/9124292.html
fabric 官网英文文档:http://www.fabfile.org/
fabric 中文站点:http://fabric-chs.readthedocs.io/zh_CN/chs/
python三大神器之一fabric使用:https://www.cnblogs.com/rufus-hua/p/5144210.html
如何用Fabric实现无密码输入提示的远程自动部署:https://blog.csdn.net/slvher/article/details/50414675
fabric实现远程操作和部署:http://python.jobbole.com/83716/
自动化运维管理 fabric:http://www.ttlsa.com/python/automation-operation-and-maintenance-tool-fabric/
Python3自动化运维之Fabric模版详解:https://www.imooc.com/article/38448
《Python自动化运维技术与最佳实践》
Fabric 是一个 Python 的库,同时它也是一个命令行工具。它提供了丰富的同 SSH 交互的接口,可以用来在本地或远程机器上自动化、流水化地执行 Shell 命令。使用 fabric 提供的命令行工具,可以很方便地执行应用部署和系统管理等操作。因此它非常适合用来做应用的远程部署及系统维护。其上手也极其简单,你需要的只是懂得基本的 Shell 命令。
fabric 依赖于 paramiko 进行 ssh 交互,fabric 的设计思路是通过几个 API 接口来完成所有的部署,因此 fabric 对系统管理操作进行了简单的封装,比如执行命令,上传文件,并行操作和异常处理等。
paramiko 是一个用于做远程控制的模块,使用该模块可以对远程服务器进行命令或文件操作,fabric 和 ansible 内部的远程管理就是使用的paramiko来现实。
Fabric是一个用于应用(批量)部署和系统(批量)管理的Python库和命令行工具,关于Fabric的介绍请参考:http://www.fabfile.org/。 Capistrano是一个用Ruby语言编写的远程服务器自动化和部署工具,关于Capistrano的介绍请参考:http://capistranorb.com/。 本文仅使用Python语言和部分Linux或Windows系统命令,借助Fabric模块和Capistrano的部署思路,实现在Linux平台和Windows平台的自动化部批量署应用或实现批量系统管理(批量执行命令,批量上传文件等),其中Fabric部分利用Fabric的模块,Capistrano部分用Python语言按照Capistrano的部署思路“重写(Python实现Capistrano)”。关于Capistrano的“重写”说明。Capistrano使用Ruby语言写的,在部署很多应用上有很大的优势,个人认为它设计最好的部分就是它的目录结构。目录结构的详细信息可以参考:http://capistranorb.com/documentation/getting-started/structure/#。有了这个目录结构可以轻松实现每一个部署版本的备份与回滚,之前用Bash Shell“重写”过一次,可以参考本文《Linux Shell脚本之远程自动化部署java maven项目 》,这次相当于用Python重写一下(Capistrano还有大量精髓的东西,本文算是抛砖引玉,其他的日后再发掘),毕竟Shell脚本不容易实现像Fabric那样的批量操作。 |
Fabric的官网是 www.fabfile.org,源码托管在Github上。你可以clone源码到本地,然后通过下面的命令来安装。
python setup.py develop
在执行源码安装前,你必须先将Fabric的依赖包Paramiko装上。所以,个人还是推荐使用pip安装,只需一条命令即可:
pip install fabric
python3 安装时使用的是fabric3 :( 安装fabric3之前,需要先卸载fabric。)
# fabric3 支持 python3
pip uninstall fabric
pip3 install fabric3
由于 fabric 不只是一个Python 模块,fabric 还是一个命令行工具,可以通过 help 进行命令的了解
[king@ubuntu]$ fab -V
Fabric 2.2.0
Paramiko 2.4.1
Invoke 1.1.0
[king@ubuntu]$ fab -h
Usage: fab [--core-opts] task1 [--task1-opts] ... taskN [--taskN-opts]
Core options:
--complete Print tab-completion candidates for given parse remainder.
--hide=STRING Set default value of run()'s 'hide' kwarg.
--no-dedupe Disable task deduplication.
--prompt-for-login-password Request an upfront SSH-auth password prompt.
--prompt-for-passphrase Request an upfront SSH key passphrase prompt.
--prompt-for-sudo-password Prompt user at start of session for the sudo.password config value.
--write-pyc Enable creation of .pyc files.
-c STRING, --collection=STRING Specify collection name to load.
-d, --debug Enable debug output.
-D INT, --list-depth=INT When listing tasks, only show the first INT levels.
-e, --echo Echo executed commands before running.
-f STRING, --config=STRING Runtime configuration file to use.
-F STRING, --list-format=STRING Change the display format used when listing tasks. Should be one of: flat (default),
nested, json.
-h [STRING], --help[=STRING] Show core or per-task help and exit.
-H STRING, --hosts=STRING Comma-separated host name(s) to execute tasks against.
-i, --identity Path to runtime SSH identity (key) file. May be given multiple times.
-l [STRING], --list[=STRING] List available tasks, optionally limited to a namespace.
-p, --pty Use a pty when executing shell commands.
-r STRING, --search-root=STRING Change root directory used for finding task modules.
-S STRING, --ssh-config=STRING Path to runtime SSH config file.
-V, --version Show version and exit.
-w, --warn-only Warn, instead of failing, when shell commands fail.
fab命令默认被安装到Python的目录下,需要创建软链接
[root@saltstack ~]# find / -type f -name "fab"
/usr/local/python2.7.10/bin/fab
[root@saltstack ~]# ln -s /usr/local/python2.7.10/bin/fab /usr/bin/fab
fabric 简介 和 各个版本差异比较:http://www.mamicode.com/info-detail-2337088.html
fabric 的典型使用方式就是,创建一个 Python 文件,该文件包含一到多个函数,然后使用 fab 命令调用这些函数。这些函数在 fabric 中成为 task。
from fabric.api import *
from fabric.contrib.console import confirm
from fabric.utils import abort
from fabric.colors import *
env.hosts = ['192.168.5.128']
env.port = 22
env.user = 'root'
env.password = 'mysql123'
def hostname():
run('hostname')
def ls(path='.'):
run('ls {0}'.format(path))
def tail(path='/etc/pas', line=10):
run('tail -n {0} {1}'.format(line, path))
def hello():
with settings(hide('everything'),warn_only=True): # 关闭显示
result = run('anetstat -lntup|grep -w 25')
print(result) # 命令执行的结果
print(result.return_code) # 返回码,0表示正确执行,1表示错误
print(result.failed)
fab 命令执行时,默认引用一个名为 fabfile.py 的文件,我们也可以通过 -f 来进行指定。
这里使用了三个 fabric 的封装:
1、获取任务列表
pyvip@Vip:~/utils$ fab -f fab_utils.py --list
Available commands:
hello
hostname
ls
tail
# 2、执行hostname函数
pyvip@Vip:~/utils$ fab -f fab_utils.py hostname
[192.168.5.128] Executing task 'hostname'
[192.168.5.128] run: hostname
[192.168.5.128] out: china
[192.168.5.128] out:
Done.
Disconnecting from 192.168.5.128... done.
# 3、多个参数的情况
pyvip@Vip:~/utils$ fab -f fab_utils.py ls:/
[192.168.5.128] Executing task 'ls'
[192.168.5.128] run: ls /
[192.168.5.128] out: bin boot data dev etc home lib lib64 lost+found media misc mnt net opt proc root sbin selinux srv sys tmp usr var
[192.168.5.128] out:
Done.
Disconnecting from 192.168.5.128... done.
需要注意的是:
万事从Hello World开始,我们创建一个”fabfile.py”文件,然后写个hello函数:
def hello():
print "Hello Fabric!"
现在,让我们在 ”fabfile.py” 的目录下 执行命令:
fab hello
你可以在终端看到”Hello Fabric!”字样。
简单解释下,”fabfile.py”文件中每个函数就是一个任务,任务名即函数名,上例中是”hello”。”fab”命令就是用来执行”fabfile.py”中定义的任务,它必须显式地指定任务名。你可以使用参数”-l”来列出当前”fabfile.py”文件中定义了哪些任务:
fab -l
任务可以带参数,比如我们将hello函数改为:
def hello(name, value):
print "Hello Fabric! %s=%s" % (name,value)
此时执行hello任务时,就要传入参数值:
fab hello:name=Year,value=2016
Fabric的脚本建议写在”fabfile.py”文件中,如果你想换文件名,那就要在”fab”命令中用”-f”指定。比如我们将脚本放在”script.py”中,就要执行:
fab -f script.py hello
python3 环境执行
fab命令作为fabric程序的入口提供了,丰富的参数调用.
# -l:查看task列表
# -f:指定fab的入口文件,默认是fabfile.py
# -g:指定网管设备,比如堡垒机环境下,填写堡垒机的IP
# -H:在命令行指定目标服务器,用逗号分隔多个服务器
# -P:以并行方式运行任务,默认为串行
# -R:以角色区分不同的服务
# -t:连接超时的时间,以秒为单位
# -w:命令执行失败时的警告,默认是终止任务
# -- Fabric参数,其他包含fabric脚本的中的参数的快捷操作,比如--user,--port,或者直接跟要执行的Linux命令
如下例子,不写一行代码,获取主机的所有 ip 地址
pyvip@Vip:~/utils$ fab -H 192.168.5.128 --port 22 --user='root' --password='mysql123' -- 'ip addr'
[192.168.5.128] Executing task ''
[192.168.5.128] run: ip a
[192.168.5.128] out: 1: lo: mtu 16436 qdisc noqueue state UNKNOWN
[192.168.5.128] out: link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
[192.168.5.128] out: inet 127.0.0.1/8 scope host lo
[192.168.5.128] out: inet6 ::1/128 scope host
[192.168.5.128] out: valid_lft forever preferred_lft forever
[192.168.5.128] out: 2: eth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000
[192.168.5.128] out: link/ether 00:0c:29:96:0a:a0 brd ff:ff:ff:ff:ff:ff
[192.168.5.128] out: inet 192.168.5.128/24 brd 192.168.5.255 scope global eth0
[192.168.5.128] out: inet6 fe80::20c:29ff:fe96:aa0/64 scope link
[192.168.5.128] out: valid_lft forever preferred_lft forever
[192.168.5.128] out: 3: pan0: mtu 1500 qdisc noop state DOWN
[192.168.5.128] out: link/ether 7a:4d:51:6c:c2:cd brd ff:ff:ff:ff:ff:ff
示例代码:
#!/usr/bin/python
# -*- coding:utf-8 -*-
from fabric.api import *
# 设置服务器登录参数
env.roledefs = {
# 操作一致的放一组,一组执行同一个操作
'servers1':['root@linux2:22',],
# 第二组
'servers2':['root@linux3:22',]
}
# 本机操作
def localtask():
local('/usr/local/nginx/nginx')
# servers1服务器组操作
@roles('servers1')
def task1():
run('/usr/local/tomcat/bin/startup.sh')
# servers2 服务器组操作
@roles('servers2')
def task2():
run('/usr/local/tomcat/bin/startup.sh')
# 执行任务
def doworks():
execute(localtask)
execute(task1)
execute(task2)
以上代码,就是简单的在本地启动nginx服务器,在linux1和linux2上启动了tomcat服务器,为了接受nginx服务器的代理,这里专门使用分组的方式为了适应机器比较多的集群的需要,另外这里没有设置服务器的密码,一是为了服务器的安全;另外集群间建议设置ssh免密登录,脚本就不用设置密码了,方法doworks执行的就是最终汇总的任务,保存之后,开始执行:
fab -f fabrictest.py doworks
执行之后,脚本会自动的依次执行指定的命令,从控制台可以看到输出,也可以在程序适当位置添加输出,打印一些必要信息
到这里,就看到fabric这个工具的功能确实很强大,更多的编程接口可以查看官网网站的文档:http://www.fabfile.org/
介绍 fabric 中的 env 对象,以及其他的比如执行命令模块,上传文件等。
env是一个全局唯一的字典,保存了Fabric所有的配置,在Fabric的实现中,他是一个_AttributeDict()对象,之所以封装成_AttributeDict()对象,是覆盖了__getattr__和__setattr__,使我们可以使用“对象.属性=值”的方式,操作字典。
我们可以通过源码的方式,查看env的配置参数,或者使用如下方式查看:
import json
from fabric.api import env
print(json.dumps(env, indent=3))
def hell(name='world'):
print('hello %s' % name)
-----------------------------------------------
结果
pyvip@Vip:~/utils$ fab -f fab_utils.py -l
{
"show": null,
"": true,
"sudo_user": null,
"default_port": "22",
"key_filename": null,
"path": "",
"hosts": [
"192.168.5.128"
],
"host_string": null,
"ok_ret_codes": [
0
],
"always_use_pty": true,
"fabfile": "fab_utils.py",
"echo_stdin": true,
"again_prompt": "Sorry, try again.",
"command": null,
"forward_agent": false,
"command_prefixes": [],
"cwd": "",
"connection_attempts": 1,
"linewise": false,
"gateway": null,
"use_exceptions_for": {
"network": false
……
env 对象的作用是定义 fabfile 的全局属性设定。下面对各属性进行说明:
针对不同主机不同密码的情况,可以使用如下的方式:
env.hosts = [
'[email protected]:22',
'[email protected]:22',
'[email protected]:22'
]
env.passwords = {
'[email protected]:22':'123456201',
'[email protected]:22':'123456202',
'[email protected]:22':'123456203'
使用示例(fabric_3.py):
from fabric.api import run,cd,env,hosts
env.hosts=['192.168.20.140:22','172.16.1.150:22'] # env.hosts=['user@ip:port',] ssh要用到的参数格式
env.password='strong'
def host_type():
with cd('/tmp/'):
run('du -ksh *')
执行命令: [root@saltstack fabric]# fab -f fabric_3.py host_type
多台服务器混合,需要在不同服务器进行不同操作时(fabric_4.py)
#!/usr/bin/env python
# coding:utf-8
from fabric.api import env,roles,run,execute
env.roledefs = {
'server1': ['[email protected]:22',],
'server2': ['[email protected]:22', ]
}
env.password = 'strong'
@roles('server1')
def task1():
run('ls /home/ -l | wc -l')
@roles('server2')
def task2():
run('du -sh /home')
def test(): # 调节主机组和主机组执行操作的顺序
execute(task2)
execute(task1)
执行命令:[root@saltstack fabric]# fab -f fabric_4.py test
密码管理,看文档
1. host,user,port,password 配置列表,所有的都写在一个文件或者直接搞到脚本里,当然这个更........
代码如下:
env.hosts = [
'host1',
'host2'
]
env.passwords = {
'host1': "pwdofhost1",
'host2': "pwdofhost2",
}
或者
env.roledefs = {
'testserver': ['host1', 'host2'],
'realserver': ['host3', ]
}
env.passwords = {
'host1': "pwdofhost1",
'host2': "pwdofhost2",
'host3': "pwdofhost3",
}
Fabric的核心API主要有7类:带颜色的输出类(color output)、上下文管理类(context managers)、装饰器类(decorators)、网络类(network)、操作类(operations)、任务类(tasks)、工具类(utils)。
Fabric提供了一组操作简单但功能强大的fabric.api命令集,简单地调用这些API就能完成大部分应用场景的需求。
Fabric.api 是Fabric中最常用也是最核心的模块。可以使用此模块来实现与远程服务器的交互。简单的使用这些API就可以完成大部分应用场景的需求。Fabric支持的常用命令及说明如下:
示例代码 1:
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()
尽管我只保留了前两台主机的返回值,但是也可以从中发现。我有多少台主机,脚本便执行了多少次本地的uname -r
。在实际应用中,有很多类似内核信息的本地命令,我只需要执行一次。甚至多次执行会出现错误,这时我就需要使用@runs_once
来对函数进行修饰,具体使用也非常简单,如下:
@runs_once #指定下一行的函数在运行时,只运行一次。
def local_uname():
local('uname -r')
新脚本执行之后就会发现本地的内核信息只输出了一次。
示例代码 2:
查看远程服务器的文件夹列表
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
run():在远程服务器上执行 Linux命令,还有一个重要的参数 pty,如果我们执行命令以后需要有一个常驻的服务进程,那么就需要设置 pty=False,避免因为 Fabric 退出导致进程的退出。
run('service mysqld start',pty=False)
关于 " fabric nohup " 问题,可以百度 " fabric nohup "。
这里给出两个解决方案的文章地址:
Fabric 与 nohup 的问题:https://segmentfault.com/a/1190000000612750
Fabric With Nohup 执行方式:https://blog.csdn.net/orangleliu/article/details/30223757
PS:上面命令执行完毕会返回输出的信息,我们可以定义变量接受,同时这个返回信息有一个方法return_code,当返回的是正确执行的结果时code为0,否则不为0
def hello():
with settings(hide('everything'), warn_only=True): # 关闭显示
result = run('anetstat -lntup|grep -w 25')
print(result) # 命令执行的结果
print(result.return_code) # 返回码,0表示正确执行,1表示错误
其实 Fabric 真正强大之处不是在执行本地命令,而是可以方便的执行远程机器上的 Shell 命令。它通过 SSH 实现。你需要的是在脚本中配置远程机器地址及登录信息:
在 远程机器 执行命令 示例方法 1:
from fabric.api import *
env.passwords = {
"[email protected]:22":"password"
}
@hosts("[email protected]:22")
def hello():
run("ls -l ~")
我们可以通过设置 env.passwords 来避免在运行过程中输密码,注意ip后面需要加端口号,示例中的22是ssh的端口号。
在 远程机器 执行命令 示例方法 2:
from fabric.api import run, env
env.hosts = ['example1.com', 'example2.com']
env.user = 'bjhee'
env.password = '111111'
def hello():
run('ls -l /home/bjhee/')
run() 方法可以用来执行远程Shell命令。上面的任务会分别到两台服务器”example1.com”和”example2.com”上执行”ls -l /home/bjhee/”命令。这里假设两台服务器的用户名都是”bjhee”,密码都是6个1。你也可以把用户直接写在hosts里,比如:
env.hosts = ['[email protected]', '[email protected]']
如果你的”env.hosts”里没有配置某个服务器,但是你又想在这个服务器上执行任务,你可以在命令行中通过”-H”指定远程服务器地址,多个服务器地址用逗号分隔:
fab -H [email protected],[email protected] hello
另外,多台机器的任务是串行执行的,关于并行任务的执行我们在之后会介绍。
如果对于不同的服务器,我们想执行不同的任务,上面的方法似乎做不到,那怎么办?我们要对服务器定义角色:
from fabric.api import env, roles, run, execute, cd
env.roledefs = {
'staging': ['[email protected]','[email protected]'],
'build': ['[email protected]']
}
env.passwords = {
'staging': '11111',
'build': '123456'
}
@roles('build')
def build():
with cd('/home/build/myapp/'):
run('git pull')
run('python setup.py')
@roles('staging')
def deploy():
run('tar xfz /tmp/myapp.tar.gz')
run('cp /tmp/myapp /home/bjhee/www/')
def task():
execute(build)
execute(deploy)
现在让我们执行:$ fab task
这时 Fabric 会先在一台 build 服务器上执行 build 任务,然后在两台 staging 服务器上分别执行 deploy 任务。”@roles” 装饰器指定了它所装饰的任务会被哪个角色的服务器执行。
如果某一任务上没有指定某个角色,但是你又想让这个角色的服务器也能运行该任务,你可以通过 ”-R” 来指定角色名,多个角色用逗号分隔:$ fab -R build deploy
这样 ”build” 和 ”staging” 角色的服务器都会运行 ”deploy” 任务了。注:”staging” 是装饰器默认的,因此不用通过 ”-R” 指定。
此外,上面的例子中,服务器的登录密码都是明文写在脚本里的。这样做不安全,推荐的方式是设置 SSH 自动登录,具体方法大家可以去网上搜搜。
实际使用中服务器的网关一帮的设有防火墙,我们无法直接ssh管理服务器。Fabric也提供给我们一种夸网关的使用方式。只需要在配置文件中定义好网关的ip地址,即需要设置 env.gateway。具体如下:
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'
}
在内网机器执行命令 示例代码 2:
from fabric.api import *
env.gateway = "[email protected]:22"
env.passwords = {
"[email protected]:22":"password1"
"[email protected]:22":"password2"
}
@hosts("[email protected]:22")
def hello():
run("ls -l ~")
密码管理
1)Fabric既支持ssh公钥认证也支持管理密码的机制
2)Fabric的密码管理机制提供了两层密码。如果你的server有相同的密码,可以在env.password中设置默认的密码;如果server密码不同,还可以在env.passwords中设置(host,password)对,为每个server设置单独的ssh密码。
网关模式文件上传与执行
本例通过定义env.gateway网关模式,即俗称的中转、堡垒机环境。通过网关对其他主机进行文件上传和执行。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from fabric.api import *
from fabric.context_managers import *
from fabric.contrib.console import confirm
env.user = 'root'
env.gateway = '192.168.1.23' #定义堡垒机IP,作为文件上传、执行的中转设置
env.hosts = ['192.168.1.21','192.168.1.22']
env.passwords = {
'[email protected]:22':'123456',
'[email protected]:22':'abcdef',
'[email protected]:22':'123abc', #堡垒机账号信息
}
lpackpath = '/home/install/lnmp.tar.gz' #本地安装包路径
rpackpath = '/tmp/install' #远程安装包路径
@task
def put_task(): #上传文件
run('mkdir -p /tmp/install')
#默认情况下,当命令执行失败时,Fabric会停止执行后续命令。有时,我们允许忽略失败的命令继续执行,比如run(‘rm /tmp/abc')在文件不存在的时候有可能失败,这时可以用with settings(warn_only=True):执行命令,这样Fabric只会打出警告信息而不会中断执行。
with settings(warn_only=True):
result = put(lpackpath,rpackpath) #上传
if result.failed and not confirm('put file failed,Continue[Y/N]?'):
abort('Aborting file put task!')
@task
def run_task(): #安装
with cd('/tmp/install'):
run('tar -zxvf lnmp.tar.gz')
with cd('lnmp/'): #使用with继承/tmp/install目录位置状态
run('./centos.sh')
@task
def go(): #上传、安装组合命令
put_task()
run_task()
simple3.py
simple3.py
执行命令:
#上传文件
fab simple3.py put_task
#执行文件
fab simple3.py run_task
#上传并执行
fab simple3.py go
sudo():功能与 run() 方法类似,只是是使用管理员权限在远程服务器上执行shell命令,区别是它相当于在 Shell 命令前加上了”sudo”,所以拥有超级用户的权限。使用此功能前,你需要将你的用户设为sudoer,而且无需输密码。具体操作可参见我的这篇文章。还有一个重要的参数 pty,如果我们执行命令以后需要有一个常驻的服务进程,那么就需要设置 pty=False,避免因为 Fabric 退出导致进程的退出。
from fabric.api import env, sudo
env.hosts = ['[email protected]', '[email protected]']
env.password = '111111'
def hello():
sudo('mkdir /var/www/myapp')
local():用来执行本地 Shell 命令,返回要执行的命令,local 是对 Python 的 Subprocess 模块的封装,更负载的功能可以直接使用 Subprocess 模块,包含 capture 参数,默认为 False,表示 subprocess 输出的信息进行显示,如果不想显示,那么指定capture=True 即可
ef test():
result = local('make test',capture=True)
print(result)
print(result.failed)
print(result.succeeded)
# 返回执行的命令
# 如果执行失败那么 result.failed 为True
# 如果执行成功那么 result.succeeded 为True
列出本地 /home/bjhee 目录下的所有文件及目录:
from fabric.api import local
def hello():
local('ls -l /home/bjhee/')
使用 capture 参数 用来 捕获 标准输出,比如:
def hello():
output = local('echo Hello', capture=True)
这样,Hello字样不会输出到屏幕上,而是保存在变量output里。”capture”参数的默认值是False。
get():从远程服务器上获取文件,通过remote_path参数声明从何处下载,通过local_path表示下载到何处。remote_path支持通配符。
get(remote_path='/etc/passwd',local_path='/tmp/passwd')
它的工作原理是基于scp命令,使用的方法如下:
from fabric.api import env, get
env.hosts = ['[email protected]',]
env.password = '111111'
def hello():
get('/var/log/myapp.log', 'myapp-0301.log')
上述任务将远程机上”/var/log/myapp.log”文件下载到本地当前目录,并命名为”myapp-0301.log”。
put():将本地的文件上传到远程服务器,参数与get相似,此外,还可以通过mode参数执行远程文件的权限配置。
put(remote_path='/etc/passwd',local_path='/tmp/passwd')
同 get一样,put 方法也是基于scp命令,使用的方法如下:
from fabric.api import env, put
env.hosts = ['[email protected]', '[email protected]']
env.password = '111111'
def hello():
put('/tmp/myapp-0301.tar.gz', '/var/www/myapp.tar.gz')
上述任务将本地”/tmp/myapp-0301.tar.gz”文件分别上传到两台远程机的”/var/www/”目录下,并命名为”myapp.tar.gz”。如果远程机上的目录需要超级用户权限才能放文件,可以在”put()”方法里加上”use_sudo”参数:
put('/tmp/myapp-0301.tar.gz', '/var/www/myapp.tar.gz', use_sudo=True)
上传文件并执行 示例代码:
from fabric.api import *
env.user = 'mysql'
env.hosts = ['192.168.56.1', '192.168.56.2']
#env.password = '1qaz@WSX'
env.passwords = {
'[email protected]:22':'1qaz@WSX',
'[email protected]:22':'1qaz@WSX',
}
@task
@runs_once
def tar_task():
with lcd('/home/mysql/yanjun_wang'):
local('tar zcvf hello.tar.gz hello_world.py')
@task
def put_task():
run('mkdir -p /home/mysql/yanjun_wang')
with cd('/home/mysql/yanjun_wang'):
put('/home/mysql/yanjun_wang/hello.tar.gz', '/home/mysql/yanjun_wang/hello.tar.gz')
@task
def check_task():
lmd5 = local('md5sum /home/mysql/yanjun_wang/hello.tar.gz', capture=True).split(' ')[0]
rmd5 = run('md5sum /home/mysql/yanjun_wang/hello.tar.gz').split(' ')[0]
if lmd5 == rmd5:
print('OK ...')
else:
print('ERROR ...')
@task
def run_task():
with cd('/home/mysql/yanjun_wang'):
run('tar zxvf hello.tar.gz')
run('python hello_world.py')
@task
def execute():
print('start ...')
tar_task()
put_task()
check_task()
run_task()
print('end ...')
文件打包上传校验 示例代码:
#!/usr/bin/env python
from fabric.api import *
from fabric.colors import *
env.hosts=['192.168.56.30']
env.user='root'
env.passwords={'[email protected]:22':'rooter'}
@runs_once
@task
def tarfile():
print yellow('tar file ...')
#使用with lcd命令,否则需要写全路径,直接lcd没用
with lcd('/var/log'):
local('tar czf messages.tar.gz messages')
@task
def putfile():
print blue('put file ...')
run('mkdir -p /tmp/log')
with cd('/tmp/log'):
#warn_only当出现异常的时候继续执行
with settings(warn_only=True):
result=put('/var/log/messages.tar.gz','/tmp/log')
if result.failed and not confirm('put file filed,Continue[Y/N]?'):
abort('Aborting file put task!')
@task
def checkfile():
print red('check file ...')
with settings(warn_only=True):
#本地local命令需要配置capture=True才能获取返回值
lmd5=local('md5sum /var/log/messages.tar.gz',capture=True).split(' ')[0]
rmd5=run('md5sum /tmp/log/messages.tar.gz').split(' ')[0]
if lmd5==rmd5:
print 'ok'
else:
print 'error'
@task
def go():
tarfile()
putfile()
checkfile()
simple3.py
下面是运行结果,有颜色的区别:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
reboot():重启远程服务器,可以通过wait参数设置等待几秒钟重启
reboot(wait=30)
有时候安装好环境后,需要重启服务器,这时就要用到”reboot()”方法,你可以用”wait”参数来控制其等待多少秒后重启,没有此参数则代表立即重启:
from fabric.api import env, reboot
env.hosts = ['[email protected]',]
env.password = '111111'
def restart():
reboot(wait=60)
上面的restart任务将在一分钟后重启服务器。
propmt():用以在 Fabric 执行任务的过程中与管理员进行交互,类似于python 的 input
key = prompt('Please specify process nice level:',key='nice',validate=int)
# 会返回采集到的key
该方法也类似于 Shell 中的 ”read” 命令,它会在终端显示一段文字来提示用户输入,并将用户的输入保存在变量里:
from fabric.api import env, get, prompt
env.hosts = ['[email protected]',]
env.password = '111111'
def hello():
filename = prompt('Please input file name: ')
get('/var/log/myapp.log', '%s.log' % filename)
现在下载后的文件名将由用户的输入来决定。我们还可以对用户输入给出默认值及类型检查:
port = prompt('Please input port number: ', default=8080, validate=int)
执行任务后,终端会显示:Please input port number: [8080]
如果你直接按回车,则port变量即为默认值8080;如果你输入字符串,终端会提醒你类型验证失败,让你重新输入,直到正确为止。
示例代码(动态获取远程目录列表):
本例调用@task修饰符标志入口函数go()对外部可见,配合@runs_once修饰符接收用户输入,最后调用worktask()函数实现远程命令执行。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from fabric.api import *
env.user = 'root'
env.hosts = ['192.168.1.22']
env.password = '123456'
@runs_once #主机遍历过程中,只有第一台触发此函数
def input_raw():
return prompt('please input directoryname:',default='/root')
def worktask(dirname):
run('ls -l'+dirname)
@task #限定只有go函数对fab命令可见,其他没有使用@task标记的函数fab命令不可用
def go():
getdirname = input_raw()
worktask(getdirname)
simple2.py
执行:fab -f simple2.py go
对于上面的结果做了一些测试发现:
1.设置了默认值,不输入就是以默认值为准,如果不设置默认值,那么dirname就是空的,ls -l的就是你登录用户的家目录,例如是root就是/root
2.对于写在go函数下的内容,有多少主机就会循环多少次,他是以主机为循环的.
3.这个脚本是对于所有的主机列出同一个目录,对于不同的主机让选择不同的目录,可以简单的修改为:
def worktask(dirname):
run('ls -l '+dirname)
@task
def go():
getdirname=raw_input("please input directory:")
worktask(getdirname)
如果你正在寻找一个用户来确认操作,请使用confrim方法。
if fabric.contrib.console.confirm("You tests failed do you want to continue?"):
#continue processing
如果你正在寻找从用户那里获取输入的方法,请使用提示方法。
password = fabric.operations.prompt("What is your password?")
首先windows机器需要安装ssh服务,注意ssh服务所用的账户需要设置能够运行exec的权限,否则无法启动windwos程序。
其次由于fabric默认使用bash,因此需要设置变量 env.shell="cmd /c",否则会报错。
env 中存储的是全局配置,有时候我们并不希望修改全局配置参数,只希望临时修改部分配置,例如:修改当前工作目录,修改日志输出级别等。在 fabric 中我们可以通过上下文管理器临时修改参数配置,而不会影响全局配置。当程序进入上下文管理器的作用域时,临时修改就会起作用;当程序离开上下文管理器时,临时修改就会消失。
Fabric 的上下文管理器是一系列与 Python 的 ”with” 语句配合使用的方法,它可以在 ”with” 语句块内设置当前工作环境的上下文。让我们介绍几个常用的:
cd():切换远程目录
def change(dir='/tmp'):
with cd(dir):
run('pwd') # /tmp
run('pwd') # /root
“cd()”方法在之前的范例中出现过,”with cd()”语句块可以用来设置远程机的工作目录:
from fabric.api import env, cd, put
env.hosts = ['[email protected]', ]
env.password = '111111'
def hello():
with cd('/var/www/'):
put('/tmp/myapp-0301.tar.gz', 'myapp.tar.gz')
上例中的文件会上传到远程机的”/var/www/”目录下。出了”with cd()”语句块后,工作目录就回到初始的状态,也就是”bjhee”用户的根目录。
lcd: 设置本地工作目录
“lcd()”就是”local cd”的意思,用法同”cd()”一样,区别是它设置的是本地的工作目录:
from fabric.api import env, cd, lcd, put
env.hosts = ['[email protected]', ]
env.password = '111111'
def hello():
with cd('/var/www/'):
with lcd('/tmp/'):
put('myapp-0301.tar.gz', 'myapp.tar.gz')
这个例子的执行效果跟上个例子一样。
path():配置远程服务器PATH环境变量,只对当前会话有效,不会影响远程服务器的其他操作,path的修改支持多种模式
def addpath():
with path('/tmp','prepend'):
run("echo $PATH")
run("echo $PATH")
添加远程机的 PATH 路径
from fabric.api import env, run, path
env.hosts = ['[email protected]', ]
env.password = '111111'
def hello():
with path('/home/bjhee/tmp'):
run('echo $PATH')
run('echo $PATH')
假设我们的PATH环境变量默认是”/sbin:/bin”,在上述”with path()”语句块内PATH变量将变为”/sbin:/bin:/home/bjhee/tmp”。出了with语句块后,PATH又回到原来的值。
prefix:设置命令执行前缀。
prefix() 前缀,它接受一个命令作为参数,表示在其内部执行的代码块,都要先执行prefix的命令参数。
from fabric.api import env, run, local, prefix
env.hosts = ['[email protected]', ]
env.password = '111111'
def hello():
with prefix('echo Hi'):
run('pwd')
local('pwd')
示例代码 2 :
def testprefix():
with cd('/tmp'):
with prefix('echo 123'):
run('echo 456')
run('echo 789')
# 转换为Linux命令为:
cd /tmp && echo '123' && echo '456'
cd /tmp && echo '123' && echo '789'
在上述”with prefix()”语句块内,所有的”run()”或”local()”方法的执行都会加上”echo Hi && “前缀,也就是效果等同于:
run('echo Hi && pwd')
local('echo Hi && pwd')
配合后面的错误处理,它可以确保在”prefix()”方法上的命令执行成功后才会执行语句块内的命令。
shell_env():设置 shell 脚本的环境变量。可以用来临时设置远程和本地机上Shell的环境变量。
def setenv():
with shell_env(HTTP_PROXY='1.1.1.1'):
run('echo $HTTP_PROXY')
run('echo $HTTP_PROXY')
# 等同于shell中的export
export HTTP_PROXY='1.1.1.1'
示例:
from fabric.api import env, run, local, shell_env
env.hosts = ['[email protected]', ]
env.password = '111111'
def hello():
with shell_env(JAVA_HOME='/opt/java'):
run('echo $JAVA_HOME')
local('echo $JAVA_HOME')
settings():通用配置,设置Fabric环境变量参数,用于临时覆盖env变量。
def who():
with settings(user='dev'): # 临时修改用户名为dev
run('who')
run('who')
Fabric 环境变量即是我们例子中一直出现的 ”fabric.api.env ”,它支持的参数可以从官方文档中查到。
from fabric.api import env, run, settings
env.hosts = ['[email protected]', ]
env.password = '111111'
def hello():
with settings(warn_only=True):
run('echo $USER')
我们将环境参数”warn_only”暂时设为True,这样遇到错误时任务不会退出。
remote_tunnel():通过SSH的端口转发建立的链接
with remote_tunnel(3306):
run('mysql -uroot -p password')
hide():用于隐藏指定类型的输出信息,hide定义的可选类型有7种
为了方便使用,fabric对以上其中类型做了进一步的封装
show():与hide相反,表示显示指定类型的输出
def hello():
with settings(show('everything'),warn_only=True): # 显示所有
result = run('netstat -lntup|grep')
print('1='+result) # 命令执行的结果
print('2='+str(result.return_code)) # 返回码,0表示正确执行,1表示错误
print('3='+str(result.failed))
结果:
pyvip@Vip:~/utils$ fab -f fab_utils.py hello
[192.168.5.128] Executing task 'hello'
[192.168.5.128] run: netstat -lntup|grep
[192.168.5.128] out: 用法: grep [选项]... PATTERN [FILE]...
[192.168.5.128] out: 试用‘grep --help’来获得更多信息。
[192.168.5.128] out:
Warning: run() received nonzero return code 2 while executing 'netstat -lntup|grep'!
NoneType
1=用法: grep [选项]... PATTERN [FILE]...
试用‘grep --help’来获得更多信息。
2=2
3=True
Done.
quiet():隐藏全部输出,仅在执行错误的时候发出告警信息,功能等同于 with settings(hide('everything'),warn_only=True) .
# 比如创建目录的时候,如果目录存在,默认情况下Fabric会报错退出,我们是允许这种错误的,
# 所以针对这种错误,我们进行如下设置,使 fabric 只打出告警信息而不会中断执行。
with settings(warn_only=True)
在执行任务中,难免出现命令执行失败的情况。而在日常使用中,肯定不会像我们测试时只执行一两条命令,一般都是一系列命令来共同完成一件事。如果其中一条命令执行失败,便会影响后续的命令。这个时候我们要让脚本停下来,以免出现更大的错误。 Fabric 会检查被调用程序的返回值,如果这些程序没有干净地退出,Fabric 会终止操作。fab 程序在执行脚本出错的时候会自动终止,不再继续往下执行。
默认,一组命令,上一个命令执行失败后,不会接着往下执行,失败后也可以进行不一样的处理,详解文档
# 默认情况
[root@saltstack fabric]# cat fabric_6.py
#!/usr/bin/env python
# coding:utf-8
#from fabric.api import run
from fabric.api import local
def host_type():
local('uname -s')
local('tt')
local('hostname')
[root@saltstack fabric]# fab -H localhost -f fabric_6.py host_type
[localhost] Executing task 'host_type'
[localhost] local: uname -s
Linux
[localhost] local: tt
/bin/sh: tt: command not found
Fatal error: local() encountered an error (return code 127) while executing 'tt'
Aborting.
# 注:由于tt执行报错,后面的hostname命令没有被执行
[root@saltstack fabric]# cat fabric_7.py
#!/usr/bin/env python
# coding:utf-8
from __future__ import with_statement
from fabric.api import local, settings, abort
from fabric.colors import *
from fabric.contrib.console import confirm
def host_type():
local('uname -s')
with settings(warn_only=True):
result = local('tt', capture=True)
if result.failed and not confirm(red("tt cmd failed. Continue anyway?")):
abort("Aborting at user request.")
local('hostname')
[root@saltstack fabric]# fab -H localhost -f fabric_7.py host_type
[localhost] Executing task 'host_type'
[localhost] local: uname -s
Linux
[localhost] local: tt
Warning: local() encountered an error (return code 127) while executing 'tt'
tt cmd failed. Continue anyway? [Y/n] y # 判断上一步执行有无异常,异常给予提示,确认是否继续
[localhost] local: hostname
saltstack
Done.
如果我们想要更加灵活,给用户另一种选择,一个名为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]
默认情况下,Fabric在任务遇到错误时就会退出,如果我们希望捕获这个错误而不是退出任务的话,就要开启”warn_only”参数。在上面介绍”settings()”上下文管理器时,我们已经看到了临时开启”warn_only”的方法了,如果要全局开启,有两个办法:
1. 在执行”fab”命令时加上”-w”参数: $ fab -w hello
2. 设置 ”env.warn_only” 环境参数为 True
from fabric.api import env
env.warn_only = True
现在遇到错误时,控制台会打出一个警告信息,然后继续执行后续任务。那我们怎么捕获错误并处理呢?像”run()”, “local()”, “sudo()”, “get()”, “put()”等SSH功能函数都有返回值。当返回值的”succeeded”属性为True时,说明执行成功,反之就是失败。你也可以检查返回值的”failed”属性,为True时就表示执行失败,有错误发生。在开启”warn_only”后,你可以通过”failed”属性检查捕获错误,并执行相应的操作。
from fabric.api import env, cd, put
env.hosts = ['[email protected]', ]
env.password = '111111'
def hello():
with cd('/var/www/'):
upload = put('/tmp/myapp-0301.tar.gz', 'myapp.tar.gz')
if upload.failed:
sudo('rm myapp.tar.gz')
put('/tmp/myapp-0301.tar.gz', 'myapp.tar.gz', use_sudo=True)
Fabric提供的命令一般都是执行某一个具体的操作,提供的上下文管理器一般都是用于临时修改配置参数,而fabric提供的装饰器,既不是执行具体的操作,也不是修改参数,而是控制如何执行这些操作,在那些服务器上执行这些操作,fabric的装饰器与人物执行紧密相关。下面从几个方面来进行说明
task就是fabric需要在远程服务器上执行的函数,在fabric中有3中方法定义一个task
from fabric.api import *
env.user='root'
env.password='mysql123'
@task
def hello():
run('echo hello')
def world():
run('echo world')
PS:默认情况下,fabfile中的所有函数对象都是一个task,但是如果我们使用了task装饰器,显示的定义了一个task,那么,其他没有通过task装饰器装饰的函数将不会被认为是一个task。
为了方便我们的使用,fabric提供了非常灵活的方式指定对哪些远程服务器执行操作,根据我们前面的知识,我们知道有两种方式:通过env.hosts来执行,或者在fab执行命令的时候使用-H参数,除此之外,还有以下需要注意的地方
from fabric.api import *
env.hosts = [
'[email protected]:22',
'[email protected]:22',
'[email protected]:22'
]
env.passwords = {
'[email protected]:22':'123456201',
'[email protected]:22':'123456202',
'[email protected]:22':'123456203'
}
@hosts('[email protected]:22')
@task
def hello():
run('ifconfig br0')
# 命令行的方式:
fab hello:hosts="[email protected];[email protected]"
role是对服务器进行分类的手段,通过role可以定义服务器的角色,以便对不同的服务器执行不同的操作,Role逻辑上将服务器进行了分类,分类以后,我们可以对某一类服务器指定一个role名即可。进行task任务时,对role进行控制。
# role在env.roledefs中进行定义
env.roledefs = {
'web':['[email protected]','192.168.10.202'] # role名称为:web
'db':['[email protected]',] # role名称为:db
}
当我们定义好role以后,我们就可以通过roles装饰器来指定在哪些role上运行task。
from fabric.api import *
env.roledefs = {
'web':['[email protected]:22','[email protected]:22',],
'db':['[email protected]:22',]
}
env.passwords = {
'[email protected]:22':'123456201',
'[email protected]:22':'123456202',
'[email protected]:22':'123456203'
}
@roles('db') # 只对role为db的主机进行操作
@task
def hello():
run('ifconfig br0')
注意:hosts 装饰器可以和 roles 装饰器一起使用(全集),看起来容易造成混乱,不建议混搭。
示例代码 2 :
from fabric.api import env, roles, run, execute, cd
env.roledefs = {
'staging': ['[email protected]','[email protected]'],
'build': ['[email protected]']
}
env.passwords = {
'staging': '11111',
'build': '123456'
}
@roles('build')
def build():
with cd('/home/build/myapp/'):
run('git pull')
run('python setup.py')
@roles('staging')
def deploy():
run('tar xfz /tmp/myapp.tar.gz')
run('cp /tmp/myapp /home/bjhee/www/')
def task():
execute(build)
execute(deploy)
现在让我们执行fab task,这时Fabric会先在一台build服务器上执行build任务,然后在两台staging服务器上分别执行deploy任务。”@roles”装饰器指定了它所装饰的任务会被哪个角色的服务器执行。
fabric执行任务的步骤如下:
PS:关于并行模式:
我们在介绍执行远程命令时曾提到过多台机器的任务默认情况下是串行执行的。Fabric支持并行任务,当服务器的任务之间没有依赖时,并行可以有效的加快执行速度。怎么开启并行执行呢?办法也是两个:
1. 在执行 ”fab” 命令时加上 ”-P” 参数:$ fab -P hello
2. 设置 ”env.parallel” 环境参数为 True
from fabric.api import env
env.parallel = True
如果,我们只想对某一任务做并行的话,我们可以在任务函数上加上”@parallel”装饰器:
from fabric.api import parallel
@parallel
def runs_in_parallel():
pass
def runs_serially():
pass
这样即便并行未开启,”runs_in_parallel()”任务也会并行执行。反过来,我们可以在任务函数上加上”@serial”装饰器:
from fabric.api import serial
def runs_in_parallel():
pass
@serial
def runs_serially():
pass
这样即便并行已经开启,”runs_serially()”任务也会串行执行。
由于并行执行影响的最小单位是任务,所以功能的启用或禁用也是以任务为单位使用 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
相比于之前的输出,可以看出命令提示更加完整。
前面介绍了task,hosts,roles和parallel装饰器,此外还有两个装饰器比较常用
runc_once 使用示例(查看本地与远程主机信息):
本示例调用local方法执行本地命令,添加@runs_once修饰符保证任务函数只执行一次,调用run方法执行远程命令。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from fabric.api import *
env.user = 'root'
env.hosts = ['192.168.1.22']
env.password = '123456'
@runs_once #查看本地系统信息,当有多台主机时只运行一次
def local_task(): #本地任务函数
local('uname -a')
def remote_task():
with cd('/var/logs'): #with的作用是让后面的表达式语句继承当前状态,实现:cd /var/logs && ls -l的效果
run('ls -l')
simple1.py
执行:
fab -f simple1.py local_task
fab -f simple1.py remote_task
fabric中还有其他的一些好用的函数
fabric提供了一个execute函数,用来对task进行封装。它最大的好处就是可以将一个大的任务拆解为很多小任务,每个小任务互相独立,互不干扰
from fabric.api import *
env.roledefs = {
'web':['[email protected]:22','[email protected]:22',],
'db':['[email protected]:22',]
}
env.passwords = {
'[email protected]:22':'123456201',
'[email protected]:22':'123456202',
'[email protected]:22':'123456203'
}
@roles('db')
def hello():
run('echo hello')
@roles('web')
def world():
run('echo world')
@task
def helloworld():
execute(hello)
execute(world)
# 函数helloworld作为入口,分别调用两个task,对不同的主机进行操作
包含一些辅助行的功能函数,这些函数位于fabric.utils下,常用的函数如下:
def helloworld():
execute(hello)
abort('----->abort') # 执行到这里时,直接退出
warn('----->warn') # 会发出提示信息,不会退出
puts('----->puts') # 会打印括号中的信息
execute(world)
fabric为了让输出日志更具有可读性,对命令行中断的颜色输出进行了封装,使用print打印带有不同颜色的文本,这些颜色包含在fabric.colors中。像warn,puts打印输出的,也可以直接渲染颜色
def ls(path='.'):
run('ls {0}'.format(path))
def hello():
execute(hell) # task任务hell
warn(yellow('----->warn')) # 会发出提示信息,不会退出
puts(green('----->puts')) # 会打印括号中的信息
execute(ls) # task任务ls
print(green('the text is green')) # 单纯的渲染文字:
def hell(name='world'):
print('hello %s' % name)
终端输出带颜色
我们习惯上认为绿色表示成功,黄色表示警告,而红色表示错误,Fabric 支持带这些颜色的输出来提示相应类型的信息:
from fabric.colors import *
def hello():
print green("Successful")
print yellow("Warning")
print red("Error")
示例代码 2(fabric_5.py):
#!/usr/bin/env python
# coding:utf-8
from fabric.colors import *
def show():
print green('success')
print red('fail')
print yellow('yellow')
执行命令:[root@saltstack fabric]# fab -f fabric_5.py show
通过”execute()”方法,可以在一个”fab”命令中多次调用同一任务,如果想避免这个发生,就要在任务函数上加上”@runs_once”装饰器。
from fabric.api import execute, runs_once
@runs_once
def hello():
print "Hello Fabric!"
def test():
execute(hello)
execute(hello)
现在不管我们”execute”多少次hello任务,都只会输出一次”Hello Fabric!”字样
更多内容请参阅Fabric的官方文档。本篇中的示例代码可以在这里下载。
有时候我们在某一步执行错误,会给用户提示,是否继续执行时,confirm就非常有用了,它包含在 fabric.contrib.console中
def testconfirm():
result = confirm('Continue Anyway?')
print(result)
# 会提示输入y/n
# y 时 result为True
# n 时 result为False
下载一个redis的包和fabfile.py放在同级目录即可,不同目录需要修改包的位置,这里使用的是redis-4.0.9版本。
#!/usr/bin/env python3
from fabric.api import *
from fabric.contrib.console import confirm
from fabric.utils import abort
from fabric.colors import *
env.hosts = ['192.168.10.202',]
env.user = 'root'
env.password = '123456202'
@runs_once
@task
def test():
with settings(warn_only=True):
local('tar xf redis-4.0.9.tar.gz')
with lcd('redis-4.0.9'):
result = local('make test',capture=True)
if result.failed and not confirm('Test is Faild Continue Anyway?'):
abort('Aborting at user request.')
with lcd('redis-4.0.9'):
local("make clean")
local('tar zcvf redis-4.0.10.tar.gz redis-4.0.9')
@task
def deploy():
put('redis-4.0.10.tar.gz','/tmp/')
with cd('/tmp'):
run('tar xf redis-4.0.10.tar.gz')
with cd('redis-4.0.9'):
sudo('make install')
@task
def start_redis():
with settings(warn_only=True):
result = run('netstat -lntup | grep -w redis-server')
if result.return_code == 0:
print(green('redis is started!'))
else:
run('set -m ; /usr/local/bin/redis-server &') # 用pty=False, fabric进程退不出来,不知道为啥,所以这里用set -m
print(green('redis start Successful'))
@task
def clean_local_file():
local('rm -rf redis-4.0.10.tar.gz')
@task
def clean_file():
with cd('/tmp'):
sudo('rm -rf redis-4.0.9')
sudo('rm -rf redis-4.0.10.tar.gz')
@task
def install():
execute(test)
execute(deploy)
execute(clean_file)
execute(clean_local_file)
execute(start_redis)
PS:关于set -m 的作用如下:
"set -m" turns on job control, you can run processes in a separate process group.
理解:在一个独立的进程组里面运行我们的进程。
form:https://github.com/DingGuodong/LinuxBashShellScriptForOps/blob/master/projects/autoOps/pythonSelf/fabfile.py
#!/usr/bin/python
# encoding: utf-8
# -*- coding: utf8 -*-
# Pythonic remote execution
# Refer: http://docs.fabfile.org/en/1.6/tutorial.html#conclusion
# Refer: https://github.com/dlapiduz/fabistrano/blob/master/fabistrano/deploy.py
# Refer: https://gist.github.com/mtigas/719452
# Refer: http://docs.fabfile.org/en/1.12/usage/env.html
# fab -i c:\Users\Guodong\.ssh\exportedkey201310171355 -f .\projects\autoOps\pythonSelf\fabfile.py dotask
import datetime
import logging
import logging.handlers
import os
import platform
import re
import sys
import time
import requests
def win_or_linux():
# os.name ->(sames to) sys.builtin_module_names
if 'posix' in sys.builtin_module_names:
os_type = 'Linux'
elif 'nt' in sys.builtin_module_names:
os_type = 'Windows'
return os_type
def is_windows():
if "windows" in win_or_linux().lower():
return True
else:
return False
def is_linux():
if "linux" in win_or_linux().lower():
return True
else:
return False
def initLoggerWithRotate():
current_time = time.strftime("%Y%m%d%H")
logpath = "/tmp"
logfile = "log_fabfile_" + current_time + ".log"
if not os.path.exists(logpath):
os.makedirs(logpath)
else:
logfile = os.path.join(logpath, logfile)
logger = logging.getLogger("fabric")
log_formatter = logging.Formatter("%(asctime)s %(filename)s:%(lineno)d %(name)s %(levelname)s: %(message)s",
"%Y-%m-%d %H:%M:%S")
file_handler = logging.handlers.RotatingFileHandler(logfile, maxBytes=104857600, backupCount=5)
file_handler.setFormatter(log_formatter)
stream_handler = logging.StreamHandler(sys.stderr)
logger.addHandler(file_handler)
logger.addHandler(stream_handler)
logger.setLevel(logging.DEBUG)
return logger
logger = initLoggerWithRotate()
os_release = platform.system()
if os_release == "Windows":
pass
elif os_release == "Linux":
distname = platform.linux_distribution()[0]
if str(distname).lower() == "ubuntu":
command_to_execute = "which pip >/dev/null 2>&1 || apt-get -y install libcurl4-openssl-dev python-pip"
os.system(command_to_execute)
elif str(distname).lower() == "centos":
command_to_execute = "which pip &>/dev/null 1>&2 || yum -y install python-pip"
os.system(command_to_execute)
else:
print "Error => Unsupported OS type."
logger.error("Unsupported OS type.")
sys.exit(1)
try:
from fabric.api import *
except ImportError:
try:
command_to_execute = "pip install fabric"
os.system(command_to_execute)
except OSError:
sys.exit(1)
finally:
from fabric.api import *
from fabric.main import main
from fabric.colors import *
from fabric.context_managers import *
from fabric.contrib.console import confirm
try:
import pycurl
except ImportError:
try:
command_to_execute = "pip install pycurl"
os.system(command_to_execute)
except OSError:
sys.exit(1)
import pycurl
try:
import pytz
except ImportError:
try:
command_to_execute = "pip install pytz"
os.system(command_to_execute)
except OSError:
sys.exit(1)
import pytz
try:
import shutil
except ImportError:
try:
command_to_execute = "pip install shutil"
os.system(command_to_execute)
except OSError:
sys.exit(1)
import shutil
try:
import certifi
except ImportError:
try:
command_to_execute = "pip install certifi"
os.system(command_to_execute)
except OSError:
sys.exit(1)
import certifi
env.roledefs = {
'test': ['[email protected]:22', ],
'nginx': ['[email protected]:22', '[email protected]:22', ],
'db': ['[email protected]:22', '[email protected]:22', ],
'sit': ['[email protected]:22', '[email protected]:22', '[email protected]:22', ],
'uat': ['[email protected]:22', '[email protected]:22', '[email protected]:22', ],
'all': ["10.6.28.27", "10.6.28.28", "10.6.28.35", "10.6.28.46", "10.6.28.93", "10.6.28.125", "10.6.28.135"]
}
env.user = "root"
env.hosts = ["10.6.28.27", "10.6.28.28", "10.6.28.35", "10.6.28.46", "10.6.28.93", "10.6.28.125", "10.6.28.135"]
env.command_timeout = 15
env.connection_attempts = 2
def show_uname():
try:
out = run("uname -a")
except KeyboardInterrupt:
logger.warning("We catch 'Ctrl + C' pressed, task canceled!")
sys.exit(1)
if out.return_code == 0:
logger.info("task finished successfully on " + env.host + " .")
else:
logger.error("task finished failed on " + env.host + " .")
# Call method: ping:www.qq.com
def ping(host):
if host is not None:
try:
out = run("ping -c1 " + host + " >/dev/null 2>&1")
except KeyboardInterrupt:
logger.warning("We catch 'Ctrl + C' pressed, task canceled!")
sys.exit(1)
if out.return_code == 0:
logger.info("task ping finished successfully on " + env.host + " .")
else:
logger.error("task ping finished failed on " + env.host + " .")
def showDiskUsage():
try:
run("df -h")
except KeyboardInterrupt:
logger.warning("We catch 'Ctrl + C' pressed, task canceled!")
sys.exit(1)
def setNameServer(server=None):
nameServerList = ""
if isinstance(server, list) and len(server) >= 1:
for host in server:
nameServerList += ("namserver %s\n" % host)
else:
nameServerList = "nameserver 182.254.116.116\n"
print("Executing on %(host)s as %(user)s" % env)
try:
out = run('test -f /etc/resolv.conf && echo "%s" > /etc/resolv.conf' % nameServerList.strip('\n'))
except KeyboardInterrupt:
logger.warning("We catch 'Ctrl + C' pressed, task canceled!")
sys.exit(1)
if out.return_code == 0:
logger.info("task finished successfully on " + env.host + " .")
run('test -f /etc/resolv.conf && cat /etc/resolv.conf')
else:
logger.error("task finished failed on " + env.host + " .")
abort("task finished failed on " + env.host + " .")
def checkWeChatApi():
qy_api = "qyapi.weixin.qq.com"
api = "api.weixin.qq.com"
ping(qy_api)
ping(api)
def showUptime():
run("uptime")
def putSelf():
try:
put(__file__, '/tmp/fabric.py')
except Exception as e:
logger.error("task putSelf failed! msg: %s" % e)
abort("task putSelf failed! msg: %s" % e)
def sudo_run(*args, **kwargs):
if env.use_sudo is not None:
sudo(*args, **kwargs)
else:
run(*args, **kwargs)
def check_var_is_absent(var):
if var is None or var == "":
print var + " is None or empty, please check and fix it!"
sys.exit(1)
def check_runtime_dependencies():
check_var_is_absent(env.basedir)
pass
def backup_file(path, extension='~'):
backup_filename = path + '.' + extension
if os.path.islink(path):
src = os.readlink(path)
else:
src = path
shutil.copy2(src, backup_filename)
return backup_filename
def rollback_file(path, extension='~'):
if os.path.islink(path):
src = os.readlink(path)
else:
src = path
if os.path.exists(src + extension):
shutil.copy2(src + extension, src)
return src
def check_network_connectivity():
internet_hostname = "www.aliyun.com"
ping = 'ping -c4 ' + internet_hostname
result_code = None
try:
run(ping)
except Exception as _:
del _
result_code = 1
if result_code is not None:
print red("Error => connect to Internet failed!")
logger.error("connect to Internet failed!")
else:
print green("Success => connect to Internet successfully!")
def check_name_resolve():
internet_hostname = "www.aliyun.com"
nslookup = 'nslookup ' + internet_hostname
result_code = None
try:
run(nslookup)
except Exception as _:
del _
result_code = 1
if result_code is not None:
print red("Error => name resolve to Internet failed!")
logger.error("name resolve to Internet failed!")
else:
print green("Success => name resolve to Internet successfully!")
def set_dns_resolver():
serverList = ['182.254.116.116', '202.106.196.115', '202.106.0.20']
setNameServer(serverList)
def set_hosts_file(hosts="/etc/hosts"):
import socket
if not os.path.exists(hosts):
if not os.path.exists(os.path.dirname(hosts)):
os.makedirs(os.path.dirname(hosts))
with open(hosts, "w") as f:
hosts_url = "https://raw.githubusercontent.com/racaljk/hosts/master/hosts"
conn = requests.head(hosts_url)
if conn.status_code != 200:
hosts_url = "https://coding.net/u/scaffrey/p/hosts/git/raw/master/hosts"
curl = pycurl.Curl()
curl.setopt(pycurl.URL, hosts_url)
curl.setopt(pycurl.CAINFO, certifi.where())
curl.setopt(pycurl.WRITEDATA, f)
curl.perform()
curl.close()
hostname = socket.gethostname() # socket.getfqdn()
print hostname
try:
ip = socket.gethostbyname(socket.gethostname()) # TODO(Guodong Ding) Ubuntu not passed here, but CentOS passed!
except Exception as _:
del _
ip = None
with open(hosts, "a") as f:
if ip is not None:
appended_content = "\n" + "127.0.0.1 " + hostname + "\n" + ip + " " + hostname + "\n"
else:
appended_content = "\n" + "127.0.0.1 " + hostname + "\n"
f.write(appended_content)
def set_capistrano_directory_structure_over_fabric():
print blue("setting capistrano directory structure ...")
capistrano_release = env.basedir + '/release'
capistrano_repository = env.basedir + '/repository'
capistrano_share = env.basedir + '/share'
capistrano_backup = env.basedir + '/backup'
if os.path.exists(env.capistrano_ds_lock):
pass
else:
if not os.path.exists(capistrano_release):
os.makedirs(capistrano_release)
if not os.path.exists(capistrano_repository):
os.makedirs(capistrano_repository)
if not os.path.exists(capistrano_share):
os.makedirs(capistrano_share)
if not os.path.exists(capistrano_backup):
os.makedirs(capistrano_backup)
with open(env.capistrano_ds_lock, 'w') as f:
if os.path.exists("/etc/timezone"):
tz = file("/etc/timezone").read().strip()
if tz == 'Asia/Chongqing' or tz == 'Asia/Shanghai':
content = datetime.datetime.now(tz=pytz.timezone(tz))
else:
content = datetime.datetime.now(tz=pytz.timezone('Asia/Shanghai'))
else:
content = datetime.datetime.now()
f.write(str(content))
print green("setting capistrano directory structure successfully!")
def git_clone_local():
code_dir = env.basedir + '/repository'
git_clone = "git clone " + env.git_address + " " + code_dir
local(git_clone)
def terminal_debug(defName):
command = "fab -i c:\Users\Guodong\.ssh\exportedkey201310171355\
-f C:/Users/Guodong/PycharmProjects/LinuxBashShellScriptForOps/projects/autoOps/pythonSelf/fabfile.py \
%s" % defName
os.system(command)
if __name__ == '__main__':
if len(sys.argv) == 1 and is_windows():
logger.info("Started.")
terminal_debug("showUptime showDiskUsage")
sys.exit(0)
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
print red("Please use 'fab -f %s'" % " ".join(str(x) for x in sys.argv[0:]))
logger.error("Syntax error. Exit now.")
sys.exit(1)
部署脚本代码 1:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from datetime import datetime
from fabric.api import *
# 登录用户和主机名:
env.user = 'root'
env.hosts = ['www.example.com'] # 如果有多个主机,fabric会自动依次部署
def pack():
' 定义一个pack任务 '
# 打一个tar包:
tar_files = ['*.py', 'static/*', 'templates/*', 'favicon.ico']
local('rm -f example.tar.gz')
local('tar -czvf example.tar.gz --exclude=\'*.tar.gz\' --exclude=\'fabfile.py\' %s' % ' '.join(tar_files))
def deploy():
' 定义一个部署任务 '
# 远程服务器的临时文件:
remote_tmp_tar = '/tmp/example.tar.gz'
tag = datetime.now().strftime('%y.%m.%d_%H.%M.%S')
run('rm -f %s' % remote_tmp_tar)
# 上传tar文件至远程服务器:
put('shici.tar.gz', remote_tmp_tar)
# 解压:
remote_dist_dir = '/srv/www.example.com@%s' % tag
remote_dist_link = '/srv/www.example.com'
run('mkdir %s' % remote_dist_dir)
with cd(remote_dist_dir):
run('tar -xzvf %s' % remote_tmp_tar)
# 设定新目录的www-data权限:
run('chown -R www-data:www-data %s' % remote_dist_dir)
# 删除旧的软链接:
run('rm -f %s' % remote_dist_link)
# 创建新的软链接指向新部署的目录:
run('ln -s %s %s' % (remote_dist_dir, remote_dist_link))
run('chown -R www-data:www-data %s' % remote_dist_link)
# 重启fastcgi:
fcgi = '/etc/init.d/py-fastcgi'
with settings(warn_only=True):
run('%s stop' % fcgi)
run('%s start' % fcgi)