用AWS Lambda和Semaphore构建和部署微服务

95 阅读5分钟

AWS Lambda亚马逊网络服务家族的一项服务,它根据各种事件来运行你的代码。当你创建一个Lambda函数并将你的代码部署到其中时,AWS Lambda会负责配置和管理运行你代码的服务器。

本教程将告诉你如何从头开始开发一个Node.js Lambda函数并将其部署到AWS Lambda。为了实现函数的持续交付工作流程,我们将通过Semaphore与Lambda的集成来实现部署过程的自动化。

一旦函数完成,它将向Semaphore上破坏你的项目构建的任何人发送短信。

我们正在开发的功能将使用。

  • Grunt(Javascript的任务运行器,我们将用它来本地调用我们的函数)
  • NPM(Node.js的主要包管理器)。
  • Twilio API(我们将使用Twilio的API来发送文本消息)
  • 用于Node.js的AWS SDK(用于AWS服务的简单且易于使用的API)

先决条件

要开发这个功能,你将需要。

初始化一个Lambda函数

Lambda函数基本上是当一个函数被调用时运行的代码。通常情况下,它是由你定义的事件调用的。

让我们创建一个新的Lambda函数。要做到这一点,我们需要进入Lambda控制台,点击立即开始

首先,我们将为我们的函数选择Hello WorldNode.js蓝图。

在你选择了蓝图后,你应该得到以下屏幕。

现在我们需要给我们的函数命名,给它一个描述,并选择运行时语言,如下图所示。

我们将保持Lambda函数代码部分的原样,一旦准备好,我们将用我们自己的代码替换该代码。

Lambda函数处理程序和角色下,我们将保留默认的index.handler 处理程序,但我们将需要创建一个新的执行角色。你可以在AWS Lambda文档中了解更多关于执行角色的信息,使用执行角色授予权限

我们将在代码中使用S3 API,所以我们将需要一个S3执行角色的函数。稍后会有更多关于这个问题的内容。

在下拉菜单中选择S3执行角色来创建一个角色。

这将带你到一个对话框,在那里你可以创建一个新的IAM角色。我们将把它命名为semaphore-deploy-role 。这个角色有一组权限,既可以让我们在代码中使用AWS SDK,又可以让Semaphore为我们进行部署。在你创建了一个新的IAM角色后,你可以点击允许

现在你已经为你的功能创建了一个IAM角色,你可以继续选择它。

接下来,我们将把超时时间增加到6秒,并把高级设置下的所需内存保持在128MB。

做完这一切后,我们的设置应该是这样的。

点击 "下一步",查看函数的细节,然后点击 "创建函数"。

编写代码

存储电话号码

现在我们已经创建了一个Lambda函数,是时候写一些代码了。我们将创建一个函数,从AWS S3检索JSON文件。我们将把在Semaphore上为我们的项目工作的用户的电子邮件和电话号码添加到这个文件中。这个文件还将包含Twilio凭证(账户SID、认证令牌和Twilio号码)。这些数据存储在S3上,所以没有人可以在我们的代码中看到它。

检索Twilio凭证

你可以从他们的网站上找回你的Twilio凭证,只要你之前创建了一个(免费的)Twilio账户。你还需要在你账户的国际设置中启用向你所在国家发送短信的功能,并验证你的号码和你需要发送短信的其他所有号码。你可以在Twilio验证的号码页面上进行验证。

接下来,打开一个你选择的编辑器,制作一个名为numbers.json 的文件。将你的电子邮件、电话号码和Twilio凭证添加到这个文件中。下面是这个文件应该是什么样子的一个例子。

{
  "twilio": {
    "twilio_account_sid": "12345678",
    "twilio_auth_token": "12345678",
    "twilio_number": "+420123456"
  },
  "hard_working_user@gmail.com": "+3333333",
  "lazy_user@gmail.com": "+2222222",
  "user_who_breaks_the_build@renderedtext.com": "+1111111"
}

转到AWS S3控制台,将numbers.json 上传到你选择的桶中。

定义依赖关系

接下来,我们将在package.json 中定义我们在Node.js应用程序中使用的依赖项。这个文件应该类似于下面的内容。

{
  "name": "congrats-you-broke-the-build",
  "version": "0.0.1",
  "main": "index.js",
  "devDependencies": {
    "aws-sdk": "2.0.23"
  },
  "dependencies": {
    "twilio": "2.1.0"
  }
}

安装Grunt CLI

我们还需要安装Grunt,以便测试我们的功能。Grunt是一个JavaScript的任务运行器,用于轻松实现项目任务的自动化,如构建和打包。用下面的代码安装它。

$ sudo npm install -g grunt-cli

如果你在安装Grunt时遇到困难,你可以在NPM Grunt页面上找到更多信息。

定义Lambda函数的逻辑

现在,我们已经将numbers.json 上传到S3,并安装了Grunt CLI,我们可以实现一些代码。当我们调用我们的Lambda函数时,exports.handler 将处理调用它的请求。事件参数是发送至我们Lambda函数的请求中包含的JSON文件。

我们需要创建一个名为index.js 的文件来保存我们的Lambda函数逻辑。首先,让我们使用以下代码段从S3获取numbers.json

var AWS = require("aws-sdk")

exports.handler = function (event, context) {
  console.log("JSON API from Semaphore: %j", event)

  AWS.config.apiVersions = {
    s3: "2006-03-01",
  }

  // My bucket with numbers.json is located in 'us-west-2' region
  var s3 = new AWS.S3({ region: "us-west-2" })
  // This is where you define bucket and a file for S3 to get
  var params = { Bucket: "congrats-you-broke-the-build", Key: "numbers.json" }

  s3.getObject(params, function (err, data) {
    if (err) console.log(err, err.stack) // an error has happened on AWS

    // Parse JSON file and put it in numbers variable
    var numbers = JSON.parse(data.Body)
  })
}

现在我们已经解析了我们的JSON文件,现在是时候对这些数字做些什么了。我们将在代码中添加一个名为manipulateNumbers 的新函数。这个函数将从我们的numbers.json 文件中接收数字。

var AWS = require("aws-sdk")

exports.handler = function (event, context) {
  console.log("JSON API from Semaphore: %j", event)

  AWS.config.apiVersions = {
    s3: "2006-03-01",
  }

  // My bucket with numbers.json is located in 'us-west-2' region
  var s3 = new AWS.S3({ region: "us-west-2" })
  // This is where you define the location of the bucket and the file S3 needs to retrieve
  var params = { Bucket: "congrats-you-broke-the-build", Key: "numbers.json" }

  s3.getObject(params, function (err, data) {
    if (err) console.log(err, err.stack) // an error has happened on AWS

    // Parse JSON file and put it in numbers variable
    var numbers = JSON.parse(data.Body)

    manipulateNumbers(numbers)
  })

  function manipulateNumbers(numbers) {
    // If someone breaks the master build on Semaphore, get inside the if statement
    if (event.branch_name == "master" && event.result == "failed") {
      // We get the name of a user who broke the build
      var blame = event.commit.author_name

      // message that is sent to the developer who broke the master branch
      var message =
        "Congrats " +
        blame +
        ", you managed to break the master branch on SemaphoreCI!."
    }
  }
}

这个函数检查branch_name 是否是master ,以及构建是否失败。它包括将发送至破坏构建的用户的信息。

我们现在有了要发送给用户的消息,但我们仍然需要编写发送该消息的代码。

var AWS = require("aws-sdk")
var twilio = require("twilio")

exports.handler = function (event, context) {
  console.log("JSON API from Semaphore: %j", event)

  AWS.config.apiVersions = {
    s3: "2006-03-01",
  }

  // My bucket with numbers.json is located in the 'us-west-2' region
  var s3 = new AWS.S3({ region: "us-west-2" })
  // This is where you define the location of the bucket and the file S3 needs to retrieve
  var params = { Bucket: "congrats-you-broke-the-build", Key: "numbers.json" }

  s3.getObject(params, function (err, data) {
    if (err) console.log(err, err.stack) // an error has happened on AWS

    // Parse JSON file and put it in numbers variable
    var numbers = JSON.parse(data.Body)

    manipulateNumbers(numbers)
  })

  function manipulateNumbers(numbers) {
    // If someone breaks the master build on Semaphore, enter the if statement
    if (event.branch_name == "master" && event.result == "failed") {
      // We get the name of the user who broke a build
      var blame = event.commit.author_name

      // The message that is sent to the developer who broke the master branch
      var message =
        "Congrats " +
        blame +
        ", you managed to brake master branch on SemaphoreCI!."

      twilioHandler(numbers, message)
    }
  }

  function twilioHandler(numbers, message) {
    var blame_mail = event.commit.author_email
    // twilio credentials
    var twilio_account_sid = numbers.twilio.twilio_account_sid
    var twilio_auth_token = numbers.twilio.twilio_auth_token
    var twilio_number = numbers.twilio.twilio_number

    var client = twilio(twilio_account_sid, twilio_auth_token)

    // Send SMS
    client.sendSms(
      {
        to: numbers[blame_mail],
        from: twilio_number,
        body: message,
      },
      function (err, responseData) {
        // this function is executed when a response is received from Twilio
        if (!err) {
          console.log(responseData)
          context.done(null, "Message sent to " + numbers[blame_mail] + "!")
        } else {
          console.log(err)
          context.done(null, "There was an error, message not sent!")
        }
      }
    )
  }
}

我们申请了一个Twilio包,我们将用它来发送消息。我们还添加了twilioHandler 函数。这个函数创建了一个Twilio客户端,并根据author_email ,从event ,从numbers 这个变量中提取电话号码。在它完成了对发送短信所需数据的检索后,它调用client.sendSms() ,将短信发送到破坏构建的用户那里。

现在我们已经准备好测试这个函数了。

在本地测试该功能

让我们创建一个event.json 文件,这样我们就可以在本地测试我们的函数。我们将尝试模仿Semaphore API将发送给我们的JSON。在该文件中,我们将把branch_name 设为master ,把构建的result 设为failed 。这就是触发向破坏主分支的用户发送短信的情景。

{
  "branch_name": "master",
  "result": "failed",
  "commit": {
    "id": "ce03782d581ed985caf9c479173a14962b0fe941",
    "url": "https://github.com/lazy_user//commit/ce03782d581ed985caf9c479173a14962b0fe941",
    "author_name": "Lazy user",
    "author_email": "lazy_user@gmail.com",
    "message": "Add index.js",
    "timestamp": "2015-12-07T11:31:38+01:00"
  }
}

接下来,我们将创建Gruntfile.js ,以便我们可以在本地调用我们的Lambda函数。

var grunt = require("grunt")
grunt.loadNpmTasks("grunt-aws-lambda")

grunt.initConfig({
  lambda_invoke: {
    default: {},
  },
})

现在我们准备测试我们的函数了。运行。

$ npm install

当NPM包安装完成后,我们可以通过执行以下一行来调用我们的Lambda函数。

$ grunt lambda_invoke

输出结果应该与此类似。

Success!  Message:
------------------
Message sent to +2222222!

部署Lambda函数

现在我们已经开发了Lambda函数并在本地进行了测试,是时候将其部署到互联网上了。

将代码推送到GitHub

为了将代码与Semaphore集成,我们需要首先将其推送到GitHub。你可以在GitHub的文档中找到关于如何做的详细说明

在Semaphore上构建项目

现在我们已经将代码推送到GitHub,我们可以将GitHub上的项目添加到Semaphore

一旦分析完成,你面前有了构建命令,你就可以点击用这些设置构建。构建应该通过,你应该有一个看起来像这样的页面。

设置部署到AWS Lambda

要设置部署,你需要进入Semaphore的项目页面,点击Set Up Deployment。你会得到一个有不同部署选项的屏幕。我们将选择AWS Lambda。

选择部署方法

Semaphore上有两种部署方法:自动手动部署。

自动部署意味着在选定的分支上每次通过构建后都会触发部署。此外,你也可以在任何时候手动部署任何分支的任何构建。

对于自动部署,您将被要求选择在每次通过构建后自动部署哪个分支。

手动部署需要手动选择要部署的构建。

我们将选择自动部署。

注意:一旦设置完成,你可以在服务器设置中随时改变部署策略。

选择分支

在这一步,我们将选择希望从哪个分支部署应用程序。我们将选择主分支。

输入AWS凭证

接下来,在下面的屏幕上输入你的AWS凭证。如果你需要帮助获得这些凭证,请参考我们关于将网络应用程序部署到Elastic Beanstalk的教程中的检索安全凭证部分。检索凭证的过程与部署到Lambda时相同。

输入凭证后,在同一屏幕上选择你的应用程序所在的区域。这将使Semaphore列出你在指定区域内的功能。

选择功能

下一步是选择我们要部署代码的函数。我们将选择congrats-you-broke-the-build 。接下来,在依赖项安装命令部分输入npm install 。这将安装我们在应用程序中使用的包,以便将它们部署到Lambda。

命名Semaphore上的服务器

在这一步,你需要为你的服务器提供一个名称。这个名字将在您的Semaphore仪表板和部署时间表上使用。

现在设置已经完成 - 你已经准备好自动部署到Lambda。你需要做的就是点击部署

在AWS Lambda上测试功能

你现在可以去你的Lambda函数进行测试。点击 "行动",选择配置测试事件

event.json 的内容复制到输入测试事件编辑器中。输入你在numbers.json 中定义的电子邮件地址和电话号码,以便能够接收信息。

点击保存并测试。恭喜你,你成功地测试了你的第一个AWS Lambda函数。

启用通知

接下来,我们将告诉Semaphore在每次构建完成后向我们的Lambda函数发送一个POST请求。让我们为我们的Lambda函数制作一个亚马逊API网关。转到Lambda函数API端点标签,点击添加API端点。配置看起来应该如下。

点击提交并复制API端点的URL。我们将需要这个URL来向Semaphore添加webhook。

现在,到Semaphore的项目设置中去。点击Notifications,然后点击Webhooks。点击添加Webhook,将你从Lambda复制的链接粘贴到URL文本字段中。你的屏幕看起来应该与此类似。

点击 "测试"。这将向你复制的API端点发送一个测试请求。接下来,进入AWS Lambda的监控页面,点击查看CloudWatch中的日志,你可以看到该函数已经被调用。

破坏构建

让我们看看这在真实的测试案例场景中是否有效。为此,我们将在Semaphore上打破一个构建。进入你的项目设置,点击构建设置,在任何线程中添加一个新的命令行。

$ false

就可以看到它是如何破坏的。现在点击 "开始"。你的构建应该失败,这将导致向你的Lambda函数发送一个请求。剩下的就是等待短信到达你的手机上。

恭喜你,你已经成功地通过Semaphore开发、测试和部署了你的第一个Lambda函数。

本教程的代码可以在GitHub仓库中找到。