使用shell命令进行复杂的运维时,代码往往变得复杂难懂,而使用python脚本语言来编写运维程序,就相当于开发普通的应用一样,所以维护和扩展都比较简单,更重要的是python运维工具fabric能自动登录其他服务器进行各种操作,这种实现使用shell是很难做到的,但是使用fabric实现就很简单,所以对于程序员的日常运维部署,建议使用python编写脚本。Fabric是基于Python实现的SSH命令行工具,简化了SSH的应用程序部署及系统管理任务,它提供了系统基础的操作组件,可以通过 SSH 的方式与远程服务器进行自动化交互, 实现本地或远程shell命令,包括:命令执行、文件上传、下载及完整执行日志输出等功能。
一. Fabric安装
Linux下默认有python环境,安装fabric有两种方式: 一是通过pip方式安装; 而是通过fabric源码方式安装. 一般选择pip方式安装, 安装过程如下: 先安装一些依赖 [root@kevin ~]# yum install make gcc gcc-c++ python-devel python-setuptools -y 安装pip 首先下载py文件:https://bootstrap.pypa.io/get-pip.py 或者百度云盘下载地址:https://pan.baidu.com/s/1o7KylCm 提取密码:eucx [root@kevin ~]# cat /etc/redhat-release CentOS Linux release 7.5.1804 (Core) [root@kevin ~]# python -V Python 2.7.5 [root@kevin ~]# wget https://bootstrap.pypa.io/get-pip.py [root@kevin ~]# chmod 755 get-pip.py [root@kevin ~]# python get-pip.py Collecting pip Downloading https://files.pythonhosted.org/packages/c2/d7/90f34cb0d83a6c5631cf71dfe64cc1054598c843a92b400e55675cc2ac37/pip-18.1-py2.py3-none-any.whl (1.3MB) 100% |████████████████████████████████| 1.3MB 56kB/s Collecting wheel Downloading https://files.pythonhosted.org/packages/ff/47/1dfa4795e24fd6f93d5d58602dd716c3f101cfd5a77cd9acbe519b44a0a9/wheel-0.32.3-py2.py3-none-any.whl Installing collected packages: pip, wheel Successfully installed pip-18.1 wheel-0.32.3 接着使用pip安装fabric [root@kevin ~]# pip install fabric 稍等一会就安装完毕了,这时输入fab就会弹出对应的选项 [root@kevin ~]# fab --version Fabric 2.4.0 Paramiko 2.4.2 Invoke 1.2.0 [root@kevin ~]# fab --help Usage: fab [--core-opts] task1 [--task1-opts] ... taskN [--taskN-opts] Core options: --complete Print tab-completion candidates for given parse remainder. --hide=STRING Set default value of run()'s 'hide' kwarg. --no-dedupe Disable task deduplication. --print-completion-script=STRING Print the tab-completion script for your preferred shell (bash|zsh|fish). --prompt-for-login-password Request an upfront SSH-auth password prompt. --prompt-for-passphrase Request an upfront SSH key passphrase prompt. --prompt-for-sudo-password Prompt user at start of session for the sudo.password config value. --write-pyc Enable creation of .pyc files. -c STRING, --collection=STRING Specify collection name to load. -d, --debug Enable debug output. -D INT, --list-depth=INT When listing tasks, only show the first INT levels. -e, --echo Echo executed commands before running. -f STRING, --config=STRING Runtime configuration file to use. -F STRING, --list-format=STRING Change the display format used when listing tasks. Should be one of: flat (default), nested, json. -h [STRING], --help[=STRING] Show core or per-task help and exit. -H STRING, --hosts=STRING Comma-separated host name(s) to execute tasks against. -i, --identity Path to runtime SSH identity (key) file. May be given multiple times. -l [STRING], --list[=STRING] List available tasks, optionally limited to a namespace. -p, --pty Use a pty when executing shell commands. -r STRING, --search-root=STRING Change root directory used for finding task modules. -S STRING, --ssh-config=STRING Path to runtime SSH config file. -V, --version Show version and exit. -w, --warn-only Warn, instead of failing, when shell commands fail. ======================================================= 温馨提示: 如果安装的是pip3, 则使用"pip3 install fabric3" 安装fabric =======================================================
二. Fabric 使用
Fabric命令说明
1) fab命令格式
fab是fabric的命令行入口 命令的格式为: # fab [options] <command>[:arg1,arg2=val2,host=foo,hosts='h1;h2',...] ...
2) fab命令常用参数
# fab --help 查看帮助 常用参数 -l 显示定义好的任务函数名 -f 指定fab入口文件,默认入口文件名为fabfile.py.. 即指定fabfile文件 -g 指定网关(中转)设备,即HOST逗号分隔要操作的主机, 比如堡垒机环境,填写堡垒机IP即可. -H 指定目标主机,多台主机用‘,’号分隔 -p 远程账号的密码,fab执行时默认使用root账户 -P 以异步并行方式运行多主机任务,默认为串行运行 -R 指定role(角色),以角色名区分不同业务组设备 -t 设置设备连接超时时间(秒) -T 设置远程主机命令执行超时时间(秒) -w 当命令执行失败,发出警告,而非默认中止任务。 其他参数: --set=KEY=VALUE,... 逗号分隔,设置环境变量 --shortlist 简短打印可用命令 -c PATH 指定本地配置文件 -D 不加载用户known_hosts文件 -i PATH 指定私钥文件 -k 不加载来自~/.ssh下的私钥文件 --port=PORT 指定SSH连接端口 -R ROLES 根据角色操作,逗号分隔 -s SHELL 指定新shell,默认是'/bin/bash -l -c' --show=LEVELS 以逗号分隔的输出 --ssh-config-path=PATH SSH配置文件路径 -T N 设置远程命令超时时间,单位秒 -u USER 连接远程主机用户名 -x HOSTS 以逗号分隔排除主机 -z INT 并发进程数 例1: 通过远程主机查询172.16.50.45 (该主机的root密码为123456)的主机名 [root@kevin ~]# fab -f fabtest.py -p 123456 -H 172.16.50.45 -- 'hostname' 例2: 本地执行命令 [root@kevin ~]# vim fabtest.py from fabric.api import local def command(): local('ls') [root@kevin ~]# fab -f fabtest.py command [localhost] local: ls fabfile.py fabfile.pyc tab.py tab.pyc Done. 例3: 远程执行命令 [root@kevin ~]# vim fabtest.py from fabric.api import run def command(): run('ls') [root@kevin ~]# fab -f fabtest.py -H 192.168.1.120 -u user command [192.168.1.120] Executing task 'command'[192.168.1.120] run: ls [192.168.1.120] Login password for 'user': [192.168.1.120] out: access.log a.py [192.168.1.120] out: Done. Disconnecting from 192.168.1.120... done. 如果在多台主机执行,只需要-H后面的IP以逗号分隔即可。 例4: 给脚本函数传入位置参数 [root@kevin ~]# vim fabfile.py from fabric.api import run def hello(name="world"): print("Hello %s!" % name) [root@kevin ~]# fab -H localhost hello [localhost] Executing task 'hello'Hello world! Done. [root@kevin ~]# fab -H localhost hello:name=Python [localhost] Executing task 'hello'Hello Python! Done. 例5: 主机列表组 [root@kevin ~]# vim fabfile.py from fabric.api import run, env env.hosts = ['root@192.168.1.120:22', 'root@192.168.1.130:22'] env.password = '123.com'env.exclude_hosts = ['root@192.168.1.120:22'] # 排除主机 def command(): run('ls') [root@kevin ~]# fab command env作用是定义fabfile全局设定,类似于变量。还有一些常用的属性: 例6: 定义角色分组 [root@kevin ~]# vim install.py from fabric.api import run, env env.roledefs = { 'web': ['192.168.1.10', '192.168.1.20'], 'db': ['192.168.1.30', '192.168.1.40'] } env.password = '123'@roles('web') def task1(): run('yum install httpd -y') @roles('db') def task2(): run('yum install mysql-server -y') def deploy(): execute(task1) execute(task2) [root@kevin ~]# fab -f install.py deploy 例7: 上传目录到远程主机 [root@kevin ~]# vim haha.py from fabric.api import * env.hosts = ['192.168.1.120'] env.user = 'user'env.password = '123.com' def task(): put('/root/abc', '/home/user') run('ls -l /home/user') [root@kevin ~]# fab -f haha.py task 例8: 从远程主机下载目录 [root@kevin ~]# vim heihei.py from fabric.api import * env.hosts = ['192.168.1.120'] env.user = 'user'env.password = '123.com' def task(): get('/home/user/b', '/opt') local('ls -l /opt') [root@kevin ~]# fab -f heihei.py task 例9: 打印颜色,有助于关键地方醒目 [root@kevin ~]# vim bobo.py from fabric.colors import * def show(): print green('Successful.') print red('Failure!') print yellow('Warning.') [root@kevin ~]# fab -f bobo.py show
3) fabfile文件的编写 (默认的文件名称为fabfile)
fab命令是结合fabfile.py文件(其他文件通过-f filename 参数来引用)来搭配使用的。fab的部分命令行参数还能通过相应的方法来代替。 先来看一个小例子 [root@kevin ~]# cat fabfile.py #!/usr/bin/env python from fabric.api import run #定义一个任务函数,通过run方法实现远程执行"uname -s"命令 def host_type(): run('uname -s') [root@kevin ~]# fab -H localhost host_type [localhost] Executing task 'host_type' [localhost] run: uname -s [localhost] Login password for 'devops': [localhost] out: Linux [localhost] out: Done. Disconnecting from localhost... done. 其中,必须要明白的是, fab命令引用的默认文件名fabfile.py! 如果使用的是默认文件名称, 则fab执行命令中就不需要跟文件名. 如果使用非默认文件名称,比如这里不是fabfile.py, 而是host_type.py 文件, 则需要通过"-f"来指定: [root@kevin ~]# fab -H localhost -f host_type.py host_type 如果目标主机未配置密钥认证信任,将会提示输入目标主机对应账号登录密码。 再来看一个小例子 [root@kevin ~]# vim fabric.py #!/usr/bin/python # -*- coding:utf-8 -*- from fabric.api import * # 设置服务器登录参数 env.roledefs = { # 操作一致的放一组,一组执行同一个操作 'servers1':['root@linux2:22',], # 第二组 'servers2':['root@linux3:22',] } # 本机操作 def localtask(): local('/usr/local/nginx/nginx') # servers1服务器组操作 @roles('servers1') def task1(): run('/usr/local/tomcat/bin/startup.sh') # servers2 服务器组操作 @roles('servers2') def task2(): run('/usr/local/tomcat/bin/startup.sh') # 执行任务 def doworks(): execute(localtask) execute(task1) execute(task2) 以上Fabric配置,实现的目的是: 简单的在本地启动nginx服务器, 在linux1和linux2上启动了tomcat服务器, 为了接受nginx服务器的代理,这里专门使用分组的方式为了适应机器比较多的集群的需要; 另外这里没有设置服务器的密码,一是为了服务器的安全;而是集群间建议设置ssh免密登录,脚本就不用设置密码了; 方法doworks执行的就是最终汇总的任务; 开始执行 [root@kevin ~]# fab -f fabric.py doworks
4) fabfile全局属性 (env对象)
fabfile之env对象的作用是定义fabfile的全局设定,支持多个属性,包含目标主机、用户名、密码、等角色. env各属性说明如下: evn.host: 定义目标主机,可以用IP或主机名表示,以Python的列表形式定义,如evn.hosts['192.168.56.133','192.168.56.134']。 env.exclude_hosts: 排除指定主机,如env.exclude_hosts=['192.168.56.133']。 env.user: 定义用户名,如env.user="root"。 env.port: 定义目标主机端口,默认为22,如env.port="22"。 env.password: 定义密码,如env.password='1234567'。 env.passwords: 与password功能一样,区别在于不同主机不同密码的应用场景,需要注意的是,配置passwords是需配置用户、主机、端口等信息; env.gateway: 定义网关(中转、堡垒机)IP,如env.gateway = '192.168.56.1'。 env.deploy_release_dir: 自定义全局变量,格式:env.+"变量名称",如env.deploy_release_dir、env.age、env.sex等。 env.roledefs: 定义角色分组,比如web组与db组主机区分开来; 比如 [root@kevin ~]# vim fabfile.py .......... env.passwords = { 'root@192.168.56.131:22':'1234567', 'root@192.168.56.132:22':'1234567', 'root@192.168.56.133:22':'1234567', 'root@192.168.56.134:22':'1234567' } [root@kevin ~]# vim fabfile.py .......... env.roledefs = { 'webservers':['192.168.56.131','192.168.56.132','192.168.56.133'], 'dbserver':['192.168.56.134','192.168.56.135'] } env.roledefs的使用方法实例: [root@kevin ~]# vim fabfile.py .......... env.roledefs = {'webserver':['192.168.1.21','192.168.1.22'],'dbserver':['192.168.1.25','192.168.1.26']} #引用分组时使用python装饰器方式来进行,如: @roles('webserver') def webtask(): run('/usr/local/nginx/sbin/nginx') @roles('webserver','dbserver') def publictask(): run('uptime') 引用时使用Python修饰符的形式进行,角色修饰符下面的任务函数为其作用域,下面来看一个示例: [root@kevin ~]# vim fabfile.py .......... @roles('webservers') def webtask(): run('/etc/init.d/nginx start') @roles('dbservers') def dbtask(): run('/etc/init.d/mysql start') @roles('webservers','dbservers') def pubclitasj(): run('uptime') def deploy(): execute(webtask) execute(dbtask) execute(pubclitask) 在命令执行fab deploy就可以实现不同角色执行不同的任务函数了。
5) Fabric常用API
Fabric提供了一组简单但功能强大的fabric.api命令集,简单地调用这些API就能完成大部分应用场景需求。Fabric常用方法及说明如下: local 执行本地命令,如:local('uname -s'); lcd 切换本地目录,如:lcd('/home'); cd 切换远程目录,如:cd('/data/logs'); run 执行远程命令,如:run('free -m'); sudo sudo方式执行远程命令,如:sudo('/etc/init.d/httpd start'); put 传本地文件到远程主机,如:put('/home/user.info','/data/user.info'); prompt 获得用户输入信息,如:prompt('please input user password:'); confirm 获得提示信息确认,如:confirm("Tests failed. Continue[Y/N]?"); reboot 重启远程主机,如:reboot(); @task 函数修饰符,标识的函数为fab可调用的,非标记对fab不可见,纯业务逻辑; @runs_once 函数修复符,标识的函数只会执行一次,不受多台主机影响。
6) Fabric应用示例说明
示例一: 查看本地和远程主机信息
查看本地信息 本示例调用local()方法执行本地(主控端)命令,添加"@runs_once"修饰符保证该任务函数只执行一次。调用run()方法执行远程命令。 [root@kevin ~]# vim fabric1.1.py #!/usr/bin/env python from fabric.api import * env.user = 'devops' env.hosts = ['localhost'] env.password = '1234567' @runs_once #查看本地系统信息,当有多台主机时只运行一次 def local_task(): #本地任务函数 local("uname -a") 通过fab命令调用local_task任务函数运行结果如下: [root@kevin ~]# fab -f fabric1.1.py local_task [localhost] Executing task 'local_task' [localhost] local: uname -a Linux devops-virtual-machine 4.15.0-20-generic #21-Ubuntu SMP Tue Apr 24 06:16:15 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux Done. 查看远程主机信息 [root@kevin ~]# vim fabric1.2.py #!/usr/bin/env python from fabric.api import * env.user = 'root' env.hosts = ['192.168.56.11'] env.password = '1234567' def remote_task(): with cd('/root'): #"with"的作用是让后面的表达式的语句继承当前状态,实现"cd /root/ && ls -l'的效果 run('ls -l') 调用remote_task任务函数运行结果如下: [root@kevin ~]# fab -f fabric1.2.py local_task [192.168.56.11] Executing task 'remote_task' [192.168.56.11] run: ls -l [192.168.56.11] out: total 4 [192.168.56.11] out: -rw-------. 1 root root 1273 May 29 11:47 anaconda-ks.cfg [192.168.56.11] out: Done. Disconnecting from 192.168.56.11... done. 如果将上面两个文件的需求, 放在一起 [root@kevin ~]# vim fabric1.py #!/usr/bin/env python # -*- encoding: utf-8 -*- from fabric.api import * env.user = 'root' env.hosts = ['192.168.1.22'] env.password = '123456' @runs_once #查看本地系统信息,当有多台主机时只运行一次 def local_task(): #本地任务函数 local('uname -a') def remote_task(): with cd('/var/logs'): #with的作用是让后面的表达式语句继承当前状态,实现:cd /var/logs && ls -l的效果 run('ls -l') [root@kevin ~]# fab -f fabric1.py local_task [root@kevin ~]# fab -f fabric1.py remote_task
示例二:动态获取远程目录列表
本示例使用"@task'修复符标志入口函数go()对外部可见,配合"@runs_once"修饰符接受用户输入,最后调用worktask()任务函数实现远程命令执行。 [root@kevin ~]# vim fabric2.py #!/usr/bin/env python from fabric.api import * env.user = 'root' env.hosts = ['192.168.56.11','192.168.56.12'] env.password = '1234567' @runs_once #主机遍历过程中,只有第一台触发此函数 def input_raw(): return prompt("Please input directory name:",default="/home") def worktask(dirname): run("ls -l "+dirname) @task #限定只有go函数对fab命令可见 def go(): getdirname = input_raw() worktask(getdirname) 解释说明: 该示例实现了一个动态输入远程目录名称,再获取目录列表的功能,由于我们只要求输入一次,在显示所有主机上该目录的列表信息,调用一个子函数input_raw()同时配置@runs_once修复符来达到此目的。 执行结果如下: [root@kevin ~]# fab -f fabric2.py go [192.168.56.11] Executing task 'go' Please input directory name: [/home] /root [192.168.56.11] run: ls -l /root [192.168.56.11] out: total 4 [192.168.56.11] out: -rw-------. 1 root root 1273 May 29 11:47 anaconda-ks.cfg [192.168.56.11] out: [192.168.56.12] Executing task 'go' [192.168.56.12] run: ls -l /root [192.168.56.12] out: total 4 [192.168.56.12] out: -rw-------. 1 root root 1273 May 29 11:59 anaconda-ks.cfg [192.168.56.12] out: Done. Disconnecting from 192.168.56.11... done. Disconnecting from 192.168.56.12... done.
示例三: 网关模式文件上传与执行
本示例通过Fabric的env对象定义网关模式,即俗称的中转、堡垒机环境。定义格式为"env.gateway='192.168.56.11'",其中IP“192.168.56.11”为堡垒机IP, 再结合任务韩素实现目标主机文件上传与执行的操作。 [root@kevin ~]# vim fabric3.py #!/usr/bin/env python from fabric.api import * from fabric.context_managers import * from fabric.contrib.console import confirm env.user = 'root' env.gateway = '192.168.56.11' #定义堡垒机IP,作为文件上传、执行的中转设备 env.hosts = ['192.168.56.12','192.168.56.13'] env.passwords = { 'root@192.168.56.11:22':'1234567', #堡垒机账号信息 'root@192.168.56.12:22':'1234567', 'root@192.168.56.13:22':'1234567' } l_pack_path = "/home/install/nginx-1.6.3.tar.gz" #本地安装包路径 r_pack_path = "/tmp/install" #远程安装包路径 @task def put_task(): run("mkdir -p /tmp/install") with settings(warn_only=True): result = put(l_pack_path,r_pack_path) #上传安装包 if result.failed and not confirm("put file failed, Continue[Y/N]?"): abort("Aborint file put task!") @task def run_task(): #执行远程命令,安装nginx with cd(r_pack_path): run("tar -xvf nginx-1.6.3.tar.gz") with cd("nginx-1.6.3/"): #使用with继续继承/tmp/install目录位置状态 run("./nginx_install.sh") @task def go(): #上传、安装 put_task() run_task() 如下命令运行结果, 默认为串行运行 [root@kevin ~]# fab -f fabric3.py go [192.168.56.12] Executing task 'go' [192.168.56.12] run: mkdir -p /tmp/install [192.168.56.12] put: /home/install/nginx-1.6.3.tar.gz -> /tmp/install/nginx-1.6.3.tar.gz [192.168.56.12] run: tar -xvf nginx-1.6.3.tar.gz ..... ..... ..... [192.168.56.12] out: cp conf/nginx.conf '/usr/local/nginx/conf/nginx.conf.default' [192.168.56.12] out: test -d '/usr/local/nginx/logs' || mkdir -p '/usr/local/nginx/logs' [192.168.56.12] out: test -d '/usr/local/nginx/logs' || mkdir -p '/usr/local/nginx/logs' [192.168.56.12] out: test -d '/usr/local/nginx/html' || cp -R html '/usr/local/nginx' [192.168.56.12] out: test -d '/usr/local/nginx/logs' || mkdir -p '/usr/local/nginx/logs' [192.168.56.12] out: make[1]: Leaving directory `/tmp/install/nginx-1.6.3' [192.168.56.12] out: nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok [192.168.56.12] out: nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful [192.168.56.12] out: [192.168.56.13] Executing task 'go' [192.168.56.13] run: mkdir -p /tmp/install [192.168.56.13] put: /home/install/nginx-1.6.3.tar.gz -> /tmp/install/nginx-1.6.3.tar.gz [192.168.56.13] run: tar -xvf nginx-1.6.3.tar.gz .... .... .... [192.168.56.13] out: cp conf/nginx.conf '/usr/local/nginx/conf/nginx.conf.default' [192.168.56.13] out: test -d '/usr/local/nginx/logs' || mkdir -p '/usr/local/nginx/logs' [192.168.56.13] out: test -d '/usr/local/nginx/logs' || mkdir -p '/usr/local/nginx/logs' [192.168.56.13] out: test -d '/usr/local/nginx/html' || cp -R html '/usr/local/nginx' [192.168.56.13] out: test -d '/usr/local/nginx/logs' || mkdir -p '/usr/local/nginx/logs' [192.168.56.13] out: make[1]: Leaving directory `/tmp/install/nginx-1.6.3' [192.168.56.13] out: nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok [192.168.56.13] out: nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful [192.168.56.13] out: Done. Disconnecting from 192.168.56.11... done. Disconnecting from 192.168.56.12... done. Disconnecting from 192.168.56.13... done. 再如下运行结果, 加P参数为异步并行执行结果 [root@kevin ~]# fab -Pf fabric3.py go [192.168.56.12] Executing task 'go' [192.168.56.13] Executing task 'go' [192.168.56.12] run: mkdir -p /tmp/install [192.168.56.13] run: mkdir -p /tmp/install [192.168.56.12] put: /home/install/nginx-1.6.3.tar.gz -> /tmp/install/nginx-1.6.3.tar.gz [192.168.56.13] put: /home/install/nginx-1.6.3.tar.gz -> /tmp/install/nginx-1.6.3.tar.gz [192.168.56.12] run: tar -xvf nginx-1.6.3.tar.gz .... .... .... [192.168.56.12] out: nginx-1.6.3/html/index.html [192.168.56.12] out: nginx-1.6.3/README [192.168.56.12] out: nginx-1.6.3/nginx_install.sh [192.168.56.12] out: nginx-1.6.3/configure [192.168.56.12] out: [192.168.56.12] run: ./nginx_install.sh [192.168.56.13] run: tar -xvf nginx-1.6.3.tar.gz [192.168.56.13] out: nginx-1.6.3/ [192.168.56.13] out: nginx-1.6.3/src/ .... .... .... [192.168.56.12] out: make[1]: Leaving directory `/tmp/install/nginx-1.6.3' [192.168.56.12] out: nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok [192.168.56.12] out: nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful [192.168.56.12] out: .... .... ... [192.168.56.13] out: make[1]: Leaving directory `/tmp/install/nginx-1.6.3' [192.168.56.13] out: nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok [192.168.56.13] out: nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful [192.168.56.13] out:
示例四: 文件打包, 上传与校验
我们时常做一些文件包分发的工作,实施步骤一般是先压缩打包,在批量上传至目标服务器,最后做一致性校验。 本示例通过put()方法实现文件的上传,通过对比本地与远程主机文件的md5,最终实现文件一致性校验。 [root@kevin ~]# vim fabric4.py #!/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.56.12','192.168.56.13'] env.passwords = { 'root@192.168.56.12:22':'1234567', 'root@192.168.56.13:22':'1234567', } @runs_once def tar_task(): #本地打包任务函数,只执行一次 with lcd('/home/devops/devops'): local("tar -zcf devops.tar.gz *") @task def put_task(): #上传文件任务函数 run("mkdir -p /root/devops") with cd("/root/devops"): with settings(warn_only=True): #put(上传)出现异常时继续执行,非终止 result = put("/home/devops/devops/devops.tar.gz","/root/devops/devops.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 /home/devops/devops/devops.tar.gz",capture=True).split(' ')[0] rmd5 = run("md5sum /root/devops/devops.tar.gz").split(' ')[0] if lmd5 == rmd5: #对比本地及远程文件md5信息 prompt("OK") else: prompt("ERROR") @task def go(): tar_task() put_task() check_task() 执行命令, 运行结果如下:(提示此程序不支持-P参数并行执行、如需并行执行,程序需要做调整). 如果只打包, 则"fab -f fabric4.py tar_task", 如果只上传, 则"fab -f fabric4.py put_task" [root@kevin ~]# fab -f fabric4.py go [192.168.56.12] Executing task 'go' [localhost] local: tar -zcf devops.tar.gz * [192.168.56.12] run: mkdir -p /root/devops [192.168.56.12] put: /home/devops/devops/devops.tar.gz -> /root/devops/devops.tar.gz [localhost] local: md5sum /home/devops/devops/devops.tar.gz [192.168.56.12] run: md5sum /root/devops/devops.tar.gz [192.168.56.12] out: a1cf2be82647cbed0d41514bd80373de /root/devops/devops.tar.gz [192.168.56.12] out: OK [192.168.56.13] Executing task 'go' [192.168.56.13] run: mkdir -p /root/devops [192.168.56.13] put: /home/devops/devops/devops.tar.gz -> /root/devops/devops.tar.gz [localhost] local: md5sum /home/devops/devops/devops.tar.gz [192.168.56.13] run: md5sum /root/devops/devops.tar.gz [192.168.56.13] out: a1cf2be82647cbed0d41514bd80373de /root/devops/devops.tar.gz [192.168.56.13] out: OK Done. Disconnecting from 192.168.56.12... done. Disconnecting from 192.168.56.13... done.
示例五: 部署LNMP业务服务环境
本示例通过env.roledefs定义不同主机角色,在使用"@roles('webservers')"修复符绑定到对应的任务函数,实现不同角色主机的部署差异。 [root@kevin ~]# vim fabric5.py #!/usr/bin/env python from fabric.colors import * from fabric.api import * env.user = 'root' env.roledefs = { 'webservers':['192.168.56.11','192.168.56.12'], 'dbservers':['192.168.56.13'] } env.passwords = { 'root@192.168.56.11:22':'1234567', 'root@192.168.56.12:22':'1234567', 'root@192.168.56.13:22':'1234567', } @roles('webservers') #使用webtask任务函数引用'webservers'角色修复符 def webtask(): 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(): 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("wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo") run("yum -y install ntp") def deploy(): execute(publictask) execute(webtask) execute(dbtask) 执行命令,结果如下: [root@kevin ~]# fab -Pf fabric5.py deploy [192.168.56.11] Executing task 'publictask' [192.168.56.12] Executing task 'publictask' [192.168.56.13] Executing task 'publictask' Install epel ntp.... [192.168.56.13] run: wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo Install epel ntp.... [192.168.56.12] run: wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo Install epel ntp.... [192.168.56.11] run: wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo [192.168.56.12] out: --2018-06-23 20:32:30-- http://mirrors.aliyun.com/repo/epel-7.repo [192.168.56.11] out: --2018-06-23 20:32:30-- http://mirrors.aliyun.com/repo/epel-7.repo [192.168.56.13] out: --2018-06-23 20:32:30-- http://mirrors.aliyun.com/repo/epel-7.repo .... [192.168.56.13] run: yum -y install ntp [192.168.56.12] run: yum -y install ntp [192.168.56.11] run: yum -y install ntp .... .... .... [192.168.56.11] Executing task 'webtask' [192.168.56.12] Executing task 'webtask' Install nginx php php-fpm... [192.168.56.11] run: yum -y install nginx Install nginx php php-fpm... [192.168.56.12] run: yum -y install nginx .... .... .... [192.168.56.13] Executing task 'dbtask' Install Mysql... [192.168.56.13] run: rpm -ivh http://dev.mysql.com/get/mysql-community-release-el6-5.noarch.rpm ..... ..... ..... [192.168.56.13] run: chkconfig --levels 235 mysqld on Done.
示例六: 分享一个生产环境代码包发布管理的配置
程序生产环境的发布是业务上线的最后一个环境,要求具备源码打包、发布、切换、回滚、版本管理等功能。 本示例实现了这一套流程功能,其中版本切换与回滚使用了Linux下的软链接实现。 [root@kevin ~]# vim fabric6.py #!/usr/local/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.host = ['192.168.56.12','192.168.56.13'] env.passwords = { 'root@192.168.56.12:22':'1234567', 'root@192.168.56.13:22':'1234567', } 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(): #打包本地项目主目录,并将压缩包存储到本地压缩包目录 prompt(yellow("Creating source package....")) with lcd(env.project_dev_source): local("tar -zcf %s.tar.gz ." %(env.project_tar_source + env.project_pack_name)) prompt(green("Creating source package success!")) @task def put_package(): #上传任务函数 prompt(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 not ("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 -r %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 sucess!")) @task def go(): #自动化程序版本发布入口函数 tar_source() put_package() make_symlink() # 需要注意: 在生产环境中将站点的根目录指向"/data/www/Lwebadmin/current",由于使用Linux软链接做切换,管理员的版本发布、回滚操作用户无感知。 [root@kevin ~]# fab -f fabric6.py go
示例七: 分享一个自动化部署 Django 项目的配置
[root@kevin ~]# vim fabric7.py # -*- coding: utf-8 -*- # 文件名要保存为 fabfile.py from __future__ import unicode_literals from fabric.api import * # 登录用户和主机名: env.user = 'root' # 如果没有设置,在需要登录的时候,fabric 会提示输入 env.password = 'youpassword' # 如果有多个主机,fabric会自动依次部署 env.hosts = ['www.example.com'] TAR_FILE_NAME = 'deploy.tar.gz' def pack(): """ 定义一个pack任务, 打一个tar包 :return: """ tar_files = ['*.py', 'static/*', 'templates/*', 'vue_app/', '*/*.py', 'requirements.txt'] exclude_files = ['fabfile.py', 'deploy/*', '*.tar.gz', '.DS_Store', '*/.DS_Store', '*/.*.py', '__pycache__/*'] exclude_files = ['--exclude=\'%s\'' % t for t in exclude_files] local('rm -f %s' % TAR_FILE_NAME) local('tar -czvf %s %s %s' % (TAR_FILE_NAME, ' '.join(exclude_files), ' '.join(tar_files))) print('在当前目录创建一个打包文件: %s' % TAR_FILE_NAME) def deploy(): """ 定义一个部署任务 :return: """ # 先进行打包 pack() # 远程服务器的临时文件 remote_tmp_tar = '/tmp/%s' % TAR_FILE_NAME run('rm -f %s' % remote_tmp_tar) # 上传tar文件至远程服务器, local_path, remote_path put(TAR_FILE_NAME, remote_tmp_tar) # 解压 remote_dist_base_dir = '/home/python/django_app' # 如果不存在, 则创建文件夹 run('mkdir -p %s' % remote_dist_dir) # cd 命令将远程主机的工作目录切换到指定目录 with cd(remote_dist_dir): print('解压文件到到目录: %s' % remote_dist_dir) run('tar -xzvf %s' % remote_tmp_tar) print('安装 requirements.txt 中的依赖包') # 我使用的是 python3 来开发 run('pip3 install -r requirements.txt') remote_settings_file = '%s/django_app/settings.py' % remote_dist_dir settings_file = 'deploy/settings.py' % name print('上传 settings.py 文件 %s' % settings_file) put(settings_file, remote_settings_file) nginx_file = 'deploy/django_app.conf' remote_nginx_file = '/etc/nginx/conf.d/django_app.conf' print('上传 nginx 配置文件 %s' % nginx_file) put(nginx_file, remote_nginx_file) # 在当前目录的子目录 deploy 中的 supervisor 配置文件上传至服务器 supervisor_file = 'deploy/django_app.ini' remote_supervisor_file = '/etc/supervisord.d/django_app.ini' print('上传 supervisor 配置文件 %s' % supervisor_file) put(supervisor_file, remote_supervisor_file) # 重新加载 nginx 的配置文件 run('nginx -s reload') run('nginx -t') # 删除本地的打包文件 local('rm -f %s' % TAR_FILE_NAME) # 载入最新的配置文件,停止原有进程并按新的配置启动所有进程 run('supervisorctl reload') # 执行 restart all,start 或者 stop fabric 都会提示错误,然后中止运行 # 但是服务器上查看日志,supervisor 有重启 # run('supervisorctl restart all') 执行 pack 任务 [root@kevin ~]# fab -f fabric7.py pack 执行 deploy 任务 [root@kevin ~]# fab -f fabric7.py deploy
示例八: 代码的自动化部署
[root@kevin ~]# vim fabric8.py #coding=utf-8 from fabric.api import local, abort, settings, env, cd, run from fabric.colors import * from fabric.contrib.console import confirm env.hosts = ["root@115.28.×××××"] env.password = "×××××" def get_git_status(): git_status_result = local("git status", capture=True) if "无文件要提交,干净的工作区" not in git_status_result: print red("****当前分支还有文件没有提交") print git_status_result abort("****已经终止") def local_unit_test(): with settings(warn_only=True): test_result = local("python manage.py test") if test_result.failed: print test_result if not confirm(red("****单元测试失败,是否继续?")): abort("****已经终止") def server_unit_test(): with settings(warn_only=True): test_result = run("python manage.py test") if test_result.failed: print test_result if not confirm(red("****单元测试失败,是否继续?")): abort("****已经终止") def upload_code(): local("git push origin dev") print green("****代码上传成功") def deploy_at_server(): print green("****ssh到服务器进行下列操作") with cd("/var/www/××××××"): #print run("pwd") print green("****将在远程仓库下载代码") run("git checkout dev") get_git_status() run("git pull origin dev") print green("****将在服务器上运行单元测试") server_unit_test() run("service apache2 restart", pty=False) print green("****重启apache2成功") print green("********代码部署成功********") def deploy(): get_git_status() local("git checkout dev", capture=False) print green("****切换到dev分支") get_git_status() print green("****将开始运行单元测试") local_unit_test() print green("****单元测试完成,开始上传代码") upload_code() deploy_at_server() fabric可以将自动化部署或者多机操作的命令固化到一个脚本里,从而减少手动的操作。上面是今天第一次接触这东西后写的,确实很实用。 运行 [root@kevin ~]# fab -ff abric8.py deploy 主要逻辑就是将本地的dev分支跑单元测试,然后提交到服务器,ssh登陆到服务器,然后pull下来,再跑单元测试,然后重启apache2。 这个写的还是比较简单的。
===============这里贴出之前线上环境使用过的一个Fabric自动化配置===============
1) 通过Fabric配置的自动化python上线脚本(包括回滚脚本): [work@qd-op-zhongkong op]$ cat xcspam-celery.py from fabric.api import * from fabric.context_managers import * import datetime env.hosts=['qd-vpc-op-rule01'] def antiwater(): with cd('/app/release'): date=datetime.datetime.now().strftime("%Y%m%d%H%M%S") repo='http://git.kevinweb.com/zuiyou_server/xcspam.git'; run('git clone --depth=1 %s' % repo) newNmae="xcspam"+"-"+date run('mv xcspam %s ' % newNmae) with cd('/app/web/xcspam'): newRelease=run('ls /app/release/ |tail -1f') run('cp -rp /app/release/%s /app/web/xcspam/ ' % newRelease) run('unlink bin') run('ln -sn %s bin' % newRelease) run('superctl restart antiwater:*') def rollantiwater(): with cd('/app/web/xcspam'): lastrelease=run('ls -rtd xcspam* |tail -2 |head -1') run('unlink bin') run('ln -sn %s bin' % lastrelease) run('superctl restart antiwater:*') def report(): with cd('/app/release'): date=datetime.datetime.now().strftime("%Y%m%d%H%M%S") repo='http://git.kevinweb.com/zuiyou_server/xcspam.git'; run('git clone --depth=1 %s' % repo) newNmae="xcspam"+"-"+date run('mv xcspam %s ' % newNmae) with cd('/app/web/xcspam'): newRelease=run('ls /app/release/ |tail -1f') run('cp -rp /app/release/%s /app/web/xcspam/ ' % newRelease) run('unlink bin') run('ln -sn %s bin' % newRelease) run('superctl restart report') def rollreport(): with cd('/app/web/xcspam'): lastrelease=run('ls -rtd xcspam* |tail -2 |head -1') run('unlink bin') run('ln -sn %s bin' % lastrelease) run('superctl restart report') def chat(): with cd('/app/release'): date=datetime.datetime.now().strftime("%Y%m%d%H%M%S") repo='http://git.kevinweb.com/zuiyou_server/xcspam.git'; run('git clone --depth=1 %s' % repo) newNmae="xcspam"+"-"+date run('mv xcspam %s ' % newNmae) with cd('/app/web/xcspam'): newRelease=run('ls /app/release/ |tail -1f') run('cp -rp /app/release/%s /app/web/xcspam/ ' % newRelease) run('unlink bin') run('ln -sn %s bin' % newRelease) run('superctl restart chat') def rollchat(): with cd('/app/web/xcspam'): lastrelease=run('ls -rtd xcspam* |tail -2 |head -1') run('unlink bin') run('ln -sn %s bin' % lastrelease) run('superctl restart chat') 可以在一个脚本中定义多个上线项目,上线的时候可以选择,如下(回滚的时候选择对应的roll即可): [work@qd-op-zhongkong op]$ fab -f xcspam-celery.py antiwater [work@qd-op-zhongkong op]$ fab -f xcspam-celery.py report [work@qd-op-zhongkong op]$ fab -f xcspam-celery.py chat 2) 脚本2,其实跟上面无异: [work@qd-op-zhongkong op]$ cat xcspam-consumer.py from fabric.api import * from fabric.context_managers import * import datetime env.hosts=['qd-vpc-op-consumer01','qd-vpc-op-consumer02'] def xcspam(): with cd('/app/release'): date=datetime.datetime.now().strftime("%Y%m%d%H%M%S") repo='http://git.kevinweb.com/zuiyou_server/xcspam.git'; run('git clone --depth=1 %s' % repo) newNmae="xcspam"+"-"+date run('mv xcspam %s ' % newNmae) with cd('/app/web/xcspam'): newRelease=run('ls /app/release/ |tail -1f') run('cp -rp /app/release/%s /app/web/xcspam/ ' % newRelease) run('unlink bin') run('ln -sn %s bin' % newRelease) run('superctl restart xcspam:*') def rollxcspam(): with cd('/app/web/xcspam'): lastrelease=run('ls -rtd xcspam* |tail -2 |head -1') run('unlink bin') run('ln -sn %s bin' % lastrelease) run('superctl restart xcspam:*') def chatxcspam(): with cd('/app/release'): date=datetime.datetime.now().strftime("%Y%m%d%H%M%S") repo='http://git.kevinweb.com/zuiyou_server/xcspam.git'; run('git clone --depth=1 %s' % repo) newNmae="xcspam"+"-"+date run('mv xcspam %s ' % newNmae) with cd('/app/web/xcspam'): newRelease=run('ls /app/release/ |tail -1f') run('cp -rp /app/release/%s /app/web/xcspam/ ' % newRelease) run('unlink bin') run('ln -sn %s bin' % newRelease) run('superctl restart chatxcspam:*') def chatxcspam(): with cd('/app/web/xcspam'): lastrelease=run('ls -rtd xcspam* |tail -2 |head -1') run('unlink bin') run('ln -sn %s bin' % lastrelease) run('superctl restart chatxcspam:*’) 3) 脚本3 [work@qd-op-zhongkong op]$ cat xcspam-consumer-all.py from fabric.api import * from fabric.context_managers import * import datetime env.hosts=['qd-vpc-op-consumer01','qd-vpc-op-consumer02'] def xcspam(): with cd('/app/release'): date=datetime.datetime.now().strftime("%Y%m%d%H%M%S") repo='http://git.kevinweb.com/zuiyou_server/xcspam.git'; run('git clone --depth=1 %s' % repo) newNmae="xcspam"+"-"+date run('mv xcspam %s ' % newNmae) with cd('/app/web/xcspam'): newRelease=run('ls /app/release/ |tail -1f') run('cp -rp /app/release/%s /app/web/xcspam/ ' % newRelease) run('unlink bin') run('ln -sn %s bin' % newRelease) run('superctl restart xcspam:*') def rollxcspam(): with cd('/app/web/xcspam'): lastrelease=run('ls -rtd xcspam* |tail -2 |head -1') run('unlink bin') run('ln -sn %s bin' % lastrelease) run('superctl restart xcspam:*') def chatxcspam(): with cd('/app/release'): date=datetime.datetime.now().strftime("%Y%m%d%H%M%S") repo='http://git.kevinweb.com/zuiyou_server/xcspam.git'; run('git clone --depth=1 %s' % repo) newNmae="xcspam"+"-"+date run('mv xcspam %s ' % newNmae) with cd('/app/web/xcspam'): newRelease=run('ls /app/release/ |tail -1f') run('cp -rp /app/release/%s /app/web/xcspam/ ' % newRelease) run('unlink bin') run('ln -sn %s bin' % newRelease) run('superctl restart chatxcspam:*') def chatxcspam(): with cd('/app/web/xcspam'): lastrelease=run('ls -rtd xcspam* |tail -2 |head -1') run('unlink bin') run('ln -sn %s bin' % lastrelease) run('superctl restart chatxcspam:*') def all(): with cd('/app/release'): date=datetime.datetime.now().strftime("%Y%m%d%H%M%S") repo='http://git.kevinweb.com/zuiyou_server/xcspam.git'; run('git clone --depth=1 %s' % repo) newNmae="xcspam"+"-"+date run('mv xcspam %s ' % newNmae) with cd('/app/web/xcspam'): newRelease=run('ls /app/release/ |tail -1f') run('cp -rp /app/release/%s /app/web/xcspam/ ' % newRelease) run('unlink bin') run('ln -sn %s bin' % newRelease) run('superctl restart all') def rollall(): with cd('/app/web/xcspam'): lastrelease=run('ls -rtd xcspam* |tail -2 |head -1') run('unlink bin') run('ln -sn %s bin' % lastrelease) run('superctl restart all') 可以根据需求去选择具体对那个项目进行上线,上述脚本定义了两个项目上线,第三个(all)即表示同时上线两个项目。 [work@qd-op-zhongkong op]$ fab -f xcspam-consumer-all.py xcspam [work@qd-op-zhongkong op]$ fab -f xcspam-consumer-all.py chatxcspam [work@qd-op-zhongkong op]$ fab -f xcspam-consumer-all.py all