作为一个合格的开发者,在本地环境下完成开发还远远不够,我们需要把Web App部署到远程服务器上,这样,广大用户才能访问到网站。
很多做开发的同学把部署这件事情看成是运维同学的工作,这种看法是完全错误的。首先,最近流行[DevOps](http://zh.wikipedia.org/wiki/DevOps)理念,就是说,开发和运维要变成一个整体。其次,运维的难度,其实跟开发质量有很大的关系。代码写得垃圾,运维再好也架不住天天挂掉。最后,DevOps理念需要把运维、监控等功能融入到开发中。你想服务器升级时不中断用户服务?那就得在开发时考虑到这一点。
下面,我们就来把awesome-python3-webapp部署到Linux服务器。
### 搭建Linux服务器
要部署到Linux,首先得有一台Linux服务器。要在公网上体验的同学,可以在Amazon的[AWS](http://aws.amazon.com/)申请一台EC2虚拟机(免费使用1年),或者使用国内的一些云服务器,一般都提供Ubuntu Server的镜像。想在本地部署的同学,请安装虚拟机,推荐使用[VirtualBox](https://www.virtualbox.org/)。
我们选择的Linux服务器版本是[Ubuntu Server 14.04 LTS](http://www.ubuntu.com/download/server),原因是apt太简单了。如果你准备使用其他Linux版本,也没有问题。
Linux安装完成后,请确保ssh服务正在运行,否则,需要通过apt安装:
~~~
~~~
$ sudo apt-get install openssh-server
~~~
~~~
有了ssh服务,就可以从本地连接到服务器上。建议把公钥复制到服务器端用户的`.ssh/authorized_keys`中,这样,就可以通过证书实现无密码连接。
### 部署方式
利用Python自带的asyncio,我们已经编写了一个异步高性能服务器。但是,我们还需要一个高性能的Web服务器,这里选择Nginx,它可以处理静态资源,同时作为反向代理把动态请求交给Python代码处理。这个模型如下:
![nginx-awesome-mysql](https://box.kancloud.cn/2016-01-15_56988a2f57da1.png)
Nginx负责分发请求:
![browser-nginx-awesome](https://box.kancloud.cn/2016-01-15_56988a2f6793b.png)
在服务器端,我们需要定义好部署的目录结构:
~~~
~~~
/
+- srv/
+- awesome/ <-- Web App根目录
+- www/ <-- 存放Python源码
| +- static/ <-- 存放静态资源文件
+- log/ <-- 存放log
~~~
~~~
在服务器上部署,要考虑到新版本如果运行不正常,需要回退到旧版本时怎么办。每次用新的代码覆盖掉旧的文件是不行的,需要一个类似版本控制的机制。由于Linux系统提供了软链接功能,所以,我们把`www`作为一个软链接,它指向哪个目录,哪个目录就是当前运行的版本:
![linux-www-symbol-link](https://box.kancloud.cn/2016-01-15_56988a2b8fa3e.png)
而Nginx和gunicorn的配置文件只需要指向`www`目录即可。
Nginx可以作为服务进程直接启动,但gunicorn还不行,所以,[Supervisor](http://supervisord.org/)登场!Supervisor是一个管理进程的工具,可以随系统启动而启动服务,它还时刻监控服务进程,如果服务进程意外退出,Supervisor可以自动重启服务。
总结一下我们需要用到的服务有:
-
Nginx:高性能Web服务器+负责反向代理;
-
Supervisor:监控服务进程的工具;
- MySQL:数据库服务。
在Linux服务器上用apt可以直接安装上述服务:
~~~
~~~
$ sudo apt-get install nginx supervisor python3 mysql-server
~~~
~~~
然后,再把我们自己的Web App用到的Python库安装了:
~~~
~~~
$ sudo pip3 install jinja2 aiomysql aiohttp
~~~
~~~
在服务器上创建目录`/srv/awesome/`以及相应的子目录。
在服务器上初始化MySQL数据库,把数据库初始化脚本`schema.sql`复制到服务器上执行:
~~~
~~~
$ mysql -u root -p < schema.sql
~~~
~~~
服务器端准备就绪。
### 部署
用FTP还是SCP还是rsync复制文件?如果你需要手动复制,用一次两次还行,一天如果部署50次不但慢、效率低,而且容易出错。
正确的部署方式是使用工具配合脚本完成自动化部署。[Fabric](http://www.fabfile.org/)就是一个自动化部署工具。由于Fabric是用Python 2.x开发的,所以,部署脚本要用Python 2.7来编写,本机还必须安装Python 2.7版本。
要用Fabric部署,需要在本机(是开发机器,不是Linux服务器)安装Fabric:
~~~
~~~
$ easy_install fabric
~~~
~~~
Linux服务器上不需要安装Fabric,Fabric使用SSH直接登录服务器并执行部署命令。
下一步是编写部署脚本。Fabric的部署脚本叫`fabfile.py`,我们把它放到`awesome-python-webapp`的目录下,与`www`目录平级:
~~~
~~~
awesome-python-webapp/
+- fabfile.py
+- www/
+- ...
~~~
~~~
Fabric的脚本编写很简单,首先导入Fabric的API,设置部署时的变量:
~~~
~~~
# fabfile.py
import os, re
from datetime import datetime
# 导入Fabric API:
from fabric.api import *
# 服务器登录用户名:
env.user = 'michael'
# sudo用户为root:
env.sudo_user = 'root'
# 服务器地址,可以有多个,依次部署:
env.hosts = ['192.168.0.3']
# 服务器MySQL用户名和口令:
db_user = 'www-data'
db_password = 'www-data'
~~~
~~~
然后,每个Python函数都是一个任务。我们先编写一个打包的任务:
~~~
~~~
_TAR_FILE = 'dist-awesome.tar.gz'
def build():
includes = ['static', 'templates', 'transwarp', 'favicon.ico', '*.py']
excludes = ['test', '.*', '*.pyc', '*.pyo']
local('rm -f dist/%s' % _TAR_FILE)
with lcd(os.path.join(os.path.abspath('.'), 'www')):
cmd = ['tar', '--dereference', '-czvf', '../dist/%s' % _TAR_FILE]
cmd.extend(['--exclude=\'%s\'' % ex for ex in excludes])
cmd.extend(includes)
local(' '.join(cmd))
~~~
~~~
Fabric提供`local('...')`来运行本地命令,`with lcd(path)`可以把当前命令的目录设定为`lcd()`指定的目录,注意Fabric只能运行命令行命令,Windows下可能需要[Cgywin](http://cygwin.com/)环境。
在`awesome-python-webapp`目录下运行:
~~~
~~~
$ fab build
~~~
~~~
看看是否在`dist`目录下创建了`dist-awesome.tar.gz`的文件。
打包后,我们就可以继续编写`deploy`任务,把打包文件上传至服务器,解压,重置`www`软链接,重启相关服务:
~~~
~~~
_REMOTE_TMP_TAR = '/tmp/%s' % _TAR_FILE
_REMOTE_BASE_DIR = '/srv/awesome'
def deploy():
newdir = 'www-%s' % datetime.now().strftime('%y-%m-%d_%H.%M.%S')
# 删除已有的tar文件:
run('rm -f %s' % _REMOTE_TMP_TAR)
# 上传新的tar文件:
put('dist/%s' % _TAR_FILE, _REMOTE_TMP_TAR)
# 创建新目录:
with cd(_REMOTE_BASE_DIR):
sudo('mkdir %s' % newdir)
# 解压到新目录:
with cd('%s/%s' % (_REMOTE_BASE_DIR, newdir)):
sudo('tar -xzvf %s' % _REMOTE_TMP_TAR)
# 重置软链接:
with cd(_REMOTE_BASE_DIR):
sudo('rm -f www')
sudo('ln -s %s www' % newdir)
sudo('chown www-data:www-data www')
sudo('chown -R www-data:www-data %s' % newdir)
# 重启Python服务和nginx服务器:
with settings(warn_only=True):
sudo('supervisorctl stop awesome')
sudo('supervisorctl start awesome')
sudo('/etc/init.d/nginx reload')
~~~
~~~
注意`run()`函数执行的命令是在服务器上运行,`with cd(path)`和`with lcd(path)`类似,把当前目录在服务器端设置为`cd()`指定的目录。如果一个命令需要sudo权限,就不能用`run()`,而是用`sudo()`来执行。
### 配置Supervisor
上面让Supervisor重启awesome的命令会失败,因为我们还没有配置Supervisor呢。
编写一个Supervisor的配置文件`awesome.conf`,存放到`/etc/supervisor/conf.d/`目录下:
~~~
~~~
[program:awesome]
command = /srv/awesome/www/app.py
directory = /srv/awesome/www
user = www-data
startsecs = 3
redirect_stderr = true
stdout_logfile_maxbytes = 50MB
stdout_logfile_backups = 10
stdout_logfile = /srv/awesome/log/app.log
~~~
~~~
配置文件通过`[program:awesome]`指定服务名为`awesome`,`command`指定启动`app.py`。
然后重启Supervisor后,就可以随时启动和停止Supervisor管理的服务了:
~~~
~~~
$ sudo supervisorctl reload
$ sudo supervisorctl start awesome
$ sudo supervisorctl status
awesome RUNNING pid 1401, uptime 5:01:34
~~~
~~~
### 配置Nginx
Supervisor只负责运行gunicorn,我们还需要配置Nginx。把配置文件`awesome`放到`/etc/nginx/sites-available/`目录下:
~~~
~~~
server {
listen 80; # 监听80端口
root /srv/awesome/www;
access_log /srv/awesome/log/access_log;
error_log /srv/awesome/log/error_log;
# server_name awesome.liaoxuefeng.com; # 配置域名
# 处理静态文件/favicon.ico:
location /favicon.ico {
root /srv/awesome/www;
}
# 处理静态资源:
location ~ ^\/static\/.*$ {
root /srv/awesome/www;
}
# 动态请求转发到9000端口:
location / {
proxy_pass http://127.0.0.1:9000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
~~~
~~~
然后在`/etc/nginx/sites-enabled/`目录下创建软链接:
~~~
~~~
$ pwd
/etc/nginx/sites-enabled
$ sudo ln -s /etc/nginx/sites-available/awesome .
~~~
~~~
让Nginx重新加载配置文件,不出意外,我们的`awesome-python3-webapp`应该正常运行:
~~~
~~~
$ sudo /etc/init.d/nginx reload
~~~
~~~
如果有任何错误,都可以在`/srv/awesome/log`下查找Nginx和App本身的log。如果Supervisor启动时报错,可以在`/var/log/supervisor`下查看Supervisor的log。
如果一切顺利,你可以在浏览器中访问Linux服务器上的`awesome-python3-webapp`了:
![awesome-run-on-server](https://box.kancloud.cn/2016-01-15_56988a2ba47f8.png)
如果在开发环境更新了代码,只需要在命令行执行:
~~~
~~~
$ fab build
$ fab deploy
~~~
~~~
自动部署完成!刷新浏览器就可以看到服务器代码更新后的效果。
### 友情链接
嫌国外网速慢的童鞋请移步网易和搜狐的镜像站点:
[](http://mirrors.163.com/)[http://mirrors.163.com/](http://mirrors.163.com/)
[](http://mirrors.sohu.com/)[http://mirrors.sohu.com/](http://mirrors.sohu.com/)
### 参考源码
[day-15](https://github.com/michaelliao/awesome-python3-webapp/tree/day-15)
- 关于
- Python简介
- 安装Python
- Python解释器
- 第一个Python程序
- 使用文本编辑器
- Python代码运行助手
- 输入和输出
- Python基础
- 数据类型和变量
- 字符串和编码
- 使用list和tuple
- 条件判断
- 循环
- 使用dict和set
- 函数
- 调用函数
- 定义函数
- 函数的参数
- 递归函数
- 高级特性
- 切片
- 迭代
- 列表生成式
- 生成器
- 迭代器
- 函数式编程
- 高阶函数
- 返回函数
- 匿名函数
- 装饰器
- 偏函数
- 模块
- 使用模块
- 安装第三方模块
- 面向对象编程
- 类和实例
- 访问限制
- 继承和多态
- 获取对象信息
- 实例属性和类属性
- 面向对象高级编程
- 使用slots
- 使用@property
- 多重继承
- 定制类
- 使用枚举类
- 使用元类
- 错误、调试和测试
- 错误处理
- 调试
- 单元测试
- 文档测试
- IO编程
- 文件读写
- StringIO和BytesIO
- 操作文件和目录
- 序列化
- 进程和线程
- 多进程
- 多线程
- ThreadLocal
- 进程 vs. 线程
- 分布式进程
- 正则表达式
- 常用内建模块
- datetime
- collections
- base64
- struct
- hashlib
- itertools
- XML
- HTMLParser
- urllib
- 常用第三方模块
- PIL
- virtualenv
- 图形界面
- 网络编程
- TCP/IP简介
- TCP编程
- UDP编程
- 电子邮件
- SMTP发送邮件
- POP3收取邮件
- 访问数据库
- 使用SQLite
- 使用MySQL
- 使用SQLAlchemy
- Web开发
- HTTP协议简介
- HTML简介
- WSGI接口
- 使用Web框架
- 使用模板
- 异步IO
- 协程
- asyncio
- aiohttp
- 实战
- Day 1 - 搭建开发环境
- Day 2 - 编写Web App骨架
- Day 3 - 编写ORM
- Day 4 - 编写Model
- Day 5 - 编写Web框架
- Day 6 - 编写配置文件
- Day 7 - 编写MVC
- Day 8 - 构建前端
- Day 9 - 编写API
- Day 10 - 用户注册和登录
- Day 11 - 编写日志创建页
- Day 12 - 编写日志列表页
- Day 13 - 提升开发效率
- Day 14 - 完成Web App
- Day 15 - 部署Web App
- Day 16 - 编写移动App
- FAQ
- 期末总结
- CoverPage