개요?

최근에 프로젝트를 진행하면서, 리눅스 커널단의 설정에 의해 이슈가 발생하였다.
그래서 이번 기회에 TCP_TW 설정에 대해 알아보고자 한다.

이전에 이슈가 된 설정은 tcp_tw_reuse, tcp_tw_recycle 옵션이었는데, 이 옵션들은 TIME_WAIT 상태의 소켓을 재사용하는 옵션이다.

TIME_WAIT?

TIME_WAIT 상태는 TCP 연결이 종료된 후, 일정 시간 동안 유지되는 상태이다.

이슈

최근 azure 의 AKS를 세팅하면서 외부 요청에 대한 타임아웃이 발생하는 이슈가 있었다.

환경

  • AKS에 하나의 파드에 두개의 컨테이너를 띄운 상태
  • NAT 게이트웨이를 사용

이슈 시나리오

  1. A컨테이너에서만 외부API를 호출하면 바로바로 응답이 옴
  2. B컨테이너에서만 외부API를 호출하면 바로바로 응답이 옴
  3. A컨테이너에서 외부API호출 후 바로 B컨테이너에서 외부API를 호출하면 타임아웃이 발생함

AKS초기 구성시에는 해당 현상이 발생하지 않았었다. 그런데, 어느 순간 해당 현상이 발생하기 시작했다.

서버 구축을 해주던 인프라팀에서 “tcp_tw_reuse, tcp_tw_recycle” 에 대한 세팅 이슈때문에 발생한것이라고 했다.

참고 블로그 해당 블로그 내용에서 NAT 를 사용할때 tcp_tw_recycle 을 활성화한 서버에 접속할경우 해당 이슈가 발생한다고 했다.
그렇다, AKS 초기 세팅에서는 발생하지 않았지만 NAT를 연결한 순간 발생한것으로 추측이된다.

재현해보자

Client / Server 띄우기

  • 도커로 Client 와 Server 를 구축해보자

# 네트워크 생성
docker network create tw-net

# 서버
docker run -d --name server_test --net tw-net -p 91:80 -it --privileged ubuntu:20.04
apt update
apt install nginx
service nginx start

# 클라이언트
docker run -d --name client_test --net tw-net -p 92:80 -it --privileged ubuntu:20.04

# 필요한 라이브러리 설치
apt-get install net-tools
apt-get install vim

Client 에서 tcp_tw_reuse 활성화하기

  1. Client -> Server 호출해보기
  • Docker 네트워크 상에서 Server IP를 확인하자.
$ docker network inspect tw-net
...

"Containers": {
    "4e879dc9a3e1ced91553d6464d98361ab639bb229d21780234f254c9ab2e8763": {
        "Name": "client_test",
        "EndpointID": "aff17140447ea02482cf5dd79ee032bfc26ce5d4bfbd643396b71a354a3e2008",
        "MacAddress": "02:42:ac:14:00:03",
        "IPv4Address": "172.20.0.3/16",
        "IPv6Address": ""
    },
    "911846c368f7419762ce537c1e002b5626b2f7a3953eec0addc945fac9914a6a": {
        "Name": "server_test",
        "EndpointID": "b0f83e1544bd7d1d7db17759324ea4b7018913c977e15f6b7fcd734bc1f54a41",
        "MacAddress": "02:42:ac:14:00:02",
        "IPv4Address": "172.20.0.2/16", <============ Server IP
        "IPv6Address": ""
    }
},
...
  1. 호출 해보자
root@4e879dc9a3e1:/# curl 172.20.0.2
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html> 

정상적으로 호출되는것을 알 수 있다.

  1. 계속 호출해서 TIME_WAIT를 확인해보자
# 4번 호출후 확인 
root@4e879dc9a3e1:/# netstat -n -t | grep 'TIME_WAIT'
tcp        0      0 172.20.0.3:42604        172.20.0.2:80           TIME_WAIT
tcp        0      0 172.20.0.3:42614        172.20.0.2:80           TIME_WAIT
tcp        0      0 172.20.0.3:42640        172.20.0.2:80           TIME_WAIT
tcp        0      0 172.20.0.3:42630        172.20.0.2:80           TIME_WAIT

각각의 다른 포트로 호출되는것을 확인할 수 있다.

그렇다면 포트를 하나로 줄이면 어떻게 될까? (포트를 고갈시켜보자)

  1. 포트를 하나로 줄여서 호출해보자
# 설정 파일
vim /etc/sysctl.conf

# 하단에 추가 
net.ipv4.ip_local_port_range = 32768 32768

# 변경 내용 적용
sysctl -p

# 호출하기
root@4e879dc9a3e1:/# curl 172.20.0.2
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

root@4e879dc9a3e1:/# curl 172.20.0.2
curl: (7) Couldn't connect to server
 
root@4e879dc9a3e1:/# netstat -n -t | grep 'TIME_WAIT'
tcp        0      0 172.20.0.3:32768        172.20.0.2:80           TIME_WAIT
  • 두번째 호출에서 curl: (7) Couldn't connect to server 발생한다.
  1. 그렇다면, 이때 tcp_tw_reuse 를 활성화하면 어떻게 될까?

# 설정 파일
vim /etc/sysctl.conf

# 하단에 추가
net.ipv4.tcp_tw_reuse = 1

# 변경 내용 적용
sysctl -p 

# 여러번 호출하기
curl 172.20.0.2 

- 정상 호출됨

결론

  • tcp_tw_reuse 를 활성화하면, TIME_WAIT 상태의 포트를 재사용할 수 있다.
  • 자세한 내용은 https://cyuu.tistory.com/139 여기를 참고

tcp_tw_recycle!?

  • tcp_tw_recycle 은 Linux 4.12 on 2017 이후 제거 되었다고 한다 (참고)
  • docker ubuntu 20.04 에서는 tcp_tw_recycle 설정이 없다. 그래서 다른 블로그의 테스트를 참고해보길 바란다.

회고

처음에 해당 현상이 발생했을때, azure의 네트워크 이슈인지 알았다.
나는 어플리케이션 레벨에서 이슈를 찾을려고 했다. 당연히 찾을 수 가 없었다.
이번의 계기로 다양한 범위에서 이슈를 찾기위한 노력이 필요하다는것을 알게 되었다.

참고