使用 Fabric 自動化部署 Django 項目

做者:HelloGitHub-追夢人物python

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

在上一篇教程中,咱們經過手工方式將代碼部署到了服務器。整個過程涉及到十幾條命令,輸了 N 個字符。一旦咱們本地的代碼有更新,整個過程又得重複來一遍,這將變得很是繁瑣。github

使用 Fabric 能夠在服務器中自動執行命令。由於整個代碼部署過程都是相同的,只要咱們用 Fabric 寫好部署腳本,之後就能夠經過運行腳本自動完成部署了。web

首先在本地安裝 Fabric:數據庫

$ pipenv install fabric --dev

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

部署過程回顧

在寫 Fabric 腳本以前,先來回顧一下當咱們在本地開發環境下更新了代碼後,在服務器上的整個部署過程。編程

  1. 遠程鏈接服務器。
  2. 進入項目根目錄,從遠程倉庫拉取最新的代碼。
  3. 若是項目引入了新的依賴,須要執行 pipenv install --deploy --ignore-pipfile 安裝最新依賴。
  4. 若是修改或新增了項目靜態文件,須要執行 pipenv run python manage.py collectstatic 收集靜態文件。
  5. 若是數據庫發生了變化,須要執行 pipenv run python manage.py migrate 遷移數據庫。
  6. 重啓 Nginx 和 Gunicorn 使改動生效。

整個過程就是這樣,把每一步操做翻譯成 Fabric 對應的腳本代碼,這樣一個自動化部署腳本就完成了。安全

完善項目配置

分離 settings 文件

爲了安全,線上環境咱們將 debug 改成了 False,但開發環境要改成 True,改來改去將很麻煩。此外,django 的 SECRET_KEY 是很私密的配置,django 的不少安全機制都依賴它,若是不慎泄露,網站將面臨巨大安全風險,像咱們如今這樣直接寫在配置文件中,萬一不當心公開了源代碼,SECRET_KEY 就會直接泄露,好的實踐是將這個值寫入環境變量,經過從環境變量取這個值。bash

解決以上問題的一個方案就是拆分 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_KEYDEBUGALLOWED_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 函數並運行,輸入服務器登陸密碼後回車,而後你會看到命令行輸出了一系列字符串,最後看到部署完畢的消息。

若是腳本運行中出錯,檢查一下命令行輸出的錯誤信息,修復問題後從新運行腳本便可。之後當你在本地開發完相關功能後,只須要執行這一個腳本文件,就能夠自動把最新代碼部署到服務器了。


『講解開源項目系列』——讓對開源項目感興趣的人再也不畏懼、讓開源項目的發起者再也不孤單。跟着咱們的文章,你會發現編程的樂趣、使用和發現參與開源項目如此簡單。歡迎留言聯繫咱們、加入咱們,讓更多人愛上開源、貢獻開源~

相關文章
相關標籤/搜索