트러블슈팅 DevOps

App Runner에서 외부 API 호출이 10초 만에 죽는다

증상

App Runner (VPC Egress 모드)에서 GCP Cloud Run 엔드포인트를 호출할 때마다 정확히 10초 후에 다음 에러가 찍히고 실패한다.

ERROR c.s.i.report.ReportServiceClient : [report-service] survey-report 호출 실패
body=Did not observe any item or terminal signal within 10000ms in 'flatMap'

이상한 점은 NAT Instance에서 직접 curl하면 0.14초 만에 202가 온다는 것이다. 로컬에서도, NAT Instance에서도 멀쩡한데 App Runner에서만 죽는다.

🚨
함정 NAT Instance가 잘 응답한다고 해서 NAT 포워딩이 동작하는 건 아니다. NAT Instance 자신이 보내는 패킷(curl)과, 다른 인스턴스를 위해 포워딩하는 패킷은 iptables의 다른 체인을 거친다.

구조와 원인

App Runner VPC Egress 모드에서 외부 인터넷으로 나가는 경로는 다음과 같다.

AWS VPC 10.0.0.0/16 PUBLIC SUBNET 10.0.0.0/24 NAT Instance t4g.nano ens5 🛡 iptables PRIVATE SUBNET 10.0.10.0/24 · 10.0.11.0/24 VPC Connector App Runner 브릿지 App Runner backoffice-api Route Table (private) 0.0.0.0/0 → NAT Instance GCP Cloud Run report-service asia-northeast3 Internet GW IGW VPC Egress MASQUERADE ← FORWARD 체인 통과 App Runner 패킷은 NAT Instance의 FORWARD 체인을 거친다 (OUTPUT 아님)

문제가 된 지점은 NAT Instance의 iptables FORWARD 체인이었다.

NAT Instance의 user_data에는 POSTROUTING MASQUERADE 규칙만 있었고, FORWARD 체인에 VPC 트래픽을 허용하는 규칙이 없었다.

# iptables -L FORWARD -n -v

Chain FORWARD (policy DROP 107K packets, 6438K bytes)
 pkts bytes target     prot opt in     out     source               destination
 107K 6438K DOCKER-USER  all  -- ...
 107K 6438K DOCKER-ISOLATION-STAGE-1  all  -- ...

FORWARD 체인에는 DOCKER-USER, DOCKER-ISOLATION-STAGE-1 체인만 있었다. VPC 트래픽을 ACCEPT하는 규칙이 없는 상태에서 default policy가 DROP이므로, App Runner에서 오는 패킷이 모두 조용히 드랍되고 있었다.

💡
핵심 차이 NAT Instance 자신이 보내는 트래픽은 OUTPUT 체인을 거친다 — FORWARD 체인을 거치지 않는다. 다른 호스트를 위해 포워딩하는 트래픽만 FORWARD 체인을 통과한다. 그래서 NAT Instance에서 curl이 성공해도, App Runner의 패킷은 드랍될 수 있다.

디버깅 과정

확인

라우팅은 정상

Private Subnet 라우트 테이블에 0.0.0.0/0 → NAT Instance 설정 확인. Security Group도 443 포트 허용. source/dest check도 비활성화.

함정

NAT Instance에서 curl 성공 → "네트워크 문제 아닌 것 같은데?"

NAT Instance에서 curl -o /dev/null -w "%{http_code}" ... GCP_URL 실행 → 0.14초 만에 202. 하지만 이건 NAT Instance 자신이 보낸 요청이라 무관하다.

발견

iptables FORWARD chain 확인

NAT Instance에서 iptables -L FORWARD -n -v 실행. 107K 패킷이 DROP된 것 확인. ACCEPT 규칙이 전혀 없음.

해결

FORWARD ACCEPT 규칙 추가

VPC 대역(10.0.0.0/16) 발신 트래픽과 그 응답 패킷을 ACCEPT하도록 규칙 추가 → 즉시 해결.

해결책

임시 조치 (재부팅 시 초기화):

sudo iptables -I FORWARD 1 -s 10.0.0.0/16 -j ACCEPT
sudo iptables -I FORWARD 2 -d 10.0.0.0/16 -m state --state RELATED,ESTABLISHED -j ACCEPT

영구 적용 — Terraform user_data에 추가:

user_data = <<-EOF
  #!/bin/bash
  echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
  sysctl -p
  dnf install -y iptables-services
  iptables -t nat -A POSTROUTING -o ens5 -s ${var.vpc_cidr} -j MASQUERADE
  # VPC 트래픽 포워딩 허용 (Docker 설치로 인한 FORWARD DROP 대응)
  iptables -I FORWARD 1 -s ${var.vpc_cidr} -j ACCEPT
  iptables -I FORWARD 2 -d ${var.vpc_cidr} -m state --state RELATED,ESTABLISHED -j ACCEPT
  service iptables save
  systemctl enable iptables
EOF
ℹ️
DOCKER-USER 체인이 왜 있었나 NAT Instance의 FORWARD 체인에 DOCKER-USER, DOCKER-ISOLATION-STAGE-1 규칙이 존재했다. 이 체인은 Docker daemon이 iptables를 수정할 때 삽입된다. 정확한 경위는 확인하지 못했지만, 결과적으로 VPC 트래픽을 허용하는 ACCEPT 규칙은 없고 policy만 DROP인 상태가 만들어져 있었다.

교훈

주제배운 것
iptables 체인자신이 보내는 트래픽(OUTPUT)과 포워딩 트래픽(FORWARD)은 다른 체인을 거친다. NAT Instance가 직접 통신되더라도 포워딩이 안 될 수 있다
DOCKER-USER 체인Docker daemon은 iptables FORWARD 체인에 DOCKER-USER, DOCKER-ISOLATION 규칙을 삽입한다. VPC ACCEPT 규칙 없이 policy가 DROP이면 포워딩이 조용히 깨진다
NAT Instance 디버깅iptables -L FORWARD -n -v의 DROP 카운터를 확인하면 패킷 드랍을 빠르게 발견할 수 있다
user_data 영속성iptables 규칙은 재부팅 시 초기화된다. service iptables save + systemctl enable iptables로 영구화 필요
← 목록으로 이전 트러블슈팅 →