简介
欢迎来到DJango入门教程系列的最后一部分!在本教程中,我们将把DJango应用程序部署到生产服务器上。同时还将为我们的服务器配置电子邮件服务和HTTPS证书。
最开始我想给出一个使用虚拟专用服务器(VPS)的示例,它的应用更加广泛一点。然后使用一个平台服务,比如Heroku,但是它有太多细节需要处理。所以最终我选择做一个主要使用VPS的教程。
我们的项目已经上线啦!如果想在阅读本教程之前在线体验一下,可以直接访问我部署的网站:www.djangoboards.com。
代码版本管理工具
版本控制是软件开发中一个极其重要的课题,尤其是在与团队一起工作并同时维护生产代码、多个功能并行开发时。无论是一个开发人员项目还是多个开发人员项目,每个项目都应该进行版本控制。
代码版本管理工具有许多选择,也许是因为GitHub的流行,Git在版本控制中成为了佼佼者。因此,如果你不熟悉版本控制,用Git就是一个很好的选择。Git有许多教程、课程和资源,很容易找到需要的资料。
GitHub和Code School有一个关于Git的很好的交互式教程,我在几年前开始从SVN转到Git时就学习过这个教程,它做得很棒。
这是一个非常重要的注意事项,本该从第一个教程开始就强调它,不过因为本系列教程的重点是Django,所以还是到这里才开始讲。如果这一切对你来说都是陌生的,别担心。一步一步来。第一个项目不会是完美的,不断学习和发展你的技能才是更重要的,要持之以恒。
Git它不仅仅是一个版本控制系统,还有一个围绕它构建的工具和服务的丰富生态系统。比如说持续集成、部署、代码评审、代码质量和项目管理等。
使用Git支持Django项目的部署过程非常高效。从源代码库中提取最新版本或在出现问题时回滚到特定版本的都非常简单。还有许多服务与Git集成以便执行自动化测试和部署。
如果你的还没有安装Git,请在这里下载https://git-scm.com/downloads。
配置Git
首先配置你的身份:
git config --global user.name "Vitor Freitas"
git config --global user.email vitor@simpleisbetterthancomplex.com
找到项目根目录(manage.py
所在的目录),初始化一个Git本地仓库:
git init
Initialized empty Git repository in /Users/vitorfs/Development/myproject/.git/
检查一下代码仓库的状态:
git status
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
accounts/
boards/
manage.py
myproject/
requirements.txt
static/
templates/
nothing added to commit but untracked files present (use "git add" to track)
在继续添加源代码文件之前,请在项目根目录中创建一个.gitignore
的新文件,这个新文件将帮助我们过滤代码仓库管理的文件,忽略诸如缓存文件、日志文件等不需要进行管理的非必要文件。
你可以从GitHub上获取Python项目的常用.gitignore
文件,这里。
确认文件的名称一定要是.gitignore
,而不是Python.gitignore
之类的。
举个例子,你可以在.gitignore
里添加对SQLite数据库文件的过滤:
.gitignore
__pycache__/
*.py[cod]
.env
venv/
# SQLite database files
*.sqlite3
好了,现在我们将项目的文件添加到Git本地仓库:
git add .
注意这里的.
,这个命令的意思是将这个目录下的所有未添加到Git仓库里的文件(不包括被.gitignore
忽略的文件),添加到仓库里。
然后提交一次代码:
git commit -m "Initial commit"
养成一个好习惯,每次提交的时候都添加备注,简要说明这次提交的内容。
远程仓库
现在让我们使用GitHub来配置这个项目的一个远程仓库把。第一步,在GitHub上创建一个账号,确认你的邮件地址。然后你就可以创建GitHub仓库了。
然后我们需要为仓库命名,其他的选项(README、.gitignore、license)都暂时不要勾选或添加,确保项目是一个空项目:
在你建好项目后应该看见下面的页面:
在本地开始配置吧,运行下面的命令:
git remote add origin git@github.com:sibtc/django-boards.git
执行push命令把本地代码推到远程仓库:
git push origin master
Counting objects: 84, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (81/81), done.
Writing objects: 100% (84/84), 319.70 KiB | 0 bytes/s, done.
Total 84 (delta 10), reused 0 (delta 0)
remote: Resolving deltas: 100% (10/10), done.
To git@github.com:sibtc/django-boards.git
* [new branch] master -> master
这里创建的代码仓库只是为了演示如何给现有的项目添加Git代码仓库以及添加远程仓库,实际上这个项目的官方远程仓库是:https://github.com/sibtc/django-beginners-guide.
项目配置
不管代码是保存在一个公开或私有的远程仓库,那些敏感信息都不应该被提交到远程仓库里,例如密钥、密码、API密钥等。
基于这个原则,我们需要对settings.py
里特定的两类信息进行处理:
- 诸如密码、秘钥之类的敏感信息;
- 特定环境使用的配置信息.
密码和密钥可以使用本地文件或环境变量进行存储,不要提交到远程仓库:
# 环境变量
import os
SECRET_KEY = os.environ['SECRET_KEY']
# 或本地文件
with open('/etc/secret_key.txt') as f:
SECRET_KEY = f.read().strip()
这里推荐一个强大的工具库Python Decouple,我每一个DJango项目开发中都会用它。它可以自动检索名为.env
本地文件来关联配置的变量,进行设置和读取。同时它也提供接口来定义默认值,并在可能的条件下将数据转换为int、bool和list等类型。
这个不是必须的,但是我强烈建议使用它。
执行下面的命令安装一下:
pip install python-decouple
myproject/settings.py
from decouple import config
SECRET_KEY = config('SECRET_KEY')
然后把敏感信息抽取出来,放到项目根目录与manage.py
同级的一个新文件.env
里,注意这里也是以点开头的文件名:
myproject/
|-- myproject/
| |-- accounts/
| |-- boards/
| |-- myproject/
| |-- static/
| |-- templates/
| |-- .env <-- 这里!
| |-- .gitignore
| |-- db.sqlite3
| +-- manage.py
+-- venv/
.env
SECRET_KEY=rqr_cjv4igscyu8&&(0ce(=sy=f2)p=f_wn&@0xsp7m$@!kp=d
.env
在.gitignore
中已经被配置为了忽略,所以每当需要部署应用程序或者在新的设备上运行这个项目时,我们都需要新建一个.env
文件并添加必要的配置变量。
让我们再安装一个三方库来帮助我们进行数据库的配置。它让我们可以通过一行文本就实现对数据库连接的配置:
pip install dj-database-url
现在我们需要解耦所有的配置:
myproject/settings.py
from decouple import config, Csv
import dj_database_url
SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=False, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv())
DATABASES = {
'default': dj_database_url.config(
default=config('DATABASE_URL')
)
}
下面是我们本地设备的一个示例配置文件.env
:
SECRET_KEY=rqr_cjv4igscyu8&&(0ce(=sy=f2)p=f_wn&@0xsp7m$@!kp=d
DEBUG=True
ALLOWED_HOSTS=.localhost,127.0.0.1
注意这里DEBUG
配置项设了一个默认值,当部署到生产环境时,只需要忽略去配置这一项就可以了,它会默认为False
。
这里ALLOWED_HOSTS
将会被转换成一个类似['.localhost', '127.0.0.1'. ]
的列表,这是本地设备的配置,如果是生产设备,它应该是['.djangoboards.com', ]
这样的或者其他你所拥有的域名。
这个配置可以保证你的应用程序只在这个域名下提供服务。
项目依赖跟踪
持续保持跟踪项目的依赖配置是一个好习惯,这样可以更方便地将项目部署到其他设备上。
我们可以通过下面的命令检查现在安装的依赖库:
pip freeze
dj-database-url==0.4.2
Django==1.11.6
django-widget-tweaks==1.4.1
Markdown==2.6.9
python-decouple==3.1
pytz==2017.2
创建一个名为requirements.txt
的文件放到项目根目录,将项目的依赖拷贝到这里:
requirements.txt
dj-database-url==0.4.2
Django==1.11.6
django-widget-tweaks==1.4.1
Markdown==2.6.9
python-decouple==3.1
这里剔除了pytz==2017.2
,因为这个是由DJango自动安装的。
将文件添加到Git仓库并推送到远程仓库:
git add .
git commit -m "Add requirements.txt file"
git push origin master
域名
需要申请一个域名来部署DJango应用程序,这样我们才能配置电子邮件服务和https证书。
这里我注册了域名www.DjangoBoards.com。
部署策略
下面是本教程中将使用的部署策略的架构设计:
云服务是使用的Digital Ocean的VPS,这里根据大家实际情况自行注册。
首先会有一个NGINX,插图使用的是一个兽人。NGINX会首先接收到所有的请求,但它不会对这些请求数据做复杂的处理。除非请求的数据是它自己能提供的服务如简单的获取静态资源,其他的请求它就直接传递给Gunicorn。
NGINX还将配置HTTPS证书,这样就只接收HTTPS请求,如果客户端尝试通过HTTP访问服务,NGINX可以重定位到HTTPS服务,然后再对请求进行处理。
我们也会安装一个certbot来自动更新Let’s Encrypt的证书。
Gunicorn是一个应用服务器,根据服务器的处理能力(处理器数量),它能生成多个worker提供并行处理多任务的能力,同时它还能管理工作负载,和执行Python以及DJango代码。
DJango承担着最多的工作,它需要访问数据库(如PostgreSQL)和文件系统,大多数的工作都集中在页面里,渲染模板,所有在我们之前教程里的工作。当DJango处理完请求后,它会返回处理结果给Gunicorn,再传递给NGINX,最终再传回用户客户端。
还需要安装PostgreSQL,一个生产级别的数据库系统,通过DJango的ORM系统,我们可以很简单的进行数据库切换。
最后一步是安装Supervisor,这是一个过程监控系统,它会全程监控Gunicorn和Django来保证服务的正常运行。如果服务器重启了,或者Gunicorn异常退出了,Supervisor都会将自动重启故障的服务。
部署VPS (Digital Ocean)
你可以使用任何其他服务提供的VPS,配置方式应该是类似的,这里我们的服务器使用Ubuntu 16.04操作系统。
首先让我们创建一个新服务器(Digital Ocean里被称为Droplet
),选择操作系统Ubuntu 16.04:
选择服务器配置,这里最低配置就可以了:
输入一个主机名,这里我输入的是django-boards
:
如果你有SSH秘钥,你可以直接添加到你的账户里,这样你就可以直接使用秘钥进行登录。如果没有就只能通过邮箱获取登录根密码。
选择一下服务器的IP地址:
在登录进这个服务器前,将我们的域名指向这个IP地址,因为DNS配置需要一点时间,所以我们可以同步进行。
这里我们添加了两个A records
,一个指向域名djangoboards.com
,另一个指向www.djangoboards.com
,接下啦我们会用NGINX配置一个规范的URL。
好了,让我们在终端上登录服务器吧:
ssh root@45.55.144.54
root@45.55.144.54's password:
登录成功后你会看到下面的消息:
You are required to change your password immediately (root enforced)
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-93-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
Get cloud support with Ubuntu Advantage Cloud Guest:
http://www.ubuntu.com/business/services/cloud
0 packages can be updated.
0 updates are security updates.
Last login: Sun Oct 15 18:39:21 2017 from 82.128.188.51
Changing password for root.
(current) UNIX password:
重新设置登录密码,然后开始配置服务器吧。
sudo apt-get update
sudo apt-get -y upgrade
如果在升级中出现任何提示,选择keep the local version currently installed
。
Python 3.6
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get update
sudo apt-get install python3.6
PostgreSQL
sudo apt-get -y install postgresql postgresql-contrib
NGINX
sudo apt-get -y install nginx
Supervisor
sudo apt-get -y install supervisor
sudo systemctl enable supervisor
sudo systemctl start supervisor
Virtualenv
wget https://bootstrap.pypa.io/get-pip.py
sudo python3.6 get-pip.py
sudo pip3.6 install virtualenv
为应用程序创建用户
使用下面的命令创建新用户:
adduser boards
一般来说,我会选择使用应用程序的名称,这样可以将每个应用程序的用户权限独立开。
将这个新用户添加到sudoers列表:
gpasswd -a boards sudo
PostgreSQL 数据库配置
首先切换到postgres用户:
sudo su - postgres
创建一个数据库用户:
createuser u_boards
创建一个新的数据库文件并将这个用户设为owner
:
createdb django_boards --owner u_boards
为用户定义一个安全的密码:
psql -c "ALTER USER u_boards WITH PASSWORD 'BcAZoYWsJbvE7RMgBPzxOCexPRVAq'"
退出postgres用户:
exit
DJango项目配置
切换到应用程序用户:
sudo su - boards
首先我们看看当前目录:
pwd
/home/boards
首先将项目代码从远程仓库clone下来:
git clone https://github.com/sibtc/django-beginners-guide.git
创建一个虚拟环境:
virtualenv venv -p python3.6
初始化并启用虚拟环境:
source venv/bin/activate
安装依赖:
pip install -r django-beginners-guide/requirements.txt
这里需要安装两个额外的库:Gunicorn和PostgreSQL驱动:
pip install gunicorn
pip install psycopg2
在/home/boards/django-beginners-guide目录下,创建一个.env文件来存储数据库的秘钥、证书和其他配置:
/home/boards/django-beginners-guide/.env
SECRET_KEY=rqr_cjv4igscyu8&&(0ce(=sy=f2)p=f_wn&@0xsp7m$@!kp=d
ALLOWED_HOSTS=.djangoboards.com
DATABASE_URL=postgres://u_boards:BcAZoYWsJbvE7RMgBPzxOCexPRVAq@localhost:5432/django_boards
数据库URL的语法是:postgres://db_user
:db_password
@db_host
:db_port
/db_name
.
接下来让我们迁移数据库、处理静态文件并创建一个超级用户:
cd django-beginners-guide
python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, boards, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying boards.0001_initial... OK
Applying boards.0002_auto_20170917_1618... OK
Applying boards.0003_topic_views... OK
Applying sessions.0001_initial... OK
再处理静态文件:
python manage.py collectstatic
Copying '/home/boards/django-beginners-guide/static/js/jquery-3.2.1.min.js'
Copying '/home/boards/django-beginners-guide/static/js/popper.min.js'
Copying '/home/boards/django-beginners-guide/static/js/bootstrap.min.js'
Copying '/home/boards/django-beginners-guide/static/js/simplemde.min.js'
Copying '/home/boards/django-beginners-guide/static/css/app.css'
Copying '/home/boards/django-beginners-guide/static/css/bootstrap.min.css'
Copying '/home/boards/django-beginners-guide/static/css/accounts.css'
Copying '/home/boards/django-beginners-guide/static/css/simplemde.min.css'
Copying '/home/boards/django-beginners-guide/static/img/avatar.svg'
Copying '/home/boards/django-beginners-guide/static/img/shattered.png'
...
这个命令会将所有静态文件拷贝到一个外部目录让NGINX为用户提供服务。稍后会提到。
为应用程序创建一个超级用户:
python manage.py createsuperuser
Gunicorn配置
Gunicorn在代理服务器后面负责执行DJango代码。
在/home/boards
下创建一个新文件命名为gunicorn_start
:
#!/bin/bash
NAME="django_boards"
DIR=/home/boards/django-beginners-guide
USER=boards
GROUP=boards
WORKERS=3
BIND=unix:/home/boards/run/gunicorn.sock
DJANGO_SETTINGS_MODULE=myproject.settings
DJANGO_WSGI_MODULE=myproject.wsgi
LOG_LEVEL=error
cd $DIR
source ../venv/bin/activate
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DIR:$PYTHONPATH
exec ../venv/bin/gunicorn ${DJANGO_WSGI_MODULE}:application \
--name $NAME \
--workers $WORKERS \
--user=$USER \
--group=$GROUP \
--bind=$BIND \
--log-level=$LOG_LEVEL \
--log-file=-
这个脚本会启动应用服务器,配置提供了诸如DJango项目所在位置、使用哪个应用程序用户启动服务器等信息。
让这个文件可以被运行:
chmod u+x gunicorn_start
创建两个空文件夹,一个用来存储socket
文件,一个用来存储日志文件:
mkdir run logs
现在我们的/home/boards
目录应该是这个样子的:
django-beginners-guide/
gunicorn_start
logs/
run/
staticfiles/
venv/
staticfiles
是由collectstatic
命令创建的。
配置Supervisor
首先在/home/boards/logs/
下创建一个空的log文件:
touch logs/gunicorn.log
创建一个新的supervisor
文件:
sudo vim /etc/supervisor/conf.d/boards.conf
[program:boards]
command=/home/boards/gunicorn_start
user=boards
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/home/boards/logs/gunicorn.log
保存文件并运行下面的命令:
sudo supervisorctl reread
sudo supervisorctl update
检查一下现在的状态:
sudo supervisorctl status boards
boards RUNNING pid 308, uptime 0:00:07
配置 NGINX
下一步是配置NGINX服务器,关联静态文件,和将请求传递给Gunicorn:
在/etc/nginx/sites-available/
目录下添加一个新的配置文件命名为boards
:
upstream app_server {
server unix:/home/boards/run/gunicorn.sock fail_timeout=0;
}
server {
listen 80;
server_name www.djangoboards.com; # here can also be the IP address of the server
keepalive_timeout 5;
client_max_body_size 4G;
access_log /home/boards/logs/nginx-access.log;
error_log /home/boards/logs/nginx-error.log;
location /static/ {
alias /home/boards/staticfiles/;
}
# checks for static file, if not found proxy to app
location / {
try_files $uri @proxy_to_app;
}
location @proxy_to_app {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://app_server;
}
}
创建一个符号链接(symbolic link)到sites-enabled
文件夹:
sudo ln -s /etc/nginx/sites-available/boards /etc/nginx/sites-enabled/boards
移除默认的NGINX网站:
sudo rm /etc/nginx/sites-enabled/default
重启NGINX服务:
sudo service nginx restart
如果此时DNS配置已经更新,那么www.djangoboards.com应该已经可以访问了。
配置电子邮件服务
最开始建议使用Mailgun,它提供可靠的免费服务计划,每月可发送12,000次。
注册一个免费账号,按照步骤填写信息就可以了,非常简单。这个需要和你的域名同时使用,这个教程里就是Namecheap。
点击添加域名
按钮添加一个新的域名,根据提示进行操作,需要保证使用"mg."的子域名:
从下图的位置获取DNS的记录:
使用服务商提供的web页面,将它添加到你的域名下。
Add it to your domain, using the web interface offered by your registrar:
对MX也做童谣的操作:
将它们也添加到你的域名下:
这一步不是强制的,既然我们已经到这里了,就也把它确认一下吧:
在我们添加完DNS记录后,点击Check DNS Records Now:
这里需要一点耐心,可能需要多花一点时间来验证DNS配置是否生效。
我们可以先配置应用程序接收的连接参数。
myproject/settings.py
EMAIL_BACKEND = config('EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend')
EMAIL_HOST = config('EMAIL_HOST', default='')
EMAIL_PORT = config('EMAIL_PORT', default=587, cast=int)
EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default='')
EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=True, cast=bool)
DEFAULT_FROM_EMAIL = 'Django Boards <noreply@djangoboards.com>'
EMAIL_SUBJECT_PREFIX = '[Django Boards] '
本地设备的.env文件应该是下面这样:
SECRET_KEY=rqr_cjv4igscyu8&&(0ce(=sy=f2)p=f_wn&@0xsp7m$@!kp=d
DEBUG=True
ALLOWED_HOSTS=.localhost,127.0.0.1
DATABASE_URL=sqlite:///db.sqlite3
EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend
而生产环境的.env文件应该是下面这样:
SECRET_KEY=rqr_cjv4igscyu8&&(0ce(=sy=f2)p=f_wn&@0xsp7m$@!kp=d
ALLOWED_HOSTS=.djangoboards.com
DATABASE_URL=postgres://u_boards:BcAZoYWsJbvE7RMgBPzxOCexPRVAq@localhost:5432/django_boards
EMAIL_HOST=smtp.mailgun.org
EMAIL_HOST_USER=postmaster@mg.djangoboards.com
EMAIL_HOST_PASSWORD=ED2vmrnGTM1Rdwlhazyhxxcd0F
你可以在Mailgun上的Domain Information分段找到证书相关的信息。
-
EMAIL_HOST
: SMTP 主机名 -
EMAIL_HOST_USER
: 默认 SMTP 登录账户 -
EMAIL_HOST_PASSWORD
: 默认 密码
我们可以测试一下生产服务器的新配置。将本地设备的配置文件settings.py修改一下,提交的远程仓库。再在服务器上拉取新代码并重启Gunicorn:
git pull
编辑.env的电子邮件服务证书。
重启Gunicorn:
sudo supervisorctl restart boards
现在我们可以尝试访问密码重置流程:
在Mailgun的dashboard上你可以查看一些邮件发送的统计信息:
配置 HTTPS 证书
现在让我们给应用程序配置一个HTTPS证书,这里选择使用Let’s Encrypt。
原来配置HTTPS没有这么简单过,甚至现在可以免费使用它,他们提供了一个自动安装和刷新证书的解决方案certbot,使用起来非常简单:
sudo apt-get update
sudo apt-get install software-properties-common
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install python-certbot-nginx
现在安装证书:
sudo certbot --nginx
只需要跟着提示填写,当被问到:
Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
选择2
就会将所有的HTTP请求重定位为HTTPS请求。
这样就可以保证所有请求都是通过HTTPS来进行的:
修改配置为自动更新证书,执行下面的命令来编辑crontab
文件:
sudo crontab -e
在文件最后添加下面的这一行代码:
0 4 * * * /usr/bin/certbot renew --quiet
这个命令会在每天的凌晨4点启动任务,更新30天内到期的证书。
小结
非常感谢所有关注本系列教程的人给予的评论和反馈,我真的很感激!这是本系列的最后一个教程。希望你喜欢!
尽管这是本系列教程的最后一部分,但我计划编写一些后续教程,探索其他有趣的主题,例如数据库优化和在当前已有的基础上添加更多功能。
顺便说一句,如果你对这个项目感兴趣,项目的源代码可在GitHub上找到:https://github.com/sibtc/django-beginners-guide/
请告诉我你还想看到什么样的教程!:-)