Fabric 应用示例

下面介绍三个比较典型的应用 Fabric 的示例,涉及文件上传与校验、环境部署、代码发布的功能,读者可以在此基础进行功能扩展,写出更加贴近业务场景的工具平台。

示例1:文件打包、上传与校验

我们时常做一些文件包分发的工作,实施步骤一般是先压缩打包,再批量上传至目标服务器,最后做一致性校验。本案例通过 put() 方法实现文件的上传,通过对比本地与远程主机文件的 md5,最终实现文件一致性校验。详细源码如下:

#!/usr/bin/env python
from fabric.api import *
from fabric.context_managers import *
from fabric.contrib.console import confirm

env.user = 'root'
env.hosts = ['192.168.1.21', '192.168.1.22']
env.password = 'LKs934jh3'


@task
@runs_once
def tar_task():  # 本地打包任务函数,只限执行一次
    with lcd("/data/logs"):
        local("tar -czf access.tar.gz access.log")


@task
def put_task():  # 上传文件任务函数
    run("mkdir -p /data/logs")
    with cd("/data/logs"):
        with settings(warn_only=True):  # put(上传)出现异常时继续执行,非终止
            result = put("/data/logs/access.tar.gz", "/data/logs/access.tar.gz")
        if result.failed and not confirm("put file failed, Continue[Y/N]?"):
            abort("Aborting file put task!")  # 出现异常时,确认用户是否继续,(Y继续)


@task
def check_task():  # 校验文件任务函数
    with settings(warn_only=True):
        # 本地local命令需要配置capture=True才能捕获返回值
        lmd5 = local("md5sum /data/logs/access.tar.gz", capture=True).split(' ')[0]
        rmd5 = run("md5sum /data/logs/access.tar.gz").split(' ')[0]
    if lmd5 == rmd5:  # 对比本地及远程文件md5信息
        print("OK")
    else:
        print("ERROR")

本示例通过定义三个功能任务函数,分别实现文件的打包、上传、校验功能,且三个功能相互独立,可分开运行,如:

fab -f simple3.py tar_task    #文件打包
fab -f simple3.py put_task    #文件上传
fab -f simple3.py check_task  #文件校验

当然,我们也可以组合在一起运行,再添加一个任务函数 go,代码如下:

@task
def go():
    tar_task()
    put_task()
    check_task()

运行 fab -f simple4.py go 就可以实现文件打包、上传、校验全程自动化。

示例2:部署LNMP业务服务环境

业务上线之前最关键的一项任务便是环境部署,往往一个业务涉及多种应用环境,比如 Web、DB、PROXY、CACHE 等,本示例通过 env.roledefs 定义不同主机角色,再使用 "@roles('webservers')" 修饰符绑定到对应的任务函数,实现不同角色主机的部署差异,详细源码如下:

fabric/simple5.py
#!/usr/bin/env python
from fabric.colors import *
from fabric.api import *

env.user = 'root'
env.roledefs = {  # 定义业务角色分组
    'webservers': ['192.168.1.21', '192.168.1.22'],
    'dbservers': ['192.168.1.23']
}

env.passwords = {
    'root@192.168.1.21:22': 'SJk348ygd',
    'root@192.168.1.22:22': 'KSh458j4f',
    'root@192.168.1.23:22': 'KSdu43598'
}


@roles('webservers')  # webtask任务函数引用'webservers'角色修饰符
def webtask():  # 部署nginx php php-fpm等环境
    print
    yellow("Install nginx php php-fpm...")
    with settings(warn_only=True):
        run("yum -y install nginx")
        run("yum -y install php-fpm php-mysql php-mbstring php-xml php-mcrypt php-gd")
        run("chkconfig --levels 235 php-fpm on")
        run("chkconfig --levels 235 nginx on")


@roles('dbservers')  # dbtask任务函数引用'dbservers'角色修饰符
def dbtask():  # 部署mysql环境
    print(yellow("Install Mysql..."))
    with settings(warn_only=True):
        run("yum -y install mysql mysql-server")
        run("chkconfig --levels 235 mysqld on")


@roles('webservers', 'dbservers')  # publictask任务函数同时引用两个角色修饰符
def publictask():  # 部署公共类环境,如epel、ntp等
    print(yellow("Install epel ntp..."))
    with settings(warn_only=True):
        run("rpm -Uvh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm")
        run("yum -y install ntp")


def deploy():
    execute(publictask)
    execute(webtask)
    execute(dbtask)

本示例通过角色来区别不同业务服务环境,分别部署不同的程序包。我们只需要一个 Python 脚本就可以完成不同业务环境的定制。

示例3:生产环境代码包发布管理

程序生产环境的发布是业务上线最后一个环节,要求具备源码打包、发布、切换、回滚、版本管理等功能,本示例实现了这一整套流程功能,其中版本切换与回滚使用了 Linux 下的软链接实现。详细源码如下:

#!/usr/bin/env python
from fabric.api import *
from fabric.colors import *
from fabric.context_managers import *
from fabric.contrib.console import confirm
import time

env.user = 'root'
env.hosts = ['192.168.1.21', '192.168.1.22']
env.password = 'LKs934jh3'

env.project_dev_source = '/data/dev/Lwebadmin/'  # 开发机项目主目录
env.project_tar_source = '/data/dev/releases/'  # 开发机项目压缩包存储目录
env.project_pack_name = 'release'  # 项目压缩包名前缀,文件名为release.tar.gz

env.deploy_project_root = '/data/www/Lwebadmin/'  # 项目生产环境主目录
env.deploy_release_dir = 'releases'  # 项目发布目录,位于主目录下面
env.deploy_current_dir = 'current'  # 对外服务的当前版本软链接

env.deploy_version = time.strftime("%Y%m%d") + "v2"  # 版本号


@runs_once
def input_versionid():  # 获得用户输入的版本号,以便做版本回滚操作
    return prompt("please input project rollback version ID:", default="")


@task
@runs_once
def tar_source():  # 打包本地项目主目录,并将压缩包存储到本地压缩包目录
    print(yellow("Creating source package..."))
    with lcd(env.project_dev_source):
        local("tar -czf %s.tar.gz ." % (env.project_tar_source + env.project_pack_name))
    print(green("Creating source package success!"))


@task
def put_package():  # 上传任务函数
    print(yellow("Start put package..."))
    with settings(warn_only=True):
        with cd(env.deploy_project_root + env.deploy_release_dir):
            run("mkdir %s" % (env.deploy_version))  # 创建版本目录
    env.deploy_full_path = env.deploy_project_root + env.deploy_release_dir + "/" + env.deploy_version

    with settings(warn_only=True):  # 上传项目压缩包至此目录
        result = put(env.project_tar_source + env.project_pack_name + ".tar.gz",
                     env.deploy_full_path)
    if result.failed and no("put file failed, Continue[Y/N]?"):
        abort("Aborting file put task!")

    with cd(env.deploy_full_path):  # 成功解压后删除压缩包
        run("tar -zxvf %s.tar.gz" % (env.project_pack_name))
        run("rm -rf %s.tar.gz" % (env.project_pack_name))

    print(green("Put & untar package success!"))


@task
def make_symlink():  # 为当前版本目录做软链接
    print(yellow("update current symlink"))
    env.deploy_full_path = env.deploy_project_root + env.deploy_release_dir + "/" + env.deploy_version
    with settings(warn_only=True):  # 删除软链接,重新创建并指定软链源目录,新版本生效
        run("rm -rf %s" % (env.deploy_project_root + env.deploy_current_dir))
        run("ln -s %s %s" % (env.deploy_full_path, env.deploy_project_root + env.deploy_current_dir))
    print(green("make symlink success!"))


@task
def rollback():  # 版本回滚任务函数
    print(yellow("rollback project version"))
    versionid = input_versionid()  # 获得用户输入的回滚版本号
    if versionid == '':
        abort("Project version ID error,abort!")

    env.deploy_full_path = env.deploy_project_root + env.deploy_release_dir + "/" + versionid
    run("rm -f %s" % env.deploy_project_root + env.deploy_current_dir)
    run("ln -s %s %s" % (env.deploy_full_path, env.deploy_project_root + env.deploy_current_dir))  # 删除软链接,重新创建并指定软链源目录,新版本生效
    print(green("rollback success!"))


@task
def go():  # 自动化程序版本发布入口函数
    tar_source()
    put_package()
    make_symlink()

本示例实现了一个通用性很强的代码发布管理功能,支持快速部署与回滚,无论发布还是回滚,都可以通过切换 current 的软链来实现,非常灵活。该功能的流程图如图7-5所示。

image 2023 12 08 16 59 42 682
Figure 1. 图7-5 生产环境代码包发布管理流程图

在生产环境中 Nginx 的配置如下:

server_name domain.com
index index.html index.htm index.php;
root /data/www/Lwebadmin/current;

将站点根目录指向 "/data/www/Lwebadmin/current",由于使用 Linux 软链接做切换,管理员的版本发布、回滚操作用户无感知,同时也规范了我们业务上线的流程。

参考提示