1. 程式人生 > 程式設計 >使用Fabric自動化部署Django專案的實現

使用Fabric自動化部署Django專案的實現

文中涉及的示例程式碼,已同步更新到HelloGitHub-Team 倉庫

在上一篇教程中,我們通過手工方式將程式碼部署到了伺服器。整個過程涉及到十幾條命令,輸了 N 個字元。一旦我們本地的程式碼有更新,整個過程又得重複來一遍,這將變得非常繁瑣。

使用 Fabric 可以在伺服器中自動執行命令。因為整個程式碼部署過程都是相同的,只要我們用 Fabric 寫好部署指令碼,以後就可以通過執行指令碼自動完成部署了。

首先在本地安裝 Fabric:

$ pipenv install fabric --dev

因為 Fabric 只需在本地使用,因此使用 --dev 選項,讓 Pipenv 將 Fabric 依賴寫到 dev-packages 配置下,線上環境就不會安裝 Fabric。

部署過程回顧

在寫 Fabric 指令碼之前,先來回顧一下當我們在本地開發環境下更新了程式碼後,在伺服器上的整個部署過程。

  • 遠端連線伺服器。
  • 進入專案根目錄,從遠端倉庫拉取最新的程式碼。
  • 如果專案引入了新的依賴,需要執行 pipenv install --deploy --ignore-pipfile 安裝最新依賴。
  • 如果修改或新增了專案靜態檔案,需要執行 pipenv run python manage.py collectstatic 收集靜態檔案。
  • 如果資料庫發生了變化,需要執行 pipenv run python manage.py migrate 遷移資料庫。
  • 重啟 Nginx 和 Gunicorn 使改動生效。

整個過程就是這樣,把每一步操作翻譯成 Fabric 對應的指令碼程式碼,這樣一個自動化部署指令碼就完成了。

完善專案配置

分離 settings 檔案

為了安全,線上環境我們將 debug 改為了 False,但開發環境要改為 True,改來改去將很麻煩。此外,django 的 SECRET_KEY 是很私密的配置,django 的很多安全機制都依賴它,如果不慎洩露,網站將面臨巨大安全風險,像我們現在這樣直接寫在配置檔案中,萬一不小心公開了原始碼,SECRET_KEY 就會直接洩露,好的實踐是將這個值寫入環境變數,通過從環境變數取這個值。

解決以上問題的一個方案就是拆分 settings.py 檔案,不同環境對應不同的 settings 檔案,django 在啟動時會從環境變數中讀取 DJANGO_SETTINGS_MODULE 的值,以這個值指定的檔案作為應用的最終配置。

我們來把 settings.py 拆分,首先在 blogproject 目錄下新建一個 Python 包,名為 settings,然後建立一個 common.py,用於存放通用配置,local.py 存放開發環境的配置,production.py 存放線上環境的配置:

blogproject\
  settings\
    __init__.py
    local.py
    production.py
  settings.py

將 settings.py 檔案中的內容全部複製到 common.py 裡,並將 SECRET_KEY、DEBUG、ALLOWED_HOSTS 這些配置移到 local.py 和 production.py 中(common.py 中這些項可以刪除)。

開發環境的配置 local.py 內容如下:

from .common import *

SECRET_KEY = 'development-secret-key'
DEBUG = True
ALLOWED_HOSTS = ['*']

線上環境的配置:

from .common import *

SECRET_KEY = os.environ['DJANGO_SECRET_KEY']
DEBUG = False
ALLOWED_HOSTS = ['hellodjango-blog-tutorial.zmrenwu.com']

注意這裡我們在頂部使用 from .common import * 將全部配置從 common.py 匯入,然後根據環境的不同,在下面進行配置覆蓋。

線上環境和開發環境不同的是,為了安全,DEBUG 模式被關閉,SECRET_KEY 從環境變數獲取,ALLOWED_HOSTS 設定了允許的 HTTP HOSTS(具體作用見後面的講解)。

以上操作完成後,一定記得刪除 settings.py

現在我們有了兩套配置,一套是 local.py,一套是 production.py,那麼啟動專案時,django 怎麼知道我們使用了哪套配置呢?答案是在執行 manage.py 指令碼時,django 預設幫我們指定了。在使用 python manage.py 執行命令時,django 可以接收一個 --settings-module 的引數,用於指定執行命令時,專案使用的配置檔案,如果引數未顯示指定,django 會從環境變數 DJANGO_SETTINGS_MODULE 裡獲取。看到 manage.py 的原始碼:

def main():
  os.environ.setdefault('DJANGO_SETTINGS_MODULE','blogproject.settings')
  try:
    from django.core.management import execute_from_command_line
  except ImportError as exc:
    raise ImportError(
      "Couldn't import Django. Are you sure it's installed and "
      "available on your PYTHONPATH environment variable? Did you "
      "forget to activate a virtual environment?"
    ) from exc
  execute_from_command_line(sys.argv)

可以看到這個 main 函式,第一行的 setdefault 為我們設定了環境變數 DJANGO_SETTINGS_MODULE 的值,這句程式碼的作用是,如果當前環境中 DJANGO_SETTINGS_MODULE 的值沒有被設定,就將其設定為 blogproject.settings,所以我們使用 python manage.py 執行命令時,django 預設為我們使用了 settings.py 這個配置。

所以我們可以通過設定環境變數,來指定 django 使用的配置檔案。

對於 manage.py,通常在開發環境下執行,因此將這裡的 DJANGO_SETTINGS_MODULE 的值改為 blogproject.settings.local,這樣執行開發伺服器時 django 會載入 blogproject/settings/local.py 這個配置檔案。

另外看到 wsgi.py 檔案中,這個檔案中有一個 application,是在線上環境時 Gunicorn 載入執行的,將這裡面的 DJANGO_SETTINGS_MODULE 改為 blogproject.settings.production

這樣,在使用 manage.py 執行命令時,載入的是 local.py 的設定,而使用 gunicorn 執行專案時,使用的是 production.py 的設定。

修改 BASE_DIR 配置項

還有需要注意的一點,看到存放通用配置的 common.py 檔案,裡面有一個配置項為:

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

這個 BASE_DIR 指向專案根目錄,其獲取方式為根據所在的配置檔案向上回溯,找到專案根目錄。因為此前的目錄結構為 HelloDjango-blog-tutorial/blogproject/settings.py,因此向上回溯 2 層就到達專案根目錄。而現在目錄結構變為 HelloDjango-blog-tutorial/blogproject/settings/common.py,需向上回溯 3 層才到達專案根目錄,因此需將 BASE_DIR 進行一個簡單修改,修改如下:

BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

即再在外面包一層 os.path.dirname,再向上回退一層,到達專案根目錄。

設定 Supervisor 環境變數

此外,由於線上環境配置中的 secret_key 從環境變數獲取,因此我們改一下 supervisor 的配置,將環境變數匯入,開啟 supervisor 的配置檔案 ~/etc/supervisor/conf.d/hellodjango-blog-tutorial.ini,新增環境變數的配置語句:

environment=DJANGO_SECRET_KEY=2pe8eih8oah2_2z1=7f84bzme7^bwuto7y&f(#@rgd9ux9mp-3

因為此前可能將程式碼傳過公開的程式碼倉庫,所以最好把線上使用的 SECRET_KEY換一下。這個網站可以自動生成 SECRET_KEY:Django Secret Key Generator。

儲存配置,然後要執行 update 命令更新配置。

$ supervisorctl -c ~/etc/supervisord.conf update

編寫 Fabric 指令碼

一切準備工作均已就緒,現在就來使用 Fabric 編寫自動部署指令碼。

Fabric 指令碼通常位於 fabfile.py 檔案裡,因此先在專案根目錄下建一個 fabfile.py 檔案。

根據上述過程編寫的指令碼程式碼如下:

from fabric import task
from invoke import Responder
from ._credentials import github_username,github_password


def _get_github_auth_responders():
  """
  返回 GitHub 使用者名稱密碼自動填充器
  """
  username_responder = Responder(
    pattern="Username for 'https://github.com':",response='{}\n'.format(github_username)
  )
  password_responder = Responder(
    pattern="Password for 'https://{}@github.com':".format(github_username),response='{}\n'.format(github_password)
  )
  return [username_responder,password_responder]


@task()
def deploy(c):
  supervisor_conf_path = '~/etc/'
  supervisor_program_name = 'hellodjango-blog-tutorial'

  project_root_path = '~/apps/HelloDjango-blog-tutorial/'

  # 先停止應用
  with c.cd(supervisor_conf_path):
    cmd = 'supervisorctl stop {}'.format(supervisor_program_name)
    c.run(cmd)

  # 進入專案根目錄,從 Git 拉取最新程式碼
  with c.cd(project_root_path):
    cmd = 'git pull'
    responders = _get_github_auth_responders()
    c.run(cmd,watchers=responders)

  # 安裝依賴,遷移資料庫,收集靜態檔案
  with c.cd(project_root_path):
    c.run('pipenv install --deploy --ignore-pipfile')
    c.run('pipenv run python manage.py migrate')
    c.run('pipenv run python collectstatic --noinput')

  # 重新啟動應用
  with c.cd(supervisor_conf_path):
    cmd = 'supervisorctl start {}'.format(supervisor_program_name)
    c.run(cmd)

來分析一下部署程式碼。

deploy 函式為部署過程的入口,加上 task 裝飾器將其標註為一個 fabric 任務。

然後定義了一些專案相關的變數,主要是應用相關程式碼和配置所在伺服器的路徑。

deploy 函式被呼叫時會傳入一個 c 引數,這個引數的值是 Fabric 在連線伺服器時建立的 ssh 客戶端例項,使用這個例項可以在伺服器上執行相關命令。

接著就是執行一系列部署命令了,進入某個目錄使用 ssh 客戶端例項的 cd 方法,執行命令使用 run 方法。

需要注意的是,每次 ssh 客戶端例項執行新的命令是無狀態的,即每次都會在伺服器根目錄執行新的命令,而不是在上一次執行的命令所在目錄,所以要在同一個目錄下連續執行多條命令,需要使用 with c.cd 上下文管理器。

最後,如果伺服器沒有加入程式碼倉庫的信任列表,執行 git pull 一般會要求輸入密碼。我們程式碼託管使用了 GitHub,所以寫了一個 GitHub 賬戶密碼響應器,一旦 Fabric 檢測到需要輸入 GitHub 賬戶密碼,就會呼叫這個響應器,自動填寫賬戶密碼。

由於響應器從 _credentials.py 模組匯入敏感資訊,因此在 fabfile.py 同級目錄新建一個 _credentials.py檔案,寫上 GitHub 的使用者名稱和密碼:

github_username = your-github-username
github_password = your-github-password

當然,這個檔案包含賬戶密碼等敏感資訊,所以一定記得將這個檔案加入 .gitignore 檔案,將其排除在版本控制系統之外,別一不小心提交了公開倉庫,導致個人 GitHub 賬戶洩露。

執行 Fabric 自動部署指令碼

進入 fabfile.py 檔案所在的目錄,用 fab 命令執行這個指令碼檔案(將 server_ip 換為你線上伺服器的 ip 地址):

fab -H server_ip --prompt-for-login-password -p deploy

這時 Fabric 會自動檢測到 fabfile.py 指令碼中的 deploy 函式並執行,輸入伺服器登入密碼後回車,然後你會看到命令列輸出了一系列字串,最後看到部署完畢的訊息。

如果指令碼執行中出錯,檢查一下命令列輸出的錯誤資訊,修復問題後重新執行指令碼即可。以後當你在本地開發完相關功能後,只需要執行這一個指令碼檔案,就可以自動把最新程式碼部署到伺服器了。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。