1. IAM — Fundamentos
Conceitos principais
O IAM (Identity and Access Management) é o serviço central de controle de acesso da AWS. Todo acesso a recursos AWS passa por uma avaliação de política IAM.
| Entidade | Descrição |
|---|---|
| User | Identidade permanente (humano ou sistema); possui access key + senha |
| Group | Conjunto de usuários; herdam as políticas anexadas ao grupo |
| Role | Identidade temporária assumida por serviços, instâncias ou federação |
| Policy | Documento JSON que define permissões (Allow/Deny) |
Estrutura de política JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3ReadOnBucket",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::meu-bucket",
"arn:aws:s3:::meu-bucket/*"
],
"Condition": {
"StringEquals": {
"s3:prefix": ["data/", "reports/"]
}
}
}
]
}Campos obrigatórios:
Effect:AllowouDeny— Deny sempre prevaleceAction: serviço:ação (ex:s3:GetObject,ec2:*)Resource: ARN do recurso (*= todos)
Campos opcionais:
Sid: identificador legível para humanosCondition: filtros adicionais (IP, MFA, tags, hora)
Inline vs Managed Policies
# Criar política gerenciada (managed — reutilizável)
aws iam create-policy \
--policy-name MinhaPoliticaS3 \
--policy-document file://politica.json
# Anexar política gerenciada a um usuário
aws iam attach-user-policy \
--user-name joao \
--policy-arn arn:aws:iam::123456789012:policy/MinhaPoliticaS3
# Criar política inline diretamente no usuário (não reutilizável)
aws iam put-user-policy \
--user-name joao \
--policy-name AcessoTemporario \
--policy-document file://inline.json
# Listar políticas anexadas
aws iam list-attached-user-policies --user-name joao
aws iam list-user-policies --user-name joao # inlineTrust Policy — quem pode assumir a role
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}AssumeRole — assumir role programaticamente
# Assumir role e obter credenciais temporárias
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/MinhaRole \
--role-session-name minha-sessao \
--duration-seconds 3600
# Resultado: AccessKeyId, SecretAccessKey, SessionToken
# Exportar para uso imediato:
export AWS_ACCESS_KEY_ID=ASIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...import boto3
sts = boto3.client('sts')
assumed = sts.assume_role(
RoleArn='arn:aws:iam::123456789012:role/MinhaRole',
RoleSessionName='sessao-python',
DurationSeconds=3600
)
creds = assumed['Credentials']
session = boto3.Session(
aws_access_key_id=creds['AccessKeyId'],
aws_secret_access_key=creds['SecretAccessKey'],
aws_session_token=creds['SessionToken']
)
s3 = session.client('s3')Instance Profile
Instance Profile vincula uma Role a uma instância EC2. A instância obtém credenciais temporárias automaticamente via IMDS (Instance Metadata Service).
# Criar instance profile
aws iam create-instance-profile --instance-profile-name MeuProfile
# Adicionar role ao profile
aws iam add-role-to-instance-profile \
--instance-profile-name MeuProfile \
--role-name MinhaRole
# Associar profile a uma instância EC2
aws ec2 associate-iam-instance-profile \
--instance-id i-0123456789abcdef0 \
--iam-instance-profile Name=MeuProfileOIDC — GitHub Actions → AWS (sem access keys)
Trust policy para GitHub Actions assumir role via OIDC:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:minha-org/meu-repo:*"
}
}
}
]
}Workflow GitHub Actions:
# .github/workflows/deploy.yml
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
aws-region: us-east-1
- name: Deploy
run: aws s3 sync dist/ s3://meu-bucket/AWS CLI — perfis e configuração
# Configuração interativa
aws configure
# AWS Access Key ID: AKIA...
# AWS Secret Access Key: ...
# Default region name: us-east-1
# Default output format: json
# Configurar perfil nomeado
aws configure --profile producao
# Arquivos gerados:
# ~/.aws/credentials
# [default]
# aws_access_key_id = AKIA...
# aws_secret_access_key = ...
#
# [producao]
# aws_access_key_id = AKIA...
# aws_secret_access_key = ...
# ~/.aws/config
# [default]
# region = us-east-1
# output = json
#
# [profile producao]
# region = sa-east-1
# output = table
# Usar perfil específico
aws s3 ls --profile producao
# Ou via variável de ambiente
export AWS_PROFILE=producao
# Verificar identidade atual
aws sts get-caller-identity
# {
# "UserId": "AIDA...",
# "Account": "123456789012",
# "Arn": "arn:aws:iam::123456789012:user/joao"
# }
# Listar usuários IAM
aws iam list-users --query 'Users[*].[UserName,CreateDate]' --output table
# Listar roles
aws iam list-roles --query 'Roles[*].[RoleName,Arn]' --output table
# Criar usuário
aws iam create-user --user-name novo-usuario
# Criar access key para usuário
aws iam create-access-key --user-name novo-usuario
# Adicionar usuário a grupo
aws iam add-user-to-group --user-name joao --group-name Desenvolvedores2. Amazon S3
Conceitos fundamentais
- Bucket: container de objetos; nome deve ser globalmente único
- Object: arquivo + metadados; chave = caminho completo (ex:
pasta/arquivo.txt) - Prefix: simulação de hierarquia de pastas (S3 é flat na verdade)
- ARN:
arn:aws:s3:::meu-bucket(bucket) /arn:aws:s3:::meu-bucket/*(objetos)
Storage Classes
| Classe | Uso | Disponibilidade | Custo relativo |
|---|---|---|---|
| Standard | Acesso frequente | 99.99% | Alto |
| Standard-IA | Acesso infrequente | 99.9% | Médio |
| One Zone-IA | IA, zona única | 99.5% | Baixo |
| Glacier Instant | Arquivo, acesso em ms | 99.9% | Muito baixo |
| Glacier Flexible | Arquivo, 1-12h | — | Mínimo |
| Glacier Deep Archive | Long-term, 12-48h | — | Mínimo absoluto |
| Intelligent-Tiering | Padrão variável desconhecido | 99.9% | Auto-otimizado |
CLI — operações essenciais
# Criar bucket (us-east-1 não precisa de LocationConstraint)
aws s3 mb s3://meu-bucket
# Criar bucket em outra região
aws s3 mb s3://meu-bucket --region sa-east-1
# Listar buckets
aws s3 ls
# Listar objetos em um bucket
aws s3 ls s3://meu-bucket/
aws s3 ls s3://meu-bucket/pasta/ --recursive --human-readable
# Upload de arquivo
aws s3 cp arquivo.txt s3://meu-bucket/destino/arquivo.txt
# Upload com storage class específica
aws s3 cp grande-arquivo.zip s3://meu-bucket/ \
--storage-class STANDARD_IA
# Download de arquivo
aws s3 cp s3://meu-bucket/arquivo.txt ./local/
# Sincronizar diretório local → S3
aws s3 sync ./dist/ s3://meu-bucket/ \
--delete \
--exclude "*.DS_Store" \
--include "*.html" \
--cache-control "max-age=86400"
# Sincronizar S3 → local
aws s3 sync s3://meu-bucket/backups/ ./backups-locais/
# Remover objeto
aws s3 rm s3://meu-bucket/arquivo.txt
# Remover bucket e todo conteúdo
aws s3 rb s3://meu-bucket --force
# Gerar URL pre-assinada (válida por 1 hora)
aws s3 presign s3://meu-bucket/documento.pdf --expires-in 3600
# Multipart upload manual (útil para arquivos > 5GB)
aws s3api create-multipart-upload \
--bucket meu-bucket \
--key grande-arquivo.zip
# Copiar objeto entre buckets
aws s3 cp s3://origem/arquivo.txt s3://destino/arquivo.txt
# Mover objeto
aws s3 mv s3://meu-bucket/antigo.txt s3://meu-bucket/novo.txtBucket Policy — exemplos JSON
Política de acesso público para site estático:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::meu-site-bucket/*"
}
]
}Política restringindo acesso a VPC específica:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyOutsideVPC",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::meu-bucket",
"arn:aws:s3:::meu-bucket/*"
],
"Condition": {
"StringNotEquals": {
"aws:SourceVpc": "vpc-0123456789abcdef0"
}
}
}
]
}Política de cross-account (outra conta pode fazer GetObject):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::999999999999:role/RoleExterna"
},
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::meu-bucket",
"arn:aws:s3:::meu-bucket/*"
]
}
]
}# Aplicar bucket policy
aws s3api put-bucket-policy \
--bucket meu-bucket \
--policy file://bucket-policy.json
# Ver política atual
aws s3api get-bucket-policy --bucket meu-bucketVersionamento
# Habilitar versionamento
aws s3api put-bucket-versioning \
--bucket meu-bucket \
--versioning-configuration Status=Enabled
# Listar versões de um objeto
aws s3api list-object-versions \
--bucket meu-bucket \
--prefix arquivo.txt
# Restaurar versão específica
aws s3api copy-object \
--bucket meu-bucket \
--copy-source "meu-bucket/arquivo.txt?versionId=abc123" \
--key arquivo.txt
# Deletar versão específica
aws s3api delete-object \
--bucket meu-bucket \
--key arquivo.txt \
--version-id abc123Lifecycle Rules — JSON
{
"Rules": [
{
"ID": "MoverParaIA",
"Status": "Enabled",
"Filter": {
"Prefix": "logs/"
},
"Transitions": [
{
"Days": 30,
"StorageClass": "STANDARD_IA"
},
{
"Days": 90,
"StorageClass": "GLACIER"
}
],
"Expiration": {
"Days": 365
},
"NoncurrentVersionExpiration": {
"NoncurrentDays": 30
}
}
]
}# Aplicar lifecycle rules
aws s3api put-bucket-lifecycle-configuration \
--bucket meu-bucket \
--lifecycle-configuration file://lifecycle.jsonS3 com Python (boto3)
import boto3
from botocore.exceptions import ClientError
from botocore.config import Config
import os
# Cliente básico
s3 = boto3.client('s3', region_name='us-east-1')
# Resource (interface mais pythônica)
s3_resource = boto3.resource('s3')
# Upload simples
def upload_arquivo(caminho_local, bucket, chave):
s3.upload_file(
caminho_local,
bucket,
chave,
ExtraArgs={
'ContentType': 'application/pdf',
'StorageClass': 'STANDARD_IA',
'Metadata': {
'autor': 'sistema',
'versao': '1.0'
}
}
)
print(f"Upload concluído: s3://{bucket}/{chave}")
# Download
def download_arquivo(bucket, chave, destino_local):
s3.download_file(bucket, chave, destino_local)
# Upload de objeto em memória (sem arquivo físico)
def upload_bytes(bucket, chave, dados: bytes):
s3.put_object(
Bucket=bucket,
Key=chave,
Body=dados,
ContentType='application/json'
)
# Listar objetos com paginação
def listar_objetos(bucket, prefix=''):
paginator = s3.get_paginator('list_objects_v2')
paginas = paginator.paginate(Bucket=bucket, Prefix=prefix)
for pagina in paginas:
for obj in pagina.get('Contents', []):
print(f"{obj['Key']} — {obj['Size']} bytes — {obj['LastModified']}")
# Gerar URL pre-assinada para download (válida 1h)
def gerar_url_download(bucket, chave, expiracao=3600):
url = s3.generate_presigned_url(
'get_object',
Params={'Bucket': bucket, 'Key': chave},
ExpiresIn=expiracao
)
return url
# Gerar URL pre-assinada para upload (PUT)
def gerar_url_upload(bucket, chave, expiracao=300):
url = s3.generate_presigned_url(
'put_object',
Params={
'Bucket': bucket,
'Key': chave,
'ContentType': 'application/octet-stream'
},
ExpiresIn=expiracao
)
return url
# Multipart upload para arquivos grandes (> 100MB recomendado)
from boto3.s3.transfer import TransferConfig
def upload_multipart(caminho, bucket, chave):
config = TransferConfig(
multipart_threshold=100 * 1024 * 1024, # 100 MB
max_concurrency=10,
multipart_chunksize=50 * 1024 * 1024, # 50 MB por parte
use_threads=True
)
s3.upload_file(caminho, bucket, chave, Config=config)
# Checar se objeto existe
def objeto_existe(bucket, chave):
try:
s3.head_object(Bucket=bucket, Key=chave)
return True
except ClientError as e:
if e.response['Error']['Code'] == '404':
return False
raise
# Copiar objeto entre buckets
def copiar_objeto(origem_bucket, origem_chave, destino_bucket, destino_chave):
s3.copy_object(
CopySource={'Bucket': origem_bucket, 'Key': origem_chave},
Bucket=destino_bucket,
Key=destino_chave
)
# Deletar objetos em lote (máx 1000 por chamada)
def deletar_em_lote(bucket, chaves):
objetos = [{'Key': k} for k in chaves]
resposta = s3.delete_objects(
Bucket=bucket,
Delete={'Objects': objetos, 'Quiet': True}
)
erros = resposta.get('Errors', [])
if erros:
print(f"Erros ao deletar: {erros}")S3 com Java SDK v2
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
import java.nio.file.Paths;
import java.time.Duration;
public class S3Example {
private final S3Client s3;
private final String bucket;
public S3Example(String bucket) {
this.s3 = S3Client.builder()
.region(Region.US_EAST_1)
.build();
this.bucket = bucket;
}
// Upload de arquivo
public void upload(String caminho, String chave) {
PutObjectRequest request = PutObjectRequest.builder()
.bucket(bucket)
.key(chave)
.contentType("application/pdf")
.build();
s3.putObject(request, RequestBody.fromFile(Paths.get(caminho)));
System.out.println("Upload concluído: " + chave);
}
// Download de arquivo
public void download(String chave, String destino) {
GetObjectRequest request = GetObjectRequest.builder()
.bucket(bucket)
.key(chave)
.build();
s3.getObject(request, Paths.get(destino));
}
// Listar objetos
public void listar(String prefix) {
ListObjectsV2Request request = ListObjectsV2Request.builder()
.bucket(bucket)
.prefix(prefix)
.build();
ListObjectsV2Response response;
do {
response = s3.listObjectsV2(request);
response.contents().forEach(obj ->
System.out.printf("%s — %d bytes%n", obj.key(), obj.size())
);
request = request.toBuilder()
.continuationToken(response.nextContinuationToken())
.build();
} while (response.isTruncated());
}
// URL pre-assinada
public String presignedUrl(String chave, Duration validade) {
try (S3Presigner presigner = S3Presigner.builder()
.region(Region.US_EAST_1)
.build()) {
GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder()
.signatureDuration(validade)
.getObjectRequest(r -> r.bucket(bucket).key(chave))
.build();
return presigner.presignGetObject(presignRequest).url().toString();
}
}
// Deletar objeto
public void deletar(String chave) {
s3.deleteObject(DeleteObjectRequest.builder()
.bucket(bucket)
.key(chave)
.build());
}
}S3 Events → Lambda trigger
{
"LambdaFunctionConfigurations": [
{
"LambdaFunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:ProcessarImagem",
"Events": ["s3:ObjectCreated:*"],
"Filter": {
"Key": {
"FilterRules": [
{
"Name": "prefix",
"Value": "uploads/"
},
{
"Name": "suffix",
"Value": ".jpg"
}
]
}
}
}
]
}# Configurar notificação
aws s3api put-bucket-notification-configuration \
--bucket meu-bucket \
--notification-configuration file://notificacao.jsonStatic Website Hosting
# Habilitar website
aws s3 website s3://meu-site-bucket/ \
--index-document index.html \
--error-document 404.html
# Ou via API:
aws s3api put-bucket-website \
--bucket meu-site-bucket \
--website-configuration '{
"IndexDocument": {"Suffix": "index.html"},
"ErrorDocument": {"Key": "404.html"}
}'
# URL do site: http://meu-site-bucket.s3-website-us-east-1.amazonaws.com3. AWS Lambda
Conceitos fundamentais
- Handler: ponto de entrada da função (ex:
index.handler,lambda_function.lambda_handler) - Event: objeto JSON com dados do trigger (varia por fonte)
- Context: objeto com info da invocação (requestId, timeout restante, ARN da função)
- Invocation types:
RequestResponse(sync): aguarda resposta, retorna resultadoEvent(async): dispara e esquece, Lambda gerencia retentativas- Event Source Mapping: Lambda consome de SQS/Kinesis/DynamoDB Streams automaticamente
Estrutura de função em Python
import json
import os
import logging
from typing import Any, Dict
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event: Dict[str, Any], context) -> Dict[str, Any]:
"""
event: dados do trigger (S3, SQS, API Gateway, etc.)
context: objeto com:
- context.function_name
- context.function_version
- context.invoked_function_arn
- context.memory_limit_in_mb
- context.aws_request_id
- context.log_group_name
- context.get_remaining_time_in_millis()
"""
logger.info("Event recebido: %s", json.dumps(event))
logger.info("Request ID: %s", context.aws_request_id)
logger.info("Tempo restante: %dms", context.get_remaining_time_in_millis())
# Ler variável de ambiente
ambiente = os.environ.get('AMBIENTE', 'dev')
tabela = os.environ.get('DYNAMO_TABLE')
try:
resultado = processar(event)
return {
'statusCode': 200,
'body': json.dumps({'resultado': resultado})
}
except Exception as e:
logger.exception("Erro ao processar: %s", e)
raise # Re-lança para Lambda registrar como falha
def processar(event):
# lógica de negócio aqui
return "OK"Estrutura de função em Node.js
// index.mjs (ES Module)
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
const s3 = new S3Client({ region: process.env.AWS_REGION });
export const handler = async (event, context) => {
console.log('Event:', JSON.stringify(event, null, 2));
console.log('Function ARN:', context.invokedFunctionArn);
console.log('Request ID:', context.awsRequestId);
try {
const resultado = await processar(event);
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ resultado })
};
} catch (erro) {
console.error('Erro:', erro);
return {
statusCode: 500,
body: JSON.stringify({ erro: erro.message })
};
}
};
async function processar(event) {
// lógica aqui
return 'processado';
}Estrutura de função em Java
package com.exemplo;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
import java.util.Map;
// Handler: com.exemplo.ProcessarSQS::handleRequest
public class ProcessarSQS implements RequestHandler<SQSEvent, Void> {
@Override
public Void handleRequest(SQSEvent event, Context context) {
context.getLogger().log("Records: " + event.getRecords().size());
for (SQSEvent.SQSMessage msg : event.getRecords()) {
context.getLogger().log("Body: " + msg.getBody());
processar(msg.getBody());
}
return null;
}
private void processar(String body) {
// lógica de negócio
}
}Deploy via CLI
# Empacotar e criar função
zip -r funcao.zip . -x "*.pyc" -x "__pycache__/*"
aws lambda create-function \
--function-name MinhaFuncao \
--runtime python3.12 \
--role arn:aws:iam::123456789012:role/lambda-role \
--handler lambda_function.lambda_handler \
--zip-file fileb://funcao.zip \
--timeout 30 \
--memory-size 256 \
--environment Variables="{AMBIENTE=prod,TABELA=minha-tabela}" \
--description "Processa eventos S3"
# Atualizar código
aws lambda update-function-code \
--function-name MinhaFuncao \
--zip-file fileb://funcao.zip
# Atualizar configuração
aws lambda update-function-configuration \
--function-name MinhaFuncao \
--timeout 60 \
--memory-size 512
# Invocar função manualmente (sync)
aws lambda invoke \
--function-name MinhaFuncao \
--payload '{"chave": "valor"}' \
--cli-binary-format raw-in-base64-out \
resposta.json
# Invocar de forma assíncrona
aws lambda invoke \
--function-name MinhaFuncao \
--invocation-type Event \
--payload '{"chave": "valor"}' \
--cli-binary-format raw-in-base64-out \
/dev/null
# Listar funções
aws lambda list-functions \
--query 'Functions[*].[FunctionName,Runtime,MemorySize,Timeout]' \
--output table
# Ver logs recentes
aws logs tail /aws/lambda/MinhaFuncao --followConcurrency — reserved vs provisioned
# Reserved concurrency: limita máximo de execuções simultâneas
aws lambda put-function-concurrency \
--function-name MinhaFuncao \
--reserved-concurrent-executions 100
# Provisioned concurrency: pré-aquece instâncias (elimina cold start)
aws lambda put-provisioned-concurrency-config \
--function-name MinhaFuncao \
--qualifier versao-1 # alias ou versão publicada
--provisioned-concurrent-executions 10
# Ver concurrency
aws lambda get-function-concurrency --function-name MinhaFuncaoLayers
# Criar layer (ex: dependências Python)
pip install -r requirements.txt -t python/
zip -r layer.zip python/
aws lambda publish-layer-version \
--layer-name dependencias-comuns \
--zip-file fileb://layer.zip \
--compatible-runtimes python3.11 python3.12 \
--description "boto3, requests, psycopg2"
# Anexar layer à função
aws lambda update-function-configuration \
--function-name MinhaFuncao \
--layers arn:aws:lambda:us-east-1:123456789012:layer:dependencias-comuns:3DLQ — Dead Letter Queue
# Configurar DLQ (para invocações assíncronas)
aws lambda update-function-configuration \
--function-name MinhaFuncao \
--dead-letter-config TargetArn=arn:aws:sqs:us-east-1:123456789012:dlq-funcaoCold Start — causas e mitigação
Causas do cold start:
- Primeira invocação após período inativo (~15 min sem tráfego)
- Escalonamento automático para novos containers
- Novo deployment
Mitigações:
- Provisioned Concurrency — mantém N instâncias quentes (custo adicional)
- Menor tamanho do pacote — menos código = inicialização mais rápida
- Runtime mais leve — Python/Node têm cold start menor que Java
- SnapStart (Java) — captura snapshot após init, restaura em ~1s
- Inicialização fora do handler — conexões DB, SDK clients fora do
lambda_handler
# Ruim — reconecta a cada invocação
def lambda_handler(event, context):
import boto3
s3 = boto3.client('s3') # inicializa aqui = cold start mais lento
...
# Bom — reutiliza conexão entre invocações no mesmo container
import boto3
s3 = boto3.client('s3') # inicializa uma vez, reutilizado pelo container
def lambda_handler(event, context):
...4. Amazon SQS
Standard vs FIFO
| Característica | Standard | FIFO |
|---|---|---|
| Throughput | Ilimitado | 3.000 msg/s com batching, 300 sem |
| Ordem | Best-effort | Garantida por grupo |
| Entregas | Ao menos uma vez (duplicatas possíveis) | Exatamente uma vez |
| Deduplicação | Não | Sim (5 min janela) |
| Sufixo URL | .sqs.amazonaws.com | .fifo |
Conceitos importantes
- Visibility Timeout: tempo que a mensagem fica invisível após ser recebida (default: 30s, máx: 12h). Se o consumer não deletar nesse tempo, a mensagem reaparece.
- Retention Period: quanto tempo a mensagem fica na fila sem ser deletada (default: 4 dias, máx: 14 dias)
- Delay Queue: atraso na entrega (0-900s). Mensagem não visível imediatamente após envio.
- Message Attributes: metadados estruturados (String, Number, Binary)
- Long Polling:
WaitTimeSecondsaté 20s — reduz chamadas vazias e custo
CLI — operações essenciais
# Criar fila Standard
aws sqs create-queue \
--queue-name minha-fila \
--attributes '{
"VisibilityTimeout": "60",
"MessageRetentionPeriod": "86400",
"DelaySeconds": "0"
}'
# Criar fila FIFO
aws sqs create-queue \
--queue-name minha-fila.fifo \
--attributes '{
"FifoQueue": "true",
"ContentBasedDeduplication": "true",
"VisibilityTimeout": "60"
}'
# Enviar mensagem
aws sqs send-message \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/minha-fila \
--message-body '{"pedido_id": 42, "valor": 150.00}'
# Enviar com atributos
aws sqs send-message \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/minha-fila \
--message-body "Pedido urgente" \
--message-attributes '{
"Prioridade": {
"DataType": "String",
"StringValue": "alta"
}
}'
# Receber mensagem (long polling 10s)
aws sqs receive-message \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/minha-fila \
--max-number-of-messages 10 \
--wait-time-seconds 10 \
--attribute-names All \
--message-attribute-names All
# Deletar mensagem (obrigatório após processar)
aws sqs delete-message \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/minha-fila \
--receipt-handle "AQEBxxx..."
# Limpar toda a fila
aws sqs purge-queue \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/minha-fila
# Ver atributos da fila
aws sqs get-queue-attributes \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/minha-fila \
--attribute-names All
# Listar filas
aws sqs list-queues --queue-name-prefix minhaboto3 — produtor e consumidor
import boto3
import json
import time
import logging
from botocore.exceptions import ClientError
logger = logging.getLogger(__name__)
QUEUE_URL = 'https://sqs.us-east-1.amazonaws.com/123456789012/minha-fila'
sqs = boto3.client('sqs', region_name='us-east-1')
# === PRODUTOR ===
def enviar_mensagem(payload: dict, delay: int = 0):
"""Envia mensagem simples."""
resposta = sqs.send_message(
QueueUrl=QUEUE_URL,
MessageBody=json.dumps(payload),
DelaySeconds=delay,
MessageAttributes={
'Fonte': {
'DataType': 'String',
'StringValue': 'sistema-pedidos'
},
'Versao': {
'DataType': 'Number',
'StringValue': '2'
}
}
)
logger.info("Mensagem enviada: %s", resposta['MessageId'])
return resposta['MessageId']
def enviar_em_lote(payloads: list):
"""Envia até 10 mensagens por vez — mais eficiente."""
entradas = [
{
'Id': str(i),
'MessageBody': json.dumps(p)
}
for i, p in enumerate(payloads[:10])
]
resposta = sqs.send_message_batch(
QueueUrl=QUEUE_URL,
Entries=entradas
)
falhas = resposta.get('Failed', [])
if falhas:
logger.error("Falhas no envio: %s", falhas)
return resposta['Successful']
# === CONSUMIDOR com Long Polling ===
def consumir_mensagens(max_iteracoes=None):
"""Consumer loop com long polling."""
iteracoes = 0
while True:
if max_iteracoes and iteracoes >= max_iteracoes:
break
resposta = sqs.receive_message(
QueueUrl=QUEUE_URL,
MaxNumberOfMessages=10, # máx 10 por chamada
WaitTimeSeconds=20, # long polling — espera até 20s
VisibilityTimeout=60, # tempo para processar
MessageAttributeNames=['All'],
AttributeNames=['All']
)
mensagens = resposta.get('Messages', [])
if not mensagens:
logger.debug("Nenhuma mensagem recebida")
iteracoes += 1
continue
for msg in mensagens:
receipt_handle = msg['ReceiptHandle']
body = json.loads(msg['Body'])
try:
processar(body, msg.get('MessageAttributes', {}))
# Deletar SOMENTE após processar com sucesso
sqs.delete_message(
QueueUrl=QUEUE_URL,
ReceiptHandle=receipt_handle
)
logger.info("Mensagem processada e deletada: %s", msg['MessageId'])
except Exception as e:
logger.exception("Erro ao processar mensagem %s: %s",
msg['MessageId'], e)
# Não deleta — mensagem voltará após visibility timeout
# Após N falhas vai para a DLQ
iteracoes += 1
def processar(body: dict, atributos: dict):
logger.info("Processando: %s", body)
# lógica de negócio aquiSQS → Lambda (Event Source Mapping)
# Configurar Lambda para consumir SQS automaticamente
aws lambda create-event-source-mapping \
--function-name MinhaFuncao \
--event-source-arn arn:aws:sqs:us-east-1:123456789012:minha-fila \
--batch-size 10 \
--maximum-batching-window-in-seconds 5 \
--function-response-types ReportBatchItemFailures # Partial failuresHandler Lambda com partial batch failure:
def lambda_handler(event, context):
"""
Retorna IDs das mensagens que FALHARAM.
Mensagens não listadas são consideradas processadas com sucesso.
"""
falhas = []
for record in event['Records']:
msg_id = record['messageId']
body = json.loads(record['body'])
try:
processar(body)
except Exception as e:
logger.error("Falha na mensagem %s: %s", msg_id, e)
falhas.append({'itemIdentifier': msg_id})
# Retornar falhas parciais (sem falhas = lista vazia = sucesso total)
return {'batchItemFailures': falhas}DLQ — configuração e monitoramento
# 1. Criar DLQ
aws sqs create-queue --queue-name minha-fila-dlq
# 2. Configurar redrive policy na fila principal
aws sqs set-queue-attributes \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/minha-fila \
--attributes '{
"RedrivePolicy": "{\"deadLetterTargetArn\":\"arn:aws:sqs:us-east-1:123456789012:minha-fila-dlq\",\"maxReceiveCount\":\"3\"}"
}'
# Monitorar DLQ
aws sqs get-queue-attributes \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/minha-fila-dlq \
--attribute-names ApproximateNumberOfMessagesFIFO — exemplos
# Enviar para fila FIFO
def enviar_fifo(pedido_id: str, cliente_id: str, payload: dict):
sqs.send_message(
QueueUrl='https://sqs.us-east-1.amazonaws.com/123456789012/pedidos.fifo',
MessageBody=json.dumps(payload),
MessageGroupId=cliente_id, # mensagens do mesmo cliente em ordem
MessageDeduplicationId=pedido_id # idempotência por pedido_id (5 min)
)5. Amazon SNS
Conceitos
- Topic: canal de publicação/assinatura
- Subscription: endpoint que recebe mensagens de um topic
- Protocols: SQS, Lambda, HTTP/HTTPS, email, SMS, application (push)
- Standard: alta throughput, ordem best-effort
- FIFO: ordem garantida, deduplicação, só pode entregar para SQS FIFO
CLI — operações essenciais
# Criar topic
aws sns create-topic --name meu-topic
# Criar topic FIFO
aws sns create-topic \
--name meu-topic.fifo \
--attributes FifoTopic=true,ContentBasedDeduplication=true
# Listar topics
aws sns list-topics
# Criar subscription (SQS)
aws sns subscribe \
--topic-arn arn:aws:sns:us-east-1:123456789012:meu-topic \
--protocol sqs \
--notification-endpoint arn:aws:sqs:us-east-1:123456789012:minha-fila
# Criar subscription (Lambda)
aws sns subscribe \
--topic-arn arn:aws:sns:us-east-1:123456789012:meu-topic \
--protocol lambda \
--notification-endpoint arn:aws:lambda:us-east-1:123456789012:function:MinhaFuncao
# Criar subscription (email)
aws sns subscribe \
--topic-arn arn:aws:sns:us-east-1:123456789012:meu-topic \
--protocol email \
--notification-endpoint joao@example.com
# Publicar mensagem
aws sns publish \
--topic-arn arn:aws:sns:us-east-1:123456789012:meu-topic \
--message "Mensagem de teste" \
--subject "Assunto do Email"
# Publicar com atributos (usado em filtering)
aws sns publish \
--topic-arn arn:aws:sns:us-east-1:123456789012:meu-topic \
--message '{"pedido_id": 99}' \
--message-attributes '{
"tipo": {
"DataType": "String",
"StringValue": "pedido-novo"
},
"prioridade": {
"DataType": "Number",
"StringValue": "1"
}
}'
# Listar subscriptions de um topic
aws sns list-subscriptions-by-topic \
--topic-arn arn:aws:sns:us-east-1:123456789012:meu-topic
# Deletar subscription
aws sns unsubscribe --subscription-arn arn:aws:sns:us-east-1:123456789012:meu-topic:uuid
# Deletar topic
aws sns delete-topic --topic-arn arn:aws:sns:us-east-1:123456789012:meu-topicboto3 — publish com atributos
import boto3
import json
sns = boto3.client('sns', region_name='us-east-1')
TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:eventos-sistema'
def publicar_evento(tipo: str, payload: dict, prioridade: str = 'normal'):
resposta = sns.publish(
TopicArn=TOPIC_ARN,
Message=json.dumps(payload),
Subject=f"Evento: {tipo}",
MessageAttributes={
'tipo': {
'DataType': 'String',
'StringValue': tipo
},
'prioridade': {
'DataType': 'String',
'StringValue': prioridade
}
}
)
return resposta['MessageId']
# Estrutura de mensagem personalizada por protocolo
def publicar_multiplos_formatos(titulo: str, corpo_texto: str, corpo_email: str):
sns.publish(
TopicArn=TOPIC_ARN,
Message=json.dumps({
'default': corpo_texto,
'email': corpo_email,
'sqs': json.dumps({'mensagem': corpo_texto, 'titulo': titulo})
}),
Subject=titulo,
MessageStructure='json' # envia formato diferente por protocolo
)Message Filtering — Filter Policy
Permite que cada subscription receba apenas mensagens relevantes:
# Aplicar filtro na subscription (só recebe pedidos com status = novo)
aws sns set-subscription-attributes \
--subscription-arn arn:aws:sns:us-east-1:123456789012:meu-topic:uuid \
--attribute-name FilterPolicy \
--attribute-value '{
"tipo": ["pedido-novo", "pedido-atualizado"],
"prioridade": ["alta", "critica"]
}'{
"tipo": ["pedido-novo"],
"valor": [{"numeric": [">=", 100]}],
"regiao": ["sul", "sudeste"],
"urgente": [true]
}Fan-out Pattern: SNS → múltiplos SQS
┌──────────────────────┐
│ SNS Topic │
│ eventos-pedidos │
└──────────────────────┘
│ │
┌──────────┘ └──────────┐
▼ ▼
SQS: processar-pagamento SQS: enviar-email
(Lambda: cobrar cartão) (Lambda: notificar cliente)Política SQS para aceitar mensagens do SNS:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "sns.amazonaws.com"
},
"Action": "sqs:SendMessage",
"Resource": "arn:aws:sqs:us-east-1:123456789012:processar-pagamento",
"Condition": {
"ArnEquals": {
"aws:SourceArn": "arn:aws:sns:us-east-1:123456789012:eventos-pedidos"
}
}
}
]
}6. Amazon RDS
Engines suportados
- MySQL, PostgreSQL, MariaDB, Oracle, SQL Server, Db2
- Aurora: compatível com MySQL/PostgreSQL, armazenamento distribuído, failover automático
Multi-AZ vs Read Replica
| Multi-AZ | Read Replica | |
|---|---|---|
| Objetivo | Alta disponibilidade | Escalabilidade de leitura |
| Sincronização | Síncrona | Assíncrona |
| Failover | Automático (~60s) | Manual (promote) |
| Endpoint | Mesmo endpoint | Endpoint separado |
| Regiões | Mesma região | Cross-region possível |
CLI — operações
# Criar instância RDS PostgreSQL
aws rds create-db-instance \
--db-instance-identifier meu-banco \
--db-instance-class db.t3.micro \
--engine postgres \
--engine-version 16.2 \
--master-username admin \
--master-user-password minhasenha \
--allocated-storage 20 \
--storage-type gp3 \
--vpc-security-group-ids sg-0123456789abcdef0 \
--db-subnet-group-name meu-subnet-group \
--backup-retention-period 7 \
--multi-az \
--no-publicly-accessible \
--tags Key=Ambiente,Value=producao
# Ver status das instâncias
aws rds describe-db-instances \
--query 'DBInstances[*].[DBInstanceIdentifier,DBInstanceStatus,Endpoint.Address]' \
--output table
# Criar snapshot manual
aws rds create-db-snapshot \
--db-instance-identifier meu-banco \
--db-snapshot-identifier meu-banco-backup-20240506
# Listar snapshots
aws rds describe-db-snapshots \
--db-instance-identifier meu-banco \
--query 'DBSnapshots[*].[DBSnapshotIdentifier,Status,SnapshotCreateTime]' \
--output table
# Restaurar de snapshot
aws rds restore-db-instance-from-db-snapshot \
--db-instance-identifier meu-banco-restaurado \
--db-snapshot-identifier meu-banco-backup-20240506 \
--db-instance-class db.t3.micro
# Point-in-time recovery
aws rds restore-db-instance-to-point-in-time \
--source-db-instance-identifier meu-banco \
--target-db-instance-identifier meu-banco-pitr \
--restore-time 2024-05-06T12:00:00Z
# Criar Read Replica
aws rds create-db-instance-read-replica \
--db-instance-identifier meu-banco-replica \
--source-db-instance-identifier meu-banco \
--db-instance-class db.t3.micro
# Promover Read Replica para instância independente
aws rds promote-read-replica \
--db-instance-identifier meu-banco-replica
# Deletar instância (sem snapshot final)
aws rds delete-db-instance \
--db-instance-identifier meu-banco \
--skip-final-snapshotConnection — boas práticas
# Python com psycopg2 + Secrets Manager
import boto3
import psycopg2
import json
from functools import lru_cache
@lru_cache(maxsize=1)
def obter_credenciais():
"""Cache em memória — só busca uma vez por container."""
sm = boto3.client('secretsmanager')
secret = sm.get_secret_value(SecretId='prod/rds/meu-banco')
return json.loads(secret['SecretString'])
def conectar():
creds = obter_credenciais()
return psycopg2.connect(
host=creds['host'],
port=creds['port'],
database=creds['dbname'],
user=creds['username'],
password=creds['password'],
connect_timeout=5,
sslmode='require'
)RDS Proxy
Por que usar:
- Lambda + RDS = muitas conexões simultâneas (Lambda escala rápido, DB tem limite)
- RDS Proxy mantém pool de conexões persistente
- Failover Multi-AZ mais rápido (~66% mais rápido)
- Integração com Secrets Manager e IAM auth
# Criar RDS Proxy
aws rds create-db-proxy \
--db-proxy-name meu-proxy \
--engine-family POSTGRESQL \
--auth '[{
"AuthScheme": "SECRETS",
"SecretArn": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/rds/meu-banco",
"IAMAuth": "REQUIRED"
}]' \
--role-arn arn:aws:iam::123456789012:role/rds-proxy-role \
--vpc-subnet-ids subnet-aaa subnet-bbb \
--vpc-security-group-ids sg-0123456789
# Endpoint do proxy: meu-proxy.proxy-xxx.us-east-1.rds.amazonaws.comBackup automático e monitoramento
# Habilitar Performance Insights
aws rds modify-db-instance \
--db-instance-identifier meu-banco \
--enable-performance-insights \
--performance-insights-retention-period 7 # dias (free tier = 7)
# Habilitar Enhanced Monitoring (métricas a cada 1s)
aws rds modify-db-instance \
--db-instance-identifier meu-banco \
--monitoring-interval 5 \
--monitoring-role-arn arn:aws:iam::123456789012:role/rds-monitoring-role
# Ver eventos recentes
aws rds describe-events \
--source-identifier meu-banco \
--source-type db-instance \
--duration 607. Amazon EKS
Conceitos
- Managed Node Groups: EC2 gerenciados pela AWS (atualizações automáticas)
- Fargate Profiles: pods serverless, sem gerenciar nós
- Add-ons: CoreDNS, kube-proxy, VPC CNI, EBS CSI Driver — gerenciados pela AWS
eksctl — criar cluster
# Instalar eksctl
curl --silent --location "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
sudo mv /tmp/eksctl /usr/local/bin
# Criar cluster básico
eksctl create cluster \
--name meu-cluster \
--region us-east-1 \
--version 1.30 \
--nodegroup-name workers \
--node-type t3.medium \
--nodes 2 \
--nodes-min 1 \
--nodes-max 4 \
--managed
# Criar cluster via arquivo YAML
cat > cluster.yaml << 'YAML'
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: meu-cluster
region: us-east-1
version: "1.30"
managedNodeGroups:
- name: workers-gerais
instanceType: t3.medium
minSize: 2
maxSize: 6
desiredCapacity: 3
volumeSize: 50
ssh:
allow: true
publicKeyPath: ~/.ssh/id_ed25519.pub
labels:
role: worker
tags:
Ambiente: producao
iam:
withAddonPolicies:
imageBuilder: true
autoScaler: true
ebs: true
efs: true
albIngress: true
cloudWatch: true
- name: workers-gpu
instanceType: g4dn.xlarge
minSize: 0
maxSize: 2
desiredCapacity: 0
labels:
role: gpu-worker
addons:
- name: vpc-cni
version: latest
- name: coredns
version: latest
- name: kube-proxy
version: latest
- name: aws-ebs-csi-driver
version: latest
wellKnownPolicies:
ebsCSIController: true
YAML
eksctl create cluster -f cluster.yaml
# Adicionar nodegroup
eksctl create nodegroup \
--cluster meu-cluster \
--name workers-2 \
--node-type t3.large \
--nodes 2
# Deletar nodegroup
eksctl delete nodegroup \
--cluster meu-cluster \
--name workers-2
# Deletar cluster
eksctl delete cluster --name meu-clusterkubectl com EKS
# Configurar kubeconfig
aws eks update-kubeconfig \
--name meu-cluster \
--region us-east-1
# Com perfil específico
aws eks update-kubeconfig \
--name meu-cluster \
--region us-east-1 \
--profile producao
# Verificar conexão
kubectl cluster-info
kubectl get nodes -o wide
# Ver contextos
kubectl config get-contexts
kubectl config current-context
# Trocar contexto
kubectl config use-context arn:aws:eks:us-east-1:123456789012:cluster/meu-cluster
# Criar namespace
kubectl create namespace aplicacao
# Definir namespace padrão
kubectl config set-context --current --namespace=aplicacao
# Listar recursos
kubectl get all -n aplicacao
kubectl get pods -A # todos os namespaces
kubectl describe pod nome-pod -n aplicacao
# Logs
kubectl logs -f deployment/minha-app -n aplicacao
kubectl logs -f pod/nome-pod -c nome-container
# Executar comando em pod
kubectl exec -it pod/nome-pod -n aplicacao -- /bin/bash
# Port forward
kubectl port-forward svc/minha-app 8080:80 -n aplicacaoIRSA — IAM Roles for Service Accounts
Permite que pods assumam roles IAM sem precisar de credenciais estáticas.
# 1. Habilitar OIDC provider no cluster
eksctl utils associate-iam-oidc-provider \
--cluster meu-cluster \
--region us-east-1 \
--approve
# Verificar OIDC
aws eks describe-cluster --name meu-cluster \
--query "cluster.identity.oidc.issuer" --output text
# 2. Criar service account com role
eksctl create iamserviceaccount \
--name minha-app-sa \
--namespace aplicacao \
--cluster meu-cluster \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
--approve \
--override-existing-serviceaccounts
# 3. Usar no deployment# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: minha-app
namespace: aplicacao
spec:
replicas: 2
selector:
matchLabels:
app: minha-app
template:
metadata:
labels:
app: minha-app
spec:
serviceAccountName: minha-app-sa # vincula ao IRSA
containers:
- name: app
image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/minha-app:latest
env:
- name: AWS_REGION
value: us-east-1ALB Ingress Controller (AWS Load Balancer Controller)
# Instalar via Helm
helm repo add eks https://aws.github.io/eks-charts
helm repo update
# Criar service account com permissões
eksctl create iamserviceaccount \
--cluster meu-cluster \
--namespace kube-system \
--name aws-load-balancer-controller \
--attach-policy-arn arn:aws:iam::123456789012:policy/AWSLoadBalancerControllerIAMPolicy \
--override-existing-serviceaccounts \
--approve
# Instalar controller
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
-n kube-system \
--set clusterName=meu-cluster \
--set serviceAccount.create=false \
--set serviceAccount.name=aws-load-balancer-controller# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minha-app-ingress
namespace: aplicacao
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:123456789012:certificate/xxx
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS": 443}]'
alb.ingress.kubernetes.io/ssl-redirect: '443'
spec:
rules:
- host: app.exemplo.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: minha-app
port:
number: 80EKS Add-ons
# Listar add-ons instalados
aws eks list-addons --cluster-name meu-cluster
# Ver versões disponíveis de um add-on
aws eks describe-addon-versions --addon-name vpc-cni
# Instalar add-on
aws eks create-addon \
--cluster-name meu-cluster \
--addon-name aws-ebs-csi-driver \
--addon-version v1.30.0-eksbuild.1
# Atualizar add-on
aws eks update-addon \
--cluster-name meu-cluster \
--addon-name coredns \
--addon-version v1.11.1-eksbuild.9
# Remover add-on
aws eks delete-addon \
--cluster-name meu-cluster \
--addon-name aws-ebs-csi-driverCluster Autoscaler e Karpenter
Cluster Autoscaler: escala node groups existentes baseado em pods pendentes (Pending). Precisa de IAM policy e configuração de tags nos ASGs.
Karpenter: provisionador de nós de nova geração. Lança instâncias EC2 diretamente sem ASG, escolhe o melhor tipo automaticamente, muito mais rápido.
# NodePool Karpenter básico
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
nodeClassRef:
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
name: default
limits:
cpu: 100
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s8. AWS Glue ETL
Conceitos
- Data Catalog: metastore centralizado (bancos de dados e tabelas)
- Crawler: descobre esquemas e popula o Data Catalog automaticamente
- Job: script ETL executado em ambiente Apache Spark gerenciado
- Connection: configuração de conexão com JDBC, S3, Kafka, etc.
- Workflow: orquestra jobs e crawlers com triggers
- DynamicFrame: abstração Glue sobre DataFrame Spark (mais tolerante a esquemas inconsistentes)
Glue Job — script PySpark
import sys
from awsglue.transforms import *
from awsglue.utils import getResolvedOptions
from pyspark.context import SparkContext
from awsglue.context import GlueContext
from awsglue.job import Job
from awsglue.dynamicframe import DynamicFrame
import pyspark.sql.functions as F
# Parâmetros do job
args = getResolvedOptions(sys.argv, [
'JOB_NAME',
'source_bucket',
'target_bucket',
'database',
'table_name'
])
# Inicialização padrão
sc = SparkContext()
glueContext = GlueContext(sc)
spark = glueContext.spark_session
job = Job(glueContext)
job.init(args['JOB_NAME'], args)
# ===== LEITURA =====
# Ler do Data Catalog (tabela catalogada pelo Crawler)
datasource = glueContext.create_dynamic_frame.from_catalog(
database=args['database'],
table_name=args['table_name'],
transformation_ctx="datasource"
)
# Ler direto do S3 (sem catalog)
datasource_s3 = glueContext.create_dynamic_frame.from_options(
format_options={"multiline": False},
connection_type="s3",
format="json",
connection_options={
"paths": [f"s3://{args['source_bucket']}/dados/"],
"recurse": True
},
transformation_ctx="datasource_s3"
)
# Ler de banco JDBC (via Connection configurada no Glue)
datasource_rds = glueContext.create_dynamic_frame.from_catalog(
database="meu_catalog",
table_name="public_pedidos",
transformation_ctx="datasource_rds"
)
# ===== TRANSFORMAÇÕES =====
# ApplyMapping: renomear e converter tipos de colunas
mapeado = ApplyMapping.apply(
frame=datasource,
mappings=[
("id_pedido", "long", "pedido_id", "long"),
("valor_total", "string", "valor", "double"),
("dt_criacao", "string", "data_criacao", "date"),
("status_cod", "int", "status", "string")
],
transformation_ctx="mapeado"
)
# DropNullFields: remover campos completamente nulos
sem_nulos = DropNullFields.apply(
frame=mapeado,
transformation_ctx="sem_nulos"
)
# Filter: filtrar registros
pedidos_validos = Filter.apply(
frame=sem_nulos,
f=lambda row: row["valor"] > 0 and row["status"] is not None,
transformation_ctx="pedidos_validos"
)
# ResolveChoice: resolver conflitos de tipo
resolvido = ResolveChoice.apply(
frame=pedidos_validos,
choice="make_struct",
transformation_ctx="resolvido"
)
# Converter para DataFrame Spark para transformações avançadas
df = resolvido.toDF()
# Transformações Spark normais
df_enriquecido = df \
.withColumn("ano_mes", F.date_format("data_criacao", "yyyy-MM")) \
.withColumn("categoria", F.when(F.col("valor") > 1000, "premium").otherwise("standard")) \
.filter(F.col("data_criacao") >= "2024-01-01") \
.dropDuplicates(["pedido_id"])
# Join com outra tabela
clientes_df = glueContext.create_dynamic_frame.from_catalog(
database=args['database'],
table_name='clientes'
).toDF()
df_final = df_enriquecido.join(
clientes_df.select("cliente_id", "nome", "regiao"),
on="cliente_id",
how="left"
)
# Converter de volta para DynamicFrame
resultado = DynamicFrame.fromDF(df_final, glueContext, "resultado")
# ===== ESCRITA =====
# Escrever no S3 particionado
glueContext.write_dynamic_frame.from_options(
frame=resultado,
connection_type="s3",
format="parquet",
connection_options={
"path": f"s3://{args['target_bucket']}/pedidos-processados/",
"partitionKeys": ["ano_mes", "regiao"]
},
format_options={
"compression": "snappy",
"useGlueParquetWriter": True
},
transformation_ctx="saida"
)
# Escrever no Redshift
glueContext.write_dynamic_frame.from_jdbc_conf(
frame=resultado,
catalog_connection="redshift-conn",
connection_options={
"dbtable": "public.pedidos_processados",
"database": "analytics"
},
redshift_tmp_dir=f"s3://{args['target_bucket']}/tmp/"
)
# Finalizar job (commit do bookmark)
job.commit()Job Bookmarks
Job bookmarks permitem processar apenas dados novos desde a última execução:
# Ler com bookmark habilitado (apenas novos arquivos desde último run)
datasource = glueContext.create_dynamic_frame.from_options(
connection_type="s3",
connection_options={
"paths": ["s3://meu-bucket/dados/"],
"recurse": True,
"useS3ListImplementation": True
},
format="parquet",
transformation_ctx="datasource" # transformation_ctx é necessário para bookmark
)# Habilitar bookmark ao criar job
aws glue create-job \
--name meu-etl \
--role GlueRole \
--command Name=glueetl,ScriptLocation=s3://bucket/scripts/meu-etl.py,PythonVersion=3 \
--default-arguments '{
"--job-bookmark-option": "job-bookmark-enable",
"--enable-metrics": "true",
"--enable-continuous-cloudwatch-log": "true"
}'
# Resetar bookmark (reprocessar tudo)
aws glue reset-job-bookmark --job-name meu-etlCrawler
# Criar crawler para S3
aws glue create-crawler \
--name crawler-dados-pedidos \
--role arn:aws:iam::123456789012:role/GlueRole \
--database-name meu_catalog \
--targets '{
"S3Targets": [
{
"Path": "s3://meu-bucket/dados/pedidos/",
"Exclusions": ["**/_temporary/**", "**/__pycache__/**"]
}
]
}' \
--schema-change-policy '{
"UpdateBehavior": "UPDATE_IN_DATABASE",
"DeleteBehavior": "LOG"
}' \
--recrawl-policy '{"RecrawlBehavior": "CRAWL_NEW_FOLDERS_ONLY"}' \
--schedule "cron(0 6 * * ? *)" # diariamente às 6h
# Criar crawler para RDS (requer Connection configurada)
aws glue create-crawler \
--name crawler-rds-postgres \
--role arn:aws:iam::123456789012:role/GlueRole \
--database-name rds_catalog \
--targets '{
"JdbcTargets": [
{
"ConnectionName": "conexao-postgres",
"Path": "meubanco/public/%"
}
]
}'
# Executar crawler
aws glue start-crawler --name crawler-dados-pedidos
# Ver status
aws glue get-crawler \
--name crawler-dados-pedidos \
--query 'Crawler.{Estado:State,UltimaExecucao:LastCrawl}'
# Listar tabelas no catalog
aws glue get-tables \
--database-name meu_catalog \
--query 'TableList[*].[Name,StorageDescriptor.Location]' \
--output tableGlue Workflow
# Criar workflow
aws glue create-workflow --name pipeline-diario
# Criar trigger inicial (schedule)
aws glue create-trigger \
--name trigger-inicio \
--workflow-name pipeline-diario \
--type SCHEDULED \
--schedule "cron(0 3 * * ? *)" \
--actions '[{"CrawlerName": "crawler-dados-pedidos"}]'
# Trigger que dispara após crawler terminar
aws glue create-trigger \
--name trigger-apos-crawler \
--workflow-name pipeline-diario \
--type CONDITIONAL \
--predicate '{
"Logical": "ANY",
"Conditions": [
{
"LogicalOperator": "EQUALS",
"CrawlerName": "crawler-dados-pedidos",
"CrawlState": "SUCCEEDED"
}
]
}' \
--actions '[{"JobName": "meu-etl"}]'
# Trigger que dispara após job terminar
aws glue create-trigger \
--name trigger-apos-etl \
--workflow-name pipeline-diario \
--type CONDITIONAL \
--predicate '{
"Logical": "ANY",
"Conditions": [
{
"LogicalOperator": "EQUALS",
"JobName": "meu-etl",
"State": "SUCCEEDED"
}
]
}' \
--actions '[{"JobName": "job-consolidacao"}]'
# Iniciar workflow manualmente
aws glue start-workflow-run --name pipeline-diario
# Ver runs
aws glue get-workflow-runs \
--name pipeline-diario \
--query 'Runs[0].{Status:Status,Start:StartedOn}'CLI — Glue Jobs
# Iniciar job
aws glue start-job-run \
--job-name meu-etl \
--arguments '{
"--source_bucket": "bucket-origem",
"--target_bucket": "bucket-destino",
"--database": "meu_catalog",
"--table_name": "pedidos"
}'
# Ver status do run
aws glue get-job-run \
--job-name meu-etl \
--run-id jr_abc123 \
--query 'JobRun.{Estado:JobRunState,Inicio:StartedOn,Duracao:ExecutionTime}'
# Ver logs no CloudWatch
aws logs get-log-events \
--log-group-name /aws-glue/jobs/output \
--log-stream-name meu-etl/jr_abc123 \
--start-from-head
# Listar runs recentes
aws glue get-job-runs \
--job-name meu-etl \
--max-results 10 \
--query 'JobRuns[*].[Id,JobRunState,StartedOn,ExecutionTime]' \
--output table
# Cancelar run
aws glue batch-stop-job-run \
--job-name meu-etl \
--job-run-ids jr_abc1239. VPC — Fundamentos
Conceitos essenciais
- VPC: rede virtual privada isolada. CIDR padrão:
10.0.0.0/16 - Subnet pública: tem rota para Internet Gateway; instâncias podem ter IP público
- Subnet privada: sem rota para IGW; acessa internet via NAT Gateway
- Route Table: tabela de rotas por subnet
- IGW (Internet Gateway): saída para internet das subnets públicas
- NAT Gateway: permite que subnets privadas acessem internet (saída apenas); caro, considere NAT Instance para dev
VPC típica — estrutura
VPC 10.0.0.0/16
│
├── us-east-1a
│ ├── Subnet pública 10.0.1.0/24 → IGW
│ └── Subnet privada 10.0.2.0/24 → NAT GW
│
├── us-east-1b
│ ├── Subnet pública 10.0.3.0/24 → IGW
│ └── Subnet privada 10.0.4.0/24 → NAT GW
│
└── Route Tables:
├── public-rt: 0.0.0.0/0 → igw-xxx
└── private-rt: 0.0.0.0/0 → nat-xxx# Criar VPC
aws ec2 create-vpc \
--cidr-block 10.0.0.0/16 \
--tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=minha-vpc}]'
# Criar subnets
aws ec2 create-subnet \
--vpc-id vpc-0123456789abcdef0 \
--cidr-block 10.0.1.0/24 \
--availability-zone us-east-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=public-1a}]'
# Criar e anexar IGW
aws ec2 create-internet-gateway
aws ec2 attach-internet-gateway \
--vpc-id vpc-0123456789 \
--internet-gateway-id igw-0123456789
# Criar NAT Gateway (requer Elastic IP)
aws ec2 allocate-address --domain vpc # retorna AllocationId
aws ec2 create-nat-gateway \
--subnet-id subnet-public-1a \
--allocation-id eipalloc-0123456789
# Criar route table e rotas
aws ec2 create-route-table --vpc-id vpc-0123456789
aws ec2 create-route \
--route-table-id rtb-0123456789 \
--destination-cidr-block 0.0.0.0/0 \
--gateway-id igw-0123456789
# Associar route table com subnet
aws ec2 associate-route-table \
--subnet-id subnet-0123456789 \
--route-table-id rtb-0123456789Security Groups vs NACLs
| Security Group | NACL | |
|---|---|---|
| Nível | Instância/ENI | Subnet |
| Estado | Stateful (resposta automática) | Stateless (regras in + out separadas) |
| Regras | Apenas Allow | Allow e Deny |
| Avaliação | Todas as regras | Número da regra (primeira match) |
| Default | Bloqueia tudo | Permite tudo |
# Criar security group
aws ec2 create-security-group \
--group-name meu-sg \
--description "SG da aplicacao" \
--vpc-id vpc-0123456789
# Liberar porta 443 de qualquer IP
aws ec2 authorize-security-group-ingress \
--group-id sg-0123456789 \
--protocol tcp \
--port 443 \
--cidr 0.0.0.0/0
# Liberar porta 5432 somente de outro security group (sem expor IP)
aws ec2 authorize-security-group-ingress \
--group-id sg-rds \
--protocol tcp \
--port 5432 \
--source-group sg-appVPC Endpoints
Permite acesso a serviços AWS sem sair da rede privada (sem NAT, sem custo de tráfego):
# Gateway Endpoint para S3 (gratuito)
aws ec2 create-vpc-endpoint \
--vpc-id vpc-0123456789 \
--service-name com.amazonaws.us-east-1.s3 \
--vpc-endpoint-type Gateway \
--route-table-ids rtb-private-0123456789
# Interface Endpoint para Secrets Manager (Interface Endpoint tem custo)
aws ec2 create-vpc-endpoint \
--vpc-id vpc-0123456789 \
--service-name com.amazonaws.us-east-1.secretsmanager \
--vpc-endpoint-type Interface \
--subnet-ids subnet-private-1a subnet-private-1b \
--security-group-ids sg-endpoints \
--private-dns-enabled10. CloudWatch
Métricas, dimensões e namespaces
# Publicar métrica customizada
aws cloudwatch put-metric-data \
--namespace "MinhAplicacao" \
--metric-name "PedidosProcessados" \
--dimensions Servico=backend,Ambiente=producao \
--value 42 \
--unit Count \
--timestamp 2024-05-06T12:00:00Z
# Listar métricas de um namespace
aws cloudwatch list-metrics \
--namespace "MinhAplicacao" \
--metric-name "PedidosProcessados"
# Obter estatísticas da métrica
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name Duration \
--dimensions Name=FunctionName,Value=MinhaFuncao \
--start-time 2024-05-06T00:00:00Z \
--end-time 2024-05-06T23:59:59Z \
--period 3600 \
--statistics Average MaximumLogs — log groups, streams e Insights
# Criar log group
aws logs create-log-group --log-group-name /minha-app/backend
# Definir retenção (dias)
aws logs put-retention-policy \
--log-group-name /minha-app/backend \
--retention-in-days 30
# Ver logs recentes (tail)
aws logs tail /aws/lambda/MinhaFuncao --follow --format short
# Obter eventos de um stream
aws logs get-log-events \
--log-group-name /aws/lambda/MinhaFuncao \
--log-stream-name "2024/05/06/[$LATEST]abc123" \
--start-from-head
# CloudWatch Logs Insights — query interativa
aws logs start-query \
--log-group-names "/aws/lambda/MinhaFuncao" \
--start-time $(date -d '1 hour ago' +%s) \
--end-time $(date +%s) \
--query-string '
fields @timestamp, @message
| filter @message like /ERROR/
| sort @timestamp desc
| limit 50
'
# Obter resultado da query (use o queryId retornado acima)
aws logs get-query-results --query-id abc-123Exemplos de queries Logs Insights:
# Top 10 funções Lambda com mais erros
stats count(*) as erros by functionName
| filter @message like /ERROR/
| sort erros desc
| limit 10
# Latência média e p99 de API Gateway
stats avg(responseLatency), pct(responseLatency, 99) by bin(5m)
# Contar por status HTTP
stats count(*) as requests by status
| filter ispresent(status)
| sort requests descAlarms
# Criar alarme de métrica
aws cloudwatch put-metric-alarm \
--alarm-name "Lambda-Erros-Alto" \
--alarm-description "Taxa de erros acima de 5%" \
--metric-name Errors \
--namespace AWS/Lambda \
--dimensions Name=FunctionName,Value=MinhaFuncao \
--statistic Sum \
--period 300 \
--evaluation-periods 2 \
--threshold 10 \
--comparison-operator GreaterThanOrEqualToThreshold \
--treat-missing-data notBreaching \
--alarm-actions arn:aws:sns:us-east-1:123456789012:alertas \
--ok-actions arn:aws:sns:us-east-1:123456789012:alertas
# Criar alarme de anomalia (sem threshold fixo)
aws cloudwatch put-metric-alarm \
--alarm-name "Lambda-Duracao-Anomalia" \
--metrics '[
{
"Id": "m1",
"MetricStat": {
"Metric": {
"Namespace": "AWS/Lambda",
"MetricName": "Duration",
"Dimensions": [{"Name": "FunctionName", "Value": "MinhaFuncao"}]
},
"Period": 300,
"Stat": "Average"
}
},
{
"Id": "ad1",
"Expression": "ANOMALY_DETECTION_BAND(m1, 2)",
"Label": "Duration (Expected)"
}
]' \
--comparison-operator GreaterThanUpperThreshold \
--threshold-metric-id ad1 \
--evaluation-periods 2
# Ver alarmes ativos
aws cloudwatch describe-alarms \
--state-value ALARM \
--query 'MetricAlarms[*].[AlarmName,StateValue,StateReason]' \
--output table11. EventBridge
Conceitos
- Event Bus: canal de eventos.
defaultrecebe eventos AWS nativos. Crie buses customizados para eventos da sua aplicação. - Rule: define quais eventos do bus são capturados e enviados para targets
- Event Pattern: filtro baseado em campos do evento JSON
- Schedule: cron ou rate expression
CLI — operações
# Criar event bus customizado
aws events create-event-bus --name minha-app-events
# Publicar evento customizado
aws events put-events \
--entries '[
{
"EventBusName": "minha-app-events",
"Source": "com.minha-empresa.backend",
"DetailType": "PedidoCriado",
"Detail": "{\"pedido_id\": 42, \"cliente_id\": 99, \"valor\": 250.00}",
"Time": "2024-05-06T12:00:00Z"
}
]'
# Criar rule com event pattern
aws events put-rule \
--name "ProcessarPedidosNovos" \
--event-bus-name minha-app-events \
--event-pattern '{
"source": ["com.minha-empresa.backend"],
"detail-type": ["PedidoCriado"],
"detail": {
"valor": [{"numeric": [">", 100]}]
}
}' \
--state ENABLED \
--description "Processa pedidos acima de R$100"
# Criar rule com schedule (cron)
aws events put-rule \
--name "LimpezaDiaria" \
--schedule-expression "cron(0 3 * * ? *)" \
--state ENABLED
# Criar rule com rate
aws events put-rule \
--name "HealthCheck" \
--schedule-expression "rate(5 minutes)" \
--state ENABLED
# Adicionar Lambda como target
aws events put-targets \
--rule "ProcessarPedidosNovos" \
--event-bus-name minha-app-events \
--targets '[
{
"Id": "LambdaProcessamento",
"Arn": "arn:aws:lambda:us-east-1:123456789012:function:ProcessarPedido"
}
]'
# Adicionar SQS como target (com DLQ para falhas de delivery)
aws events put-targets \
--rule "ProcessarPedidosNovos" \
--event-bus-name minha-app-events \
--targets '[
{
"Id": "FilaPedidos",
"Arn": "arn:aws:sqs:us-east-1:123456789012:pedidos-queue",
"DeadLetterConfig": {
"Arn": "arn:aws:sqs:us-east-1:123456789012:events-dlq"
}
}
]'
# Dar permissão para EventBridge invocar Lambda
aws lambda add-permission \
--function-name ProcessarPedido \
--statement-id EventBridgeRule \
--action lambda:InvokeFunction \
--principal events.amazonaws.com \
--source-arn arn:aws:events:us-east-1:123456789012:rule/ProcessarPedidosNovosExemplo: evento S3 → EventBridge → Lambda
{
"source": ["aws.s3"],
"detail-type": ["Object Created"],
"detail": {
"bucket": {
"name": ["meu-bucket-uploads"]
},
"object": {
"key": [{"prefix": "imagens/"}]
}
}
}# Habilitar EventBridge notifications no bucket S3
aws s3api put-bucket-notification-configuration \
--bucket meu-bucket-uploads \
--notification-configuration '{
"EventBridgeConfiguration": {}
}'# Handler Lambda recebendo evento S3 via EventBridge
def lambda_handler(event, context):
bucket = event['detail']['bucket']['name']
chave = event['detail']['object']['key']
tamanho = event['detail']['object']['size']
print(f"Novo arquivo: s3://{bucket}/{chave} ({tamanho} bytes)")
processar_imagem(bucket, chave)12. Secrets Manager e Parameter Store
Secrets Manager
Gerencia segredos rotativos (senhas, API keys, certificados).
# Criar segredo
aws secretsmanager create-secret \
--name prod/rds/meu-banco \
--description "Credenciais RDS producao" \
--secret-string '{
"host": "meu-banco.cluster-xxx.us-east-1.rds.amazonaws.com",
"port": 5432,
"dbname": "meubanco",
"username": "admin",
"password": "senha-segura-aqui"
}'
# Recuperar segredo (valor atual)
aws secretsmanager get-secret-value \
--secret-id prod/rds/meu-banco \
--query SecretString \
--output text
# Atualizar valor
aws secretsmanager put-secret-value \
--secret-id prod/rds/meu-banco \
--secret-string '{"host": "...", "password": "nova-senha"}'
# Habilitar rotação automática (requer Lambda de rotação)
aws secretsmanager rotate-secret \
--secret-id prod/rds/meu-banco \
--rotation-lambda-arn arn:aws:lambda:us-east-1:123456789012:function:RotacaoRDS \
--rotation-rules AutomaticallyAfterDays=30
# Listar segredos
aws secretsmanager list-secrets \
--query 'SecretList[*].[Name,LastChangedDate]' \
--output table
# Deletar segredo (com recuperação de 7 dias)
aws secretsmanager delete-secret \
--secret-id prod/rds/meu-banco \
--recovery-window-in-days 7
# Deletar imediatamente (sem recuperação)
aws secretsmanager delete-secret \
--secret-id prod/rds/meu-banco \
--force-delete-without-recoverySecrets Manager com Python (boto3)
import boto3
import json
import logging
from functools import lru_cache
from botocore.exceptions import ClientError
logger = logging.getLogger(__name__)
# Cache global — reutilizado entre invocações Lambda no mesmo container
_cache_segredos = {}
def obter_segredo(nome_segredo: str, cache: bool = True) -> dict:
"""
Recupera e faz parse do segredo JSON.
Cache em memória evita chamadas repetidas ao Secrets Manager
durante o warm start do Lambda.
"""
if cache and nome_segredo in _cache_segredos:
return _cache_segredos[nome_segredo]
sm = boto3.client('secretsmanager', region_name='us-east-1')
try:
resposta = sm.get_secret_value(SecretId=nome_segredo)
except ClientError as e:
codigo = e.response['Error']['Code']
if codigo == 'ResourceNotFoundException':
raise ValueError(f"Segredo '{nome_segredo}' não encontrado") from e
elif codigo == 'AccessDeniedException':
raise PermissionError(f"Sem acesso ao segredo '{nome_segredo}'") from e
raise
# SecretString para JSON, SecretBinary para binário
if 'SecretString' in resposta:
segredo = json.loads(resposta['SecretString'])
else:
import base64
segredo = base64.b64decode(resposta['SecretBinary'])
if cache:
_cache_segredos[nome_segredo] = segredo
return segredo
# Uso:
# creds = obter_segredo('prod/rds/meu-banco')
# conn = psycopg2.connect(host=creds['host'], ...)Secrets Manager com Java SDK v2
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class SecretsHelper {
private static final SecretsManagerClient CLIENT =
SecretsManagerClient.builder()
.region(Region.US_EAST_1)
.build();
// Cache thread-safe em memória
private static final Map<String, String> CACHE = new ConcurrentHashMap<>();
private static final ObjectMapper MAPPER = new ObjectMapper();
public static String obterSegredoJson(String nomeSegredo) {
return CACHE.computeIfAbsent(nomeSegredo, nome -> {
GetSecretValueResponse resposta = CLIENT.getSecretValue(
GetSecretValueRequest.builder()
.secretId(nome)
.build()
);
return resposta.secretString();
});
}
@SuppressWarnings("unchecked")
public static Map<String, String> obterCredenciais(String nomeSegredo) {
try {
return MAPPER.readValue(obterSegredoJson(nomeSegredo), Map.class);
} catch (Exception e) {
throw new RuntimeException("Erro ao parsear segredo: " + nomeSegredo, e);
}
}
}SSM Parameter Store
Mais simples que Secrets Manager. Ideal para configurações (não segredos críticos).
| Tipo | Descrição | Criptografia |
|---|---|---|
| String | Valor texto simples | Não |
| StringList | Lista separada por vírgulas | Não |
| SecureString | Criptografado com KMS | Sim (KMS) |
# Criar parâmetros
aws ssm put-parameter \
--name "/minha-app/producao/db-host" \
--value "meu-banco.cluster-xxx.rds.amazonaws.com" \
--type String \
--description "Endpoint RDS producao"
# Criar parâmetro criptografado
aws ssm put-parameter \
--name "/minha-app/producao/api-key" \
--value "sk-super-secreta-aqui" \
--type SecureString \
--key-id arn:aws:kms:us-east-1:123456789012:key/minha-chave-kms \
--description "API Key do serviço externo"
# Ler parâmetro (String)
aws ssm get-parameter \
--name "/minha-app/producao/db-host" \
--query Parameter.Value \
--output text
# Ler parâmetro SecureString (decrypt automático)
aws ssm get-parameter \
--name "/minha-app/producao/api-key" \
--with-decryption \
--query Parameter.Value \
--output text
# Ler múltiplos parâmetros de um prefix
aws ssm get-parameters-by-path \
--path "/minha-app/producao/" \
--recursive \
--with-decryption \
--query 'Parameters[*].[Name,Value]' \
--output table
# Atualizar parâmetro (criando nova versão)
aws ssm put-parameter \
--name "/minha-app/producao/db-host" \
--value "novo-endpoint.rds.amazonaws.com" \
--type String \
--overwrite
# Ver histórico de versões
aws ssm get-parameter-history \
--name "/minha-app/producao/db-host"
# Deletar parâmetro
aws ssm delete-parameter --name "/minha-app/producao/db-host"Parameter Store com Python
import boto3
import json
ssm = boto3.client('ssm', region_name='us-east-1')
# Cache local (Lambda: persiste entre invocações no mesmo container)
_parametros_cache = {}
def obter_parametro(nome: str, descriptografar: bool = True, usar_cache: bool = True) -> str:
if usar_cache and nome in _parametros_cache:
return _parametros_cache[nome]
resposta = ssm.get_parameter(
Name=nome,
WithDecryption=descriptografar
)
valor = resposta['Parameter']['Value']
if usar_cache:
_parametros_cache[nome] = valor
return valor
def obter_parametros_por_prefix(prefix: str, descriptografar: bool = True) -> dict:
"""Carrega todos os parâmetros de um caminho de uma vez."""
resultado = {}
paginator = ssm.get_paginator('get_parameters_by_path')
for pagina in paginator.paginate(
Path=prefix,
Recursive=True,
WithDecryption=descriptografar
):
for param in pagina['Parameters']:
# Extrai o nome relativo ao prefix
nome_curto = param['Name'].replace(prefix, '').lstrip('/')
resultado[nome_curto] = param['Value']
return resultadoPadrão: Lambda lendo secrets no cold start com cache
import boto3
import json
import os
import psycopg2
# ============================================================
# INICIALIZAÇÃO FORA DO HANDLER = executa apenas no cold start
# Reutilizado em todas as invocações subsequentes no container
# ============================================================
def _carregar_config():
"""Carrega toda a configuração uma única vez no cold start."""
sm = boto3.client('secretsmanager')
ssm = boto3.client('ssm')
# Segredo: credenciais RDS
secret_id = os.environ['DB_SECRET_ARN']
secret_resp = sm.get_secret_value(SecretId=secret_id)
db_creds = json.loads(secret_resp['SecretString'])
# Parâmetros de configuração
prefix = os.environ['SSM_PREFIX'] # ex: /minha-app/prod
params = {}
paginator = ssm.get_paginator('get_parameters_by_path')
for pagina in paginator.paginate(Path=prefix, WithDecryption=True):
for p in pagina['Parameters']:
chave = p['Name'].split('/')[-1]
params[chave] = p['Value']
return db_creds, params
# Executado no cold start, resultado reutilizado entre invocações
_db_creds, _config = _carregar_config()
# Conexão DB criada no cold start e reutilizada
_conn = psycopg2.connect(
host=_db_creds['host'],
port=_db_creds['port'],
database=_db_creds['dbname'],
user=_db_creds['username'],
password=_db_creds['password']
)
# ============================================================
# HANDLER — executa em cada invocação
# ============================================================
def lambda_handler(event, context):
global _conn
# Reconectar se conexão perdeu (timeout, failover, etc.)
if _conn.closed:
_conn = psycopg2.connect(
host=_db_creds['host'],
port=_db_creds['port'],
database=_db_creds['dbname'],
user=_db_creds['username'],
password=_db_creds['password']
)
with _conn.cursor() as cur:
cur.execute("SELECT COUNT(*) FROM pedidos WHERE status = 'pendente'")
total = cur.fetchone()[0]
return {
'statusCode': 200,
'body': json.dumps({'pedidos_pendentes': total})
}Referência rápida — ARN format
arn:partition:service:region:account-id:resource-type/resource-id
Exemplos:
arn:aws:s3:::meu-bucket
arn:aws:s3:::meu-bucket/*
arn:aws:lambda:us-east-1:123456789012:function:MinhaFuncao
arn:aws:iam::123456789012:role/MinhaRole
arn:aws:iam::123456789012:user/joao
arn:aws:sqs:us-east-1:123456789012:minha-fila
arn:aws:sns:us-east-1:123456789012:meu-topic
arn:aws:rds:us-east-1:123456789012:db:meu-banco
arn:aws:eks:us-east-1:123456789012:cluster/meu-cluster
arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/rds/meu-banco
arn:aws:ssm:us-east-1:123456789012:parameter/minha-app/prod/config
arn:aws:events:us-east-1:123456789012:rule/MeuEvento
arn:aws:logs:us-east-1:123456789012:log-group:/aws/lambda/MinhaFuncaoReferência rápida — Regiões comuns
| Código | Região |
|---|---|
us-east-1 | Virgínia do Norte (mais barata, mais serviços) |
us-east-2 | Ohio |
us-west-2 | Oregon |
sa-east-1 | São Paulo |
eu-west-1 | Irlanda |
eu-central-1 | Frankfurt |
ap-southeast-1 | Singapura |
ap-northeast-1 | Tóquio |
AWS Organizations
AWS Organizations permite gerenciar múltiplas contas AWS de forma centralizada — controle de acesso, faturamento consolidado e criação automatizada de contas novas.
Conceitos fundamentais
| Conceito | Descrição |
|---|---|
| Management Account | Conta raiz da organização; paga todas as outras; não deve ter cargas de trabalho |
| Member Account | Qualquer conta dentro da organização |
| Root | Nó raiz da hierarquia de OUs |
| OU (Organizational Unit) | Agrupamento lógico de contas (ex: Dev, Prod, Sandbox) |
| SCP (Service Control Policy) | Política que limita o que é possível fazer nas contas filhas |
Estrutura hierárquica
Root
├── OU: Workloads
│ ├── OU: Prod
│ │ ├── conta-prod-financeiro
│ │ └── conta-prod-plataforma
│ └── OU: Dev
│ ├── conta-dev-financeiro
│ └── conta-dev-sandbox
├── OU: Segurança
│ ├── conta-log-archive
│ └── conta-security-tooling
└── OU: Infraestrutura
└── conta-networkSCPs — Service Control Policies
SCPs definem o teto máximo de permissões para contas e OUs. Elas não concedem permissões — apenas limitam o que as políticas IAM locais podem autorizar. A conta de management não é afetada por SCPs.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyLeaveOrganization",
"Effect": "Deny",
"Action": "organizations:LeaveOrganization",
"Resource": "*"
},
{
"Sid": "AllowOnlyApprovedRegions",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["us-east-1", "sa-east-1", "us-west-2"]
}
}
}
]
}{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ProtectCloudTrail",
"Effect": "Deny",
"Action": [
"cloudtrail:StopLogging",
"cloudtrail:DeleteTrail",
"cloudtrail:UpdateTrail"
],
"Resource": "*"
}
]
}Comandos CLI principais
# Listar todas as contas da organização
aws organizations list-accounts
# Listar OUs filhas do root
ROOT_ID=$(aws organizations list-roots --query 'Roots[0].Id' --output text)
aws organizations list-organizational-units-for-parent --parent-id "$ROOT_ID"
# Listar contas em uma OU
aws organizations list-accounts-for-parent \
--parent-id ou-xxxx-yyyyyyyy
# Listar SCPs anexadas a uma OU
aws organizations list-policies-for-target \
--target-id ou-xxxx-yyyyyyyy \
--filter SERVICE_CONTROL_POLICY
# Criar uma nova conta membro
aws organizations create-account \
--email nova-conta@empresa.com \
--account-name "Prod-Financeiro" \
--iam-user-access-to-billing ALLOW
# Mover conta para uma OU
aws organizations move-account \
--account-id 123456789012 \
--source-parent-id "$ROOT_ID" \
--destination-parent-id ou-xxxx-yyyyyyyyConsolidated Billing
Com Organizations, todas as contas membro têm seu custo consolidado na management account. Vantagens:
- Volume discounts: uso agregado de EC2, S3, etc. pode atingir faixas de desconto mais altas
- Reserved Instances e Savings Plans compartilhados: RIs ociosas em uma conta são aplicadas automaticamente em outras contas da organização
- Uma única fatura para o departamento financeiro
Account Factory (AWS Control Tower)
O AWS Control Tower automatiza a criação de contas com guardrails (SCPs e Config Rules) pré-configurados. Account Factory permite criar novas contas via console, CLI ou Terraform, já com logging, segurança e conformidade aplicados.
# Account Factory via AWS Service Catalog (Control Tower)
aws servicecatalog provision-product \
--product-name "AWS Control Tower Account Factory" \
--provisioning-artifact-name "AWS Control Tower Account Factory" \
--provisioned-product-name "conta-dev-novo-time" \
--provisioning-parameters \
Key=AccountName,Value=dev-novo-time \
Key=AccountEmail,Value=dev-novo-time@empresa.com \
Key=ManagedOrganizationalUnit,Value="Dev (ou-xxxx-yyyyyyyy)" \
Key=SSOUserEmail,Value=admin@empresa.com \
Key=SSOUserFirstName,Value=Admin \
Key=SSOUserLastName,Value=DevNovoTimeCost Explorer e Budgets
Cost Explorer
O Cost Explorer é o serviço de análise de custos da AWS. Permite visualizar gastos históricos (até 12 meses), projeções futuras e anomalias de custo.
Ativar o Cost Explorer (feito uma vez na management account):
aws ce put-usage-definition --no-cli-pager 2>/dev/null || \
aws ce update-cost-allocation-tags-status || true
# O Cost Explorer é ativado via console em Billing > Cost Explorer > EnableConsultas via CLI:
# Custo total dos últimos 30 dias por serviço
aws ce get-cost-and-usage \
--time-period Start=2025-04-01,End=2025-05-01 \
--granularity MONTHLY \
--metrics "BlendedCost" "UnblendedCost" \
--group-by Type=DIMENSION,Key=SERVICE \
--query 'ResultsByTime[0].Groups[*].[Keys[0],Total.BlendedCost.Amount]' \
--output table
# Custo diário do mês atual
aws ce get-cost-and-usage \
--time-period Start=2025-05-01,End=2025-05-06 \
--granularity DAILY \
--metrics "UnblendedCost"
# Custo por conta (em uma organização)
aws ce get-cost-and-usage \
--time-period Start=2025-04-01,End=2025-05-01 \
--granularity MONTHLY \
--metrics "UnblendedCost" \
--group-by Type=DIMENSION,Key=LINKED_ACCOUNTCost Allocation Tags
Tags de alocação de custo permitem rastrear gastos por projeto, time, ambiente ou qualquer dimensão de negócio. É preciso ativar as tags no console de Billing para que apareçam no Cost Explorer.
# Ativar tag de alocação de custo
aws ce update-cost-allocation-tags-status \
--cost-allocation-tags-status TagKey=Projeto,Status=Active \
--cost-allocation-tags-status TagKey=Ambiente,Status=Active \
--cost-allocation-tags-status TagKey=Time,Status=Active
# Listar tags ativas
aws ce list-cost-allocation-tags --status Active
# Verificar custo por tag (após ativação — dados disponíveis a partir do dia seguinte)
aws ce get-cost-and-usage \
--time-period Start=2025-04-01,End=2025-05-01 \
--granularity MONTHLY \
--metrics "UnblendedCost" \
--group-by Type=TAG,Key=ProjetoAWS Budgets — criar alertas de custo
# Criar budget mensal de $500 com alerta em 80% e 100%
aws budgets create-budget \
--account-id 123456789012 \
--budget '{
"BudgetName": "budget-mensal-producao",
"BudgetLimit": {
"Amount": "500",
"Unit": "USD"
},
"TimeUnit": "MONTHLY",
"BudgetType": "COST"
}' \
--notifications-with-subscribers '[
{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 80,
"ThresholdType": "PERCENTAGE"
},
"Subscribers": [
{
"SubscriptionType": "EMAIL",
"Address": "ops@empresa.com"
}
]
},
{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 100,
"ThresholdType": "PERCENTAGE"
},
"Subscribers": [
{
"SubscriptionType": "EMAIL",
"Address": "ops@empresa.com"
},
{
"SubscriptionType": "SNS",
"Address": "arn:aws:sns:us-east-1:123456789012:alertas-custo"
}
]
}
]'# Listar budgets existentes
aws budgets describe-budgets --account-id 123456789012
# Verificar execução atual de um budget
aws budgets describe-budget \
--account-id 123456789012 \
--budget-name budget-mensal-producao \
--query 'Budget.{Limite:BudgetLimit,Atual:CalculatedSpend.ActualSpend}'VPC Peering e Transit Gateway
VPC Peering
VPC Peering conecta duas VPCs diretamente, permitindo tráfego privado entre elas como se estivessem na mesma rede. Funciona entre VPCs da mesma conta, de contas diferentes e até de regiões diferentes (inter-region peering).
Limitações críticas:
- Não é transitivo: se VPC-A faz peering com VPC-B e VPC-B faz peering com VPC-C, VPC-A não consegue alcançar VPC-C
- Não suporta edge-to-edge routing (ex: VPN ou Direct Connect através do peering)
- Máximo de 125 peerings ativos por VPC (soft limit)
- CIDRs não podem se sobrepor
# Criar peering request
aws ec2 create-vpc-peering-connection \
--vpc-id vpc-11111111 \
--peer-vpc-id vpc-22222222 \
--peer-owner-id 123456789012 \
--peer-region us-east-1
# Aceitar o peering (na conta/região de destino)
aws ec2 accept-vpc-peering-connection \
--vpc-peering-connection-id pcx-abcdef123456
# Adicionar rotas em AMBAS as route tables (obrigatório)
# Na VPC-A: rota para o CIDR da VPC-B via peering
aws ec2 create-route \
--route-table-id rtb-aaaaaaaaa \
--destination-cidr-block 10.2.0.0/16 \
--vpc-peering-connection-id pcx-abcdef123456
# Na VPC-B: rota para o CIDR da VPC-A via peering
aws ec2 create-route \
--route-table-id rtb-bbbbbbbbb \
--destination-cidr-block 10.1.0.0/16 \
--vpc-peering-connection-id pcx-abcdef123456Transit Gateway
Transit Gateway é um hub de roteamento regional que conecta múltiplas VPCs e redes on-premises (VPN/Direct Connect) em uma topologia hub-and-spoke. Resolve o problema de escalabilidade do VPC Peering.
Vantagens sobre Peering:
- Roteamento transitivo: qualquer VPC conectada ao TGW pode alcançar qualquer outra
- Escalabilidade: suporta até 5.000 attachments por TGW
- Centraliza o controle de roteamento em route tables do TGW
- Suporta VPN e Direct Connect no mesmo hub
- Funciona entre regiões via TGW Peering
# Criar Transit Gateway
aws ec2 create-transit-gateway \
--description "TGW principal us-east-1" \
--options AutoAcceptSharedAttachments=enable,DefaultRouteTableAssociation=enable
# Criar attachment — conectar uma VPC ao TGW
aws ec2 create-transit-gateway-vpc-attachment \
--transit-gateway-id tgw-0123456789abcdef \
--vpc-id vpc-11111111 \
--subnet-ids subnet-aaa subnet-bbb # uma subnet por AZ
# Listar attachments
aws ec2 describe-transit-gateway-vpc-attachments \
--filters Name=transit-gateway-id,Values=tgw-0123456789abcdef
# Adicionar rota nas route tables das VPCs (aponta para o TGW)
aws ec2 create-route \
--route-table-id rtb-aaaaaaaaa \
--destination-cidr-block 10.0.0.0/8 \
--transit-gateway-id tgw-0123456789abcdefTopologia hub-and-spoke com Transit Gateway
┌─────────────────┐
│ Transit Gateway │
│ (hub central) │
└────────┬────────┘
┌─────────────────┼──────────────────┐
│ │ │
┌──────┴──────┐ ┌──────┴──────┐ ┌───────┴─────┐
│ VPC Prod │ │ VPC Dev │ │ VPC Shared │
│ 10.1.0.0/16 │ │ 10.2.0.0/16 │ │ 10.0.0.0/16 │
└─────────────┘ └─────────────┘ └─────────────┘
│
┌───────┴──────┐
│ On-premises │
│ (VPN/DX) │
└──────────────┘VPC Peering vs Transit Gateway
| Critério | VPC Peering | Transit Gateway |
|---|---|---|
| Roteamento transitivo | Não | Sim |
| Número de conexões | N×(N-1)/2 peerings para N VPCs | N attachments para N VPCs |
| Custo de dados | Sem custo extra por hora; $ por GB | $ por hora por attachment + $ por GB |
| Largura de banda | Sem limite adicional | Até 50 Gbps por VPC attachment |
| Quando usar | 2–5 VPCs, sem necessidade de roteamento transitivo | 5+ VPCs, on-premises, roteamento complexo |
ALB — Roteamento Avançado
Listener Rules — condições de roteamento
O ALB (Application Load Balancer) roteia requisições com base em regras no listener. Cada regra tem condições e uma ação. As regras são avaliadas em ordem de prioridade (menor número = maior prioridade). A regra default (prioridade mais baixa) define o comportamento para tudo que não casa.
Condições disponíveis:
# Regra por path (rota para target group de API)
aws elbv2 create-rule \
--listener-arn arn:aws:elasticloadbalancing:...:listener/... \
--priority 10 \
--conditions '[
{
"Field": "path-pattern",
"PathPatternConfig": {
"Values": ["/api/*", "/v1/*", "/v2/*"]
}
}
]' \
--actions '[
{
"Type": "forward",
"TargetGroupArn": "arn:aws:elasticloadbalancing:...:targetgroup/api-tg/..."
}
]'
# Regra por host header (multi-tenant)
aws elbv2 create-rule \
--listener-arn arn:aws:elasticloadbalancing:...:listener/... \
--priority 20 \
--conditions '[
{
"Field": "host-header",
"HostHeaderConfig": {
"Values": ["admin.meusite.com", "backoffice.meusite.com"]
}
}
]' \
--actions '[
{
"Type": "forward",
"TargetGroupArn": "arn:aws:elasticloadbalancing:...:targetgroup/admin-tg/..."
}
]'
# Regra combinada: host + path
aws elbv2 create-rule \
--listener-arn arn:aws:elasticloadbalancing:...:listener/... \
--priority 5 \
--conditions '[
{
"Field": "host-header",
"HostHeaderConfig": { "Values": ["app.meusite.com"] }
},
{
"Field": "path-pattern",
"PathPatternConfig": { "Values": ["/checkout/*"] }
}
]' \
--actions '[
{
"Type": "forward",
"TargetGroupArn": "arn:aws:elasticloadbalancing:...:targetgroup/checkout-tg/..."
}
]'Weighted Target Groups — canary releases e blue/green
# Regra com pesos — 90% para produção, 10% para canary
aws elbv2 create-rule \
--listener-arn arn:aws:elasticloadbalancing:...:listener/... \
--priority 100 \
--conditions '[{"Field": "path-pattern", "PathPatternConfig": {"Values": ["/*"]}}]' \
--actions '[
{
"Type": "forward",
"ForwardConfig": {
"TargetGroups": [
{
"TargetGroupArn": "arn:aws:...:targetgroup/prod-tg/...",
"Weight": 90
},
{
"TargetGroupArn": "arn:aws:...:targetgroup/canary-tg/...",
"Weight": 10
}
],
"TargetGroupStickinessConfig": {
"Enabled": true,
"DurationSeconds": 300
}
}
}
]'Sticky Sessions
# Habilitar sticky sessions em um target group
aws elbv2 modify-target-group-attributes \
--target-group-arn arn:aws:elasticloadbalancing:...:targetgroup/meu-tg/... \
--attributes \
Key=stickiness.enabled,Value=true \
Key=stickiness.type,Value=lb_cookie \
Key=stickiness.lb_cookie.duration_seconds,Value=86400OIDC Authentication Action
O ALB suporta autenticação via OIDC diretamente na camada de load balancing, sem precisar implementar OAuth no código da aplicação.
aws elbv2 create-rule \
--listener-arn arn:aws:elasticloadbalancing:...:listener/... \
--priority 1 \
--conditions '[{"Field": "path-pattern", "PathPatternConfig": {"Values": ["/app/*"]}}]' \
--actions '[
{
"Type": "authenticate-oidc",
"Order": 1,
"AuthenticateOidcConfig": {
"Issuer": "https://accounts.google.com",
"AuthorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"TokenEndpoint": "https://oauth2.googleapis.com/token",
"UserInfoEndpoint": "https://openidconnect.googleapis.com/v1/userinfo",
"ClientId": "meu-client-id.apps.googleusercontent.com",
"ClientSecret": "meu-client-secret",
"Scope": "openid email profile",
"OnUnauthenticatedRequest": "authenticate"
}
},
{
"Type": "forward",
"Order": 2,
"TargetGroupArn": "arn:aws:elasticloadbalancing:...:targetgroup/app-tg/..."
}
]'Query string e HTTP header como condições adicionais:
# Condição por query string (?version=v2)
{
"Field": "query-string",
"QueryStringConfig": {
"Values": [{"Key": "version", "Value": "v2"}]
}
}
# Condição por header customizado (ex: feature flag via header)
{
"Field": "http-header",
"HttpHeaderConfig": {
"HttpHeaderName": "X-Feature-Flag",
"Values": ["beta", "canary"]
}
}ECS vs EKS — Quando usar cada um
Tabela comparativa
| Critério | ECS | EKS |
|---|---|---|
| Complexidade operacional | Baixa — AWS gerencia o control plane inteiro | Alta — mesmo com managed nodes, Kubernetes tem curva íngreme |
| Controle e extensibilidade | Limitado à API da AWS | Total — operators, CRDs, service meshes, qualquer plugin K8s |
| Portabilidade | Proprietário AWS | Padrão Kubernetes — portável entre clouds e on-premises |
| Ecosystem | Integração nativa com IAM, ALB, CloudWatch, ECR | Helm charts, operadores CNCF, todo o ecossistema K8s |
| Custo do control plane | Sem custo adicional | $0,10/hora por cluster (~$73/mês) |
| Networking | VPC nativo (awsvpc mode) simples | CNI plugins, múltiplas opções (aws-vpc-cni, Calico, Cilium) |
| Service Discovery | AWS Cloud Map, ECS Service Connect | CoreDNS, Kubernetes Service, Ingress |
| Scaling | Service Auto Scaling + Fargate | HPA, VPA, KEDA, Cluster Autoscaler |
| Casos de uso ideais | Apps AWS-nativas, equipes pequenas, microserviços sem necessidade de K8s | Plataformas multi-cloud, equipes K8s, workloads complexos, ML/Batch |
ECS com Fargate vs ECS com EC2
| ECS + Fargate | ECS + EC2 | |
|---|---|---|
| Infraestrutura | Serverless — sem gerenciar instâncias | Você gerencia o cluster de instâncias |
| Custo | Maior por vCPU/GB (paga por task) | Menor para cargas previsíveis e altas |
| Controle | Sem acesso ao host | Acesso SSH, customização de AMI, GPU |
| Cold start | ~10-30s por task | Instância já ativa — task inicia em segundos |
| Spot | Fargate Spot (~70% desconto) | EC2 Spot + gerenciar interrupções |
| Ideal para | Workloads variáveis, ambientes dev, microserviços small-medium | Workloads previsíveis e intensivos, GPU, restrições de compliance |
EKS — Managed Node Groups vs Self-managed
EKS Managed Node Groups:
- AWS cria e gerencia as instâncias EC2 no Auto Scaling Group
- Drain automático de nodes durante atualizações
- Sem acesso ao launch template original (mas customizável)
- Suporte a Spot instances nativo
EKS Self-managed Nodes:
- Você cria o ASG e registra os nodes manualmente
- Controle total do AMI e configuração do kubelet
- Útil para requisitos específicos de hardware ou conformidade
EKS Fargate:
- Cada pod roda em micro-VM isolada (sem shared host)
- Sem gerenciar nodes
- Não suporta DaemonSets, volumes EBS, ou hostNetwork
- Ideal para workloads isoladas ou ambientes multi-tenantQuando escolher ECS
ECS é a escolha certa quando:
✓ Time sem experiência em Kubernetes
✓ Aplicação AWS-nativa sem planos de migrar para outra cloud
✓ Integração profunda com outros serviços AWS é prioridade
✓ Simplicidade operacional vale mais que flexibilidade
✓ Budget não comporta o custo fixo do EKS control plane
✓ Workloads simples: APIs, workers, jobs batch via ECS Scheduled TasksQuando escolher EKS
EKS é a escolha certa quando:
✓ Time já conhece Kubernetes ou a empresa padronizou em K8s
✓ Multi-cloud ou portabilidade entre ambientes é requisito
✓ Workloads avançados: ML com GPUs, serviços stateful com operadores
✓ Necessidade de KEDA, service mesh (Istio/Linkerd), GitOps (Argo CD/Flux)
✓ Plataforma interna com dezenas de times compartilhando o cluster
✓ Compliance exige isolamento a nível de pod (EKS Fargate)