【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到现有策略上并创建用户。创建完成后,请记下密钥和秘钥。

スクリーンショット 2019-02-23 11.59.32.png

在serverless配置中注册键和密钥。

serverless config credentials --provider aws --key メモしたkey --secret メモしたsecret

我们来部署一下。 .)

serverless deploy

一旦部署完成后,应该可以在AWS控制台的Lambda中确认函数已经上传成功。

スクリーンショット 2019-02-23 12.14.56.png

让我们打开 serverless-sample-dev-second。
由于在serverless.yml中,second的事件触发器是http,所以该函数将对http请求进行执行。

当您通过浏览器访问API Gateway的API终端点中所写的URL时,将实际执行函数。

スクリーンショット 2019-02-23 12.19.55.png

在函数内调用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时,应该可以确认数据已经填充进去了。

スクリーンショット 2019-02-23 16.18.52.png

尝试将函数安排在每个小时执行一次的计划中。

只需要在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作为触发器。

スクリーンショット 2019-02-23 16.31.35.png

让我们删除已部署的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的界面。

スクリーンショット 2019-02-26 0.53.35.png

只需通过指定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)
})

我认为你可以确认数据已经被正确保存。

スクリーンショット 2019-02-26 1.05.41.png

让我们在本地运行的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

广告
将在 10 秒后关闭
bannerAds