程序員瑞士軍刀之 Fabric

Fabric是一個Python庫, 也是一個命令行工具, 通過 SSH 來做應(yīng)用程序的部署和系統(tǒng)管理任務(wù)

它可以執(zhí)行本地的遠(yuǎn)程的系統(tǒng)命令, 上傳下載文件, 以及其他能用Python編程完成的任務(wù)

其實(shí)它是一個工具框架, 啟動時會默認(rèn)執(zhí)行一個 python 文件 fabfile.py

安裝

先安裝好 python3.x僧凰, 再安裝 fabric3

pip install fabric3

快速上手

簡單寫個小例子

$vi fabfile
    from fabric.api import *

    env.hosts = ['10.224.64.106']
    env.user   = "root"
    env.password = "pass"
    
    def freedisk(param='-h'):
        cmd = 'df ' + param
        run(cmd)
    
    def listfile(folder='~'):
        cmd = 'ls -l ' + folder
        run(cmd)
        
    def pullcodes(folder='/workspace/cpp/snippets'):
        with cd(folder):
            run("git pull origin master")
# 察看遠(yuǎn)程服務(wù)器上的磁盤剩余空間
$ fab listfile:folder=/home/walter    
  • 為安全起見, 不用在文件中存放密碼, 在命令行提示輸入

    $ fab -u root -I -H 10.224.64.106 freedisk

  • 更好的做法是把本機(jī)私鑰預(yù)先拷貝到目標(biāo)服務(wù)器上, 這樣就不用輸入密碼了

    1. 在本機(jī)上生成公鑰  ~/.ssh/id_rsa.pub
    ssh-keygen -t rsa
    
    2. 拷貝此公鑰到目標(biāo)服務(wù)器 10.224.64.106 上
    scp id_rs.pub root@10.224.64.106:/root
    
    3. 目標(biāo)服務(wù)器 10.224.64.106 上
    cat id_rsa.pub >> ~/.ssh/authorized_keys
    chmod 700 ~/.ssh/authorized_keys

常用方法

  • run (fabric.operations.run)
  • sudo (fabric.operations.sudo)
  • local (fabric.operations.local)
  • get (fabric.operations.get)
  • put (fabric.operations.put)
  • prompt (fabric.operations.prompt)
  • reboot (fabric.operations.reboot)

常用函數(shù)

  • cd (fabric.context_managers.cd)
  • lcd (fabric.context_managers.lcd)
  • path (fabric.context_managers.path)
  • settings (fabric.context_managers.settings)
  • prefix (fabric.context_managers.prefix)

高階用法

設(shè)置角色role來指定遠(yuǎn)程的服務(wù)器范圍
或者直接用字典由輸入?yún)?shù)指定, 例如:

# usage:  
# fab localpull:rtc
# fab checkfiles:hf2
from fabric.api import *
from fabric.context_managers import *
from fabric.contrib.console import confirm 


env.user = 'root'
env.roledefs = {
    'qa': ['root@10.224.57.202:22'],
    'dev': ['root@10.224.64.106:22']
}

env.passwords = { 
    'root@10.224.57.202:22': 'pass',
    'root@10.224.64.106:22': 'pass',
    'root@10.224.64.107:22': 'pass'
  } 

@roles('dev')
@task
def localpull(app='web'):
    if app == 'web':
        code_dir = '/workspace/walter/hfweb'
        with lcd(code_dir):
            local("git pull origin master")
    elif app == 'rtc':
        code_dir = '/workspace/walter/hfrtc'
        with lcd(code_dir):
            local("git pull origin master")
            local("git branch -l")

test_servers = {'hf1':['root@10.224.64.46:22'],
    'hf2':['root@10.224.64.106:22'],
    'hf3':['root@10.224.64.107:22']}

@task
def listfiles():
    run("ls -l")

@task
def checkfiles(target_env='hf2'):
    execute("listfiles", hosts=test_servers[target_env])

示例

例1:批量上傳下載文件

from fabric.api import *
from fabric.context_managers import *
from fabric.contrib.console import confirm 

env.user='root'
env.hosts=['10.224.64.106'] 
env.passwords = { 
    'root@10.224.64.106:22': 'password'
  } 

local_dir='/workspace/cpp/codelab'
remote_dir = '/opt/cpp/codelab'
file_list = [
    'src/FileUtils.cpp',
    'src/FileUtils.h',
    'src/Makefile.am',
    'src/StringUtils.cpp'
]

@task
def hostinfo():
    run('uname -s')
          
@task
def upload(): #upload file task 
    with cd(remote_dir) :
        for filename in file_list:
            local_file  = local_dir  + "/" + filename
            remote_file = remote_dir + "/" + filename
            #print local_file, " to ", remote_file
            with settings(warn_only=True):    #when upload error,continue 
                result = put(local_file, remote_file) 
            if result.failed and not confirm("put file failed,Continue[Y/N]?"): 
                abort("Aborting file put task!")


@task
def download(): #upload file task 
    with cd(remote_dir) :
        for filename in file_list:
            local_file  = local_dir  + "/" + filename
            remote_file = remote_dir + "/" + filename
            #print local_file, " to ", remote_file
            with settings(warn_only=True):    #when upload error,continue 
                result = get(remote_file,local_file)
            if result.failed and not confirm("put file failed,Continue[Y/N]?"): 
                abort("Aborting file put task!")

例2: 創(chuàng)建 gitbook 目錄, 以使用 markdown 來寫作

from fabric.api import *
#import SimpleHTTPServer
import http.server
import socketserver
import os
from utils import chapters

BOOK_CONTENT_FOLDER = './book/content'
BOOK_OUTPUT_FOLDER = './book/output'

@task
def build_book(is_open_index=False):
    local( "rm -rf %s" % BOOK_OUTPUT_FOLDER)
    local( "mkdir -p %s" % BOOK_OUTPUT_FOLDER)

    cmd = "gitbook build %s %s --log=debug --debug" % (BOOK_CONTENT_FOLDER, BOOK_OUTPUT_FOLDER)
    local(cmd)
    if is_open_index:
        local("open %s/index.html" % BOOK_OUTPUT_FOLDER)

@task
def build_pdf():
    cmd = "gitbook pdf ./book/content/"
    local(cmd)


@task
def build_epub():
    cmd = "gitbook epub ./book/content/"
    local(cmd)
    local("ebook-convert book.epub book.docx")

@task
def init_book():
    create_templates()


@task
def serve_book(port=8000):

    web_dir = os.path.join(os.path.dirname(__file__), BOOK_OUTPUT_FOLDER)
    os.chdir(web_dir)

    Handler = http.server.SimpleHTTPRequestHandler
    httpd = socketserver.TCPServer(("", port), Handler)
    print("serving at port", port)
    httpd.serve_forever()

def md2rst(mdfile):
    rstfile = mdfile[:-3];
    cmd = "pandoc --from=markdown --to=rst --output=%s.md %s" % (rstfile, mdfile)
    print(cmd)
    local(cmd)

def rst2md(rstfile):
    mdfile = rstfile[:-3];
    cmd = "pandoc --from=rst --to=markdown --output=%s.md %s" % (mdfile, rstfile)
    print(cmd)
    local(cmd)

def create_templates():
    create_chapter(chapters.folder1, chapters.files1)
    create_chapter(chapters.folder2, chapters.files2)
    create_chapter(chapters.folder3, chapters.files3)
    create_chapter(chapters.folder4, chapters.files4)
    create_chapter(chapters.folder5, chapters.files5)

def create_chapter(folder, files):
    print("--- create chapter in %s ---" % folder)
 
    cmd = "mkdir -p %s/%s" % (BOOK_CONTENT_FOLDER, folder)
    local(cmd)

    for file in files:
        cmd = "touch %s/%s/%s" % (BOOK_CONTENT_FOLDER, folder, file)
        local(cmd)


例3. 用 Fabric 玩轉(zhuǎn) docker 基本命令

from fabric.api import *
from fabric.context_managers import *
from fabric.contrib.console import confirm
import os, subprocess

local_path = os.path.dirname(os.path.abspath(__file__))
local_dir = os.getcwd()

backend_service_ports={
"tomcat": "8080",
"kanban": "8080",
"cassandra": "9042",
"elasticsearch": "9200 9300",
"influxdb": "8086",
"postgres": "5432",
"rabbitmq": "4369 5671 5672 15671 15672 25672",
"redis": "6379",
"riak": "8087 8098",
"kafka-zookeeper": "2181 9092"
}

need_print_cmd=True
only_display_cmd=False

docker_image_prefix="walterfan-"
docker_container_prefix="msa-"

restart_policy="--restart always"
jenkins_volume_mapping = "/o/jenkins:/var/jenkins_home"
jenkins_container_name="jenkins"
jenkins_image_name="walterfan-jenkins"

def run_cmd(cmd):
    if(need_print_cmd):
        print(cmd)
    if not only_display_cmd:
        local(cmd)


@task
def jenkins_build():
    docker_build("jenkins")

@task
def jenkins_run(listen_port="1980"):
    cmd = "docker run %s -v %s -p %s:8080 -p 50000:50000 --name=%s -d %s" % (restart_policy, jenkins_volume_mapping, listen_port, jenkins_container_name, jenkins_image_name)
    run_cmd(cmd)


@task
def jenkins_start():
    cmd = "docker start %s" % jenkins_container_name
    run_cmd(cmd)

@task
def jenkins_stop():
    cmd = "docker stop %s" % jenkins_container_name
    local(cmd)
    #cmd = "docker cp jenkins-container:/var/log/jenkins/jenkins.log jenkins.log"
    #local(cmd)

@task
def jenkins_remove():
    docker_remove(jenkins_container_name)

@task
def jenkins_commit(message):
    cmd = "docker commit -m \"%s\" %s walterfan/jenkins:1.0" % (message, jenkins_container_name)

@task
def jenkins_check():
    cmd = "docker exec %s ps -ef | grep java" % jenkins_container_name
    print(cmd)
    local(cmd)

    cmd = "docker exec %s cat /var/jenkins_home/secrets/initialAdminPassword" % jenkins_container_name
    print(cmd)
    local(cmd)


#-----------------------------grafana influx --------------------------#
@task
def graflux_build():
    cmd = "docker build --tag %s docker/%s" % ("graflux", "graflux")
    run_cmd(cmd)


@task
def graflux_start():
    grafana_port = 3000
    influx_api_port = 8086
    influx_web_port = 8083
    cmd = "docker run --name local-graflux -d -p %d:3000 -p %d:8086 -p %d:8083 graflux" % (grafana_port, influx_api_port, influx_web_port)
    print(cmd)
    local(cmd)

@task
def influx():
    """
    execute the influx command in graflux docker
    """
    cmd = "docker exec -it local-graflux influx"
    run_cmd(cmd)

@task
def graflux_bash():
    """
    execute the /bin/bash in graflux docker
    """
    cmd = "docker exec -it local-graflux /bin/bash"
    run_cmd(cmd)

@task
def graflux_stop():
    #cmd = "docker stop local-graflux"
    docker_remove("local-graflux")

@task
def redis_cli():
    cmd = "docker exec -it local-redis redis-cli"
    local(cmd)
@task
def redis_bash():
    cmd = "docker exec -it local-redis /bin/bash"
    local(cmd)


@task
def cassandra_cql(cql=''):
    cmd = "docker exec -it local-cassandra /usr/bin/cqlsh "
    if cql:
        cmd = cmd + " -e '%s'" % cql
    local(cmd)

@task
def mysql_cli(usr='root'):
    cmd = "docker exec -it local-mysql /usr/bin/mysql -u %s -p" % usr
    local(cmd)

@task
def mysql_bash():
    cmd = "docker exec -it local-mysql /bin/bash"
    local(cmd)
#---------------------------- freeswitch -------------------------------#
#bettervoice/freeswitch-container   1.6.16
@task
def freeswitch_start():
    cmd = "sudo docker run --name freeswitch -p 5060:5060/tcp -p 5060:5060/udp -p 5080:5080/tcp -p 5080:5080/udp -p 8021:8021/tcp \
    -p 7443:7443/tcp -p 60535-65535:60535-65535/udp \
    -v %s/etc/freeswitch:/usr/local/freeswitch/conf bettervoice/freeswitch-container:1.6.16" % local_path
    print(cmd)
    local(cmd)

@task
def freeswitch_stop():
    docker_remove(freeswitch)
#-----------------------------------------------------------#
@task
def start_services():
    cmd = "docker-compose up -d"
    run_cmd(cmd)

@task
def stop_services():
    cmd = "docker-compose down -v"
    run_cmd(cmd)
#----------------------------- general command ----------------

@task
def link_war(war_package, war_name):
    cmd = "docker exec tomcat ln -s %s/%s /usr/local/tomcat/webapps/%s" % (local_path, war_package, war_name)
    local(cmd)

@task
def deploy_war(war_package, war_name):
    cmd = "docker cp %s/%s tomat:/usr/local/tomcat/webapps/%s" % (local_path, war_package, war_name)
    local(cmd)

@task
def undeploy_war(war_name):
    cmd = "docker exec tomcat rm -rf /usr/local/tomcat/webapps/%s" % (war_name)
    local(cmd)
    cmd = "docker exec tomcat rm -f /usr/local/tomcat/webapps/%s.war" % (war_name)
    local(cmd)
#----------------------------- general commands ---
def get_container_id(container_name):
    str_filter = "-aqf name=%s" % container_name;
    arr_cmd = ["docker", "ps", str_filter]
    container_id = subprocess.check_output(arr_cmd).strip()
    return container_id

def get_port_args(service_name="kanban", increment=0):
    str_port = ""
    ports = backend_service_ports[service_name]
    if ports:
        arr_port = ports.split("\\s")
        for port in arr_port:
            str_port = str_port + "-p %s:%d" %(port, int(port) + int(increment))
    return str_port


@task
def docker_rename(old_name, new_name):
    cmd = "docker tag %s %s" % (old_name, new_name)
    run_cmd(cmd)


@task
def docker_build(service_name="local-tomcat"):
    docker_image_name = docker_image_prefix + service_name
    cmd = "docker build --tag %s docker/%s" % (docker_image_name, service_name)
    run_cmd(cmd)


@task
def docker_run(service_name="local-tomcat", volume_args="-v /workspace:/workspace"):
    port_args = get_port_args(service_name)

    docker_container_name = docker_container_prefix + service_name
    docker_image_name = docker_image_prefix + service_name

    cmd = "docker run %s %s %s -d --name %s %s" % (restart_policy, volume_args, port_args, docker_container_name, docker_image_name)
    run_cmd(cmd)

@task
def docker_stop(container_name="local-tomcat"):
    cmd = "docker stop %s" % (container_name)
    run_cmd(cmd)

@task
def docker_list():
    cmd = "docker ps"
    run_cmd(cmd)

@task
def docker_exec(container_name="local-tomcat", instruction="/bin/bash"):

    instruction = "/bin/bash"
    cmd = "docker exec -it %s %s" % (container_name,    instruction)
    run_cmd(cmd)

@task
def docker_remove(container_name="kanban"):
    cmd1 = "docker kill %s|| true" % container_name
    run_cmd(cmd1)

    cmd2 = "docker rm -v %s || true" % container_name
    run_cmd(cmd2)

@task
def docker_commit(container_id, image_name, message=""):
    cmd = "docker commit -m \"%s\" %s %s" % (message, container_id, image_name)
    run_cmd(cmd)

@task
def docker_install():
    #cmd  ="brew remove docker && brew upgrade"
    cmd = "brew cask install docker && open /Applications/Docker.app"
    run_cmd(cmd)

@task
def help():
    print("examples:\tfab docker_run:cassandra,\"-v /opt:/workspace\" ")

FAQ

問題1: 切換環(huán)境

寫一個字典對象,在參數(shù)里傳入環(huán)境類型, 再組成所需的環(huán)境變量


environments = {
    "integration": {
         "ApiServiceUrl" :"https://checklist-int.example.com/checklist/api/v1", 
         "environment":"integration"
    },
    "production":{
         "ApiServiceUrl" :"https://checklist.example.com/checklist/api/v1", 
         "environment":"production"
    },
    "lab":{
         "ApiServiceUrl" :"https://checklist-lab.example.com/checklist/api/v1", 
         "environment":"lab"
    },
    "local":{
         "ApiServiceUrl" :"https://localhost:2008/checklist/api/v1", 
         "environment":"lab"
    }

}

def get_env_vars(env_type):
    defined_vars = " "
    for key, value in environments[env_type].iteritems():
        defined_vars = defined_vars + " -D%s=%s" %(key, value)
    return defined_vars;

問題2: 如何讀取配置文件

  • 以 json file 為例
class ProvisionConfig:
    def __init__(self, json_file):

        self.read_config(json_file)

        self.base_path = self.config_data['basePath']
        self.username = self.config_data['username']
        self.locale = self.config_data['locale']

    def read_config(self, json_file):
        json_data=open(json_file)
        self.config_data = json.load(json_data)

問題3: 如何獲取命令行的輸出結(jié)果

比如想獲得 docker 容器的id, 可以用如下命令

docker ps -aqf name=jenkins

用 subprocess.check_output 方法就可以獲取輸出珊肃, 以下面這個函數(shù)為例

def get_container_id(container_name):
    str_filter = "-aqf name=%s" % container_name;
    arr_cmd = ["docker", "ps", str_filter]
    container_id = subprocess.check_output(arr_cmd).strip()
    return container_id

問題4: fab put error: paramiko.ssh_exception.SSHException: Channel closed

解決方法:

  • 編輯 /etc/ssh/sshd_config:
    <pre>
    vi /etc/ssh/sshd_config
    </pre>

  • 加上一行 Subsystem sftp internal-sftp
    <pre>
    Port 22
    Protocol 2
    LogLevel INFO
    X11Forwarding no
    MaxAuthTries 4
    IgnoreRhosts yes
    HostbasedAuthentication no
    PermitRootLogin yes
    PermitEmptyPasswords no
    PermitUserEnvironment no
    Ciphers aes128-ctr,aes192-ctr,aes256-ctr
    ClientAliveInterval 600
    Banner /etc/issue
    Subsystem sftp internal-sftp
    </pre>

  • 保存并重啟 SSH server:

    service sshd restart 

參考鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末渣淳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子但汞,更是在濱河造成了極大的恐慌,老刑警劉巖陵霉,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件被盈,死亡現(xiàn)場離奇詭異析孽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)害捕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門绿淋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人尝盼,你說我怎么就攤上這事吞滞。” “怎么了盾沫?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵裁赠,是天一觀的道長。 經(jīng)常有香客問我赴精,道長佩捞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任一忱,我火速辦了婚禮谭确,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘逐哈。我一直安慰自己芬迄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布禀梳。 她就那樣靜靜地躺著杜窄,像睡著了一般算途。 火紅的嫁衣襯著肌膚如雪塞耕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天郊艘,我揣著相機(jī)與錄音荷科,去河邊找鬼纱注。 笑死胆胰,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瞎嬉。 我是一名探鬼主播厚柳,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼别垮!你這毒婦竟也來了碳想?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤逊移,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后胳泉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體岩遗,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年钳吟,在試婚紗的時候發(fā)現(xiàn)自己被綠了窘拯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坝茎。...
    茶點(diǎn)故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡嗤放,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出次酌,到底是詐尸還是另有隱情舆乔,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布吊宋,位于F島的核電站颜武,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鳞上。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一唾糯、第九天 我趴在偏房一處隱蔽的房頂上張望涡上。 院中可真熱鬧,春花似錦芋酌、人聲如沸雁佳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽星澳。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間阀坏,已是汗流浹背笆檀。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留士修,地道東北人樱衷。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像箫老,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容

  • sshd_config配置詳解 重啟SSH服務(wù) service sshd restart 名稱 sshd_conf...
    一指彈風(fēng)閱讀 22,958評論 0 4
  • 由于SFTP是SSH的一部分(與傳統(tǒng)的FTP沒有任何關(guān)系),因此绅这,配置SFTP不需要傳統(tǒng)的FTP服務(wù)器軟件。僅需要...
    陽明散人閱讀 2,401評論 0 4
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理度苔,服務(wù)發(fā)現(xiàn)浑度,斷路器,智...
    卡卡羅2017閱讀 134,633評論 18 139
  • 我還沒有資格保護(hù)你 ------金鐘大 我們終究還是陌路人 -------...
  • 有人說 十八歲之前 淋一場雨 翹一堂課 談次戀愛 還有 剪頭短發(fā) 從那以后 我就成為了 別人口中的那個短發(fā)姑娘 雪...
    配么閱讀 140評論 0 1