大家好,小编为大家解答网络自动化运维的应用现状及展望的问题。很多人还不知道网络自动化运维考试答案,现在让我们一起来看看吧!
Python自动化运维常用模块
Python telnetlib模块
基本原理
在介绍 telnetlib 中各个 read 函数的意义前,首先了解 telnetlib 的工作原理。
telnetlib 首先通过 socket 连接从网络接收数据,把数据存储到自己的 raw queue 中,然后对其进行(telnet 协议相关的)处理(cook)python 炫酷代码雨。处理结果存放在 cooked queue 中供应用程序取用。整个过程如下图所示:
read方法
与队列相关
- read_very_lazy
只从 cookedq 读取已处理好的数据。 - read_lazy
如果 rawq 有数据,对 rawq 里的数据进行处理,然后从cookedq 中读取处理好的数据。 - - read_eager
从系统的 socket buffer 接受数据(即非阻塞模式 1读取数据)并处理,然后从 cookedq 中读取数据。 - read_very_eager
与 read_eager 类似。不同之处在于 read_eager 只要从cookedq 成功读取到数据就返回,而 read_very_eager 会试图读尽可能多的数据。
其他read方法
-
read_until(expected, timeout=None)
读取直到遇到给定的预期字节字符串,或者直到超时时间。 -
read_all( )
读取所有数据,直到EOF以字节为单位;阻塞直到连接关闭。
其他方法
- open(host, port=0[, timeout])
连接到主机。可选的第二个参数是端口号,默认为标准Telnet端口(23)。可选的timeout参数以秒为单位指定用于阻止连接尝试之类的操作的超时(如果未指定,将使用全局默认超时设置)。 - close( )
关闭连接。 - write(buffer)
将字节字符串写入套接字 - set_debuglevel( debuglevel )
- 这个函数默认的参数时0,以上代码使用的是1,就将交互过程的信息都打印出来了。可以看到来回交互的都是byte stream。
Python netmiko模块
netmiko针对不同网络设备做了优化,可以更有效地处理SSH连接,它还支持不同的设备厂商和平台。
使用方法
在使用netmiko模块时,不可避免地会使用到ConnectHandler函数,ConnectHandler函数提供定义的字典来开始创建连接。
以下是ConnectHandler函数的定义及示例
定义
def ConnectHandler(*args, **kwargs):
"""Factory function selects the proper class and creates object based on device_type."""
device_type = kwargs["device_type"]
if device_type not in platforms:
if device_type is None:
msg_str = platforms_str
else:
msg_str = telnet_platforms_str if "telnet" in device_type else platforms_str
raise ValueError(
"Unsupported 'device_type' "
"currently supported platforms are: {}".format(msg_str)
)
ConnectionClass = ssh_dispatcher(device_type)
return ConnectionClass(*args, **kwargs)
示例
def netmiko_connect(request):
"""Connect to arista1 and return connection object"""
password = os.getenv("PYNET_PASSWORD") if os.getenv("PYNET_PASSWORD") else getpass()
arista1 = {
"device_type": "arista_eos",
"host": "arista1.lasthop.io",
"username": "pyclass",
"password": password,
}
net_connect = ConnectHandler(**arista1)
def fin():
net_connect.disconnect()
request.addfinalizer(fin)
return net_connect
常用方法
常用
- net_connect.enable() # 输入启用模式
- net_connect.send_command() # 向下发送命令,返回输出(基于模式)
- net_connect.send_config_set() # 将配置命令发送到远程设备
- net_connect.disconnect() # 关闭连接
了解
-
net_connect.send_config_from_file() # 发送从文件加载的配置命令
-
net_connect.save_config() # 将running#config保存到startup#config
-
net_connect.send_command_timing() # 沿通道发送命令,返回输出(基于时序)
-
net_connect.find_prompt() # 返回当前路由器提示符
-
net_connect.commit() # 在Juniper和IOS#XR上执行提交操作
-
net_connect.write_channel() # 通道的低级写入
-
net_connect.read_channel() # 通道的低级写入
Python yaml模块
YAML是一种直观的能够被电脑识别的的数据序列化格式,是一个专门用来写配置文件的语言,容易被人类阅读,并且容易和脚本语言交互。
yaml文件规则
- 区分大小写;
- 使用缩进表示层级关系;
- 使用空格键缩进或者Tab键缩进,二者不能混用
- 缩进的空格数目不固定,只需要相同层级的元素左侧对齐
- 文件中的字符串不需要使用引号标注,但若字符串包含有特殊字符则需用引号标注
- 注释标识为#
yaml文件数据结构
- 字典:键值对的集合,键值对用冒号 : 结构表示,冒号与值之间需用空格分隔
# yaml键值对嵌套:即python中字典嵌套字典
usr1:
name: a
psw: 123
usr2:
name: b
psw: 456
python解析该yaml文件后为
{'usr1': {'name': 'a', 'psw': 123}, 'usr2': {'name': 'b', 'psw': 456}}
- 列表:一组按序排列的值,前加有 “-” 符号,符号与值之间需用空格分隔
# yaml键值对中嵌套数组
usr3:
- a
- b
- c
usr4:
- b
python解析该yaml文件后为
{'usr3': ['a', 'b', 'c'], 'usr4': ['b']}
- 纯量(scalars):单个的、不可再分的值(如:字符串、bool值、整数、浮点数、时间、日期、null等),None值可用null可 ~ 表示
- 多个文档在一个yaml文件,使用 — 分隔方式来分段
# 分段yaml文件中多个文档
---
animal1: dog
age: 2
---
animal2: cat
age: 3
常用方法
- load() :读取yml文件,并返回一个对象
import yaml
f = open(r'E:\AutomaticTest\Test_Framework\config\config.yml')
y = yaml.load(f)
print (y)
- load_all():生成一个迭代器
如果string或文件包含几块yaml文档,你可以使用yaml.load_all来解析全部的文档。
import yaml
f = '''
---
name: James
age: 20
---
name: Lily
age: 19
'''
y = yaml.load_all(f)
for data in y:
print(data)
输出结果为:
{'name': 'James', 'age': 20}
{'name': 'Lily', 'age': 19}
- yaml.dump():将一个python对象生成为yaml文档
import yaml
aproject = {'name': 'Silenthand Olleander',
'race': 'Human',
'traits': ['ONE_HAND', 'ONE_EYE']
}
print(yaml.dump(aproject,第二个为可选参数))
输出结果为:
name: Silenthand Olleander
race: Human
traits:
- ONE_HAND
- ONE_EYE
yaml.dump接收的第二个参数一定要是一个打开的文本文件或二进制文件,yaml.dump会把生成的yaml文档写到文件里。
- yaml.dump_all():将多个段输出到一个文件中
import yaml
obj1 = {"name": "James", "age": 20}
obj2 = ["Lily", 19]
with open(r'E:\AutomaticTest\Test_Framework\config\config.yml', 'w') as f:
yaml.dump_all([obj1, obj2], f)
两段数据参数用列表传入
输出结果为:
{age: 20, name: James}
--- [Lily, 19]
- & 锚点和 * 别名,可以用来引用
defaults: &defaults
adapter: postgres
host: localhost
development:
database: myapp_development
<<: *defaults
test:
database: myapp_test
<<: *defaults
相当于:
defaults:
adapter: postgres
host: localhost
development:
database: myapp_development
adapter: postgres
host: localhost
test:
database: myapp_test
adapter: postgres
host: localhost
& 用来建立锚点(defaults),<< 表示合并到当前数据,* 用来引用锚点。
Python jinja2模块
这个类的实例用于存储配置、全局对象,并用于从文件系统或其它位置加载模板。模板在Python中广泛使用,模板简单来说就是一个其中包涵占位变量表示动态的部分的文件,模板文件在经过动态赋值后,返回给用户(可以理解为渲染)。
jinja2的优点
jinja2之所以被广泛使用是因为它具有以下优点:
- 相对于Template,jinja2更加灵活,它提供了控制结构,表达式和继承等。
- 相对于Mako,jinja2仅有控制结构,不允许在模板中编写太多的业务逻辑。
- 相对于Django模板,jinja2性能更好。
- Jinja2模板的可读性很棒。
jinja2语法
作为一个模板系统,它还提供了特殊的语法,我们按照它支持的语法进行编写之后,就能使用jinja2模块进行渲染。
基本语法
在jinja2中,存在三种语法:
- 控制结构 {% %}
- 变量取值 {{ }}
- 注释 {# #}
例子:
{# This is jinja code
filenames = {{ filenames }}
{% for file in filenames %}
...
{% endfor %}
#}
jinja2中的过滤器
变量可以通过“过滤器”进行修改,过滤器可以理解为是jinja2里面的内置函数和字符串处理函数。
用法示例:
{{ 'abc' | captialize }}
# Abc
{{ 'abc' | upper }}
# ABC
{{ 'hello world' | title }}
# Hello World
{{ "hello world" | replace('world','daxin') | upper }}
# HELLO DAXIN
{{ 18.18 | round | int }}
# 18
常用方法
大多数应用都在初始化的时候撞见一个Environment对象,并用它加载模板。Environment支持两种加载方式:
- PackageLoader:包加载器
- FileSystemLoader:文件系统加载器
常用方法还包括: - get_template():获取模板目录下的某个具体文件,并会返回已加载的 Template。还可用来继承。
- render():接受变量,对模板进行渲染
以PackageLoader为例:
from jinja2 import PackageLoader,Environment
file_loader = PackageLoader('templates') 我们定义一个PackageLoader。 # 从templates目录中检索模板。
env = Environment(loader=PackageLoader('python_project','templates')) # 创建一个包加载器对象
template = env.get_template('bast.html') # 使用get_template()方法获得模板
template.render(name='daxin',age=18) # 渲染
PackageLoader()的两个参数为:python包的名称,以及模板目录名称。
FileSystemLoader:文件系统加载器,不需要模板文件存在某个Python包下,可以直接访问系统中的文件。
以FileSystemLoader为例的继承示例:
from jinja2 import Environment, FileSystemLoader
content = 'This is about page'
file_loader = FileSystemLoader('templates')
env = Environment(loader=file_loader)
template = env.get_template('about.html')
output = template.render(content=content)
print(output)
Python subprocess模块
subprocess使用时,父进程创建子进程去执行一个外部程序,并提供了标准输入输出和管道(pipe)的实现方法,同时获取它们的返回码。
使用方法
subprocess.popen()
subprocess.Popen类用于在一个新进程中执行一个子程序,程序运行subprocess.Popen()类,父进程创建子进程后,不会等待子进程执行完成。如果需要等待子进程,需要加入wait()方法阻塞父进程。
语法及参数
subprocess.Popen(args[, bufsize, stdin, stdout, stderr, …]):Popen类的构造函数,返回结果为subprocess.Popen对象。
- args:需要执行的系统命令,可为字符串序列(列表或元组,shell为默认值False即可,建议为列表),也可为字符串(使用字符串时,需将shell赋值为True)
- shell:默认为False,若args为序列时,shell=False;若args为字符串时,shell=True,表示通过shell执行命令
- stdout、stdin、stderr:分别表示子程序标准输出、标准输入、标准错误,可为subprocess.PIPE、一个有效的文件描述符、文件对象或None。
若为subprocess.PIPE:代表打开通向标准流的管道,创建一个新的管道;若为None表示没有任何重定向,子进程会继承父进程;stderr也可为subprocess.STDOUT,表示将子程序的标准错误输出重定向到了标准输出。 - cwd:默认值为None;若非None,则表示将会在执行这个子进程之前改变当前工作目录;
- bufsize:指定缓冲策略,0表示不缓冲,1表示行缓冲,其它整数表示缓冲区大小,负数表示使用系统默认值0;
- env:用于指定子进程的环境变量。若env为None,那么子进程的环境变量将从父进程中继承;若env非None,则表示子程序的环境变量由env值来设置,它的值必须是一个映射对象。
- universal_newlines: 不同系统的换行符不同。若True,则该文件对象的stdin,stdout和stderr将会以文本流方式打开;否则以二进制流方式打开。
subprocess.Popen对象常用方法
如:PopenObject为subprocess.Popen( )对象
-
PopenObject.communicate([input, timeout]):与进程进行交互(如发送数据到stdin、读取stdout和stderr数据),它会阻塞父进程,直到子进程完成。
input:表示将发送到子进程的字符串数据,默认为None;
timeout:超时判断,若超过timeout秒后仍未结束则抛出TimeoutExpired异常;
communicate返回值:一个元组(stdout_data, stderr_data) -
PopenObject.poll() :用于检查命令是否已经执行结束,若结束返回状态码;若未结束返回None
-
PopenObject.wait([timeout, endtime]):等待子进程结束,并返回状态码;若超过timeout(s)进程仍未结束,则抛出异常
-
PopenObject.send_signal(signal):发送信号signal给子进程
-
PopenObject.terminate():停止子进程
-
PopenObject.kill():杀死子进程
常用方法
-
subprocess.call(args[, stdout, …]):执行args命令,返回值为命令执行状态码(类似os.system);
若未指定stdout,则命令执行后的结果输出到屏幕;
若指定stdout,则命令执行后的结果输出到stdout;
若执行成功,则函数返回值为0;若执行失败,则函数返回值为1; -
subprocess.check_call(args[, stdout, …]):执行args命令,返回值为命令执行状态码;
若未指定stdout,则命令执行后的结果输出到屏幕;
若指定stdout,则命令执行后的结果输出到stdout;
若执行成功,则函数返回值为0;若执行失败,抛出异常; -
subprocess.check_output(args[, stderr, …]):执行args命令,返回值为命令执行的输出结果;
若执行成功,则函数返回值为命令输出结果;若执行失败,则抛出异常;
以subprocess.call()为例,用法如下:
child = subprocess.call('python --version', shell =True)
print(child)
使用示例(好好研读帮助理解)
def subprocess_Popen1():
print("***通过communicate函数分别输出PopenObject对象的输出流和错误流***")
args = [["adb", "devices"], ["adb", "devices11"]]
for arg in args:
popen_object = subprocess.Popen(arg, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
object_stdout, object_stderr = popen_object.communicate()
output = {"popen_object": popen_object,
"object_stdout": object_stdout,
"object_stderr": object_stderr}
print(output)
"""
{'popen_object': <subprocess.Popen object at 0x0000000002212400>, 'object_stdout': b'List of devices attached \r\n106D111805005938\tdevice\r\n\r\n', 'object_stderr': b''}
{'popen_object': <subprocess.Popen object at 0x0000000002577C18>, 'object_stdout': b'', 'object_stderr': b'Android Debug Bridge version 1.0.31\r\n\r\n -a .....}
"""
print("***通过stdout和stderr方法输出PopenObject对象输出流和错误流***")
p0 = subprocess.Popen(["adb", "devices"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
object_stdout = p0.stdout.read()
p0.stdout.close()
object_stderr = p0.stderr.read()
p0.stderr.close()
print(object_stdout) # 结果:b'List of devices attached \r\n338b123f0504\tdevice\r\n\r\n'
print(object_stderr) # 结果:b''
print("***Popen对象stdin写入功能:使用stdout和stderr输出")
args = ["python", "python1"]
for arg in args:
p4 = subprocess.Popen([arg], shell=True, stdout=subprocess.PIPE,
stdin=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
p4.stdin.write("print('hello')")
p4.stdin.close()
out = p4.stdout.read()
p4.stdout.close()
err = p4.stderr.read()
p4.stderr.close()
print("out:%s err:%s" % (out, err))
"""
***Popen对象stdin写入功能
out:hello
err:
out: err:'python1' 不是内部或外部命令,也不是可运行的程序或批处理文件。
"""
print("***Popen对象stdin写入功能:使用communicate输出")
p4 = subprocess.Popen(["python"], stdout=subprocess.PIPE,
stdin=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
p4.stdin.write("print('hello')")
output = p4.communicate()
print(output) # 结果:('hello\n', '')
print("***不含encoding参数***")
p1 = subprocess.Popen("adb devices", shell=True, stdout=subprocess.PIPE)
out1 = p1.stdout.readlines()
print(out1) # 结果: [b'List of devices attached \r\n', b'106D111805005938\tdevice\r\n', b'\r\n']
print("***含encoding参数***")
p2 = subprocess.Popen("adb devices", shell=True, stdout=subprocess.PIPE, encoding="utf-8")
out2 = p2.stdout.readlines()
print(out2) # 结果: ['List of devices attached \n', '106D111805005938\tdevice\n', '\n']
print("***Popen对象检查命令是否结束,等待进程结束")
print(p2.poll()) # 结果: None
print(p2.wait()) # 结果: 0
print(p2.poll()) # 结果: 0
print("***Popen对象communicate函数,它会阻塞父进程直至子进程完成")
p3 = subprocess.Popen("adb devices", shell=True, stdout=subprocess.PIPE)
out = p3.communicate()[0]
print(out) # 结果:b'List of devices attached \r\n338b123f0504\tdevice\r\n\r\n'
print(p3.poll()) # 结果:0
subprocess_Popen1()
def subprocess_Popen2():
"""
1. 通过管道功能,实现adb shell ps | findstr top功能
2. 直接为args赋值为一个字符串,实现adb shell ps | findstr top功能
:return:
"""
print("***通过管道方式***")
p1 = subprocess.Popen(["adb", "shell", "ps"], stdout=subprocess.PIPE)
p2 = subprocess.Popen(["findstr", "top"], stdin=p1.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p2.communicate()
print(out, err) # 结果:b'shell 8508 8504 2600 1044 c004e5f8 b6f40938 S top\r\r\n' b''
print("***通过传一个字符串方式***")
p3 = subprocess.Popen("adb shell ps | findstr top", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p3.communicate()
print(out, err) # 结果:b'shell 8508 8504 2600 1044 c004e5f8 b6f40938 S top\r\r\n' b''
subprocess_Popen2()
Python fabric模块
Fabric命令及相关参数总结
fab命令参数
- -f:指定fab入口文件,默认为fabfile.py。
- -l:显示可用任务函数名。
- -H:指定目标主机,多台主机用“,”分隔
- -R:指定角色(Role)。
- -w:当命令执行失败,发出告警,而非默认终止任务。
- -P:以异步并行方式运行多台主机任务,默认为串行运行。
- -t:设置设备连接超时时间。
- -T:设置远程主机命令执行超时时间。
- -g:指定网关(中转设备),比如堡垒机环境,填写堡垒机IP即可。
方法总结
- local:执行本地命令,如:local(‘hostname’)
- lcd:切换本地目录,lcd(’/root’)
- cd:切换远程目录,cd(‘cd’)
- run:执行远程命令,如:run(‘hostname R1’)
- sudo:sudo执行远程命令,如:sudo('echo “123456″)
- put:上传本地文件到远程主机,如:put(src,des)
- get:从远程主机下载文件到本地,如:get(des,src)
- prompt:获取用户输入信息,如:prompt(‘please enter a new password:’)
- reboot:重启远程主机,reboot()
- @task:函数修饰符,标识的函数为fab可调用的
- @runs_once:函数修饰符,表示的函数只会执行一次
Fabric简单脚本
简单举例
我们创建一个”fabfile.py”文件,”fabfile.py”文件中每个函数就是一个任务,任务名即函数名,例中是”hello”。”fab”命令就是用来执行”fabfile.py”中定义的任务,它必须显式地指定任务名。你可以使用参数”-l”来列出当前”fabfile.py”文件中定义了哪些任务:
def hello():
print "Hello Fabric!"
在”fabfile.py”的目录下执行命令:
$ fab hello
任务可以带参数,比如我们将hello函数改为:
def hello(name, value):
print "Hello Fabric! %s=%s" % (name,value)
此时执行hello任务时,就要传入参数值:
$ fab hello:name=Year,value=2016
本地fabric脚本
“fabric.api”包里的local()方法可以用来执行本地Shell命令,比如让我们列出本地”/home/ckr”目录下的所有文件及目录:
from fabric.api import local
def hello():
local('ls -l /home/ckr/')
local()方法有一个capture参数用来捕获标准输出,比如:
def hello():
output = local('echo Hello', capture=True)
Fabric默认执行的脚本是fabfile.py,如果要换脚本文件需要使用 -f 指定。比如我们将hello任务放到.py中就要执行:
fab -f .py hello
远程fabric脚本(重,主)
Fabric真正强大之处不是在执行本地命令,而是可以方便的执行远程机器上的Shell命令。它通过SSH实现,你需要的是在脚本中配置远程机器地址及登录信息:
SSH自动登陆
将登陆密码写到脚本文件里是不安全的,推荐的方法是设置SSH KEY自动登陆。
登陆本地机器生成KEY:
$ ssh-keygen -t rsa -f ~/.ssh/id_rsa_fabric
生成密钥对之后将公钥添加到远程服务器的~/.ssh/authorized_keys文件中,就可以实现自动登陆了。
#-*- coding:utf-8 -*-
from fabric.api import env, roles, run, execute, cd
env.hosts = ['10.216.224.65', '10.216.224.66']
env.user = 'user'
env.key_filename = '~/.ssh/id_rsa_fabric'
def taskA():
with cd('/usr/local/webserver'):
run('pwd')
※注意:authorized_keys文件权限只所有者可写,其他用户均无写权限,否则sshd将认为不安全不允许使用该文件导致还需要输入密码认证。
执行远程命令
常用环境变量(env.)
- user:可以通过设置env.user来指定Fabric建立SSH连接时使用的用户名(默认使用本地用户名)。
- password:用来显式设置默认连接或者在需要的时候提供sudo密码。如果没有设置密码或密码错误,Fabric将会提示你输入。
- passwords:密码字典,针对不同的机器设置密码。必须由username@host:port三部分构成,缺一不可,否则运行时还是会要求输入密码。
- warn_only:布尔值,用来设置Fabric是否在检测到远程错误时退出。
- hosts:全局主机列表。
- roledefs:定义角色名和主机列表的映射字典。
- getway:定义网关
- port:定义目标主机端口,默认为22
#-*- coding:utf-8 -*-
from fabric.api import run, env
# env被称为环境字典,用来配置一些运行环境相关的信息
env.hosts = ['192.168.1.100', '192.168.1.101']
env.user = 'user'
env.password = 'passwd'
def taskA():
run('cd /usr/local/webserver/php && ls -l')
run('sudo /usr/local/webserver/nginx/sbin/nginx -t')
fabric.api包里的run()方法可以用来执行远程Shell命令。上面的任务会分别到两台服务器”192.168.1.100”和”192.168.1.101”上执行命令。这里假设两台服务器的用户名都是”user”,密码都是“passwd”。
env.hosts是设置机器列表的,也可以把用户直接写到hosts里:
env.hosts = ['user@192.168.1.100', 'user@192.168.1.101']
如果你的env.hosts里没有配置某个服务器,但是你又想在这个服务器上执行任务,你可以在命令行中通过-H指定远程服务器地址,多个服务器地址用逗号分隔:
fab -H 192.168.1.102,192.168.1.103 taskA
如果对于不同的服务器想执行不同的任务,上面的程序就做不到了,我们需要对服务器定义角色:
#-*- coding:utf-8 -*-
from fabric.api import env, roles, run, execute, cd
env.roledefs = {
'dev': ['user1@10.216.224.65', 'user2@10.216.224.66'],
'online': ['user3@45.33.108.82']
}
# host strings必须由username@host:port三部分构成,缺一不可,否则运行时还是会要求输入密码
env.passwords = {
'user1@10.216.224.65:22': 'passwd1',
'user2@10.216.224.66:22': 'passwd2',
'user3@45.33.108.82:22': 'passwd3'
}
@roles('dev')
def taskA():
with cd('/usr/local/webserver'):
run('pwd')
@roles('online')
def taskB():
run('pwd')
def task():
execute(taskA)
execute(taskB)
然后执行task任务:
$ fab task
Fabric会在dev机器上执行taskA任务,然后在online机器上执行taskB任务。@roles装饰器指定了它所装饰的任务会被哪个角色的服务器执行。
如果某一任务上没有指定某个角色,但是你又想让这个角色的服务器也能运行该任务,你可以通过-R来指定角色名,多个角色用逗号分隔:
$ fab -R online taskA
SSH功能函数
到目前为止,我们介绍了local()和run()函数分别用来执行本地和远程Shell命令。Fabric还提供了其他丰富的功能函数来辅助执行命令,这里我们介绍几个常用的:
- sudo: 以超级用户权限执行远程命令
功能类似于run()方法,区别是它相当于在Shell命令前加上了sudo,所以拥有超级用户的权限。使用此功能前,你需要将你的用户设为sudoer,而且无需输密码。
from fabric.api import env, sudo
env.hosts = ['ckr@example1.com', 'ckr@example2.com']
env.password = '111111'
def hello():
sudo('mkdir /var/www/myapp')
- get(remote, local): 从远程机器上下载文件到本地
它的工作原理是基于scp命令,使用的方法如下:
from fabric.api import env, get
env.hosts = ['ckr@example.com',]
env.password = '111111'
def hello():
get('/var/log/myapp.log', 'myapp-0301.log')
上述任务将远程机上”/var/log/myapp.log”文件下载到本地当前目录,并命名为”myapp-0301.log”。
- put(local, remote): 从本地上传文件到远程机器上
同get一样,put方法也是基于scp命令,使用的方法如下:
from fabric.api import env, put
env.hosts = ['ckr@example1.com', 'ckr@example2.com']
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)
- prompt(): 提示输入
该方法类似于Shell中的read命令,它会在终端显示一段文字来提示用户输入,并将用户的输入保存在变量里:
from fabric.api import env, get, prompt
env.hosts = ['ckr@example.com',]
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”;如果你输入字符串,终端会提醒你类型验证失败,让你重新输入,直到正确为止。
- reboot: 重启服务器
看方法名就猜到了,有时候安装好环境后,需要重启服务器,这时就要用到reboot()方法,你可以用wait参数来控制其等待多少秒后重启,没有此参数则代表立即重启:
from fabric.api import env, reboot
env.hosts = ['ckr@example.com',]
env.password = '111111'
def restart():
reboot(wait=60)
上面的restart任务将在一分钟后重启服务器。
Fabric上下文管理器
Fabric的上下文管理器是一系列与Python的”with”语句配合使用的方法,它可以在”with”语句块内设置当前工作环境的上下文。让我们介绍几个常用的:
- cd(): 设置远程机器的当前工作目录
cd()方法在之前的范例中出现过,with cd()语句块可以用来设置远程机的工作目录:
from fabric.api import env, cd, put
env.hosts = ['ckr@example1.com', ]
env.password = '111111'
def hello():
with cd('/var/www/'):
put('/tmp/myapp-0301.tar.gz', 'myapp.tar.gz')
上例中的文件会上传到远程机的”/var/www/“目录下。出了with cd()语句块后,工作目录就回到初始的状态,也就是”ckr”用户的根目录。
- lcd(): 设置本地工作目录
lcd()就是”local cd”的意思,用法同cd()一样,区别是它设置的是本地的工作目录:
from fabric.api import env, cd, lcd, put
env.hosts = ['ckr@example1.com', ]
env.password = '111111'
def hello():
with cd('/var/www/'):
with lcd('/tmp/'):
put('myapp-0301.tar.gz', 'myapp.tar.gz')
这个例子的执行效果跟上个例子一样。
- path: 添加远程机的PATH路径
from fabric.api import env, run, path
env.hosts = ['ckr@example1.com', ]
env.password = '111111'
def hello():
with path('/home/ckr/tmp'):
run('echo $PATH')
run('echo $PATH')
假设我们的PATH环境变量默认是”/sbin:/bin”,在上述with path()语句块内PATH变量将变为”/sbin:/bin:/home/ckr/tmp”。出了with语句块后,PATH又回到原来的值。
- settings(): 设置Fabric环境变量参数
Fabric环境变量即是我们例子中一直出现的fabric.api.env,它支持的参数可以从官方文档中查到。
from fabric.api import env, run, settings
env.hosts = ['ckr@example1.com', ]
env.password = '111111'
def hello():
with settings(warn_only=True):
run('echo $USER')
我们将环境参数warn_only暂时设为True,这样遇到错误时任务不会退出。
- shell_env(): 设置Shell环境变量
可以用来临时设置远程和本地机上Shell的环境变量。
from fabric.api import env, run, local, shell_env
env.hosts = ['ckr@example1.com', ]
env.password = '111111'
def hello():
with shell_env(JAVA_HOME='/opt/java'):
run('echo $JAVA_HOME')
local('echo $JAVA_HOME')
- prefix: 设置命令执行前缀
from fabric.api import env, run, local, prefix
env.hosts = ['ckr@example1.com', ]
env.password = '111111'
def hello():
with prefix('echo Hi'):
run('pwd')
local('pwd')
在上述with prefix()语句块内,所有的run()或local()方法的执行都会加上echo Hi &&前缀,也就是效果等同于:
run('echo Hi && pwd')
local('echo Hi && pwd')
配合后一节我们会讲到的错误处理,它可以确保在prefix()方法上的命令执行成功后才会执行语句块内的命令。
错误处理
默认情况下,Fabric在任务遇到错误时就会退出,如果我们希望捕获这个错误而不是退出任务的话,就要开启warn_only参数。在上面介绍settings()上下文管理器时,我们已经看到了临时开启warn_only的方法了,如果要全局开启,有两个办法:
- 在执行fab命令时加上-w参数
$ fab -w hello
- 设置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 = ['ckr@example1.com', ]
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支持并行任务,当服务器的任务之间没有依赖时,并行可以有效的加快执行速度。怎么开启并行执行呢?办法也是两个:
- 在执行fab命令时加上-P参数
$ fab -P hello
- 设置env.parallel环境参数为True
from fabric.api import env
env.parallel = True
以上是对任务并行做一个全局控制。如果只想对某一个任务做并行的话,我们可以在任务函数上加上@parallel装饰器,这样即便全局并行未开启,被@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()任务也会串行执行。