Automação de Incidentes com PagerDuty e AWS Lambda: O Guia do SRE
Arquitetura de Integração: Do Evento à Ação Automatizada
Todo ciclo de automação de incidentes começa com uma decisão de arquitetura robusta. O objetivo não é apenas alertar, mas executar ações corretivas definidas em código (runbooks), com auditabilidade e fachada de segurança. A escolha da aws lambda como motor de execução é estratégica: oferece granularidade, cobre as cobranças apenas em execução, e é nativa do ecossistema AWS. No entanto, uma Lambda pura é cega. O pagerduty fornece a inteligência contextual: severidade, metadados do alerta e o gatilho humano/automático. A integração se dá via Evento PagerDuty (ou o mais robusto PagerDuty Events API v2) disparando via Webhook, ou via AWS EventBridge se o ambiente já está orquestrado com SNS/CloudWatch. Para este guia, focaremos no modelo Webhook direto para Lambda, pois minimiza latência e dependências externas.
A arquitetura de referência é simples, mas poderosa. Um alerta é criado no PagerDuty (ex: monitoramento de disco do CloudWatch). Isso dispara um webhook configurado com um URL assinado na AWS API Gateway. A API Gateway autentica a chamada, dispara a Lambda, e a Lambda interage com a AWS API (EC2, RDS, ECS) e atualiza o estado no PagerDuty via sua própria API. É crítico que a Lambda tenha permissões mínimas concedidas via IAM Role, seguindo o princípio do menor privilégio. Um runbook automatizado para “High CPU” pode, por exemplo, escalar um grupo de auto-scaling (ASG) ou iniciar uma instancia de debug.
{
"source": "PagerDuty",
"action": "trigger",
"severity": "error",
"custom_details": {
"instance_id": "i-0123456789abcdef0",
"cpu_percent": "95",
"region": "us-east-1"
}
}
Configuração do PagerDuty: Webhooks e a Estrutura do Payload
A ponte entre o SaaS do PagerDuty e sua infraestrutura AWS é definida nos serviços. Navegue até o seu serviço no PagerDuty, vá para “Integrations” e adicione uma nova integração do tipo “API v2”. O que muitos negligenciam é a configuração do Webhook no PagerDuty. É necessário especificar o endpoint URL que a AWS API Gateway irá fornecer. A chave aqui é a “Secret Key” ou “Signing Key” que o PagerDuty usa para assinar o payload. Isso não é opcional; seu Lambda precisa validar essa assinatura para garantir que a chamada é legitima e não um ataque de forgery.
O payload enviado pelo PagerDuty é extenso. Ele contém o objeto `event`, `links`, `data` e, mais importante, `custom_details`. Você deve projetar seus alertas no PagerDuty para enviar a máxima contextualização (Ex: IDs de instância, nomes de database, métricas específicas). Isso evita que sua Lambda tenha que fazer consultas adicionais desnecessárias para inferir o contexto. Um exemplo de payload vindo do PagerDuty via webhook para um incidente de memória alta seria estruturado assim. Observe o campo `images` que contém links visuais, e como `custom_details` é dicionário livre.
{
"event": {
"id": "abc123",
"event_type": "incident.triggered",
"resource_type": "incident",
"occurred_at": "2023-10-27T18:00:00Z",
"severity": "critical",
"links": [],
"images": [
{
"src": "https://via.placeholder.com/600x400",
"href": "https://example.com"
}
]
},
"data": {
"incident": {
"id": "PABC123",
"type": "incident",
"title": "High Memory Usage on DB-01",
"service": {
"id": "PQWERT",
"summary": "Production Database",
"type": "service_reference"
},
"assignee": null,
"urgency": "high",
"status": "triggered",
"created_at": "2023-10-27T18:00:00Z",
"custom_fields": {
"database_id": "db-01-prod",
"node_name": "primary-us-east-1a"
}
}
}
}
Definição da Infraestrutura com Terraform (IaC)
Não existe build de automação estável sem IaC. Utilizaremos Terraform para provisionar a Lambda, a API Gateway, a Role IAM e a CloudWatch Log Group. Isso garante que todas as permissões e configurações sejam replicáveis e versionadas. Comece pelo `provider` e pela definição da Role IAM. A Lambda precisará de permissão para escrever logs e, dependendo do runbook, interagir com outros serviços (neste exemplo, concederemos `ec2:DescribeInstances` e `ec2:RebootInstances` como um placeholder para ação de remediation).
provider "aws" {
region = "us-east-1"
}
resource "aws_iam_role" "lambda_execution_role" {
name = "pagerduty-automation-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
resource "aws_iam_policy" "lambda_policy" {
name = "pagerduty-lambda-policy"
description = "IAM policy for PagerDuty automation Lambda"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Effect = "Allow"
Resource = "arn:aws:logs:*:*:*"
},
{
Action = [
"ec2:DescribeInstances",
"ec2:RebootInstances",
"ec2:DescribeTags"
]
Effect = "Allow"
Resource = "*"
}
]
})
}
resource "aws_iam_role_policy_attachment" "lambda_policy_attach" {
role = aws_iam_role.lambda_execution_role.name
policy_arn = aws_iam_policy.lambda_policy.arn
}
Em seguida, definimos o código da função Lambda. É uma prática recomendar uma função Lambda compactada (ZIP) ou Docker, mas para simplicidade, usaremos o código inline ou um bucket S3. O exemplo abaixo assume um Dockerfile, que é a melhor prática para ambientes complexos com dependências.
resource "aws_lambda_function" "pagerduty_automation" {
function_name = "PagerDuty-AutoRemediation"
description = "Handles PagerDuty alerts and performs auto-remediation"
image_uri = "${aws_ecr_repository.repo.repository_url}:latest"
package_type = "Image"
role = aws_iam_role.lambda_execution_role.arn
timeout = 30
memory_size = 256
environment {
variables = {
PAGERDUTY_API_TOKEN = var.pagerduty_api_token
ENFORCE_SAFETY_MODE = "true"
}
}
}
O Motor de Execução: A Lógica da Lambda com Python
Com a infraestrutura definida, a lógica da função precisa ser resiliente e segura. O código deve validar a assinatura do webhook, decodificar o JSON e decidir a ação baseada no payload. A validação de assinatura é o primeiro filtro de segurança. O hash é calculado usando o segredo do PagerDuty.
import os
import json
import hmac
import hashlib
import boto3
from http import HTTPStatus
PAGERDUTY_SIGNING_KEY = os.environ.get('PAGERDUTY_SIGNING_KEY')
def lambda_handler(event, context):
# 1. Validate PagerDuty Webhook Signature
headers = event.get('headers', {})
body = event.get('body')
if not body or not headers:
return {'statusCode': HTTPStatus.BAD_REQUEST, 'body': 'Missing data'}
signature = headers.get('x-pagerduty-signature')
expected_signature = 'v1=' + hmac.new(
PAGERDUTY_SIGNING_KEY.encode('utf-8'),
body.encode('utf-8'),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected_signature):
return {'statusCode': HTTPStatus.FORBIDDEN, 'body': 'Invalid signature'}
# 2. Parse and Analyze Payload
payload = json.loads(body)
event_type = payload['event']['event_type']
# Process only triggered incidents, ignore acknowledgments/updates
if event_type != 'incident.triggered':
return {'statusCode': HTTPStatus.OK, 'body': 'No action required for event type'}
incident = payload['data']['incident']
service_name = incident['service']['summary']
custom_fields = incident.get('custom_fields', {})
# 3. Decision Matrix / Runbook Routing
# Example: Auto-reboot instance for 'High CPU' on specific service
if 'High CPU' in incident['title'] and service_name == 'Production Web Tier':
instance_id = custom_fields.get('instance_id')
if instance_id:
print(f"Initiating remediation for instance {instance_id}")
result = remediate_high_cpu(instance_id)
update_pagerduty_status(incident['id'], result)
return {'statusCode': HTTPStatus.OK, 'body': 'Processed'}
def remediate_high_cpu(instance_id):
"""Execute AWS API calls for remediation"""
ec2 = boto3.client('ec2')
try:
# Verify instance exists before acting
response = ec2.describe_instances(InstanceIds=[instance_id])
if not response['Reservations']:
return "Instance not found"
# Action: Reboot (Replace with ASG cycle for production safety)
ec2.reboot_instances(InstanceIds=[instance_id])
return f"Reboot initiated for {instance_id}"
except Exception as e:
print(f"Remediation failed: {str(e)}")
return f"Failed: {str(e)}"
def update_pagerduty_status(incident_id, note):
"""Add a note to PagerDuty incident via API"""
api_token = os.environ.get('PAGERDUTY_API_TOKEN')
headers = {
'Authorization': f'Token token={api_token}',
'Content-Type': 'application/json'
}
# Implementation of requests to PagerDuty API v2 omitted for brevity
pass
Segurança e Tratamento de Erros: O que Acontece Quando Falha?
Automatizar incidentes traz risco. Se a Lambda falha silenciosamente, o sistema pode ficar em estado degradado. A primeira linha de defesa é a tratamento de exceções granular no código (bloco try/except). Logs detalhados são vitais. Enviar tudo para o CloudWatch Logs é padrão, mas pense além: use o CloudWatch Logs Insights para criar dashboards de falhas. Estruture seus logs no formato JSON para facilitar a query.
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def remediate_high_cpu(instance_id):
try:
logger.info({"action": "start_remediation", "instance": instance_id, "trigger": "high_cpu"})
# ... lógica ...
logger.info({"action": "success", "instance": instance_id})
except botocore.exceptions.ClientError as e:
logger.error({"action": "failed", "instance": instance_id, "error": e.response['Error']['Code']})
# Notificar falha crítica para humanos
trigger_pagerduty_incident("Automation Failed", f"Lambda failed to reboot {instance_id}")
Outro aspecto crítico é o “circuit breaker” automático. Se a Lambda falhar N vezes consecutivas em um curto período (ex: API da AWS fora do ar), ela deve desativar a automação temporariamente. Isso pode ser implementado verificando o histórico de erros via CloudWatch Metrics ou DynamoDB. A arquitectura deve incluir uma “backdoor” manual sempre: permitir que engenheiros suplantem a automação com um comentário específico no incidente (ex: “@bypass-automation”). A Lambda deve ler o log do incidente antes de atuar.
Monitoramento do SRE: Observability da Automação
Como o SRE monitora o monitor? Precisamos de métricas sobre a própria automação. O AWS Lambda exporta métricas nativas (invocations, errors, duration) para CloudWatch. Crie alarms que disparem PagerDuty se o erro rate da Lambda ultrapassar 5% ou se a duração média estiver anormalmente alta (indicando que os runbooks estão lentos ou falhando).
Além das métricas padrão, injete métricas customizadas dentro do código da Lambda usando o CloudWatch Embedded Metric Format (EMF). Isso permite analisar, por exemplo, “quantidade de reboots automatizados por hora” ou “taxa de sucesso de remediation”. Isso transforma a observabilidade de logs discretos para dashboards acionáveis.
from aws_lambda_powertools import Logger, Metrics
metrics = Metrics()
logger = Logger()
@metrics.log_metrics
def lambda_handler(event, context):
# ... lógica ...
metrics.add_metric(name="RemediationAttempts", unit="Count", value=1)
if remediation_successful:
metrics.add_metric(name="RemediationSuccess", unit="Count", value=1)
else:
metrics.add_metric(name="RemediationFailure", unit="Count", value=1)
Testando o Pipeline: Simulação e Validação
Nunca desploye uma Lambda de remediation sem teste. A criação de ambientes de staging é essencial. Use o SAM Local ou o Docker Lambda para testar o handler localmente. Para simular eventos do PagerDuty, utilize o comando `curl` apontando para o endpoint local (via sam local start-api) ou gere payloads de teste estáticos em JSON.
# Simulação de evento local usando JSON
{
"headers": {
"x-pagerduty-signature": "v1=assinatura_calculada_aqui"
},
"body": "{"event": {"event_type": "incident.triggered"}, "data": {...}}"
}
# Comando para testar via AWS CLI (se o endpoint estiver público em staging)
aws lambda invoke
--function-name PagerDuty-AutoRemediation
--payload file://event_test.json
output.json
Para assinatura de teste no local, você precisa replicar o algoritmo HMAC-SHA256 do PagerDuty. Crie um script Python simples para gerar a assinatura válida com o seu segredo local e o corpo do payload de teste. Isso garante que a validação de segurança da Lambda não bloquee seu próprio teste. Integre esses testes no seu pipeline CI/CD (GitHub Actions, GitLab CI) para rodar antes de cada deploy.
Otimização e Custos: A Realidade Operacional
Automação tem custo direto e indireto. A Lambda, com sua granularidade, é economicamente viável, mas requisições frequentes de alertas críticos podem acumular custos. Monitore o número de invocações através da AWS Cost Explorer com tags específicas. Otimização de cold start é relevante se a latência na remediation for crítica (ex: remediar um overflow de tráfego). Para isso, reserve a função (provisioned concurrency) ou mantenha-a pequena e rápida ( linguagem Go/Rust vs Python ) se a lógica permitir.
Outro ponto é a limpeza de dados. Alertas podem ser redundantes. Implemente um cache em DynamoDB (TTL de 5 minutos) para evitar que a mesma instância seja reiniciada múltiplas vezes num curto espaço de tempo, ou que uma ação seja disparada enquanto outra ainda está em andamento. A infraestrutura de “runbooks” deve ter idempotência garantida. A mesma chamada de API (ex: reboot) aplicada duas vezes deve ser segura, mas evitar o workload extra é o ideal.
Sou um profissional na área de Tecnologia da informação, especializado em monitoramento de ambientes, Sysadmin e na cultura DevOps. Possuo certificações de Segurança, AWS e Zabbix.


