Go e AWS: Deploy de Aplicações na Nuvem

A AWS (Amazon Web Services) é a maior plataforma de cloud computing do mundo, oferecendo mais de 200 serviços. Quando combinada com Go, uma linguagem compilada com excelente performance e baixo consumo de recursos, você obtém uma stack poderosa e econômica para aplicações em produção.

Neste guia completo, você aprenderá desde os conceitos básicos até técnicas avançadas para deploy de aplicações Go na AWS, incluindo EC2, Lambda, S3, RDS e muito mais.

Por que usar Go na AWS?

Antes de mergulharmos nas implementações práticas, vamos entender porque Go e AWS formam uma combinação tão eficiente:

Vantagens do Go para Cloud Native

  1. Binário único: Deploy simples com um único arquivo executável
  2. Startup rápido: Ideal para serverless (Lambda) onde cada milissegundo importa
  3. Baixo consumo de memória: Econômico para serviços que rodam 24/7
  4. Concorrência nativa: Goroutines são perfeitas para serviços distribuídos
  5. Excelente ecossistema: AWS SDK for Go v2 é moderno e bem documentado

Comparação: Go vs outras linguagens na AWS

CaracterísticaGoPythonNode.jsJava
Cold Start (Lambda)~10-50ms~100-500ms~50-200ms~500ms-2s
Memória típica10-50MB50-150MB50-100MB100-500MB
Tempo de buildRápidoN/ARápidoLento
ConcorrênciaNativaGIL limitaEvent loopThreads pesadas

Configurando o AWS SDK para Go

O AWS SDK for Go v2 é a ferramenta oficial para interagir com os serviços AWS. Vamos configurá-lo corretamente.

Instalação

go get github.com/aws/aws-sdk-go-v2
go get github.com/aws/aws-sdk-go-v2/config
go get github.com/aws/aws-sdk-go-v2/credentials

Configuração de Credenciais

A AWS oferece várias formas de autenticação. A mais segura para produção é usando IAM Roles, mas para desenvolvimento local, usamos AWS CLI ou variáveis de ambiente.

Opção 1: Config file (~/.aws/credentials)

[default]
aws_access_key_id = AKIA...
aws_secret_access_key = ...
region = us-east-1

[producao]
aws_access_key_id = AKIA...
aws_secret_access_key = ...
region = sa-east-1

Opção 2: Variáveis de ambiente

export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_REGION="sa-east-1"

Criando um Cliente AWS

package main

import (
    "context"
    "log"
    
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/ec2"
)

func main() {
    ctx := context.Background()
    
    // Carrega configuração do ambiente
    cfg, err := config.LoadDefaultConfig(ctx,
        config.WithRegion("sa-east-1"),
    )
    if err != nil {
        log.Fatalf("Falha ao carregar configuração AWS: %v", err)
    }
    
    // Cria cliente EC2
    client := ec2.NewFromConfig(cfg)
    
    // Agora você pode usar o cliente
    listarInstancias(ctx, client)
}

Deploy em EC2: Máquinas Virtuais

EC2 (Elastic Compute Cloud) é o serviço de máquinas virtuais da AWS. É ideal para aplicações que precisam rodar continuamente.

Preparando sua Aplicação

Primeiro, compile sua aplicação para Linux (arquitetura x86_64 ou ARM):

# Build para EC2 (Linux x86_64)
GOOS=linux GOARCH=amd64 go build -o meuapp-linux-amd64 .

# Ou para instâncias ARM (melhor custo-benefício)
GOOS=linux GOARCH=arm64 go build -o meuapp-linux-arm64 .

Script de User Data para Bootstrap

O User Data permite configurar automaticamente a instância no primeiro boot:

#!/bin/bash

# Atualiza o sistema
apt-get update && apt-get upgrade -y

# Cria usuário para a aplicação
useradd -r -s /bin/false appuser

# Diretório da aplicação
mkdir -p /opt/meuapp

# Baixa a aplicação (de S3 ou GitHub Releases)
aws s3 cp s3://meu-bucket-deploy/meuapp-linux-amd64 /opt/meuapp/meuapp
chmod +x /opt/meuapp/meuapp

# Cria arquivo de serviço systemd
cat > /etc/systemd/system/meuapp.service << 'EOF'
[Unit]
Description=Minha Aplicacao Go
After=network.target

[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/meuapp
ExecStart=/opt/meuapp/meuapp
Restart=always
RestartSec=5
Environment=PORT=8080
Environment=AWS_REGION=sa-east-1

[Install]
WantedBy=multi-user.target
EOF

# Inicia o serviço
systemctl daemon-reload
systemctl enable meuapp
systemctl start meuapp

Criando Instância EC2 com Go

package main

import (
    "context"
    "encoding/base64"
    "fmt"
    "log"
    
    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/ec2"
    "github.com/aws/aws-sdk-go-v2/service/ec2/types"
)

func criarInstanciaEC2(ctx context.Context, client *ec2.Client) error {
    // Script de inicialização (user data)
    userData := `#!/bin/bash
apt-get update
apt-get install -y golang-go
# ... restante do setup
`
    
    userDataEncoded := base64.StdEncoding.EncodeToString([]byte(userData))
    
    input := &ec2.RunInstancesInput{
        ImageId:      aws.String("ami-0c02fb55956c7d316"), // Amazon Linux 2
        InstanceType: types.InstanceTypeT3Micro,
        MinCount:     aws.Int32(1),
        MaxCount:     aws.Int32(1),
        KeyName:      aws.String("minha-chave-ssh"),
        UserData:     aws.String(userDataEncoded),
        SecurityGroupIds: []string{"sg-xxxxxxxxxxxx"},
        SubnetId:     aws.String("subnet-xxxxxxxxxxxx"),
        TagSpecifications: []types.TagSpecification{
            {
                ResourceType: types.ResourceTypeInstance,
                Tags: []types.Tag{
                    {Key: aws.String("Name"), Value: aws.String("servidor-go")},
                    {Key: aws.String("Ambiente"), Value: aws.String("producao")},
                },
            },
        },
    }
    
    result, err := client.RunInstances(ctx, input)
    if err != nil {
        return fmt.Errorf("falha ao criar instância: %w", err)
    }
    
    fmt.Printf("Instância criada: %s\n", *result.Instances[0].InstanceId)
    return nil
}

func main() {
    ctx := context.Background()
    
    cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("sa-east-1"))
    if err != nil {
        log.Fatal(err)
    }
    
    client := ec2.NewFromConfig(cfg)
    
    if err := criarInstanciaEC2(ctx, client); err != nil {
        log.Fatal(err)
    }
}

AWS Lambda: Serverless com Go

Lambda é o serviço serverless da AWS, ideal para funções que respondem a eventos. O Go tem cold starts extremamente rápidos, tornando-o excelente para Lambda.

handler.go - Estrutura Básica

package main

import (
    "context"
    "encoding/json"
    "log"
    
    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
)

// Request representa o payload de entrada
type Request struct {
    Name string `json:"name"`
}

// Response representa a resposta
type Response struct {
    StatusCode int    `json:"statusCode"`
    Message    string `json:"message"`
}

func HandleRequest(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    var req Request
    
    if err := json.Unmarshal([]byte(request.Body), &req); err != nil {
        return events.APIGatewayProxyResponse{
            StatusCode: 400,
            Body:       `{"error": "JSON inválido"}`,
        }, nil
    }
    
    if req.Name == "" {
        req.Name = "Mundo"
    }
    
    response := Response{
        StatusCode: 200,
        Message:    "Olá, " + req.Name + "!",
    }
    
    body, _ := json.Marshal(response)
    
    return events.APIGatewayProxyResponse{
        StatusCode: 200,
        Body:       string(body),
        Headers: map[string]string{
            "Content-Type": "application/json",
        },
    }, nil
}

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

Build e Deploy via CLI

# Build para Lambda (Linux + ARM64 = melhor performance/custo)
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o bootstrap -tags lambda.norpc .

# Compacta para upload
zip function.zip bootstrap

# Deploy via AWS CLI
aws lambda create-function \
    --function-name minha-funcao-go \
    --runtime provided.al2 \
    --handler bootstrap \
    --architectures arm64 \
    --role arn:aws:iam:: conta:role/minha-role-lambda \
    --zip-file fileb://function.zip

# Atualiza função existente
aws lambda update-function-code \
    --function-name minha-funcao-go \
    --zip-file fileb://function.zip

Lambda com S3 Trigger

package main

import (
    "context"
    "fmt"
    "log"
    
    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/s3"
)

func handler(ctx context.Context, s3Event events.S3Event) error {
    cfg, err := config.LoadDefaultConfig(ctx)
    if err != nil {
        return err
    }
    
    s3Client := s3.NewFromConfig(cfg)
    
    for _, record := range s3Event.Records {
        bucket := record.S3.Bucket.Name
        key := record.S3.Object.Key
        
        log.Printf("Processando arquivo: s3://%s/%s", bucket, key)
        
        // Lógica de processamento aqui
        // Ex: thumbnail de imagem, parsing de CSV, etc.
        
        resp, err := s3Client.GetObject(ctx, &s3.GetObjectInput{
            Bucket: &bucket,
            Key:    &key,
        })
        if err != nil {
            return fmt.Errorf("falha ao ler objeto: %w", err)
        }
        defer resp.Body.Close()
        
        // Processa o arquivo...
        log.Printf("Tamanho do arquivo: %d bytes", resp.ContentLength)
    }
    
    return nil
}

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

Otimizando Cold Starts

Para minimizar o cold start em Lambda:

package main

import (
    "context"
    "sync"
    
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb"
)

// Cliente singleton para reutilização entre invocações
var (
    dynamoClient *dynamodb.Client
    once         sync.Once
)

func getDynamoClient() *dynamodb.Client {
    once.Do(func() {
        cfg, _ := config.LoadDefaultConfig(context.Background())
        dynamoClient = dynamodb.NewFromConfig(cfg)
    })
    return dynamoClient
}

// init() roda uma vez no cold start
func init() {
    // Pré-inicializa conexões
    _ = getDynamoClient()
}

func handler(ctx context.Context, event interface{}) error {
    client := getDynamoClient()
    // Usa cliente já inicializado...
}

Integração com S3: Armazenamento de Objetos

S3 (Simple Storage Service) é o serviço de armazenamento de objetos. Ideal para arquivos, backups, e assets estáticos.

Operações Básicas

package main

import (
    "context"
    "fmt"
    "io"
    "os"
    
    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/s3"
)

type S3Service struct {
    client *s3.Client
    bucket string
}

func NewS3Service(ctx context.Context, bucket string) (*S3Service, error) {
    cfg, err := config.LoadDefaultConfig(ctx)
    if err != nil {
        return nil, err
    }
    
    return &S3Service{
        client: s3.NewFromConfig(cfg),
        bucket: bucket,
    }, nil
}

// Upload de arquivo
func (s *S3Service) Upload(ctx context.Context, key string, body io.Reader) error {
    _, err := s.client.PutObject(ctx, &s3.PutObjectInput{
        Bucket:      aws.String(s.bucket),
        Key:         aws.String(key),
        Body:        body,
        ContentType: aws.String("application/octet-stream"),
    })
    return err
}

// Download de arquivo
func (s *S3Service) Download(ctx context.Context, key string, dest io.Writer) error {
    result, err := s.client.GetObject(ctx, &s3.GetObjectInput{
        Bucket: aws.String(s.bucket),
        Key:    aws.String(key),
    })
    if err != nil {
        return err
    }
    defer result.Body.Close()
    
    _, err = io.Copy(dest, result.Body)
    return err
}

// Gerar URL pré-assinada (temporária)
func (s *S3Service) GetPresignedURL(ctx context.Context, key string, expireMinutes int) (string, error) {
    presignClient := s3.NewPresignClient(s.client)
    
    req, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
        Bucket: aws.String(s.bucket),
        Key:    aws.String(key),
    }, s3.WithPresignExpires(time.Duration(expireMinutes)*time.Minute))
    
    return req.URL, err
}

// Listar objetos
func (s *S3Service) ListObjects(ctx context.Context, prefix string) ([]string, error) {
    var keys []string
    
    paginator := s3.NewListObjectsV2Paginator(s.client, &s3.ListObjectsV2Input{
        Bucket: aws.String(s.bucket),
        Prefix: aws.String(prefix),
    })
    
    for paginator.HasMorePages() {
        page, err := paginator.NextPage(ctx)
        if err != nil {
            return nil, err
        }
        
        for _, obj := range page.Contents {
            keys = append(keys, *obj.Key)
        }
    }
    
    return keys, nil
}

func main() {
    ctx := context.Background()
    
    service, err := NewS3Service(ctx, "meu-bucket-producao")
    if err != nil {
        panic(err)
    }
    
    // Upload exemplo
    file, _ := os.Open("documento.pdf")
    defer file.Close()
    
    err = service.Upload(ctx, "uploads/documento.pdf", file)
    if err != nil {
        panic(err)
    }
    
    fmt.Println("Upload concluído!")
}

RDS e DynamoDB: Conexão com Bancos de Dados

Conectando ao RDS (PostgreSQL/MySQL)

package main

import (
    "database/sql"
    "fmt"
    "log"
    
    _ "github.com/lib/pq" // PostgreSQL driver
)

func conectarRDS() (*sql.DB, error) {
    // É importante usar SSL em produção
    // Baixe o certificado: https://truststore.pki.rds.amazonaws.com/
    connStr := "host=banco.xxxxx.sa-east-1.rds.amazonaws.com " +
        "port=5432 " +
        "user=admin " +
        "password=senha-secreta " +
        "dbname=meubanco " +
        "sslmode=require"
    
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        return nil, err
    }
    
    // Testa conexão
    if err := db.Ping(); err != nil {
        return nil, err
    }
    
    // Configurações de pool
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(10)
    
    return db, nil
}

DynamoDB com Go

package main

import (
    "context"
    "fmt"
    
    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)

type Usuario struct {
    ID    string `dynamodbav:"id"`
    Nome  string `dynamodbav:"nome"`
    Email string `dynamodbav:"email"`
    Idade int    `dynamodbav:"idade"`
}

type DynamoService struct {
    client    *dynamodb.Client
    tableName string
}

func NewDynamoService(ctx context.Context, tableName string) (*DynamoService, error) {
    cfg, err := config.LoadDefaultConfig(ctx)
    if err != nil {
        return nil, err
    }
    
    return &DynamoService{
        client:    dynamodb.NewFromConfig(cfg),
        tableName: tableName,
    }, nil
}

func (s *DynamoService) PutUsuario(ctx context.Context, u Usuario) error {
    item, err := attributevalue.MarshalMap(u)
    if err != nil {
        return err
    }
    
    _, err = s.client.PutItem(ctx, &dynamodb.PutItemInput{
        TableName: aws.String(s.tableName),
        Item:      item,
    })
    
    return err
}

func (s *DynamoService) GetUsuario(ctx context.Context, id string) (*Usuario, error) {
    result, err := s.client.GetItem(ctx, &dynamodb.GetItemInput{
        TableName: aws.String(s.tableName),
        Key: map[string]types.AttributeValue{
            "id": &types.AttributeValueMemberS{Value: id},
        },
    })
    if err != nil {
        return nil, err
    }
    
    if result.Item == nil {
        return nil, fmt.Errorf("usuário não encontrado")
    }
    
    var usuario Usuario
    err = attributevalue.UnmarshalMap(result.Item, &usuario)
    return &usuario, err
}

func main() {
    ctx := context.Background()
    
    service, err := NewDynamoService(ctx, "Usuarios")
    if err != nil {
        panic(err)
    }
    
    usuario := Usuario{
        ID:    "user-123",
        Nome:  "João Silva",
        Email: "joao@exemplo.com",
        Idade: 30,
    }
    
    if err := service.PutUsuario(ctx, usuario); err != nil {
        panic(err)
    }
    
    fmt.Println("Usuário salvo com sucesso!")
}

Melhores Práticas para Deploy em Produção

1. Health Checks e Graceful Shutdown

package main

import (
    "context"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
    
    "github.com/gorilla/mux"
)

func main() {
    router := mux.NewRouter()
    
    // Health check para Load Balancer
    router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        // Verifica conexões de banco, etc.
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"status": "ok"}`))
    }).Methods("GET")
    
    srv := &http.Server{
        Addr:    ":8080",
        Handler: router,
    }
    
    // Graceful shutdown
    stop := make(chan os.Signal, 1)
    signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
    
    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("Erro no servidor: %v", err)
        }
    }()
    
    <-stop
    
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    
    if err := srv.Shutdown(ctx); err != nil {
        log.Printf("Erro no shutdown: %v", err)
    }
}

2. Observabilidade (CloudWatch)

import "github.com/aws/aws-sdk-go-v2/service/cloudwatch"

func emitirMétrica(ctx context.Context, client *cloudwatch.Client, valor float64) {
    _, err := client.PutMetricData(ctx, &cloudwatch.PutMetricDataInput{
        Namespace: aws.String("MinhaApp/Go"),
        MetricData: []types.MetricDatum{
            {
                MetricName: aws.String("RequisicoesPorSegundo"),
                Value:      aws.Float64(valor),
                Unit:       types.StandardUnitCount,
                Dimensions: []types.Dimension{
                    {
                        Name:  aws.String("Ambiente"),
                        Value: aws.String("producao"),
                    },
                },
            },
        },
    })
}

3. Segurança

  • Nunca commit credenciais: Use AWS Secrets Manager ou IAM Roles
  • Criptografia em trânsito: Sempre use HTTPS
  • Security Groups: Restrinja acesso apenas ao necessário
  • Atualizações regulares: Mantenha a AMI e pacotes atualizados
// Exemplo: Ler segredo do Secrets Manager
func getSecret(ctx context.Context, secretName string) (string, error) {
    cfg, _ := config.LoadDefaultConfig(ctx)
    client := secretsmanager.NewFromConfig(cfg)
    
    result, err := client.GetSecretValue(ctx, &secretsmanager.GetSecretValueInput{
        SecretId: aws.String(secretName),
    })
    if err != nil {
        return "", err
    }
    
    return *result.SecretString, nil
}

Pipeline CI/CD com GitHub Actions

# .github/workflows/deploy-aws.yml
name: Deploy to AWS

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Go
      uses: actions/setup-go@v4
      with:
        go-version: '1.21'
    
    - name: Run tests
      run: go test -v ./...
    
    - name: Build
      run: |
        GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o app .
    
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: sa-east-1
    
    - name: Deploy to S3 (para EC2 pull)
      run: |
        aws s3 cp app s3://meu-bucket-deploy/app-${{ github.sha }}
    
    - name: Deploy to Lambda (se for função Lambda)
      run: |
        zip function.zip app
        aws lambda update-function-code \
          --function-name minha-funcao \
          --zip-file fileb://function.zip

Custos e Otimização

EC2 vs Lambda: Quando usar cada um?

CenárioMelhor OpçãoPor quê?
API com tráfego constanteEC2 (ou ECS/EKS)Mais barato para uso contínuo
Processamento de filasLambdaEscala automaticamente
Batch jobs agendadosLambda + CloudWatch EventsPaga apenas pelo tempo de execução
Aplicação complexaECS/FargateMelhor controle sem gerenciar servidores

Dicas de economia:

  1. Instâncias Spot: Até 90% mais barato para cargas tolerantes a interrupções
  2. Reserved Instances: Compromisso de 1-3 anos para desconto de até 75%
  3. Lambda: Configure memória corretamente (mais memória = mais CPU)
  4. S3: Use lifecycle policies para arquivar dados antigos

Exemplo Completo: Aplicação RESTful

Aqui está uma aplicação completa que combina vários serviços AWS:

// cmd/api/main.go
package main

import (
    "log"
    "net/http"
    
    "github.com/gorilla/mux"
)

type Server struct {
    db    *sql.DB
    s3    *S3Service
    cache *CacheService
}

func main() {
    ctx := context.Background()
    
    // Inicializa serviços
    db, err := conectarRDS()
    if err != nil {
        log.Fatal(err)
    }
    
    s3, err := NewS3Service(ctx, os.Getenv("S3_BUCKET"))
    if err != nil {
        log.Fatal(err)
    }
    
    server := &Server{db: db, s3: s3}
    
    // Rotas
    router := mux.NewRouter()
    router.HandleFunc("/health", server.healthCheck).Methods("GET")
    router.HandleFunc("/users", server.createUser).Methods("POST")
    router.HandleFunc("/users/{id}", server.getUser).Methods("GET")
    router.HandleFunc("/upload", server.uploadFile).Methods("POST")
    
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }
    
    log.Printf("Servidor iniciando na porta %s", port)
    log.Fatal(http.ListenAndServe(":"+port, router))
}

Conclusão

A combinação de Go e AWS oferece uma plataforma poderosa para aplicações em nuvem. A simplicidade do Go, com seu binário único e startup rápido, casa perfeitamente com a flexibilidade dos serviços AWS.

Próximos Passos

  1. Experimente o AWS Free Tier (gratuito por 12 meses)
  2. Comece com Lambda para experimentar sem custos fixos
  3. Use Elastic Beanstalk para deployments simples de aplicações web
  4. Explore AWS CDK para infraestrutura como código
  5. Monitore seus custos no AWS Billing Dashboard

Recursos Adicionais


Tem dúvidas sobre deploy na AWS? A AWS oferece créditos gratuitos para startups através do programa AWS Activate.