Desplegando Go en AWS Lambda con GitHub Actions y Terraform

Introducci贸n

El despliegue de aplicaciones serverless se ha vuelto cada vez m谩s popular debido a su escalabilidad, eficiencia en costos y facilidad de gesti贸n. Amazon Web Services (AWS) Lambda proporciona una poderosa plataforma de computaci贸n sin servidor, mientras que Go (Golang) ofrece un lenguaje de programaci贸n de alto rendimiento y tipado est谩tico.

En este art铆culo, exploraremos c贸mo desplegar aplicaciones basadas en Go en AWS Lambda integrando GitHub Actions y Terraform. Al combinar estas herramientas, los desarrolladores pueden automatizar el proceso de despliegue, simplificar el control de versiones y asegurar pr谩cticas consistentes de infraestructura como c贸digo para sus proyectos sin servidor.

Prerrequisitos

Antes de comenzar, necesitar谩s lo siguiente:

Paso 1: Crear un repositorio GitHub

En primer lugar, crea un repositorio GitHub para tu aplicaci贸n en Go.

Ve a GitHub y haz clic en “New repository”. Ingresa un nombre para tu repositorio y haz clic en “Create repository”.

Paso 2: Crear una aplicaci贸n Go

En un directorio vac铆o, crea una aplicaci贸n Go:

go mod init go_lambda

Esto crear谩 un archivo go.mod que se usar谩 para gestionar las dependencias de tu aplicaci贸n en Go.

Luego, inicializa el repositorio Git:

git init

Despu茅s, crea un archivo .gitignore:

# .gitignore
**/.terraform/*
*.tfstate
bootstrap
lambda-handler.zip

Por 煤ltimo, establece el origen remoto:

git remote add origin <TU_URL_DEL_REPOSITORIO>

Paso 3: Crear un archivo main.go

Crea el archivo main.go:

// 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() {
 // Hacer que el controlador est茅 disponible para llamadas a procedimientos remotos por AWS Lambda
 lambda.Start(hello)
}

Este archivo contiene el c贸digo m铆nimo necesario para crear una aplicaci贸n Go que se pueda desplegar en AWS Lambda. Importa los paquetes events y lambda de la biblioteca aws-lambda-go y define una funci贸n hello que devuelve una respuesta que nuestra API Gateway retornar谩 al cliente. La funci贸n main es el punto de entrada para nuestra aplicaci贸n y llama a la funci贸n hello. La funci贸n lambda.Start inicia el controlador de AWS Lambda1.

Paso 4: Instalar dependencias

Luego, instala las dependencias para tu aplicaci贸n Go:

go get github.com/aws/aws-lambda-go/events
go get github.com/aws/aws-lambda-go/lambda

Esto instalar谩 aws-lambda-go/events, que contiene la estructura APIGatewayProxyResponse, y aws-lambda-go/lambda, que contiene la funci贸n lambda.Start.

Paso 5: Crear el backend remoto

Usaremos S3 como nuestro backend remoto para Terraform. Esto es necesario porque estaremos usando GitHub Actions para desplegar nuestro c贸digo y necesitamos almacenar el estado de Terraform en una ubicaci贸n remota.

NOTA No estaremos creando una tabla DynamoDB para el bloqueo de estado para mantener las cosas simples, pero deber铆as considerar hacer esto si est谩s desplegando a producci贸n. Puedes leer m谩s sobre esto en la documentaci贸n de Terraform.

Para crear el bucket S3 para nuestro backend remoto, usaremos el siguiente comando:

aws s3api create-bucket --bucket <TU_NOMBRE_DE_BUCKET> --region us-east-1

Alternativamente, puedes crear el bucket utilizando la Consola de AWS siguiendo las instrucciones en la documentaci贸n de AWS.

Paso 6: Crear la configuraci贸n de Terraform

Ahora que hemos creado nuestra aplicaci贸n Go y hemos instalado las dependencias, podemos crear la configuraci贸n de Terraform. Usaremos la siguiente configuraci贸n de Terraform para desplegar nuestra aplicaci贸n Go en AWS Lambda:

# providers.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.7.0"
    }
  }
}
# backend.tf
terraform {
  backend "s3" {
    # 隆Reemplaza esto con el nombre de tu bucket!
    bucket = "<TU_NOMBRE_DE_BUCKET>"
    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 = "Esta es mi API para fines de demostraci贸n"
}

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}/*/*"
}

Paso 7: Configurar credenciales de AWS para GitHub Actions

Para configurar credenciales de AWS para GitHub Actions, usaremos federaci贸n OIDC. Esta es una forma segura de autenticarse con AWS sin tener que almacenar tus credenciales de AWS en tu repositorio de GitHub.

Necesitaremos seguir los siguientes pasos para configurar credenciales de AWS para GitHub Actions:

  1. Abre la Consola de AWS y navega al servicio IAM.
  2. Haz clic en “Identity providers” en la barra de navegaci贸n izquierda.
  3. Haz clic en “Add provider”.
  4. Selecciona “OpenID Connect” como el tipo de proveedor.
  5. Para “Provider URL”, ingresa https://token.actions.githubusercontent.com2.
  6. Haz clic en “Get thumbprint”.
  7. Para “Audience”, ingresa sts.amazonaws.com2.
  8. Haz clic en “Add provider”.

Luego, necesitaremos crear un rol IAM para GitHub Actions. Necesitaremos seguir los siguientes pasos para crear un rol IAM para GitHub Actions:

  1. Haz clic en el proveedor que acabas de crear. Deber铆a llamarse “token.actions.githubusercontent.com”.
  2. Haz clic en “Assign role”.
  3. Selecciona “Create a new role” y haz clic en “Next”.
  4. Para “Identity provider”, selecciona “token.actions.githubusercontent.com”.
  5. Para Audience, ingresa sts.amazonaws.com.
  6. Haz clic en “Next: Permissions”.
  7. Selecciona “AmazonS3FullAccess”, “AWSLambdaFullAccess”, “IAMFullAccess”, y “AmazonAPIGatewayAdministrator” y haz clic en “Next: Tags”.
  8. Haz clic en “Next: Review”.
  9. Para “Role name”, ingresa github-actions y haz clic en “Create role”.
  10. Busca el rol que acabas de crear y haz clic en 茅l.
  11. Copia el “ARN” (Deber铆a parecer algo como arn:aws:iam::000000000000:role/github-actions). Lo necesitar谩s en el siguiente paso.

NOTA: Considera usar una pol铆tica IAM m谩s restrictiva para tu rol IAM de GitHub Actions.

Paso 8: Crear el flujo de trabajo de GitHub Actions

Luego, crearemos el flujo de trabajo de GitHub Actions. Usaremos el siguiente flujo de trabajo para desplegar nuestra aplicaci贸n Go en AWS Lambda:

No olvides reemplazar <ROL_IAM> con el ARN del rol IAM que has creado en el paso anterior.

# .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: producci贸n

    defaults:
      run:
        shell: bash

    steps:
    # Checkout de repositorio al runner de GitHub Actions
    - name: Checkout
      uses: actions/checkout@v3
      
    # Configurar credenciales de AWS
    # Necesitar谩s reemplazar <ROL_IAM> con el ARN del rol IAM que creaste en el paso anterior
    - name: Configure AWS Credentials
      uses: aws-actions/configure-aws-credentials@v2
      with:
        role-to-assume: <ROL_IAM>
        aws-region: us-east-1
      
    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v1
        
    - name: Configurar entorno Go
      uses: actions/setup-go@v4.0.1

    # Este paso construye la aplicaci贸n Go y crea un archivo zip que contiene el binario
    # Es importante notar que el binario debe llamarse "bootstrap"
    - name: Construir aplicaci贸n Go
      run: |
        GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o bootstrap main.go
        zip lambda-handler.zip bootstrap        

    # Inicializar un directorio de trabajo de Terraform nuevo o existente creando archivos iniciales, cargando cualquier estado remoto, descargando m贸dulos, 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 y event_name
      run: |
        echo ${{github.ref}}
        echo ${{github.event_name}}        

      # Al hacer push a "main", construir o cambiar la infraestructura seg煤n los archivos de configuraci贸n de Terraform
    - name: Terraform Apply
      if: github.ref == 'refs/heads/main' && github.event_name == 'push'
      run: terraform apply -auto-approve -input=false

    - name: Output URL de invocaci贸n de API Gateway
        if: github.ref == 'refs/heads/main' && github.event_name == 'push'
        run: |
          terraform output api_endpoint          

Paso 9: Desplegar la aplicaci贸n Go en AWS Lambda

Ahora que hemos creado el flujo de trabajo de GitHub Actions, podemos subir nuestro c贸digo a GitHub y dejar que GitHub Actions despliegue nuestra aplicaci贸n Go en AWS.

git add .
git commit -m "Desplegar aplicaci贸n Go en AWS Lambda"
git push --set-upstream origin main

Puedes ver el flujo de trabajo de GitHub Actions yendo a la pesta帽a “Actions” en tu repositorio de GitHub.

Paso 10: Probar la API

Ahora que hemos desplegado nuestra aplicaci贸n Go en AWS Lambda, podemos obtener la URL de invocaci贸n de API Gateway yendo a la pesta帽a “Actions” en tu repositorio de GitHub y haciendo clic en la 煤ltima ejecuci贸n del flujo de trabajo. Luego, viendo la salida del paso “Output API Gateway invocation URL”.

Si hacemos una solicitud a la URL de invocaci贸n de API Gateway, deber铆amos ver la siguiente respuesta:

Salida de GitHub Actions

Conclusi贸n

En este art铆culo, exploramos c贸mo desplegar aplicaciones basadas en Go en AWS Lambda usando GitHub Actions y Terraform. Al combinar estas herramientas, los desarrolladores pueden automatizar el proceso de despliegue, simplificar el control de versiones y asegurar pr谩cticas consistentes de infraestructura como c贸digo para sus proyectos sin servidor.

Puedes encontrar el c贸digo fuente para este art铆culo en el repositorio de GitHub.