How to deploy Lambda function on AWS using Terraform

Batuhan Bulut
7 min readJun 18, 2024

--

Serverless functions are critical for DevOps and SysOps since you don’t need to worry about Infrastructure.

We will be looking for Amazon Web Services (AWS) Lambda functions and how can we deploy AWS Lambda with Terraform.

Github Link for all materials : https://github.com/batuhan-bulut/terraform-aws-lambda

What is AWS Lambda?

Lambda is a compute service that you can use to build applications without provisioning or managing servers.

With AWS Lambda you can execute your scripts in supported languages without thinking the servers. You can execute codes written in Node.JS, Python, C#, and many more languages.

With Lambda you can do;
- Check the status of your EC2 Instances on Regions.
- Run some automation with AWS CLI.
- Schedue a task with AWS SQS.
- etc.

With this possibilities, AWS Lambda has very important place for DevOps and SysOps teams.

It’s easy to deploy a AWS Lambda from AWS Website with GUI.

What if you have multiple accounts and you need to deploy the same Lambda to different regions? What you gonna do? Deploy Lambda’s one-by-one?

This is where IaC and Terraform joins the game.

What is IaC ? (Infrastructure as Code)

Infrastructure as code (IaC) is the ability to provision and support your infrastructure using code instead of manual processes.

With IaC, we can easily setup our any environment (Development, Test, Stating, Production, etc.). Populer providers has their CDK (Cloud Development Kit ) for your requirements.

Why Terraform Important?

Let’s say you have an app in AWS and it uses many services (EC2, RDS, Lambda, SQS, etc.). and you can deploy this application with AWS CDK easyily.

What if management decides to switch Azure, GCP or another cloud provider?

Most of your knowledge in AWS CDK is not important anymore because this providers has their CDK’s.

This is where Terraform joins to our workflow.

What is Terraform ?

HashiCorp Terraform is an infrastructure as code tool that lets you define both cloud and on-prem resources in human-readable configuration files that you can version, reuse, and share for AWS, GCP, Azure, Alibaba Cloud, Kubernetes and many more.

As you see, with knowledge and experience in Terraform we can manage many resources with simple commands.

On this document, we will focus AWS side of Terraform.

Lights, Camera, Action!

Before start make sure you have;
— AWS Account
— Terraform Installed on your local (Installation Guide)

Be Careful : This actions can create a cost on AWS side.

What will our Terraform code do;

On this document, we will use variables instead of hard-coded values. With this, you can execute the same script with different settings.

This is our terraform/main.tf file, it contains the skeleton of our code.

provider "aws" {
region = var.region
}

# IAM role for AWS Lambda
resource "aws_iam_role" "terraform_lambda_iam_role" {
name = var.terraform_lambda_iam_role.name
assume_role_policy = var.terraform_lambda_iam_role.assume_role_policy
}

# Create a new policy for Lambda
resource "aws_iam_policy" "iam_policy_for_lambda" {
name = "iam_policy_${var.terraform_lambda_iam_role.name}"
description = var.iam_policy_for_lambda.description
policy = var.iam_policy_for_lambda.policy
}

# Attach IAM Policy to IAM Role
resource "aws_iam_role_policy_attachment" "attach_iam_policy_to_iam_role" {
role = aws_iam_role.terraform_lambda_iam_role.name
policy_arn = aws_iam_policy.iam_policy_for_lambda.arn
}

# Create a zip file for upload to Lambda
data "archive_file" "zip_python_lambda_code" {
type = "zip"
output_path = "${path.module}/python/${var.zip_python_lambda_code.name}.zip"
source_file = "${path.module}/../${var.zip_python_lambda_code.name}.py"
}

# Create the Lambda function
resource "aws_lambda_function" "terraform_lambda" {
filename = "${path.module}/python/${var.zip_python_lambda_code.name}.zip"
function_name = var.terraform_lambda.name
role = aws_iam_role.terraform_lambda_iam_role.arn
runtime = var.terraform_lambda.runtime
handler = "${var.zip_python_lambda_code.name}.lambda_handler"
}

In this code block we read all variables from terraform/terraform.tf file. We can execute the same Terraform file with different configurations for different environments like Production, Staging or even different AWS Region.

To read this variables, we need to definition of variables. We can declare variables with “.tf” files.

This is how terraform.tf file looks like for code above.

variable "region" {
type = string
description = "AWS Region"
default = "eu-central-1"
}

variable "zip_python_lambda_code" {
type = map(string)
description = "Name of the Python file"
default = {
name = "index"
}
}

variable "terraform_lambda_iam_role" {
type = map(string)
description = "aws_iam_role - terraform_lambda_iam_role variables"
default = {
name = "Lambda-from-terraform"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
}

variable "iam_policy_for_lambda" {
type = map(string)
description = "IAM policy for lambda"
default = {
description = "IAM policy - created by Terraform"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*",
"Effect": "Allow"
}
]
}
EOF
}
}

variable "terraform_lambda" {
type = map(string)
description = "Variables for lambda"
default = {
name = "Lambda_Terraform"
runtime = "python3.12"
handler = "index.lambda_handler"
}
}

On this file we declare the variables and their default values, In Terraform we can declare different types of variables.

You can check Terraform documents for more info.

If you don’t want to use default values for your code, you can add a terraform/vars.tfvars file for override default variables. Context of this file is regular “key = value”.

Here it’s an example for .tfvars file

region = "us-east-1"

terraform_lambda= {
name = "Override_Name"
runtime = "python3.9"
handler = "override.lambda_handler"
}

With this Terraform will override default values for region and terraform_lambda variables.

Note: if you want to change a variable from object like terraform_lambda like this example, you need to pass all variables in the object, otherwise it will raise an error.

and let’s add a simple Pyton App to our root folder named index.py

# Simple Hello function
import json

def lambda_handler(event, context):
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}

This is how folders should look like

Folder structure

Showtime!

We’re ready for execute the code.

First we need to execute terraform init command for initalize a working directory containing Terraform configuration files.

$terraform init

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Reusing previous version of hashicorp/archive from the dependency lock file
- Using previously-installed hashicorp/aws v5.54.1
- Using previously-installed hashicorp/archive v2.4.2

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

After that, we can execute terraform plan to see the changes in the infrastructure.

data.archive_file.zip_python_lambda_code: Reading...
data.archive_file.zip_python_lambda_code: Read complete after 0s [id=111]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create

Terraform will perform the following actions:

# aws_iam_policy.iam_policy_for_lambda will be created
+ resource "aws_iam_policy" "iam_policy_for_lambda" {
+ arn = (known after apply)
+ attachment_count = (known after apply)
+ description = "IAM policy - created by Terraform"
+ id = (known after apply)
+ name = "iam_policy_Lambda-from-terraform"
+ name_prefix = (known after apply)
+ path = "/"
+ policy = jsonencode(
{
+ Statement = [
+ {
+ Action = [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:PutLogEvents",
]
+ Effect = "Allow"
+ Resource = "arn:aws:logs:*:*:*"
},
]
+ Version = "2012-10-17"
}
)
+ policy_id = (known after apply)
+ tags_all = (known after apply)
}

# aws_iam_role.terraform_lambda_iam_role will be created
+ resource "aws_iam_role" "terraform_lambda_iam_role" {
+ arn = (known after apply)
+ assume_role_policy = jsonencode(
{
+ Statement = [
+ {
+ Action = "sts:AssumeRole"
+ Effect = "Allow"
+ Principal = {
+ Service = "lambda.amazonaws.com"
}
+ Sid = ""
},
]
+ Version = "2012-10-17"
}
)
+ create_date = (known after apply)
+ force_detach_policies = false
+ id = (known after apply)
+ managed_policy_arns = (known after apply)
+ max_session_duration = 3600
+ name = "Lambda-from-terraform"
+ name_prefix = (known after apply)
+ path = "/"
+ tags_all = (known after apply)
+ unique_id = (known after apply)
}

# aws_iam_role_policy_attachment.attach_iam_policy_to_iam_role will be created
+ resource "aws_iam_role_policy_attachment" "attach_iam_policy_to_iam_role" {
+ id = (known after apply)
+ policy_arn = (known after apply)
+ role = "Lambda-from-terraform"
}

# aws_lambda_function.terraform_lambda will be created
+ resource "aws_lambda_function" "terraform_lambda" {
+ architectures = (known after apply)
+ arn = (known after apply)
+ code_sha256 = (known after apply)
+ filename = "./python/index.zip"
+ function_name = "Override_Name"
+ handler = "index.lambda_handler"
+ id = (known after apply)
+ invoke_arn = (known after apply)
+ last_modified = (known after apply)
+ memory_size = 128
+ package_type = "Zip"
+ publish = false
+ qualified_arn = (known after apply)
+ qualified_invoke_arn = (known after apply)
+ reserved_concurrent_executions = -1
+ role = (known after apply)
+ runtime = "python3.9"
+ signing_job_arn = (known after apply)
+ signing_profile_version_arn = (known after apply)
+ skip_destroy = false
+ source_code_hash = (known after apply)
+ source_code_size = (known after apply)
+ tags_all = (known after apply)
+ timeout = 3
+ version = (known after apply)
}

Plan: 4 to add, 0 to change, 0 to destroy.

and we can execute terraform apply to push this changes to our infra.

After that you will see the success message on the console. It means every resource are created on AWS side. You can go and check & execute your lambda function from AWS GUI or can check the Lambda function in CLI.

aws lambda list-functions --region us-east-1 | grep Override_Name 

"FunctionName": "Override_Name",
"FunctionArn": "arn:aws:lambda:us-east-1:11111:function:Override_Name",
"LogGroup": "/aws/lambda/Override_Name"

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Batuhan Bulut
Batuhan Bulut

Written by Batuhan Bulut

Sofware engineer, master of troubleshooting, developing stuffs in backend

No responses yet

Write a response