Building DevSecOps solutions using AWS, Terraform and Kubernetes

Lambda Cron Example (Terraform)

  • 18th September 2022

Introduction

A common issue people experience when transitioning to serverless infrastructure is finding where to configure a cron.

During this article we will look at using EventBridge to trigger a lambda on a schedule. We will implement this using Terraform.

EventBridge Lambda Cron

Setup Golang Lambda

I will gloss over the section of creating a lambda, as this is something I have covered in Golang and Python already.

For this example we will use a simple Golang Hello World example.

Create main.go, then add this golang snippet:

package main

import (
    "log"

    "context"

    "github.com/aws/aws-lambda-go/lambda"
)

func handler(ctx context.Context) error {
    log.Println("Golang Lambda executed via Eventbridge Cron")

    return nil
}

func main() {
    lambda.Start(handler)
}

Then we will run these CLI commands to create our .zip file:

# Initialise our golang project
go mod init example.com/demo
go get github.com/aws/aws-lambda-go/lambda

# If you are on a mac, let Go know you want linux
export GOOS=linux
export GOARCH=amd64
export CGO_ENABLED=0

# Build our lambda
go build -o hello

# Zip up our binary ready for terraform
zip -r function.zip hello

Setup Terraform

Create main.tf, then add this terraform snippet:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

# Configure the AWS Provider

provider "aws" {
  region = "eu-west-1"
}

This just lets terraform know that we want to use the AWS provider, and that we’ll be working in the eu-west-1 region.

You can now run this from the CLI to initialise terraform:

terraform init

Create a Lambda in Terraform

Create lambda.tf, then add this terraform snippet:

# Create out lambda, using a locally sourced zip file
resource "aws_lambda_function" "demo_lambda_hello_world" {
  function_name = "demo-lambda-hello-world"
  role          = aws_iam_role.demo_lambda_role.arn
  package_type  = "Zip"
  handler       = "hello"
  runtime       = "go1.x"

  filename         = "function.zip"
  source_code_hash = filebase64sha256("function.zip")

  depends_on = [
    aws_iam_role.demo_lambda_role
  ]

  tags = {
    Name = "Demo Lambda Hello World"
  }
}

Create IAM Role for Lambda

Our lambda will need some basic permissions to work, these might look jarring at first but they are fairly straight forward once you read through them.

Essentially, this role will be assumed by our Lambda and give it access to write to Cloudwatch Logs.

Create iam.tf, then add this terraform snippet:

# Store the AWS account_id in a variable so we can refernce it in our IAM policy
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

locals {
  account_id = data.aws_caller_identity.current.account_id
}

# Lambda IAM Role
resource "aws_iam_role" "demo_lambda_role" {
  name = "demo-lambda-role"

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

  inline_policy {
    name = "demo-lambda-policies"
    policy = jsonencode({
      "Version" : "2012-10-17",
      "Statement" : [
        {
          "Effect" : "Allow",
          "Action" : "logs:CreateLogGroup",
          "Resource" : "arn:aws:logs:${data.aws_region.current.name}:${local.account_id}:*"
        },
        {
          "Effect" : "Allow",
          "Action" : [
            "logs:CreateLogStream",
            "logs:PutLogEvents"
          ],
          "Resource" : [
            "arn:aws:logs:${data.aws_region.current.name}:${local.account_id}:log-group:/aws/lambda/*:*"
          ]
        }
      ]
    })
  }
}

Setup our Cron

Now that we have our lambda setup, we can set up the cron.

  • You can either use a cron, for example: cron(*/5 * * * ? *)
  • Or; you can use a rate, for example: rate(5 minutes)

Create eventbridge.tf, then add this terraform snippet:

# Create our schedule
resource "aws_cloudwatch_event_rule" "demo_lambda_every_5_minutes" {
  name                = "demo-lambda-every-5-minutes"
  description         = "Fires every 5 minutes"
  schedule_expression = "rate(5 minutes)"
}

# Trigger our lambda based on the schedule
resource "aws_cloudwatch_event_target" "trigger_lambda_on_schedule" {
  rule      = aws_cloudwatch_event_rule.demo_lambda_every_5_minutes.name
  target_id = "lambda"
  arn       = aws_lambda_function.demo_lambda_hello_world.arn
}

Add Lambda Permission

In order for our cron to work, we need to let our Lambda know that EventBridge is allowed to Invoke it.

Inside lambda.tf, add this terraform snippet:

resource "aws_lambda_permission" "allow_cloudwatch_to_call_split_lambda" {
  statement_id  = "AllowExecutionFromCloudWatch"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.demo_lambda_hello_world.function_name
  principal     = "events.amazonaws.com"
  source_arn    = aws_cloudwatch_event_rule.demo_lambda_every_5_minutes.arn
}

Deploy Terraform

First, run

terraform plan

Now, once we’re confident we can run:

terraform apply

This will now create the resources from the terraform files.

Confirm it’s running

After it has been running for 5 minutes you can check the Cloudwatch Logs group to confirm it has been triggered.

To confirm it is working find your Lambda in the console, then select “Monitoring”. You should now see your lambda running under “Recent invocations”:

Lambda Cron Output

Cleanup

If you were just experimenting, remember you can destroy the resources when you’re done by running:

terraform destroy

While Lambda’s are cheap to invoke, it is always good to keep your account clean to avoid any unnecessary billing.

And that’s it! You have now configured a Lambda cronjob using EventBridge.

Rhuaridh

Please get in touch through my socials if you would like to ask any questions - I am always happy to speak tech!