houzeyu 2019-06-30
当我们开发了一个简单的 Flask 程序,想把项目部署上线,我们可以选择传统的部署方式或者云部署方式把项目部署上线。在本文中,笔者将使用阿里云轻量应用服务器
安装CentOS 7
系统部署一个简单的 Flask 项目。
要部署一个网站,首先要做的就是购买域名和服务器,市面上主要有阿里云、腾讯云、亚马逊云等云服务器供应商,你可以自由选择。除了域名和服务器外,还需要申请 SSL 证书,为开启 HTTPS 访问做准备。
对于还从未购买过域名的用户,推荐使用阿里云和腾讯云购买域名,可以享受一元首购优惠。
阿里云和腾讯云针对有专门针对学生的学生机,价格非常实惠。其中阿里云是只要你的年龄 24 岁以下自动认定为大学生,可以尝试一下。
SSL证书是数字证书的一种,类似于驾驶证、护照和营业执照的电子副本。因为配置在服务器上,也称为SSL服务器证书。SSL 证书就是遵守 SSL 协议,由受信任的数字证书颁发机构 CA ,在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能。
阿里云和腾讯云控制台都提供了 SSL 证书购买的功能,对于需要付费的 SSL 证书,你可以直接挑选一个价格合适的然后付钱即可。如果想申请免费的 SSL 证书,可以直接参考下方链接:
备案是指向主管机关报告事由存案以备查考。行政法角度看备案,实践中主要是《立法法》和《法规规章备案条例》的规定。根据中华人民共和国信息产业部第十二次部务会议审议通过的《非经营性互联网信息服务备案管理办法》精神,在中华人民共和国境内提供非经营性互联网信息服务,应当办理备案。未经备案,不得在中华人民共和国境内从事非经营性互联网信息服务。而对于没有备案的网站将予以罚款和关闭。
简单来说,购买了服务器之后,如果希望通过域名能正常访问到您的网站,就需要进行网站备案。
这里仅以阿里云服务器控制台为例,其它云服务器请参考官方说明文档。
首先,选择服务器控制台中的 站点设置 > 域名
菜单;然后点击 添加域名
按钮,为你的域名同时添加 'www' 及 '@' 记录。假设你购买的域名为 demo.com ,则同时添加的两条记录为:
这两个域名都能访问到你的网站首页。
通过 SSH 远程连接服务器实例,可以方便的对服务器进行管理。你可以手动输入命令生成 SSH 密钥连接服务器;也可以通过云服务器控制台自动生成密钥,然后导出密钥到本地,再使用导出的密钥连接服务器。这里推荐通过云服务器控制台生成密钥的方式。
相较于传统的用户名和密码认证方式,使用 SSH 密钥有以下优势:
在客户端的 Shell 中执行下面命令生成 SSH 密钥对:
$ ssh-keygen -t rsa -b 4096 -C "[email protected]"
在客户端的 Shell 中执行下面命令授予 .ssh
文件夹 600 权限:
$ chmod 700 ~/.ssh $ chmod 600 ~/.ssh/authorizes_keys
在客户端的 Shell 中执行下面命令将客户端私钥拷贝到服务器:
# 执行下面的命令会被要求输入服务器对应用户的密码,密码输入正确才能成功完成拷贝 # 记得将下面命令中的 [email protected] 替换成你自己的服务器的 SSH 地址 $ ssh-copy-id -i ~/.ssh/id_rsa.pub [email protected]
在客户端的 Shell 中执行下面命令,进行 SSH 免密码登陆测试:
$ ssh [email protected]
在客户端的 ~/.bashrc
文件中为远程连接的命令取个别名,以后就可以方便的进行登陆了:
$ vim ~/.bashrc
在文件中找到下面这一行:
# some more ls aliases
在该行代码下面再添加一行并保存,内容如下:
alias ecs='ssh [email protected]'
在客户端的 Shell 中执行下面命令,使刚刚修改文件生效:
$ source ~/.bashrc
在客户端的 Shell 中执行下面命令,查看你已经设置的别名:
$ alias
MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件。MySQL 软件采用了双授权政策,分为社区版和商业版,由于其体积小、速度快、总体拥有成本低,尤其是开放源码这一特点,一般中小型网站的开发都选择 MySQL 作为网站数据库。
在 Download MySQL Yum Repository 页面获取 MySQL 8 Community Yum 仓库文件的链接,例如:
https://repo.mysql.com//mysql80-community-release-el7-2.noarch.rpm
通过 SSH 远程连接服务器实例,执行下面命令切换到其它拥有 root 权限的用户,阿里云服务器实例默认有一个拥有 root 权限的 admin 用户,这里以切换到 admin 用户为例子:
$ su admin
执行下面命令,下载 MySQL 8 Community Yum 仓库文件:
$ wget https://repo.mysql.com//mysql80-community-release-el7-2.noarch.rpm
执行下面命令,安装 MySQL 8 Community Yum 仓库文件:
$ sudo yum localinstall mysql80-community-release-el7-2.noarch.rpm
执行下面命令,检查 MySQL 8 Community Yum 仓库文件是否正确安装 :
$ yum repolist enabled | grep "mysql.*-community.*"
执行下面命令,安装 MySQL 8 Community :
$ sudo yum install mysql-community-server
使用 service
命令管理 MySQL 服务:
$ sudo service mysqld start # 启动 MySQL 服务 $ sudo service mysqld stop # 停止 MySQL 服务 $ sudo service mysqld restart # 重启 MySQL 服务 $ sudo service mysqld status # 查看 MySQL 服务状态
使用 systemctl
命令管理 MySQL 服务:
$ sudo systemctl start mysqld # 启动 MySQL 服务 $ sudo systemctl stop mysqld # 停止 MySQL 服务 $ sudo systemctl restart mysqld # 重启 MySQL 服务 $ sudo systemctl status mysqld # 查看 MySQL 服务状态 $ sudo systemctl enable mysqld # 设置 MySQL 服务开机自启动 $ sudo systemctl disable mysqld # 关闭 MySQL 服务开机自启动
首次启动 MySQL 服务,会自动初始化数据目录、生成 SSL 证书和密钥文件、创建超级用户 ' root'@'localhost'
,超级用户的密码被设置并存储在错误日志文件中。可以使用以下命令查询临时密码:
$ sudo grep 'temporary password' /var/log/mysqld.log
输入以下命令,根据提示输入上一步获得的临时密码,连接数据库服务器:
$ mysql -u root -p Enter password: (在这里输入上一步查询到的临时密码)
连接 MySQL 服务器后,在 MySQL 命令行中为 ' root'@'localhost'
设置新密码,使临时密码失效:
mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'new_password';
新版 MySQL 的安全策略要求输入的密码要包含大写字母、小写字母、数字、特殊符号,推荐使用密码管理工具生成随机密码来作为你的新密码。
为了更加方便的远程连接 MySQL 服务器,接下来需要允许 MySQL 的 root 账户在其它地址登陆:
mysql> USE mysql; mysql> UPDATE user SET host = '%' WHERE host = 'root'; # 这里的 host = '%' 中的 % 表示允许在任意地址登陆,你也可以设置为指定的局域网 IP、公网 IP、域名等
Cent OS 预装了一个 Python 2,并且系统很多组件都依赖于 Python 2 ,笔者在安装和使用 Python 3 时就因为这些依赖情况遇到了很多问题,最后总结下来,正确的安装和使用 Python 3 的过程如下:
使用 Yum 安装编译安装 Python 3 时依赖的包:
$ yum -y groupinstall "Development tools" $ yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel libffi-devel
下载 Python 3.6.7 版本的安装包,其它版本的请自行去 Download Python | Python.org 获取链接:
$ wget https://www.python.org/ftp/python/3.6.7/Python-3.6.7.tgz
在当前用户目录解压下载的 Python 安装包:
$ tar -zxvf Python-3.6.7.tgz
进入已解压的 Python 安装文件根目录:
$ cd Python-3.6.7
通过编译配置指定 Python 的安装位置:
$ ./configure --prefix=/usr/local/python3
使用 make
命令开始编译安装 Python:
$ make && make install
为了和系统自带的 python
和 pip
命令区分开来,给刚刚安装的 Python 建立软链接,并为其设置命令别名。分别取名为 python3
、pip3
:
$ ln -s /usr/local/python3/bin/python3.6 /usr/bin/python3 $ ln -s /usr/local/python3/bin/pip3 /usr/bin/pip3
测试 Python 3 是否正确安装,输入 python3
命令:
$ python3 Python 3.6.7 (default, Feb 4 2019, 19:05:27) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] on linux Type "help", "copyright", "credits" or "license" for more information. >>>
测试 Pip 3 是否正确安装,输入 pip3
命令:
$ pip3 -V pip 10.0.0 from /usr/local/python3/lib/python3.6/site-packages/pip (python 3.6)
更新 Pip :
$ pip3 install --upgrade pip
Pipenv 是 Pipfile 主要倡导者、requests 作者 Kenneth Reitz 写的一个命令行工具,主要包含了 Pipfile、pip、click、requests 和 virtualenv ,使用 Pipenv 可以方便的管理 Python 虚拟环境、管理依赖文件。Pipfile 和 Pipenv本来都是Kenneth Reitz 的个人项目,后来贡献给了 pypa 组织。Pipfile 是社区拟定的依赖管理文件,用于替代过于简陋的 requirements.txt 文件。
执行下面命令,安装 Pipenv :
$ pip3 install pipenv
执行下面命令,为 Pipenv 可执行文件设置软链接,之后可以通过 pipenv
命令来使用 Pipenv :
$ ln -s /usr/local/python3/bin/pipenv /usr/bin/pipenv
切换到一个拥有 root 权限的用户,这里以 admin 用户为例:
$ su admin
在用户目录下为你的项目创建一个目录,并进入项目目录,项目名称以 FlaskApp 为例:
$ cd ~ $ mkdir FlaskApp $ cd FlaskApp
执行下面命令,为项目创建 Python 虚拟环境,默认将虚拟环境保存在 ~/.local/share/virtualenvs
:
$ pipenv install
如果想把虚拟环境保存至项目根目录,需要设置环境变量 PIPENV_VENV_IN_PROJECT=1
,再执行创建命令:
$ export PIPENV_VENV_IN_PROJECT=1 $ pipenv install
虚拟环境创建完成后,执行下面命令为虚拟环境安装 Flask 包:
$ pipenv install flask
在项目根目录编写一个简单的 Flask Demo 进行测试:
# 新建并打开一个名为 app.py 的文件 $ vim app.py
输入下面的代码并保存:
from flask import Flask app = Flask(__name__) @app.route('/') def hello_flask(): return 'Hello Flask!'
使用 pipenv run
调用虚拟环境中的 Python 执行 flask run
命令可以运行编写的代码:
$ pipenv run flask run
也可以使用 pipenv shell
命令进入虚拟环境,然后再在虚拟环境执行 flask run
命令运行程序:
$ pipenv shell (venv)$ flask run
Flask 默认运行的地址和端口为 http://127.0.0.1:5000 ,云服务器实例不包含桌面环境的话,你很难去浏览这个页面。你可以设置 flask 运行的地址和端口,然后尝试从外网访问该页面。先执行下面命令,让 flask 允许外网访问,并且监听 80 端口:
$ pipenv run flask run --host 0.0.0.0 --port 80
然后你可以通过你的服务器公网 IP 或 域名 直接访问到该页面。
flask run
命令启动的开发服务器是由 Werkzeug 提供的。细分的话, Werkzeug 提供的这个开发服务器应该被称为 WSGI 服务器,而不是单纯意义上的 Web 服务器。在生产环境中,我们需要一个更强健、性能更高的 WSGI 服务器。这些 WSGI 服务器也被称为独立 WSGI 容器,因为它们可以承载我们编写的 WSGI 程序,然后处理 HTTP 请求和响应。这通常有很多选择,比如 Gunicorn 。 Gunicorn 是 Green Unicorn 的简写,意为绿色独角兽,是一款专为 UNIX 设计的 Python WSGI HTTP 服务器。是一个Pre-fork 工人模型。Gunicorn 服务器广泛兼容各种 web 框架,实现简单,节省服务器资源,速度相当快。
安装 Gunicorn :
$ pipenv install gunicorn
使用 Gunicorn 运行一个 WSGI 程序:
$ pipenv run gunicorn --workers=4 --bind=0.0.0.0:8000 app:app # --workers = 4 表示使用 4 worker 进程运行程序,建议 worker 数量为 ( CPU 核心数 × 2 ) + 1 # Gunicorn 默认只允许从本地 8000 端口访问,--bind=0.0.0.0:8000 表示允许使用 8000 端口从外部访问 # app:app 冒号前面的 app 表示 app.py 文件,冒号后面的 app 表示 flask 程序的名称
也可以把 --workers
简写为 -w
、--bind
简写为 -b
,如下:
# 没有 -b 或者 --bind 参数,默认监听 127.0.0.1:8000 $ pipenv run gunicorn -w 4 app:app # 指定 -b 0.0.0.0:8000 监听 8000 端口的外部请求 $ pipenv run gunicorn -w 4 -b 0.0.0.0:8000 app:app
像 Gunicorn 这类 WSGI 服务器内置的 Web 服务器还不够强健,虽然程序可以正常运行,但是更流行的部署方式是使用一个常规的 Web 服务器运行在前端,为 WSGI 提供反向代理。比较流行的开源 Web 服务器有 Nginx 、Apache 等,这里选择使用和 Gunicorn 集成良好的 Nginx 。
访问 nginx packages 获取对应版本 Nginx 的 Yum 仓库的链接,例如:
http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
下载 Nginx Yum 仓库文件:
$ wget http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
安装 Nginx Yum 仓库文件:
$ sudo yum localinstall nginx-release-centos-7-0.el7.ngx.noarch.rpm
安装 Nginx :
$ sudo yum install nginx
进入 Nginx 配置文件目录:
$ cd /etc/nginx/
创建 cert 目录,并上传你的 SSL 证书到该目录:
$ mkdir cert
上传 SSL 证书到 cert 目录你可以使用 scp
命令,或者使用 FileZilla 等 SFTP 软件,我上传的文件如下:
$ cd cert $ ls ssl.key ssl.pem
进入 /etc/nginx/conf.d/
目录编辑默认的配置文件 default.conf
:
$ cd /etc/nginx/conf.d/ $ vim default.conf
删除文件中原有的全部内容,新增下面内容并保存:
# 监听 http 请求,强制跳转到 https server { listen 80; # 这里的 your.domain.com 换成你购买的域名 server_name your.domain.com; # 这里的 your.domain.com 换成你购买的域名 return 301 https://your.domain.com$request_uri; } # 监听 https 请求 server { listen 443; # 这里的 your.domain.com 换成你购买的域名 server_name your.domain.com; access_log /var/log/nginx/host.access.log; error_log /var/log/nginx/host.error.log; ssl on; # 这部分的 ssl.pem ssl.key 换成你上传的与其对应的文件 ssl_certificate cert/ssl.pem; ssl_certificate_key cert/ssl.key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_prefer_server_ciphers on; location / { # 转发请求给 Gunicorn proxy_pass http://127.0.0.1:8000; proxy_redirect off; # 为了能正常运行,重写请求头 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 处理静态文件夹中的静态文件 location /static { alias /home/admin/FlaskApp/static/; # 设置静态文件缓存过期时间为 30 天 expires 30d; } }
测试配置正确性:
$ sudo nginx -t
如果出现的提示中没有报错,则可以启动 nginx 了。
启动 nginx :
$ sudo nginx
现在,你可以使用 Gunicorn 不指定 --bind
参数运行 Flask 程序,然后尝试从外网通过 HTTPS 访问,判断 nginx 反向代理是否设置成功。
使用 nginx
命令管理 Nginx :
$ sudo nginx # 启动 Nginx 服务 $ sudo nginx -s stop # 关闭 Nginx 服务 $ sudo nginx -s reload # 重载 Nginx 服务 $ sudo nginx -s reopen # 重启 Nginx 服务 $ sudo nginx -s quit # 退出 Nginx 服务
使用 service
命令管理 Nginx 服务:
$ sudo service nginx start # 启动 Nginx 服务 $ sudo service nginx stop # 停止 Nginx 服务 $ sudo service nginx restart # 重启 Nginx 服务 $ sudo service nginx status # 查看 Nginx 服务状态
使用 systemctl
命令管理 Nginx 服务:
$ sudo systemctl start nginx # 启动 Nginx 服务 $ sudo systemctl stop nginx # 停止 Nginx 服务 $ sudo systemctl restart nginx # 重启 Nginx 服务 $ sudo systemctl status nginx # 查看 Nginx 服务状态 $ sudo systemctl enable nginx # 设置 Nginx 服务开机自启动 $ sudo systemctl disable nginx # 关闭 Nginx 服务开机自启动
如果 Nginx 已经启动却又被启动了一次,可能会报错。比如:找不到 nginx.pid
文件、提示 XX 端口已经被使用等等...,解决办法如下:
# 杀掉占用 80 端口的进程 $ sudo fuser -k 80/tcp # 杀掉占用 443 端口的进程 $ sudo fuser -k 443/tcp # 使用默认配置文件重新启动 Nginx $ sudo nginx -c /etc/nginx/nginx.conf
Supervisor 是用 Python 开发的一套通用的进程管理程序,能将一个普通的命令行进程变为后台 daemon ,并监控进程状态,异常退出时能自动重启。它是通过 fork/exec 的方式把这些被管理的进程当作 Supervisor 的子进程来启动,这样只要在 Supervisor 的配置文件中把要管理的进程的可执行文件的路径写进去即可。也实现当子进程挂掉的时候,父进程可以准确获取子进程挂掉的信息的,可以选择是否自己启动和报警。Supervisor 还提供了一个功能,可以为 supervisord 或者每个子进程设置一个非 root 的用户,这个用户就可以管理它对应的进程。
安装 Supervisor :
$ sudo yum install supervisor
检查 Supervisor 配置文件:
$ vim /etc/supervisord.conf
找到最后一行,检查是否是如下内容:
[include] files = supervisord.d/*.ini
如果不是,则修改文件使其跟上面内容一致。
进入 /etc/supervisord.d/
目录, 为项目创建一个 Supervisor 配置文件:
$ cd /etc/supervisord.d/ $ vi FlaskApp.ini
配置文件内容为:
[program:app] ; 下面命令中的 app:app 请修改为你实际部署时的项目名称 command=pipenv run gunicorn -w 4 app:app ; 下面的路径请修改为你创建的项目的根目录 directory=/home/admin/FlaskApp autostart=true autorestart=true stopsignal=QUIT stopasgroup=true killasgroup=true ; 下面的用户请修改为创建该项目的用户 user=admin redirect_stderr=true ; log 文件的路径你可以重新自定义 stdout_logfile=/home/admin/FlaskApp/log/supervisor.log ; 解决编码问题 [supervisord] environment=LC_ALL='en_US.UTF-8',LANG='en_US.UTF-8'
启动 Supervisor :
$ supervisord -c /etc/supervisord.conf
使用 service
命令管理 Supervisor 服务:
$ sudo service supervisord start # 启动 Supervisor 服务 $ sudo service supervisord stop # 停止 Supervisor 服务 $ sudo service supervisord restart # 重启 Supervisor 服务 $ sudo service supervisord status # 查看 Supervisor 服务状态
使用 systemctl
命令管理 Supervisor 服务:
$ sudo systemctl start supervisord # 启动 Supervisor 服务 $ sudo systemctl stop supervisord # 停止 Supervisor 服务 $ sudo systemctl restart supervisord # 重启 Supervisor 服务 $ sudo systemctl status supervisord # 查看 Supervisor 服务状态 $ sudo systemctl enable supervisord # 设置 Supervisor 服务开机自启动 $ sudo systemctl disable supervisord # 关闭 Supervisor 服务开机自启动
进入 Supervisor 控制台,管理后台进程:
$ sudo supervisorctl app RUNNING pid 2696, uptime 23:46:00 supervisor > help # 输入 help 命令,查看 supervisor 支持的命令 default commands (type help <topic>): ===================================== add clear fg open quit remove restart start stop update avail exit maintail pid reload reread shutdown status tail version
使用 status
命令,查看正在运行的后台进程:
supervisor> status app RUNNING pid 2696, uptime 23:49:37
使用 stop
命令,结束指定的进程:
supervisor> stop app app: stopped
使用 start
命令,启动指定的进程:
supervisor> start app app: started