其實在Glow的技術團隊中是沒有全職的Ops或是Sys admin,我想很多小的創業團隊也是如此。但是運維是整個產品釋出過程中必不可少的一環,所以想寫一個針對開發工程師(特別是Python工程師)的運維入門教程。如果你是專業的運維工程師,請不要浪費你自己的時間。

先說一下,什麼是“輕量化運維”?簡而言之,就是用最快最省事的方法做好最基本的運維工作。這裡的工作主要包括以下幾個部分

  1. 生產環境伺服器叢集的日常管理
  2. 自動化系統配置與程式碼釋出
  3. 系統狀態與效能監控

今天先來聊聊伺服器的日常管理。這裡包括各種瑣碎的任務,例如重啟服務,安裝或是更新軟體包,備份日誌檔案或是資料庫等等。通常對於這類任務,我們要解決兩個問題

  • 針對每項任務編寫指令碼,避免重複勞動。雖然許多運維都是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)]
}

這裡我們定義了webdb兩個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。整個程式碼釋出的流程如下,

  1. 顯示將要部署的版本與線上版本的code diff,讓釋出人員做最後的確認。
  2. 清理git repo中所有的untracked檔案,這其中可能包括一些編譯後的程式碼(如 *.pyc),或是其他臨時生成的檔案。保證當前的git repo處在一個乾淨的狀態
  3. 將程式碼切換到tag對應的版本
  4. 重新啟動Webserver,這裡我們用supervisor來管理webserver程序。
  5. 向網站傳送一個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環境。