前陣子要用腳本遠程重啟linux系統,開始用shell腳本沒有實現,后面用pexpect實現了,后面發現pexpect太麻煩,又用了paramiko來實現,最近看了一下一個更強大的遠程工具fabric,也有更好的實現方式。
這里記錄一下:
摘自https://www.cnblogs.com/zhoujinyi/p/6023839.html
背景:
關於Fabric的介紹,可以看官網說明。簡單來說主要功能就是一個基於Python的服務器批量管理庫/工具,Fabric 使用 ssh(通過 paramiko 庫)在多個服務器上批量執行任務、上傳、下載。在使用Fabric之前,都用Python的paramiko模塊來實現需求,相比之后發現Fabric比paramiko模塊強大很多。具體的使用方法和說明可以看官方文檔介紹。下面寫類一個用paramiko(apt-get install python-paramiko)封裝的遠程操作類的模板:
#!/usr/bin/python # -*- encoding: utf-8 -*- import paramiko import sys reload(sys) sys.setdefaultencoding('utf8') class Remote_Ops(): def __init__(self,hostname,ssh_port,username='',password=''): self.hostname = hostname self.ssh_port = ssh_port self.username = username self.password = password #密碼登入的操作方法 def ssh_connect_exec(self,cmd): try: ssh_key = paramiko.SSHClient() ssh_key.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh_key.connect(hostname=self.hostname, port=self.ssh_port, username=self.username, password=self.password, timeout=10) # paramiko.util.log_to_file('syslogin.log') except Exception, e: print('Connect Error:ssh %s@%s: %s' % (self.username, self.hostname, e)) exit() stdin, stdout, stderr = ssh_key.exec_command(cmd,get_pty=True) #切換root stdin.write(self.password+'\n') stdin.flush() err_list = stderr.readlines() if len( err_list ) > 0: print 'ERROR:' + err_list[0] exit() # print stdout.read() for item in stdout.readlines()[2:]: print item.strip() ssh_key.close() #ssh登陸的操作方法 def ssh_connect_keyfile_exec(self,file_name,cmd): try: ssh_key = paramiko.SSHClient() ssh_key.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh_key.connect(hostname=self.hostname, port=self.ssh_port, key_filename=file_name, timeout=10) # paramiko.util.log_to_file('syslogin.log') except Exception, e: print e exit() stdin, stdout, stderr = ssh_key.exec_command(cmd) err_list = stderr.readlines() if len( err_list ) > 0: print 'ERROR:' + err_list[0] exit() for item in stdout.readlines(): print item.strip() ssh_key.close() if __name__ == '__main__': #密碼登陸的操作方法: test = Remote_Ops('10.211.55.11', 22, 'zjy', 'zhoujinyi') test.ssh_connect_exec('sudo ls -lh /var/lib/mysql/') #ssh key登陸的操作方法:(需要到root下運行) file_name = '/var/root/.ssh/id_rsa' test1 = Remote_Ops('10.211.55.11', 22) test1.ssh_connect_keyfile_exec(file_name,'apt-get update')
關於更多的paramiko信息可以看官方文檔和python運維之paramiko、python遠程連接paramiko 模塊。本文將要介紹的是Fabric的使用方法。
說明:
1.安裝
$ pip install fabric OR $ sudo apt-get install fabric
2.參數(fab -h)
~$ fab -h 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 --shortlist alias for -F short --list -V, --version show program's version number and exit -a, --no_agent don't use the running SSH agent -A, --forward-agent forward local agent to remote end --abort-on-prompts abort instead of prompting (for password, host, etc) #出現提示就中指,如密碼、主機提示 -c PATH, --config=PATH specify location of config file to use --colorize-errors Color error output #輸出顏色錯誤 -D, --disable-known-hosts do not load user known_hosts file -e, --eagerly-disconnect disconnect from hosts as soon as possible -f PATH, --fabfile=PATH #指定fab執行的文件,默認是fabfile.py python module file to import, e.g. '../other.py' -g HOST, --gateway=HOST #指定堡壘機(中轉機)的地址 gateway host to connect through --gss-auth Use GSS-API authentication --gss-deleg Delegate GSS-API client credentials or not --gss-kex Perform GSS-API Key Exchange and user authentication --hide=LEVELS comma-separated list of output levels to hide #output隱藏的等級設置,多個等級逗號分隔 -H HOSTS, --hosts=HOSTS #指定操作的服務器,多個用逗號分隔 comma-separated list of hosts to operate on -i PATH path to SSH private key file. May be repeated. #指定私鑰文件 -k, --no-keys don't load private key files from ~/.ssh/ --keepalive=N enables a keepalive every N seconds # --linewise print line-by-line instead of byte-by-byte -n M, --connection-attempts=M make M attempts to connect before giving up --no-pty do not use pseudo-terminal in run/sudo -p PASSWORD, --password=PASSWORD #指定遠程登陸的密碼包括sudo password for use with authentication and/or sudo -P, --parallel default to parallel execution method #指定是否並行執行的 --port=PORT SSH connection port #指定ssh的默認端口 -r, --reject-unknown-hosts #指定拒絕的主機 reject unknown hosts --sudo-password=SUDO_PASSWORD #指定sudo密碼 password for use with sudo only --system-known-hosts=SYSTEM_KNOWN_HOSTS load system known_hosts file before reading user known_hosts -R ROLES, --roles=ROLES #指定role,以role來區分不同執行函數 comma-separated list of roles to operate on -s SHELL, --shell=SHELL #指定的shell的執行環境, specify a new shell, defaults to '/bin/bash -l -c' --show=LEVELS comma-separated list of output levels to show #指定顯示output的等級,多個用逗號分隔 --skip-bad-hosts skip over hosts that can't be reached #指定跳過不能到達的主機 --skip-unknown-tasks skip over unknown tasks #指定跳過不識別的執行函數 --ssh-config-path=PATH #指定ssh配置文件的路徑 Path to SSH config file -t N, --timeout=N set connection timeout to N seconds #指定連接超時時間 -T N, --command-timeout=N #指定遠程命令超時時間 set remote command timeout to N seconds -u USER, --user=USER username to use when connecting to remote hosts #指定遠程登陸用戶 -w, --warn-only warn, instead of abort, when commands fail #指定命令錯誤發出警告而不是中止 -x HOSTS, --exclude-hosts=HOSTS comma-separated list of hosts to exclude #指定排除的主機 -z INT, --pool-size=INT number of concurrent processes to use in parallel mode #指定並發線程的數量
使用
①:命令行接口
~$ fab -u zjy -p zhoujinyi -H 10.211.55.9,10.211.55.11 -- 'ls -lh /tmp/'
效果:
[10.211.55.9] Executing task '<remainder>' [10.211.55.9] run: ls -lh /tmp/ [10.211.55.9] out: 總用量 16K [10.211.55.9] out: -rw-rw-r-- 1 zjy zjy 853 11月 10 18:42 change_pwd.py [10.211.55.9] out: [10.211.55.11] Executing task '<remainder>' [10.211.55.11] run: ls -lh /tmp/ [10.211.55.11] out: 總用量 12K [10.211.55.11] out: -rw-rw-r-- 1 zjy zjy 2.4K 11月 10 18:29 remote_ops.py [10.211.55.11] out:
不推薦使用命令行,最好都寫到一個文件腳本里,方便也安全。
②:腳本
注:默認fabric使用一個名為fabfile.py文件里,如:
#!/usr/bin/python from fabric.api import local, lcd def lsfab(): with lcd('/tmp/'): local('ls')
效果:
[localhost] local: ls com.apple.launchd.ESurbfatee com.apple.launchd.ykbSkcZdfZ mykey.txt parallels_crash_dumps Done.
如果寫到其他文件則需要通過-f來指定執行:
~$ mv fabfile.py ttt.py
~$ fab -f ttt.py lsfab
③:參數
定義的執行函數里帶參數:
#!/usr/bin/python from fabric.api import * def hello(name,age): print "hello,%s,%s" %(name,age)
帶參數的執行函數執行:
~$ fab hello:zhoujy,123 hello,zhoujy,123 Done.
④:模塊說明
1:from fabric.api import * local #執行本地命令,如local('uname -s') lcd #切換本地目錄,如lcd('/home') cd #切換遠程目錄,如cd('/var/logs') run #執行遠程命令,如run('free -m') sudo #sudo方式執行遠程命令,如sudo('/etc/init.d/httpd start') put #上次本地文件導遠程主機,如put('/home/user.info','/data/user.info') get #從遠程主機下載文件到本地,如:get('/data/user.info','/home/user.info') prompt #獲得用戶輸入信息,如:prompt('please input user password:') confirm #獲得提示信息確認,如:confirm('Test failed,Continue[Y/N]?') reboot #重啟遠程主機,如:reboot() @task #函數修飾符,標識的函數為fab可調用的,非標記對fab不可見,純業務邏輯 @runs_once #函數修飾符,標識的函數只會執行一次,不受多台主機影響 @roles() #運行指定的角色組里,通過env.roledefs里的定義
2:from fabric.colors import * print blue(text) print cyan(text) print green(text) print magenta(text) print red(text) print white(text) print yellow(text) 3:
...
用到再補充 ...
基礎實例說明
實例1: 本地操作
from fabric.api import * def lsfab(): with lcd('/tmp/'): #本地切換目錄 local('ls') #本地執行命令 def host_name(): local('uname -s') #本地執行命令
用-l查看可以執行的命令函數:
~$ fab -f fab_ops.py -l Available commands: host_name lsfab
可以執行上面2個執行函數,執行:
~$ fab -f fab_ops.py host_name [localhost] local: uname -s Darwin Done.
上面2個函數合並,並且對外只顯示一個執行入口函數(@task):
from fabric.api import * def lsfab(): with lcd('/tmp/'): local('ls') def host_name(): local('uname -s') @task def go(): lsfab() host_name()
執行:
~$ fab -f fab_ops.py go [localhost] local: ls com.apple.launchd.ESurbfatee com.apple.launchd.ykbSkcZdfZ parallels_crash_dumps [localhost] local: uname -s Darwin Done.
實例2:遠程操作,env變量
from fabric.api import * #配置遠程服務器 env.hosts = [ '10.211.55.9', '10.211.55.11' ] #端口 env.port = '22' #用戶 env.user = 'zjy' #密碼,遠程服務器密碼都一樣 env.password = 'zhoujinyi' def lsfab(): with cd('/tmp/'): #遠程切換目錄 run('ls') #遠程命令運行 def host_name(): run('uname -s') @task def go(): lsfab() host_name()
執行看到的信息:執行的函數,命令和命令的輸出結果。
~$ fab -f fab_ops.py go [10.211.55.9] Executing task 'go' [10.211.55.9] run: ls [10.211.55.9] out: 1 2 3 [10.211.55.9] out: [10.211.55.9] run: uname -s [10.211.55.9] out: Linux [10.211.55.9] out: [10.211.55.11] Executing task 'go' [10.211.55.11] run: ls [10.211.55.11] out: a b c mongodb-27017.sock [10.211.55.11] out: [10.211.55.11] run: uname -s [10.211.55.11] out: Linux [10.211.55.11] out: Done. Disconnecting from 10.211.55.11... done. Disconnecting from 10.211.55.9... done.
遠程機器的密碼不一致,怎么配置?這時可以用env.passwords來替換env.password:注意格式:user@ip:pwd
env.passwords = { 'zjy@10.211.55.9:22' : 'zjy', 'zjy@10.211.55.11:22': 'zhoujinyi', }
實例3:如何讓不同服務器組執行不同的操作?如DB和WEB服務器各自執行自己的操作。這里需要用env.roledefs來定義角色組,根據不同的roles來使用execute進行不同的操作。
#!/usr/bin/python # -*- encoding: utf-8 -*- from fabric.api import * #定義角色,操作一致的服務器可以放在一組。因為服務器的用戶端口不一樣,需要在role里指定用戶、IP和端口 env.roledefs = { 'dbserver':['zjy@10.211.55.9:22','zjy@10.211.55.11:22'], 'webserver':['zhoujy@192.168.200.25:221'], } #密碼,遠程服務器密碼不一致時使用,格式user@host:port:pwd env.passwords = { 'zjy@10.211.55.9:22' : 'zjy', 'zjy@10.211.55.11:22': 'zhoujinyi', 'zhoujy@192.168.200.25:221':'123456', } @task #入口 @roles('dbserver') #角色修飾符 def get_memory(): run('free -m') @task @roles('webserver') def mkfile_task(): with cd('/home/zhoujy/'): run('touch xxxx.log')
執行效果:執行get_memory函數,在dbserver中的主機上執行,mkfile_touch函數則在webserver中的主機上執行。
通過env.roledefs和env.passwords指定好了用戶名、端口和密碼,這時上面2個函數合並,並且對外只顯示一個執行函數(@task),還要注意的是因為各自的執行函數處於不同的roles下執行的,要放到一個函數里執行,需要添加:execute(),這樣可以在一個執行函數里操作多個遠程的機器。
... ... @task def go(): execute(get_memory) execute(mkfile_task)
效果:

實例4:多台服務器並行執行,@parallel
#!/usr/bin/python # -*- encoding: utf-8 -*- from fabric.api import *
#要是各個服務器的端口、用戶名不一樣,就不能配置env.port、env.user了,需要在env.hosts中設置端口:用戶@IP:端口 env.hosts = ['zjy@10.211.55.9:22','zjy@10.211.55.11:22','zhoujy@192.168.200.25:221'] #密碼,遠程服務器密碼不一致時使用,格式user@host:port:pwd env.passwords = { 'zjy@10.211.55.9:22' : 'zjy', 'zjy@10.211.55.11:22': 'zhoujinyi', 'zhoujy@192.168.200.25:221':'123456', } @task #入口 @parallel def get_memory(): run('free -m')
執行效果:執行函數同時在多個主機上運行,加快執行效率

[zjy@10.211.55.9:22] Executing task 'get_memory' [zjy@10.211.55.11:22] Executing task 'get_memory' [zhoujy@192.168.200.25:221] Executing task 'get_memory' [zhoujy@192.168.200.25:221] run: free -m [zjy@10.211.55.11:22] run: free -m [zjy@10.211.55.9:22] run: free -m [zjy@10.211.55.9:22] out: total used free shared buffers cached [zjy@10.211.55.9:22] out: Mem: 990 749 240 0 17 136 [zjy@10.211.55.9:22] out: -/+ buffers/cache: 595 395 [zjy@10.211.55.9:22] out: Swap: 1021 0 1021 [zjy@10.211.55.9:22] out: [zjy@10.211.55.11:22] out: total used free shared buffers cached [zjy@10.211.55.11:22] out: Mem: 3949 943 3006 0 13 230 [zjy@10.211.55.11:22] out: -/+ buffers/cache: 699 3249 [zjy@10.211.55.11:22] out: Swap: 1021 0 1021 [zjy@10.211.55.11:22] out: [zhoujy@192.168.200.25:221] out: total used free shared buffers cached [zhoujy@192.168.200.25:221] out: Mem: 3993 2747 1245 0 464 1271 [zhoujy@192.168.200.25:221] out: -/+ buffers/cache: 1011 2981 [zhoujy@192.168.200.25:221] out: Swap: 0 0 0 [zhoujy@192.168.200.25:221] out: Done.
實例5:輸出信息等級設置:with settings(hide(...)):
#!/usr/bin/python # -*- encoding: utf-8 -*- from fabric.api import * from fabric.colors import * env.hosts = ['zjy@10.211.55.9:22','zjy@10.211.55.11:22','zhoujy@192.168.200.25:221'] #密碼,遠程服務器密碼不一致時使用,格式user@host:port:pwd env.passwords = { 'zjy@10.211.55.9:22' : 'zjy', 'zjy@10.211.55.11:22': 'zhoujinyi', 'zhoujy@192.168.200.25:221':'123456', } @runs_once #只執行一次,避免每個主機處理都輸入一次 def put_file():
#獲取輸入信息 return prompt("輸入上傳的文件名(絕對路徑),默認當前目錄文件:",default ="") @task def put_task(): local_path = put_file() remote_path = "/tmp/" put(local_path,remote_path) #上傳 #輸出等級設置,隱藏指定的類型 with settings(hide('running', 'stdout', 'stderr','warnings','everything')): result = put(local_path,remote_path) print yellow("%s上傳成功...") %env.host
隱藏:

~$ fab -f zz.py put_task 1 ↵ [zjy@10.211.55.9:22] Executing task 'put_task' 輸入上傳的文件名(絕對路徑),默認當前目錄文件: /Users/jinyizhou/fabric_script/single_fab_ops.py [zjy@10.211.55.9:22] put: /Users/jinyizhou/fabric_script/single_fab_ops.py -> /tmp/single_fab_ops.py 10.211.55.9上傳成功... 10.211.55.11上傳成功... 192.168.200.25上傳成功... Done. Disconnecting from zhoujy@192.168.200.25:221... done. Disconnecting from zjy@10.211.55.11... done. Disconnecting from zjy@10.211.55.9... done.
沒有隱藏:

~$ fab -f zz.py put_task 126 ↵ [zjy@10.211.55.9:22] Executing task 'put_task' 輸入上傳的文件名(絕對路徑),默認當前目錄文件: /Users/jinyizhou/fabric_script/single_fab_ops.py [zjy@10.211.55.9:22] put: /Users/jinyizhou/fabric_script/single_fab_ops.py -> /tmp/single_fab_ops.py [zjy@10.211.55.9:22] put: /Users/jinyizhou/fabric_script/single_fab_ops.py -> /tmp/single_fab_ops.py 10.211.55.9上傳成功... [zjy@10.211.55.11:22] Executing task 'put_task' [zjy@10.211.55.11:22] put: /Users/jinyizhou/fabric_script/single_fab_ops.py -> /tmp/single_fab_ops.py [zjy@10.211.55.11:22] put: /Users/jinyizhou/fabric_script/single_fab_ops.py -> /tmp/single_fab_ops.py 10.211.55.11上傳成功... [zhoujy@192.168.200.25:221] Executing task 'put_task' [zhoujy@192.168.200.25:221] put: /Users/jinyizhou/fabric_script/single_fab_ops.py -> /tmp/single_fab_ops.py [zhoujy@192.168.200.25:221] put: /Users/jinyizhou/fabric_script/single_fab_ops.py -> /tmp/single_fab_ops.py 192.168.200.25上傳成功... Done. Disconnecting from zhoujy@192.168.200.25:221... done. Disconnecting from zjy@10.211.55.11... done. Disconnecting from zjy@10.211.55.9... done.
實例6:異常處理、捕獲:
異常處理:settings(warn_only=True)遇到錯誤繼續執行還是退出settings(warn_only=False),False是默認方式。
from fabric.api import * def lsfab(): with lcd('/tmp/'): local('lsa') #執行不存在的命令,報錯退出 print "xxxxxxx" def lsfab1(): with settings(warn_only=True): #執行不存在的命令,報錯,繼續執行后面的 with lcd('/tmp/'): local('lsa') print "xxxxxx"
效果:

~$ fab -f zz.py lsfab [localhost] local: lsa /bin/sh: lsa: command not found Fatal error: local() encountered an error (return code 127) while executing 'lsa' Aborting. ~$ fab -f zz.py lsfab1 [localhost] local: lsa /bin/sh: lsa: command not found Warning: local() encountered an error (return code 127) while executing 'lsa' xxxxxx Done.
異常捕獲:if result.failed:abord()捕獲到異常之后直接退出;if result.failed and not confirm("")捕獲到異常之后確認退出還是繼續
#!/usr/bin/python # -*- encoding: utf-8 -*- from fabric.colors import * from fabric.contrib.console import confirm from fabric.api import * def lsfab(): with settings(warn_only=True): with lcd('/Users/jinyizhou/uuii/'): result = local('lsn',capture=True) #通過local來執行任務,需要通過capture=True來得到值 if result.failed: #即使warn_only設置成True,捕捉到錯誤之后,還是退出 abort(red("錯誤...")) print 'xxxxx' def lsfab1(): with settings(warn_only=True): with lcd('/Users/jinyizhou/uuii/'): result = local('lsn',capture=True) if result.failed and not confirm("failed. Continue?"): #即使warn_only設置成True,捕捉到錯誤之后,還是要確認是否退出或則繼續 abort(red("錯誤...")) print 'xxxxx'
效果:

~$ fab -f zz.py lsfab [localhost] local: lsn Warning: local() encountered an error (return code 127) while executing 'lsn' Fatal error: 錯誤... Aborting. ~$ fab -f zz.py lsfab1 [localhost] local: lsn Warning: local() encountered an error (return code 127) while executing 'lsn' failed. Continue? [Y/n] Y xxxxx Done.
實例7:通過中轉(路由)機連接遠程機執行,env.gateway
#!/usr/bin/python # -*- encoding: utf-8 -*- from fabric.api import * from fabric.colors import * #中轉機 env.gateway = 'zjy@10.211.55.9:22' #中轉機的地址,注意中轉機的端口、密碼和其他遠程機器不一樣,需要設置成user@ip:port的格式。 #操作服務器 env.hosts = ['zjy@10.211.55.11:22','zhoujy@192.168.200.25:221'] env.passwords = { 'zjy@10.211.55.9:22' : 'zjy', 'zjy@10.211.55.11:22': 'zhoujinyi', 'zhoujy@192.168.200.25:221':'123456', } #遠程連接超時時間 env.timeout = 30 #命令超時時間 env.command_timeout = 30 @runs_once #只執行一次,避免每個主機處理都輸入一次 def put_file(): return prompt("輸入上傳的文件名(絕對路徑),默認當前目錄文件:",default ="") @task def put_task(): local_path = put_file() remote_path = "/tmp/" put(local_path,remote_path) #輸出等級設置,隱藏指定的類型 with settings(hide('running','stdout', 'stderr','everything'),warn_only=True): result = put(local_path,remote_path) print yellow("%s上傳成功...") %env.host if result.failed and not confirm("put file failed,Continue[Y/N]?"): abort("Aborting file put task!")
效果:本地執行腳本通過中轉機來向遠程機器上傳文件,本機和遠程機不需要有關聯
~$ fab -f zz.py put_task [zjy@10.211.55.11:22] Executing task 'put_task' 輸入上傳的文件名(絕對路徑),默認當前目錄文件: /Users/jinyizhou/fabric_script/gateway.txt [zjy@10.211.55.11:22] put: /Users/jinyizhou/fabric_script/gateway.txt -> /tmp/gateway.txt 10.211.55.11上傳成功... 192.168.200.25上傳成功... Done. Disconnecting from zhoujy@192.168.200.25:221... done. Disconnecting from zjy@10.211.55.11... done. Disconnecting from zjy@10.211.55.9... done. #最后斷開中轉機
實例8:通過ssh key(密鑰)登陸
#!/usr/bin/python # -*- encoding: utf-8 -*- from fabric.api import * from fabric.colors import * #中轉機 env.gateway = 'zjy@10.211.55.9:22' #中轉機的地址,注意中轉機的端口、密碼和其他遠程機器不一樣,需要設置成user@ip:port的格式。 #操作服務器 env.hosts = ['zhoujy@192.168.200.25:221','zhoujy@192.168.200.102:222'] env.passwords = { 'zhoujy@192.168.200.25:221':'123456', 'zhoujy@192.168.200.102:222':'123456', } #env.use_ssh_config = True #密鑰登陸中轉服務器 env.key_filename = ['~/.ssh/id_rsa'] #遠程連接超時時間 env.timeout = 30 #命令超時時間 env.command_timeout = 30 @runs_once #只執行一次,避免每個主機處理都輸入一次 def put_file(): return prompt("輸入上傳的文件名(絕對路徑),默認當前目錄文件:",default ="/home/zjy/ttxx.py") @task def put_task(): local_path = put_file() remote_path = "/tmp/" put(local_path,remote_path) #輸出等級設置,隱藏指定的類型 with settings(hide('running','stdout', 'stderr','everything'),warn_only=True): result = put(local_path,remote_path) print yellow("%s上傳成功...") %env.host if result.failed and not confirm("put file failed,Continue[Y/N]?"): abort("Aborting file put task!")
效果:執行腳本的機器通過密鑰登陸GATEWAY機器,再在GATEWAY機器上通過賬號密碼登陸要操作的機器(不確定這里能否也通過SSH密鑰登陸?)。
zhoujy@192.168.200.25:221] Executing task 'put_task' 輸入上傳的文件名(絕對路徑),默認當前目錄文件: [/home/zjy/ttxx.py] [zhoujy@192.168.200.25:221] put: /home/zjy/ttxx.py -> /tmp/ttxx.py 192.168.200.25上傳成功... 192.168.200.102上傳成功... Done. Disconnecting from zhoujy@192.168.200.25:221... done. Disconnecting from zhoujy@192.168.200.102:222... done. Disconnecting from 10.211.55.9... done. #最后退出GATEWAY
如何建立SSH的密鑰連接,在MySQL MHA 搭建&測試里有說明,可以了解一下。
應用說明
應用1:帶參數批量修改服務器密碼
#!/usr/bin/python # -*- coding: utf-8 -*- from fabric.api import * import socket import paramiko from fabric.colors import * env.user = 'zjy' env.password = 'zhoujinyi' env.hosts = ['10.211.55.11','10.211.55.9'] def isup(host): print 'connecting host: %s' % host timeout = socket.getdefaulttimeout() socket.setdefaulttimeout(1) up = True try: paramiko.Transport((host, 22)) except Exception, e: up = False print '%s down, %s' % (host, e) finally: socket.setdefaulttimeout(timeout) return up @task @parallel def passwd(user,password): with settings(hide('running', 'stdout', 'stderr','everything'), warn_only=True): if isup(env.host): sudo("echo -e '%s\n%s' | passwd %s" % (password, password, user)) print yellow("%s 更新密碼...") %env.host
執行效果:
~$ fab -f change_pwd.py passwd:zjy,zjy 1 ↵ [10.211.55.11] Executing task 'passwd' [10.211.55.9] Executing task 'passwd' connecting host: 10.211.55.9 connecting host: 10.211.55.11 10.211.55.9 更新密碼... 10.211.55.11 更新密碼... Done.
應用2:遠程開、關、升級MySQL,並且自動完成MHA相關的切換開啟工作。

#!/usr/bin/python # -*- encoding: utf-8 -*- #--------------------------------------------------------------------- # Purpose: MHA自動切換 # Author: zhoujy # Created: 2016-11-07 # Update: 2016-11-07 #--------------------------------------------------------------------- from fabric.api import * from fabric.context_managers import * from fabric.contrib.console import confirm from fabric.colors import * import os import re import getpass import socket import paramiko #IP(主從)+項目名, MySQL_IP = { '192.168.1.7':'ABD', '192.168.1.8':'ABD', '192.168.1.10':'DEW', '192.168.1.5':'DEW', '192.168.1.7':'YTE', '192.168.1.8':'YTE', '192.168.1.5':'POQ', '192.168.1.6':'POQ', '192.168.1.91':'QWE', '192.168.1.72':'QWE', } Run_IP = raw_input("輸入要操作的IP:") Run_IP_Port = Run_IP+':45678' #啟用ssh key登陸 env.use_ssh_config = True env.key_filename = ['/root/.ssh/id_rsa'] #復制賬號密碼 Rep_Password = "123456" env.roledefs = { 'mha_manager':['192.168.1.19:45678'], 'db_server':[Run_IP_Port], } #mha日志名 mha_log = { 'ABD':'ABD_manager.log', 'DEW':'DEW_manager.log', 'YTE':'YTE_manager.log', 'POQ':'POQ_manager.log', 'QWE':'QWE_manager.log', } #mha配置文件名 mha_cfg = { 'ABD':'ABD_mha.cnf', 'DEW':'DEW_mha.cnf', 'YTE':'YTE_mha.cnf', 'POQ':'POQ_mha.cnf', 'QWE':'QWE_mha.cnf', } #mysql版本 mysql_version = { '5.6':'percona-server-server-5.6', '5.7':'percona-server-server-5.7', } program_name = MySQL_IP.get(Run_IP) logfile_name = mha_log.get(program_name) cfgfile_name = mha_cfg.get(program_name) env.timeout = 30 env.command_timeout = 100 pat = re.compile('(.*:)(.*)') version_pat = re.compile('(.*)(is already the newest version*)') is_slave = 0 is_alive = 0 is_slave_status = 0 is_newest_version = 0 #更新包 @task #@roles('db_all_server') @roles('db_server') def update_list_task(): try: with settings(hide('running','stdout', 'stderr','everything'), warn_only=False): sudo("apt-get update") print yellow("%s 檢查更新完成...") %env.host except SystemExit: print red("%s 檢查更新錯誤!請檢查錯誤...") %env.host exit() #查看延遲 #@task #@roles('db_server') def get_delaytime(): with settings(hide('running','warnings','stdout','stderr'), warn_only=True): result = local("mysql --login-path=remote_mha --host=%s -e 'show slave status\\G' | grep 'Seconds_Behind_Master' " %Run_IP,capture=True) if result.failed: print red("%s get_delaytime 檢查出錯,請查找原因!\n" %Run_IP) return -1 else: delaytime = int(result.split(':')[1]) return delaytime #查看是不是從庫 #@task #@roles('db_server') def get_slave(): with settings(hide('running','warnings','stdout','stderr'), warn_only=True): result = local("mysql --login-path=remote_mha --host=%s -e \"show global status like 'Slave_running'\" " %Run_IP,capture=True) if result.failed: print red("%s get_slave 檢查出錯,請查找原因!\n" %Run_IP) return -1 else: is_slave = result.split('\t')[2] if is_slave =='ON': return 1 elif is_slave =='OFF': return 0 #查看數據庫版本 #@task #@roles('db_server') def get_mysql_version(): with settings(hide('running','warnings','stdout','stderr'), warn_only=True): result = local("mysql --login-path=remote_mha --host=%s -e \"show global variables like 'version'\" " %Run_IP,capture=True) if result.failed: print red("%s get_mysql_version 檢查出錯,請查找原因!\n" %Run_IP) return -1 else: version = result.split('\t')[2][:3] return version #檢查是否有復制狀態 #@task #@roles('db_server') def get_mysql_status(): with settings(hide('running','warnings','stdout','stderr'), warn_only=True): result = local("mysql --login-path=remote_mha --host=%s -e \"show slave status\\G \" " %Run_IP,capture=True) if result.failed: print red("%s get_mysql_status 檢查出錯,請查找原因!\n" %Run_IP) return -1 else: items = result if items: return 1 else: return 0 #檢查是否存活 #@task #@roles('db_server') def check_mysql_alive(): with settings(hide('running','warnings','stdout','stderr'), warn_only=True): result = local("mysql --login-path=remote_mha --host=%s -e \"show status like 'Uptime' \" " %Run_IP,capture=True) if result.failed: print red("%s check_mysql_alive 檢查出錯,請查找原因!\n" %Run_IP) return -1 else: is_alive = result.split("\t")[2] if is_alive: return 1 else: return 0 #開啟數據庫 #@task @roles('db_server') def start_mysql(): if Run_IP in MySQL_IP.keys(): print green("正在運行 %s 項目的 start_mysql ...\n") %MySQL_IP[Run_IP] else: print red("IP不存在,退出...\n") exit() with settings(hide('running','stdout', 'stderr','everything'), warn_only=False): global is_alive global is_slave_status is_alive = check_mysql_alive() if is_alive > 0: is_slave_status = get_mysql_status() print red("%s 數據庫已是開啟狀態,不能再start,退出...\n") %env.host exit() else: result = run("/etc/init.d/mysql start") print yellow("%s 數據庫啟動完成...\n") %env.host is_slave_status = get_mysql_status() if is_slave_status: local("mysql --login-path=remote_mha --host=%s -e 'start slave' " %Run_IP) print yellow("%s 數據庫復制開啟完成...\n") %env.host else: print red("%s 需要進行change master操作...\n") %env.host #關閉數據庫 #@task @roles('db_server') def stop_mysql(): if Run_IP in MySQL_IP.keys(): print green("正在運行 %s 項目的 stop_mysql ...\n") %MySQL_IP[Run_IP] else: print red("IP不存在,退出...\n") exit() # try: with settings(hide('running','stdout', 'stderr','everything'), warn_only=True): global is_slave is_slave = get_slave() if is_slave == 1: print green("%s 的 %s 從庫關閉\n") %(Run_IP,MySQL_IP[Run_IP]) delaytime=get_delaytime() if delaytime >0: print yellow("從庫延遲主庫 %s 秒!\n") %delaytime is_continue = raw_input("是否關閉[Y/N]?:") if is_continue.upper() == 'Y': result = run("/etc/init.d/mysql stop") print yellow("%s 數據庫關閉完成...\n") %env.host else: print red("退出...\n") exit() else: result = run("/etc/init.d/mysql stop") print yellow("%s 數據庫關閉完成...\n") %env.host elif is_slave == 0: print green("%s 的 %s 主庫關閉\n") %(Run_IP,MySQL_IP[Run_IP]) is_continue = raw_input("是否關閉[Y/N]?:") if is_continue.upper() == 'Y': result = run("/etc/init.d/mysql stop") print yellow("%s 數據庫關閉完成...\n") %env.host else: print red("退出...\n") exit() else: print red("數據庫關閉失敗!退出...\n") exit() # except SystemExit: # print red("%s 數據庫關閉失敗!請檢查錯誤...\n") %env.host #更新數據庫 #@task @roles('db_server') def update_mysql(): if Run_IP in MySQL_IP.keys(): print blue("正在運行 %s 項目的 update_mysql ...\n") %MySQL_IP[Run_IP] else: print red("IP不存在,退出...\n") exit() # try: with settings(hide('running','warnings','stdout','stderr'), warn_only=False): version = get_mysql_version() global is_slave global is_newest_version is_slave = get_slave() if is_slave == 1: print green("%s 是 %s 的一個從庫\n") %(Run_IP,MySQL_IP[Run_IP]) delaytime=get_delaytime() if delaytime >0: print yellow("從庫延遲主庫 %s 秒!\n") %delaytime is_continue = raw_input("是否更新[Y/N]?:") if is_continue.upper() == 'Y': result = sudo("apt-get -y install %s" %mysql_version[version]) if version_pat.search(result): is_newest_version = 1 print yellow("%s 不用安裝,已經是最新版本...\n") %env.host else: print yellow("%s 安裝更新完成...\n") %env.host else: print red("退出...\n") exit() else: result = sudo("apt-get -y install %s" %mysql_version[version]) if version_pat.search(result): is_newest_version = 1 print yellow("%s 不用安裝,已經是最新版本...\n") %env.host else: print yellow("%s 安裝更新完成...\n") %env.host elif is_slave == 0: print green("%s是 %s 一個主庫\n") %(Run_IP,MySQL_IP[Run_IP]) result = sudo("apt-get -y install %s " %mysql_version[version]) if version_pat.search(result): is_newest_version = 1 print yellow("%s 不用安裝,已經是最新版本...\n") %env.host else: print yellow("%s 安裝更新完成...\n") %env.host else: print red("更新錯誤,退出...\n") exit() # except SystemExit: # print red("%s 安裝更新錯誤!請檢查錯誤...\n") %env.host # exit() #清理mha log #@task #@roles('mha_manager') def clean_log(): with settings(hide('running','warnings','stdout','stderr'), warn_only=True): with lcd("/usr/local/masterha"): result = local("echo 'clean log ...' > %s" %logfile_name,capture=True) if result.failed: abort(red("MHA Log清理失敗!退出...")) print green("MHA Log 清理完成...\n") #查看mha log #@task #@roles('mha_manager') def get_mha_log(): with settings(hide('running','warnings','stdout','stderr'), warn_only=True): with lcd("/usr/local/masterha"): #local 配合capture=True stdout=local("grep 'CHANGE MASTER' %s" %logfile_name,capture=True) if stdout.failed: abort(red("MHA日志里找不到CHANGE,退出...")) log_info = pat.search(stdout).group(2) print green("在MHA Log中找到 Change Log:\n") + yellow('%s') %log_info return log_info #開啟mha #@task #@roles('mha_manager') def start_mha(): with lcd("/usr/local/masterha"): # 需要安裝supervisor local("supervisorctl start %s " %program_name) with settings(hide('running','stdout','stderr'), warn_only=True): result=local("ps -ef | grep master* | grep -v color | awk '{print $9,$10,$11}'| grep '%s'" %cfgfile_name,capture=True) if result.failed: abort(red("開啟MHA失敗!退出...")) print green("MHA Log 開啟完成...\n") #Change master #@task @roles('mha_manager') def change_master(): mha_log_info = get_mha_log() # print green("在MHA Log中找到 Change Log:\n") + yellow('%s') %query change_info = mha_log_info.replace('xxx',Rep_Password) with settings(hide('running','warnings','stdout','stderr'), warn_only=True): result = local("mysql --login-path=remote_mha --host=%s -e \" %s \" " %(Run_IP,change_info),capture=True) if result.failed: abort(red("%s Change Master Failed !請檢查錯誤...") %env.host) else: print green("%s Change Master 完成...\n") %env.host with settings(hide('running','warnings','stdout','stderr'), warn_only=True): result = local("mysql --login-path=remote_mha --host=%s -e 'start slave' " %Run_IP,capture=True) if result.failed: abort(red("%s Start Slave Failed !請先檢查錯誤...") %env.host) else: print green("%s Start Slave 完成...\n") %env.host clean_log() is_run_mha = raw_input("是否開啟MHA?[Y/N]") if is_run_mha.upper() == 'Y': start_mha() else: print red("退出...") exit() #執行更新 @task def update_task(): execute(update_mysql) global is_slave global is_newest_version if not is_newest_version: if not is_slave: is_continue = raw_input("是否進行change master[Y/N]:") if is_continue.upper() == 'Y': execute(change_master) else: exit() else: print green("%s 從庫升級,不需要change,退出...") %Run_IP exit() else: print green("%s 已經是最新版本了,退出...") %Run_IP exit() #執行關閉 @task def stop_task(): execute(stop_mysql) #執行開啟 @task def start_task(): execute(start_mysql) global is_slave_status if not is_slave_status: is_continue = raw_input("是否進行change master[Y/N]:") if is_continue.upper() == 'Y': execute(change_master) else: exit() else: print green("%s 從庫啟動,不需要change,退出...") %Run_IP exit()
該腳本執行在Ubuntu上,並且用於Percona5.6、5.7的相關操作,以及mha的切換開啟工作。需要注意的是,mysql無密碼登陸用了mysql_config_editor和遠程開啟mha后台運行程序時用到的進程監控(deamon運行)Supervisor程序。
總結:
通過上面的實例和應用說明,大致的介紹了Fabric基本常見的用法,一些比較深入的用法可以看官方文檔(好東西太多了,需要的時候再上去扒一下),遇到相關問題可以留言,一起討論學習。