fabric執行遠程Linux命令


前陣子要用腳本遠程重啟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運維之paramikopython遠程連接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.roledefsenv.passwords指定好了用戶名、端口和密碼,這時上面2個函數合並,並且對外只顯示一個執行函數(@task),還要注意的是因為各自的執行函數處於不同的roles下執行的,要放到一個函數里執行,需要添加:execute(),這樣可以在一個執行函數里操作多個遠程的機器。 

復制代碼
...
...

@task
def go():
    execute(get_memory)
    execute(mkfile_task)
復制代碼

效果:

  View Code

實例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基本常見的用法,一些比較深入的用法可以看官方文檔(好東西太多了,需要的時候再上去扒一下),遇到相關問題可以留言,一起討論學習。

參考文檔:

Fabric官方文檔

使用 Fabric 批量執行服務器任務

PYTHON FABRIC實現遠程操作和部署

自動化運維工具Fabric


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM