메뉴
HN
Hacker News 30일 전

AI 에이전트가 발견한 구글 쿠버네티스 엔진의 버그

IMP
8/10
핵심 요약

Lovable 플랫폼의 인프라 엔지니어가 AI 디버깅 에이전트를 활용해 Google Kubernetes Engine(GKE) 내의 네트워킹 버그를 신속하게 추적해냈습니다. 문제의 원인은 Google이 Cilium 기반 네트워크 데몬(anetd)에 통합한 WireGuard 모듈의 동시성 제어 결함과 패킷 크기(MTU) 불일치였습니다. 이 사례는 대규모 인프라에서 발생하는 간헐적 장애를 분석할 때 AI 에이전트가 얼마나 강력한 도구가 될 수 있는지를 보여줍니다.

번역된 본문

지난주, 우리 사용자들은 원인을 알 수 없는 에러를 겪기 시작했습니다. 때로는 프로젝트를 열 때 실패하고, 때로는 GitHub에서 코드를 클론(clone)할 때 시간 초과가 발생했습니다. 심지어 우리 모두가 두려워하는 'Connection reset by peer(연결이 상대방에 의해 재설정됨)' 에러까지 보였습니다. 명확한 패턴이 전혀 보이지 않았는데, 이는 항상 가장 최악의 패턴입니다. 피크 시간대에 매 초 50개 이상의 샌드박스를 생성하는 Lovable과 같은 플랫폼에서는 아주 적은 비율의 실패만으로도 사용자에게 큰 문제가 될 수 있습니다. 인프라 내부 어딘가가 불안정했고, 우리는 그 원인을 찾아야만 했습니다.

Sascha, 우리 인프라 엔지니어 중 한 명이 훌륭한 디버깅의 시작점이라 할 수 있는 로그 분석부터 시작했습니다. 하지만 수백만 줄의 로그를 살펴봐야 했고, 어떤 패턴도 쉽게 눈에 띄지 않았습니다. 그는 새로운 시도를 해보기로 했습니다. 그는 이미 디버깅을 위한 AI 에이전트(AI agent)를 실험하고 있었고, 지금이 적기라는 생각이 들었습니다. 그는 Clickhouse 로그에 접근할 수 있는 에이전트를 설정하고 질문을 던지기 시작했습니다.

에이전트는 의심스러운 문제를 하나 찾아냈습니다. Google Kubernetes Engine(GKE) 클러스터 내의 anetd 파드(pod)들이 끊임없이 재시작되고 있었던 것입니다. 6일 동안 파드당 약 120번의 재시작, 즉 거의 매 시간마다 한 번씩 충돌(crash)이 발생하고 있었습니다. 이게 말이 될 리가 없었습니다! 참고로 anetd는 Kubernetes 클러스터 내부의 네트워킹 레이어인 Cilium의 구글 구현체입니다. anetd가 충돌하면 새로운 파드가 네트워크 인터페이스를 얻지 못합니다. 그리고 제품 전체가 계속해서 새로운 샌드박스를 띄우는 데 의존하는 상황에서, 네트워크 불안정성은 즉각적인 사용자 오류로 이어집니다.

Sascha는 크래시 덤프(crash dump)를 파고들었습니다. 스택 트레이스(stack trace)는 동시성 맵 접근(Concurrent map-access) 패닉을 가리켰습니다. 여러 고루틴(goroutine)이 적절한 락(lock) 없이 동시에 동일한 데이터 구조에 읽기 및 쓰기를 시도하고 있었던 것입니다. 핵심은 이 패닉이 발생한 위치였습니다. 바로 anetd의 WireGuard 모듈 내부였습니다.

WireGuard 자체는 오픈소스 암호화 프로토콜로, 구글이 소유하지 않습니다. 하지만 구글은 이를 GKE용 네트워킹 데몬인 anetd에 통합하는 코드를 소유하고 있습니다. 패닉은 구글의 통합 코드, 특히 WireGuard 연결을 추적하는 맵(map) 데이터 구조에 대한 동시 접근을 관리하는 방식에서 발생했습니다. 이것이 중요한 이유는, 버그가 WireGuard 자체가 아니라 구글의 구현체에 있음을 의시하기 때문입니다. 즉, 이 문제를 해결하려면 구글의 도움이 필요했습니다.

우리는 구글의 담당 팀과 통화를 시작했습니다. 일요일이었고 사용자들에게 영향을 미치고 있는 상황이었기에 팀이 급히 구성되었습니다. 구글 측 대표의 권고는 명확했습니다. '투명한 노드 간 암호화(transparent node-to-node encryption)를 비활성화하라'는 것이었습니다. 이렇게 하면 WireGuard 모듈에서 직면한 버그를 완전히 우회할 수 있었습니다. 우리는 트레이드오프를 논의했습니다. 보안 관점에서 노드 간 암호화를 비활성화하는 것은 이상적이지 않았지만, 클러스터는 이미 구글의 프라이빗 네트워크에서 실행되고 있었고, 사용자가 에러를 겪고 있을 때 '안정성이 완벽함보다 낫다'는 판단을 내렸습니다.

변경 사항을 배포하고 모든 anetd 파드를 재시작한 후 대시보드를 지켜보았습니다. 충돌이 멈췄습니다. 약 4시간 동안 우리는 문제가 해결되었다고 생각했습니다. 일부 팀원은 로그오프했습니다. 그런 다음 다시 Slack 알림이 울리기 시작했습니다.

우리는 인메모리 데이터 스토어인 Valkey에 대한 무작위 연결 실패를 목격했습니다. 처음에는 Valkey 자체를 의심했습니다. CPU 사용량이 증가하고 있었기에, 포화 상태가 아닌지 확인하기 위해 노드 수를 두 배로 늘렸습니다. 안타깝게도 에러는 계속 발생했습니다.

통화에 참여 중이었던 또 다른 엔지니어 Erik은 직감이 있었습니다. 응용 프로그램 코드를 전혀 변경하지 않았기 때문에, 문제는 스택의 더 깊은 곳에 있을 것이라는 점이었습니다. 아마도 네트워크 문제일 것이라고 추측했습니다. 그는 몇몇 노드에서 tcpdump를 실행하여 패킷 캡처를 시작했습니다. 나머지 팀원들이 다른 가능성을 추적하는 동안, Erik은 Wireshark로 트래픽을 필터링했습니다. 그리고 마침내 결정적인 증거를 발견했습니다. 'Destination unreachable (Fragmentation needed)(목적지에 도달할 수 없음 - 단편화 필요)' 에러였습니다. 그 순간 모든 것이 명확해지기 시작했습니다.

바로 MTU 불일치 문제, 즉 '우리에겐 더 큰 패킷이 필요합니다'라는 상황이었습니다. 상황은 이랬습니다. WireGuard가 활성화되었을 때, 클러스터는 WireGuard의 오버헤드를 수용하기 위해 1420바이트의 MTU(최대 전송 단위)를 사용하고 있었습니다...

원문 보기
원문 보기 (영어)
The Scent Last week, our users started seeing errors that didn't make sense. Sometimes opening a project would fail. Sometimes cloning code from GitHub would time out. We were even seeing the dreaded "Connection reset by peer". There was no real obvious pattern, which is always the worst kind of pattern. On a platform like Lovable, which currently creates more than 50 sandboxes per second during peak hours, even a small percentage of failures can be a big problem for our users. Something in our infrastructure was wobbling, and we needed to find it. Following the Trail Sascha, one of our infrastructure engineers, started where any good debugging session begins: the logs. But we had millions of log lines to sift through, and patterns weren't jumping out. He decided to try something new. He'd been experimenting with AI agents for debugging, and this felt like the right moment to lean on them. He set up an agent with access to our Clickhouse logs and started asking it questions. The agent surfaced a suspicious issue: the anetd pods in our Google Kubernetes Engine cluster were restarting constantly, around 120 restarts per pod over six days, which is almost one crash per hour. Surely, this couldn't be right! For context, anetd is Google's implementation of Cilium, the networking layer inside our Kubernetes clusters. When anetd crashes, new pods can't get network interfaces. And when your entire product depends on spinning up fresh sandboxes continuously, networking instability quickly translates into user-facing failures. Sascha dug into the crash dumps. The stack trace pointed to a concurrent map-access panic, multiple goroutines trying to read and write to the same data structure at the same time without proper locking. But the key detail was where the panic happened: inside the Wireguard module of anetd. WireGuard itself is an open-source encryption protocol, which Google does not own. But they do own the code that integrates it into anetd, their networking daemon for GKE. The panic was happening in Google's integration code, specifically in how they were managing concurrent access to a map data structure that tracked Wireguard connections. This matters because it means the bug was in Google's implementation, not in WireGuard itself. Ergo, we'd need Google's help to fix it. Pulling in Support We got on a call with Google's account team. It was a Sunday, but this was affecting users, so the team assembled. Their representative's recommendation was straightforward: disable transparent node-to-node encryption. This would bypass whatever bug we were hitting in the WireGuard module entirely. We talked through the tradeoffs. Disabling encryption between nodes wasn't ideal from a security perspective, but our cluster already ran on Google's private network, and stable is better than perfect when users are seeing errors. We rolled out the change, restarted all the anetd pods, and watched the dashboards. The crashes stopped. For about four hours, we thought we were done. Some of the team logged off. Then the Slack notifications started rolling in again. A Second Trail We were seeing random connection failures to Valkey, our in-memory data store. At first, we suspected Valkey itself. CPU usage was climbing. We doubled the node count to make sure it wasn't saturated. Sadly, the errors kept coming. Erik, another engineer on the call, had a hunch. We hadn't changed any application code, so the problem had to be deeper in the stack. Probably networking. He spun up tcpdump on a few nodes and started capturing packets. The rest of the team chased other potential leads while he filtered through the traffic in Wireshark. Then he found the smoking gun: "Destination unreachable (Fragmentation needed)." That's when everything started to click for us. The MTU Mismatch: "We're gonna need a bigger packet." Here's what was happening: When WireGuard was enabled, our cluster used an MTU (maximum transmission unit) of 1420 bytes to account for WireGuard's encryption overhead. Normally, Ethernet uses a standard MTU of 1500 bytes. When we disabled WireGuard, we expected the configuration to change to use the full 1500 bytes. However, some nodes in the cluster hadn't been restarted yet. They were still using the old 1420-byte MTU. This particularly affected Valkey connections because they were distributed across nodes with mismatched MTU settings. So depending on which node your API pod was running on, you might connect fine... or fail mysteriously. The fix was simple once we understood it: reroll all the nodes to get a consistent MTU configuration across the cluster. Resolution The Sunday call stretched past three hours. We shared screens, walked through stack traces and packet captures, validated theories. There was good collaboration with Google's team. They recognized the anetd bug immediately once we showed them the evidence. Not many customers create and delete pods at our volume, so we'd surfaced something they hadn't caught yet. Distributed systems rarely fail in just one layer. The Wireguard crashes were the first layer. The MTU mismatch was hidden underneath, only becoming visible once we fixed the initial problem. We watched our error dashboards until the Valkey connection failures disappeared. By the time the last errors cleared, we all felt accomplished. It was late Sunday, and we decided to monitor through Monday before declaring full victory, but the immediate crisis was over. What We Learned The real lesson here was about layered failures. When you fix one thing in a distributed system, you need to watch carefully for what emerges next. We're more methodical now about validation after infrastructure changes. For Sascha, this incident changed how he approaches debugging entirely. It was the first time he leaned heavily on AI agents for investigation, and the ability to query logs at scale and surface patterns without manual parsing was a game-changer. "I haven't gone back," he said afterward. The team also learned to trust our instincts when pushing back on vendors. Erik was right about the MTU issue even when Google's initial assessment disagreed. That kind of technical conviction matters when it comes to problems like this. Google, for its part, has since patched the WireGuard concurrency bug. We're not the only ones who benefit from that fix. Work on Problems Like This If debugging complex cloud infrastructure sounds interesting to you, we're hiring at Lovable. We work on challenging technical problems every day, and we'd love to have you on the team.