Fabric是一個Python的庫,它提供了豐富的同SSH互動的介面,可以用來在本地或遠端機器上自動化、流水化地執行Shell命令。因此它非常適合用來做應用的遠端部署及系統維護。其上手也極其簡單,你需要的只是懂得基本的Shell命令。本文將為大家詳細介紹Fabric的使用。
內容索引
安裝Fabric
首先Python的版本必須是2.7以上,可以通過下面的命令檢視當前Python的版本:
1 |
$ python -V |
Fabric的官網是www.fabfile.org,原始碼託管在Github上。你可以clone原始碼到本地,然後通過下面的命令來安裝。
1 |
$ python setup.py develop |
在執行原始碼安裝前,你必須先將Fabric的依賴包Paramiko裝上。所以,個人還是推薦使用pip安裝,只需一條命令即可:
1 |
$ pip install fabric |
第一個例子
萬事從Hello World開始,我們建立一個”fabfile.py”檔案,然後寫個hello函式:
1 2 |
def hello(): print "Hello Fabric!" |
現在,讓我們在”fabfile.py”的目錄下執行命令:
1 |
$ fab hello |
你可以在終端看到”Hello Fabric!”字樣。
簡單解釋下,”fabfile.py”檔案中每個函式就是一個任務,任務名即函式名,上例中是”hello”。”fab”命令就是用來執行”fabfile.py”中定義的任務,它必須顯式地指定任務名。你可以使用引數”-l”來列出當前”fabfile.py”檔案中定義了哪些任務:
1 |
$ fab -l |
任務可以帶引數,比如我們將hello函式改為:
1 2 |
def hello(name, value): print "Hello Fabric! %s=%s" % (name,value) |
此時執行hello任務時,就要傳入引數值:
1 |
$ fab hello:name=Year,value=2016 |
Fabric的指令碼建議寫在”fabfile.py”檔案中,如果你想換檔名,那就要在”fab”命令中用”-f”指定。比如我們將指令碼放在”script.py”中,就要執行:
1 |
$ fab -f script.py hello |
執行本地命令
“fabric.api”包裡的”local()”方法可以用來執行本地Shell命令,比如讓我們列出本地”/home/bjhee”目錄下的所有檔案及目錄:
1 2 3 4 |
from fabric.api import local def hello(): local('ls -l /home/bjhee/') |
“local()”方法有一個”capture”引數用來捕獲標準輸出,比如:
1 2 |
def hello(): output = local('echo Hello', capture=True) |
這樣,Hello字樣不會輸出到螢幕上,而是儲存在變數output裡。”capture”引數的預設值是False。
執行遠端命令
Fabric真正強大之處不是在執行本地命令,而是可以方便的執行遠端機器上的Shell命令。它通過SSH實現,你需要的是在指令碼中配置遠端機器地址及登入資訊:
1 2 3 4 5 6 7 8 |
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/') |
“fabric.api”包裡的”run()”方法可以用來執行遠端Shell命令。上面的任務會分別到兩臺伺服器”example1.com”和”example2.com”上執行”ls -l /home/bjhee/”命令。這裡假設兩臺伺服器的使用者名稱都是”bjhee”,密碼都是6個1。你也可以把使用者直接寫在hosts裡,比如:
1 |
env.hosts = ['bjhee@example1.com', 'bjhee@example2.com'] |
如果你的”env.hosts”裡沒有配置某個伺服器,但是你又想在這個伺服器上執行任務,你可以在命令列中通過”-H”指定遠端伺服器地址,多個伺服器地址用逗號分隔:
1 |
$ fab -H bjhee@example3.com,bjhee@example4.com hello |
另外,多臺機器的任務是序列執行的,關於並行任務的執行我們在之後會介紹。
如果對於不同的伺服器,我們想執行不同的任務,上面的方法似乎做不到,那怎麼辦?我們要對伺服器定義角色:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
from fabric.api import env, roles, run, execute, cd env.roledefs = { 'staging': ['bjhee@example1.com','bjhee@example2.com'], 'build': ['build@example3.com'] } 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) |
現在讓我們執行:
1 |
$ fab task |
這時Fabric會先在一臺build伺服器上執行build任務,然後在兩臺staging伺服器上分別執行deploy任務。”@roles”裝飾器指定了它所裝飾的任務會被哪個角色的伺服器執行。
如果某一任務上沒有指定某個角色,但是你又想讓這個角色的伺服器也能執行該任務,你可以通過”-R”來指定角色名,多個角色用逗號分隔:
1 |
$ fab -R build deploy |
這樣”build”和”staging”角色的伺服器都會執行”deploy”任務了。注:”staging”是裝飾器預設的,因此不用通過”-R”指定。
此外,上面的例子中,伺服器的登入密碼都是明文寫在指令碼里的。這樣做不安全,推薦的方式是設定SSH自動登入,具體方法大家可以去網上搜搜。
SSH功能函式
到目前為止,我們介紹了”local()”和”run()”函式分別用來執行本地和遠端Shell命令。Fabric還提供了其他豐富的功能函式來輔助執行命令,這裡我們介紹幾個常用的:
- sudo: 以超級使用者許可權執行遠端命令
功能類似於”run()”方法,區別是它相當於在Shell命令前加上了”sudo”,所以擁有超級使用者的許可權。使用此功能前,你需要將你的使用者設為sudoer,而且無需輸密碼。具體操作可參見我的這篇文章。
1 2 3 4 5 6 7 |
from fabric.api import env, sudo env.hosts = ['bjhee@example1.com', 'bjhee@example2.com'] env.password = '111111' def hello(): sudo('mkdir /var/www/myapp') |
- get(remote, local): 從遠端機器上下載檔案到本地
它的工作原理是基於scp命令,使用的方法如下:
1 2 3 4 5 6 7 |
from fabric.api import env, get env.hosts = ['bjhee@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命令,使用的方法如下:
1 2 3 4 5 6 7 |
from fabric.api import env, put env.hosts = ['bjhee@example1.com', 'bjhee@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”引數:
1 |
put('/tmp/myapp-0301.tar.gz', '/var/www/myapp.tar.gz', use_sudo=True) |
- prompt: 提示輸入
該方法類似於Shell中的”read”命令,它會在終端顯示一段文字來提示使用者輸入,並將使用者的輸入儲存在變數裡:
1 2 3 4 5 6 7 8 |
from fabric.api import env, get, prompt env.hosts = ['bjhee@example.com',] env.password = '111111' def hello(): filename = prompt('Please input file name: ') get('/var/log/myapp.log', '%s.log' % filename) |
現在下載後的檔名將由使用者的輸入來決定。我們還可以對使用者輸入給出預設值及型別檢查:
1 |
port = prompt('Please input port number: ', default=8080, validate=int) |
執行任務後,終端會顯示:
1 |
Please input port number: [8080] |
如果你直接按回車,則port變數即為預設值8080;如果你輸入字串,終端會提醒你型別驗證失敗,讓你重新輸入,直到正確為止。
- reboot: 重啟伺服器
看方法名就猜到了,有時候安裝好環境後,需要重啟伺服器,這時就要用到”reboot()”方法,你可以用”wait”引數來控制其等待多少秒後重啟,沒有此引數則代表立即重啟:
1 2 3 4 5 6 7 |
from fabric.api import env, reboot env.hosts = ['bjhee@example.com',] env.password = '111111' def restart(): reboot(wait=60) |
上面的restart任務將在一分鐘後重啟伺服器。
上下文管理器
Fabric的上下文管理器是一系列與Python的”with”語句配合使用的方法,它可以在”with”語句塊內設定當前工作環境的上下文。讓我們介紹幾個常用的:
- cd: 設定遠端機器的當前工作目錄
“cd()”方法在之前的範例中出現過,”with cd()”語句塊可以用來設定遠端機的工作目錄:
1 2 3 4 5 6 7 8 |
from fabric.api import env, cd, put env.hosts = ['bjhee@example1.com', ] 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()”一樣,區別是它設定的是本地的工作目錄:
1 2 3 4 5 6 7 8 9 |
from fabric.api import env, cd, lcd, put env.hosts = ['bjhee@example1.com', ] env.password = '111111' def hello(): with cd('/var/www/'): with lcd('/tmp/'): put('myapp-0301.tar.gz', 'myapp.tar.gz') |
這個例子的執行效果跟上個例子一樣。
- path: 新增遠端機的PATH路徑
123456789from fabric.api import env, run, pathenv.hosts = ['bjhee@example1.com', ]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又回到原來的值。
- settings: 設定Fabric環境變數引數
Fabric環境變數即是我們例子中一直出現的”fabric.api.env”,它支援的引數可以從官方文件中查到。
1 2 3 4 5 6 7 8 |
from fabric.api import env, run, settings env.hosts = ['bjhee@example1.com', ] env.password = '111111' def hello(): with settings(warn_only=True): run('echo $USER') |
我們將環境引數”warn_only”暫時設為True,這樣遇到錯誤時任務不會退出。
- shell_env: 設定Shell環境變數
可以用來臨時設定遠端和本地機上Shell的環境變數。
1 2 3 4 5 6 7 8 9 |
from fabric.api import env, run, local, shell_env env.hosts = ['bjhee@example1.com', ] env.password = '111111' def hello(): with shell_env(JAVA_HOME='/opt/java'): run('echo $JAVA_HOME') local('echo $JAVA_HOME') |
- prefix: 設定命令執行字首
123456789from fabric.api import env, run, local, prefixenv.hosts = ['bjhee@example1.com', ]env.password = '111111'def hello():with prefix('echo Hi'):run('pwd')local('pwd')
在上述”with prefix()”語句塊內,所有的”run()”或”local()”方法的執行都會加上”echo Hi && “字首,也就是效果等同於:
12run('echo Hi && pwd')local('echo Hi && pwd')
配合後一節我們會講到的錯誤處理,它可以確保在”prefix()”方法上的命令執行成功後才會執行語句塊內的命令。
錯誤處理
預設情況下,Fabric在任務遇到錯誤時就會退出,如果我們希望捕獲這個錯誤而不是退出任務的話,就要開啟”warn_only”引數。在上面介紹”settings()”上下文管理器時,我們已經看到了臨時開啟”warn_only”的方法了,如果要全域性開啟,有兩個辦法:
- 在執行”fab”命令時加上”-w”引數
1 |
$ fab -w hello |
- 設定”env.warn_only”環境引數為True
123from fabric.api import envenv.warn_only = True
現在遇到錯誤時,控制檯會打出一個警告資訊,然後繼續執行後續任務。那我們怎麼捕獲錯誤並處理呢?像”run()”, “local()”, “sudo()”, “get()”, “put()”等SSH功能函式都有返回值。當返回值的”succeeded”屬性為True時,說明執行成功,反之就是失敗。你也可以檢查返回值的”failed”屬性,為True時就表示執行失敗,有錯誤發生。在開啟”warn_only”後,你可以通過”failed”屬性檢查捕獲錯誤,並執行相應的操作。
1234567891011from fabric.api import env, cd, putenv.hosts = ['bjhee@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”引數
1 |
$ fab -P hello |
- 設定”env.parallel”環境引數為True
123from fabric.api import envenv.parallel = True
如果,我們只想對某一任務做並行的話,我們可以在任務函式上加上”@parallel”裝飾器:
1 2 3 4 5 6 7 8 |
from fabric.api import parallel @parallel def runs_in_parallel(): pass def runs_serially(): pass |
這樣即便並行未開啟,”runs_in_parallel()”任務也會並行執行。反過來,我們可以在任務函式上加上”@serial”裝飾器:
1 2 3 4 5 6 7 8 |
from fabric.api import serial def runs_in_parallel(): pass @serial def runs_serially(): pass |
這樣即便並行已經開啟,”runs_serially()”任務也會序列執行。
補充
這個部分用來補充Fabric的一些特別功能:
- 終端輸出帶顏色
我們習慣上認為綠色表示成功,黃色表示警告,而紅色表示錯誤,Fabric支援帶這些顏色的輸出來提示相應型別的資訊:
1 2 3 4 5 6 |
from fabric.colors import * def hello(): print green("Successful") print yellow("Warning") print red("Error") |
- 限制任務只能被執行一次
通過”execute()”方法,可以在一個”fab”命令中多次呼叫同一任務,如果想避免這個發生,就要在任務函式上加上”@runs_once”裝飾器。
1 2 3 4 5 6 7 8 9 |
from fabric.api import execute, runs_once @runs_once def hello(): print "Hello Fabric!" def test(): execute(hello) execute(hello) |
現在不管我們”execute”多少次hello任務,都只會輸出一次”Hello Fabric!”字樣