LabHub

리눅스 네트워크 진단 · 소켓과 포트 · 이론

127.0.0.1 에 묶으면 밖에서 못 들어온다

LabHub 에서 이어서 보기

한 줄 요약

포트가 열려 있느냐는 질문은 사실 두 가지다. 누가 듣고 있느냐어느 주소에서 듣고 있느냐. 서버 안에서 curl localhost 는 되는데 밖에서는 안 되는 사고의 대부분이 두 번째 질문에서 갈린다.

왜 이게 필요했나

애플리케이션을 배포했다. 서버에 들어가 curl http://localhost:8080/health 를 치면 200 이 온다. 그런데 로드밸런서는 계속 헬스체크 실패를 보고한다. 방화벽을 확인해도 8080 은 열려 있다.

ss -ltn 을 쳐 보면 답이 나온다.

LISTEN  0  5  127.0.0.1:8080  0.0.0.0:*

127.0.0.1:8080 - 루프백 주소에만 묶여 있다. 이 소켓은 같은 호스트 안에서 온 연결만 받는다. 밖에서 오는 패킷은 커널이 아예 전달하지 않는다. 방화벽 문제가 아니라 바인딩 문제다.

이 구분은 보안 설계이기도 하다. 관리용 포트나 디버그 엔드포인트는 일부러 루프백에만 묶어 외부 노출을 원천 차단한다. 반대로 서비스 포트를 실수로 루프백에 묶으면 위 상황이 된다.

어떻게 동작하나

ss 는 netstat 의 후계자이고 최신 배포판의 표준이다. 자주 쓰는 조합은 네 개다.

ss -ltn          # TCP 리스닝 소켓만, 이름 해석 없이ss -ltnp         # + 어느 프로세스가 잡고 있는지 (root 필요)ss -tan state establishedss -s            # 상태별 요약

리스닝 소켓의 Recv-Q 와 Send-Q 는 뜻이 다르다. 확립된 연결에서는 각각 "아직 읽히지 않은 수신 데이터"와 "아직 확인되지 않은 송신 데이터"지만, 리스닝 소켓에서는 Recv-Q 가 accept 를 기다리는 완료 큐의 현재 길이, Send-Q 가 백로그 최대치다. 그래서 리스닝 소켓의 Recv-Q 가 계속 차 있으면 애플리케이션이 accept 를 못 따라가고 있다는 뜻이고, 워커 수나 백로그를 손봐야 한다.

소켓 상태에서 실무적으로 중요한 것은 두 가지다.

이 둘을 혼동해 net.ipv4.tcp_tw_reuse 같은 파라미터를 만지는 일이 흔한데, CLOSE-WAIT 는 커널 파라미터로 해결되지 않는다.

/proc/net/tcp 를 읽을 줄 알면 도구가 없는 환경에서도 진단할 수 있다. 주소와 포트가 16진수로 적혀 있고(8080 은 1F90), 상태도 16진수 코드다(0A = LISTEN, 01 = ESTABLISHED, 06 = TIME_WAIT, 08 = CLOSE_WAIT).

현장에서 만나는 모습

"Address already in use". 포트를 이미 누군가 잡고 있다. ss -ltnp 로 PID 를 확인하는 것이 첫 동작이고, 대개는 이전 프로세스가 완전히 종료되지 않은 경우다. 여기서 무작정 kill -9 를 하기 전에 그 프로세스가 무엇인지 확인해야 한다.

소켓도 fd 다. 앞 코스에서 본 그대로다. 연결이 늘면 fd 도 늘고, fd 한도에 먼저 부딪힐 수 있다. ls /proc/PID/fd 를 readlink 하면 socket:[12345] 형태로 드러난다.

컨테이너 안에서는 네임스페이스가 다르다. 컨테이너 안의 ss 는 그 네임스페이스의 소켓만 보여 준다. 호스트에서 보이는 포트와 컨테이너 안에서 보이는 포트가 다른 것이 정상이다.

다음 실습에서 할 것

로컬에 HTTP 서버 두 개를 서로 다른 주소에 묶어 띄우고, ss 로 바인딩 범위가 어떻게 다른지 확인한다. 연결을 하나 붙잡아 ESTABLISHED 를 관찰하고, /proc/net/tcp 에서 16진수 포트를 직접 찾아본다. 리스닝 소켓의 백로그 값을 읽고, 마지막에는 "이 포트를 누가 잡고 있는가"에 답하는 스크립트를 만든다.