K8S系列文章之 自动化运维利器 Fabric

Fabric 主要用在应用部署与系统管理等任务的自动化,简单轻量级,提供有丰富的 SSH 扩展接口。在 Fabric 1.x 版本中,它混杂了本地及远程两类功能;但自 Fabric 2.x 版本起,它分离出了独立的 Invoke 库,来处理本地的自动化任务,而 Fabric 则聚焦于远程与网络层面的任务。

为了做到这点,Fabric 主要依赖另一大核心组件 Paramiko,它是基于 SSH 协议的远程控制模块,Fabric 在其基础上封装出了更加友好的接口,可以远程执行 Shell 命令、传输文件、批量操作服务器、身份认证、多种配置与设置代理,等等。

一、Fabric 的版本区分

Fabric 自身存在着 2 个大版本:Fabric 1 和 Fabric 2,而在这个库的基础上,还有两个很容易混淆的相关库:Fabric2 和 Fabric3(注意这里的数字是库名的一部分)。

它们的区分如下:

Fabric 1.x:支持 Python 2.5-2.7,但不支持 Python 3

Fabric 2.x:支持 Python 2.7 与 3.4+,但不兼容 Fabric 1.x 的 fabfile

Fabric2:等同于 Fabric 2.x,为了使不同版本共存(装一个 1.x 旧版本,再装它作为新版本)

Fabric3:一个基于 Fabric 1.x 的 fork(非官方),兼容 Python 2&3,兼容 Fabric1.x 的 fabfile

综上可见,我们推荐使用官方的 Fabric 2.x 系列版本,但同时要注意,某些过时的教程可能是基于早期版本的(或非官方的 Fabric3,也是基于 Fabric 1.x),需要注意识别。

例如,在 Fabric 1.x 系列中这么写导入:from fabric.api import run;在新版本中将报错:"ImportError: No module named api"(PS:可根据是否有 fabric.api 来判断 Fabric 的版本,就像在 Python 中根据 print 语句或 print 函数来判断版本一样)。同时,由于新版本不支持老版本的 fabfile,在使用时就可能报错:"No idea what 'xxx' is!"

Fabric 2 是非兼容性版本,相比于前个版本,它主要改进的点有:

支持 Python 2.7 与 3.4+

线程安全,取消了多进程的并发实现

API 围绕 fabric.connection.Connection 进行了重组

全面修改了命令行解析器,允许在每个任务的基础上使用规则的 GNU/POSIX 风格的标志和选项(不再需要 fab mytask:weird = custom,arg = format)

可以声明前置任务与后置任务

......(官方列了​ ​10几条​​ [1],本文不一一罗列)

之前介绍过的 invoke,就是在开发 Fabric 2 时被分离出来的,具体的原因可参见​ ​这个回答​​ [2]。总而言之,在使用 Fabric 时,应该注意版本差异的问题。

二、Fabric 的基本用法

安装PIP

pip是Python包管理工具 可以安装各类软件

yum -y install python-pip

升级PIP

pip install --upgrade pip

检查PIP版本

pip --versionpip 19.3.1 from /usr/lib/python2.7/site-packages/pip (python 2.7)

安装docker compose,自行更改版本号.

pip install -U docker-compose==1.24.1

检查docker compose版本

docker-compose version

安装fabric

1、安装

首先是安装:​​pip intall fabric​​ ,安装后,可在命令行窗口查看版本信息:

>>> fab -V
Fabric 2.5.0
Paramiko 2.7.1
Invoke 1.4.0

执行"fab -V",以上结果可看出我安装的是 Fabric 2.5.0 版本,同时可看到它的两个核心依赖库 Paramiko 及 Invoke 的版本信息。

2、一个简单的例子

Fabric 主要用于远程任务,即要对远程服务器进行操作,下面是一个简单的例子:# 可使用任意的文件名

from fabric import Connection

host_ip = '47.xx.xx.xx'  # 服务器地址
user_name = 'root' # 服务器用户名
password = '****'  # 服务器密码
cmd = 'date'  # shell 命令,查询服务器上的时间

con = Connection(host_ip, user_name, connect_kwargs={'password': password})
result = con.run(cmd, hide=True)

print(result)

以上代码,通过账号+密码登录到远程服务器,然后执行​​date​​命令,查看服务器的时间,执行结果:

Command exited with status 0.
=== stdout ===
Fri Feb 14 15:33:05 CST 2020

(no stderr)

现在打印的结果中,除了服务器时间,还有一些无关的信息。这是因为它打印的"result"是一个"fabric.runners.Result"类,我们可以把其中的信息解析出来:

print(result.stdout)  # Fri Feb 14 15:33:05 CST 2020
print(result.exited)  # 0
print(result.ok)      # True
print(result.failed)  # False
print(result.command) # date
print(result.connection.host) # 47.xx.xx.xx

上述代码使用了 Connection 类及其 run() 方法,可在连接的服务器上运行 shell 命令。如果需要用管理员权限,则需替换成 sudo() 方法。如果要在本地执行 shell 命令,则需替换成 local() 方法。

除此之外,还有 get()、put() 等方法,详见下文介绍。

3、命令行用法

上例代码可写在任意的 .py 脚本中,然后运行该脚本,或者稍微封装下再导入到其它脚本中使用。

另外,Fabric 还是个命令行工具,可以通过​​fab​​命令来执行任务。我们稍微改造一下上例的代码:# 文件名:fabfile.py

from fabric import Connection
from fabric import task

host_ip = '47.xx.xx.xx'  # 服务器地址
user_name = 'root' # 服务器用户名
password = '****'  # 服务器密码
cmd = 'date'  # shell 命令,查询服务器上的时间

@task
def test(c):
    """
    Get date from remote host.
    """
    con = Connection(host_ip, user_name, connect_kwargs={'password': password})
    result = con.run(cmd, hide=True)
    print(result.stdout)  # 只打印时间

解释一下,主要的改动点有:

fabfile.py 文件名:入口代码的脚本名必须用这个名字

@task 装饰器:需要从 fabric 中引入这个装饰器,它是对 invoke 的 @task 装饰器的封装,实际用法跟 invoke 一样(注意:它也需要有上下文参数"c",但实际上它并没有在代码块中使用,而是用了 Connection 类的实例)

然后,在该脚本同级目录的命令行窗口中,可以查看和执行相应的任务:

>>> fab -l
Available tasks:
  test   Get date from remote host.

>>> fab test
Fri Feb 14 16:10:24 CST 2020

fab 是 Invoke 的扩展实现,继承了很多原有功能,所以执行"fab --help",与之前介绍的"inv --help"相比,你会发现它们的很多参数与解释都是一模一样的。

fab 针对远程服务的场景,添加了几个命令行选项(已标蓝),其中:

--prompt-for-login-password:令程序在命令行中输入 SSH 登录密码(上例在代码中指定了 connect_kwargs.password 参数,若用此选项,可要求在执行时再手工输入密码)

--prompt-for-passphrase:令程序在命令行中输入 SSH 私钥加密文件的路径

-H 或 --hosts:指定要连接的 host 名

-i 或 --identity:指定 SSH 连接所用的私钥文件

-S 或 --ssh-config:指定运行时要加载的 SSH 配置文件

关于 Fabric 的命令行接口,更多内容可查看​ ​文档​​ [3]。

4、交互式操作

远程服务器上若有交互式提示,要求输入密码或"yes"之类的信息,这就要求 Fabric 能够监听并作出回应。

以下是一个简单示例。引入 invoke 的 Responder,初始化内容是一个正则字符串和回应信息,最后赋值给 watchers 参数:

from invoke import Responder
from fabric import Connection
c = Connection('host')
sudopass = Responder(
     pattern=r'\[sudo\] password:',
     response='mypassword\n')
c.run('sudo whoami', pty=True, watchers=[sudopass])

5、传输文件

本地与服务器间的文件传输是常见用法。Fabric 在这方面做了很好的封装,Connection 类中有以下两个方法可用:

get(*args, **kwargs):拉取远端文件到本地文件系统或类文件(file-like)对象

put(*args, **kwargs):推送本地文件或类文件对象到远端文件系统

在已建立连接的情况下,示例:

(略)

con.get('/opt/123.txt', '123.txt')

con.put('test.txt', '/opt/test.txt')

第一个参数指的是要传输的源文件,第二个参数是要传输的目的地,可以指定成文件名或者文件夹(为空或 None 时,使用默认路径):

(略)

con.get('/opt/123.txt', '') # 为空时,使用默认路径

con.put('test.txt', '/opt/') # 指定路径 /opt/

get() 方法的默认存储路径是​​os.getcwd​​ ,而 put() 方法的默认存储路径是 home 目录。

6、服务器批量操作

对于服务器集群的批量操作,最简单的实现方法是用 for 循环,然后逐一建立 connection 和执行操作,类似这样:

for host in ('web1', 'web2', 'mac1'):

result = Connection(host).run('uname -s')

但有时候,这样的方案会存在问题:

如果存在多组不同的服务器集群,需要执行不同操作,那么需要写很多 for 循环

如果想把每组操作的结果聚合起来(例如字典形式,key-主机,value-结果),还得在 for 循环之外添加额外的操作

for 循环是顺序同步执行的,效率太低,而且缺乏异常处理机制(若中间出现异常,会导致跳出后续操作)

对于这些问题,Fabric 提出了 Group 的概念,可将一组主机定义成一个 Group,它的 API 方法跟 Connection 一样,即一个 Group 可简化地视为一个 Connection。

然后,开发者只需要简单地操作这个 Group,最后得到一个结果集即可,减少了自己在异常处理及执行顺序上的工作。

Fabric 提供了一个 fabric.group.Group 基类,并由其派生出两个子类,区别是:

SerialGroup(*hosts, **kwargs):按串行方式执行操作

ThreadingGroup(*hosts, **kwargs):按并发方式执行操作

Group 的类型决定了主机集群的操作方式,我们只需要做出选择即可。然后,它们的执行结果是一个​​fabric.group.GroupResult​​类,它是 dict 的子类,存储了每个主机 connection 及其执行结果的对应关系。

>>> from fabric import SerialGroup
>>> results = SerialGroup('web1', 'web2', 'mac1').run('uname -s')
>>> print(results)
<GroupResult: {
    <Connection 'web1'>: <CommandResult 'uname -s'>,
    <Connection 'web2'>: <CommandResult 'uname -s'>,
    <Connection 'mac1'>: <CommandResult 'uname -s'>,
}>

另外,GroupResult 还提供了 failed 与 succeeded 两个属性,可以取出失败/成功的子集。由此,也可以方便地批量进行二次操作。 ​ ​原文​​

三、Fabric 的进阶用法

1、身份认证

Fabric 使用 SSH 协议来建立远程会话,它是一种相对安全的基于应用层的加密传输协议。

基本来说,它有两种级别的安全认证方式:

基于口令的身份认证:使用账号与密码来登录远程主机,安全性较低,容易受到"中间人"攻击

基于密钥的身份认证:使用密钥对方式(公钥放服务端,私钥放客户端),不会受到"中间人"攻击,但登录耗时较长

前文在举例时,我们用了第一种方式,即通过指定 connect_kwargs.password 参数,使用口令来登录。

Fabric 当然也支持采用第二种方式,有三种方法来指定私钥文件的路径,优先级如下:

优先查找 connect_kwargs.key_filename 参数,找到则用作私钥

其次查找命令行用法的 --identify 选项

最后默认使用操作系统的 ssh_config 文件中的​​IdentityFile​​ 的值

如果私钥文件本身还被加密过,则需要使用 connect_kwargs.passphrase 参数。

2、配置文件

Fabric 支持把一些参数项与业务代码分离,即通过配置文件来管理它们,例如前面提到的密码和私钥文件,可写在配置文件中,避免与代码耦合。

Fabric 基本沿用了 Invoke 的配置文件体系(官方文档中列出了 9 层),同时增加了一些跟 SSH 相关的配置项。支持的文件格式有 .yaml、.yml、.json 与 .py(按此次序排优先级),推荐使用 yaml 格式(后缀可简写成 yml)。

其中,比较常用的配置文件有:

系统级的配置文件:/etc/fabric.yml

用户级的配置文件:~/.fabric.yml(Windows 在 C:\Users\xxx 下)

项目级的配置文件:/myproject/fabric.yml

以上文件的优先级递减,由于我本机是 Windows,为了方便,我在用户目录建一个".fabric.yml"文件,内容如下:

# filename:.fabric.yml

user: root
connect_kwargs:
  password: xxxx
# 若用密钥,则如下
#  key_filename:
#    - your_key_file

我们把用户名和密码抽离出来了,所以 fabfile 中就可以删掉这些内容:

# 文件名:fabfile.py
from fabric import Connection
from fabric import task

host_ip = '47.xx.xx.xx'  # 服务器地址
cmd = 'date'  # shell 命令,查询服务器上的时间

@task
def test(c):
    """
    Get date from remote host.
    """
    con = Connection(host_ip)
    result = con.run(cmd, hide=True)
    print(result.stdout)

然后,在命令行中执行:

>>> fab test
Tue Feb 18 10:33:38 CST 2020

配置文件中还可以设置很多参数,详细可查看​ ​文档​​ [4]。

3、网络网关

如果远程服务是网络隔离的,无法直接被访问到(处在不同局域网),这时候需要有网关/代理/隧道,这个中间层的机器通常被称为跳板机或堡垒机。

Fabric 中有两种网关解决方案,对应到 OpenSSH 客户端的两种选项:

ProxyJump:简单,开销少,可嵌套

ProxyCommand:开销大,不可嵌套,更灵活

在创建 Fabric 的 Connection 对象时,可通过指定 gateway 参数来应用这两种方案:

ProxyJump 方式就是在一个 Connection 中嵌套一个 Connection 作为前者的网关,后者使用 SSH 协议的​​direct-tcpip​​ 为前者打开与实际远程主机的连接,而且后者还可以继续嵌套使用自己的网关。

from fabric import Connection

c = Connection('internalhost', gateway=Connection('gatewayhost'))

ProxyCommand 方式是客户端在本地用 ssh 命令(类似"ssh -W %h:%p gatewayhost"),创建一个子进程,该子进程与服务端进行通信,同时它能读取标准输入和输出。

其他参考文档

pip install Fabric # 安装

pip freeze > requirements.txt # 把安装包写入文件中

一个官网例子:

复制代码
def hello(name='sitin'):
    print("Hello world %s!" % name)

使用fab执行一下效果如下:

这里面我们需要知道fab是fabric安装的命令行工具,我们主要是通过它进行操作。

我个人平时用的比较多的命令有:

run 远端执行命令

local 本地执行命令

cd 远端切换目录

lcd 本地切换

@task 装饰器声明函数为fab task

简单的脚本我觉得是已经够用了,复杂一点需要更多操作了,详情见后文。

部署步骤

通常情况下,作为一个Python工程师我们发布代码需要做的事儿常见的有以下几点:

  1. git pull 拉取最新代码,比如master分支(或者develop分支)
  2. tar 打包最新代码
  3. rsync增量同步到远端服务器,去掉一些不需要的本地目录
  4. 备份数据库或者备份代码
  5. supervisor指定重启远端一个或多个服务,通过交互式指令判断
  6. sentry查看日志正常与否

除了最后一步,这里面所有的操作我们都在fabfile.py就进行操作了,一般情况下fabfile.py放在项目根目录,当然你放在其他地方也没有什么问题。通过**-f**进行指定就行。

概要讲了,下面请参看我们的一个实战例子

一个例子

复制代码
from fabric.api import (
    with_settings,
    hosts,
    cd,  # 远端
    lcd, # 本地切换目录
    run, # 执行
    env,
)
EST_ENV = '127.0.0.1'
TEST_USER = 'test'
env.forward_agent = True# 允许本地 SSH 代理连接远程终端时跳转
@hosts(TEST_ENV) # 指定远程操作的机器地址
@with_settings(user=TEST_USER) # 用来临时设定 env 变量,可以等同于 with settings
def deploy_test():
    # 发布测试环境
    local('git pull --rebase upsgream dev') # local执行本地命令拉取代码到本地,这个可以用CI自动发布,就不用拉取到本地。
    local('rsync -r . --exclude=tmp/ --exclude=backup/ sitin@yourip:/data/your_project') # 上传代码
    with cd('/data/your_project'): # 表示所有操作在这个目录下面
        run('docker-compose pull test')  # test镜像名
        backup_db() # 这里其实就是一个普通备份函数
        run('docker-compose stop test') # 执行远端命令同local相反
        run('docker-compose rm -f test')
        run('docker-compose run --rm test python manage.py migrate') # db同步
        run('docker-compose up -d test')

在终端执行命令

fab deploy_test # 就能进行发布了测试环境了

fab deploy_product # 如果有就能发布了

通常情况下测试,开发,服务器与线上操作不太一样,我们可以通过上面方式进行操作。除了上面的操作之后,如果我们测试线上完全一直或者多台服务器,可以通过指定不同角色来进行选择服务器的发布。

复制代码
env.roledefs = { 
   'test': ['test@yourip'],  # 指定多台机器 
   'dev': ['dev@yourip'],   
   'prod': ['opt@yourip2'],
   }
   
def deploy(branch=master): 
    pass

fab -R test(上面定义的角色) deploy -f fabfile.py

deploy这里还可以指定发布哪个分支的代码

这样指定某一个角色的服务器,某一个分支进行发布非常简单方便,对于经常使用的复杂命令操作我们还可以作为缩写命令来进行操作。

相关推荐
sszdzq1 小时前
Docker
运维·docker·容器
book01211 小时前
MySql数据库运维学习笔记
运维·数据库·mysql
唐古乌梁海2 小时前
【pytest】编写自动化测试用例命名规范README
自动化·pytest
bugtraq20212 小时前
XiaoMi Mi5(gemini) 刷入Ubuntu Touch 16.04——安卓手机刷入Linux
linux·运维·ubuntu
xmweisi2 小时前
【华为】报文统计的技术NetStream
运维·服务器·网络·华为认证
VVVVWeiYee2 小时前
BGP配置华为——路径优选验证
运维·网络·华为·信息与通信
陆鳐LuLu2 小时前
日志管理利器:基于 ELK 的日志收集、存储与可视化实战
运维·elk·jenkins
DC_BLOG3 小时前
Linux-GlusterFS进阶分布式卷
linux·运维·服务器·分布式
cookies_s_s3 小时前
Linux--进程(进程虚拟地址空间、页表、进程控制、实现简易shell)
linux·运维·服务器·数据结构·c++·算法·哈希算法
zhouwu_linux4 小时前
MT7628基于原厂的SDK包, 修改ra1网卡的MAC方法。
linux·运维·macos