Day 16 – 要串接 API 除了 API Gateway 你還有另外的選擇 Application Load Balancer

2020 12th 鐵人賽

不知道大家還記不記得在 Day 6 – AWS CDK 部署 Lambda 與 API Gateway 服務 (上) 的時候有說過要介紹 Lambda 串接 ALB,那時候因為還沒介紹 VPC,而我們已經介紹過 VPC 了,所以今天就來介紹它吧!

https://i2.wp.com/ithelp.ithome.com.tw/upload/images/20201002/20117701HtwmQ7qpm1.jpg?w=640&ssl=1

創建 Application Load Balancer 串接 Lambda

準備 Lambda

這次的 Lambda 與上次的大同小異,主要在於直接印出寫 API 的資訊

  • Method
  • Path
  • Query

大家可以直接用這個網頁做 Debug 或是開發

檔案:lambda/index.js

exports.handler = async function (event) {
  console.log("request:", JSON.stringify(event, undefined, 2));

  return {
    statusCode: 200,
    headers: { "Content-Type": "text/html" },
    body:
      `<meta charset="utf-8">` +
      `<h1><a href="https://ithelp.ithome.com.tw/users/20117701/ironman/3734" target="_blank">用 CDK 定義 AWS 架構 AWS Lambda!</a></h1>` +
      `<p><a href="https://blog.clarence.tw/" target="_blank">https://blog.clarence.tw/</a></p>` +
      `<p>Method: ${event.httpMethod}</p>` +
      `<p>Path: ${event.path}</p>` +
      `<p>Query: ${JSON.stringify(
        event.queryStringParameters,
        undefined,
        2
      )}</p>`,
  };
};

準備 VPC 與 Lambda Target

在這邊 VPC 建立方法與之前教大家 EC2 建立機器的 VPC 相同,主要不同的地方在於我們把 targets 放入 new targets.LambdaTarget(lambdaFunction)

const vpc = new ec2.Vpc(this, "VPC", {
  maxAzs: 3,
  natGateways: 0,
});

const lambdaFunction = new lambda.Function(this, "lambda", {
  runtime: lambda.Runtime.NODEJS_10_X,
  handler: "index.handler",
  code: lambda.Code.fromAsset("lambda"),
});

const lb = new elbv2.ApplicationLoadBalancer(this, "ALB", {
  vpc,
  internetFacing: true,
});
new cdk.CfnOutput(this, "ALBDomain", {
  value: lb.loadBalancerDnsName,
});

const listener = lb.addListener("Listener", { port: 80 });
listener.addTargets("LambdaTarget", {
  targets: [new targets.LambdaTarget(lambdaFunction)],

  healthCheck: {
    enabled: true,
  },
});

測試看看 API 吧!

我們這次拿到的 ALB Domain:http://cdklb-albae-1ixf52wqbsc7b-2018343058.us-west-2.elb.amazonaws.com/

拿到的資訊如下:

  • 用 CDK 定義 AWS 架構 AWS Lambda!
  • https://blog.clarence.tw/
  • Method: GET
  • Path: /
  • Query: {}

https://i1.wp.com/ithelp.ithome.com.tw/upload/images/20200930/20117701IU2ijBKA50.png?w=640&ssl=1

結合 Lambda API 與 EC2 API

使用了 Lambda 串接 ALB 大家有注意到可以玩什麼不一樣的變化嗎?

就是可以把前天教的 ALB 串接 EC2 加上 ALB 串接 Lambda 做一個組合技拉! 如此應該滿好玩的!因為大家就可以是情況幫它們拆負載或是把用 Lambda 比較好寫的服務用 Lambda 寫,想要用 EC2 組合的服務放機器,做很多種變化。

準備 CDK 腳本

話不多說現在就教大家怎麼做吧! 準備一下目標,我們希望目標 path 是 /api* 的全部導入 Lambda 而其他都給 EC2 如此我們會有兩個 Target 並且要切 path,而在 ALB 裡面如果設定 conditions 就需要給 priority

conditions

先來介紹一下 conditions

  • hostHeaders:用於設定 host 條件
  • httpHeader:用於設定 header 條件
  • httpRequestMethods:用於設定 method 條件
  • pathPatterns:用於設定 path 條件
  • queryStrings:用於設定 query 條件
  • sourceIps:用於設定 source ip 條件
const listener = lb.addListener("Listener", { port: 80 });
listener.addTargets("LambdaTarget", {
  priority: 1,
  conditions: [elbv2.ListenerCondition.pathPatterns(["/api*"])],

  targets: [new targets.LambdaTarget(lambdaFunction)],

  healthCheck: {
    enabled: true,
  },
});

listener.addTargets("EC2Target", {
  healthCheck: {
    enabled: true,
    path: "/phpinfo.php",
  },
  port: 80,
  targets: [new targets.InstanceTarget(ec2Instance)],
});

組合一下

拆散解釋大家可能會看不懂,所以我們還是把它整個整理一下吧!

const vpc = new ec2.Vpc(this, "VPC", {
  maxAzs: 3,
  natGateways: 0,
});

const lambdaFunction = new lambda.Function(this, "lambda", {
  runtime: lambda.Runtime.NODEJS_10_X,
  handler: "index.handler",
  code: lambda.Code.fromAsset("lambda"),
});

const asset = new assets.Asset(this, "Asset", {
  path: path.join(__dirname, "../", "ec2-config", "configure.sh"),
});

const ec2Instance = new ec2.Instance(this, "Instance", {
  vpc,
  instanceType: ec2.InstanceType.of(
    ec2.InstanceClass.T3,
    ec2.InstanceSize.NANO
  ),
  machineImage: ec2.MachineImage.latestAmazonLinux({
    generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
  }),
  vpcSubnets: {
    subnetType: ec2.SubnetType.PUBLIC,
  },
  keyName: "KeyPair",
});
const localPath = ec2Instance.userData.addS3DownloadCommand({
  bucket: asset.bucket,
  bucketKey: asset.s3ObjectKey,
});
ec2Instance.userData.addExecuteFileCommand({
  filePath: localPath,
  arguments: "--verbose -y",
});
asset.grantRead(ec2Instance.role);

const lb = new elbv2.ApplicationLoadBalancer(this, "ALB", {
  vpc,
  internetFacing: true,
});
new cdk.CfnOutput(this, "ALBDomain", {
  value: lb.loadBalancerDnsName,
});

const listener = lb.addListener("Listener", { port: 80 });
listener.addTargets("LambdaTarget", {
  priority: 1,
  conditions: [elbv2.ListenerCondition.pathPatterns(["/api*"])],

  targets: [new targets.LambdaTarget(lambdaFunction)],

  healthCheck: {
    enabled: true,
  },
});

listener.addTargets("EC2Target", {
  healthCheck: {
    enabled: true,
    path: "/phpinfo.php",
  },
  port: 80,
  targets: [new targets.InstanceTarget(ec2Instance)],
});
listener.connections.allowTo(ec2Instance, ec2.Port.tcp(80));

測試看看 API 與 EC2 的結合吧!

  • 可以看到我們的 PATH 可以直接在 Lambda 的 event.path 取得它
    • URL: http://cdklb-albae-1ixf52wqbsc7b-2018343058.us-west-2.elb.amazonaws.com/api
    • Method: GET
    • Path: /api
    • Query: {}

https://i0.wp.com/ithelp.ithome.com.tw/upload/images/20200930/20117701l2np9lHq8f.png?w=640&ssl=1

  • 就算是 query string 也可以取得呦!
    • URL: http://cdklb-albae-1ixf52wqbsc7b-2018343058.us-west-2.elb.amazonaws.com/api/api?target=ec2
    • Method: GET
    • Path: /api?target=ec2
    • Query: {"target": "ec2"}

https://i2.wp.com/ithelp.ithome.com.tw/upload/images/20200930/20117701u6zVm9NtXF.png?w=640&ssl=1

  • 看一下 root URL 吧!
    • URL: http://cdklb-albae-1ixf52wqbsc7b-2018343058.us-west-2.elb.amazonaws.com/

https://i1.wp.com/ithelp.ithome.com.tw/upload/images/20200930/20117701qnRwYT1G64.png?w=640&ssl=1

參考資料

  • https://docs.aws.amazon.com/zh_tw/elasticloadbalancing/latest/application/load-balancer-listeners.html

以上為今日的 Application Load Balancer 與 Lambda 整合介紹,希望有幫助到大家

想要看更多嗎?歡迎到我的部落格參觀

文章內容主要是網路或是程式開發類型的文章