Study/Linux

[Linux]TCP Keepalive

seomj 2024. 4. 24. 16:00

TCP Keepalive 옵션을 이용해서 TCP 기반의 통신에서 세션을 유지하는 방법

이를 통해서 시스템이 얻는 것은 무엇인지, 그리고 주의해야 할 부분들은 어떤 것이 있는지


TCP Keepalive

TCP Keepalive는 일정 시간이 지나면 연결된 세션의 두 종단이 서로 살아있는지를 확인하는 아주 작은 양의 패킷을 하나 보낸다. 연결을 유지하는 게 유리한 쪽에서만 진행해도 된다. 클라이언트 혹은 서버 둘 중에 하나라도 이 기능을 사용한다면 세션은 유지된다. Keepalive 패킷을 주고 받은 후 타이머는 다시 원래 값으로 돌아가 카운트를 진행한다. 이와 같은 방식으로 두 종단 간에 Keepalive를 확인하면서 양쪽의 세션이 끊기지 않고 유지된다.

 

netstat을 사용해서 타이머를 확인해보자.

root@worker1:~# netstat -napo
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name     Timer
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN      522374/cupsd         off (0.00/0/0)
tcp        0      0 127.0.0.1:37237         0.0.0.0:*               LISTEN      890/kubelet          off (0.00/0/0)

맨 마지막 열에 Timer를 통해 현재 소켓에 설정된 타이머 값을 볼 수 있다.

여기서는 타이머가 모두 off 상태이지만, TIME_WAIT, FIN_WAIT 타이머 등을 확인할 수 있으며 연결된 ESABLISHED 상태의 소켓에서는 Keepalive 타이머를 확인할 수 있다.

 

TCP Keepalive의 파라미터들

net.ipv4.tcp_keepalive_time

keepalive 소켓의 유지 시간

타이머는 이 시간을 기준으로 동작한다.

 

net.ipv4.tcp_keepalive_probes

keepalive 패킷을 보낸 최대 전송 횟수

예를 들어 3으로 설정했다면 최초의 keepalive 패킷을 포함하여 3번의 패킷을 보내고 그 후에도 응답이 없으면 연결을 끊는다.

 

net.ipv4.tcp_keepalive_intvl

재전송 패킷을 보내는 주기

keepalive 확인 패킷에 응답이 없으면 몇 초 후에 재전송 패킷을 보낼 것인지 그 값을 정의한다.

 

⇒ net.ipv4.tcp_keepalive_time초 동안 기다린 후 keepalive 확인 패킷을 보내고, 이에 대한 응답이 오지 않는다면 net.ipv4.tcp_keepalive_intvl 간격으로 net.ipv4.tcp_keepalive_probes번의 패킷을 더 보낸다.

 

TCP Keepalive와 좀비 커넥션

불필요한 TCP Handshake를 줄일 수 있어 전체적으로 서비스의 품질을 높일 수 있다.

하지만 가장 큰 효과는 잘못된 커넥션 유지(좀비 커넥션) 소켓을 방지하는 기능이다.

 

DB 서버 한 대와 애플리케이션 서버 한 대가 있다.

  1. DB 서버와 애플리케이션 서버 연결
  2. DB 서버에서 mysqld를 종료
    → 클라이언트의 소켓 상태가 ESTABLISHED 상태에서 CLOSE_WAIT 상태가 된다.
  3. 다시 한 번 두 서버를 연결
  4. iptables를 이용해서 DB 서버에서 애플리케이션으로 가는 모든 패킷을 DROP
    iptables -A OUTPUT -p tcp -d 10.10.10.10 -j DROP
  5. DB 서버 종료
  6. 애플리케이션 서버에서 DB 서버와의 소켓 상태 확인
    → CLOSE_WAIT 상태가 아닌 ESTABLISHED 상태가 유지된다. DB 서버에서 설정된 iptables로 인해 FIN 패킷을 받지 못해서, 클라이언트 입장에서는 DB 서버와의 연결이 끊어졌는지 알 방법이 없다.

하지만 keepalive 옵션을 활용하면 keepalive 타이머에 설정된 일정 시간이 지나면 keepalive 패킷에 대한 응답을 못 받았기 때문에 소켓이 종료된다.

 

TCP Keepalive와 HTTP Keepalive

TCP Keepalive는 두 종단 간의 연결을 유지하기 위함

HTTP Keepalive는 최대한 연결을 유지하기 위함

 

ex. 60으로 설정

TCP Keepalive는 60초 간격으로 연결이 유지되는지를 확인

HTTP Keepalive는 60초 동안 유지, 60초가 지난 후 요청이 없다면 연결 종료

 

두 값이 서로 다를 때

 

TCP Keepalive < HTTP Keepalive

TCP Keepalive를 30초, Apache Keepalive를 60초로 설정

→ 30초가 지나면 keepalive timer가 다시 30초부터 시작해서 동작한다. 하지만 2번째 타이머까지 종료가 되면 Apache Keepalive Timeout에 의해 서버가 먼저 클라이언트와의 연결을 종료한다.

 

TCP Keepalive > HTTP Keepalive

TCP Keepalive를 120초, Apache Keepalive를 60초로 설정

→ 서서히 줄어들다가 Apache Keepalive Timeout에 지정한 60초가 지나면 연결이 끊어진다.

 

  TCP Keepalive가 설정되어 있어도 HTTP Keepalive가 설정되어 있다면 이에 따라 동작한다.

 

로드 밸런서

로드 밸런서를 사용하는 환경에서 이슈가 생길 수 있다.

클라이언트에서는 간헐적으로 타임아웃이 발생하며, 서버에서는 사용하지 않는 것으로 보이는 소켓이 ESTABLISHED 상태로 유지되고 있다. 이를 TCPDUMP로 확인해보면 타임아웃이 발생하는 순간에 클라이언트에서 서버로 발송한 패킷들에 대해 RST로 응답 패킷이 왔다.

이러한 이슈의 원인은 로드 밸런서의 Idle timeout 때문이다.

 

로드 밸런서 환경에서 통신이 이루어지는 과정이다.

DSR(Direct Server Return) 구조이기 때문에 클라이언트에서 서버로 보내는 요청은 로드 밸런서를, 서버가 클라이언트로 응답을 보낼 때는 직접 보낸다.

로드 밸런서는 클라이언트와 서버 간 TCP Handshake를 끝내고 정상적으로 맺어진 세션들을 세션 테이블에 저장한다. 그리고 Idle timeout 기능을 통해서 일정 시간 동안 사용되지 않은 세션을 세션 테이블에서 정리한다. 두 종단에 세션이 지워졌음을 알리는 역할은 하지 않아 문제가 된다.

 

처음 연결하는 과정이다.

  1. 클라이언트는 MQ를 사용하기 위해 10.10.10.100:5678로 SYN 패킷을 보낸다.
  2. 로드 밸런서는 클라이언트 패킷을 확인한 후 기존 세션 정보가 있는지 확인한다. 정보가 없다면 어떤 서버에 연결 요청을 전달할 것인지를 결정한다. 그리고 이를 세션 테이블에 기록한다.
  3. 서버는 SYN 패킷을 받았기에 SYN+ACK를 클라이언트로 직접 보낸다.
  4. 클라이언트는 SYN+ACK의 응답으로 ACK를 보내고 이를 로드 밸런서가 받아 서버에 전달한다.

 

여기까진 문제가 되지 않는다.

하지만 일정 시간 이상으로 패킷이 흐르지 않는 세션에 대해서는 Idle timeout으로 세션 테이블에서 삭제한다.

  1. 클라이언트는 로드 밸런서에서 세션 테이블이 지워진 상태에서도 로컬 포트 1234를 통해 요청을 보낸다.
  2. 로드 밸런서는 세션 테이블에서 연결 정보를 찾을 수 없고 랜덤하게 서버에 요청을 전달한다. 이는 기존 서버로 전달할 수도 있고 다른 서버로 전달할 수도 있다.
  3. 10.10.10.12는 비정상적인 패킷으로 판단하고 RST 패킷을 보낸다.

 

이렇게 되면 클라이언트는 TCP Handshake를 맺고 다시 요청을 보내야 한다. 이때 소요되는 시간이 애플리케이션에서 설정한 타임아웃 임계치를 넘어가게 되면서 Timeout Exception을 경험하게 된다.

 

정리

  • TCP Keepalive는 커널 레벨에서 종단 간의 세션을 유지시켜 주는 기능을 한다.
  • TCP Keepalive 설정을 이용하면 연결이 끊어졌는데도 FIN 패킷을 받지 못해 정리되지 않고 남아있는 좀비 커넥션을 없앨 수 있다.
  • HTTP Keepalive가 설정되어 있다면 TCP Keepalive 설정 값과 다르다고 하더라도 의도한 대로 정상적으로 동작한다.
  • 로드 밸런서를 사용하는 환경에서 TCP 기반의 서비스를 하는 경우에는 반드시 TCP Keepalive를 설정해야 한다.

 

 

 

<참고>

DevOps와 SE를 위한 리눅스 커널 이야기

 

 

'Study > Linux' 카테고리의 다른 글

[Linux]TCP 재전송  (1) 2024.04.25
[Linux]TIME_WAIT 소켓  (0) 2024.04.22
[Linux]NUMA  (1) 2024.04.18
[Linux]swap  (0) 2024.04.14
[Linux]free 명령과 메모리  (0) 2024.04.10