第 8 章 使用过渡网站测试部署
术语开发运维(DevOps)。
8.1 TDD 以及部署的危险区域
部署过程中的一些危险区域如下:
静态文件(CSS、JavaScript、图片等)
Web 服务器往往需要特殊的配置才能伺服静态文件
数据库
可能会遇到权限和路径问题,还要小心处理,在多次部署之间不能丢失数据。
依赖
要保证服务器上安装了网站依赖的包,而且版本要正确。
相应的解决方案,下面一一说明:
使用与生产环境一样的基础架构部署“过渡网站”(staging site),这么做可以测试部署的过程,确保部署真正的网站时操作正确。
可以在过渡网站中运行功能测试,确保服务器中安装了正确的代码和依赖包。而且为了测试网站的布局,我们编写了冒烟测试,这样就能知道是否正确加载了 CSS。
在可能运行多个 Python 应用的设备中,可以使用 virtualenv 管理包和依赖。
最后,一切操作都自动化完成。使用自动化脚本部署新版本,使用同一个脚本把网站部署到过渡环境和生产环境,这么做能尽量保证过渡网站和线上网站一样。
内容提要
修改功能测试,以便在过渡服务器中运行
架设服务器,安装全部所需的软件,再把过渡和线上环境使用的域名指向这个服务器
使用 Git 把代码上传到服务器
使用 Django 开发服务器在过渡环境的域名下尝试运行过渡网站
学习如何在服务器中使用 virtualenv 管理项目的 Python 依赖
让功能测试一直运行着,告诉我们哪些功能可以正常运行哪些不能
使用 Gunicorn、Upstart 和域套接字配置过渡网站,以便能在生产环境中使用
配置好之后,编写一个脚本,自动执行前面手动完成的操作,这样以后就能自动部署网站了。
最后,使用自动化脚本把网站的生产版本部署到真正的域名下
8.2 一如既往,先写测试
稍微修改一下功能测试,让它能在过渡网站中运行。添加一个参数,指定测试所用的临时服务器地址:
LiveServerTestCase 有一定的缺陷,其中一个缺陷是,总是假定你想使用它自己的测试服务器。因此,还要修改到用 self.live_server_url 的三处测试代码:
运行功能测试,确保上述改动没有破坏现有功能。
然后指定过渡服务器的 URL 再运行试试,使用的过渡服务器地址是 superlists-staging.ottg.eu
。
可以看到,和预期一样,两个测试都失败了,因为还没架设过渡网站。看起来功能测试的测试对象是正确的,所以做一次提交吧。
8.3 注册域名
watch0.top
8.4 手动配置托管网站的服务器
可以把部署的过程分成两个任务:
配置新服务器,用于托管代码
把新版代码部署到配置好的服务器中
有些人喜欢每次部署都用全新服务器,不过这种做法只适用于大型的复杂网站,或者对现有网站做了重大修改。对于简单的网站来说,分别完成上述两个任务更合理。虽然最终这两个任务都要完全自动化,但就目前而言更适合手动配置。
8.4.1 选择在哪里托管网站
托管网站有大量不同的方案,不过基本上可以归纳为两类:
运行自己的服务器(可能是虚拟服务器)
使用“平台即服务”(Platform-As-A-Service,PaaS)提供商,如 Heroku、DotCloud、OpenShift 或 PythonAnywhere
对小型网站而言,PaaS 的优势尤其明显,强烈建议考虑使用 PaaS。不过本书不适用 PaaS。要学习一些优秀的老式服务器管理方法,包括 SSH 和 Web 服务器配置。这些方法永远不会过时。
8.4.2 搭建服务器
不规定怎么搭建服务器,不管你选择使用 Amazon AWS、Rackspace 或 Digital Ocean,还是自己的数据中心里的服务器,抑或楼梯后橱柜里的 Raspberry Pi,只要满足以下条件即可:
服务器的系统使用 Ubuntu(13.04 或以上)
有访问服务器的 root 权限
外网可访问
可以通过 SSH 登录
强烈推荐 Ubuntu 是因为其中安装了 Python 3.4,而且有一些配置 Nginx 的特殊方式。
8.4.3 用户账户、SSH 和权限
如果需要创建非 root 用户,可以这么做:
通过 SSH 登录时,建议别用密码,应该学习如何使用私钥认证。若想使用私钥认证,要从自己的电脑中获取公钥,然后将其附加到服务器用户账户下的 ~/.ssh/authorized_keys 文件中。使用 Bitbucket 或 GitHub 时也会有类似的操作。
8.4.4 安装 Nginx
我们需要一个 Web 服务器。在服务器中安装 Nginx 只需执行一次 apt-get 命令即可,然后再执行一个命令就能看到 Nginx 默认的欢迎页面:
现在访问服务器的 IP 地址就能看到 Nginx 的 "Welcome to nginx" 页面。
如果没看到这个页面,可能是因为防火墙没有开放 80 端口。以 AWS 为例,或许你要配置服务器的 "Security Group" 才能打开 80 端口。
既然有 root 权限,下面就来安装所需的系统级关键软件:Python、Git、pip 和 virtualenv。
8.4.5 解析过渡环境和线上环境所用的域名
要把过渡环境和线上环境所用的域名解析到服务器上。
8.4.6 使用功能测试确认域名可用而且 Nginx 正在运行
为了确认一切顺利,可以再次运行功能测试。会发现失败消息稍微有点不同,其中一个消息和 Ngix 有关:
个人实践
结果:
另外,其中遇到了个问题,如果加上了这一段代码:
会有一个 AttributeError: type object 'NewVisitorTest' has no attribute 'server_thread'
错误。
8.5 手动部署代码
接着要让过渡网站运行起来,检查 Ngix 和 Django 之间能否通信。从这一步起,配置结束了,进入“部署”阶段。在部署的过程中,要思考如何自动化这些操作。
需要一个文件夹用来存放源码。假设源码放在一个非 root 用户的 home 目录中,比如说路径是 /home/elspeth(好像所有共享主机的系统都是这么设置的。无论使用什么主机,一定要以非 root 用户身份运行 Web 应用)。按照下面的文件结构存放网站的代码:
每个网站(过渡网站,线上网站或其他网站)都放在各自的文件夹中。在各自文件夹中又有单独的子文件夹,分别存放源码、数据库和静态我呢间。采用这种结构的逻辑依据是,不同版本的网站源码可能会变,但数据库始终不变。静态文件夹也在同一个相对位置,即 ../static。最后,virtualenv 也有自己的子文件夹。
8.5.1 调整数据库中的位置
首先,在 settings.py 中修改数据库的位置,而且要保证修改后的位置在本地电脑中也能使用。使用 os.path.abspath 能避免以后混淆当前工作目录:
在本地测试一下:
现在可以正常使用了,做次提交:
把源码上传到服务器所需的全部 Base 命令如下。export 的作用是创建一个可在 base 中使用的 “本地变量”:
使用 export 定义的 Bash 变量只在当前终端会话中有效。如果退出服务器后再登陆,就需要重新定义。这个特性有点隐晦,因为 Bash 不会报错,而是直接用空字符串表示未定义的变量,这种处理方式会导致诡异的结果。可以执行
echo $SITENAME
现在网站安装好了,在开发服务器中运行试试——这是一个冒烟测试,检查所有活动部件是否连接起来了:
可以发现还没有安装 Django。
创建虚拟环境
使用 virtualenv 能解决多用户使用 Django 的问题。virtualenv 使用一种优雅的方式在不同的位置安装 Python 包的不同版本,把不同的版本放在各自的 “虚拟环境” 中。
先在本地电脑中试一下:
沿用为服务器规划的文件夹结构:
个人实践
注意确保 virtualenv 全路径都是 ASCII 字符。
上述命令会创建一个文件夹,路径是 ../virtualenv。在这个文件夹中有自己的一份 Python 和 pip,还有一个位置用于安装 Python 包。这个文件夹是自成一体的 Python “虚拟”环境。若想使用这个虚拟环境,可以执行 activate 脚本,修改系统路径和 Python 路径,让系统使用这个虚拟环境中的可执行文件和包:
在 Windows 中使用 virtualenv
在 Windows 中有细微的差别,使用时要注意两件事:
virtualenv/bin 文件夹叫 virtualenv/Scripts
在 Git-Bash 中不要试图运行 active.bat,这个文件是为 DOS shell 编写的,要执行 source ..\virtualenv\Scripts\activate。source 才是关键。
看到了错误消息 "ImportError: No module named django",这是因为还没在虚拟环境中安装 Django。下面进行安装,可以看到 Django 被安装到虚拟环境的 site-packages 文件夹中:
为了保存虚拟环境中所需的包列表,也为了以后能再次创建相同的虚拟环境,可以执行 pip freeze 命令,创建一个 requirements.txt 文件,再把这个文件添加到仓库中:
Django 1.7 还没发布的话,可以使用
pip install https://github.com/django/ django/archive/stable/1.7.x.zip
安装这个版本。在 requirements.txt 中可以 把“Django==1.7”换成这个 URL,pip 很智能,能解析 URL。可以在本地 执行pip install -r requirements.txt命令,测试复建虚拟环境。会看到 pip 提示,所有包都已安装。
现在执行 git push 命令,把更新推送到代码分享网站中。
然后,在服务器上拉取这些更新,创建一个虚拟环境,再执行 pip install -r requirements.txt 命令,让服务器中的虚拟环境和本地一样:
看起来服务器运行得很顺畅,按 Ctrl-C 键暂时关闭服务器。
注意,使用虚拟环境不一定要执行 activate,直接指定虚拟环境中的 python 或 pip 路径也行。在服务器上,我们就直接使用路径。
看起来服务器运行得很顺畅,按 Ctrl-C 键暂时关闭服务器。
注意,使用虚拟环境并不一定要执行 activate,直接指定虚拟环境中的 python 或 pip 的路径也行。在服务器上,我们就直接使用路径。
8.5.3 简单配置 Nginx
下面创建一个 Nginx 配置文件,把过渡网站收到的请求交给 Django 处理。如下是一个极简的配置(server: /etc/nginx/sites-available/watch0.top
):
这个配置只对过渡网站的域名有效,而且会把所有请求“代理”到本地 8000 端口,等待 Django 处理请求后得到的响应。
把这个配置保存为 superlists 文件,放在 /etc/nginx/sites-available 文件夹里,然后创建一个符号链接,把这个文件加入启用的网站列表中:
在 Debian 和 Ubuntu 中,这是保存 Nginx 配置的推荐做法——把真正的配置文件放在 sites-available 文件夹中,然后在 sites-enabled 文件夹中创建一个符号链接。这么做便于切换网站的在线状态。
还可以把默认的 "Welcome to nginx" 页面删除,避免混淆:
现在测试一下配置:
如果用的是长域名,还要编辑 /etc/nginx/nginx.conf 文件,把 server_names_hash_bucket_size 64; 这行的注释去掉,这样才能使用长域名。执行 reload 命令时,如果配置有问题, Nginx 会提醒你。
个人实践
之间建错了,名字全用的 superlists,但是自己的域名并不是这个。
删除符号链接:
统一改成自己的域名之后发现成功了。
接下来看看功能测试的结果如何:python3 manage.py test functional_tests --liveserver=watch0.top
尝试提交新待办事项时测试失败了,因为还没设置数据库。运行测试时你可能注意到了 Django 的黄色报错页,页面中显示的消息和测试失败的消息差不多。
8.5.4 使用迁移创建数据库
执行 migrate 命令,可以指定 --noinput 参数,禁止两次询问。
再运行功能测试试试,发现网站运行起来了。
如果看到 502 - Bad Gateway 错误,可能是因为执行 migrate 命令之后忘记使用 manage.py runserver 重启开发服务器
8.6 为部署到生产环境做好准备
在生产环境中真的不能使用 Django 开发服务器。而且,不能依靠 runserver 手动启动服务器。
8.6.1 换用 Gunicorn
Django 提供了很多功能,包括 ORM、各种中间件、网站后台等。
Gunicorn 需要知道 WSGI(Web Server Gateway Interface,Web 服务器网关接口)服务器的路径。这个路径往往可以使用一个名为 application 的函数获取。Django 在文件 superlists/wsgi.py 中提供了这个函数:
如果现在访问网站,会发现所有样式都失效了。如果运行功能测试,会看到的确出问题了。添加待办事项的测试能顺利通过,但布局和样式的测试失败了。
样式失效的原因是,Django 开发服务器会自动伺服静态文件,但 Gunicorn 不会。现在配置 Nginx,让它代为伺服静态文件。
8.6.2 让 Nginx 伺服静态文件
首先,执行 collectstatic 命令,把所有静态文件复制到一个 Nginx 能找到的文件夹中:
下面配置 Nginx,让它伺服静态文件。
然后重启 Nginx 和 Gunicorn:
接下来可以运行功能测试进行确认。
8.6.3 换用 Unix 套接字
如果想要同时伺服过渡网站和线上网站,这两个网站就不能共用 8000 端口。可以为不同的网站分配不同端口。但更好的方法是使用 Unix 域套接字。域套接字类似于硬盘中的文件,不过还可以用来处理 Nginx 和 Gunicorn 之间的通信。要把套接字保存在文件夹 /tmp 中。下面修改 Nginx 的代理设置:
个人实践
vim 全选删除操作:ggVGd。
gg 让光标移到首行,在 vim 才有效,vi 中无效
V 是进入Visual(可视)模式
G 光标移到最后一行
d 删除选中内容
proxy_set_header
的作用是让 Gunicorn 和 Django 知道它们运行在哪个域名下。ALLOWED_HOSTS
安全功能需要这个设置。
现在重启 Gunicorn,不过这一次告诉它监听套接字,而不是默认的端口:
还要再次运行功能测试,确保所有测试仍能通过:
8.6.4 把 DEBUG 设为 False,设置 ALLOWED_HOSTS
然后重启 Gunicorn,再运行功能测试,确保一切正常。
在服务器中别提交这些改动。现在只是为了让网站正常运行做的小调整,不是需要纳入仓库的改动。一般来说,简单起见,只会在本地电脑中把改动提交到 Git 仓库。如果需要把代码同步到服务器中,再使用 git push 和 git pull。
8.6.5 使用 Upstart 确保引导时启动 Gunicorn
部署的最后一步是确保服务器引导时自动启动 Gunicorn,如果 Gunicorn 崩溃了还要自动重启。在 Ubuntu 中,可以使用 Upstart 实现这个功能。
Upstart 脚本保存在 /etc/init 中,而且文件名必须以 .conf 结尾。
现在可以使用 start 命令启动 Gunicorn 了:
然后可以再次运行功能测试。
个人实践
发现不支持中文,所以最终填入文件的内容是:
8.6.6 保存改动:把 Gunicorn 添加到 requirements.txt
回到本地仓库,应该把 Gunicorn 添加到虚拟环境所需的包列表中:
8.7 自动化
总结一下配置和部署的过程。
配置
假设有用户账户和 home 目录
apt-get nginx git python-pip
pip install virtualenv
添加 Nginx 虚拟主机配置
添加 Upstart 任务,自动启动 Gunicorn
部署
在 ~/sites 中创建目录结构
拉取源码,保存到 source 文件夹中
启用 ../virtualenv 中的虚拟环境
pip install -r requirements.txt
执行 manage.py migrate,创建数据库
执行 collectstatic 命令,收集静态文件
在 settings.py 中设置 DEBUG = False 和 ALLOWED_HOSTS
重启 Gunicorn
运行功能测试,确保一切正常
假设现在不用完全自动化配置过程,则应该把 Nginx 和 Upstart 配置文件保存起来,便于以后重用。下面把这两个配置文件保存到仓库中一个新建的子文件夹中:
以后使用这两个文件配置新网站就容易了,查找替换 SITENAME 即可。
其他步骤做些笔记就行了,在仓库中建个文件保存说明:
然后提交上述改动:
测试驱动服务器配置和部署
测试去除了部署过程中的某些不确定性
常见痛点:数据库、静态文件、依赖和自定义设置
测试允许我们做实验
Last updated
Was this helpful?