1. 程式人生 > 實用技巧 >Python 應用自動化部署工具Fabirc

Python 應用自動化部署工具Fabirc

介紹

Fabirc是基於python實現的SSH命令列工具,非常適合應用的自動化部署,或者執行系統管理任務。

python2:pip3 install fabric

python3:pip3 install fabric3

簡單的例子:

root@openstack:~# cat fabfile.py
def hello():
    print('hello world!')
 
root@openstack:~# fab hello
hello world!

這個fab簡單地匯入了fabfile,並執行定義的hello函式。

命令列啟動

fab作為Fabric程式的命令列入口,提供了豐富的引數呼叫,命令格式如下:

root@openstack:~# fab --help
Usage: fab [options] <command>[:arg1,arg2=val2,host=foo,hosts='h1;h2',...] ...

各引數含義如下:

引數項含義
-l 顯示可用任務函式名
-f 指定fab入口檔案,預設為fabfile.py
-g 指定閘道器(中轉裝置),比如堡壘機環境,填寫堡壘機IP即可
-H 指定目標主機,多臺主機用“,”分隔
-P 以非同步並行方式執行多臺主機任務,預設為序列執行
-R 指定角色(Role)
-t 設定裝置連線超時時間
-T 設定遠端主機命令執行超時時間
-w 當命令執行失敗,發出告警,而非預設終止任務

fabfile全域性屬性設定

env物件的作用是定義fabfile的全域性設定,各屬性說明如下:

屬性含義
env.host 定義目標主機,以python的列表表示,如env.host=['xx.xx.xx.xx','xx.xx.xx.xx']
env.exclude_hosts 排除指定主機,以python的列表表示
env.port 定義目標主機埠,預設為22
env.user 定義使用者名稱
env.password 定義密碼
env.passwords 與password功能一樣,區別在於不同主機配置不同密碼的應用情景,配置此項的時候需要配置使用者、主機、埠等資訊,如:env.passwords = {'[email protected]:22': '123', '[email protected]':'234'}
env.getway 定義閘道器
env.deploy_release_dir 自定義全域性變數
env.roledefs 定義角色分組

常用的API

Fabric支援常用的方法及說明如下:

方法說明
local 執行本地命令,如:local('hostname')
lcd 切換本地目錄,lcd('/root')
cd 切換遠端目錄,cd('cd')
run 執行遠端命令,如:run('hostname')
sudo sudo執行遠端命令,如:sudo('echo “123456″ passwd --stdin root')
put 上傳本地檔案到遠端主機,如:put(src,des)
get 從遠端主機下載檔案到本地,如:get(des,src)
prompt 獲取使用者輸入資訊,如:prompt(‘please enter a new password:’)
confirm 獲取提示資訊確認,如:confirm('failed.Continue[Y/n]?')
reboot 重啟遠端主機,reboot()
@task 函式修飾符,標識的函式為fab可呼叫的
@runs_once 函式修飾符,表示的函式只會執行一次



從一個例項入手

假設我們需要為一個 web 應用建立 fabfile 。具體的情景如下:這個 web 應用的程式碼使用 git 託管在一臺遠端伺服器vcshost上,我們把它的程式碼庫克隆到了本地localhost中。我們希望在我們把修改後的程式碼 push 回 vcshost 時,自動把新的版本安裝到另一臺遠端伺服器my_server上。我們將通過自動化本地和遠端 git 命令來完成這些工作。

關於 fabfile 檔案放置位置的最佳時間是專案的根目錄:

.
|-- __init__.py
|-- app.wsgi
|-- fabfile.py <-- our fabfile!
|-- manage.py
`-- my_app
    |-- __init__.py
    |-- models.py
    |-- templates
    |   `-- index.html
    |-- tests.py
    |-- urls.py
    `-- views.py

註解

在這裡我們使用一個 Django 應用為例——不過 Fabric 並s依賴於外部程式碼,除了它的 SSH 庫。

作為起步,我們希望先執行測試準備好部署後,再提交到 VCS(版本控制系統):

from fabric.api import local

def prepare_deploy():
    local("./manage.py test my_app")
    local("git add -p && git commit")
    local("git push")

這段程式碼的輸出會是這樣:

$ fab prepare_deploy
[localhost] run: ./manage.py test my_app
Creating test database...
Creating tables
Creating indexes
..........................................
----------------------------------------------------------------------
Ran 42 tests in 9.138s

OK
Destroying test database...

[localhost] run: git add -p && git commit

<interactive Git add / git commit edit message session>

[localhost] run: git push

<git push session, possibly merging conflicts interactively>

Done.

這段程式碼很簡單,匯入一個 Fabric API:local,然後用它執行本地 Shell 命令並與之互動,剩下的 Fabric API 也都類似——它們都只是 Python。

參見

業務(Operation),指定 fabfile

用你的方式來組織

因為 Fabric “只是 Python”,所以你可以按你喜歡的方式來組織 fabfile 。比如說,把任務分割成多個子任務:

from fabric.api import local

def test():
    local("./manage.py test my_app")

def commit():
    local("git add -p && git commit")

def push():
    local("git push")

def prepare_deploy():
    test()
    commit()
    push()

這個prepare_deploy任務仍可以像之前那樣呼叫,但現在只要你願意,就可以呼叫更細粒度的子任務。

故障

我們的基本案例已經可以正常工作了,但如果測試失敗了會怎樣?我們應該抓住機會即使停下任務,並在部署之前修復這些失敗的測試。

Fabric 會檢查被呼叫程式的返回值,如果這些程式沒有乾淨地退出,Fabric 會終止操作。下面我們就來看看如果一個測試用例遇到錯誤時會發生什麼:

$ fab prepare_deploy
[localhost] run: ./manage.py test my_app
Creating test database...
Creating tables
Creating indexes
.............E............................
======================================================================
ERROR: testSomething (my_project.my_app.tests.MainTests)
----------------------------------------------------------------------
Traceback (most recent call last):
[...]

----------------------------------------------------------------------
Ran 42 tests in 9.138s

FAILED (errors=1)
Destroying test database...

Fatal error: local() encountered an error (return code 2) while executing './manage.py test my_app'

Aborting.

太好了!我們什麼都不用做,Fabric 檢測到了錯誤並終止,不會繼續執行 commit 任務。

參見

Failure handling (usage documentation)

故障處理

但如果我們想更加靈活,給使用者另一個選擇,該怎麼辦?一個名為warn_only的設定(或著說環境變數,通常縮寫為env var)可以把退出換為警告,以提供更靈活的錯誤處理。

讓我們把這個設定丟到test函式中,然後注意這個local呼叫的結果:

from __future__ import with_statement
from fabric.api import local, settings, abort
from fabric.contrib.console import confirm

def test():
    with settings(warn_only=True):
        result = local('./manage.py test my_app', capture=True)
    if result.failed and not confirm("Tests failed. Continue anyway?"):
        abort("Aborting at user request.")

[...]

為了引入這個新特性,我們需要新增一些新東西:

  • 在 Python 2.5 中,需要從__future__中匯入with

  • Fabriccontrib.console子模組提供了confirm函式,用於簡單的 yes/no 提示。

  • settings上下文管理器提供了特定程式碼塊特殊設定的功能。

  • local這樣執行命令的操作會返回一個包含執行結果(.failed.return_code屬性)的物件。

  • abort函式用於手動停止任務的執行。

即使增加了上述複雜度,整個處理過程仍然很容易理解,而且它已經遠比之前靈活。

參見

上下文管理器,環境變數完整列表

建立連線

讓我們回到 fabfile 的主旨:定義一個deploy任務,讓它在一臺或多臺遠端伺服器上執行,並保證程式碼是最新的:

def deploy():
    code_dir = '/srv/django/myproject'
    with cd(code_dir):
        run("git pull")
        run("touch app.wsgi")

這裡再次引入了一些新的概念:

  • Fabric 是 Python——所以我們可以自由地使用變數、字串等常規的 Python 程式碼;

  • cd函式是一個簡易的字首命令,相當於執行cd/to/some/directory,和lcd函式類似,只不過後者是在本地執行。

  • ~fabric.operations.run` 和local類似,不過是在遠端而非本地執行。

我們還需要保證在檔案頂部匯入了這些新函式:

from __future__ import with_statement
from fabric.api import local, settings, abort, run, cd
from fabric.contrib.console import confirm

改好之後,我們重新部署:

$ fab deploy
No hosts found. Please specify (single) host string for connection: my_server
[my_server] run: git pull
[my_server] out: Already up-to-date.
[my_server] out:
[my_server] run: touch app.wsgi

Done.

我們並沒有在 fabfile 中指定任何連線資訊,所以 Fabric 依舊不知道該在哪裡執行這些遠端命令。遇到這種情況時,Fabric 會在執行時提示我們。連線的定義使用 SSH 風格的“主機串”(例如:user@host:port ),預設使用你的本地使用者名稱——所以在這個例子中,我們只需要指定主機名my_server

與遠端互動

如果你已經得到了程式碼,說明gitpull執行非常順利——但如果這是第一次部署呢?最好也能應付這樣的情況,這時應該使用gitclone來初始化程式碼庫:

def deploy():
    code_dir = '/srv/django/myproject'
    with settings(warn_only=True):
        if run("test -d %s" % code_dir).failed:
            run("git clone user@vcshost:/path/to/repo/.git %s" % code_dir)
    with cd(code_dir):
        run("git pull")
        run("touch app.wsgi")

和上面呼叫local一樣,run也提供基於 Shell 命令構建乾淨的 Python 邏輯。這裡最有趣的部分是gitclone:因為我們是用 git 的 SSH 方法來訪問 git 伺服器上的程式碼庫,這意味著我們遠端執行的run需要自己提供身份驗證。

舊版本的 Fabric(和其他類似的高層次 SSH 庫)像在監獄裡一樣執行遠端命令,無法提供本地互動。當你迫切需要輸入密碼或者與遠端程式互動時,這就很成問題。

Fabric 1.0 和後續的版本突破了這個限制,並保證你和另一端的會話互動。讓我們看看當我們在一臺沒有 git checkout 的新伺服器上執行更新後的 deploy 任務時會發生什麼:

$ fab deploy
No hosts found. Please specify (single) host string for connection: my_server
[my_server] run: test -d /srv/django/myproject

Warning: run() encountered an error (return code 1) while executing 'test -d /srv/django/myproject'

[my_server] run: git clone user@vcshost:/path/to/repo/.git /srv/django/myproject
[my_server] out: Cloning into /srv/django/myproject...
[my_server] out: Password: <enter password>
[my_server] out: remote: Counting objects: 6698, done.
[my_server] out: remote: Compressing objects: 100% (2237/2237), done.
[my_server] out: remote: Total 6698 (delta 4633), reused 6414 (delta 4412)
[my_server] out: Receiving objects: 100% (6698/6698), 1.28 MiB, done.
[my_server] out: Resolving deltas: 100% (4633/4633), done.
[my_server] out:
[my_server] run: git pull
[my_server] out: Already up-to-date.
[my_server] out:
[my_server] run: touch app.wsgi

Done.

注意那個Password:提示——那就是我們在 web 伺服器上的遠端git應用在請求 git 密碼。我們可以在本地輸入密碼,然後像往常一樣繼續克隆。

參見

與遠端程式整合

預定義連線

在執行輸入連線資訊已經是非常古老的做法了,Fabric 提供了一套在 fabfile 或命令列中指定伺服器資訊的簡單方法。這裡我們不展開說明,但是會展示最常用的方法:設定全域性主機列表env.hosts

env是一個全域性的類字典物件,是 Fabric 很多設定的基礎,也能在 with 表示式中使用(事實上,前面見過的~fabric.context_managers.settings就是它的一個簡單封裝)。因此,我們可以在模組層次上,在 fabfile 的頂部附近修改它,就像這樣:

from __future__ import with_statement
from fabric.api import *
from fabric.contrib.console import confirm

env.hosts = ['my_server']

def test():
    do_test_stuff()

fab載入 fabfile 時,將會執行我們對env的修改並儲存設定的變化。最終結果如上所示:我們的deploy任務將在my_server上執行。

這就是如何指定 Fabric 一次性控制多臺遠端伺服器的方法:env.hosts是一個列表,fab對它迭代,對每個連線執行指定的任務。

參見

環境字典 env,How host lists are constructed

總結

雖然經歷了很多,我們的 fabfile 檔案仍然相當短。下面是它的完整內容:

from __future__ import with_statement
from fabric.api import *
from fabric.contrib.console import confirm

env.hosts = ['my_server']

def test():
    with settings(warn_only=True):
        result = local('./manage.py test my_app', capture=True)
    if result.failed and not confirm("Tests failed. Continue anyway?"):
        abort("Aborting at user request.")

def commit():
    local("git add -p && git commit")

def push():
    local("git push")

def prepare_deploy():
    test()
    commit()
    push()

def deploy():
    code_dir = '/srv/django/myproject'
    with settings(warn_only=True):
        if run("test -d %s" % code_dir).failed:
            run("git clone user@vcshost:/path/to/repo/.git %s" % code_dir)
    with cd(code_dir):
        run("git pull")
        run("touch app.wsgi")

但它已經涉及到了 Fabric 中的很多功能:

  • 定義 fabfile 任務,並用fab執行;

  • local呼叫本地 shell 命令;

  • 通過settings修改 env 變數;

  • 處理失敗命令、提示使用者、手動取消任務;

  • 以及定義主機列表、使用run來執行遠端命令。

還有更多這裡沒有涉及到的內容,你還可以看看所有“參見”中的連結,以及索引頁的內容表。

更多請參考:https://fabric-chs.readthedocs.io/zh_CN/chs/tutorial.html