Resposta a incidentes no Azure: automação de alertas, runbooks e integração com ferramentas de on-call
Você já validou a resiliência com Engenharia de Caos, definiu SLIs, SLOs e Error Budgets. Mas quando o incidente real acontece , e vai acontecer, a pergunta que define o impacto não é "o que deu errado?", mas sim: "quanto tempo levamos para detectar, responder e resolver?"
Esse tempo tem nome: MTTD (Mean Time to Detect), MTTE (Mean Time to Engage) e MTTR (Mean Time to Recover). E a diferença entre um incidente de 5 minutos e um de 5 horas está diretamente ligada à qualidade do seu processo de resposta a incidentes e, principalmente, ao nível de automação que você tem.
Neste artigo, vamos explorar como construir um fluxo completo de resposta a incidentes no Azure: desde alertas inteligentes com Azure Monitor, passando por automação com Action Groups e Runbooks, até a integração com ferramentas de on-call e a prática de post-mortems blameless.
Por que resposta a incidentes importa mais do que prevenção?
Parece contraintuitivo, mas é uma premissa fundamental de SRE: você não pode prevenir todos os incidentes. Sistemas distribuídos falham de formas imprevisíveis, é a natureza da complexidade.
O que separa organizações maduras das demais não é a ausência de incidentes, mas a velocidade e qualidade da resposta:
| Métrica | O que mede | Meta típica |
|---|---|---|
| MTTD (Mean Time to Detect) | Quanto tempo até o sistema detectar a anomalia | < 5 minutos |
| MTTE (Mean Time to Engage) | Quanto tempo até uma pessoa qualificada ser notificada e começar a investigar | < 15 minutos |
| MTTR (Mean Time to Recover) | Quanto tempo até o serviço voltar ao estado estável | < 60 minutos |
| MTTF (Mean Time to Fix) | Quanto tempo até a causa raiz ser corrigida permanentemente | Varia por severidade |
O objetivo não é eliminar incidentes, é reduzir MTTD, MTTE e MTTR continuamente. Se você consegue detectar em 2 minutos, engajar em 5 e recuperar em 15, o impacto para o usuário é mínimo, mesmo que a falha seja séria.
Anatomia de um fluxo de resposta a incidentes no Azure
Um fluxo maduro de incident response no Azure envolve quatro camadas:
┌────────────────────────────────────────────────────────────────┐
│ 1. DETECÇÃO Azure Monitor Alerts, Log Analytics, │
│ Application Insights, Service Health │
├────────────────────────────────────────────────────────────────┤
│ 2. NOTIFICAÇÃO Action Groups (email, SMS, webhook, │
│ Azure Functions, Logic Apps) │
├────────────────────────────────────────────────────────────────┤
│ 3. AUTOMAÇÃO Automation Runbooks, Azure Functions, │
│ Logic Apps, auto-remediation │
├────────────────────────────────────────────────────────────────┤
│ 4. GESTÃO Integração com on-call tools, │
│ war rooms, post-mortems │
└────────────────────────────────────────────────────────────────┘
Vamos detalhar cada uma.
Camada 1: Detecção | Alertas inteligentes com Azure Monitor
A primeira linha de defesa é a detecção automática. Alertas mal configurados são piores do que nenhum alerta, geram fadiga (alert fatigue) e fazem a equipe ignorar notificações reais.
Tipos de alerta no Azure Monitor
| Tipo | Uso ideal | Latência típica |
|---|---|---|
| Metric alerts | Métricas numéricas (CPU, memória, latência, taxa de erro) | ~1 minuto |
| Log search alerts | Queries KQL em Log Analytics ou Application Insights | 1-15 minutos (configurável) |
| Activity log alerts | Eventos do plano de controle (VM deletada, role assignment, deployment) | ~1-5 minutos |
| Service Health alerts | Incidentes do próprio Azure (outages, manutenções planejadas) | Tempo real |
| Smart Detection | Anomalias detectadas automaticamente pelo Application Insights | Automático |
Alertas baseados em SLOs
Se você já definiu SLOs (como no artigo anterior), seus alertas devem refletir isso. A ideia é alertar quando o error budget está sendo consumido rápido demais, não apenas quando uma métrica pontual ultrapassa um limiar.
// SLI: taxa de sucesso das requisições (últimas 6 horas, janela deslizante)
let sloTarget = 99.9;
let windowSize = 6h;
requests
| where timestamp >= ago(windowSize)
| summarize
totalRequests = count(),
successfulRequests = countif(success == true)
| extend
successRate = round(100.0 * successfulRequests / totalRequests, 3),
sloTarget = sloTarget,
errorBudgetTotal = round(100.0 - sloTarget, 3),
errorBudgetConsumed = round(100.0 - (100.0 * successfulRequests / totalRequests), 3)
| extend
errorBudgetRemainingPct = round(100.0 * (1 - errorBudgetConsumed / errorBudgetTotal), 2)
| where errorBudgetRemainingPct < 50
Burn rate alerts, o padrão recomendado pelo Google SRE
Em vez de alertar quando o SLO é violado (reativo demais) ou quando qualquer erro acontece (barulhento demais), use burn rate alerts:
// Burn rate: quantas vezes mais rápido que o normal o error budget está sendo consumido
// Burn rate = 1 significa consumo normal (o budget duraria exatamente a janela do SLO)
// Burn rate > 14 em 1h = alerta crítico (budget se esgota em ~2 dias)
// Burn rate > 6 em 6h = alerta de warning (budget se esgota em ~5 dias)
let sloTarget = 0.999;
let sloPeriod = 30d;
let shortWindow = 1h;
let longWindow = 6h;
let burnRateThresholdCritical = 14.0;
let burnRateThresholdWarning = 6.0;
let errorBudget = 1 - sloTarget;
// Janela curta (1h)
let shortWindowErrors = requests
| where timestamp >= ago(shortWindow)
| summarize
total = count(),
errors = countif(success == false)
| extend errorRate = 1.0 * errors / total
| extend burnRate = errorRate / errorBudget;
// Janela longa (6h)
let longWindowErrors = requests
| where timestamp >= ago(longWindow)
| summarize
total = count(),
errors = countif(success == false)
| extend errorRate = 1.0 * errors / total
| extend burnRate = errorRate / errorBudget;
shortWindowErrors
| extend windowType = "short", threshold = burnRateThresholdCritical
| union (longWindowErrors | extend windowType = "long", threshold = burnRateThresholdWarning)
| where burnRate > threshold
Criando o alerta via CLI
# Criar um log search alert baseado em burn rate
az monitor scheduled-query create \
--name "alert-slo-burn-rate-critical" \
--resource-group meu-rg \
--scopes "/subscriptions/<sub-id>/resourceGroups/meu-rg/providers/Microsoft.Insights/components/meu-appinsights" \
--condition "count 'requests | where timestamp >= ago(1h) | summarize total=count(), errors=countif(success==false) | extend burnRate=(1.0*errors/total)/0.001 | where burnRate > 14' > 0" \
--severity 0 \
--evaluation-frequency 5m \
--window-size 10m \
--action-groups "/subscriptions/<sub-id>/resourceGroups/meu-rg/providers/Microsoft.Insights/actionGroups/ag-critical-oncall"
Boas práticas para evitar alert fatigue
| Prática | Por que |
|---|---|
| Categorize por severidade | Sev0 (crítico) aciona on-call imediatamente. Sev3 (informativo) vai para um canal de Slack/Teams. |
| Use supressão temporal | Não alerte 50 vezes pelo mesmo problema. Configure autoMitigate e períodos de supressão. |
| Alerte sobre sintomas, não causas | Alerte "taxa de erro acima de 1%" e não "CPU acima de 80%". CPU alta pode ser normal sob carga. |
| Revise alertas regularmente | Alertas que nunca disparam ou sempre disparam são igualmente inúteis. Faça alert hygiene mensal. |
| Defina dono para cada alerta | Todo alerta precisa ter um time responsável. Alerta sem dono = alerta ignorado. |
Camada 2: Notificação | Action Groups
Os Action Groups do Azure Monitor são o hub central de notificação. Eles definem quem e como será notificado quando um alerta dispara.
Tipos de ação disponíveis
| Tipo | Uso |
|---|---|
| Email/SMS/Voz | Notificação direta a pessoas |
| Azure Function | Execução de código serverless para automação ou enriquecimento |
| Logic App | Orquestração de workflows complexos |
| Webhook | Integração com ferramentas externas (PagerDuty, OpsGenie, Slack, Teams) |
| Automation Runbook | Execução de scripts de remediação automática |
| ITSM | Integração com ServiceNow, Provance, etc. |
| Event Hub | Streaming de alertas para pipelines de dados |
| Secure Webhook | Webhook com autenticação via Azure AD |
Criando um Action Group com múltiplas ações
# Criar action group que combina notificação + automação
az monitor action-group create \
--name ag-critical-oncall \
--resource-group meu-rg \
--short-name CritOnCall \
--action email oncall-team oncall@empresa.com \
--action sms oncall-sms "+5511999999999" \
--action webhook pagerduty-integration "https://events.pagerduty.com/integration/<key>/enqueue" \
--action automation-runbook auto-restart-service \
"/subscriptions/<sub-id>/resourceGroups/meu-rg/providers/Microsoft.Automation/automationAccounts/meu-automation/runbooks/Restart-UnhealthyService" \
"/subscriptions/<sub-id>/resourceGroups/meu-rg/providers/Microsoft.Automation/automationAccounts/meu-automation" \
true
Estratégia de escalonamento por severidade
Uma boa prática é criar Action Groups diferentes por nível de severidade:
Sev0 (Crítico) → ag-sev0: PagerDuty (on-call) + SMS + Runbook auto-remediação
Sev1 (Alto) → ag-sev1: Teams channel + Email do time + Ticket ITSM
Sev2 (Médio) → ag-sev2: Teams channel + Email
Sev3 (Informativo) → ag-sev3: Log centralizado + Dashboard
# Exemplo: Action Group para Sev1 com criação automática de ticket
az monitor action-group create \
--name ag-sev1-high \
--resource-group meu-rg \
--short-name Sev1High \
--action email platform-team platform@empresa.com \
--action webhook teams-channel "https://outlook.office.com/webhook/<teams-webhook-url>" \
--action logic-app create-ticket \
"/subscriptions/<sub-id>/resourceGroups/meu-rg/providers/Microsoft.Logic/workflows/lapp-create-incident-ticket" \
"https://<logic-app-trigger-url>"
Camada 3: Automação | Runbooks e auto-remediação
Aqui é onde a mágica acontece. Em vez de apenas notificar uma pessoa para executar um procedimento manual, automatize as ações de remediação mais comuns.
Azure Automation Runbooks
Runbooks são scripts (PowerShell ou Python) que rodam no Azure Automation e podem ser acionados automaticamente por alertas.
Exemplo 1: Reiniciar um App Service não saudável
# Runbook: Restart-UnhealthyAppService.ps1
param(
[Parameter(Mandatory=$true)]
[object]$WebhookData
)
# Parsear o payload do alerta
$alertPayload = ConvertFrom-Json -InputObject $WebhookData.RequestBody
$alertContext = $alertPayload.data.alertContext
$resourceId = $alertPayload.data.essentials.alertTargetIDs[0]
# Extrair informações do recurso
$resourceParts = $resourceId -split '/'
$resourceGroupName = $resourceParts[4]
$appServiceName = $resourceParts[8]
Write-Output "Alerta recebido: $($alertPayload.data.essentials.alertRule)"
Write-Output "Recurso afetado: $appServiceName no RG $resourceGroupName"
# Conectar usando Managed Identity
Connect-AzAccount -Identity
# Verificar o estado atual
$app = Get-AzWebApp -ResourceGroupName $resourceGroupName -Name $appServiceName
Write-Output "Estado atual: $($app.State)"
if ($app.State -ne "Running" -or $alertContext) {
Write-Output "Reiniciando App Service $appServiceName..."
Restart-AzWebApp -ResourceGroupName $resourceGroupName -Name $appServiceName
# Aguardar e verificar
Start-Sleep -Seconds 30
$app = Get-AzWebApp -ResourceGroupName $resourceGroupName -Name $appServiceName
Write-Output "Estado após restart: $($app.State)"
if ($app.State -eq "Running") {
Write-Output "✅ App Service reiniciado com sucesso."
} else {
Write-Output "⚠️ App Service não voltou ao estado Running. Escalonando para equipe."
# Aqui você poderia chamar outro webhook para escalonar
}
}
Exemplo 2: Escalar um VMSS quando CPU está crítica
# Runbook: Scale-OutVMSS.ps1
param(
[Parameter(Mandatory=$true)]
[object]$WebhookData
)
$alertPayload = ConvertFrom-Json -InputObject $WebhookData.RequestBody
$resourceId = $alertPayload.data.essentials.alertTargetIDs[0]
$resourceParts = $resourceId -split '/'
$resourceGroupName = $resourceParts[4]
$vmssName = $resourceParts[8]
Connect-AzAccount -Identity
$vmss = Get-AzVmss -ResourceGroupName $resourceGroupName -VMScaleSetName $vmssName
$currentCapacity = $vmss.Sku.Capacity
$maxCapacity = 20
$scaleIncrement = 2
Write-Output "VMSS: $vmssName | Capacidade atual: $currentCapacity"
if ($currentCapacity + $scaleIncrement -le $maxCapacity) {
$newCapacity = $currentCapacity + $scaleIncrement
Write-Output "Escalando de $currentCapacity para $newCapacity instâncias..."
$vmss.Sku.Capacity = $newCapacity
Update-AzVmss -ResourceGroupName $resourceGroupName `
-VMScaleSetName $vmssName `
-VirtualMachineScaleSet $vmss
Write-Output "✅ VMSS escalado para $newCapacity instâncias."
} else {
Write-Output "⚠️ Capacidade máxima ($maxCapacity) atingida. Escalonando para equipe."
}
Exemplo 3: Diagnosticar e coletar evidências automaticamente
Nem toda automação precisa remediar. Às vezes, o mais valioso é coletar informações diagnósticas automaticamente para acelerar a investigação humana:
# Runbook: Collect-IncidentDiagnostics.ps1
param(
[Parameter(Mandatory=$true)]
[object]$WebhookData
)
$alertPayload = ConvertFrom-Json -InputObject $WebhookData.RequestBody
$resourceId = $alertPayload.data.essentials.alertTargetIDs[0]
$alertTime = $alertPayload.data.essentials.firedDateTime
Connect-AzAccount -Identity
# Coletar métricas dos últimos 30 minutos
$startTime = (Get-Date $alertTime).AddMinutes(-30)
$endTime = Get-Date $alertTime
Write-Output "=== Diagnóstico automático de incidente ==="
Write-Output "Recurso: $resourceId"
Write-Output "Hora do alerta: $alertTime"
Write-Output "Janela de análise: $startTime até $endTime"
# Coletar métricas
$metrics = Get-AzMetric -ResourceId $resourceId `
-TimeGrain 00:01:00 `
-StartTime $startTime `
-EndTime $endTime `
-MetricName "CpuPercentage","MemoryPercentage","Http5xx","HttpResponseTime"
foreach ($metric in $metrics) {
Write-Output "`n--- $($metric.Name.Value) ---"
$metric.Data | Where-Object { $_.Average -ne $null } |
Select-Object TimeStamp, Average, Maximum |
Format-Table -AutoSize
}
# Coletar logs recentes do Activity Log
$activityLogs = Get-AzActivityLog `
-StartTime $startTime `
-EndTime $endTime `
-ResourceId $resourceId `
-MaxRecord 20
Write-Output "`n=== Activity Log (últimos 30 min) ==="
$activityLogs | Select-Object EventTimestamp, OperationName, Status, Caller |
Format-Table -AutoSize
# Salvar resultado em um blob para referência
$diagnosticReport = @{
AlertRule = $alertPayload.data.essentials.alertRule
Severity = $alertPayload.data.essentials.severity
FiredAt = $alertTime
Resource = $resourceId
MetricsSummary = $metrics | ForEach-Object {
@{ Name = $_.Name.Value; MaxValue = ($_.Data | Measure-Object -Property Maximum -Maximum).Maximum }
}
RecentActivity = $activityLogs | Select-Object EventTimestamp, OperationName, Status
}
$reportJson = $diagnosticReport | ConvertTo-Json -Depth 5
Write-Output "`n=== Relatório JSON ==="
Write-Output $reportJson
Decisão: quando automatizar vs. quando escalonar
Nem tudo deve ser automatizado. Use esta heurística:
| Cenário | Ação recomendada |
|---|---|
| Problema conhecido com solução determinística (restart, scale-out) | ✅ Automatizar completamente |
| Problema que requer investigação mas tem passos diagnósticos conhecidos | ⚡ Automatizar coleta de dados, escalonar para humano |
| Problema raro ou com alto risco de efeito colateral | 🧑 Notificar humano diretamente |
| Falha em cascata afetando múltiplos serviços | 🚨 Escalonar para war room imediatamente |
Camada 4: Gestão | Integração com ferramentas de on-call
Alertas do Azure Monitor precisam chegar à pessoa certa, no momento certo. Isso requer integração com ferramentas de on-call rotation e incident management.
Integração com PagerDuty
# Criar webhook no Action Group para PagerDuty
az monitor action-group create \
--name ag-pagerduty-oncall \
--resource-group meu-rg \
--short-name PD-OnCall \
--action webhook pagerduty \
"https://events.pagerduty.com/integration/<integration-key>/enqueue"
A integração nativa do PagerDuty com Azure Monitor já faz o mapeamento de severidade:
| Azure Monitor Severity | PagerDuty Urgency |
|---|---|
| Sev0 (Critical) | High |
| Sev1 (Error) | High |
| Sev2 (Warning) | Low |
| Sev3 (Informational) | Low |
Integração com Microsoft Teams para war rooms
Use Logic Apps para criar canais dedicados de war room automaticamente quando um incidente Sev0 é declarado:
{
"definition": {
"triggers": {
"When_alert_fires": {
"type": "Request",
"kind": "Http",
"inputs": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"essentials": {
"type": "object",
"properties": {
"alertRule": { "type": "string" },
"severity": { "type": "string" },
"firedDateTime": { "type": "string" }
}
}
}
}
}
}
}
}
},
"actions": {
"Create_Teams_Channel": {
"type": "ApiConnection",
"inputs": {
"method": "post",
"path": "/v1.0/teams/<team-id>/channels",
"body": {
"displayName": "🚨 INC-@{formatDateTime(utcNow(), 'yyyyMMdd-HHmm')}",
"description": "War room para incidente: @{triggerBody()?['data']?['essentials']?['alertRule']}"
}
}
},
"Post_Initial_Message": {
"type": "ApiConnection",
"runAfter": { "Create_Teams_Channel": ["Succeeded"] },
"inputs": {
"method": "post",
"path": "/v1.0/teams/<team-id>/channels/@{body('Create_Teams_Channel')?['id']}/messages",
"body": {
"body": {
"contentType": "html",
"content": "<h2>🚨 Incidente Declarado</h2><p><b>Alerta:</b> @{triggerBody()?['data']?['essentials']?['alertRule']}</p><p><b>Severidade:</b> @{triggerBody()?['data']?['essentials']?['severity']}</p><p><b>Hora:</b> @{triggerBody()?['data']?['essentials']?['firedDateTime']}</p><hr/><p><b>Próximos passos:</b></p><ul><li>Incident Commander: [a ser definido]</li><li>Comunicação: atualizações a cada 15 min</li><li>Runbook: consultar Wiki/Runbooks</li></ul>"
}
}
}
}
}
}
}
Integração com Azure DevOps para tracking
Automatize a criação de Work Items para cada incidente:
# Usando Azure DevOps CLI para criar um bug/incident automaticamente
az boards work-item create \
--title "INC-$(date +%Y%m%d) - Alerta: <alert-name>" \
--type "Bug" \
--org "https://dev.azure.com/minha-org" \
--project "MeuProjeto" \
--area "Operations\\Incidents" \
--fields "System.Tags=incident;sev0" \
"Microsoft.VSTS.Common.Severity=1 - Critical" \
"System.Description=<detalhes do alerta>"
Construindo runbooks operacionais eficazes
Além da automação, sua equipe precisa de runbooks documentados para cenários que exigem intervenção humana. Um bom runbook é a diferença entre um engenheiro de on-call resolvendo em 10 minutos e outro levando 2 horas.
Estrutura recomendada para runbooks
# Runbook: [Nome do Cenário]
## Resumo
- **Serviço afetado**: [nome do serviço]
- **Impacto típico**: [o que o usuário percebe]
- **Severidade**: Sev[0-3]
- **SLO relacionado**: [qual SLO é impactado]
## Detecção
- **Alerta**: [nome do alerta no Azure Monitor]
- **Query KQL para validar**:
```kusto
[query para confirmar o problema]
Diagnóstico
- Verificar [métrica/log/dashboard]
- Confirmar se [condição] é verdadeira
- Checar dependências: [lista de dependências]
Remediação
Caminho 1: [cenário mais comum]
- Executar:
[comando] - Verificar:
[validação] - Confirmar recuperação no dashboard
Caminho 2: [cenário alternativo]
- Se o Caminho 1 não funcionar, [próxima ação]
Escalonamento
- Se não resolver em [X minutos], escalonar para [time/pessoa]
- Canal de comunicação: [Teams channel / bridge call]
Post-incidente
- [ ] Registrar timeline no ticket
- [ ] Atualizar este runbook se necessário
- [ ] Agendar post-mortem se Sev0/Sev1
### Vinculando runbooks a alertas
Uma prática poderosa é incluir o link do runbook diretamente na descrição do alerta:
```bash
az monitor metrics alert create \
--name "alert-api-error-rate-high" \
--resource-group meu-rg \
--scopes "<app-insights-resource-id>" \
--condition "avg requests/failed > 10" \
--severity 1 \
--action ag-sev1-high \
--description "Taxa de erro da API acima do limiar. Runbook: https://wiki.empresa.com/runbooks/api-error-rate-high"
Post-mortems blameless: aprendendo com incidentes
O ciclo de resposta a incidentes não termina quando o serviço volta ao normal. A etapa mais importante para melhoria contínua é o post-mortem blameless, uma análise estruturada do incidente focada em aprendizado, não em culpa.
Por que "blameless"?
Se as pessoas têm medo de serem punidas, elas escondem informações. Se escondem informações, você nunca descobre a causa raiz real. A cultura blameless entende que:
Sistemas complexos falham por múltiplos fatores combinados. Culpar uma pessoa é simplificar demais e perder a chance de corrigir o sistema.
Template de post-mortem
# Post-Mortem: INC-20260324
## Resumo executivo
- **Data do incidente**: 2026-03-24
- **Duração**: 47 minutos (14:03 - 14:50 UTC)
- **Severidade**: Sev1
- **Impacto**: 12% das requisições da API retornaram erro 503
- **Usuários afetados**: ~3.200
- **SLO impactado**: Disponibilidade (target: 99.9%, real na janela: 99.2%)
- **Error budget consumido**: 8.4% do budget mensal
## Timeline
| Hora (UTC) | Evento |
|------------|--------|
| 14:00 | Deploy v2.14.3 iniciado no slot de staging |
| 14:03 | Swap de slots executado (staging → production) |
| 14:05 | Alerta "API Error Rate > 1%" disparou (MTTD: 2 min) |
| 14:08 | On-call recebe notificação PagerDuty (MTTE: 5 min) |
| 14:12 | On-call confirma erro 503 no Application Insights |
| 14:15 | Identificada connection string incorreta no novo deployment |
| 14:18 | Rollback iniciado (swap reverso) |
| 14:22 | Rollback concluído, erros cessam |
| 14:50 | Métricas confirmam retorno ao estado estável (MTTR: 47 min) |
## Causa raiz
A variável de ambiente `COSMOS_CONNECTION_STRING` foi alterada no
App Configuration mas não foi atualizada no slot de staging. O swap
de slots carregou a configuração desatualizada em produção.
## Fatores contribuintes
1. Não havia validação automatizada de configurações pré-swap.
2. O health check do slot não testava conectividade com Cosmos DB.
3. O runbook de deploy não incluía verificação de App Configuration.
## O que funcionou bem
- ✅ Alerta disparou em 2 minutos (dentro do target de MTTD).
- ✅ On-call respondeu em 5 minutos (dentro do target de MTTE).
- ✅ Rollback foi rápido graças ao uso de deployment slots.
## O que pode melhorar
- ⚠️ Health check precisa validar dependências (DB, cache, etc.).
- ⚠️ Pipeline de deploy precisa validar configurações antes do swap.
- ⚠️ Faltava alerta de "configuração divergente entre slots".
## Ações corretivas (Action Items)
| # | Ação | Responsável | Prazo | Status |
|---|------|-------------|-------|--------|
| 1 | Adicionar health check que valida Cosmos DB | Time Backend | 1 semana | Pendente |
| 2 | Criar step no pipeline que compara App Config entre slots | Time DevOps | 2 semanas | Pendente |
| 3 | Adicionar alerta de drift de configuração | Time SRE | 1 semana | Pendente |
| 4 | Atualizar runbook de deploy com checklist pré-swap | Time DevOps | 1 semana | Pendente |
KQL para análise de post-mortem
Durante o post-mortem, queries KQL ajudam a reconstruir a timeline com precisão:
// Reconstruir o impacto do incidente minuto a minuto
let incidentStart = datetime(2026-03-24T14:00:00Z);
let incidentEnd = datetime(2026-03-24T15:00:00Z);
requests
| where timestamp between (incidentStart .. incidentEnd)
| summarize
totalRequests = count(),
failedRequests = countif(resultCode startswith "5"),
avgDuration = avg(duration),
p95Duration = percentile(duration, 95),
p99Duration = percentile(duration, 99)
by bin(timestamp, 1m)
| extend errorRate = round(100.0 * failedRequests / totalRequests, 2)
| order by timestamp asc
// Identificar quais operações foram mais afetadas
let incidentStart = datetime(2026-03-24T14:03:00Z);
let incidentEnd = datetime(2026-03-24T14:22:00Z);
requests
| where timestamp between (incidentStart .. incidentEnd)
| where success == false
| summarize
failedCount = count(),
distinctUsers = dcount(user_Id)
by name, resultCode
| order by failedCount desc
| take 10
// Correlacionar com mudanças no Activity Log (deploys, config changes)
AzureActivity
| where TimeGenerated between (datetime(2026-03-24T13:30:00Z) .. datetime(2026-03-24T15:00:00Z))
| where OperationNameValue has_any ("Microsoft.Web/sites/slots/slotsswap",
"Microsoft.Web/sites/config/write",
"Microsoft.AppConfiguration")
| project TimeGenerated, OperationNameValue, ActivityStatusValue, Caller
| order by TimeGenerated asc
Montando a operação: um framework completo
Juntando tudo, aqui está como organizar sua operação de resposta a incidentes no Azure:
1. Antes do incidente (preparação)
- SLIs e SLOs definidos (com burn rate alerts configurados).
- Action Groups criados por severidade, com escalation path claro.
- Runbooks documentados e vinculados a cada alerta.
- Automação configurada para cenários conhecidos (restart, scale-out, diagnóstico).
- Rotação de on-call definida na ferramenta de incident management.
- Game Day periódico para testar o fluxo completo (veja nosso artigo sobre Engenharia de Caos).
2. Durante o incidente (resposta)
Alerta dispara
↓
Action Group notifica on-call + executa automação
↓
On-call avalia: automação resolveu?
├── SIM → Verificar métricas, fechar alerta, registrar
└── NÃO → Seguir runbook
↓
Resolveu?
├── SIM → Registrar e monitorar
└── NÃO → Escalonar
↓
War room (Teams channel automático)
↓
Incident Commander coordena resposta
↓
Resolução + comunicação para stakeholders
3. Depois do incidente (aprendizado)
- Post-mortem blameless em até 48 horas (para Sev0/Sev1).
- Action items registrados e rastreados até conclusão.
- Runbooks atualizados com os aprendizados.
- Alertas revisados, precisa de novo alerta? Algum alerta precisa ser ajustado?
- Novo experimento de caos para validar as correções.
Métricas operacionais: medindo a maturidade
Para saber se sua operação está melhorando, acompanhe estas métricas ao longo do tempo:
| Métrica | Como medir | Meta |
|---|---|---|
| MTTD | Timestamp do alerta - timestamp do início real do problema | Diminuir mês a mês |
| MTTE | Timestamp do acknowledge - timestamp do alerta | < 15 min para Sev0 |
| MTTR | Timestamp da recuperação - timestamp do início | Diminuir mês a mês |
| Incidentes/mês | Contagem de incidentes Sev0+Sev1 | Diminuir mês a mês |
| % de auto-remediação | Incidentes resolvidos por automação / total | Aumentar mês a mês |
| Action items concluídos | Items de post-mortem fechados / abertos | > 80% no prazo |
| Alert fatigue | Alertas que não resultaram em ação / total de alertas | < 20% |
// Dashboard KQL: MTTR por mês (requer custom tracking table)
IncidentTracking_CL
| where TimeGenerated >= ago(180d)
| where Severity_s in ("Sev0", "Sev1")
| extend MTTR_minutes = datetime_diff('minute', ResolvedAt_t, DetectedAt_t)
| summarize
avgMTTR = avg(MTTR_minutes),
p50MTTR = percentile(MTTR_minutes, 50),
p95MTTR = percentile(MTTR_minutes, 95),
incidentCount = count()
by bin(TimeGenerated, 30d)
| order by TimeGenerated asc
Conclusão
Resposta a incidentes não é sobre ter um herói que acorda às 3h da manhã e resolve tudo sozinho. É sobre construir um sistema de alertas inteligentes, automação confiável, processos claros e cultura de aprendizado que minimiza o impacto dos incidentes inevitáveis.
No Azure, você tem todas as peças: Azure Monitor para detecção, Action Groups para notificação, Automation Runbooks e Logic Apps para automação, e integração nativa com ferramentas de on-call como PagerDuty e Microsoft Teams.
A jornada que construímos nos últimos artigos forma um ciclo completo:
- Engenharia de Caos → Descubra fragilidades antes que elas virem incidentes.
- SRE (SLIs/SLOs/Error Budgets) → Meça a confiabilidade com dados objetivos.
- Resposta a Incidentes → Quando o incidente acontecer, detecte rápido, responda com automação e aprenda com cada ocorrência.
O resultado é um sistema que não apenas é resiliente por design, mas que melhora continuamente a cada incidente. E essa é a verdadeira essência de operações maduras na nuvem.