程序员瑞士军刀之 Fabric

Fabric是一个Python库, 也是一个命令行工具, 通过 SSH 来做应用程序的部署和系统管理任务

它可以执行本地的远程的系统命令, 上传下载文件, 以及其他能用Python编程完成的任务

其实它是一个工具框架, 启动时会默认执行一个 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")
# 察看远程服务器上的磁盘剩余空间
$ fab listfile:folder=/home/walter    
  • 为安全起见, 不用在文件中存放密码, 在命令行提示输入

    $ fab -u root -I -H 10.224.64.106 freedisk

  • 更好的做法是把本机私钥预先拷贝到目标服务器上, 这样就不用输入密码了

    1. 在本机上生成公钥  ~/.ssh/id_rsa.pub
    ssh-keygen -t rsa
    
    2. 拷贝此公钥到目标服务器 10.224.64.106 上
    scp id_rs.pub root@10.224.64.106:/root
    
    3. 目标服务器 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)

常用函数

  • 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)

高阶用法

设置角色role来指定远程的服务器范围
或者直接用字典由输入参数指定, 例如:

# 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: 创建 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 玩转 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: 切换环境

写一个字典对象,在参数里传入环境类型, 再组成所需的环境变量


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: 如何获取命令行的输出结果

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

docker ps -aqf name=jenkins

用 subprocess.check_output 方法就可以获取输出, 以下面这个函数为例

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 

参考链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • sshd_config配置详解 重启SSH服务 service sshd restart 名称 sshd_conf...
    一指弹风阅读 21,161评论 0 4
  • 由于SFTP是SSH的一部分(与传统的FTP没有任何关系),因此,配置SFTP不需要传统的FTP服务器软件。仅需要...
    阳明散人阅读 2,272评论 0 4
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 我还没有资格保护你 ------金钟大 我们终究还是陌路人 -------...
    繁星点点照亮星空阅读 479评论 3 2
  • 有人说 十八岁之前 淋一场雨 翘一堂课 谈次恋爱 还有 剪头短发 从那以后 我就成为了 别人口中的那个短发姑娘 雪...
    配么阅读 123评论 0 1