做者:HelloGitHub-追梦人物python
文中涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库git
在上一篇教程中,咱们经过手工方式将代码部署到了服务器。整个过程涉及到十几条命令,输了 N 个字符。一旦咱们本地的代码有更新,整个过程又得重复来一遍,这将变得很是繁琐。github
使用 Fabric 能够在服务器中自动执行命令。由于整个代码部署过程都是相同的,只要咱们用 Fabric 写好部署脚本,之后就能够经过运行脚本自动完成部署了。web
首先在本地安装 Fabric:数据库
$ pipenv install fabric --dev
由于 Fabric 只需在本地使用,所以使用 --dev
选项,让 Pipenv 将 Fabric 依赖写到 dev-packages 配置下,线上环境就不会安装 Fabric。django
在写 Fabric 脚本以前,先来回顾一下当咱们在本地开发环境下更新了代码后,在服务器上的整个部署过程。编程
pipenv install --deploy --ignore-pipfile
安装最新依赖。pipenv run python manage.py collectstatic
收集静态文件。pipenv run python manage.py migrate
迁移数据库。整个过程就是这样,把每一步操做翻译成 Fabric 对应的脚本代码,这样一个自动化部署脚本就完成了。安全
为了安全,线上环境咱们将 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_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 的设置。
还有须要注意的一点,看到存放通用配置的 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,再向上回退一层,到达项目根目录。
此外,因为线上环境配置中的 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 脚本一般位于 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 帐户泄露。
进入 fabfile.py 文件所在的目录,用 fab 命令运行这个脚本文件(将 server_ip 换为你线上服务器的 ip 地址):
fab -H server_ip --prompt-for-login-password -p deploy
这时 Fabric 会自动检测到 fabfile.py 脚本中的 deploy 函数并运行,输入服务器登陆密码后回车,而后你会看到命令行输出了一系列字符串,最后看到部署完毕的消息。
若是脚本运行中出错,检查一下命令行输出的错误信息,修复问题后从新运行脚本便可。之后当你在本地开发完相关功能后,只须要执行这一个脚本文件,就能够自动把最新代码部署到服务器了。
『讲解开源项目系列』——让对开源项目感兴趣的人再也不畏惧、让开源项目的发起者再也不孤单。跟着咱们的文章,你会发现编程的乐趣、使用和发现参与开源项目如此简单。欢迎留言联系咱们、加入咱们,让更多人爱上开源、贡献开源~