Skip to main content

How to Speedrun Setting Up a Go Lambda Function with Terraform

·4 mins·

Go seems like the perfect language for Lambdas; simple, concise, and compiled. In this we will:

  • Create our go lambda function.
  • Use terraform to spin up our resources.
  • Use the event bridge to create a cron of our lambda being called every minute.
  • Be able to view our lambda being triggered in cloudwatch.

I don’t offer money-back guarantees around here, but getting this up and running is as quick as removing the open-source license from software that people have built companies on.

Hide yo face
Not aimed at anyone in particular, especially someone who’s just sold to IBM.

Let’s get into it.

Step 0: Prerequisties #

  • You will need to have Terraform setup
  • A working AWS account
  • go installed locally

Step 1: Prep Your Go Code #

First things first, let’s get your Go function ready. Create a new file named main.go in your project directory.

main.go:

package main

import (
    "github.com/aws/aws-lambda-go/lambda"
    "log"
)

func handler() error {
    log.Println("Hello from Lambda!")
    return nil
}

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

This function does nothing more than log a message, but it’s a good start. Next steps:

# mac skill issue inbound
GOOS=linux 

# compile
go build -o bootstrap main.go
zip function.zip bootstrap

Step 2: Set the Stage with Terraform #

Personally I like to create a directory in my project called .terraform/ but feel free to use your base directory in the project. You’ll want a few files to manage this setup properly: main.tf, variables.tf, lambda.tf, eventbridge.tf.

main.tf Setup of generic terraform information for the project.

terraform {
  required_version = ">= 0.14"

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

provider "aws" {
  region = var.region
}

data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

variables.tf Declare your essentials here.

variable "region" {
  description = "AWS region to deploy resources"
  type        = string
  default     = "eu-west-2"
}

variable "function_name" {
  description = "Name of our lambda function"
  type        = string
  default     = "getting-started-with-lambda"
}

variable "lambda_role_name" {
  description = "IAM role name for our Lambda function"
  type        = string
  default     = "go-lambda-role"
}

variable "event_rule_name" {
  description = "Name for the EventBridge rule"
  type        = string
  default     = "go-lambda-every-minute"
}

variable "event_description" {
  description = "Description of what the EventBridge rule does"
  type        = string
  default     = "Fires every minute"
}

variable "event_schedule_expression" {
  description = "Schedule expression for the EventBridge rule"
  type        = string
  default     = "rate(1 minute)"
}

iam.tf Define your IAM role that the Lambda will use.

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

resource "aws_iam_role" "go_lambda_role" {
  name = var.lambda_role_name

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

  inline_policy {
    name = "go-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/*:*"
          ]
        }
      ]
    })
  }
}

lambda.tf Here’s where the magic happens. Define your Lambda function resource.

resource "aws_lambda_function" "go_lambda_function" {
  function_name = var.function_name
  role          = aws_iam_role.go_lambda_role.arn
  package_type  = "Zip"
  handler       = "bootstrap"
  runtime       = "provided.al2023"
  # This will need changing if you don't create a subdirectory for terraform
  filename         = "../function.zip"
  source_code_hash = filebase64sha256("../function.zip")

  depends_on = [
    aws_iam_role.go_lambda_role
  ]

  tags = {
    Name = "Go Lambda Example"
  }
}

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

eventbridge.tf Set up the event bridge to run your lambda function on a one minute schedule.

resource "aws_cloudwatch_event_rule" "go_lambda_every_minute" {
  name                = var.event_rule_name
  description         = var.event_description
  schedule_expression = var.event_schedule_expression
}

resource "aws_cloudwatch_event_target" "trigger_lambda_on_schedule" {
  rule      = aws_cloudwatch_event_rule.go_lambda_every_minute.name
  target_id = "lambda"
  arn       = aws_lambda_function.go_lambda_function.arn
}

Step 3: Deploy #

Firstly you will need to initalize your terraform in the repository.

terraform init

and you will then need to plan and apply

terraform plan # review the above after running
terraform apply # select yes when happy to apply & deploy

Step 4: Check the logs! #

You should now be able to view the lambda function being triggered under the cloud watch log group we created.

Cloudwatch Logs

Step 5: Destroy #

Although this is all using the free tier, make sure to tear always destroy your aws resources when they’re not in use.

terraform destory #select yes

Hope you enjoyed.