轻量化运维之一 Fabric
其实在Glow的技术团队中是没有全职的Ops或是Sys admin,我想很多小的创业团队也是如此。但是运维是整个产品发布过程中必不可少的一环,所以想写一个针对开发工程师(特别是Python工程师)的运维入门教程。如果你是专业的运维工程师,请不要浪费你自己的时间。
先说一下,什么是“轻量化运维”?简而言之,就是用最快最省事的方法做好最基本的运维工作。这里的工作主要包括以下几个部分
- 生产环境服务器集群的日常管理
- 自动化系统配置与代码发布
- 系统状态与性能监控
今天先来聊聊服务器的日常管理。这里包括各种琐碎的任务,例如重启服务,安装或是更新软件包,备份日志文件或是数据库等等。通常对于这类任务,我们要解决两个问题
- 针对每项任务编写脚本,避免重复劳动。虽然许多运维都是Shell脚本达人,但大部分程序员看到Shell脚本都是一个脑袋两个大。
- 需要在许多台机器上执行同一项任务。
如果你碰巧对Python略知一二,那么恭喜你,从此以后你只要使用Fabric这个神器,就再不用为Shell脚本头痛了。
Fabric初探 - 执行单条指令
Fabric可以通过pip来安装
pip install fabric
它的功能是将一个任务通过ssh在多台服务器上执行,而每个任务可以是单条shell指令或是一段python脚本。一个最基本的例子
fab -H web0,web1 -- sudo apt-get update
在web0和web1两台机器上以root来运行apt-get update
指令。当然前提是,当前用户可以通过本机ssh到web0和web1两台机器。如果你嫌一台一台服务器按序执行太慢,你可以加上-P
参数,让所有服务器同时执行。
用Role对服务器分组
在之前的例子中,我们已经知道Fabric可以在多台服务器上执行任务。但当服务器数量较多时,手动指定一组服务器就比较麻烦了。Fabric提供了Role的概念,你可以将一组功能相同的服务器定义为一个Role,Role的定义需要写在名为fabfile.py
文件里,fabric
在执行时会默认读取当前目录下的fabfile.py
文件,例如:
from fabric.api import env
env.roledefs = {
'web': ['web%d.example.com' % i for i in xrange(10)],
'db': ['db%d.example.com' % i for i in xrange(4)]
}
这里我们定义了web
和db
两个Role,其中web
包括了web0.example.com到web9.example.com共10台服务器,db
包括了4台服务器。例如,我们需要在所有的web服务器上安装nginx,在所有的db服务器上安装mysql,则只需下面两行命令
fab -P -R web -- sudo apt-get -y install nginx
fab -P -R db -- sudo apt-get -y install mysql-server
这里用-P
也使得安装过程在这些服务器上并行执行。
Fabric的核心 - 执行任务
单条指令能做的事情非常有限,Fabric中可以定义任务。通常一个任务对应于fabfile.py
中的一个函数方法,配合Fabric自身提供的API,可以对远程的服务器执行一组指令。既可以保留Shell指令简小精悍的优点,又不失Python语言出色的可读性。我们来看一个代码发布的例子。
在这个例子中,我们假设目标服务器上已经有代码的git repo,并且我们已经为将要部署的代码生成了tag并push到了remote。整个代码发布的流程如下,
- 显示将要部署的版本与线上版本的code diff,让发布人员做最后的确认。
- 清理git repo中所有的untracked文件,这其中可能包括一些编译后的代码(如 *.pyc),或是其他临时生成的文件。保证当前的git repo处在一个干净的状态
- 将代码切换到tag对应的版本
- 重新启动Webserver,这里我们用supervisor来管理webserver进程。
- 向网站发送一个http请求,确保网站在代码更新后处于正常状态。
import requests
from fabric.api import task, sudo, prompt
@task
def deploy(tag):
"""
Deploy new code version and reload the webserver
Version: 1.0
"""
with cd('/repos/example'):
sudo('git diff --stat HEAD..{}'.format(tag))
if not prompt('Does the code diff look good to you? [y/N]').lower() == 'y':
print 'Abort'
return
sudo('git clean -xdf')
sudo('git fetch --all')
sudo('git checkout {}'.format(tag))
sudo('supervisorctl restart webserver')
resp = requests.get('http://www.example.com/ping')
print 'Web server health check: {}'.format('OK' if resp.status_code == 200 else 'FAILED')
用下面的命令来执行该任务
fab -H web0.example.com deploy:v0.1.0
以上的代码看上去还不错,但如果你需要在多台服务器上部署代码的话,则会碰到一些问题。比如code diff和http request都会被执行多次,而实际上它们只需要被执行一次。在下面的版本中我们用到了Fabric的execute
方法,它可以在一个任务中调用另一个任务,并指定在哪些服务器上执行这个子任务。以下是改进后的版本,
import requests
import sys
from fabric.api import task, sudo, prompt
@task
def deploy(tag):
"""
Deploy new code version and reload the webserver
Version: 2.0
"""
execute(check_code_diff, hosts=['web0.example.com'])
execute(update_code, roles=['www'])
execute(restart_webserver, roles=['www'])
resp = requests.get('http://www.example.com/ping')
print 'Web server health check: {}'.format('OK' if resp.status_code == 200 else 'FAILED')
@task
def check_code_diff(tag):
with cd('/repos/example'):
sudo('git diff --stat HEAD..{}'.format(tag))
if not prompt('Does the code diff look good to you? [y/N]').lower() == 'y':
sys.exit(1)
@task
@parallel
def update_code(tag):
sudo('git clean -xdf')
sudo('git fetch --all')
sudo('git checkout {}'.format(tag)
@task
@parallel(pool_size=4)
def restart_webserver():
sudo('supervisorctl restart webserver')
执行该任务时不需要指定服务器,因为已经在任务代码中指定了。、
fab deploy:v0.1.0
这个版本解决了上面提到的两个问题。另外值得注意的是update_code
可以在所有服务器上并行执行,但我们不能对所有的服务器同时重启webserver,这样做会使得整个网站在短时间内无法响应任何请求。在这个例子中我们一共有10台服务器,将@parallel(pool_size=4)
保证同一时间最多只有4台服务器在重启webserver进程,确保了发布过程中web服务的可用性。
小结
Fabric是将Python, Shell和SSH的功能很优雅地结合在了一起,同时自身又非常的轻量,适合大部分服务器群的日常管理工作。下次和大家分享一个更重量级的武器Ansible,它的强项在于多环境下服务器的自动化配置,用同一套代码管理development, sandbox和production环境。