共计 12119 个字符,预计需要花费 31 分钟才能阅读完成。
引言
作为一只后端工程师,经常会碰到本地开发环境跟远程服务器生产环境配置不一样的情况,比如说本地数据库使用的是 SQLite,而在远程上的数据库用的却是 Mysql。
对于一些小的修改,比如数据库的密码不一样之类的,当然可以选择直接 SSH 登录上服务器然后开 VIM 修改,但是我觉得这并非一个“懒惰”的工程师的追求。
接下来我将介绍两种方法来部署不同配置的 Django。当然,既然是不同的环境,两份配置文件是必须的。
Ubuntu 14.04 下 Django+MySQL 安装部署全过程 http://www.linuxidc.com/Linux/2016-02/128714.htm
Django1.8 返回 json 字符串和接收 post 的 json 字符串内容 http://www.linuxidc.com/Linux/2015-07/120226.htm
如何使用 Docker 组件开发 Django 项目?http://www.linuxidc.com/Linux/2015-07/119961.htm
Ubuntu Server 12.04 安装 Nginx+uWSGI+Django 环境 http://www.linuxidc.com/Linux/2012-05/60639.htm
Django+Nginx+uWSGI 部署 http://www.linuxidc.com/Linux/2013-02/79862.htm
Django 实战教程 http://www.linuxidc.com/Linux/2013-09/90277.htm
Django Python MySQL Linux 开发环境搭建 http://www.linuxidc.com/Linux/2013-09/90638.htm
准备
新建一个 Django 项目 settingsTest,并准备两份不同的 settings:
├── manage.py
└── settingsTest
├── __init__.py
├── dev_settings.py
├── pro_settings.py
├── settings.py
├── urls.py
└── wsgi.py
在 manage.py 中,django 已经自动设置了读取的配置模块,当然这是可以修改的。
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settingsTest.settings")
从默认的 settings 中导入不同配置
在这里我不打算修改 django 的默认配置模块,我选择了在 settingsTest.settings 中导入 dev_settings.py 或者 pro_settings.py 的配置,但是如何辨别当前环境是生产环境还是开发环境呢?答案是通过设置一个生产环境才有的环境变量,然后在 settings.py 中判断这个环境变量的存在与否来决定导入哪一份配置。
值得注意的是,settings.py 本身是一个 py 模块,因此在里面添加 if 语句是可以允许的。(详细查阅:Django Settings)
# settings.py
import os
## Default settings
## Shared settings between development and production
if os.environ.get('DJANGO_PRODUCTION_SETTINGS', None):
from .pro_settings import *
else:
from .dev_settings import *
在运行时指定不同的 settings 模块
manage.py 在运行时还提供了–settings 选项用于指定 settings 模块,因此用于启动 django 自带的 debug 服务器还是挺方便的。当然,如果是使用诸如 uwsgi 之类的生产服务器就应该通过修改 manage.py 中的 DJANGO_SETTINGS_MODULE 环境变量来实现。
python manage.py rumserver 0.0.0.0:8000 --settings=settingsTest.pro_settings
这里有 stack overflow 的传送门:How to manage local vs production settings in Django?
更多详情见请继续阅读下一页的精彩内容 :http://www.linuxidc.com/Linux/2016-10/135743p2.htm
引言
在上一篇自动部署 Django 项目详解(一):开发配置与生产配置,已经给出了通过环境变量来实现不同配置选择的解决方案。既然是环境变量,那就可以通过 shell 脚本或者 Python 脚本来实现。
接下来,我将通过使用 python 编写的 Git Hooks 来实现在向生产服务 Git 推送的时候完成自动部署。
准备
首先明确 Git Hooks 要完成哪些任务:
- 判断 python debug server 在 CentOS 系统服务器上是否运行在指定端口,如果在运行则杀掉该进程。
- git pull 前端的 release 分支。
- git pull 后端的 release 分支并通过 nohup 重启 python debug server。
明确要完成的任务之后,还有几点利用 Python 编写 Git Hooks 要注意的地方:
-
由于 Git Hooks 是支持 shell,python,ruby 脚本的,因此在 post-receive 脚本的第一行应该声明使用哪个解释器(interpreter):
#!/usr/bin/env python
stack overflow 传送门:Why do people write #!/usr/bin/env python on the first line of a Python script?
- stack overflow 传送门:How do I“cd”in Python?
- 如何在 Python 中执行 shell 命令呢?我使用的是 Python 中的 subprocess 模块,这个模块是为了取代诸如 os.system,os.popen,commands 模块,其中 commands 模块在 Python3.0 中已经被废弃了。同时使用 subprocess 模块也是 Python3 cookbook 所推荐的。
subprocess 官方文档传送门:subprocess — Subprocess management。
Python Cookbook 传送门:执行外部命令并获取它的输出。
杀掉 Python debug server 进程
如果 server 进程运行在 9999 端口,使用 linux 提供的命令如何杀掉进程呢?
首先通过 lsof 命令观察 9999 端口是否有进程运行,如果有则获取该进程的 pid,得到以后通过 kill 命令杀掉该进程。因此,我们要在 Python 中完成对 lsof 命令输出的截取。
其中,subprocess.call() 调用会返回命令执行的状态,1 表示错误,0 表示正常执行。
import subprocess
import os
import signal
# kill the python debug server
server_status = subprocess.call(lsof_command, shell=True)
if server_status == 0:
server_res_bytes = subprocess.check_output("lost -i:9999 | tail -n 1", shell=True)
server_res = server_res_bytes.split()
python_server_pid = int(server_res[1])
try:
os.kill(python_server_pid, signal.SIGKILL)
except OSError:
pass
更新前端和后端分支
在确认 Python debug server 已经被杀掉的基础上,可以进行对分支的 pull 和 checkout 了。
# checkout the FrontEnd branch
os.chdir(frontend_path)
subprocess.call("git fetch --all", shell=True)
subprocess.call("git reset --hard origin/" + fronend_branch, shell=True)
# checkout the BackEnd branch
os.chdir(backend_path)
subprocess.call("git fetch --all", shell=True)
subprocess.call("git reset --hard origin/" + backend_branch, shell=True)
OK,但是在这里我遇到了一个神秘的错误:
remote: fatal: Not a git repository: '.'
这是为什么呢?原来 Git Hooks 在执行的时候会设置 $GIT_DIR 变量为. 而并非.git,这会导致 Git 会去寻找./HEAD,而并非.git/HEAD,因此在 hooks 脚本一开始需要执行 unset GIT_DIR 或者把 GIT_DIR 设置成.git。
在一开始的时候,我直接调用了 subprocess.call(‘unset GIT_DIR’, shell=True),然而并不起作用,这是因为 subprocess 只是在子进程中 unset 了变量,而并非当前的 Python 进程,因此我要通过 os.environ 影响当前 Python 进程衍生的子进程。
# didn't work
subprocess.call('unset GIT_DIR', shell=True)
# work fine
os.unsetenv('GIT_DIR')
stackoveflow 传送门:Git checkout in post-receive hook:“Not a git repository‘.’”
以及 Git 如果在 Git 仓库中 Git_DIR 就会被设置成.git,而非 git 仓库的时候就是.,而 Git 命令在执行的时候会默认去寻找.git 目录。传送门:Receiving“fatal: Not a git repository”when attempting to remote add a Git repo
重启 Python debug server
最后只需要通过 manage.py 重启 debug server 就可以了,但在这之前需要设置上一篇中提及在生产环境的环境变量。
# restart the python debug server
os.putenv('DJANGO_PRODUCTION_SETTINGS', 'TRUE')
try:
subprocess.call("nohup python manage.py runserver 9999 &", shell=True)
except Exception:
pass
OK,通过 nohup 启动之后我尴尬地发现在提交的时候远程卡住了除非我发出了终止指令。。。
这是因为通过 ssh 启动的 nohup 必须要重定向 stdin,stdout,stderr。
stackoveflow 传送门:Use SSH to start a background process on a remote server, and exit session
简单说一下 linux 的重定向,0 是标准输入,1 是标准输出,2 是错误输出,如今我想把 1 和 2 都输入到一个文件中,不能直接使用 1 > file 2> file,因为这会引起文件的接受混乱,必须用特殊的语法,比如 2 >&1。
# work fine
subprocess.call("nohup python manage.py runserver 9999 > /nohup.out 2>&1 &", shell=True)
引言
作为一只后端工程师,经常会碰到本地开发环境跟远程服务器生产环境配置不一样的情况,比如说本地数据库使用的是 SQLite,而在远程上的数据库用的却是 Mysql。
对于一些小的修改,比如数据库的密码不一样之类的,当然可以选择直接 SSH 登录上服务器然后开 VIM 修改,但是我觉得这并非一个“懒惰”的工程师的追求。
接下来我将介绍两种方法来部署不同配置的 Django。当然,既然是不同的环境,两份配置文件是必须的。
Ubuntu 14.04 下 Django+MySQL 安装部署全过程 http://www.linuxidc.com/Linux/2016-02/128714.htm
Django1.8 返回 json 字符串和接收 post 的 json 字符串内容 http://www.linuxidc.com/Linux/2015-07/120226.htm
如何使用 Docker 组件开发 Django 项目?http://www.linuxidc.com/Linux/2015-07/119961.htm
Ubuntu Server 12.04 安装 Nginx+uWSGI+Django 环境 http://www.linuxidc.com/Linux/2012-05/60639.htm
Django+Nginx+uWSGI 部署 http://www.linuxidc.com/Linux/2013-02/79862.htm
Django 实战教程 http://www.linuxidc.com/Linux/2013-09/90277.htm
Django Python MySQL Linux 开发环境搭建 http://www.linuxidc.com/Linux/2013-09/90638.htm
准备
新建一个 Django 项目 settingsTest,并准备两份不同的 settings:
├── manage.py
└── settingsTest
├── __init__.py
├── dev_settings.py
├── pro_settings.py
├── settings.py
├── urls.py
└── wsgi.py
在 manage.py 中,django 已经自动设置了读取的配置模块,当然这是可以修改的。
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settingsTest.settings")
从默认的 settings 中导入不同配置
在这里我不打算修改 django 的默认配置模块,我选择了在 settingsTest.settings 中导入 dev_settings.py 或者 pro_settings.py 的配置,但是如何辨别当前环境是生产环境还是开发环境呢?答案是通过设置一个生产环境才有的环境变量,然后在 settings.py 中判断这个环境变量的存在与否来决定导入哪一份配置。
值得注意的是,settings.py 本身是一个 py 模块,因此在里面添加 if 语句是可以允许的。(详细查阅:Django Settings)
# settings.py
import os
## Default settings
## Shared settings between development and production
if os.environ.get('DJANGO_PRODUCTION_SETTINGS', None):
from .pro_settings import *
else:
from .dev_settings import *
在运行时指定不同的 settings 模块
manage.py 在运行时还提供了–settings 选项用于指定 settings 模块,因此用于启动 django 自带的 debug 服务器还是挺方便的。当然,如果是使用诸如 uwsgi 之类的生产服务器就应该通过修改 manage.py 中的 DJANGO_SETTINGS_MODULE 环境变量来实现。
python manage.py rumserver 0.0.0.0:8000 --settings=settingsTest.pro_settings
这里有 stack overflow 的传送门:How to manage local vs production settings in Django?
更多详情见请继续阅读下一页的精彩内容 :http://www.linuxidc.com/Linux/2016-10/135743p2.htm
引言
在上一篇自动部署 Django 项目详解使用 Python 编写 Git Hooks,笔者直接通过 Python 模拟正常的人肉 linux 命令来确定 python debug server 是否在指定端口运行,如果正在运行则先杀掉该进程,在更新了 Git 仓库之后再人肉启动 python debug server。
咦,好像有哪里不对,为什么不直接删掉文件,然后进程不就自动结束了吗?这样子就不用检查端口是否有进程在运行了。
相信大家都试过,在 linux 下如果先开启 python debug server 进程,然后把进程的相关文件都删掉,而进程还能正常运行。这是因为 linux 下的删除(rm)其实是减少对文件的 link 数目,虽然删除了文件,但是打开的进程还保持着对文件的一个 link,因此在执行了 rm 之后文件并不会被立即删除,只有 link 数为 0 的时候才会被删除。
因此,笔者希望在服务器上运行一个进程管理工具监控 Git 仓库,每当仓库更新的时候,进程管理工具就自动重启 python debug server,而 Git Hooks 只要负责仓库的更新就可以了。
在 Python 的进程管理工具中,supervisor 应该是比较广为人知,而 uWSGI emperor 的曝光度似乎并不太高,然而基于 nginx+uwsgi+django 的标配,笔者还是对 uWSGI emperor 的折腾比较期待的。
uWSGI 文档传送门:Quickstart for Python/WSGI applications
准备
-
uWSGI 与 uwsgi 是同一样东西吗?它们有什么区别?
先给出结论:uWSGI 是一个生产用服务器,而 uwsgi 是一种网络通信协议(在 uWSGI 文档中也承认了这个名字的选择真的是 wrong name choice)。
Django 为开发者提供了一个开发调试用的服务器,只要通过 python manage.py runserver 就可以在默认的运行端口 8000 提供服务,但是这个服务器不能在生产环境中使用,需要更为稳定的服务器提供服务,而 uWSGI 也是 Django 官方文档中推荐使用的生产服务器。
至于 uwsgi 协议则多用于与反向代理服务器的内部信息通信,nginx 支持对 uwsgi 协议的解析,而为什么使用 uwsgi 协议而不是在内部继续使用 http 协议进行通信,uWSGI 的文档给出以下解释:Why not simply use HTTP as the protocol?
A good question with a simple answer: HTTP parsing is slow, really slow. Why should we do a complex task twice? The web server has already parsed the request! The uwsgi protocol is very simple to parse for a machine, while HTTP is very easy to parse for a human. As soon as humans are being used as servers, we will abandon the uwsgi protocol in favor of the HTTP protocol. All this said, you can use uWSGI via Native HTTP support, FastCGI, ZeroMQ and other protocols as well.uWSGI 文档传送门:Frequently Asked Questions (FAQ)
-
折腾环境选择
笔者开发使用的 mac 系统:uname -a Darwin bogon 15.6.0 Darwin Kernel Version 15.6.0: Thu Jun 23 18:25:34 PDT 2016; root:xnu-3248.60.10~1/RELEASE_X86_64 x86_64
要部署到的 CentOS 服务器系统:
Linux localhost.localdomain 3.10.0-327.28.3.el7.x86_64 #1 SMP Thu Aug 18 19:05:49 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
配置文件 uwsgi.ini
首先新建一个名为 uwsgi_test 的 Django 项目:
uwsgi_test
├── manage.py
├── uwsgi_test
│ ├── __init__.py
│ ├── __init__.pyc
│ ├── settings.py
│ ├── settings.pyc
│ ├── urls.py
│ ├── urls.pyc
│ ├── wsgi.py
│ └── wsgi.pyc
└── uwsgi_test.ini
uwsgi_test.ini 配置如下:
[uwsgi]
chdir = /path/to/uwsgi_test
module = uwsgi_test.wsgi
master = true
processes = 4
http = :8080
vaccum = true
当在 Mac 系统上运行如下的 uwsgi 的启动命令之后就能访问浏览器的 8080 端口并看到 Django 的默认欢迎界面了。
uwsgi --ini uwsgi_test.ini
在以上的配置中生成了一个主进程和四个进程,以及一个 Http 路由(Http Router)进程,其中主进程负责当 4 个进程中有进程 die 的时候重新生成一个进程,而 Http Router 进程则负责将请求转发给 WSGI Application。
*** Operational MODE: preforking ***
WSGI app 0 (mountpoint='') ready in 0 seconds on interpreter 0x7f9c5ae000d0 pid: 8514 (default app)
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 8514)
spawned uWSGI worker 1 (pid: 8515, cores: 1)
spawned uWSGI worker 2 (pid: 8516, cores: 1)
spawned uWSGI worker 3 (pid: 8517, cores: 1)
spawned uWSGI worker 4 (pid: 8518, cores: 1)
spawned uWSGI http 1 (pid: 8519)
配置选项:http,socket 与 http-socket
-
当选择 http 的选项的时候,uWSGI 就会启动一个 Http 服务器(或者叫 Http Router)来负责把请求传给 WSGI 的 Application,因此,所有的浏览器都能直接访问由 uWSGI 通过 http 选项启动的端口(此时 uWSGI 使用的是 HTTP 协议进行通信)。此时,Http Router 就相当于一个 nginx 负责反向代理,然后在内部通过 uwsgi 协议或其他协议传给处理的进程。
(uWSGI 官方文档对 Http 选项以及 Http Router 的一些讲解传送门:Native HTTP support)
(uWSGI 官方文档传送门:Things to know (best practices and“issues”) READ IT !!!) -
当选择 socket 选项的时候,此时 uWSGI 就会通过默认的 uwsgi 协议与外界进行通信了,因此 socket 选项一般是与 nginx 反向服务器搭配进行,并且不会启动 Http Router 进程,如果此时直接通过浏览器访问端口(Http 协议),uWSGI 是不会进行响应的。其中,官方文档更推荐通过.sock 文件进行信息通信而非 TCP 端口。
-
当选择 http-socket 选项的时候,也不会产生 Http Router 进程,而 uWSGI 中所有负责处理的进程都会通过 Http 协议与外界通信。
在 centos 系统上运行配置文件
当笔者在 centos 系统上部署相同的项目和配置文件时,uWSGI 却报了一个奇怪的错误。
The -s/--socket option is missing and stdin is not a socket.
笔者在上面使用的明明是 http 选项,为什么会出现 socket 选项呢?
这是因为不同的 linux 发行版的包安装工具所安装的 uWSGI,有时候会以模块的方式安装,并且在运行的时候默认不会加载这些模块,比如 http 选项就需要 http 的 plugin。因此官方建议下载 uWSGI 的源码下来自行 build。
uWSGI 官方文档解释如下:
Installing uWSGI with Python support
When you start learning uWSGI, try to build from official sources: using distribution-supplied packages may bring you plenty of headaches. When things are clear, you can use modular builds (like the ones available in your distribution).Installing via your package distribution is not covered (would be impossible to make everyone happy), but all of the general rules apply.
One thing you may want to take into account when testing this quickstart with distro-supplied packages, is that very probably your distribution has built uWSGI in modular way (every feature is a different plugin that must be loaded). To complete this quickstart, you have to prepend –plugin python,http to the first series of examples, and –plugin python when the HTTP router is removed (if this doesn’t make sense to you, just continue reading).
因此,在 ini 配置文件中加上:
plugin = python, http
就能解决这个问题了。
uWSGI emperor
OK,在前面的介绍中,uWSGI 终于能正常运行了。下面介绍正餐 emperor 模式吧。
emperor 翻译为中文的意思其实就是皇帝的意思,那么既然有皇帝,那就肯定有“臣子”(vassals),其中“臣子”就是指实际运行的一个 app 实例,在这里就是 uwsgi_test.ini 配置文件了。
emperor 模式开启的命令是:
sudo uwsgi --emperor /path/to/vassals/
其中,vassals 文件夹里包含了各个 app 的配置文件,这个文件夹将会被 emperor 一直监视,只要有配置文件的修改或新建,app 实例就会被重新加载或新建,可以通过软链接将实际项目中的配置文件链接到 vassal 文件夹目录下:
ln -s /path/to/uwsgi_test.ini /path/to/vassals/
uWSGI 官方文档展示了 uWSGI emperor 的功能:
- Whenever an imperial monitor detects a new configuration file, a new uWSGI instance will be spawned with that configuration.
- Whenever a configuration file is modified (its modification time changed, so touch –no-dereference may be your friend), the corresponding app will be reloaded.
- Whenever a config file is removed, the corresponding app will be stopped.
- If the emperor dies, all the vassals die.
- If a vassal dies for any reason, the emperor will respawn it.
在文档中,emperor 监视的是配置文件目录,但是问题来了,笔者希望每次 Git 仓库更新一次就重新启动实例,而如果这次更新并没有对配置文件进行修改,实例自然就不会重新启动了。
幸好配置文件提供了 touch-reload 选项,只要指定文件夹发生改动就重启实例:
touch-reload = /path/to/uwsgi_test
# or touch-reload = .git/index
最后一个问题,在上一篇 自动部署 Django 项目详解(一):开发配置与生产配置中,笔者通过环境变量来确定 Django 配置的加载,因此在 uWSGI 的配置文件中还需要加上对环境变量的添加:
env = DJANGO_PRODUCTION_SETTINGS=TRUE
最后,使用 nohup 命令启动 uWSGI emperor 即可实现进程管理(别忘了 Git Hooks 需修改为只拉取仓库更新)
完整的配置
[uwsgi]
plugin = python, http
env = DJANGO_PRODUCTION_SETTINGS=TRUE
chdir = /path/to/uwsgi_test
module = umefit.wsgi
master=True
processes = 4
http = :8080
vaccum=True
touch-reload = /path/to/uwsgi_test
OK,搞定。
Django 的详细介绍 :请点这里
Django 的下载地址 :请点这里
本文永久更新链接地址 :http://www.linuxidc.com/Linux/2016-10/135743.htm