AWS Lambda Intro

Aws Lambda Intro

AWS Lambda Intro
Page content

1. AWS Lambda 概念

AWS Lambda 属于AWS提供的无服务器架构的计算服务(serverless computing service),用户可以使用 AWS Lambda 支持的语言和相应的运行时环境来创建函数和自包含的应用并上传到 AWS Lambda上去,AWS Lambda会以高效灵活的方式去运行函数和应用。

Lambda 函数可以用来完成很多种计算任务,如网页服务,处理数据流,调用api及和其它的AWS服务集成。

“五服务器架构的计算"指的是用户不需要自己维护服务器基础设施,这意味着AWS Lambda会为用户写的 Lambda函数维护其运行的服务器和运行时环境,用户完全不用考虑。

2. AWS Lambda 是如何工作的

用户创建的每个Lambda函数都运行在各自的容器中。当某个函数被创建后,Lambda 服务就会将其打包到 一个容器当中,然后在一个多租户的集群上来运行该函数,这些集群中的机器都被AWS统一管理。在每个 函数开始运行之前,每个函数的容器都会被分配一定的RAM和CPU。当函数运行完成后,最初分配的RAM值 会被乘以该函数的运行时间。所以用户会为分配的RAM和函数运行的时间付费。

另外,AWS Lambda一个架构上的特点是,一个账户下的多个函数或者多个实例对应的同一个函数都可以 并发执行。这使得AWS Lambda成为高度可伸缩的云计算方案的不二之选。

3. AWS Lambda 支持的语言和运行时环境

  • Node.js 8.10
  • Node.js 10.x (normally the latest LTS version from the 10.x series)
  • Node.js 12.x (normally the latest LTS version from the 12.x series)
  • Python 2.7
  • Python 3.6
  • Python 3.7
  • Python 3.8
  • Ruby 2.5
  • Java 8
    • This includes JVM-based languages that can run on Java 8’s JVM — the latest Clojure 1.10 and Scala 2.12 both run on Java 8 so can be used with AWS Lambda
  • Java 11
  • Go 1.x (latest release)
  • C# — .NET Core 1.0
  • C# — .NET Core 2.1
  • PowerShell Core 6.0

AWS Lambda 支持上述主流的语言,每种语言的运行时环境都由 Amazon Linux 或者 Amazon Linux 2机器来 提供,AWS 为每种支持的语言都提供了SDK,以便于快速开发自己的Lambda 函数。

4. 在AWS 弹性伸缩中使用js api的lambda function实例

在加入到弹性伸缩组中的实例还未提供服务之前修改实例的某些属性,比如加上标签: 弹性伸缩事件 —> Amazon EventBridge —> rule —> Lambda function

4.1 Amazon EC2 Auto Scaling 生命周期内的实例状态之间的过渡

        EC2 Instance <------------- Detached <------------   Detaching 
            |      											     |
            |         [<----EC2_INSTANCE_LAUNCHING--->]          |                           [<------EC2_INSTANCE_TERMINATING------->]
ASG ---> Pending ---> Pending:Wait ---> Pending:Proceed ---> InService ---> Terminating ---> Terminating:Wait ---> Terminating:Proceed ---> Terminated
            |    exit standby									 |
            |  <---------------   Standby	<-------------	 EnteringStandby

4.2 Amazon EventBridge rule

  • Amazon EventBridge rule
{
  "source": [
    "aws.autoscaling"
  ],
  "detail-type": [ //匹配的事件
    "EC2 Instance Launch Successful",
    "EC2 Instance-launch Lifecycle Action",
    "EC2 Instance-terminate Lifecycle Action"
  ],
  "detail": {
    "AutoScalingGroupName": [
      "cake-autoscale-test" //伸缩组名称
    ]
  }
}

当Amazon EventBridge rule匹配到某个伸缩事件后就会调用下面的js lambda,在lambda中判断 此次调用对应的事件名称,根据事件来修改或者新加tag到实例。

const AWS = require('aws-sdk');
const ec2 = new AWS.EC2();
AWS.config.update({ region: 'cn-northwest-1' }); // hard code the region

const config = {
  debug: true, // if true, print additional info to logs
  dryRunApiCalls: false, // if true, changes will not be made to AWS resources
};

exports.handler = function(event, context) {
  try {

    let initType = "EC2 Instance-launch Lifecycle Action";
    let initDesc = "autoscaling:EC2_INSTANCE_LAUNCHING";
    let doneType = "EC2 Instance Launch Successful";
    let closeType = "EC2 Instance-terminate Lifecycle Action";
    let closeDesc = "autoscaling:EC2_INSTANCE_TERMINATING";

    let oneKey = "autoscalingStatus";
    let valueInit = "init";
    let valueDone = "done";
    let valueClose = "closing";

    let ifaceTkey = "Srv";
    let ifaceTvalueHost = "host";
    let ifaceTvalueCake = "cake_web";

    let e = event;
    let instanceId = e["detail"]["EC2InstanceId"];

    console.log("EVENT: \n" + JSON.stringify(event, null, 2));

    // ####  Add instances's tags  ####
    if (e['detail-type'] === initType &&
      e['detail']['LifecycleTransition'] === initDesc) {
      createOrModifyTag(instanceId, oneKey, valueInit);
      console.log(`\nEC2: Created tag: {Name : ${oneKey}, Value: ${valueInit}}\n`);
    }
    else if (e['detail-type'] === doneType) {
      createOrModifyTag(instanceId, oneKey, valueDone);
      console.log(`\nEC2: Modified tag(tag value from "${valueInit}" to "${valueDone}""): {Name : ${oneKey}, Value: ${valueDone}}\n`);
      
      /* ####  Add NIC's tags #### */
      debugMessage("NIC: Calling getInstanceInfoAndAddIfaceID()...");
      getInstanceInfoAndAddIfaceID(instanceId, ifaceTkey, ifaceTvalueHost, ifaceTvalueCake);
      
    }
    else if (e['detail-type'] === closeType &&
      e['detail']['LifecycleTransition'] === closeDesc) {
      createOrModifyTag(instanceId, oneKey, valueClose);
      console.log(`\nEC2: Modified tag(tag value from "${valueDone}" to "${valueClose}"): {Name : ${oneKey}, Value: ${valueClose}}\n`);
    }
    else {
      console.log("Something wrong, check log details!");
    }
  }
  catch (err) {
    console.log("ERROR: " + err);
  }
};

/* ###### createTags ####### */
async function createOrModifyTag(instanceId, key, value) {
  let params = {
    Resources: [
      instanceId
    ],
    Tags: [{
      Key: key,
      Value: value
    }, ],
  };
  debugMessage(`EC2: Calling ec2.createTags(${JSON.stringify(params)})`);
  let response = await ec2.createTags(params).promise();
  return;
}

/* ######   getNetInterfaceId   ####### */
/* ###### createNetInterfaceTag ####### */
async function getInstanceInfoAndAddIfaceID(instanceId, ifaceTkey, ifaceTvalueHost, ifaceTvalueCake) {
  let iKey = ifaceTkey;
  let itvHost = ifaceTvalueHost;
  let itvCake = ifaceTvalueCake;

  // debug
  console.log(`getInstanceInfoAndAddIfaceID: instanceId: ${instanceId}, ifaceTkey: ${ifaceTkey}, ifaceTvalueHost: ${ifaceTvalueHost}, ifaceTvalueCake: ${ifaceTvalueCake}`);

  let params = {
    InstanceIds: [
      instanceId
    ],
  };

  // get instance info which include ifaceid and ifaceindex
  debugMessage(`NIC: Calling ec2.describeInstances(${JSON.stringify(params)})`);
  let response = await ec2.describeInstances(params).promise();
  //let responseD = await ec2.describeInstances(params).promise();
  //debugMessage(`NIC: ec2.describeInstances() response:\n ${JSON.stringify(response,null,2)}`);

  let data = JSON.stringify(response, null, 2);
  console.log("DescribeEC2 data : " + data);
  let dataobj = JSON.parse(data);

  //debug
  console.log(`DescribeEC2 data dataobj is: ${dataobj}`);

  let idAndIndex = {};
  let ifaceNum = Object.keys(dataobj.Reservations[0].Instances[0].NetworkInterfaces).length;
  if (ifaceNum !== 0 && ifaceNum !== null && ifaceNum !== undefined) {
    for (let i = 0; i < ifaceNum; i++) {
      idAndIndex[dataobj.Reservations[0].Instances[0].NetworkInterfaces[i].Attachment.DeviceIndex] =
        dataobj.Reservations[0].Instances[0].NetworkInterfaces[i].NetworkInterfaceId;
    }
  }
  else {
    console.log(`ifaceNum is illegal, it's value is: ${ifaceNum}`);
  }

  console.log("NIC eth0: " + idAndIndex[0]);
  if (idAndIndex[1] != undefined) {
    console.log("NIC eth1: " + idAndIndex[1]);
  }

  // create tags for iface eth0 and eth1
  debugMessage(`NIC0: Calling createOrModifyIfaceTag()...`);
  createOrModifyIfaceTag(idAndIndex[0], iKey, itvCake);

  if (idAndIndex[1] != undefined) {
    debugMessage(`NIC1: Called createOrModifyIfaceTag()...`);
    createOrModifyIfaceTag(idAndIndex[1], iKey, itvHost);
  }
  return;
}

/* ###### createOrModifyIfaceTag ####### */
async function createOrModifyIfaceTag(ifaceID, ifaceTkey, ifaceTvalue) {
  let params = {
    Resources: [
      ifaceID
    ],
    Tags: [
      {
      Key: ifaceTkey,
      Value: ifaceTvalue
      }, 
    ],
  };
  debugMessage(`NIC: Calling ec2.createTags(${JSON.stringify(params)})`);
  let response = await ec2.createTags(params).promise();
  return;
}

/* ###### debugMessage ####### */
function debugMessage(message) {
  if (config.debug) {
    console.log(message);
  }
}