{"id":"IL-0461","org":"Datadog","date":"2022-04-13","docs":[],"title":{"en":"Every rollout of Datadog's metrics query service on Kubernetes raised errors that the logs blamed on DNS","ko":"메트릭 질의 서비스를 롤아웃할 때마다 DNS 오류가 나며 질의가 실패했다"},"domain":["kubernetes","aws","networking"],"impact":{"scope":"Errors and added latency for dashboards and monitors that read from the metrics query service during its rollouts; client retries kept most errors from users","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"The errors looked like DNS but were not. ENA metrics and VPC Flow Logs gave the visibility into VPC connection tracking that host metrics did not, and Cilium, Kubernetes and AWS networking interact in ways that can make reverse path filtering drop packets it should allow. DNS propagation time depends on many settings. The causes were changes Datadog had made itself, to work around a dependency bug, to resolve earlier incidents and to override default reconnect parameters, which looked safe at the time and showed their effects much later, when the link was hard to see.","ko":"gRPC, 쿠버네티스, AWS 같은 강력한 추상화도 엣지 케이스에서는 평소에 숨어 있던 복잡함이 오류로 드러난다. 몇 달을 조사한 끝에, 이번 장애의 뿌리가 팀이 직접 한 변경에 있다는 것을 찾았다. 의존성의 버그를 피해 가려던 변경, 이전 장애를 고치려던 변경, 기본 재연결 파라미터를 덮어쓴 변경이었다. 이 변경들은 당시에는 안전해 보였지만 한참 뒤에 상관관계를 찾기 어려운 때에 뜻밖의 결과를 냈다. DNS 문제처럼 보여도 DNS 문제가 아닐 때가 있다. AWS ENA 지표와 VPC Flow Logs 는 연결 추적을 들여다보는 데 중요하다. DNS 전파 시간은 여러 요인에 좌우된다."},"related":[],"sources":[{"url":"https://www.datadoghq.com/blog/engineering/grpc-dns-and-load-balancing-incident/","kind":"blog","archived":"https://web.archive.org/web/20260623045649/https://www.datadoghq.com/blog/engineering/grpc-dns-and-load-balancing-incident/"}],"summary":{"en":"Datadog's metrics query service runs on EC2 instances with two ENIs: ens5 for the host and ens6 for the pods. Cilium in ipam:eni mode gives each pod a routable VPC address on ens6, a route to its veth and an IP rule that sends its traffic out through ens6. When a rollout deletes a pod, Cilium removes the route and the rule, but the VPC keeps delivering traffic for that IP to ens6. A SYN sent to the deleted pod's IP then has no return path through ens6, so reverse path filtering treats it as a Martian packet and drops it before it reaches the Linux conntrack, and the client gets no SYN-ACK, RST or ICMP error. The hosts used loose mode, which should allow this asymmetric path; reading the kernel code after the incident, the team found that the check falls through to the drop because ens6 has no IP address assigned, and adding an unrelated address to ens6 made loose mode behave as expected.\n\nClients needed about 60 seconds to learn the new pod addresses: the service waits 10 seconds after GracefulStop, the external-dns version in use only updates the Route 53 record after the kubelet has deleted the pod and syncs every 15 seconds with a 15-second TTL, and the gRPC clients use the default min_time_between_resolutions of 30 seconds. In June 2021 Datadog had switched one service's clients from pick_first to round_robin to fix another issue and then made round_robin the default; the metrics query clients picked it up in an August 2021 rollout. Because the application creates one gRPC channel per backend, the policy did not change the number of connections, but with round_robin a channel reconnects automatically whenever it is disconnected, and an aggressive reconnect timeout set long before made each of about 900 clients retry about every 300 milliseconds, up to 30,000 SYN packets per second. Each unanswered SYN took an entry in the VPC conntrack, entries are only removed after 60 seconds without an answer, and the table filled. The drops broke node-local-dns connections to its upstream resolvers, which surfaced as DNS errors.\n\nDatadog removed the custom gRPC reconnect parameters to restore the defaults, the next rollout showed none of the errors seen since September, and the incident was closed. Datadog later contributed a change to Cilium that adds an unreachable route for a deleted pod's IP, so incoming SYNs are not dropped as Martian packets and clients get a clear ICMP error. The investigation took months and involved an AWS support case.","ko":"2021년 9월부터 Datadog 의 메트릭 질의 서비스를 롤아웃할 때마다 오류가 늘었다. 이 서비스는 데이터 저장소에서 실시간과 과거 메트릭 데이터를 읽어 웹 프런트엔드와 모니터 평가 클라이언트에 준다. 로그는 DNS 오류를 가리켰고, 팀은 OOM 이 나던 node-local-dns 파드의 메모리를 64 MB 에서 256 MB 로 올렸지만 DNS 오류는 계속됐다. ENA 지표의 conntrack_allowance_exceeded 로 VPC conntrack 이 포화된 것을 확인했다. 더 큰 인스턴스 유형으로 올리면 문제가 사라진다는 것도 확인했지만, 팀은 conntrack 테이블이 왜 차는지 알고자 했다.\n\nVPC Flow Logs 를 보니 롤아웃 때 메트릭 평가 클라이언트가 지워진 파드의 옛 IP 로 SYN 을 대량으로 보냈다. 이 클러스터는 Cilium 을 ipam:eni 모드로 쓰고, 파드 트래픽은 보조 ENI 인 ens6 으로 다닌다. 파드가 지워지면 Cilium 이 그 파드의 경로와 IP 규칙을 지우지만 VPC 는 옛 IP 로 가는 트래픽을 여전히 그 노드의 ens6 으로 보낸다. 돌아갈 경로가 기본 경로인 ens5 뿐이라, 역경로 필터가 이 패킷을 martian 으로 보고 리눅스 conntrack 에 넣기 전에 버렸다. 호스트가 SYN-ACK, RST, ICMP host unreachable 중 어느 것도 보내지 않아 클라이언트는 옛 IP 라는 것을 몰랐고, 이 연결 요청은 VPC conntrack 에만 남았다. 응답이 없는 요청은 60초 안에 지워져야 하지만, 지워지는 속도보다 쌓이는 속도가 빨랐다. 역경로 필터는 loose 모드였다. 장애를 닫은 뒤 커널 코드를 보니, IP 주소가 없는 장치에서는 no_addr 검사 때문에 역경로 필터가 strict 모드처럼 동작했다. ens6 에 IP 를 하나 할당하자 예상대로 동작했다.\n\n클라이언트가 새 파드 IP 를 받기까지는 약 60초가 걸렸다. 쓰던 external-dns 버전은 kubelet 이 파드 삭제를 끝낸 뒤에야 레코드를 바꿨다. 서비스는 SIGTERM 을 받은 뒤 GracefulStop 에 10초 타임아웃을 두었다. external-dns 는 15초마다 동기화했고, TTL 은 15초였다. gRPC 클라이언트는 min_time_between_resolutions 기본값 때문에 30초마다만 DNS 를 다시 조회했다. 2021년 6월 다른 장애를 고치려고 그 서비스의 클라이언트를 round_robin 으로 바꿨고, 그 뒤 round_robin 을 여러 서비스의 기본 정책으로 정했다. 8월 메트릭 질의 클라이언트 롤아웃에서 이 기본값이 처음 적용됐다. 이 앱은 백엔드마다 gRPC 채널을 하나씩 만들었으므로 연결 수는 pick_first 를 쓸 때와 같았다. 다만 round_robin 은 끊긴 채널을 자동으로 다시 연결했다. 여기에 오래전에 정한 공격적인 재연결 타임아웃이 겹쳐, 600개 파드에 15개씩 컨테이너가 있는 약 900개 클라이언트가 약 300 ms 마다 재연결하며 초당 최대 30,000개의 SYN 을 보냈다. 이 재연결 파라미터를 지우고 기본값으로 되돌린 다음 롤아웃에서는 오류가 나지 않았고, 장애를 닫았다."},"trigger":{"en":"Rollouts of the metrics query service, which delete the old pods. The client behaviour behind the failures came from an August 2021 rollout of the metrics query clients that applied round_robin as the new default gRPC load balancing policy.","ko":"2021년 8월 메트릭 질의 클라이언트의 업데이트를 롤아웃하면서 새 기본값인 gRPC round_robin 부하 분산 정책이 처음 적용됐다. round_robin 은 끊긴 채널을 자동으로 다시 연결하는데, 오래전에 넣어 둔 매우 공격적인 gRPC 재연결 타임아웃과 겹쳐 서비스 롤아웃 때마다 사라진 파드 IP 로 SYN 이 쏟아졌다."},"severity":"unknown","symptoms":["high-error-rate","dns-resolution-failures","high-latency","oom-killed","packet-loss","stale-service-routing"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":{"en":"Errors rose during rollouts of the metrics query service, and the service's logs showed DNS errors when connecting to its data stores.","ko":"2021년 9월부터 메트릭 질의 서비스 롤아웃 때 오류가 늘어나는 것을 보았고, 서비스 로그에서 DNS 오류가 데이터 저장소 연결을 막는 것을 확인했다."},"components":["conntrack","cilium","linux-kernel","coredns","dns-resolver","external-dns","http-client-library"],"root_cause":"config-change","remediation":[{"text":{"en":"Raised the memory of the node-local-dns pods from 64 MB to 256 MB, which stopped the OOM kills but not the DNS errors","ko":"node-local-dns 파드의 메모리를 64 MB 에서 256 MB 로 올렸다. OOM 오류는 멈췄지만 DNS 오류는 계속됐다."}},{"text":{"en":"Tested larger instance types and confirmed that scaling up the service's infrastructure resolved the errors","ko":"AWS 가 더 많은 연결을 추적할 수 있다고 한 더 큰 인스턴스 유형으로 서비스 인프라를 올려 보았고, 그것만으로 문제가 사라지는 것을 확인했다."}},{"text":{"en":"Removed the custom gRPC reconnect parameters from the clients to restore the defaults","ko":"SYN 폭주를 일으킨 사용자 지정 gRPC 재연결 파라미터를 지우고 기본값으로 되돌리는 PR 을 만들었다. 다음 롤아웃에서는 오류가 나지 않았다."}}],"action_items":[{"text":{"en":"Contribute a change to Cilium that adds an unreachable route for a deleted pod's IP so clients get an ICMP error","ko":"Cilium 프로젝트에 PR 을 기여한다. 이 PR 은 옛 파드 IP 에 unreachable 경로를 더해, 지워진 파드로 가는 요청이 martian 패킷이 되지 않게 한다. 그러면 역경로 필터가 SYN 을 버리지 않고, 클라이언트는 분명한 ICMP 오류를 받는다."}}],"contributing":["delayed-effect-change","retry-storm","limit-unknown","monitoring-blind-spot"],"recurrence_of":null,"symptoms_text":{"en":"From September 2021, each rollout of an update to Datadog's metrics query service, which serves real-time and historical metric data to the web frontend and to monitor evaluation, came with a rise in errors. Clients retried failed queries, so few errors reached users, but the retries slowed dashboards and monitors. The service's logs showed DNS errors stopping it from connecting to the data stores it depends on.\n\nIn the cluster, the node-local-dns pods of NodeLocal DNSCache, a slimmed-down CoreDNS running as a DaemonSet, were being OOM killed with a 64 MB memory limit and were rejecting requests above their max_concurrent limit of 1,000 during rollouts. Raising the memory to 256 MB stopped the OOM kills, but the DNS errors continued. The request rate, about 400 queries per second and close to 2,000 during rollouts, was far below what node-local-dns should handle, and its metrics showed it marking the upstream DNS resolvers as unhealthy, which pointed to failing connections to the upstreams.\n\nNetwork throughput stayed well below the 5-Gbps limit of the instance types, but TCP retransmits rose with each rollout. After ENA metrics were enabled in the Datadog Agent, the metric conntrack_allowance_exceeded rose during rollouts, showing packets dropped because the VPC connection tracking table was saturated, while the Linux conntrack on the same hosts peaked below 60,000 connections. Tests on larger instance types showed that scaling up the service removed the errors.\n\nVPC Flow Logs showed that after a pod was deleted, clients that evaluate metric data kept sending SYN packets to its old IP address, and the SYNs got no answer: one old IP received about 90,000 connection attempts in about 90 seconds. On the old pod's node the kernel dropped these packets with \"IPv4: martian source 10.x.y.z from 10.a.b.c, on dev ens6\", and a route lookup returned \"RTNETLINK answers: Invalid cross-device link\".","ko":"2021년 9월부터 메트릭 질의 서비스의 업데이트를 롤아웃할 때마다 오류가 늘었다. 서비스 로그에는 DNS 오류 때문에 메트릭 데이터 저장소에 연결하지 못했다는 내용이 찍혔다. 클라이언트가 실패한 질의를 자동으로 재시도해 사용자에게 보이는 오류는 적었지만, 재시도 때문에 대시보드와 모니터의 지연이 늘었다.\n\n노드마다 도는 NodeLocal DNSCache DaemonSet 인 node-local-dns 파드에 OOM 오류가 났다. 이 파드는 메모리 한도가 64 MB 였고, max_concurrent 설정으로 동시 요청을 1,000개까지만 처리했다. 롤아웃 중에는 그 한도를 넘는 요청을 거절했다. 서비스의 요청률은 평소 초당 400건 안팎이었고 롤아웃 중에도 2,000건 가까이에 그쳐, node-local-dns 가 동시 요청 한도에 닿을 이유가 없어 보였다.\n\nnode-local-dns 지표에서 로컬 DNS 캐시가 상위 DNS 리졸버를 unhealthy 로 표시하고 있었다. 팀은 헬스체크 실패를 node-local-dns 가 상위 리졸버에 연결하지 못한다는 뜻으로 보았다. 그렇다면 요청마다 forward 플러그인의 5초 타임아웃 동안 슬롯을 잡고 있어, 초당 200건만으로도 max_concurrent 한도에 닿는다는 설명이 됐다.\n\n인스턴스 유형의 지속 처리량 한도인 5 Gbps 에는 한참 못 미쳤지만, 롤아웃에 맞춰 TCP 재전송이 늘었다. 처리량 그래프에 잡히지 않는 짧은 트래픽 급증을 의심해 Datadog Agent 에서 ENA 지표를 켜자, 대역폭과 처리량에는 이상이 없었고 conntrack_allowance_exceeded 지표가 늘었다. VPC conntrack 이 포화된 것이었다. 같은 인스턴스의 리눅스 conntrack 연결 수는 60,000개 아래로 낮았다.\n\nVPC Flow Logs 를 TCP 플래그별로 모아 보니, 롤아웃 중 파드가 지워진 뒤에도 메트릭 데이터를 평가하는 클라이언트들이 옛 파드 IP 로 계속 SYN 을 보냈다. 옛 IP 하나가 약 90초 동안 약 90,000번의 연결 시도를 받았고, 기대했던 RST 는 돌아가지 않았으며 SYN 은 응답 없이 버려졌다.\n\n노드 두 대로 재현하자 받는 쪽 노드의 tcpdump 에는 SYN 과 재시도가 보였지만 응답이 없었다. `ip route get 10.x.y.z from 10.a.b.c iif ens6` 은 \"RTNETLINK answers: Invalid cross-device link\" 를 냈고, 커널 로그에는 \"IPv4: martian source 10.x.y.z from 10.a.b.c, on dev ens6\" 이 찍혔다. 역경로 필터가 loose 모드로 설정돼 있었는데도 strict 모드처럼 패킷을 버렸다."},"updated_at":"2026-11-14T12:34:10.947900Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0461","org_slug":"datadog"}