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
- Binário único: Deploy simples com um único arquivo executável
- Startup rápido: Ideal para serverless (Lambda) onde cada milissegundo importa
- Baixo consumo de memória: Econômico para serviços que rodam 24/7
- Concorrência nativa: Goroutines são perfeitas para serviços distribuídos
- Excelente ecossistema: AWS SDK for Go v2 é moderno e bem documentado
Comparação: Go vs outras linguagens na AWS
| Característica | Go | Python | Node.js | Java |
|---|---|---|---|---|
| Cold Start (Lambda) | ~10-50ms | ~100-500ms | ~50-200ms | ~500ms-2s |
| Memória típica | 10-50MB | 50-150MB | 50-100MB | 100-500MB |
| Tempo de build | Rápido | N/A | Rápido | Lento |
| Concorrência | Nativa | GIL limita | Event loop | Threads 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ário | Melhor Opção | Por quê? |
|---|---|---|
| API com tráfego constante | EC2 (ou ECS/EKS) | Mais barato para uso contínuo |
| Processamento de filas | Lambda | Escala automaticamente |
| Batch jobs agendados | Lambda + CloudWatch Events | Paga apenas pelo tempo de execução |
| Aplicação complexa | ECS/Fargate | Melhor controle sem gerenciar servidores |
Dicas de economia:
- Instâncias Spot: Até 90% mais barato para cargas tolerantes a interrupções
- Reserved Instances: Compromisso de 1-3 anos para desconto de até 75%
- Lambda: Configure memória corretamente (mais memória = mais CPU)
- 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
- Experimente o AWS Free Tier (gratuito por 12 meses)
- Comece com Lambda para experimentar sem custos fixos
- Use Elastic Beanstalk para deployments simples de aplicações web
- Explore AWS CDK para infraestrutura como código
- Monitore seus custos no AWS Billing Dashboard
Recursos Adicionais
- AWS SDK for Go v2 Documentation
- AWS Go Builder’s Library
- Well-Architected Framework
- Go em Kubernetes - Aprenda a orquestrar seus containers
- Go e Docker - Containerização de aplicações Go
Tem dúvidas sobre deploy na AWS? A AWS oferece créditos gratuitos para startups através do programa AWS Activate.