0%

使用Terraform部署AWS Lambda

手动置顶:写这一篇博客不代表我喜欢Terraform。

这部分内容比较啰嗦,推荐直接去看我的Github。
使用本地Terraform部署:https://github.com/chengqing-su/lambda-deployment-via-terraform
使用Docker部署:https://github.com/chengqing-su/lambda-deployment-via-dockerized-terraform

使用本地的Terraform部署

一个最简单的AWS Lambda组成:

  • Lambda 的code: 定义这个Lambda做什么以及具体怎么做
  • AWS Lambda function’s execution role:定义这个Lambda function有权限做什么
  • AWS Lambda function 资源
  • AWS Cloudwatch Log Group(可选):执行的日志

写了一个很简单的demo,这个demo只是简单地输出日志,该demo使用的是Nodejs 12和Typescript编写的。

1
2
3
4
5
6
7
8
9
10
├── README.md
├── deployment
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
├── package.json
├── src
│ └── index.ts
├── tsconfig.json
└── yarn.lock

如果觉得这部分内容,过于啰嗦,可以直接到Github上看代码:https://github.com/chengqing-su/lambda-deployment-via-terraform

然后如果感兴趣如何使用Docker部署,以及为啥要使用Docker部署可以直接看“容器化部署” 或者看我的代码:https://github.com/chengqing-su/lambda-deployment-via-dockerized-terraform

Lambda的code

业务代码放在了src下面。这是一个极其简单的demo,因此没有测试代码,如果有测试的话,需要将测试放在与src同级的tests下面(有点啰嗦了)。

1
2
3
4
5
6
7
8
9
import {
CloudWatchLogsEvent
} from "aws-lambda";
export const handler = async (
event: CloudWatchLogsEvent
): Promise<void> => {
console.log(event)
console.log("This is test lambda")
}

接着便是infra的代码,放在了deployment下面。
通常而言,需要先对lambda的业务代码进行打包。下面的代码就是生产我们最终用于部署的业务代码,并打包成一个zip压缩包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
resource "null_resource" "package" {
provisioner "local-exec" {
working_dir = "${path.module}/../"
command = "yarn && yarn compile"
}
triggers = {
run_every_time = uuid()
}
}

data "archive_file" "lambda" {
type = "zip"
source_dir = "${path.module}/../dist"
output_path = "${path.module}/../function.zip"

depends_on = [null_resource.package]
}

AWS Lambda function’s execution role

Lambda function’s execution role 定义了这个function访问AWS服务和资源的权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# lambda execution role
resource "aws_iam_role" "execution_role" {
name = "lambda_execution_role"

assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
}
}
]
}
EOF
}

resource "aws_iam_role_policy_attachment" "lambda_logs_policy" {
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
role = aws_iam_role.execution_role.name
}

AWS Lambda function 资源

1
2
3
4
5
6
7
8
9
10
11
resource "aws_lambda_function" "function" {
function_name = var.function_name
handler = "index.handler"
role = aws_iam_role.execution_role.arn
runtime = "nodejs12.x"

filename = data.archive_file.lambda.output_path

depends_on = [data.archive_file.lambda]
}

AWS Cloudwatch Log Group

创一个Log Group 用来存放Lambda执行的日志。

1
2
3
4
resource "aws_cloudwatch_log_group" "logs" {
name = "/aws/lambda/${var.function_name}"
retention_in_days = 90
}

使用本地Terraform部署

啰啰嗦嗦地写到了这里,还是得说一下怎么部署,说出来又觉得过于简单。

1
2
3
cd deployment/
terraform init
terraform deploy -auto-prove

容器化部署

使用本地化部署的痛点就是:

  • 如果没有安装相关的环境,还得重新安装一遍环境。如果有环境,也不知道环境是不是干净的,也不知道其他人在跑的时候用环境版本对不对。比如在上面这个demo中,就需要安装Node.js 12(最新的LTS版本是14), Yarn, 以及Terraform 0.14.4(如果版本高了或者低了都可能出现无法预估的问题)。
  • 在实际的项目,我们会使用多个Lambda,甚至Lambda之间也会使用不同的技术栈,有的使用Python,有的使用Ruby,有的使用Node。那么问题来了,把所有环境都安装一边是不是过于麻烦?

所以,容器化是必要的。

首先,会在根目录引入一个docker-compose.yaml,下面是一个node的示例:

1
2
3
4
5
6
7
version: "2"
services:
dev:
image: node:12
working_dir: /app
volumes:
- .:/app

然后再引入一个和deployment同级的auto目录,里面会存放的是一些自动化脚本。简单看一个Terraform的示例auto/terraform

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash -e

cd "$(dirname "$0")/.."

docker run --rm \
--volume "$(pwd):/app" \
--workdir "/app/deployment" \
-e AWS_ACCESS_KEY_ID \
-e AWS_SECRET_ACCESS_KEY \
-e AWS_DEFAULT_REGION=ap-southeast-1 \
hashicorp/terraform:0.14.4 \
"${@}"

因为了auto/terraform之后,我们如何部署?这个时候,还会添加一个新的自动化脚本auto/deploy,如下:

1
2
3
4
5
6
#!/bin/bash -e

"$(dirname "$0")"/compile

"$(dirname "$0")"/terraform init
"$(dirname "$0")"/terraform apply -auto-approve

那么部署就变得极其简单了,

1
2
3
4
5
export AWS_ACCESS_KEY_ID=<YOUR_AWS_ACCESS_KEY_ID>
export AWS_SECRET_ACCESS_KEY=<YOUR_AWS_SECRET_ACCESS_KEY>
export AWS_DEFAULT_REGION=<YOUR_AWS_DEFAULT_REGION>

./auto/deploy