使用 Fabric 批量执行服务器任务


安装配置Fabric

yum install python-pip     #需要有EPEL源
pip-python install -U pip
pip install virtualenv    ##python环境虚拟工具(可选)
pip install fabric
#可能需要修改下PIP的源
vi ~/.pip/pip.conf
[global]
timeout = 6000
index-url = http://e.pypi.python.org/simple
[install]
use-mirrors = true
mirrors = http://e.pypi.python.org

#或者指定源安装:pip install fabric -i http://pypi.v2ex.com/simple

Fabric 是基于 SSH 协议的 Python 工具,相比传统的 ssh/scp 方式,用Python 的语法写管理命令更易读也更容易扩展,管理单台或者多台机器犹如本地操作一般。Fabric 使用 ssh(通过 paramiko 库)在多个服务器上批量执行任务,我们只需要用 Python 编写这些任务脚本并指定要执行这些任务的服务器就可以了。
传统维护方法:
$ ssh x.x.x.x 'uname -a'

Fabric 示例:
$ cat fabfile.py
from fabric.api import run
def uname():
    run('uname -a')
$ fab -H x.x.x.x uname

Fabric 的优点

  • 角色定义
  • 代码易读
  • 封装了本地、远程操作(还需要自己封装system/popen/ssh操作么?)
  • 参数灵活(动态指定 host/role 等,还有并发执行 基于multiprocessing )
  • 完整的日志输出
  • 罗列的这些,其实日常工作里基本都有类似的封装了,但是有现成的一个成熟的工具,干啥不用呢?对吧。、

    常用的配置

env.host           -- 主机ip,当然也可以-H参数指定
env.password       -- 密码,打好通道的请无视
env.roledefs       -- 角色分组,比如:{'web': ['x', 'y'], 'db': ['z']}
fab -l             -- 显示可用的task(命令)
fab -H             -- 指定host,支持多host逗号分开
fab -R             -- 指定role,支持多个
fab -P             -- 并发数,默认是串行
fab -w             -- warn_only,默认是碰到异常直接abort退出
fab -f             -- 指定入口文件,fab默认入口文件是:fabfile/fabfile.py
更多请参考:fab --help

常用的函数

local('pwd')                     -- 执行本地命令
lcd('/tmp')                      -- 切换本地目录
cd('/tmp')                       -- 切换远程目录
run('uname -a')                  -- 执行远程命令
sudo('/etc/init.d/nginx start')  -- 执行远程sudo,注意pty选项

例子

示例1:管理远程 nginx 服务
$ cat fabfile.py
from fabric.api import *
@task            ##一个修饰符,就是把当前的func暴露给fabric用。
def nginx_start():
    ''' nginx start '''
    sudo('/etc/init.d/nginx start')

@task
def nginx_stop():
    ''' nginx stop '''
    sudo('/etc/init.d/nginx stop')

$ fab --list      -- 查看可用命令
Available commands:

    nginx_start  nginx start
    nginx_stop   nginx stop

$ fab -H x.x.x.x nginx_start  -- 启动 nginx

示例2:基于角色
$ cat fabfile.py
from fabric.api import *
env.roledefs = {'nginx': ['x.x.x.x', 'y.y.y.y'], 'mysql': 'z.z.z.z'}
@task
def mysql_start()
    ''' mysql start '''
    sudo('/etc/init.d/mysql start')

$ fab --list      -- 查看可用命令
Available commands:
    nginx_start  nginx start
    nginx_stop   nginx stop
    mysql_start  mysql start

$ fab -R nginx nginx_start  -- 启动 nginx
$ fab -R mysql mysql_start  -- 启动 mysql

示例3:混合本地和远程操作
$ cat fabfile
def hello():
    ''' test hello '''
    with lcd('/tmp'):  # 切换到 /tmp 目录下
       local('svn co http://xxx xxx') # check 代码到本地
       local('tar czf xxx.tar.gz xxx/') # 压缩本地包
       put('xxx.tar.gz', '/tmp') # 上传压缩包到远程 /tmp 目录下
    with cd('/tmp'):   # 切换到远程 /tmp 目录
       run('tar zxf xxx.tar.gz') # 远程解压



来看一个更复杂点的例子,在多个服务器(grid00, grid02, …, grid05)上更换 root 密码(假设原密码是root),注意加上 @parallel,这样任务是并行执行的,在大量服务器上会快很多:

#!/usr/bin/python
# -*- coding: utf-8 -*-
from fabric.api import *
import string
from random import choice
import socket
import paramiko

env.user = 'root'
env.password = 'root'
env.hosts = [ 'grid00', 'grid01', 'grid02', 'grid03', 'grid04', 'grid05']

@task
@parallel
def passwd(user, passwd=False):
    with settings(hide('running', 'stdout', 'stderr'), warn_only=True):
       if isup(env.host):
            if not passwd:
                passwd = genpass()
            sudo("echo -e '%s\\n%s' | passwd %s" % (passwd, passwd, user))

def genpass(length=10):
    return ''.join(choice(string.ascii_letters + string.digits) for _ in range(length))

def isup(host):
    print 'connecting host: %s' % host
    timeout = socket.getdefaulttimeout()
   socket.setdefaulttimeout(1)
    up = True
    try:
       paramiko.Transport((host, 22))
    except Exception, e:
       up = False
       print '%s down, %s' % (host, e)
    finally:
       socket.setdefaulttimeout(timeout)
       return up
使用 fab -l 查看我们刚编写的 fabfile.py 里面的可用命令,这个命令就是那个函数名 def passwd(user, passwd=False):

$ fab -l
Available commands:

    passwd
使用这个命令批量更换 grid00-grid05 的 root 密码为 test,passwd 传递参数的时候接冒号,并且用户名和密码参数用逗号隔开:

$ fab passwd:root,test
[grid00] Executing task 'passwd'
[grid01] Executing task 'passwd'
[grid02] Executing task 'passwd'
[grid03] Executing task 'passwd'
[grid04] Executing task 'passwd'
[grid05] Executing task 'passwd'
connecting host: grid05
connecting host: grid04
connecting host: grid02
connecting host: grid03
connecting host: grid01
connecting host: grid00

Done.