Skip to main content

Terraform

Terraform is one of the most popular infrastructure as code (IaC) tools available today. It enables the automation of infrastructure across any cloud. Terraform allows you to build any kind of infrastructure, not just serverless. For this example, let's focus on deploying a Lambda function.

Deploying .NET Lambda functions using Terraform adds an additional complexity. Your .NET function code needs to be compiled before Terraform can deploy your resources. This is less of an issue for interpreted languages like NodeJS and Python as no compilation is required.

However, the first thing you'll need to do is install Terraform..

Once installed, create a new directory:

start-terraform

mkdir terraform-dotnet-serverless
cd terraform-dotnet-serverless

Then create a file called main.tf.

Copy in the below code. There's a lot, don't worry I'll walk through it step by step:

Click Here for Code Sample
main.tf

terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
resource "random_uuid" "bucket_random_id" {
}
# Configure the AWS Provider
provider "aws" {
region = "eu-west-1"
}
# Create S3 bucket to store our application source code.
resource "aws_s3_bucket" "lambda_bucket" {
bucket = "${random_uuid.bucket_random_id.result}-dotnet-tf-bucket"
acl = "private"
force_destroy = true
}
data "archive_file" "lambda_archive" {
type = "zip"
source_dir = "Functions/Dotnet.CDK.Lambda/src/Dotnet.CDK.Lambda/bin/Release/net6.0/linux-x64/publish"
output_path = "Dotnet.CDK.Lambda.zip"
}
resource "aws_s3_object" "lambda_bundle" {
bucket = aws_s3_bucket.lambda_bucket.id
key = "Dotnet.CDK.Lambda.zip"
source = data.archive_file.lambda_archive.output_path
etag = filemd5(data.archive_file.lambda_archive.output_path)
}
resource "aws_cloudwatch_log_group" "aggregator" {
name = "/aws/lambda/${aws_lambda_function.function.function_name}"
retention_in_days = 30
}
resource "aws_iam_role" "lambda_function_role" {
name = "FunctionIamRole_dotnet-terraform-lambda"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "lambda_policy_attach" {
role = aws_iam_role.lambda_function_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_lambda_function" "function" {
function_name = "dotnet-terraform-lambda"
s3_bucket = aws_s3_bucket.lambda_bucket.id
s3_key = aws_s3_object.lambda_bundle.key
runtime = "dotnet6"
handler = "Dotnet.CDK.Lambda::Dotnet.CDK.Lambda.Function::FunctionHandler"
source_code_hash = data.archive_file.lambda_archive.output_base64sha256
role = aws_iam_role.lambda_function_role.arn
timeout = 30
}
output "function_name" {
value = aws_lambda_function.function.function_name
}

Let's also create a new Lambda function to use as an example.


mkdir Functions
cd Functions
dotnet new lambda.EmptyFunction -n Dotnet.CDK.Lambda

Terraform Provider

The opening section of the file configures the AWS provider for Terraform. More documentation on this is found in the Terraform documentation.

main.tf

terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
# Configure the AWS Provider
provider "aws" {
region = "eu-west-1"
}
#...

S3 Bucket for Code Storage

When packaging Lambda applications as ZIP files an S3 bucket is required to store the code. This code creates a new S3 bucket using a random identifier. You can update the bucket property if you wish to use a different bucket name.

main.tf

#...
resource "random_uuid" "bucket_random_id" {
}
# Create S3 bucket to store our application source code.
resource "aws_s3_bucket" "lambda_bucket" {
bucket = "${random_uuid.bucket_random_id.result}-dotnet-tf-bucket"
acl = "private"
force_destroy = true
}
#...

Lambda ZIP Package

Terraform provides a data source for generating ZIP files. The publish directory of the Lambda function is specified as the source directory and a ZIP file is generated. This ZIP file is then uploaded to S3 using the aws_s3_object resource.

main.tf

#...
data "archive_file" "lambda_archive" {
type = "zip"
source_dir = "Functions/Dotnet.CDK.Lambda/src/Dotnet.CDK.Lambda/bin/Release/net6.0/linux-x64/publish"
output_path = "Dotnet.CDK.Lambda.zip"
}
resource "aws_s3_object" "lambda_bundle" {
bucket = aws_s3_bucket.lambda_bucket.id
key = "Dotnet.CDK.Lambda.zip"
source = data.archive_file.lambda_archive.output_path
etag = filemd5(data.archive_file.lambda_archive.output_path)
}
#...

CloudWatch

Additionally, a Amazon CloudWatch log group is defined to store logs generated by the Lambda function.

main.tf

#...
resource "aws_cloudwatch_log_group" "aggregator" {
name = "/aws/lambda/${aws_lambda_function.function.function_name}"
retention_in_days = 30
}
#...

IAM

An IAM role and policy allow Lambda execution is then created to provide Lambda with the AWSLambdaBasicExecutionRole required to run.

main.tf

#...
resource "aws_iam_role" "lambda_function_role" {
name = "FunctionIamRole_dotnet-terraform-lambda"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "lambda_policy_attach" {
role = aws_iam_role.lambda_function_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
#...

Lambda Function Resource

A Lambda function is defined using the aws_lambda_function resource. Notice that the s3_bucket, s3_key and source_code_hash properties are using values from the S3 and asset objects defined earlier.

main.tf

#...
resource "aws_lambda_function" "function" {
function_name = "dotnet-terraform-lambda"
s3_bucket = aws_s3_bucket.lambda_bucket.id
s3_key = aws_s3_object.lambda_bundle.key
runtime = "dotnet6"
handler = "Dotnet.CDK.Lambda::Dotnet.CDK.Lambda.Function::FunctionHandler"
source_code_hash = data.archive_file.lambda_archive.output_base64sha256
role = aws_iam_role.lambda_function_role.arn
timeout = 30
}
#...

Output Name

And finally the name of the Lambda function is output to the console.

main.tf

#...
output "function_name" {
value = aws_lambda_function.function.function_name
}
#...

Terraform Provider

The opening section of the file configures the AWS provider for Terraform. More documentation on this is found in the Terraform documentation.

S3 Bucket for Code Storage

When packaging Lambda applications as ZIP files an S3 bucket is required to store the code. This code creates a new S3 bucket using a random identifier. You can update the bucket property if you wish to use a different bucket name.

Lambda ZIP Package

Terraform provides a data source for generating ZIP files. The publish directory of the Lambda function is specified as the source directory and a ZIP file is generated. This ZIP file is then uploaded to S3 using the aws_s3_object resource.

CloudWatch

Additionally, a Amazon CloudWatch log group is defined to store logs generated by the Lambda function.

IAM

An IAM role and policy allow Lambda execution is then created to provide Lambda with the AWSLambdaBasicExecutionRole required to run.

Lambda Function Resource

A Lambda function is defined using the aws_lambda_function resource. Notice that the s3_bucket, s3_key and source_code_hash properties are using values from the S3 and asset objects defined earlier.

Output Name

And finally the name of the Lambda function is output to the console.

main.tf
ExpandClose

terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
# Configure the AWS Provider
provider "aws" {
region = "eu-west-1"
}
#...

Once defined, run the below commands from a terminal window in the same folder as the main.tf file.

deploy

dotnet publish ./Functions/Dotnet.CDK.Lambda/src/Dotnet.CDK.Lambda/ -c Release -r linux-x64
terraform init
terraform apply

Once deployed, invoke the Lambda function in AWS using the below command replacing the OUTPUT_FUNCTION_NAME with the value output to your terminal:


dotnet lambda invoke-function -n OUTPUT_FUNCTION_NAME -p hello