从行业趋势看,Serverless是云计算必经的一场革命,无服务器云函数是实现微服务的最好的方法之一,该如何使用Serverless框架?
我对于 serverless 的第一认知是:Serverless 是由一堆云服务构建后端服务的,如存储、计算、授权都是由不同的服务来构建的。而作为一个开发人员,我们所要做的就是了解如何搭配不同的云服务。
因此,在进行更多的定义之前,我打算先熟悉一下 serverless,以便于我更好地了解什么是 serverless 应用开发。
先按官网的 demo,进行实验。
npm install -g serverless
或者,和我一样使用:
yarn global add serverless
1.登录 AWS 账号,然后点击进入 IAM (即,Identity & Access Management)。
2.点击用户,然后添加用户,如 serveless-admin,并在『选择 AWS 访问类型』里,勾上编程访问。
编程访问 serverless
3.点击下一步权限,选择『直接附加现有策略』,输入AdministratorAccess,然后创建用户。
注意
:由于是 AdministratorAccess 权限,所以不要泄漏你的密钥出去。
然后导出证书,并使用 serverless depoy
保存到本地。
export AWS_ACCESS_KEY_ID=<your-key-here>export AWS_SECRET_ACCESS_KEY=<your-secret-key-here>serverless deploy
将会自动生成配置到 ~/.aws/credentials
或者,如官方的示例:
serverless config credentials --provider aws --key AKIAIOSFODNN7EXAMPLE --secret wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
serverless create --template aws-nodejs --path hello-world
Serverless: Generating boilerplate...Serverless: Generating boilerplate in "/Users/fdhuang/learing/serverless-guide/hello-world" _______ __| _ .-----.----.--.--.-----.----| .-----.-----.-----.| |___| -__| _| | | -__| _| | -__|__ --|__ --||____ |_____|__| \___/|_____|__| |__|_____|_____|_____|| | | The Serverless Application Framework| | serverless.com, v1.23.0 -------'Serverless: Successfully generated boilerplate for template: "aws-nodejs"(play-env)
生成两个文件;
├── handler.js└── serverless.yml
其中的 handler.js 的内容是:
'use strict';module.exports.hello = (event, context, callback) => { const response = { statusCode: 200, body: JSON.stringify({ message: 'Go Serverless v1.0! Your function executed successfully!', input: event, }), }; callback(null, response); // Use this code if you don't use the http event with the LAMBDA-PROXY integration // callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', event });};
而 serverless.yml
的内容,因为注释所有的内容,因此相当于是空的。
$serverless deploy -v
日志如下:
Serverless: Packaging service...Serverless: Excluding development dependencies...Serverless: Uploading CloudFormation file to S3...Serverless: Uploading artifacts...Serverless: Uploading service .zip file to S3 (409 B)...Serverless: Validating template...Serverless: Updating Stack...Serverless: Checking Stack update progress...CloudFormation - UPDATE_IN_PROGRESS - AWS::CloudFormation::Stack - hello-world-devCloudFormation - CREATE_IN_PROGRESS - AWS::Logs::LogGroup - HelloLogGroupCloudFormation - CREATE_IN_PROGRESS - AWS::IAM::Role - IamRoleLambdaExecutionCloudFormation - CREATE_IN_PROGRESS - AWS::Logs::LogGroup - HelloLogGroupCloudFormation - CREATE_IN_PROGRESS - AWS::IAM::Role - IamRoleLambdaExecutionCloudFormation - CREATE_COMPLETE - AWS::Logs::LogGroup - HelloLogGroupCloudFormation - CREATE_COMPLETE - AWS::IAM::Role - IamRoleLambdaExecutionCloudFormation - CREATE_IN_PROGRESS - AWS::Lambda::Function - HelloLambdaFunctionCloudFormation - CREATE_IN_PROGRESS - AWS::Lambda::Function - HelloLambdaFunctionCloudFormation - CREATE_COMPLETE - AWS::Lambda::Function - HelloLambdaFunctionCloudFormation - CREATE_IN_PROGRESS - AWS::Lambda::Version - HelloLambdaVersionPSzzisjnTvvYknuXwQOlAvdkQZ67qXYSvgoAi9T8W0CloudFormation - CREATE_IN_PROGRESS - AWS::Lambda::Version - HelloLambdaVersionPSzzisjnTvvYknuXwQOlAvdkQZ67qXYSvgoAi9T8W0CloudFormation - CREATE_COMPLETE - AWS::Lambda::Version - HelloLambdaVersionPSzzisjnTvvYknuXwQOlAvdkQZ67qXYSvgoAi9T8W0CloudFormation - UPDATE_COMPLETE_CLEANUP_IN_PROGRESS - AWS::CloudFormation::Stack - hello-world-devCloudFormation - UPDATE_COMPLETE - AWS::CloudFormation::Stack - hello-world-devServerless: Stack update finished...Service Informationservice: hello-worldstage: devregion: us-east-1stack: hello-world-devapi keys: Noneendpoints: Nonefunctions: hello: hello-world-dev-helloStack OutputsHelloLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-1:706605665335:function:hello-world-dev-hello:1ServerlessDeploymentBucketName: hello-world-dev-serverlessdeploymentbucket-bk066p5c9zgl
然后,让我们来触发一下这个函数:
$ serverless invoke -f hello -l
服务器返回了下面的结果:
{ "statusCode": 200, "body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{}}"}--------------------------------------------------------------------START RequestId: 041138f9-bc81-11e7-aa63-0dbab83f773d Version: $LATESTEND RequestId: 041138f9-bc81-11e7-aa63-0dbab83f773dREPORT RequestId: 041138f9-bc81-11e7-aa63-0dbab83f773d Duration: 2.49 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 20 MB
这意味着,我们的第一个服务已经成功上线了。
我们也可以通过下面的命令来获取相应的日志:
serverless logs -f hello -t
末了,记得执行:
$ serverless remove
它可以帮你省很多钱
我说一个比较新颖的,使用 Serverless 进行 AI 预测推理
在这里我们使用 TensorFlow 中的 MNIST 实验作为案例来进行下面的介绍。MNIST 是一个包含了 6 万训练图片和 1 万测试图片的手写数字图片集合,图片为 28x28-pixel 大小的黑白图片。此训练集通常用来训练对数字的识别能力。
关于如何编写代码,使用 MNIST 训练集完成模型训练,可以见 TF层指南:建立卷积神经网络,这篇文章详细介绍了如何通过使用 Tensorflow layer 构建卷积神经网络,并设置如何进行训练和评估。
而在进行训练和评估后,就可以进行模型的导出了。TensorFlow 的模型文件包含了深度学习模型的 Graph 和参数,也就是 checkpoint 文件。在导出模型文件后,我们可以加载模型文件继续训练或者对外提供推理服务。
这里我们可以通过 SavedModelBuilder 模块来进行模型到处保存,更具体的文档和操作方法可见 训练和导出 TF 模型。
导出后的文件,为 saved_model.pb 文件, variables 文件夹及包含的若干variables文件,分别是模型的图文件和参数文件。后续在提供推理能力时,就是使用这些图及变量文件,加载到 TF Serving 内。
为了便于后续的操作,我们在这里也直接提供我们导出的模型文件供后续操作,可以点击这里的导出模型文件来下载。
测试文件我们可以从 MNIST 的测试集中随意抽取若干,用于验证我们最终推理 API 的工作状态。同样,我们也在这里准备了若干图片用于最终验证,可以点击这里的测试图片文件来下载。或者记录以下连接,用于直接测试在线图片的推理情况。
https://main.qcloudimg.com/raw/84783c178cdc6d6b2302bc1b4749b91b.bmp
https://main.qcloudimg.com/raw/0f4630a815c44107a79a224d3263da2c.bmp
https://main.qcloudimg.com/raw/360a7cdd638d22d4145c94e67b3c059f.bmp
https://main.qcloudimg.com/raw/90067917b01d4b4d31f207ac78e70416.bmp
https://main.qcloudimg.com/raw/7828379e768aa5c8a6d09a3d58f64921.bmp
https://main.qcloudimg.com/raw/79e0c07766c739c880dfed8d0433ff83.bmp
https://main.qcloudimg.com/raw/b5f1c6f4ba08c3376c333c5a62f0c1dd.bmp
https://main.qcloudimg.com/raw/40adedc18205428276c3753bac51e740.bmp
https://main.qcloudimg.com/raw/4c750171b4b31772f0b923beef92c9f3.bmp
https://main.qcloudimg.com/raw/8504482825667f97b21209b9570249e6.bmp
https://main.qcloudimg.com/raw/5f22f6f83d26a3f267c823cd5437bdfc.bmp
https://main.qcloudimg.com/raw/fb1f59a3fdbcad0a140045508f28f688.bmp
我们使用如下示例来创建函数程序包。
创建目录 mnist_demo,并在根目录下创建文件 mnist.py,文件内容如下。
#!/usr/bin/env python2.7
import os
import sys
import base64
import urllib
import tensorflow as tf
import numpy as np
import utils
import json
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
# Load model
cur_dir = os.getcwd()
model_dir = cur_dir+"/export/4"
sess = tf.Session(graph=tf.Graph())
meta_graph_def = tf.saved_model.loader.load(sess, [tf.saved_model.tag_constants.SERVING], model_dir)
x = sess.graph.get_tensor_by_name('x:0')
y = sess.graph.get_tensor_by_name('y:0')
def run(event):
local_path = ""
if event['image_base64'] != "":
data=base64.b64decode(event['image_base64'])
local_path = '/tmp/test_image.xxx'
file=open(local_path,'wb')
file.write(data)
file.close()
elif event['image_url'] != "":
img_url = event['image_url']
filename = urllib.unquote(img_url).decode('utf8').split('/')[-1]
local_path = os.path.join('/tmp/', filename)
if not utils.download_image(event['image_url'], local_path):
return False, -1
else:
print('Please specify a image.')
try:
x_ = utils.get_image_array(local_path)
predict = tf.argmax(y, 1)
res = sess.run(predict, feed_dict={x: x_})[0]
return True, sess.run(predict, feed_dict={x: x_})[0]
except Exception as e:
print(e)
return False, -1
def demo(event, context):
res, num = run(event)
print num
return num
def apigw_interface(event, context):
if 'requestContext' not in event.keys():
return {"errMsg":"request not from api gw"}
body_str = event['body']
body_info = json.loads(body_str)
res, num = run(body_info)
return {"result":num}
从代码中我们可以看到,函数在初始化时就将目录 export 下的文件作为模型加载到了 TensorFlow 中。在实际的事件处理中,既可以从事件中抽取 base64 编码后的图片,也可以识别 url 参数,并均把图片保存至本地 /tmp 目录下。然后在使用 util 工具,对图片进行规整处理后,将处理后的数据送入 TensorFlow,获得推理结果并返回。
在根目录下同时创建 util.py,代码内容如下。此模块提供了图片下载、图片处理等辅助能力。
#!/usr/bin/env python2.7
import urllib2
import numpy as np
from PIL import Image
def download_image(img_url, local_file):
ret = True
try:
f = urllib2.urlopen(img_url)
data = f.read()
with open(local_file, "wb") as img:
img.write(data)
except Exception as e:
print(e)
ret = False
return ret
def get_image_array(img_path):
x_s = 28
y_s = 28
n0 = 0
n255 = 0
threshold = 100
im = None
im = Image.open(img_path)
img = np.array(im.resize((x_s, y_s), Image.ANTIALIAS).convert('L'))
for x in range(x_s):
for y in range(y_s):
if img[x][y] > threshold:
n255 = n255 + 1
else:
n0 = n0 + 1
if(n255 > n0) :
for x in range(x_s):
for y in range(y_s):
img[x][y] = 255 - img[x][y]
if(img[x][y] < threshold) :
img[x][y] = 0
arr = img.reshape((1, 784))
arr = arr.astype(np.float32)
arr = np.multiply(arr, 1.0 / 255.0)
return arr
同时由于 util.py 中使用了 PIL 库,需要在代码根目录下提供 PIL 库,可以使用此 PIL库文件 来下载库并解压后放置在代码根目录。
最终,我们得到的代码目录结构为如下结构,其中PIL文件夹下由于文件过多就不进行展开了。
mnist_demo
|
|-- mnist.py
|-- utils.py
| export
| 4
|-- saved_model.pb
| variables
|-- variables.data-00000-of-00001
|-- variables.index
| PIL
|-- ...
在 mnist_demo 这个目录下,我们选择所有文件然后打包为 zip 包。注意,这些文件需要在 zip 包的根目录下,而不是 mnist_demo 文件夹在zip包的根目录。
最终我们得到了一个可以上传到云函数的 zip 包。如果对于前面的操作都不想进行,也可以直接在这里下载已经打好的包即可。
由于创建的函数部署包稍大,所以我们需要通过对象存储来上传代码包。
我们可以在腾讯云对象存储 COS 中先创建一个 bucket,例如在广州区创建名为 code 的 bucket,并将上一步获取的代码包上传 bucket,作为我们后续创建函数的代码来源。
进入腾讯云无服务器云函数 SCF 的控制台,选择广州区以后,点击新建函数,为函数起一个比较容易记住的名字,例如 testai,选择运行环境为 Python 2.7,然后下一步到代码配置页面。
在代码配置页面,选择代码输入种类为 通过 COS 上传 zip 包
,选择刚刚创建的bucket为 cos,并填写对象文件为 /mnist_demo.zip
。
同时,函数执行方法需要确定为 mnist.apigw_interface,对应代码包中的 mnist 文件和 apigw_interface 函数。
点击函数界面右上角的测试按钮,并使用如下测试模版来测试函数。
{
"requestContext": {
"serviceName": "testsvc",
"path": "/test/{path}",
"httpMethod": "POST",
"requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
"identity": {
"secretId": "abdcdxxxxxxxsdfs"
},
"sourceIp": "10.0.2.14",
"stage": "prod"
},
"headers": {
"Accept-Language": "en-US,en,cn",
"Accept": "text/html,application/xml,application/json",
"Host": "service-3ei3tii4-251000691.ap-guangzhou.apigateway.myqloud.com",
"User-Agent": "User Agent String"
},
"body": "{\"image_base64\": \"\", \"image_url\": \"https://main.qcloudimg.com/raw/84783c178cdc6d6b2302bc1b4749b91b.bmp\"}",
"pathParameters": {
"path": "value"
},
"queryStringParameters": {
"foo": "bar"
},
"headerParameters":{
"Refer": "10.0.2.14"
},
"stageVariables": {
"stage": "test"
},
"path": "/test/value?foo=bar",
"httpMethod": "POST"
}
这里可以看到,我们主要使用了body字段,并在body字段内填写的数据结构为:
{
"image_base64": "",
"image_url": "https://main.qcloudimg.com/raw/84783c178cdc6d6b2302bc1b4749b91b.bmp"
}
这个数据结构也是我们创建的函数所能接受和处理的结构,如果有 base64 编码的图片文件内容,则使用编码的内容,或者使用url传入的图片地址,将图片下载到本地后交由 TensorFlow 进行预测推理。在这里测试时,我们可以用上我们在一开始准备的图片地址,而在实际推理时,可以替换成其他可访问的图片地址。
点击测试运行后,如果使用的是上面的地址,使用的是第一副图片,那么函数的返回内容将是 {"result": 0}
,标识其推理出来的图片内是数值 0。
接下来我们通过 API 网关服务,来创建一个 API 对刚刚创建的推理函数进行封装,并对外提供 API 服务。
首先在 API 网关的控制台,在广州区创建一个 API 服务。服务名可以起一个容易记住的名字,例如 testai。
接着进入API 管理,创建 API。同样可以起一个容易记住的名字,例如 ai。请求路径可以写为 /ai,请求方法为 POST。为了方便调试,我们这里可以勾选上免鉴权。
接着下一步,后端类型选择为 cloud function,并选择我们在前面创建的函数 testai。
最后一步填入响应内容为 JSON,描述正确示例为 {"result":0}
,错误示例为 {"errMsg":"error info"}
即可。
点击 API 查看界面的 API 调试,进入调试页面。确定 Content-Type 为 application/json,输入框内填入以下内容后点击发送请求。
{
"image_base64": "",
"image_url": "https://main.qcloudimg.com/raw/84783c178cdc6d6b2302bc1b4749b91b.bmp"
}
确认响应的 body 为 {"result": 0},符合预期。同理,这里的 url 地址同样可以更改,并且响应内容随图片的不同而不同。
在 API 调试成功后,我们可以在服务列表页面,将我们刚刚创建的 testai 服务发布到 发布环境
。
然后根据 testai 的服务域名,我们可以得到刚才的 API 的完整路径为:http://service-kzeyrb6x-1253970226.ap-beijing.apigateway.myqcloud.com/release/ai
。我们可以使用 http request 的发起工具,例如 Postman,或 restclient 等,向此 API 地址发起 POST请求,POST内容可以为如下内容。
{
"image_base64": "",
"image_url": "https://main.qcloudimg.com/raw/84783c178cdc6d6b2302bc1b4749b91b.bmp"
}
或
{
"image_base64": "Qk1mCQAAAAAAADYAAAAoAAAAHAAAABwAAAABABgAAAAAADAJAAAAAAAAAAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////",
"image_url": ""
}
这两个不同的数据结构,分别测试了使用 base64 编码的图片内容,或者使用图片 url 地址传递图片内容的方式。同时可以根据自身需求,修改数据结构内的 image_base64 或 image_url 内容,查看测试结果。
在 serverless落地场景中,对对象文件的处理很常见。对象文件处理指的是对对象文件进行操作后的回调处理。回调通常是在对象文件创建或删除操作后产生的事件。云函数可以在获取到这个事件后进行后续的处理。这里常见的处理逻辑是下面几种,比如说图片处理,针对图片去生成各种尺寸的缩略图或者进行裁剪,然后再次存储到对象存储数据中,之后可以根据不同客户端的请求展示不同大小的图片到前端。
文件批量打包,用户需要进行文件筛选和打包的时候可以通过使用云函数来处理。在上传文件后,如果需要选择哪些文件来打包,把文件生成压缩包以供下载,这都可以由事件处理来进行。
日志归档分析,以及业务系统回调,也是云函数所承载的业务逻辑。比如说日志归档分析这种用法,用户会把每天的前端应用服务器的日志上传到对象存储中归档,归档后会触发云函数执行,云函数会拉下这些日志文件进行实时分析,它会抽取这些日志中的错误数,或者是其他业务相关或者用户关注的内容,然后再把它抽取到的信息或者统计到的信息写回数据库,供用户后续进行排查、使用。用户自身API调用也是,例如用户生成的一些视频文件上传到对象存储,会触发云函数,将上传文件的信息通知到用户的转码系统,通过视频转码转成不同分辨率然后再进行存储。当然转码是用户自身实现的业务系统,这块通过回调通知,通知它自身的业务系统。这些就是云函数在Serverless架构和对象存储连用的落地场景。
相似问题