IL-0493 Netlify · 2021-03-25gcpnetworking
Netlify's Google Cloud TCP load balancer stopped serving about 5% of global traffic as all backends failed health checks
Google Cloud's TCP load balancer uses Direct Server Return, so each backend instance must be able to answer with the load balancer IP as its source IP. On GCP, the networking agent google-guest-agent, which ships in the GCP base image Netlify used for its backends, injects the load balancer IP into each node's local route table. On the day of the incident, an unattended update restarted systemd-networkd, which removed that route table entry. google-guest-agent watches several network manager units and re-adds the IP when they restart, but it had no watcher for systemd-networkd, so the backends were left without the IP. As the update reached each node over several hours, the node failed the load balancer's health check and stopped receiving traffic, while Netlify's own health checks, which reached the backends on their own IPs, kept reporting them healthy. Requests were dropped once systemd-networkd restarted on the last backend.
Believing the cause was the zonal L4 load balancer issue on the provider's status page, Netlify restored traffic by other routes: Google's global TCP proxy was ruled out because it does not support TCP port 80, so the team built separate load balancers for enterprise and regular traffic, gave enterprise customers alternative IPs at 15:30 UTC, and from 15:55 UTC provisioned an AWS network load balancer backed by Global Accelerator whose IP was published for self-serve customers at 16:46 UTC. At 18:38 UTC Google support pointed to a problem with the backends' virtual IP, and at 18:46 UTC restarting the GCP networking daemon on all affected nodes restored the remaining traffic on the Google load balancer.
- Severity
- –
- Scope
- Sites reached through the static IP of the Google Cloud TCP load balancer used for A records: about 5% of Netlify's global traffic was not served
- Users affected
- a portion of Netlify customers, those using the static A-record IP
- Region
- –
- Time to detect
- 0m
- Time to resolve
- 4h 7m
Symptoms
At 14:39 UTC on March 25, 2021, Netlify's monitors alerted that the health check of one of its load balancers was failing. That Google Cloud TCP load balancer served the single static IP that customers use for A records, and about 5% of Netlify's global traffic stopped being served until 18:46 UTC.
Netlify has two kinds of health checks for this load balancer: one connects through the load balancer IP to a backend node, and one talks to each backend directly. Throughout the incident the node-level checks reported the backends as healthy. The GCP console showed the backend nodes failing the load balancer's own health checks, but gave no further diagnostic information.
At 14:48 UTC a banner on the provider's status page read: "We are experiencing an issue with L4 load balancers in us-west1-c. Multiple managed services relying on LB and located in this zone might be affected." With nothing else to go on, the team took this as the cause and worked on mitigations, expecting a long provider resolution. The provider outage later turned out to be unrelated, and following it delayed the resolution.
Symptom Probe failuresHigh error rate
Root cause
Root cause Software defect
- Trigger
- Unattended operating system updates, whose timing Netlify randomises across production nodes, restarted the systemd-networkd unit on each node over several hours, and each restart removed the custom route table entry that held the load balancer IP.
Detection
Netlify's monitors alerted at 14:39 UTC that the load balancer health check was failing. The load balancer's own backend health checks were not part of Netlify's alerting, so the backends dropping out over the preceding hours raised no alert.
Remediation
- Gave enterprise customers a separate range of IPs on a new load balancer for enterprise traffic
- Provisioned an AWS network load balancer backed by Global Accelerator and published its IP for self-serve customers
- Restarted the GCP networking daemon on all affected backend nodes, which restored traffic on the Google load balancer
Action items
- Patch the google-guest-agent systemd configuration on all GCP CDN nodes to react to systemd-networkd restarts
- Report the bug to Google and open an issue in the google-guest-agent repository
- Include the load balancer's backend health checks in alerting
- Add a canary node to the backend to test updates and watch for issues over several days
- Decouple traffic tiers and move to multiple regions
- Offer customers more ways to route apex domains to Netlify, such as ALIAS and ANAME records
- Route enterprise traffic through its own IP
Lessons
Netlify's own node-level health checks could not see that the backends were failing the load balancer's health check, and alerting on the load balancer's backend health checks would have given more time to diagnose before the outage.
Sources
Related cases
IL-0150A latent race condition in DynamoDB's DNS automation left an empty DNS record for dynamodb.us-east-1.amazonaws.com, and the DynamoDB and EC2 launch failures that followed caused EKS, ECS and Fargate container launch failures and cluster scaling delays in us-east-1Amazon Web Services · 2025same root cause · shared Cloud load balancer
IL-0352Heroku Router 2.0 spread HTTP requests unevenly across dynos for four days, causing latency and timeoutsHeroku · 2025same root cause · shared Cloud load balancer
IL-0131A new kind of GenerateContent request made Vertex AI prediction API servers segfault, and once enough of them restarted at the same time the serving fleet could not carry the loadGoogle Cloud · 2024same root cause · shared Cloud load balancer
IL-0529New Google Cloud VMs and other instances came up without network connectivity in all zones after VPC controllers stoppedGoogle Cloud · 2024same root cause · shared Cloud load balancer
IL-0253Firefox froze and could not load any web content for close to two hours, stuck in an HTTP/3 request to TelemetryMozilla · 2022same root cause · shared Cloud load balancer
IL-0243Google's global External Proxy Load Balancing returned 404 errors for most HTTP/S endpoints for 33 minutesGoogle Cloud · 2021same root cause · shared Cloud load balancer
IL-0106A malformed Traefik IngressRoute triggered a Traefik v2 bug once a node rollover restarted every Traefik pod, and all Kubernetes services returned 404DFDS · 2021same root cause · shared Cloud load balancer
IL-0345Many Render services in the Oregon region returned 503 errors for about four hoursRender · 2020same root cause · shared Cloud load balancer
IL-0030Most nodes of a cluster went NotReady together for 15 minutes while the API server sat behind an AWS ELBZalando · 2019same root cause · shared Cloud load balancer
IL-0338Google Cloud HTTP(S) and TCP/SSL Proxy load balancers returned 502s or reset connections on 33% to 87% of requestsGoogle Cloud · 2018same root cause · shared Cloud load balancer
IL-0395Google Cloud HTTP(S) Load Balancer gave 502s on 25% of requests for 22 minutes after reverting to a stale configurationGoogle Cloud · 2017same root cause · shared Cloud load balancer
IL-0396New or live-migrated GCE instances, VPN tunnels and network load balancers were unreachable on public IPs for over 2hGoogle Cloud · 2017same root cause · shared Cloud load balancer
Check your cluster for this
For AI tools and scripts
- This case as JSON: https://incidentlib-api.benchlet.com/cases/IL-0493
- The cases most like it: https://incidentlib-api.benchlet.com/cases/IL-0493/similar
- MCP server (Streamable HTTP, no key):
https://incidentlib-mcp.benchlet.com/mcp
The API needs no key. The MCP server offers search_incidents, get_incident, list_incidents, get_topic and vocab; both are explained on API and MCP.