与其说Serverless架构是一个新的概念/架构,倒不如说他是一个全新的思路,一种新的编程范式,在这种新的架构下,或者说新的编程范式下,使用全新的思路来做Serverless应用是再好不过的了,但是实际上并不是这样的,原生的Serverless开发框架是非常少的,以Web框架为例,目前的主流的Web框架“均不支持Serverless模式部署”,一方面是我们要尝试接触Serverless,一方面又没办法完全放弃传统框架,所以将传统框架如果更简单、更快速、更科学的部署到Serverless架构上就是一个值得探讨的问题。
请求集成方案
请求集成方案实际上就是把真是的API网关请求,直接透传给FaaS平台,而不在中途增加任何转换逻辑,以阿里云函数计算的HTTP函数为例,当我们想要把传统框架(例如Django,Flask,Express,Next.js等)部署到阿里云函数计算平台上,并且体验Serverless带来的按量付费,弹性伸缩等红利时,得益于阿里云函数计算的HTTP函数和HTTP触发器,使用者不仅可以快速、简单的将框架部署到阿里云函数计算,更可以保持和传统开发一样的体验。以Python的Bottle框架为例,当我们开发一个Bottle项目之后:
# index.py
import bottle
@bottle.route('/hello/<name>')
def index(name):
return "Hello world"
if __name__ == '__main__':
bottle.run(host='localhost', port=8080, debug=True)
我们可以直接在本地进行调试。当我们想要把该项目部署到阿里云函数计算上,我们只需要增加一个default_app的对象即可:
app = bottle.default_app()
整个项目:
# index.py
import bottle
@bottle.route('/hello/<name>')
def index(name):
return "Hello world"
app = bottle.default_app()
if __name__ == '__main__':
bottle.run(host='localhost', port=8080, debug=True)
此时,我们在阿里云函数计算平台,创建函数时,将函数入口设置为:index.app即可。除了Bottle之外,其他的Web框架的操作方法是类似的,再以Flask为例:
# index.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
if __name__ == '__main__':
app.run(
host="0.0.0.0",
port=int("8001")
)
在配置函数的时候写上入口函数为:index.app即可,就可以保证该Flask项目运行在函数计算平台上。
当然,除了使用已有的语言化的Runtime,我们还可以考虑使用Custom Runtime和Custom Container来实现,例如,一个Web项目完成之后,我们可以编写一个Bootstrap文件(在Bootstrap文件中写一些启动命令即可),例如我要启动一个Express的项目,我把我的Express项目准备完成之后,可以直接通过Bootstrap为:
#!/usr/bin/env bash
export PORT=9000
npm run star
除了上面的方法,其实阿里云函数计算还提供了更简单的Web框架迁移的方案,可以直接将大部分的传统Web框架迁移到函数计算中,在函数计算控制台,寻找到应用中心,可以看到Web应用框架部分:
选择好对应的环境之后,只需要上传代码,做好简单的配置,即可让自己的传统Web项目运行在阿里云函数计算平台上。
如果通过开发者工具来进行部署,以Serverless Devs为例,通过Serverless Devs的一行指令部署一个框架项目:
s init bottle && cd bottle && s deploy
部署结果:
根据返回的网址,可以看到:
综上所述,通过阿里云函数计算来进行传统Web框架的部署和迁移是相对方便的,并且得益于HTTP函数与HTTP触发器,整个过程也是侵入性非常低的,当然将传统Web框架部署到阿里云上时,我们的可选方案也是比较多的:
- 编程语言化的Runtime:只需要写好函数入口即可
- Custom Runtime:只需要写好Bootstrap即可
- Custom Container:直接按照规范上传镜像文件即可
部署途径也是多种多样的:
- 直接在控制台创建函数部署;
- 应用中心处创建Web应用;
- 开发者工具直接部署;
其他方案
相对于阿里云的HTTP函数以及HTTP触发器而言,AWS,华为云,腾讯云等FaaS平台则需要借助API网关以及一个转换层来实现传统框架部署到FaaS平台的过程。
以Python Web框架为例,在通常情况下,我们使用Flask等框架实际上要通过web_server,进入到下一个环节,而云函数更多是一个函数,本不需要启动web server,所以我们就可以直接调用wsgi_app这个方法:
其中这里的environ就是我们需要对event/context等进行处理后的对象,也就是我们所说的转换层要做的工作;start_response可以认为是我们的一种特殊的数据结构,例如我们的response结构形态等。以Flask项目为例,这个转换层实际上是:
import sys
import json
from urllib.parse import urlencode
from flask import Flask
try:
from cStringIO import StringIO
except ImportError:
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
from werkzeug.wrappers import BaseRequest
def make_environ(event):
environ = {}
for hdr_name, hdr_value in event['headers'].items():
hdr_name = hdr_name.replace('-', '_').upper()
if hdr_name in ['CONTENT_TYPE', 'CONTENT_LENGTH']:
environ[hdr_name] = hdr_value
continue
http_hdr_name = 'HTTP_%s' % hdr_name
environ[http_hdr_name] = hdr_value
apigateway_qs = event['queryStringParameters']
request_qs = event['queryString']
qs = apigateway_qs.copy()
qs.update(request_qs)
body = ''
if 'body' in event:
body = event['body']
environ['REQUEST_METHOD'] = event['httpMethod']
environ['PATH_INFO'] = event['path']
environ['QUERY_STRING'] = urlencode(qs) if qs else ''
environ['REMOTE_ADDR'] = 80
environ['HOST'] = event['headers']['host']
environ['SCRIPT_NAME'] = ''
environ['SERVER_PORT'] = 80
environ['SERVER_PROTOCOL'] = 'HTTP/1.1'
environ['CONTENT_LENGTH'] = str(len(body))
environ['wsgi.url_scheme'] = ''
environ['wsgi.input'] = StringIO(body)
environ['wsgi.version'] = (1, 0)
environ['wsgi.errors'] = sys.stderr
environ['wsgi.multithread'] = False
environ['wsgi.run_once'] = True
environ['wsgi.multiprocess'] = False
BaseRequest(environ)
return environ
class LambdaResponse(object):
def __init__(self):
self.status = None
self.response_headers = None
def start_response(self, status, response_headers, exc_info=None):
self.status = int(status[:3])
self.response_headers = dict(response_headers)
class FlaskLambda(Flask):
def __call__(self, event, context):
if 'httpMethod' not in event:
return super(FlaskLambda, self).__call__(event, context)
response = LambdaResponse()
body = next(self.wsgi_app(
make_environ(event),
response.start_response
))
return {
'statusCode': response.status,
'headers': response.response_headers,
'body': body
}
当然这层转换对于某些情况下还是比较麻烦的,所以在很多时候,是可以借助常见的开发者工具来进行传统框架的部署操作的,例如借助开源的开发者工具Serverless Devs,Serverless Framework等。