IL-0230 CircleCI · 2023-03-14kubernetesnetworking
kube-proxy sync failures during a Kubernetes upgrade corrupted node iptables, and CircleCI jobs stopped starting
In the week before the incident the CircleCI infrastructure team was upgrading its main production Kubernetes cluster step by step. On 13 March, after a few components were updated, some services reported missing metrics; the team reverted the changes and the metrics came back. The team adjusted the plan to limit the blast radius and at about 17:40 UTC on 14 March tried a different way of upgrading those components. Between the two Kubernetes versions, the way kube-proxy handles and formats its iptables rulesets had changed. kube-proxy rewrites each node's iptables rules with iptables-save and iptables-restore in Proxier.syncProxyRules() whenever Service and Endpoints objects change. After the kube-proxy rollback on 13 March a small number of sync failures had already started. As the upgrade rolled out more pods and Endpoints changed, the failures grew, and on 14 March a spike of sync errors left iptables on many nodes badly corrupted, so traffic between services failed from about 18:00 UTC.
With all three suspected networking workloads looking healthy, the team restarted node-local-dns-cache and CoreDNS, which made DNS resolution fail completely. A single node restart showed that traffic through a freshly restarted node worked, so the team restarted every node in the cluster; each restart built a fresh iptables ruleset and the sync errors went away. By 23:00 UTC all nodes had been rolled and jobs recovered except those using contexts or the machine executor. After a separate pool of dependencies was restarted, all jobs ran correctly by 01:15 UTC on 15 March, and the incident is given as lasting until about 01:27 UTC.
The recovery caused two further incidents in the RabbitMQ queuing system. Logging for RabbitMQ was found missing, so the team rolled the rest of the infrastructure; at about 02:50 UTC RabbitMQ connection errors delayed jobs again, mitigated by 03:35 UTC and resolved by 05:14 UTC. The report ties a later restart of the RabbitMQ nodes, done to fix a few corrupted queues, to a further degradation from 09:50 UTC in which GitHub checks were delayed by up to 30 minutes; it was mitigated by 11:08 UTC and resolved at 13:15 UTC.
- Severity
- –
- Scope
- Pipelines of all customers started late and then jobs did not run; Docker, Machine, macOS and Windows jobs, the CircleCI UI and Runner were affected. Two follow-on RabbitMQ incidents delayed all pipelines from 02:50 to 05:14 UTC and GitHub checks by up to 30 minutes from 09:50 to 13:15 UTC on 15 March.
- Users affected
- all customers
- Region
- –
- Time to detect
- –
- Time to resolve
- 7h 27m
Symptoms
On 14 March 2023 at about 18:00 UTC, communication errors rose on one CircleCI service, and pipelines for all customers began to start late until jobs did not run at all. Within an hour the errors had spread to many services and to every function of the application, including the CircleCI UI and API.
Job processing delays kept climbing and job queues backed up across several services. A few services were restarted to ease some of the symptoms. The common thread turned out to be failed service-to-service communication inside the main production Kubernetes cluster. Engineers searched logs and metrics and tried to reach services by their internal Kubernetes domain names from inside pods, and suspected kube-proxy, CoreDNS or node-local-dns-cache, but all three workloads looked healthy.
Between 20:00 and 21:00 UTC the team restarted node-local-dns-cache and CoreDNS, and DNS resolution then failed completely. Restarting one node made requests through that node work again, which pointed at kube-proxy. After every node had been restarted by 23:00 UTC, jobs ran again except those using contexts or the machine executor.
The analysis afterwards found that kube-proxy, which runs on every node, had a spike of sync errors at the start of the incident, logged as "Failed to execute iptables-restore" err="exit status 1 (iptables-restore: line 8022 failed\n)" followed by "Sync failed" retryingTime="30s". A small number of the same errors had already appeared on 13 March, after a first upgrade attempt had made some services report missing metrics and had been rolled back, but nobody noticed them then.
Symptom Service outageIntermittent connection failuresDNS resolution failures
Root cause
Root cause Breaking change in an upgrade
- Trigger
- At about 17:40 UTC on 14 March the infrastructure team resumed the upgrade of the main production Kubernetes cluster with a changed approach for some components, after a first attempt on 13 March had been rolled back. The ongoing upgrade left kubelet and kube-proxy at mismatched versions.
Detection
At 18:00 UTC engineers saw communication errors rise on one service, then job processing delays climbing and job queues backing up across several services. The kube-proxy "Sync failed" errors were found only in the analysis after the incident.
Remediation
- Restarted every node in the cluster so each node built a fresh iptables ruleset
- Restarted a separate pool of dependencies so jobs using contexts or the machine executor ran again
- Added capacity to work through the backlog of jobs
- Restarted the RabbitMQ nodes to fix a few corrupted queues
Action items
- Improve the Kubernetes upgrade process to limit the risk of future upgrades
- Add metrics and alerts for kube-proxy sync errors
- Evaluate ways to make the RabbitMQ infrastructure more resilient
Lessons
No single trigger caused the incident. Because networking underlies everything in the cluster, Kubernetes upgrades carry inherent risk, and CircleCI's risk mitigation did not hold up when several events interacted.
Sources
- https://status.circleci.com/incidents/dcqb3fykhgvg · official-status · archived copy
Related cases
IL-0144A systemd security update installed by unattended-upgrades lost the DNS configuration of Ubuntu 18.04 VMs, so AKS kubelets could not resolve registry names and pods failed with ImagePullBackOffMicrosoft Azure · 2022same root cause · shared kubelet, Client-side DNS resolver and cache
IL-0101After Google forced a GKE control plane upgrade to 1.33.2 at a deprecation deadline, no new node could initialise, so autoscaling and spot node replacement stopped for 15 hoursWealthy · 2025 · sev1same root cause · shared Client-side DNS resolver and cache
IL-0079A GKE node auto-upgrade to an ubuntu_containerd image with a kernel regression left kube-proxy unable to apply its ip6tables rules, so load balancer health checks failed and several clusters lost all ingress trafficAltinity · 2025same root cause · shared kube-proxy
Check your cluster for this
For AI tools and scripts
- This case as JSON: https://incidentlib-api.benchlet.com/cases/IL-0230
- The cases most like it: https://incidentlib-api.benchlet.com/cases/IL-0230/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.