Deploying AWS Lambda with Terraform

Deploying AWS Lambda with Terraform

Serverless is a hot topic in Cloud, so as Infrastructure as Code(IaC). Infrastructure as code (IaC) tools allow you to manage infrastructure with configuration files rather than through a graphical user interface. IaC allows you to build, change, and manage your infrastructure in a safe, consistent, and repeatable way by defining resource configurations that you can version, reuse, and share.

It’s quite easy to get used to Terraform if you are familiar with CloudFormation as they all being tools to implement infrastructure as code on Cloud Provider, such as AWS. It’s a cornerstone of DevOps, designed to boost the agility, productivity and quality of work within organizations.

This article will cover: the basic components of deploying lambda, and related step to set up a lambda through Terraform.

Deploying AWS Lambda with Terraform‬‏

A series article will be published to cover several topic including:

  • Difference between Terraform and CloudFormation
  • Workflow of Setting up Schedule Lambda to Sync Data Between two s3 Bucket.
  • Automate Terraform with GitHub Actions
  • Use PGP to Encrypt Your Terraform Secrets

Deploy a Lambda Function with Terraform

Let’s deploy a simple lambda function to AWS using Terraform. Talk is cheap, let’s show you the code.

Prerequisite

  • Install Terraform CLI
    • Install it through Terraform’s website.
    • Check the version of it to ensure it works using terraform --version
  • Creating AWS IAM User from console
    • Ensure IAM user has programmatic access.
    • Giving it Administrator access permission to ensure followed resources creation.

Main Steps

Source Code Needed

For this project, let’s create a directory named src to store is source code. Create file hello.py ,which contain followed code:

1
2
3
4
5
6
7
8
def lambda_handler(event, context):
"""
:param event:
:param context:
:return:
"""
print('Event receive: ', event)
return "Mission Completed!"

Define Variables

The next step is to define all the variables in var.tf, all of them can be hard code in other resources definition, but hard to manage them and not to mention reuse them. For that end, variable is suitable to be extracted and define in separate way.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
variable "function_name" {
default = "hello"
}

variable "source_folder" {
default = "data/"
}

variable "source_directory" {
type = string
default = "./src"
}

variable "lambda_runtime" {
type = string
description = "Lambda Parameter - Runtime. E.x. python3.6"
default = "python3.8"
}

variable "lambda_handler" {
type = string
description = "Lambda Parameter - Handler reference, e.x. index.lambda_handler"
default = "hello.lambda_handler"
}

variable "builds_dir" {
type = string
description = "The directory where the lambda zip should be built"
default = "lambda_function_payload.zip"
}

Data Definition

For the lambda function, it needs proper permission to perform manipulation of other sources and it may have multiple permission statement and sometimes hard to read if policies are directly put in main file. Below is a data sample to give lambda function permission to list all objects in a s3 bucket.

And also, AWS allow zip file to be uploaded for lambda function, that’s why data type archive_file is needed and it point to the builds directory for zip file, and need to know which folder is the source code folder.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
data "aws_iam_policy_document" "lambda_permissions" {
statement {
actions = [
"s3:ListBucket",
]
resources = [
"arn:aws:s3:::${var.target_bucket}",
]
}
}

data "archive_file" "zip_the_python_code" {
type = "zip"
source_dir = var.source_directory
output_path = var.builds_dir
}

Lambda Definition

Finally, we get to define the related resources in lambda.tf, it contains three resources:

  • lambda_s3_policy: A policy to consume predefined permission in data.tf
  • iam_role_for_lambda: An IAM role for lambda to consume role and also attach above policy to the role.
  • schedule_lambda: Lambda function with many definitions:
    • filename point to the zip file
    • function_name need no explanation
    • runtime define the version and explainer of the function, such as python3.7
    • handler take two input (event and context) when function is invoked. For this project hello is the file name and lambda_handler is the method name.
    • Predefined role is assigned to the function
    • source_code_hash is aim to detect code change and update when needed since hash will change when the source code change.
    • environment define the variables for function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
resource "aws_iam_policy" "lambda_s3_policy" {
policy = data.aws_iam_policy_document.lambda_permissions.json
}

resource "aws_iam_role" "iam_role_for_lambda" {
name = "iam_role_for_lambda"
assume_role_policy = jsonencode({
"Version" : "2012-10-17",
"Statement" : [
{
"Action" : "sts:AssumeRole",
"Principal" : {
"Service" : "lambda.amazonaws.com"
},
"Effect" : "Allow",
"Sid" : ""
}
]
})
managed_policy_arns = [aws_iam_policy.lambda_s3_policy.arn]
}

resource "aws_lambda_function" "meta_schedule_lambda" {
filename = var.builds_dir
function_name = var.function_name
handler = var.lambda_handler
runtime = var.lambda_runtime
role = aws_iam_role.iam_role_for_lambda.arn
source_code_hash = data.archive_file.zip_the_python_code.output_base64sha256
environment {
variables = {
TARGET_BUCKET = var.target_bucket
}
}
}

Provider Information

provider.tf should be noted that the region in which S3 bucket is created, same region should be entered above.

1
2
3
provider "aws" {
region = "us-east-1"
}

Run Command Line

Now we almost done all the code work. And run some command to deploy:

  • terraform init to download all the required plugins.
  • terraform plan to check the integrity of the code.
  • terraform apply to deploy all the resources into AWS cloud. Also, terraform apply -auto-approve could spare you from manually input yes during the procedure to do the confirmation.

Well done! And all we need to do is to check whether resources have been created through AWS Console.


Terraform Lambda Resources

There are a lot resources in Terraform to provision lambda function and related resources such as attached policies.

aws_lambda_function

A Lambda function needs code and an IAM role to run a function. Code is deployed on an S3 bucket as a deployment package (zip file).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
resource "aws_lambda_function" "sample_lambda" {
filename = "lambda_function_payload.zip"
function_name = "lambda_terraform_function_name"
role = aws_iam_role.iam_role_for_lambda.arn
handler = "data.test"
source_code_hash = filebase64sha256("lambda_function_payload.zip")
runtime = "nodejs12.x"
environment {
variables = {
foo = "bar"
}
}
}
}

A single resource of aws_lambda_function is not enough, because no permission is granted by default, so it has to be configured with a execution role to allow the function to assume role to get permission it need.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
resource "aws_iam_role" "iam_role_for_lambda" {
name = "iam_role_for_lambda"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}

aws_lambda_alias

Resource aws_lambda_alias provide clients with a function identifier that you can update to invoke a different version. It also useful when a lambda function still in test stage and invocation could be split and route to different version of function.

1
2
3
4
5
6
7
8
9
10
11
resource "aws_lambda_alias" "test_lambda_alias" {
name = "my_alias"
description = "a sample description"
function_name = aws_lambda_function.lambda_function_test.arn
function_version = "1"
routing_config {
additional_version_weights = {
"2" = 0.5
}
}
}

aws_lambda_function_event_invoke_config

Manages an asynchronous invocation configuration for a Lambda Function or Alias. Such as destination configuration:

1
2
3
4
5
6
7
8
9
10
11
resource "aws_lambda_function_event_invoke_config" "example" {
function_name = aws_lambda_alias.example.function_name
destination_config {
on_failure {
destination = aws_sqs_queue.example.arn
}
on_success {
destination = aws_sns_topic.example.arn
}
}
}

aws_lambda_layer_version

This provides a Lambda Layer Version resource. Lambda Layers allow you share the reusable code through layers across multiple Lambda functions.

1
2
3
4
5
resource "aws_lambda_layer_version" "lambda_nodejs_layer" {
filename = "lambda_nodejs_layer_payload.zip"
layer_name = "lambda_layer_nodejs"
compatible_runtimes = ["nodejs12.0"]
}

aws_lambda_permission

This resource provides other AWS services, such as S3 and DynamoDB, access to the Lambda function.

1
2
3
4
5
6
7
8
resource "aws_lambda_permission" "allow_s3" {
statement_id = "AllowExecutionFromS3"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.s3_lambda.function_name
principal = "events.amazonaws.com"
source_arn = "arn:aws:events:ap-east-2:121112424343:rule/RunDaily"
qualifier = aws_lambda_alias.s3_alias.name
}

References

Author

Haojun(Vincent) Gao

Posted on

2022-04-11

Updated on

2022-04-11

Licensed under

Comments