Cloud

AWS

Referência dos principais serviços AWS — S3, Lambda, SQS, SNS, RDS, EKS, Glue ETL e mais.

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.

EntidadeDescrição
UserIdentidade permanente (humano ou sistema); possui access key + senha
GroupConjunto de usuários; herdam as políticas anexadas ao grupo
RoleIdentidade temporária assumida por serviços, instâncias ou federação
PolicyDocumento 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: Allow ou Deny — Deny sempre prevalece
  • Action: serviço:ação (ex: s3:GetObject, ec2:*)
  • Resource: ARN do recurso (* = todos)

Campos opcionais:

  • Sid: identificador legível para humanos
  • Condition: 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  # inline

Trust 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=MeuProfile

OIDC — 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 Desenvolvedores

2. 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

ClasseUsoDisponibilidadeCusto relativo
StandardAcesso frequente99.99%Alto
Standard-IAAcesso infrequente99.9%Médio
One Zone-IAIA, zona única99.5%Baixo
Glacier InstantArquivo, acesso em ms99.9%Muito baixo
Glacier FlexibleArquivo, 1-12hMínimo
Glacier Deep ArchiveLong-term, 12-48hMínimo absoluto
Intelligent-TieringPadrão variável desconhecido99.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.txt

Bucket 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-bucket

Versionamento

# 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 abc123

Lifecycle 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.json

S3 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.json

Static 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.com

3. 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 resultado
    • Event (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 --follow

Concurrency — 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 MinhaFuncao

Layers

# 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:3

DLQ — 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-funcao

Cold 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:

  1. Provisioned Concurrency — mantém N instâncias quentes (custo adicional)
  2. Menor tamanho do pacote — menos código = inicialização mais rápida
  3. Runtime mais leve — Python/Node têm cold start menor que Java
  4. SnapStart (Java) — captura snapshot após init, restaura em ~1s
  5. 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ísticaStandardFIFO
ThroughputIlimitado3.000 msg/s com batching, 300 sem
OrdemBest-effortGarantida por grupo
EntregasAo menos uma vez (duplicatas possíveis)Exatamente uma vez
DeduplicaçãoNãoSim (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: WaitTimeSeconds até 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 minha

boto3 — 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 aqui

SQS → 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 failures

Handler 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 ApproximateNumberOfMessages

FIFO — 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-topic

boto3 — 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-AZRead Replica
ObjetivoAlta disponibilidadeEscalabilidade de leitura
SincronizaçãoSíncronaAssíncrona
FailoverAutomático (~60s)Manual (promote)
EndpointMesmo endpointEndpoint separado
RegiõesMesma regiãoCross-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-snapshot

Connection — 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.com

Backup 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 60

7. 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-cluster

kubectl 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 aplicacao

IRSA — 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-1

ALB 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: 80

EKS 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-driver

Cluster 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: 30s

8. 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-etl

Crawler

# 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 table

Glue 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_abc123

9. 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-0123456789

Security Groups vs NACLs

Security GroupNACL
NívelInstância/ENISubnet
EstadoStateful (resposta automática)Stateless (regras in + out separadas)
RegrasApenas AllowAllow e Deny
AvaliaçãoTodas as regrasNúmero da regra (primeira match)
DefaultBloqueia tudoPermite 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-app

VPC 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-enabled

10. 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 Maximum

Logs — 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-123

Exemplos 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 desc

Alarms

# 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 table

11. EventBridge

Conceitos

  • Event Bus: canal de eventos. default recebe 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/ProcessarPedidosNovos

Exemplo: 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-recovery

Secrets 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).

TipoDescriçãoCriptografia
StringValor texto simplesNão
StringListLista separada por vírgulasNão
SecureStringCriptografado com KMSSim (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 resultado

Padrã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/MinhaFuncao

Referência rápida — Regiões comuns

CódigoRegião
us-east-1Virgínia do Norte (mais barata, mais serviços)
us-east-2Ohio
us-west-2Oregon
sa-east-1São Paulo
eu-west-1Irlanda
eu-central-1Frankfurt
ap-southeast-1Singapura
ap-northeast-1Tó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

ConceitoDescrição
Management AccountConta raiz da organização; paga todas as outras; não deve ter cargas de trabalho
Member AccountQualquer conta dentro da organização
RootNó 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-network

SCPs — 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-yyyyyyyy

Consolidated 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=DevNovoTime

Cost 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 > Enable

Consultas 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_ACCOUNT

Cost 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=Projeto

AWS 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-abcdef123456

Transit 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-0123456789abcdef

Topologia 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érioVPC PeeringTransit Gateway
Roteamento transitivoNãoSim
Número de conexõesN×(N-1)/2 peerings para N VPCsN attachments para N VPCs
Custo de dadosSem custo extra por hora; $ por GB$ por hora por attachment + $ por GB
Largura de bandaSem limite adicionalAté 50 Gbps por VPC attachment
Quando usar2–5 VPCs, sem necessidade de roteamento transitivo5+ 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=86400

OIDC 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érioECSEKS
Complexidade operacionalBaixa — AWS gerencia o control plane inteiroAlta — mesmo com managed nodes, Kubernetes tem curva íngreme
Controle e extensibilidadeLimitado à API da AWSTotal — operators, CRDs, service meshes, qualquer plugin K8s
PortabilidadeProprietário AWSPadrão Kubernetes — portável entre clouds e on-premises
EcosystemIntegração nativa com IAM, ALB, CloudWatch, ECRHelm charts, operadores CNCF, todo o ecossistema K8s
Custo do control planeSem custo adicional$0,10/hora por cluster (~$73/mês)
NetworkingVPC nativo (awsvpc mode) simplesCNI plugins, múltiplas opções (aws-vpc-cni, Calico, Cilium)
Service DiscoveryAWS Cloud Map, ECS Service ConnectCoreDNS, Kubernetes Service, Ingress
ScalingService Auto Scaling + FargateHPA, VPA, KEDA, Cluster Autoscaler
Casos de uso ideaisApps AWS-nativas, equipes pequenas, microserviços sem necessidade de K8sPlataformas multi-cloud, equipes K8s, workloads complexos, ML/Batch

ECS com Fargate vs ECS com EC2

ECS + FargateECS + EC2
InfraestruturaServerless — sem gerenciar instânciasVocê gerencia o cluster de instâncias
CustoMaior por vCPU/GB (paga por task)Menor para cargas previsíveis e altas
ControleSem acesso ao hostAcesso SSH, customização de AMI, GPU
Cold start~10-30s por taskInstância já ativa — task inicia em segundos
SpotFargate Spot (~70% desconto)EC2 Spot + gerenciar interrupções
Ideal paraWorkloads variáveis, ambientes dev, microserviços small-mediumWorkloads 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-tenant

Quando 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 Tasks

Quando 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)