部署

项目的所有功能都开发完成,并且经过测试没有 bug 后,就可以把项目部署到服务器上了,这里以虚拟机 Ubuntu 20.04 LTS Server 版的操作系统为例进行讲解。读者可以自行购买云服务器,如阿里云、腾讯云、华为云等进行部署,操作方式大同小异。

导出依赖包

在项目开发完成后,我们把项目使用的虚拟环境的依赖包导出,以方便在服务器上进行安装。在 PyCharm 的 Terminal 中输入以下命令完成依赖包的导出。

pip freeze > requirements.txt

执行完上述命令后,会在项目的根路径下生成一个 requirements.txt 文件,这个文件记录了当前项目所依赖的包。当项目上传到服务器后,通过以下命令即可一次性完成所有依赖包的安装。

pip install -r requirements.txt

使用Git上传代码

本地开发好的项目代码,可以通过多种方式上传到服务器,如 scp、Git 等。Git 更新代码更加方便,而且有版本管理功能,可以随时切换到之前的版本,本书选择 Git 来提交代码。首先在开发机上安装 Git,只要从 https://git-scm.com/downloads 根据自己的操作系统下载最新的 Git,然后安装即可。

Git 安装完成后,还需要用到 Git 服务器。可以自己搭建 Git 服务器,也可以使用第三方 Git 服务。如国内的有 Gitee(官网 www.gitee.com)、Coding(官网 www.coding.net),国外的有 Github(官网 www.github.com)、Gitlab(官网 www.gitlab.com)等。Github 是全球最大的代码托管网站,并且可以免费创建任意数量的私有项目,我们以 Github 为例来讲解。首先在 Github 上注册账号,然后在 Github 网站上创建一个名叫 pythonbbs 的仓库,为了使项目不被其他用户看到,选中 Private 单选按钮,如图 9-52 所示。

image 2025 01 22 17 38 03 122
Figure 1. 图9-52 在Github上创建仓库

在图 9-52 中单击 Create repository 按钮后,出现如图 9-53 所示界面。

在图 9-53 中,可以看到新创建的 pythonbbs 项目的仓库地址,后续需要将本地的仓库地址和远程的仓库地址映射起来。

接下来,在本地 pythonbbs 项目的根路径下右击,在弹出的快捷菜单中选择 Git Bash Here 命令,就会打开 Git 的操作终端,如图 9-54 所示。

image 2025 01 22 17 39 38 349
Figure 2. 图9-53 创建仓库后的界面
image 2025 01 22 17 39 55 611
Figure 3. 图9-54 Git操作终端

执行以下命令,将本地仓库的代码推送到 Github 服务器。

  1. 初始化仓库

    $ git init

    执行以上命令后,会将 pythonbbs 项目初始化成 git 仓库,在项目的根路径下就会多出一个 .git 文件夹。

  2. 添加远程仓库地址

    $ git remote add [远程仓库地址]

    以上命令的作用是在本地仓库添加远程仓库地址,后期可以把代码推送到这个地址。读者在操作时应该将“[远程仓库地址]”修改为自己的仓库地址。仓库地址可以通过图 9-53 中所示方式获取。

  3. 添加所有代码到缓存区

    $ git add .

    pythonbbs 项目下的所有代码添加到缓存区。

  4. 将代码提交到本地仓库

    $ git commit -m "first commit"

    将缓存区的代码添加到本地仓库中。

  5. 将本地仓库代码推送到 Github 远程仓库

    $ git push origin main

git push origin main 中的 main 代表分支,main 分支一般用来表示项目最新的稳定版本。在 Git 中,main 分支原先使用的名称是 master,为了避免种族歧视问题,将 master 修改为 main

完成以上 5 个步骤,即可实现将本地仓库的代码推送到 Github 服务器上,以后我们在自己的服务器上,通过 pull 命令即可完成代码下载。现在机器有 3 种角色,分别是本地的开发机器、Git 服务器以及运行网站的网站服务器,三者之间的角色关系和工作流程如图 9-55 所示。

我们在本地开发机器上开发的代码,经测试没有 bug 后推送到 Git 服务器上(现在用的是 Github),然后在网站服务器上拉取代码,即可完成网站服务器代码的更新。

image 2025 01 22 17 43 57 334
Figure 4. 图9-55 3个机器的角色关系和工作流程

生产环境的配置

在 Git 中,分支是一个非常有用的功能。使用 Git 有一个基本的工作流程,就是尚未完成测试的代码,一般先放到 dev 或者 development 分支下。在测试没有 bug 后,再把代码合并到 main 分支下,并修改为生产环境下的配置信息,最后再把代码推送到网站服务器上,完成代码的更新。

按照 Git 的工作流程,在 main 分支下将 pythonbbs/config.py 中的 ProductionConfig 类根据实际情况修改配置信息,如数据库的域名和端口号等,并且设置 DEBUG=False。然后再在 pythonbbs/app.py 中加载配置类,将之前的 DevelopmentConfig 修改为 ProductionConfig,示例代码如下:

app.config.from_object(config.ProductionConfig)

完成以上操作后,在 Git 终端使用以下命令将代码推送到 Git 服务器:

$ git add .
$ git commit -m "production config"
$ git push origin main

安装常用软件

  1. 安装 OpenSSH

    OpenSSH 可实现远程控制,是一款能够在开发机上连接网站服务器的软件。OpenSSH 是安装在网站服务器上的,通过以下命令即可完成安装:

    $ sudo apt install openssh-server openssh-client

    如果你的开发机是 Windows 系统,那么可以使用 PuTTY( 官网 )或者 Xshell( 官网 )进行连接;如果是 Mac 系统,则可以直接在系统自带的终端软件下使用 ssh 命令进行连接。

  2. 安装 Vim

    Vim 是在 Linux 系统上使用的一款非常好用的文本编辑软件,几乎是 Linux 系统必备的软件,通过以下命令即可完成安装:

    $ sudo apt install vim
  3. 安装 MySQL

    这里我们是为了演示项目,把 MySQL 安装在了网站服务器上。在公司中如果有条件,建议购买单独的 MySQL 服务器,或者搭建独立的 MySQL 服务器,最大限度地保证数据的安全。在网站服务器上安装 MySQL 的命令如下:

    $ sudo apt install mysql-server mysql-client
    $ sudo apt install libmysqld-dev

    执行上述两条命令可以安装 MySQL 服务器和客户端。

  4. 安装 Redis

    我们的 pythonbbs 项目使用 Redis,用来缓存邮箱验证码和 Celery 数据,通过以下命令可安装 Redis:

    $ sudo apt install redis
  5. 安装 Python3

    有的服务器包比较旧,可能没有 Python3,可以通过以下命令安装:

    # 安装 Python3 软件
    $ sudo apt install python3
    
    # 安装 Python3 下的 pip 工具
    $ sudo apt install python3-pip
    
    # 安装 Python3 依赖的头文件等的开发包
    $ sudo apt install python3-dev
  6. 安装 Git

    网站服务器需要通过 Git 工具从 Git 服务器上拉取代码,通过以下命令可以安装 Git:

    $ sudo apt install git

配置网站

在 9.12.4 节中的软件都安装完成后,我们就可以配置网站了,步骤如下。

  1. 创建新用户

    默认的 root 账号权限过大,不建议使用 root 用户部署网站。接下来使用以下命令创建一个新的用户:

    $ adduser zhiliao
    $ usermod -aG sudo zhiliao
    $ su zhiliao

    上述命令首先创建了一个名叫 zhiliao 的用户,并且赋予了 root 权限,然后切换到了 zhiliao 用户。

  2. 使用 Git 拉取代码

    现在代码还是在 Git 服务器上,我们在 /home/zhiliao 下通过以下命令拉取代码到网站服务器:

    $ cd /home/zhiliao
    $ mkdir pythonbbs
    $ git remote add origin https://github.com/NunchakusHuang/pythonbbs.git
    $ git pull origin main

    在执行 git pull 命令时,会提示输入 Github 网站的用户名和密码,输入之后,就可以将 pythonbbs 项目的代码拉取到网站服务器上了。

  3. 安装 Python 依赖包

    我们从开发机上导出了 Python 依赖包到 requirements.txt 中。在网站服务器上,可以通过以下命令完成依赖包的安装:

    $ pip install -r requirements.txt
  4. 创建数据库

    登录 MySQL 后,使用以下命令创建名叫 pythonbbs 的数据库:

    > create database pythonbbs charset utf8mb4;
  5. 同步 ORM 模型到数据库中

    我们在开发机上使用 migrate 生成的迁移脚本,可以直接在网站服务器上进行映射。通过以下命令即可完成:

    $ flask db upgrade

    以上命令会执行 pythonbbs/migrations/versions 下的所有迁移脚本,将 ORM 模型同步到数据库中生成表。

  6. 创建初始数据

    网站运营前应该把初始数据创建好,包括角色、权限、板块以及初始的管理员用户,相关命令格式如下:

    • 创建角色:

      flask create-role
    • 创建权限:

      flask create-permission
    • 创建板块:

      flask create-board
    • 创建管理员:

      flask create-admin --username 张三 --email xx@qq.com --password 111111
  7. 运行 Celery

    pythonbbs 项目的运行依赖于 Celery,而 Celery 又依赖于 Redis,因此在运行 Celery 之前,先运行 Redis。命令如下:

    $ sudo service redis start

使用Gunicorn部署网站

在学习使用 Gunicorn(是 Green Unicorn 的简称)部署网站之前,先来厘清 Web 服务器、应用服务器和 Web 应用框架 3 个概念。

  • Web 服务器:负责处理 HTTP 请求,并响应静态文件。常见的有 Apache、Nginx 以及微软的 IIS 等。

  • 应用服务器:负责处理逻辑的服务器。如 Java、Python 的代码是不能直接通过 Nginx 这种 Web 服务器来处理的,只能通过应用服务器来处理,常见的应用服务器有 uWSGI、Gunicorn 以及 Tomcat 等。

  • Web 应用框架:使用某种编程语言、封装了常用的 Web 功能的框架就是 Web 应用框架。Flask、Django 以及 Java 中的 SSH(Structs+Spring+Hibernate)等都是 Web 应用框架。

WSGI(Web Server Gateway Interface,Web 服务网关接口)是 Python 中定义的 Web 服务器和 Web 应用框架之间的一种简单而通用的接口。我们在开发阶段使用 werkzeug 作为 WSGI 应用服务器,但是 werkzeug 仅用于开发,不能用于生产环境。在生产环境中应该选择性能强悍、稳定性高的 WSGI 应用服务器,如 uWSGI 和 Gunicorn。因为 Gunicorn 性能强、配置简单,所以选择用 Gunicorn 作为 pythonbbs 项目的应用服务器。

首先通过以下命令安装 Gunicorn:

$ sudo pip install gunicorn

接下来在项目的根路径下,创建 gunicorn.conf.py 文件,作为 Gunicorn 的运行配置文件,代码如下:

import multiprocessing

bind = "127.0.0.1:8000"
workers = multiprocessing.cpu_count() * 2 + 1
accesslog = "/var/log/pythonbbs/access.log"
daemon = True

上述代码中每个配置项的意义如下:

  • bind:监听的 IP 地址和端口号,语法为 IP:PORT。如果想让其他机器能够访问,则设置成 0.0.0.0 或者留空,然后再让 Gunicorn 监听 8000 端口即可。后面会用 Nginx 做 Web 服务器,Nginx 和 Gunicorn 之间要通过 8000 端口进行通信,所以 IP 设置为 127.0.0.1

  • workers:workers 的数量。Gunicorn 官方推荐的配置是 CPU 数量 × 2 + 1。

  • accesslog:连接 Gunicorn 的日志文件。

  • daemon:是否作为守护进程执行,设置为 True

最后在 pythonbbs 项目的根路径下,执行以下命令启动 pythonbbs 项目:

$ gunicorn app:app

上述命令会自动在当前路径下寻找 gunicorn.config.py 文件,然后执行 app.py 模块下的 app 对象。至此,Gunicorn 就完成了对 pythonbbs 项目的部署。

读者将 bind 的 IP 地址修改为 0.0.0.0,执行 gunicorn reload 命令重新加载配置文件,然后就可以通过 http://[机器的IP地址]:8000 访问该网站了。

使用Nginx部署网站

虽然 Gunicorn 可以让网站正常运行,但这不是最佳选择。下面使用 Gunicorn 推荐的 Nginx 来作为 Web 服务器。Nginx、Gunicorn 和 pythonbbs 三者之间的关系如图 9-56 所示。

image 2025 01 22 17 55 56 110
Figure 5. 图9-56 Nginx、Gunicorn、pythonbbs三者关系图

通过图 9-56 可以发现,在浏览器向服务器发送请求后,先是由 Nginx 来处理,在遇到以 /static 开头的静态文件 URL 后,则直接返回静态文件;在遇到非静态路由后,则通过反向代理给 Gunicorn 服务器,Gunicorn 再传递给 pythonbbs 项目进行逻辑处理。使用 Nginx 作为 Web 服务器有以下好处:

  • Gunicorn 对静态文件处理效率并不好,包括响应速度和缓存等,Nginx 则不然。

  • Nginx 作为专业的 Web 服务器,可以对外界隐藏应用服务器,更加安全。

  • Nginx 运维起来更加方便。如设置负载均衡、配置 IP 黑名单等都非常方便,如果用 Gunicorn 实现会很麻烦。

要使用 Nginx,首先需要通过以下命令安装 Nginx:

$ sudo apt install nginx

Nginx 的常用命令如下:

  • 启动:

    sudo service nginx start
  • 关闭:

    sudo service nginx stop
  • 重启:

    sudo service nginx restart
  • 测试配置文件:

    sudo service nginx configtest

启动 Nginx 后,在浏览器中访问 http://[服务器IP],即可看到如图 9-57 所示的界面。

image 2025 01 22 17 57 58 788
Figure 6. 图9-57 Nginx默认界面

图9-57 是 Nginx 的默认界面,看到此界面说明 Nignx 已经运行成功。接下来再添加 Nginx 配置文件,在 /etc/nginx/conf.d 目录下创建 pythonbbs.conf 文件,然后添加以下代码。

upstream pythonbbs {
    server 127.0.0.1:8000;  # 配置服务器
}

server {
    listen 80;  # 监听的端口号
    server_name 192.168.0.2;  # 域名
    charset utf-8;
    access_log /var/log/nginx/pythonbbs_access.log;
    error_log /var/log/nginx/pythonbbs_error.log;

    # 最大的文件上传尺寸
    client_max_body_size 75M;

    # 静态文件访问的URL
    location /static {
        # 静态文件地址
        alias /home/zhiliao/pythonbbs/static;
        # 静态文件过期时间
        expires 60d;
    }

    location /media {
        # 文件上传地址
        alias /home/zhiliao/pythonbbs/media;
        expires 100d;
    }

    # 最后,发送所有非静态文件请求到 Gunicorn
    location / {
        proxy_pass http://pythonbbs;
        # uwsgi_params 文件地址
        include /etc/nginx/uwsgi_params;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

上述配置代码中,upstream 是用来设置负载均衡的,里面可以设置多个 server, Nginx 会根据策略选择不同的后端服务器来实现负载均衡。下面的 server 部分用来配置 Nginx 服务器,这里监听 80 端口,并且指定服务器名称为 192.168.0.2,以后通过这个服务器名称即可访问到 Nginx 服务器。读者在这里可以修改为自己真实服务器的 IP 地址,或者如果有域名,并且做好了域名和服务器的映射,则可以修改为域名。access_log 和 error_log 是配置客户端连接 Nginx 服务器的日志。location/static 用于指定以 /static 开头的静态文件的处理逻辑,使用 alias 指定静态文件寻找的路径,并且过期时间为 60 天。另外,我们还设置了 /media,用来获取用户上传后的图片。最后是非静态文件和非 media 文件,通过反向代理传给 Gunicorn 服务器,下面设置的 proxy_set_header 是为了能准确获取客户端的信息。

现在通过 80 端口和 8000 端口都可以访问到网站,原因是 Nginx 监听 80 端口,Gunicorn 监听 8000 端口。我们的预想是客户端仅通过 80 端口访问,8000 端口用于 Nginx 和 Gunicorn 通信。可以通过防火墙关闭 8000 端口来实现这个需求,执行命令如下。

$ sudo ufw enable
$ sudo ufw allow 80
$ sudo ufw deny 8000
$ sudo ufw allow 22

上述命令的作用分别是开启防火墙、打开 80 端口、关闭 8000 端口,打开 22 端口并且为了能在开发机上通过远程连接服务器,我们开启了 22 端口,也就是 SSH 服务。

压力测试

在项目部署完成后,我们可以进行压力测试,来评估 Nginx+Gunicorn 部署的网站的效率。使用的是 apache2-utils 中的 ab 命令。

  1. 安装 apache2-utils

    通过以下命令安装 apache2-utils

    $ sudo apt install apache2-utils
  2. 执行压力测试命令

    执行以下命令进行压力测试:

    $ ab -n 50000 -c 1000 http://127.0.0.1/

    其中,命令参数的含义如下:

    • -n:总共发起请求的数量。这里设置为总共发起 50000 个请求。

    • -c:并发的数量。这里设置并发数为 1000 个请求。

    • http://127.0.0.1/:压力测试的 URL。

  3. 输出结果

    执行完上述命令后,您可以看到类似如下的输出结果。

    image 2025 01 22 18 03 41 561
    image 2025 01 22 18 03 54 285
    image 2025 01 22 18 04 11 194

以上每项指标代表的意义如下。

  • Completed xxx requests:请求完成的数量。

  • Server Software:服务器软件名称。

  • Server Hostname:服务器主机名。

  • Server Port:服务器端口号。

  • Document Path:请求的 URL PATH。

  • Document Length:HTTP 响应数据的正文长度。

  • Concurrency Level:并发量。

  • Time taken for tests:总共耗费的时间。

  • Complete requests:完成的请求数。

  • Failed requests:失败的请求数。

  • Total transferred:所有请求响应数据的长度。

  • HTML transferred:所有请求响应数据中 HTML 内容的长度。

  • Requests per second:每秒完成的请求数,也叫 RPS(吞吐率)。

  • Time per request (mean):一个请求用户平均等待的时间。

  • Time per request (mean, across all concurrent requests):服务器完成一个请求平均的时间。

  • Transfer rate:网络传输速率。

  • Connection Times (ms):从请求到响应的整个过程消耗的时间。包括以下几个部分:

    • Connect(网络连接)

    • Processing(程序处理)

    • Waiting(等待)

    其中:

    • min:最小值

    • mean:平均值

    • [+/-sd]:标准差(反映数据的离散程度,数值越大表示越不稳定)

    • median:中位数

    • max:最大值

  • Percentage of the requests served within a certain time (ms):在 50000 个请求中的不同完成率阶段进行采样,请求所消耗的时间。例如:

    • 50%,也就是在第 25000 个请求时,花费了 70ms 的时间。

    • 在第 50000 个请求消耗了 1107ms 的时间。

以上压力测试的硬件环境配置为 2GB内存+单核CPU 3600Hz+机械硬盘。其中QPS(queriesper second,每秒查询率)达到了 13000 以上,说明网站运行的效率还是不错的。