使用shell命令進行復雜的運維時,程式碼往往變得複雜難懂,而使用python指令碼語言來編寫運維程式,就相當於開發普通的應用一樣,所以維護和擴充套件都比較簡單,更重要的是python運維工具fabric能自動登入其他伺服器進行各種操作,這種實現使用shell是很難做到的,但是使用fabric實現就很簡單,所以對於程式設計師的日常運維部署,建議使用python編寫指令碼。Fabric是基於Python實現的SSH命令列工具,簡化了SSH的應用程式部署及系統管理任務,它提供了系統基礎的操作元件,可以通過 SSH 的方式與遠端伺服器進行自動化互動, 實現本地或遠端shell命令,包括:命令執行、檔案上傳、下載及完整執行日誌輸出等功能。Fabric在Paramiko的基礎上做了更高一層的封裝,操作起來會更加簡單。Fabric官網地址為:http://www.fabfile.org/
一. Fabric安裝
Linux下預設有python環境,安裝fabric有兩種方式: 一是通過pip方式安裝; 而是通過fabric原始碼方式安裝. 一般選擇pip方式安裝, 安裝過程如下: 先安裝一些依賴 [root@kevin ~]# yum install make gcc gcc-c++ python-devel python-setuptools -y 安裝pip 首先下載py檔案:https://bootstrap.pypa.io/get-pip.py 或者百度雲盤下載地址:https://pan.baidu.com/s/1o7KylCm 提取密碼:eucx [root@kevin ~]# cat /etc/redhat-release CentOS Linux release 7.5.1804 (Core) [root@kevin ~]# python -V Python 2.7.5 [root@kevin ~]# wget https://bootstrap.pypa.io/get-pip.py [root@kevin ~]# chmod 755 get-pip.py [root@kevin ~]# python get-pip.py Collecting pip Downloading https://files.pythonhosted.org/packages/c2/d7/90f34cb0d83a6c5631cf71dfe64cc1054598c843a92b400e55675cc2ac37/pip-18.1-py2.py3-none-any.whl (1.3MB) 100% |████████████████████████████████| 1.3MB 56kB/s Collecting wheel Downloading https://files.pythonhosted.org/packages/ff/47/1dfa4795e24fd6f93d5d58602dd716c3f101cfd5a77cd9acbe519b44a0a9/wheel-0.32.3-py2.py3-none-any.whl Installing collected packages: pip, wheel Successfully installed pip-18.1 wheel-0.32.3 接著使用pip安裝fabric [root@kevin ~]# pip install fabric 稍等一會就安裝完畢了,這時輸入fab就會彈出對應的選項 [root@kevin ~]# fab --version Fabric 2.4.0 Paramiko 2.4.2 Invoke 1.2.0 [root@kevin ~]# fab --help Usage: fab [--core-opts] task1 [--task1-opts] ... taskN [--taskN-opts] Core options: --complete Print tab-completion candidates for given parse remainder. --hide=STRING Set default value of run()'s 'hide' kwarg. --no-dedupe Disable task deduplication. --print-completion-script=STRING Print the tab-completion script for your preferred shell (bash|zsh|fish). --prompt-for-login-password Request an upfront SSH-auth password prompt. --prompt-for-passphrase Request an upfront SSH key passphrase prompt. --prompt-for-sudo-password Prompt user at start of session for the sudo.password config value. --write-pyc Enable creation of .pyc files. -c STRING, --collection=STRING Specify collection name to load. -d, --debug Enable debug output. -D INT, --list-depth=INT When listing tasks, only show the first INT levels. -e, --echo Echo executed commands before running. -f STRING, --config=STRING Runtime configuration file to use. -F STRING, --list-format=STRING Change the display format used when listing tasks. Should be one of: flat (default), nested, json. -h [STRING], --help[=STRING] Show core or per-task help and exit. -H STRING, --hosts=STRING Comma-separated host name(s) to execute tasks against. -i, --identity Path to runtime SSH identity (key) file. May be given multiple times. -l [STRING], --list[=STRING] List available tasks, optionally limited to a namespace. -p, --pty Use a pty when executing shell commands. -r STRING, --search-root=STRING Change root directory used for finding task modules. -S STRING, --ssh-config=STRING Path to runtime SSH config file. -V, --version Show version and exit. -w, --warn-only Warn, instead of failing, when shell commands fail. ======================================================= 溫馨提示: 如果安裝的是pip3, 則使用"pip3 install fabric3" 安裝fabric =======================================================
二. Fabric 使用
Fabric命令說明
1) fab命令格式
fab是fabric的命令列入口 命令的格式為: # fab [options] <command>[:arg1,arg2=val2,host=foo,hosts='h1;h2',...] ...
2) fab命令常用引數
# fab --help 檢視幫助 常用引數 -l 顯示定義好的任務函式名 -f 指定fab入口檔案,預設入口檔名為fabfile.py.. 即指定fabfile檔案 -g 指定閘道器(中轉)裝置,即HOST逗號分隔要操作的主機, 比如堡壘機環境,填寫堡壘機IP即可. -H 指定目標主機,多臺主機用‘,’號分隔 -p 遠端賬號的密碼,fab執行時預設使用root賬戶 -P 以非同步並行方式執行多主機任務,預設為序列執行 -R 指定role(角色),以角色名區分不同業務組裝置 -t 設定裝置連線超時時間(秒) -T 設定遠端主機命令執行超時時間(秒) -w 當命令執行失敗,發出警告,而非預設中止任務。 其他引數: --set=KEY=VALUE,... 逗號分隔,設定環境變數 --shortlist 簡短列印可用命令 -c PATH 指定本地配置檔案 -D 不載入使用者known_hosts檔案 -i PATH 指定私鑰檔案 -k 不載入來自~/.ssh下的私鑰檔案 --port=PORT 指定SSH連線埠 -R ROLES 根據角色操作,逗號分隔 -s SHELL 指定新shell,預設是'/bin/bash -l -c' --show=LEVELS 以逗號分隔的輸出 --ssh-config-path=PATH SSH配置檔案路徑 -T N 設定遠端命令超時時間,單位秒 -u USER 連線遠端主機使用者名稱 -x HOSTS 以逗號分隔排除主機 -z INT 併發程式數 例1: 通過遠端主機查詢172.16.50.45 (該主機的root密碼為123456)的主機名 [root@kevin ~]# fab -f fabtest.py -p 123456 -H 172.16.50.45 -- 'hostname' 例2: 本地執行命令 [root@kevin ~]# vim fabtest.py from fabric.api import local def command(): local('ls') [root@kevin ~]# fab -f fabtest.py command [localhost] local: ls fabfile.py fabfile.pyc tab.py tab.pyc Done. 例3: 遠端執行命令 [root@kevin ~]# vim fabtest.py from fabric.api import run def command(): run('ls') [root@kevin ~]# fab -f fabtest.py -H 192.168.1.120 -u user command [192.168.1.120] Executing task 'command'[192.168.1.120] run: ls [192.168.1.120] Login password for 'user': [192.168.1.120] out: access.log a.py [192.168.1.120] out: Done. Disconnecting from 192.168.1.120... done. 如果在多臺主機執行,只需要-H後面的IP以逗號分隔即可。 例4: 給指令碼函式傳入位置引數 [root@kevin ~]# vim fabfile.py from fabric.api import run def hello(name="world"): print("Hello %s!" % name) [root@kevin ~]# fab -H localhost hello [localhost] Executing task 'hello'Hello world! Done. [root@kevin ~]# fab -H localhost hello:name=Python [localhost] Executing task 'hello'Hello Python! Done. 例5: 主機列表組 [root@kevin ~]# vim fabfile.py from fabric.api import run, env env.hosts = ['root@192.168.1.120:22', 'root@192.168.1.130:22'] env.password = '123.com'env.exclude_hosts = ['root@192.168.1.120:22'] # 排除主機 def command(): run('ls') [root@kevin ~]# fab command env作用是定義fabfile全域性設定,類似於變數。還有一些常用的屬性: 例6: 定義角色分組 [root@kevin ~]# vim install.py from fabric.api import run, env env.roledefs = { 'web': ['192.168.1.10', '192.168.1.20'], 'db': ['192.168.1.30', '192.168.1.40'] } env.password = '123'@roles('web') def task1(): run('yum install httpd -y') @roles('db') def task2(): run('yum install mysql-server -y') def deploy(): execute(task1) execute(task2) [root@kevin ~]# fab -f install.py deploy 例7: 上傳目錄到遠端主機 [root@kevin ~]# vim haha.py from fabric.api import * env.hosts = ['192.168.1.120'] env.user = 'user'env.password = '123.com' def task(): put('/root/abc', '/home/user') run('ls -l /home/user') [root@kevin ~]# fab -f haha.py task 例8: 從遠端主機下載目錄 [root@kevin ~]# vim heihei.py from fabric.api import * env.hosts = ['192.168.1.120'] env.user = 'user'env.password = '123.com' def task(): get('/home/user/b', '/opt') local('ls -l /opt') [root@kevin ~]# fab -f heihei.py task 例9: 列印顏色,有助於關鍵地方醒目 [root@kevin ~]# vim bobo.py from fabric.colors import * def show(): print green('Successful.') print red('Failure!') print yellow('Warning.') [root@kevin ~]# fab -f bobo.py show
3) fabfile檔案的編寫 (預設的檔名稱為fabfile)
fab命令是結合fabfile.py檔案(其他檔案通過-f filename 引數來引用)來搭配使用的。fab的部分命令列引數還能通過相應的方法來代替。 先來看一個小例子 [root@kevin ~]# cat fabfile.py #!/usr/bin/env python from fabric.api import run #定義一個任務函式,通過run方法實現遠端執行"uname -s"命令 def host_type(): run('uname -s') [root@kevin ~]# fab -H localhost host_type [localhost] Executing task 'host_type' [localhost] run: uname -s [localhost] Login password for 'devops': [localhost] out: Linux [localhost] out: Done. Disconnecting from localhost... done. 其中,必須要明白的是, fab命令引用的預設檔名fabfile.py! 如果使用的是預設檔名稱, 則fab執行命令中就不需要跟檔名. 如果使用非預設檔名稱,比如這裡不是fabfile.py, 而是host_type.py 檔案, 則需要通過"-f"來指定: [root@kevin ~]# fab -H localhost -f host_type.py host_type 如果目標主機未配置金鑰認證信任,將會提示輸入目標主機對應賬號登入密碼。 再來看一個小例子 [root@kevin ~]# vim fabric.py #!/usr/bin/python # -*- coding:utf-8 -*- from fabric.api import * # 設定伺服器登入引數 env.roledefs = { # 操作一致的放一組,一組執行同一個操作 'servers1':['root@linux2:22',], # 第二組 'servers2':['root@linux3:22',] } # 本機操作 def localtask(): local('/usr/local/nginx/nginx') # servers1伺服器組操作 @roles('servers1') def task1(): run('/usr/local/tomcat/bin/startup.sh') # servers2 伺服器組操作 @roles('servers2') def task2(): run('/usr/local/tomcat/bin/startup.sh') # 執行任務 def doworks(): execute(localtask) execute(task1) execute(task2) 以上Fabric配置,實現的目的是: 簡單的在本地啟動nginx伺服器, 在linux1和linux2上啟動了tomcat伺服器, 為了接受nginx伺服器的代理,這裡專門使用分組的方式為了適應機器比較多的叢集的需要; 另外這裡沒有設定伺服器的密碼,一是為了伺服器的安全;而是叢集間建議設定ssh免密登入,指令碼就不用設定密碼了; 方法doworks執行的就是最終彙總的任務; 開始執行 [root@kevin ~]# fab -f fabric.py doworks
4) fabfile全域性屬性 (env物件)
fabfile之env物件的作用是定義fabfile的全域性設定,支援多個屬性,包含目標主機、使用者名稱、密碼、等角色. env各屬性說明如下: evn.host: 定義目標主機,可以用IP或主機名錶示,以Python的列表形式定義,如evn.hosts['192.168.56.133','192.168.56.134']。 env.exclude_hosts: 排除指定主機,如env.exclude_hosts=['192.168.56.133']。 env.user: 定義使用者名稱,如env.user="root"。 env.port: 定義目標主機埠,預設為22,如env.port="22"。 env.password: 定義密碼,如env.password='1234567'。 env.passwords: 與password功能一樣,區別在於不同主機不同密碼的應用場景,需要注意的是,配置passwords是需配置使用者、主機、埠等資訊; env.gateway: 定義閘道器(中轉、堡壘機)IP,如env.gateway = '192.168.56.1'。 env.deploy_release_dir: 自定義全域性變數,格式:env.+"變數名稱",如env.deploy_release_dir、env.age、env.sex等。 env.roledefs: 定義角色分組,比如web組與db組主機區分開來; 比如 [root@kevin ~]# vim fabfile.py .......... env.passwords = { 'root@192.168.56.131:22':'1234567', 'root@192.168.56.132:22':'1234567', 'root@192.168.56.133:22':'1234567', 'root@192.168.56.134:22':'1234567' } [root@kevin ~]# vim fabfile.py .......... env.roledefs = { 'webservers':['192.168.56.131','192.168.56.132','192.168.56.133'], 'dbserver':['192.168.56.134','192.168.56.135'] } env.roledefs的使用方法例項: [root@kevin ~]# vim fabfile.py .......... env.roledefs = {'webserver':['192.168.1.21','192.168.1.22'],'dbserver':['192.168.1.25','192.168.1.26']} #引用分組時使用python裝飾器方式來進行,如: @roles('webserver') def webtask(): run('/usr/local/nginx/sbin/nginx') @roles('webserver','dbserver') def publictask(): run('uptime') 引用時使用Python修飾符的形式進行,角色修飾符下面的任務函式為其作用域,下面來看一個示例: [root@kevin ~]# vim fabfile.py .......... @roles('webservers') def webtask(): run('/etc/init.d/nginx start') @roles('dbservers') def dbtask(): run('/etc/init.d/mysql start') @roles('webservers','dbservers') def pubclitasj(): run('uptime') def deploy(): execute(webtask) execute(dbtask) execute(pubclitask) 在命令執行fab deploy就可以實現不同角色執行不同的任務函式了。
5) Fabric常用API
Fabric提供了一組簡單但功能強大的fabric.api命令集,簡單地呼叫這些API就能完成大部分應用場景需求。Fabric常用方法及說明如下: local 執行本地命令,如:local('uname -s'); lcd 切換本地目錄,如:lcd('/home'); cd 切換遠端目錄,如:cd('/data/logs'); run 執行遠端命令,如:run('free -m'); sudo sudo方式執行遠端命令,如:sudo('/etc/init.d/httpd start'); put 傳本地檔案到遠端主機,如:put('/home/user.info','/data/user.info'); prompt 獲得使用者輸入資訊,如:prompt('please input user password:'); confirm 獲得提示資訊確認,如:confirm("Tests failed. Continue[Y/N]?"); reboot 重啟遠端主機,如:reboot(); @task 函式修飾符,標識的函式為fab可呼叫的,非標記對fab不可見,純業務邏輯; @runs_once 函式修復符,標識的函式只會執行一次,不受多臺主機影響。
6) Fabric應用示例說明
示例一: 檢視本地和遠端主機資訊
檢視本地資訊 本示例呼叫local()方法執行本地(主控端)命令,新增"@runs_once"修飾符保證該任務函式只執行一次。呼叫run()方法執行遠端命令。 [root@kevin ~]# vim fabric1.1.py #!/usr/bin/env python from fabric.api import * env.user = 'devops' env.hosts = ['localhost'] env.password = '1234567' @runs_once #檢視本地系統資訊,當有多臺主機時只執行一次 def local_task(): #本地任務函式 local("uname -a") 通過fab命令呼叫local_task任務函式執行結果如下: [root@kevin ~]# fab -f fabric1.1.py local_task [localhost] Executing task 'local_task' [localhost] local: uname -a Linux devops-virtual-machine 4.15.0-20-generic #21-Ubuntu SMP Tue Apr 24 06:16:15 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux Done. 檢視遠端主機資訊 [root@kevin ~]# vim fabric1.2.py #!/usr/bin/env python from fabric.api import * env.user = 'root' env.hosts = ['192.168.56.11'] env.password = '1234567' def remote_task(): with cd('/root'): #"with"的作用是讓後面的表示式的語句繼承當前狀態,實現"cd /root/ && ls -l'的效果 run('ls -l') 呼叫remote_task任務函式執行結果如下: [root@kevin ~]# fab -f fabric1.2.py local_task [192.168.56.11] Executing task 'remote_task' [192.168.56.11] run: ls -l [192.168.56.11] out: total 4 [192.168.56.11] out: -rw-------. 1 root root 1273 May 29 11:47 anaconda-ks.cfg [192.168.56.11] out: Done. Disconnecting from 192.168.56.11... done. 如果將上面兩個檔案的需求, 放在一起 [root@kevin ~]# vim fabric1.py #!/usr/bin/env python # -*- encoding: utf-8 -*- from fabric.api import * env.user = 'root' env.hosts = ['192.168.1.22'] env.password = '123456' @runs_once #檢視本地系統資訊,當有多臺主機時只執行一次 def local_task(): #本地任務函式 local('uname -a') def remote_task(): with cd('/var/logs'): #with的作用是讓後面的表示式語句繼承當前狀態,實現:cd /var/logs && ls -l的效果 run('ls -l') [root@kevin ~]# fab -f fabric1.py local_task [root@kevin ~]# fab -f fabric1.py remote_task
示例二:動態獲取遠端目錄列表
本示例使用"@task'修復符標誌入口函式go()對外部可見,配合"@runs_once"修飾符接受使用者輸入,最後呼叫worktask()任務函式實現遠端命令執行。 [root@kevin ~]# vim fabric2.py #!/usr/bin/env python from fabric.api import * env.user = 'root' env.hosts = ['192.168.56.11','192.168.56.12'] env.password = '1234567' @runs_once #主機遍歷過程中,只有第一臺觸發此函式 def input_raw(): return prompt("Please input directory name:",default="/home") def worktask(dirname): run("ls -l "+dirname) @task #限定只有go函式對fab命令可見 def go(): getdirname = input_raw() worktask(getdirname) 解釋說明: 該示例實現了一個動態輸入遠端目錄名稱,再獲取目錄列表的功能,由於我們只要求輸入一次,在顯示所有主機上該目錄的列表資訊,呼叫一個子函式input_raw()同時配置@runs_once修復符來達到此目的。 執行結果如下: [root@kevin ~]# fab -f fabric2.py go [192.168.56.11] Executing task 'go' Please input directory name: [/home] /root [192.168.56.11] run: ls -l /root [192.168.56.11] out: total 4 [192.168.56.11] out: -rw-------. 1 root root 1273 May 29 11:47 anaconda-ks.cfg [192.168.56.11] out: [192.168.56.12] Executing task 'go' [192.168.56.12] run: ls -l /root [192.168.56.12] out: total 4 [192.168.56.12] out: -rw-------. 1 root root 1273 May 29 11:59 anaconda-ks.cfg [192.168.56.12] out: Done. Disconnecting from 192.168.56.11... done. Disconnecting from 192.168.56.12... done.
示例三: 閘道器模式檔案上傳與執行
本示例通過Fabric的env物件定義閘道器模式,即俗稱的中轉、堡壘機環境。定義格式為"env.gateway='192.168.56.11'",其中IP“192.168.56.11”為堡壘機IP, 再結合任務韓素實現目標主機檔案上傳與執行的操作。 [root@kevin ~]# vim fabric3.py #!/usr/bin/env python from fabric.api import * from fabric.context_managers import * from fabric.contrib.console import confirm env.user = 'root' env.gateway = '192.168.56.11' #定義堡壘機IP,作為檔案上傳、執行的中轉裝置 env.hosts = ['192.168.56.12','192.168.56.13'] env.passwords = { 'root@192.168.56.11:22':'1234567', #堡壘機賬號資訊 'root@192.168.56.12:22':'1234567', 'root@192.168.56.13:22':'1234567' } l_pack_path = "/home/install/nginx-1.6.3.tar.gz" #本地安裝包路徑 r_pack_path = "/tmp/install" #遠端安裝包路徑 @task def put_task(): run("mkdir -p /tmp/install") with settings(warn_only=True): result = put(l_pack_path,r_pack_path) #上傳安裝包 if result.failed and not confirm("put file failed, Continue[Y/N]?"): abort("Aborint file put task!") @task def run_task(): #執行遠端命令,安裝nginx with cd(r_pack_path): run("tar -xvf nginx-1.6.3.tar.gz") with cd("nginx-1.6.3/"): #使用with繼續繼承/tmp/install目錄位置狀態 run("./nginx_install.sh") @task def go(): #上傳、安裝 put_task() run_task() 如下命令執行結果, 預設為序列執行 [root@kevin ~]# fab -f fabric3.py go [192.168.56.12] Executing task 'go' [192.168.56.12] run: mkdir -p /tmp/install [192.168.56.12] put: /home/install/nginx-1.6.3.tar.gz -> /tmp/install/nginx-1.6.3.tar.gz [192.168.56.12] run: tar -xvf nginx-1.6.3.tar.gz ..... ..... ..... [192.168.56.12] out: cp conf/nginx.conf '/usr/local/nginx/conf/nginx.conf.default' [192.168.56.12] out: test -d '/usr/local/nginx/logs' || mkdir -p '/usr/local/nginx/logs' [192.168.56.12] out: test -d '/usr/local/nginx/logs' || mkdir -p '/usr/local/nginx/logs' [192.168.56.12] out: test -d '/usr/local/nginx/html' || cp -R html '/usr/local/nginx' [192.168.56.12] out: test -d '/usr/local/nginx/logs' || mkdir -p '/usr/local/nginx/logs' [192.168.56.12] out: make[1]: Leaving directory `/tmp/install/nginx-1.6.3' [192.168.56.12] out: nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok [192.168.56.12] out: nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful [192.168.56.12] out: [192.168.56.13] Executing task 'go' [192.168.56.13] run: mkdir -p /tmp/install [192.168.56.13] put: /home/install/nginx-1.6.3.tar.gz -> /tmp/install/nginx-1.6.3.tar.gz [192.168.56.13] run: tar -xvf nginx-1.6.3.tar.gz .... .... .... [192.168.56.13] out: cp conf/nginx.conf '/usr/local/nginx/conf/nginx.conf.default' [192.168.56.13] out: test -d '/usr/local/nginx/logs' || mkdir -p '/usr/local/nginx/logs' [192.168.56.13] out: test -d '/usr/local/nginx/logs' || mkdir -p '/usr/local/nginx/logs' [192.168.56.13] out: test -d '/usr/local/nginx/html' || cp -R html '/usr/local/nginx' [192.168.56.13] out: test -d '/usr/local/nginx/logs' || mkdir -p '/usr/local/nginx/logs' [192.168.56.13] out: make[1]: Leaving directory `/tmp/install/nginx-1.6.3' [192.168.56.13] out: nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok [192.168.56.13] out: nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful [192.168.56.13] out: Done. Disconnecting from 192.168.56.11... done. Disconnecting from 192.168.56.12... done. Disconnecting from 192.168.56.13... done. 再如下執行結果, 加P引數為非同步並行執行結果 [root@kevin ~]# fab -Pf fabric3.py go [192.168.56.12] Executing task 'go' [192.168.56.13] Executing task 'go' [192.168.56.12] run: mkdir -p /tmp/install [192.168.56.13] run: mkdir -p /tmp/install [192.168.56.12] put: /home/install/nginx-1.6.3.tar.gz -> /tmp/install/nginx-1.6.3.tar.gz [192.168.56.13] put: /home/install/nginx-1.6.3.tar.gz -> /tmp/install/nginx-1.6.3.tar.gz [192.168.56.12] run: tar -xvf nginx-1.6.3.tar.gz .... .... .... [192.168.56.12] out: nginx-1.6.3/html/index.html [192.168.56.12] out: nginx-1.6.3/README [192.168.56.12] out: nginx-1.6.3/nginx_install.sh [192.168.56.12] out: nginx-1.6.3/configure [192.168.56.12] out: [192.168.56.12] run: ./nginx_install.sh [192.168.56.13] run: tar -xvf nginx-1.6.3.tar.gz [192.168.56.13] out: nginx-1.6.3/ [192.168.56.13] out: nginx-1.6.3/src/ .... .... .... [192.168.56.12] out: make[1]: Leaving directory `/tmp/install/nginx-1.6.3' [192.168.56.12] out: nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok [192.168.56.12] out: nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful [192.168.56.12] out: .... .... ... [192.168.56.13] out: make[1]: Leaving directory `/tmp/install/nginx-1.6.3' [192.168.56.13] out: nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok [192.168.56.13] out: nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful [192.168.56.13] out:
示例四: 檔案打包, 上傳與校驗
我們時常做一些檔案包分發的工作,實施步驟一般是先壓縮打包,在批量上傳至目標伺服器,最後做一致性校驗。 本示例通過put()方法實現檔案的上傳,通過對比本地與遠端主機檔案的md5,最終實現檔案一致性校驗。 [root@kevin ~]# vim fabric4.py #!/usr/bin/env python from fabric.api import * from fabric.context_managers import * from fabric.contrib.console import confirm env.user = 'root' env.hosts = ['192.168.56.12','192.168.56.13'] env.passwords = { 'root@192.168.56.12:22':'1234567', 'root@192.168.56.13:22':'1234567', } @runs_once def tar_task(): #本地打包任務函式,只執行一次 with lcd('/home/devops/devops'): local("tar -zcf devops.tar.gz *") @task def put_task(): #上傳檔案任務函式 run("mkdir -p /root/devops") with cd("/root/devops"): with settings(warn_only=True): #put(上傳)出現異常時繼續執行,非終止 result = put("/home/devops/devops/devops.tar.gz","/root/devops/devops.tar.gz") if result.failed and not confirm("put file failed.Continue[Y/N]?"): abort("Aborting file put task!") #出現異常時,確認使用者是否繼續,(Y繼續) @task def check_task(): #校驗檔案任務函式 with settings(warn_only=True): #本地local命令需要配置capture=True才能捕獲返回值 lmd5 = local("md5sum /home/devops/devops/devops.tar.gz",capture=True).split(' ')[0] rmd5 = run("md5sum /root/devops/devops.tar.gz").split(' ')[0] if lmd5 == rmd5: #對比本地及遠端檔案md5資訊 prompt("OK") else: prompt("ERROR") @task def go(): tar_task() put_task() check_task() 執行命令, 執行結果如下:(提示此程式不支援-P引數並行執行、如需並行執行,程式需要做調整). 如果只打包, 則"fab -f fabric4.py tar_task", 如果只上傳, 則"fab -f fabric4.py put_task" [root@kevin ~]# fab -f fabric4.py go [192.168.56.12] Executing task 'go' [localhost] local: tar -zcf devops.tar.gz * [192.168.56.12] run: mkdir -p /root/devops [192.168.56.12] put: /home/devops/devops/devops.tar.gz -> /root/devops/devops.tar.gz [localhost] local: md5sum /home/devops/devops/devops.tar.gz [192.168.56.12] run: md5sum /root/devops/devops.tar.gz [192.168.56.12] out: a1cf2be82647cbed0d41514bd80373de /root/devops/devops.tar.gz [192.168.56.12] out: OK [192.168.56.13] Executing task 'go' [192.168.56.13] run: mkdir -p /root/devops [192.168.56.13] put: /home/devops/devops/devops.tar.gz -> /root/devops/devops.tar.gz [localhost] local: md5sum /home/devops/devops/devops.tar.gz [192.168.56.13] run: md5sum /root/devops/devops.tar.gz [192.168.56.13] out: a1cf2be82647cbed0d41514bd80373de /root/devops/devops.tar.gz [192.168.56.13] out: OK Done. Disconnecting from 192.168.56.12... done. Disconnecting from 192.168.56.13... done.
示例五: 部署LNMP業務服務環境
本示例通過env.roledefs定義不同主機角色,在使用"@roles('webservers')"修復符繫結到對應的任務函式,實現不同角色主機的部署差異。 [root@kevin ~]# vim fabric5.py #!/usr/bin/env python from fabric.colors import * from fabric.api import * env.user = 'root' env.roledefs = { 'webservers':['192.168.56.11','192.168.56.12'], 'dbservers':['192.168.56.13'] } env.passwords = { 'root@192.168.56.11:22':'1234567', 'root@192.168.56.12:22':'1234567', 'root@192.168.56.13:22':'1234567', } @roles('webservers') #使用webtask任務函式引用'webservers'角色修復符 def webtask(): print(yellow('Install nginx php php-fpm...')) with settings(warn_only=True): run("yum -y install nginx") run("yum -y install php-fpm php-mysql php-mbstring php-xml php-mcrypt php-gd") run("chkconfig --levels 235 php-fpm on") run("chkconfig --levels 235 nginx on") @roles('dbservers') #dbtask任務函式引用'dbservers'角色修復符 def dbtask(): print(yellow("Install Mysql...")) with settings(warn_only=True): run("yum -y install mysql mysql-server") run("chkconfig --levels 235 mysqld on") @roles('webservers','dbservers') #publictask任務函式同時引用兩個角色修復符 def publictask(): #部署公共類環境,如epel、ntp等 print(yellow("Install epel ntp....")) with settings(warn_only=True): run("wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo") run("yum -y install ntp") def deploy(): execute(publictask) execute(webtask) execute(dbtask) 執行命令,結果如下: [root@kevin ~]# fab -Pf fabric5.py deploy [192.168.56.11] Executing task 'publictask' [192.168.56.12] Executing task 'publictask' [192.168.56.13] Executing task 'publictask' Install epel ntp.... [192.168.56.13] run: wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo Install epel ntp.... [192.168.56.12] run: wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo Install epel ntp.... [192.168.56.11] run: wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo [192.168.56.12] out: --2018-06-23 20:32:30-- http://mirrors.aliyun.com/repo/epel-7.repo [192.168.56.11] out: --2018-06-23 20:32:30-- http://mirrors.aliyun.com/repo/epel-7.repo [192.168.56.13] out: --2018-06-23 20:32:30-- http://mirrors.aliyun.com/repo/epel-7.repo .... [192.168.56.13] run: yum -y install ntp [192.168.56.12] run: yum -y install ntp [192.168.56.11] run: yum -y install ntp .... .... .... [192.168.56.11] Executing task 'webtask' [192.168.56.12] Executing task 'webtask' Install nginx php php-fpm... [192.168.56.11] run: yum -y install nginx Install nginx php php-fpm... [192.168.56.12] run: yum -y install nginx .... .... .... [192.168.56.13] Executing task 'dbtask' Install Mysql... [192.168.56.13] run: rpm -ivh http://dev.mysql.com/get/mysql-community-release-el6-5.noarch.rpm ..... ..... ..... [192.168.56.13] run: chkconfig --levels 235 mysqld on Done.
示例六: 分享一個生產環境程式碼包釋出管理的配置
程式生產環境的釋出是業務上線的最後一個環境,要求具備原始碼打包、釋出、切換、回滾、版本管理等功能。 本示例實現了這一套流程功能,其中版本切換與回滾使用了Linux下的軟連結實現。 [root@kevin ~]# vim fabric6.py #!/usr/local/env python from fabric.api import * from fabric.colors import * from fabric.context_managers import * from fabric.contrib.console import confirm import time env.user = 'root' env.host = ['192.168.56.12','192.168.56.13'] env.passwords = { 'root@192.168.56.12:22':'1234567', 'root@192.168.56.13:22':'1234567', } env.project_dev_source = '/data/dev/Lwebadmin/' #開發伺服器專案主目錄 env.project_tar_source = '/data/dev/releases/' #開發伺服器專案壓縮包儲存目錄 env.project_pack_name = 'release' #專案壓縮包字首,檔名為release.tar.gz env.deploy_project_root = '/data/www/Lwebadmin/' #專案生產環境主目錄 env.deploy_release_dir = 'releases' #專案釋出目錄,位於主目錄下面 env.deploy_current_dir = 'current' #對外服務的當前版本軟連結 env.deploy_version = time.strftime("%Y%m%d")+"v2" #版本號 @runs_once def input_versionid(): #獲得使用者輸入的版本號,以便做版本回滾操作 return prompt("Please input project rollback version ID:",default="") @task @runs_once def tar_source(): #打包本地專案主目錄,並將壓縮包儲存到本地壓縮包目錄 prompt(yellow("Creating source package....")) with lcd(env.project_dev_source): local("tar -zcf %s.tar.gz ." %(env.project_tar_source + env.project_pack_name)) prompt(green("Creating source package success!")) @task def put_package(): #上傳任務函式 prompt(yellow("Start put package....")) with settings(warn_only=True): with cd(env.deploy_project_root + env.deploy_release_dir): run("mkdir %s" %(env.deploy_version)) #建立版本目錄 env.deploy_full_path = env.deploy_project_root + env.deploy_release_dir + "/" + env.deploy_version with settings(warn_only=True): #上傳專案壓縮包至此目錄 result = put(env.project_tar_source + env.project_pack_name + ".tar.gz",env.deploy_full_path) if result.failed and not ("put file failed,Continue[Y/N]?"): abort("Aborting file put task!") with cd(env.deploy_full_path): #成功解壓後刪除壓縮包 run("tar -zxvf %s.tar.gz" %(env.project_pack_name)) run("rm -rf %s.tar.gz" %(env.project_pack_name)) print(green("Put & untar package success!")) @task def make_symlink(): #為當前版本目錄做軟連結 print(yellow("update current symlink")) env.deploy_full_path = env.deploy_project_root + env.deploy_release_dir + "/" + env.deploy_version with settings(warn_only=True): #刪除軟連結,重新建立並指定軟連結源目錄,新版本生效 run("rm -rf %s" %(env.deploy_project_root + env.deploy_current_dir)) run("ln -s %s %s" %(env.deploy_full_path,env.deploy_project_root + env.deploy_current_dir)) print(green("make symlink success!")) @task def rollback(): #版本回滾任務函式 print(yellow("rollback project version")) versionid = input_versionid() #獲取使用者輸入的回滾版本號 if versionid == '': abort("Project version ID error,abort!") env.deploy_full_path = env.deploy_project_root + env.deploy_release_dir + "/" + versionid run("rm -r %s" %(env.deploy_project_root + env.deploy_current_dir)) run("ln -s %s %s" %(env.deploy_full_path,env.deploy_project_root + env.deploy_current_dir)) #刪除軟連結,重新建立並指定軟連結源目錄,新版本生效 print(green("rollback sucess!")) @task def go(): #自動化程式版本釋出入口函式 tar_source() put_package() make_symlink() # 需要注意: 在生產環境中將站點的根目錄指向"/data/www/Lwebadmin/current",由於使用Linux軟連結做切換,管理員的版本釋出、回滾操作使用者無感知。 [root@kevin ~]# fab -f fabric6.py go
示例七: 分享一個自動化部署 Django 專案的配置
[root@kevin ~]# vim fabric7.py # -*- coding: utf-8 -*- # 檔名要儲存為 fabfile.py from __future__ import unicode_literals from fabric.api import * # 登入使用者和主機名: env.user = 'root' # 如果沒有設定,在需要登入的時候,fabric 會提示輸入 env.password = 'youpassword' # 如果有多個主機,fabric會自動依次部署 env.hosts = ['www.example.com'] TAR_FILE_NAME = 'deploy.tar.gz' def pack(): """ 定義一個pack任務, 打一個tar包 :return: """ tar_files = ['*.py', 'static/*', 'templates/*', 'vue_app/', '*/*.py', 'requirements.txt'] exclude_files = ['fabfile.py', 'deploy/*', '*.tar.gz', '.DS_Store', '*/.DS_Store', '*/.*.py', '__pycache__/*'] exclude_files = ['--exclude=\'%s\'' % t for t in exclude_files] local('rm -f %s' % TAR_FILE_NAME) local('tar -czvf %s %s %s' % (TAR_FILE_NAME, ' '.join(exclude_files), ' '.join(tar_files))) print('在當前目錄建立一個打包檔案: %s' % TAR_FILE_NAME) def deploy(): """ 定義一個部署任務 :return: """ # 先進行打包 pack() # 遠端伺服器的臨時檔案 remote_tmp_tar = '/tmp/%s' % TAR_FILE_NAME run('rm -f %s' % remote_tmp_tar) # 上傳tar檔案至遠端伺服器, local_path, remote_path put(TAR_FILE_NAME, remote_tmp_tar) # 解壓 remote_dist_base_dir = '/home/python/django_app' # 如果不存在, 則建立資料夾 run('mkdir -p %s' % remote_dist_dir) # cd 命令將遠端主機的工作目錄切換到指定目錄 with cd(remote_dist_dir): print('解壓檔案到到目錄: %s' % remote_dist_dir) run('tar -xzvf %s' % remote_tmp_tar) print('安裝 requirements.txt 中的依賴包') # 我使用的是 python3 來開發 run('pip3 install -r requirements.txt') remote_settings_file = '%s/django_app/settings.py' % remote_dist_dir settings_file = 'deploy/settings.py' % name print('上傳 settings.py 檔案 %s' % settings_file) put(settings_file, remote_settings_file) nginx_file = 'deploy/django_app.conf' remote_nginx_file = '/etc/nginx/conf.d/django_app.conf' print('上傳 nginx 配置檔案 %s' % nginx_file) put(nginx_file, remote_nginx_file) # 在當前目錄的子目錄 deploy 中的 supervisor 配置檔案上傳至伺服器 supervisor_file = 'deploy/django_app.ini' remote_supervisor_file = '/etc/supervisord.d/django_app.ini' print('上傳 supervisor 配置檔案 %s' % supervisor_file) put(supervisor_file, remote_supervisor_file) # 重新載入 nginx 的配置檔案 run('nginx -s reload') run('nginx -t') # 刪除本地的打包檔案 local('rm -f %s' % TAR_FILE_NAME) # 載入最新的配置檔案,停止原有程式並按新的配置啟動所有程式 run('supervisorctl reload') # 執行 restart all,start 或者 stop fabric 都會提示錯誤,然後中止執行 # 但是伺服器上檢視日誌,supervisor 有重啟 # run('supervisorctl restart all') 執行 pack 任務 [root@kevin ~]# fab -f fabric7.py pack 執行 deploy 任務 [root@kevin ~]# fab -f fabric7.py deploy
示例八: 程式碼的自動化部署
[root@kevin ~]# vim fabric8.py #coding=utf-8 from fabric.api import local, abort, settings, env, cd, run from fabric.colors import * from fabric.contrib.console import confirm env.hosts = ["root@115.28.×××××"] env.password = "×××××" def get_git_status(): git_status_result = local("git status", capture=True) if "無檔案要提交,乾淨的工作區" not in git_status_result: print red("****當前分支還有檔案沒有提交") print git_status_result abort("****已經終止") def local_unit_test(): with settings(warn_only=True): test_result = local("python manage.py test") if test_result.failed: print test_result if not confirm(red("****單元測試失敗,是否繼續?")): abort("****已經終止") def server_unit_test(): with settings(warn_only=True): test_result = run("python manage.py test") if test_result.failed: print test_result if not confirm(red("****單元測試失敗,是否繼續?")): abort("****已經終止") def upload_code(): local("git push origin dev") print green("****程式碼上傳成功") def deploy_at_server(): print green("****ssh到伺服器進行下列操作") with cd("/var/www/××××××"): #print run("pwd") print green("****將在遠端倉庫下載程式碼") run("git checkout dev") get_git_status() run("git pull origin dev") print green("****將在伺服器上執行單元測試") server_unit_test() run("service apache2 restart", pty=False) print green("****重啟apache2成功") print green("********程式碼部署成功********") def deploy(): get_git_status() local("git checkout dev", capture=False) print green("****切換到dev分支") get_git_status() print green("****將開始執行單元測試") local_unit_test() print green("****單元測試完成,開始上傳程式碼") upload_code() deploy_at_server() fabric可以將自動化部署或者多機操作的命令固化到一個指令碼里,從而減少手動的操作。上面是今天第一次接觸這東西后寫的,確實很實用。 執行 [root@kevin ~]# fab -ff abric8.py deploy 主要邏輯就是將本地的dev分支跑單元測試,然後提交到伺服器,ssh登陸到伺服器,然後pull下來,再跑單元測試,然後重啟apache2。 這個寫的還是比較簡單的。
===============這裡貼出之前線上環境使用過的一個Fabric自動化配置===============
1) 通過Fabric配置的自動化python上線指令碼(包括回滾指令碼): [work@qd-op-zhongkong op]$ cat xcspam-celery.py from fabric.api import * from fabric.context_managers import * import datetime env.hosts=['qd-vpc-op-rule01'] def antiwater(): with cd('/app/release'): date=datetime.datetime.now().strftime("%Y%m%d%H%M%S") repo='http://git.kevinweb.com/zuiyou_server/xcspam.git'; run('git clone --depth=1 %s' % repo) newNmae="xcspam"+"-"+date run('mv xcspam %s ' % newNmae) with cd('/app/web/xcspam'): newRelease=run('ls /app/release/ |tail -1f') run('cp -rp /app/release/%s /app/web/xcspam/ ' % newRelease) run('unlink bin') run('ln -sn %s bin' % newRelease) run('superctl restart antiwater:*') def rollantiwater(): with cd('/app/web/xcspam'): lastrelease=run('ls -rtd xcspam* |tail -2 |head -1') run('unlink bin') run('ln -sn %s bin' % lastrelease) run('superctl restart antiwater:*') def report(): with cd('/app/release'): date=datetime.datetime.now().strftime("%Y%m%d%H%M%S") repo='http://git.kevinweb.com/zuiyou_server/xcspam.git'; run('git clone --depth=1 %s' % repo) newNmae="xcspam"+"-"+date run('mv xcspam %s ' % newNmae) with cd('/app/web/xcspam'): newRelease=run('ls /app/release/ |tail -1f') run('cp -rp /app/release/%s /app/web/xcspam/ ' % newRelease) run('unlink bin') run('ln -sn %s bin' % newRelease) run('superctl restart report') def rollreport(): with cd('/app/web/xcspam'): lastrelease=run('ls -rtd xcspam* |tail -2 |head -1') run('unlink bin') run('ln -sn %s bin' % lastrelease) run('superctl restart report') def chat(): with cd('/app/release'): date=datetime.datetime.now().strftime("%Y%m%d%H%M%S") repo='http://git.kevinweb.com/zuiyou_server/xcspam.git'; run('git clone --depth=1 %s' % repo) newNmae="xcspam"+"-"+date run('mv xcspam %s ' % newNmae) with cd('/app/web/xcspam'): newRelease=run('ls /app/release/ |tail -1f') run('cp -rp /app/release/%s /app/web/xcspam/ ' % newRelease) run('unlink bin') run('ln -sn %s bin' % newRelease) run('superctl restart chat') def rollchat(): with cd('/app/web/xcspam'): lastrelease=run('ls -rtd xcspam* |tail -2 |head -1') run('unlink bin') run('ln -sn %s bin' % lastrelease) run('superctl restart chat') 可以在一個指令碼中定義多個上線專案,上線的時候可以選擇,如下(回滾的時候選擇對應的roll即可): [work@qd-op-zhongkong op]$ fab -f xcspam-celery.py antiwater [work@qd-op-zhongkong op]$ fab -f xcspam-celery.py report [work@qd-op-zhongkong op]$ fab -f xcspam-celery.py chat 2) 指令碼2,其實跟上面無異: [work@qd-op-zhongkong op]$ cat xcspam-consumer.py from fabric.api import * from fabric.context_managers import * import datetime env.hosts=['qd-vpc-op-consumer01','qd-vpc-op-consumer02'] def xcspam(): with cd('/app/release'): date=datetime.datetime.now().strftime("%Y%m%d%H%M%S") repo='http://git.kevinweb.com/zuiyou_server/xcspam.git'; run('git clone --depth=1 %s' % repo) newNmae="xcspam"+"-"+date run('mv xcspam %s ' % newNmae) with cd('/app/web/xcspam'): newRelease=run('ls /app/release/ |tail -1f') run('cp -rp /app/release/%s /app/web/xcspam/ ' % newRelease) run('unlink bin') run('ln -sn %s bin' % newRelease) run('superctl restart xcspam:*') def rollxcspam(): with cd('/app/web/xcspam'): lastrelease=run('ls -rtd xcspam* |tail -2 |head -1') run('unlink bin') run('ln -sn %s bin' % lastrelease) run('superctl restart xcspam:*') def chatxcspam(): with cd('/app/release'): date=datetime.datetime.now().strftime("%Y%m%d%H%M%S") repo='http://git.kevinweb.com/zuiyou_server/xcspam.git'; run('git clone --depth=1 %s' % repo) newNmae="xcspam"+"-"+date run('mv xcspam %s ' % newNmae) with cd('/app/web/xcspam'): newRelease=run('ls /app/release/ |tail -1f') run('cp -rp /app/release/%s /app/web/xcspam/ ' % newRelease) run('unlink bin') run('ln -sn %s bin' % newRelease) run('superctl restart chatxcspam:*') def chatxcspam(): with cd('/app/web/xcspam'): lastrelease=run('ls -rtd xcspam* |tail -2 |head -1') run('unlink bin') run('ln -sn %s bin' % lastrelease) run('superctl restart chatxcspam:*’) 3) 指令碼3 [work@qd-op-zhongkong op]$ cat xcspam-consumer-all.py from fabric.api import * from fabric.context_managers import * import datetime env.hosts=['qd-vpc-op-consumer01','qd-vpc-op-consumer02'] def xcspam(): with cd('/app/release'): date=datetime.datetime.now().strftime("%Y%m%d%H%M%S") repo='http://git.kevinweb.com/zuiyou_server/xcspam.git'; run('git clone --depth=1 %s' % repo) newNmae="xcspam"+"-"+date run('mv xcspam %s ' % newNmae) with cd('/app/web/xcspam'): newRelease=run('ls /app/release/ |tail -1f') run('cp -rp /app/release/%s /app/web/xcspam/ ' % newRelease) run('unlink bin') run('ln -sn %s bin' % newRelease) run('superctl restart xcspam:*') def rollxcspam(): with cd('/app/web/xcspam'): lastrelease=run('ls -rtd xcspam* |tail -2 |head -1') run('unlink bin') run('ln -sn %s bin' % lastrelease) run('superctl restart xcspam:*') def chatxcspam(): with cd('/app/release'): date=datetime.datetime.now().strftime("%Y%m%d%H%M%S") repo='http://git.kevinweb.com/zuiyou_server/xcspam.git'; run('git clone --depth=1 %s' % repo) newNmae="xcspam"+"-"+date run('mv xcspam %s ' % newNmae) with cd('/app/web/xcspam'): newRelease=run('ls /app/release/ |tail -1f') run('cp -rp /app/release/%s /app/web/xcspam/ ' % newRelease) run('unlink bin') run('ln -sn %s bin' % newRelease) run('superctl restart chatxcspam:*') def chatxcspam(): with cd('/app/web/xcspam'): lastrelease=run('ls -rtd xcspam* |tail -2 |head -1') run('unlink bin') run('ln -sn %s bin' % lastrelease) run('superctl restart chatxcspam:*') def all(): with cd('/app/release'): date=datetime.datetime.now().strftime("%Y%m%d%H%M%S") repo='http://git.kevinweb.com/zuiyou_server/xcspam.git'; run('git clone --depth=1 %s' % repo) newNmae="xcspam"+"-"+date run('mv xcspam %s ' % newNmae) with cd('/app/web/xcspam'): newRelease=run('ls /app/release/ |tail -1f') run('cp -rp /app/release/%s /app/web/xcspam/ ' % newRelease) run('unlink bin') run('ln -sn %s bin' % newRelease) run('superctl restart all') def rollall(): with cd('/app/web/xcspam'): lastrelease=run('ls -rtd xcspam* |tail -2 |head -1') run('unlink bin') run('ln -sn %s bin' % lastrelease) run('superctl restart all') 可以根據需求去選擇具體對那個專案進行上線,上述指令碼定義了兩個專案上線,第三個(all)即表示同時上線兩個專案。 [work@qd-op-zhongkong op]$ fab -f xcspam-consumer-all.py xcspam [work@qd-op-zhongkong op]$ fab -f xcspam-consumer-all.py chatxcspam [work@qd-op-zhongkong op]$ fab -f xcspam-consumer-all.py all