随手记录第十三话 -- 关于Python自动化部署神器fabric的应用与差异

1.前言

Python 的 Fabric 库在系统管理和自动化任务方面发挥着重要作用,随着其发展,出现了版本的迭代。

2.版本区分

  • 语法风格: Fabric 2 采用了更现代、简洁的 Python 语法,相比 1 版本更加清晰易读。
  • 任务task: 2 版本对任务的定义和组织方式进行了优化,使其更具逻辑性和结构性。
  • 上下文: 在上下文的处理上,2 版本有了更明确和方便的机制。

3.Fabric 1 版本的特点

较为传统的任务声明和执行模式,代码风格可能相对较为复杂。

  • 代码示列
from fabric.api import *

env.hosts = ['192.168.168.131']
env.port = 22
env.user = 'root'
env.password = '123456'

def start_test():
	# jar目录
    localRootPath = "/Users/admin/python_test"
    liunxPath = "~/local/system"
    run("mkdir -p %s" % liunxPath)
    with lcd(localRootPath):
        local("ls")
        put("*.jar", liunxPath)
        run("ls")
  • 打开命令行,执行这个任务
fab start_test
  • 输出日志
[192.168.168.131] Executing task 'start_test'
[192.168.168.131] run: mkdir -p ~/local/system
[localhost] local: ls
abc.txt         ccc.yml         member.jar      system.jar      test            test222
[192.168.168.131] put: /Users/admin/python_test/member.jar -> /home/suadmin/local/system/member.jar
[192.168.168.131] put: /Users/admin/python_test/system.jar -> /home/suadmin/local/system/system.jar
[192.168.168.131] run: ls
[192.168.168.131] out: ftmo-service  jdk-8u301-linux-x64.tar.gz  local  logs  nacos

本地的指令lcd,local,远程的指令使用的是cd,run,上传文件用的put,本地参数默认支持通配符。

4.Fabric 2 版本

使用了注解来定义任务,代码更加明了。明显的区分了本地和远程的操作对象,也就是class区分了。

  • 代码示列
import glob
import os
from fabric import task, Connection

dict = {
    "dev": {
        "host": "192.168.168.131",
        "port": 22,
        "user": "root",
        "connect_kwargs": {"password": "123456"}
    }
}

#这里的c 默认是本地操作对象  远程对象需要自己连接 env参数可以通过指令传参进去
@task
def liunxTest(c, env):
    print("开始部署本地环境:" + env)
    ssh = dict[env]
    #建立连接
    remoteConn = Connection(ssh["host"], user=ssh["user"], port=ssh["port"], connect_kwargs=ssh["connect_kwargs"])
    c.run("ls /Users/admin/python_test")
    
    #2版本的put不支持通配符 需要自己获取文件或者目录传到服务器 匹配方法在下面
    localRootPath = "/Users/admin/python_test"
    # fileList = searchFile(localRootPath, "*.jar")
    fileList = searchFileAll(localRootPath)
    print(fileList)
	
	#服务器连接上的家目录 不能用“~”了 可以用相对路径
    liunxPath = "local/system"
    remoteConn.run("mkdir -p %s" % liunxPath)
    with remoteConn.cd(liunxPath):
        for file in fileList:
        	#获取相对目录路径
            relPath = os.path.relpath(file, localRootPath)
            print("执行文件:", file,relPath)
            #如果是目录 则创建 否则才上传
            if os.path.isdir(file):
                remoteConn.run("mkdir -p %s" % relPath)
            else:
                remoteConn.put(file, os.path.join(liunxPath,relPath))
        remoteConn.run("ls")

# 查指定目录下指定后缀的文件
def searchFile(path, suffix):
    files = glob.glob(os.path.join(path, suffix))
    files = [filePath for filePath in files if os.path.isfile(filePath)]
    return files
    
def searchFileAll(path):
    """递归搜索目录中的文件和文件夹"""
    matches = []
    for root, dirnames, filenames in os.walk(path):
        for dirname in dirnames:
            matches.append(os.path.join(root, dirname))
        for filename in filenames:
            matches.append(os.path.join(root, filename))
    return matches
  • 启动任务
# 启动任务 传参 dev
fab2 liunxTest dev
  • 日志输出
开始部署本地环境:dev
['/Users/admin/python_test/test', '/Users/admin/python_test/test222', '/Users/admin/python_test/.DS_Store', '/Users/admin/python_test/abc.txt', '/Users/admin/python_test/member.jar', '/Users/admin/python_test/system.jar', '/Users/admin/python_test/ccc.yml', '/Users/admin/python_test/test/test1', '/Users/admin/python_test/test/test.txt', '/Users/admin/python_test/test/test1/test1.txt', '/Users/admin/python_test/test222/1222.txt']
执行文件: /Users/admin/python_test/test test
执行文件: /Users/admin/python_test/test222 test222
执行文件: /Users/admin/python_test/abc.txt abc.txt
执行文件: /Users/admin/python_test/member.jar member.jar
执行文件: /Users/admin/python_test/system.jar system.jar
执行文件: /Users/admin/python_test/ccc.yml ccc.yml
执行文件: /Users/admin/python_test/test/test1 test/test1
执行文件: /Users/admin/python_test/test/.DS_Store test/.DS_Store
执行文件: /Users/admin/python_test/test/test.txt test/test.txt
执行文件: /Users/admin/python_test/test/test1/test1.txt test/test1/test1.txt
执行文件: /Users/admin/python_test/test222/1222.txt test222/1222.txt
abc.txt
ccc.yml
member.jar
system.jar
test
test222

5.使用场景与选择

对于新的项目,如果追求简洁和现代的代码风格,Fabric 2 是更好的选择。
若要维护基于 1 版本的旧项目,则需根据实际情况决定是否迁移到 2 版本。

6.Python3虚拟环境及fabric下载配置

Python3自行下载,虚拟环境配置,Python2忽略这个

# 虚拟环境
# python3 -m venv ./python3_venv
# 激活
# source python3_venv/bin/activate
# 在当前窗口 调用的python和pip会下载到对应的虚拟环境中
#  https://pypi.org/  python仓库
#  pip install fabric==1.14.0  #1版本
#  pip install fabric2   #2版本
# IDEA新增pythonSDK 目录选择  /Users/admin/python3_venv/bin/python3.9
# 虚拟环境下载的包会在/Users/admin/python3_venv/lib/python3.9/site-packages
# 关闭窗口则会退出虚拟环境 在idea中下载以SDK为准

7.总计

无论是 Fabric 1 版本还是 2 版本,都有其适用的场景和价值。理解它们之间的区别,能帮助我们在不同的项目中做出合适的选择,充分发挥 Fabric 在自动化和系统管理方面的强大功能。随着技术的不断进步,我们可以根据需求灵活运用,以实现更高效的开发和管理。
希望这篇文章能为你深入了解 Python Fabric 的不同版本提供有益的参考。

8.自己用的,备忘一下

  • 配置文件 dev_path.conf
[member]
localPath=/Users/admin/apple/java-work/test/test-service
dirName=member
sshPath=~/test-service
  • python代码 基于1版本的(因为先入为主),支持vue代码发布
#fab start agent 走这个
if env.get('env') == 'agent':
    env.host_string = 'ubuntu@xx.xx.xx.xx'
    env.key_filename = "/Users/admin/.ssh/id_rsa"
else:
    # 其他走这个
    env.hosts = ['192.168.168.131']
    env.port = 22
    env.user = 'root'
    env.password = '123456'

class Docker:
    def __init__(self, configPath):
        self.configPath = configPath
        self.con = ConfigParser.SafeConfigParser()
        self.con.read(self.configPath)

    def packet(self):
        localPath = self.con.get(self.serviceName, "localPath")
        print('#########################################')
        print("Beginning Pack", localPath)
        if self.isPacket == 1:
            with lcd(localPath):
                local("ls")
                if self.con.has_option(self.serviceName, "web"):
                    # web打包 npm run build:prod
                    a = local("npm run build:prod", capture=True)
                else:
                    # mvn clean install
                    a = local("mvn clean install -Dmaven.test.skip=true", capture=True)
                if "BUILD FAILURE" in a:
                    print(a)
                    print("BUILD Failed")
                    return
            print(u"BUILD SUCCESS")
            return "success"

    def commitFileToServer(self):
        '''
        上传本地文件到服务器
        '''
        localPath = self.con.get(self.serviceName, "localPath")
        sshPath = self.con.get(self.serviceName, "sshPath")
        dirName = self.con.get(self.serviceName, "dirName")
        liunxPath = sshPath + "/" + dirName
        run("mkdir -p %s" % liunxPath)
        if self.con.has_option(self.serviceName, "web"):
            # 取配置路径下的dist目录
            with lcd(localPath + "/dist"):
                local("ls")
                with cd(liunxPath):
                    run("rm -fr ./*")
                    put("*", liunxPath)
            try:
                with lcd(localPath + "/deploy/" + self.active):
                    local("ls")
                    with cd(sshPath):
                        run("rm -fr *.yaml")
                        put("*", sshPath)
            except:
                print("无deploy配置,跳过")
            print(u"web资源上传完成!")
        else:
            # 服务端
            with cd(liunxPath):
                with lcd(localPath + "/" + dirName + "/target"):
                    local("ls")
                    run("pwd")
                    run("mkdir -p logs")
                    # .jar结尾的复制到服务器
                    run("rm -fr *.sh *.jar *.yaml ./lib Dockerfile")
                    put("*.jar", liunxPath)
                    try:
                        put("./lib", liunxPath)
                    except:
                        print("无lib目录,跳过")
                # 项目下的deploy下复制docker相关的
                with lcd(localPath + "/" + "deploy" + "/" + self.active):
                    put("Dockerfile", liunxPath)
                    # sed -i -e 's/${app_name}/advert/g' -e 's/${active}/dev/g' Dockerfile
                    # 改为docker-compose传参
                    run("sed -i -e 's/${app_name}/%s/g' -e 's/${active}/%s/g' Dockerfile" % (dirName, self.active))
                    put(dirName + "/*", liunxPath)
                    put("build.sh", liunxPath)
                    run("dos2unix build.sh")
                    run("chmod 777 *.yaml")
                    run("chmod 777 *.sh")
            print(u"jar包上传完成!")

    # 停止 && 启动 容器
    def sshCmd(self):
        sshPath = self.con.get(self.serviceName, "sshPath")
        dirName = self.con.get(self.serviceName, "dirName")
        if self.con.has_option(self.serviceName, "web"):
            with cd(sshPath):
                run("sudo docker-compose stop")
                run("sudo docker-compose up -d")
                time.sleep(2)
                run("sudo docker-compose logs -f")
        else:
            with cd(sshPath + "/" + dirName):
                run("./build.sh start %s" % self.serviceName)
                time.sleep(2)
                run("sudo docker-compose up -d")
                time.sleep(2)
                run("sudo docker-compose logs -f --tail 100")

    def selectPath(self):
        arr = {}
        redaFile = open(self.configPath, "r")
        inputSteam = redaFile.readlines()
        index = 1
        for i in range(inputSteam.__len__()):
            lineStr = inputSteam[i]
            if lineStr.startswith("["):
                line = re.sub(r'\[|\]\n', "", lineStr)
                arr[str(index)] = line
                index += 1
        redaFile.close()
        print(u"请选择发布的项目:")
        print(arr)
        path = raw_input()
        print(u"是否需要打包:")
        isPacket = input("1.yes 2.no:")
        active = {"1": "dev", "2": "test", "3": "prod"}
        print(u"环境选择:")
        print(active)
        a = input()
        if arr[path] != None:
            self.serviceName = arr[path]
            self.isPacket = isPacket
            self.active = active[str(a)]
            return
        else:
            print(u"输入选项不存在,请重新选择")
            self.createPath()


def start():
    path = "./path.conf"
    if env.get('env') != None:
        path = "./%s_path.conf" % (env.get('env').split("_")[0])
    print(u"读取配置 %s" % path)
    docker = Docker(path)
    print(u"选择项目\n")
    docker.selectPath()
    print(u"打包")
    docker.packet()
    print(u"文件传输")
    docker.commitFileToServer()
    print(u"执行liunx命令")
    docker.sshCmd()

并不是每个人通用,谨慎copy

以上就是本文的全部内容了!

上一篇:随手记录第十二话 – JDK8-21版本的新增特性记录(Lambda,var,switch,instanceof,record,virtual虚拟线程等)
下一篇:随手记录第十四话 – 在 Spring Boot 3.2.3 中使用 springdoc-openapi-starter-webmvc-ui

烹羊宰牛且为乐,会须一饮三百杯

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值