【Serverless Framework】使用Node.js开始无服务器教程
关于这篇文章
首先对于无服务器,可能存在以下疑问或担忧:“虽然想体验一下无服务器,但使用云服务好像麻烦。”、“虽然有兴趣,但似乎很难。”、“如果能轻松实现就好了,但实际情况如何呢?”针对这些疑问,本篇教程旨在帮助对无服务器感兴趣的人们探索并入门,为成为无服务器新手提供一个启蒙的途径。
首先,什么是无服务器(Serverless)?
Serverless指的不是没有服务器,而是指不需要管理服务器。
基本上,只需要在没有任何服务器设置的情况下,注册一段代码,以便在发生某个事件时自动执行函数。
这个服务被称为FaaS(函数即服务)。
AWS的Lambda、Azure的Azure Functions和GCP的Google Cloud Functions是著名的。
在开始教程之前,
在本教程中,我们将使用Nodejs进行实现。此外,我们将使用AWS(亚马逊网络服务)的无服务器代表lambda。因此,如果无法使用Nodejs或者没有AWS账户,将无法进行下一步。
教程
在这个教程中,我们将创建一个无服务器应用程序,每小时获取自己(或kousaku-maron)在Qiita上的文章信息,并将其保存到数据库中。
无服务器计算的引入
在创建无服务器应用程序时,需要部署要执行的代码,并在AWS控制台中设置执行时机、日志等配置。
有一个框架可以使您能够在不需要进行繁琐的GUI操作并附带AWS设置的情况下部署代码并执行。让我们安装一个方便实用的框架Serverless。
npm install -g serverless
完成Serverless的安装后,让我们来创建本次所需的无服务器应用程序吧。
由于Serverless为使用的云服务以及语言提供了相应的模板,我们可以利用这些模板来创建。
这次我们要在nodejs中使用lambda来创建应用程序,所以我们将使用aws-nodejs-ecma-script模板进行创建。如果不需要使用ES6/ES7进行编写,也可以使用aws-nodejs模板。
serverless create --template aws-nodejs-ecma-script --name serverless-sample --path serverless-sample
由于尚未安装所需的软件包,因此将进行安装。
cd serverless-sample
npm install
关于无服务器应用程序的配置
Serverless应用程序由两个文件组成:一个包含要执行的代码的文件和一个包含云端配置的文件。
请阅读各云供应商的FaaS服务文档,以获取关于「包含函数的文件」的编写方式。因为这是各云供应商在FaaS服务中的说明,所以我省略了解释。
“包含云端配置的文件”被写在 serverless.yml 中。
最重要的是提供者和功能的项目。
供应商可以设置云服务、运行时(语言)等应用程序的整体配置。功能可以设置在应用程序内执行的代码、执行该代码的时机、代码使用的环境变量等函数(代码)的单独配置。
service:
name: serverless-sample
# Add the serverless-webpack plugin
plugins:
- serverless-webpack
provider:
name: aws
runtime: nodejs6.10
functions:
first:
handler: first.hello
second:
handler: second.hello
events:
- http:
method: get
path: second
尝试部署无服务器应用
当将应用程序部署到AWS上时,需要权限。
您可以在AWS控制台的IAM部分创建一个专门用于无服务器的用户。
在添加用户并检查程序访问后,请直接附加AdministratorAccess到现有策略上并创建用户。创建完成后,请记下密钥和秘钥。
在serverless配置中注册键和密钥。
serverless config credentials --provider aws --key メモしたkey --secret メモしたsecret
我们来部署一下。 .)
serverless deploy
一旦部署完成后,应该可以在AWS控制台的Lambda中确认函数已经上传成功。
让我们打开 serverless-sample-dev-second。
由于在serverless.yml中,second的事件触发器是http,所以该函数将对http请求进行执行。
当您通过浏览器访问API Gateway的API终端点中所写的URL时,将实际执行函数。
在函数内调用Qiita API并尝试访问
为了在函数内发送HTTP请求,需要安装axios。
npm install axios --save
由于我们不再使用默认注册的first和second函数,所以让我们将它们删除。文件也可以删除,没有问题。我们可以注册并创建一个新的qiita,并准备好qiita.js作为替代。
因为想要使用async/await,所以将运行时更改为Node.js 8.10。同时,将Qiita API的终点作为环境变量设置为endpoint。
如果您有 Qiita 账户并且已经发布过文章,那么请将您的账户名称更改为 kousaku-maron。
service:
name: serverless-sample
# Add the serverless-webpack plugin
plugins:
- serverless-webpack
provider:
name: aws
runtime: nodejs8.10
functions:
qiita:
handler: qiita.hello
environment:
endpoint: https://qiita.com/api/v2/users/kousaku-maron/items
我将写一个可以在qiita.js中执行的函数。
通过axios将内部发送HTTP请求到已在环境变量中注册的端点,并将返回的数据通过回调函数返回。
import axios from 'axios'
export const hello = async (event, context, callback) => {
const res = await axios({
method: 'get',
url: process.env.endpoint,
params: {
page: 1,
per_page: 100,
}
})
const result = []
if(res.data) {
Promise.all(res.data.map(async element => {
const record = {
id: element.id,
title: element.title,
url: element.url,
likes_count: element.likes_count,
created_at: element.created_at,
updated_at: element.updated_at,
tags: element.tags,
}
result.push(record)
}))
}
callback(null, {
message: 'qiita article data success.',
data: result,
event,
})
}
在无服务器计算中,有一个功能可以在本地运行函数。
让我们在本地运行它并确认结果是否正确。
顺便提一下,如果省略“local”,就可以执行部署的 Lambda 函数。
serverless invoke local qiita
如果控制台显示出这样的结果,那就算成功了。
{
"message": "qiita article data success.",
"data": [
{
"id": "3887e57c5c5519abb46d",
"title": "【Unity】音ゲーの仕組みを学び「〇〇の達人」をUnityで作る パート3",
"url": "https://qiita.com/kousaku-maron/items/3887e57c5c5519abb46d",
"likes_count": 1,
"created_at": "2019-02-16T15:45:47+09:00",
"updated_at": "2019-02-16T15:46:29+09:00",
"tags": [
{
"name": "C#",
"versions": []
},
{
"name": "Unity",
"versions": []
},
{
"name": "ゲーム制作",
"versions": []
}
]
}
...
]
...
}
尝试将获取的数据存储到DynamoDB中。
因为使用aws-sdk,所以需要进行安装。
npm install aws-sdk --save
为了将通过Qiita API获取的数据保存到DynamoDB中,首先需要在serverless.yml中添加DynamoDB的配置。
尽管看起来很复杂,但我会详细解释,所以请放心…
service:
name: serverless-sample
# Add the serverless-webpack plugin
plugins:
- serverless-webpack
provider:
name: aws
runtime: nodejs8.10
timeout: 60
environment:
DYNAMODB_TABLE: qiitaTable
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
# Resource: arn:aws:dynamodb:${opt:stage, self:provider.stage}:*:table/${self:provider.environment.DYNAMODB_TABLE}
Resource: arn:aws:dynamodb:us-east-1:*:table/qiitaTable
functions:
qiita:
handler: qiita.hello
environment:
endpoint: https://qiita.com/api/v2/users/kousaku-maron/items
tableName: ${self:provider.environment.DYNAMODB_TABLE}
resources:
Resources:
qiitaTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:provider.environment.DYNAMODB_TABLE}
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
我正在为提供者添加Lambda超时设置、保存表名以及为了能够保存到DynamoDB而需要的权限设置。
由于数据库保存处理需要花费时间,因此我们会视情况适当设置超时时间。
权限可以在iamRoleStatements中进行设置,以允许对特定资源执行特定操作。
通过使用※ ${self:provider.stage} 来引用在yml中的值,但是在iamRoleStatements的Resource中使用时会出错,因此直接输入了值。
provider:
name: aws
runtime: nodejs8.10
timeout: 60
environment:
DYNAMODB_TABLE: qiitaTable
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
# Resource: arn:aws:dynamodb:${opt:stage, self:provider.stage}:*:table/${self:provider.environment.DYNAMODB_TABLE}
Resource: arn:aws:dynamodb:us-east-1:*:table/qiitaTable
在functions中,设置tableName的环境变量。该值将在qiita.js中使用。
functions:
qiita:
handler: qiita.hello
environment:
endpoint: https://qiita.com/api/v2/users/kousaku-maron/items
tableName: ${self:provider.environment.DYNAMODB_TABLE}
添加一个名为 “资源” 的项目,并在部署时设置创建 DynamoDB 表。
在这里,您可以设置不仅创建DynamoDB,还创建其他资源如S3。
DynamoDB的表格必须设置主键,可以使用AttributeDefinitions和KeySchema进行设置。
resources:
Resources:
qiitaTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:provider.environment.DYNAMODB_TABLE}
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
我将使用 qiita.js 编写将数据保存到 DynamoDB 的处理函数。
我将借助 aws-sdk 创建一个名为 putItem 的函数来保存数据到 DynamoDB,并使用 Promise 来处理。
我们将保存从 Qiita API 获取到的数据。
import axios from 'axios'
import AWS from 'aws-sdk'
const documentClient = new AWS.DynamoDB.DocumentClient()
export const hello = async (event, context, callback) => {
const res = await axios({
method: 'get',
url: process.env.endpoint,
params: {
page: 1,
per_page: 100,
}
})
if(res.data) {
Promise.all(res.data.map(async element => {
const params = {
TableName: process.env.tableName,
Item:{
'id': element.id,
'title': element.title,
'url': element.url,
'likes_count': element.likes_count,
'created_at': element.created_at,
'updated_at': element.updated_at,
'tags': element.tags,
}
}
await putItem(params)
}))
}
callback(null, {
message: 'write qiita article data to dynamoDB success.',
event,
})
}
const putItem = (params) => {
return new Promise((resolve, reject) => {
documentClient.put(params, (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
}
如果能够完成到这一步,请先部署并尝试运行一下。
可以通过命令行使用“serverless invoke –function qiita”来执行Lambda函数。
serverless deploy
serverless invoke --function qiita
当您在AWS控制台上查看DynamoDB时,应该可以确认数据已经填充进去了。
尝试将函数安排在每个小时执行一次的计划中。
只需要在serverless.yml文件中稍作修改,就可以让创建的函数每隔1小时自动执行。
创建事件并添加日程。只需2行,非常简单。
请参考Rate或Cron的调度表达式来进行详细的计划安排。
service:
name: serverless-sample
# Add the serverless-webpack plugin
plugins:
- serverless-webpack
provider:
name: aws
runtime: nodejs8.10
timeout: 60
environment:
DYNAMODB_TABLE: qiitaTable
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
# Resource: arn:aws:dynamodb:${opt:stage, self:provider.stage}:*:table/${self:provider.environment.DYNAMODB_TABLE}
Resource: arn:aws:dynamodb:us-east-1:*:table/qiitaTable
functions:
qiita:
handler: qiita.hello
events:
- schedule: cron(0 * * * ? *)
environment:
endpoint: https://qiita.com/api/v2/users/kousaku-maron/items
tableName: ${self:provider.environment.DYNAMODB_TABLE}
resources:
Resources:
qiitaTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:provider.environment.DYNAMODB_TABLE}
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
让我们来部署一下吧。
serverless deploy
在AWS控制台上查看部署的Lambda函数,应该会看到已设置Cloudwatch Events作为触发器。
让我们删除已部署的Lambda函数。
只需执行下面的命令。
serverless remove
关于插件(选项)
这部分是可选的,如果不看也没有问题。
在教程中没有提到,但Serverless有一个可轻松扩展功能的插件系统。
在本节中,我们将介绍一些使用插件进行扩展的示例。
尝试在本地执行与DynamoDB连接的函数。
在本地安装一个用于在无服务器环境中操作DynamoDB的插件。
由于我的环境中最新的插件不能正常工作,所以我特别指定了版本。这个问题已经在GitHub的issue中提出过了。
npm install --save-dev serverless-dynamodb-local@0.2.35
将已安装的插件配置到serverless.yml文件中。
service:
name: serverless-sample
# Add the serverless-webpack plugin
plugins:
- serverless-webpack
- serverless-dynamodb-local
...
使用插件在本地安装运行的dynamoDB。
serverless dynamodb install
在安装完DynamoDB之后,我们需要在serverless.yml中对本地的DynamoDB使用的表进行配置。
我正在设置添加自定义内容,并将端口设置为8000来启动。
...
custom:
dynamodb:
start:
port: 8000
inMemory: true
migrate: true
resources:
Resources:
qiitaTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:provider.environment.DYNAMODB_TABLE}
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
让我们启动DynamoDB试试看。
serverless dynamodb start
当访问http://localhost:8000/shell/时,您应该能够看到操作DynamoDB的界面。
只需通过指定region和endpoint,就可以将使用于qiita函数的dynamoDB切换为本地的dynamoDB。
...
const documentClient = new AWS.DynamoDB.DocumentClient({
region: 'localhost',
endpoint: 'http://localhost:8000'
})
...
让我们在本地环境中执行qiita函数。
serverless invoke local --function qiita
请在可以操作dynamoDB的界面的左侧粘贴并执行以下代码,以确认数据是否保存在本地的dynamoDB中。
const params = {
TableName: 'qiitaTable',
}
dynamodb.scan(params, (err, data) => {
if (err) ppJson(err)
else ppJson(data)
})
我认为你可以确认数据已经被正确保存。
让我们在本地运行的dynamoDB中使用一个命令来终止正在尝试连接到8000端口的进程。
lsof -i :8000 -t | xargs kill
试着整理旧版本的代码并减少其容量。
明天、添加文本
最后
我在这篇文章中创建了一个函数,通过Qiita API获取的数据保存到DynamoDB中。
通过使用API和操作AWS资源,应该学到了如何使用Twitter API创建机器人,创建备份数据库的批处理,并且能够在无服务器环境下完成各种任务,对吗?
我已在github上公开了代码样本。
https://github.com/kousaku-maron/qiita-sample/tree/master/serverless-sample