로드밸런서 셋팅 후 연결 지연 현상

ALB 연결 후 새 창에서 발생하는 21초 대기 시간의 원인과 해결 과정 기록.

Jun Noh

이제 완벽했다고 생각했다. 로드밸런서가 앞단에서 든든하게 SSL 인증서로 HTTPS 요청을 모두 쳐내주고, 뒤에 숨은 인스턴스들도 문제없이 트래픽을 주고받고 있었다.

‘아, 도움 많이 될꺼야~‘하면서 커피 한 잔을 하려는데… 뭔가 이상했다.

가끔 페이지에 접속하면 웹 요청이 바보처럼 느렸다.

웃긴 건 첫 연결만 더럽게 느리고, 그 다음부터는 언제 그랬냐는 듯 쌩쌩하게 잘 돌아간다는 거다.

혹시나 해서 새 시크릿 탭을 열고 접속을 시도해 보니, 화면이 하얗게 굳은 채로 한참을 멍 때리다가 뒤늦게 확 뜨는 기이한 현상이 반복됐다.

증상

  • 특정 조건에서만 무한 로딩: 새 기기, 시크릿 창, 또는 쿠키를 날리고 ‘처음’ 접속할 때만 대기 시간이 발생한다.
  • 정확한 지연 시간: 대충 느린 게 아니라, 정확히 21초(또는 1분) 동안 브라우저가 하얀 화면에 멈춰 있다.
  • 유령 같은 패킷: 접속이 뚫리고 나면(TCP Keep-Alive 덕분에) 0.1초 만에 로딩이 된다. 지연되는 그 20여 초 동안 Nginx 에러 로그는 피라미드처럼 고요하고, 서버에 tcpdump를 켜놔도 패킷 자체가 아예 들어오지 않는다.

삽질

“패킷이 인스턴스에 도달조차 하지 않는다”는 사실을 바탕으로, ALB와 EC2 인스턴스 사이의 구간을 범인으로 확신하고 모든 계층을 미친 듯이 쑤시기 시작했다.

1. [Network Layer] MTU 파편화 및 PMTUD 의심

가장 먼저 의심한 건 패킷 크기였다. AWS 프라이빗 서브넷은 보통 Jumbo Frames(9001)를 쓰는데, 외부 망이나 NAT 인스턴스가 이걸 수용하지 못해(1500) 덩치 큰 인증서 뭉치가 도중에 드랍되는 게 아닐까 싶었다.

[조치] 서버의 MTU를 1500으로 강제 고정하고, ALB 보안 그룹에 ICMP 패킷을 허용해서 Path MTU Discovery가 동작하게 만들었다.

# Ubuntu netplan에서 MTU 고정
sudo vi /etc/netplan/50-cloud-init.yaml

network:
    version: 2
    ethernets:
        eth0:
            dhcp4: true
            mtu: 1500  # 추가

sudo netplan apply

결과: 여전히 21초 지연. (실패)

2. [Application Layer] Nginx DNS 조회 병목 비활성화

Nginx 로그가 안 찍히는 게 맘에 걸렸다. 혹시 Nginx가 클라이언트 요청을 받고 백엔드(Next.js)로 넘기기도 전에 localhost 도메인을 해석(DNS Lookup)하느라 혼자 멈춰 있는 건가?

[조치] Nginx 설정에서 불필요한 DNS 서칭을 아예 낭심 차듯 차단하고, 타임아웃을 극단적으로 줄였다. localhost라는 이름 대신 127.0.0.1(또는 Docker 내부망 IP)을 직접 하드코딩해 박아버렸다.

server {
    listen 80;
    server_name example.com;

    # 외부 DNS 찌르다 멈추는 현상 방지 (5초 타임아웃)
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    location / {
        # localhost 대신 IP를 직접 명시하여 DNS Lookup 원천 차단
        proxy_pass http://127.0.0.1:3000; 
        
        # ALB가 60초간 대기하는 것을 막기 위해 프록시 타임아웃 단축
        proxy_connect_timeout 5s;
        proxy_send_timeout 10s;
        proxy_read_timeout 10s;
        
        # ... 기타 proxy_set_header 생략
    }
}

결과: 장엄하게 Nginx 리로드 후 접속해 보았으나 변함없이 21초 벽에 부딪힘. (실패)

3. [Kernel Layer] TCP Timestamps (PAWS) 충돌 제거

ALB를 거쳐 들어오는 패킷들의 타임스탬프가 꼬여서, 리눅스 커널 형님이 이걸 보안 위협으로 간주하고 패킷을 조용히 버리는(PAWS) 현상일 수도 않겠는가. 커널 레벨까지 의심이 뻗었다.

[조치] 서버의 커널 파라미터를 건드려 TCP 타임스탬프 기능을 아예 꺼버렸다.

# TCP Timestamps 비활성화
sudo sysctl -w net.ipv4.tcp_timestamps=0

# 영구 반영
echo "net.ipv4.tcp_timestamps=0" | sudo tee -a /etc/sysctl.conf

결과: 커널까지 후벼팠건만 끄떡없는 21초. 이쯤 되니 무서워진다. (실패)

이 외에도 대상 그룹의 좀비 인스턴스 청소, Client IP Preservation(클라이언트 IP 보존) 끄기, ALB의 HTTP/2 오프 등 내가 아는 모든 인프라 튜닝 버튼은 다 눌러봤지만 결과는 처참했다.

결정적 단서

서버 내부의 문제가 아니라는 확신(과 체념)이 섰다. 이제 남은 용의자는 단 하나, ‘내 로컬 PC ↔ ALB’ 구간뿐이었다.

CMD을 열고, 먼저 curl -Iv https://patchnote.earth 명령어를 쳤을 때 DNS가 반환해 준 ALB의 실제 IP 두 개를 curl로 직접 찔러보았다.

C:\> curl.exe -Iv https://52.78.131.250 -H "Host: patchnote.earth"
* Trying 52.78.131.250:443...
* Connected! (0.1초 만에 즉시 성공)

C:\> curl.exe -Iv https://15.165.215.3 -H "Host: patchnote.earth"
* Trying 15.165.215.3:443...
* Failed to connect to 15.165.215.3 port 443 after 21040 ms: Could not connect to server

“21040 ms” (약 21초). 아. 찾았다.

21초는 네트워크 공학에서 OS(운영체제)가 TCP SYN 패킷을 보냈는데 응답이 없을 때 재전송을 완전히 포기하고 다음 IP로 넘어가는(Fallback) 아주 정확한 타임아웃 규격 시간이었다.

새 브라우저가 도메인에 접속하려고 할 때, 하필 먹통인 두 번째 IP(15.165.215.3)를 먼저 할당받아버리면 21초 동안 벽에다 패킷을 던지며 허송세월을 보낸 것이다.

그러다 21초 뒤에 “아, 이 길은 죽었네!” 하고 포기한 뒤, 첫 번째 정상 IP로 재시도해서 접속이 뚫렸던 거다.

정답이다. 연금술사.

결론적으로, 로드밸런서가 물고 있는 IP 중 하나가 아예 응답이 없는 ‘블랙홀’ 상태였다. 원인은 어이없을 만큼 단순했다.

ALB는 고가용성(버티기)을 위해 무조건 2개 이상의 가용 영역(AZ) 서브넷에 연결되어야 한다.

그런데 ALB가 세팅된 그 2개의 서브넷 중 하나가 하필 ‘라우팅 테이블에 인터넷 게이트웨이(IGW)가 연결되지 않은 프라이빗 서브넷’이었던 것이다.

외부 인터넷 망에서는 죽었다 깨어나도 찾아갈 수가 없는 시체 IP를, ALB는 해맑게 클라이언트에게 쥐여주고 있었던 거다.

[최종 해결책]

  1. AWS 콘솔에서 VPC -> 서브넷 메뉴로 들어간다.
  2. ALB와 연결된 두 개의 서브넷 중에서 라우팅 테이블이 꼬여있는(먹통인 애) 서브넷을 색출해낸다.
  3. 해당 서브넷의 라우팅 테이블0.0.0.0/0 -> igw-xxxx(인터넷 게이트웨이) 경로를 딱 한 줄 추가해 줬다.

원인을 찾는 데는 반나절을 넘게 썼는데, 정작 조치는 단 1분 만에 끝났다.

라우팅 테이블 설정을 고치자마자 그 지긋지긋하던 21초의 지연은 거짓말처럼 사라졌고, 새 기기건 시크릿 모드건 0.1초 만에 팍팍 뜨기 시작했다.

결론

  1. “특정 IP만 타임아웃이 난다”는 사실을 로그나 핑 찌르기로 일찍 알았다면 단숨에 해결했을 문제다.
  2. 하지만 이 문제를 잡겠다고 서버 MTU 조정, Nginx Proxy 최적화 튜닝, TCP 커널 파라미터 개조까지 인프라 전반을 싹 다 후벼 파서 뜯어고친 덕분에… 의도치 않게 서버 네트워크 최고 효율 패치를 완료했다. (오히려 좋은…건가?)
  3. 백엔드 에러 로그만 멍하니 보는 것도 좋지만, 클라이언트(PC) 레벨에서 패킷을 직접 추적(curl -Iv)하며 원인을 좁혀가는 기초 네트워크 공사가 얼마나 중요한지 다시금 뼈에 새겼다. 완벽한 삽질이었다.

마침.

다른 글 보기