Incident Library

IL-0386 incident.io · 2023-09-26 (게시일)kubernetesgcpdatabasenetworking

Google Cloud 로 옮긴 뒤 GKE 노드에서 Postgres 연결 타임아웃과 memcached i/o timeout 이 간헐적으로 튀었다

incident.io 는 인프라를 Google Cloud 로 모두 옮겼고, 옮긴 뒤 Postgres 연결 타임아웃이 평소보다 많이 나서 인시던트를 선언했다. 초당 새 연결이 수백 개씩 오르내리는 것을 보고 연결이 만료될 때의 경쟁 조건을 의심했다. 그래서 연결 최대 수명을 15m 에서 30m 로 늘리고 연결 풀을 고정 크기로 바꿨다. 새 연결은 초당 약 200개에서 약 1개로 줄었다. memcached 에서도 연결 타임아웃이 났고, MaxIdleConns 를 2 에서 20 으로 올리자 바로 나아졌지만 며칠 뒤 read i/o 타임아웃이 훨씬 많아졌다. 변수를 줄이려고 Google 이 운영하는 memcached 를 쿠버네티스 클러스터 안의 자체 memcached 로 옮겼지만 거의 달라지지 않았다.

같은 때 실수로 들어간 데이터베이스 join 때문에 외부 제3자 서비스로 중복 네트워크 호출을 수천 번 보내던 문제가 따로 있었다. 이 문제를 고친 코드가 운영에 배포되자 데이터베이스 연결 문제와 캐시 문제가 거의 사라졌다. 남은 캐시 오류를 쫓아 영향받은 요청의 트레이스와 노드 로그를 보니, 요청 하나에서 캐시 set 수백 번과 Google Cloud Storage URL 생성 호출이 한꺼번에 일어났다. 그 노드에서는 새 연결 수와 드롭된 패킷이 함께 늘었고, 다른 요청인 헬스 체크까지 캐시에 닿지 못했다. 문제는 프로세스 하나가 아니라 노드 전체에 걸쳐 있었다.

클러스터 설정에서 관련 있어 보이는 것은 Dataplane V2 를 켠 것 하나였다. GKE 문서는 노드에서 TCP 연결을 짧게 열고 닫는 일이 많으면 GKE Dataplane V2 에이전트 파드인 anetd 가 인스턴스마다 vCPU 2~3개까지 쓸 수 있다고 적고, HTTP 호출의 keep-alive 와 연결 풀링을 권한다. anetd 로그에는 눈에 띄는 것이 없었지만 CPU 가 꽤 뛰었다. incident.io 는 지금까지의 관찰과 조치가 이 설명과 맞는다고 봤다. 연결 풀을 고쳐 새 연결을 줄이자 데이터베이스와 캐시 타임아웃이 대부분 사라졌다. 상황을 키운 것은 잘못된 쿼리가 goroutine 수천 개를 띄워 외부 연결을 연 일이었다. 외부 요청을 병렬로 많이 보낼 때는 문제가 드물게나마 계속 났다. Dataplane V2 를 끄는 방안도 검토했지만 택하지 않았다. 끄는 것은 스스로 일으킨 문제를 덮는 일이었다. 끄려면 새 클러스터를 만들어 워크로드를 옮겨야 했다. Dataplane V2 는 앞으로 Autopilot 클러스터에서 끌 수 없는 기본값이 될 가능성이 컸고, incident.io 는 엄격한 네트워크 정책 같은 장점도 쓰고 싶었다.

심각도
–
영향 범위
A small share of requests hit Postgres connection timeouts and memcached i/o timeouts; at times health checks on an affected node failed
영향 사용자
–
지역
–
감지까지
–
복구까지
–

증상

인프라를 Google Cloud 로 모두 옮긴 뒤 Postgres 연결 타임아웃이 평소보다 많이 나서 인시던트를 선언했다. 초당 새로 여는 연결 수가 수백 개 수준에서 크게 오르내렸다.

일부 API 응답을 빠르게 하려고 쓰는 memcached 에서도 연결 타임아웃이 띄엄띄엄 났다. memcache 클라이언트의 기본 네트워크 타임아웃은 100ms 였고, memcache 서버까지는 보통 1ms 도 걸리지 않았다.

memcache 클라이언트의 MaxIdleConns 를 올리고 며칠이 지나자 "read i/o" 타임아웃이 훨씬 많이 보였다. 앱은 캐시에 값을 쓰다가 saving value to cache: setting cache value: read tcp 10.16.4.33:35586->10.67.0.5:11211: i/o timeout 오류를 냈다.

이 오류는 사고 타임라인 이벤트 목록을 캐시에 넣을 때 가장 자주 났다. 수십만 건의 요청 가운데 몇 건에서만 났고, 사용자, 조직, 노드, 요청 어느 쪽으로도 일정한 패턴이 보이지 않았다.

영향받은 요청 하나의 트레이스를 보니 캐시 set 이 수백 번 거의 동시에 일어났고, 같은 때 Google Cloud Storage 객체의 URL 을 만드는 호출도 아주 많았다. 캐시 호출 시간은 처음에 약 1ms 이하였다가 몇 자릿수만큼 늘어났고, 편차도 크게 벌어졌다.

그 요청을 처리한 쿠버네티스 노드의 지표에서는 새로 여는 연결 수가 크게 튀었고, 같은 때 드롭된 패킷이 늘었다. 노드 로그에는 전혀 다른 요청인 헬스 체크까지 캐시에 닿지 못해 실패한 기록이 있었다.

처음에는 연결이 만료될 때 여러 goroutine 이 같은 죽은 연결을 한꺼번에 다시 쓰려다 새 연결을 대량으로 여는 경쟁 조건을 의심했다. 연결 설정을 바꾸자 새 연결은 초당 약 200개에서 약 1개로 줄었지만, 캐시 오류는 완전히 사라지지 않았다.

변수를 줄이려고 memcached 를 Google 이 운영하는 서비스에서 클러스터 안의 자체 memcached 로 옮겼지만 거의 달라지지 않았다. 그러다 외부 서비스로 중복 호출을 보내던 별개의 문제를 고친 코드가 운영에 배포되자 문제가 거의 사라졌다. incident.io 는 이것을 보고 GKE 나 GCP 네트워크 자체에는 문제가 없을 가능성이 크다고 봤다.

증상 간헐적 타임아웃패킷 손실

원인

촉발
노드에서 짧게 열고 닫는 TCP 연결이 대량으로 생겼다. 연결 풀이 초당 수백 개씩 새 연결을 열었고, 실수로 들어간 데이터베이스 join 때문에 외부 제3자 서비스로 가는 중복 호출 수천 번이 한꺼번에 나갔다.

감지

Postgres 연결 타임아웃이 평소보다 많이 나는 것을 보고 인시던트를 선언했다.

조치

후속 조치

교훈

가설을 너무 빨리 좁히지 않는다. 이런 네트워크 타임아웃을 보면 같은 파드만이 아니라 같은 노드에서 도는 모든 것을 포함해 시스템 전체의 활동을 봐야 한다. 오류와 그 영향은 대개 요청 하나에 머물지만 늘 그렇지는 않다. 외부로 나가는 네트워크 호출의 동시성도 제한해야 한다. 이전에는 공급자의 rate limit 이 주된 제약이었지만, rate limit 안에 있어도 노드에 영향을 줄 수 있다.

출처

관련 사례

내 클러스터에도 있는지 확인하기

kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이 사례와 같은 상태를 점검합니다. 이 사례의 구성 요소: CNI 플러그인, 데이터베이스, 인메모리 캐시. 점검 항목의 근거는 이 사례 같은 공개 보고서입니다.

AI 도구와 스크립트용

API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.