My First go Lambda Function

Background

Updated post and migrated to Hugo Site.

Python is an easy to read language and has great support and libraries in the developer community. Most of my AWS Lambda functions are written in Python.

However, some of my recent projects revolve around EKS and I have been looking at more code in Go and decided to learn it.

The first personal project that came to mind was trying out an AWS Lambda function written in go using the latest AWS SDK for Go V2.

This post is all about deploying my first go Lambda function, there is no pros and cons or comparison between Python and go.

All my previous post have been using Serverless, which I love to use for personal projects. however, this deployment I am going to do using Terraform.

As I get more comfortable with Go, I might convert my other Python Lambda functions to Go to get a better grasp of the language.

Requirements

I do most of my work on my Ubuntu workstation and have setup a local AWS profile to access my AWS account. ensure, you have an AWS account and configured your credentials to access AWS.

Download and Install Terraform.

In my demonstration I have already configured an AWS profile called ‘automation’. You can either create a profile or assume a role or simply modify the provider.tf file and add your AWS credentials. See Access.

Install Go on your workstation.

Procedure

There are two steps involved in publishing your Lambda function

  • Compiling your Go code into a binary
  • Deploying the binary using Terraform

Note: Running this demo will incur some cost on AWS. You are responsible for all charges incurred in your account. You can lower your cost by cleaning up and removing all resources after you are done.

Instead of just writing a Hello world, I wanted to use the SDK to query some AWS resources, so the first go Lambda is going to attempt in listing all running AWS EC2 instances in my account.

Github Link

You can download the code for this post from my github repository.

If you are new to Go, checkout some of the Go Tutorials and Go Docs.

This is my first attempt at Go, I may not have followed all the Go rules, formatting, error handling correctly. As I learn more, I will surely update my code and this post.

Feel free to comment and provide feedback on this post!

main.go

The init function is where the EC2 client is configured to run in us-east-1.


func init() {
	cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion("us-east-1"))
	if err != nil {
		panic("configuration error, " + err.Error())
	}

	client = ec2.NewFromConfig(cfg)
}

In a future update, will try to find a way to pass that as a parameter instead of a fixed string.

As per AWS docs, setting the package to be main and the main function has the HandleRequest call. I dont have an event being passed, so decided to leave it out.

Within the HandleRequest function, set up a DescribeInstances call with a filter to list only running EC2 instances.

	result, err := client.DescribeInstances(context.TODO(), &ec2.DescribeInstancesInput{
		Filters: []types.Filter{
			{
				Name: aws.String("instance-state-name"),
				Values: []string{
					"running",
				},
			},
		},
	})

Additional filters or tags could be added by you to narrow down the result if needed.

Then loop through the result to print the EC2 instance information.

	var status []string
	for _, r := range result.Reservations {
		for _, ins := range r.Instances {
			status = append(status, fmt.Sprintf("InstanceID: %v State: %v", *ins.InstanceId, ins.State.Name))
		}

	}

It would be nice to save this information in a DynamoDB table for audit or e-mail using SNS, but since this is my first attempt, I kept this function really simple.

cd into the <project root>/go build main.go folder and

Compile the code by running go build main.go which should create a binary main.

cd back to the <project root> folder.

Terraform

Now that we have created the binary for our AWS Lambda function, we can move on to the Terraform steps.

main.tf

  • In this file, we create a zip archive with our go binary.
  • The resource ‘aws_lambda_function’ creates our AWS Lambda function with the zip file as its source and other settings to indicate the runtime is go.
  • I also create a Cloudwatch Event rule to run the function every 30 min.
  • The resource ‘aws_lambda_permission’ sets up Events to invoke our AWS Lambda function.

iam.tf

  • In this file, we setup permission for Lambda to work with CloudWatch logs and the ability to Describe Instances.

versions.tf

  • Used to set my provider versions.

Init, plan and apply

Go ahead and run a Terraform Init next.

terraform init

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching ">= 4.0.0"...
- Finding hashicorp/null versions matching ">= 3.0.0"...
- Finding latest version of hashicorp/archive...
- Installing hashicorp/aws v4.19.0...
- Installed hashicorp/aws v4.19.0 (signed by HashiCorp)
- Installing hashicorp/null v3.1.1...
- Installed hashicorp/null v3.1.1 (signed by HashiCorp)
- Installing hashicorp/archive v2.2.0...
- Installed hashicorp/archive v2.2.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

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.

Followed by a Terraform plan. If the plan is good, you can go ahead and run an apply, which will create the AWS resources.

terraform apply -auto-approve

Abbreviated output

  # aws_lambda_permission.first_go_lamabda_perms will be created
  + resource "aws_lambda_permission" "first_go_lamabda_perms" {
      + action              = "lambda:InvokeFunction"
      + function_name       = "first-go-lambda"
      + id                  = (known after apply)
      + principal           = "events.amazonaws.com"
      + source_arn          = (known after apply)
      + statement_id        = (known after apply)
      + statement_id_prefix = (known after apply)
    }

Plan: 7 to add, 0 to change, 0 to destroy.
aws_cloudwatch_event_rule.every_30_min: Creating...
aws_iam_policy.first_go_lambda_ec2: Creating...
aws_iam_role.first_go_lambda_role: Creating...
aws_iam_policy.first_go_lambda_ec2: Creation complete after 1s [id=arn:aws:iam::0123456789012:policy/first-go-lambda-policy]
aws_cloudwatch_event_rule.every_30_min: Creation complete after 1s [id=first-go-lambda-30-min]
aws_iam_role.first_go_lambda_role: Creation complete after 1s [id=first-go-lambda-role]
aws_iam_role_policy_attachment.attach_ec2_policy: Creating...
aws_lambda_function.running_ec2: Creating...
aws_iam_role_policy_attachment.attach_ec2_policy: Creation complete after 0s [id=first-go-lambda-role-20220619135057044700000001]
aws_lambda_function.running_ec2: Still creating... [10s elapsed]
aws_lambda_function.running_ec2: Creation complete after 14s [id=first-go-lambda]
aws_lambda_permission.first_go_lamabda_perms: Creating...
aws_cloudwatch_event_target.first_go_lambda_target: Creating...
aws_lambda_permission.first_go_lamabda_perms: Creation complete after 0s [id=terraform-20220619135110963400000002]
aws_cloudwatch_event_target.first_go_lambda_target: Creation complete after 0s [id=first-go-lambda-30-min-terraform-20220619135110967900000003]

Testing

You can either wait for the function to run, or you can invoke it from the command line as shown below to confirm the function was deployed correctly.


aws lambda invoke --function-name arn:aws:lambda:us-east-1:0123456789012:function:first-go-lambda response.json && cat response.json
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}
["InstanceID: i-0777e6b054614xxxx State: running"]

Summary

Using Terraform, it was quite easy to deploy the Lambda function using Go runtime.

Let me know by commenting below, if you need any clarification with this post. Also if no longer required, you can run ‘terraform destroy‘ to remove all the resources that were created. Leaving resources in your account will incurr cost.

Further Reading

See my other posts related to Lambda functions.

Terraform Docs
Terraform Access
AWS IAM

Photo Credit

Photo by Markus Spiske on Unsplash

Leave a Reply