配置搭建阿里云服务器nginx+uwsgi (python)

关于使用nginx+uwsgi搭建web服务器,网上有很多教程,但是对新手来说都有些不好理解。下面我总结了一下,纯基础、好使。

首先理解一些基本概念:

WSGI是什么?

WSGI,全称 Web Server Gateway Interface,或者 Python Web Server Gateway Interface ,是为 Python 语言定义的 Web 服务器和 Web 应用程序或框架之间的一种简单而通用的接口。自从 WSGI 被开发出来以后,许多其它语言中也出现了类似接口。

WSGI 的官方定义是,the Python Web Server Gateway Interface。从名字就可以看出来,这东西是一个Gateway,也就是网关。网关的作用就是在协议之间进行转换。

uWSGI是什么?

uWSGI是一个Web服务器,它实现了WSGI协议、uwsgi、http等协议。Nginx中HttpUwsgiModule的作用是与uWSGI服务器进行交换。

nginx、uwsgi、flask之间的关系

  • 首先nginx 是对外的服务接口,外部浏览器通过url访问nginx

  • nginx 接收到浏览器发送过来的http请求,将包进行解析,分析url,如果是静态文件请求就直接访问用户给nginx配置的静态文件目录,直接返回用户请求的静态文件。如果是动态那么nginx就将请求转发给uwsgi,通过flask框架转换为某一函数或者方法进行调用,并处理后,返回给uwsgi。uwsgi转发给nginx,nginx最终将返回结果返回给浏览器。

  • 由此可见nginx并不是必须的,uwsgi完全可以完成整个的和浏览器交互的流程。但是要考虑到某些情况。

  • 安全问题程序不能直接被浏览器访问到,而是通过nginx,nginx只开放某个接口,uwsgi本身是内网接口,这样运维人员在nginx上加上安全性的限制,可以达到保护程序的作用。

  • 负载均衡问题一个uwsgi很可能不够用,即使开了多个work也是不行,毕竟一台机器的cpu和内存都是有限的,有了nginx做代理,一个nginx可以代理多台uwsgi完成uwsgi的负载均衡。

  • 静态文件问题用django或是uwsgi这种东西来负责静态文件的处理是很浪费的行为,而且他们本身对文件的处理也不如nginx好,所以整个静态文件的处理都直接由nginx完成,静态文件的访问完全不去经过uwsgi以及其后面的东西。

好的接下来直接进入搭建过程。 首先你得有一台服务器。这个是我自己的阿里云esc服务器。然后通过ssh root@云服务器ip地址,然后输入密码进行登录。

安装 Python 环境

不多说,一般系统都自带python环境以及pip

安装Nginx

pip install nginx

或者

yum install nginx

安装VirtualEnv

不同的项目可能会引用各种不同的依赖包,为了避免版本与和应用之间的冲突而造成的“依赖地狱”。VirtualEnv 可以为每个Python应用创建独立的开发环境,使他们互不影响。

pip install virtualenv

安装VirtualEnv 后只需要在项目目录内运行 virtualenv 目录名 就可以建立一个虚拟环境文件夹。 假定我的项目目录叫 /home/www/myproj进入该目录后运行

virtualenv venv

项目目录下得到新的venv目录即虚拟环境 (虚拟环境目录可以叫venv,自己随意)

输入

source venv/bin/activate 

进入虚拟环境,前面多了虚拟目录名(venv)

安装 uWSGI

pip install uwsgi

安装 Flask

pip install flask

项目文件

假设到现在为止项目里面只有一个最简单的文件 hello.py,里面内容如下:

from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return "Hello World!"

配置uwsgi

同志们,项目的准备工作准备的差不多了。接下来就是一些配置问题(哎,以前碰到的坑巨多)。

好,在项目目录下新建一个uwsgi.ini

touch uwsgi.ini

创建好了以后,用vim在uwsgi.ini写入以下内容:

[uwsgi]
master = true
home=/home/www/myproj/venv
wsgi-file = hello.py
callable = app
socket=127.0.0.1:5000
stats=127.0.0.1:9191
processes = 4
threads = 4
buffer-size=32768

运行uwsgi试试,如果输出类似以下信息,说明成功。

uwsgi --ini uwsgi.ini
(venv)my_flask root$ uwsgi uwsgi.ini

[uWSGI] getting INI configuration from uwsgi.ini

*** Starting uWSGI 2.0.8 (64bit) on [Tur Sep 19 14:34:11 2017] 
// 此处略去那些无用的启动信息
Stats server enabled on 127.0.0.1:9191 fd: 15 ***

ok,uwsgi已经配置完成,ctrl+c 关闭程序。

配置Nginx

找到配置文件nginx.conf(我的在目录/etc/nginx/nginx.conf),修改以下部分

server {
        charset      utf-8;
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  xxxx域名地址
        root         /home/www/myproj;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
           include      uwsgi_params;
           uwsgi_pass   127.0.0.1:5000;
           uwsgi_param UWSGI_PYHOME /home/www/myproj/venv;
           uwsgi_param UWSGI_CHDIR  /home/www/myproj;
        }
    }

启动Nginx服务器、启动uwsgi服务器

这个地方要注意,我在这卡了我1天。因为有俩个服务器,所以俩个都要启动。

service nginx restart

uwsgi --ini uwsgi.ini

大功告成

nginx+uwsgi+django部署python项目

初次部署nginx+uwsgi+django真是经历了千难万险,因此在这里整理分享一下,希望在这条路上行走的你有所帮助。

我使用的系统是fedora27,已经安装了python3和pip3,这两个没有安装的各位先自行安装。

Nginx

  • 安装
dnf install nginx
  • 配置
location / {
    uwsgi_pass  127.0.0.1:9090;
    include     uwsgi_params; # the uwsgi_params file you installed
}

nginx的配置只需要改这些

Django

  • 安装
dnf install setuptools
pip3 install django
pip3 install PyMySQL
pip3 install mysqlclient
#安装mysqlclient可能会遇到以下下问题,
dnf install mysql-devel # 解决 OSError: mysql_config not found
dnf install gcc # 解决 unable to execute 'gcc': No such file or directory
dnf install redhat-rpm-config # 解决gcc: 错误:/usr/lib/rpm/redhat/redhat-hardened-cc1:No such file or directory
#python-devel for python2.x
#python3-devel for python3.x
dnf install python3-devel 解决 致命错误:Python.h:No such file or directory
  • 启动
#在你的项目目录下
[jeffery@localhost pythontest]$ python3 manage.py runserver 0.0.0.0:8000
Performing system checks...

System check identified no issues (0 silenced).
November 30, 2017 - 08:11:58
Django version 1.11.7, using settings 'pythontest.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.

这样django就启动成功了,可以在浏览器中用8000端口访问一下项目

uwsgi

  • 安装
pip3 install uwsgi
  • django_socket.ini配置
[uwsgi]
socket = 127.0.0.1:9090
chdir = /projects/pythontest
module = pythontest.wsgi
processes = 2
pidfile = uwsgi.pid
daemonize = /var/log/uwsgi/uwsgi9090.log

chdir——你的项目根目录
module——入口文件,即wsgi.py相对于项目根目录的位置,“.”相当于一层目录。如果是用django创建的项目,wsgi.py这个文件是自动生成的,自己找一下。
daemonize——作为后台进程执行,日志输出的地方,目录没有的自己创建下

  • wsgi.py
import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pythontest.settings")

application = get_wsgi_application()
  • 启动
uwsgi --ini django_socket.ini

启动完我们就可以去上面daemonize 配置的日志中看看是否启动成功
Nginx中配置的地址端口要和django_socket.ini中保持一致
接下来用浏览器访问下Nginx,有人应该已经能够成功看到自己的python项目了

不行的请继续往下看

VirtualEnv

VirtualEnv的作用:创建隔离的Python环境,解决模块或库的版本冲突或依赖。(我的系统中既有python2.7又有python3.6)

  • 安装
pip3 install virtualenv
  • 创建虚拟环境
virtualenv mytest

mytest是我这边起的名字,可以随便起
创建不了的看下自己当前用户权限够不够, 不是root用户就到 /home目录下找你现在登录的角色的目录,然后进入用户目录下再执行上面的安装命令,执行完以后用ls看下有没有mytest这个文件夹,进入mytest的bin中

[jeffery@localhost /]$ cd /home/jeffery/mytest/bin/
[jeffery@localhost bin]$ ls
activate          django-admin      pip          python         wheel
activate.csh      django-admin.py   pip3         python3
activate.fish     easy_install      pip3.6       python3.6
activate_this.py  easy_install-3.6  __pycache__  python-config
  • 激活mytest虚拟环境
[jeffery@localhost bin]$ source ./activate
(mytest) [jeffery@localhost bin]$ 

此时我们看到命令行前面加上了(mytest),这代表我们在mytest的虚拟环境中,这个时候按照上面django的安装配置重新配置一次,最后在这个虚拟环境中启动django。(我们上面启动的可以不要了,虚拟环境中启动就可以了)

  • 修改django_socket.ini
[uwsgi]
socket = 127.0.0.1:9090
chdir = /projects/pythontest
module = pythontest.wsgi
processes = 2
pidfile = uwsgi.pid
daemonize = /var/log/uwsgi/uwsgi9090.log
home = /home/jeffery/mytest

这里面我们加上了最后一行,也就是配置home——虚拟环境mytest的根目录。

重新启动uwsgi,用浏览器访问Nginx,成功访问python项目。

启动jenkins时指定jvm参数

环境

java:1.7
jenkins:2.5
操作系统:win7
服务器:centos6
工具:CRT

场景

今天jenkins用着用着报了以下错误(从日志中查看):

javax.servlet.ServletException: org.apache.commons.jelly.JellyTagException: jar:file:/home/jenkins/war/WEB-

INF/lib/jenkins-core-2.25.jar!/lib/layout/layout.jelly:83:72: <st:include> Error setting property 'page', 

exception - java.lang.OutOfMemoryError: PermGen space
        at org.kohsuke.stapler.jelly.JellyFacet$1.dispatch(JellyFacet.java:103)
        at org.kohsuke.stapler.Stapler.tryInvoke(Stapler.java:746)
        at org.kohsuke.stapler.Stapler.invoke(Stapler.java:876)
        at org.kohsuke.stapler.Stapler.invoke(Stapler.java:649)
        at hudson.init.impl.InstallUncaughtExceptionHandler$1.reportException

(InstallUncaughtExceptionHandler.java:30)
        at org.kohsuke.stapler.compression.CompressionFilter.reportException(CompressionFilter.java:77)
        at org.kohsuke.stapler.compression.CompressionFilter.doFilter(CompressionFilter.java:55)
        at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
        at hudson.util.CharacterEncodingFilter.doFilter(CharacterEncodingFilter.java:82)
        at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
        at org.kohsuke.stapler.DiagnosticThreadNameFilter.doFilter(DiagnosticThreadNameFilter.java:30)
        at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
        at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
        at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
        at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:553)
        at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
        at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
        at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
        at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
        at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
        at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
        at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
        at org.eclipse.jetty.server.Server.handle(Server.java:499)
        at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)
        at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
        at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:544)
        at winstone.BoundedExecutorService$1.run(BoundedExecutorService.java:77)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:744)
Caused by: org.apache.commons.jelly.JellyTagException: jar:file:/home/jenkins/war/WEB-INF/lib/jenkins-core-

2.25.jar!/lib/layout/layout.jelly:83:72: <st:include> Error setting property 'page', exception - 

java.lang.OutOfMemoryError: PermGen space
        at org.apache.commons.jelly.impl.TagScript.handleException(TagScript.java:726)
        at org.apache.commons.jelly.impl.TagScript.run(TagScript.java:281)
        at org.apache.commons.jelly.TagSupport.invokeBody(TagSupport.java:161)
        at org.apache.commons.jelly.tags.core.ForEachTag.doTag(ForEachTag.java:150)
        at org.apache.commons.jelly.impl.TagScript.run(TagScript.java:269)
        at org.apache.commons.jelly.tags.core.CoreTagLibrary$1.run(CoreTagLibrary.java:98)
        at org.apache.commons.jelly.impl.ScriptBlock.run(ScriptBlock.java:95)
        at org.apache.commons.jelly.tags.core.CoreTagLibrary$1.run(CoreTagLibrary.java:98)
        at org.apache.commons.jelly.impl.ScriptBlock.run(ScriptBlock.java:95)
        at org.apache.commons.jelly.tags.core.CoreTagLibrary$2.run(CoreTagLibrary.java:105)
        at org.kohsuke.stapler.jelly.CallTagLibScript.run(CallTagLibScript.java:120)
        at org.apache.commons.jelly.impl.ScriptBlock.run(ScriptBlock.java:95)
        at org.apache.commons.jelly.tags.core.CoreTagLibrary$2.run(CoreTagLibrary.java:105)
        at org.kohsuke.stapler.jelly.JellyViewScript.run(JellyViewScript.java:95)
        at org.kohsuke.stapler.jelly.DefaultScriptInvoker.invokeScript(DefaultScriptInvoker.java:63)
        at org.kohsuke.stapler.jelly.DefaultScriptInvoker.invokeScript(DefaultScriptInvoker.java:53)
        at org.kohsuke.stapler.jelly.JellyFacet$1.dispatch(JellyFacet.java:95)
        ... 29 more
Caused by: java.lang.IllegalArgumentException: Error setting property 'page', exception - 

java.lang.OutOfMemoryError: PermGen space
        at org.apache.commons.beanutils.ConvertingWrapDynaBean.set(ConvertingWrapDynaBean.java:74)
        at org.apache.commons.jelly.impl.TagScript.run(TagScript.java:265)
        ... 44 more
Caused by: java.lang.OutOfMemoryError: PermGen space

十一月 29, 2017 10:45:57 上午 org.eclipse.jetty.util.log.JavaUtilLog warn
警告: Could not send response error 500: javax.servlet.ServletException: 

org.apache.commons.jelly.JellyTagException: jar:file:/home/jenkins/war/WEB-INF/lib/jenkins-core-

2.25.jar!/lib/layout/layout.jelly:83:72: <st:include> Error setting property 'page', exception - 

java.lang.OutOfMemoryError: PermGen space

可以很明显看出是内存溢出;

官网也给出了解决办法:

修改jvm参数

我们先来看看官方是怎么解释(为什么会内存溢出):

As your project grows, and you use new tools to either build or analyze your code, you will inevitably exceed the memory settings which your JVM provides by default. This is especially true on 64 bit JVM’s since they double the size of the reference pointer. This page aims to show you how to increase the memory available to your build process.

上面的意思是:

随着我们项目的增长,和使用新的工具去构建或分析代码,会不可避免的超过jvm提供的默认值。这在64位jvm中尤为明显,因为它们引用的指针大小是翻倍的(相对32位)。接下来将展示如何增加可用于构建过程的内存(通俗点说就是增加 jvm 可用的内存)。

Heap or Permgen

内存溢出分来两种;

官方介绍:

There are two OutOfMemoryErrors which people usually encounter. The first is related to heap space: java.lang.OutOfMemoryError: Heap space When you see this, you need to increase the maximum heap space. You can do this by adding the following to your JVM arguments -Xmx200m where you replace the number 200 with the new heap size in megabytes. 
The second is related to PermGen: java.lang.OutOfMemoryError: PermGen space. When you see this, you need to increase the maximum Permanent Generation space, which is used for things like class files and interned strings. You can do this by adding the following to your JVM arguments -XX:MaxPermSize=128m where you replace the number 128 with the new PermGen size in megabytes.

意思就是:

通常情况下,我们会遇到两种内存溢出的错误。第一种是关于堆溢出:java.lang.OutOfMemoryError: Heap space;当你看到这个错误时,你需要增加堆空间的最大值。你可以添加以下内容作为jvm的参数:-Xmx200m,数字200你可以使用新值替换。比如4096即:-Xmx4096m。
第二种是关于永久代的(java7以后版本会移除永久代):java.lang.OutOfMemoryError: PermGen space。当你看到这个错误时,你需要增加永久代空间的最大值,这个空间是被像 类文件和interned字符串所使用的。你可以添加以下内容作为jvm的参数:-XX:MaxPermSize=128m,数字128你可以使用新值替换。比如512即:-XX:MaxPermSize=512m。

我的改法

很明显我的问题是第二种,所以我需要使用-XX:MaxPermSize=128m这个参数:

我的改法是:

vim /etc/sysconfig/jenkins
# 找到 JENKINS_JAVA_OPTIONS
# JENKINS_JAVA_OPTIONS="-Djava.awt.headless=true"
# 改为
JENKINS_JAVA_OPTIONS="-XX:MaxPermSize=512m -Djava.awt.headless=true"

保存好后,之后,在重启jenkins。但是呢我启动后,还是报内存溢出,连页面都打不开。看了下进程:

[root@master01 log]# ps -ef | grep jenkins
root       357 45410  0 11:06 pts/4    00:00:00 grep jenkins
root     22293     1  0 Nov13 ?        00:54:21 /usr/java/jdk1.7.0_51/bin/java -cp /home/jenkins/dataspace/plugins/maven-plugin/WEB-INF/lib/maven32-agent-1.12-alpha-1.jar:/home/activemq/apache-maven-3.2.3/boot/plexus-classworlds-2.5.1.jar:/home/activemq/apache-maven-3.2.3/conf/logging jenkins.maven3.agent.Maven32Main /home/activemq/apache-maven-3.2.3 /var/cache/jenkins/war/WEB-INF/lib/remoting-2.62.jar /home/jenkins/dataspace/plugins/maven-plugin/WEB-INF/lib/maven32-interceptor-1.12-alpha-1.jar /home/jenkins/dataspace/plugins/maven-plugin/WEB-INF/lib/maven3-interceptor-commons-1.12-alpha-1.jar 48531
root     24012     1  0 Nov21 ?        01:38:52 /usr/java/jdk1.7.0_51/bin/java -Dcom.sun.akuma.Daemon=daemonized -Djava.awt.headless=true -DJENKINS_HOME=/home/jenkins/dataspace -jar /home/jenkins/lib/jenkins.war --logfile=/home/jenkins/log/jenkins.log --webroot=/home/jenkins/war --daemon --httpPort=7080 --ajp13Port=-1 --debug=5 --handlerCountMax=100 --handlerCountMaxIdle=20

这里我们看到进程id:22293,这个进程估计是我之前启动一个maven job依赖下来的,这个maven项目每次启动都把jenkins直接跑崩了!杀死这个进程后,重启jenkins就可以了

官方的改法

官方的改法都是在jenkins的配置页面中进行修改,我之所没这么做,是因为我jenkins连页面都打不开(因为内存溢出啦)。

maven类型项目

①全局配置:
如果你使用的是Maven2/3项目类型,你可以在jenkins全局配置中设置-Xmx or -XX:MaxPermSize。通过导航(Manage Jenkins -> Configure System),接着找到Maven Project Configuration,在Global MAVEN_OPTS这一栏中添加需要设置的jvm参数并点击保存。随后的Maven2/3 job构建将会使用新的设置。

②项目设置:
这个设置是针对每个job的。首先在job页面中点击configure按钮,接着找打Build部分并点击advanced,接着在MAVEN_OPTS这个选项中设置相关参数。

Freestyle projects with Maven Build Steps

如果你有一个自由式项目并使用Invoke Top Level Maven Targets构建步骤,你可以点击高级按钮,在JVM Options这一栏添加jvm参数。

另外,可以在jenkins全局配置,通过添加MAVEN_OPTS全局变量来影响所有自由风格maven的构建步骤。具体配置路径:先点击Manage Jenkins,接着Configure System,在Global properties这一栏中,勾选Environment Variables复选框,接着添加一个新的名为MAVEN_OPTS的环境变量,并设置一个合适的值:

未分类

Gradle build steps

这里同理上面,可以在系统设置中添加全局变量

Ant build steps

对于Ant 步骤,其没有全局环境变量,你必须在每个单独的构建步骤中设置Ant选项。在构建的设置中,找到Invoke Ant步骤,点击高级按钮,Java Options这一栏输入参数。

参考地址:
https://wiki.jenkins.io/display/JENKINS/Builds+failing+with+OutOfMemoryErrors

python virtualenv虚拟环境介绍

最近折腾tensorflow的编译安装,重新用virtualenv, 发现生疏了,就简单记录下吧

在开发Python应用程序的时候,系统安装的Python3只有一个版本:3.4。所有第三方的包都会被pip安装到Python3的site-packages目录下。

如果我们要同时开发多个应用程序,那这些应用程序都会共用一个Python,就是安装在系统的Python 3。如果应用A需要jinja 2.7,而应用B需要jinja 2.6怎么办?

这种情况下,每个应用可能需要各自拥有一套“独立”的Python运行环境。virtualenv就是用来为一个应用创建一套“隔离”的Python运行环境。

首先,我们用pip安装virtualenv:

$ pip3 install virtualenv

然后,假定我们要开发一个新的项目,需要一套独立的Python运行环境,可以这么做:

第一步,创建目录:

Mac:~ $ mkdir myproject
Mac:~ $ cd myproject/
Mac:myproject michael$

第二步,创建一个独立的Python运行环境,命名为venv:

Mac:myproject  $ virtualenv --no-site-packages venv
Using base prefix '/usr/local/.../Python.framework/Versions/3.4'
New python executable in venv/bin/python3.4
Also creating executable in venv/bin/python
Installing setuptools, pip, wheel...done.

命令virtualenv就可以创建一个独立的Python运行环境,我们还加上了参数–no-site-packages,这样,已经安装到系统Python环境中的所有第三方包都不会复制过来,这样,我们就得到了一个不带任何第三方包的“干净”的Python运行环境。

新建的Python环境被放到当前目录下的venv目录。有了venv这个Python环境,可以用source进入该环境:

Mac:myproject michael$ source venv/bin/activate
(venv)Mac:myproject michael$

注意到命令提示符变了,有个(venv)前缀,表示当前环境是一个名为venv的Python环境。

下面正常安装各种第三方包,并运行python命令:

(venv)Mac:myproject michael$ pip install jinja2
...
Successfully installed jinja2-2.7.3 markupsafe-0.23
(venv)Mac:myproject michael$ python myapp.py
...

在venv环境下,用pip安装的包都被安装到venv这个环境下,系统Python环境不受任何影响。也就是说,venv环境是专门针对myproject这个应用创建的。

退出当前的venv环境,使用deactivate命令:

(venv)Mac:myproject michael$ deactivate 
Mac:myproject michael$

此时就回到了正常的环境,现在pip或python均是在系统Python环境下执行。

完全可以针对每个应用创建独立的Python运行环境,这样就可以对每个应用的Python环境进行隔离。

virtualenv是如何创建“独立”的Python运行环境的呢?原理很简单,就是把系统Python复制一份到virtualenv的环境,用命令source venv/bin/activate进入一个virtualenv环境时,virtualenv会修改相关环境变量,让命令python和pip均指向当前的virtualenv环境。

小结

virtualenv为应用提供了隔离的Python运行环境,解决了不同应用间多版本的冲突问题。

uwsgi+flask 部署网站

摘要: 采用uwsgi作为web服务,将flask框架开发的web程序运行在centos云服务器上

概要

本文主要是叙述用uwsgi作为web服务运行flask开发的网站。本人查阅网上很多资料。跟着他们的步骤做,总是出现各种问题。折腾好久后,终于能正常运行了。故记录下来,供自己和他人查阅。

开发环境

centos7 64bit 云服务器,python 3.5 , uwsgi 2.0

因为博主用flask开发的web使用的python3.5 ,所以,就在云服务器上安装了3.5.如果你的项目是其他版本python开发的,请安装对应的版本。这里不多加叙述安装问题。

安装uwsgi

因为我的python版本是3.5的。所以一下所有关于python的操作指令都是python3 和pip3.这个如果你的不是,请对应自己的来改。比如你的是python 2.7版本的,你的python指令的开头应该是python或者python2,安装包指令应该是pip2或者pip

有很多方式提供安装,比如yum 安装,手动下载安装包,编译安装,已经pip安装。这里我们选择pip安装。这种方式感觉更靠谱。因为我用yum install uwsgi指令安装,最后uwsgi指定的python版本是我的旧版本2.7.而不是最新的3.5。所以卸载 了,又重新采用了pip安装

安装uwsgi的指令如下:

pip3 install uwsgi

建立软连接

因为我的python3.5是手动下载安装的。所以上一步安装uwsgi,安装成功后,只在我指定的安装目录下。而这个目录下,没有设置环境变量。所以我再shell上输入uwsgi,是不被识别的。为了解决这个问题,我需要建立一个软连接,然后将软连接文件放在环境变量中有的路径下。比如/usr/bin下。

ln -s /usr/python3.5/bin/uwsgi /usr/bin/uwsgi
*如果你安装完后,在shell上面,输入uwsgi已经能够被识别了,那么上面的指令你可以不用操作了。

安装一些依赖包

有些资料上面写着

yum install build-essential 
yum install python-dev

这两条指令,但是这两条指令,在centos上是不叫这个名字的。所以会发现安装不成功。

以下是正确的安装指令:

安装build-essential需要下面的指令:

yum groupinstall "Development Tools"

yum install -y gcc 

yum install g++


yum kernel-devel

因为centos上面没有python-dev的包,而是要安装Python-devel

yum install Python-devel

flask程序包的目录结构

未分类

app是一个包,里面只有一个view.py文件和init文件.初始化文件是空的。下面贴出view.py文件的内容

# encoding: utf-8
#!/usr/bin/ python3
'''
@author: rabbit

@contact: [email protected]

@time: 2017/11/27 14:09

@desc:

'''

from flask import  Flask

app=Flask(__name__)

@app.route("/")
def hello():
    return "Hello World"


#if __name__ == "__main__":
#    app.run(host='0.0.0.0', port=5000)

manager.py的作用只是作为一个脚本启动入口,通过它启动flaskweb网站。比如你输入

python3 manage.py runserver --host 0.0.0.0

manager.py 内容也贴出来给大家

# encoding: utf-8
#!/usr/bin/env python
'''
@author: rabbit

@contact: [email protected]

@time: 2017/11/27 14:15

@desc:

'''

from flask_script import Manager, Shell

from app.view import app

manager=Manager(app)

def make_shell_context():
    return dict(app=app)

manager.add_command("shell",Shell(make_context=make_shell_context))

@manager.command
def deploy():
    '''run deployment tasks'''
    pass


if __name__=='__main__':
    manager.run()

使用ini文件配置uwsgi,使网站运行起来

我们可以看到项目里面有个叫config.ini文件,这个是我后期加到项目里的。不是该网站程序所自带的。

我们可以vim 去编辑这个config.ini并保存它。指令如下:

先创建一个新的文件,名字叫config.ini

vim config.ini

未分类

也给出文件内容,方便大家复制粘贴

[uwsgi]


#uwsgi启动时,所使用的地址和端口(这个是http协议的)
http=0.0.0.0:5000

#uwsgi 启动时所使用的地址与端口(这个是socke协议)
socket=0.0.0.0:8001


#指向网站目录
chdir=/root/deployTest

#python 启动程序文件

wsgi-file=manage.py

#python 程序内用以启动的application 变量名

callable=app


#处理器数

processes=4

#线程数

threads=2

#状态检测地址

stats=127.0.0.1:9191

对上面的内容我稍微解释一下。因为本人当初看别人写的这个地方的 配置,说的太模糊了。一直运行不起来,也不晓得是uwsgi没有安装好,还是这个配置文件的问题。搞的很头疼。简要说明如下:

chdir=/root/deloyTest

这个一定要指定到你的项目的目录中

callable=app

这个最难理解。因为当时项目最开始建立了一个叫app的文件夹,同时也建立了一个app.py的文件。app.py文件里面又有一个变量叫app。所以看到有的资料这么写,我很懵逼。最后我才把项目的app.py改成了view.py.经过本人测试。这个app的意思是对应view.py文件里面的flask变量名app.如果view.py里面的变量名改成了application。那么这个配置文件里面也要把app换成application.

启动uwsgi 挂起网站

一起准备妥当,只差最后临门一脚了

指令如下:

未分类

正常运行,界面如上。

浏览器输入网址,应该就能得到我们网站的界面了。

截图如下:

未分类

最后祝你成功!

Linux服务器使用Crontab控制定时任务

Crontab,可以将任何脚本、程序或文档安排在某个最方便的时间运行。通俗的讲就是定时执行某个脚本、程序。

常见用途:

定时关机
定时检查更新
定时对系统配置、数据库、日志进行备份
定时清理垃圾文件

检测是否已安装

crontab
-bash: crontab: command not found

执行 crontab 命令如果报 command not found,就表明没有安装.

开始安装

#yum -y install vixie-cron crontabs -y

crontab 并不支持-h或–help之类的选项,不过还是可以通过它查看命令帮助

# crontab -h

也可以使用 info crontab 命令查看详细的帮助信息。

cron服务提供crontab命令来设定cron服务的,以下是这个命令的一些参数与说明:

crontab -u //设定某个用户的cron服务,一般root用户在执行这个命令的时候需要此参数
crontab -l //列出某个用户cron服务的详细内容
crontab -r //删除没个用户的cron服务
crontab -e //编辑某个用户的cron服务

比如说root查看自己的cron设置:

crontab -u root -l

再例如,root想删除fred的cron设置:

crontab -u fred -r

在编辑cron服务时,编辑的内容有一些格式和约定,输入:

crontab -u root -e

进入vi编辑模式,编辑的内容一定要符合下面的格式:/1 * * * ls >> /tmp/ls.txt


Crontab配置方法:

重点来了:

每个用户可以直接修改文件 /var/spool/cron/user来控制/etc/crontab文件,root的话就是

/var/spool/cron/root

直接编辑此文件即可自动化运行,示例:

echo  "0 1 * * * /sbin/reboot > /dev/null 2>&1"  >> /var/spool/cron/root

这个脚本每天1点自动重启服务器
添加之后使用

crontab -l

可以看到已经添加了Crontab任务。

方法二:

echo "0 1 * * * /sbin/reboot > /dev/null 2>&1" >1.sh
crontab 1.sh

意思也就是先把定时任务存为文件,在使用Crontab来调用,也可以添加成功。
添加之后使用

crontab -l

也可以看到已经添加了Crontab任务。
这样就能使得Crontab添加脚本自动化运行相关程序。
Crontab比init.d运行级别大一些,用Crontab来控制程序运行更加完美。


Crontab状态控制

/sbin/service crond start //启动服务
/sbin/service crond stop //关闭服务
/sbin/service crond restart //重启服务
/sbin/service crond reload //重新载入配置
/sbin/service crond status //查看状态

或者使用

# service crond start
# service crond stop
# service crond restart
# service crond reload
# service crond status

想记录下来的也就是—之间部分。。。
附上一个Crontab定时重启服务器脚本:

#!/bin/bash
#  .-------------------------------------------------------------
#  | Bash Name: 服务器定时重启脚本
#  | Version: 1.0
#  | This Is a Shell Script ,Easy To Use
#  | Web Site: https://4ker.cc/crontab.html
#  |--------------------------------------------------------------
#  | Author: 刺猬 <[email protected]>
#  | Copyright (c) 2017, https://4ker.cc All Rights Reserved.
#  .--------------------------------------------------------------
echo "欢迎使用服务器定时重启脚本"
echo '请输入重启服务器的时间:(示例:数字1-9,表示1-9点):'
read time
yum install vixie-cron crontabs -y
chkconfig crond on
systemctl enable crond.service
service crond start
/bin/systemctl start crond.service
echo  "0 $time * * * /sbin/reboot > /dev/null 2>&1"  >> /var/spool/cron/root
echo "添加成功,服务器在每天$time自动重启!"
sleep 1
echo "服务器将重启使脚本生效!"
reboot
echo ;

Flask 使用 flask_login 登录时报的一个错误

在用 flask_login 做登录的时候,在 base.html 里面写了这样的判断: {% if current_user.is_authenticated %} xxx {% else %} xxx {% endif %}

导致每当我访问登录页和注册页的时候报错,错误如下

未分类

这个错误通常有一个问题,就是没有对 flask_login 进行初始化

    login_manager = LoginManager()
    login_manager.init_app(app)

    @login_manager.user_loader
    def load_user(id):
        return User.query.get(int(id))

    login_manager.login_view = 'front.login'

但是这一步我是做了的,那那是哪里出的问题呢?

问题出自 macros 这个页面上

未分类

注意,这里的 macros 里面不应该去继承 “base.html” 的,因为 macros.html 是一个宏定义文件,它不应该从任何模板继承!

Flask+uWSGI 的 Logging 支持

本文基于 Flask 0.12.2 。

当 Flask App 被部署到生产环境时,我们会选择关闭 DEBUG 配置。在这种情况下,Flask 中使用 flask.current_app.logger.info() 打印的 LOG 仿佛消失了一样。它们去了哪里呢?

默认的 Handler

下面的源码位于 [flask.logging][loggin] 中。从源码可以看出,Flash 自动创建了 logger 并加入了一个 DEBUG 级别的 Handler 和一个 ERROR 级别的 Handler。根据 DEBUG 变量的值,DEBUG Handler 在生产环境下是不生效的。因此我们就只能看到来自于 ProductHandler 的 ERROR 级别 Log 信息。

def create_logger(app):
    """Creates a logger for the given application.  This logger works
    similar to a regular Python logger but changes the effective logging
    level based on the application's debug flag.  Furthermore this
    function also removes all attached handlers in case there was a
    logger with the log name before.
    """
    Logger = getLoggerClass()
    class DebugLogger(Logger):
        def getEffectiveLevel(self):
            if self.level == 0 and app.debug:
                return DEBUG
            return Logger.getEffectiveLevel(self)
    class DebugHandler(StreamHandler):
        def emit(self, record):
            if app.debug and _should_log_for(app, 'debug'):
                StreamHandler.emit(self, record)
    class ProductionHandler(StreamHandler):
        def emit(self, record):
            if not app.debug and _should_log_for(app, 'production'):
                StreamHandler.emit(self, record)
    debug_handler = DebugHandler()
    debug_handler.setLevel(DEBUG)
    debug_handler.setFormatter(Formatter(DEBUG_LOG_FORMAT))
    prod_handler = ProductionHandler(_proxy_stream)
    prod_handler.setLevel(ERROR)
    prod_handler.setFormatter(Formatter(PROD_LOG_FORMAT))
    logger = getLogger(app.logger_name)
    # just in case that was not a new logger, get rid of all the handlers
    # already attached to it.
    del logger.handlers[:]
    logger.__class__ = DebugLogger
    logger.addHandler(debug_handler)
    logger.addHandler(prod_handler)
    # Disable propagation by default
    logger.propagate = False
    return logger

要解决这个问题,我们需要创建自己的 Handler 。

创建自己的 Handler

在创建了 Flask app 之后,调用下面的 _set_logger 方法将 app 实例传入即可。详细的介绍见代码中的注释。

def _set_logger(flaskapp):
    """
    设置 Flask app 的logger
    """
    # 删除 Flask 的默认 Handler
    del flaskapp.logger.handlers[:]
    if flaskapp.config.get('DEBUG'):
        # 在 DEBUG 模式下,使用 StreamHandler,并使用 DEBUG 级别,这样可以将所有的信息都输出到控制台
        hdr = logging.StreamHandler()
        hdr.setLevel(logging.DEBUG)
        flask.logger.setLevel(DEBUG)
    else:
        # 不使用 StreamHandler 的原因,是 uwsgi 可能会在标准输出中加入它自己的 Log,为了避免Log被弄乱,单独使用一个 FileHandler
        hdr = logging.FileHandler(config.getdir('logs', 'app.log'), encoding='utf8')
        hdr.setLevel(logging.INFO)
        flask.logger.setLevel(INFO)
    # 加入足够详细的信息
    LOG_FORMAT = """
[%(asctime)s] %(levelname)s in %(module)s.%(funcName)s [%(pathname)s:%(lineno)d]:
%(message)s"""
    hdr.setFormatter(logging.Formatter(LOG_FORMAT))
    # 如果存在 sqlalchemy 的 Log 对象,也为其加入这个 Handler
    for log in (flaskapp.logger, logging.getLogger('sqlalchemy')):
        if log:
            log.addHandler(hdr)

我们还可以重写 Flask 对象的 log_exception 方法,自动将所有的异常记录下来,并提供一些更详细的信息:

class MYFlask(Flask):
    def log_exception(self, exc_info):
        """...description omitted..."""
        self.logger.error(
            """
Request:   {method} {path}
IP:        {ip}
Agent:     {agent_platform} | {agent_browser} {agent_browser_version}
Raw Agent: {agent}
            """.format(
                method=request.method,
                path=request.path,
                ip=request.remote_addr,
                agent_platform=request.user_agent.platform,
                agent_browser=request.user_agent.browser,
                agent_browser_version=request.user_agent.version,
                agent=request.user_agent.string,
            ), exc_info=exc_info
        )

uWSGI 的 Logging 配置

我使用 INI 格式的配置文件,文件名一般为 uwsgi.ini。其中关于 Logging 的配置,我常用这样几个:

; 将写入 log 的工作委托给 master 进程
log-master = true
; 单独开一个线程进行 log 写入工作,这样有更好的性能
threaded-log = true
; 所有 log 都会写入这个文件
; 若希望所有 log 放在一起,设置了此选项后,不要设置 req-logger 和 logger 选项
; %d 代表 uwsgi.ini 所在文件夹(包含结尾的/), %n 代表 uwsgi.ini 的主文件名
; 魔术变量: http://uwsgi-docs-zh.readthedocs.io/zh_CN/latest/Configuration.html#magicvars
daemonize = %dlogs/%n.log
; 将 uWSGI 请求 log 写入单独的 log 文件,这样做可以让log更加分离,便于查错
; 设置了此选项后,daemonize 设置的输出文件就得不到任何输出了
req-logger = file:%dlogs/req.log
; 将 uWSGI stdout/stderr log 写入单独的 log 文件
; 因为设定了 req-logger ,必须同时设定 logger ,此时 daemonize 不会有任何输出
logger = file:%dlogs/%n.log

自定义 uWSGI 的请求 log

uWSGI 会在 log 中自动写入请求 log,默认的格式如下:

[pid: 22740|app: 0|req: 162/324] 127.0.0.1 () {36 vars in 608 bytes} [Wed Nov 29 11:42:08 2017] GET /login/?code=001SkzFb1ppEDu0lTzHb1WPCFb1SkzF0 => generated 181 bytes in 69 msecs (HTTP/1.1 200) 5 headers in 209 bytes (2 switches on core 1)

关于其中信息如何解释,文档中并没有详细进行介绍,只能通过阅读 源码 logging.c 理解。

我在 uWSGI 的邮件列表中找到一封邮件 Default Log Format Explained? 介绍了 log 中每个项的详细作用。

pid -> the pid of the worker managing the request
app -> the id (it is a integer, starting from 0) of the app, it makes
sense when multiple apps are hosted in the same instance. It is -1 when no
app managed the request (like when serving static files) or when the ‘app’
concept does not apply (like with php or cgi’s)
req: N/M -> N is the number of managed requests by the current worker for
the specific app, M is the grand total (sum of all requests of all
workers)

then you have REMOTE_ADDR followd by the (optional) REMOTE_USER (very
similar to apache)

vars are the number of CGI vars in the request, and their size (from the
uwsgi protocol point of view). The size is never higher than the
–buffer-size (higher requests are discarded)

The time of the request follows

Then you have REQUEST_METHOD + REQUEST_URI

Then the response size and the time required for generating it

“via” is the techology used to send the response, currently can be
sendfile, routing or offloading.

The response status follows, as well as the number of response headers.

“core” is the low-level concept for uWSGI concurrency context in a process
(can be a thread or a greenlet or a fiber or a goroutine and so on…)
while switches count is incremented whenever an app “yield” its status
(this has various meanings based on the lower concurrency model used)

根据上面找到的资料和 格式化uWSGI请求日志 文档,通过设置 log-format 选项,我们可以模仿出默认的请求 log:

log-format = [pid: %(pid)] %(addr) (%(user)) {%(vars) vars in %(pktsize) bytes} [%(ctime)] %(method) %(uri) => generated %(rsize) bytes in %(msecs) msecs (%(proto) %(status)) %(headers) headers in %(hsize) bytes (%(switches) switches on core %(core))

除了 app: 和 req: 没有提供对应变量,其它的值都可以显示出来。

其它

日志编码器 也是一个重要的选项,若有需要可以添加该设置。

uWSGI 还可以使用 touch-logrotate 和 touch-logreopen 来实现 logging rotate,但为了让系统更加简单的独立,我建议使用 logrotate 来实现 logging rotate,并已经在 uWSGI+rsyslog 实现 rotating logging 一文中介绍过具体做法。

需要注意的是,我在 单独使用 logrotate 中提到的使用 copytruncate 选项替换 create 选项,是因为没有通知 uWSGI 重新打开 log 文件。要做到这一点非常简单,除了使用刚才提到的 touch-logreopen 之外,还可以使用 Master FIFO 中的 l 命令。

在阿里云上通过Ubuntu+uwsgi+nginx+mysql部署Flask(新手向)

0. 前言

这其实也是你所看到的这个网站的部署方式。

老规矩,上环境。截至2017年11月:

Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-62-generic x86_64)

uwsgi –version 2.0.15

nginx version: nginx/1.10.3 (Ubuntu)

supervisord -v 3.2.0

而我的个人电脑是

4.13.12-1-ARCH x86_64 GNU/Linux

1. 创建Ubuntu并创建新用户

在阿里云购买好ECS,我选择的最便宜的1核那个,一年300多,买完之后在控制台可以选择系统,选择好后输入root用户的密码。

未分类

随后会告诉你一个网页上可以连接到服务器的密码,你得记住。随后在网页上登录到服务器。键入

adduser edison

创建一个新用户,当然了用户名你自己取,这里是我的名字。

随后在我的电脑上用ssh连接服务器,这样方便操作些,此处怎么ssh连接服务器就不说了,百度谷歌一大把。

此处先用

ssh root@ip 

来连接。会提示加入密钥回车就好,然后输入你root也就是刚开始创建云盘时的那个密码。就连接成功了。

输入

nano /etc/sudoers

为新创建的用户加入sudo权限:

未分类

输入

apt-get update

更新索引,这里如果比较慢的话可以替换成中科大的源,如何替换百度谷歌就好也很简单。随后开始安装必要的软件。

2.安装python3和git

先安装python3

apt install python3-pip

随后使用pip3安装虚拟环境模块

pip3 install virtualenv

此时可以切换至edison这个新用户。

ssh edison@ip

我是把网站放在了github上,之后更新程序也方便管理。所以这里安装git

未分类

这里发现提示了

sudo: unable to resolve host:xxx

的信息,不影响使用的。可以通过修改hosts文件解决

未分类

未分类

输入y然后回车继续安装git。

在/home/edison下新建目录blog,在blog下git clone把源代码拷下来。

创建虚拟环境:

未分类

注意这里的虚拟环境使用的python3版本。

可以通过启动虚拟环境查看,确实是py3:

未分类

在blog下再新建一个logs文件夹。此时blog应该有三个文件夹,其他两个一个是venv文件夹一个是你的程序的文件夹。

未分类

3. 安装nginx

输入

sudo apt-get install nginx

安装nginx。输入

sudo /etc/init.d/nginx start

启动服务,shell会显示ok提示启动nginx服务成功。这时候打开浏览器,直接输入你的ECS服务器的公网ip就可以看到

未分类

就说明nginx启动成功。接下来用一个小的flask程序进行小小的演示。

创建一个test.py:

未分类

然后在虚拟环境下(根据前文,这时候已经在虚拟环境中了,可以从下图的shell里第一行的前面的venv字眼看出)

pip3 install flask

安装flask模块。

4. 安装uwsgi和supervisor并配置

接下来安装

pip install uwsgi

未分类

然后(此时所在文件夹是blog里)新建一个uwsgi的配置文件,并编辑以下内容,然后启用uwsgi:

未分类

未分类

未分类

可以看到uwsgi已经在运行了。这时候ctrl+c退出uwsgi。

接下来安装supervisor后进入配置文件夹,新建一个文件并输入:

sudo apt-get install supervisor
cd /etc/supervisor/conf.d
sudo nano blogSupervisor.conf

未分类

然后输入

sudo service supervisor start

开启进程管理。

5. 配置nginx

进入/etc/nginx/sites-available/,编辑里面default文件,其中内容是:

未分类

然后输入

sudo service nginx restart

重启nginx服务器。打开浏览器输入你的公网ip,就能看到

Hello World! 啦

未分类

这里部署就基本完了,只要把上述的配置文件里提到test这个py文件替换成你的文件,例如manage.py就好了。

6. Mysql和后续

接下来简单说说怎么使用mysql:

安装pymysql,在config.py里使用

“mysql+pymysql://root:xxx@localhost:3306/xxxxx”

的方式启用mysql。

p.s

在使用mysql上尝试了很多方法,但是由于ubuntu的系统python是2,而虚拟环境中python为3,所有总有一些包安装不上,特别是mysqldb的问题,只能使用mysql+pymysql的方法。但是很奇怪的现象是,我的archlinux默认的python是3.6,使用的mariadb,但是不用pymysql也能连接上mysql,而且:

$ python                                                           
Python 3.6.3 (default, Oct 24 2017, 14:48:20) 
[GCC 7.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import MySQLdb
>>> 

百思不得其解,如果有缘人看到了这里希望能给出答复。

在Nginx上部署Python Flask应用

1. Introduction

本文主要系翻译自digitalocean的教程How To Serve Flask Applications with uWSGI and Nginx on Ubuntu 16.04,部分进行了修改。

主要介绍了在nginx服务器上利用uWSGI部署Flask应用的步骤。

2. 准备工作

在开始之前,先确保有一个非root的用户部署在你的服务器上,这个用户必须使用sudo才能执行管理员命令。

FindHao注:非root用户是为了安全性考虑,但是由于购买的vps系统一般默认root用户,通过lnmp.org一键安装lnmp之后,会创建www用户和www用户组,因此下文的配置是以root用户配置为例,但是期间会插入修改目录权限的步骤。

3. 安装必要的软件

我们将尽量通过piporpip3来安装包,而非使用发行版源里的工具,避免源里工具版本过低造成的问题。

更新源,并安装python。

python2:

apt update
apt install python-pip python-dev

python3:

apt update
apt install python3-pip python3-dev

4. 创建python虚拟环境

4.1 安装virtualenv

为了将我们的Flask应用与系统里的其他python文件项目隔离开,接下来配置python虚拟环境。

使用pip安装virtualenv:

# python2
pip install virtualenv
# python3
pip3 install virtualenv

现在,为我们的Flask应用创建目录:

mkdir ~/myproject
cd ~/myproject

或者直接在lnmp的目录里,clone你的项目:

cd /home/wwwroot/
git clone XXXX/myproject.git
# 或者直接拷贝你的项目到对应的域名目录下

FindHao注:
由于我们是在root下操作的,因此应该将项目的文件所属权都移交给www

chown -R www.www ./*

由于lnmp会在域名目录下创建一个.user.ini的文件,导致你无法删除lnmp创建的目录,运行下面代码后删除即可:

chattr -i ``/home/wwwroot/yoursite/``.user.ini

如果是需要修改文件,记得修改完以后运行:

chattr +i ``/home/wwwroot/yoursite/``.user.ini

4.2 创建并激活虚拟环境

创建虚拟环境的目录:

virtualenv myenv

myenv的目录是用来存放本地python的镜像,以及后面通过pip安装的包将安装到myenv目录里,而不是系统的目录里。

在安装之前,需要先激活刚刚建立的虚拟环境:

source myenv/bin/activate

你的命令行前面会多个提示,表明你是在虚拟环境操作:

(myenv)user@host:~/myproject#.

5. 设置Flask应用

5.1 安装Flaks和uWSGI

注意,无论你使用的哪个python版本,进入虚拟环境以后,应该使用pip,而不是pip3

(myenv) # pip install uwsgi flask

5.2 建立sample

现在我们创建一个简单的样例程序来测试flask和uwsgi是否正常运行。

(myenv) # vim index.py

FindHao注:
修改文件权限:

chown www.www index.py

内容如下:

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "<h1 style='color:blue'>Hello There!</h1>"

if __name__ == "__main__":
    app.run(host='0.0.0.0')

如果你开启了防火墙,要在你防火墙规则里关闭对5000端口的禁用。

运行我们的flask sample:

(myenv) # python index.py

在浏览器里访问你的vps ip+:5000端口:

http://server_domain_or_IP:5000

如果看到如下内容表示运行正常,可以CTRL C终止sample的运行继续下一步了。

未分类

6. 配置uWSGI

6.1 测试uWSGI

首先我们需要测试uWSGI运行正常:

(myenv) # uwsgi --socket 0.0.0.0:5000 --protocol=http -w wsgi:app

你应该再次看到上面的hell there的提示。

CTRL C终止,并deactivate退出虚拟环境

6.2 创建uWSGI配置文件

为了让我们的项目启动和配置更灵活,我们创建uWSGI配置文件:

# vim myproject.ini

内容如下:

[uwsgi]
module = wsgi:app

master = true
processes = 5

socket = myproject.sock
chmod-socket = 660
vacuum = true

die-on-term = true

stats = 127.0.0.1:1234

logto = /var/log/myproject.log

[uwsgi]头表示我们这是个uwsgi的配置。module指向wsgi.py文件,文件里的回调是app。

接下来的部分表明项目运行在master mode,并且有五个worker进程处理请求。

在上面测试的时候,我们的uWSGI是通过网络端口暴露出来的,但是由于后面我们将使用nginx来处理实际的client连接,然后传递给uwsgi,而由于这些操作都是在一个机器上运行的,因此这里我们改成socket的模式更安全快速。我们指定socket文件是当前项目目录下的myproject.sock。

我们还必须改变socket的权限。待会我们会迁移uWSGI进程的所属组到nginx的组,因此我们必须确保socket的所属组用户能从它那里读写信息。同时在进程结束的时候,也需要清理socket(vacuum)。

die-on-term,可以确保init system和uWSGI有相同的环境。

logto是保存日志文件。

stats监控程序的url,只有设置了这个参数以后才能用 uwsgitop :1234来观看监控,类似于linux的top命令

你可能会注意到,我们没有像前面命令行一样指定一个协议,这是因为uWSGI默认使用uwsgi协议,一个快速的二进制协议,nginx内置了这个协议,用这个协议比http协议更快。

6.3 uwsgitop

前面的配置文件定义了stats参数,则可以安装pip3 install uwsgitop并通过uwsgitop :1234来像top一样查看前面项目的运行情况。

6.4 日志

前面我们指定了logto来保存日志,但是www用户对日志的目录没有写权限,因此需要手动建立这个log文件,并chown给www。

同时由于默认日志是一直在写入,文件会不停的增长,因此还需要使用系统的logrotate日志工具对其进行处理。

创建一个日志的配置文件

# vim /etc/logrotate.d/myproject-log-file

内容如下:

/var/log/myproject.log {
    monthly
    rotate 5
    compress
    delaycompress
    missingok
    notifempty
    create 644 root root
    postrotate
        /usr/bin/killall -HUP rsyslogd
    endscript
}
  • monthly: 日志文件将按月轮循。其它可用值为‘daily’,‘weekly’或者‘yearly’。
  • rotate 5: 一次将存储5个归档日志。对于第六个归档,时间最久的归档将被删除。
  • compress: 在轮循任务完成后,已轮循的归档将使用gzip进行压缩。
  • delaycompress: 总是与compress选项一起用,delaycompress选项指示logrotate不要将最近的归档压缩,压缩将在下一次轮循周期进行。这在你或任何软件仍然需要读取最新归档时很有用。
  • missingok: 在日志轮循期间,任何错误将被忽略,例如“文件无法找到”之类的错误。
  • notifempty: 如果日志文件为空,轮循不会进行。
  • create 644 root root: 以指定的权限创建全新的日志文件,同时logrotate也会重命名原始日志文件。
  • postrotate/endscript: 在所有其它指令完成后,postrotate和endscript里面指定的命令将被执行。在这种情况下,rsyslogd 进程将立即再次读取其配置并继续运行。

7. 创建systemd文件

Systemd 是 Linux 系统工具,用来启动守护进程,已成为大多数发行版的标准配置。

接下来我们将创建一个service文件来保证系统自动启动uWSGI和我们的Flask应用。

以Debian为例,创建一个myproject.service文件:

# vim /lib/systemd/system/myproject.service

内容如下:

[Unit]
Description=uWSGI instance to serve myproject
After=network.target

[Service]
User=www
Group=www
WorkingDirectory=/home/sammy/myproject
Environment="PATH=/home/sammy/myproject/myprojectenv/bin"
ExecStart=/home/sammy/myproject/myprojectenv/bin/uwsgi --ini myproject.ini

PIDFile=/run/findyoutube.net.pid
ExecReload=/home/wwwroot/www.findyoutube.net/ftbenv/bin/uwsgi --reload /run/findyoutube.net.pid
ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/findyoutube.net.pid
TimeoutStopSec=3
KillMode=mixed

[Install]
WantedBy=multi-user.target

7.1 Unit字段

[Unit]区块通常是配置文件的第一个区块,用来定义 Unit 的元数据,以及配置与其他 Unit 的关系。

  • Description:简短描述
  • After:如果该字段指定的 Unit 也要启动,那么必须在当前 Unit 之前启动

7.2 Service字段

[Service]区块用来 Service 的配置,只有 Service 类型的 Unit 才有这个区块。

  • ExecStart:启动当前服务的命令
  • ExecReload:重启当前服务时执行的命令
  • ExecStop:停止当前服务时执行的命令
  • Environment:指定环境变量
  • KillMode: mixed:主进程将收到 SIGTERM 信号,子进程收到 SIGKILL 信号

user和group用来指定进程所属用户和组。lnmp默认是www.www。设置working directory和path来告诉init system我们的项目在哪里,虚拟环境是怎样的。然后执行uwsgi开启Flask应用。

FindHao注:

在这个service文件里,我添加了PIDFile、ExecReload、ExecStop等几个字段,因为如果没有reload和stop字段,要重启我们的Flask应用,只能通过重启vps或者杀掉uwsgi进程的方式。

PIDFile指定了我们Flask应用的进程文件,可以传给后面stop和reload用。

reload使用uwsgi自带的reload。

stop则通过进程文件的防止找到指定进程并结束掉它。

7.3 Install 字段

[Install]通常是配置文件的最后一个区块,用来定义如何启动,以及是否开机启动。

  • WantedBy:它的值是一个或多个 Target,当前 Unit 激活时(enable)符号链接会放入/etc/systemd/system目录下面以 Target 名 + .wants后缀构成的子目录中
    7.4 运行

保存上面的配置文件后,常用的systemd命令有:

systemctl start myproject # 开启应用
systemctl enable myproject # 添加开机自启动
systemctl disable myproject # 取消开机启动
systemctl restart myproject # 重启应用
systemctl reload myproject # 重新加载配置
systemctl stop myproject # 停止应用
systemctl status myproject # 查看应用状态

开启应用后,查看应用状态,如果是active,表明成功启动运行,如果failed,可以通过journalctl查看运行日志,并G(vim的查看形式,即Shift + g到最后一行)看看启动的错误是什么。

8. 配置Nginx来处理请求

如果是lnmp安装的nginx,则nginx的配置目录为/usr/local/nginx/conf/vhost。

如果之前没有安装,则可以通过apt直接安装nginx,但是源里的nginx可能版本比较老,对于一些新特性不支持,建议添加nginx官方源里安装,参考 《nginx配置https》 3.6 nginx 支持HTTP2。单独安装的nginx配置文件目录一般为/etc/nginx/sites-available

在nginx的配置目录新建一个nginx的配置文件myproject,内容如下:

server {
    listen 80;
    server_name server_domain_or_IP;

    location / {
        include uwsgi_params;
        uwsgi_pass unix:/home/sammy/myproject/myproject.sock;
    }
}

listen表示监听80端口,server_name则是你的域名或者ip。

localtion /则表示对于域名或者ip/的请求处理方式。首先通过包含uwsgi_params来加载一些默认的uWSGI参数。然后uwsgi_pass转发请求给我们定义的socket。

这只是创建了可用的配置文件,如果要启用,还需要将其软连接到enable目录:

# ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled

检查我们的配置文件是否编写正确:

# nginx -t

如果提示ok,则重启nginx:

systemctl restart nginx

访问我们的域名或者ip,可以看成已经正常运行。