How to Run Terraform in AWS Lambda (if you must)

Table of Contents

Terraform in Lambda

For most Terraform use cases, Lambda may not be the right choice. In most cases, a standard CI/CD pipeline in CodePipeline / CodeBuild, GitHub, GitLab, or HCP Terraform would work best, preferably with a manual approval stage between plan & apply for a human to review & approve the Terraform plan before it’s applied.

In some cases though, Lambda might be the way to go. If any of the following apply to your use case, consider Lambda for Terraform:

  • You need a fully unattended Terraform plan & apply, with no human review of the plan
  • The Terraform config is simple enough or the infra non-critical enough to always auto-approve the plan, even if it destroys infra
  • All you need is a simple way to keep some infra always in sync with its Terraform config, undo any manual changes (drift) that might have happened to the infra
  • You want a quick & easy (serverless) way to schedule your Terraform runs without the overhead of building pipelines
  • You run Terraform as a step in your step function state machine: Lambda is perfect for that

The Terraform Config

The Terraform config you wish to apply in the Lambda can be anything as long as you provide it the necessary inputs. The AWS Terraform provider for example can use the AWS IAM permissions from the Lambda function’s execution role to manage AWS infra. The Terraform state must be external so it persists across Lambda runs. If you use S3 for Terraform state, ensure the Lambda function has permissions to access the state bucket. Here’s a simple initial config:

provider "aws" {}

terraform {
required_version = ">= 1.11"

required_providers {
aws = { source = "hashicorp/aws" }
}

backend "s3" {
bucket = "my-terraform-state-bucket"
key = "my-terraform-state"
}
}

AWS SAM

We’ll use the AWS Serverless Application Model (SAM) to build our Lambda app. To begin, create a SAM template template.yaml:

AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
Handler: bootstrap
Runtime: provided.al2023
Architectures: [ arm64 ]
FunctionName: my-function
Policies:
- S3CrudPolicy:
BucketName: my-terraform-state-bucket
Events:
Daily:
Type: Schedule
Properties:
Schedule: cron(...)
Environment:
Variables:
TF_INPUT: '0'
TF_DATA_DIR: /tmp
TF_CLI_ARGS: -no-color
TF_IN_AUTOMATION: 'true'

There are a few key things going on here:

  • The function is granted access to the Terraform state bucket using an S3 CRUD policy
  • The function is scheduled to run daily
  • “Terraform in automation” is enabled & Terraform input is disabled so it doesn’t block for any user input
  • Terraform CLI’s color output is disabled so the Lambda logs are readable
  • And most importantly, the Terraform data directory is set to /tmp, which is the only writable directory in Lambda; rest of the file system is readonly

SAM Build

Since we’re using a custom Lambda runtime, we need to instruct SAM CLI how to build the Lambda function. This is done using a Makefile:

build-MyFunction:
rm -rf $(ARTIFACTS_DIR)/*
cp bootstrap *.tf $(ARTIFACTS_DIR)
$(eval TF_VERSION := $(shell curl -s https://api.releases.hashicorp.com/v1/releases/terraform/latest | jq -r '.version'))
curl -so terraform.zip https://releases.hashicorp.com/terraform/$(TF_VERSION)/terraform_$(TF_VERSION)_linux_arm64.zip
unzip -q terraform.zip terraform
mv terraform $(ARTIFACTS_DIR)
rm terraform.zip

This script is run when you call the sam build CLI command:

  • It cleans the build directory & copies the Terraform config into it
  • It also downloads the latest Terraform & packages it into the Lambda function

More details at https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/building-custom-runtimes.html

OS-Only Lambda Runtime

All we need to run Terraform is a minimal OS with a shell. None of the programming language runtimes provided by Lambda are required. This is available through the provided Lambda runtime. We’ll use the provided.al2023 runtime in this article. It provides the Amazon Linux 2023 OS with Bash.

Since no language runtime is available, we must provide our own. This is just a shell script. The file must be named bootstrap & must be executable. It must listen for Lambda events & send responses to the Lambda service. Here is the bootstrap for Terraform:

#!/usr/bin/env bash
set -e

cp terraform ./*.tf "$TF_DATA_DIR"
cd "$TF_DATA_DIR"
./terraform init
API_BASE_URL="http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation"

while true; do
HEADERS="$(mktemp)"
curl -sSLD "$HEADERS" "$API_BASE_URL/next"
REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)
./terraform apply -parallelism=1000 -auto-approve
curl -sX POST "$API_BASE_URL/$REQUEST_ID/response"
done

/var/task is the current directory when a Lambda function starts. It’s also where your Lambda code is located. Since the Lambda file system is readonly (except /tmp), Terraform will fail to run there trying to create the .terraform directory, the dependency lock file, download the providers, etc. So this script:

  • Moves all code to /tmp & initializes Terraform
  • Calls API/next & waits for a Lambda invocation event to trigger it
  • Runs Terraform apply & returns a success response to Lambda

More details at https://docs.aws.amazon.com/lambda/latest/dg/runtimes-walkthrough.html#runtimes-walkthrough-function

Deploy to AWS

With all the pieces in place, the SAM app can now be deployed to AWS:

sam build && sam deploy --s3-bucket my-sam-apps \
--stack-name my-terraform-app --capabilities CAPABILITY_IAM

The next run of the schedule should now trigger the Lambda!

About the Author ✍🏻

Harish KM is a Principal DevOps Engineer at QloudX. 👨🏻‍💻

With over a decade of industry experience as everything from a full-stack engineer to a cloud architect, Harish has built many world-class solutions for clients around the world! 👷🏻‍♂️

With over 20 certifications in cloud (AWS, Azure, GCP), containers (Kubernetes, Docker) & DevOps (Terraform, Ansible, Jenkins), Harish is an expert in a multitude of technologies. 📚

These days, his focus is on the fascinating world of DevOps & how it can transform the way we do things! 🚀

Leave a Reply

Your email address will not be published. Required fields are marked *