Introduction
Deploying serverless applications has become increasingly popular due to its scalability, cost-efficiency, and ease of management. Amazon Web Services (AWS) Lambda provides a powerful serverless computing platform, while Go (Golang) offers a performant and statically typed programming language.
In this article, we will explore how to deploy Go-based applications to AWS Lambda using the seamless integration of GitHub Actions and Terraform. By combining these tools, developers can automate the deployment process, streamline version control, and ensure consistent infrastructure as code practices for their serverless projects.
Prerequisites
Before we begin, you will need to have the following prerequisites:
- An AWS account
- A GitHub account
- AWS CLI installed and configured with your AWS credentials (see the AWS documentation for instructions)
- Go installed (see the Go documentation for instructions)
- Terraform installed (see the Terraform documentation for instructions)
Step 1: Create a GitHub repository
First of all, create a GitHub repository for your Go application.
Go to GitHub and click on "New repository". Enter a name for your repository and click on "Create repository".
Step 2: Create a Go application
In an empty directory, create a Go application:
go mod init go_lambda
This will create a go.mod
file that will be used to manage dependencies for your Go application.
Next, initialize the Git repository:
git init
Next, create a .gitignore
file:
# .gitignore
**/.terraform/*
*.tfstate
bootstrap
lambda-handler.zip
Lastly, set the remote origin:
git remote add origin <YOUR_REPOSITORY_URL>
Step 3: Create a main.go file
Next, create the main.go file:
// main.go
package main
import (
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
func hello() (events.APIGatewayProxyResponse, error) {
return events.APIGatewayProxyResponse{
Body: "Hello World!",
StatusCode: 200,
}, nil
}
func main() {
// Make the handler available for Remote Procedure Call by AWS Lambda
lambda.Start(hello)
}
This file contains the minimal code required to create a Go application that can be deployed to AWS Lambda. It imports the events
and lambda
packages from the aws-lambda-go
library and defines a hello
function that returns a response that our API Gateway will return to the client. The main
function is the entry point for our application and calls the hello
function. The lambda.Start
function starts the AWS Lambda handler1.
Step 4: Install dependencies
Next, install the dependencies for your Go application:
go get github.com/aws/aws-lambda-go/events
go get github.com/aws/aws-lambda-go/lambda
This will install aws-lambda-go/events, which contains the APIGatewayProxyResponse
struct, and aws-lambda-go/lambda, which contains the lambda.Start
function.
Step 5: Create the remote backend
We will use S3 as our remote backend for Terraform. This is necessary because we will be using GitHub Actions to deploy our code and we need to store the Terraform state in a remote location.
NOTE We will not be creating a DynamoDB table for state locking to keep things simple, but you should consider doing this if you are deploying to production. You can read more about this in the Terraform documentation.
To create the S3 bucket for our remote backend, we will use the following command:
aws s3api create-bucket --bucket <YOUR_BUCKET_NAME> --region us-east-1
Alternatively, you can create the bucket using the AWS Console following the instructions in the AWS documentation.
Step 6: Creating the Terraform configuration
Now that we have created our Go application and installed the dependencies, we can create the Terraform configuration. We will use the following Terraform configuration to deploy our Go application to AWS Lambda:
# providers.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.7.0"
}
}
}
# backend.tf
terraform {
backend "s3" {
# Replace this with your bucket name!
bucket = "<YOUR_BUCKET_NAME>"
key = "go-lambda-test.tfstate"
region = "us-east-1"
}
}
# main.tf
resource "aws_lambda_function" "go_function" {
filename = "lambda-handler.zip"
function_name = "go-lambda-test"
handler = "bootstrap"
role = aws_iam_role.iam_for_lambda.arn
source_code_hash = filebase64sha256("lambda-handler.zip")
runtime = "go1.x"
}
data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
actions = ["sts:AssumeRole"]
}
}
resource "aws_iam_role" "iam_for_lambda" {
name = "iam_for_lambda"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
# API Gateway
resource "aws_api_gateway_rest_api" "go_api" {
name = "go_api"
description = "This is my API for demonstration purposes"
}
resource "aws_api_gateway_resource" "go_api_resource" {
rest_api_id = aws_api_gateway_rest_api.go_api.id
parent_id = aws_api_gateway_rest_api.go_api.root_resource_id
path_part = "test"
}
resource "aws_api_gateway_method" "go_api_method" {
rest_api_id = aws_api_gateway_rest_api.go_api.id
resource_id = aws_api_gateway_resource.go_api_resource.id
http_method = "GET"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "go_api_integration" {
rest_api_id = aws_api_gateway_rest_api.go_api.id
resource_id = aws_api_gateway_resource.go_api_resource.id
http_method = aws_api_gateway_method.go_api_method.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.go_function.invoke_arn
}
resource "aws_api_gateway_method" "proxy_root" {
rest_api_id = aws_api_gateway_rest_api.go_api.id
resource_id = aws_api_gateway_rest_api.go_api.root_resource_id
http_method = "ANY"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "proxy_root_integration" {
rest_api_id = aws_api_gateway_rest_api.go_api.id
resource_id = aws_api_gateway_rest_api.go_api.root_resource_id
http_method = aws_api_gateway_method.proxy_root.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.go_function.invoke_arn
}
resource "aws_api_gateway_deployment" "go_api_deployment" {
depends_on = [
aws_api_gateway_integration.go_api_integration,
aws_api_gateway_integration.proxy_root_integration,
]
rest_api_id = aws_api_gateway_rest_api.go_api.id
stage_name = "test"
}
resource "aws_lambda_permission" "apigw" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.go_function.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.go_api.execution_arn}/*/*"
}
Step 7: Configure AWS credentials for GitHub Actions
To configure AWS credentials for GitHub Actions, we will use OIDC federation. This is a secure way to authenticate with AWS without having to store your AWS credentials in your GitHub repository.
We will need to follow the following steps to configure AWS credentials for GitHub Actions:
- Open the AWS Console and navigate to the IAM service.
- Click on "Identity providers" in the left navigation bar.
- Click on "Add provider".
- Select "OpenID Connect" as the provider type.
- For "Provider URL", enter
https://token.actions.githubusercontent.com
2. - Click on "Get thumbprint".
- For "Audience", enter
sts.amazonaws.com
2. - Click on "Add provider".
Next, we will need to create an IAM role for GitHub Actions. We will need to follow the following steps to create an IAM role for GitHub Actions:
- Click on the provider you just created. It should be called "token.actions.githubusercontent.com".
- Click on "Assign role".
- Select "Create a new role" and click on "Next".
- For "Identity provider", select "token.actions.githubusercontent.com".
- For Audience, enter
sts.amazonaws.com
. - Click on "Next: Permissions".
- Select "AmazonS3FullAccess", "AWSLambdaFullAccess", "IAMFullAccess", and "AmazonAPIGatewayAdministrator" and click on "Next: Tags".
- Click on "Next: Review".
- For "Role name", enter
github-actions
and click on "Create role". - Search for the role you just created and click on it.
- Copy the "ARN" (It should look something like
arn:aws:iam::000000000000:role/github-actions
). You will need this in the next step.
NOTE: You should consider using a more restrictive IAM policy for your GitHub Actions IAM role.
Step 8: Create the GitHub Actions workflow
Next, we will create the GitHub Actions workflow. We will use the following workflow to deploy our Go application to AWS Lambda:
Do not forget to replace <IAM_ROLE>
with the IAM role ARN you created in the previous step.
# .github/workflows/deploy.yml
name: 'Deploy'
on:
push:
branches: [ "main" ]
pull_request:
workflow_dispatch:
permissions:
id-token: write
contents: read
jobs:
terraform:
name: 'Terraform'
runs-on: ubuntu-latest
environment: production
defaults:
run:
shell: bash
steps:
# Checkout the repository to the GitHub Actions runner
- name: Checkout
uses: actions/checkout@v3
# Configure AWS Credentials
# You will need to replace <IAM_ROLE> with the IAM role ARN you created in the previous step
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: <IAM_ROLE>
aws-region: us-east-1
- name: Setup Terraform
uses: hashicorp/setup-terraform@v1
- name: Setup Go environment
uses: actions/setup-go@v4.0.1
# This step builds the Go application and creates a zip file containing the binary
# It is important to note that the binary must be named "bootstrap"
- name: Build Go application
run: |
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o bootstrap main.go
zip lambda-handler.zip bootstrap
# Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc.
- name: Terraform Init
run: terraform init
- name: Terraform Format
run: terraform fmt -check
- name: Terraform Plan
run: terraform plan -input=false
- name: Output ref and event_name
run: |
echo ${{github.ref}}
echo ${{github.event_name}}
# On push to "main", build or change infrastructure according to Terraform configuration files
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve -input=false
- name: Output API Gateway invocation URL
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: |
terraform output api_endpoint
Step 9: Deploy the Go application to AWS Lambda
Now that we have created the GitHub Actions workflow, we can push our code to GitHub and let GitHub Actions deploy our Go application to AWS.
git add .
git commit -m "Deploy Go application to AWS Lambda"
git push --set-upstream origin main
You can view the GitHub Actions workflow by going to the "Actions" tab in your GitHub repository.
Step 10: Test the API
Now that we have deployed our Go application to AWS Lambda, we can get the API Gateway invocation URL by going to the "Actions" tab in your GitHub repository and clicking on the latest workflow run. Then seeing the output of the "Output API Gateway invocation URL" step.
If we make a request to the API Gateway invocation URL, we should see the following response:
Conclusion
In this article, we explored how to deploy Go-based applications to AWS Lambda using GitHub Actions and Terraform. By combining these tools, developers can automate the deployment process, streamline version control, and ensure consistent infrastructure as code practices for their serverless projects.
You can find the source code for this article in the GitHub repository.
Top comments (0)