재기동하려는 데몬이 "이미 사용 중인 주소" 라며 뜨지 않는다. 그 포트를 잡고 있는 것은 다른 서비스가 아니라 아웃바운드 연결을 열면서 커널이 임시로 배정한 포트다. 클러스터 노드처럼 나가는 연결이 많은 서버에서 간헐적으로 생기고, 재현이 잘 안 돼 원인 찾기가 어렵다.
클라이언트 쪽 연결에는 출발 포트가 필요하고, 커널은 다음 범위에서 골라 준다.
sysctl net.ipv4.ip_local_port_range
# net.ipv4.ip_local_port_range = 32768 60999
문제는 데몬이 듣는 포트가 이 범위 안에 들어 있을 때다. 예를 들어 어떤 서비스가 39000 번을 쓰는데 그 서비스가 내려간 사이에 다른 프로세스가 아웃바운드 연결을 열면서 39000 을 배정받으면, 서비스가 다시 뜰 때 바인드에 실패한다.
net.ipv4.ip_local_reserved_ports 는 커널이 임시 포트로 고르지 않을 번호를 지정한다. 이 목록에 넣은 포트는 애플리케이션이 명시적으로 바인드할 때만 쓰인다.
지금 값을 본다.
sysctl net.ipv4.ip_local_reserved_ports
즉시 적용한다. 목록은 쉼표로 나누고 범위는 하이픈으로 적는다.
sudo sysctl -w net.ipv4.ip_local_reserved_ports="8020,8032,9083,10000-10002,39000"
재부팅 후에도 남기려면 파일로 둔다.
# /etc/sysctl.d/90-reserved-ports.conf
net.ipv4.ip_local_reserved_ports = 8020,8032,9083,10000-10002,39000
sudo sysctl --system
sysctl net.ipv4.ip_local_reserved_ports
임시 포트 범위 안에 있는 서비스 포트만 넣으면 된다. 1024 미만이나 32768 미만의 포트는 애초에 임시 포트로 뽑히지 않으므로 넣을 이유가 없다.
지금 열려 있는 수신 포트를 뽑아 범위와 대조한다.
ss -lntup | awk 'NR>1 {split($5,a,":"); print a[length(a)]}' | sort -n | uniq
하둡 계열은 임시 포트 범위와 겹치는 번호를 여럿 쓴다. Kudu · Impala · HBase · Ozone 처럼 포트를 여러 개 쓰는 서비스는 설치 문서의 포트 목록을 그대로 옮겨 오는 편이 빠르다.
임시 포트 범위 자체를 올려 겹치지 않게 할 수도 있다.
net.ipv4.ip_local_port_range = 40000 60999
이 방법은 간단하지만 쓸 수 있는 출발 포트 수가 줄어 동시 연결이 많은 서버에서는 불리하다. 서비스 포트가 흩어져 있으면 예약 목록 쪽이 낫다.
애플리케이션에서 SO_REUSEADDR 을 켜는 것은 다른 문제(TIME_WAIT 상태의 자기 포트 재사용)를 푸는 수단이고, 남이 점유한 포트를 뺏어 오지는 못한다.
예약이 먹었는지 보려면 나가는 연결을 많이 만들어 놓고 해당 포트가 출발 포트로 잡히는지 본다.
ss -ntu state all | awk '{print $5}' | grep -c ':39000$'
바인드 실패가 다시 났을 때는 누가 잡고 있었는지 즉시 확인한다.
ss -lntup 'sport = :39000'
lsof -nP -iTCP:39000
이 설정은 IPv4 쪽 이름이지만 IPv6 에도 적용된다. 별도 항목이 없다.
예약 목록이 임시 포트 범위를 다 덮어 버리면 아웃바운드 연결을 못 연다. 목록을 넓게 잡지 않는다.
이미 그 포트를 잡고 있는 연결을 끊어 주지는 않는다. 설정은 앞으로의 배정에만 영향을 준다. 지금 충돌 중이라면 해당 연결이 끝나기를 기다리거나 프로세스를 정리해야 한다.