-
Linux TIME_WAIT 소켓 정리커널(Kernel) 2022. 12. 11. 16:50
환경 정보
OS: ubuntu18.04
이번 포스팅은 4-way handshake에서 TIME_WAIT 소켓을 어떻게 관리하면 좋을지에 대한 내용들을 다룬다.
TCP 통신 과정
tcpdump를 통해서 client(192.168.9.194) => nginx(192.168.9.75:80)로 가는 패킷을 dump한다.
tcpdump -i any port 80 -A -nn -w nginx.pcap
curl 192.168.9.75
Wireshark를 이용하면, 방금 nginx로 보낸 요청이 tcp 3-way handshake, http get 요청, 4-way handshake라는 단계로 이루어져 있음을 확인할 수 있다.
일단 대략적인 흐름은 다음과 같다.
3-way handshake
1. [클라이언트 => nginx] SYN 패킷 전송
2. [nginx => 클라이언트] 응답으로 SYN, ACK 패킷 전송
3. [클라이언트 => nginx] 응답으로 ACK 패킷 전송
HTTP GET
4. [클라이언트 => nginx] HTTP 요청 전송
4-way handshake
5. [nginx => 클라이언트] 연결을 끊기 위해 FIN 패킷 전송
6. [클라이언트 => nginx] 응답으로 ACK 패킷 전송
7. [클라이언트 => nginx] 자신이 사용한 소켓을 정리 후, FIN 패킷 전송
8. [nginx => 클라이언트] 응답으로 ACK 패킷 전송
TIME_WAIT 소켓
TIME_WAIT 소켓은 커넥션 종료 이후 발생할 수 있는 문제점을 방지하기 위해서 존재한다.
예를 들면, 네트워크 장애로 인해 연결을 끊는 과정에서 패킷이 유실될 수 있는데
이러한 문제 때문에 일정시간 동안 TIME_WAIT 소켓을 만들어 재전송을 통해서 문제를 해결할 수 있다.
TIME_WAIT 소켓은 active closer(연결을 먼저 끊는)에서 생성된다.
클라이언트, 서버 모두 연결을 먼저 끊을 수 있기 때문에 클라이언트와 서버에서 TIME_WAIT 소켓이 만들어질 수 있다.
TIME_WAIT 소켓 확인하기 client (192.168.9.194) => server (192.168.9.75:80)
telnet으로 접속했을 때
telnet 연결을 끊었을 때
위의 timewait 소켓은 약 54초 뒤에 사라진다.
클라이언트 입장에서의 TIME_WAIT 소켓
TIME_WAIT 소켓은 앞서 클라이언트와 서버 모두에서 생성될 수 있다고 했다.
그렇다면 클라이언트에서 TIME_WAIT 소켓이 생기는 경우는 어떤 경우가 될 수 있을까
예를 들어보면, 백엔드 api 서버가 db와 커넥션을 맺고 있을 때, 백엔드 api 서버는 db에 대한 클라이언트가 된다.
이때, 백엔드 api 서버가 db와의 커넥션을 먼저 끊는다면, 백엔드 api 서버에서 TIME_WAIT 소켓이 생길 수 있다.
클라이언트 입장에서 TIME_WAIT 소켓이 많아지게 되면, 로컬 포트가 고갈되어 애플리케이션 타임 아웃이 발생할 수 있다.
로컬 포트 고갈을 방지하기 위해 다음과 같은 방법을 사용할 수 있다.
1. net.ipv4.tcp_tw_reuse 커널 파라미터 사용하기
일반적으로 TIME_WAIT 상태의 소켓은 다시 사용할 수 없는데
net.ipv4.tcp_tw_reuse 커널 파라미터를 이용하면 TIME_WAIT 소켓을 재사용할 수 있다.
net.ipv4.tcp_tw_reuse를 1로 설정하면 TIME_WAIT 소켓을 재사용해서 외부로 요청을 보낼 수 있게 해 준다.
2. connection pool 방식
커넥션을 맺을 때마다 TCP 연결/끊기를 하는 방식은 애플리케이션 응답 시간을 느려지게 하고, 커널 개입을 늘리면서 리소스 낭비를 발생시킨다.
커넥션 풀 방식은 연결을 끊지 않고, 미리 소켓을 열어둔다.
미리 열어둔 소켓을 통해 요청을 처리하게 되고 보다 애플리케이션의 응답속도를 높일 수 있다.
또한 커넥션 풀 방식을 통해서 로컬 포트의 범위를 지정함으로써 무분별한 사용을 막을 수 있다.
참고 1. 클라이언트가 소켓을 할당받는 과정
1. [애플리케이션 => 커널] 서버와 통신할 때, 사용할 포트를 요청
2. [커널 => 애플리케이션] 가용한 로컬 포트를 찾아서 할당
3. [애플리케이션 => 커널] 목적지 IP, 목적지 포트를 주고 할당받은 로컬 포트로 소켓 생성 요청
[출발지 포트, 출발지 IP, 목적지 포트, 목적지 IP] 쌍을 가진 소켓은 커널에 유일하게 존재한다.
4. [커널 => 애플리케이션] 소켓에 접근할 때 사용할 FD 제공
참고 2. net.ipv4.ip_local_port_range 커널 파라미터
net.ipv4.ip_local_port_range 커널 파라미터를 통해 로컬 포트의 범위를 지정할 수 있다.
서버 입장에서의 TIME_WAIT 소켓
서버는 소켓을 열어놓고 요청을 받아들이는 입장이기 때문에 로컬 포트 고갈과 같은 문제는 일어나지 않는다.
하지만 클라이언트와 마찬가지로 TIME_WAIT 소켓이 많으면 불필요한 연결 끊기/맺기가 반복될 수 있다.
서버 입장에서는 다음 방법들을 통해 TIME_WAIT 소켓을 줄인다.
1. net.ipv4.tw_recycle 커널 파라미터(추천하지 않음)
서버 입장에서 TIME_WAIT 상태의 소켓을 빠르게 회수하고 재활용할 수 있게 해주는 커널 파라미터다.
이 커널 파라미터를 사용하면 TIME_WAIT 소켓을 없애는 것이 아니라
TIME_WAIT의 timeout을 ms 단위로 변경하여 TIME_WAIT 소켓이 생겼다가 바로 사라진다.
하지만 이 방식은 다음과 같은 이유 때문에 추천하지 않는다고 한다.
두 클라이언트가 동일한 nat를 사용할 경우, 서버 입장에서는 같은 IP를 달고 오기 때문에 출발지 포트로 구분해야 한다.
한 클라이언트의 연결이 끊어진 후, 다른 클라이언트 간의 timestamp가 끊어진 클라이언트의 timestamp보다 작게 되면
패킷을 드랍하여 문제가 발생할 수 있기 때문이다.
2. Keepalive
nginx 서버의 keepalive_timeout을 0으로 설정한 후에 telnet으로 요청을 1개 보냈을 때
nginx는 1개의 요청에 대한 응답을 보낸 후, FIN 패킷을 내려준다.
클라이언트가 한 개의 요청을 보냈을 때 위와 같이 HTTP 헤더에 connection: close라는 값과 함께 연결이 종료된다.
즉, 만약 10번 요청을 하게 된다면 TIME_WAIT 소켓이 10개 생성된다.
keepalive를 이용하면 하나의 세션을 연결해놓은 후, 그 연결을 유지하면서 여러 개의 요청을 처리하는 것이 가능해진다.
여전히 TIME_WAIT 소켓이 생기기는 하지만 리소스 적인 측면에서 경제적이고, 애플리케이션 응답 속도를 높일 수 있다.
nginx keepalive_timeout=60인 경우, 클라이언트는 하나의 세션에서 요청을 여러 번 보낼 수 있다.
60초 동안 요청이 발생하지 않는다면, nginx는 연결을 끊는다.
즉, keepalive를 이용해서 하나의 세션에 10번 요청을 하게 되었을 때, 서버에서는 1개의 TIME_WAIT 만 남길 수 있다.
정리
1. 클라이언트 입장에서 TIME_WAIT 소켓 발생하는 경우
문제: 로컬 포트가 부족할 수 있다.
해결: reuse 커널 파라미터를 통해서 소켓을 재사용하는 방법이 있지만 TCP 맺음/끊기는 반복적으로 일어날 수 있다. 따라서 커넥션 풀을 사용해서 TIME_WAIT 줄이자.
2. 서버 입장에서 TIME_WAIT 소켓 발생
문제: 로컬 포트 문제는 없지만, 불필요한 연결/끊기로 인한 많은 리소스 사용한다.
해결: recycle 파라미터를 사용할 수 있지만, 권하지 않는 방법이며 keepalive를 통해 TIME_WAIT를 줄이자.
Reference
Devops와 SE를 위한 리눅스 커널 이야기
반응형'커널(Kernel)' 카테고리의 다른 글
Linux TCP 재전송과 타임 아웃 (0) 2022.12.18 Linux TCP Keepalive 정리 (0) 2022.12.11 Linux NUMA 아키텍처 정리 (0) 2022.12.05 Linux Swap 영역 정리 (0) 2022.12.04 Linux 메모리 정리(buffer, cache, Active/Inactive, slab) (0) 2022.11.28