輕量化運維之一 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環境。