Django 将项目部署到云服务器详解

我们的博客虽然还有很多不完善的地方,但是没关系,越早把它部署到互联网上,才能越早发现线上特有的问题。现在也提倡渐进式开发,让产品在迭代中快速成长。

部署考验的不是你的 Django 编程水平,而是你对 Linux 的操作能力,以及对网络通信的理解。多说无益,直接开干!

配置服务器

要架设网站,首先你要有一台连接到互联网的服务器。国内比较出名的云服务器属阿里云腾讯云百度云,三家各有优劣,大家自行了解比较,并选择自己适合的购买。

利益相关:博主自己用的是阿里云,所以教程会以阿里云ECS作为例子讲解。新用户通过推广链接注册有折扣和现金券(目前是20元);学生有优惠服务器每月9.5元,性能还不错,很划算。如果你想用其他云服务器,操作流程也差不多,不必担心。

首先进入阿里云ECS的购买页面

图片字很小,看不清楚的同学将就一下放大看吧。

挑重点说一下:

点击下一步,来到网络和安全组页面:

这页默认就行了,公网带宽选最低的 1M ,初期够用了。

点击下一步,到系统配置页面:

为了后面远程连接服务器更简单,这里勾选自定义密码,也就是输入用户/密码的认证方式了。实际上秘钥对的认证方式更安全些,以后摸熟了再改回来吧。

点击下一步,到分组设置页面。这个页面全部默认设置就好了。点击下一步,确认订单无误后,就可以付款啦。

付款成功后,通过控制台就可以看到已购买的云服务器了:

这里有时候会有黄字提醒你服务器的网络端口没开,点击黄字链接去开通一下:

把 22(远程连接端口)、443(HTTPS端口)、80(HTTP端口)都打开,3389端口顺便也开了。

至此服务器的购买、配置就完成啦。稍等几分钟后等待初始化完成,就可以得到服务器的公网 IP 地址,博主的是 118.31.35.48 ,后面会用到。

接下来就是正式的部署。

正式部署

开发时我们用的是 Django 自带的开发服务器,但那个性能太差了,不可能用到线上环境。所以线上部署时,我们不仅要安装 Django,还要安装 NginxGunicorn,这三兄弟的工作流程如下:

  • 客户端发来 http 请求,Nginx 作为直接对外的服务器接口,对 http 请求进行分析
  • 如果是静态资源请求,则由Nginx自己处理(效率极高)
  • 如果是动态资源请求,则把它转发给 Gunicorn
  • Gunicorn 对请求进行预处理后,转发给 Django,最终完成资源的返回

如果用餐馆来做比喻的话,Nginx 就是迎宾小姐,客人如果点了酒水,迎宾小姐自己就帮忙拿了;而 Gunicorn 是传菜员,Django 是厨师,他两一起满足客人对现炒美食的需求。

远程连接

部署的第一步就是想办法连接到云服务器上去,否则一切都免谈。鉴于项目是在 Windows 环境开发的,推荐用 XShell 来作为远程连接的工具,非常的好用。XShell 有学校及家庭版本,填一下姓名和邮箱就可以免费使用了。千万别嫌麻烦去下载来路不明的“绿色版”、“纯净版”,万一有木马你哭都哭不出来了。

XShell 怎么使用就不赘述了,以读者的聪明才智,稍微查阅一下就明白了。

使用相当简单,基本就是把主机 IP、端口号(22)以及登录验证填好就能连接了。

连接成功后,就能在 XShell 窗口中看到阿里云的欢迎字样了:

Welcome to Ubuntu 16.04.6 LTS (GNU/Linux 4.4.0-151-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

Welcome to Alibaba Cloud Elastic Compute Service !

root@dusaiphoto:~$ 

root@dusaiphoto:~$是命令提示符,输入命令时不需要你输入这个。本文后面把 root@dusaiphoto:字符省略掉,方便大家阅读。

代码部署

为了防止系统太旧引起的各种麻烦,先升级一下库的版本:

~$ sudo apt-get update
~$ sudo apt-get upgrade

完成之后,接着安装需要的几个包:

~$ sudo apt-get install nginx
~$ sudo apt-get install python3
~$ sudo apt-get install python3-pip
~$ sudo apt-get install git
~$ sudo pip3 install virtualenv

分别安装了 nginxpython3pipgitvirtualenv。其中 python3 和 pip3 的写法是因为阿里云自带了 Python2.7,把它们区分一下。之前开发时虚拟环境用的 python 自带的,为了避免读者的版本不同造成的各类错误,稳妥起见用 virtualenv 库来创建虚拟环境,操作步骤都是差不多的。

接下来就是要改一下 Django 的配置文件 settings.py

my_blog/settings.py

# 关闭调试模式
DEBUG = False

# 允许的服务器
ALLOWED_HOSTS = ['*']

# 静态文件收集目录
STATIC_ROOT = os.path.join(BASE_DIR, 'collected_static')
  • 部署时要关闭调试模式,避免安全性问题(此时 Django 就不再处理静态资源了)。
  • ALLOWED_HOSTS指明了允许访问的服务器名称或 IP,星号表示允许所有的请求。实际部署时请改成你的域名或 IP,比如ALLOWED_HOSTS = ['.dusaiphoto.com', '127.0.0.1']
  • 项目中有很多静态文件,部署时需要找一个地方统一收集起来,也就是STATIC_ROOT指定的地址了。

因为项目代码需要通过 GitHub 仓库进行下载(就像本教程的示例代码一样),因此修改完毕后需要把代码上传到 GitHub。怎么上传这里也不赘述了,博主之前写过一篇《Win 10 连接 GitHub》的文章,有需要的读者稍微读一下。

注意:一般情况下,需要将开发时生成的迁移文件(各个 app 中的 migrations 目录)一并上传到服务器端,这样可以保证各环境中的数据库操作一致。也就是说,服务器上不再需要 makemigrations 了,只进行 migrate 就可以了。这下理解为什么数据迁移要设计成两条指令了吧。

另外,虚拟环境一般是需要在服务器上重新生成的,因此我们需要把开发中用到的库列一个清单,以便在服务器上统一安装。在本地虚拟环境中输入:

pip freeze > requirements.txt

项目中就多了个 requirements.txt 文件,里面记录了项目需要的库的清单。

教程为了演示,上传了媒体资源和数据库,实际开发时可千万不要上传。

然后更新 Git 记录并上传到 GitHub。重新回到服务器的命令行,给项目代码创建目录并进入此目录:

~$ mkdir -p /home/sites/dusaiphoto.com
~$ cd /home/sites/dusaiphoto.com

目录位置是随便你的,但建议找个地方统一管理所有的网站项目。

然后从 GitHub 中拉取项目代码:

../dusaiphoto.com$ git clone https://github.com/stacklens/django_blog_tutorial.git

这里拉取的博客教程的代码。建议读者先拉取教程代码来测试,成功之后再重新部署自己的代码。完成之后可以输入 ls 指令,看看代码文件夹是否正常生成了。

接着在服务器生成虚拟环境:

../dusaiphoto.com$ virtualenv --python=python3.5 env
../dusaiphoto.com$ source env/bin/activate
(env) ../dusaiphoto.com$ 

这里用 virtualenv 生成并激活了虚拟环境。python 版本选择 3.5 还是 3.7 都可以,区别并不大。

接下来就是安装库、收集静态资源、数据迁移了:

(env) ../dusaiphoto.com$ cd django_blog_tutorial
(env) ../django_blog_tutorial$ pip3 install -r requirements.txt
(env) ../django_blog_tutorial$ python3 manage.py collectstatic
(env) ../django_blog_tutorial$ python3 manage.py migrate

代码部署基本就完成了,接下来配置 Nginx

Nginx

前面我们安装了 Nginx ,先来试试安装是否正常。启动 nginx 服务:

(env) ~$ sudo service nginx start

打开浏览器,输入你的服务器公网 IP 地址

Nginx 欢迎界面出现了,神奇吧。但这个默认配置显然是不能用的,所以需要重新写 Nginx 的配置文件。进入 /etc/nginx/sites-available 目录,这里是定义 Nginx 可用配置的地方。输入指令 sudo vi dusaiphoto.com 创建配置文件并打开 vi 编辑器

(env) ~$ cd /etc/nginx/sites-available
(env) /etc/nginx/sites-available$ 
(env) /etc/nginx/sites-available$ sudo vi dusaiphoto.com

关于 vi 编辑器如何使用也不赘述了,这里只说两个最基本的操作:

  • i 键切换到编辑模式,这时候才可以进行输入、删除、修改等操作
  • Ctrl + c 退回到命令模式,然后输入 :wq + Enter 保存文件修改并退回到服务器命令行

回到正题,用 vidusaiphoto.com 文件中写入:

server {
  charset utf-8;
  listen 80;
  server_name 118.31.35.48;  # 改成你的 IP

  location /static {
    alias /home/sites/dusaiphoto.com/django_blog_tutorial/collected_static;
  }

  location /media {
    alias /home/sites/dusaiphoto.com/django_blog_tutorial/media;
  }

  location / {
    proxy_set_header Host $host;
    proxy_pass http://unix:/tmp/118.31.35.48.socket;  # 改成你的 IP
  }
}

此配置会监听 80 端口(通常 http 请求的端口),监听的 IP 地址写你自己的服务器公网 IP

配置中有3个规则:

  • 如果请求 static 路径则由 Nginx 转发到目录中寻找静态资源
  • 如果请求 media 路径则由 Nginx 转发到目录中寻找媒体资源
  • 其他请求则交给 Django 处理

如果你已经申请好域名了,就把配置中有 IP 的地方都修改为域名,比如:server_name www.dusaiphoto.com;

写好后就退出 vi 编辑器,回到命令行。因为我们写的只是 Nginx 的可用配置,所以还需要把这个配置文件链接到在用配置上去:

(env) ../sites-available$ sudo ln -s /etc/nginx/sites-available/dusaiphoto.com /etc/nginx/sites-enabled

至此 Nginx 就配置好了,接下来搞定 Gunicorn

有的读者无论怎么配置都只能看到 Nginx 欢迎页面,有可能是 sites-enabled 目录中的 default 文件覆盖了你写的配置。将 default 文件删掉就可以正常代理自己的配置文件了。

Gunicorn及测试

先回到项目所在的目录,并且进入虚拟环境,然后输入:

(env) ../django_blog_tutorial$ pip3 install gunicorn
(env) ../django_blog_tutorial$ sudo service nginx reload
(env) ../django_blog_tutorial$ gunicorn --bind unix:/tmp/118.31.35.48.socket my_blog.wsgi:application

这里的三个步骤分别是:

  • 安装 Gunicorn
  • 重启 Nginx 服务
  • 启动 Gunicorn

启动 Gunicorn 也是一样,如果你已经有域名了,就把套接字中的 IP 地址换成域名;wsgi 字眼前面是项目的名称。另外 sudo service nginx reload 可替换成 sudo service nginx restart,区别是 reload 只重载配置文件,restart 重启整个服务。

接下来用浏览器访问服务器试一下:

大功告成啦,撒花庆祝!

后续工作

遗留问题

成功部署到线上后,还有些小问题没解决。

第一个问题是进入文章详情页面, ckeditor 无法加载了,并且浏览器报出 prism_patched.js Not Found的错误。这个问题的根源在于之前我们在开发 ckeditor 的代码高亮功能时,prism 模块是直接插入到虚拟环境的库中的,但问题是部署时虚拟环境是需要重新建立的,所以就缺少了这个 prism 插件导致报错。

解决办法也很简单,在虚拟环境中找到 prism 插件的位置:

..\env\Lib\site-packages\ckeditor\static\ckeditor\ckeditor\plugins\prism

然后把它原封不动的复制到项目的 static 中完全相同的目录中去:

my_blog\static\ckeditor\ckeditor\plugins\prism

这样做行得通的原因是 django-ckeditor 也是一个 app,Django 访问 app 资源时会优先在项目中搜索,没有才去虚拟环境里搜索。

然后就是通过 GitHub 更新服务器代码,并且重新收集静态文件:

(env) ../django_blog_tutorial$ git pull origin master
(env) ../django_blog_tutorial$ python3 manage.py collectstatic

这个问题给我们的启示就是:针对三方库、资源的改动最好不要直接修改源文件或环境,而是想办法在项目副本中更改,这样更便于维护。

有读者发现旧的富文本评论中的表情没显示,这个别担心,新发表的评论是没问题的。

第二个问题是 GitHub 登录不正常了,这个多半不是代码的问题,而是你的服务器以及 GitHub 的回调配置问题。这里也不展开讲了,请读者返回前面第三方登录的文章再研究下。

第三个问题是开发阶段用的 sqlite 数据库虽然很方便,但是性能较差。线上以性能为王,所以需要将数据库更换为 MYSQL 这类主流的高性能数据库。远程安装配置 MYSQL 的方法可以参考一下刘江的博客,讲得很清楚。

第四个问题是教程部署是以 root 用户进行的,这是服务器中具有最高权限的用户,除掉自身的瞎操作,一旦被攻击者登录了会相当惨烈(尤其是用户/密码的登录方式,安全系数极低)。比较好的方式是重新创建一个普通用户来进行部署,并且将登录方式改为秘钥对。

后期运维

你的网站是需要不断更新优化代码的。每次修改代码后,更新到服务器上也很简单。在虚拟环境中并进入项目目录,依次(collectstatic 和 migrate 是可选的)执行以下命令:

git pull origin master
python3 manage.py collectstatic
python3 manage.py migrate
# 重启 gunicorn
pkill gunicorn
gunicorn --bind unix:/tmp/118.31.35.48.socket my_blog.wsgi:application

加上 cd 更改目录的指令,部署过程有十几条指令,手动输入也太麻烦了。简单粗暴的办法是利用 XShell 的宏,把部署指令写成顺序执行的脚本,点几个按钮就完成了,非常方便。

更高级的做法是在服务器上编写自动化部署的脚本,这个就读者以后慢慢研究吧

如果你更改了 Nginx 的配置文件,还需要重启 Nginx 服务:

sudo service nginx reload

最后,还记得前面章节开发的日志记录功能不?看看项目中有 logs 目录吗?

域名及优化

相对部署来说,域名配置就很容易了。阿里云提供域名的购买、备案(顶级域名必须,约10个工作日)、解析服务,简直全家桶有没有。重点提醒有了域名之后要改的地方:

  • settings.py 中的 ALLOWED_HOSTS
  • Nginx 中与 IP/域名 有关的位置
  • Gunicorn 中与 IP/域名 有关的位置

域名搞定之后,接着就可以着手考虑把网站升级为 https 版本了,这是大趋势,一定要做。(这个也留给读者自己折腾)

另外,开发时为了效率把所有静态资源都下载到本地,但是部署时不推荐这样做,原因是静态文件通常体积都较大,你花血汗钱买的服务器载入会很慢。尽量远程 CDN 调用,像这样:

<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>

国内推荐BootCDN,速度快还免费。

媒体资源也是类似,小图还无所谓,大图就要放在七牛云这类的对象存储云上,否则你网页的载入速度会很悲剧的。

最后再次提醒,在开发时我们往 settings.py 中写入如 SECRET_KEY 、邮箱密码等各种敏感信息,部署时千万不要直接上传到互联网(GitHub 库是公开的!),而是把这些信息写到服务器本地,然后在 settings.py 中读取。

进程托管

部署过程中还有个新手经常碰到的问题,就是当 SSH 终端一关闭,Web 服务也一起被关闭了,导致网站无法连接。这个问题在 @frostming 的文章 《Web 服务的进程托管》 中用了三种常见方法解决了,并且还实现了异常重启和开机自启动。有类似疑惑的同学可以前往围观。

总结

部署可以说是入门者最大的难关了,也是检验成果、获取成就感的关键一步。多查资料,要相信你遇到的问题别人早就遇到过了。

部署是菜鸟的毕业礼,也是新人的第一课。

路漫漫其修远兮,吾将上下而求索。


教程来源于Github,感谢杜赛 www.dusaiphoto.com大佬的无私奉献,致敬!

技术教程推荐

深入浅出gRPC -〔李林锋〕

机器学习40讲 -〔王天一〕

黄勇的OKR实战笔记 -〔黄勇〕

编译原理实战课 -〔宫文学〕

Django快速开发实战 -〔吕召刚〕

人人都用得上的写作课 -〔涵柏〕

陶辉的网络协议集训班02期 -〔陶辉〕

Kubernetes入门实战课 -〔罗剑锋〕

B端产品经理入门课 -〔董小圣〕