当前位置:   article > 正文

python使用fabric之只看这篇就够了_python fabric

python fabric

fabric 是什么

项目地址在: https://gitee.com/xiaofeipapa/fabric-tutorial

建议先下载代码, 边看边运行.

fabric 是python的一个组件, 能够在远程服务器运行你写好的脚本, 也就是说这是一个运维自动化工具.

以前运维人员在远程服务器上安装软件, 要先登录 -> 写脚本(如果习惯本地写程序, 那么还要上传程序到服务器) -> 针对服务器反馈输入数据 -> 调试 …

如果用fabric, 这个流程就是全部自动化了. fabric的程序开发流程一般是这样:

  1. 先在远程服务器试验你的脚本/想法.
  2. 用python fabric 实现它们.
  3. 运行结果, 观察是否正确.

如果只需要操作一两台服务器, 那么手动操作肯定比写程序快. 如果要操作几十上百台机器, 那么当然是写好程序, 然后让他们自动在几十台服务器上面跑更方便. (程序员就是要学会偷懒! )

2021版教程说明

网上能找到的fabric 大多东摘西抄, 程序不能运行. 锅主要由fabric 来背, 一开始他们并不支持 python3, 所以当python3 流行之后, 以前针对 python2 的fabric教程就基本不能用了; 于是有热心程序员们搞了个非官方的fabric3 , 写了一些教程(很少且很多文章是错的), 但后来fabric 官方支持了 python3 , 也出了个教程…

于是在互联网上至少存在3种fabric 教程. 针对python2的, 针对非官方python3版本的, 还有官方python3 官方版本的, 初学者大概会被弄得要发疯了吧…

所以我将这份教程命名为2021版, 表示这是今年最新的版本. 同时这是针对官方python3版本的教材, 如果你不熟悉fabric, 就不用看网上其他教程了, 看这一篇即可.

最后, 这篇教程用的是 python3 的fabric 版本. 因此你本机要先安装python3. 如果是linux 版本, 一般是这样:

sudo apt install python3-pip
  • 1

安装完成之后, 只要用:

sudo pip3 install xxx
  • 1

所安装的包/组件就都是python3版本了.

如果是windows系统, 假设你下载安装的就是python3的软件, 那么当你使用 pip install 的时候, 这个pip 就是3的版本. 所以不需要再多设置.

如何安装

linux:

sudo pip3 install fabric
  • 1

windows:

pip install fabric
  • 1

实验环境

如果你很熟悉docker 或者自己就有云服务器, 那么试验环境就有了. 如果你没有(特别是windows 用户, 很难装好docker) 怎么办.

你可以使用我的这个虚拟机镜像. 首先你要安装 virtualbox, 下载地址在这里: https://www.virtualbox.org/wiki/Downloads

在某云盘下载我的这个虚拟机镜像:
这是个 xubuntu 系统(ubuntu的变种), 我最爱的系统之一.

安装之后用virtualbox 把它导入, 一步步操作如下:

打开virtualbox之后, 选择New
image.png

Type选择Linux, Version 选择Ubuntu(64bit) , 然后点击Next.

image.png
内存默认 1024 就够了, 点Next

image.png
选择"Use an existing … " , 然后点右边那个文件夹图标

在打开的界面中, 选择 Add
image.png

然后选择刚才下载的xub.vdi文件, 如图:
image.png

然后点 choose
image.png

这时候你会看到 create 已经变得可选了, 点它. 就会看到虚拟机已经成功创建.

image.png

点击右上方的Settings, 然后在弹出的界面设置网卡:
image.png
(中文名字应该是桥接网络) . 这样选了之后, 你的电脑(称为宿主机) 就可以访问虚拟机里的操作系统了.

双击xub 启动系统, 你会看到 xubuntu的界面. 这个系统有两个用户的信息, 分别是:
xiaofeipapa / 密码 aa
root / 密码 test

在xubuntu的桌面点击鼠标右键, 选择 Open Terminal Here, 打开命令行, 输入:

ifconfig 
  • 1

查看自己的ip是多少. 在我这里是 192.168.135.24 .

然后在宿主机里打开命令行, 输入:

ssh root@192.168.135.24 
  • 1

然后输入root的密码(test ) , 就可以看到ssh 登录成功了. 好了, 你拥有一个实验环境, 可以在上面试验fabric了.

实验环境信息总结

ip : 192.168.135.24 ( 也许你的机器会不同, 在虚拟机里用ifconfig来查看)
用户: root / test

fabric 基础

以下程序的参数都针对我那个虚拟机镜像, 如果你有自己的虚拟机或者云服务器, 改相应参数即可.

起步: 连接服务器, 执行命令

#! /usr/bin/python3
# -*- coding: UTF-8 -*-
"""
  作者: 小肥爬爬
  简书: https://www.jianshu.com/u/db796a501972
  gitee: https://gitee.com/xiaofeipapa/python-toolkit
  您可以自由转载此博客文章, 恳请保留原链接, 谢谢!
"""
from fabric import Connection


def do_it():

    host = '192.168.135.24'
    user = 'root'
    password = 'test'

    # ssh 连接的正确姿势
    conn = Connection(host=host, user=user, connect_kwargs={'password': password})

    # 在远程机器运行命令(用run方法), 并获得返回结果
    # hide 表示隐藏远程机器在控制台的输出, 达到静默的效果
    # 默认 warn是False, 如果远程机器运行命令出错, 那么本地会抛出异常堆栈. 设为True 则不显示这堆栈.
    cmd = 'ls /tmp'
    result = conn.run(cmd, hide=True, warn=True, encoding='utf-8')

    # 正常运行时, 信息在 stdout里
    print('-------- 下面是 stdout 信息')
    print(result.stdout.strip())

    # 出错时, 信息在 stderr 里
    print('-------- 下面是 stderr 信息')
    print(result.stderr.strip())


if __name__ == '__main__':
    do_it()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

注释里已经把api用法说得很清楚了. 请阅读并运行程序加深印象.

封装工具类 fab_utils.py

在我们写程序的时候, 其实大部分时候都运行正常. 如果每次run之后都去判断 stdout, stderr … 代码很快变得混乱不堪. 所以我的选择是将之封装成方法, 使之更加轻便. 这个文件命名为 fab_utils.py, 内容如下:

#! /usr/bin/python3
# -*- coding: UTF-8 -*-
"""
  作者: 小肥爬爬
  简书: https://www.jianshu.com/u/db796a501972
  gitee: https://gitee.com/xiaofeipapa/python-toolkit
  您可以自由转载此博客文章, 恳请保留原链接, 谢谢!

  将fabric api 再封装一层, 使之更好用
"""


class FabException(Exception):
    def __init__(self, msg):
        Exception.__init__(self, msg)


# 运行远程命令
def run(conn, cmd, hide=True, warn=True):

    r = conn.run(cmd, encoding='utf8', hide=hide, warn=warn)

    result, err = r.stdout.strip(), r.stderr.strip()
    if err:
        raise FabException(err)

    return result
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

以后, 我会把这个工具库逐渐扩大, 加入更多有用的方法. 经过这种方式, fabric的大多数功能就变成你的工具库, 可以轻松写出大多数的运维程序.

写一个程序 s2_test_fab_utils.py 来测试:

#! /usr/bin/python3
# -*- coding: UTF-8 -*-
"""
  作者: 小肥爬爬
  简书: https://www.jianshu.com/u/db796a501972
  gitee: https://gitee.com/xiaofeipapa/python-toolkit
  您可以自由转载此博客文章, 恳请保留原链接, 谢谢!
"""
from fabric import Connection
import fab_utils


def do_it():

    host = '192.168.135.24'
    user = 'root'
    password = 'test'

    # ssh 连接的正确姿势
    conn = Connection(host=host, user=user, connect_kwargs={'password': password})

    # 结果变得更简单了
    cmd = 'ls /tmp'
    result = fab_utils.run(conn, cmd)

    # 正常运行时, 信息在 stdout里
    print('-------- 下面是结果')
    print(result)

    # 出错时, 程序会抛出异常
    # 来一个出错的例子, tmp/xiaofeipapa 目录不存在
    cmd = 'ls /tmp/xiaofeipapa'
    result = fab_utils.run(conn, cmd)
    print(result)


if __name__ == '__main__':
    do_it()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

运行这个结果, 你能看到第一个例子成功输出结果, 第二个例子抛出异常, 这就是我想要的效果.

ssh无密码登录ssh

登录密码写在参数里当然不安全. 用linux 系统的同学都知道, 可以配置无密码方式访问服务器, 避免暴露密码明文在参数里. (windows我暂时不知道怎么办, 有待请教. )

首先在宿主机生成密钥, 一路按回车:

ssh-keygen -t rsa
  • 1

然后运行这个脚本:

ssh-copy-id root@192.168.135.24
  • 1

输入root的登录密码 test, 以后就可以无密码访问服务器了. 相应地, fabric的连接变成如下:

conn = Connection(host=host, user=user)
  • 1

不用输入密码了.

运行自定义shell脚本

像ls, cd 都是bash 预先提供的命令. 如果要运行自己写的shell怎么办? 有两个办法: 如果sh 文件很大, 那么先在本地写好, 然后传到服务器上运行. 如果shell很少, 直接用这个方式运行:

conn.client.exec_command(cmd)
  • 1

下面的这个例子演示了如何运行短的shell脚本:

#! /usr/bin/python3
# -*- coding: UTF-8 -*-
"""
  作者: 小肥爬爬
  简书: https://www.jianshu.com/u/db796a501972
  gitee: https://gitee.com/xiaofeipapa/python-toolkit
  您可以自由转载此博客文章, 恳请保留原链接, 谢谢!
"""
from fabric import Connection
import fab_utils


def do_it():

    host = '192.168.0.12'
    user = 'root'

    # ssh 连接的正确姿势
    conn = Connection(host=host, user=user)

    # 运行shell之前, 要随便运行一个命令, 获得运行环境
    fab_utils.run(conn, 'uname -a')

    # 运行shell脚本
    # 在shell里 [ -d xxx ] 表示检查文件夹是否存在
    # [ -d xxx ] 表示检查文件是否存在
    cmd = '[ -d /tmp ] && echo ok'
    _stdin, _stdout, _stderr = conn.client.exec_command(cmd)
    result = _stdout.read().strip().decode('utf8')

    print('--- std out: ')
    print(result)


if __name__ == '__main__':
    do_it()


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

检查文件/夹是否存在

bash没有提供检查文件夹是否存在, 文件是否存在的方法, 而这两个方法是经常需要使用的. 所以我们可以用简单的shell脚本来封装这两个方法. 在 fab_utils.py 里加入如下方法:

# 检查文件夹是否存在def is_dir_exists(conn, dir_path):    cmd = '[ -d ' + dir_path + ' ] && echo ok'    _stdin, _stdout, _stderr = conn.client.exec_command(cmd)    result = _stdout.read().strip().decode('utf8')    return result == 'ok'# 检查文件文件是否存在def is_file_exists(conn, file_path):    cmd = '[ -f ' + file_path + ' ] && echo ok'    _stdin, _stdout, _stderr = conn.client.exec_command(cmd)    result = _stdout.read().strip().decode('utf8')    return result == 'ok'# 判断是否有该文件夹, 不存在时可以通过参数控制生成文件夹def check_has_dir(conn, dir_path, create_if_not_exist=True):    if not is_dir_exists(conn, dir_path):        if create_if_not_exist:            run(conn, 'mkdir -p ' + dir_path)# 判断是否有该文件, 不存在时可以通过参数控制生成文件夹def check_has_file(conn, file_path, create_if_not_exist=True):    if not is_dir_exists(conn, file_path):        if create_if_not_exist:            run(conn, 'touch ' + file_path)
  • 1

然后写一个python文件来检查:

#! /usr/bin/python3
# -*- coding: UTF-8 -*-
"""
  作者: 小肥爬爬
  简书: https://www.jianshu.com/u/db796a501972
  gitee: https://gitee.com/xiaofeipapa/python-toolkit
  您可以自由转载此博客文章, 恳请保留原链接, 谢谢!
"""
from fabric import Connection
import fab_utils
import os, os.path


def do_it():

    host = '192.168.0.12'
    user = 'root'

    # ssh 连接的正确姿势
    conn = Connection(host=host, user=user)

    # 运行shell之前, 要随便运行一个命令, 获得运行环境
    fab_utils.run(conn, 'uname -a')

    # 检查文件夹
    dir_path = '/root/data'
    fab_utils.check_has_dir(conn, dir_path)

    # 查看创建文件夹结果
    result = fab_utils.run(conn, 'ls ' + dir_path)
    print(result)

    # 检查文件
    file_path = os.path.join(dir_path, 'done.txt')
    fab_utils.check_has_file(conn, file_path)

    # 查看创建文件结果
    result = fab_utils.run(conn, 'ls ' + file_path)
    print(result)


if __name__ == '__main__':
    do_it()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

安全的删除

我们都知道在服务器上尽量不要用rm命令, 否则删错文件那就完了. 所以在用fabric的时候我们同样要注意这个问题. 在fab_utils.py 里加入这个方法:

# 在服务器安全删除文件/文件夹的方法
def safe_rm(conn, file_path):

    # 先检查是否存在
    try:
        run(conn, 'ls ' + file_path)
    except FabException:
        print(file_path + ' 不存在')
        return

    # 在/tmp下生成当天目录
    result = run(conn, 'date +%Y-%m-%d')

    trash_dir = '/tmp/my_trash/' + result
    check_has_dir(conn, trash_dir)

    # 在当天下生成uuid, 确保mv 不会失败
    result = run(conn, 'uuidgen')
    trash_dir = trash_dir + '/' + result
    check_has_dir(conn, trash_dir)

    run(conn, 'mv ' + file_path + ' ' + trash_dir)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

写一个文件来测试. 程序在 s5_rm_test.py

#! /usr/bin/python3
# -*- coding: UTF-8 -*-
"""
  作者: 小肥爬爬
  简书: https://www.jianshu.com/u/db796a501972
  gitee: https://gitee.com/xiaofeipapa/python-toolkit
  您可以自由转载此博客文章, 恳请保留原链接, 谢谢!
"""
from fabric import Connection
import fab_utils
import os, os.path


def do_it():

    host = '192.168.0.12'
    user = 'root'

    # ssh 连接的正确姿势
    conn = Connection(host=host, user=user)

    # 运行shell之前, 要随便运行一个命令, 获得运行环境
    fab_utils.run(conn, 'uname -a')

    # 删除之前创建的文件夹 /root/data
    dir_path = '/root/data'
    fab_utils.safe_rm(conn, dir_path)

    if fab_utils.is_dir_exists(conn, dir_path):
        print('--- 文件夹还在')
    else:
        print('--- 文件夹已经删除')


if __name__ == '__main__':
    do_it()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

写入文本文件

在服务器上我们通常会有写文件的任务, 例如写入环境变量到配置文件, 在fabric 里可以用简单的shell命令来完成:

conn.run("echo 'hello' >> /tmp/test.txt")
  • 1

这个命令又是双引号又是单引号, 所以还是封装起来更好用. 在fab_utils.py 里加入如下方法:

# 追加一行到文件末尾
def append_line(conn, file_path, content):
    if not is_file_exists(conn, file_path):
        return

    cmd = "echo '" + content + "' >> " + file_path
    run(conn, cmd)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

然后写一个文件来测试, 代码在 s6_env_var.py

#! /usr/bin/python3
# -*- coding: UTF-8 -*-
"""
  作者: 小肥爬爬
  简书: https://www.jianshu.com/u/db796a501972
  gitee: https://gitee.com/xiaofeipapa/python-toolkit
  您可以自由转载此博客文章, 恳请保留原链接, 谢谢!
"""
from fabric import Connection
import fab_utils


def do_it():

    host = '192.168.0.12'
    user = 'root'

    # ssh 连接的正确姿势
    conn = Connection(host=host, user=user)

    # 运行shell之前, 要随便运行一个命令, 获得运行环境
    fab_utils.run(conn, 'uname -a')

    # 在/tmp 下生成 test_rc 文件,
    file_path = '/root/test_rc'
    fab_utils.check_has_file(conn, file_path)

    # 写入内容到文件里

    fab_utils.append_line(conn, file_path, "#==其他配置信息1")
    fab_utils.append_line(conn, file_path, "#==其他配置信息2")
    fab_utils.append_line(conn, file_path, "#==其他配置信息3")
    fab_utils.append_line(conn, file_path, "\n")
    fab_utils.append_line(conn, file_path, "#==JAVA安装信息")
    fab_utils.append_line(conn, file_path, "JAVA_HOME=XXX")
    fab_utils.append_line(conn, file_path, "CLASS_PATH=YYY")
    fab_utils.append_line(conn, file_path, "export PATH=$PATH:JAVA_HOME/bin")

    # 查看文件内容
    result = fab_utils.run(conn, 'cat ' + file_path)
    print(result)


if __name__ == '__main__':
    do_it()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

自动响应

有些命令如 passwd , adduser 会等候你继续输入下一步. 像这种需要手动响应的自动化程序应该怎么写? 在fabric 里这样做:

from invoke import Responder

# 其他代码
resp1 = Responder(
        pattern=r'Enter new UNIX password:',
        response=pwd + '\n'
    )

    resp2 = Responder(
        pattern=r'Retype new UNIX password:',
        response=pwd + '\n'
    )

    conn.run('passwd ' + username, pty=True, hide=True, watchers=[resp1, resp2])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

Responder 是 invoke 框架的一部分(fabric 已经包括invoke框架) . 它的使用方法如下:

  1. pattern: 正则表达式, 用来匹配命令行的输出. 如果里面有需要转义的字符, 千万要记得转义.
  2. response: 需要输入的内容.
    这两个参数结合起来的意思是: 如果匹配到命令输出, 需要输入信息的时候, 就自动输入response定好的字符串.

如果不确定某些命令的pattern和resonse 是什么. 可以先在服务器手动输入命令看看结果, 然后再写程序. 如果程序多次需要输入, 那么就要写多个responder, 用个数组包起来.

注意: 如果程序长时间没有反馈, 证明你的pattern 写得有问题.

最常见的原因就是转义字符没有进行处理. 下面这是一个例子:

# 在yum上安装软件
def yum_install(conn, cmd):

    resp = Responder(
        # pattern=r'Is this ok [y/d/N]:',   # 错误例子: 没有正则转义
        pattern=r'Is this ok \[y/d/N\]:',
        response='y\n'
    )

    conn.run(cmd, pty=True, watchers=[resp], hide=True)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

下面是一个完整的例子: 我们将用户xiaofeipapa的密码改为 bb, 整个过程是自动化无人工干预的:

#! /usr/bin/python3
# -*- coding: UTF-8 -*-
"""
  作者: 小肥爬爬
  简书: https://www.jianshu.com/u/db796a501972
  gitee: https://gitee.com/xiaofeipapa/python-toolkit
  您可以自由转载此博客文章, 恳请保留原链接, 谢谢!
"""
from fabric import Connection
import fab_utils
from invoke import Responder


def do_it():

    host = '192.168.0.12'
    user = 'root'

    # ssh 连接的正确姿势
    conn = Connection(host=host, user=user)

    # 运行shell之前, 要随便运行一个命令, 获得运行环境
    fab_utils.run(conn, 'uname -a')

    # 将密码改成bb
    username = 'xiaofeipapa'
    pwd = 'bb'
    resp1 = Responder(
        pattern=r'Enter new UNIX password:',
        response=pwd + '\n'
    )

    resp2 = Responder(
        pattern=r'Retype new UNIX password:',
        response=pwd + '\n'
    )

    conn.run('passwd ' + username, pty=True, hide=True, watchers=[resp1, resp2])


if __name__ == '__main__':
    do_it()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

fabric 实战案例: 在远程服务器上安装 jdk环境

前面零零散散地介绍了很多fabric的用法, 现在用这个案例把它综合起来: 在远程服务器(虚拟机) 里安装jdk环境.

这个任务做开发/运维的同学应该都不陌生. java技术栈的运维架构通常是1个nginx, 后端挂若干个安装jdk服务器, 以此达到负载均衡的目的.它的任务可以分解为:

  1. 在远程服务器上下载解压jdk包.
  2. 在配置文件写入若干个环境变量(如JAVA_HOME, CLASSPATH) , 指向jdk的解压路径.
  3. 创建新的登录用户, 开发者用它来登录机器, 发布jar 包.

当然其中还包括一些弯弯绕绕的必要任务. 例如怎么判断任务已经运行过, 所以不会再重复运行? 怎么判断jdk包已经存在, 不用再重新下载? 怎么判断环境变量已经写入到配置文件, 不用重复写入?

这些技巧一一列举如下.

怎么知道任务是否第一次执行?

思路: 给任务一个名字, 例如 install_jdk. 第一次在目标机器运行之后, 就会在 /root 目录下生成这个文件: /root/data/install_jdk/done.txt . 所以程序可以判断这个文件是否存在, 如果不存在表示此任务还没执行过. 如果存在, 那么可以通过命令行参数来指定是否再次运行任务.

代码如下:

    def _prepare(self, rerun):
        """
        检查任务是否已经启动过
        :return:
        """
        # 必须要先run连接远程服务器, 否则 conn.client.exec_command 会报错
        fab_utils.run(self.conn, 'ls /tmp')

        # 生成 /root/data 目录
        base_dir = '/root/data'
        fab_utils.create_if_remote_dir_not_exist(self.conn, base_dir)

        # 检查该task 是否已经执行过. 如果已经执行过, 在 /root/data/task 目录下有 done.txt 文件
        task_home = base_dir + '/' + self.task
        fab_utils.create_if_remote_dir_not_exist(self.conn, task_home)
        self.task_home = task_home

        task_file = task_home + '/done.txt'
        if not fab_utils.is_remote_file_exist(self.conn, task_file):
            return

        # 如果存在, 又不指定重新运行, 则退出
        if not rerun:
            title('此程序已经在目标机器运行过了. 如果想再次运行, 请设置启动参数 -r true')
            exit(0)

# ...... 其他代码
# 命令行指定启动参数

if __name__ == '__main__':

    import argparse

    parser = argparse.ArgumentParser('传入参数:***.py')
    parser.add_argument('-r', '--rerun', default=True)    # 开发时用
    # parser.add_argument('-r', '--rerun', default=False)

    args = parser.parse_args()

    rerun = args.rerun

    print('---- 参数 rerun: ', rerun)

    do_it(args)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

判断jdk存在,不用重复下载

思路: 当第一次下载好jdk包的时候, 我们可以拿到该包的md5, 然后如果服务器上已经存在jdk包并且md5一致, 那么就不用再下载. 否则开始下载jdk.

代码如下:

        with self.conn.cd(self.task_home):

            # 检查下载jdk
            file_path = self.task_home + '/' + jdk_file
            file_md5 = 'ef599e322eee42f6769991dd3e3b1a31'
            down_link = 'wget https://repo.huaweicloud.com/java/jdk/8u181-b13/' + jdk_file
            fab_utils.check_download(self.conn, file_path, file_md5, down_link)

            # 创建jvm目录
            fab_utils.create_if_remote_dir_not_exist(self.conn, jvm_dir)

            # 解压. 华为的这个包解压后名字不同, 所以手动指定一个目录
            hint('正在解压 jdk ......')
            fab_utils.run(self.conn, 'tar xvf ' + jdk_file + ' -C ' + jvm_dir + '  --strip-components 1 ')

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

写入配置文件与重复检查

有两个思路:

  1. 利用sed, 如果发现已经有相应的环境变量, 将之删除重新写入.
  2. 用 echo 显示 JAVA_HOME, 如果有值

第一个思路容易错删其他内容, 所以后来采用了第二个思路. 代码如下:

        run_result = conn.run('echo $JAVA_HOME ', hide=True).stdout.strip()
        if run_result == jvm_dir:
            hint(system_file + ' 已经包含 JAVA_HOME, 不需要重新设置')
        else:
            # 追加内容到系统配置
            conn.run("echo '# ===== java 配置' >> " + system_file)
            conn.run("echo 'export JAVA_HOME=" + jvm_dir + "' >> " + system_file)

            # 将 exporter jar 也加入到 classpath 里
            export = 'export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/jre/lib/rt.jar:'\
                     + exporter_jar_path
            conn.run("echo '" + export + "' >> " + system_file)
            conn.run("echo 'export PATH=$PATH:$JAVA_HOME/bin' >> " + system_file)

            # 使环境变量生效
            conn.run('source ' + system_file)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

这里还有个小窍门, linux 那么多配置文件如 /etc/profile , /etc/bashrc , 究竟应该写到哪个文件? 查了一下资料, /etc/profile 只在用户第一次登录的时候起作用, 所以应该写到 /etc/bashrc 里.

怎么生成新用户

思路: 生成新用户很简单, 需要考虑的是给它设定密码. 考察了一下linux上生成密码的方式, 最后决定用 pwgen . 用法:

# 生成10个字符的密码, 个数为1
sudo apt install pwgen 
pwgen -s 10 1
  • 1
  • 2
  • 3

代码大概长这样:

    def add_user(self, username):
        """
        增加用户, 并设置密码.
        :param username: 用户名
        :return: 登录密码
        """

        if self._is_user_exist(username):
            title('用户 ' + username + ' 已经存在')
            fab_utils.close_connection(self.conn)
            return None

        # 生成用户. 该命令只在centos 测试过
        fab_utils.run(self.conn, 'adduser ' + username)

        # 生成10位密码
        cmd = 'pwgen -s 10 1'
        password = fab_utils.run(self.conn, cmd)

        # 更改密码
        fab_utils.yum_passwd(self.conn, username, password)

        # 退出登录
        fab_utils.close_connection(self.conn)

        # 登录进行测试
        self.verify_login(username, self.host, password)

        return password
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

完整代码

项目地址: https://gitee.com/xiaofeipapa/fabric-tutorial

请看 setup_java.py 文件

总结

通过这么多方法和整个案例的展示, 你应该掌握了fabric 90%的用法, 剩下的就靠你自己探索了. 希望你用fabric玩得愉快~

记得给个star和赞啊, 少年~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/447414
推荐阅读
相关标签
  

闽ICP备14008679号