Python Environment: add gevent based WSGI server framework (#750)

* Python Environment: add gevent based WSGI server framework

This supply gevent based WSGI server framework to Python Environment, to
allow customer to run their functions under multi-threads.

* classify the flask based application

This set up the flask based application as individual class, to make
sure the consistent and integrity.

* Add instruction of WSGI server selection applied in Python Environment

* Python Environment starting logger: provide framework name
This commit is contained in:
xiekeyang
2018-06-26 15:50:06 +08:00
committed by Ta-Ching Chen
parent c863616b38
commit 71d8b769d5
3 changed files with 109 additions and 80 deletions
+20
View File
@@ -43,3 +43,23 @@ Or, if you already have an environment, you can update its image:
After this, fission functions that have the env parameter set to the After this, fission functions that have the env parameter set to the
same environment name as this command will use this environment. same environment name as this command will use this environment.
## Web Server Framework
Python environment build and start a WSGI server, to support high HTTP
traffic. As it is applied in different use cases, this provides two server
frameworks: `bjoern` and `gevent`. They all support high concurrency request.
`bjoern` has good performance on RPS, to be ideal for most light resource
utilization cases.
`gevent` is a good supplement because of its internal multi-threads. It
supports heavy resource load functions, with well distribution of response
time.
Python environment pod remains `bjoern` framework by default. And it runs `gevent`
framework by setting the container environment `WSGI_FRAMEWORK` value to `GEVENT`.
The environment value is configured normally in two ways. One way is to set in Dockerfile
and build it into image. The other way is to set in Kubernetes deployment spec during
pod running and restart it.
+1
View File
@@ -5,3 +5,4 @@ python-dateutil
requests==2.7.0 requests==2.7.0
redis redis
hiredis hiredis
gevent
+41 -33
View File
@@ -5,24 +5,40 @@ import sys
import imp import imp
import os import os
import bjoern import bjoern
from gevent.pywsgi import WSGIServer
from flask import Flask, request, abort, g from flask import Flask, request, abort, g
app = Flask(__name__) class FuncApp(Flask):
def __init__(self, name, loglevel = logging.DEBUG):
super(FuncApp, self).__init__(name)
userfunc = None # init the class members
self.userfunc = None
self.root = logging.getLogger()
self.ch = logging.StreamHandler(sys.stdout)
@app.route('/specialize', methods=['POST']) #
def load(): # Logging setup. TODO: Loglevel hard-coded for now. We could allow
global userfunc # functions/routes to override this somehow; or we could create
# separate dev vs. prod environments.
#
self.root.setLevel(loglevel)
self.ch.setLevel(loglevel)
self.ch.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
self.logger.addHandler(self.ch)
#
# Register the routers
#
@self.route('/specialize', methods=['POST'])
def load():
# load user function from codepath # load user function from codepath
codepath = '/userfunc/user' codepath = '/userfunc/user'
userfunc = (imp.load_source('user', codepath)).main self.userfunc = (imp.load_source('user', codepath)).main
return "" return ""
@app.route('/v2/specialize', methods=['POST']) @self.route('/v2/specialize', methods=['POST'])
def loadv2(): def loadv2():
global userfunc
body = request.get_json() body = request.get_json()
filepath = body['filepath'] filepath = body['filepath']
handler = body['functionName'] handler = body['functionName']
@@ -58,17 +74,17 @@ def loadv2():
mod = imp.load_source(moduleName, filepath) mod = imp.load_source(moduleName, filepath)
# load user function from module # load user function from module
userfunc = getattr(mod, funcName) self.userfunc = getattr(mod, funcName)
return "" return ""
@app.route('/healthz', methods=['GET']) @self.route('/healthz', methods=['GET'])
def healthz(): def healthz():
return "", 200 return "", 200
@app.route('/', methods=['GET', 'POST', 'PUT', 'HEAD', 'OPTIONS', 'DELETE']) @self.route('/', methods=['GET', 'POST', 'PUT', 'HEAD', 'OPTIONS', 'DELETE'])
def f(): def f():
if userfunc == None: if self.userfunc == None:
print("Generic container: no requests supported") print("Generic container: no requests supported")
abort(500) abort(500)
# #
@@ -78,26 +94,18 @@ def f():
# g.myKey = myValue # g.myKey = myValue
# And the user func can then access that (after doing a "from flask import g"). # And the user func can then access that (after doing a "from flask import g").
# #
return userfunc() return self.userfunc()
# app = FuncApp(__name__, logging.DEBUG)
# Logging setup. TODO: Loglevel hard-coded for now. We could allow
# functions/routes to override this somehow; or we could create
# separate dev vs. prod environments.
#
def setup_logger(loglevel):
global app
root = logging.getLogger()
root.setLevel(loglevel)
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(loglevel)
ch.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
app.logger.addHandler(ch)
# #
# TODO: this starts the built-in server, which isn't the most # TODO: this starts the built-in server, which isn't the most
# efficient. We should use something better. # efficient. We should use something better.
# #
setup_logger(logging.DEBUG) if os.environ.get("WSGI_FRAMEWORK") == "GEVENT":
app.logger.info("Starting server") app.logger.info("Starting gevent based server")
bjoern.run(app, '0.0.0.0', 8888, reuse_port=True) svc = WSGIServer(('0.0.0.0', 8888), app)
svc.serve_forever()
else:
app.logger.info("Starting bjoern based server")
bjoern.run(app, '0.0.0.0', 8888, reuse_port=True)