作者 | 刘宇
前言:与其说 Serverless 架构是一个新的概念 / 架构,不如说它是一个全新的思路、一种新的编程范式。
在 Serverless 这种新的架构或者说是新的编程范式下,使用全新的思路来做 Serverless 应用是再好不过的了,但实际并非如此。原生的 Serverless 开发框架是非常少的。以 Web 框架为例,目前主流的 Web 框架均不支持 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 为例,可以先创建 index.py:
# -*- coding: utf-8 -*- from bottle import route, run @route('/') def hello(): return "Hello World!" run(host='0.0.0.0', debug=False, port=9000)
然后编写资源和行为描述文件:
edition: 1.0.0 name: functionApp access: defaule services: bottleExample: #服务名称 component: devsapp/bottle #组件名称 actions: pre-deploy: #在deploy之前运行 - run: pip3 install -t . -r requirements.txt #要运行的命令行 path: ./src #命令行运行的路径 props: #组件的属性值 region: cn-shenzhen service: name: serverless-devs-bottle description: Serverless Devs示例程序 function: name: bottle description: bottle项目 memorySize: 256 code: src: ./src customContainerConfig: command: '["python3"]' args: '["./bottle/index.py"]' #服务名称 component: devsapp/bottle #组件名称 actions: pre-deploy: #在deploy之前运行 - run: pip3 install -t . -r requirements.txt #要运行的命令行 path: ./src #命令行运行的路径 props: #组件的属性值 region: cn-shenzhen service: name: serverless-devs-bottle description: Serverless Devs示例程序 function: name: bottle description: bottle项目 memorySize: 256 code: src: ./src customContainerConfig: command: '["python3"]' args: '["./bottle/index.py"]'
完成之后,执行 deploy 指令:
s deploy
部署如图所示:
在 Serverless Devs 上部署 bottle 框架
根据返回的网址,可以看到如下图所示的结果:
Serverless Devs 部署结果预览
综上所述,通过阿里云函数计算进行传统 Web 框架的部署和迁移是相对方便的,并且得益于 HTTP 函数与 HTTP 触发器,整个过程侵入性非常低。当然,将传统 Web 框架部署到阿里云函数计算时,可选方案也是比较多的。
- 编程语言化的 Runtime:只需要写好函数入口即可。
- Custom Runtime:只需要写好 Bootstrap 即可。
- Custom Container:直接按照规范上传镜像文件即可。
部署途径也是多种多样的,具体如下。
- 直接在控制台创建函数。
- 在应用中心处创建 Web 应用。
- 使用开发者工具直接部署。
其它方案
相对于阿里云的 HTTP 函数以及 HTTP 触发器而言,AWS、华为云、腾讯云等 FaaS 平台需要借助 API 网关以及转换层来实现将传统 Web 框架部署到 FaaS 平台。
如图所示,通常情况下使用 Flask 等框架实际上要通过 Web Server 进入下一个环节,而云函数更多是一个函数,本不需要启动 Web Server,所以可以直接调用 wsgi_app 方法。
传统WSGI Web Server工作原理示例
这里的 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 }
当然,转换在某些情况下还是比较麻烦的,所以在很多时候,我们可以借助常见的开发者工具进行传统 Web 框架的部署,例如借助开源的开发者工具 Serverless Devs、Serverless Framework 等。
关于作者:
刘宇(江昱)国防科技大学电子信息专业在读博士,阿里云 Serverless 产品经理,阿里云 Serverless 云布道师,CIO 学院特聘讲师。
新书推荐
本书会通过多个开源项目、多个云厂商的多款云产品,以及多种途径向读者介绍什么是 Serverless 架构、如何上手 Serverless 架构、不同领域中 Serverless 架构的应用以及如何从零开发一个 Serverless 应用等。本书可以帮助读者将 Serverless 架构融入到自己所在的领域,把 Serverless 项目真实落地,获得 Serverless 架构带来的技术红利。
Serverless 工程系列文
第一篇:Serverless 工程实践 | 从云计算到 Serverless
第二篇:Serverless 工程实践 | 细数 Serverless 的配套服务