Troubleshooting a Production API Issue in Azure
The goal is to stabilize first, diagnose second, fix third. Work from the outside in: is it the platform, the network, your code, or a dependency?
1. Triage (first 5 minutes)
Establish the facts before touching anything:
- What is the symptom? Errors (5xx/4xx), slowness, timeouts, or total outage?
- Scope: all users or some? All endpoints or one? One region or instance, or everywhere?
- When did it start? Line it up against deployments, config changes, certificate expiries, and traffic spikes. Most incidents trace back to a recent change.
- Is it Azure? Check Azure Service Health and Resource Health for your subscription and the specific resource. Platform incidents and maintenance show up here.
If a recent deployment is the likely cause, roll back first and investigate later. With deployment slots, swap back. That's the fastest mitigation you have.
2. Check the golden signals in Application Insights
Open Application Insights and go through these in order:
- Failures blade: which operations and exception types are spiking, with sample failed requests.
- Performance blade: which operations got slower, and whether the time is in your code or in a dependency.
- Application Map: which node is red (slow or failing). This shows quickly whether the API or something it calls is at fault.
- Live Metrics: real-time view while you test or mitigate.
- End-to-end transaction details: follow a single failed request through all services using its operation ID.
Useful KQL:
// Failure trend by result code
requests
| where timestamp > ago(2h)
| summarize total = count(), failed = countif(success == false) by bin(timestamp, 5m)
| extend failureRate = todouble(failed) / total
| render timechart
// Is it a dependency?
dependencies
| where timestamp > ago(2h) and success == false
| summarize count() by target, type, resultCode
| order by count_ desc
// Latency percentiles by operation
requests
| where timestamp > ago(2h)
| summarize p50 = percentile(duration, 50), p95 = percentile(duration, 95), count() by name
| order by p95 desc
// Top exceptions
exceptions
| where timestamp > ago(2h)
| summarize count() by type, outerMessage
| order by count_ desc
Compare the bad period against the same time yesterday or last week to separate real regressions from normal patterns.
3. Narrow down by symptom
| Symptom | Likely causes | Where to look |
|---|---|---|
| 500s after a deploy | Bug, missing config, bad migration | Exceptions, deployment history, diff of app settings |
| 500s / timeouts, no deploy | Failing dependency (SQL, Redis, downstream API), expired secret or cert | Dependencies table, Application Map |
| 401/403 | Expired token or secret, changed app role or RBAC, clock/audience mismatch, rotated key | Entra sign-in logs, role assignments, Key Vault access logs |
| 429 | Throttling by a downstream service (Cosmos DB RU limits, Key Vault, Storage) or by APIM | Response headers, service metrics |
| 502/503/504 | App crashing or restarting, instances unhealthy, gateway timeout (App Gateway/Front Door ~230s limit on App Service) | Health check status, restarts, Diagnose and solve problems |
| Slow responses | Slow SQL queries, cold starts, thread pool starvation, CPU/memory pressure, chatty calls | Performance blade, dependency durations, App Service metrics |
| Intermittent failures | One bad instance, SNAT port exhaustion, transient faults without retries, DNS issues | Per-instance breakdown (cloud_RoleInstance), connection metrics |
| Works in one place, not another | NSG/firewall/private endpoint/DNS change | Network diagnostics (below) |
4. Platform-level diagnostics
App Service / Functions:
- Diagnose and solve problems blade: guided detectors for HTTP 5xx, high CPU, memory, slow requests, SNAT exhaustion, and app crashes. Start here when the cause isn't obvious.
- Metrics: CPU, memory working set, requests, response time, HTTP 5xx, and instance count. Look for saturation or a scale-out that didn't happen.
- Log stream for live output:
az webapp log tail --name my-app --resource-group my-rg - Kudu (SCM) console for process explorer, environment variables, and crash dumps.
- Health check: verify the path is configured and which instances are being removed from rotation.
Containers / AKS / Container Apps:
kubectl get pods,kubectl describe pod,kubectl logs --previousfor restart loops (CrashLoopBackOff,OOMKilled).- Check readiness and liveness probes, resource limits, and node health.
- For Container Apps, use the Log stream and
ContainerAppConsoleLogs/ContainerAppSystemLogstables.
APIM / Front Door / Application Gateway (if in front):
- Check whether failures originate at the gateway or the backend. Gateway logs show backend response codes, backend time vs. total time, and WAF blocks.
- A WAF rule or rate limit blocking legitimate traffic often looks like an app problem.
5. Check dependencies
Most production API incidents originate in something the API calls:
- Azure SQL: Query Performance Insight, blocking and deadlocks, DTU/vCore saturation, connection limits, failover events. Check whether a new query plan or missing index is the cause.
- Cosmos DB: 429s and RU consumption, hot partitions.
- Service Bus / Event Hubs: growing queue depth, dead-letter counts, lock-expired errors, throttling.
- Key Vault: throttling, or access denied after a role or network change.
- Redis: memory pressure, evictions, connection limits.
- Storage: throttling and latency metrics.
- Third-party APIs: check their status pages and your outgoing call durations.
6. Network and connectivity
If the app can't reach something or can't be reached:
- Network Watcher: Connection troubleshoot, IP flow verify, and NSG flow logs show whether a rule is dropping traffic.
- DNS: private endpoints need correct private DNS zone links. From Kudu, run
nameresolver <host>andtcpping <host:port>(on Windows App Service). - Firewall and access restrictions: recent changes to storage, SQL, or Key Vault firewalls often cause sudden
403s or timeouts. - SNAT port exhaustion: many outbound connections to the same endpoint can exhaust ports. Reuse
HttpClientthroughIHttpClientFactory, use connection pooling, or add a NAT Gateway. - TLS and certificates: expired or rotated certificates cause sudden handshake failures.
7. Compare against change history
Ask "what changed?" and verify it from records, not memory:
- Activity Log: shows control-plane changes (config updates, scaling, role assignments, deployments, key rotations). Filter by time window around the incident start.
- Deployment history / CI-CD pipeline runs and any app setting changes.
- Entra audit logs: app registration, secret, or permission changes.
- Resource Graph or Change Analysis (in Diagnose and solve problems) to see configuration diffs.
8. Go deeper when needed
- Memory or CPU issues: capture a memory dump or profiler trace through Diagnose and solve problems (Collect Memory Dump, Profiler) or
dotnet-dump/dotnet-tracein containers. - Thread pool starvation (common with sync-over-async): look for rising response times with low CPU, and check
ThreadPoolmetrics in Application Insights ordotnet-counters. - Reproduce in a lower environment using the same request payload and the operation ID details from the failure.
- Increase logging temporarily for the affected component, then revert it. Sampling can hide rare failures, so check whether it's dropping the data you need.
9. Mitigate while you investigate
- Roll back or swap slots.
- Scale up/out if it's resource saturation (buys time, doesn't fix the cause).
- Restart the app or a single bad instance as a last resort. Note the time and capture evidence first, because a restart destroys state.
- Disable the faulty feature with a feature flag, or route traffic away (Front Door / Traffic Manager) from an unhealthy region.
- Add retries with backoff and circuit breakers (Polly, or
Microsoft.Extensions.Http.Resilience) if a dependency is flaky. - Communicate: post status updates early, even if the cause is unknown.
10. Afterwards
- Write a blameless post-incident review: timeline, root cause, detection gap, and action items.
- Add the missing alert that would have caught it sooner (failure rate, p95 latency, dependency failures, queue depth, certificate and secret expiry).
- Add health checks, synthetic availability tests (Standard tests), and dashboards for the signals you had to hunt for.
- Make the fix safer to ship: deployment slots, canary releases, automated rollback, and load testing for the failure mode.
Quick checklist:
- Check Service Health and recent changes (deploys, config, secrets, certs).
- Open Application Insights: Failures, Performance, Application Map.
- Decide: my code, a dependency, or the platform/network?
- Pull logs and traces for a single failing operation ID.
- Mitigate (rollback, scale, failover) before the deep dive if users are affected.
- Confirm recovery from the metrics, then run the post-incident review.
No comments:
Post a Comment
Note: only a member of this blog may post a comment.