如何高效地遠(yuǎn)程部署?自動(dòng)化運(yùn)維利器 Fabric 教程

關(guān)于 Python 自動(dòng)化的話題,在上一篇文章中,我介紹了 Invoke 庫(kù),它是 Fabric 的最重要組件之一。Fabric 也是一個(gè)被廣泛應(yīng)用的自動(dòng)化工具庫(kù),是不得不提的自動(dòng)化運(yùn)維利器,所以,本文將來(lái)介紹一下它。

Fabric 主要用在應(yīng)用部署與系統(tǒng)管理等任務(wù)的自動(dòng)化,簡(jiǎn)單輕量級(jí),提供有豐富的 SSH 擴(kuò)展接口。在 Fabric 1.x 版本中,它混雜了本地及遠(yuǎn)程兩類功能;但自 Fabric 2.x 版本起,它分離出了獨(dú)立的 Invoke 庫(kù),來(lái)處理本地的自動(dòng)化任務(wù),而 Fabric 則聚焦于遠(yuǎn)程與網(wǎng)絡(luò)層面的任務(wù)。

為了做到這點(diǎn),F(xiàn)abric 主要依賴另一大核心組件 Paramiko,它是基于 SSH 協(xié)議的遠(yuǎn)程控制模塊,F(xiàn)abric 在其基礎(chǔ)上封裝出了更加友好的接口,可以遠(yuǎn)程執(zhí)行 Shell 命令、傳輸文件、批量操作服務(wù)器、身份認(rèn)證、多種配置與設(shè)置代理,等等。

一、Fabric 的版本區(qū)分

Python 2 版本已經(jīng)被官宣在今年元旦“退休”了,未來(lái)只會(huì)是 Python 3 的舞臺(tái)。為了適應(yīng) Python 版本的非兼容性遷移,很多項(xiàng)目也必須推出自己的新版本(兼容或只支持 Python 3),其中就包括本文的主角 Fabric。

Fabric 自身存在著 2 個(gè)大版本:Fabric 1 和 Fabric 2,而在這個(gè)庫(kù)的基礎(chǔ)上,還有兩個(gè)很容易混淆的相關(guān)庫(kù):Fabric2 和 Fabric3(注意這里的數(shù)字是庫(kù)名的一部分)。

它們的區(qū)分如下:

  • Fabric 1.x:支持 Python 2.5-2.7,但不支持 Python 3
  • Fabric 2.x:支持 Python 2.7 與 3.4+,但不兼容 Fabric 1.x 的 fabfile
  • Fabric2:等同于 Fabric 2.x,為了使不同版本共存(裝一個(gè) 1.x 舊版本,再裝它作為新版本)
  • Fabric3:一個(gè)基于 Fabric 1.x 的 fork(非官方),兼容 Python 2&3,兼容 Fabric1.x 的 fabfile

綜上可見(jiàn),我們推薦使用官方的 Fabric 2.x 系列版本,但同時(shí)要注意,某些過(guò)時(shí)的教程可能是基于早期版本的(或非官方的 Fabric3,也是基于 Fabric 1.x),需要注意識(shí)別。

例如,在 Fabric 1.x 系列中這么寫(xiě)導(dǎo)入:from fabric.api import run;在新版本中將報(bào)錯(cuò):“ImportError: No module named api”(PS:可根據(jù)是否有 fabric.api 來(lái)判斷 Fabric 的版本,就像在 Python 中根據(jù) print 語(yǔ)句或 print 函數(shù)來(lái)判斷版本一樣)。同時(shí),由于新版本不支持老版本的 fabfile,在使用時(shí)就可能報(bào)錯(cuò):“No idea what 'xxx' is!”

Fabric 2 是非兼容性版本,相比于前個(gè)版本,它主要改進(jìn)的點(diǎn)有:

  • 支持 Python 2.7 與 3.4+
  • 線程安全,取消了多進(jìn)程的并發(fā)實(shí)現(xiàn)
  • API 圍繞 fabric.connection.Connection 進(jìn)行了重組
  • 全面修改了命令行解析器,允許在每個(gè)任務(wù)的基礎(chǔ)上使用規(guī)則的 GNU/POSIX 風(fēng)格的標(biāo)志和選項(xiàng)(不再需要 fab mytask:weird = custom,arg = format)
  • 可以聲明前置任務(wù)與后置任務(wù)
  • ……(官方列了10幾條 [1],本文不一一羅列)

之前介紹過(guò)的 invoke,就是在開(kāi)發(fā) Fabric 2 時(shí)被分離出來(lái)的,具體的原因可參見(jiàn)這個(gè)回答 [2]。總而言之,在使用 Fabric 時(shí),應(yīng)該注意版本差異的問(wèn)題。

二、Fabric 的基本用法

1、安裝

首先是安裝:pip intall fabric ,安裝后,可在命令行窗口查看版本信息:

>>> fab -V
Fabric 2.5.0
Paramiko 2.7.1
Invoke 1.4.0

執(zhí)行“fab -V”,以上結(jié)果可看出我安裝的是 Fabric 2.5.0 版本,同時(shí)可看到它的兩個(gè)核心依賴庫(kù) Paramiko 及 Invoke 的版本信息。

2、一個(gè)簡(jiǎn)單的例子

Fabric 主要用于遠(yuǎn)程任務(wù),即要對(duì)遠(yuǎn)程服務(wù)器進(jìn)行操作,下面是一個(gè)簡(jiǎn)單的例子:

# 可使用任意的文件名
from fabric import Connection

host_ip = '47.xx.xx.xx'  # 服務(wù)器地址
user_name = 'root' # 服務(wù)器用戶名
password = '****'  # 服務(wù)器密碼
cmd = 'date'  # shell 命令,查詢服務(wù)器上的時(shí)間

con = Connection(host_ip, user_name, connect_kwargs={'password': password})
result = con.run(cmd, hide=True)

print(result)

以上代碼,通過(guò)賬號(hào)+密碼登錄到遠(yuǎn)程服務(wù)器,然后執(zhí)行date命令,查看服務(wù)器的時(shí)間,執(zhí)行結(jié)果:

Command exited with status 0.
=== stdout ===
Fri Feb 14 15:33:05 CST 2020

(no stderr)

現(xiàn)在打印的結(jié)果中,除了服務(wù)器時(shí)間,還有一些無(wú)關(guān)的信息。這是因?yàn)樗蛴〉摹皉esult”是一個(gè)"fabric.runners.Result"類,我們可以把其中的信息解析出來(lái):

print(result.stdout)  # Fri Feb 14 15:33:05 CST 2020
print(result.exited)  # 0
print(result.ok)      # True
print(result.failed)  # False
print(result.command) # date
print(result.connection.host) # 47.xx.xx.xx

上述代碼使用了 Connection 類及其 run() 方法,可在連接的服務(wù)器上運(yùn)行 shell 命令。如果需要用管理員權(quán)限,則需替換成 sudo() 方法。如果要在本地執(zhí)行 shell 命令,則需替換成 local() 方法。

除此之外,還有 get()、put() 等方法,詳見(jiàn)下文介紹。

3、命令行用法

上例代碼可寫(xiě)在任意的 .py 腳本中,然后運(yùn)行該腳本,或者稍微封裝下再導(dǎo)入到其它腳本中使用。

另外,F(xiàn)abric 還是個(gè)命令行工具,可以通過(guò)fab命令來(lái)執(zhí)行任務(wù)。我們稍微改造一下上例的代碼:

# 文件名:fabfile.py
from fabric import Connection
from fabric import task

host_ip = '47.xx.xx.xx'  # 服務(wù)器地址
user_name = 'root' # 服務(wù)器用戶名
password = '****'  # 服務(wù)器密碼
cmd = 'date'  # shell 命令,查詢服務(wù)器上的時(shí)間

@task
def test(c):
    """
    Get date from remote host.
    """
    con = Connection(host_ip, user_name, connect_kwargs={'password': password})
    result = con.run(cmd, hide=True)
    print(result.stdout)  # 只打印時(shí)間

解釋一下,主要的改動(dòng)點(diǎn)有:

  • fabfile.py 文件名:入口代碼的腳本名必須用這個(gè)名字
  • @task 裝飾器:需要從 fabric 中引入這個(gè)裝飾器,它是對(duì) invoke 的 @task 裝飾器的封裝,實(shí)際用法跟 invoke 一樣(注意:它也需要有上下文參數(shù)“c”,但實(shí)際上它并沒(méi)有在代碼塊中使用,而是用了 Connection 類的實(shí)例)

然后,在該腳本同級(jí)目錄的命令行窗口中,可以查看和執(zhí)行相應(yīng)的任務(wù):

>>> fab -l
Available tasks:
  test   Get date from remote host.

>>> fab test
Fri Feb 14 16:10:24 CST 2020

fab 是 Invoke 的擴(kuò)展實(shí)現(xiàn),繼承了很多原有功能,所以執(zhí)行“fab --help”,與之前介紹的“inv --help”相比,你會(huì)發(fā)現(xiàn)它們的很多參數(shù)與解釋都是一模一樣的。

fab 針對(duì)遠(yuǎn)程服務(wù)的場(chǎng)景,添加了幾個(gè)命令行選項(xiàng)(已標(biāo)藍(lán)),其中:

  • --prompt-for-login-password:令程序在命令行中輸入 SSH 登錄密碼(上例在代碼中指定了 connect_kwargs.password 參數(shù),若用此選項(xiàng),可要求在執(zhí)行時(shí)再手工輸入密碼)
  • --prompt-for-passphrase:令程序在命令行中輸入 SSH 私鑰加密文件的路徑
  • -H 或 --hosts:指定要連接的 host 名
  • -i 或 --identity:指定 SSH 連接所用的私鑰文件
  • -S 或 --ssh-config:指定運(yùn)行時(shí)要加載的 SSH 配置文件

關(guān)于 Fabric 的命令行接口,更多內(nèi)容可查看文檔 [3]。

4、交互式操作

遠(yuǎn)程服務(wù)器上若有交互式提示,要求輸入密碼或“yes”之類的信息,這就要求 Fabric 能夠監(jiān)聽(tīng)并作出回應(yīng)。

以下是一個(gè)簡(jiǎn)單示例。引入 invoke 的 Responder,初始化內(nèi)容是一個(gè)正則字符串和回應(yīng)信息,最后賦值給 watchers 參數(shù):

from invoke import Responder
from fabric import Connection
c = Connection('host')
sudopass = Responder(
     pattern=r'\[sudo\] password:',
     response='mypassword\n')
c.run('sudo whoami', pty=True, watchers=[sudopass])

5、傳輸文件

本地與服務(wù)器間的文件傳輸是常見(jiàn)用法。Fabric 在這方面做了很好的封裝,Connection 類中有以下兩個(gè)方法可用:

  • get(*args, **kwargs):拉取遠(yuǎn)端文件到本地文件系統(tǒng)或類文件(file-like)對(duì)象
  • put(*args, **kwargs):推送本地文件或類文件對(duì)象到遠(yuǎn)端文件系統(tǒng)

在已建立連接的情況下,示例:

# (略)
con.get('/opt/123.txt', '123.txt')
con.put('test.txt', '/opt/test.txt')

第一個(gè)參數(shù)指的是要傳輸?shù)脑次募?,第二個(gè)參數(shù)是要傳輸?shù)哪康牡?,可以指定成文件名或者文件夾(為空或 None 時(shí),使用默認(rèn)路徑):

# (略)
con.get('/opt/123.txt', '')  # 為空時(shí),使用默認(rèn)路徑
con.put('test.txt', '/opt/') # 指定路徑 /opt/

get() 方法的默認(rèn)存儲(chǔ)路徑是os.getcwd ,而 put() 方法的默認(rèn)存儲(chǔ)路徑是 home 目錄。

6、服務(wù)器批量操作

對(duì)于服務(wù)器集群的批量操作,最簡(jiǎn)單的實(shí)現(xiàn)方法是用 for 循環(huán),然后逐一建立 connection 和執(zhí)行操作,類似這樣:

for host in ('web1', 'web2', 'mac1'):
    result = Connection(host).run('uname -s')

但有時(shí)候,這樣的方案會(huì)存在問(wèn)題:

  • 如果存在多組不同的服務(wù)器集群,需要執(zhí)行不同操作,那么需要寫(xiě)很多 for 循環(huán)
  • 如果想把每組操作的結(jié)果聚合起來(lái)(例如字典形式,key-主機(jī),value-結(jié)果),還得在 for 循環(huán)之外添加額外的操作
  • for 循環(huán)是順序同步執(zhí)行的,效率太低,而且缺乏異常處理機(jī)制(若中間出現(xiàn)異常,會(huì)導(dǎo)致跳出后續(xù)操作)

對(duì)于這些問(wèn)題,F(xiàn)abric 提出了 Group 的概念,可將一組主機(jī)定義成一個(gè) Group,它的 API 方法跟 Connection 一樣,即一個(gè) Group 可簡(jiǎn)化地視為一個(gè) Connection。

然后,開(kāi)發(fā)者只需要簡(jiǎn)單地操作這個(gè) Group,最后得到一個(gè)結(jié)果集即可,減少了自己在異常處理及執(zhí)行順序上的工作。

Fabric 提供了一個(gè) fabric.group.Group 基類,并由其派生出兩個(gè)子類,區(qū)別是:

  • SerialGroup(*hosts, **kwargs):按串行方式執(zhí)行操作
  • ThreadingGroup(*hosts, **kwargs):按并發(fā)方式執(zhí)行操作

Group 的類型決定了主機(jī)集群的操作方式,我們只需要做出選擇即可。然后,它們的執(zhí)行結(jié)果是一個(gè)fabric.group.GroupResult類,它是 dict 的子類,存儲(chǔ)了每個(gè)主機(jī) connection 及其執(zhí)行結(jié)果的對(duì)應(yīng)關(guān)系。

>>> from fabric import SerialGroup
>>> results = SerialGroup('web1', 'web2', 'mac1').run('uname -s')
>>> print(results)
<GroupResult: {
    <Connection 'web1'>: <CommandResult 'uname -s'>,
    <Connection 'web2'>: <CommandResult 'uname -s'>,
    <Connection 'mac1'>: <CommandResult 'uname -s'>,
}>

另外,GroupResult 還提供了 failed 與 succeeded 兩個(gè)屬性,可以取出失敗/成功的子集。由此,也可以方便地批量進(jìn)行二次操作。 原文

三、Fabric 的進(jìn)階用法

1、身份認(rèn)證

Fabric 使用 SSH 協(xié)議來(lái)建立遠(yuǎn)程會(huì)話,它是一種相對(duì)安全的基于應(yīng)用層的加密傳輸協(xié)議。

基本來(lái)說(shuō),它有兩種級(jí)別的安全認(rèn)證方式:

  • 基于口令的身份認(rèn)證:使用賬號(hào)與密碼來(lái)登錄遠(yuǎn)程主機(jī),安全性較低,容易受到“中間人”攻擊
  • 基于密鑰的身份認(rèn)證:使用密鑰對(duì)方式(公鑰放服務(wù)端,私鑰放客戶端),不會(huì)受到“中間人”攻擊,但登錄耗時(shí)較長(zhǎng)

前文在舉例時(shí),我們用了第一種方式,即通過(guò)指定 connect_kwargs.password 參數(shù),使用口令來(lái)登錄。

Fabric 當(dāng)然也支持采用第二種方式,有三種方法來(lái)指定私鑰文件的路徑,優(yōu)先級(jí)如下:

  • 優(yōu)先查找 connect_kwargs.key_filename 參數(shù),找到則用作私鑰
  • 其次查找命令行用法的 --identify 選項(xiàng)
  • 最后默認(rèn)使用操作系統(tǒng)的 ssh_config 文件中的IdentityFile 的值

如果私鑰文件本身還被加密過(guò),則需要使用 connect_kwargs.passphrase 參數(shù)。

2、配置文件

Fabric 支持把一些參數(shù)項(xiàng)與業(yè)務(wù)代碼分離,即通過(guò)配置文件來(lái)管理它們,例如前面提到的密碼和私鑰文件,可寫(xiě)在配置文件中,避免與代碼耦合。

Fabric 基本沿用了 Invoke 的配置文件體系(官方文檔中列出了 9 層),同時(shí)增加了一些跟 SSH 相關(guān)的配置項(xiàng)。支持的文件格式有 .yaml、.yml、.json 與 .py(按此次序排優(yōu)先級(jí)),推薦使用 yaml 格式(后綴可簡(jiǎn)寫(xiě)成 yml)。

其中,比較常用的配置文件有:

  • 系統(tǒng)級(jí)的配置文件:/etc/fabric.yml
  • 用戶級(jí)的配置文件:~/.fabric.yml(Windows 在 C:\Users\xxx 下)
  • 項(xiàng)目級(jí)的配置文件:/myproject/fabric.yml

以上文件的優(yōu)先級(jí)遞減,由于我本機(jī)是 Windows,為了方便,我在用戶目錄建一個(gè)".fabric.yml"文件,內(nèi)容如下:

# filename:.fabric.yml

user: root
connect_kwargs:
  password: xxxx
# 若用密鑰,則如下
#  key_filename:
#    - your_key_file

我們把用戶名和密碼抽離出來(lái)了,所以 fabfile 中就可以刪掉這些內(nèi)容:

# 文件名:fabfile.py
from fabric import Connection
from fabric import task

host_ip = '47.xx.xx.xx'  # 服務(wù)器地址
cmd = 'date'  # shell 命令,查詢服務(wù)器上的時(shí)間

@task
def test(c):
    """
    Get date from remote host.
    """
    con = Connection(host_ip)
    result = con.run(cmd, hide=True)
    print(result.stdout) 

然后,在命令行中執(zhí)行:

>>> fab test
Tue Feb 18 10:33:38 CST 2020

配置文件中還可以設(shè)置很多參數(shù),詳細(xì)可查看文檔 [4]。

3、網(wǎng)絡(luò)網(wǎng)關(guān)

如果遠(yuǎn)程服務(wù)是網(wǎng)絡(luò)隔離的,無(wú)法直接被訪問(wèn)到(處在不同局域網(wǎng)),這時(shí)候需要有網(wǎng)關(guān)/代理/隧道,這個(gè)中間層的機(jī)器通常被稱為跳板機(jī)或堡壘機(jī)。

Fabric 中有兩種網(wǎng)關(guān)解決方案,對(duì)應(yīng)到 OpenSSH 客戶端的兩種選項(xiàng):

  • ProxyJump:簡(jiǎn)單,開(kāi)銷少,可嵌套
  • ProxyCommand:開(kāi)銷大,不可嵌套,更靈活

在創(chuàng)建 Fabric 的 Connection 對(duì)象時(shí),可通過(guò)指定 gateway 參數(shù)來(lái)應(yīng)用這兩種方案:

ProxyJump 方式就是在一個(gè) Connection 中嵌套一個(gè) Connection 作為前者的網(wǎng)關(guān),后者使用 SSH 協(xié)議的direct-tcpip 為前者打開(kāi)與實(shí)際遠(yuǎn)程主機(jī)的連接,而且后者還可以繼續(xù)嵌套使用自己的網(wǎng)關(guān)。

from fabric import Connection

c = Connection('internalhost', gateway=Connection('gatewayhost'))

ProxyCommand 方式是客戶端在本地用 ssh 命令(類似“ssh -W %h:%p gatewayhost”),創(chuàng)建一個(gè)子進(jìn)程,該子進(jìn)程與服務(wù)端進(jìn)行通信,同時(shí)它能讀取標(biāo)準(zhǔn)輸入和輸出。

這部分的實(shí)現(xiàn)細(xì)節(jié)分別在paramiko.channel.Channelparamiko.proxy.ProxyCommand,除了在參數(shù)中指定,也可以在 Fabric 支持的配置文件中定義。更多細(xì)節(jié),請(qǐng)查閱文檔 [5]。

四、小結(jié)

Fabric 的非兼容版本造成了一定程度的社區(qū)分裂,這無(wú)疑跟 Python 3 的推行脫不開(kāi)關(guān)系,但是我們有理由相信,新版本優(yōu)勝于老版本。

網(wǎng)上關(guān)于 Fabric 的文章,很多已過(guò)時(shí)了。本文針對(duì)最新的官方文檔,梳理出了較為全面的知識(shí)點(diǎn),可以帶大家很好地入門(mén) Fabric。

讀完本文,相信讀者們只需要幾分鐘就能輕松上手使用。如若有所疑問(wèn),歡迎通過(guò)以下方式聯(lián)系我。

--------------

公眾號(hào):Python貓

頭條號(hào):Python貓

知乎:豌豆花下貓

掘金:豌豆花下貓

--------------

相關(guān)鏈接:

Invoke教程:https://mp.weixin.qq.com/s/up8lxuRhJQAXRzxkirF3nA

1、http://www.fabfile.org/upgrading.html#upgrading

2、http://www.pyinvoke.org/faq.html#invoke-split-from-fabric

3、http://docs.fabfile.org/en/2.5/cli.html

4、http://docs.fabfile.org/en/2.5/concepts/configuration.html

5、http://docs.fabfile.org/en/2.5/concepts/networking.html

公眾號(hào)【Python貓】, 本號(hào)連載優(yōu)質(zhì)的系列文章,有喵星哲學(xué)貓系列、Python進(jìn)階系列、好書(shū)推薦系列、技術(shù)寫(xiě)作、優(yōu)質(zhì)英文推薦與翻譯等等,歡迎關(guān)注哦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容