1. Flask、nginx和uwsgi的概念及关系

  • nginx是一个web服务器
  • Flask也是一个web框架,常用的其他web框架还有Django
  • uwsgi与WSGI一样,是一种通信协议。首先要说明,uWSGI是一个web服务器,它实现了WSGI、uwsgi、http等协议,其作用就是把HTTP协议转化成语言支持的网络协议,用于处理客户端请求,并向客户端返回响应。这里的uwsgi则是uWSGI服务器的独占协议(只适用于uWSGI服务器?),与WSGI是两种不同的协议

通过上面的描述中可以得出,uWSGI+Flask就可以实现一个完整的web服务,似乎不需要Nginx服务器。
当一个服务访问量过大时,我们可能会考虑多部署几台web服务器,这几台web服务器都可以处理客户端请求,但问题是如何将客户端请求分发到各个web服务器上?这就是Nginx的作用->反向代理服务器。它们的关系如下图所示:

废话少说,我们接下来直接实战。

2. 创建一个Flask轻量级的Web应用

项目的目录结构如下:

  • cert 证书文件夹:包含两个文件,注册好域名后就可以下载到这两文件,本人是在在阿里上注册的。
  • .env : 设置环境变量的文件
  • Dockerfile: 用于build image的
  • main.py: Flask Web应用程序的入口
  • nginx.conf: nginx配置文件
  • uwsgi.ini:  uwsgi 配置文件
  • requirements.txt: python依赖包

Flask 应用的比较轻量简单,主要看main.py,具体解释直接看代码就好。

import os
import sys

# sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
import json
from flask import request, Flask, jsonify
from dotenv import load_dotenv

# 创建一个Flask应用
app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False

@app.route('/')
def show_hello():
    myname = os.getenv('myname')
    return myname

if __name__ == '__main__':
    # 从.env文件中加载环境变量
    load_dotenv()
    # 本地运行的话,可以直接访问localhost:8086
    app.run(port=8086)

3. 配置uwsgi

这是uwsgi.ini里面的内容,可以参考里面的注释, 注意这里采用的是socket通信,端口是8086,如果只想用uwsgi作web服务器就使用http-socket,如果和nginx一起使用,就用socket, 和上面的命令是二选一的关系。更过关于nWSGI相关的内容可参考这里

[uwsgi]
chdir = /app
wsgi-file = main.py
venv = venv
socket = :8086
callable = app
chmod-socket = 666
plugins = python3
buffer-size = 65535

# 使用uwsgi前一定要安装此插件 sudo apt-get install uwsgi-plugin-python3
# [uwsgi]
# chdir = /home/cong/Desktop/docker-flask-nginx-demo/my_flask
# wsgi-file = run.py         # 指定要加载的WSGI文件,也即包含Flask实例(app)的文件名称
# callable = app             # 指出uWSGI加载的模块中哪个变量将被调用, 也即Flask实例名称
# socket = :8086             # 指定socket文件,也可以指定为127.0.0.1:9000,用于配置监听特定端口的套接字
# http-socket = :8086 # 如果只想用uwsgi作web服务器就使用http-socket,如果和nginx一起使用,就用socket, 和上面的命令是二选一的关系。
# venv = venv
# chmod-socket = 666
# processes = 4              # 指定开启的工作进程数量(这里是开启4个进程)
# threads = 2                # 设置每个工作进程的线程数
# master = true              # 启动主进程,来管理其他进程,其它的uwsgi进程都是这个master进程的子进程
# vacuum = true              # 当服务器退出的时候自动删除unix socket文件和pid文件
# die-on-term = true         # ?
# buffer-size = 65535        # 设置用于uwsgi包解析的内部缓存区大小为128k。默认是4k
# limit-post = 104857600     # 限制http请求体大小

4. 配置nginx

这是nginx.conf里面的内容,8086是uwsgi里定义的端口,我们主要使用这个, 80 端会自己重定向到443,以便用户直接使用https来访问我们的应用,8085和81是用作测试的,可以忽略。

user www-data;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    #include /etc/nginx/conf.d/*.conf;

    server {
        listen       80;
        server_name  xxxxxter.cn;
        return       301 https://$server_name$request_uri; # 重定向
    }

    server {
      listen 81;
      charset utf-8;
      client_max_body_size 75M;
      location ^~/exam {
        proxy_pass http://127.0.0.1:8085; #使用代码
      }
    }

    server {
      listen 82;
      charset utf-8;
      client_max_body_size 75M;
      location ^~/exam {
        include /etc/nginx/uwsgi_params;
        uwsgi_pass 127.0.0.1:8086;
        uwsgi_send_timeout 300;
        uwsgi_connect_timeout 300;
        uwsgi_read_timeout 300;
      }
    }

    server {
        listen                    443 ssl;
        server_name               xxxxxter.cn;
        ssl_certificate           /etc/nginx/cert/3421147_bingter.cn.pem;
        ssl_certificate_key       /etc/nginx/cert/3421147_bingter.cn.key;
        ssl_ciphers               ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
        ssl_protocols             TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_session_timeout       5m;
        charset utf-8;
        client_max_body_size 75M;
        location ^~/exam {
            include /etc/nginx/uwsgi_params;
            uwsgi_pass 127.0.0.1:8086;
            uwsgi_send_timeout 300;
            uwsgi_connect_timeout 300;
            uwsgi_read_timeout 300;
            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_redirect off;
        }
        location ^~/exam/static {
            proxy_pass http://127.0.0.1:8085/static;
        }
        # location ^~/api/{
        #     proxy_pass http://demo;
        #     proxy_http_version 1.1;
        #     proxy_set_header Host $host;
        #     proxy_set_header Upgrade $http_upgrade;
        #     proxy_set_header Connection $connection_upgrade;
        #     proxy_set_header X-Real-IP $remote_addr;
        #     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        #     proxy_redirect off;
        #     proxy_connect_timeout 1;
        #     proxy_buffer_size 64k;
        #     proxy_buffers   4 64k;
        #     proxy_busy_buffers_size 128k;
        # }
        # location / {
        #     root /root/react-demo/;
        #     index index.html;
        # }
    }

    server {
      listen 8085;
      charset utf-8;
      client_max_body_size 75M;
      location / {
        include /etc/nginx/uwsgi_params;
        uwsgi_pass 127.0.0.1:8086;
        uwsgi_send_timeout 300;
        uwsgi_connect_timeout 300;
        uwsgi_read_timeout 300;
      }
    }
}

5. Dockerfile

使用它来用于生成自定义的镜像。

# 使用最新的ubuntu基础镜像
FROM ubuntu

RUN mkdir /app

COPY . /app

WORKDIR /app

RUN apt-get update -yqq

# uwsgi-plugin-python3 这是在uwsgi中运行python程序的一个插件
RUN apt-get install python3 python3-pip python3-venv nginx-core nginx uwsgi uwsgi-plugin-python3 -y

# 将nginx的配置文件拷贝到/etc/nginx下
COPY nginx.conf /etc/nginx/nginx.conf

# 添加证书文件
COPY cert /etc/nginx/cert

# nginx运行是需要有用户名的,默认安装好nginx后就有用户名www-data,如果想用其他用户可以使用如下代码增加组和用户
# RUN  groupadd www-data && useradd -G www-data www-data

# Python一般是在虚拟环境下运行了,下面三步是用于创建虚拟环境:
# 第一步:定义虚拟环境的名称
ENV VIRTUAL_ENV=venv

# 第二步:生成虚拟环境
RUN python3 -m venv venv

# 第三步:最关键,它会激活虚拟环境,这是英文说明:The most important part is setting PATH: PATH is a list of directories which are searched for commands to run. activate simply adds the virtualenv’s bin/ directory to the start of the list.
ENV PATH="$VIRTUAL_ENV/bin:$PATH"

# 因为docker image是分层的,每一行的Run命令就是一层,同时也是不同的进程,所以像如下设置虚拟环境,是没用的。
# RUN source venv/bin/activate
# ubuntu 默认使用得是/bin/sh, 而不是/bin/bash, bash用得是source, sh用得是点
# RUN . ./venv/bin/activate

# 对pip进行升级
RUN python3 -m pip install --upgrade pip

# 安装运行python所需要的包
RUN pip3 install -r requirements.txt

# 启动nginx和uwsgi,这里一定要加上-g,主要目的就是设置pid=1。因为docker 容器默认会把容器内部第一个进程,也就是pid=1的程序作为docker容器是否正在运行的依据,如果pid=1的程序挂了,那容器就挂了。
ENTRYPOINT nginx -g "daemon on;" && uwsgi --ini uwsgi.ini

6. Build镜像与运行容器

# 打包镜像
cd /home/cong/ xxxxx_api
docker build -t wucong60/xxxxx_api .
docker login -u wucong60
docker push wucong60/xxxxx_api

# 在容器中运行
docker pull wucong60/xxxxx_api
docker run -itd -p 80:80 -p 443:443 --name=xxxxx_api wucong60/xxxxx_api

7. 运行效果

可以看到容器正常运行,进入容器内部,nginx作为pid=1 也正常的运行。

浏览器访问API,返回成功,注意这里是https的哦

加油! 

参考链接

https://pythonspeed.com/articles/activate-virtualenv-dockerfile/ 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐