
저희 팀에서는 스케줄링을 통해 매일 외부 API에서 특정 데이터를 가져와서 DB에 저장하는 로직이 존재하는데요. 해당 스케줄러에서 어느날 다음과 에러가 발생했습니다 ㅠㅠ
org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://helloworld:8080/hello": Connection reset
connection reset은 처음 겪는 에러여서 발생한 원인은 바로 파악하지 못했습니다.
다만 로그를 보니 스케줄러가 최초 실행 및 재시도 시간으로부터 정확히 2분뒤에 에러가 발생했기에, "어디선가 타임아웃을 발생시켰구나!!"라고 추측할 수 있었습니다.
가장 먼저 떠올랐는데요. 그렇지만 제 기억 속에서도, 실제 코드에서도 타임아웃을 2분으로 설정한 건 없었습니다.
"혹시 내가 설정을 잘못해서 default option이 적용됐나?"라는 의심도 했지만, 로컬 환경에서 테스트 해보니 RestClient의 경우 명확히 ConnectionTimeout 혹은 ReadTimeout이라는 에러를 반환했습니다.
외부 API라고 간략히 이야기했지만, 해당 API는 사내 레거시 프로젝트의 API입니다. 신규 프로젝트는 NaverCloud를, 레거시 프로젝트는 AWS를 이용하는데 해당 서버 앞단에 ALB가 존재하는 형태라 ALB의 타임아웃을 의심했습니다.
그렇지만 ..
이젠 원인이 안떠올라서 무지성 구글링을 했었는데요. 우연히 2분이라는 키워드에 걸려서 네이버클라우드 docs에서 다음과 같은 글을 발견했습니다!!

문제가 발생한 프로젝트는 k8s 환경으로 구성되어 있고 외부 통신은 모두 NAT Gateway를 통해 전달되는 상황이였고, 2분 타임아웃도 명확히 일치했습니다.
그리고 설정이 거의 동일할거라고 추측한 AWS의 NAT Gateway 문서에는 클라이언트와 연결을 끊어도 서버에 FIN 패킷을 전달하지 않는다고 적혀있었습니다.
실제로 서버 로그를 보니, 클라이언트와 연결이 끊긴 시점에도 서버는 커넥션이 끊기지않고 로직이 계속 돌아가고 있었습니다.

이젠 이유도 알았고, Solution에 적힌대로 TCP keepalive를 활성화하는 방식으로 해결만 하면 되는 상황!
하나 짚고 넘어가야 할 건 HTTP keepalive와 TCP keepalive가 다르다는 것입니다.
즉 TCP keepalive 패킷 전송 시간을 timeout 시간보다 짧게 설정하여, timeout 발생전에 패킷을 전송한다면 유휴 커넥션으로 판단하지않아 timeout 시간을 연장할 수 있습니다.
구체적으로 TCP keepalive 관련된 설정은 다음과 같습니다.(liunx)
tcp_keepalive_time
- 연결 후 처음으로 TCP Keep-Alive 패킷을 보내기까지의 시간
cat /proc/sys/net/ipv4/tcp_keepalive_time
sysctl net.ipv4.tcp_keepalive_time=7200
tcp_keepalive_intvl
- keepalive 패킷 전송 간격
cat /proc/sys/net/ipv4/tcp_keepalive_intvl
sysctl net.ipv4.tcp_keepalive_intvl
tcp_keepalive_probes
- 연결이 끊어질 때 까지 재전송 시도 횟수(retry)
cat /proc/sys/net/ipv4/tcp_keepalive_probes
sysctl net.ipv4.tcp_keepalive_probes
실제로 keepalive time과 interval을 10초로 설정하니 다음과 같이 10초 간격으로 keepalive 패킷을 전송하는 것을 볼 수 있었습니다.

var connectionManager = new PoolingHttpClientConnectionManager();
var socketConfig = SocketConfig.custom()
.setSoKeepAlive(true)
.build();
connectionManager.setDefaultSocketConfig(socketConfig);
다만 k8s 버전 마다 다르지만, 대부분의 경우 해당 설정이 기본적으로 변경 불가능해 다음과 같은 에러를 만날 수 있습니다.
forbidden sysctl: "net.ipv4.tcp_keepalive_time" not whitelisted
따라서 해당 설정을 사용할 수 있도록 하는 작업이 필요합니다.
// kubelet-config.yml
allowedUnsafeSysctls:
-"net.ipv4.tcp_keepalive_time"
해당 설정을 추가하니 문제 없이 변경할 수 있었고 자고 일어나서 확인해보니 스케줄러도 타임아웃 없이 정상적으로 동작할 수 있었습니다 야호!
사실 리소스가 크게 드는 작업은 아니였지만, 사내에 k8s 환경으로 검증 서버가 갖추어져 있지 않아서 운영에서 벌벌 떨면서 했었습니다 ㅋㅋ ㅠㅠ
그렇지만 잘 해결했고, 그 과정에서 네트워크단에 대해 좀 더 배울 수 있어서 되게 재밌는 경험이였습니다.
Referecne
https://dev-alxndr.github.io/posts/NAT-Gateway-Connection-Rest-%EC%9D%B4%EC%8A%88-%EB%B6%84%EC%84%9D/
https://github.com/aws/karpenter-provider-aws/issues/1279#issuecomment-1039968290
https://togomi.tistory.com/74