首页  »  AWS 教程

使用 Amazon Neptune 构建游戏好友推荐引擎

将应用程序部署至 AWS Lambda 和 Amazon API Gateway

在本模块中,您将学习如何将应用程序代码部署至 AWS Lambda(一种无服务器计算服务)。使用 AWS Lambda 时,您无需关注服务器管理或容量规划。只需上传要运行的代码,当配置的事件触发器被触发时,AWS 就会运行这些代码。

Amazon API Gateway 就是可用作 Lambda 事件触发器的服务之一。在 Amazon API Gateway 中可以配置 HTTP 端点,将请求转发至 AWS Lambda 函数处理。通过结合使用 Lambda 和 API Gateway,您无需配置或管理任何服务器,就能构建一个功能完备的 Web 应用程序。此外,Lambda 和 API Gateway 均按使用量计费,您只需为实际使用的资源付费。

在下面的步骤中,我们将使用 AWS 命令行界面 (AWS CLI) 来设置 Lambda 函数,并通过 API Gateway 配置 HTTP 路由。如果您正在使用 Lambda 和 API Gateway 构建应用程序,建议使用 AWS 无服务器应用程序模型 (AWS SAM) 或 Serverless Framework 等工具,以基础设施即代码 (IaC) 的方式管理您的无服务器应用程序。

 完成时间

15 分钟


  • 在前一个模块中,我们创建了一个安全组,并配置 Neptune 实例,使其能被 AWS Lambda 函数访问。访问 Neptune 实例必须在 VPC 内部进行,而不能通过公共互联网。

    在本模块,我们将部署在 AWS Lambda 上运行的应用程序代码。为了让 AWS Lambda 函数能访问 Neptune 实例,该函数必须位于 VPC 内的某个子网中,并配置一个能访问 Neptune 实例的安全组。

    我们的 Lambda 函数还需要使用 AWS SDK 来访问 Amazon Cognito 用户池。所有与 Amazon Cognito 的通信都是经由公共互联网进行的。为了让位于私有子网内的 Lambda 函数能访问公网,您必须配置一个 NAT Gateway(NAT 网关),将私有网络流量转换为公网流量。

    所需的网络资源的具体细节超出了本教程的范畴。现在,您可以使用 scripts/ 目录下的 create-networking.sh 脚本来预配所需的资源。

    您可以运行以下命令来执行该脚本:

    bash scripts/create-networking.sh

    您应当会看到以下输出结果:

    Fetching VPC Id
    Fetching subnet Id
    Creating Elastic IP address
    Creating NAT Gateway
    Waiting for NAT Gateway to be ready...
    Creating private subnet
    Creating route table
    Creating route
    Associating route table with subnet
    Networking resources created!

    我们已经创建了私有子网和相关的网络资源。下一步,我们将把应用程序代码部署至 Lambda 函数。

  • 首先,您需要将函数代码打包,并部署至 AWS Lambda。Lambda 要求您上传一个包含所有应用程序代码的 ZIP 文件。此外,您还需要指定使用的 Runtime(运行时),以及作为代码入口点的文件和函数。该入口点称为 Handler(处理程序),每当传入事件触发您的代码时,该处理程序就会被调用。

    除了函数代码,您还需要为 Lambda 函数提供一个 AWS Identity and Access Management (IAM) 角色。Lambda 函数在执行时会担任该角色,从而获得访问 AWS 资源的权限,例如对数据库进行读写、向队列发送消息或将输出记录到 Amazon CloudWatch

     scripts/ 目录中,有一个名为 create-lambda.sh 的文件。该脚本会执行下列四项操作:

    1. 将 application/ 目录的内容压缩成一个 zip 文件,供 Lambda 函数使用。
    2. 创建一个 IAM 角色,供 Lambda 函数担任。
    3. 为该 IAM 角色添加 IAM policy,允许 Lambda 函数访问 Amazon Aurora Serverless 数据库的数据 API。
    4. 通过上传 zip 文件来创建 Lambda 函数。

    如果您想查看执行这些步骤所用的 AWS CLI 命令,可以在文件资源管理器中打开 scripts/create-lambda.sh 文件。

    要执行此脚本,请在终端中运行以下命令:

    bash scripts/create-lambda.sh

    您应当会看到以下输出结果:

    Building zip file
    Creating IAM role
    Adding policy to IAM role
    Sleeping for IAM role propagation
    Creating Lambda function
    Lambda function created with ARN <functionArn>

    您已经成功创建了 Lambda 函数。

  • Lambda 函数部署完成后,接下来可以使用 Amazon API Gateway 通过 HTTP 访问该函数。API Gateway 为后端服务提供了一个功能强大的访问层。它高度可配置,提供了身份验证、请求验证、速率限制等功能。

    在 scripts/ 目录中,有一个名为 create-rest-api.sh 的文件,用于配置 API Gateway REST API 并将其与您的 Lambda 函数关联。该脚本处理了很多任务,包括:

    1. 在 API Gateway 中创建一个 REST API。
    2. 在该 REST API 中配置代理资源和方法,将所有传入的请求路径和方法都发送至单个 Lambda 函数。
    3. 添加允许 REST API 触发 Lambda 函数的权限。

    API Gateway 中有很多配置选项,本教程无法涵盖所有细节。要了解更多信息,请参阅 Amazon API Gateway 的概念

    在终端中运行以下命令来执行该脚本并配置 REST API:

    bash scripts/create-rest-api.sh

    您应当会在终端看到以下输出结果:

    Creating REST API
    Fetching root resource
    Creating proxy resource
    Creating method
    Adding integration
    Creating deployment
    Fetching account ID
    Adding lambda permission
    REST API created
    
    Your API is available at: https://<id>.execute-api.<region>.amazonaws.com/prod

    现在您已经配置了 REST API 连接至 Lambda 函数。输出内容的包含了访问该 REST API 的基础 URL。

    基础 URL 的值已经添加至 env.sh 文件中。接下来,对该文件执行 source 命令,在终端中设置 BASE_URL 环境变量。

    source env.sh
  • 现在您的 API 已经启动并运行了,让我们来测试一下。在测试端点之前,我们先简要地过一下代码。

    该 Web 应用程序是使用 Express.js 构建的,Express.js 是一个流行的 Node.js Web 应用程序框架。在 AWS Lambda 上构建应用程序时,您不必使用 Express.js 这样现成的 Web 框架,但如果您有 Express 的使用经验,会让学习过程更容易些。

    Express 应用程序的核心代码位于 application/app.js 文件中。在文件资源管理器中打开该文件。

    // application/app.js
    const express = require('express')
    const bodyParser = require('body-parser')
    const { createUser, fetchUser, fetchUserRecommendations, followUser } = require('./data')
    const { createCognitoUser, login, verifyToken } = require('./auth')
    const { validateCreateUser, validateFollowUser } = require('./validate')
    
    const app = express()
    app.use(bodyParser.json())
    
    function wrapAsync(fn) {
      return function(req, res, next) {
        fn(req, res, next).catch(next);
      };
    }
    // Login
    app.post('/login', wrapAsync(async (req, res) => {
      const idToken = await login(req.body.username, req.body.password)
      res.json({ idToken })
    }))
    
    // Create user
    app.post('/users', wrapAsync(async (req, res) => {
      const validated = validateCreateUser(req.body)
      if (!validated.valid) {
        throw new Error(validated.message)
      }
      await createCognitoUser(req.body.username, req.body.password, req.body.email)
      const user = await createUser(req.body.username, req.body.interests)
      res.json(user)
    }))
    
    // Fetch user
    app.get('/users/:username', wrapAsync(async (req, res) => {
      const user = await fetchUser(req.params.username)
      res.json(user)
    }))
    
    // Follow user
    app.post('/users/:username/friends', wrapAsync(async (req, res) => {
      const validated = validateFollowUser(req.body)
      if (!validated.valid) {
        throw new Error(validated.message)
      }
      const token = await verifyToken(req.header('Authorization'))
      if (token['cognito:username'] != req.params.username) {
        throw new Error('Unauthorized')
      }
      const friendship = await followUser({ follower: req.params.username, friend: req.body.username })
      res.json(friendship)
    }))
    
    // Fetch user recommendations
    app.get('/users/:username/recommendations', wrapAsync(async (req, res) => {
      const recommendations = await fetchUserRecommendations(req.params.username)
      res.json(recommendations)
    }))
    
    app.use(function(error, req, res, next) {
      res.status(400).json({ message: error.message });
    });
    
    module.exports = app

    在文件的开头部分,导入了 Express 和其他依赖项,包括我们在前面模块中提到的身份验证辅助函数和数据访问函数。然后配置了函数所需的各种路由,例如用于用户登录并获取身份令牌的 /login,或用于获取特定用户详细信息的 /users/:username。最后,在文件末尾,脚本将导出 Express 应用程序。

    接下来看一下 application/handler.js。该个文件包含了入口点方法,告诉 Lambda 在接收到请求时调用哪个函数。该文件内容如下所示:

    // application/handler.js
    const awsServerlessExpress = require('aws-serverless-express')
    const app = require('./app')
    const server = awsServerlessExpress.createServer(app)
    
    exports.handler = (event, context) => { awsServerlessExpress.proxy(server, event, context) }

    该处理程序使用了 aws-serverless-express 包。该包将传入的 API Gateway 请求转换为 Express.js 框架预期的标准请求格式。这样就可以很容易地在 Lambda 和 API Gateway 上运行 Express.js 代码了。

    让我们通过获取示例数据中某个用户的兴趣来测试一下端点。该请求会经由 API Gateway 到达您的 Lambda 函数,该函数向 Neptune 发起请求并返回结果。

    在终端中运行以下命令:

    curl -X GET ${BASE_URL}/users/amy81

    注意,由于 VPC 资源仍在预配中,第一个请求可能需要几秒钟,甚至可能返回错误。后续请求会快很多。 

    您应当会在终端看到以下输出结果:

    {"username":"amy81","friends":["gilbertamber"],"interests":["Cooking","Sports","Woodworking","Nature"]}

    成功了!您访问到了您的 API 端点,从而触发了您的 Lambda 函数,该函数从 Neptune 中检索所请求的用户的好友和兴趣。


总结

在本模块中,您部署了 Lambda 函数,并使用 API Gateway 配置了 REST API。然后您查看了应用程序的运行情况,并调用端点对其进行了测试。

在下一个模块,您将体验应用程序的所有功能。您将创建一个新用户并使用凭证登录,然后查看您的推荐,关注新的好友。