簡介
fabric是一個Python的庫,同時它也是一個命令行工具。使用fabric提供的命令行工具,可以很方便地執行應用部署和系統管理等操作。
fabric依賴於paramiko進行ssh交互,fabric的設計思路是通過幾個API接口來完成所有的部署,因此fabric對系統管理操作進行了簡單的封裝,比如執行命令,上傳文件,並行操作和異常處理等。
#安裝 # fabric3支持python3 pip3 install fabric3
由於fabric比較特殊它還是一個命令行工具,可以通過help進行命令的了解
pyvip@Vip:~/utils$ 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 --set=KEY=VALUE,... comma separated KEY=VALUE pairs to set Fab env vars ……
錯誤的提示
# fab -help Traceback (most recent call last): File "/usr/local/python3/bin/fab", line 11, in <module> load_entry_point('Fabric==1.14.0', 'console_scripts', 'fab')() File "/usr/local/python3/lib/python3.6/site-packages/pkg_resources/__init__.py", line 480, in load_entry_point return get_distribution(dist).load_entry_point(group, name) File "/usr/local/python3/lib/python3.6/site-packages/pkg_resources/__init__.py", line 2693, in load_entry_point return ep.load() File "/usr/local/python3/lib/python3.6/site-packages/pkg_resources/__init__.py", line 2324, in load return self.resolve() File "/usr/local/python3/lib/python3.6/site-packages/pkg_resources/__init__.py", line 2330, in resolve module = __import__(self.module_name, fromlist=['__name__'], level=0) File "/usr/local/python3/lib/python3.6/site-packages/fabric/main.py", line 13, in <module> from operator import isMappingType ImportError: cannot import name 'isMappingType' # 說明你使用的python版本可fabric版本不同,python3 安裝時使用的是fabric3
入門使用
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)
PS:fab命令執行時,默認引用一個名為fabfile.py的文件,我們也可以通過-f來進行指定(文件名不能為abc.py,會沖突).
這里使用了三個fabric的封裝:
- run:用於執行遠程命令的封裝
- sudo:以sudo權限執行遠程命令
- env:保存用戶配置的字典(保存了相關的配置,比如登錄用戶名env.user,密碼env.password,端口env.port等,如果沒有指定用戶名那么默認使用當前用戶,端口使用22)
1、獲取任務列表 pyvip@Vip:~/utils$ fab -f fab_utils.py --list Available commands: hello hostname ls tail 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.
需要注意的是:
- 一次可以多個task,按照順序執行: fab -f fab_util.py hostname ls
- 給task傳遞參數使用task:參數,多個參數按照位置進行傳遞(和Python相同,對於關鍵字的參數可以,在命令行中指定:fab ls:path=/home)
fabric的命令行參數
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 a ' [192.168.5.128] Executing task '<remainder>' [192.168.5.128] run: ip a [192.168.5.128] out: 1: lo: <LOOPBACK,UP,LOWER_UP> 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: <BROADCAST,MULTICAST,UP,LOWER_UP> 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: <BROADCAST,MULTICAST> 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
常用的對象和方法介紹
介紹fabric中的env對象,以及其他的比如執行命令模塊,上傳文件等。
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配置如下:
- env.hosts:定義目標服務器列表
- env.exclude_hosts:排除特定的服務器
- env.user SSH:到遠程服務器的用戶名
- env.port:遠程服務器的端口號
- env.key_filename:私鑰文件的位置
- env.password SSH:到遠程服務器的密碼
針對不同主機不同密碼的情況,可以使用如下的方式:
env.hosts = [ 'root@192.168.10.201:22', 'root@192.168.10.202:22', 'root@192.168.10.203:22' ] env.passwords = { 'root@192.168.10.201:22':'123456201', 'root@192.168.10.202:22':'123456202', 'root@192.168.10.203:22':'123456203'
fabric提供的命令
run():在遠程服務器上執行Linux命令,還有一個重要的參數pty,如果我們執行命令以后需要有一個常駐的服務進程,那么就需要設置pty=False,避免因為Fabric退出導致進程的退出
run('service mysqld start',pty=False)
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表示錯誤
結果
[192.168.5.128] Executing task 'hello' /bin/bash: anetstat: command not found 1 True Done. Disconnecting from 192.168.5.128... done.
sudo():與run類似,使用管理員權限在遠程服務器上執行shell命令,還有一個重要的參數pty,如果我們執行命令以后需要有一個常駐的服務進程,那么就需要設置pty=False,避免因為Fabric退出導致進程的退出。
local():用以執行本地命令,返回要執行的命令,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
get():從遠程服務器上獲取文件,通過remote_path參數聲明從何處下載,通過local_path表示下載到何處。remote_path支持通配符。
get(remote_path='/etc/passwd',local_path='/tmp/passwd')
put():將本地的文件上傳到遠程服務器,參數與get相似,此外,還可以通過mode參數執行遠程文件的權限配置。
get(remote_path='/etc/passwd',local_path='/tmp/passwd')
reboot():重啟遠程服務器,可以通過wait參數設置等待幾秒鍾重啟
reboot(wait=30)
propmt():用以在Fabric執行任務的過程中與管理員進行交互,類似於python的input
key = prompt('Please specify process nice level:',key='nice',validate=int) # 會返回采集到的key
fabric的上下文管理器
env中存儲的是全局配置,有時候我們並不希望修改全局配置參數,只希望臨時修改部分配置,例如:修改當前工作目錄,修改日志輸出級別等。
在fabric中我們可以通過上下文管理器臨時修改參數配置,而不會影響全局配置。當程序進入上下文管理器的作用域時,臨時修改就會起作用;當程序離開上下文管理器時,臨時修改就會消失。
cd():切換遠程目錄
def change(dir='/tmp'): with cd(dir): run('pwd') # /tmp run('pwd') # /root
lcd():切換本地目錄
path():配置遠程服務器PATH環境變量,只對當前會話有效,不會影響遠程服務器的其他操作,path的修改支持多種模式
- append:默認行為,將給定的路徑添加到PATH后面。
- prepend:將給定的路徑添加到PATH的前面。
- replace:替換當前環境的PATH變量。
def addpath(): with path('/tmp','prepend'): run("echo $PATH") run("echo $PATH")
prefix():前綴,它接受一個命令作為參數,表示在其內部執行的代碼塊,都要先執行prefix的命令參數。
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'
shell_env():設置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'
settings():通用配置,用於臨時覆蓋env變量
def who(): with settings(user='dev'): # 臨時修改用戶名為dev run('who') run('who')
remote_tunnel():通過SSH的端口轉發建立的鏈接
with remote_tunnel(3306): run('mysql -uroot -p password')
hide():用於隱藏指定類型的輸出信息,hide定義的可選類型有7種
- status:狀態信息,如服務器斷開鏈接,用戶使用ctrl+C等,如果Fabric順利執行,不會有狀態信息
- aborts:終止信息,一般將fabric當作庫使用的時候需要關閉
- warnings:警告信息,如grep的字符串不在文件中
- running:fabric運行過程中的數據
- stdout:執行shell命令的標准輸出
- stderr:執行shell命令的錯誤輸出
- user:用戶輸出,類似於Python中的print函數
為了方便使用,fabric對以上其中類型做了進一步的封裝
- output:包含stdout,stderr
- everything:包含stdout,stderr,warnings,running,user
- commands:包含stdout,running
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提供的裝飾器,既不是執行具體的操作,也不是修改參數,而是控制如何執行這些操作,在那些服務器上執行這些操作,fabric的裝飾器與人物執行緊密相關。下面從幾個方面來進行說明
- hosts:定制執行task的服務器列表
- roles:定義執行task的role列表
- parallel:並行執行task
- serial:串行執行task
- task:定義一個task
- runs_once:該task只執行一次
fabric的task
task就是fabric需要在遠程服務器上執行的函數,在fabric中有3中方法定義一個task
- 默認情況下,fabfile中每一個函數都是一個task。
- 繼承自fabric的task類,這種方式比較難用,不推薦。
- 使用fabric的task裝飾器,這是使用fabric最快速的方式,也是推薦的用法。
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的host
為了方便我們的使用,fabric提供了非常靈活的方式指定對哪些遠程服務器執行操作,根據我們前面的知識,我們知道有兩種方式:通過env.hosts來執行,或者在fab執行命令的時候使用-H參數,除此之外,還有以下需要注意的地方
- 指定host時,可以同時指定用戶名和端口號: username@hostname:port
- 通過命令行指定要多哪些hosts執行人物:fab mytask:hosts="host1;host2"
- 通過hosts裝飾器指定要對哪些hosts執行當前task
- 通過env.reject_unkown_hosts控制未知host的行為,默認True,類似於SSH的StrictHostKeyChecking的選項設置為no,不進行公鑰確認。
from fabric.api import * env.hosts = [ 'root@192.168.10.201:22', 'root@192.168.10.202:22', 'root@192.168.10.203:22' ] env.passwords = { 'root@192.168.10.201:22':'123456201', 'root@192.168.10.202:22':'123456202', 'root@192.168.10.203:22':'123456203' } @hosts('root@192.168.10.201:22') @task def hello(): run('ifconfig br0') # 命令行的方式: fab hello:hosts="root@192.168.10.201;root@192.168.10.202"
fabric的role
role是對服務器進行分類的手段,通過role可以定義服務器的角色,以便對不同的服務器執行不同的操作,Role邏輯上將服務器進行了分類,分類以后,我們可以對某一類服務器指定一個role名即可。進行task任務時,對role進行控制。
# role在env.roledefs中進行定義 env.roledefs = { 'web':['root@192.168.10.201','192.168.10.202'] # role名稱為:web 'db':['root@192.168.10.203',] # role名稱為:db } 當我們定義好role以后,我們就可以通過roles裝飾器來指定在哪些role上運行task。 from fabric.api import * env.roledefs = { 'web':['root@192.168.10.201:22','root@192.168.10.202:22',], 'db':['root@192.168.10.203:22',] } env.passwords = { 'root@192.168.10.201:22':'123456201', 'root@192.168.10.202:22':'123456202', 'root@192.168.10.203:22':'123456203' } @roles('db') # 只對role為db的主機進行操作 @task def hello(): run('ifconfig br0')
注意:hosts裝飾器可以和roles裝飾器一起使用(全集),看起來容易造成混亂,不建議混搭。
fabric的執行模型
fabric執行任務的步驟如下:
- 創建任務列表,這些任務就是fab命令行參數指定的任務,fab會保持這些任務的順序
- 對於每個任務,構造需要執行該任務的服務器列表,服務器列表可以通過命令行參數指定,或者env.hosts指定,或者通過hosts和roles裝飾器指定
- 遍歷任務列表,對於每一台服務器分別執行任務,可以將任務列表和服務器列表看作是兩個for循環,任務列表是外層循環,服務器列表是內存循環,fabric默認是串行執行的可以通過裝飾器或者命令行參數確定任務執行的方式
- 對於沒有指定服務器的任務默認為本地任務,僅執行一次
PS:關於並行模式:
- 通過命令行參數-P(--parallel)通知Fabric並行執行task
- 通過env.parallel設置設否需要並行執行
- 通過parallel裝飾器通知Fabric並行執行task,它接受一個pool_size作為參數(默認為0),表示可以有幾個任務並行執行
其他裝飾器
前面介紹了task,hosts,roles和parallel裝飾器,此外還有兩個裝飾器比較常用
- runs_once:只執行一次,防止task被多次調用。例如,對目錄打包進行上傳,上傳動作對不同的服務器可能會執行多次,但是打包的動作只需要執行一次即可。
- serial:強制當前task穿行執行。使用該參數時優先級最高,即便是制定了並發執行的參數
常用的功能函數
fabric中還有其他的一些好用的函數
封裝task
fabric提供了一個execute函數,用來對task進行封裝。它最大的好處就是可以將一個大的任務拆解為很多小任務,每個小任務互相獨立,互不干擾
from fabric.api import * env.roledefs = { 'web':['root@192.168.10.201:22','root@192.168.10.202:22',], 'db':['root@192.168.10.203:22',] } env.passwords = { 'root@192.168.10.201:22':'123456201', 'root@192.168.10.202:22':'123456202', 'root@192.168.10.203: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,對不同的主機進行操作
utils函數
包含一些輔助行的功能函數,這些函數位於fabric.utils下,常用的函數如下:
- abort:終止函數執行,打印錯誤信息到stderr,並且以退出碼1退出。
- warn:輸出警告信息,但是不會終止函數的執行
- puts:打印輸出,類似於Python中的print函數
def helloworld(): execute(hello) abort('----->abort') # 執行到這里時,直接退出 warn('----->warn') # 會發出提示信息,不會退出 puts('----->puts') # 會打印括號中的信息 execute(world)
帶顏色的輸出
fabric為了讓輸出日志更具有可讀性,對命令行中斷的顏色輸出進行了封裝,使用print打印帶有不同顏色的文本,這些顏色包含在fabric.colors中。像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) 黃色
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)
確認信息
有時候我們在某一步執行錯誤,會給用戶提示,是否繼續執行時,confirm就非常有用了,它包含在 fabric.contrib.console中
def testconfirm(): result = confirm('Continue Anyway?') print(result) # 會提示輸入y/n # y 時 result為True # n 時 result為False
使用Fabric源碼安裝redis
下載一個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.
理解:在一個獨立的進程組里面運行我們的進程。
參考資料
http://www.cnblogs.com/dachenzi/p/8695330.html